User Tools

Site Tools


os:freertos:examples

FreeRTOS program examples

This page contains several application examples for the WSN430 platform. Their goal is to show how to implement the main features of FreeRTOS, how the specification of the platform should impact on the code and to give a few examples that could be as many start points for other developments.

01: simple multitask

This first example is a simple application consisting of two independent tasks running concurrently. The goal is to show how to setup a multitask environment, define and create the tasks, start a preemptive scheduler, as well as to show some task time control functions. The source code for this project is found in the folder: 01_simple_multitask, and the source code of interest in the main.c source file.

In this design, there are two independent tasks: the first called ’LED’ just blinks the LEDs at a fixed rate, and the second called ’Temperature’ periodically asks the external temperature sensor for a measurement and displays the result on a UART link to a PC. Both tasks will run at the same priority, and the scheduler might preempt the tasks for letting the other one execute.

The LED task will first configure the LED driver and initialize an LED state variable then enter the infinite loop where it sets the LEDs according to the state variable, increments this variable, then asks the scheduler to block the task for a given time.

The Temperature task will first initialize the UART communication driver and the temperature sensor driver, request the latter a continuous sampling and enters the infinite loop. In the loop, the task reads the temperature from the sensor, displays it on the serial port, and waits for a given time.

A task implementation consists of a function containing an infinite loop that will be executed at runtime, and whose prototype is as follows:

void TaskFunction( void* pvParameters );

The source code of these two task is:

/**
 * The LED task function.
 * It increments a variable and displays its value on the 3 LEDs,
 * then waits for a given delay and start over again.
 * \param pvParameter NULL is passed as parameter.
 */
static void vLEDTask(void* pvParameters)
{
    uint16_t led_state = 0;
    /* The LEDs are updated every 200 ticks, about 200 ms */
    const uint16_t blinkDelay = 200;
    /* Initialize the LEDs */
    LEDS_INIT();
 
    /* Infinite loop */
    while (1)
    {
        /* Set the LEDs according to the value of the led_state value */
        LEDS_SET(led_state);
        /* Increment the variable */
        led_state += 1;
        /* Block the task for the defined time */
        vTaskDelay(blinkDelay);
    }
}
 
/**
 * The temperature measurement task function.
 * It reads the temperature from the sensor and print it
 * on the serial port.
 * \param pvParameters NULL is passed, unused here.
 */
static void vTempTask(void* pvParameters)
{
    uint8_t msb;
    uint16_t samplecount = 0;
    uint16_t xLastWakeTime = xTaskGetTickCount();
    /* The display period is 1000 ticks, about 1s */
    const uint16_t xWakePeriod = 1000;
 
    /* Initialize the uart port, and the temperature sensor */
    uart0_init(UART0_CONFIG_1MHZ_115200);
    ds1722_init();
    ds1722_set_res(8);
    ds1722_sample_cont();
 
    /* Infinite loop */
    while(1)
    {
        /* Read the sensor and increment the sample count */
        msb = ds1722_read_MSB();
        samplecount++;
 
        /* Print the result on the uart port */
        printf("Sample #%u: temperature = %u C\r\n", samplecount, msb);
 
        /* Block until xWakePeriod ticks since previous call */
        vTaskDelayUntil(&xLastWakeTime, xWakePeriod);
    }
}

Note that the two tasks use different task control functions to obtain delays from the scheduler. On one hand, the LED task use the following function:

void vTaskDelay( portTickType xTicksToDelay )

which asks the scheduler to block the task for xTicksToDelay ticks from the time of call. On the other hand, the Temperature task uses the function:

void vTaskDelayUntil( portTickType *pxPreviousWakeTime, portTickType xTimeIncrement ) 

which asks the scheduler to block the task until xTimeIncrement ticks have expired since the time value pointed by pxPreviousWakeTime. This variable is then updated to the time when the task is unblocked. This function ensures the loop will be executed with a given period no matter what the execution time is.

The main function of the program needs to add the tasks to the scheduler and start it. This is done using the xTaskCreate and vTaskStartScheduler functions. Here is the complete code of the main.c file.

1 |main.c
#include <stdio.h>
#include <io.h>
#include <signal.h>
 
/* Scheduler includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
 
/* Project includes */
#include "clock.h"
#include "leds.h"
#include "uart0.h"
#include "ds1722.h"
 
/* Hardware initialization */
static void prvSetupHardware( void );
static void vLEDTask(void* pvParameters);
static void vTempTask(void* pvParameters);
 
/**
 * Putchar function required by stdio.h module to be able to use printf()
 */
int putchar(int c)
{
    return uart0_putchar(c);
}
 
/**
 * The main function.
 */
int main( void )
{
    /* Setup the hardware. */
    prvSetupHardware();
 
    /* Add the two tasks to the scheduler */
    xTaskCreate(vLEDTask, "LED", configMINIMAL_STACK_SIZE, NULL, 1, NULL );
    xTaskCreate(vTempTask, "Temperature", configMINIMAL_STACK_SIZE, NULL, 1, NULL );
 
 
    /* Start the scheduler. */
    vTaskStartScheduler();
 
    /* As the scheduler has been started we should never get here! */
    return 0;
}
 
/**
 * Initialize the main hardware parameters.
 */
static void prvSetupHardware( void )
{
    /* Stop the watchdog timer. */
    WDTCTL = WDTPW + WDTHOLD;
 
    /* Setup MCLK 8MHz and SMCLK 1MHz */
    set_mcu_speed_xt2_mclk_8MHz_smclk_1MHz();
 
    /* Enable Interrupts */
    eint();
}
 
/**
 * The LED task function.
 * It increments a variable and displays its value on the 3 LEDs,
 * then waits for a given delay and start over again.
 * \param pvParameter NULL is passed as parameter.
 */
static void vLEDTask(void* pvParameters)
{
    uint16_t led_state = 0;
    /* The LEDs are updated every 200 ticks, about 200 ms */
    const uint16_t blinkDelay = 200;
 
    /* Initialize the LEDs */
    LEDS_INIT();
 
    /* Infinite loop */
    while (1)
    {
        /* Set the LEDs according to the value of the led_state value */
        LEDS_SET(led_state);
 
        /* Increment the variable */
        led_state += 1;
 
        /* Block the task for the defined time */
        vTaskDelay(blinkDelay);
    }
}
 
/**
 * The temperature measurement task function.
 * It reads the temperature from the sensor and print it
 * on the serial port.
 * \param pvParameters NULL is passed, unused here.
 */
static void vTempTask(void* pvParameters)
{
    uint8_t msb;
    uint16_t samplecount = 0;
    uint16_t xLastWakeTime = xTaskGetTickCount();
    /* The display period is 1000 ticks, about 1s */
    const uint16_t xWakePeriod = 1000;
 
    /* Initialize the uart port, and the temperature sensor */
    uart0_init(UART0_CONFIG_1MHZ_115200);
    ds1722_init();
    ds1722_set_res(8);
    ds1722_sample_cont();
 
    /* Infinite loop */
    while(1)
    {
        /* Read the sensor and increment the sample count */
        msb = ds1722_read_MSB();
        samplecount++;
 
        /* Print the result on the uart port */
        printf("Sample #%u: temperature = %u C\r\n", samplecount, msb);
 
        /* Block until xWakePeriod ticks since previous call */
        vTaskDelayUntil(&xLastWakeTime, xWakePeriod);
    }
}

02: intertask communication

The second example is a simple application that consists of two tasks running concurrently and communicating with each other. The goal is to show how to make a task send data to another using a queue. The source files of this project are found in the folder: 02_queue_multitask, and the source code of interest in the main.c file.

In this design, there are two tasks. The first, called 'Temperature' is really similar to the homonym from the previous example, but instead of printing the measure value every time it reads the sensor, it will send the data to the other task, called 'Print'. This task will just wait until some data arrives, and print it on the UART link.

The 'Print' task first initializes the UART driver and enters its infinite loop where it waits for data to be available on the queue, and prints it each time it reads some. The 'Temperature' task initializes the sensor driver, starts the sampling, and enters the loop where it reads the measure value, places it in the queue, and waits for a given time.

Sending the data between the tasks is done with a queue. A queue is created using the xQueueCreate function:

xQueueHandle xQueueCreate( unsigned portBASE_TYPE uxQueueLength, unsigned portBASE_TYPE uxItemSize );

This function allocates memory for uxQueueLength items of uxItemSize bytes each, and returns a queue handle used to access it later.

The functions used with a queue are xQueueSendToBack to place an item and xQueueReceive to read an item:

portBASE_TYPE xQueueSendToBack( xQueueHandle xQueue, const void * pvItemToQueue, portTickType xTicksToWait );
portBASE_TYPE xQueueReceive( xQueueHandle xQueue, void *pvBuffer, portTickType xTicksToWait );

In both functions, the first parameter is the queue handle, the second is a pointer to the item that will be placed or written. Both operations are done by copy. The third parameter is the time to wait until the action can be done. If set to 0, the function will return immediately whether or not an item has been put/read to/from the queue. If portMAX_DELAY is specified, the function will block until the action is possible. Finally if another value is used, the function will block until the action is possible or the number of ticks corresponding to the value has elapsed.

In the example, the Temperature tasks does not wait if the queue is full when data need to be copied on the queue to respect timing, however the Print task waits until some data arrive, because it has nothing else to do.

The complete application code is found here (main.c file).

1 |main.c
#include <stdio.h>
#include <io.h>
#include <signal.h>
 
/* Scheduler includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
 
/* Project includes */
#include "clock.h"
#include "leds.h"
#include "uart0.h"
#include "ds1722.h"
 
/* Function Prototypes */
static void prvSetupHardware( void );
static void vPrintTask(void* pvParameters);
static void vTempTask(void* pvParameters);
 
int putchar(int c)
{
    return uart0_putchar(c);
}
 
/* Global Variables */
xQueueHandle xQueue;
 
/**
 * The main function.
 */
int main( void )
{
    /* Setup the hardware. */
    prvSetupHardware();
 
    /* Create the Queue for communication between the tasks */
    xQueue = xQueueCreate( 5, sizeof(uint8_t) );
 
    /* Add the two tasks to the scheduler */
    xTaskCreate(vPrintTask, "Print", configMINIMAL_STACK_SIZE, NULL, 1, NULL );
    xTaskCreate(vTempTask, "Temperature", configMINIMAL_STACK_SIZE, NULL, 1, NULL );
 
 
    /* Start the scheduler. */
    vTaskStartScheduler();
 
    /* As the scheduler has been started we should never get here! */
    return 0;
}
 
/**
 * Initialize the main hardware parameters.
 */
static void prvSetupHardware( void )
{
    /* Stop the watchdog timer. */
    WDTCTL = WDTPW + WDTHOLD;
 
    /* Setup MCLK 8MHz and SMCLK 1MHz */
    set_mcu_speed_xt2_mclk_8MHz_smclk_1MHz();
 
    /* Enable Interrupts */
    eint();
}
 
/**
 * The Print task function.
 * It waits until a sensor value has been put in the queue,
 * and prints its value on the uart port.
 * \param pvParameter NULL is passed as parameter.
 */
static void vPrintTask(void* pvParameters)
{
    uint8_t temp_meas;
    uint16_t samplecount = 0;
 
    /* Initialize the uart port */   
    uart0_init(UART0_CONFIG_1MHZ_115200);
 
    /* Infinite loop */
    while (1)
    {
        /* Wait until an element is received from the queue */
        if (xQueueReceive(xQueue, &temp_meas, portMAX_DELAY))
        {
            samplecount++;
            /* Print the result on the uart port */
            printf("Sample #%u: temperature = %u C\r\n", samplecount, temp_meas);
        }
    }
}
 
/**
 * The temperature measurement task function.
 * It reads the temperature from the sensor and puts it on the queue
 * \param pvParameters NULL is passed, unused here.
 */
static void vTempTask(void* pvParameters)
{
    uint8_t msb;
    uint16_t xLastWakeTime = xTaskGetTickCount();
 
    /* The sample period is 1000 ticks, about 1s */
    const uint16_t xWakePeriod = 1000;
 
    /* Initialize the temperature sensor */
    ds1722_init();
    ds1722_set_res(8);
    ds1722_sample_cont();
 
    /* Infinite loop */
    while(1)
    {
        /* Read the sensor */
        msb = ds1722_read_MSB();
 
        /* Put the read value on the queue */
        xQueueSendToBack(xQueue, &msb, 0);
 
        /* Block until xWakePeriod (=1000) ticks since previous call */
        vTaskDelayUntil(&xLastWakeTime, xWakePeriod);
    }
}

03: task synchronization

The third example is a simple application consisting of a single task that synchronizes with an interrupt service routine. The goal is to show how to use the interrupt capabilities of the MSP430 to synchronize FreeRTOS tasks. The source code for this project is found in the folder: 03_semphr_synchro, and the source code of interest in the main.c file.

In this design, there is a single task, called 'LED' that will update the LEDs every time a character has arrived on the UART serial port. The UART driver can register a callback function that will be called from an interrupt service routine each time a character arrives. This example shows how to synchronize the task with the callback function being called.

The solution used is a binary semaphore. It is equivalent to a queue with only one item of size 0 byte, that has two states: full or empty. The semaphore is created by the vSemaphoreCreateBinary macro:

vSemaphoreCreateBinary( xSemaphoreHandle xSemaphore )

Operations on a semaphore are 'take' and 'give', executed with the following macros:

xSemaphoreTake( xSemaphoreHandle xSemaphore, portTickType xBlockTime )
xSemaphoreGive( xSemaphoreHandle xSemaphore )

Both macros will return pdTrue if the semaphore was taken/given correctly, and pdFalse if an error occured. The xBlockTime parameter of the first macro is the maximum number of ticks to wait for the semaphore to be available. It corresponds to the same parameter used in with the queue functions.

When accessing a queue or its derivatives (such as the binary semaphore) from an interrupt service routine, special functions and macros should be used. They end FromISR in their names: xSemaphoreTakeFromISR and xSemaphoreGiveFromISR.

The LED task initializes the LED driver, and enters its loop where it increments the state variable, waits until the semaphore is available and takes it (empties the queue), and updates the LED display according to the state. The task will block on the semaphore, and will be unblocked by the uart callback function, releasing the semaphore every time a character is received by the UART.

Here is the complete code.

1 |main.c
#include <stdio.h>
#include <io.h>
#include <signal.h>
 
/* Scheduler includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
 
/* Project includes */
#include "clock.h"
#include "leds.h"
#include "uart0.h"
#include "ds1722.h"
 
/* Function Prototypes */
static void prvSetupHardware( void );
static void vLEDTask(void* pvParameters);
static void rx_char_cb(uint8_t c);
 
int putchar(int c)
{
    return uart0_putchar(c);
}
 
/* Global Variables */
xSemaphoreHandle xSemaphore;
 
/**
 * The main function.
 */
int main( void )
{
    /* Setup the hardware. */
    prvSetupHardware();
 
    /* Create the Semaphore for synchronization between UART and LED task */
    vSemaphoreCreateBinary( xSemaphore)
 
    /* Add the only task to the scheduler */
    xTaskCreate(vLEDTask, "LED", configMINIMAL_STACK_SIZE, NULL, 1, NULL );
 
    /* Start the scheduler. */
    vTaskStartScheduler();
 
    /* As the scheduler has been started we should never get here! */
    return 0;
}
 
/**
 * Initialize the main hardware parameters.
 */
static void prvSetupHardware( void )
{
    /* Stop the watchdog timer. */
    WDTCTL = WDTPW + WDTHOLD;
 
    /* Setup MCLK 8MHz and SMCLK 1MHz */
    set_mcu_speed_xt2_mclk_8MHz_smclk_1MHz();
 
    /* Configure the UART module for serial communication */
    uart0_init(UART0_CONFIG_1MHZ_115200);
    uart0_register_callback(rx_char_cb);
    printf("type any char to update the LEDs\r\n");
 
    /* Enable Interrupts */
    eint();
}
 
/**
 * The LEDs task function.
 * It waits the semaphore is given, then takes it and update the LEDs
 * \param pvParameters NULL is passed, unused here.
 */
static void vLEDTask(void* pvParameters)
{
    uint16_t leds_state = 0;
 
    /* Initialize the LEDs */
    LEDS_INIT();
 
    /* Infinite loop */
    while(1)
    {
        /* Increment the LED state */
        leds_state ++;
        /* Block until the semaphore is given */
        xSemaphoreTake(xSemaphore, portMAX_DELAY);
        /* update the LEDs and loop */
        LEDS_SET(leds_state);
    }
}
 
/**
 * The callback function called when a char is received.
 * It gives the semaphore.
 * \param c the received char
 */
static void rx_char_cb(uint8_t c)
{
    static portBASE_TYPE xHigherPriorityTaskWoken;
    /* Give the semaphore */
    xSemaphoreGiveFromISR(xSemaphore, &xHigherPriorityTaskWoken);
}

04: mutual exclusion

The fourth example is a bigger application than the previous example that consists of a three tasks using a mutual exclusion (mutex) mechanism for accessing the same resource. The goal is to show how to design a basic application while taking into account hardware constraints. The source code for this project is found in the 04_mutex_multitask folder, and the source code of interest in the main.c, radioTask.h and radioTask.c source files.

In this design, there are three tasks, each accessing a peripheral device through the same hardware SPI bus. Since the tasks evolve in a concurrent multitask environment, each task could be preempted at any time, and to be sure every communication with a peripheral is not halted to start another communication, a mutex is used. Therefore, when a task want to access its corresponding peripheral device, it will first have to take the mutex. Then, when the task has obtained it, communicate with the device, and give back the mutex to allow the other tasks to use the SPI ressource.

The application is as follows: a 'Temperature' task reads periodically an external temperature sensor, does an average on these values and passes it to the two other tasks 'Flash' and 'Radio' via two queues. The 'Flash' task stores the values from the queue in a buffer, that will be recopied to the flash memory device once it gets full. The 'Radio' task does almost the same, but sends the temperature values over the air in a packet when enough samples have been read. The intertask communications are done with two queues (Temperature ⇒ Flash and Temperature ⇒ Radio) and one mutex.

A mutex behaves very similarly to a binary semaphore, but include a priority inheritance mechanism, allowing a task having taken a mutex to run the highest priority amongst the tasks blocked waiting for the mutex. The macros used to give and take a mutex are the exact same as those handling binary semaphores, and the macro to create a mutex is:

xSemaphoreHandle xSemaphoreCreateMutex( void )

Since the Radio task code is quite large, it's been moved to the separate source files radioTask.c and radioTask.h. The latter contains a function prototype that will be used by the main function to create the Radio task.

Here is the main.c source file:

1 |main.c
#include <stdio.h>
#include <io.h>
#include <signal.h>
 
/* Scheduler includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
 
/* Project includes */
#include "radioTask.h"
 
/* Driver includes */
#include "clock.h"
#include "leds.h"
#include "uart0.h"
#include "ds1722.h"
#include "m25p80.h"
 
/* Function Prototypes */
static void prvSetupHardware( void );
static void vFlashTask(void* pvParameters);
static void vTempTask(void* pvParameters);
 
 
int putchar(int c)
{
    return uart0_putchar(c);
}
 
/* Global Variables */
xQueueHandle xFlashQueue, xRadioQueue;
xSemaphoreHandle xSPIMutex;
 
/**
 * The main function.
 */
int main( void )
{
    /* Setup the hardware. */
    prvSetupHardware();
 
    /* Create the Queues */
    xRadioQueue = xQueueCreate( 5, sizeof(uint8_t) );
    xFlashQueue = xQueueCreate( 5, sizeof(uint8_t) );
 
    /* Create the Mutex */
    xSPIMutex = xSemaphoreCreateMutex();
 
    /* Add the tasks to the scheduler */
    xTaskCreate(vTempTask, "Temperature", configMINIMAL_STACK_SIZE, NULL, 1, NULL );
    xTaskCreate(vFlashTask, "Flash",      configMINIMAL_STACK_SIZE, NULL, 1, NULL );
 
    /* Create the Radio Task */
    vCreateRadioTask(xRadioQueue, xSPIMutex);
 
    /* Start the scheduler. */
    vTaskStartScheduler();
 
    /* As the scheduler takes been started we should never get here! */
    return 0;
}
 
/**
 * Initialize the main hardware parameters.
 */
static void prvSetupHardware( void )
{
    /* Stop the watchdog timer. */
    WDTCTL = WDTPW + WDTHOLD;
 
    /* Setup MCLK 8MHz and SMCLK 1MHz */
    set_mcu_speed_xt2_mclk_8MHz_smclk_1MHz();
 
    /* Init the LEDs */
    LEDS_INIT();
    LEDS_OFF();
 
    /* Init the UART module */
    uart0_init(UART0_CONFIG_1MHZ_115200);
    printf("Mutex Example Program Start\r\n");
 
    /* Enable Interrupts */
    eint();
}
 
/**
 * The Flash task function.
 * It waits for incoming data. It stores data from the queue 
 * in a 256byte local buffer, and when it's full, writes it to the flash.
 * \param pvParameter NULL is passed as parameter.
 */
static void vFlashTask(void* pvParameters)
{
    uint8_t  rx_item;
    uint8_t  buffer[256];
    uint16_t buffer_index = 0;
    uint16_t flash_page = 0;
 
    /* Wait for the mutex to be free and take it */
    xSemaphoreTake(xSPIMutex, portMAX_DELAY);
 
    /* Erase the flash */
    LED_RED_ON();
    printf("Flash takes mutex\r\n");
    m25p80_init();
    m25p80_erase_bulk();
    m25p80_load_page(0, buffer);
 
    printf("Flash gives mutex\r\n");
    LED_RED_OFF();
 
    /* Give the mutex back */
    xSemaphoreGive(xSPIMutex);
 
    while (1)
    {
        /* Wait for an item to be put in the queue */
        if ( xQueueReceive(xFlashQueue, &rx_item, portMAX_DELAY) )
        {
            /* Store this item in the buffer */
            buffer[buffer_index] = rx_item;
            buffer_index++;
 
            if (buffer_index == 256)
            {
                buffer_index = 0;
 
                xSemaphoreTake(xSPIMutex, portMAX_DELAY);
                LED_RED_ON();
                printf("Flash takes mutex\r\n");
 
                m25p80_save_page(flash_page, buffer);
 
                printf("Flash gives mutex\r\n");
                LED_RED_OFF();
                xSemaphoreGive(xSPIMutex);
 
                flash_page ++;
            }
        }
    }
}
 
/**
 * The temperature measurement task function.
 * It reads the temperature from the sensor and puts it on the radio
 * and flash queues for sending and storing.
 * \param pvParameters NULL is passed, unused here.
 */
static void vTempTask(void* pvParameters)
{
    uint8_t msb;
    uint16_t xLastWakeTime = xTaskGetTickCount();
    uint16_t sampleCount = 0, sampleAvg = 0;
 
    /* The sample period is 1000 ticks, about 1s */
    const uint16_t xWakePeriod    = 200;
 
    /* The maximum time waiting for the mutex is 100 ticks */
    const uint16_t xMaxMutexBlockTime = 100;
 
    /* Initialize the temperature sensor */
    xSemaphoreTake(xSPIMutex, portMAX_DELAY);
    LED_GREEN_ON();
    ds1722_init();
    ds1722_set_res(8);
    ds1722_sample_cont();
    LED_GREEN_OFF();
    xSemaphoreGive(xSPIMutex);
 
    /* Infinite loop */
    while(1)
    {
        /* Block until xWakePeriod ticks since previous call */
        vTaskDelayUntil(&xLastWakeTime, xWakePeriod);
 
        /* Try to take the mutex for xMaxMutexBlockTime ticks max */
        if ( xSemaphoreTake(xSPIMutex, xMaxMutexBlockTime) )
        {
            LED_GREEN_ON();
            /* If got the mutex : */
            /* Read the sensor */
            msb = ds1722_read_MSB();
 
            /* Give the mutex back */
            xSemaphoreGive(xSPIMutex);
            LED_GREEN_OFF();
 
            /* Do calculation */
            sampleAvg += msb;
            sampleCount += 1;
 
            if (sampleCount == 4)
            {
                sampleAvg >>= 2;
                /* Put the read value on the flash and radio queues
                 * Don't block */
                xQueueSendToBack(xFlashQueue, &sampleAvg, 0);
                xQueueSendToBack(xRadioQueue, &sampleAvg, 0);
 
                /* Reset the variables */
                sampleCount = 0;
                sampleAvg = 0;
            }
        } /* if the mutex was not obtained, skip the sampling for now */
    }
}

The radioTask.h and radioTask.c files:

1 | radioTask.h
#ifndef _RADIOTASK_H_
#define _RADIOTASK_H_
 
/**
 * Function called by the main function. It stores the data queue
 * and SPI mutex handles, and adds the task to the scheduler.
 * \param xQueue handle of the queue for temperature measurements
 * \param xMutex handle of the SPI mutex
 */
void vCreateRadioTask(xQueueHandle xQueue, xSemaphoreHandle xMutex);
 
#endif
1 | radioTask.c
#include <stdio.h>
#include <io.h>
 
/* Scheduler includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
 
#include "radioTask.h"
 
#include "cc1100.h"
#include "leds.h"
 
 
static void vEndOfTx_cb(void);
static void vRadioTask(void* pvParameters);
 
/* File variables */
static xSemaphoreHandle xSPIMutex, xTxSem;
static xQueueHandle xDataQueue;
 
 
void vCreateRadioTask(xQueueHandle xQueue, xSemaphoreHandle xMutex)
{
    /* Stores the handles */
    xDataQueue = xQueue;
    xSPIMutex = xMutex;
 
    /* Create a Semaphore for synchronization with radio interrupts */
    vSemaphoreCreateBinary(xTxSem);
    xSemaphoreTake(xTxSem, 0);
 
    /* Add the radio task to the scheduler */
    xTaskCreate(vRadioTask, "Radio", configMINIMAL_STACK_SIZE, NULL, 1, NULL );
 
}
 
/**
 * The radio task.
 * \param pvParameters NULL is passed at start-up
 */
static void vRadioTask(void* pvParameters)
{
    /* Create the TX packet buffer, and associated variables */
    uint8_t tx_buffer[4];
    uint16_t tx_buffer_index = 0;
    uint8_t tx_length = 4;
 
    /* Take the mutex */
    xSemaphoreTake(xSPIMutex, portMAX_DELAY);
    LED_BLUE_ON();
    printf("Radio takes mutex\r\n");
 
    /* Initialize the radio chip driver, and configure the radio */
 
    cc1100_init();
    cc1100_cmd_idle();
 
    cc1100_cfg_append_status(CC1100_APPEND_STATUS_DISABLE);
    cc1100_cfg_crc_autoflush(CC1100_CRC_AUTOFLUSH_DISABLE);
    cc1100_cfg_white_data(CC1100_DATA_WHITENING_ENABLE);
    cc1100_cfg_crc_en(CC1100_CRC_CALCULATION_ENABLE);
    cc1100_cfg_freq_if(0x0C);
    cc1100_cfg_fs_autocal(CC1100_AUTOCAL_NEVER);
 
    cc1100_cfg_mod_format(CC1100_MODULATION_MSK);
 
    cc1100_cfg_sync_mode(CC1100_SYNCMODE_30_32);
 
    cc1100_cfg_manchester_en(CC1100_MANCHESTER_DISABLE);
 
    // set channel bandwidth (560 kHz)
    cc1100_cfg_chanbw_e(0);
    cc1100_cfg_chanbw_m(2);
 
    // set data rate (0xD/0x2F is 250kbps)
    cc1100_cfg_drate_e(0x0D);
    cc1100_cfg_drate_m(0x2F);
 
    uint8_t table[1];
    table[0] = 0xC2; // 10dBm
    cc1100_cfg_patable(table, 1);
    cc1100_cfg_pa_power(0);
 
    /* Give the mutex back */
    xSemaphoreGive(xSPIMutex);
    LED_BLUE_OFF();
    printf("Radio gives mutex\r\n");
 
    uint8_t data_item;
 
    /* Enter the infinite loop */
    while (1)
    {
        /* Wait for an item to be put in the queue */
        if ( xQueueReceive(xDataQueue, &data_item, portMAX_DELAY) )
        {
            /* Copy the item in the packet buffer */
            tx_buffer[tx_buffer_index] = data_item;
            tx_buffer_index ++;
 
            /* Check if the buffer is full */
            if (tx_buffer_index == 4)
            {
                tx_buffer_index = 0;
 
                /* Take the semaphore and send the packet */
                xSemaphoreTake(xSPIMutex, portMAX_DELAY);
                LED_BLUE_ON();
                printf("Radio takes mutex\r\n");
 
                cc1100_cmd_idle();
                cc1100_cmd_flush_tx();
                cc1100_cmd_calibrate();
 
                cc1100_cfg_gdo0(CC1100_GDOx_SYNC_WORD);
                cc1100_gdo0_int_set_falling_edge();
                cc1100_gdo0_int_clear();
                cc1100_gdo0_int_enable();
                cc1100_gdo0_register_callback(vEndOfTx_cb);
 
                cc1100_gdo2_int_disable();
 
                cc1100_fifo_put((&tx_length), 1);
                cc1100_fifo_put(tx_buffer, tx_length);
 
                cc1100_cmd_tx();
 
                printf("Radio gives mutex\r\n");
                LED_BLUE_OFF();
 
                /* Give the semaphore back */
                xSemaphoreGive(xSPIMutex);
 
                /* Wait until the packet has been successfully sent */
                while (! xSemaphoreTake(xTxSem, portMAX_DELAY));
            }
        }
    }
}
 
/**
 * Callback function called when a packet has been sent.
 */
void vEndOfTx_cb(void)
{
    uint16_t xHigherPriorityTaskWoken; 
 
    /* Give the semaphore, indicating the task the packer is sent */
    xSemaphoreGiveFromISR(xTxSem, &xHigherPriorityTaskWoken);
}

Star-Network MAC example

The two following examples (05 and 06) implement a very simple star network MAC layer. In both programs, a task is created for handling all the MAC functionalities: one is a network device and the other is a coordinator.

The network device (example 05) tries first to join a network. It sends a Attach Request Frame, hoping a coordinator will receive it and answer it. If this happens, the network device can send/receive packets to/from the coordinator.

The coordinator receives the Attach Request Frames, and accepts them. It can then communicate (send and receive packets) with all the nodes attached to it. A typical application is to have one coordinator and a set of network devices attached to it. The coordinator is connected to a PC, the network devices sample some data and transmit them to the coordinator which relays it to the PC.

05: network device

As described above, this example implements the MAC layer of a network device. The source files are located in the 05_network_device folder. All the MAC related files are grouped in the mac subfolder (mac.c and mac.h). The interface between the MAC task and the application is described in the mac/mac.h file. First the task is created calling the vMacTaskCreate function, with as parameters a handle to a mutex that will be used every time the SPI bus should be used, to prevent conflicts with other tasks (see example 04), and the priority the task should run at.

Packets can then be sent using the xSendPacket function, passing as parameters the packet length and a pointer to the packet. The packet will be sent only if the MAC task is in the right state at the time of call (there is no buffering). Note that the packet will be sent to the coordinator the device is attached to. The application interfacing with the MAC layer should implement a function named vPacketReceived, that will be called every time a packet is received from the coordinator.

The application found in the main.c file just repeatedly sends a character string to the coordinator.

06: network coordinator

This example implements the MAC layer of a coordinator device for a star network. The source files are located in the 06_coordinator folder. All the MAC related files are grouped in the mac subfolder (mac.c and mac.h files). The interface between the MAC task and the application is described in the mac/mac.h file. First the task is created by calling the vMacTaskCreate function, with as parameters a handle to a mutex that will be used every time the SPI bus should be used, to prevent conflicts with other tasks (see example 04), and the priority the task should run at.

The coordinator MAC task will accept network device attach requests within a limit of 10 nodes, and is able to communicate with all the nodes independently. To do so, the xSendPacketTo function is used by the application to send packets. The destination node address must be specified. It is possible to list all the attached nodes by calling the xGetAttachedNodes function. A pointer to a char array must be provided, that will the point to the beginning of the node list, and the number of nodes will be returned.

The vPacketReceivedFrom function must be provided by the application since it will be called every time a packet is received from any of the attached nodes.

os/freertos/examples.txt · Last modified: 2018/04/13 14:47 (external edit)