找回密碼
 立即注冊(cè)

QQ登錄

只需一步,快速開(kāi)始

搜索
查看: 9073|回復(fù): 4
打印 上一主題 下一主題
收起左側(cè)

UCos-ii在STM32上的移植詳解

[復(fù)制鏈接]
跳轉(zhuǎn)到指定樓層
樓主
ID:419321 發(fā)表于 2018-11-1 20:01 | 只看該作者 回帖獎(jiǎng)勵(lì) |倒序?yàn)g覽 |閱讀模式
UCos-ii_在STM32上的移植詳解,通過(guò)本例程可以學(xué)習(xí)一下ucosii!
下載代碼
stm32標(biāo)準(zhǔn)外設(shè)庫(kù)是stm32全系列芯片的外設(shè)驅(qū)動(dòng),有了它可以大大加速我們開(kāi)發(fā)stm32。
首先從 st公司的網(wǎng)站下載最新的stm32標(biāo)準(zhǔn)外設(shè)庫(kù),寫本文時(shí)最新的版本是V3.5.0。
解壓該zip文件,得到如下文件夾和文件
STM32F10x_StdPeriph_Lib_V3.5.0\
_htmresc
Libraries
Project
Utilities
Release_Notes.html
stm32f10x_stdperiph_lib_um.chm
其中Libraries包含庫(kù)的源代碼,Project包含 stm32 各個(gè)外設(shè)的使用范例和一個(gè)工程模板,Utilities是使用st公司評(píng)估板的例子, stm32f10x_stdperiph_lib_um.chm教我們?cè)趺从脴?biāo)準(zhǔn)外設(shè)庫(kù)。

工程目錄結(jié)構(gòu)
既然準(zhǔn)備使用32位單片機(jī), 應(yīng)該是個(gè)不小項(xiàng)目, 因此工程目錄也應(yīng)該做個(gè)規(guī)劃。這里我推薦一下我所使用的目錄結(jié)構(gòu)。假設(shè)工程名字叫template,建一個(gè)名為template 的文件夾,該目錄下有個(gè)3 個(gè)固定文件夾doc,src,include,doc用來(lái)存放工程相關(guān)的資料文件,src放源代碼,在 src下每個(gè)功能模塊一個(gè)文件夾,include放各個(gè)模塊都要使用的公共頭文件。 output放編譯輸出文件,內(nèi)含兩個(gè)子文件夾obj和 list。
template\
   doc
   src
   include
   output\obj
             \list
整理庫(kù)代碼
由于Libraries下的CMSIS文件夾中很多代碼是和編譯器及芯片相關(guān)的,導(dǎo)致文件夾多且深度大,不利于工程維護(hù),實(shí)際上一個(gè)項(xiàng)目往往是用固定的編譯器和芯片,因此有必要對(duì)庫(kù)進(jìn)行整理。

在 src下建立 libstm32目錄
1. 把 Libraries\STM32F10x_StdPeriph_Driver\下的內(nèi)容拷貝到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. 根據(jù)你所選的芯片類型,將
Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\arm\下對(duì)應(yīng)的啟動(dòng)文件拷貝到cmsis文件夾中。這里我拷貝的是startup_stm32f10x_hd.s(大容量型stm32芯片的啟動(dòng)文件)。
下面對(duì)該庫(kù)文件做個(gè)簡(jiǎn)單介紹:
Libraries\STM32F10x_StdPeriph_Driver\下的內(nèi)容很好理解就是stm32 的各個(gè)外設(shè)模塊驅(qū)動(dòng)代碼。
    misc.h和 misc.c 是和 CM3 內(nèi)核有關(guān)的NVIC 和SysTick 的驅(qū)動(dòng)代碼。   
    Libraries\CMSIS 下是什么呢?cmsis英文全稱:Cortex Microcontroller
Software Interface Standard,是Cortex系列處理器硬件抽象層,可以理解為cortex內(nèi)核的軟件接口。
    core_cm3.c, core_cm3.h
它們的目錄名為 CoreSupport,說(shuō)明這兩個(gè)文件是CM3內(nèi)核支撐文件,其他使用 CM3 內(nèi)核的芯片也可以用,不一定是stm32。這兩個(gè)文件用來(lái)獲取設(shè)置CM3 內(nèi)核,配置一些內(nèi)核寄存器。
stm32f10x.h, system_stm32f10x.c, system_stm32f10x.h 和
startup_stm32f10x_hd.s在 DeviceSupport目錄下,說(shuō)明這幾個(gè)文件是和具體的芯片有關(guān)的,也就是stm32 芯片的支撐文件。其中stm32f10x.h 是標(biāo)準(zhǔn)外設(shè)庫(kù)的入口,使用標(biāo)準(zhǔn)外設(shè)庫(kù)的代碼中必須包含該頭文件。system_stm32f10x.c,
system_stm32f10x.h 這兩個(gè)文件提供函數(shù)用來(lái)初始化stm32 芯片,配置 PLL、系統(tǒng)時(shí)鐘和內(nèi)置flash 接口。startup_stm32f10x_hd.s是大容量型stm32 芯片的啟動(dòng)文件。 建立工程   
使用keil MDK(我使用 4.12版)在 template 目錄下建立工程,工程名為template。選一個(gè) stm32 系列的芯片,哪一個(gè)都無(wú)所謂(我選的是STM32F101RC,因?yàn)槲业陌遄泳褪怯眠@個(gè)芯片),接下來(lái)要注意的是當(dāng)彈出是否拷貝啟動(dòng)代碼到工程文件夾時(shí)要選No,因?yàn)闃?biāo)準(zhǔn)外設(shè)庫(kù)里已經(jīng)有啟動(dòng)代碼了。
將 UV4 中project window 里的頂層目錄名改為template,并將第一個(gè)group名改為libstm32。把libstm32目錄下所有.c和.s 文件加載到工程里的libstm32。 在 src下建立一個(gè)init目錄用來(lái)放置系統(tǒng)初始化代碼。把Project\STM32F10x_StdPeriph_Template\下的 stm32f10x_it.c拷貝到 init文件夾中,stm32f10x_it.h,stm32f10x_conf.h 拷貝到include文件夾中。
stm32f10x_it.c,stm32f10x_it.h 是中斷服務(wù)程序文件。stm32f10x_conf.h 是標(biāo)準(zhǔn)外設(shè)庫(kù)的配置文件,對(duì)于工程中不需要的外設(shè),可以注釋掉里面的包含的頭文件。這里我建議先僅留下stm32f10x_gpio.h,stm32f10x_rcc.h,misc.h,用到什么再打開(kāi)什么,這樣編譯起來(lái)快一點(diǎn),當(dāng)然也可都留著。

使用stm32標(biāo)準(zhǔn)外設(shè)庫(kù)
事實(shí)上,stm32標(biāo)準(zhǔn)外設(shè)庫(kù)的使用在stm32f10x_stdperiph_lib_um.chm中的How to use the Library一節(jié)中已有說(shuō)明,下面我把其中的步驟羅列一下:
1. 根據(jù)所選芯片,把Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\arm中的啟動(dòng)代碼加到工程中,這一步在上面已經(jīng)做過(guò)了。
2. 在stm32f10x.h 的66-73 行,根據(jù)所選芯片類型,去掉相應(yīng)注釋,這里我去掉STM32F10X_HD 行的注釋(大容量型 stm32芯片)。
3. 去掉105 行的USE_STDPERIPH_DRIVER 注釋,啟用 stm32 標(biāo)準(zhǔn)外設(shè)庫(kù)。
4. 在system_stm32f10x.c的110-115行,根據(jù)所選芯片主頻,去掉相應(yīng)注釋,默認(rèn) SYSCLK_FREQ_72MHz注釋已去掉,如果你的芯片主頻是72MHz,就不用做修改了,這里我的芯片是36MHz,注釋SYSCLK_FREQ_72MHz,去掉SYSCLK_FREQ_36MHz注釋。


跑馬燈程序
現(xiàn)在可以使用stm32 標(biāo)準(zhǔn)外設(shè)庫(kù)了,下面以一個(gè)簡(jiǎn)單的跑馬燈程序說(shuō)明。
在 init目錄下建立main.c 作為系統(tǒng)入口。
在 src下建立一個(gè)bsp目錄用來(lái)放置板級(jí)支持代碼,建立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++
選項(xiàng)卡的 Include Paths 中添加.\include;  .\src\libstm32\cmsis; .\src\libstm32\inc;
.\src\bsp;。
Output選項(xiàng)卡 Select Folder for Objects 中選.\output\obj。
Listing 選項(xiàng)卡 Select Folder for Listings中選.\output\list。
Debug選項(xiàng)卡選use ULINK Cortex Debugger, Run to main()打鉤,這一步大家可以根據(jù)自己手上的仿真器做不同選擇。編譯運(yùn)行。 ucosii在stm32 上的移植詳解

雖然目前網(wǎng)上已經(jīng)有不少關(guān)于ucosii在stm32 上的移植版本,包括micrium也有官方移植版本。但這些版本具體是怎么移植出來(lái)的,又該怎么基于移植好的ucosii開(kāi)發(fā)應(yīng)用軟件,網(wǎng)上介紹的并不多。這里介紹一下我的移植經(jīng)歷,希望對(duì)大家有所幫助。
我的移植基本上是從零開(kāi)始的。首先想要做好移植,有兩方面的內(nèi)容是必須要了解。1.目標(biāo)芯片;2.ucosii 內(nèi)核原理。
雖然我們移植的目標(biāo)芯片是stm32,但操作系統(tǒng)的移植基本是針對(duì)Cortex-M3內(nèi)核(以下簡(jiǎn)稱CM3)而言的,所以我們只需了解CM3 內(nèi)核就好了。stm32芯片就是CM3內(nèi)核加上各種各樣的外設(shè)。
怎么才能了解CM3 呢?看一本書<<ARM Cortex-M3 權(quán)威指南>>(宋巖譯,網(wǎng)上多的很)就好了,很多同學(xué)可能想,看完這本書移植的新鮮勁都沒(méi)了,因此我把該書和移植有關(guān)的章節(jié)都列了出來(lái),并對(duì)其中的重點(diǎn)內(nèi)容進(jìn)行介紹,我數(shù)了數(shù)相關(guān)章節(jié)還不到100頁(yè),就這點(diǎn)內(nèi)容,總要看了吧。
相關(guān)章節(jié)如下:
chapter2 Cortex-M3 概覽
2.1 - 2.9
主要了解Cortex-M3的概貌。剛開(kāi)始看時(shí)不用追求全部理解,后面會(huì)有詳細(xì)介紹,很多內(nèi)容多看幾遍就明白。其中2.8 指令集,只要了解,CM3只使用thumb2就 ok了。
chapter3 Cortex-M3 基礎(chǔ)
3.1 寄存器組
    R0-R12: 通用寄存器
R13: 堆棧寄存器
有兩個(gè),MSP 和 PSP,同時(shí)只能看見(jiàn)一個(gè) 引用R13時(shí),引用的是正在使用的那個(gè)
MSP:可用于異常服務(wù)和應(yīng)用程序
PSP:只能用于應(yīng)用程序
系統(tǒng)復(fù)位后,用的堆棧指針是MSP。
  
R14: 連接寄存器,又名LR,存儲(chǔ)返回地址
R15: 程序計(jì)數(shù)寄存器,又名PC
3.2 特殊功能寄存器
程序狀態(tài)字寄存器組(PSRs)
中斷屏蔽寄存器組(PRIMASK, FAULTMASK, BASEPRI)
控制寄存器(CONTROL)
程序狀態(tài)字寄存器組(PSRs)分為
應(yīng)用程序 PSR(APSR)
中斷號(hào) PSR(IPSR)
執(zhí)行 PSR(EPSR)     每個(gè)都是32位,由于這3 個(gè)寄存器有效位是錯(cuò)開(kāi)的,因此可以組合訪問(wèn)。
中斷屏蔽寄存器組(PRIMASK, FAULTMASK, BASEPRI),這三個(gè)寄存器用于
控制異常的使能和除能。
控制寄存器(CONTROL)它有兩個(gè)作用:
1.定義特權(quán)級(jí)別
2.選擇當(dāng)前使用哪個(gè)堆棧指針
3.3 操作模式和特權(quán)極別
操作模式: 處理者模式和線程模式
異常處理:處理者模式
主程序:線程模式
ucosii 不區(qū)分特權(quán)級(jí)和用戶級(jí),程序始終工作在特權(quán)級(jí)
這兩個(gè)堆棧指針的切換是全自動(dòng)的,就在出入異常服務(wù)例程時(shí)由硬件處理。
3.4 - 3.7
沒(méi)什么好講的,需要看。
3.8 復(fù)位序列
0x00000000 MSP 初值
0x00000004 PC 初值 復(fù)位向量
chapter7 異常
7.1 異常類型
分為系統(tǒng)異常(編號(hào)1-15)和外部中斷(大于15)
7.2 優(yōu)先級(jí)
CM3支持3 個(gè)固定的高優(yōu)先級(jí)和多達(dá)256 級(jí)的可編程優(yōu)先級(jí)。
在 NVIC 中,每個(gè)中斷都有一個(gè)優(yōu)先級(jí)配置寄存器(1 個(gè)byte),用來(lái)配置該
中斷的優(yōu)先級(jí)。但該寄存器并不是每個(gè)位都被使用,不同制造商生產(chǎn)的芯片不相同,
譬如stm32使用 4位,也就是說(shuō)stm32支持16 個(gè)可編程優(yōu)先級(jí)(參考: chapter9)。  
注意該寄存器是以 MSB對(duì)齊的,因此 stm32每個(gè)中斷的優(yōu)先級(jí)配置寄存器7:4
位有效,3:0 位無(wú)效。
對(duì)于優(yōu)先級(jí),CM3 又分為搶占優(yōu)先級(jí)和亞優(yōu)先級(jí),
NVIC中的應(yīng)用程序中斷及復(fù)位控制寄存器(AIRCR)的優(yōu)先級(jí)分組(10:8)描述了
如何劃分搶占優(yōu)先級(jí)和亞優(yōu)先級(jí)。
什么意思?以stm32 為例,優(yōu)先級(jí)配置寄存器不是7:4位有效嗎,如果 AIRCR
中的優(yōu)先級(jí)分組值為4,則優(yōu)先級(jí)配置寄存器的7:5 位確定搶占優(yōu)先級(jí),位4 確定
亞優(yōu)先級(jí)。此時(shí)所有中斷有8個(gè)搶占優(yōu)先級(jí),每個(gè)搶占優(yōu)先級(jí)有2 個(gè)亞優(yōu)先級(jí)。
搶占優(yōu)先級(jí)高的中斷可以搶占搶占優(yōu)先級(jí)低的中斷,即搶占優(yōu)先級(jí)決定了中斷是
否可以嵌套。
相同搶占優(yōu)先級(jí)的中斷不能嵌套,但當(dāng)搶占優(yōu)先級(jí)相同的異常有不止一個(gè)到來(lái)
時(shí),就優(yōu)先響應(yīng)亞優(yōu)先級(jí)最高的異常。
參考附錄D  
表D.9 中斷優(yōu)先級(jí)寄存器陣列 0xE000_E400 - 0xE000_E4EF 共240 個(gè)。
表D.16 系統(tǒng)異常優(yōu)先級(jí)寄存器 0xE000_ED18 - 0xE000_ED23 共12 個(gè)。
優(yōu)先級(jí)相同,看中斷號(hào),中斷號(hào)小的優(yōu)先。
7.3 向量表   
初始在0x00000000 處,可以通過(guò)向量表偏移量寄存器(VTOR)(地址:
0xE000_ED08)更改,一般無(wú)需更改。
7.4 中斷輸入及掛起行為
需要看。
7.5 Fault異常
可不看。
7.6 SVC和PendSV
SVC
SVC 主要用在分特權(quán)級(jí)和用戶級(jí)的操作系統(tǒng),ucosii 不區(qū)分特權(quán)級(jí)和用戶級(jí),
可以不管這個(gè)東西。
這里說(shuō)點(diǎn)題外話,一開(kāi)始我很奇怪為什么會(huì)提供這種中斷,因?yàn)檫@種中斷一般
都是用在大型的操作系統(tǒng)上,如linux系統(tǒng)上,可CM3 又不提供MMU,應(yīng)該是無(wú)
法移植linux系統(tǒng)。后來(lái)我才知道uclinux是針對(duì)沒(méi)有MMU的嵌入式系統(tǒng)而設(shè)計(jì)的,
不過(guò)還是很懷疑有人會(huì)在像stm32這種芯片上用uclinux。
PendSV
PendSV 中斷主要做上下文切換,也就是任務(wù)切換,是ucosii 移植過(guò)程中最重要
的中斷。
主要有兩點(diǎn):
1.PendSV中斷是手工往NVIC的PendSV懸起寄存器中寫1產(chǎn)生的(由OS
寫)。
2.PendSV中斷優(yōu)先級(jí)必須設(shè)為最低。
在講移植代碼時(shí)會(huì)介紹具體是如何做的。
對(duì)于7.6的PendSV 部分應(yīng)認(rèn)真研讀一下。
chapter8 NVIC 與中斷控制
NVIC負(fù)責(zé)芯片的中斷管理,它和CM3內(nèi)核緊密相關(guān)。
如果對(duì)于CM3 中斷配置不是很了解,可以看看 8.1, 8.2, 8.3, 8.4 節(jié)。
8.7 節(jié)講述了 SysTick定時(shí)器,需要看。
chapter9 中斷的具體行為
9.1 中斷/異常的響應(yīng)序列
當(dāng)CM3 開(kāi)始響應(yīng)一個(gè)中斷時(shí)
1.xPSR, PC, LR, R12以及 R3‐R0 入棧
2.取向量
     3.選擇堆棧指針 MSP/PSP,更新堆棧指針SP,更新連接寄存器LR,更新程序
計(jì)數(shù)器 PC
對(duì)移植ucosii 來(lái)說(shuō),需要注意1,3
9.2 異常返回
在CM3 中,進(jìn)入中斷時(shí),LR 寄存器的值會(huì)被自動(dòng)更新。9.6 節(jié)對(duì)更新后的值進(jìn)
行說(shuō)明。這里統(tǒng)稱EXC_RETURN。返回時(shí)通過(guò)把 EXC_RETURN往PC里寫來(lái)識(shí)
別返回動(dòng)作的。因?yàn)镋XC_RETURN是一個(gè)特殊值,所以對(duì)于CM3,匯編語(yǔ)言就
不需要類似reti 這種指令,而用C語(yǔ)言開(kāi)發(fā)時(shí),不需要特殊編譯器命令指示一個(gè)函數(shù)為中斷服務(wù)程序。實(shí)際上,中斷服務(wù)程序如果是c代碼編寫,匯編成匯編代碼,
函數(shù)結(jié)尾一般是reti。
9.3 嵌套的中斷
只要注意:中斷嵌套不能過(guò)深即可。
9.4 和9.5
這兩節(jié)說(shuō)明CM3對(duì)中斷的響應(yīng)能力大大提高了,主要是硬件機(jī)制的改進(jìn)。但對(duì)
移植來(lái)說(shuō),并不需要關(guān)注。
9.6 異常返回值
對(duì)不同狀態(tài)進(jìn)入中斷時(shí),LR 寄存器的值進(jìn)行說(shuō)明,需要看。這里有一點(diǎn)需要注
意,該點(diǎn)在講移植代碼時(shí)再介紹。
9.7 和9.8
對(duì)移植來(lái)說(shuō),并不需要關(guān)注。
chapter10 Cortex-M3的低層編程
這一章僅需關(guān)注 10.2節(jié),因?yàn)閷?duì)移植來(lái)說(shuō)匯編與C的接口是必須面對(duì)的。
10.2 匯編與 C 的接口
有兩點(diǎn)需要知道:
1.當(dāng)主調(diào)函數(shù)需要傳遞參數(shù)(實(shí)參)時(shí),它們使用R0‐R3。其中 R0傳遞第一
個(gè),R1 傳遞第 2個(gè)……在返回時(shí),把返回值寫到R0 中。
2.在函數(shù)中,用匯編寫代碼時(shí),R0-R3, R12可以隨便使用,而使用R4‐R11,
則必須先PUSH,后POP。
以上內(nèi)容和移植多少都有些關(guān)系,剛開(kāi)始看,可能不太明白,多看幾遍就好了。 ucosii在stm32 上的移植詳解 2  

在詳解1 中主要講了移植需要用到的CM3 內(nèi)核知識(shí),本文講一講ucosii 的原理
和代碼組成。ucosii最經(jīng)典的學(xué)習(xí)資料莫過(guò)于邵貝貝老師的<<嵌入式實(shí)時(shí)操作系統(tǒng)
uc/os-ii(第2 版)>>,我想這本書對(duì)學(xué) ucosii 已經(jīng)足夠了,因?yàn)樗製cosii V2.55代
碼都講了一遍。移植前應(yīng)該好好看看此書。
下面說(shuō)說(shuō)我對(duì)ucosii 的理解。應(yīng)該說(shuō) ucosii 這個(gè)內(nèi)核還是比較簡(jiǎn)單的,基本可
以分為任務(wù)調(diào)度,任務(wù)同步和內(nèi)存管理三個(gè)部分。
任務(wù)調(diào)度
ucosii 為保證實(shí)時(shí)性,給每個(gè)任務(wù)分配一個(gè)不同的優(yōu)先級(jí)。當(dāng)發(fā)生任務(wù)切換時(shí),
總是切換到就緒的最高優(yōu)先級(jí)任務(wù)。有2種情況會(huì)發(fā)生任務(wù)切換。
1.任務(wù)等待資源就緒或自我延時(shí);
2.退出中斷;
情況1可以理解為任務(wù)主動(dòng)放棄cpu 的使用權(quán)。
情況2可以理解為中斷后,某種資源可能就緒了,需要任務(wù)切換。
需要注意的是SysTick中斷,這個(gè)中斷是os的“心跳”,必須得有。這樣就使得
cpu 會(huì)發(fā)生周期性地做任務(wù)切換。由于 ucosii不支持時(shí)間片輪轉(zhuǎn)調(diào)度,因此在該中
斷中必須做的工作僅有os的時(shí)間管理。也就是調(diào)用OSTimeTick()。
任務(wù)同步
任務(wù)同步和大多數(shù)操作系統(tǒng)的做法差不多,如果學(xué)過(guò)操作系統(tǒng)或是有多線程編
程經(jīng)驗(yàn)的話,應(yīng)該很好理解。無(wú)非是任務(wù)A因?yàn)槟硞(gè)資源未就緒,就放棄cpu 使用
權(quán),等任務(wù)B或是中斷使該資源就緒,當(dāng)再次任務(wù)進(jìn)行切換時(shí)如果任務(wù)A 優(yōu)先級(jí)最
高,則任務(wù)A繼續(xù)執(zhí)行。具體怎么實(shí)現(xiàn)就看邵老師的書吧。
內(nèi)存管理
ucosii 的內(nèi)存管理比較簡(jiǎn)單,就不說(shuō)了。
下面看看ucosii 代碼組成:
os_core.c是 ucosii 的核心,它包含了內(nèi)核初始化,任務(wù)切換,事件塊管理等,
其中事件塊是各個(gè)同步量(這里我把互斥量,信號(hào)量,郵箱,隊(duì)列統(tǒng)稱為同步量,
不是很科學(xué),圖個(gè)方便。事件標(biāo)志組不是以事件塊為基礎(chǔ)的,不過(guò)原理也差不多)
的基礎(chǔ)。
os_task.c  任務(wù)管理代碼。
os_flag.c
os_mbox.c
os_mutex.c
os_q.c
os_sem.c  各個(gè)同步量管理代碼。
os_mem.c  內(nèi)存管理代碼。
os_time.c  時(shí)間管理代碼,主要做各種延時(shí)。
os_tmr.c
定時(shí)器管理代碼,這部分代碼時(shí)從V2.81版才開(kāi)始有的,邵老師的書講的是
V2.55版的代碼,是沒(méi)有這部分內(nèi)容的。如果前面的代碼都理解的話,這部分代碼
也是不難理解的。一個(gè)定時(shí)器大體由3 部分組成:定時(shí)時(shí)間,回調(diào)函數(shù)和屬性。當(dāng)
定時(shí)時(shí)間到了的話,就進(jìn)行一次回調(diào)函數(shù)的處理,定時(shí)器屬性說(shuō)明定時(shí)器是周期性
的定時(shí)還是只做一次定時(shí)。如果用戶使能了OS_TMR_EN,ucosii 會(huì)在內(nèi)部創(chuàng)建一
個(gè)定時(shí)器任務(wù),負(fù)責(zé)處理各個(gè)定時(shí)器。這個(gè)任務(wù)一般應(yīng)該由硬件定時(shí)器的中斷函數(shù)
中調(diào)用 OSTmrSignal()去激活。所以從本質(zhì)上說(shuō)os_tmr.c中的定時(shí)器是由一個(gè)硬件
定時(shí)器分化出來(lái)的。
默認(rèn)情況下是由 SysTick中斷里通過(guò) OSTimeTickHook()去激活定時(shí)器任務(wù)的。  
移植相關(guān)文件
os_cpu.h:       進(jìn)行數(shù)據(jù)類型定義,處理器相關(guān)代碼和幾個(gè)函數(shù)原型。
os_cpu_c.c:     定義一些用戶hook 函數(shù)。
os_cpu_a.asm: 移植需要用匯編代碼完成的函數(shù),主要就是任務(wù)切換函數(shù)。
os_dbg.c:       內(nèi)核調(diào)試相關(guān)數(shù)據(jù)和函數(shù),可以不改。
ucosii 內(nèi)核就介紹到這里。
ucosii在stm32 上的移植詳解 3  
移植詳解1和2 中主要講了移植需要用到的基礎(chǔ)知識(shí),本文則對(duì)具體的移植過(guò)
程進(jìn)行介紹。
首先從 micrium網(wǎng)站上下載官方移植版本(編譯器使用ARM/Keil的,V2.86 版
本,V2.85有問(wèn)題)。
    下載地址:micrium點(diǎn)com/page/downloads/ports/st/stm32
    解壓縮后得到如下文件夾和文件:
    Micrium\
       AppNotes
       Licensing
       Software
       ReadMe.pdf
AppNotes包含ucosii移植說(shuō)明文件。這兩個(gè)文件中我們僅需關(guān)心
Micrium\AppNotes\AN1xxx-RTOS\AN1018-uCOS-II-Cortex-M3\AN-1018.pdf。因
為這個(gè)文件對(duì)ucosii 在CM3內(nèi)核移植過(guò)程中需要修改的代碼進(jìn)行了說(shuō)明。
Licensing包含ucosii使用許可證。
Software 下有好幾個(gè)文件夾,在本文的移植中僅需關(guān)心uCOS-II即可。
CPU: stm32 標(biāo)準(zhǔn)外設(shè)庫(kù)
EvalBoards: micrium 官方評(píng)估板相關(guān)代碼
uc-CPU: 基于 micrium官方評(píng)估板的 ucosii 移植代碼
uC-LCD:micrium官方評(píng)估板LCD驅(qū)動(dòng)代碼
uc-LIB: micrium官方的一個(gè)庫(kù)代碼
uCOS-II: ucosii 源代碼
uC-Probe: 和uC-Probe 相關(guān)代碼
ReadMe.pdf就不說(shuō)了。
好了,官方的東西介紹完了,該我們自己建立工程著手移植了。關(guān)于建立工程,
并使用stm32 標(biāo)準(zhǔn)外設(shè)庫(kù)在我之前的文章《stm32標(biāo)準(zhǔn)外設(shè)庫(kù)使用詳解》已有介紹,
這里請(qǐng)大家下載其中模板代碼(http://download.csdn.net/source/3448543),本文
的移植是基于這個(gè)工程的。
建立文件夾
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 中無(wú)需修改部分
ucosii\port下的代碼是移植時(shí)需要修改的。為防止對(duì)源碼的誤改動(dòng)造成移植失
敗,可以把ucosii\src下的代碼文件設(shè)為只讀。
這里根據(jù)AN-1018.pdf和移植詳解1、2 中介紹的移植基礎(chǔ)知識(shí),對(duì)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;
因?yàn)镃M3是32位寬的,所以O(shè)S_STK(堆棧的數(shù)據(jù)類型)被類型重定義為
unsigned int。
因?yàn)镃M3的狀態(tài)寄存器(xPSR)是32 位寬的,因此OS_CPU_SR 被類型重
定義為unsigned int。OS_CPU_SR 是在 OS_CRITICAL_METHOD方法 3中保存
cpu 狀態(tài)寄存器用的。在 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 中,到時(shí)再解釋。
#define  OS_STK_GROWTH        1
CM3中,棧是由高地址向低地址增長(zhǎng)的,因此OS_STK_GROWTH 定義為1。  
#define OS_TASK_SW() OSCtxSw()
   定義任務(wù)切換宏,OSCtxSw()是用匯編代碼寫的,代碼在os_cpu_a.asm中,到
時(shí)再解釋。
#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);
申明幾個(gè)函數(shù),這里要注意最后三個(gè)函數(shù)需要注釋掉,為什么呢?
OS_CPU_SysTickHandler()定義在os_cpu_c.c中,是 SysTick 中斷的中斷處
理函數(shù),而stm32f10x_it.c,中已經(jīng)有該中斷函數(shù)的定義SysTick_Handler(),這里
也就不需要了,是不是很奇怪官方移植版為什么會(huì)這樣弄吧,后面我會(huì)解釋的。
OS_CPU_SysTickInit()定義在os_cpu_c.c 中,用于初始化 SysTick定時(shí)器,它
依賴于 OS_CPU_SysTickClkFreq(),而此函數(shù)我們自己會(huì)實(shí)現(xiàn),所以注釋掉。
OS_CPU_SysTickClkFreq()定義在BSP.C (Micrium\Software\EvalBoards)中,
而本文移植中并未用到BSP.C,后面我們會(huì)自己實(shí)現(xiàn),因此可以把它注釋掉。

os_cpu_c.c
ucosii 移植時(shí)需要我們寫 10個(gè)相當(dāng)簡(jiǎn)單的C函數(shù)。
OSInitHookBegin()
OSInitHookEnd()
OSTaskCreateHook()
OSTaskDelHook()
OSTaskIdleHook()
OSTaskStatHook()
OSTaskStkInit()
OSTaskSwHook()
OSTCBInitHook()
OSTimeTickHook()
這些函數(shù)除了OSTaskStkInit(),都是一些hook 函數(shù)。這些hook 函數(shù)如果不使
能的話,都不會(huì)用上,也都比較簡(jiǎn)單,看看就應(yīng)該明白了,所以就不介紹。
下面就說(shuō)一說(shuō)OSTaskStkInit()。說(shuō)之前還是得先說(shuō)一下任務(wù)切換,因?yàn)槌跏蓟?br /> 任務(wù)堆棧,是為任務(wù)切換服務(wù)的。代碼在正常運(yùn)行時(shí),一行一行往下執(zhí)行,怎么才
能跑到另一個(gè)任務(wù)(即函數(shù))執(zhí)行呢?首先大家可以回想一下中斷過(guò)程,當(dāng)中斷發(fā)
生時(shí),原來(lái)函數(shù)執(zhí)行的地方(程序計(jì)數(shù)器PC、處理器狀態(tài)寄存器及所有通用寄存
器,即當(dāng)前代碼的現(xiàn)場(chǎng))被保存到棧里面去了,然后開(kāi)始取中斷向量,跑到中斷函
數(shù)里面執(zhí)行。執(zhí)行完了呢,想回到原來(lái)函數(shù)執(zhí)行的地方,該怎么辦呢,只要把棧中
保存的原來(lái)函數(shù)執(zhí)行的信息恢復(fù)即可(把棧中保存的代碼現(xiàn)場(chǎng)重新賦給cpu的各個(gè)
寄存器),一切就都回去了,好像什么事都沒(méi)發(fā)生一樣。這個(gè)過(guò)程大家應(yīng)該都比較
熟悉,任務(wù)切換和這有什么關(guān)系,試想一下,如果有3 個(gè)函數(shù) foo1(), foo2(), foo3()
像是剛被中斷,現(xiàn)場(chǎng)保存到棧里面去了,而中斷返回時(shí)做點(diǎn)手腳(調(diào)度程序的作用),
想回哪個(gè)回哪個(gè),是不是就做了函數(shù)(任務(wù))切換了?吹竭@里應(yīng)該有點(diǎn)明白
OSTaskStkInit()的作用了吧,它被任務(wù)創(chuàng)建函數(shù)調(diào)用,所以要在開(kāi)始時(shí),在棧中作
出該任務(wù)好像剛被中斷一樣的假象。(關(guān)于任務(wù)切換的原理邵老師書中的3.06 節(jié)有
介紹)。
那么中斷后棧中是個(gè)什么情形呢,<<ARM Cortex-M3 權(quán)威指南>>中9.1.1 有介
紹,xPSR,PC,LR,R12,R3-R0被自動(dòng)保存到棧中的,R11-R4 如果需要保存,
只能手工保存。因此OSTaskStkInit()的工作就是在任務(wù)自己的棧中保存cpu的所有
寄存器。這些值里R1-R12都沒(méi)什么意義,這里用相應(yīng)的數(shù)字代號(hào)(如R1 用
0x01010101)主要是方便調(diào)試。
其他幾個(gè):
xPSR = 0x01000000L,xPSR T位(第24位)置1,否則第一次執(zhí)行任務(wù)時(shí)
Fault,
PC 肯定得指向任務(wù)入口,
R14 = 0xFFFFFFFEL,最低4位為E,是一個(gè)非法值,主要目的是不讓使用
R14,即任務(wù)是不能返回的。
R0 用于傳遞任務(wù)函數(shù)的參數(shù),因此等于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
把上面這些宏定義也注釋掉,因?yàn)樗鼈兌加糜贠S_CPU_SysTickHandler(),
OS_CPU_SysTickInit()。
os_cpu_a.asm
這個(gè)文件包含著必須用匯編寫的代碼。
    EXTERN  OSRunning    ; External references
    EXTERN  OSPrioCur
    EXTERN  OSPrioHighRdy
    EXTERN  OSTCBCur
    EXTERN  OSTCBHighRdy
    EXTERN  OSIntNesting
    EXTERN  OSIntExit
    EXTERN  OSTaskSwHook
    申明這些變量是在其他文件定義的,本文件只做引用(有幾個(gè)好像并未引用,不
過(guò)沒(méi)有關(guān)系)。
    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
    申明這些函數(shù)是在本文件中定義的。
NVIC_INT_CTRL   EQU     0xE000ED04   ;中斷控制及狀態(tài)寄存器ICSR的地址
NVIC_SYSPRI14   EQU     0xE000ED22   ;PendSV 優(yōu)先級(jí)寄存器的地址 NVIC_PENDSV_PRI  EQU   0xFF   ;PendSV中斷的優(yōu)先級(jí)為255(最低)
NVIC_PENDSVSET   EQU   0x10000000   ;位28為1
定義幾個(gè)常量,類似C語(yǔ)言中的#define預(yù)處理指令。
OS_CPU_SR_Save
MRS     R0, PRIMASK   ;讀取 PRIMASK 到R0 中,R0為返回值
CPSID   I             ;PRIMASK=1,關(guān)中斷(NMI和硬 fault可以響應(yīng))
BX      LR            ;返回
OS_CPU_SR_Restore
MSR     PRIMASK, R0   ;讀取 R0到 PRIMASK中,R0為參數(shù)
BX      LR            ;返回
OSStartHighRdy()由OSStart()調(diào)用,用來(lái)啟動(dòng)最高優(yōu)先級(jí)任務(wù),當(dāng)然任務(wù)必須在
OSStart()前已被創(chuàng)建。
OSStartHighRdy
    ;設(shè)置PendSV中斷的優(yōu)先級(jí) #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
;設(shè)置PSP為 0 #2
MOVS    R0, #0                ;R0 = 0
MSR     PSP, R0               ;PSP = R0
;設(shè)置OSRunning為TRUE
    LDR     R0, =OSRunning        ;R0 = OSRunning
    MOVS    R1, #1                ;R1 = 1
STRB    R1, [R0]              ;OSRunning = 1
;觸發(fā)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             ;開(kāi)中斷   
OSStartHang   ;死循環(huán),應(yīng)該不會(huì)到這里
B       OSStartHang  

#1.PendSV中斷的優(yōu)先級(jí)應(yīng)該為最低優(yōu)先級(jí),原因在<<ARM Cortex-M3 權(quán)威指南
>>的 7.6節(jié)已有說(shuō)明。
#2.PSP 設(shè)置為 0,是告訴具體的任務(wù)切換程序(OS_CPU_PendSVHandler()),
這是第一次任務(wù)切換。做過(guò)切換后PSP就不會(huì)為 0 了,后面會(huì)看到。
#3.往中斷控制及狀態(tài)寄存器ICSR(0xE000ED04)第28位寫1即可產(chǎn)生PendSV中
斷。這個(gè)<<ARM Cortex-M3 權(quán)威指南>>8.4.5 其它異常的配置寄存器有說(shuō)明。
當(dāng)一個(gè)任務(wù)放棄 cpu 的使用權(quán),就會(huì)調(diào)用OS_TASK_SW()宏,而
OS_TASK_SW()就是 OSCtxSw()。OSCtxSw()應(yīng)該做任務(wù)切換。但是在CM3 中,
所有任務(wù)切換都被放到PendSV 的中斷處理函數(shù)中去做了,因此OSCtxSw()只需簡(jiǎn)
單的觸發(fā)PendSV 中斷即可。OS_TASK_SW()是由 OS_Sched()調(diào)用。
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();    /* 觸發(fā)PendSV 中斷 */
            }
        }
    }
    /* 一旦開(kāi)中斷,PendSV中斷函數(shù)會(huì)執(zhí)行(當(dāng)然要等更高優(yōu)先級(jí)中斷處理完) */
   OS_EXIT_CRITICAL();   
} OSCtxSw
    ;觸發(fā)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                    ;返回
    當(dāng)一個(gè)中斷處理函數(shù)退出時(shí),OSIntExit()會(huì)被調(diào)用來(lái)決定是否有優(yōu)先級(jí)更高的任
務(wù)需要執(zhí)行。如果有 OSIntExit()對(duì)調(diào)用OSIntCtxSw()做任務(wù)切換。
OSIntCtxSw
    ;觸發(fā)PendSV中斷
    LDR     R0, =NVIC_INT_CTRL
    LDR     R1, =NVIC_PENDSVSET
    STR     R1, [R0]
    BX      LR
    看到這里有些同學(xué)可能奇怪怎么OSCtxSw()和 OSIntCtxSw()完全一樣, 事實(shí)上,
這兩個(gè)函數(shù)的意義是不一樣的,OSCtxSw()做的是任務(wù)之間的切換,如任務(wù)A因?yàn)?br /> 等待某個(gè)資源或是做延時(shí)切換到任務(wù)B,而OSIntCtxSw()則是中斷退出時(shí),由中斷
狀態(tài)切換到另一個(gè)任務(wù)。由中斷切換到任務(wù)時(shí), CPU寄存器入棧的工作已經(jīng)做完了,
所以無(wú)需做第二次了(參考邵老師書的3.10節(jié))。這里只不過(guò)由于CM3的特殊機(jī)
制導(dǎo)致了在這兩個(gè)函數(shù)中只要做觸發(fā)PendSV中斷即可,具體切換由PendSV 中斷
來(lái)處理。
    前面已經(jīng)說(shuō)過(guò)真正的任務(wù)切換是在PendSV中斷處理函數(shù)里做的,由于CM3在
中斷時(shí)會(huì)有一半的寄存器自動(dòng)保存到任務(wù)堆棧里,所以在PendSV 中斷處理函數(shù)中
只需保存R4-R11 并調(diào)節(jié)堆棧指針即可。
PendSV 中斷處理函數(shù)偽代碼如下:
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 已自動(dòng)保存
    CPSID   I                    ;任務(wù)切換期間需要關(guān)中斷
    MRS     R0, PSP              ;R0 = PSP
    ;如果PSP == 0,跳到OS_CPU_PendSVHandler_nosave 執(zhí)行 #1
    CBZ     R0, OS_CPU_PendSVHandler_nosave   
    ;保存R4-R11 到任務(wù)堆棧
    SUBS    R0, R0, #0x20        ;R0 -= 0x20                          
    STM     R0, {R4-R11}         ;保存 R4-R11到任務(wù)堆棧
    ;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
;調(diào)用OSTaskSwHook()
PUSH    {R14}                ;保存R14,因?yàn)楹竺嬉{(diào)用函數(shù)            
    LDR     R0, =OSTaskSwHook    ;R0 = &OSTaskSwHook  
    BLX     R0                   ;調(diào)用OSTaskSwHook()
    POP     {R14}                ;恢復(fù) 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), 此時(shí) R0是新任務(wù)
的 SP
;SP = OSTCBHighRdy->OSTCBStkPtr #3  
LDM     R0, {R4-R11}   ;從任務(wù)堆棧SP恢復(fù)R4-R11
ADDS    R0, R0, #0x20  ;R0 += 0x20
MSR     PSP, R0    ;PSP = R0,用新任務(wù)的 SP加載PSP
ORR     LR, LR, #0x04 ;確保 LR 位2 為 1,返回后使用進(jìn)程堆棧 #4  
CPSIE   I                    ;開(kāi)中斷
BX      LR                   ;中斷返回
    END
#1 如果 PSP == 0,說(shuō)明OSStartHighRdy()啟動(dòng)后第一次做任務(wù)切換,而任務(wù)剛創(chuàng)
建時(shí)R4-R11 已經(jīng)保存在堆棧中了,所以不需要再保存一次了。
#2 OSTCBStkPtr是任務(wù)控制塊結(jié)構(gòu)體的第一個(gè)變量,所以*OSTCBCur = SP(不是
很科學(xué))就是 OSTCBCur->OSTCBStkPtr = SP;
#3 和#2類似。
#4 因?yàn)樵谥袛嗵幚砗瘮?shù)中使用的是 MSP,所以在返回任務(wù)后必須使用PSP,所以
LR位2 必須為 1。
os_dbg.c
用于系統(tǒng)調(diào)試,可以不管。
需要修改的代碼就介紹到這里,如果還有不明白之處,就再看看AN-1018.pdf,
邵老師的書和<<ARM Cortex-M3權(quán)威指南>>。
ucosii在stm32 上的移植詳解 4  
詳解3中有一個(gè)問(wèn)題還沒(méi)解釋,就是stm32f10x_it.c 中已經(jīng)有 SysTick中斷函
數(shù)的定義SysTick_Handler(),為什么官方版非要弄個(gè)OS_CPU_SysTickHandler()。
答案就在啟動(dòng)文件上,一般我們自己開(kāi)發(fā)基于stm32芯片的軟件,都會(huì)使用標(biāo)準(zhǔn)外
設(shè)庫(kù)CMSIS中提供的啟動(dòng)文件,而官方移植的啟動(dòng)文件卻是自己寫的,在兩個(gè)文
件 init.s,vectors.s 中
(Micrium\Software\EvalBoards\ST\STM3210B-EVAL\RVMDK)。init.s 負(fù)責(zé)進(jìn)入
main(),vectors.s 設(shè)置中斷向量。OS_CPU_SysTickHandler 和
OS_CPU_PendSVHandler就是在vectors.s 中被設(shè)置的。
我的移植是使用標(biāo)準(zhǔn)外設(shè)庫(kù)CMSIS中startup_stm32f10x_hd.s作為啟動(dòng)文件
的,那該怎么在這個(gè)文件中設(shè)置OS_CPU_SysTickHandler呢,事實(shí)上在
startup_stm32f10x_hd.s文件中,PendSV 中斷向量名為PendSV_Handler,所以
只需用OS_CPU_PendSVHandler把所有出現(xiàn)PendSV_Handler的地方替換掉就可
以了。
那么為什么OS_CPU_SysTickHandler 不用這種方式處理呢,這樣也就不用注
釋 os_cpu.c 中的 OS_CPU_SysTickHandler(),這主要是基于兩個(gè)原因:
1. startup_stm32f10x_hd.s盡量少該,能不改就不改。
2. 如果保留OS_CPU_SysTickHandler(),在以后開(kāi)發(fā)過(guò)程中,改動(dòng)
OS_CPU_SysTickHandler()中的內(nèi)容可能性是非常大的,如果一不小把該文件其他
部分改了造成了問(wèn)題,這個(gè) bug 就非常難查了,所以我一般移植好后就把ucosii的
這些文件設(shè)置為只讀。
    對(duì)于上面的原因1,一開(kāi)始移植時(shí),我曾做過(guò)在PendSV_Handler()中調(diào)用
OS_CPU_PendSVHandler(),后來(lái)發(fā)現(xiàn)這樣不行,這是為什么呢?問(wèn)題出在LR寄
存器上。
PendSV_Handler()
{
        OS_CPU_PendSVHandler();
}
匯編出來(lái)的代碼會(huì)是這樣:
PendSV_Handler PROC
   PUSH     {r4,lr}
   BL       OS_CPU_PendSVHandler
   POP      {r4,pc}
ENDP 這樣在進(jìn)入OS_CPU_PendSVHandler之后,LR 寄存器中存放的是指令 POP
{r4,pc}的地址+1。在 OS_CPU_PendSVHandler 中的ORR LR, LR, #0x04 就不會(huì)
起作用,也就無(wú)法使用PSP,移植因此失敗。其實(shí)在AN-1018.pdf的 3.04.06中也
有強(qiáng)調(diào)OS_CPU_PendSVHandler必須被放置在中斷向量表中。 一開(kāi)始我也沒(méi)注意。  
到這里移植的大部分工作都做完了,下面剩下的就是把工程配置好,SysTick
中斷處理好。
在工程中建立ucosii 組,把 ucosii下的文件都加進(jìn)該組。這里別忘了把
os_cpu_a.asm 加入。
    在工程的Options中,c/c++選項(xiàng)卡的 Include Paths 中添
加.\src\ucosii\src;.\src\ucosii\port。
    編譯工程,會(huì)發(fā)現(xiàn)缺少app_cfg.h 和 os_cfg.h 文件,app_cfg.h是用來(lái)配置應(yīng)用
軟件的,主要是任務(wù)的優(yōu)先級(jí)和堆棧大小,中斷優(yōu)先級(jí)等信息。目前還沒(méi)有基于
ucosii 開(kāi)發(fā)應(yīng)用軟件,所以只需在include 文件夾中創(chuàng)建一個(gè)空的app_cfg.h 文件即
可。os_cfg.h 是用來(lái)配置ucosii 系統(tǒng)的?截
Micrium\Software\EvalBoards\ST\STM3210B-EVAL\RVMDK\OS-Probe\os_cfg.h
到 template\include,對(duì)其做如下修改:
#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
所做的修改主要是把一些功能給去掉,減少內(nèi)核大小,也利于調(diào)試。等移植完
成后,如果需要該功能,再做開(kāi)啟。     接下來(lái)就剩下處理好SysTick中斷和啟動(dòng)任務(wù)了。SysTick是系統(tǒng)的“心跳”,本
質(zhì)上來(lái)說(shuō)就是一個(gè)定時(shí)器。先把原來(lái)main.c中的內(nèi)容刪除,添加如下代碼:
#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()用來(lái)初始化并啟動(dòng) SysTick 定時(shí)器。
     RCC_GetClocksFreq()用來(lái)獲取系統(tǒng)時(shí)鐘。
     SysTick_Config()初始化并使能SysTick定時(shí)器。      這里要注意的是 OS_TICKS_PER_SEC,它是每秒鐘的 ticks數(shù),如果為1000,
就是1s中1000 個(gè) ticks,也就是說(shuō) 1ms就會(huì)產(chǎn)生一個(gè)SysTick中斷。系統(tǒng)的時(shí)間
片為1ms。
    在邵老師的書中3.11 節(jié)已有明確說(shuō)明,必須在調(diào)用OSStart()之后,才能開(kāi)啟時(shí)
鐘節(jié)拍器(SysTick)。一般會(huì)把它放在第一個(gè)任務(wù)(啟動(dòng)任務(wù))中。
    startup_task()用來(lái)創(chuàng)建其他應(yīng)用任務(wù),創(chuàng)建完其他任務(wù)后,就會(huì)自己刪除自己。  
    文件中的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();
}
    這個(gè)代碼是仿照OS_CPU_SysTickHandler()中代碼的,在邵老師書的 3.11節(jié)亦
有說(shuō)明。這里就不解釋。
    至此ucosii在stm32 上的移植已全部完成。
ucosii在stm32 上的移植詳解 5  

詳解1-4 把移植過(guò)程都已經(jīng)介紹了。接下來(lái)的工作是驗(yàn)證移植是否ok 以及如何
基于移植好的ucosii 開(kāi)發(fā)應(yīng)用程序。前一個(gè)問(wèn)題可以說(shuō)是后一個(gè)問(wèn)題的特殊情況,
一般我們會(huì)創(chuàng)建兩個(gè)簡(jiǎn)單的任務(wù),看看任務(wù)切換是否成功來(lái)驗(yàn)證移植是否ok,因?yàn)?br /> 任務(wù)切換可以說(shuō)是ucosii 最核心的功能。

任務(wù)代碼(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()創(chuàng)建任務(wù):
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); 把任務(wù)的堆棧大小和優(yōu)先級(jí)寫入app_cfg.h,定義任務(wù)堆棧,編譯調(diào)試。
在任務(wù)中打斷點(diǎn),用模擬器調(diào)試可以發(fā)現(xiàn)已經(jīng)可以做任務(wù)切換了。如果有板子,
燒到板子中運(yùn)行,可以看到兩個(gè)燈會(huì)以1Hz 的頻率閃爍。
可以認(rèn)為移植初步成功,內(nèi)核其他功能有待在應(yīng)用中繼續(xù)驗(yàn)證。
如何基于移植好的ucosii 開(kāi)發(fā)應(yīng)用程序呢?
    開(kāi)發(fā)應(yīng)用程序大部分都是為了處理或控制一個(gè)真實(shí)的物理系統(tǒng),而真實(shí)的物理系
統(tǒng)往往都是模擬系統(tǒng),為了方便計(jì)算機(jī)處理,首先需要對(duì)系統(tǒng)做離散化處理。針對(duì)
ucosii,離散化過(guò)程是通過(guò)系統(tǒng)“心跳”(SysTick)來(lái)實(shí)現(xiàn)的。一般應(yīng)用程序都有多
個(gè)任務(wù)(不多任務(wù)誰(shuí)用ucosii 。,任務(wù)可以分為周期任務(wù)和非周期任務(wù)。周期任
務(wù)是周期性循環(huán)地處理事情的任務(wù),而非周期任務(wù)一般是某個(gè)條件觸發(fā)才執(zhí)行的任
務(wù)。這里有一個(gè)問(wèn)題,SysTick的時(shí)間是多少合適。SysTick 的時(shí)間一般取周期性任
務(wù)中周期最短的時(shí)間值。譬如說(shuō),系統(tǒng)里有3個(gè)周期性任務(wù):系統(tǒng)主任務(wù)(如處理
pid等,任務(wù)周期4ms),鍵盤掃描任務(wù)(任務(wù)周期16ms),通信任務(wù)(任務(wù)周期
128ms),SysTick時(shí)間就取4ms。當(dāng)然在SysTick 時(shí)間較小時(shí),要注意系統(tǒng)負(fù)荷
問(wèn)題,這時(shí)最好測(cè)一下cpu 使用率及各個(gè)任務(wù)的時(shí)間等。
    周期性任務(wù)的開(kāi)發(fā)套路是怎么樣的呢?看看定時(shí)器任務(wù)的做法就知道了,代碼在
os_tmr.c。首先在 OSTmr_Init()中初始化OSTmrSemSignal,然后 OSTmr_Task()
任務(wù)會(huì)一直等待OSTmrSemSignal,等 到OSTmrSemSignal 后去處理各個(gè)定時(shí)器。
那么誰(shuí)在釋放OSTmrSemSignal呢?OSTmrSignal(),這個(gè)函數(shù)要求放在一定頻率
的時(shí)鐘中斷里,默認(rèn)是在SysTick中斷中(如果使能OS_TIME_TICK_HOOK_EN)。
好了,現(xiàn)在我們可以總結(jié)總結(jié)周期性任務(wù)的一般套路了。
首先在任務(wù)初始化函數(shù)中初始化一個(gè)信號(hào)量(一般會(huì)用信號(hào)量),偽代碼如下:
void task_init(void)
{
        task_sem = OSSemCreate(0);
}
在任務(wù)中等待信號(hào)量
void task (void *p_arg)
{
        for (;;)
        {
                OSSemPend(task_sem, 0, &err);                 /* TODO: task handle here */
        }
}
周期性的釋放信號(hào)量  
OSSemPost(task_sem);
    對(duì)于上面所說(shuō)系統(tǒng)主任務(wù),OSSemPost(task_sem)可以放在SysTick_Handler()
中。所以一般來(lái)說(shuō) OS_CPU_SysTickHandler()改動(dòng)的可能性是非常大的。
    非周期任務(wù)的開(kāi)發(fā)套路又是怎樣的呢?其實(shí)和周期性任務(wù)是差不多的,只是信號(hào)
量不是周期性地釋放,而是按需釋放。
    其他內(nèi)核功能就不多介紹了,大家按需使用,不是很難。
    該移植代碼在我自己開(kāi)發(fā)的一個(gè)小玩意上已得到一段時(shí)間的驗(yàn)證,未發(fā)現(xiàn)問(wèn)題。
但由于水平所限,并不敢保證該移植是沒(méi)有任何問(wèn)題的,殷切希望大家批評(píng)指正。

完整的pdf格式文檔51黑下載地址:
UCos-ii_在STM32上的移植詳解.pdf (326.01 KB, 下載次數(shù): 36)


評(píng)分

參與人數(shù) 1黑幣 +50 收起 理由
admin + 50 共享資料的黑幣獎(jiǎng)勵(lì)!

查看全部評(píng)分

分享到:  QQ好友和群QQ好友和群 QQ空間QQ空間 騰訊微博騰訊微博 騰訊朋友騰訊朋友
收藏收藏2 分享淘帖 頂1 踩
回復(fù)

使用道具 舉報(bào)

沙發(fā)
ID:5203 發(fā)表于 2018-11-3 09:27 | 只看該作者
謝謝分享
回復(fù)

使用道具 舉報(bào)

板凳
ID:138270 發(fā)表于 2019-4-6 16:21 | 只看該作者
謝謝分享
回復(fù)

使用道具 舉報(bào)

地板
ID:138270 發(fā)表于 2019-4-6 16:22 | 只看該作者
正在學(xué)習(xí),謝謝分享
回復(fù)

使用道具 舉報(bào)

5#
ID:138270 發(fā)表于 2019-4-6 16:25 | 只看該作者
謝謝分享,正在學(xué)習(xí)
回復(fù)

使用道具 舉報(bào)

本版積分規(guī)則

手機(jī)版|小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術(shù)交流QQ群281945664

Powered by 單片機(jī)教程網(wǎng)

快速回復(fù) 返回頂部 返回列表