Showing posts with label eclipse. Show all posts
Showing posts with label eclipse. Show all posts

Friday, August 5, 2016

FreeRTOS 9.0.0 on ArduinoMEGA - Blink project

For a quick start, refer to the project on github:
http://github.com/brunolalb/FreeRTOS_ArduinoMEGA-Blink

FreeRTOS 8.2.3 to 9.0.0 changelog

It's finally time to start working on the FreeRTOS port to ArduinoMEGA. It's been a long time since I started this blog that even a new version of FreeRTOS came up. The system is now on 9.0.0 version, so that's the one I'll use from now on. So first of all, you should download the source code from their website: click here. There aren't many differences from the latest version and the code is pretty much compatible. The complete changelog can be checked here, but I'll describe some of the new features, enhancements and new ports implemented.

  • Support to completely statically allocated systems: remember the post about the memory allocation systems? heap_1.c, heap_2.c, etc? Those were about dynamic allocation. The new version supports static allocation, which means the RAM footprint is defined by linking time, rather than running time. More info can be found here and here.
  • Forcing a task to leave the blocked state: while the task is waiting for something (a semaphore take with timeout or a simple delay), the task is remained in blocked state. The new version implements an API function that unblocks a task (xTaskAbortDelay()). More info here.
  • Deleting tasks: in prior versions, when a task was deleted, its memory were only freed by the Idle function, independently of who has deleted it. Now, when one task is deleted by another task, the memory (stack and TCB) is freed immediately.
  • Obtaining a Task Handle from the Task Name: it's now possible to do this with the new API function: xTaskGetHandle(). More info here.
  • configAPPLICATION_ALLOCATED_HEAP: it's now possible to specify the memory position where the heap will be allocated when using heap_1 or heap_2 (and heap_4, but that was already possible) by declaring the array "uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];". More info here.

Creating required files and the Eclipse project

The initial objective will be to have the bare minimal amount of code to run the FreeRTOS and just make a LED blink. On your file explorer, create the folder where your project will be. In that folder, create another folder called FreeRTOS, where we'll keep all the RTOS code, just for cleanliness purposes. Let's copy the source files from the FreeRTOS official folder:
  • The basic kernel: Source/tasks.c, queue.c and list.c
  • Memory management: Source/MemMang/heap_1.c (this is the simpler one)
  • The portable bits: Source/portable/[compiler]/[processor]/port.c and portmacro.c
    • In this case, I created the folder WinAVR as the [compiler] and ATmega2560 as the [processor].
    • I copied the files from the ATmega323 port (FreeRTOSv9.0.0\FreeRTOS\Source\portable\GCC\ATMega323) to start with something close to what I want.
  • Header files: Source/include/ (you may copy the whole folders) and Demo\Common\include\partest.h.
To the root folder of the project we'll add the application specifics (I copied them from the ATmega323 Demo project, in FreeRTOSv9.0.0\FreeRTOS\Demo\AVR_ATMega323_WinAVR):
  • FreeRTOSConfig.h 
  • main.c
  • ParTest/ParTest.c
This is what we have in our project folder:


Supposing that you already have your Eclipse configured (if not, check here), open it and go to File > New > C++ Project.
  1. Choose the Empty Project under the folder "AVR Cross Target Application"
  2. Pick a name (FreeRTOS_ArduinoMEGA - rev0.1) and point the location to your project folder


  3. Click Next
  4. Click Next again (Debug and Release configs)
  5. Choose the MCU (ATmega2560) and Frequency (16000000 - that's 16 followed by 6 zeroes, 16MHz)
  6. Click Finish. This is what your Project Explorer should be showing:


  7. In the Project Properties, under AVR > AVRDude, in the box Programmer Configuration, choose the config you created for the Blink project (or follow the steps 9 and 10 here): ArduinoMEGA2560 - STK500v2.
  8. Still on the Project Properties, go to C/C++ Build > Settings. On the tab Tool Settings:
    1. Additional Tools in Toolchain, check "Generate HEX file for Flash memory"
    2. AVR Compiler > Optimization, Optimization Levels: Size Optimization (-Os), uncheck Pack Structs and Short Enums
    3. AVR Compiler > Language Standard, uncheck "char is unsigned" and "bitfields are unsigned"
    4. Do the same to AVR C++ Compiler
  9. Adjusting some paths under C/C++ General > Paths and Symbols > Includes, add the following workspace directories:
    1. Project's root
    2. FreeRTOS/Demo/Common/include
    3. FreeRTOS/Source/include
    4. FreeRTOS/Source/portable/WinAVR/ATmega2560
By now, the project should be ready, but the code is all wrong, so let's work on that.

Working on port.c and portmacro.h

Before we start, we should define a few things. First of all, the main timer for the FreeRTOS (the one that will provide the Tick) will be Timer 1 and the Tick will be 1 ms (1000 Hz). No special reason, as it could be any other, but two things made me choose this: it's the same used in AVR323 and it's the same time we used on the Blink project.
This timer should produce interrupts every 1 ms, without any interference of the FreeRTOS functions, thus a simple timer that counts to a certain value, indicates an interrupt and resets on its own, restarting the process. Refer to the function prvSetupTimerInterrupt( void) around line 402 of the port.c file. There are a few differences, since the ATmega323 is bit simpler.
/*
 * Setup timer 1 compare match A to generate a tick interrupt.
 */
static void prvSetupTimerInterrupt( void )
{
uint32_t ulCompareMatch;
uint8_t ucHighByte, ucLowByte;

 /* Using 16bit timer 1 to generate the tick.  Correct fuses must be
 selected for the configCPU_CLOCK_HZ clock. */

 ulCompareMatch = configCPU_CLOCK_HZ / configTICK_RATE_HZ;

 /* We only have 16 bits so have to scale to get our required tick rate. */
 ulCompareMatch /= portCLOCK_PRESCALER;

 /* Adjust for correct value. */
 ulCompareMatch -= ( uint32_t ) 1;

 /* Setup compare match value for compare match A.  Interrupts are disabled 
 before this is called so we need not worry here. */
 ucLowByte = ( uint8_t ) ( ulCompareMatch & ( uint32_t ) 0xff );
 ulCompareMatch >>= 8;
 ucHighByte = ( uint8_t ) ( ulCompareMatch & ( uint32_t ) 0xff );
 OCR1AH = ucHighByte;
 OCR1AL = ucLowByte;

 /* Setup clock source and compare match behaviour. */
 ucLowByte = portCLEAR_COUNTER_ON_MATCH_TCCR1A;
 TCCR1A = ucLowByte;
 ucLowByte = portCLEAR_COUNTER_ON_MATCH_TCCR1B | portPRESCALE_64;
 TCCR1B = ucLowByte;

 /* Enable the interrupt - this is okay as interrupt are currently globally
 disabled. */
 ucLowByte = TIMSK1;
 ucLowByte |= portCOMPARE_MATCH_A_INTERRUPT_ENABLE;
 TIMSK1 = ucLowByte;
}
/*-----------------------------------------------------------*/
You probably realize two changes: The addition of the TCCR1A config and the correct config of the TIMSK1 register (was TIMSK on ATmega323). The definitions (around line 90) change to the following.
/* Hardware constants for timer 1. */
#define portCLEAR_COUNTER_ON_MATCH_TCCR1A  ( ( uint8_t ) 0b00000000 )
#define portCLEAR_COUNTER_ON_MATCH_TCCR1B  ( ( uint8_t ) 0b00001000 )
#define portPRESCALE_64     ( ( uint8_t ) 0b00000011 )
#define portCLOCK_PRESCALER    ( ( uint32_t ) 64 )
#define portCOMPARE_MATCH_A_INTERRUPT_ENABLE  ( ( uint8_t ) 0b00000010 )

Even though both microcontrollers use the same AVR architecture, the ATmega2560 has 2 more registers to be saved: RAMPZ and EIND, thus the functions portSAVE_CONTEXT() and portRESTORE_CONTEXT() will need a tweek.

#define portSAVE_CONTEXT()     \
 asm volatile ( "push r0   \n\t" \
   "in r0, __SREG__  \n\t" \
   "cli    \n\t" \
   "push r0   \n\t" \
   "in r0, 0x3b  \n\t" \
   "push r0   \n\t" \
   "in r0, 0x3c  \n\t" \
   "push r0   \n\t" \
   "push r1   \n\t" \
   "clr r1   \n\t" \
   "push r2   \n\t" \
   "push r3   \n\t" \
   "push r4   \n\t" \
   "push r5   \n\t" \
   "push r6   \n\t" \
   "push r7   \n\t" \
   "push r8   \n\t" \
   "push r9   \n\t" \
   "push r10   \n\t" \
   "push r11   \n\t" \
   "push r12   \n\t" \
   "push r13   \n\t" \
   "push r14   \n\t" \
   "push r15   \n\t" \
   "push r16   \n\t" \
   "push r17   \n\t" \
   "push r18   \n\t" \
   "push r19   \n\t" \
   "push r20   \n\t" \
   "push r21   \n\t" \
   "push r22   \n\t" \
   "push r23   \n\t" \
   "push r24   \n\t" \
   "push r25   \n\t" \
   "push r26   \n\t" \
   "push r27   \n\t" \
   "push r28   \n\t" \
   "push r29   \n\t" \
   "push r30   \n\t" \
   "push r31   \n\t" \
   "lds r26, pxCurrentTCB \n\t" \
   "lds r27, pxCurrentTCB + 1 \n\t" \
   "in r0, 0x3d  \n\t" \
   "st x+, r0   \n\t" \
   "in r0, 0x3e  \n\t" \
   "st x+, r0   \n\t" \
 );
#define portRESTORE_CONTEXT()     \
 asm volatile ( "lds r26, pxCurrentTCB \n\t" \
   "lds r27, pxCurrentTCB + 1 \n\t" \
   "ld r28, x+   \n\t" \
   "out __SP_L__, r28  \n\t" \
   "ld r29, x+   \n\t" \
   "out __SP_H__, r29  \n\t" \
   "pop r31   \n\t" \
   "pop r30   \n\t" \
   "pop r29   \n\t" \
   "pop r28   \n\t" \
   "pop r27   \n\t" \
   "pop r26   \n\t" \
   "pop r25   \n\t" \
   "pop r24   \n\t" \
   "pop r23   \n\t" \
   "pop r22   \n\t" \
   "pop r21   \n\t" \
   "pop r20   \n\t" \
   "pop r19   \n\t" \
   "pop r18   \n\t" \
   "pop r17   \n\t" \
   "pop r16   \n\t" \
   "pop r15   \n\t" \
   "pop r14   \n\t" \
   "pop r13   \n\t" \
   "pop r12   \n\t" \
   "pop r11   \n\t" \
   "pop r10   \n\t" \
   "pop r9   \n\t" \
   "pop r8   \n\t" \
   "pop r7   \n\t" \
   "pop r6   \n\t" \
   "pop r5   \n\t" \
   "pop r4   \n\t" \
   "pop r3   \n\t" \
   "pop r2   \n\t" \
   "pop r1   \n\t" \
   "pop r0   \n\t" \
   "out 0x3c, r0  \n\t" \
   "pop r0   \n\t" \
   "out 0x3b, r0  \n\t" \
   "pop r0   \n\t" \
   "out __SREG__, r0  \n\t" \
   "pop r0   \n\t" \
 );

The same goes to the pxPortInitialiseStack(). After saving the address for the task, you'll need an extra increment of the stack pointer, and after saving the interrupt enable, you'll need to save the initialize value of both RAMPZ and EIND registers (they'll be initialized with 0). Then, you can save the rest of the registers. I'll copy here only part of the function.

 /* The start of the task code will be popped off the stack last, so place
 it on first. */
 usAddress = ( unsigned portSHORT ) pxCode;
 *pxTopOfStack = ( StackType_t ) ( usAddress & ( unsigned portSHORT ) 0x00ff );
 pxTopOfStack--;

 usAddress >>= 8;
 *pxTopOfStack = ( StackType_t ) ( usAddress & ( unsigned portSHORT ) 0x00ff );
 pxTopOfStack--;

 *pxTopOfStack = 0;
 pxTopOfStack--;

 /* Next simulate the stack as if after a call to portSAVE_CONTEXT().  
 portSAVE_CONTEXT places the flags on the stack immediately after r0
 to ensure the interrupts get disabled as soon as possible, and so ensuring
 the stack use is minimal should a context switch interrupt occur. */
 *pxTopOfStack = ( StackType_t ) 0x00; /* R0 */
 pxTopOfStack--;
 *pxTopOfStack = portFLAGS_INT_ENABLED;
 pxTopOfStack--;

 /* If we have an ATmega2560, we are also saving the RAMPZ and EIND registers.
  * We should default those to 0.
  */
 *pxTopOfStack = ( portSTACK_TYPE ) 0x00; /* EIND */
 pxTopOfStack--;
 *pxTopOfStack = ( portSTACK_TYPE ) 0x00; /* RAMPZ */
 pxTopOfStack--;


 /* Now the remaining registers.   The compiler expects R1 to be 0. */
 *pxTopOfStack = ( StackType_t ) 0x00; /* R1 */
 pxTopOfStack--;

The port.c is done.

As for the header (portmacro.h), there is no need to change anything (that's the cool thing of starting from a similar architecture).

FreeRTOSConfig.h

This file won't need much work. First of all, our Arduino is running at 16 MHz, thus the configCPU_CLOCK_HZ has to be changed. The tick rate remains at 1000 Hz. The ATmega2560 has 8KB of SRAM, instead of the 2KB available in the ATmega323, thus change the configTOTAL_HEAP_SIZE to something closer to 8KB (I used 7500). I'm not using the idle hook, thus set configUSE_IDLE_HOOK to 0.

main.c

This will need some extra work, as by now we're only trying to blink a led, so most of it is going away. This is what I'm left with.

#include <stdlib.h>
#include <string.h>

/* Scheduler include files. */
#include "FreeRTOS.h"
#include "task.h"

/* Demo file headers. */
#include "partest.h"

/* Priority definitions for most of the tasks in the demo application.  Some
tasks just use the idle priority. */
#define mainLED_TASK_PRIORITY   ( tskIDLE_PRIORITY + 1 )

/* The sleeping period for blinking the LED */
#define mainLEDBLINK_TASK_PERIOD  ( ( TickType_t ) 1000 / portTICK_PERIOD_MS  )

/* LED that is toggled every mainLEDBLINK_TASK_PERIOD */
#define mainLEDBLINK_TASK_LED   ( 6 )

/*
 * The task function for the "Blink" task.
 */
static void vBlink( void *pvParameters );

/*-----------------------------------------------------------*/

int main( void )
{
 /* Setup the LED's for output. */
 vParTestInitialise();

 /* Create the tasks defined within this file. */
 xTaskCreate( vBlink, "Blink", configMINIMAL_STACK_SIZE, NULL, mainLED_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;
}
/*-----------------------------------------------------------*/

static void vBlink( void *pvParameters )
{
 /* The parameters are not used. */
 ( void ) pvParameters;

 /* Cycle forever, toggling the LED and sleeping */
 while(1)
 {
  vParTestToggleLED(mainLEDBLINK_TASK_LED);
  vTaskDelay(mainLEDBLINK_TASK_PERIOD);
 }

}
/*-----------------------------------------------------------*/

I know ArduinoMEGA's LED is connected to the 7th bit of PORTB, but I added an extra on the 6th bit (digital port 12 on the board) for debug purposes.

ParTest.c

The last changes are in ParTest.c, as the original used the whole port as LEDs. I'm using only bits 6 and 7. Also, the ATmega2560 has this register PINB that, when you write a 1 to a bit, it toggles the value of the output.

#include "FreeRTOS.h"
#include "task.h"
#include "partest.h"

/*-----------------------------------------------------------
 * Simple parallel port IO routines.
 *-----------------------------------------------------------*/

#define partstLEDS_OUTPUT ( ( unsigned char ) 0b11000000 )
#define partstALL_OUTPUTS_OFF ( ( unsigned char ) 0b00111111 )
#define partstMAX_OUTPUT_LED ( ( unsigned char ) 7 )
#define partstMIN_OUTPUT_LED ( ( unsigned char ) 6 )

static volatile unsigned char ucCurrentOutputValue = partstALL_OUTPUTS_OFF;

/*-----------------------------------------------------------*/

void vParTestInitialise( void )
{
 ucCurrentOutputValue = partstALL_OUTPUTS_OFF;

 /* Set port B direction to outputs.  Start with all output off. */
 DDRB = partstLEDS_OUTPUT;
 PORTB &= ucCurrentOutputValue;
}
/*-----------------------------------------------------------*/

void vParTestSetLED( unsigned portBASE_TYPE uxLED, signed portBASE_TYPE xValue )
{
unsigned char ucBit = ( unsigned char ) 1;

 if(( uxLED <= partstMAX_OUTPUT_LED ) && ( uxLED >= partstMIN_OUTPUT_LED ))
 {
  ucBit <<= uxLED; 

  vTaskSuspendAll();
  {
   if( xValue == pdFALSE )
   {
    ucBit ^= ( unsigned char ) 0xff;
    ucCurrentOutputValue &= ucBit;
    PORTB &= ucCurrentOutputValue;
   }
   else
   {
    ucCurrentOutputValue |= ucBit;
    PORTB |= ucCurrentOutputValue;
   }
  }
  xTaskResumeAll();
 }
}
/*-----------------------------------------------------------*/

void vParTestToggleLED( unsigned portBASE_TYPE uxLED )
{
 unsigned char ucBit;

 if(( uxLED <= partstMAX_OUTPUT_LED ) && ( uxLED >= partstMIN_OUTPUT_LED ))
 {
  ucBit = ( ( unsigned char ) 1 ) << uxLED;

  vTaskSuspendAll();
  {

   PINB = ucBit;
   ucCurrentOutputValue = PORTB;
  }
  xTaskResumeAll();   
 }
}

I guess this is it!
Compile, add the forgotten ; and send it to your Arduino. Try changing the delay on the Blink task and send it again. I uploaded the whole project to GitHub.

Monday, August 1, 2016

ArduinoMEGA on Eclipse without Arduino SDK

More than being quite limited, the Arduino IDE comes with its own SDK, which means we can't have our own main() function. I chose Eclipse because it's free and easy to use and there are a few tutorials around on how to use an Arduino board with Eclipse IDE. So, I read lots of them and came up with my own step by step:

  1. On Windows:
    1. Download and install Eclipse IDE for C/C++ Developers (today, the latest is Mars 2)
    2. Download and install WinAVR (today, the latest is 20100110)
    3. Open Eclipse, go to Help > Install New Software.... Add the following to the "Work with" section: http://avr-eclipse.sourceforge.net/updatesite and install the plugin.
    4. Done!
  2. On Linux (Ubuntu in my case):
    1. Install Eclipse
      1. sudo apt-get install eclipse
    2. Install AVR compiler
      1. sudo apt-get install avrdude binutils-avr gcc-avr avr-libc gdb-avr
    3. Open Eclipse, go to Help > Install New Software.... Add the following to the "Work with" section: http://avr-eclipse.sourceforge.net/updatesite and install the plugin.
    4. Add your user to the dialout group, then logout and login again (this is in order to your user have permission to access the serial port).
      1. sudo usermod -a -G dialout <username>
    5. Done!


Wait, what? This is it? Yep! Unless you didn't install WinAVR on its default location (C:/WinAVR-20100110). In that case, you'll need to adjust the paths in the AVR Eclipse plugin (on Eclipse, go to Window > Preference > AVR > Paths):
  • AVR-GCC: <WinAVR path>\bin
  • GNU make: <WinAVR path>\utils\bin
  • AVR Header Files: <WinAVR path>\avr\include
  • AVRDude: <WinAVR path>\bin

Yes, this screen is from Ubuntu.

Now let's create a first project just to check whether it works. Can you guess? A Blink project! This time, we won't use any of the easy Arduino stuff. Instead, let's open the microcontroller datasheet and start configuring the registers. In Eclipse, go to File > New > C++ Project (in Linux, you'll go to File > New > Project and choose C++ Project under C/C++ Folder).
  1. Choose the Empty Project under the folder "AVR Cross Target Application"


  2. Pick a name (Blink) and a location
  3. Click Next
  4. Click Next again
  5. Choose the MCU (ATmega2560) and Frequency (16000000 - that's 16 followed by 6 zeroes, 16MHz)
  6. Click Finish
  7. Go to the project properties (click with the left button on the project and go to Properties)
  8. Under AVR > Target Hardware you can check if the MCU and frequency were correctly chosen

  9. Under AVR > AVRDude, in the box Programmer Configuration, click New....
  10. Choose the bootloader version (Atmel STK500 Version 2.x firmware), choose a name for the configuration, choose the correct COM port in "Override default port" (use the Device Manager in Windows -i.e. COM18 -, or, in Linux, the command line "dmesg | grep tty" and remember to put "/dev/" before the port name - i.e. /dev/ttyUSB0), and override default baudrate with 115200  and click OK.
  11. Still on the Project Properties, go to C/C++ Build > Settings. On the tab Tool Settings:
    1. Additional Tools in Toolchain, check "Generate HEX file for Flash memory"
    2. AVR Compiler > Optimization, Optimization Levels: Size Optimization (-Os), uncheck Pack Structs and Short Enums
    3. AVR Compiler > Language Standard, uncheck "char is unsigned" and "bitfields are unsigned"
    4. Do the same to AVR C++ Compiler
  12. Now for the source code files, click with the left button on the project folder (in Project Explorer) and go to New > Folder, choose the project and the name ("src") and click Finish. Again in the Project Explorer, left click on new folder and New > Source File, choose the name ("blink.cpp") and Finish.
  13. Copy the following source code to your file.
    #include <avr/io.h>
    #include <avr/interrupt.h>
    
    int main(void)
    {
     //Setup the clock (timer 1, 62.5kHz (16MHz / 256 = 62.5kHz), CTC mode, interrupt on)
     cli(); //Disable global interrupts
     TCCR1A = 0b00000000; //no physical outputs (COM1Ax, COM1Bx and COM1Cx == 0b00), WGM10 and WGM11 = 0.
     TCCR1B = 0b00001000; //input capture off, WGM13 = 0, WGM12 = 1, prescaler 1/256 (CS1x = 0b100 - off by now, thus 0b000)
     TCCR1C = 0b00000000; //writing a 1 to any of the 3 most significative bits forces a compare match
     TCNT1 = 0x0000; //resets the timer counter
     OCR1A = 62499; //Count 62500 cycles for 1 second interrupt
     OCR1B = 0x0000; //output compare register B is not used
     OCR1C = 0x0000; //output compare register C is not used
     ICR1 = 0x0000; //input capture not used
     TIMSK1 = 0b00000010; //input capture interrupt off, output compare B and C interrupts off, output compare A interrupt on, overflow interrupt off
     TIFR1 = 0x00; //resets the interrupt flags
     TCCR1B |= 0b00000100; //turn on the timer (CS1x = 0b100)
     sei(); //Enable global interrupts
    
     //Setup the I/O for the LED (digital pin 13, or PB7, according to the Arduino pin mapping)
     DDRB = 0b10000000; //PB7 is digital pin 13 (LED), configured as an output
     PORTB |= 0b10000000; //Set PB7 high to turn on LED
    
     while(1) { } //Loop forever, interrupts do the rest
    }
    
    ISR(TIMER1_COMPA_vect) //Interrupt Service Routine - flag is cleared automatically
    {
      PORTB ^= (1<<PB7); //Use xor to toggle the LED
    }
  14. Build it (Ctrl+B)
    1. Edit: If you're using Windows 10 (or other versions) and encountered this error: make: *** [src/blink.o] Error -1073741502, follow the steps on this post.
  15. To transfer to the board, there's a little catch (in Windows): seems like Arduino IDE uses a modified version of AVRDude that resets the board before attempting to upload the HEX file (this is needed in order to the Arduino enter the bootloader). So, if you get this timeout message, just hold the reset button on your board and release when you click the Upload button (that's the AVR button with an arrow pointing downwards, or CTRL+ALT+U).
    avrdude: stk500_2_Receive_Message(): timeout
  16. Now modify the OCR1A to 6249, compile and upload again: The timer will run 10 times faster, thus the LED must blink faster, if it doesn't, well...find out what's wrong and tell me in the comments box :)

Right now you should have a working hardware, with a working IDE and burner (close enough).
The project is available on GitHub.

On the next post, let's talk business and start porting the FreeRTOS.

Monday, January 25, 2016

Understanding Blinky - part 1

Ok, all compiled, all working, but nothing was truly understood. Let's dig through Blinky's code and study a bit about how does it work. It probably won't make much difference, but I'll be using the Eclipse + MinGW version to explain. Just for me to remember, this project creates two tasks and a queue. One task is the Sender (it posts a message on the queue) and the other is the Receiver (it receives the message from the queue and prints another message on the screen).

main_blinky()

We'll start checking main_blinky() function (main_blinky.c, line 162), since I believe it's a little more practical than going through FreeRTOS' main() function. The first thing it does is to create a queue (xQueueCreate() [1]), with mainQUEUE_LENGTH positions (1 position only), each of them with sizeof( unsigned long ) bytes (in my case, 4 bytes). A queue is a simple way to send messages from one task to the other, usually used as a FIFO. In this case, the queue identifier (xQueue) is a global variable that can be used by both tasks.

Moving on, the tasks are created with xTaskCreate() and the parameters are [2]:

  • A pointer to the function that implements the task (prvQueueReceiveTask for the Receiver and prvQueueSendTask for the Sender)
  • The name of the task, only used for easier debug ("RX" and "TX")
  • The size of the stack, in Words (configMINIMAL_STACK_SIZE for both, which is 50 Words, or 200 bytes, since each Word is 4 bytes wide)
  • A pointer to the parameter(s) sent to the task (mainQUEUE_RECEIVE_PARAMETER and mainQUEUE_SEND_PARAMETER, used just to show how the functionality works; it is important to notice that the content must exist during the entire time the task is running, thus declaring it static is a good idea, otherwise, the task could access some address that no longer exists)
  • The priority of the task (mainQUEUE_RECEIVE_TASK_PRIORITY for the receiver and mainQUEUE_SEND_TASK_PRIORITY for the sender; in this case, the receiver has a higher priority and both the task have priorities higher than the Idle Task, which has the lowest possible priority)
  • A handler to the task, which can be used to check if the task was created, to delete the task, usw. (this is not used on Blinky).
Finally, the scheduler is started and the main_blinky ends on an endless loop.

prvQueueSendTask

The Sender uses a few local variables: xNextWakeTime, which holds the moment, in ticks, when the task will wake up; ulValueToSend, which holds the value to be stored on the queue; and xBlockTime, which stored the amount of time, in ticks, that the task will be sleeping (blocked state - 200 ms). The task starts by checking whether the parameters are right using the configASSERT() function, and if they're not, it will stop the execution. The xNextWakeTime is initialized with the amount of ticks since the scheduler started (xTaskGetTickCount()). The endless loop contains a delay function (vTaskDelayUntil()), which receives as parameters the previous wake time (currently stored on xNextWakeTime) and the amount of ticks the task will remain in blocked stated (xBlockTime), and the post-to-queue funtion (xQueueSend(), which is equivalent to xQueueSendToBack(), thus using the queue as a FIFO [3]), which receives as parameters the handler/identifier of the queue (xQueue, the global variable), the value to be sent (ulValueToSend - the value will be copied to the queue) and the amount of time, in ticks, that the function should wait if the queue is full (for Blinky, the queue should always be empty by the time this function is called, so the wait period is zero).

prvQueueReceiveTask

The Receiver only uses one local variable: ulReceivedValue, which will be filled with the valued stored on the queue. Similar to the Sender, the first action is to check whether the parameters received by the task are right and stop the execution in case they're not. The endless loop will receive the value from the queue and, if the value is right, will print a message. The function used to receive is xQueueReceive() [4], which receives as parameters the handler/identifier of the queue (xQueue), a place to store the value read (ulReceivedValue) and the amount of time, in ticks, it will wait in blocked mode until there is something on the queue (portMAX_DELAY, which is the maximum amount of time possible; and, as INCLUDE_vTaskSuspend is set to 1 on FreeRTOSConfig.h, the task may wait forever, or actually until the Sender posts a messsage). The rest of the task is pretty straight forward.


Idle Task? Scheduler? Blocked mode? Scenes for our next episode...