I'm struggling with my first real freeRTOS project. I am basically using an ATmega328P microcontroller and an nRF24L01+ radio as a "node". I have two of these nodes and I am using them to talk to each other. I have successfully ported freeRTOS to my microcontroller so that I can use tasks to blink LEDs and to do very basic send/receive of data using my radio driver which I also wrote. The part that I'm struggling with is how to best structure my code and tasks to create something like a network layer on top of my radio driver.
Here were my initial thoughts:
When the radio's data ready interrupt fired, I would set a global boolean variable to signal to my "radio_task()" function to read data from the radios FIFO.
I would have a function called "radio_task()", which would NOT actually be a freeRTOS task, which would check the outgoing queue and use my radio driver (in combination with freeRTOS receiveFromQueue()) to send the front packet on the queue if there is one. This function would also check the global boolean and read a packet from the radio's FIFO if there was one. It would then sendToQueue() this read packet onto the incoming queue.
I would then have an actual freeRTOS called nwk_task() which would have a state machine to do various network maintenance and upkeep etc. Inside the state machine there would be some calls to a nwk_send() function which would sendToQueue() for the outgoing queue. Likewise there would have to be some receiveFromQueue() calls on the incoming queue which would process whatever packets I receive. Then lastly this freeRTOS task would call my function radio_task()
I would potentially have other tasks running which would monitor sensor data and perhaps also call nwk_send() when certain events occur based on sensor data etc.
I feel like that is not a terrible approach, but I'm running into a few difficulties that I thought I would ask about. Questions:
I've created a typedef for my nwk_packet_t which is a struct consisting of an address, a statically defined array which is the maximum size for a radio packet, and a data_length which says how much of the array is actually used for this packet. How do I make a freeRTOS queue which holds my nwk_packet_t 's?? Also is there a better way to make my nwk_packet_t than using the statically sized array? I thought about using a pointer but then I have to make sure the array that I'm sending isn't overwritten until it's actually pulled off the queue right?
I'm confused about where blocking would/needs to occur. By using the global boolean flag in my data ready ISR I allow interrupts to occur more frequently, but does that mean I need to use portENTER_CRITICAL() when I actually push/pull data from the queues?
How would a more experienced embedded programmer structure the tasks and queues to allow for a network layer on top of my basic radio driver?
Sorry for my severe lack of understanding on some of these topics, this is my first venture into OS level programming. Everything else I've done has been very low level and basic C. Thanks for your help!
EDIT: One thing I forgot to mention is that my radio has some hardware provisions for acking. You can set a message to be auto acknowledged with a certain number of retries and the hardware will handle all of that. However, a message can still fail which you can read from the radio or have the radio trigger an interrupt when this happens. So I would like to have some notion of a message success and a message failure that does some type of callback but I have no idea how to work that into my structure. Any ideas on that would fit in with removing stuff from the queues etc.?
Well there is a lot to take in in your post, so a step at a time:
“When the radio's data ready interrupt fired, I would set a global boolean variable to signal to my "radio_task()" function to read data from the radios FIFO.”
Is radio_task() (the function, rather than task) called periodically to ensure the responsiveness of your radio, and to ensure the receive FIFO does not get full? Common design patterns would be:
1) Have the interrupt handle some of the faster top level protocol management, then signal a task when a complete packet is ready for parsing.
2) Have the interrupt do nothing more than save the received data into a buffer then signal a task that there is data available for processing. The task then does all the protocol management.
In both cases the task would remain in the Blocked state until signaled from the interrupt, and the signally from interrupt to task would be performed using a semaphore. Whether approach 1 or 2 was best would be completely dependent on the protocol and what else the system was doing.
The problem with a global variable is that it has no event signalling capability - meaning a task cannot Block on a global variable changing, and be automatically removed form the Blocked state when it does change, as it can be using a semaphore.
“It would then sendToQueue() this read packet onto the incoming queue”
It may be possible to do that from the interrupt, depending on the packet. It might be that you want to buffer the packet in RAM, then send a pointer to the buffer on a queue to unblock a task that was blocked waiting for something to arrive on the queue. The receiving task would then own the buffer, and free it (return it for re-use) when it has finished processing its contents.
“I would then have an actual freeRTOS called nwk_task() which would have a state machine to do various network maintenance and upkeep etc. I”
I'm not sure if I'm answering your question, but hopefully giving some food for thought.
I solution might be something like:
for( ;; )
/* Wait to be told there is new data. */
xSemaphoreTake( .. );
/* Process the received data. */
if( xProcessData() == pdTRUE )
/* If processing the received data resulted in a packet to be returned. */
...being careful that the hardware is only accessed from a single task, or when protected by something like a mutex.
Copyright (C) Amazon Web Services, Inc. or its affiliates. All rights reserved.