The steps to integrate incoming UART data with FreeRTOS on a PSoC 6. Part 2: now with low power support.
image source: Infineon application note AN219528 PSoC 6 MCU Low-Power Modes and Power Reduction Techniques |
In the first iteration of this exercise, I wrote:
The focus is on saving processor power. The design does not poll for incoming data.
It yields all powers to the RTOS scheduler (possibly going to low power mode) until a trigger fires after a defined number of bites arrived at the UART input.
But I could not get it to work. Whenever FreeRTOS allowed the controller to go in deep sleep, I lost received bytes when the UART trigger woke it up.
To get a working example in time for Arduino Day Workshop: NanoDrone II: AI and Computer Vision with LoRa (Win a PSoC6 and a Pair of MKR 1300 Boards!), I reconfigured FreeRTOS to not go in deep sleep.
Now I've studied up, and know how to manage the PSoC peripherals when switching power modes. And I was able to get the design working with deep sleep support.
Inspiration: an article of Mark Saunders for a slightly earlier version of the API, combined with the recent PSoC6 low power example for the CapSense module.
PSoC6 power mode management and UART
The controller supports a number of power modes. In the high power modes, all peripherals can be fully operational and reactive.
But even though the serial blocks support several low power modes, there are consequences.
Everything above deep sleep doesn't need your attention. The block and controller are awake enough to deal with serial data.
Once you go to deep sleep, the situation becomes different. Sending is disabled - and that's normal: we are trying to do as little as possible in deep sleep.
Receiving data with interrupt still works. It is one of the valid wake-up triggers. But you need to prepare the UART block.
The PSoC6 HAL API has the necessary functions and constructs to deal with it. The work is to register this.
In the exercise I only had to change two sources:
- the FreeRTOS configuration. We used the AnyCloud MQTT Client example as base and that supports deep sleep.
I had altered it to disable that functionality. I've now reverted to the original example's version.
image source: me
- the UART task - more specific the UART initialisation block.
Configure UART to support wake-up from deep sleep after receiving 16 bytes on the RX pin
In the original code, the UART task triggered after receiving 16 bytes.
Now it will do the same. But because the controller is in deep sleep, the UART will first wake it up and do some book-keeping.
It will also do some actions before going to sleep, such as checking that we're not sending anything, and put pins in a low power mode.
All that work is done for us by the PSoC6 HAL function Cy_SCB_UART_DeepSleepCallback().
This function knows what to do before going to sleep, and after the wake-up. We just have to give it some information on what UART we're using, and register it in the HAL power mode manager.
I need declare and fill two structures with configuration info:
// low power support cy_stc_syspm_callback_params_t callback_params = { .base = UART_SCB, .context = &uartContext }; cy_stc_syspm_callback_t uart_deep_sleep_cb = { Cy_SCB_UART_DeepSleepCallback, CY_SYSPM_DEEPSLEEP, 0, &callback_params, NULL, NULL };
The first one holds the info on what UART we're using (the PSoC has a lot of them available). Both fields have to be filled with constructs we already use to initialise the UART and handle triggers.
The second structure configures what function will handle power mode changes, and under what circumstances. We will request that the standard API function will do this, for deep sleep, without special filtering.
You can use the 3rd value to keep UART sleeping if the controller woke up for another impulse.
Then, in my already existing init function, I have to register the callback in the power mode manager.
I've posted a subset of the code below, and indicated what line was altered or added.
void initUART() { // ... /* Hook interrupt service routine and enable interrupt */ (void) Cy_SysInt_Init(&uartIntrConfig, &UART_Isr); NVIC_EnableIRQ(UART_INTR_NUM); // this line is added for low power mode /* Register a deep sleep callback for UART block. */ Cy_SysPm_RegisterCallback(&uart_deep_sleep_cb); /* Enable UART to operate */ Cy_SCB_UART_Enable(UART_SCB); }
The full source is available on the previous UART post. I'm also attaching a zip of this version of the ModusToolbox project to the blog post.
And I'll create a pull request to merge this with the NanoDrone II git.
I've also tagged it as a first release candidate in my git. That's a neat feature that allows you to download the current state of the project source as a zip archive.
image source: me
Top Comments