WETS: i primi test
In un precedente articolo vi avevo parlato della nuova libreria che stavo svilupando per realizzare un semplice scheduler event-driven per microcontrollori. Proprio oggi ho rilasciato la prima release candidate, ed in questo articolo vi voglio mostrare un piccolo esempio di come utilizzare la libreria.
L'esempio consiste nel generare eventi ritardarti che permettano di far lampeggiare un led per 50ms ogni secondo e contemporaneamente generare un evento ciclico ogni 500ms. L'esempio è molto semplice, ma mostra in maniera efficace le caratteristiche della libreria.
Come set-up di lavoro ho deciso di utilizzare la scheda di sviluppo NUCLEO-L073RZ della ST Microelectronics, e come ambiente di sviluppo il nuovo STM32CubeIDE.
Iniziamo dal firmware.
Come già detto nel precedente articolo, WETS è nata per appoggiarsi alla libohiboard anche se è facilmente adattabile ad altre esigenze.
Nel nostro esempio utilizzeremo la libohiboard, quindi il primo passo è inserirla e configurarla all'interno del nostro progetto come mostrato in questo articolo.
Configurata la libohiboard, ed inserita WETS all'interno del progetto, siamo pronti per costruire il main()
e tutto il necessario per far funzionare il nostro esempio.
#include "wets/wets.h" static LowPowerTimer_Config mTimerConfig = { .clockSource = LOWPOWERTIMER_CLOCKSOURCE_INTERNAL_LSE, .prescaler = LOWPOWERTIMER_CLOCKPRESCALER_DIV1, .triggerSource = LOWPOWERTIMER_TRIGGERSOURCE_SOFTWARE, .updateMode = LOWPOWERTIMER_UPDATEMODE_IMMEDIATE, .counterSource = LOWPOWERTIMER_COUNTERSOURCE_INTERNAL, }; static void initClock (void) { Clock_Config clockConfig = { .source = CLOCK_INTERNAL_HSI | CLOCK_EXTERNAL_LSE_CRYSTAL, .sysSource = CLOCK_SYSTEMSOURCE_HSI, .hsiState = CLOCK_OSCILLATORSTATE_ON, .lsiState = CLOCK_OSCILLATORSTATE_OFF, .lseState = CLOCK_OSCILLATORSTATE_ON, .output = CLOCK_OUTPUT_SYSCLK | CLOCK_OUTPUT_HCLK | CLOCK_OUTPUT_PCLK1 | CLOCK_OUTPUT_PCLK2, .ahbDivider = CLOCK_AHBDIVIDER_1, .apb1Divider = CLOCK_APBDIVIDER_1, .apb2Divider = CLOCK_APBDIVIDER_1, .hsiDivider = CLOCK_HSIDIVIDER_1, }; Clock_init(&clockConfig); } static void initTimer (void) { // Add callback to Low-Power Timer configuration mTimerConfig.counterCallback = (LowPowerTimer_CounterCallback)WETS_timerIsrCallback; // Configure Low-Power Timer LowPowerTimer_init(OB_LPTIM1,&mTimerConfig); // Compute tiks and set prescaler... LowPowerTimer_startCounter(OB_LPTIM1,328); } static void initGpio (void) { Gpio_config(GPIO_PINS_PC0, GPIO_PINS_OUTPUT); Gpio_clear(GPIO_PINS_PC0); Gpio_config(GPIO_PINS_PC1, GPIO_PINS_OUTPUT); Gpio_clear(GPIO_PINS_PC1); Gpio_config(GPIO_PINS_PB0, GPIO_PINS_OUTPUT); Gpio_clear(GPIO_PINS_PB0); } void Board_init (void) { initClock(); initTimer(); initGpio(); } #define APPLICATION_LED_KEEP_ALIVE_ON 0x00000001ul #define APPLICATION_LED_KEEP_ALIVE_OFF 0x00000002ul #define APPLICATION_LED_CYCLIC_TOGGLE 0x00000004ul int main (void) { Board_init(); WETS_init(); // Start Led events for keep-alive message WETS_addDelayEvent(keepAliveLed, 3, APPLICATION_LED_KEEP_ALIVE_ON, 1000); // Add cyclic events WETS_addCyclicEvent(cyclicEvent, 3, APPLICATION_LED_CYCLIC_TOGGLE, 500); WETS_loop(); return 0; }Analizziamo il codice funzione per funzione:
-
La funzione
initClock()
si occupa di inzializzare il clock del microcontrollore. Anche se non è fondamentale per comprendere il nostro esempio, è necessaria per configurare nella giusta maniera il clock di sistema. In particolare, la funzione abilità come sorgenti sia l'HSI (High Speed Internal Oscillator), che nel caso del nostro microcontrollore è di 16MHz, sia il LSE (Low-Speed External Oscillator), che nella scheda di sviluppo è un quarzo da 32768Hz. Oltre ad abilitare le varie sorgenti, la funzione deve anche scegliere quale usare per il clock di sistema; in questo caso viene scelto l'HSI (riga 19). Da riga 25 a riga 31 vengono invece impostati tutti i divisori dei clock generati dalla relativa periferica, e che vengono usati da tutte le altre periferiche del microcontrollere. -
Scorrendo il codice, la funzione successiva è
initTimer()
. Questa funzione si occupa di inizializzare il timer low-power del microcontrollore. Si è scelto di utilizzare il timer low-power perché, all'interno della famiglia STM32L4 e STM32L0, è l'unico timer in grado di svegliare il microcontrollore dallo stato di STOP, quindi fondamentale per poter far lavorare WETS anche in progetti low-power. Da notare come alla riga 40 venga impostata come callback del timer la funzioneWETS_timerIsrCallback
. Questa funzione si occupa di aggiornare lo stato del timer interno a WETS, in modo tale che lo scheduler possa girare e generare gli eventi. -
La funzione
initGpio()
si occupa di inizializzare come output di uscita i tre pin che verranno utilizzati per fare debug sul nostro progetto. -
Alla riga 70 troviamo finalmente la funzione
main()
. Completata l'inizializzazione del microcontrollore, contenuta all'interno della funzioneBoard_init()
, lo step successivo invoca la funzioneWETS_init()
. Questa funzione inizializza tutta l'architettura della libreria. Nelle righe successive vengono attivati gli eventi che sono lo scopo di questo esempio: un evento ritardato che verrà servito dopo 1 secondo, il cui event ID èAPPLICATION_LED_KEEP_ALIVE_ON
, ed un evento ciclico che verrà servito ogni 500ms il cui ID èAPPLICATION_LED_CYCLIC_TOGGLE
. L'utima istruzione del nostro main richiama la funzioneWETS_loop()
la quale si occupa di controllare di continuo lo stato dello scheduler e di generare gli eventi al momento giusto.
uint32_t keepAliveLed (uint32_t events) { if (events & APPLICATION_LED_KEEP_ALIVE_ON) { Gpio_set(GPIO_PINS_PC1); WETS_addDelayEvent(keepAliveLed, 3, APPLICATION_LED_KEEP_ALIVE_OFF, 50); return (events ^ APPLICATION_LED_KEEP_ALIVE_ON); } if (events & APPLICATION_LED_KEEP_ALIVE_OFF) { Gpio_clear(GPIO_PINS_PC1); WETS_addDelayEvent(keepAliveLed, 3, APPLICATION_LED_KEEP_ALIVE_ON, 950); return (events ^ APPLICATION_LED_KEEP_ALIVE_OFF); } // The event is not for this callback! return events; } uint32_t cyclicEvent (uint32_t events) { if (events & APPLICATION_LED_CYCLIC_TOGGLE) { Gpio_toggle(GPIO_PINS_PB0); return (events ^ APPLICATION_LED_CYCLIC_TOGGLE); } // The event is not for this callback! return events; }La funzione
keepAliveLed()
si occupa di gestire gli eventi ritardati come da specifica del nostro esempio.
In particolare, ogni volta che si genera l'evento APPLICATION_LED_KEEP_ALIVE_ON
viene programmata la generazione di un evento APPLICATION_LED_KEEP_ALIVE_OFF
trascorsi 50ms.
Viceversa, quando si genera l'evento APPLICATION_LED_KEEP_ALIVE_OFF
viene programmata la generazione di un evento APPLICATION_LED_KEEP_ALIVE_ON
dopo 950ms.La funzione
cyclicEvent()
invece è la callback che si occupa di asservire alla generazione dell'evento ciclico ogni 500ms.
Per testare il codice appena scritto, ho collegato alla NUCLEO-L073RZ un analizzatore di stati logici della Saleae, il Logic8.
In particolare ho collegato i primi tre canali dell'analizzatore rispettivamente ai pin GPIO_PINS_PC0
, GPIO_PINS_PC1
e GPIO_PINS_PB0
.
Il pin GPIO_PINS_PC0
viene fatto togglare all'interno della callback dell'interrupt del timer che alimenta lo scheduler, con l'obiettivo di controllare la presenza di eventuali ritardi nella gestione dell'interrupt.
Nelle figure seguenti si possono osservare le acquisizioni fatte con l'analizzatore.
Il primo esempio applicativo con WETS è terminato!
A breve seguiranno esempi e test più complessi, in modo tale da mettere a dura prova la libreria... ed ovviamente anche la documentazione completa!
Stay scheduled! :)
Articoli correlati