標(biāo)題: UCos-ii在STM32上的移植詳解 [打印本頁]

作者: 領(lǐng)會至愛    時間: 2018-11-1 20:01
標(biāo)題: UCos-ii在STM32上的移植詳解
UCos-ii_在STM32上的移植詳解,通過本例程可以學(xué)習(xí)一下ucosii!
下載代碼
stm32標(biāo)準(zhǔn)外設(shè)庫是stm32全系列芯片的外設(shè)驅(qū)動,有了它可以大大加速我們開發(fā)stm32。
首先從 st公司的網(wǎng)站下載最新的stm32標(biāo)準(zhǔn)外設(shè)庫,寫本文時最新的版本是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 各個外設(shè)的使用范例和一個工程模板,Utilities是使用st公司評估板的例子, stm32f10x_stdperiph_lib_um.chm教我們怎么用標(biāo)準(zhǔn)外設(shè)庫。

工程目錄結(jié)構(gòu)
既然準(zhǔn)備使用32位單片機, 應(yīng)該是個不小項目, 因此工程目錄也應(yīng)該做個規(guī)劃。這里我推薦一下我所使用的目錄結(jié)構(gòu)。假設(shè)工程名字叫template,建一個名為template 的文件夾,該目錄下有個3 個固定文件夾doc,src,include,doc用來存放工程相關(guān)的資料文件,src放源代碼,在 src下每個功能模塊一個文件夾,include放各個模塊都要使用的公共頭文件。 output放編譯輸出文件,內(nèi)含兩個子文件夾obj和 list。
template\
   doc
   src
   include
   output\obj
             \list
整理庫代碼
由于Libraries下的CMSIS文件夾中很多代碼是和編譯器及芯片相關(guān)的,導(dǎo)致文件夾多且深度大,不利于工程維護(hù),實際上一個項目往往是用固定的編譯器和芯片,因此有必要對庫進(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\下對應(yīng)的啟動文件拷貝到cmsis文件夾中。這里我拷貝的是startup_stm32f10x_hd.s(大容量型stm32芯片的啟動文件)。
下面對該庫文件做個簡單介紹:
Libraries\STM32F10x_StdPeriph_Driver\下的內(nèi)容很好理解就是stm32 的各個外設(shè)模塊驅(qū)動代碼。
    misc.h和 misc.c 是和 CM3 內(nèi)核有關(guān)的NVIC 和SysTick 的驅(qū)動代碼。   
    Libraries\CMSIS 下是什么呢?cmsis英文全稱:Cortex Microcontroller
Software Interface Standard,是Cortex系列處理器硬件抽象層,可以理解為cortex內(nèi)核的軟件接口。
    core_cm3.c, core_cm3.h
它們的目錄名為 CoreSupport,說明這兩個文件是CM3內(nèi)核支撐文件,其他使用 CM3 內(nèi)核的芯片也可以用,不一定是stm32。這兩個文件用來獲取設(shè)置CM3 內(nèi)核,配置一些內(nèi)核寄存器。
stm32f10x.h, system_stm32f10x.c, system_stm32f10x.h 和
startup_stm32f10x_hd.s在 DeviceSupport目錄下,說明這幾個文件是和具體的芯片有關(guān)的,也就是stm32 芯片的支撐文件。其中stm32f10x.h 是標(biāo)準(zhǔn)外設(shè)庫的入口,使用標(biāo)準(zhǔn)外設(shè)庫的代碼中必須包含該頭文件。system_stm32f10x.c,
system_stm32f10x.h 這兩個文件提供函數(shù)用來初始化stm32 芯片,配置 PLL、系統(tǒng)時鐘和內(nèi)置flash 接口。startup_stm32f10x_hd.s是大容量型stm32 芯片的啟動文件。 建立工程   
使用keil MDK(我使用 4.12版)在 template 目錄下建立工程,工程名為template。選一個 stm32 系列的芯片,哪一個都無所謂(我選的是STM32F101RC,因為我的板子就是用這個芯片),接下來要注意的是當(dāng)彈出是否拷貝啟動代碼到工程文件夾時要選No,因為標(biāo)準(zhǔn)外設(shè)庫里已經(jīng)有啟動代碼了。
將 UV4 中project window 里的頂層目錄名改為template,并將第一個group名改為libstm32。把libstm32目錄下所有.c和.s 文件加載到工程里的libstm32。 在 src下建立一個init目錄用來放置系統(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è)庫的配置文件,對于工程中不需要的外設(shè),可以注釋掉里面的包含的頭文件。這里我建議先僅留下stm32f10x_gpio.h,stm32f10x_rcc.h,misc.h,用到什么再打開什么,這樣編譯起來快一點,當(dāng)然也可都留著。

使用stm32標(biāo)準(zhǔn)外設(shè)庫
事實上,stm32標(biāo)準(zhǔn)外設(shè)庫的使用在stm32f10x_stdperiph_lib_um.chm中的How to use the Library一節(jié)中已有說明,下面我把其中的步驟羅列一下:
1. 根據(jù)所選芯片,把Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\arm中的啟動代碼加到工程中,這一步在上面已經(jīng)做過了。
2. 在stm32f10x.h 的66-73 行,根據(jù)所選芯片類型,去掉相應(yīng)注釋,這里我去掉STM32F10X_HD 行的注釋(大容量型 stm32芯片)。
3. 去掉105 行的USE_STDPERIPH_DRIVER 注釋,啟用 stm32 標(biāo)準(zhǔn)外設(shè)庫。
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è)庫了,下面以一個簡單的跑馬燈程序說明。
在 init目錄下建立main.c 作為系統(tǒng)入口。
在 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()打鉤,這一步大家可以根據(jù)自己手上的仿真器做不同選擇。編譯運行。 ucosii在stm32 上的移植詳解

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

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

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

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

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

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



作者: zjlzdf    時間: 2018-11-3 09:27
謝謝分享
作者: beijita000    時間: 2019-4-6 16:21
謝謝分享
作者: beijita000    時間: 2019-4-6 16:22
正在學(xué)習(xí),謝謝分享
作者: beijita000    時間: 2019-4-6 16:25
謝謝分享,正在學(xué)習(xí)




歡迎光臨 (http://www.torrancerestoration.com/bbs/) Powered by Discuz! X3.1