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.
- Choose the Empty Project under the folder "AVR Cross Target Application"
- Pick a name (FreeRTOS_ArduinoMEGA - rev0.1) and point the location to your project folder
- Click Next
- Click Next again (Debug and Release configs)
- Choose the MCU (ATmega2560) and Frequency (16000000 - that's 16 followed by 6 zeroes, 16MHz)
- Click Finish. This is what your Project Explorer should be showing:
- 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.
- Still on the Project Properties, go to C/C++ Build > Settings. On the tab Tool Settings:
- Additional Tools in Toolchain, check "Generate HEX file for Flash memory"
- AVR Compiler > Optimization, Optimization Levels: Size Optimization (-Os), uncheck Pack Structs and Short Enums
- AVR Compiler > Language Standard, uncheck "char is unsigned" and "bitfields are unsigned"
- Do the same to AVR C++ Compiler
- Adjusting some paths under C/C++ General > Paths and Symbols > Includes, add the following workspace directories:
- Project's root
- FreeRTOS/Demo/Common/include
- FreeRTOS/Source/include
- FreeRTOS/Source/portable/WinAVR/ATmega2560
By now, the project should be ready, but the code is all wrong, so let's work on that.
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.
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 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).
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.
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.
No comments:
Post a Comment