网站的图片要会员才能下载怎么做,网站微信公众号链接怎么做,巨鹿网站建设设计,静态网站 价格写在前面#xff1a; 由于时间的不足与学习的碎片化#xff0c;写博客变得有些奢侈。 但是对于记录学习#xff08;忘了以后能快速复习#xff09;的渴望一天天变得强烈。 既然如此 不如以天为单位#xff0c;以时间为顺序#xff0c;仅仅将博客当做一个知识学习的目录 由于时间的不足与学习的碎片化写博客变得有些奢侈。 但是对于记录学习忘了以后能快速复习的渴望一天天变得强烈。 既然如此 不如以天为单位以时间为顺序仅仅将博客当做一个知识学习的目录记录笔者认为最通俗、最有帮助的资料并尽量总结几句话指明本质以便于日后搜索起来更加容易。 标题的结构如下“类型”“知识点”——“简短的解释” 部分内容由于保密协议无法上传。 点击此处进入学习日记的总目录 2024.03.25UCOSIII第二十二节系统启动流程详解 三十六、UCOSIII系统启动流程详解1、运行启动文件2、主流程 main3、系统初始化函数OSInit()1. 空闲任务的初始化2. 空闲任务的定义3. 时钟节拍任务的初始化 4、启动任务AppTaskStart()1. 时间戳初始化2. SysTick初始化3. 内存初始化 5、任务调度器启动函数OSStart() 三十六、UCOSIII系统启动流程详解
本章总结一下系统在刚启动时初始化的过程 介绍顺序为上电后的运行顺序
1、运行启动文件
在系统上电的时候第一个执行的是启动文件里面由汇编编写的复位函数Reset_Handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDRR0, SystemInitBLX R0
LDRR0, __mainBX R0
ENDP复位函数的最后会调用C库函数__main__main函数的主要工作是初始化系统的堆和栈最后调用C中的main()函数从而去到C的世界。
2、主流程 main
首先看main函数
int main (void)
{OS_ERR err;OSInit(err); /* Init uC/OS-III. */OSTaskCreate((OS_TCB *)AppTaskStartTCB, /* Create the start task */(CPU_CHAR *)App Task Start,(OS_TASK_PTR ) AppTaskStart,(void *) 0,(OS_PRIO ) APP_TASK_START_PRIO,(CPU_STK *)AppTaskStartStk[0],(CPU_STK_SIZE) APP_TASK_START_STK_SIZE / 10,(CPU_STK_SIZE) APP_TASK_START_STK_SIZE,(OS_MSG_QTY ) 5u,(OS_TICK ) 0u,(void *) 0,(OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),(OS_ERR *)err);OSStart(err); /* Start multitasking (i.e. give control to uC/OS-III). */
}这种启动方式在野火的教程里被称作小心翼翼十分谨慎法
这种方法是在main()函数中将硬件和RTOS系统先初始化好然后创建一个启动任务后就启动调度器。 在启动任务里面创建各种应用任务当所有任务都创建成功后启动任务把自己删除具体的伪代码实现如下
int main (void)
{/* 硬件初始化 */HardWare_Init(); //(1)/* RTOS 系统初始化 */RTOS_Init(); //(2)/* 创建一个任务 */RTOS_TaskCreate(AppTaskCreate); //(3)/* 启动RTOS开始调度 */RTOS_Start(); //(4)
}/* 起始任务在里面创建任务 */
voidAppTaskCreate( void *arg ) //(5)
{/* 创建任务1然后执行 */RTOS_TaskCreate(Task1); //(6)/* 当任务1阻塞时继续创建任务2然后执行 */RTOS_TaskCreate(Task2);/* ......继续创建各种任务 *//* 当任务创建完成删除起始任务 */RTOS_TaskDelete(AppTaskCreate); //(7)
}void Task1( void *arg ) //(8)
{while (1){/* 任务实体必须有阻塞的情况出现 */}
}void Task2( void *arg ) //(9)
{while (1){/* 任务实体必须有阻塞的情况出现 */}
}(1)硬件初始化。来到硬件初始化这一步还属于裸机的范畴 我们可以把需要使用到的硬件都初始化好而且测试好确保无误。(2)RTOS系统初始化。比如RTOS里面的全局变量的初始化 空闲任务的创建等。不同的RTOS它们的初始化有细微的差别。(3)创建一个开始任务。然后在这个初始任务里面创建各种应用任务。(4)启动RTOS调度器开始任务调度。这个时候调度器就去执行刚刚创建好的初始任务。(5)我们通常说任务是一个不带返回值的无限循环的C函数 但是因为初始任务的特殊性它不能是无限循环的只执行一次后就关闭。在初始任务里面我们创建我们需要的各种任务。(6)创建任务。每创建一个任务后它都将进入就绪态系统会进行一次调度 如果新创建的任务的优先级比初始任务的优先级高的话那将去执行新创建的任务 当新的任务阻塞时再回到初始任务被打断的地方继续执行。反之则继续往下创建新的任务直到所有任务创建完成。(7)各种应用任务创建完成后初始任务自己关闭自己使命完成。(8)(9)任务实体通常是一个不带返回值的无限循环的C函数函数体必须有阻塞的情况出现 不然任务如果优先权恰好是最高会一直在while循环里面执行其他任务没有执行的机会。
3、系统初始化函数OSInit()
在调用创建任务函数之前我们必须要对系统进行一次初始化。 而系统的初始化是根据我们配置宏定义进行初始化的 有一些则是系统必要的初始化如空闲任务时钟节拍任务等。
下面我们来看看系统初始化的主要源码
void OSInit (OS_ERR *p_err)
{CPU_STK *p_stk;CPU_STK_SIZE size;if (p_err (OS_ERR *)0){OS_SAFETY_CRITICAL_EXCEPTION();return;}OSInitHook(); /*初始化钩子函数相关的代码*/OSIntNestingCtr (OS_NESTING_CTR)0; /*清除中断嵌套计数器*/OSRunning OS_STATE_OS_STOPPED; /*未启动多任务处理*/OSSchedLockNestingCtr (OS_NESTING_CTR)0;/*清除锁定计数器*/OSTCBCurPtr (OS_TCB *)0; /*将OS_TCB指针初始化为已知状态 */OSTCBHighRdyPtr (OS_TCB *)0;OSPrioCur (OS_PRIO)0; /*将优先级变量初始化为已知状态*/OSPrioHighRdy (OS_PRIO)0;OSPrioSaved (OS_PRIO)0;if (OSCfg_ISRStkSize (CPU_STK_SIZE)0){p_stk OSCfg_ISRStkBasePtr; /*清除异常栈以进行栈检查*/if (p_stk ! (CPU_STK *)0){size OSCfg_ISRStkSize;while (size (CPU_STK_SIZE)0){size--;*p_stk (CPU_STK)0;p_stk;}}}OS_PrioInit(); /*初始化优先级位图表*/OS_RdyListInit(); /*初始化就绪列表*/OS_TaskInit(p_err); /*初始化任务管理器*/if (*p_err ! OS_ERR_NONE){return;}OS_IdleTaskInit(p_err); /* 初始化空闲任务 */ if (*p_err ! OS_ERR_NONE){return;}OS_TickTaskInit(p_err); /* 初始化时钟节拍任务*/ if (*p_err ! OS_ERR_NONE){return;}OSCfg_Init();
}在这个系统初始化中我们主要看两个地方
一个是空闲任务的初始化一个是时钟节拍任务的初始化
这两个任务是必须存在的任务否则系统无法正常运行。
1. 空闲任务的初始化
其实初始化就是创建一个空闲任务空闲任务的相关信息由系统默认指定 用户不能修改
void OS_IdleTaskInit (OS_ERR *p_err)
{
#ifdef OS_SAFETY_CRITICALif (p_err (OS_ERR *)0){OS_SAFETY_CRITICAL_EXCEPTION();return;}
#endifOSIdleTaskCtr (OS_IDLE_CTR)0; //(1)
/* ---------------- CREATE THE IDLE TASK ---------------- */OSTaskCreate((OS_TCB *)OSIdleTaskTCB,(CPU_CHAR *)((void *)μC/OS-III Idle Task),(OS_TASK_PTR)OS_IdleTask,(void *)0,(OS_PRIO )(OS_CFG_PRIO_MAX - 1u),(CPU_STK *)OSCfg_IdleTaskStkBasePtr,(CPU_STK_SIZE)OSCfg_IdleTaskStkLimit,(CPU_STK_SIZE)OSCfg_IdleTaskStkSize,(OS_MSG_QTY )0u,(OS_TICK )0u,(void *)0,(OS_OPT)(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR |OS_OPT_TASK_NO_TLS),(OS_ERR *)p_err); //(2)
}(1)OSIdleTaskCtr在os.h头文件中定义是一个32位无符号整型变量 该变量的作用是用于统计空闲任务的运行的怎么统计呢我们在空闲任务中讲解。现在初始化空闲任务系统就将OSIdleTaskCtr清零。(2)我们可以很容易看到系统只是调用了OSTaskCreate()函数来创建一个任务这个任务就是空闲任务 任务优先级为OS_CFG_PRIO_MAX-1OS_CFG_PRIO_MAX是一个宏该宏定义表示μC/OS的任务优先级数值的最大值我们知道 在μC/OS系统中任务的优先级数值越大表示任务的优先级越低所以空闲任务的优先级是最低的。 空闲任务栈大小为OSCfg_IdleTaskStkSize它也是一个宏在os_cfg_app.c文件中定义默认为128 则空闲任务栈默认为128*4512字节。
2. 空闲任务的定义
空闲任务其实就是一个函数其函数入口是OS_IdleTask
void OS_IdleTask (void *p_arg)
{CPU_SR_ALLOC();/* Prevent compiler warning for not using p_arg*/p_arg p_arg;while (DEF_ON){CPU_CRITICAL_ENTER();OSIdleTaskCtr;
#if OS_CFG_STAT_TASK_EN 0uOSStatTaskCtr;
#endifCPU_CRITICAL_EXIT();/* Call user definable HOOK */OSIdleTaskHook();}
}空闲任务的作用还是很大的它是一个无限的死循环。 因为其优先级是最低的所以任何优先级比它高的任务都能抢占它从而取得CPU的使用权。
为什么系统要空闲任务呢 因为CPU是不会停下来的即使啥也不干CPU也不会停下来此时系统就必须保证有一个随时处于就绪态的任务 而且这个任务不会抢占其他任务当且仅当系统的其他任务处于阻塞中系统才会运行空闲任务。 这个任务可以做很多事情任务统计 钩入用户自定义的钩子函数实现用户自定义的功能等但是需要注意的是在钩子函数中用户不允许调用任何可以使空闲任务阻塞的函数接口 空闲任务是不允许被阻塞的。
3. 时钟节拍任务的初始化
OS_TickTaskInit()函数也是创建一个时钟节拍任务
void OS_TickTaskInit (OS_ERR *p_err)
{
#ifdef OS_SAFETY_CRITICALif (p_err (OS_ERR *)0){OS_SAFETY_CRITICAL_EXCEPTION();return;}
#endifOSTickCtr (OS_TICK)0u; /* Clear the tick counter */OSTickTaskTimeMax (CPU_TS)0u;OS_TickListInit();/* Initialize the tick list data structures *//* ---------------- CREATE THE TICK TASK ---------------- */if (OSCfg_TickTaskStkBasePtr (CPU_STK *)0){*p_err OS_ERR_TICK_STK_INVALID;return;}if (OSCfg_TickTaskStkSize OSCfg_StkSizeMin){*p_err OS_ERR_TICK_STK_SIZE_INVALID;return;}/* Only one task at the Idle Task priority */if (OSCfg_TickTaskPrio (OS_CFG_PRIO_MAX - 1u)){*p_err OS_ERR_TICK_PRIO_INVALID;return;}OSTaskCreate((OS_TCB *)OSTickTaskTCB,(CPU_CHAR *)((void *)μC/OS-III Tick Task),(OS_TASK_PTR )OS_TickTask,(void *)0,(OS_PRIO )OSCfg_TickTaskPrio,(CPU_STK *)OSCfg_TickTaskStkBasePtr,(CPU_STK_SIZE)OSCfg_TickTaskStkLimit,(CPU_STK_SIZE)OSCfg_TickTaskStkSize,(OS_MSG_QTY )0u,(OS_TICK )0u,(void *)0,(OS_OPT)(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR | OS_OPT_TASK_NO_TLS),(OS_ERR *)p_err);
}4、启动任务AppTaskStart()
系统在启动任务里面创建各种应用任务当所有任务都创建成功后启动任务把自己删除
static void AppTaskStart (void *p_arg)
{CPU_INT32U cpu_clk_freq;CPU_INT32U cnts;OS_ERR err;(void)p_arg;BSP_Init(); /* Initialize BSP functions */CPU_Init();cpu_clk_freq BSP_CPU_ClkFreq(); /* Determine SysTick reference freq. */cnts cpu_clk_freq / (CPU_INT32U)OSCfg_TickRate_Hz; /* Determine nbr SysTick increments */OS_CPU_SysTickInit(cnts); /* Init uC/OS periodic time src (SysTick). */Mem_Init(); /* Initialize Memory Management Module */#if OS_CFG_STAT_TASK_EN 0uOSStatTaskCPUUsageInit(err); /* Compute CPU capacity with no task running */
#endifCPU_IntDisMeasMaxCurReset();OSTaskCreate((OS_TCB *)AppTaskLed1TCB, /* Create the Led1 task */(CPU_CHAR *)App Task Led1,(OS_TASK_PTR ) AppTaskLed1,(void *) 0,(OS_PRIO ) APP_TASK_LED1_PRIO,(CPU_STK *)AppTaskLed1Stk[0],(CPU_STK_SIZE) APP_TASK_LED1_STK_SIZE / 10,(CPU_STK_SIZE) APP_TASK_LED1_STK_SIZE,(OS_MSG_QTY ) 5u,(OS_TICK ) 0u,(void *) 0,(OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),(OS_ERR *)err);OSTaskCreate((OS_TCB *)AppTaskLed2TCB, /* Create the Led2 task */(CPU_CHAR *)App Task Led2,(OS_TASK_PTR ) AppTaskLed2,(void *) 0,(OS_PRIO ) APP_TASK_LED2_PRIO,(CPU_STK *)AppTaskLed2Stk[0],(CPU_STK_SIZE) APP_TASK_LED2_STK_SIZE / 10,(CPU_STK_SIZE) APP_TASK_LED2_STK_SIZE,(OS_MSG_QTY ) 5u,(OS_TICK ) 0u,(void *) 0,(OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),(OS_ERR *)err);OSTaskCreate((OS_TCB *)AppTaskLed3TCB, /* Create the Led3 task */(CPU_CHAR *)App Task Led3,(OS_TASK_PTR ) AppTaskLed3,(void *) 0,(OS_PRIO ) APP_TASK_LED3_PRIO,(CPU_STK *)AppTaskLed3Stk[0],(CPU_STK_SIZE) APP_TASK_LED3_STK_SIZE / 10,(CPU_STK_SIZE) APP_TASK_LED3_STK_SIZE,(OS_MSG_QTY ) 5u,(OS_TICK ) 0u,(void *) 0,(OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),(OS_ERR *)err);OSTaskDel ( AppTaskStartTCB, err );}其中需要注意的如下
CPU_Init();
//时间戳初始化OS_CPU_SysTickInit(cnts);
//MCU的内核定时器SysTick初始化Mem_Init();
//内存初始化1. 时间戳初始化
在启动任务AppTaskStart()中有一个CPU初始化函数CPU初始化函数可以初始化时间戳
void CPU_Init (void)
{
/* --------------------- INIT TS ---------------------- */
#if ((CPU_CFG_TS_EN DEF_ENABLED) || \(CPU_CFG_TS_TMR_EN DEF_ENABLED))CPU_TS_Init(); /* 时间戳测量的初始化 */#endif
/* -------------- INIT INT DIS TIME MEAS -------------- */
#ifdef CPU_CFG_INT_DIS_MEAS_ENCPU_IntDisMeasInit(); /* 最大关中断时间测量初始化 */#endif/* ------------------ INIT CPU NAME ------------------- */
#if (CPU_CFG_NAME_EN DEF_ENABLED)CPU_NameInit(); //CPU 名字初始化
#endif
}时间戳它的精度高达ns级别是CPU内核的一个重要资源。
在Cortex-M注意M0内核不可用内核中有一个外设叫DWT(Data Watchpoint and Trace)是用于系统调试及跟踪 它有一个32位的寄存器叫CYCCNT。 CYCCNT是一个向上的计数器记录的是内核时钟运行的个数。 内核时钟跳动一次 该计数器就加1当CYCCNT溢出之后会清零重新开始向上计数。 CYCCNT的精度非常高其精度取决于内核的频率是多少 如果是STM32F1系列内核时钟是72M那精度就是1/72M 14ns而程序的运行时间都是微秒级别的所以14ns的精度是远远够的。 CYCCNT最长能记录的时间为60s2的32次方/72000000(假设内核频率为72M内核跳一次的时间大概为1/72M14ns) 而如果是STM32H7系列这种400M主频的芯片那它的计时精度高达2.5ns1/400000000 2.5。 如果是i.MX RT1052这种比较厉害的处理器最长能记录的时间为 8.13s2的32次方/528000000(假设内核频率为528M 内核跳一次的时间大概为1/528M1.9ns) 。
想要启用DWT外设需要由另外的内核调试寄存器DEMCR的位24控制写1启用DEMCR的地址是0xE000 EDFC。 启用DWT_CYCCNT寄存器之前先清零。 让我们看看DWT_CYCCNT的基地址从ARM-Cortex-M手册中可以看到其基地址是0xE000 1004 复位默认值是0而且它的类型是可读可写的我们往0xE000 1004这个地址写0就将DWT_CYCCNT清零了。 关于CYCCNTENA它是DWT控制寄存器的第一位写1启用则启用CYCCNT计数器否则CYCCNT计数器将不会工作它的地址是0xE000EDFC。 所以想要使用DWT的CYCCNT步骤
先启用DWT外设这个由另外内核调试寄存器DEMCR的位24控制写1启用在启用CYCCNT寄存器之前先清零。启用CYCCNT寄存器这个由DWT的CYCCNTENA 控制也就是DWT控制寄存器的位0控制写1启用
这样子我们就能去看看μC/OS的时间戳的初始化了
#define DWT_CR *(CPU_REG32 *)0xE0001000
#define DWT_CYCCNT *(CPU_REG32 *)0xE0001004
#define DEM_CR *(CPU_REG32 *)0xE000EDFC#define DEM_CR_TRCENA (1 24)#define DWT_CR_CYCCNTENA (1 0)#if (CPU_CFG_TS_TMR_EN DEF_ENABLED)
void CPU_TS_TmrInit (void)
{CPU_INT32U cpu_clk_freq_hz;/* Enable Cortex-M3s DWT CYCCNT reg. */DEM_CR | (CPU_INT32U)DEM_CR_TRCENA;DWT_CYCCNT (CPU_INT32U)0u;DWT_CR | (CPU_INT32U)DWT_CR_CYCCNTENA;cpu_clk_freq_hz BSP_CPU_ClkFreq();CPU_TS_TmrFreqSet(cpu_clk_freq_hz);
}
#endif2. SysTick初始化
时钟节拍的频率表示操作系统每1秒钟产生多少个tick。 tick即是操作系统节拍的时钟周期时钟节拍就是系统以固定的频率产生中断时基中断 并在中断中处理与时间相关的事件推动所有任务向前运行。 时钟节拍需要依赖于硬件定时器在STM32 裸机程序中经常使用的SysTick时钟是 MCU的内核定时器通常都使用该定时器产生操作系统的时钟节拍。
用户需要先在“ os_cfg_app.h”中设定时钟节拍的频率该频率越高 操作系统检测事件就越频繁可以增强任务的实时性但太频繁也会增加操作系统内核的负担加重所以用户需要权衡该频率的设置。
我们在这里采用默认的 1000Hz之后若无特别声明均采用 1000 Hz也就是时钟节拍的周期为 1 ms。
函数OS_CPU_SysTickInit()用于初始化时钟节拍中断初始化中断的优先级SysTick中断的启用等等这个函数要跟不同的CPU进行编写 并且在系统任务的第一个任务开始的时候进行调用如果在此之前进行调用可能会造成系统奔溃因为系统还没有初始化好就进入中断 可能在进入和退出中断的时候会调用系统未初始化好的一些模块
cpu_clk_freq BSP_CPU_ClkFreq(); /* Determine SysTick reference freq. */
cnts cpu_clk_freq / (CPU_INT32U)OSCfg_TickRate_Hz;
OS_CPU_SysTickInit(cnts); /*Init μC/OS periodic time src (SysTick).*/3. 内存初始化
我们都知道内存在嵌入式中是很珍贵的存在而一个系统是软件则必须要有一块内存属于系统所管理的。 所以在系统创建任务之前 就必须将系统必要的东西进行初始化。 μC/OS采用一块连续的大数组作为系统管理的内存 CPU_INT08U Mem_Heap[LIB_MEM_CFG_HEAP_SIZE] 在使用之前就需要先将管理的内存进行初始化
Mem_Init();5、任务调度器启动函数OSStart()
在创建完任务的时候我们需要开启调度器。 因为创建仅仅是把任务添加到系统中还没真正调度那怎么才能让系统支持运行呢 μC/OS为我们提供一个系统启动的函数接口——OSStart()我们使用OSStart()函数就能让系统开始运行
void OSStart (OS_ERR *p_err)
{
#ifdef OS_SAFETY_CRITICALif (p_err (OS_ERR *)0) {OS_SAFETY_CRITICAL_EXCEPTION();return;}
#endifif (OSRunning OS_STATE_OS_STOPPED) {OSPrioHighRdy OS_PrioGetHighest();/* Find the highest priority */OSPrioCur OSPrioHighRdy;OSTCBHighRdyPtr OSRdyList[OSPrioHighRdy].HeadPtr;OSTCBCurPtr OSTCBHighRdyPtr;OSRunning OS_STATE_OS_RUNNING;OSStartHighRdy();/* Execute target specific code to start task */*p_err OS_ERR_FATAL_RETURN;/* OSStart() is not supposed to return */}else{*p_err OS_ERR_OS_RUNNING; /* OS is already running */}
}