65.9K
CodeProject 正在变化。 阅读更多。
Home

如何将Free RTOS移植到Atmega128

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.31/5 (10投票s)

2011年6月12日

CPOL

4分钟阅读

viewsIcon

53636

downloadIcon

1588

如何将Free RTOS移植到Atmega128。

引言

我已经成功将 Free RTOS 移植到 atmega128,虽然我没有编写所有的移植函数,但我会解释它们是如何工作的。

Using the Code

Free RTOS 有一个针对 GCC ATMega323 的移植版本,而不是 Atmega128,但令人惊讶的是,为 atmega323 编写的所有移植函数都适用于 Atmega128,正如那些成功移植的人所说的那样。

我正在使用适用于 Windows 的 AVR Studio 4,并使用 Microsoft Studio 2008 作为我的源代码编辑器,正如您可以在我上传的项目和源代码中看到的那样。

首先,您需要从 Atmel 网站下载 AVR Studio 4,然后从他们的网站下载 WinAVR,因为该项目将使用 avr-gcc 编译器来编译所有源代码。

您可以下载我的移植版本,然后从 AVR Studio 4 中打开 ".aps" 文件,然后直接运行它,它应该会编译。 编译完成后,您将看到一个 ".hex" 文件被创建,您可以使用此文件来编程您的 PCB 板,我正在使用 Ponyprog2000。

我的 LED 在 PORTE 引脚 2 上,我可以看到它在我的 PCB 板上闪烁,在您的情况下可能不同。

现在我将解释 RTOS 如何工作,为什么我们需要 "port" 动作。

RTOS 只为我们提供内核源代码,如任务调度器、信号量、互斥锁等,以及它们的实现。 我们希望在我们的嵌入式系统中拥有多个任务,RTOS 将能够为我们做到这一点。 但在不同的处理器中,情况可能有所不同。

要调度一个任务,我们需要保存 CPU 中的所有寄存器,不同的架构、不同的寄存器和不同的汇编指令,例如 port.c 文件中的以下几行

#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" \        

它保存 SREG 中断使能标志,清除中断标志,以及通用寄存器(在 atmega128 中有 32 个)。

然后它初始化任务的堆栈空间,在大多数 CPU 中,堆栈从上到下增长(0xffff 到 0)。 但有些是不同的。

然后我们需要设置我们的 OS tick 功能,它在 "prvSetupTimerInterrupt" 函数中

 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;        

当 OS ticks 时,我们可以重新调度我们的任务,这可以通过调用 "vPortYieldFromTick" 来完成,

正如您可以在 port.c 中 OS tick 中断的以下行中看到的那样

 #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    

大多数时候,我们不需要知道 RTOS 背后是什么,对于我们来说,我们只需要创建几个任务,或者根据 RTOS 文档设置中断处理程序。

在我的例子中,我创建了 3 个任务,一个是每隔 400 毫秒闪烁一次 LED,另一个是每隔 50 毫秒重置外部看门狗引脚,第三个任务是刷新 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;
}

正如您所看到的,重置看门狗的优先级高于闪烁 LED,我希望尽可能快地重置外部看门狗,因此它的优先级高于其他任务。

RTOS 将从内部 RAM 或外部 RAM 中为我们分配一些内存,用作各个任务的堆栈空间。

像闪烁 LED 这样的简单任务,它们的堆栈大小只需要小于 50 字节,这些字节用于保存 32 个寄存器、函数指针地址等。 当 RTOS 需要调度任务时,它会将当前正在运行的任务保存到堆栈中,这包括保存 32 个寄存器、保存当前任务创建的局部变量等。

我的闪烁 LED 任务是使用以下函数创建的

xTaskCreate(flash_led, (signed portCHAR *)"flash_led",
	configMINIMAL_STACK_SIZE, NULL, Priority, NULL ); 

FreeRTOSConfig.h 文件中,您将看到以下行

#define configMINIMAL_STACK_SIZE ( ( unsigned portSHORT ) 85 )

所以我们的闪烁 LED 任务使用了 85 字节。

如果像绘制 LCD 屏幕这样的任务花费的时间太长,它会被其他任务多次中断,那么它的堆栈需要足够大才能多次保存寄存器和局部变量。

在我的例子中,我创建了一个像下面的 lcd 任务

 xTaskCreate(lcd_task, (signed portCHAR *)"lcd_task",
	configMINIMAL_STACK_SIZE*2, NULL, Priority, NULL ); 

在我的例子中,我需要告诉 RTOS 我的堆大小,Atmega128 有 4k 字节的内部内存,在我的 FreeRTOSConfig.h 文件中,我有以下行: 

#define configTOTAL_HEAP_SIZE  ( (size_t ) ( 2800 ) )

为 AVR libc 函数调用保留一些空间,因为它们可能会使用一些堆内存。

我们的每个任务都是一个无限循环,闪烁 LED 任务就像下面这样。 我们必须以这种方式创建我们的任务

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);
 }
}   

上面的代码使用了信号量,我们希望独占访问 PORTE ,而没有像 "reset watchdog" 这样的其他任务的干扰。

然后它等待 400 毫秒再次闪烁 LED,在此期间,RTOS 会将我们的闪烁 LED 任务让给其他一些任务。

下一步是添加从 RS485 连接接收的数据,在我们收到数据后,我们需要将它们放入队列中,然后我们的接收数据任务可以处理它。

首先,我们初始化我们的 RS485 端口

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(); 

接下来,我们启用中断例程

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();
          }
     }
} 

然后,在接收任务例程中,我们处理我们收到的内容

 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 通过消息队列支持进程间通信。

在 atmega128 上运行 RTOS 是更复杂应用程序的一个好的开始。 Atmega128 只有 4k 内部 RAM,如果您安装了外部 RAM,那么您的系统中可以运行更多的任务。 它比旧式的单个 while 循环做所有事情更容易或更方便。

我们使用 Atmega128 作为处理器来完成所有的 LCD 显示、电路检查、简单的数字通信等。

在这个阶段,本文就结束了。

© . All rights reserved.