Introduction
I have successfully ported Free RTOS to atmega128, although I did not write all the port functions, but I will explain how they work.
Using the Code
Free RTOS has a port to GCC ATMega323, not Atmega128, but surprisingly, all the port functions written for atmega323 work for Atmega128, as it is told by those who have successfully ported.
I am using AVR Studio 4 for Windows, and use Microsoft Studio 2008 as my source code editor, as you can see in my uploaded projects and source code.
First, you need to download AVR Studio 4 from Atmel web site, then download WinAVR from their web site as well as this project will use avr-gcc compiler to compile all the source code.
You can download my ports, then open the ".aps" file from AVR Studio 4, then run it straightaway, it should compile. After it is compiled, you will see a ".hex" created, you can use this file program your PCB board, I am using Ponyprog2000.
My led is at PORTE pin 2, I can see it flashes my PCB board, in your case might be different.
Now I will explain how RTOS works, why we need a "port" actions.
RTOS only provides us the kernel source code, like the task scheduler, semaphore, mutex, etc. And their implementations. We want to have multi tasks in our embedded system, RTOS will be able to do this for us. But in different processors, things can be different.
To schedule a task, we need to save all the registers in CPU, different architectures, different registers, and different assembly instructions, like the following lines in port.c file:
#define portSAVE_CONTEXT() \
asm volatile ( "push r0 \n\t" \
"in r0, __SREG__ \n\t" \
"cli \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" \
It saves SREG enable interrupt flags, clears interrupt flags, and general registers (32 in atmega128).
Then it initializes stack space for task, in most CPUs stack grows from top to bottom (0xffff to 0). But some are different.
Then we need to setup our OS tick functionality, it is in "prvSetupTimerInterrupt
" function:
OCR1AH = ucHighByte;
OCR1AL = ucLowByte;
ucLowByte = portCLEAR_COUNTER_ON_MATCH | portPRESCALE_64;
TCCR1B = ucLowByte;
ucLowByte = TIMSK;
ucLowByte |= portCOMPARE_MATCH_A_INTERRUPT_ENABLE;
TIMSK = ucLowByte;
When OS ticks, we can reschedule our tasks, this can be done by calling "vPortYieldFromTick
",
As you can see this from the following line in OS tick interrupt in port.c:
#if configUSE_PREEMPTION == 1
void SIG_OUTPUT_COMPARE1A( void ) __attribute__ ( ( signal, naked ) );
void SIG_OUTPUT_COMPARE1A( void )
{
vPortYieldFromTick();
asm volatile ( "reti" );
}
#else
void SIG_OUTPUT_COMPARE1A( void ) __attribute__ ( ( signal ) );
void SIG_OUTPUT_COMPARE1A( void )
{
vTaskIncrementTick();
}
#endif
Most of the times, we don't need to know what's behind the RTOS, for us we only need to create a few tasks, or setup interrupt handlers according to RTOS documents.
In my case, I create 3 tasks, one is to flash led at 400ms intervals, another is to reset external watch dog pin by 50ms intervals, 3rd task is refresh LCD.
portSHORT main(void) {
Startflash_led(tskIDLE_PRIORITY + 1);
StartReset_watchdog(tskIDLE_PRIORITY + 2);
StartLCD_task(tskIDLE_PRIORITY + 1);
vTaskStartScheduler();
return 0;
}
As you can see, the reset watch dog has higher priority than flash led, I want to reset external watch dog as quickly as possible, therefore it has higher priority than others.
RTOS will allocate some memory from internal RAM or external RAM for us, to use as stack space for individual tasks.
Simple task like flash led, their stack size only needs less than 50 bytes, these bytes are used to save 32 registers, function pointer address, etc. When RTOS will need to schedule tasks, it will save current running task to stack, this includes saving 32 registers, saving local variables that are created by current task, etc.
My flash led task is created by using the following function:
xTaskCreate(flash_led, (signed portCHAR *)"flash_led",
configMINIMAL_STACK_SIZE, NULL, Priority, NULL );
At FreeRTOSConfig.h file, you will see the following line:
#define configMINIMAL_STACK_SIZE ( ( unsigned portSHORT ) 85 )
So our flash led task stack used 85
bytes.
If a task like drawing LCD screen is taking too long time, it will be interrupted many times by other tasks, then its stack needs to be big enough to save registers and local variables many times.
In my case, I create an lcd
task like the following:
xTaskCreate(lcd_task, (signed portCHAR *)"lcd_task",
configMINIMAL_STACK_SIZE*2, NULL, Priority, NULL );
In my case, I need to tell RTOS my heap size, Atmega128 has 4k bytes internal memory, in my FreeRTOSConfig.h file, I have the following line:
#define configTOTAL_HEAP_SIZE ( (size_t ) ( 2800 ) )
Leave some space to AVR libc
function calls, as they might use some heap memories.
Each one of our tasks is an infinite loop, flash led task is like the following. We have to create our task this way:
void flash_led() {
portTickType last_start_time;
const portTickType ticks = 400; DDRE |= 4;
for(;;)
{
while( xSemaphoreTake( xSemaphore, ( portTickType ) 254 ) == pdFALSE ); if (PORTE & _BV(PE2)) PORTE&=(~_BV(PE2));
else PORTE |= _BV(PE2);
xSemaphoreGive( xSemaphore ); last_start_time = xTaskGetTickCount(); vTaskDelayUntil(&last_start_time, ticks);
}
}
The above code used semaphore, we want to access the PORTE
exclusively, without other tasks like "reset watchdog" interference.
Then it waits for 400ms flash led again, during this period, RTOS will yield our flash led task to some other tasks.
The next step is adding receive data from RS485 connection, after we have received data we need to put them into a queue, then our receiving data task can handle it.
First, we initialize our RS485 port:
portENTER_CRITICAL();
{
xRxedChars = xQueueCreate( uxQueueLength, ( unsigned portBASE_TYPE )
sizeof( signed char ) );
xCharsForTx = xQueueCreate( uxQueueLength/2,
( unsigned portBASE_TYPE ) sizeof( signed char ) );
MASTER_UBRRH=0;
MASTER_UBRRL = 25;
MASTER_UCSRB = (COMM_RX_INT_ENABLE|COMM_RX_ENABLE|COMM_9_BITS_SIZE|COMM_TX_ENABLE);
MASTER_UCSRC = (0x6);
cbi(PORTD,4); }
portEXIT_CRITICAL();
Next, we enable interrupt routine:
SIGNAL(SIG_UART1_RECV )
{
signed char cChar;
signed portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
cChar=MASTER_UCSRB;
if (cChar & COMM_ADDR_BIT) {
cChar = MASTER_UDR;
if (cChar & BROADCAST_ADDRESS)
{
data_for_me=true;
need_to_reply=(cChar == stComState.addr)?true:false;
cChar=ESCAPE; xQueueSendFromISR( xRxedChars, &cChar, &xHigherPriorityTaskWoken );
}
else
{
stComState.got_data =false;
data_for_me=false;
}
}
else if (data_for_me)
{
cChar = MASTER_UDR;
if (need_to_reply) {
}
if (cChar==ESCAPE)
xQueueSendFromISR( xRxedChars, &cChar, &xHigherPriorityTaskWoken );
xQueueSendFromISR( xRxedChars, &cChar, &xHigherPriorityTaskWoken );
if( xHigherPriorityTaskWoken != pdFALSE )
{
taskYIELD();
}
}
}
Then, at receiving task routine, we handle what we have received:
if (!xSerialGetChar(NULL,&ch,portMAX_DELAY)) return;
if (ch==ESCAPE)
{
if (is_cmd)
is_cmd=false;
else
{
is_cmd=true; return;
}
}
if (is_cmd)
{
current_cmd=ch;
count=0;
memset(buffer,0,BUFFER_SIZE);
is_cmd=false; }
if (is_display_line(current_cmd))
{
stComState.got_data =true;
buffer[count++]=ch;
if (can_display(count)) display_line(buffer);
}
Free RTOS supports interprocess communication by message queue.
RTOS on atmega128 is a good start of more complicated applications. Atmega128 only got 4k internal RAM, if you have external RAM installed, then you can have more tasks running in your system. It's more easy or convenient than the old style of single while
loop doing everything.
We used Atmega128 as a processor to do all the LCD displays, electric circuit checking, simple digital communications, etc.
At this stage, this article is finished.