freeRTOS 是一个实时的内核,完全免费,即使你用做商用,并且可以配置成抢占式或者支持时间片的抢占式,不像ucosii,开源但是收费,而且只支持抢占式。
目标硬件平台平台:基于arm926ejs的SOC
IDE:CodeWarrior 5.7.0
debug:realdebug + jlink
freeRTOS 版本:V7.0.1
说明:此文档只关注OS的移植,不涉及目标板的boot的初始化配置(PLL MMU CACHE MEMORY...),以及demo\common 下驱动的实现。
1.)下载freeRTOS 源码
https://freertos.svn.sourceforge.net/svnroot/freertos/tags/V7.0.1 下载最新版
https://freertos.svn.sourceforge.net/svnroot/freertos 下载所有的版本,很大,不推荐。
2.)freeRTOS 目录结构
freeRTOS-
-Demo
-许多porting实例
-common
-License
-Source
-include 所有头文件
-portable 包含硬件相关的代码
-TraceCon
Demo 下面每个子目录对应每个porting的实例,子目录里包含了开发环境的工程目录,task 的实现,概括来说此目录存放APPLICATION 相关的文件。
子目录命名遵循一定的规则:CPUCORENAME_SOCNAME_COMPILERNAME.
common 目录下包含硬件驱动,文件系统,网络驱动等在具体平台上的实现。此文章不涉及。
Source 此目录下包含真正的OS kernel的source code.
include 下的头文件和 croutine.c list.c queue.c timers.c tasks.c 为硬件无关的代码,所有硬件平台都共享这些文件。
portable 下包含众多以compiler 命名的文件夹 比如codewarrior rvds等,以及MemMang目录,此目录下包含heap_1.c heap_2.c heap_3.c
实现了MEMORY 管理,具体porting的时候你只需要选择一个文件到IDE,否则会报重复定义的错误。
在以compiler命名的目录下又有一些以SOC名字命名的文件夹。 每个[compiler]目录下的[soc]目录对应着一个具体的porting实例。这些目录下都包含着
类似的文件,port.c portasm.s portmacro.h 有多有少,但是都是实现了硬件平台相关的代码。
3.) 开始移植。
在Demo目录下创建ARM9_XXXX_CodeWarrior目录,添加了boot 代码,scatter 文件 以及FreeRTOSConfig.h和main.c
其中FreeRTOSConfig.h 来自demo中的arm7的实例,文件里重要配置了一些宏,代码如下:
#define configUSE_PREEMPTION 1 ///1配置成抢占式的,0配置成支持时间片
#define configUSE_IDLE_HOOK 1 ///使用idle hook 函数,需要application 实现
#define configUSE_TICK_HOOK 1 ///使用tick hook 函数,需要application 实现
#define configCPU_CLOCK_HZ ( ( unsigned long ) 240000000 ) ///cpu clock 240M
#define configTICK_RATE_HZ ( ( portTickType ) 1 ) ///每秒tick数
#define configMAX_PRIORITIES ( ( unsigned portBASE_TYPE ) 4 )///优先级级数
#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 90 ) ///栈大小
#define configTOTAL_HEAP_SIZE ( ( size_t ) 13 * 1024 ) ///堆大小
#define configMAX_TASK_NAME_LEN ( 8 ) ///task name的长度
#define configUSE_TRACE_FACILITY 0
#define configUSE_16_BIT_TICKS 0 ///对于32的cpu 都配置成0
#define configIDLE_SHOULD_YIELD 1 ///待研究:(
#define configQUEUE_REGISTRY_SIZE 0 ///待研究:(
/* Co-routine definitions. */ ///待研究:(
#define configUSE_CO_ROUTINES 0
#define configMAX_CO_ROUTINE_PRIORITIES ( 2 ) ///待研究:(
/* Set the following definitions to 1 to include the API function, or zero
to exclude the API function. */
#define INCLUDE_vTaskPrioritySet 1
#define INCLUDE_uxTaskPriorityGet 1
#define INCLUDE_vTaskDelete 1
#define INCLUDE_vTaskCleanUpResources 0
#define INCLUDE_vTaskSuspend 1
#define INCLUDE_vTaskDelayUntil 1
#define INCLUDE_vTaskDelay 1
main.c 则实现了几个任务函数,hook函数,以及入口函数,每个任务都有下面的格式
static portTASK_FUNCTION( vTask1, pvParameters )
{
/* Calculate the LED and flash rate. */
for(;;)
{
portTickType xTickCount = xTaskGetTickCount();
printf("task1:%d\n",xTickCount);
/* Delay for half the flash period then turn the LED off. */
vTaskDelay( 4 );
#if configUSE_PREEMPTION == 0
taskYIELD();
#endif
}
}
在 source\portalbe 目录下创建CodeWarrior目录(如果已经有了,则不需要创建),在codeWarrior目录下创建xxxx(SOC名字) 在此目录下
创建了port.c portasm.s portmacro.h
portmacro.h:
#define portCHAR char
#define portFLOAT float
#define portDOUBLE double
#define portLONG long
#define portSHORT short
#define portSTACK_TYPE unsigned portLONG
#define portBASE_TYPE portLONG
#if( configUSE_16_BIT_TICKS == 1 )
typedef unsigned portSHORT portTickType;
#define portMAX_DELAY ( portTickType ) 0xffff
#else
typedef unsigned portLONG portTickType;
#define portMAX_DELAY ( portTickType ) 0xffffffff
#endif
/*-----------------------------------------------------------*/
/* Hardware specifics. */
#define portSTACK_GROWTH ( -1 )
#define portTICK_RATE_MS ( ( portTickType ) 1000 / configTICK_RATE_HZ )
#define portBYTE_ALIGNMENT 8
/*-----------------------------------------------------------*/
#define portEXIT_SWITCHING_ISR(SwitchRequired) \
{ \
extern void vTaskSwitchContext(void); \
\
if(SwitchRequired) \
{ \
vTaskSwitchContext(); \
} \
} \
extern void vPortYield( void );
#define portYIELD() vPortYield()
/* Critical section management. */
#define portDISABLE_INTERRUPTS() __disable_irq()
#define portENABLE_INTERRUPTS() __enable_irq()
extern void vPortEnterCritical( void );
extern void vPortExitCritical( void );
#define portENTER_CRITICAL() vPortEnterCritical();
#define portEXIT_CRITICAL() vPortExitCritical();
/*-----------------------------------------------------------*/
/* Compiler specifics. */
#define inline
#define register
#define portNOP() __asm{ NOP }
/*-----------------------------------------------------------*/
/* Task function macros as described on the FreeRTOS.org WEB site. */
#define portTASK_FUNCTION_PROTO( vFunction, pvParameters ) void vFunction( void *pvParameters )
#define portTASK_FUNCTION( vFunction, pvParameters ) void vFunction( void *pvParameters )
代码中大部分都是声明了一些函数,以及定义了数据类型
真正重要有三个地方
#define portYIELD() vPortYield() ///这个宏告诉os, task如何放弃CPU. vPortYield会在portasm.s 中实现。
#define portDISABLE_INTERRUPTS() __disable_irq() ////实现了关中断 __disable_irq() 和 __enable_irq() 支持的库函数
#define portENABLE_INTERRUPTS() __enable_irq() ////实现了开中断
#define portNOP() __asm{ NOP } ////实现了nop
以上都是跟硬件相关的。
port.c
主要实现了几个重要函数。
1.
portSTACK_TYPE *pxPortInitialiseStack( portSTACK_TYPE *pxTopOfStack, pdTASK_CODE pxCode, void *pvParameters )
{
portSTACK_TYPE *pxOriginalTOS;
/* Setup the initial stack of the task. The stack is set exactly as
expected by the portRESTORE_CONTEXT() macro.
Remember where the top of the (simulated) stack is before we place
anything on it. */
pxOriginalTOS = pxTopOfStack;
/* To ensure asserts in tasks.c don't fail, although in this case the assert
is not really required. */
pxTopOfStack--;
/* First on the stack is the return address - which in this case is the
start of the task. The offset is added to make the return address appear
as it would within an IRQ ISR. */
*pxTopOfStack = ( portSTACK_TYPE ) pxCode + portINSTRUCTION_SIZE;
pxTopOfStack--;
*pxTopOfStack = ( portSTACK_TYPE ) 0xaaaaaaaa; /* R14 */
pxTopOfStack--;
*pxTopOfStack = ( portSTACK_TYPE ) pxOriginalTOS; /* Stack used when task starts goes in R13. */
pxTopOfStack--;
*pxTopOfStack = ( portSTACK_TYPE ) 0x12121212; /* R12 */
pxTopOfStack--;
*pxTopOfStack = ( portSTACK_TYPE ) 0x11111111; /* R11 */
pxTopOfStack--;
*pxTopOfStack = ( portSTACK_TYPE ) 0x10101010; /* R10 */
pxTopOfStack--;
*pxTopOfStack = ( portSTACK_TYPE ) 0x09090909; /* R9 */
pxTopOfStack--;
*pxTopOfStack = ( portSTACK_TYPE ) 0x08080808; /* R8 */
pxTopOfStack--;
*pxTopOfStack = ( portSTACK_TYPE ) 0x07070707; /* R7 */
pxTopOfStack--;
*pxTopOfStack = ( portSTACK_TYPE ) 0x06060606; /* R6 */
pxTopOfStack--;
*pxTopOfStack = ( portSTACK_TYPE ) 0x05050505; /* R5 */
pxTopOfStack--;
*pxTopOfStack = ( portSTACK_TYPE ) 0x04040404; /* R4 */
pxTopOfStack--;
*pxTopOfStack = ( portSTACK_TYPE ) 0x03030303; /* R3 */
pxTopOfStack--;
*pxTopOfStack = ( portSTACK_TYPE ) 0x02020202; /* R2 */
pxTopOfStack--;
*pxTopOfStack = ( portSTACK_TYPE ) 0x01010101; /* R1 */
pxTopOfStack--;
*pxTopOfStack = ( portSTACK_TYPE ) pvParameters; /* R0 */
pxTopOfStack--;
/* The last thing onto the stack is the status register, which is set for
system mode, with interrupts enabled. */
*pxTopOfStack = ( portSTACK_TYPE ) portINITIAL_SPSR;
if( ( ( unsigned long ) pxCode & 0x01UL ) != 0x00UL )
{
/* We want the task to start in thumb mode. */
*pxTopOfStack |= portTHUMB_MODE_BIT;
}
pxTopOfStack--;
return pxTopOfStack;
}
初始化任务的栈,使他的栈就像是刚发生中断一样。
2.
static void prvSetupTimerInterrupt( void )
{
//add code here
//平台相关
}
初始化一个timer,提供系统tick。
3.
portBASE_TYPE xPortStartScheduler( void )
{
/* Start the timer that generates the tick ISR. */
prvSetupTimerInterrupt();
/* Start the first task. This is done from portISR.c as ARM mode must be
used. */
vPortStartFirstTask();
/* Should not get here! */
return 0;
}
启动scheduler,只调用一次。vPortStartFirstTask在portasm.s 中实现。
4.
void freeRtosTickIrqHandler( void )
{
// Increment the tick counter.
vTaskIncrementTick();
#if configUSE_PREEMPTION == 1
{
// The new tick value might unblock a task. Ensure the highest task that
// is ready to execute is the task that will execute when the tick ISR
// exits.
vTaskSwitchContext();
}
#endif
}
实现了timer的中断处理函数。application也可以自己再实现一个isr,增加功能,但是一定要调用这个函数。
5
void vPortEnterCritical( void )
{
/* Disable interrupts as per portDISABLE_INTERRUPTS(); */
portDISABLE_INTERRUPTS();
/* Now interrupts are disabled ulCriticalNesting can be accessed
directly. Increment ulCriticalNesting to keep a count of how many times
portENTER_CRITICAL() has been called. */
ulCriticalNesting++;
}
/*-----------------------------------------------------------*/
void vPortExitCritical( void )
{
if( ulCriticalNesting > portNO_CRITICAL_NESTING )
{
/* Decrement the nesting count as we are leaving a critical section. */
ulCriticalNesting--;
/* If the nesting level has reached zero then interrupts should be
re-enabled. */
if( ulCriticalNesting == portNO_CRITICAL_NESTING )
{
/* Enable interrupts as per portEXIT_CRITICAL(). */
portENABLE_INTERRUPTS();
}
}
}
////上面两个函数实现了临界代码函数
portasm.s
IMPORT vTaskSwitchContext
IMPORT vTaskIncrementTick
IMPORT top_level_int_handler
EXPORT vPortYieldProcessor
EXPORT vPortStartFirstTask
EXPORT vPreemptiveTick
EXPORT vPortYield
EXPORT irqHandler
IMPORT ulCriticalNesting ;
IMPORT pxCurrentTCB ;
MACRO
portRESTORE_CONTEXT
LDR R0, =pxCurrentTCB ; Set the LR to the task stack. The location was...
LDR R0, [R0] ; ... stored in pxCurrentTCB
LDR LR, [R0]
LDR R0, =ulCriticalNesting ; The critical nesting depth is the first item on...
LDMFD LR!, {R1} ; ...the stack. Load it into the ulCriticalNesting var.
STR R1, [R0] ;
LDMFD LR!, {R0} ; Get the SPSR from the stack.
MSR SPSR_cxsf, R0 ;
LDMFD LR, {R0-R14}^ ; Restore all system mode registers for the task.
NOP ;
LDR LR, [LR, #+60] ; Restore the return address
; And return - correcting the offset in the LR to obtain ...
SUBS PC, LR, #4 ; ...the correct address.
MEND
; /**********************************************************************/
MACRO
portSAVE_CONTEXT
STMDB SP!, {R0} ; Store R0 first as we need to use it.
STMDB SP,{SP}^ ; Set R0 to point to the task stack pointer.
NOP ;
SUB SP, SP, #4 ;
LDMIA SP!,{R0} ;
STMDB R0!, {LR} ; Push the return address onto the stack.
MOV LR, R0 ; Now we have saved LR we can use it instead of R0.
LDMIA SP!, {R0} ; Pop R0 so we can save it onto the system mode stack.
STMDB LR,{R0-LR}^ ; Push all the system mode registers onto the task stack.
NOP ;
SUB LR, LR, #60 ;
MRS R0, SPSR ; Push the SPSR onto the task stack.
STMDB LR!, {R0} ;
LDR R0, =ulCriticalNesting ;
LDR R0, [R0] ;
STMDB LR!, {R0} ;
LDR R0, =pxCurrentTCB ; Store the new top of stack for the task.
LDR R1, [R0] ;
STR LR, [R1] ;
MEND
ARM
AREA PORT_ASM, CODE, READONLY
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Starting the first task is done by just restoring the context
; setup by pxPortInitialiseStack
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
vPortStartFirstTask
PRESERVE8
portRESTORE_CONTEXT
vPortYield
PRESERVE8
SVC 0
bx lr
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Interrupt service routine for the SWI interrupt. The vector table is
; configured in the startup.s file.
;
; vPortYieldProcessor() is used to manually force a context switch. The
; SWI interrupt is generated by a call to taskYIELD() or portYIELD().
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
vPortYieldProcessor
PRESERVE8
; Within an IRQ ISR the link register has an offset from the true return
; address, but an SWI ISR does not. Add the offset manually so the same
; ISR return code can be used in both cases.
ADD LR, LR, #4
; Perform the context switch.
portSAVE_CONTEXT ; Save current task context
LDR R0, =vTaskSwitchContext ; Get the address of the context switch function
MOV LR, PC ; Store the return address
BX R0 ; Call the contedxt switch function
portRESTORE_CONTEXT ; restore the context of the selected task
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; IRQ handler
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
irqHandler
portSAVE_CONTEXT ; Save the context of the current task...
BL top_level_int_handler
portRESTORE_CONTEXT ; Restore the context of the selected task.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
END
vPortStartFirstTask 实现了开始第一个就绪的任务。
vPortYield 实现了产生软件中断
///以下两个函数在平台的启动代码中的vector.s中,分别为swi和irq 异常的入口函数。
vPortYieldProcessor 实现的软中断的中断处理函数
irqHandler 实现了irq异常的处理函数
top_level_int_handler 读者可以自己实现,概括来说,就是读取中断寄存器,找出是谁产生的了中断,然后调用相关的isr。伪代码如下
void top_level_int_handler()
{
get current interrupt bit position in the register.
switch(position)
{
case timer0:timer0isr();break;
...
}
}
4. 编译调试
把源代码加到工程中,去掉错误信息,生成axf文件。 用realdebug通过jlink连上目标板,加载axf文件,如愿出现print信息。
5. 小结
如果对板子的boot很熟悉,再参考demo中arm7的一些例子,还是很快就能完成porting的。
但是还是对任务的调度,memory的管理,还是不了解。