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.

推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"