如何将Free RTOS移植到Atmega128






4.31/5 (10投票s)
如何将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 显示、电路检查、简单的数字通信等。
在这个阶段,本文就结束了。