Tool/software:
I have a main loop that enters LPM3 by default at startup. I also have a timer (TB2) configured to wake the CPU for about 10 ms every 2 seconds. A corresponding UART (eUSCI_A1) interrupt sets the enter_LPM3
flag, which either wakes the CPU from LPM3 or puts it back to LPM3.
Because SMCLK and the UART are disabled in LPM3, I expect the only way the CPU could catch the 'w'
(wake-up) command is if it arrives during that 10 ms window. That’s just 10 ms out of every 2 seconds – about 0.5% of the time. Logically, that should fail most of the time (99.5%), but in practice, my code magically works 100% of the time. I can put the CPU into LPM3 or wake it up at any moment, and I never miss the 'w'
command.
Unless I’m misinterpreting my own code, my guess is that, while the CPU is in LPM3 and SMCLK/UART are supposedly off, the Receive Shift Register might still be shifting in incoming UART data. Then, as soon as the CPU wakes for that 10 ms window and enables SMCLK, the data appears in UCAxRXBUF
and fires the interrupt. Otherwise, I can’t explain why my code works so reliably. Hope someone can shed me some light on this.
I'm not sure if posing the code is necessary but here is it just in case:
My main loop:
// Beginning of my main loop // 'enter_LPM3' is initialized to be true // Main loop while(1) { // 'enter_LPM3' is initialized to be true, meaning we enter LPM3 after power up if (enter_LPM3) { // (LPM3) Small delay to increase the time-window in catching 'w' UART command (~10ms @ 1MHz MCLK) // before entering LPM3. __delay_cycles(10000); P1OUT &= ~BIT0; // LED Off // Check enter_LPM3 again before entering LPM3 because the UART ISR may set 'enter_LPM3' to false // with a 'w' wake-up command if (enter_LPM3) { __bis_SR_register(LPM3_bits | GIE); // Enter LPM3 (Wake up by Timer_B2) } } // Other work }
My timer and timer ISR:
// (LPM3) Configure Timer2_B3 (TB2) void configure_tb2_wakeup(void) { TB2CTL |= TBCLR; // (TB2) Clear Timer_B2 TB2CTL |= TBSSEL__ACLK; // (TB2) Source Timer_B2 from ACLK (32.768 kHz) //TB2CTL |= ID__1; // (TB2) No clock division //TB2EX0 |= TBIDEX__1; // (TB2) No extra division //TB2CCR0 = 327; // (TB2) Set wake-up interval (~10ms: 327 / 32.768kHz ≈ 10ms) //TB2CCR0 = 32768; // (TB2) Set wake-up interval (~1s: 32768 / 32.768kHz ≈ 1s) //TB2CCR0 = 65535; // (CANNOT USE - OVERFLOW!)(TB2) Set wake-up interval (~2s: 32.768kHz / 65536 ≈ 2s) // Because TB2CCR0 is a 16-bit counter that has a maximum value of 65535 and overflows at 65536, // we can use the clock divider to slow down ACLK in order to achieve a more accurate 2s interrupt TB2CTL |= ID__4; // (TB2) Divide ACLK by 4 (32.768 kHz / 4 = 8.192 kHz) TB2EX0 |= TBIDEX__8; // (TB2) Further divide by 8 (8.192 kHz / 8 = 1.024 kHz) TB2CCR0 = 2048; // (TB2) Set wake-up interval (~2s: 2048 / 1.024 kHz = ~2s interval TB2CCTL0 |= CCIE; // (TB2) Enable Timer_B2 interrupt TB2CTL |= MC__UP; // (TB2) Set Timer_B2 to UP mode } #pragma vector = TIMER2_B0_VECTOR __interrupt void ISR_TB2_Wakeup(void) { // Wake up the CPU if it's in LPM3. Otherwise, do nothing (CPU is already awake). if (enter_LPM3) { P1OUT |= BIT0; // LED On __bic_SR_register_on_exit(LPM3_bits); // Wake up from LPM3 (not immediately but after we exit this ISR) } }
UART and UART ISR:
void configure_uart(void) { UCA1CTLW0 |= UCSWRST; // (eUSCI_A1)(UART) hold eUSCI_A1 in SW reset UCA1CTLW0 |= UCSSEL__SMCLK; // (eUSCI_A1)(UART) clock source: use SMCLK 1MHz UCA1BRW = 8; // (eUSCI_A1)(UART) 115200 baud (UG-p.589) <-- neg error UCA1MCTLW |= 0xD600; // (eUSCI_A1)(UART) 115200 baud (UG-p.589) <-- neg error P4SEL1 &= ~BIT3; // (eUSCI_A1)(UART) P4.3/TXD (P4SEL1.3=0) P4SEL0 |= BIT3; // (eUSCI_A1)(UART) P4.3/TXD (P4SEL0.3=1) P4SEL1 &= ~BIT2; // (eUSCI_A1)(UART) P4.2/RXD (P4SEL1.2=0) P4SEL0 |= BIT2; // (eUSCI_A1)(UART) P4.2/RXD (P4SEL0.2=1) UCA1CTLW0 &= ~UCSWRST; // Take UART out of reset } #pragma vector = EUSCI_A1_VECTOR __interrupt void ISR_EUSCI_A1(void) { if((UCA1IFG & UCRXIFG) != 0) { char rx_char = UCA1RXBUF; // Read UART input if(rx_char == 'w') // UCRXIFG is cleared automatically when UCA1RXBUF is read { P1OUT |= BIT0; // LED On enter_LPM3 = false; // (LPM3) Stays awake __bic_SR_register_on_exit(LPM3_bits); // (LPM3) * Forces wake-up immediately * } else if(rx_char == 's') { P1OUT &= ~BIT0; // LED Off enter_LPM3 = true; // (LPM3) Let main.c enters LPM3 } } // UART transmit the byte read from the SPI if((UCA1IFG & UCTXCPTIFG) != 0) { // See INFO.txt if(position_UART == ((sizeof(read_result)/2) - 1)) // if all characters in a string has completed its transmission { UCA1IE &= ~UCTXCPTIE; // (eUSCI_A1)(UART) disable UCTXCPTIE IRQ } else // more characters to be sent { position_UART++; UCA1TXBUF = read_result[position_UART]; } UCA1IFG &= ~UCTXCPTIFG; // (eUSCI_A1)(UART) clear UCTXCPTIFG IFG } }
From the guide:
"A peripheral module requests its clock sources automatically from the CS module if required for its proper
operation, regardless of the current mode of operation (see Figure 3-5)."
Or, the system is in LPM3 when the UART detects a falling edge (start bit) on its input. It requests its clock causing it to run. When reception of the data completes, the clock can stop and it returns to LPM3.
Thank you I missed that in the guide. Does the UART (1) service the interrupt immediately in LPM3 before returning to LPM3 automatically or (2) does it only set the interrupt flag, and the ISR gets serviced only after the CPU gets out of LPM3?
**Attention** This is a public forum