澳门网络娱乐游戏平台-澳门电子游戏娱乐网址-官方直营

澳门官网赌场uC/OS-II在思想51单片机上的移植

前段时间买了嵌入式实时操作系统uC/OS-II这本书认真的看了三周,因为之前没有学过操作系统的相关课程,刚开始的时候有点吃力,好在是坚持下来了,感觉学到了不少的东西。就想着自己也移植着试试看。之前学习51单片机的时候认真听课了。感觉51学的还可以,就试着往51上移植。国庆放假来后这一周的时间终于移植成功了。或许不能说是成功了,因为没有真正的把生成的HEX文件下到单片机上运行。只是刚才调试的时候启动多任务成功。多任务切换成功。至于中断级的任务切换,没有调试。89C52只有256B的RAM,实在是少的可怜。也不想再试了。把自己这几天调试的过程记录下来。下面先把移植有关的源文件贴上来OS_CPU.H/**********************************************************************************数据类型定义*********************************************************************************/typedefunsignedcharBOOLEAN;typedefunsignedcharINT8U;//8位无符号整数typedefsignedcharINT8S;//8位有符号整数typedefunsignedshortintINT16U;//16位无符号整数typedefsignedshortintINT16S;//16位有符号整数typedefunsignedlongintINT32U;//32位无符号整数typedefsignedlongintINT32S;//32位有符号整数typedefunsignedcharOS_STK;//堆栈宽度定义为8位typedefunsignedcharOS_CPU_SR;//定义程序状态字位8位/**********************************************************************************与具体的处理器有关的代码*********************************************************************************/#defineOS_CRITICAL_METHOD1#ifOS_CRITICAL_METHOD==1#defineOS_ENTER_CRITICAL()EA=0#defineOS_EXIT_CRITICAL()EA=1#endif#ifOS_CRITICAL_METHOD==2#defineOS_ENTER_CRITICAL();#pragmaasmPUSHPSW;CLREA;#pragmaendasm#defineOS_EXIT_CRITICAL()#pragmaasmPOPPSW#pragmaendasm#endif#ifOS_CRITICAL_METHOD==3#defineOS_ENTER_CRITICAL();cpu_sr=PSW;EA=0;#defineOS_EXIT_CRITICAL()PSW=cpu_sr#endif#defineOS_STK_GROWTH0//定义堆栈的增长方向,0表示向上增长#defineOS_TASK_SW()OSCtxSw()//任务切换

    熟悉ucos,或者读过Jean.J.Labrosse写过的ucos书籍的人,一定会知道ucos中著名的临界区管理宏:OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()。

下载代码
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
放各个模块都要使用的公共头文件。

    同样是通过关中断来保护临界区,OS_ENTER_CRITICAL/OS_EXIT_CRITICAL一共实现了三种实现方式,如下所示:

output 放编译输出文件,内含两个子目录 obj
和 list。
template 
 doc 
 src 
 include 
 outputobj 
 list 整理库代码
因为 Libraries 下的 CMSIS 目录中非常多代码是和编译器及芯片相关的。导致
目录多且深度大,不利于project维护。实际上一个项目往往是用固定的编译器和芯
片,因此有必要对库进行整理。
在 src 下建立 libstm32 文件夹

1.       #if OS_CRITICAL_METHOD == 1  

  1. 把 LibrariesSTM32F10x_StdPeriph_Driver下的内容复制到 libstm32 文件夹
  2. 在 libstm32 文件夹下建立 cmsis 文件夹,把
    LibrariesCMSISCM3CoreSupport下的 core_cm3.c,core_cm3.h。
    LibrariesCMSISCM3DeviceSupportSTSTM32F10x下的 stm32f10x.h,
    system_stm32f10x.c,system_stm32f10x.h 复制到 cmsis 目录中。
  3. 依据你所选的芯片类型,将
    LibrariesCMSISCM3DeviceSupportSTSTM32F10xstartuparm下相应的启动
    文件复制到 cmsis 目录中。这里我拷贝的是 startup_stm32f10x_hd.s(大容量型
    stm32 芯片的启动文件)。
    以下对该库文件做个简介:
    LibrariesSTM32F10x_StdPeriph_Driver下的内容非常好理解就是 stm32 的各个
    外设模块驱动代码。
     misc.h 和 misc.c 是和 CM3 内核有关的 NVIC 和 SysTick 的驱动代码。 
     LibrariesCMSIS 下是什么呢?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。

2.       #define OS_ENTER_CRITICAL() __asm__("cli")  

选一个 stm32 系列的芯片,哪一个都无所谓(我选的是 STM32F101RC,
由于我的板子就是用这个芯片),接下来要注意的是当弹出是否拷贝启动代码到工
程目录时要选 No,由于标准外设库里已经有启动代码了。
将 UV4 中 project window 里的顶层文件夹名改为 template,并将第一个 group
名改为 libstm32。把 libstm32 文件夹下全部.c 和.s 文件载入到project里的 libstm32。
在 src 下建立一个 init 文件夹用来放置系统初始化代码。把
ProjectSTM32F10x_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 一节中已有说明,以下我把当中的步骤罗列一下:

3.       #define OS_EXIT_CRITICAL() __asm__("sti")  

  1. 依据所选芯片,把
    LibrariesCMSISCM3DeviceSupportSTSTM32F10xstartuparm 中的启动代码
    加到project中。这一步在上面已经做过了。
  2. 在 stm32f10x.h 的 66-73 行,依据所选芯片类型。去掉对应凝视,这里我去掉
    STM32F10X_HD 行的凝视(大容量型 stm32 芯片)。
  3. 去掉 105 行的 USE_STDPERIPH_DRIVER 凝视,启用 stm32 标准外设库。

4.       #endif  

  1. 在 system_stm32f10x.c 的 110-115 行。依据所选芯片主频,去掉对应凝视,默
    认 SYSCLK_FREQ_72MHz 凝视已去掉,假设你的芯片主频是 72MHz。就不用做
    改动了。这里我的芯片是 36MHz,凝视 SYSCLK_FREQ_72MHz,去掉
    SYSCLK_FREQ_36MHz 凝视。

澳门官网赌场,5.         

跑马灯程序
如今能够使用 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 组,并将各种代码增加。

6.       #if OS_CRITICAL_METHOD == 2  

在工程的 Options 中,c/c++
选项卡的 Include Paths 中加入.include; .srclibstm32cmsis; .srclibstm32inc; 
.srcbsp;。

7.       #define OS_ENTER_CRITICAL() __asm__("pushf nt cli")  

Output 选项卡 Select Folder for Objects 中选.outputobj。

8.       #define OS_EXIT_CRITICAL() __asm__("popf")  

Listing 选项卡 Select Folder for Listings 中选.outputlist。
Debug 选项卡选 use ULINK Cortex Debugger, Run to main()打钩,这一步大家
能够依据自己手上的仿真器做不同选择。编译执行。ucosii在stm32 上的移植具体解释
尽管眼下网上已经有不少关于 ucosii 在 stm32 上的移植版本号,包含 micrium 也
有官方移植版本号。

9.       #endif  

但这些版本号详细是怎么移植出来的,又该怎么基于移植好的 ucosii
开发应用软件,网上介绍的并不多。这里介绍一下我的移植经历,希望对大家有所
帮助。
我的移植基本上是从零開始的。

10.      

首先想要做好移植,有双方面的内容是必需要
了解。

11.    #if OS_CRITICAL_METHOD == 3  

1.目标芯片;2.ucosii 内核原理。
尽管我们移植的目标芯片是 stm32,但操作系统的移植基本是针对 Cortex-M3
内核(下面简称 CM3)而言的,所以我们仅仅需了解 CM3 内核就好了。stm32 芯片
就是 CM3 内核加上各种各样的外设。

12.    #define OS_ENTER_CRITICAL() (cpu_sr = OSCPUSaveSR())  

怎么才干了解 CM3 呢?看一本书<<ARM Cortex-M3 权威指南>>(宋岩译。网
上多的非常)就好了,非常多同学可能想,看完这本书移植的新奇劲都没了,因此我把
该书和移植有关的章节都列了出来。并对当中的重点内容进行介绍。我数了数相关
章节还不到 100 页,就这点内容,总要看了吧。

13.    #define OS_EXIT_CRITICAL() (OSCPURestoreSR(cpu_sr))  

相关章节例如以下:
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 个寄存器有效位是错开的。因此能够组合訪问。

14.    #endif  

中断屏蔽寄存器组(PRIMASK, FAULTMASK, BASEPRI)。这三个寄存器用于
控制异常的使能和除能。
控制寄存器(CONTROL)它有两个作用:
1.定义特权级别
2.选择当前使用哪个堆栈指针
3.3 操作模式和特权极别
操作模式: 处理者模式和线程模式
异常处理:处理者模式
主程序:线程模式
ucosii 不区分特权级和用户级。程序始终工作在特权级
这两个堆栈指针的切换是全自己主动的,就在出入异常服务例程时由硬件处理。

   第一种方式,OS_ENTER_CRITICAL()简单地关中断,OS_EXIT_CRITICAL()简单地开中断。这种方式虽然简单高效,但无法满足嵌套的情况。如果有两层临界区保护,在退出内层临界区时就会开中断,使外层的临界区也失去保护。虽然ucos的内核写的足够好,没有明显嵌套临界区的情况,但谁也无法保证一定没有,无法保证今后没有,无法保证在附加的驱动或什么位置没有,所以基本上第一种方法是没有人用的。

3.4 - 3.7 
没什么好讲的,须要看。

    第二种方式,OS_ENTER_CRITICAL()会在关中断前保存之前的标志寄存器内容到堆栈中,OS_EXIT_CRITICAL()从堆栈中恢复之前保存的状态。这样就允许了临界区嵌套的情况。但现在看来,这种方法还存在很大的问题,甚至会出现致命的漏洞。

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)。

    在OS_CRITICAL_METHOD=2的情况下,假设有如下代码:

注意该寄存器是以 MSB 对齐的,因此 stm32 每一个中断的优先级配置寄存器 7:4
位有效。3:0 位无效。

  1. function_a()  
  2. {  
  3.      int a=(1<<31);  
  4.      OS_ENTER_CRITICAL();  
  5.      function_b(a);  
  6.      OS_EXIT_CRITICAL();  
  7.        
  8. }  

对于优先级。CM3 又分为抢占优先级和亚优先级,
NVIC 中的应用程序中断及复位控制寄存器(AIRCR)的优先级分组(10:8)描写叙述了
怎样划分抢占优先级和亚优先级。
什么意思?以 stm32 为例,优先级配置寄存器不是 7:4 位有效吗。假设 AIRCR
中的优先级分组值为 4。则优先级配置寄存器的 7:5 位确定抢占优先级,位 4 确定
亚优先级。

   会出现什么情况?在我的实验中,OS_EXIT_CRITICAL()之后,会出现处理器异常。为什么会出现处理起异常,让我来模拟一下它的汇编代码。之所以是模拟,并非是我虚构数据,而是因为我实际碰到问题的函数复杂一些,理解起来就需要更多的代码。而这个问题是有普遍意义的,所以请允许我来浅显地揭示这个隐藏的bug。

此时全部中断有 8 个抢占优先级,每一个抢占优先级有 2 个亚优先级。
抢占优先级高的中断能够抢占抢占优先级低的中断,即抢占优先级决定了中断是
否能够嵌套。

  1. function_a:  
  2.      push ebp  
  3.      mov ebp, esp  
  4.      sub esp, 8  
  5.      mov 4(esp), 0x80000000  
  6.      pushfd  
  7.      cli  
  8.      mov edi, 4(esp)  
  9.      mov (esp), edi  
  10.      call function_b  
  11.     popfd  
  12.     mov esp, ebp  
  13.     ret  

同样抢占优先级的中断不能嵌套,但当抢占优先级同样的异常有不止一个到来
时。就优先响应亚优先级最高的异常。
參考附录 D 
表 D.9 中断优先级寄存器阵列 0xE000_E400 - 0xE000_E4EF 共 240 个。
表 D.16 系统异常优先级寄存器 0xE000_ED18 - 0xE000_ED23 共 12 个。
优先级同样,看中断号。中断号小的优先。

    这是参照了gcc编译结果的汇编模拟,无论是否加优化选项这一问题都存在。这个问题的起因很简单,gcc想聪明一点,一次把堆栈降个够,然后它就可以在栈上随意放参数去调用其他函数。尤其是在调用函数较多的时候,这种做法就更有意义。而且,gcc这种聪明与优化选项O好像没有太大关系,好像没有什么能禁止它这么做。但问题是,gcc不知道我们的OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()是操作了堆栈的,我尝试过使用__asm____volatile__("pushfd ntcli":::"memory")来通知gcc内存数据改变了,但显然gcc不认为堆栈也改变了。于是,OS_ENTER_CRITICAL()保存在栈上的状态就被冲掉了,比如被这里调用参数a的值。在恢复时,是否会引发异常,会引发什么异常,这个就要靠运气了。但我相信一个人的运气不会总是那么好的,所以最后别使用OS_CRITICAL_METHOD=2。

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。

   第三种,在关中断前,使用局部变量保存中断状态。这也是几乎所有实时操作系统共有的选择。但ucos是一朵奇葩,为了兼容前两种方式,OS_ENTER_CRITICAL()/ OS_EXIT_CRITICAL()宏定义并没有提供传递状态参数的功能。所以它的临界去必须这么用:

PendSV 
PendSV 中断主要做上下文切换,也就是任务切换,是 ucosii 移植过程中最重要
的中断。
主要有两点:
1.PendSV 中断是手工往 NVIC 的 PendSV 悬起寄存器中写 1 产生的(由 OS
写)。

  1. function_a()  
  2. {  
  3. #if OS_CRITICAL_METHOD == 3  
  4.     int cpu_sr;  
  5. #endif  
  6.       int a = 1<<31;  
  7.       OS_ENTER_CRITICAL();  
  8.       function_b(a);  
  9.       OS_EXIT_CRITICAL();  
  10. }  

2.PendSV 中断优先级必须设为最低。
在讲移植代码时会介绍详细是怎样做的。

    这种代码怎么看怎么别扭,可能是因为在函数体内加了宏定义吧。然后,第三种方法对同一个函数体内的嵌套临界区无法支持,这在一些很长大的函数中使用时或许会造成一定困扰。 好吧,如果有了问题,就要有解决方案,毕竟我不是为了让大家对ucos失去信心的。我们可以参考下一般的实时操作系统是如何实现关中断临界区的,就是以显式的方式用局部变量保存中断状态。

对于 7.6 的 PendSV 部分应认真研读一下。
chapter8 NVIC 与中断控制
NVIC 负责芯片的中断管理。它和 CM3 内核紧密相关。

  int_lock()和int_unlock()的可以用汇编更高效地实现,也可以选择只恢复中断标志的状态。这种方法让我们显示地管理状态保存的情况,我觉得至少要比宏定义清楚多了。

假设对于 CM3 中断配置不是非常了解,能够看看 8.1, 8.2, 8.3, 8.4 节。

  1. int int_lock()  
  2. {  
  3.    int cpu_sr;  
  4.     __asm__ __volatile__("pushfd nt pop %0nt cli":"=r"(cpu_sr));  
  5.     return cpu_sr;  
  6. }  
  7.   
  8. void int_unlock(int cpu_sr)  
  9. {  
  10.      __asm__ __volatile__("push %0nt popfd"::"r"(cpu_sr));  
  11. }  
  12.   
  13. function_a()  
  14. {  
  15.    int a, cpu_sr;  
  16.    a=1<<31;  
  17.    cpu_sr = int_lock();  
  18.    function_b(a);  
  19.    int_unlock(cpu_sr);  
  20. }       

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。

本文由澳门网络娱乐游戏平台发布于操作系统,转载请注明出处:澳门官网赌场uC/OS-II在思想51单片机上的移植

相关阅读