下载代码
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 各个外设的使用范例和一
个project模板,Utilities是使用st公司评估板的样例,stm32f10x_stdperiph_lib_um.chm
教我们怎么用标准外设库。
project文件夹结构
既然准备使用 32 位单片机,应该是个不小项目,因此project文件夹也应该做个规划。
这里我推荐一下我所使用的文件夹结构。如果project名字叫 template,建一个名为
template 的目录,该目录下有个 3 个固定目录 doc。src。include。doc 用来存
放project相关的资料文件。src 放源码,在 src 下每一个功能模块一个目录,include
放各个模块都要使用的公共头文件。
output 放编译输出文件,内含两个子目录 obj
和 list。
template\
doc
src
include
output\obj
\list 整理库代码
因为 Libraries 下的 CMSIS 目录中非常多代码是和编译器及芯片相关的。导致
目录多且深度大,不利于project维护。实际上一个项目往往是用固定的编译器和芯
片,因此有必要对库进行整理。
在 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 芯片的启动
文件。建立project
使用 keil MDK(我使用 4.12 版)在 template 文件夹下建立project。project名为
template。
选一个 stm32 系列的芯片,哪一个都无所谓(我选的是 STM32F101RC,
由于我的板子就是用这个芯片),接下来要注意的是当弹出是否拷贝启动代码到工
程目录时要选 No,由于标准外设库里已经有启动代码了。
将 UV4 中 project window 里的顶层文件夹名改为 template,并将第一个 group
名改为 libstm32。把 libstm32 文件夹下全部.c 和.s 文件载入到project里的 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 是标
准外设库的配置文件,对于project中不须要的外设。能够凝视掉里面的包括的头文件。
这里我建议先仅留下 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 中的启动代码
加到project中。这一步在上面已经做过了。
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 有问题)。
下载地址:http://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 就不说了。
好了,官方的东西介绍完了,该我们自己建立project着手移植了。
关于建立project,
并使用stm32 标准外设库在我之前的文章《stm32 标准外设库使用具体解释》已有介绍,
这里请大家下载当中模板代码(http://download.csdn.net/source/3448543),本文
的移植是基于这个project的。
建立目录
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必须被放置在中断向量表中。一開始我也没注意。
到这里移植的大部分工作都做完了。以下剩下的就是把project配置好,SysTick
中断处理好。
在project中建立 ucosii 组,把 ucosii 下的文件都加进该组。这里别忘了把
os_cpu_a.asm 增加。
在project的 Options 中,c/c++选项卡的 Include Paths 中添
加.\src\ucosii\src;.\src\ucosii\port。
编译project,会发现缺少 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()修改的可能性是很大的。
非周期任务的开发套路又是如何的呢?事实上和周期性任务是几乎相同的,仅仅是信号
量不是周期性地释放,而是按需释放。
其它内核功能就不多介绍了,大家按需使用,不是非常难。
本文代码:http://download.csdn.net/source/3472653
该移植代码在我自己开发的一个小玩意上已得到一段时间的验证。未发现问题。
但因为水平所限,并不敢保证该移植是没有不论什么问题的,殷切希望大家批评指正。