找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 8477|回复: 4
收起左侧

UCos-ii在STM32上的移植详解

[复制链接]
ID:419321 发表于 2018-11-1 20:01 | 显示全部楼层 |阅读模式
UCos-ii_在STM32上的移植详解,通过本例程可以学习一下ucosii!
下载代码
stm32标准外设库是stm32全系列芯片的外设驱动,有了它可以大大加速我们开发stm32。
首先从 st公司的网站下载最新的stm32标准外设库,写本文时最新的版本是V3.5.0。
解压该zip文件,得到如下文件夹和文件
STM32F10x_StdPeriph_Lib_V3.5.0\
_htmresc
Libraries
Project
Utilities
Release_Notes.html
stm32f10x_stdperiph_lib_um.chm
其中Libraries包含库的源代码,Project包含 stm32 各个外设的使用范例和一个工程模板,Utilities是使用st公司评估板的例子, stm32f10x_stdperiph_lib_um.chm教我们怎么用标准外设库。

工程目录结构
既然准备使用32位单片机, 应该是个不小项目, 因此工程目录也应该做个规划。这里我推荐一下我所使用的目录结构。假设工程名字叫template,建一个名为template 的文件夹,该目录下有个3 个固定文件夹doc,src,include,doc用来存放工程相关的资料文件,src放源代码,在 src下每个功能模块一个文件夹,include放各个模块都要使用的公共头文件。 output放编译输出文件,内含两个子文件夹obj和 list。
template\
   doc
   src
   include
   output\obj
             \list
整理库代码
由于Libraries下的CMSIS文件夹中很多代码是和编译器及芯片相关的,导致文件夹多且深度大,不利于工程维护,实际上一个项目往往是用固定的编译器和芯片,因此有必要对库进行整理。

在 src下建立 libstm32目录
1. 把 Libraries\STM32F10x_StdPeriph_Driver\下的内容拷贝到libstm32目录

2. 在 libstm32 目录下建立 cmsis文件夹,把
Libraries\CMSIS\CM3\CoreSupport\下的core_cm3.c,core_cm3.h;
Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\下的 stm32f10x.h,system_stm32f10x.c,system_stm32f10x.h 拷贝到 cmsis文件夹中。
3. 根据你所选的芯片类型,将
Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\arm\下对应的启动文件拷贝到cmsis文件夹中。这里我拷贝的是startup_stm32f10x_hd.s(大容量型stm32芯片的启动文件)。
下面对该库文件做个简单介绍:
Libraries\STM32F10x_StdPeriph_Driver\下的内容很好理解就是stm32 的各个外设模块驱动代码。
    misc.h和 misc.c 是和 CM3 内核有关的NVIC 和SysTick 的驱动代码。   
    Libraries\CMSIS 下是什么呢?cmsis英文全称:Cortex Microcontroller
Software Interface Standard,是Cortex系列处理器硬件抽象层,可以理解为cortex内核的软件接口。
    core_cm3.c, core_cm3.h
它们的目录名为 CoreSupport,说明这两个文件是CM3内核支撑文件,其他使用 CM3 内核的芯片也可以用,不一定是stm32。这两个文件用来获取设置CM3 内核,配置一些内核寄存器。
stm32f10x.h, system_stm32f10x.c, system_stm32f10x.h 和
startup_stm32f10x_hd.s在 DeviceSupport目录下,说明这几个文件是和具体的芯片有关的,也就是stm32 芯片的支撑文件。其中stm32f10x.h 是标准外设库的入口,使用标准外设库的代码中必须包含该头文件。system_stm32f10x.c,
system_stm32f10x.h 这两个文件提供函数用来初始化stm32 芯片,配置 PLL、系统时钟和内置flash 接口。startup_stm32f10x_hd.s是大容量型stm32 芯片的启动文件。 建立工程   
使用keil MDK(我使用 4.12版)在 template 目录下建立工程,工程名为template。选一个 stm32 系列的芯片,哪一个都无所谓(我选的是STM32F101RC,因为我的板子就是用这个芯片),接下来要注意的是当弹出是否拷贝启动代码到工程文件夹时要选No,因为标准外设库里已经有启动代码了。
将 UV4 中project window 里的顶层目录名改为template,并将第一个group名改为libstm32。把libstm32目录下所有.c和.s 文件加载到工程里的libstm32。 在 src下建立一个init目录用来放置系统初始化代码。把Project\STM32F10x_StdPeriph_Template\下的 stm32f10x_it.c拷贝到 init文件夹中,stm32f10x_it.h,stm32f10x_conf.h 拷贝到include文件夹中。
stm32f10x_it.c,stm32f10x_it.h 是中断服务程序文件。stm32f10x_conf.h 是标准外设库的配置文件,对于工程中不需要的外设,可以注释掉里面的包含的头文件。这里我建议先仅留下stm32f10x_gpio.h,stm32f10x_rcc.h,misc.h,用到什么再打开什么,这样编译起来快一点,当然也可都留着。

使用stm32标准外设库
事实上,stm32标准外设库的使用在stm32f10x_stdperiph_lib_um.chm中的How to use the Library一节中已有说明,下面我把其中的步骤罗列一下:
1. 根据所选芯片,把Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\arm中的启动代码加到工程中,这一步在上面已经做过了。
2. 在stm32f10x.h 的66-73 行,根据所选芯片类型,去掉相应注释,这里我去掉STM32F10X_HD 行的注释(大容量型 stm32芯片)。
3. 去掉105 行的USE_STDPERIPH_DRIVER 注释,启用 stm32 标准外设库。
4. 在system_stm32f10x.c的110-115行,根据所选芯片主频,去掉相应注释,默认 SYSCLK_FREQ_72MHz注释已去掉,如果你的芯片主频是72MHz,就不用做修改了,这里我的芯片是36MHz,注释SYSCLK_FREQ_72MHz,去掉SYSCLK_FREQ_36MHz注释。


跑马灯程序
现在可以使用stm32 标准外设库了,下面以一个简单的跑马灯程序说明。
在 init目录下建立main.c 作为系统入口。
在 src下建立一个bsp目录用来放置板级支持代码,建立led.c,led.h。
代码如下:
led.h

#ifndef _LED_H_
#define _LED_H_

#include <stdint.h>

#define LED_0     0
#define LED_1     1
#define LED_2     2

void led_init(void);
void led_on(uint32_t n);
void led_off(uint32_t n);

#endif

led.c

#include "stm32f10x.h"
#include "led.h"

void led_init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
  GPIO_Init(GPIOC, &GPIO_InitStructure);
}




void led_on(uint32_t n) {
switch (n) {
case LED_0:
     GPIO_SetBits(GPIOC, GPIO_Pin_6);
      break;
    case LED_1:
      GPIO_SetBits(GPIOC, GPIO_Pin_7);
      break;
    case LED_2:
      GPIO_SetBits(GPIOC, GPIO_Pin_8);
      break;
    default:
      break;
  }
}

void led_off(uint32_t n){
  switch (n) {
    case LED_0:
      GPIO_ResetBits(GPIOC, GPIO_Pin_6);
      break;
    case LED_1:                             
      GPIO_ResetBits(GPIOC, GPIO_Pin_7);
     break;
    case LED_2:
      GPIO_ResetBits(GPIOC, GPIO_Pin_8);
      break;
    default:
     break;
}
}

main.c
#include "led.h"
static void delay(uint32_t ms){
uint32_t count = 8000;
  while (ms--) {
    while (count--);
    count = 8000;
  }
}

int main(void){
  led_init();
   for (;;) {
    led_on(LED_0);
   led_off(LED_1);
  led_off(LED_2);
  delay(1000);

  led_off(LED_0);
  led_on(LED_1);
  led_off(LED_2);
  delay(1000);

  led_off(LED_0);
  led_off(LED_1);
  led_on(LED_2);  
  delay(1000);
}
}

在 project中建立init, bsp 组,并将各种代码加入。在工程的Options 中, c/c++
选项卡的 Include Paths 中添加.\include;  .\src\libstm32\cmsis; .\src\libstm32\inc;
.\src\bsp;。
Output选项卡 Select Folder for Objects 中选.\output\obj。
Listing 选项卡 Select Folder for Listings中选.\output\list。
Debug选项卡选use ULINK Cortex Debugger, Run to main()打钩,这一步大家可以根据自己手上的仿真器做不同选择。编译运行。 ucosii在stm32 上的移植详解

虽然目前网上已经有不少关于ucosii在stm32 上的移植版本,包括micrium也有官方移植版本。但这些版本具体是怎么移植出来的,又该怎么基于移植好的ucosii开发应用软件,网上介绍的并不多。这里介绍一下我的移植经历,希望对大家有所帮助。
我的移植基本上是从零开始的。首先想要做好移植,有两方面的内容是必须要了解。1.目标芯片;2.ucosii 内核原理。
虽然我们移植的目标芯片是stm32,但操作系统的移植基本是针对Cortex-M3内核(以下简称CM3)而言的,所以我们只需了解CM3 内核就好了。stm32芯片就是CM3内核加上各种各样的外设。
怎么才能了解CM3 呢?看一本书<<ARM Cortex-M3 权威指南>>(宋岩译,网上多的很)就好了,很多同学可能想,看完这本书移植的新鲜劲都没了,因此我把该书和移植有关的章节都列了出来,并对其中的重点内容进行介绍,我数了数相关章节还不到100页,就这点内容,总要看了吧。
相关章节如下:
chapter2 Cortex-M3 概览
2.1 - 2.9
主要了解Cortex-M3的概貌。刚开始看时不用追求全部理解,后面会有详细介绍,很多内容多看几遍就明白。其中2.8 指令集,只要了解,CM3只使用thumb2就 ok了。
chapter3 Cortex-M3 基础
3.1 寄存器组
    R0-R12: 通用寄存器
R13: 堆栈寄存器
有两个,MSP 和 PSP,同时只能看见一个 引用R13时,引用的是正在使用的那个
MSP:可用于异常服务和应用程序
PSP:只能用于应用程序
系统复位后,用的堆栈指针是MSP。
  
R14: 连接寄存器,又名LR,存储返回地址
R15: 程序计数寄存器,又名PC
3.2 特殊功能寄存器
程序状态字寄存器组(PSRs)
中断屏蔽寄存器组(PRIMASK, FAULTMASK, BASEPRI)
控制寄存器(CONTROL)
程序状态字寄存器组(PSRs)分为
应用程序 PSR(APSR)
中断号 PSR(IPSR)
执行 PSR(EPSR)     每个都是32位,由于这3 个寄存器有效位是错开的,因此可以组合访问。
中断屏蔽寄存器组(PRIMASK, FAULTMASK, BASEPRI),这三个寄存器用于
控制异常的使能和除能。
控制寄存器(CONTROL)它有两个作用:
1.定义特权级别
2.选择当前使用哪个堆栈指针
3.3 操作模式和特权极别
操作模式: 处理者模式和线程模式
异常处理:处理者模式
主程序:线程模式
ucosii 不区分特权级和用户级,程序始终工作在特权级
这两个堆栈指针的切换是全自动的,就在出入异常服务例程时由硬件处理。
3.4 - 3.7
没什么好讲的,需要看。
3.8 复位序列
0x00000000 MSP 初值
0x00000004 PC 初值 复位向量
chapter7 异常
7.1 异常类型
分为系统异常(编号1-15)和外部中断(大于15)
7.2 优先级
CM3支持3 个固定的高优先级和多达256 级的可编程优先级。
在 NVIC 中,每个中断都有一个优先级配置寄存器(1 个byte),用来配置该
中断的优先级。但该寄存器并不是每个位都被使用,不同制造商生产的芯片不相同,
譬如stm32使用 4位,也就是说stm32支持16 个可编程优先级(参考: chapter9)。  
注意该寄存器是以 MSB对齐的,因此 stm32每个中断的优先级配置寄存器7:4
位有效,3:0 位无效。
对于优先级,CM3 又分为抢占优先级和亚优先级,
NVIC中的应用程序中断及复位控制寄存器(AIRCR)的优先级分组(10:8)描述了
如何划分抢占优先级和亚优先级。
什么意思?以stm32 为例,优先级配置寄存器不是7:4位有效吗,如果 AIRCR
中的优先级分组值为4,则优先级配置寄存器的7:5 位确定抢占优先级,位4 确定
亚优先级。此时所有中断有8个抢占优先级,每个抢占优先级有2 个亚优先级。
抢占优先级高的中断可以抢占抢占优先级低的中断,即抢占优先级决定了中断是
否可以嵌套。
相同抢占优先级的中断不能嵌套,但当抢占优先级相同的异常有不止一个到来
时,就优先响应亚优先级最高的异常。
参考附录D  
表D.9 中断优先级寄存器阵列 0xE000_E400 - 0xE000_E4EF 共240 个。
表D.16 系统异常优先级寄存器 0xE000_ED18 - 0xE000_ED23 共12 个。
优先级相同,看中断号,中断号小的优先。
7.3 向量表   
初始在0x00000000 处,可以通过向量表偏移量寄存器(VTOR)(地址:
0xE000_ED08)更改,一般无需更改。
7.4 中断输入及挂起行为
需要看。
7.5 Fault异常
可不看。
7.6 SVC和PendSV
SVC
SVC 主要用在分特权级和用户级的操作系统,ucosii 不区分特权级和用户级,
可以不管这个东西。
这里说点题外话,一开始我很奇怪为什么会提供这种中断,因为这种中断一般
都是用在大型的操作系统上,如linux系统上,可CM3 又不提供MMU,应该是无
法移植linux系统。后来我才知道uclinux是针对没有MMU的嵌入式系统而设计的,
不过还是很怀疑有人会在像stm32这种芯片上用uclinux。
PendSV
PendSV 中断主要做上下文切换,也就是任务切换,是ucosii 移植过程中最重要
的中断。
主要有两点:
1.PendSV中断是手工往NVIC的PendSV悬起寄存器中写1产生的(由OS
写)。
2.PendSV中断优先级必须设为最低。
在讲移植代码时会介绍具体是如何做的。
对于7.6的PendSV 部分应认真研读一下。
chapter8 NVIC 与中断控制
NVIC负责芯片的中断管理,它和CM3内核紧密相关。
如果对于CM3 中断配置不是很了解,可以看看 8.1, 8.2, 8.3, 8.4 节。
8.7 节讲述了 SysTick定时器,需要看。
chapter9 中断的具体行为
9.1 中断/异常的响应序列
当CM3 开始响应一个中断时
1.xPSR, PC, LR, R12以及 R3‐R0 入栈
2.取向量
     3.选择堆栈指针 MSP/PSP,更新堆栈指针SP,更新连接寄存器LR,更新程序
计数器 PC
对移植ucosii 来说,需要注意1,3
9.2 异常返回
在CM3 中,进入中断时,LR 寄存器的值会被自动更新。9.6 节对更新后的值进
行说明。这里统称EXC_RETURN。返回时通过把 EXC_RETURN往PC里写来识
别返回动作的。因为EXC_RETURN是一个特殊值,所以对于CM3,汇编语言就
不需要类似reti 这种指令,而用C语言开发时,不需要特殊编译器命令指示一个函数为中断服务程序。实际上,中断服务程序如果是c代码编写,汇编成汇编代码,
函数结尾一般是reti。
9.3 嵌套的中断
只要注意:中断嵌套不能过深即可。
9.4 和9.5
这两节说明CM3对中断的响应能力大大提高了,主要是硬件机制的改进。但对
移植来说,并不需要关注。
9.6 异常返回值
对不同状态进入中断时,LR 寄存器的值进行说明,需要看。这里有一点需要注
意,该点在讲移植代码时再介绍。
9.7 和9.8
对移植来说,并不需要关注。
chapter10 Cortex-M3的低层编程
这一章仅需关注 10.2节,因为对移植来说汇编与C的接口是必须面对的。
10.2 汇编与 C 的接口
有两点需要知道:
1.当主调函数需要传递参数(实参)时,它们使用R0‐R3。其中 R0传递第一
个,R1 传递第 2个……在返回时,把返回值写到R0 中。
2.在函数中,用汇编写代码时,R0-R3, R12可以随便使用,而使用R4‐R11,
则必须先PUSH,后POP。
以上内容和移植多少都有些关系,刚开始看,可能不太明白,多看几遍就好了。 ucosii在stm32 上的移植详解 2  

在详解1 中主要讲了移植需要用到的CM3 内核知识,本文讲一讲ucosii 的原理
和代码组成。ucosii最经典的学习资料莫过于邵贝贝老师的<<嵌入式实时操作系统
uc/os-ii(第2 版)>>,我想这本书对学 ucosii 已经足够了,因为他把ucosii V2.55代
码都讲了一遍。移植前应该好好看看此书。
下面说说我对ucosii 的理解。应该说 ucosii 这个内核还是比较简单的,基本可
以分为任务调度,任务同步和内存管理三个部分。
任务调度
ucosii 为保证实时性,给每个任务分配一个不同的优先级。当发生任务切换时,
总是切换到就绪的最高优先级任务。有2种情况会发生任务切换。
1.任务等待资源就绪或自我延时;
2.退出中断;
情况1可以理解为任务主动放弃cpu 的使用权。
情况2可以理解为中断后,某种资源可能就绪了,需要任务切换。
需要注意的是SysTick中断,这个中断是os的“心跳”,必须得有。这样就使得
cpu 会发生周期性地做任务切换。由于 ucosii不支持时间片轮转调度,因此在该中
断中必须做的工作仅有os的时间管理。也就是调用OSTimeTick()。
任务同步
任务同步和大多数操作系统的做法差不多,如果学过操作系统或是有多线程编
程经验的话,应该很好理解。无非是任务A因为某个资源未就绪,就放弃cpu 使用
权,等任务B或是中断使该资源就绪,当再次任务进行切换时如果任务A 优先级最
高,则任务A继续执行。具体怎么实现就看邵老师的书吧。
内存管理
ucosii 的内存管理比较简单,就不说了。
下面看看ucosii 代码组成:
os_core.c是 ucosii 的核心,它包含了内核初始化,任务切换,事件块管理等,
其中事件块是各个同步量(这里我把互斥量,信号量,邮箱,队列统称为同步量,
不是很科学,图个方便。事件标志组不是以事件块为基础的,不过原理也差不多)
的基础。
os_task.c  任务管理代码。
os_flag.c
os_mbox.c
os_mutex.c
os_q.c
os_sem.c  各个同步量管理代码。
os_mem.c  内存管理代码。
os_time.c  时间管理代码,主要做各种延时。
os_tmr.c
定时器管理代码,这部分代码时从V2.81版才开始有的,邵老师的书讲的是
V2.55版的代码,是没有这部分内容的。如果前面的代码都理解的话,这部分代码
也是不难理解的。一个定时器大体由3 部分组成:定时时间,回调函数和属性。当
定时时间到了的话,就进行一次回调函数的处理,定时器属性说明定时器是周期性
的定时还是只做一次定时。如果用户使能了OS_TMR_EN,ucosii 会在内部创建一
个定时器任务,负责处理各个定时器。这个任务一般应该由硬件定时器的中断函数
中调用 OSTmrSignal()去激活。所以从本质上说os_tmr.c中的定时器是由一个硬件
定时器分化出来的。
默认情况下是由 SysTick中断里通过 OSTimeTickHook()去激活定时器任务的。  
移植相关文件
os_cpu.h:       进行数据类型定义,处理器相关代码和几个函数原型。
os_cpu_c.c:     定义一些用户hook 函数。
os_cpu_a.asm: 移植需要用汇编代码完成的函数,主要就是任务切换函数。
os_dbg.c:       内核调试相关数据和函数,可以不改。
ucosii 内核就介绍到这里。
ucosii在stm32 上的移植详解 3  
移植详解1和2 中主要讲了移植需要用到的基础知识,本文则对具体的移植过
程进行介绍。
首先从 micrium网站上下载官方移植版本(编译器使用ARM/Keil的,V2.86 版
本,V2.85有问题)。
    下载地址:micrium点com/page/downloads/ports/st/stm32
    解压缩后得到如下文件夹和文件:
    Micrium\
       AppNotes
       Licensing
       Software
       ReadMe.pdf
AppNotes包含ucosii移植说明文件。这两个文件中我们仅需关心
Micrium\AppNotes\AN1xxx-RTOS\AN1018-uCOS-II-Cortex-M3\AN-1018.pdf。因
为这个文件对ucosii 在CM3内核移植过程中需要修改的代码进行了说明。
Licensing包含ucosii使用许可证。
Software 下有好几个文件夹,在本文的移植中仅需关心uCOS-II即可。
CPU: stm32 标准外设库
EvalBoards: micrium 官方评估板相关代码
uc-CPU: 基于 micrium官方评估板的 ucosii 移植代码
uC-LCD:micrium官方评估板LCD驱动代码
uc-LIB: micrium官方的一个库代码
uCOS-II: ucosii 源代码
uC-Probe: 和uC-Probe 相关代码
ReadMe.pdf就不说了。
好了,官方的东西介绍完了,该我们自己建立工程着手移植了。关于建立工程,
并使用stm32 标准外设库在我之前的文章《stm32标准外设库使用详解》已有介绍,
这里请大家下载其中模板代码(http://download.csdn.net/source/3448543),本文
的移植是基于这个工程的。
建立文件夹
template\src\ucosii
template\src\ucosii\src
template\src\ucosii\port; 把 Micrium\Software\uCOS-II\Source 下的文件拷贝至template\src\ucosii\src;
把Micrium\Software\uCOS-II\Ports\ARM-Cortex-M3\Generic\RealView下的文
件拷贝至 template\src\ucosii\port;
ucosii\src下的代码是ucosii 中无需修改部分
ucosii\port下的代码是移植时需要修改的。为防止对源码的误改动造成移植失
败,可以把ucosii\src下的代码文件设为只读。
这里根据AN-1018.pdf和移植详解1、2 中介绍的移植基础知识,对ucosii\port
下的代码解释一下。
os_cpu.h
#ifdef   OS_CPU_GLOBALS
#define  OS_CPU_EXT
#else
#define  OS_CPU_EXT  extern
#endif
typedef unsigned char BOOLEAN;
typedef unsigned char INT8U;
typedef signed char INT8S;
typedef unsigned short INT16U;
typedef signed short INT16S;
typedef unsigned int INT32U;
typedef signed int INT32S;
typedef float FP32;
typedef double FP64;
就不解释了。
typedef unsigned int OS_STK;  
typedef unsigned int OS_CPU_SR;
因为CM3是32位宽的,所以OS_STK(堆栈的数据类型)被类型重定义为
unsigned int。
因为CM3的状态寄存器(xPSR)是32 位宽的,因此OS_CPU_SR 被类型重
定义为unsigned int。OS_CPU_SR 是在 OS_CRITICAL_METHOD方法 3中保存
cpu 状态寄存器用的。在 CM3 中,移植 OS_ENTER_CRITICAL(),
OS_EXIT_CRITICAL()选方法3 是最合适的。
#define  OS_CRITICAL_METHOD   3 #if OS_CRITICAL_METHOD == 3
#define  OS_ENTER_CRITICAL()  {cpu_sr = OS_CPU_SR_Save();}
#define  OS_EXIT_CRITICAL()   {OS_CPU_SR_Restore(cpu_sr);}
#endif
   具体定义宏OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL(),其中
OS_CPU_SR_Save()和OS_CPU_SR_Restore()是用汇编代码写的,代码在
os_cpu_a.asm 中,到时再解释。
#define  OS_STK_GROWTH        1
CM3中,栈是由高地址向低地址增长的,因此OS_STK_GROWTH 定义为1。  
#define OS_TASK_SW() OSCtxSw()
   定义任务切换宏,OSCtxSw()是用汇编代码写的,代码在os_cpu_a.asm中,到
时再解释。
#if OS_CRITICAL_METHOD == 3                        
OS_CPU_SR  OS_CPU_SR_Save(void);
void       OS_CPU_SR_Restore(OS_CPU_SR cpu_sr);
#endif
void       OSCtxSw(void);
void       OSIntCtxSw(void);
void       OSStartHighRdy(void);
void       OS_CPU_PendSVHandler(void);
  
void       OS_CPU_SysTickHandler(void);
void       OS_CPU_SysTickInit(void);
INT32U     OS_CPU_SysTickClkFreq(void);
申明几个函数,这里要注意最后三个函数需要注释掉,为什么呢?
OS_CPU_SysTickHandler()定义在os_cpu_c.c中,是 SysTick 中断的中断处
理函数,而stm32f10x_it.c,中已经有该中断函数的定义SysTick_Handler(),这里
也就不需要了,是不是很奇怪官方移植版为什么会这样弄吧,后面我会解释的。
OS_CPU_SysTickInit()定义在os_cpu_c.c 中,用于初始化 SysTick定时器,它
依赖于 OS_CPU_SysTickClkFreq(),而此函数我们自己会实现,所以注释掉。
OS_CPU_SysTickClkFreq()定义在BSP.C (Micrium\Software\EvalBoards)中,
而本文移植中并未用到BSP.C,后面我们会自己实现,因此可以把它注释掉。

os_cpu_c.c
ucosii 移植时需要我们写 10个相当简单的C函数。
OSInitHookBegin()
OSInitHookEnd()
OSTaskCreateHook()
OSTaskDelHook()
OSTaskIdleHook()
OSTaskStatHook()
OSTaskStkInit()
OSTaskSwHook()
OSTCBInitHook()
OSTimeTickHook()
这些函数除了OSTaskStkInit(),都是一些hook 函数。这些hook 函数如果不使
能的话,都不会用上,也都比较简单,看看就应该明白了,所以就不介绍。
下面就说一说OSTaskStkInit()。说之前还是得先说一下任务切换,因为初始化
任务堆栈,是为任务切换服务的。代码在正常运行时,一行一行往下执行,怎么才
能跑到另一个任务(即函数)执行呢?首先大家可以回想一下中断过程,当中断发
生时,原来函数执行的地方(程序计数器PC、处理器状态寄存器及所有通用寄存
器,即当前代码的现场)被保存到栈里面去了,然后开始取中断向量,跑到中断函
数里面执行。执行完了呢,想回到原来函数执行的地方,该怎么办呢,只要把栈中
保存的原来函数执行的信息恢复即可(把栈中保存的代码现场重新赋给cpu的各个
寄存器),一切就都回去了,好像什么事都没发生一样。这个过程大家应该都比较
熟悉,任务切换和这有什么关系,试想一下,如果有3 个函数 foo1(), foo2(), foo3()
像是刚被中断,现场保存到栈里面去了,而中断返回时做点手脚(调度程序的作用),
想回哪个回哪个,是不是就做了函数(任务)切换了。看到这里应该有点明白
OSTaskStkInit()的作用了吧,它被任务创建函数调用,所以要在开始时,在栈中作
出该任务好像刚被中断一样的假象。(关于任务切换的原理邵老师书中的3.06 节有
介绍)。
那么中断后栈中是个什么情形呢,<<ARM Cortex-M3 权威指南>>中9.1.1 有介
绍,xPSR,PC,LR,R12,R3-R0被自动保存到栈中的,R11-R4 如果需要保存,
只能手工保存。因此OSTaskStkInit()的工作就是在任务自己的栈中保存cpu的所有
寄存器。这些值里R1-R12都没什么意义,这里用相应的数字代号(如R1 用
0x01010101)主要是方便调试。
其他几个:
xPSR = 0x01000000L,xPSR T位(第24位)置1,否则第一次执行任务时
Fault,
PC 肯定得指向任务入口,
R14 = 0xFFFFFFFEL,最低4位为E,是一个非法值,主要目的是不让使用
R14,即任务是不能返回的。
R0 用于传递任务函数的参数,因此等于p_arg。
OS_STK *OSTaskStkInit (void (*task)(void *p_arg), void *p_arg, OS_STK
*ptos, INT16U opt) {
OS_STK *stk;
(void)opt;                        /* 'opt' is not used, prevent warning */
stk       = ptos;                 /* Load stack pointer                 */
/* Registers stacked as if auto-saved on exception */
*(stk)    = (INT32U)0x01000000L;  /* xPSR        */
*(--stk)  = (INT32U)task;         /* Entry Point */
/* R14 (LR) (init value will cause fault if ever used)*/
*(--stk)  = (INT32U)0xFFFFFFFEL;   
*(--stk)  = (INT32U)0x12121212L;  /* R12 */
*(--stk)  = (INT32U)0x03030303L;  /* R3  */
*(--stk)  = (INT32U)0x02020202L;  /* R2  */
*(--stk)  = (INT32U)0x01010101L;  /* R1  */
*(--stk)  = (INT32U)p_arg;        /* R0 : argument  */
/* Remaining registers saved on process stack */
*(--stk)  = (INT32U)0x11111111L;  /* R11 */
*(--stk)  = (INT32U)0x10101010L;  /* R10 */
*(--stk)  = (INT32U)0x09090909L;  /* R9  */
*(--stk)  = (INT32U)0x08080808L;  /* R8  */
*(--stk)  = (INT32U)0x07070707L;  /* R7  */
*(--stk)  = (INT32U)0x06060606L;  /* R6  */
*(--stk)  = (INT32U)0x05050505L;  /* R5  */
*(--stk)  = (INT32U)0x04040404L;  /* R4  */
return (stk);
}
把 OS_CPU_SysTickHandler(), OS_CPU_SysTickInit()注释掉。
#define  OS_CPU_CM3_NVIC_ST_CTRL    (*((volatile INT32U *)0xE000E010))  
#define  OS_CPU_CM3_NVIC_ST_RELOAD  (*((volatile INT32U
*)0xE000E014))  
#define  OS_CPU_CM3_NVIC_ST_CURRENT (*((volatile INT32U
*)0xE000E018))  
#define  OS_CPU_CM3_NVIC_ST_CAL     (*((volatile INT32U *)0xE000E01C))   
#define  OS_CPU_CM3_NVIC_ST_CTRL_COUNT      0x00010000
#define  OS_CPU_CM3_NVIC_ST_CTRL_CLK_SRC 0x00000004
#define  OS_CPU_CM3_NVIC_ST_CTRL_INTEN       0x00000002
#define  OS_CPU_CM3_NVIC_ST_CTRL_ENABLE   0x00000001
把上面这些宏定义也注释掉,因为它们都用于OS_CPU_SysTickHandler(),
OS_CPU_SysTickInit()。
os_cpu_a.asm
这个文件包含着必须用汇编写的代码。
    EXTERN  OSRunning    ; External references
    EXTERN  OSPrioCur
    EXTERN  OSPrioHighRdy
    EXTERN  OSTCBCur
    EXTERN  OSTCBHighRdy
    EXTERN  OSIntNesting
    EXTERN  OSIntExit
    EXTERN  OSTaskSwHook
    申明这些变量是在其他文件定义的,本文件只做引用(有几个好像并未引用,不
过没有关系)。
    EXPORT  OS_CPU_SR_Save    ; Functions declared in this file
    EXPORT  OS_CPU_SR_Restore
    EXPORT  OSStartHighRdy
    EXPORT  OSCtxSw
    EXPORT  OSIntCtxSw
    EXPORT  OS_CPU_PendSVHandler
    申明这些函数是在本文件中定义的。
NVIC_INT_CTRL   EQU     0xE000ED04   ;中断控制及状态寄存器ICSR的地址
NVIC_SYSPRI14   EQU     0xE000ED22   ;PendSV 优先级寄存器的地址 NVIC_PENDSV_PRI  EQU   0xFF   ;PendSV中断的优先级为255(最低)
NVIC_PENDSVSET   EQU   0x10000000   ;位28为1
定义几个常量,类似C语言中的#define预处理指令。
OS_CPU_SR_Save
MRS     R0, PRIMASK   ;读取 PRIMASK 到R0 中,R0为返回值
CPSID   I             ;PRIMASK=1,关中断(NMI和硬 fault可以响应)
BX      LR            ;返回
OS_CPU_SR_Restore
MSR     PRIMASK, R0   ;读取 R0到 PRIMASK中,R0为参数
BX      LR            ;返回
OSStartHighRdy()由OSStart()调用,用来启动最高优先级任务,当然任务必须在
OSStart()前已被创建。
OSStartHighRdy
    ;设置PendSV中断的优先级 #1
LDR     R0, =NVIC_SYSPRI14    ;R0 = NVIC_SYSPRI14
LDR     R1, =NVIC_PENDSV_PRI  ;R1 = NVIC_PENDSV_PRI
STRB    R1, [R0] ;*(uint8_t *)NVIC_SYSPRI14 = NVIC_PENDSV_PRI
;设置PSP为 0 #2
MOVS    R0, #0                ;R0 = 0
MSR     PSP, R0               ;PSP = R0
;设置OSRunning为TRUE
    LDR     R0, =OSRunning        ;R0 = OSRunning
    MOVS    R1, #1                ;R1 = 1
STRB    R1, [R0]              ;OSRunning = 1
;触发PendSV中断 #3
LDR     R0, =NVIC_INT_CTRL    ;R0 = NVIC_INT_CTRL
LDR     R1, =NVIC_PENDSVSET   ;R1 = NVIC_PENDSVSET
STR     R1, [R0]   ;*(uint32_t *)NVIC_INT_CTRL = NVIC_PENDSVSET
CPSIE   I             ;开中断   
OSStartHang   ;死循环,应该不会到这里
B       OSStartHang  

#1.PendSV中断的优先级应该为最低优先级,原因在<<ARM Cortex-M3 权威指南
>>的 7.6节已有说明。
#2.PSP 设置为 0,是告诉具体的任务切换程序(OS_CPU_PendSVHandler()),
这是第一次任务切换。做过切换后PSP就不会为 0 了,后面会看到。
#3.往中断控制及状态寄存器ICSR(0xE000ED04)第28位写1即可产生PendSV中
断。这个<<ARM Cortex-M3 权威指南>>8.4.5 其它异常的配置寄存器有说明。
当一个任务放弃 cpu 的使用权,就会调用OS_TASK_SW()宏,而
OS_TASK_SW()就是 OSCtxSw()。OSCtxSw()应该做任务切换。但是在CM3 中,
所有任务切换都被放到PendSV 的中断处理函数中去做了,因此OSCtxSw()只需简
单的触发PendSV 中断即可。OS_TASK_SW()是由 OS_Sched()调用。
void  OS_Sched (void)
{
#if OS_CRITICAL_METHOD == 3
    OS_CPU_SR  cpu_sr = 0;
#endif

OS_ENTER_CRITICAL();
    if (OSIntNesting == 0) {
        if (OSLockNesting == 0) {
            OS_SchedNew();
            if (OSPrioHighRdy != OSPrioCur) {
                OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
#if OS_TASK_PROFILE_EN > 0
                OSTCBHighRdy->OSTCBCtxSwCtr++;
#endif
                OSCtxSwCtr++;  
                OS_TASK_SW();    /* 触发PendSV 中断 */
            }
        }
    }
    /* 一旦开中断,PendSV中断函数会执行(当然要等更高优先级中断处理完) */
   OS_EXIT_CRITICAL();   
} OSCtxSw
    ;触发PendSV中断
    LDR     R0, =NVIC_INT_CTRL    ;R0 = NVIC_INT_CTRL  
    LDR     R1, =NVIC_PENDSVSET   ;R1 = NVIC_PENDSVSET
    STR     R1, [R0]              ;*(uint32_t *)NVIC_INT_CTRL = NVIC_PENDSVSET
    BX      LR                    ;返回
    当一个中断处理函数退出时,OSIntExit()会被调用来决定是否有优先级更高的任
务需要执行。如果有 OSIntExit()对调用OSIntCtxSw()做任务切换。
OSIntCtxSw
    ;触发PendSV中断
    LDR     R0, =NVIC_INT_CTRL
    LDR     R1, =NVIC_PENDSVSET
    STR     R1, [R0]
    BX      LR
    看到这里有些同学可能奇怪怎么OSCtxSw()和 OSIntCtxSw()完全一样, 事实上,
这两个函数的意义是不一样的,OSCtxSw()做的是任务之间的切换,如任务A因为
等待某个资源或是做延时切换到任务B,而OSIntCtxSw()则是中断退出时,由中断
状态切换到另一个任务。由中断切换到任务时, CPU寄存器入栈的工作已经做完了,
所以无需做第二次了(参考邵老师书的3.10节)。这里只不过由于CM3的特殊机
制导致了在这两个函数中只要做触发PendSV中断即可,具体切换由PendSV 中断
来处理。
    前面已经说过真正的任务切换是在PendSV中断处理函数里做的,由于CM3在
中断时会有一半的寄存器自动保存到任务堆栈里,所以在PendSV 中断处理函数中
只需保存R4-R11 并调节堆栈指针即可。
PendSV 中断处理函数伪代码如下:
OS_CPU_PendSVHandler()
{
        if (PSP != NULL) {
                Save R4-R11 onto task stack;
                OSTCBCur->OSTCBStkPtr = SP;
        }
        OSTaskSwHook();
        OSPrioCur = OSPrioHighRdy;
        OSTCBCur = OSTCBHighRdy;         PSP = OSTCBHighRdy->OSTCBStkPtr;
        Restore R4-R11 from new task stack;
        Return from exception;  
}

OS_CPU_PendSVHandler             ;xPSR, PC, LR, R12, R0-R3 已自动保存
    CPSID   I                    ;任务切换期间需要关中断
    MRS     R0, PSP              ;R0 = PSP
    ;如果PSP == 0,跳到OS_CPU_PendSVHandler_nosave 执行 #1
    CBZ     R0, OS_CPU_PendSVHandler_nosave   
    ;保存R4-R11 到任务堆栈
    SUBS    R0, R0, #0x20        ;R0 -= 0x20                          
    STM     R0, {R4-R11}         ;保存 R4-R11到任务堆栈
    ;OSTCBCur->OSTCBStkPtr = SP;
    LDR     R1, =OSTCBCur        ;R1 = &OSTCBCur
    LDR     R1, [R1]             ;R1 = *R1 (R1 = OSTCBCur)
    STR     R0, [R1]             ;*R1 = R0 (*OSTCBCur = SP) #2                        
OS_CPU_PendSVHandler_nosave
;调用OSTaskSwHook()
PUSH    {R14}                ;保存R14,因为后面要调用函数            
    LDR     R0, =OSTaskSwHook    ;R0 = &OSTaskSwHook  
    BLX     R0                   ;调用OSTaskSwHook()
    POP     {R14}                ;恢复 R14
    ;OSPrioCur = OSPrioHighRdy;
    LDR     R0, =OSPrioCur       ;R0 = &OSPrioCur
    LDR     R1, =OSPrioHighRdy   ;R1 = &OSPrioHighRdy
    LDRB    R2, [R1]             ;R2 = *R1 (R2 = OSPrioHighRdy)
    STRB    R2, [R0]             ;*R0 = R2 (OSPrioCur = OSPrioHighRdy)
    ;OSTCBCur = OSTCBHighRdy;
    LDR     R0, =OSTCBCur        ;R0 = &OSTCBCur      
    LDR     R1, =OSTCBHighRdy    ;R1 = &OSTCBHighRdy
    LDR     R2, [R1]             ;R2 = *R1 (R2 = OSTCBHighRdy)
    STR     R2, [R0]             ;*R0 = R2 (OSTCBCur = OSTCBHighRdy) LDR     R0, [R2]             ;R0 = *R2 (R0 = OSTCBHighRdy), 此时 R0是新任务
的 SP
;SP = OSTCBHighRdy->OSTCBStkPtr #3  
LDM     R0, {R4-R11}   ;从任务堆栈SP恢复R4-R11
ADDS    R0, R0, #0x20  ;R0 += 0x20
MSR     PSP, R0    ;PSP = R0,用新任务的 SP加载PSP
ORR     LR, LR, #0x04 ;确保 LR 位2 为 1,返回后使用进程堆栈 #4  
CPSIE   I                    ;开中断
BX      LR                   ;中断返回
    END
#1 如果 PSP == 0,说明OSStartHighRdy()启动后第一次做任务切换,而任务刚创
建时R4-R11 已经保存在堆栈中了,所以不需要再保存一次了。
#2 OSTCBStkPtr是任务控制块结构体的第一个变量,所以*OSTCBCur = SP(不是
很科学)就是 OSTCBCur->OSTCBStkPtr = SP;
#3 和#2类似。
#4 因为在中断处理函数中使用的是 MSP,所以在返回任务后必须使用PSP,所以
LR位2 必须为 1。
os_dbg.c
用于系统调试,可以不管。
需要修改的代码就介绍到这里,如果还有不明白之处,就再看看AN-1018.pdf,
邵老师的书和<<ARM Cortex-M3权威指南>>。
ucosii在stm32 上的移植详解 4  
详解3中有一个问题还没解释,就是stm32f10x_it.c 中已经有 SysTick中断函
数的定义SysTick_Handler(),为什么官方版非要弄个OS_CPU_SysTickHandler()。
答案就在启动文件上,一般我们自己开发基于stm32芯片的软件,都会使用标准外
设库CMSIS中提供的启动文件,而官方移植的启动文件却是自己写的,在两个文
件 init.s,vectors.s 中
(Micrium\Software\EvalBoards\ST\STM3210B-EVAL\RVMDK)。init.s 负责进入
main(),vectors.s 设置中断向量。OS_CPU_SysTickHandler 和
OS_CPU_PendSVHandler就是在vectors.s 中被设置的。
我的移植是使用标准外设库CMSIS中startup_stm32f10x_hd.s作为启动文件
的,那该怎么在这个文件中设置OS_CPU_SysTickHandler呢,事实上在
startup_stm32f10x_hd.s文件中,PendSV 中断向量名为PendSV_Handler,所以
只需用OS_CPU_PendSVHandler把所有出现PendSV_Handler的地方替换掉就可
以了。
那么为什么OS_CPU_SysTickHandler 不用这种方式处理呢,这样也就不用注
释 os_cpu.c 中的 OS_CPU_SysTickHandler(),这主要是基于两个原因:
1. startup_stm32f10x_hd.s尽量少该,能不改就不改。
2. 如果保留OS_CPU_SysTickHandler(),在以后开发过程中,改动
OS_CPU_SysTickHandler()中的内容可能性是非常大的,如果一不小把该文件其他
部分改了造成了问题,这个 bug 就非常难查了,所以我一般移植好后就把ucosii的
这些文件设置为只读。
    对于上面的原因1,一开始移植时,我曾做过在PendSV_Handler()中调用
OS_CPU_PendSVHandler(),后来发现这样不行,这是为什么呢?问题出在LR寄
存器上。
PendSV_Handler()
{
        OS_CPU_PendSVHandler();
}
汇编出来的代码会是这样:
PendSV_Handler PROC
   PUSH     {r4,lr}
   BL       OS_CPU_PendSVHandler
   POP      {r4,pc}
ENDP 这样在进入OS_CPU_PendSVHandler之后,LR 寄存器中存放的是指令 POP
{r4,pc}的地址+1。在 OS_CPU_PendSVHandler 中的ORR LR, LR, #0x04 就不会
起作用,也就无法使用PSP,移植因此失败。其实在AN-1018.pdf的 3.04.06中也
有强调OS_CPU_PendSVHandler必须被放置在中断向量表中。 一开始我也没注意。  
到这里移植的大部分工作都做完了,下面剩下的就是把工程配置好,SysTick
中断处理好。
在工程中建立ucosii 组,把 ucosii下的文件都加进该组。这里别忘了把
os_cpu_a.asm 加入。
    在工程的Options中,c/c++选项卡的 Include Paths 中添
加.\src\ucosii\src;.\src\ucosii\port。
    编译工程,会发现缺少app_cfg.h 和 os_cfg.h 文件,app_cfg.h是用来配置应用
软件的,主要是任务的优先级和堆栈大小,中断优先级等信息。目前还没有基于
ucosii 开发应用软件,所以只需在include 文件夹中创建一个空的app_cfg.h 文件即
可。os_cfg.h 是用来配置ucosii 系统的。拷贝
Micrium\Software\EvalBoards\ST\STM3210B-EVAL\RVMDK\OS-Probe\os_cfg.h
到 template\include,对其做如下修改:
#define OS_APP_HOOKS_EN           0
#define OS_DEBUG_EN               0
#define OS_EVENT_MULTI_EN         0
#define OS_SCHED_LOCK_EN          0
#define OS_TICK_STEP_EN           0
#define OS_TASK_CHANGE_PRIO_EN    0  
#define OS_TASK_QUERY_EN          0  
#define OS_TASK_STAT_EN           0
#define OS_TASK_STAT_STK_CHK_EN   0
#define OS_TASK_SUSPEND_EN        0  
#define OS_FLAG_EN                0
#define OS_MBOX_EN                0  
#define OS_TIME_DLY_HMSM_EN       0  
#define OS_TIME_DLY_RESUME_EN     0
#define OS_TIME_GET_SET_EN        0
#define OS_TIME_TICK_HOOK_EN      0
所做的修改主要是把一些功能给去掉,减少内核大小,也利于调试。等移植完
成后,如果需要该功能,再做开启。     接下来就剩下处理好SysTick中断和启动任务了。SysTick是系统的“心跳”,本
质上来说就是一个定时器。先把原来main.c中的内容删除,添加如下代码:
#include "ucos_ii.h"
#include "stm32f10x.h"
static OS_STK startup_task_stk[STARTUP_TASK_STK_SIZE];
static void systick_init(void)
{
        RCC_ClocksTypeDef rcc_clocks;
        RCC_GetClocksFreq(&rcc_clocks);
        SysTick_Config(rcc_clocks.HCLK_Frequency / OS_TICKS_PER_SEC);
}
static void startup_task(void *p_arg)
{
        systick_init();     /* Initialize the SysTick. */
#if (OS_TASK_STAT_EN > 0)
        OSStatInit();      /* Determine CPU capacity. */
#endif
/* TODO: create application tasks here */
         
        OSTaskDel(OS_PRIO_SELF);
}

int main(void)
{
        OSInit();
        OSTaskCreate(startup_task, (void *)0,
              &startup_task_stk[STARTUP_TASK_STK_SIZE - 1],
              STARTUP_TASK_PRIO);
         OSStart();
         return 0;
}
systick_init()用来初始化并启动 SysTick 定时器。
     RCC_GetClocksFreq()用来获取系统时钟。
     SysTick_Config()初始化并使能SysTick定时器。      这里要注意的是 OS_TICKS_PER_SEC,它是每秒钟的 ticks数,如果为1000,
就是1s中1000 个 ticks,也就是说 1ms就会产生一个SysTick中断。系统的时间
片为1ms。
    在邵老师的书中3.11 节已有明确说明,必须在调用OSStart()之后,才能开启时
钟节拍器(SysTick)。一般会把它放在第一个任务(启动任务)中。
    startup_task()用来创建其他应用任务,创建完其他任务后,就会自己删除自己。  
    文件中的STARTUP_TASK_STK_SIZE,STARTUP_TASK_PRIO 需要在
app_cfg.h中定义。代码如下:
/* task priority */
#define STARTUP_TASK_PRIO                          4
/* task stack size */
#define STARTUP_TASK_STK_SIZE                  80
在 stm32f10x_it.c 中,还需要添加SysTick中断的处理代码:
void SysTick_Handler(void)
{  
        OSIntEnter();
        OSTimeTick();
        OSIntExit();
}
    这个代码是仿照OS_CPU_SysTickHandler()中代码的,在邵老师书的 3.11节亦
有说明。这里就不解释。
    至此ucosii在stm32 上的移植已全部完成。
ucosii在stm32 上的移植详解 5  

详解1-4 把移植过程都已经介绍了。接下来的工作是验证移植是否ok 以及如何
基于移植好的ucosii 开发应用程序。前一个问题可以说是后一个问题的特殊情况,
一般我们会创建两个简单的任务,看看任务切换是否成功来验证移植是否ok,因为
任务切换可以说是ucosii 最核心的功能。

任务代码(main.c):
static void task1(void *p_arg)
{
        for (;;)
        {
                led_on(LED_0);
                OSTimeDly(500);
                led_off(LED_0);
                OSTimeDly(500);   
        }
}
static void task2(void *p_arg)
{
        for (;;)
        {
                led_on(LED_1);
                OSTimeDly(500);
                led_off(LED_1);
                OSTimeDly(500);
        }
}
在 startup_task()创建任务:
err = OSTaskCreate(task1, (void *)0,
                   &task1_stk[TASK1_STK_SIZE-1], TASK1_PRIO);        
err = OSTaskCreate(task2, (void *)0,
                   &task2_stk[TASK2_STK_SIZE-1], TASK2_PRIO); 把任务的堆栈大小和优先级写入app_cfg.h,定义任务堆栈,编译调试。
在任务中打断点,用模拟器调试可以发现已经可以做任务切换了。如果有板子,
烧到板子中运行,可以看到两个灯会以1Hz 的频率闪烁。
可以认为移植初步成功,内核其他功能有待在应用中继续验证。
如何基于移植好的ucosii 开发应用程序呢?
    开发应用程序大部分都是为了处理或控制一个真实的物理系统,而真实的物理系
统往往都是模拟系统,为了方便计算机处理,首先需要对系统做离散化处理。针对
ucosii,离散化过程是通过系统“心跳”(SysTick)来实现的。一般应用程序都有多
个任务(不多任务谁用ucosii 啊),任务可以分为周期任务和非周期任务。周期任
务是周期性循环地处理事情的任务,而非周期任务一般是某个条件触发才执行的任
务。这里有一个问题,SysTick的时间是多少合适。SysTick 的时间一般取周期性任
务中周期最短的时间值。譬如说,系统里有3个周期性任务:系统主任务(如处理
pid等,任务周期4ms),键盘扫描任务(任务周期16ms),通信任务(任务周期
128ms),SysTick时间就取4ms。当然在SysTick 时间较小时,要注意系统负荷
问题,这时最好测一下cpu 使用率及各个任务的时间等。
    周期性任务的开发套路是怎么样的呢?看看定时器任务的做法就知道了,代码在
os_tmr.c。首先在 OSTmr_Init()中初始化OSTmrSemSignal,然后 OSTmr_Task()
任务会一直等待OSTmrSemSignal,等 到OSTmrSemSignal 后去处理各个定时器。
那么谁在释放OSTmrSemSignal呢?OSTmrSignal(),这个函数要求放在一定频率
的时钟中断里,默认是在SysTick中断中(如果使能OS_TIME_TICK_HOOK_EN)。
好了,现在我们可以总结总结周期性任务的一般套路了。
首先在任务初始化函数中初始化一个信号量(一般会用信号量),伪代码如下:
void task_init(void)
{
        task_sem = OSSemCreate(0);
}
在任务中等待信号量
void task (void *p_arg)
{
        for (;;)
        {
                OSSemPend(task_sem, 0, &err);                 /* TODO: task handle here */
        }
}
周期性的释放信号量  
OSSemPost(task_sem);
    对于上面所说系统主任务,OSSemPost(task_sem)可以放在SysTick_Handler()
中。所以一般来说 OS_CPU_SysTickHandler()改动的可能性是非常大的。
    非周期任务的开发套路又是怎样的呢?其实和周期性任务是差不多的,只是信号
量不是周期性地释放,而是按需释放。
    其他内核功能就不多介绍了,大家按需使用,不是很难。
    该移植代码在我自己开发的一个小玩意上已得到一段时间的验证,未发现问题。
但由于水平所限,并不敢保证该移植是没有任何问题的,殷切希望大家批评指正。

完整的pdf格式文档51黑下载地址:
UCos-ii_在STM32上的移植详解.pdf (326.01 KB, 下载次数: 36)

评分

参与人数 1黑币 +50 收起 理由
admin + 50 共享资料的黑币奖励!

查看全部评分

回复

使用道具 举报

ID:5203 发表于 2018-11-3 09:27 | 显示全部楼层
谢谢分享
回复

使用道具 举报

ID:138270 发表于 2019-4-6 16:21 | 显示全部楼层
谢谢分享
回复

使用道具 举报

ID:138270 发表于 2019-4-6 16:22 | 显示全部楼层
正在学习,谢谢分享
回复

使用道具 举报

ID:138270 发表于 2019-4-6 16:25 | 显示全部楼层
谢谢分享,正在学习
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表