Showing posts with label save context. Show all posts
Showing posts with label save context. 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.

Tuesday, April 19, 2016

ATMega323 port

This post will talk about the portable part of FreeRTOS. As the Windows port is not exactly a port (the FreeRTOS actually runs on top of another operating system, which incurs in a few anomalies when the was made), I'll examine the Atmega323 for WinAVR port files, located under FreeRTOS\Source\portable\GCC\ATMega323. First of all, a quick review on the source file structure.

FreeRTOS Source Files Organization

When you download and extract the zip file containing the FreeRTOS [1], you'll see two directories: FreeRTOS-Plus and FreeRTOS. The first one comprises the FreeRTOS+ ecosystem [2] and the second, the FreeRTOS source code itself (I'll focus on that). Under FreeRTOS directory, there are 3 folders: License (only has the license itself), Demo (all the different official ports and demo applications available) and Source (the real time kernel source). The core RTOS kernel, contained under the Source folder, is comprised of basically three files: tasks.c, queue.c and list.c. The others (croutine.c for co-routine implementation, timers.c for software timers and event_groups.c) are optional.

Each ported processor will require some specific code (mainly the files port.c and portmacro.h), which, for the official ports, is located under FreeRTOS/Source/Portable/[compiler]/[architecture]. For instance, the ATMega323 port we'll check later can be found in FreeRTOS\Source\portable\GCC\ATMega323, as AVR-GCC (WinAVR [3]) is the compiler and ATMega323 is the architecture. The Windows port files we've been using can be found under FreeRTOS\Source\portable\MSVC-MingW.

As the memory management (heap) routines are also needed, the samples we discussed earlier are also provided in the portable layer/folder structure under FreeRTOS\Source\portable\MemMang. There you'll find the 5 sample heap implementations, but you can also write your own and place it there.

Under the Demo folder is where you'll find the specific demo applications source code, along with the common demo implementation code for several functionalities (queues, semaphores, timers, etc) I talked a few times about (here and here). Each official demo application has its own folder, named to indicate the port to which they relate, under FreeRTOS/Demo/ (this is where the FreeRTOSConfig.h is located, for example) as well as an official webpage [4]. Under FreeRTOS/Demo/Common/Minimal/, you'll find the basic implementation for the functionalities that is shared between several applications. The Windows port uses the file under FreeRTOS/Demo/Common/Full/, but that should be avoided, as those are deprecated.
The basic structure of the Windows port demo application can be seen below:

FreeRTOS/
 +- Demo/
 |   +- Common/Minimal/
 |   +- WIN32-MingW/
 +- Source/
     +- *.c
     +- include/
     +- portable/
         +- MemMang/
         +- MSVC-MingW/


Creating a new application

To create a new application from an existing port, the quickest path is to use a Demo application and modify it to fit your needs. Compile and run the standard demo project and, when it runs as expected, you can add and/or remove whatever you may want.

Official Porting Guide

Here, I'll give you a little resume on the official porting guide provided by FreeRTOS [5]. Later on, we'll check the ATMega323 port files.

So the first thing you should do is to familiarize yourself with the source files organization (done that!) and create a folder under FreeRTOS/Source/portable/[compiler]/[processor]. Copy an empty port.c and portmacro.h there (you can use complete files from other ports, just remember clean all functions and macro bodies and only leave the stubs). Create a directory for the demo project for the new port under FreeRTOS/Demo/[architecture_compiler] and add a copy of FreeRTOSConfig.h and main.c (remember to only leave the stubs and modify some of the options from the config file that are hardware dependent, such as the tick rate, heap size, etc - check my other post about that). Now create a new folder under [architecture_compiler] called ParTest and copy some version of ParTest.c with just the stubs inside. This file will have a few LED tests that should run when your port is working fine: setup a few GPIOs to be used as LED outputs, turn on, off or toggle specific LEDs (remember what I said in some earlier post? blinking LEDs is the hello world of the embedded applications).

Now that everything is in place, you can create a project (makefile) that will successfully compile (not run, as there are a lot of stubs to implement) everything:
  • The basic kernel: Source/tasks.c, queue.c and list.c
  • The portable bits: Source/portable/[compiler]/[processor]/port.c
  • Memory management: Source/MemMang/heap_?.c (choose one of them)
  • Application specifics: Demo/[architecture_compiler]/main.c and ParTest/ParTest.c
And the hard part: implementing the stubs. The official guide suggests to start from the pxPortInitializeStack(), as it's very architecture dependent.

ATMega323 port.c

The windows port is not really a conventional port, since the FreeRTOS runs over another operating system that is not real time, and there surely might be a few things to learn by examining it, but as not to waste any time, I'll examine the Atmega323 port for the WinAVR (AVR-GCC) compiler, since it'll be much closer to our goal of porting to an Atmega2560. The port files can be found under FreeRTOS\Source\portable\GCC\ATMega323/ and the demo project under FreeRTOS\Demo\AVR_ATMega323_WinAVR/. You can make an Eclipse project with the files (it's not ready, as was the Windows port project), but as this is for studies purposes, there is no need. I should just warn you of one thing:


Starting with the pxPortInitialiseStack() in port.c, as the official guide says. This function is responsible for initializing the stack of a task as if it has already been there, so that the context change runs as smoothly as possible. This means some data must be stored in a certain order so that it can be retrieved in the right order later.

StackType_t *pxPortInitialiseStack( 
                    StackType_t *pxTopOfStack, 
                    TaskFunction_t pxCode, 
                    void *pvParameters )

First the types:

  • StackType_t, is defined in portmacro.h as a portSTACK_TYPE, which is defined as a uint8_t. This represents the type of the data that will be stored in the stack (in this case, 1 byte-wide).
  • TaskFunction_t, is the return type that has to be used by the tasks. It's defined, in projdefs.h (Source/include/projdefs.h) as a pointer to a void-type function.
The parameters are: a pointer to the current top of the stack, a pointer to the start of the tasks code and a pointer to a set of parameters. This set of parameters is the same you define when creating the task with xTaskCreate(). 

Now to the code, you'll see the first thing it does is to insert a few values to the start of the stack (0x11, 0x22 and 0x33). I haven't actually found much about it, but since this demo was written a long time ago, I presume it was used before any stack overflow detection mechanism was implemented (a few other Demo projects I checked don't have this). Ok, so after each value is added to the stack, the pxTopOfStack pointer is updated to the next position.

Next, the address to the start of the code (pxCode) is added to the stack (as the address is 16-bit wide, it has to be added in two steps, LSB first). Next, the 32 CPU registers (ATMega323 has 32 general purpose registers, where the last 6 are actually 3 16-bit registers called X, Y and Z [6] page 11) are stored with a few singularities: the global interrupt flag is inserted just after register 0 (see ATMega323's SREG) and the address to the parameters is placed just before the X register (also in two steps, as the pxCode address). In the end, the new pxTopOfStack is returned.

Now let's check the context saving and restoring. Context saving means to save all internal registers of the microcontroller in order to, when a task goes back to running mode, it seems like nothing is changed. For instance, if the task stops in the middle of some calculations, a few values will be stored in the registers and those should be saved so that the calculations can go on. Context restoring means to get all the stored values and put them back to the correspondent register. Those functions (portSAVE_CONTEXT() and portRESTORE_CONTEXT()) are written in assembly and are responsible for saving the registers I described earlier. These two and the pxPortInitialiseStack() must be absolutely synchronized, otherwise the value of one register can end up in another and the tasks will most likely fail.

portSAVE_CONTEXT() uses the following assembly instructions (you can check ATMega323 datasheet page 233 [6] for that):
  • push r?: pushes the value in a register into the stack (the stack pointer is already in the right position)
  • in r?,<REG>: loads the value of an I/O Space Register (<REG>) to a Rx register
    • ps.: addresses 0x3D and 0x3E correspond to the Stack Pointer register addresses
  • cli: disables the global interrupts
  • clr r?: clears the register
  • lds r?,<variable>: load direct from RAM (the value in the <variable> is stored in r?)
  • st x+,r?: store indirect and post increment (the value in the address pointed by the X register is updated with the value in the r? register, then the value of X is incremented)
portRESTORE_CONTEXT():
  • ld r?,x+: that's the opposite from "st x+,r?", so the value addressed by the X register is stored in r?, then the value of X is incremented
  • out <REG>,r?: the opposite of "in r?,<REG>" (this time, the Stack Pointer registers are referenced by its names __SP_L__ and __SP_H__)
  • pop r?: pops a value from the stack to the register.
Ok, but when are these functions used? One time is when the task yields manually (i.e. calls vPortYield(), that, if you follow the defines, is the implementation of taskYIELD()). When the task is manually yielded, it has its context saved (a call to portSAVE_CONTEXT()), then the context is changed to the next task to run (you can check the vTaskSwitchContext() in tasks.c) and then the context of the new task is restored (a call to portSAVE_CONTEXT()). Another place those functions are used is when a Tick occurs and another task must take place (vPortYieldFromTick(): the process is the same I described, with the difference that the Tick count is incremented).

Another port function is configuring the timer to generate the tick (prvSetupTimerInterrupt()). This is very architecture specific, since the internal registers should be configured such as an event occurs at the Tick Rate. For ATMega323, the Timer 1 is used in Output Compare mode, the counter is reset after an interrupt and the prescaler is set to 64. In the end, the interrupt is enabled (it will actually only be enabled when the global interrupt flag is set). The interrupt routine is also defined in port.c and has two options: when the scheduler is preemptive (the scheduler stops the current task to other take place), vPortYieldFromTick() is called; when the scheduler is set to cooperative (each task has to yield itself), only the Tick Count is incremented.

Last, but not least, the xPortStartScheduler() function, as the name says, will start the scheduler. First, it calls the prvSetupTimerInterrupt() described above, then, restores the context (portRESTORE_CONTEXT()), which will configure the microcontroller to run the task pointed by pxCurrentTCB. In the end, there is a asm call to "RET", which is a subroutine return and all it does is put the next data in the Stack in the Program Counter (PC), which, if you remember from the pxPortInitialiseStack(), was the pxCode parameter, which is the address where the task code starts (it is added before any register is added to the stack, as it would happen when the Tick Interrupt happens - the point where the code stopped is saved to the stack, then the context is saved, starting from register r0).

As a final observation in this file, I just talk a little about the attributes signal and naked, that we see in some function definitions (at least I didn't know they even exist), such as interruptions. These are directives to the compiler change the way it builds its output. The signal attribute ensures that the compiler inserts code that will save and restore every register that has been used in the interruption code and that the return will be done by a "RETI" instruction instead of the original "RET", as the first will re-enable the interruptions on exit [7]. The naked attribute ensures that the compiler won't add any code to the start and end of the interrupt function, which means nothing will be saved nor restored and this will all be user's responsibility. This is specially useful when the Tick interrupt with preemptive scheduler occurs, since the context switch is done there. Without the attribute, a few registers would be saved to the stack, then the current context saved (causing the registers to be saved twice in the stack), the task context is changed, the new task's context is restored and finally the code produced by the compiler would restore the registers saved, losing the tasks context. Also, when naked is used, the return method ("RETI") must be explicitly declared.

ATMega323 portmacro.h

The portmacro.h contains a few definitions of variable types that are highly architecture dependent such as the portLONG, portSHORT, portSTACK_TYPE, portBASE_TYPE, etc. A few macros are also defined here, such as those for critical code management (you need to disable interruptions): portENTER_CRITICAL() first saves the Status Register, then disables the global interruption flag (even if it was already disabled) and pushes the saved Status Register to the stack; the portEXIT_CRITICAL() pops the saved Status Register from the stack and restores it (there is no need to re-enable the global interruption flag, since it is part of the Status Register). The portSTACK_GROWTH is defined according to the datasheet [6], page 22, where it says
"The Stack Pointer is decremented by one when data is pushed onto the Stack with the PUSH instruction (...)"
thus the portSTACK_GROWTH is defined as -1.



[1] http://www.freertos.org/a00104.html
[2] http://www.freertos.org/FreeRTOS-Plus/
[3] http://winavr.sourceforge.net/
[4] http://www.freertos.org/a00090.html
[5] http://www.freertos.org/FreeRTOS-porting-guide.html
[6] http://www.atmel.com/Images/doc1457.pdf
[7] http://www.freertos.org/implementation/a00012.html