In the stopped state the STM8L needs just a few µA - but in this state the console can't wake up the device: the USART gets no clock. Only GPIO interrupts and peripherals that are clocked from the LSI or the LSE will work! A possible solution is a pin-change interrupt on RxD for waking the core - and the USART - up. In "Active-Halt" mode RTC and WUT peripherals run on 38kHz LSI or 32.768kHz LSE clock (see here). This means that it's possible to have a background task without keeping the core busy.
One of the challenges ist that STM8L docs doesn't describe the USART behavior if the clock is turned on after the start-bit edge, i.e., if the character that wakes the core from "Active-Halt" can still be received.
The first step is a "falling-edge pin change interrupt" - it can trigger on the falling edge of PA3/USART_RX
(which is used in the STM8 eForth 2.2.26 binary fpr STM8L051F3).
RM0031 12.6 "External interrupts" shows the structure of pin change interrupts. I'll use a "bit-x" not the "port-x" option. Using this option requires the interrupt to be acknowledget by setting the corresponding bit in EXTI_SR1
(RM0031 12.9.7). The interrupt priority configuration in RM0031 12.9.1 and 12.9.2 is of no importance since we only want to exit HALT mode.
For each port bit interrupt "edge" or "level" configuration has to be done with EXTI_CR1
for port bits #0..#3 or EXTI_CR2
for bits #4..#7 with a pair of bits. RM0031 12.9.3 and 12.9.4 shows the configuration of the interrupt condition in EXTI_CRx
:
Bits | Configuration |
---|---|
00 | Falling edge and low level |
01 | Rising edge only |
10 | Falling edge only |
11 | Rising and falling edge |
The text states that "these bits can only be written when I1 and I0 in the CCR register are both set to 1 (level 3)" but the meaning of this isn't self-evident: both flags are set after RESET
, TRAP
or when interrupts are disabled using the SIM
instruction (POP CC
will also work, see RM0031 12.7).
This means that setting EXTI_CRx
will work in BOARDINIT
(in boardcore.inc
) but it will require a SIM ... RIM
sequence after COLD
.
Of course, I didn't know that right away. I learned it while trying to figure out why the ISR ran at a cadence of aboyt 8.5µs during the low-time of the serial interface:
Here is the code that works as intended:
\res MCU: STM8L051
\res export PA_DDR PA_CR1 PA_CR2
\res export EXTI_SR1 EXTI_CR1 INT_EXTI3
#require ]B!
#require :NVM
3 CONSTANT RXBIT
$9A CONSTANT RIM \ STM8 assembler enable interrupts
$9B CONSTANT SIM \ STM8 assembler disable interrupts
NVM
VARIABLE telly
:NVM
SAVEC
[ 1 EXTI_SR1 RXBIT ]B! \ 12.9.7 aknowledge interrupt
1 telly +!
IRET
;NVM INT_EXTI3 !
: init
0 telly !
[ SIM C, ]
[ 1 EXTI_CR1 RXBIT 2* 1+ ]B! \ 12.9.3 falling edge
[ RIM C, ]
[ 0 PA_DDR RXBIT ]B! \ 10.9.3 set to input
[ 1 PA_CR1 RXBIT ]B! \ 10.9.4 enable pull-up
[ 1 PA_CR2 RXBIT ]B! \ 10.9.5 enable interrupt
;
RAM
Tests show that the PA3
pin interrupt and the PA3/USART_RX
function work concurrently: entering telly ?
on the console shows that the value of the variable increases with every falling edge of PA3/USART_RX
.
This means that we can move on to the next step, using HALT when there is no input.
When the STM8 eForth console waits for the next keystroke from the serial interface it executes the 'IDLE
task (which, in the absence of buffering, must have a shorter execution time than the character time at the current baud rate). In a low power application it would be better to halt the MCU while there is nothing to do and wake it up just in time.
Using HALT
means that the CPU stops the HSI (the 16MHz "High Speed Internal" clock) but that means that the serial interface is halted as well. As demonstrated, a falling edge interrupt can be used on PA3/USART_RX
and that can be used to get the MCU out of the HALT mode when a new character arrives. The question remains if starting the USART clock after the falling edge of the start-bit will work.
Also it's important not to put the MCU to sleep while thare are characters to be transmitted. A counter in the 'IDLE
task is used to keep the MCU awake while reception or transmission is expected to continue.
The GPIOs PB0
and PB1
are used for testing (I use PulseView and cheap "8-channel 24MHz Logic Analyzer").
Here is the code:
\res MCU: STM8L051
\res export PA_DDR PA_CR1 PA_CR2
\res export PB_DDR PB_ODR PB_CR1 PB_CR2
\res export EXTI_SR1 EXTI_CR1 INT_EXTI3
0 CONSTANT DEB0 \ pin debug PB0
1 CONSTANT DEB1 \ pin debug PB1
500 CONSTANT CLOOPS \ idle loops before halt (@9600 baud)
3 CONSTANT RXBIT \ GPIO port bit number PA3/USART_RX
$8E CONSTANT HALT \ STM8 assembler halt
$9A CONSTANT RIM \ STM8 assembler enable interrupts
$9B CONSTANT SIM \ STM8 assembler disable interrupts
#require ]B!
#require ]B?
#require :NVM
#require 'IDLE
NVM
VARIABLE iloops
:NVM
SAVEC
[ 1 PB_ODR DEB0 ]B! \ pin debug: wakeup from HALT
[ 1 EXTI_SR1 RXBIT ]B! \ 12.9.7 acknowledge interrupt
CLOOPS iloops ! \ do idle loops - keeps the CPU from HALTing
IRET
;NVM INT_EXTI3 !
\ idle loop and HALT
: dohalt ( -- )
[ 0 PB_ODR DEB0 ]B! \ pin debug: idle start
[ 1 PB_ODR DEB1 ]B! \ pin debug: idle loop
iloops @ ?DUP IF
1- iloops !
ELSE
[ HALT C, ]
THEN
[ 0 PB_ODR 1 ]B!
;
: init
[ SIM C, ]
[ 1 EXTI_CR1 RXBIT 2* ]B! \ 12.9.3 rising edge
[ 1 EXTI_CR1 RXBIT 2* 1+ ]B! \ 12.9.3 and falling edge
[ 0 PA_DDR RXBIT ]B! \ 10.9.3 set to input
[ 1 PA_CR1 RXBIT ]B! \ 10.9.4 enable pull-up
[ 1 PA_CR2 RXBIT ]B! \ 10.9.5 enable interrupt
[ ' dohalt ] LITERAL 'IDLE !
[ 1 PB_DDR DEB0 ]B! \ pin debug PB0
[ 0 PB_ODR DEB0 ]B!
[ 0 PB_CR1 DEB0 ]B!
[ 1 PB_CR2 DEB0 ]B!
[ 1 PB_DDR DEB1 ]B! \ pin debug PB1
[ 0 PB_ODR DEB1 ]B!
[ 0 PB_CR1 DEB1 ]B!
[ 1 PB_CR2 DEB1 ]B!
CLOOPS iloops !
[ RIM C, ]
;
RAM
DEB0 signals the "handshake" between the PA3
edge interrupt and the 'IDLE
loop. A DEB1 puls indicates one run of the IDLE task. In between arriving characters DEB1 stays high since the MCU is in HALT state. For a first test I use picocom, a simple serial terminal, since it doesn't buffer the input as e4thcom does it. The following PulseView diagram shows words<enter>
:
The DEB1 shows the 'IDLE task loops and the HALT state. At the moment executing WORDS
, for listing the vocabulary, keeps the CPU active. It would also be possible to HALT the MCU while waiting for the TX buffer to be emptied but for a battery powered application that would be just a minor improvement.
The following recording shows how RX, edge change interrupt, 'IDLE task and the "keep awake" time through iloops
interact:
The number of 'IDLE task loops was tested at 9600 baud (CLOOPS
). Possibly the USART transmission status flags can be used to detect when it's safe to enter the HALT state but I've found no way to detect that the USART is receiving a character.
Through STM8 eForth issue #375 and STM8 eForth issue #379 the groundwork was laid for creating Background Task implementations that doesn't depend on the STM8 timers (TIM2, TIM3 or TIM1): a custom bgtask.inc
that uses any periodic ISR (or an external interrupt if you like) can be placed into a board configuration folder. The C ISR dummy functions for the SDAS linker (called by SDCC) were moved to forth.h
so that they can target specific.
The RTC peripharal uses the LSE (32.768 kHz clock crystal) or the LSI (internal 38kHz oscillator) and it can wake up the device in active-halt mode when the CPU is stopped and the main power regulator and the HSI clock are shut down.
This write-up shows how all STM8 eForth features, including the background task, can be used in an STM8L low-power application.
Consumption is down to 10 to 20µA whith a fully responsive console and an (empty) 10ms background task. The background task scheduler uses the LSE crystal oscillator that also feeds the RTC. When there is "load" on the background task, consumption increases up to what's to be expected at the full clock speed of 16MHz clock (it draws about 700µA). The background task rate is thus an important parameter for the application. It's also possible to use the RTC alarm, not the WUT for waking up the application. That's an interesting option, e.g., for data logging or for home automation tasks.
A point left to clarify is that AN3147 chapter 2.2 states that applications of RM0031 devices, unlike RM0013 devices, need to select whether the MVP (main core voltage regulator) or the LPVR (low-power voltage regulator) is to be used in Active-Halt mode.
Code for the low-power console is below (files bgtask.inc
, boardcore.inc
and stm8l-halt.fs
).