1. 准备
在正式移植之前,我们需要准备base工程,现在一般MCU原厂都有提供SDK,很少有让用户自己建立base工程的情况。LPC1788这款MCU相对较老,不容易找到SDK,但是网上有很多前辈出过LPC1788的开发板,我们可以基于其配套的例程作为base工程。
- 删除无用的文件:
- 删除串口源码,因为用不到
- 新建main.c,保留main函数,更改一个自己的工程名字,本文取名“OS_Demo”,保证工程编译通过,基础准备工作完成。
#include "LPC177x_8x.h"
int main(void)
{
return 0;
}
2. 移植
2.1. 加入源码
工程项目中,新建一个uCOS-III文件夹,专门用于存放uCOS的源码。在uCOS-III路径下新建Config文件夹,用于保存OS配置相关的文件。
uC-CPU-develop,uC-LIB-develop,uC-OS3-develop这种第三方源码我们希望原封不动放入,保持不删减、不修改,对OS相关配置只需要修改Config下的文件。
uCOS-III提供了配置文件的模板,分别存放在各目录下的"Cfg/Template"目录下,如下图统一把他们拷贝到Config目录下。
2.2. 加入工程
- 新建工程目录树:
- 将"uC-OS3-develop/Source"目录下与内核相关的源码加入工程:
- 加入与处理器平台移植相关的文件,比如任务切换底层汇编实现等源码都放在"uC-OS3-develop/Ports"目录下,可以看到支持的处理器平台众多,LPC1788是Cortex-M3内核,属于ARMv7-M架构。因此:
- 加入"uC-OS3-develop/Ports/ARM-Cortex-M/ARMv7-M/ARM"下的文件;
- 加入"uC-OS3-develop/Ports/ARM-Cortex-M/ARMv7-M/os_cpu_c.c"。
-
加入"uC-LIB-develop"目录下相关源码:
-
加入"uC-CPU-develop"目录下相关源码:
6.加入"Config"目录下的源码:
- 头文件包含路径加入到工程配置:
至此,所有文件加入完成,可以进行一次编译。
2.3. 编译问题
第一次编译会出现如下错误,提示CPU_CFG_NVIC_PRIO_BITS
未定义。
打开cpu_cfg.h定位到242行,使能CPU_CFG_NVIC_PRIO_BITS
的定义:
#if 1
#define CPU_CFG_NVIC_PRIO_BITS 4u
#endif
再次编译可以通过。
2.4. 中断适配
大多数RTOS在Cortex-M3/M4上的移植都需要Systick、PendSV。
Systick是一个定时器,用于周期性地触发中断,以提供系统时钟。在RTOS中,Systick通常用于测量时间间隔,例如用于定时器、计数器等。在Systick中断中调度任务的执行,使得任务的执行时间能够得到控制。
PendSV异常用于任务切换,为了保证操作系统的实时性,除了使用 Systick 的时间片调度,还得加入PendSV异常加入抢占式调度。
- 新建
lpc1788_it.c
,这里面加入Systick中断处理函数。
#include "os.h"
void SysTick_Handler(void)
{
OSIntEnter(); //用于统计中断的嵌套层数,对嵌套层数+1
OSTimeTick(); //统计时间,遍历任务,对延时任务计时减1
OSIntExit(); //对嵌套层数减1,在退出中断前启动任务调度
}
- 在
startup_LPC177x_8x.s
启动汇编中,将PendSV_Handler
替换为OS提供的OS_CPU_PendSVHandler
至此基本移植工作结束,接下来建立两个任务验证任务是否能正常调度。
3. 验证
1.重写的main
,创建一个启动任务:
#define STARTUP_TASK_STK_SIZE 100
#define STARTUP_TASK_PRIO 3
static OS_TCB StartUp_TCB;
static CPU_STK StartUp_Stk[STARTUP_TASK_STK_SIZE];
int main(void)
{
OS_ERR err;
/* 初始化"uC/OS-III"内核 */
OSInit(&err);
/*创建任务*/
OSTaskCreate((OS_TCB *)&StartUp_TCB, // 任务控制块指针
(CPU_CHAR *)"StartUp", // 任务名称
(OS_TASK_PTR )Task_Start, // 任务代码指针
(void *)0, // 传递给任务的参数parg
(OS_PRIO )STARTUP_TASK_PRIO, // 任务优先级
(CPU_STK *)&StartUp_Stk[0], // 任务堆栈基地址
(CPU_STK_SIZE)STARTUP_TASK_STK_SIZE/10, // 堆栈剩余警戒线
(CPU_STK_SIZE)STARTUP_TASK_STK_SIZE, // 堆栈大小
(OS_MSG_QTY )0, // 可接收的最大消息队列数
(OS_TICK )0, // 时间片轮转时间
(void *)0, // 任务控制块扩展信息
(OS_OPT )(OS_OPT_TASK_STK_CHK |
OS_OPT_TASK_STK_CLR), // 任务选项
(OS_ERR *)&err); // 返回值
/* 启动多任务系统,控制权交给uC/OS-III */
OSStart(&err);
}
- 在启动任务中,调用
OS_CPU_SysTickInit
函数初始化Systick中断,中断周期1ms,由于Systick定时器属于Cortex-M3内核提供,所以Systick初始化uCOS-III已经帮我们写好。然后初始化系统相关的基本外设。本文创建两个LED_Task任务,用于验证OS工作是否正常。
- 创建LED任务:
#define TASK_LED1_STK_SIZE 256
#define TASK_LED2_STK_SIZE 256
#define TASK_LED1_PRIO 4
#define TASK_LED2_PRIO 5
static OS_TCB LED1_TCB;
static CPU_STK LED1_Stk[TASK_LED1_STK_SIZE];
static OS_TCB LED2_TCB;
static CPU_STK LED2_Stk[TASK_LED2_STK_SIZE];
void Task_Start(void *p_arg)
{
OS_ERR err;
(void)p_arg;
OS_CPU_SysTickInit(SystemCoreClock/1000); /* Initialize the SysTick. */
led_init();
//创建任务1
OSTaskCreate((OS_TCB *)&LED1_TCB, // 任务控制块指针
(CPU_CHAR *)"LED1", // 任务名称
(OS_TASK_PTR )led1_task, // 任务代码指针
(void *)0, // 传递给任务的参数parg
(OS_PRIO )TASK_LED1_PRIO, // 任务优先级
(CPU_STK *)&LED1_Stk[0], // 任务堆栈基地址
(CPU_STK_SIZE)TASK_LED1_STK_SIZE/10, // 堆栈剩余警戒线
(CPU_STK_SIZE)TASK_LED1_STK_SIZE, // 堆栈大小
(OS_MSG_QTY )0, // 可接收的最大消息队列数
(OS_TICK )0, // 时间片轮转时间
(void *)0, // 任务控制块扩展信息
(OS_OPT )(OS_OPT_TASK_STK_CHK |
OS_OPT_TASK_STK_CLR), // 任务选项
(OS_ERR *)&err); // 返回值
//创建任务2
OSTaskCreate((OS_TCB *)&LED2_TCB, // 任务控制块指针
(CPU_CHAR *)"LED2", // 任务名称
(OS_TASK_PTR )led2_task, // 任务代码指针
(void *)0, // 传递给任务的参数parg
(OS_PRIO )TASK_LED2_PRIO, // 任务优先级
(CPU_STK *)&LED2_Stk[0], // 任务堆栈基地址
(CPU_STK_SIZE)TASK_LED2_STK_SIZE/10, // 堆栈剩余警戒线
(CPU_STK_SIZE)TASK_LED2_STK_SIZE, // 堆栈大小
(OS_MSG_QTY )0, // 可接收的最大消息队列数
(OS_TICK )0, // 时间片轮转时间
(void *)0, // 任务控制块扩展信息
(OS_OPT )(OS_OPT_TASK_STK_CHK |
OS_OPT_TASK_STK_CLR), // 任务选项
(OS_ERR *)&err); // 返回值
//任务删除自己
OSTaskDel(&StartUp_TCB, &err);
}
- LED1 Task实现LED亮500ms,灭500ms。
void led1_task(void *p_arg)
{
OS_ERR err;
(void)p_arg;
while (1)
{
LED1_ON;
OSTimeDlyHMSM(0, 0,0,500,OS_OPT_TIME_HMSM_STRICT,&err); //延时500ms
LED1_OFF;
OSTimeDlyHMSM(0, 0,0,500,OS_OPT_TIME_HMSM_STRICT,&err); //延时500ms
}
}
- LED2 Task实现LED2亮200ms,灭200ms。
void led2_task(void *p_arg)
{
OS_ERR err;
(void)p_arg;
while (1)
{
LED2_ON;
OSTimeDlyHMSM(0, 0,0,100,OS_OPT_TIME_HMSM_STRICT,&err); //延时100ms
LED2_OFF;
OSTimeDlyHMSM(0, 0,0,100,OS_OPT_TIME_HMSM_STRICT,&err); //延时100ms
}
}
- 如下图使用逻辑分析仪测量LED1、LED2的IO翻转时间为预期,证明系统时钟和Systick等配置正确无误。
本文是一篇实战性较强的文章,重点指导大家如何在LPC1788如何移植uCOS-III,一些思路和方法,同样可以迁移到其他处理器平台,对于uCOS-III基础知识推荐这篇博客:实时操作系统UCOS学习笔记5—-UCOSIII移植。