NOWAE

Our ideas, your fun!

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 funzione WETS_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 funzione Board_init(), lo step successivo invoca la funzione WETS_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 funzione WETS_loop() la quale si occupa di controllare di continuo lo stato dello scheduler e di generare gli eventi al momento giusto.
Nell'ultimo punto dell'elenco precedente abbiamo descritto tutta la catena di attivazione del nostro scheduler, ma non ci siamo soffermati sulle callback dei due eventi.
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.

Figura: Misura dei tempi di generazione degli eventi ciclici.
Figura: Misura dei tempi di generazione degli eventi ritardati.
Figura: Misura dei tempi di gestione dell'interrupt del timer low-power.

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