stm32 ucosii消息队列 串口_正点原子STM32F407探索者开发板资料连载第六十三章 UCOSII 实验...
1)實驗平臺:alientek 阿波羅 STM32F767 開發板
2)摘自《STM32F7 開發指南(HAL 庫版)》關注官方微信號公眾號,獲取更多資料:正點原子
http://weixin.qq.com/r/hEhUTLbEdesKrfIv9x2W (二維碼自動識別)
第六十三章 UCOSII 實驗 3-消息隊列、信號量集和軟件定時
器
上一章,我們學習了 UCOSII 的信號量和郵箱的使用,本章,我們將學習消息隊列、信號
量集和軟件定時器的使用。本章分為如下幾個部分:
63.1 UCOSII 消息隊列、信號量集和軟件定時器簡介
63.2 硬件設計
63.3 軟件設計
63.4 下載驗證
63.1 UCOSII 消息隊列、信號量集和軟件定時器簡介
上一章,我們介紹了信號量和郵箱的使用,本章我們介紹比較復雜消息隊列、信號量集以
及軟件定時器的使用。
消息隊列
使用消息隊列可以在任務之間傳遞多條消息。消息隊列由三個部分組成:事件控制塊、消
息隊列和消息。當把事件控制塊成員 OSEventType 的值置為 OS_EVENT_TYPE_Q 時,該事件
控制塊描述的就是一個消息隊列。
消息隊列的數據結構如圖 63.1.1 所示。從圖中可以看到,消息隊列相當于一個共用一個任
務等待列表的消息郵箱數組,事件控制塊成員 OSEventPtr 指向了一個叫做隊列控制塊(OS_Q)
的結構,該結構管理了一個數組 MsgTbl[],該數組中的元素都是一些指向消息的指針。
圖 63.1.1 消息隊列的數據結構
隊列控制塊(OS_Q)的結構定義如下:
表 63.1.1 隊列控制塊各參數含義
其中,可以移動的指針為 OSQIn 和 OSQOut,而指針 OSQStart 和 OSQEnd 只是一個標志
(常指針)。當可移動的指針 OSQIn 或 OSQOut 移動到數組末尾,也就是與 OSQEnd 相等時,
可移動的指針將會被調整到數組的起始位置 OSQStart。也就是說,從效果上來看,指針 OSQEnd
與 OSQStart 等值。于是,這個由消息指針構成的數組就頭尾銜接起來形成了一個如圖 63.1.2
所示的循環的隊列。
圖 63.1.2 消息指針數組構成的環形數據緩沖區
在UCOSII初始化時,系統將按文件os_cfg.h中的配置常數OS_MAX_QS定義OS_MAX_QS
個隊列控制塊,并用隊列控制塊中的指針 OSQPtr 將所有隊列控制塊鏈接為鏈表。由于這時還
沒有使用它們,故這個鏈表叫做空隊列控制塊鏈表。
接下來我們看看在 UCOSII 中,與消息隊列相關的幾個函數(未全部列出,下同)。
1) 創建消息隊列函數
創建一個消息隊列首先需要定義一指針數組,然后把各個消息數據緩沖區的首地址存
入這個數組中,然后再調用函數 OSQCreate 來創建消息隊列。創建消息隊列函數 OSQCreate
的原型為:
OS_EVENT *OSQCreate(void**start,INT16U size);其中,start 為存放消息緩沖區指針數組的地址,size 為該數組大小。該函數的返回值
為消息隊列指針。
2) 請求消息隊列函數
請求消息隊列的目的是為了從消息隊列中獲取消息。任務請求消息隊列需要調用函數
OSQPend,該函數原型為:
void*OSQPend(OS_EVENT*pevent,INT16U timeout,INT8U *err);其中,pevent 為所請求的消息隊列的指針,timeout 為任務等待時限,err 為錯誤信息。
3) 向消息隊列發送消息函數
任務可以通過調用函數 OSQPost 或 OSQPostFront 兩個函數來向消息隊列發送消息。
函數 OSQPost 以 FIFO(先進先出)的方式組織消息隊列,函數 OSQPostFront 以 LIFO(后
進先出)的方式組織消息隊列。這兩個函數的原型分別為:
INT8U OSQPost(OS_EVENT*pevent,void *msg); INT8U OSQPostFront (OS_EVENT*pevent,void*msg);其中,pevent 為消息隊列的指針,msg 為待發消息的指針。
消息隊列還有其他一些函數,這里我們就不介紹了,感興趣的朋友可以參考《嵌入式實時
操作系統 UCOSII 原理及應用》第五章,關于隊列更詳細的介紹,也請參考該書。
信號量集
在實際應用中,任務常常需要與多個事件同步,即要根據多個信號量組合作用的結果來決
定任務的運行方式。UCOSII 為了實現多個信號量組合的功能定義了一種特殊的數據結構——
信號量集。
信號量集所能管理的信號量都是一些二值信號,所有信號量集實質上是一種可以對多個輸
入的邏輯信號進行基本邏輯運算的組合邏輯,其示意圖如圖 63.1.3 所示
圖 63.1.3 信號量集示意圖
不同于信號量、消息郵箱、消息隊列等事件,UCOSII 不使用事件控制塊來描述信號量集,
而使用了一個叫做標志組的結構 OS_FLAG_GRP 來描述。OS_FLAG_GRP 結構如下:
typedef struct { INT8U OSFlagType; //識別是否為信號量集的標志 void *OSFlagWaitList; //指向等待任務鏈表的指針 OS_FLAGS OSFlagFlags; //所有信號列表 }OS_FLAG_GRP;成員 OSFlagWaitList 是一個指針,當一個信號量集被創建后,這個指針指向了這個信號量
集的等待任務鏈表。
與其他前面介紹過的事件不同,信號量集用一個雙向鏈表來組織等待任務,每一個等待任
務都是該鏈表中的一個節點(Node)。標志組 OS_FLAG_GRP 的成員 OSFlagWaitList 就指向了
信號量集的這個等待任務鏈表。等待任務鏈表節點 OS_FLAG_NODE 的結構如下:
typedef struct {void *OSFlagNodeNext; //指向下一個節點的指針void *OSFlagNodePrev; //指向前一個節點的指針void *OSFlagNodeTCB; //指向對應任務控制塊的指針void *OSFlagNodeFlagGrp; //反向指向信號量集的指針OS_FLAGS OSFlagNodeFlags; //信號過濾器INT8U OSFlagNodeWaitType; //定義邏輯運算關系的數據 } OS_FLAG_NODE;其中 OSFlagNodeWaitType 是定義邏輯運算關系的一個常數(根據需要設置),其可選值和
對應的邏輯關系如表 63.1.2 所示:
表 63.1.2 OSFlagNodeWaitType 可選值及其意義
OSFlagFlags、OSFlagNodeFlags、OSFlagNodeWaitType 三者的關系如圖 63.1.4 所示:
圖 63.1.4 標志組與等待任務共同完成信號量集的邏輯運算及控制
圖中為了方便說明,我們將 OSFlagFlags 定義為 8 位,但是 UCOSII 支持 8 位/16 位/32 位
定義,這個通過修改 OS_FLAGS 的類型來確定(UCOSII 默認設置 OS_FLAGS 為 16 位)。
上圖清楚的表達了信號量集各成員的關系:OSFlagFlags 為信號量表,通過發送信號量集的
任務設置;OSFlagNodeFlags 為信號濾波器,由請求信號量集的任務設置,用于選擇性的挑選
OSFlagFlags 中的部分(或全部)位作為有效信號;OSFlagNodeWaitType 定義有效信號的邏輯
運算關系,也是由請求信號量集的任務設置,用于選擇有效信號的組合方式(0/1? 與/或?)。
舉個簡單的例子,假設請求信號量集的任務設置 OSFlagNodeFlags 的值為 0X0F,設置
OSFlagNodeWaitType 的值為 WAIT_SET_ANY,那么只要 OSFlagFlags 的低四位的任何一位為
1,請求信號量集的任務將得到有效的請求,從而執行相關操作,如果低四位都為 0,那么請求
信號量集的任務將得到無效的請求。
接下來我們看看在 UCOSII 中,與信號量集相關的幾個函數。
1) 創建信號量集函數
任務可以通過調用函數 OSFlagCreate 來創建一個信號量集。函數 OSFlagCreate 的原
型為:
OS_FLAG_GRP *OSFlagCreate (OS_FLAGS flags,INT8U *err );其中,flags 為信號量的初始值(即 OSFlagFlags 的值),err 為錯誤信息,返回值為該
信號量集的標志組的指針,應用程序根據這個指針對信號量集進行相應的操作。
2) 請求信號量集函數
任務可以通過調用函數 OSFlagPend 請求一個信號量集,函數 OSFlagPend 的原型為:
OS_FLAGS OSFlagPend(OS_FLAG_GRP*pgrp, OS_FLAGS flags, INT8U wait_type, INT16U timeout, INT8U *err);其中,pgrp 為所請求的信號量集指針,flags 為濾波器(即 OSFlagNodeFlags 的值),
wait_type 為邏輯運算類型(即 OSFlagNodeWaitType 的值),timeout 為等待時限,err 為錯
誤信息。
3) 向信號量集發送信號函數
任務可以通過調用函數 OSFlagPost 向信號量集發信號,函數 OSFlagPost 的原型為:
OS_FLAGS OSFlagPost (OS_FLAG_GRP *pgrp, OS_FLAGS flags, INT8U opt, INT8U *err);其中,pgrp 為所請求的信號量集指針,flags 為選擇所要發送的信號,opt 為信號有效選項,err 為錯誤信息。
所謂任務向信號量集發信號,就是對信號量集標志組中的信號進行置“1”(置位)或
置“0”(復位)的操作。至于對信號量集中的哪些信號進行操作,用函數中的參數 flags
來指定;對指定的信號是置“1”還是置“0”,用函數中的參數 opt 來指定(opt =
OS_FLAG_SET 為置“1”操作;opt = OS_FLAG_CLR 為置“0”操作)。
信號量集就介紹到這,更詳細的介紹,請參考《嵌入式實時操作系統 UCOSII 原理及應用》
第六章。
軟件定時器
UCOSII 從 V2.83 版本以后,加入了軟件定時器,這使得 UCOSII 的功能更加完善,在其上
的應用程序開發與移植也更加方便。在實時操作系統中一個好的軟件定時器實現要求有較高的
精度、較小的處理器開銷,且占用較少的存儲器資源。
通過前面的學習,我們知道 UCOSII 通過 OSTimTick 函數對時鐘節拍進行加 1 操作,同時
遍歷任務控制塊,以判斷任務延時是否到時。軟件定時器同樣由 OSTimTick 提供時鐘,但是軟
件定時器的時鐘還受 OS_TMR_CFG_TICKS_PER_SEC 設置的控制,也就是在 UCOSII 的時鐘
節拍上面再做了一次“分頻”,軟件定時器的最快時鐘節拍就等于 UCOSII 的系統時鐘節拍。這
也決定了軟件定時器的精度。
軟件定時器定義了一個單獨的計數器 OSTmrTime,用于軟件定時器的計時,UCOSII 并不
在 OSTimTick 中進行軟件定時器的到時判斷與處理,而是創建了一個高于應用程序中所有其他
任務優先級的定時器管理任務 OSTmr_Task,在這個任務中進行定時器的到時判斷和處理。時
鐘節拍函數通過信號量給這個高優先級任務發信號。這種方法縮短了中斷服務程序的執行時間,
但也使得定時器到時處理函數的響應受到中斷退出時恢復現場和任務切換的影響。軟件定時器
功能實現代碼存放在 tmr.c 文件中,移植時只需在 os_cfg.h 文件中使能定時器和設定定時器
的相關參數。
UCOSII 中軟件定時器的實現方法是,將定時器按定時時間分組,使得每次時鐘節拍到來
時只對部分定時器進行比較操作,縮短了每次處理的時間。但這就需要動態地維護一個定時器
組。定時器組的維護只是在每次定時器到時時才發生,而且定時器從組中移除和再插入操作不
需要排序。這是一種比較高效的算法,減少了維護所需的操作時間。
UCOSII 軟件定時器實現了 3 類鏈表的維護:
OS_EXT OS_TMR OSTmrTbl[OS_TMR_CFG_MAX]; //定時器控制塊數組 OS_EXT OS_TMR *OSTmrFreeList;//空閑定時器控制塊鏈表指針
OS_EXT OS_TMR_WHEEL OSTmrWheelTbl[OS_TMR_CFG_WHEEL_SIZE];//定時器輪
其中 OS_TMR 為定時器控制塊,定時器控制塊是軟件定時器管理的基本單元,包含軟件定
時器的名稱、定時時間、在鏈表中的位置、使用狀態、使用方式,以及到時回調函數及其參數
等基本信息。
OSTmrTbl[OS_TMR_CFG_MAX];:以數組的形式靜態分配定時器控制塊所需的 RAM 空間,
并存儲所有已建立的定時器控制塊,OS_TMR_CFG_MAX 為最大軟件定時器的個數。
OSTmrFreeLiSt:為空閑定時器控制塊鏈表頭指針。空閑態的定時器控制塊(OS_TMR)中,
OSTmrnext 和 OSTmrPrev 兩個指針分別指向空閑控制塊的前一個和后一個,組織了空閑控制塊
雙向鏈表。建立定時器時,從這個鏈表中搜索空閑定時器控制塊。
OSTmrWheelTbl[OS_TMR_CFG_WHEEL_SIZE]:該數組的每個元素都是已開啟定時器的
一個分組,元素中記錄了指向該分組中第一個定時器控制塊的指針,以及定時器控制塊的個數。
運行態的定時器控制塊(OS_TMR)中,OSTmrnext 和 OSTmrPrev 兩個指針同樣也組織了所在分
組中定時器控制塊的雙向鏈表。軟件定時器管理所需的數據結構示意圖如圖 63.1.5 所示:
圖 63.1.5 軟件定時器管理所需的數據結構示意圖
OS_TMR_CFG_WHEEL_SIZE 定義了 OSTmrWheelTbl 的大小,同時這個值也是定時器分
組的依據。按照定時器到時值與 OS_TMR_CFG_WHEEL_SIZE 相除的余數進行分組:不同余數
的定時器放在不同分組中;相同余數的定時器處在同一組中,由雙向鏈表連接。這樣,余數值
為 0~OS_TMR_CFG_WHEEL_SIZE-1 的不同定時器控制塊,正好分別對應了數組元素
OSTmr-WheelTbl[0]~OSTmrWheelTbl[OS_TMR_CFGWHEEL_SIZE-1]的不同分組。每次時鐘節
拍到來時,時鐘數 OSTmrTime 值加 1,然后也進行求余操作,只有余數相同的那組定時器才有
可能到時,所以只對該組定時器進行判斷。這種方法比循環判斷所有定時器更高效。隨著時鐘
數的累加,處理的分組也由 0~OS_TMR_CFG_WHE EL_SIZE-1 循環。這里,我們推薦
OS_TMR_CFG_WHEEL_SIZE 的取值為 2 的 N 次方,以便采用移位操作計算余數,縮短處理時
間。
信號量喚醒定時器管理任務,計算出當前所要處理的分組后,程序遍歷該分組中的所有控
制塊,將當前 OSTmrTime 值與定時器控制塊中的到時值(OSTmrMatch)相比較。若相等(即到
時),則調用該定時器到時回調函數;若不相等,則判斷該組中下一個定時器控制塊。如此操作,
直到該分組鏈表的結尾。軟件定時器管理任務的流程如圖 63.1.6 所示。
圖 63.1.6 軟件定時器管理任務流程
當運行完軟件定時器的到時處理函數之后,需要進行該定時器控制塊在鏈表中的移除和再
插入操作。插入前需要重新計算定時器下次到時時所處的分組。計算公式如下:
定時器下次到時的 OSTmrTime 值(OSTmrMatch)=定時器定時值+當前 OSTmrTime 值
新分組=定時器下次到時的 OSTmrTime 值(OSTmrMatch)%OS_TMR_CFG_WHEEL_SIZE
接下來我們看看在 UCOSII 中,與軟件定時器相關的幾個函數。
1) 創建軟件定時器函數
創建軟件定時器通過函數 OSTmrCreate 實現,該函數原型為:
OS_TMR *OSTmrCreate (INT32U dly, INT32U period, INT8U opt, OS_TMR_CALLBACK callback,void *callback_arg, INT8U *pname, INT8U *perr);dly,用于初始化定時時間,對單次定時(ONE-SHOT 模式)的軟件定時器來說,這
就是該定時器的定時時間,而對于周期定時(PERIODIC 模式)的軟件定時器來說,這是
該定時器第一次定時的時間,從第二次開始定時時間變為 period。
period,在周期定時(PERIODIC 模式),該值為軟件定時器的周期溢出時間。
opt,用于設置軟件定時器工作模式。可以設置的值為:OS_TMR_OPT_ONE_SHOT
或 OS_TMR_OPT_PERIODIC,如果設置為前者,說明是一個單次定時器;設置為后者則
表示是周期定時器。
callback,為軟件定時器的回調函數,當軟件定時器的定時時間到達時,會調用該函數。
callback_arg,回調函數的參數。
pname,為軟件定時器的名字。
perr,為錯誤信息。
軟件定時器的回調函數有固定的格式,我們必須按照這個格式編寫,軟件定時器的回
調函數格式為:void (*OS_TMR_CALLBACK)(void *ptmr, void *parg)。其中,函數名我們
可以自己隨意設置,而 ptmr 這個參數,軟件定時器用來傳遞當前定時器的控制塊指針,所
以我們一般設置其類型為 OS_TMR*類型,第二個參數(parg)為回調函數的參數,這個就
可以根據自己需要設置了,你也可以不用,但是必須有這個參數。
2) 開啟軟件定時器函數
任務可以通過調用函數 OSTmrStart 開啟某個軟件定時器,該函數的原型為:BOOLEAN OSTmrStart (OS_TMR *ptmr, INT8U *perr);
其中 ptmr 為要開啟的軟件定時器指針,perr 為錯誤信息。
3) 停止軟件定時器函數
任務可以通過調用函數 OSTmrStop 停止某個軟件定時器,該函數的原型為:
OSTmrStop (OS_TMR *ptmr,INT8U opt,void *callback_arg,INT8U *perr);
其中 ptmr 為要停止的軟件定時器指針。
opt 為停止選項,可以設置的值及其對應的意義為:
OS_TMR_OPT_NONE,直接停止,不做任何其他處理
OS_TMR_OPT_CALLBACK,停止,用初始化的參數執行一次回調函數
OS_TMR_OPT_CALLBACK_ARG,停止,用新的參數執行一次回調函數
callback_arg,新的回調函數參數。
perr,錯誤信息。
軟件定時器我們就介紹到這。
63.2 硬件設計
本節實驗功能簡介:本章我們在 UCOSII 里面創建 7 個任務:開始任務、LED 任務、觸摸
屏任務、隊列消息顯示任務、信號量集任務、按鍵掃描任務和主任務,開始任務用于創建郵箱、
消息隊列、信號量集以及其他任務,之后掛起;觸摸屏任務用于在屏幕上畫圖,測試 CPU 使
用率;隊列消息顯示任務請求消息隊列,在得到消息后顯示收到的消息數據;信號量集任務用
于測試信號量集,采用 OS_FLAG_WAIT_SET_ANY 的方法,任何按鍵按下(包括 TPAD),
該任務都會控制蜂鳴器發出“滴”的一聲;按鍵掃描任務用于按鍵掃描,優先級最高,將得到
的鍵值通過消息郵箱發送出去;主任務創建 3 個軟件定時器(定時器 1,100ms 溢出一次,顯
示 CPU 和內存使用率;定時 2,200ms 溢出一次,在固定區域不停的顯示不同顏色;定時
3,,100ms 溢出一次,用于自動發送消息到消息隊列),并通過查詢消息郵箱獲得鍵值,根據
鍵值執行 DS1 控制、控制軟件定時器 3 的開關、觸摸區域清屏、觸摸屏校和軟件定時器 2 的
開關控制等。
所要用到的硬件資源如下:
1) 指示燈 DS0 、DS1
2) 4 個機械按鍵(KEY0/KEY1/KEY2/KEY_UP)
3) TPAD 觸摸按鍵
4) 蜂鳴器
5) TFTLCD 模塊
這些,我們在前面的學習中都已經介紹過了。
63.3 軟件設計
本章,我們在第四十二章實驗 (實驗 37 )的基礎上修改,首先,是 UCOSII 代碼的添加,
具體方法同第 61 章一模一樣,本章就不再詳細介紹了。另外由于我們創建了 7 個任務,加上統
計任務、空閑任務和軟件定時器任務,總共 10 個任務,如果你還想添加其他任務,請把
OS_MAX_TASKS 的值適當改大。
另外,我們還需要在 os_cfg.h 里面修改軟件定時器管理部分的宏定義,修改如下:
#define OS_TMR_EN 1u //使能軟件定時器功能 #define OS_TMR_CFG_MAX 16u //最大軟件定時器個數 #define OS_TMR_CFG_NAME_EN 1u //使能軟件定時器命名 #define OS_TMR_CFG_WHEEL_SIZE 8u //軟件定時器輪大小 #define OS_TMR_CFG_TICKS_PER_SEC 100u //軟件定時器的時鐘節拍(10ms) #define OS_TASK_TMR_PRIO 0u //軟件定時器的優先級,設置為最高 這樣我們就使能 UCOSII 的軟件定時器功能了,并且設置最大軟件定時器個數為 16,定時 器輪大小為 8,軟件定時器時鐘節拍為 10ms(即定時器的最少溢出時間為 10ms)。 最后,我們只需要修改 main.c 文件了,打開 main.c,輸入如下代碼: //START 任務 //設置任務優先級 #define START_TASK_PRIO 10 //開始任務的優先級設置為最低 //設置任務堆棧大小 #define START_STK_SIZE 64 //任務堆棧 OS_STK START_TASK_STK[START_STK_SIZE]; //任務函數 void start_task(void *pdata); //LED 任務 //設置任務優先級 #define LED_TASK_PRIO 7 //設置任務堆棧大小 #define LED_STK_SIZE 64 //任務堆棧 OS_STK LED_TASK_STK[LED_STK_SIZE]; //任務函數 void led_task(void *pdata); //觸摸屏任務 //設置任務優先級 #define TOUCH_TASK_PRIO 6 //設置任務堆棧大小 #define TOUCH_STK_SIZE 128 //任務堆棧 OS_STK TOUCH_TASK_STK[TOUCH_STK_SIZE]; //任務函數 void touch_task(void *pdata); //隊列消息顯示任務 //設置任務優先級 #define QMSGSHOW_TASK_PRIO 5 //設置任務堆棧大小 #define QMSGSHOW_STK_SIZE 128 //任務堆棧 OS_STK QMSGSHOW_TASK_STK[QMSGSHOW_STK_SIZE]; //任務函數 void qmsgshow_task(void *pdata); //主任務 //設置任務優先級 #define MAIN_TASK_PRIO 4 //設置任務堆棧大小 #define MAIN_STK_SIZE 128 //任務堆棧 OS_STK MAIN_TASK_STK[MAIN_STK_SIZE]; //任務函數 void main_task(void *pdata); //信號量集任務 //設置任務優先級 #define FLAGS_TASK_PRIO 3 //設置任務堆棧大小 #define FLAGS_STK_SIZE 128 //任務堆棧 OS_STK FLAGS_TASK_STK[FLAGS_STK_SIZE]; //任務函數 void flags_task(void *pdata); //按鍵掃描任務 //設置任務優先級 #define KEY_TASK_PRIO 2 //設置任務堆棧大小 #define KEY_STK_SIZE 128 //任務堆棧 OS_STK KEY_TASK_STK[KEY_STK_SIZE]; //任務函數 void key_task(void *pdata); // OS_EVENT * msg_key; //按鍵郵箱事件塊 OS_EVENT * q_msg; //消息隊列 OS_TMR * tmr1; //軟件定時器 1 OS_TMR * tmr2; //軟件定時器 2 OS_TMR * tmr3; //軟件定時器 3 OS_FLAG_GRP * flags_key;//按鍵信號量集 void * MsgGrp[256]; //消息隊列存儲地址,最大支持 256 個消息 //軟件定時器 1 的回調函數 //每 100ms 執行一次,用于顯示 CPU 使用率和內存使用率 void tmr1_callback(OS_TMR *ptmr,void *p_arg) { static u16 cpuusage=0; static u8 tcnt=0; POINT_COLOR=BLUE; if(tcnt==5) { LCD_ShowxNum(202,10,cpuusage/5,3,16,0); //顯示 CPU 使用率 cpuusage=0; tcnt=0; } cpuusage+=OSCPUUsage; tcnt++; LCD_ShowxNum(202,30,my_mem_perused(SRAMIN),3,16,0);//顯示內存使用率 LCD_ShowxNum(202,50,((OS_Q*)(q_msg->OSEventPtr))->OSQEntries,3,16,0X80); //顯示隊列當前的大小 } //軟件定時器 2 的回調函數 void tmr2_callback(OS_TMR *ptmr,void *p_arg) { static u8 sta=0; switch(sta) { case 0: LCD_Fill(131,221,lcddev.width-1,lcddev.height-1,RED); break; case 1: LCD_Fill(131,221,lcddev.width-1,lcddev.height-1,GREEN); break; case 2: LCD_Fill(131,221,lcddev.width-1,lcddev.height-1,BLUE); break; case 3: LCD_Fill(131,221,lcddev.width-1,lcddev.height-1,MAGENTA); break; case 4: LCD_Fill(131,221,lcddev.width-1,lcddev.height-1,GBLUE); break; case 5: LCD_Fill(131,221,lcddev.width-1,lcddev.height-1,YELLOW); break; case 6: LCD_Fill(131,221,lcddev.width-1,lcddev.height-1,BRRED); break; } sta++; if(sta>6)sta=0; } //軟件定時器 3 的回調函數 void tmr3_callback(OS_TMR *ptmr,void *p_arg) { u8* p; u8 err; static u8 msg_cnt=0; //msg 編號 p=mymalloc(SRAMIN,13); //申請 13 個字節的內存 if(p) { sprintf((char*)p,"ALIENTEK %03d",msg_cnt); msg_cnt++; err=OSQPost(q_msg,p); //發送隊列 if(err!=OS_ERR_NONE) //發送失敗 { myfree(SRAMIN,p); //釋放內存 OSTmrStop(tmr3,OS_TMR_OPT_NONE,0,&err); //關閉軟件定時器 3 } } } //加載主界面 void ucos_load_main_ui(void) { LCD_Clear(WHITE); //清屏 POINT_COLOR=RED; //設置字體為紅色 LCD_ShowString(10,10,200,16,16,"Explorer STM32"); LCD_ShowString(10,30,200,16,16,"UCOSII TEST3"); LCD_ShowString(10,50,200,16,16,"ATOM@ALIENTEK");LCD_ShowString(10,75,240,16,16,"TPAD:TMR2 SW KEY_UP:ADJUST");LCD_ShowString(10,95,240,16,16,"KEY0:DS0 KEY1:Q SW KEY2:CLR"); LCD_DrawLine(0,70,lcddev.width-1,70); LCD_DrawLine(150,0,150,70); LCD_DrawLine(0,120,lcddev.width-1,120); LCD_DrawLine(0,220,lcddev.width-1,220); LCD_DrawLine(130,120,130,lcddev.height-1); LCD_ShowString(5,125,240,16,16,"QUEUE MSG");//隊列消息 LCD_ShowString(5,150,240,16,16,"Message:"); LCD_ShowString(5+130,125,240,16,16,"FLAGS");//信號量集 LCD_ShowString(5,225,240,16,16,"TOUCH"); //觸摸屏 LCD_ShowString(5+130,225,240,16,16,"TMR2"); //隊列消息 POINT_COLOR=BLUE;//設置字體為藍色LCD_ShowString(170,10,200,16,16,"CPU: %");LCD_ShowString(170,30,200,16,16,"MEM: %");LCD_ShowString(170,50,200,16,16," Q :000"); delay_ms(300); } int main(void) {HAL_Init(); //初始化 HAL 庫Stm32_Clock_Init(336,8,2,7); //設置時鐘,168Mhz delay_init(168); //初始化延時函數 uart_init(115200); //初始化 USART LED_Init(); //初始化 LED KEY_Init(); //初始化按鍵 BEEP_Init(); //初始化蜂鳴器LCD_Init(); //初始化 LCDTPAD_Init(8); //初始化觸摸按鍵tp_dev.init(); //初始化觸摸屏my_mem_init(SRAMIN); //初始化內部內存池ucos_load_main_ui(); //加載主界面 OSInit(); //UCOS 初始化OSTaskCreateExt((void(*)(void*) )start_task, //任務函數(void* )0, //傳遞給任務函數的參數(OS_STK* )&START_TASK_STK[START_STK_SIZE-1], //任務堆棧棧頂(INT8U )START_TASK_PRIO, //任務優先級(INT16U )START_TASK_PRIO, //任務 ID,這里設置為和優先級一樣(OS_STK* )&START_TASK_STK[0], //任務堆棧棧底(INT32U )START_STK_SIZE, //任務堆棧大小(void* )0, //用戶補充的存儲區(INT16U )OS_TASK_OPT_STK_CHK| OS_TASK_OPT_STK_CLR|OS_TASK_OPT_SAVE_FP); //任務選項,為了保險起見,所有任務都保存浮點寄存器的值 OSStart(); //開始任務 } /// //畫水平線 //x0,y0:坐標 //len:線長度 //color:顏色 void gui_draw_hline(u16 x0,u16 y0,u16 len,u16 color) { if(len==0)return; LCD_Fill(x0,y0,x0+len-1,y0,color); } //畫實心圓 //x0,y0:坐標 //r:半徑 //color:顏色 void gui_fill_circle(u16 x0,u16 y0,u16 r,u16 color) { u32 i; u32 imax = ((u32)r*707)/1000+1; u32 sqmax = (u32)r*(u32)r+(u32)r/2; u32 x=r; gui_draw_hline(x0-r,y0,2*r,color); for (i=1;i<=imax;i++) { if ((i*i+x*x)>sqmax)// draw lines from outside { if (x>imax) { gui_draw_hline (x0-i+1,y0+x,2*(i-1),color); gui_draw_hline (x0-i+1,y0-x,2*(i-1),color); } x--; } // draw lines from inside (center) gui_draw_hline(x0-x,y0+i,2*x,color); gui_draw_hline(x0-x,y0-i,2*x,color); } } //兩個數之差的絕對值 //x1,x2:需取差值的兩個數 //返回值:|x1-x2| u16 my_abs(u16 x1,u16 x2) { if(x1>x2)return x1-x2; else return x2-x1; } //畫一條粗線 //(x1,y1),(x2,y2):線條的起始坐標 //size:線條的粗細程度 //color:線條的顏色 void lcd_draw_bline(u16 x1, u16 y1, u16 x2, u16 y2,u8 size,u16 color) { u16 t; int xerr=0,yerr=0,delta_x,delta_y,distance; int incx,incy,uRow,uCol; if(x1<size|| x2<size||y1<size|| y2<size)return; delta_x=x2-x1; //計算坐標增量 delta_y=y2-y1; uRow=x1; uCol=y1; if(delta_x>0)incx=1; //設置單步方向 else if(delta_x==0)incx=0;//垂直線 else {incx=-1;delta_x=-delta_x;} if(delta_y>0)incy=1; else if(delta_y==0)incy=0;//水平線 else{incy=-1;delta_y=-delta_y;} if( delta_x>delta_y)distance=delta_x; //選取基本增量坐標軸 else distance=delta_y; for(t=0;t<=distance+1;t++ )//畫線輸出 { gui_fill_circle(uRow,uCol,size,color);//畫點 xerr+=delta_x ; yerr+=delta_y ; if(xerr>distance) { xerr-=distance; uRow+=incx; } if(yerr>distance) { yerr-=distance; uCol+=incy; } } } /// //開始任務 void start_task(void *pdata) { OS_CPU_SR cpu_sr=0;u8 err; pdata=pdata; msg_key=OSMboxCreate((void*)0); //創建消息郵箱 q_msg=OSQCreate(&MsgGrp[0],256); //創建消息隊列 flags_key=OSFlagCreate(0,&err); //創建信號量集 OSStatInit(); //開啟統計任務 OS_ENTER_CRITICAL(); //進入臨界區(關閉中斷)//LED 任務OSTaskCreateExt((void(*)(void*) )led_task,(void* )0, (OS_STK* )&LED_TASK_STK[LED_STK_SIZE-1],(INT8U )LED_TASK_PRIO,(INT16U )LED_TASK_PRIO,(OS_STK* )&LED_TASK_STK[0],(INT32U )LED_STK_SIZE,(void* )0,(INT16U )OS_TASK_OPT_STK_CHK| OS_TASK_OPT_STK_CLR|OS_TASK_OPT_SAVE_FP); //觸摸任務OSTaskCreateExt((void(*)(void*) )touch_task,(void* )0,(OS_STK* )&TOUCH_TASK_STK[TOUCH_STK_SIZE-1],(INT8U )TOUCH_TASK_PRIO,(INT16U )TOUCH_TASK_PRIO,(OS_STK* )&TOUCH_TASK_STK[0],(INT32U )TOUCH_STK_SIZE,(void* )0,(INT16U )OS_TASK_OPT_STK_CHK| OS_TASK_OPT_STK_CLR|OS_TASK_OPT_SAVE_FP); //消息隊列顯示任務OSTaskCreateExt((void(*)(void*) )qmsgshow_task,(void* )0, (OS_STK* )&QMSGSHOW_TASK_STK[QMSGSHOW_STK_SIZE-1],(INT8U )QMSGSHOW_TASK_PRIO,(INT16U )QMSGSHOW_TASK_PRIO,(OS_STK* )&QMSGSHOW_TASK_STK[0],(INT32U )QMSGSHOW_STK_SIZE,(void* )0,(INT16U )OS_TASK_OPT_STK_CHK| OS_TASK_OPT_STK_CLR|OS_TASK_OPT_SAVE_FP);//主任務OSTaskCreateExt((void(*)(void*) )main_task,(void* )0,(OS_STK* )&MAIN_TASK_STK[MAIN_STK_SIZE-1],(INT8U )MAIN_TASK_PRIO,(INT16U )MAIN_TASK_PRIO,(OS_STK* )&MAIN_TASK_STK[0],(INT32U )MAIN_STK_SIZE,(void* )0,(INT16U )OS_TASK_OPT_STK_CHK| OS_TASK_OPT_STK_CLR|OS_TASK_OPT_SAVE_FP);//信號量集任務 OSTaskCreateExt((void(*)(void*) )flags_task,(void* )0,(OS_STK* )&FLAGS_TASK_STK[FLAGS_STK_SIZE-1],(INT8U )FLAGS_TASK_PRIO,(INT16U )FLAGS_TASK_PRIO,(OS_STK* )&FLAGS_TASK_STK[0],(INT32U )FLAGS_STK_SIZE,(void* )0,(INT16U )OS_TASK_OPT_STK_CHK| OS_TASK_OPT_STK_CLR|OS_TASK_OPT_SAVE_FP);//按鍵任務OSTaskCreateExt((void(*)(void*) )key_task,(void* )0,(OS_STK* )&KEY_TASK_STK[KEY_STK_SIZE-1],(INT8U )KEY_TASK_PRIO,(INT16U )KEY_TASK_PRIO,(OS_STK* )&KEY_TASK_STK[0],(INT32U )KEY_STK_SIZE,(void* )0,(INT16U )OS_TASK_OPT_STK_CHK| OS_TASK_OPT_STK_CLR|OS_TASK_OPT_SAVE_FP);OS_EXIT_CRITICAL(); //退出臨界區(開中斷) OSTaskSuspend(START_TASK_PRIO); //掛起開始任務 } //LED 任務 void led_task(void *pdata) { u8 t; while(1) { t++; delay_ms(10); if(t==8)LED0=1; //LED0 滅 if(t==100) //LED0 亮 { t=0; LED0=0; } } } //觸摸屏任務 void touch_task(void *pdata) { u32 cpu_sr; u16 lastpos[2]; //最后一次的數據 while(1) { tp_dev.scan(0); if(tp_dev.sta&TP_PRES_DOWN) //觸摸屏被按下 { if(tp_dev.x[0]<(130-1)&&tp_dev.y[0]<lcddev.height&&tp_dev.y[0]>(220+1)) { if(lastpos[0]==0XFFFF) { lastpos[0]=tp_dev.x[0]; lastpos[1]=tp_dev.y[0]; } OS_ENTER_CRITICAL(); //進入臨界段,防止其他任務,打斷 LCD 操作,導致液晶亂序. lcd_draw_bline(lastpos[0],lastpos[1],tp_dev.x[0],tp_dev.y[0],2,RED);//畫線 OS_EXIT_CRITICAL(); lastpos[0]=tp_dev.x[0]; lastpos[1]=tp_dev.y[0]; } }else { lastpos[0]=0XFFFF; delay_ms(10); //沒有按鍵按下的時候 } } } //隊列消息顯示任務 void qmsgshow_task(void *pdata) { u8 *p; u8 err; while(1) { p=OSQPend(q_msg,0,&err);//請求消息隊列 LCD_ShowString(5,170,240,16,16,p);//顯示消息 myfree(SRAMIN,p); delay_ms(500); } } //主任務 void main_task(void *pdata) { u32 key=0; u8 err; u8 tmr2sta=1; //軟件定時器 2 開關狀態 u8 tmr3sta=0; //軟件定時器 3 開關狀態 u8 flagsclrt=0;//信號量集顯示清零倒計時 tmr1=OSTmrCreate(10,10,OS_TMR_OPT_PERIODIC,(OS_TMR_CALLBACK) tmr1_callback,0,"tmr1",&err); //100ms 執行一次 tmr2=OSTmrCreate(10,20,OS_TMR_OPT_PERIODIC,(OS_TMR_CALLBACK) tmr2_callback,0,"tmr2",&err); //200ms 執行一次 tmr3=OSTmrCreate(10,10,OS_TMR_OPT_PERIODIC,(OS_TMR_CALLBACK) tmr3_callback,0,"tmr3",&err); //100ms 執行一次 OSTmrStart(tmr1,&err);//啟動軟件定時器 1 OSTmrStart(tmr2,&err);//啟動軟件定時器 2 while(1) { key=(u32)OSMboxPend(msg_key,10,&err); if(key) { flagsclrt=51;//500ms 后清除 OSFlagPost(flags_key,1<<(key-1),OS_FLAG_SET,&err); //設置對應的信號量為 1 } if(flagsclrt)//倒計時 { flagsclrt--; if(flagsclrt==1)LCD_Fill(140,162,239,162+16,WHITE);//清除顯示 } switch(key) { case 1://控制 DS1 LED1=!LED1; break; case 2://控制軟件定時器 3 tmr3sta=!tmr3sta; if(tmr3sta)OSTmrStart(tmr3,&err); else OSTmrStop(tmr3,OS_TMR_OPT_NONE,0,&err);//關閉軟件定時器 3 break; case 3://清除 LCD_Fill(0,221,129,lcddev.height-1,WHITE); break; case 4://校準 OSTaskSuspend(TOUCH_TASK_PRIO); //掛起觸摸屏任務 OSTaskSuspend(QMSGSHOW_TASK_PRIO);//掛起隊列信息顯示任務 OSTmrStop(tmr1,OS_TMR_OPT_NONE,0,&err);//關閉軟件定時器 1 if(tmr2sta)OSTmrStop(tmr2,OS_TMR_OPT_NONE,0,&err); //關閉軟件定時器 2 if((tp_dev.touchtype&0X80)==0)TP_Adjust(); OSTmrStart(tmr1,&err); //重新開啟軟件定時器 1 if(tmr2sta)OSTmrStart(tmr2,&err); //重新開啟軟件定時器 2 OSTaskResume(TOUCH_TASK_PRIO); //解掛 OSTaskResume(QMSGSHOW_TASK_PRIO); //解掛 ucos_load_main_ui(); //重新加載主界面 break; case 5://軟件定時器 2 開關 tmr2sta=!tmr2sta; if(tmr2sta)OSTmrStart(tmr2,&err); //開啟軟件定時器 2 else { OSTmrStop(tmr2,OS_TMR_OPT_NONE,0,&err);//關閉軟件定時器 2 LCD_ShowString(148,262,240,16,16,"TMR2 STOP"); //提示定時器 2 關閉了 } break; } delay_ms(10); } } //信號量集處理任務 void flags_task(void *pdata) { u16 flags; u8 err; while(1) { flags=OSFlagPend(flags_key,0X001F,OS_FLAG_WAIT_SET_ANY,0,&err); //等待信號量 if(flags&0X0001)LCD_ShowString(140,162,240,16,16,"KEY0 DOWN "); if(flags&0X0002)LCD_ShowString(140,162,240,16,16,"KEY1 DOWN "); if(flags&0X0004)LCD_ShowString(140,162,240,16,16,"KEY2 DOWN "); if(flags&0X0008)LCD_ShowString(140,162,240,16,16,"KEY_UP DOWN"); if(flags&0X0010)LCD_ShowString(140,162,240,16,16,"TPAD DOWN "); BEEP=1; delay_ms(50); BEEP=0; OSFlagPost(flags_key,0X001F,OS_FLAG_CLR,&err);//全部信號量清零 } } //按鍵掃描任務 void key_task(void *pdata) { u8 key; while(1) { key=KEY_Scan(0); if(key==0) { if(TPAD_Scan(0))key=5; } if(key)OSMboxPost(msg_key,(void*)key);//發送消息 delay_ms(10); } }本章 main.c 的代碼有點多,因為我們創建了 7 個任務,3 個軟件定時器及其回調函數,所
以,整個代碼有點多,我們創建的 7 個任務為:start_task、led_task、touch_task、qmsgshow_task 、
flags_task 、main_task 和 key_task,優先級分別是 10 和 7~2,堆棧大小除了 start_task 和 led_task
是 64,其他都是 128。
我們還創建了 3 個軟件定時器 tmr1、tmr2 和 tmr3,tmr1 用于顯示 CPU 使用率和內存使用
率,每 100ms 執行一次;tmr2 用于在 LCD 的右下角區域不停的顯示各種顏色,每 200ms 執行
一次;tmr3 用于定時向隊列發送消息,每 100ms 發送一次。
本章,我們依舊使用消息郵箱 msg_key 在按鍵任務和主任務之間傳遞鍵值數據,我們創建
信號量集 flags_key,在主任務里面將按鍵鍵值通過信號量集傳遞給信號量集處理任務 flags_task,
實現按鍵信息的顯示以及發出按鍵提示音。
本章,我們還創建了一個大小為 256 的消息隊列 q_msg,通過軟件定時器 tmr3 的回調函數
向消息隊列發送消息,然后在消息隊列顯示任務 qmsgshow_task 里面請求消息隊列,并在 LCD
上面顯示得到的消息。消息隊列還用到了動態內存管理。
在主任務 main_task 里面,我們實現了 63.2 節介紹的功能:KEY0 控制 LED1 亮滅;KEY1
控制軟件定時器 tmr3 的開關,間接控制隊列信息的發送;KEY2 清除觸摸屏輸入;KEY_UP 用
于觸摸屏校準,在校準的時候,要先掛起觸摸屏任務、隊列消息顯示任務,并停止軟件定時器
tmr1 和 tmr2,否則可能對校準時的 LCD 顯示造成干擾;TPAD 按鍵用于控制軟件定時器 tmr2
的開關,間接控制屏幕顯示。
軟件設計部分就為大家介紹到這里。
63.4 下載驗證
在代碼編譯成功之后,我們通過下載代碼到探索者 STM32F4 開發板上,可以看到 LCD 顯
示界面如圖 63.4.1 所示:
圖 63.4.1 初始界面
從圖中可以看出,默認狀態下,CPU 使用率為 10%左右。比上一章多出很多,這主要是
key_task 里面增加不停的刷屏(tmr2)操作導致的。
通過按 KEY0,可以控制 DS1 的亮滅;
通過按 KEY1 則可以啟動 tmr3 控制消息隊列發送,可以在 LCD 上面看到 Q 和 MEM 的值
慢慢變大(說明隊列消息在增多,占用內存也隨著消息增多而增大),在 QUEUE MSG 區,開
始顯示隊列消息,再按一次 KEY1 停止 tmr3,此時可以看到 Q 和 MEM 逐漸減小。當 Q 值變
為 0 的時候,QUEUE MSG 也停止顯示(隊列為空)。
通過 KEY2 按鍵,清除 TOUCH 區域的輸入。
通過 KEY_UP 按鍵,可以進行觸摸屏校準。
通過 TPAD 按鍵,可以啟動/停止 tmr2,從而控制屏幕的刷新。
在 TOUCH 區域,可以輸入手寫內容。
任何按鍵按下,蜂鳴器都會發出“滴”的一聲,提示按鍵被按下,同時在 FLAGS 區域顯
示按鍵信息。
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的stm32 ucosii消息队列 串口_正点原子STM32F407探索者开发板资料连载第六十三章 UCOSII 实验...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: el表达式的语法_「手把手教python
- 下一篇: @order注解_Spring Boot