收藏 分享(赏)

uCOS-II 移植与深入实战指南.pdf

上传人:精品资料 文档编号:8417418 上传时间:2019-06-25 格式:PDF 页数:77 大小:1.63MB
下载 相关 举报
uCOS-II 移植与深入实战指南.pdf_第1页
第1页 / 共77页
uCOS-II 移植与深入实战指南.pdf_第2页
第2页 / 共77页
uCOS-II 移植与深入实战指南.pdf_第3页
第3页 / 共77页
uCOS-II 移植与深入实战指南.pdf_第4页
第4页 / 共77页
uCOS-II 移植与深入实战指南.pdf_第5页
第5页 / 共77页
点击查看更多>>
资源描述

1、教程简介名称 uCOS-II移植与深入实战指南作者 WildFire Team 野火科技版本 V1.0硬件平台 野火STM32 ISO/ISO-MINI开发板淘宝店 http:/论坛 http:/野火系列教程简介,可到论坛下载。STM32篇 零死角玩转STM32系统篇 uCOS-II移植与深入实战指南GUI篇 emWin实战指南GSM篇 野火WF-SIM900A数据手册野火WF-SIM900A用户手册GPS篇 野火WF-NEO-6M模块数据手册野火WF-NEO-6M模块用户手册开源共享 共同进步!-第 2 页-从0开始移植UCOS-II 到野火STM32开发板前言uC/OS是一个微型的实时操作

2、系统,包括了一个操作系统最基本的一些特性,如任务调度、任务通信、内存管理、中断管理、定时管理等。而且这是一个代码完全开放的实时操作系统,简单明了的结构和严谨的代码风格,非常适合初涉嵌入式操作系统的人士学习。很多人在学习STM32中,都想亲自移植一下uC/OS,而不是总是用别人已经移植好的。在我学习uC/OS的过程中,查找了很多资料,也看过很多关于如何移植uC/OS到STM32处理器上的教程,但都不尽人意,主要是写得太随意了,思路很乱,读者看到最后还是不确定该怎样移植。为此,我决定写这个教程,让广大读者真正了解怎样移植。学前建议:C语言+数据结构 Wildfire Team2011年11月3日-

3、第 3 页-1、官方源代码介绍首先我们下载源代码,官方下载地址:http:/ (下载资料需要注册帐号)或者网盘下载:http:/ 4 页-文件名 说明AppNotes 包含uCOS-II的说明文件,其中文件MicriumAppNotesAN1xxx-RTOSAN1018-uCOS-II-Cortex-M3AN-1018.pdf是很重要的。这个文件对uC/OS在M3内核移植过程中需要修改的代码做了详细的说明。Licensing 包含了uCOS-II使用许可证Software应用软件,我们这里用到的就是uCOS-II文件夹。在整个移植过程中我们只需用到uCOS-II下的两个文件,分别是Ports和

4、Source.uCOS-IIDoc uC/OS官方自带说明文档和教程Ports 官方移植到M3的移植文件(IAR工程)cpu.h 定义数据类型、处理器相关代码、声明函数原型cpu_c.c 定义用户钩子函数,提供扩充软件功能的入口点。(所谓钩子函数,就是指那些插入到某函数中拓展这些函数功能的函数)cpu_a.asm 与处理器相关汇编函数,主要是任务切换函数os_dbg.c 内核调试数据和函数Source uC/OS的源代码文件ucos_ii.h 内部函数参数设置os_core.c 内核结构管理,uC/OS的核心,包含了内核初始化,任务切换,事件块管理、事件标志组管理等功能。-第 5 页-os_t

5、ime.c 时间管理,主要是延时os_tmr.c 定时器管理,设置定时时间,时间到了就进行一次回调函数处理。os_task.c 任务管理os_mem.c 内存管理os_sem.c 信号量os_mutex.c 互斥信号量os_mbox.c 消息邮箱os_q.c 队列os_flag.c 事件标志组CPU STM32标准外设库EvalBoards micrium官方评估板的代码OS-Probe-LCD os_cfg.h 内核配置uC-CPU 基于micrium官方评估板的CPU移植代码uC-LIB micrium官方的一个库代码uC-Probe uC-Probe有关的代码,是一个通用工具,能让嵌入式

6、开发人员在实时环境中监测嵌入式系统。以上这些都是下载下来的官方资源。有没有发现,uC/OS的代码文件都被分开放到不同的文件夹里了?呵呵,这个是官方移植好到STM32的uC/OS系统,他已经帮我们对uC/OS的文件进行分类存放。如果你不想要移植好的,也可以下载没有移植的,那样就所以文件都放在一个文件夹里。下载地址:http:/ 6 页-提示一下,如果是没移植好的,是找不到main函数的哦!初学者,相信很多都下载没移植好的,然后直接看它的源代码,然后看到头晕也找不到工程的入口。其实,uC/OS就是一个库而已,熟悉它的运行流程和函数接口,就可以基本跑起来。在自己亲自移植之前,总是看到移植好的例程包含

7、有CPU、uC-CPU、uC-LIB、uCOS-II四个文件夹下的代码。uCOS-II文件夹下的是源代码,这个好理解;但是前面三个有什么用啊?通常看其他移植教程时,一般都说只需改os_cpu.h,os_cpu_a.asm和os_cpu_c.c就可以了,就没听说过有CPU、uC-CPU、uC-LIB这些的。心中一直很纳闷,难道后三个都要自己编写的吗?后来在上面网址把源代码下载后,才知道CPU、uC-CPU、uC-LIB这三个文件是官方自己写的移植文件,而我们使用了标准外设库CMSIS中提供的启动文件及固件库了,因此可以不用这三个文件,哈哈,心中的疑团解决了!先看一下开发板与uC/OS-II的框架

8、图(注意APP.C就是main文件,我们下面移植的文件并没有APP_VECT.C这个文件,应用文件可以灵活处理的)-第 7 页-第 8 页-2、重要文件代码详解移植前,我们需要先了解一下uC/OS的重要文件代码。对于从没接触过uC/OS或者其他嵌入式系统的朋友们,你们需要先了解uC/OS的工作原理和各模块功能,不然就不知道为啥这样移植。推荐教程作者 书名 推荐理由野火团队 初探uCOS-II 清晰简单地讲解了uC/OS的运行流程,方便初学者学习。任哲 嵌入式实时操作系统uC/OS-II原理及应用(北京航空航天出版社) 通俗易懂的一本uC/OS教程,非常适合初学者学习。不过教程没得到更新,不能适

9、应uC/OS的发展,但还是值得推荐。Joseph Yiu 著宋岩译 Cortex-M3 权威指南 呵呵,不用说吧?移植uC/OS到M3内核中,怎么能不了解内核呢?下面的内容主要来自于刚才下载的文件里面的MicriumAppNotesAN1xxx-RTOSAN1018-uCOS-II-Cortex-M3AN-1018.pdf文件来讲的,因为这文件是uC/OS作者移植uC/OS到STM32的移植手册,里面谈到很多移植说需要注意的事项和相关知识。我在这里添加也按照作者的思路来讲解,并加入个人理解,如果有误,欢迎指出错误。-第 9 页-2.1 os_cpu.h定义数据类型、处理器相关代码、声明函数原型

10、全局变量OS_CPU_GLOBALS和OS_CPU_EXT 允许我们是否使用全局变量。1. #ifdef OS_CPU_GLOBALS2. #define OS_CPU_EXT3. #else /如果没有定义 OS_CPU_GLOBALS4. #define OS_CPU_EXT extern /则用OS_CPU_EXT声明变量已经外部定义了。5. #endif数据类型6. typedef unsigned char BOOLEAN;7. typedef unsigned char INT8U;8. typedef signed char INT8S;9. typedef unsigned s

11、hort INT16U; /大多数Cortex-M3编译器,short是16位,int是32位10. typedef signed short INT16S;11. typedef unsigned int INT32U;12. typedef signed int INT32S;13. typedef float FP32; /尽管包含了浮点数,但uC/OS-II中并没用到14. typedef double FP64;15.16. typedef unsigned int OS_STK; /M3是32位,所以堆栈的数据类型OS_STK设置32位17. typedef unsigned in

12、t OS_CPU_SR; /M3的状态寄存器(xPSR)是32位临界段临界段,就是不可被中断的代码段,例如常见的入栈出栈等操作就不可被中断。uC/OS-II是一个实时内核,需要关闭中断进入和开中断退出临界段。为此,uC/OS-II定义了两个宏定义来关中断OS_ENTER_CRITICAL()和开中断OS_EXIT_CRITICAL()。18. #define OS_CRITICAL_METHOD 3 /进入临界段的三种模式,一般选择第3种,即这里设置为319.20.21. #define OS_ENTER_CRITICAL() cpu_sr = OS_CPU_SR_Save(); /进入临界段

13、22. #define OS_EXIT_CRITICAL() OS_CPU_SR_Restore(cpu_sr); /退出临界段-第 10 页-事实上,有3种开关中断的方法,根据不同的处理器选用不同的方法。大部分情况下,选用第3种方法。另外,关于汇编函数OS_CPU_SR_Save()和OS_CPU_SR_Restore(),在后面谈到os_cpu_a.asm文件时会再说。栈生长方向M3的栈生长方向是由高地址向低地址增长的,因此OS_STK_GROWTH定义为1。23. #define OS_STK_GROWTH 1任务切换宏定义任务切换宏,关于汇编函数OSCtxSw(),在后面谈到os_cp

14、u_a.asm文件时会再说。24. #define OS_TASK_SW() OSCtxSw()函数原型开中断和关中断如果定义了进入临界段的模式为3,就声明开中断和关中断函数25. #if OS_CRITICAL_METHOD = 326. OS_CPU_SR OS_CPU_SR_Save(void);27. void OS_CPU_SR_Restore(OS_CPU_SR cpu_sr);28. #endif任务管理函数-第 11 页-29. /*任务切换的函数*/30. void OSCtxSw(void); /用户任务切换31. void OSIntCtxSw(void); /中断任务切

15、换函数32. void OSStartHighRdy(void); /在操作系统第一次启动的时候调用的任务切换33.34. void OS_CPU_PendSVHandler(void); /用户中断处理函数,旧版本为OSPendSV35.36. void OS_CPU_SysTickHandler(void); /系统定时中断处理函数,时钟节拍函数37. void OS_CPU_SysTickInit(void); /系统SysTick定时器初始化38.39. INT32U OS_CPU_SysTickClkFreq(void); /返回 SysTick定时器的时钟频率关于任务切换,利用到异

16、常处理知识,可以看Cortex-M3 权威指南(Joseph Yiu著宋岩译)中第3.4小节。关于PendSV,有不懂的朋友,可以看Cortex-M3 权威指南中第7.6小节SVC和PendSV:SVC(系统服务调用,亦简称系统调用)和 PendSV(可悬起系统调用),它们多用在上了操作系统的软件开发中。SVC用于产生系统函数的调用请求,SVC异常是必须在执行SVC指令后立即得到响应的。PendSV(可悬起的系统调用)则不同,它是可以像普通的中断一样被悬起的(不像SVC那样会上访)。OS可以利用它“缓期执行”一个异常直到其它重要的任务完成后才执行动作。悬起PendSV的方法是:手工往NVIC的

17、PendSV悬起寄存器中写1。悬起后,如果优先级不够高,则将缓期等待执行。PendSV 的典型使用场合是在上下文切换时(在不同任务之间切换)。例如,一个系统中有两个就绪的任务,上下文切换被触发的场合可以是: 执行一个系统调用 系统滴答定时器(SysTick)中断。(轮转调度中需要)注:此部分内容出自Cortex-M3 权威指南这三个函数是为SysTick定时器服务的-第 12 页-关于SysTick定时器的三个函数,为了便于理解,我们把它注释掉,不采用官方的,自己编写: 需要注释的函数OS_CPU_SysTickHandler() 在os_cpu_c.c中定义,是SysTick中断的中断处理函

18、数,而在stm32f10x_it.c中已经有该中断函数的定义SysTick_Handler(),这里也就不需要了。OS_CPU_SysTickClkFreq() 定义在BSP.C中,此函数我们自己会编写,把它注释掉。OS_CPU_SysTickInit() 定义在os_cpu_c.c中,用于初始化SysTick定时器,它依赖于OS_CPU_SysTickClkFreq(),也要注释掉。2.2 os_cpu_c.c移植uC/OS时,我们需要写10个相当简单的C函数:9个钩子函数和1个任务堆栈结构初始化函数。钩子函数所谓钩子函数,指那些插入到某些函数中为扩展这些函数功能的函数。一般地,钩子函数为第

19、三方软件开发人员提供扩充软件功能的入口点。为了拓展系统功能,uC/OS-II中提供有大量的钩子函数,用户不需要修改uC/OS-II内核代码程序,而只需要向钩子函数添加代码就可以扩充uC/OS-II的功能。注:此部分内容出自张勇的嵌入式操作系统原理与面向任务程序设计基于uC/OS-II v2.86和ARM920T尽管uC/OS-II中提供了大量的钩子函数,但实际上,移植时我们需要编写的也就9个钩子函数:40. OSInitHookBegin() /OSIinit() 系统初始化函数开头的钩子函数41. OSInitHookEnd() /OSIinit() 系统初始化函数结尾的钩子函数42. OS

20、TaskCreateHook() /OSTaskCreate()或OSTaskCreateExt() 创建任务钩子函数43. OSTaskDelHook() /OSTaskDel() 删除任务钩子函数-第 13 页-44. OSTaskIdleHook() /OS_TaskIdle() 空闲任务钩子函数45. OSTaskStatHook() /OSTaskStat() 统计任务钩子函数46. OSTaskSwHook() /OSTaskSW() 任务切换钩子函数47. OSTCBInitHook() /OS_TCBInit() 任务控制块初始化钩子函数48. OSTimeTickHook()

21、 /OSTaskTick() 时钟节拍钩子函数这些函数都是一些钩子函数,一般由用户拓展。如果要用到这些钩子函数,需要在OS_CFG.H中定义OS_CPU_HOOKS_EN为1,即:49. #define OS_CPU_HOOKS_EN 1 /在OS_CFG.H中定义钩子函数的编写,例如:50. /* 系统初始化函数 OSInit() 开头调用 */51. void OSInitHookBegin (void)52. 53. #if OS_TMR_EN 0 /当使用OS_TMR.C 定时器管理模块54. OSTmrCtr = 0; /初始化系统节拍计数变量OSTmrCtr为055. /每个时钟节

22、拍OSTmrCtr(全局变量,初始值为0)增156. #endif57. 58. /* 创建任务 OSTaskCreate() 或 OSTaskCreateExt()中调用 */59. void OSTaskCreateHook (OS_TCB *ptcb)60. 61. #if OS_APP_HOOKS_EN 0 /如果有定义应用任务62. App_TaskCreateHook(ptcb); /调用应用任务创建钩子函数63. #else /否则64. (void)ptcb; /告诉编译器 ptcb 没用到65. #endif66. 67. /* 切换任务时被调用 */68. void OST

23、askSwHook (void)69. 70. #if OS_APP_HOOKS_EN 071. App_TaskSwHook(); /应用任务切换时调用的钩子函数72. #endif73. 74. /* 每个系统节拍到了 */75. void OSTimeTickHook (void)76. 77. #if OS_APP_HOOKS_EN 078. App_TimeTickHook(); /应用软件的时钟节拍钩子79. #endif80.81. #if OS_TMR_EN 0 /如果有启动定时器管理82. OSTmrCtr+; /计时变量OSTmrCtr 加 183. if (OSTmrCt

24、r = (OS_TICKS_PER_SEC / OS_TMR_CFG_TICKS_PER_SEC) /如果时间到了84. OSTmrCtr = 0; /计时清085. OSTmrSignal(); /发送信号量OSTmrSemSignal(初始值为0)86. /以便软件定时器扫描任务OSTmr_Task能请求到信号量而继续运行下去87. 88. #endif89. -第 14 页-这些钩子函数是必须声明的,但不是必须定义的,只是为了拓展你的系统功能而已。任务堆栈结构初始化函数90. OSTaskStkInit() /任务堆栈结构初始化函数通常,我们的任务定义都是这样的:91. void MyT

25、ask (void *p_arg)92. 93. /* 可选,例如处理 p_arg变量 */94. while (1) 95. /* 任务主体 */96. 97. 典型的ARM编译器(Cortex-M3也是这样)都会把这个函数的第一个参量传递到R0寄存器中。对于像ARM内核一般都比较多寄存器的单片机,我们可以把函数中断的局部变量保存在寄存器中,以加快速度。98. OS_STK *OSTaskStkInit (void (*task)(void *pd), void *p_arg,99.OS_STK *ptos, INT16U opt)100.101. OS_STK *stk;102.103.1

26、04. (void)opt; / opt 并没有用到,防止编译器提示警告105. stk = ptos; / 加载栈指针106.107. /* 中断后xPSR,PC,LR,R12,R3-R0被自动保存到栈中*/108. *(stk) = (INT32U)0x01000000L; / xPSR109. *(-stk) = (INT32U)task; / 任务入口 (PC)110. *(-stk) = (INT32U)0xFFFFFFFEL; / R14 (LR)111. *(-stk) = (INT32U)0x12121212L; / R12112. *(-stk) = (INT32U)0x03

27、030303L; / R3113. *(-stk) = (INT32U)0x02020202L; / R2114. *(-stk) = (INT32U)0x01010101L; / R1115. *(-stk) = (INT32U)p_arg; / R0 : 变量116.117. /* 剩下的寄存器需要手动保存在堆栈 */118. *(-stk) = (INT32U)0x11111111L; / R11119. *(-stk) = (INT32U)0x10101010L; / R10120. *(-stk) = (INT32U)0x09090909L; / R9121. *(-stk) = (

28、INT32U)0x08080808L; / R8122. *(-stk) = (INT32U)0x07070707L; / R7123. *(-stk) = (INT32U)0x06060606L; / R6124. *(-stk) = (INT32U)0x05050505L; / R5125. *(-stk) = (INT32U)0x04040404L; / R4126.127. return (stk);128.-第 15 页-这是初始化任务堆栈函数。OSTaskStkInit()被任务创建函数调用,所以要在开始时,在栈中作出该任务好像刚被中断一样的假象。在ARM内核中,函数中断后,xPS

29、R,PC,LR,R12,R3-R0被自动保存到栈中的,R11-R4如果需要保存,只能手工保存。为了模拟被中断后的假象,OSTaskStkInit()的工作就是在任务自己的栈中保存cpu的所有寄存器。这些值里R1-R12都没什么意义,这里用相应的数字代号(如R1 0x01010101)主要是方便调试。问大家两个问题,以便大家知道是否掌握了这个知识点:为什么程序是*(-stk) = (INT32U)*;而不是保存寄存器的值:*(-stk) = *(INT32U)*呢?答案很简单,就是上面说的,任务还没开始运行,栈里保存的R1-R12值都没什么意义的,这里仅仅是模拟中断那样的假象,R1-R12可以是

30、其他任意义的值。为什么程序是*(-stk) = (INT32U)*;而不是*(+stk) = (INT32U)*前面已经讲过,M3的栈生长方向是由高地址向低地址增长的。-第 16 页-栈初始化后,各寄存器的初始值如下:xPSR = 0x01000000L,xPSR T位(第24位)置1,否则第一次执行任务时Fault,PC必须指向任务入口,R14 = 0xFFFFFFFEL,最低4位为E,是一个非法值,主要目的是不让使用R14,即任务是不能返回的。R0 用于传递任务函数的参数,因此等于p_arg。SysTick时钟初始化OS_CPU_SysTickInit()会被第一个任务调用,以便初始化Sy

31、sTick定时器。OS_CPU_SysTickInit()将会调用OS_CPU_SysTickClkFreq()获取系统时钟频率,用户需要为自己的开发板编写此函数获取时钟频率。129.void OS_CPU_SysTickInit (void)130.131. INT32U cnts;-第 17 页-132.133.134. cnts = OS_CPU_SysTickClkFreq() / OS_TICKS_PER_SEC;135. /OS_CPU_SysTickClkFreq()获取时钟频率136. /OS_TICKS_PER_SEC定义每秒时钟节拍中断的次数,即时钟节拍时间为1/OS_TI

32、CKS_PER_SEC137.138. /* 使能SysTick定时器 */139. OS_CPU_CM3_NVIC_ST_RELOAD = (cnts - 1);140.141. /* 使能SysTick定时器中断 */142. OS_CPU_CM3_NVIC_ST_CTRL |= OS_CPU_CM3_NVIC_ST_CTRL_CLK_SRC143. | OS_CPU_CM3_NVIC_ST_CTRL_ENABLE;144.145. OS_CPU_CM3_NVIC_ST_CTRL |= OS_CPU_CM3_NVIC_ST_CTRL_INTEN;146. 但在这里,为了便于理解,我们需要手

33、动修改成自己的,不用这些函数(看上面任务管理函数中需要注释掉的函数)。除了注释刚才上面说的三个函数外,我们还要注释掉这些宏定义:147./*148.* SYS TICK DEFINES149.*/150.#define OS_CPU_CM3_NVIC_ST_CTRL (*(volatile INT32U *)0xE000E010)151./* SysTick Ctrl 声明外部定义,相当于C语言的extern172.EXTERN OSPrioCur173.EXTERN OSPrioHighRdy174.EXTERN OSTCBCur175.EXTERN OSTCBHighRdy176.EXTE

34、RN OSIntNesting177.EXTERN OSIntExit178.EXTERN OSTaskSwHook申明这些变量是在其他文件定义的。声明全局变量由于编译器的原因,我们需要将下面的PUBIC改为EXPORT。(如果下载的源代码是用RealView编译的,则此处就不用改了,因为代码本来就是用EXPORT)179.PUBLIC OS_CPU_SR_Save ; 声明函数在此文件定义180.PUBLIC OS_CPU_SR_Restore181.PUBLIC OSStartHighRdy182.PUBLIC OSCtxSw183.PUBLIC OSIntCtxSw184.PUBLIC

35、OS_CPU_PendSVHandler修改后185.EXPORT OS_CPU_SR_Save ; 声明函数在此文件定义186.EXPORT OS_CPU_SR_Restore187.EXPORT OSStartHighRdy188.EXPORT OSCtxSw189.EXPORT OSIntCtxSw190.EXPORT OS_CPU_PendSVHandler关于EXPORT的用法和意义,可以参考RealView 编译工具4.0 版汇编器指南第7.8.7小节EXPORT或GLOBAL:EXPORT指令声明一个符号,链接器可以使用该符号解析不同对象和库文件中的符号引用。GLOBAL是EXP

36、ORT的同义词。使用EXPORT可使其他文件中的代码能够访问当前文件中的符号。与EXPORT相对应的是IMPORT,可以参考RealView编译工具4.0版汇编器指南第7.8.10小节IMPORT和EXTERN:-第 19 页-这些指令为汇编器提供一个未在当前汇编中定义的名称。在链接时,名称被解析为在其他对象文件中定义的符号。该符号被当作程序地址。如果未指定WEAK且在链接时没有找到相应的符号,则链接器会产生错误。段由于编译器的原因,也要将下面的内容替换一下:191.RSEG CODE:CODE:NOROOT(2) ; RSEG CODE:选择段code。第二个CODE表示代码段的意思,只读。

37、192. ; NOROOT表示:如果这段中的代码没调用,则允许连接器丢弃这段193. ; (2)表示:4字节对齐。假如是(n),则表示2n对齐替换为:194.AREA |.text|, CODE, READONLY, ALIGN=2 ;AREA |.text| 表示:选择段 |.text|。195. ;CODE表示代码段,READONLY表示只读(缺省)196. ;ALIGN=2表示4字节对齐。若ALIGN=n,这2n对齐197.THUMB ;Thumb 代码198.REQUIRE8 ;指定当前文件要求堆栈八字节对齐199.PRESERVE8 ;令指定当前文件保持堆栈八字节对齐对于汇编命令,想

38、了解更多,请看RealView 编译工具4.0 版汇编器指南关于段的补充:段可以分为代码段和数据段,其中代码段的内容就是可执行代码。用keil编译时,经常会出现这样的提示:Code是代码占用的空间,RO-data是Read Only只读常量的大小,如const型,RW-data是(Read Write)初始化了的可读写变量的大小,ZI-data是(Zero Initialize)没有初始化的可读写变量的大小。ZI-data不会被算做代码里因为不会被初始化。简单的说就是在烧写的时候是FLASH中的被占用的空间为:Code+ROData+RW Data程序运行的时候,芯片内部RAM使用的空间为:R

39、W Data + ZI Data-第 20 页-向量中断控制器NVIC前面讲过,关于PendSV,可以看Cortex-M3 权威指南中第7.6小节SVC和PendSV。不知道有多少位朋友看过呢?呵呵,如果看过,那下面的内容,就容易理解很多,不然,像看天书那样。200.NVIC_INT_CTRL EQU 0xE000ED04 ;中断控制及状态寄存器ICSR的地址201. ;见Cortex-M3 权威指南第8.4.5小节 表8.5)202.NVIC_SYSPRI14 EQU 0xE000ED22 ;系统异常优先级寄存器PRI_14203. ;即设置PendSV的优先级204. ;见Cortex-M

40、3 权威指南第8.4.2小节 表8.3B205.NVIC_PENDSV_PRI EQU 0xFF ;定义PendSV的可编程优先级为255,即最低206. ;为啥是最低呢?大家思考一下207.NVIC_PENDSVSET EQU 0x10000000 ;中断控制及状态寄存器ICSR的位28208. ;写 1 以悬起 PendSV中断。读取它则返回 PendSV 的状态关于向量中断控制器NVIC,推荐大家看Cortex-M3 权威指南的第7、第8章,里面有很详细的说明,我这里就不做太多的解释。回答一下刚才提出的问题:为啥要把PendSV 的可编程优先级设为最低?与SVC异常必须在执行SVC指令后

41、立即得到响应的不同,它是可以像普通的中断一样被悬起的(不像SVC那样会上访)。OS可以利用它“缓期执行”一个异常直到其它重要的任务完成后才执行动作。悬起PendSV的方法是:手工往NVIC的PendSV悬起寄存器中写1。悬起后,如果优先级不够高,则将缓期等待执行(这里是为什么需要定义NVIC_PENDSVSET的原因)。PendSV的典型使用是用在任务切换上。假如系统使用SysTick异常进行任务切换,则正常情况下:-第 21 页-但实际上,有时候单片机会进入中断状态响应其他中断,这时如果再产生滴答定时器中断,进行任务切换,打断了原来的中断服务,则运行流程为:显然,中断服务被打断了,间距的时间

42、比较长,这是实时系统所无法忍受的。为此,引入了PendSV来完美解决这个问题了:-第 22 页-PendSV异常会自动延迟上下文切换的请求,直到其它的ISR都完成了处理后才放行。为实现这个机制,需要把PendSV编程为最低优先级的异常。注:此部分内容出自Cortex-M3 权威指南中断与中断方式3相关的有两个汇编函数:209.; OS_ENTER_CRITICAL() 里进入临界段调用,保存现场环境210.OS_CPU_SR_Save211. MRS R0, PRIMASK ; 读取 PRIMASK 到 R0(保存全局中断标记,除了故障中断)212. CPSID I ; PRIMASK=1,关

43、中断213. BX LR ; 返回,返回值保存在R0214.215.216.; OS_EXIT_CRITICAL() 里退出临界段调用,恢复现场环境217.OS_CPU_SR_Restore218. MSR PRIMASK,R0 ; 读取 R0到PRIMASK中(恢复全局中断标记),通过R0传递参数219. BX LR功能:关全局中断前,保存全局中断标志,进入临界段。退出临界段后恢复中断标记。-第 23 页-汇编命令讲解:功能 作用CPS (更改处理器状态) 会更改CPSR中的一个或多个模式以及A、I和F位,但不更改其他CPSR位。CPSID就是中断禁止,CPSIE 中断允许。A 表示 启用或

44、禁用不精确的中止。I 表示 启用或禁用IRQ中断。F 表示 启用或禁用FIQ中断。此处CPSID I就表示禁止IRQ中断MRS 将CPSR或SPSR的内容移到一个通用寄存器中。MSR 将立即数或通用寄存器的内容加载到CPSR或SPSR的指定字段中。BL 跳转指令,可将下一个指令的地址复制到LR(R14,链接寄存器)中。注:此部分内容出自RealView编译工具4.0版汇编器指南启动最高优先级任务OSStartHighRdy()启动最高优先级任务,由OSStart()里调用,调用前必须先调用OSTaskCreate创建至少一个用户任务,否则系统会发生崩毁。220.OSStartHighRdy22

45、1. LDR R0, =NVIC_SYSPRI14 ; 装载 系统异常优先级寄存器PRI_14222. ; 即设置PendSV中断优先级的寄存器223. LDR R1, =NVIC_PENDSV_PRI ; 装载 PendSV的可编程优先级(255)224. STRB R1, R0 ; 无符号字节寄存器存储。R1是要存储的寄存器225. ; 存储到内存地址所基于的寄存器226. ; 即设置PendSV中断优先级为255227.228. MOV R0, #0 ; 把数值0复制到R0寄存器-第 24 页-229. MSR PSP, R0 ; 将R0的内容加载到程序状态寄存器PSR的指定字段中。23

46、0.231. LDR R0, _OS_Running ; OSRunning = TRUE232. MOV R1, #1233. STRB R1, R0234.235. LDR R0, =NVIC_INT_CTRL ; 装载 中断控制及状态寄存器ICSR的地址236. LDR R1, =NVIC_PENDSVSET ; 中断控制及状态寄存器ICSR的位28237. STR R1, R0 ; 设置 中断控制及状态寄存器ICSR 位28 为 1238. ; 以悬起(允许)PendSV中断239.240. CPSIE I ; 开中断(前面已经讲解过)任务切换当任务放弃 CPU的使用权时,就会调用OS

47、_TASK_SW()一般情况下,OS_TASK_SW()是做任务切换。但在M3中,任务切换的工作都被放到PendSV的中断处理服务中去做以加快处理速度,因此OS_TASK_SW()只需简单的悬起(允许)PendSV 中断即可。当然,这样就只有当再次开中断的时候,PendSV中断处理函数才能执行。OS_TASK_SW()是由OS_Sched()(此函数在OS_CORE.C)调用。241./*任务级调度器*/242.void OS_Sched (void)243.244.#if OS_CRITICAL_METHOD = 3245. OS_CPU_SR cpu_sr = 0;246.#endif247.248. OS_ENTER_CRITICAL();249. if (OSIntNesting = 0) /如果没中断服务运行250. if (OSLockNesting = 0) /调度器没上锁251. OS_SchedNew(); /查找最高优先级就绪任务252. /见 os_core.c ,会修改 OSPrioHighRdy253. if (OSPrioH

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 企业管理 > 管理学资料

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报