To quick start, grab the source code in https://github.com/brunolalb/FreeRTOS_ArduinoMEGA-Demo_Blinky.
FreeRTOS Demo Blinky
Yet another blinky project. This time I'm using the functionality we've studied a few months ago: here and here. This blinky project makes use of a Queue and has two tasks instead of one. One task (prvQueueSendTask()) is a sender and will add a constant value to a queue every 200 ms, while the other task (prvQueueReceiveTask()) is the receiver and will try to get the value from the queue (blocking operation) and, if the value is right, will toggle a LED on PORTB.PB6 (the same from the Blink project).Another test performed in this project is the implementation of the configASSERT() function, which is very useful for debugging purposes. In this project, a parameter is passed to the tasks when they're created and the task should check it in order to attest that it's right. The configASSERT() will turn on the LED embedded in ArduinoMEGA's board (PORTB.PB7), as by now there's nothing much implemented yet. In the future, it could be changed to a message sent through the Serial port.
To get things moving faster, I thought on using the whole main_blinky.c from the Win32 Demo with as little changes as possible. The problem is that it wouldn't be very efficient, once the Windows Demo uses several long variables (32 bits wide) and we're running on an 8-bits microcontroller. Thus I made a few changes to optimize the code.
The main() function initializes not only the queue, which now stores unsigned char variables, but also the LEDs, using the same function as the Blink project. It then creates both tasks with predefined parameters (changed to accommodate one unsigned int) and starts the scheduler.
int main( void ) { /* Create the queue. */ xQueue = xQueueCreate( mainQUEUE_LENGTH, sizeof( unsigned char ) ); /* Setup the LED's for output. */ vParTestInitialise(); if( xQueue != NULL ) { /* Start the two tasks as described in the comments at the top of this file. */ xTaskCreate( prvQueueReceiveTask, /* The function that implements the task. */ "RX", /* The text name assigned to the task - for debug only as it is not used by the kernel. */ configMINIMAL_STACK_SIZE, /* The size of the stack to allocate to the task. */ ( void * ) mainQUEUE_RECEIVE_PARAMETER, /* The parameter passed to the task - just to check the functionality. */ mainQUEUE_RECEIVE_TASK_PRIORITY, /* The priority assigned to the task. */ NULL ); /* The task handle is not required, so NULL is passed. */ xTaskCreate( prvQueueSendTask, "TX", configMINIMAL_STACK_SIZE, ( void * ) mainQUEUE_SEND_PARAMETER, mainQUEUE_SEND_TASK_PRIORITY, NULL ); } /* In this port, to use preemptive scheduler define configUSE_PREEMPTION as 1 in portmacro.h. To use the cooperative scheduler define configUSE_PREEMPTION as 0. */ vTaskStartScheduler(); return 0; }
The receiver task now blinks a LED instead of printing something. The request from the queue blocks the task for a long time while waiting something to be added to it.
static void prvQueueReceiveTask( void *pvParameters ) { unsigned char ucReceivedValue; /* Remove compiler warning in the case that configASSERT() is not defined. */ ( void ) pvParameters; /* Check the task parameter is as expected. */ configASSERT( ( ( unsigned int ) pvParameters ) == mainQUEUE_RECEIVE_PARAMETER ); for( ;; ) { /* Wait until something arrives in the queue - this task will block indefinitely provided INCLUDE_vTaskSuspend is set to 1 in FreeRTOSConfig.h. */ xQueueReceive( xQueue, &ucReceivedValue, portMAX_DELAY ); /* To get here something must have been received from the queue, but is it the expected value? If it is, toggle the LED. */ if( ucReceivedValue == 100UL ) { /* The Windows Blinky Demo prints a message with printf(). * In Arduino, we'll just blink a LED */ vParTestToggleLED(mainLEDBLINK_TASK_LED); ucReceivedValue = 0U; } } }
The sender task gave a bit more trouble, even though the changes in its code were minimal (this is why I'm not pasting it here). Its objective is to add a certain value to the queue, then sleep for a certain amount of time (200 ms). Instead of using the vTaskDelay(), as used in our last project, it uses the vTaskDelayUntil() (it is used to ensure the routine will run every X milliseconds, despite the execution time), and the time was previously defined in milliseconds. To calculate the number of ticks to wait, the pdMS_TO_TICKS() macro is used. If you expand the macro (you can check in FreeRTOS/Source/Include/projdefs.h), you'll see that it multiplies the time, in milliseconds, with the tick rate in Hz, casting everything to TickType_t, which, in Windows is an uint32_t (thus 32-bits), but in our port is an uint16_t (thus 16-bits). This means it will multiply 200 (ms) with 1000 (Hz), which exceeds 16-bits. In order to the macro to work, it has to be redefined in FreeRTOSConfig.h (if you check the comments in projdefs.h, it's already something expected). This is what was added to the end of the header:
/* It is a good idea to define configASSERT() while developing. configASSERT() uses the same semantics as the standard C assert() macro. */ extern void vAssertCalled( unsigned long ulLine, const char * const pcFileName ); #define configASSERT( x ) if( ( x ) == 0 ) vAssertCalled( __LINE__, __FILE__ ) /* Due to problems with integer overflow, this macro is defined here */ #define pdMS_TO_TICKS( xTimeInMs ) ( ( TickType_t ) ( ( ( unsigned long ) ( xTimeInMs ) * ( unsigned long ) configTICK_RATE_HZ ) / ( unsigned long ) 1000 ) )
As you can see, I casted this multiplication operation to an unsigned long, which is 4 bytes wide.
The vAssertCalled(), which is defined in main.c only sets the LED.
void vAssertCalled( unsigned long ulLine, const char * const pcFileName ) { /* Parameters are not used. */ ( void ) ulLine; ( void ) pcFileName; vParTestSetLED(mainASSERTCALLED_LED, pdTRUE); }
The project was uploaded to GitHub.
No comments:
Post a Comment