环境搭建
暂略
通常用户对LiteOS的初始化调用如下:
1 2 3 4
| main(): LOS_KernelInit(); // 内核初始化 LiteOS_Task_sample(); // 用户业务代码,创建初始任务等 LOS_Start(); // 开启调度
|
开发适配
LiteOS-M的shell组件适配
LiteOS-M添加shell组件,主要思路为:
- 适配对接指定的串口读写接口,并配置串口接收中断触发写指定的shell事件:
g_shellInputEvent
,使得shell线程能够接收事件读取uart数据
- 在配置文件中启用
LOSCFG_USE_SHELL
相关宏定义,等,使得相关源代码编译链接
- 调用指定的初始化接口
LosShellInit()
以下为本人之适配代码片段摘选,驱动接口参考实现即可,不拘泥于形式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| // uart.h extern EVENT_CB_S g_shellInputEvent;
VOID ShellUartInit(VOID); INT32 UartPutc(INT32 c, VOID *file); uint8_t UartGetc(VOID);
// uart_shell.c
// 适配接口 INT32 UartPutc(INT32 ch, VOID *file) { char RL = '\r'; if (ch == '\n') { drv_uart_write(DEV_UART1, (uint8_t *)&RL, 1); } drv_uart_write(DEV_UART1, (uint8_t *)&ch, 1); return 0; }
// 适配接口实现,由用户自己实现 uint8_t UartGetc(void) { uint8_t data;
if (drv_uart_read(DEV_UART1, &data, 1) != 0) { return data; } else { return 0; } }
// 注册uart1接收中断,当收到一帧数据时,向shell线程阻塞等的事件写标志,触发shell线程读取接收数据并处理 void uart1_receive_cb(enum uart_dev_name uart_num, uint16_t length) { (void)LOS_EventWrite(&g_shellInputEvent, 0x1); }
VOID ShellUartInit(VOID) { drv_uart_set_rx_indicate(DEV_UART1, uart1_receive_cb); LosShellInit(); // 由用户调用,初始化shell线程 }
|
LiteOS中断管理之SysTick
主要介绍LiteOS如何重建中断映射向量、自定义SysTick中断服务函数、初始化SysTick时基
LiteOS中断映射表初始化
- 函数调用顺序:
LOS_KernelInit() -> ArchInit() -> HalHwiInit()
如下代码SCB->VTOR = (UINT32)(UINTPTR)hwiForm;
将中断向量映射基地址设为自定义变量地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| # arch/arm/cortex-m4/iar/los_interrupt.c > HalHwiInit()
#if (LOSCFG_USE_SYSTEM_DEFINED_INTERRUPT == 1) UINT32 index; HWI_PROC_FUNC *hwiForm = (HWI_PROC_FUNC *)ArchGetHwiFrom(); hwiForm[0] = 0; /* [0] Top of Stack */ hwiForm[1] = (HWI_PROC_FUNC)Reset_Handler; /* [1] reset */ for (index = 2; index < OS_VECTOR_CNT; index++) { /* 2: The starting position of the interrupt */ hwiForm[index] = (HWI_PROC_FUNC)HalHwiDefaultHandler; } /* Exception handler register */ hwiForm[NonMaskableInt_IRQn + OS_SYS_VECTOR_CNT] = (HWI_PROC_FUNC)HalExcNMI; hwiForm[HARDFAULT_IRQN + OS_SYS_VECTOR_CNT] = (HWI_PROC_FUNC)HalExcHardFault; hwiForm[MemoryManagement_IRQn + OS_SYS_VECTOR_CNT] = (HWI_PROC_FUNC)HalExcMemFault; hwiForm[BusFault_IRQn + OS_SYS_VECTOR_CNT] = (HWI_PROC_FUNC)HalExcBusFault; hwiForm[UsageFault_IRQn + OS_SYS_VECTOR_CNT] = (HWI_PROC_FUNC)HalExcUsageFault; hwiForm[SVCall_IRQn + OS_SYS_VECTOR_CNT] = (HWI_PROC_FUNC)HalExcSvcCall; hwiForm[PendSV_IRQn + OS_SYS_VECTOR_CNT] = (HWI_PROC_FUNC)HalPendSV; hwiForm[SysTick_IRQn + OS_SYS_VECTOR_CNT] = (HWI_PROC_FUNC)SysTick_Handler;
/* Interrupt vector table location */ SCB->VTOR = (UINT32)(UINTPTR)hwiForm; #endif
|
配置SysTick中断服务函数
- 函数调用顺序:
LOS_KernelInit() -> OsTickTimerInit() -> OsTickTimerInit() -> g_sysTickTimer->init(OsTickHandler) -> SysTickStart(OsTickHandler)
1 2 3 4 5 6 7 8 9
| # kernel/src/los_tick.c LITE_OS_SEC_TEXT VOID OsTickHandler(VOID) { #if (LOSCFG_BASE_CORE_TICK_WTIMER == 0) OsUpdateSysTimeBase(); #endif
LOS_SchedTickHandler(); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| # arch/arm/cortex-m4/gcc/los_timer.c STATIC UINT32 SysTickStart(HWI_PROC_FUNC handler) { UINT32 ret; ArchTickTimer *tick = &g_archTickTimer;
tick->freq = OS_SYS_CLOCK;
#if (LOSCFG_USE_SYSTEM_DEFINED_INTERRUPT == 1) #if (LOSCFG_PLATFORM_HWI_WITH_ARG == 1) OsSetVector(tick->irqNum, handler, NULL); #else OsSetVector(tick->irqNum, handler); // 设置SysTick的中断向量 #endif #endif
ret = SysTick_Config(LOSCFG_BASE_CORE_TICK_RESPONSE_MAX); // 在此处配置的SysTick中断无效,后续开启调度时会reload if (ret == 1) { return LOS_ERRNO_TICK_PER_SEC_TOO_SMALL; }
return LOS_OK; }
|
1 2 3 4 5 6 7 8
| # arch/arm/common/los_common_interrupt.c VOID OsSetVector(UINT32 num, HWI_PROC_FUNC vector) { if ((num + OS_SYS_VECTOR_CNT) < OS_VECTOR_CNT) { g_hwiForm[num + OS_SYS_VECTOR_CNT] = HalInterrupt; g_hwiHandlerForm[num + OS_SYS_VECTOR_CNT] = vector; } }
|
初始化SysTick时基
- 函数调用顺序:
LOS_Start() -> ArchStartSchedule() -> OsSchedStart() -> OsSchedSetNextExpireTime() -> OsTickTimerReload() -> SysTickReload()
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| # arch/arm/cortex-m4/gcc/los_timer.c # 调用此函数,传入的值实际上是 STATIC UINT64 SysTickReload(UINT64 nextResponseTime) { if (nextResponseTime > g_archTickTimer.periodMax) { nextResponseTime = g_archTickTimer.periodMax; } SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; SysTick->LOAD = (UINT32)(nextResponseTime - 1UL); /* set reload register */ SysTick->VAL = 0UL; /* Load the SysTick Counter Value */ NVIC_ClearPendingIRQ(SysTick_IRQn); SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; return nextResponseTime; }
|
初始化调用逻辑总结
- 在
arch/arm/cortex-m4/iar/los_interrupt.c > HalHwiInit()
中执行中断向量表初始化
但在此处初始化的SysTick_Handler向量是弱函数,并且其在后面会被替换
- 在
LOS_KernelInit() -> OsTickTimerInit() -> OsTickTimerInit()
重设SysTick中断服务函数
- 在
LOS_Start() -> ArchStartSchedule() -> OsSchedStart()
中调用 重置SysTick中断时基为系统时钟/调度间隔Tick值 (如:时钟源 / 1000 = 1ms时基)
- 最后开启系统调度
补充说明
- LiteOS的内核初始化和系统启动都对Systick进行了设置,内核初始化时设置了Systick的重装载值为24位最大值,即0xFFFFFF
- 系统启动时,重置Systick累计的节拍,并设置重装载值为
SystemCoreClock / 1000
,其中SystemCoreClock为系统时钟源频率。
槽点
shell交互-uart-dma中断服务函数的配置
- 芯片上电执行
LOS_KernelInit()
,其中会重新定义中断向量,但在这个过程之前和之后都会有日志打印
- 如果日志打印是用uart + DMA完成的,其中需要注册中断服务函数。
- 但是uart的初始化打开应该在系统初始化前执行,但中断服务函数要在系统初始化后才能注册。给编程带来不便
软件定时器的使用
鸡肋的软件定时器,只能在初始化时定好时间周期,后续不可更改。
其提供的API过少,只有初始化、开始、结束的有限几个接口。用户无法获取当前定时器剩余tick,无法重新设置定时周期,无法在使用过程中更改定时器的 单次或者周期定时等属性
该软件定时器中,不支持申请互斥量
- 在写私有消息队列过程中,涉及临界区操作,需要申请互斥量,但在定时器的回调函数中申请互斥量会直接返回错误。
定时器任务创建源码如下:
如下所示:其中系统在创建定时器线程时会或上OS_TASK_FLAG_SYSTEM_TASK
这个标志,然后在申请互斥量、信号量、事件时,都会检查当前是否运行在定时器任务中,如果为真,则直接返回错误
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| LITE_OS_SEC_TEXT_INIT UINT32 OsSwtmrTaskCreate(VOID) { UINT32 ret; TSK_INIT_PARAM_S swtmrTask;
// Ignore the return code when matching CSEC rule 6.6(4). (VOID)memset_s(&swtmrTask, sizeof(TSK_INIT_PARAM_S), 0, sizeof(TSK_INIT_PARAM_S));
swtmrTask.pfnTaskEntry = (TSK_ENTRY_FUNC)OsSwtmrTask; swtmrTask.uwStackSize = LOSCFG_BASE_CORE_TSK_SWTMR_STACK_SIZE; swtmrTask.pcName = "Swt_Task"; swtmrTask.usTaskPrio = 0; ret = LOS_TaskCreate(&g_swtmrTaskID, &swtmrTask); if (ret == LOS_OK) { OS_TCB_FROM_TID(g_swtmrTaskID)->taskStatus |= OS_TASK_FLAG_SYSTEM_TASK; } return ret; }
|
互斥量操作时,会进行以下检查:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| STATIC_INLINE UINT32 OsMuxValidCheck(LosMuxCB *muxPended) { if (muxPended->muxStat == OS_MUX_UNUSED) { return LOS_ERRNO_MUX_INVALID; }
if (OS_INT_ACTIVE) { return LOS_ERRNO_MUX_IN_INTERR; }
if (g_losTaskLock) { PRINT_ERR("!!!LOS_ERRNO_MUX_PEND_IN_LOCK!!!\n"); return LOS_ERRNO_MUX_PEND_IN_LOCK; }
if (g_losTask.runTask->taskStatus & OS_TASK_FLAG_SYSTEM_TASK) { return LOS_ERRNO_MUX_PEND_IN_SYSTEM_TASK; }
return LOS_OK; }
|
空闲任务函数
查看LiteOS之原版api,是存在创建空闲任务钩子函数的,但最新版代码已经删除,代码如下:
由以下代码可知,空闲任务作用仅为回收结束任务资源,以及可选的电源管理。
- 个人操作:增设一个专门的低优先级任务来执行一些空闲操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| LITE_OS_SEC_TEXT VOID OsIdleTask(VOID) { while (1) { OsRecycleFinishedTask();
if (PmEnter != NULL) { PmEnter(); } else { (VOID)ArchEnterSleep(); } } }
LITE_OS_SEC_TEXT_INIT UINT32 OsIdleTaskCreate(VOID) { UINT32 retVal; TSK_INIT_PARAM_S taskInitParam; // Ignore the return code when matching CSEC rule 6.6(4). (VOID)memset_s((VOID *)(&taskInitParam), sizeof(TSK_INIT_PARAM_S), 0, sizeof(TSK_INIT_PARAM_S)); taskInitParam.pfnTaskEntry = (TSK_ENTRY_FUNC)OsIdleTask; taskInitParam.uwStackSize = LOSCFG_BASE_CORE_TSK_IDLE_STACK_SIZE; taskInitParam.pcName = "IdleCore000"; taskInitParam.usTaskPrio = OS_TASK_PRIORITY_LOWEST; retVal = LOS_TaskCreateOnly(&g_idleTaskID, &taskInitParam); if (retVal != LOS_OK) { return retVal; }
OsSchedSetIdleTaskSchedParam(OS_TCB_FROM_TID(g_idleTaskID)); return LOS_OK; }
|
参考站点