How to port Free RTOS to Atmega128
Introduction
I have successfully ported Free RTOS to atmega128, although I did not write all the port functions, but I will explain how they works.
Background
(Optional) Is there any background to this article that may be useful such as an introduction to the basic ideas presented?
Using the code
Free RTOS has a port to GCC ATMega323, not Atmega128, but surprisingly, all the port functions written for atmega323 works for Atmega128, as it is told by those who has 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 straighway, 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 provide us the kernel source code, like the task sheduler, semaphore,mutex, etc. And their implementations. We want to have multi tasks in our embeded system, RTOS will be able to do this for us. But in different processors, things can be different.
To shedule a task, we need to save all the registers in CPU, different architecture, different registers, and different assembly instructions, like 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, clear interrupt flags, and general registers (32 in atmega128).
Then it's initialize stack space for task, in most CPU stack grow 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; /* Setup clock source and compare match behaviour. */ ucLowByte = portCLEAR_COUNTER_ON_MATCH | portPRESCALE_64; TCCR1B = ucLowByte; /* Enable the interrupt - this is okay as interrupt are currently globally disabled. */ ucLowByte = TIMSK; ucLowByte |= portCOMPARE_MATCH_A_INTERRUPT_ENABLE; TIMSK = ucLowByte;
When OS ticks, we can reshedule our tasks, this can be done by calling "vPortYieldFromTick",
as you can see this from following line in OS tick interrupt in port.c
#if configUSE_PREEMPTION == 1 /* * Tick ISR for preemptive scheduler. We can use a naked attribute as * the context is saved at the start of vPortYieldFromTick(). The tick * count is incremented after the context is saved. */ void SIG_OUTPUT_COMPARE1A( void ) __attribute__ ( ( signal, naked ) ); void SIG_OUTPUT_COMPARE1A( void ) { vPortYieldFromTick(); asm volatile ( "reti" ); } #else /* * Tick ISR for the cooperative scheduler. All this does is increment the * tick count. We don't need to switch context, this can only be done by * manual calls to taskYIELD(); */ void SIG_OUTPUT_COMPARE1A( void ) __attribute__ ( ( signal ) ); void SIG_OUTPUT_COMPARE1A( void ) { vTaskIncrementTick(); } #endif
At most of the time, we don't need to konw 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) { //Start Tasks Startflash_led(tskIDLE_PRIORITY + 1); StartReset_watchdog(tskIDLE_PRIORITY + 2); StartLCD_task(tskIDLE_PRIORITY + 1); //RunSchedular vTaskStartScheduler(); return 0; }
As you can see, the reset watch dog has higher priorty than flash led, I want to reset external watch dog as quick 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 need less than 50 bytes, these bytes are used to save 32 registers, function pointer address, etc. When RTOS will need to shedule tasks, it will save current running task to stack, this includes saving 32 registers, saving local variables that created by current task, etc.
My flash led task is created by using following function:
xTaskCreate(flash_led, (signed portCHAR *)"flash_led", configMINIMAL_STACK_SIZE, NULL, Priority, NULL );
At FreeRTOSConfig.h file, you will see 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, taking too long time, it will be interrupted many times by other tasks, then its stack need to be bigger enough to save registers and local variables many times.
In my case I create lcd task like following:
xTaskCreate(lcd_task, (signed portCHAR *)"lcd_task", configMINIMAL_STACK_SIZE*10, NULL, Priority, NULL );
850 bytes allocated to lcd task, that's a lot memory, this indicate I need to restructure my LCD task.
In my case, I need to tell RTOS my heap size, Atmega128 has 4k bytes internal memory, in my FreeRTOSConfig.h file I have 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 task is an infinit loop, flash led task is like following. We have to create our task this way.
void flash_led() { portTickType last_start_time; const portTickType ticks = 400; // 1 KHz tick -> 400ms delay DDRE |= 4; for(;;) { while( xSemaphoreTake( xSemaphore, ( portTickType ) 254 ) == pdFALSE ); //wait for it if (PORTE & _BV(PE2)) PORTE&=(~_BV(PE2)); else PORTE |= _BV(PE2); xSemaphoreGive( xSemaphore ); //Done with the , release the semaphore last_start_time = xTaskGetTickCount(); // Last time where task was blocked 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 wait for 400ms flash led again, during this period, RTOS will yield our flash led task to some other tasks.
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(); { /* Create the queues used by the com test task. */ xRxedChars = xQueueCreate( uxQueueLength, ( unsigned portBASE_TYPE ) sizeof( signed char ) ); xCharsForTx = xQueueCreate( uxQueueLength/2, ( unsigned portBASE_TYPE ) sizeof( signed char ) ); //9600bps 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);//turn on receive line, half duplex } 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) //this byte is address { cChar = MASTER_UDR; if (cChar & BROADCAST_ADDRESS) { data_for_me=true; need_to_reply=(cChar == stComState.addr)?true:false; cChar=ESCAPE;//indicate an address coming xQueueSendFromISR( xRxedChars, &cChar, &xHigherPriorityTaskWoken ); } else { stComState.got_data =false; data_for_me=false; } } else if (data_for_me) { /* Get the character and post it on the queue of Rxed characters. If the post causes a task to wake force a context switch as the woken task may have a higher priority than the task we have interrupted. */ cChar = MASTER_UDR; if (need_to_reply) //master board may need quick reply from us { } if (cChar==ESCAPE) xQueueSendFromISR( xRxedChars, &cChar, &xHigherPriorityTaskWoken ); xQueueSendFromISR( xRxedChars, &cChar, &xHigherPriorityTaskWoken ); if( xHigherPriorityTaskWoken != pdFALSE ) { taskYIELD(); } } }
Then at receiving task routine we handle we have received:
if (!xSerialGetChar(NULL,&ch,portMAX_DELAY)) return; if (ch==ESCAPE) { if (is_cmd) is_cmd=false; else { is_cmd=true;//next char will be command return; } } if (is_cmd) { current_cmd=ch; count=0; memset(buffer,0,BUFFER_SIZE); is_cmd=false;//next char will be data } 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.
At this stage this article is finished.
Post Comment
FF9DPr Thank you ever so for you article.Really thank you! Cool.
HqlyQn Shiva habitait dans etait si enthousiaste,
nzz4X9 With havin so much written content do you ever run into any issues of plagorism or copyright violation?
AdXfgg Thank you for your blog.Much thanks again. Want more.
S9w6pO Really informative blog post.Much thanks again. Keep writing.
Zfx9Jg I loved your blog article.Much thanks again. Great.
Em5rWg Im obliged for the article.Really looking forward to read more. Great.