Skip to main content Skip to local navigation

Zwei Blinkenlights

Two LEDs blinking on a microcontroller board.
The Green LED is driven by the SysTick timer and the Red LED is driven by the Low Power Timer (LPTMR)

I'm doing some rapid "blinkenlights" prototypes for timer code today on the K32L2 board. The goal is to have two separate timers driving two LEDs using interrupt service routines. None of this code is optimized or ideal... I'm leveraging the NXP SDK and the examples that go with it for one Systick-driven LED and another LED driven by the LPTMR (Low Power Timer). I chose to use the Low Power Timer because I think that it might be better to produce sub-millisecond timer alarms than the RTC (Real Time Clock) on this chip and simpler to set up that the Periodic Interrupt Timer (PIT) -- which I think might be tied to the DMA and, therefore, be a little too complicated to set up (for now).

So, I combined the code with the other two Blinkenlight examples from today (1 and 2) with NXP's example for the LPTMR from the SDK.

The goal, down the road, will be to simplify the SDK calls and to make them consistent with how it's done within the TinyUSB K32 branch.

In this example the green LED is driven by the SysTick timer. In the main loop the green LED is toggled based on a global variable that was modified from within the SysTick ISR. The red LED, on the other hand, is toggled from within the LPTMR's ISR. In a future, cleaned up version of this, I'll toggle both LEDs within their ISRs and then try to do ADC capture within the higher frequency ISR so that we can have time-stamped ADC values that can, then, be framed in terms of initial values and then rate values (derivatives!).

This program is based on the LPTMR's SDK example. I chose it because it does a good job in setting up the LPTMR peripheral hardware. In a future version of this I'll have to figure out all the setup for LPTMR by itself, similar to what I've done previously with the K43 and LPC802 micros in courses that I've taught at York (EECS 3215) and overseas in Karlsruhe, Germany.

Within pin_mux.c the following needs to be done to set up the LEDs:

void BOARD_InitPins(void)
{
    /* Port A Clock Gate Control: Clock enabled */
    CLOCK_EnableClock(kCLOCK_PortA);


    /* PORTA1 (pin 23) is configured as LPUART0_RX */
    PORT_SetPinMux(PORTA, 1U, kPORT_MuxAlt2);

    /* PORTA2 (pin 24) is configured as LPUART0_TX */
    PORT_SetPinMux(PORTA, 2U, kPORT_MuxAlt2);

    /* don't really need the UART */

    SIM->SOPT5 = ((SIM->SOPT5 &
                   /* Mask bits to zero which are setting */
                   (~(SIM_SOPT5_LPUART0TXSRC_MASK | SIM_SOPT5_LPUART0RXSRC_MASK)))

                  /* LPUART0 Transmit Data Source Select: LPUART0_TX pin. */
                  | SIM_SOPT5_LPUART0TXSRC(SOPT5_LPUART0TXSRC_LPUART_TX)

                  /* LPUART0 Receive Data Source Select: LPUART_RX pin. */
                  | SIM_SOPT5_LPUART0RXSRC(SOPT5_LPUART0RXSRC_LPUART_RX));

	/* ------------------   Port E for Red LED ---------------*/
    /* Port E Clock Gate Control: Clock enabled */
    CLOCK_EnableClock(kCLOCK_PortE);

    gpio_pin_config_t gpioe_pin19_config = {
        .pinDirection = kGPIO_DigitalOutput,
        .outputLogic = 0U
    };
    /* Initialize GPIO functionality on pin PTE31 (pin 19)  */
    GPIO_PinInit(GPIOE, 31U, &gpioe_pin19_config);

    /* PORTE31 (pin 19) is configured as PTE31 */
    PORT_SetPinMux(PORTE, 31U, kPORT_MuxAsGpio);
    /* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */

     /* ------------- Port D for Green LED ------------- */
    /* Port D Clock Gate Control: Clock enabled */
    CLOCK_EnableClock(kCLOCK_PortD);

    gpio_pin_config_t gpiod_pin5_config = {
        .pinDirection = kGPIO_DigitalOutput,
        .outputLogic = 0U
    };
    /* Initialize GPIO functionality on pin PTD5 (pin 5)  */
    GPIO_PinInit(GPIOD, 5U, &gpiod_pin5_config);

    /* PORTD5 (pin 5) is configured as PTD5 */
    PORT_SetPinMux(PORTD, 5U, kPORT_MuxAsGpio);
    /* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */



}

Then, the main file, lptmr.c, which contains the main function is modified from the SDK example to include support for the green LED and the SysTick timer:

/*
 * Copyright (c) 2015, Freescale Semiconductor, Inc.
 * Copyright 2016-2017 NXP
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "fsl_debug_console.h"
#include "pin_mux.h"
#include "clock_config.h"
#include "board.h"

#include "fsl_lptmr.h"

/*******************************************************************************
 * Definitions
 ******************************************************************************/
#define DEMO_LPTMR_BASE   LPTMR0
#define DEMO_LPTMR_IRQn   LPTMR0_IRQn
#define LPTMR_LED_HANDLER LPTMR0_IRQHandler
/* Get source clock for LPTMR driver */
#define LPTMR_SOURCE_CLOCK CLOCK_GetFreq(kCLOCK_LpoClk)
/* Define LPTMR microseconds counts value */
#define LPTMR_USEC_COUNT 40000U					// decrease this to make LED flash frequency higher.  40000 is very frequent but still visible.
#define LED_INIT()       LED_RED_INIT(LOGIC_LED_ON)
#define LED_TOGGLE()     LED_RED_TOGGLE()


// LED1 (Green) 	PTD5 - D13/SCK/LED/int
#define LED_PIN_CLOCK         kCLOCK_PortD
#define LED_GPIO              GPIOD
#define LED_PORT              PORTD
#define LED_PIN               5
#define LED_STATE_ON          0

#define BOARD_LED2_GPIO     BOARD_LED_GREEN_GPIO
#define BOARD_LED2_GPIO_PIN BOARD_LED_GREEN_GPIO_PIN


/*******************************************************************************
 * Prototypes
 ******************************************************************************/
// helper function to turn an LED on or off. (Green LED)
// This is how TinyUSB does it.
void board_led_write(bool state)
{
  GPIO_PinWrite(LED_GPIO, LED_PIN, state ? LED_STATE_ON : (1-LED_STATE_ON));
}

/*******************************************************************************
 * Variables
 ******************************************************************************/
volatile uint32_t g_systickCounter;

volatile uint32_t lptmrCounter = 0U;

/*******************************************************************************
 * Code
 ******************************************************************************/
void LPTMR_LED_HANDLER(void)
{
    LPTMR_ClearStatusFlags(DEMO_LPTMR_BASE, kLPTMR_TimerCompareFlag);
    lptmrCounter++;
    LED_TOGGLE();				// Red LED toggles with Low Power Timer.
    /*
     * Workaround for TWR-KV58: because write buffer is enabled, adding
     * memory barrier instructions to make sure clearing interrupt flag completed
     * before go out ISR
     */
    __DSB();
    __ISB();
}

/* System Timer (SysTick) Interrupt Service and helper routines. */
void SysTick_Handler(void)
{
    if (g_systickCounter != 0U)
    {
        g_systickCounter--;
    }
}

void SysTick_DelayTicks(uint32_t n)
{
    g_systickCounter = n;
    while (g_systickCounter != 0U)
    {
    }
}




/*!
 * @brief Main function
 */
int main(void)
{
    uint32_t currentCounter = 0U;
    lptmr_config_t lptmrConfig;



    /* Board pin, clock, debug console init */
    BOARD_InitBootPins();				// will configure both LEDs (Red and Green)
    BOARD_InitBootClocks();
    BOARD_InitDebugConsole();

    LED_INIT();			//  turns on the Red LED after its been configured.



    /* Configure LPTMR */
    /*
     * lptmrConfig.timerMode = kLPTMR_TimerModeTimeCounter;
     * lptmrConfig.pinSelect = kLPTMR_PinSelectInput_0;
     * lptmrConfig.pinPolarity = kLPTMR_PinPolarityActiveHigh;
     * lptmrConfig.enableFreeRunning = false;
     * lptmrConfig.bypassPrescaler = true;
     * lptmrConfig.prescalerClockSource = kLPTMR_PrescalerClock_1;
     * lptmrConfig.value = kLPTMR_Prescale_Glitch_0;
     */
    LPTMR_GetDefaultConfig(&lptmrConfig);

    /* Initialize the LPTMR */
    LPTMR_Init(DEMO_LPTMR_BASE, &lptmrConfig);

    /*
     * Set timer period.
     * Note : the parameter "ticks" of LPTMR_SetTimerPeriod should be equal or greater than 1.
     */
    LPTMR_SetTimerPeriod(DEMO_LPTMR_BASE, USEC_TO_COUNT(LPTMR_USEC_COUNT, LPTMR_SOURCE_CLOCK));

    /* Enable timer interrupt */
    LPTMR_EnableInterrupts(DEMO_LPTMR_BASE, kLPTMR_TimerInterruptEnable);

    /* Enable at the NVIC */
    EnableIRQ(DEMO_LPTMR_IRQn);

  //  PRINTF("Low Power Timer Example\r\n");

    /* Set systick reload value to generate 1ms interrupt */
    if (SysTick_Config(SystemCoreClock / 1000U))
    {
        while (1)
        {
        }
    }

    /* Start counting on Low Power Timer */
    LPTMR_StartTimer(DEMO_LPTMR_BASE);
    while (1)
    {

    	// For low power timer.
        if (currentCounter != lptmrCounter)
        {
            currentCounter = lptmrCounter;
  //          PRINTF("LPTMR interrupt No.%d \r\n", currentCounter);
        }

        /* Delay 1000 ms */
        SysTick_DelayTicks(1000U);
        GPIO_PortToggle(BOARD_LED2_GPIO, 1u << BOARD_LED2_GPIO_PIN);



    }
}

Conclusion & Future Work

I've got two timers working on the board. SysTick is the simplest to set up and LPTMR isn't bad but is more of a hassle. There is more to be done here, like speeding up the frequency -- for this demo something visible was all I wanted. Beyond that, I want to simplify this example and add it to other projects that also use TinyUSB. Once I've got it incorporated into the TinyUSB project I'll look to add derivative and filtering support on ISR-based ADC readings.


a pen

James Andrew Smith is a Professional Engineer and Associate Professor in the Electrical Engineering and Computer Science Department of York University’s Lassonde School, with degrees in Electrical and Mechanical Engineering from the University of Alberta and McGill University.  Previously a program director in biomedical engineering, his research background spans robotics, locomotion, human birth, music and engineering education. While on sabbatical in 2018-19 with his wife and kids he lived in Strasbourg, France and he taught at the INSA Strasbourg and Hochschule Karlsruhe and wrote about his personal and professional perspectives.  James is a proponent of using social media to advocate for justice, equity, diversity and inclusion as well as evidence-based applications of research in the public sphere. You can find him on Twitter.  You can find him on BlueSky. Originally from Québec City, he now lives in Toronto, Canada.