1.UCOSII原理
UCOSII 是一個可以基於ROM 運行的、可裁減的、搶佔式、實時多任務內核,具有高度可
移植性,特別適合於微處理器和控制器,是和很多商業操作系統性能相當的實時操作系統
(RTOS)。
UCOSII 是專門爲計算機的嵌入式應用設計的, 絕大部分代碼是用C 語言編寫的。CPU 硬
件相關部分是用匯編語言編寫的、總量約200 行的彙編語言部分被壓縮到最低限度,爲的是便
於移植到任何一種其它的CPU 上。用戶只要有標準的ANSI 的C 交叉編譯器,有彙編器、連
接器等軟件工具,就可以將UCOSII 嵌人到開發的產品中。UCOSII 具有執行效率高、佔用空間
小、實時性能優良和可擴展性強等特點, 最小內核可編譯至 2KB 。UCOSII 已經移植到了幾
乎所有知名的CPU 上。
UCOSII體系結構如圖所示
UCOSII在版本2.80後,支持任務數提高到255個。
任務,其實就是一個死循環函數,該函數實現一定的功能,一個工程可以有很多這樣的任務(最多255個),UCOSII對這些任務進行調度管理,讓這些任務可以併發工作(併發的意思是:各任務輪流佔用CPU,而不是同時佔用,任何時候還是隻有1個任務能夠佔用CPU),這就是UCOSII最基本的功能。Ucos任務的一般格式爲:
void MyTask(void *pdata){
//任務準備工作
while(1){
//任務MyTask實際代碼
...
OSTimeDlyHMSM();//調用任務延時函數,釋放cpu控制權
}
}
下面有幾個概念需要掌握
1.任務優先級
每個任務都有一個唯一的優先級,換言之,每個任務的優先級都不一樣。在UCOSII中,優先級高的任務比優先級低的任務有CPU優先使用權,只有高優先級的任務讓出使用權時(用的多的是函數OSTimeDlyHMSM();//調用任務延時函數,釋放cpu控制權),低優先級的任務纔可以使用CPU。
2.任務堆棧
任務堆棧是存儲器中的一塊連續區域,每個任務都有自己的堆棧,主要是爲了滿足任務切換和響應中斷時保存CPU寄存器中的內容。
3.任務控制塊
任務控制塊OS_TCB,用來記錄任務堆棧指針,任務當前狀態以及任務優先級等任務屬性。UCOSII的任何任務都是通過任務控制塊(TCB)來控制,一旦任務創建,任務控制塊OS_TCB就會被賦值。每個任務管理塊有3個最重要的參數:任務函數指針;任務堆棧指針;任務優先級。任務控制塊就是任務在系統裏面的身份證(UCOSII通過優先級識別任務)。
4 .任務就緒表
任務就緒表,用來記錄系統中所有處於就緒狀態的任務。它是一個位圖,系統中每個任務都在這個位圖中佔據一個進制位,該位置的狀態(1或者0)就表示任務是否處於就緒狀態。
5.任務調度
任務調度的作用一是在任務就緒表中查找優先級最高的就緒任務,二是實現任務的切換。比如說,當一個任務釋放cpu控制權後,進行一次任務調度,這個時候任務調度器首先要去任務就緒表查詢優先級最高的就緒任務,查到之後,進行一次任務切換,轉而去執行下一個任務。
下面是幾個關於任務的函數:
1)任務建立函數
一般使用OSTaskCreate
定義爲
INT8U OSTaskCreate (void (*task)(void *p_arg),
void *p_arg,
OS_STK *ptos,
INT8U prio)
解釋如下
該函數包括4個參數:task:是指向任務代碼的指針;p_arg:是任務開始執行時,傳遞給任務參數的指針;ptos:是分配給任務的堆棧的棧頂指針;prio是分配給任務的優先級。
/*
*********************************************************************************************************
* CREATE A TASK
*
* Description: This function is used to have uC/OS-II manage the execution of a task. Tasks can either
* be created prior to the start of multitasking or by a running task. A task cannot be
* created by an ISR.
*
* Arguments : task is a pointer to the task's code
*
* p_arg is a pointer to an optional data area which can be used to pass parameters to
* the task when the task first executes. Where the task is concerned it thinks
* it was invoked and passed the argument 'p_arg' as follows:
*
* void Task (void *p_arg)
* {
* for (;;) {
* Task code;
* }
* }
*
* ptos is a pointer to the task's top of stack. If the configuration constant
* OS_STK_GROWTH is set to 1, the stack is assumed to grow downward (i.e. from high
* memory to low memory). 'pstk' will thus point to the highest (valid) memory
* location of the stack. If OS_STK_GROWTH is set to 0, 'pstk' will point to the
* lowest memory location of the stack and the stack will grow with increasing
* memory locations.
*
* prio is the task's priority. A unique priority MUST be assigned to each task and the
* lower the number, the higher the priority.
*
* Returns : OS_ERR_NONE if the function was successful.
* OS_PRIO_EXIT if the task priority already exist
* (each task MUST have a unique priority).
* OS_ERR_PRIO_INVALID if the priority you specify is higher that the maximum allowed
* (i.e. >= OS_LOWEST_PRIO)
* OS_ERR_TASK_CREATE_ISR if you tried to create a task from an ISR.
*********************************************************************************************************
*/
2)任務刪除函數
任務刪除,是把任務置於睡眠狀態。一般使用OSTaskDel
原型爲
INT8U OSTaskDel(INT8U prio);
其中參數prio就是要刪除任務的優先級
特別注意:任務不能隨便刪除,必須在確保被刪除任務的資源被釋放的前提下才能刪除!
3)請求任務刪除函數
向被刪除任務發送刪除請求,實現任務釋放自身佔用的資源並刪除。函數爲OSTaskDelReq
,原型如下
INT8U OSTaskDelReq(INT8U prio);
其中參數prio就是要刪除任務的優先級
4)優先級更改函數
函數爲OSTaskChangePrio
,其原型如下
INT8U OSTaskChangePrio(INT8U oldprio,INT8U newprio);
其中參數oldprio是要任務的之前的優先級,newprio是更改之後的優先級。
5)任務掛起函數
任務掛起函數把任務的就緒標誌刪除,做好任務掛起記錄,被掛起的任務,在解掛之後可以繼續運行。任務觀其函數爲OsTaskSuspend
定義爲
INT8U OSTaskSuspend (INT8U prio)
{
BOOLEAN self;
OS_TCB *ptcb;
INT8U y;
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
#if OS_ARG_CHK_EN > 0u
if (prio == OS_TASK_IDLE_PRIO) { /* Not allowed to suspend idle task */
return (OS_ERR_TASK_SUSPEND_IDLE);
}
if (prio >= OS_LOWEST_PRIO) { /* Task priority valid ? */
if (prio != OS_PRIO_SELF) {
return (OS_ERR_PRIO_INVALID);
}
}
#endif
OS_ENTER_CRITICAL();
if (prio == OS_PRIO_SELF) { /* See if suspend SELF */
prio = OSTCBCur->OSTCBPrio;
self = OS_TRUE;
} else if (prio == OSTCBCur->OSTCBPrio) { /* See if suspending self */
self = OS_TRUE;
} else {
self = OS_FALSE; /* No suspending another task */
}
ptcb = OSTCBPrioTbl[prio];
if (ptcb == (OS_TCB *)0) { /* Task to suspend must exist */
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_SUSPEND_PRIO);
}
if (ptcb == OS_TCB_RESERVED) { /* See if assigned to Mutex */
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_NOT_EXIST);
}
y = ptcb->OSTCBY;
OSRdyTbl[y] &= (OS_PRIO)~ptcb->OSTCBBitX; /* Make task not ready */
if (OSRdyTbl[y] == 0u) {
OSRdyGrp &= (OS_PRIO)~ptcb->OSTCBBitY;
}
ptcb->OSTCBStat |= OS_STAT_SUSPEND; /* Status of task is 'SUSPENDED' */
OS_EXIT_CRITICAL();
if (self == OS_TRUE) { /* Context switch only if SELF */
OS_Sched(); /* Find new highest priority task */
}
return (OS_ERR_NONE);
}
解釋如下
/*
*********************************************************************************************************
* SUSPEND A TASK
*
* Description: This function is called to suspend a task. The task can be the calling task if the
* priority passed to OSTaskSuspend() is the priority of the calling task or OS_PRIO_SELF.
*
* Arguments : prio is the priority of the task to suspend. If you specify OS_PRIO_SELF, the
* calling task will suspend itself and rescheduling will occur.
*
* Returns : OS_ERR_NONE if the requested task is suspended
* OS_ERR_TASK_SUSPEND_IDLE if you attempted to suspend the idle task which is not allowed.
* OS_ERR_PRIO_INVALID if the priority you specify is higher that the maximum allowed
* (i.e. >= OS_LOWEST_PRIO) or, you have not specified OS_PRIO_SELF.
* OS_ERR_TASK_SUSPEND_PRIO if the task to suspend does not exist
* OS_ERR_TASK_NOT_EXITS if the task is assigned to a Mutex PIP
*
* Note : You should use this function with great care. If you suspend a task that is waiting for
* an event (i.e. a message, a semaphore, a queue ...) you will prevent this task from
* running when the event arrives.
*********************************************************************************************************
*/
6)任務恢復函數
有任務掛起函數,就有任務恢復函數,通過該函數將被掛起的任務恢復,讓調度器能夠重新調度該函數。函數爲OSTaskResume
原型爲
INT8U OSTaskResume(INT8U prio);
UCOSII信號量和郵箱
任務間的同步依賴於任務間的通信。在UCOSII 中,是使用信號量、郵箱(消息郵箱)和消息隊列這些被稱作事件的中間環節來實現任務之間的通信。使用事件控制塊來描述具體的信息,事件控制塊結構體
typedef struct{
INT8U OSEventType;//事件的類型
INT16U OSEventCnt;//信號量計數器
void *OSEventPtr; //消息或消息隊列的指針
INT8U OSEventGrp; //等待事件的任務組
INT8U OSEventTbl[OS_EVENT_TBL_SIZE];//任務等待表
#if OS_EVENT_NAME_EN > 0u
INT8U *OSEventName; //事件名
#endif
}OS_EVENT;
信號量
信號量是一類事件,它是爲了給共享資源設立一個標誌,該標誌表示該共享資源的佔用情況。這樣,當一個任務在訪問共享資源之前,就可以先對這個標誌進行查詢,從而在瞭解資源被佔用的情況之後,再來決定自己的行爲。
信號量可以分爲兩種:一種是二值型信號量(也稱爲互斥信號量),另外一種是N 值信號量。
郵箱
在多任務操作系統中,常常需要在任務與任務之間通過傳遞“消息”的方式來進行通信。爲此需要在內存中創建一個存儲空間作爲該消息的緩衝區,稱爲消息緩衝區,這樣在任務間傳遞數據(消息)的最簡單辦法就是傳遞消息緩衝區的指針。把用來傳遞消息緩衝區指針的數據結構叫做郵箱(消息郵箱)。
在UCOSII中,通過事件控制塊的OSEventPrt
來傳遞消息緩衝區指針,同時使事件控制塊的成員OSEventType
爲常數OS_EVENT_TYPE_MBOX,則該事件控制塊就叫做消息郵箱。
下面看一下關於信號量的函數
1)創建信號量
函數爲OSSemCreate
OS_EVENT *OSSemCreate (INT16U cnt);
該函數返回值爲已創建的信號量的指針,而參數cnt則是信號量計數器(OSEventCnt)的初始值。
2)請求信號量函數
函數爲OSSemPend
void OSSemPend ( OS_EVENT *pevent, INT16U timeout, INT8U *err);
其中,參數pevent是被請求信號量的指針,timeout爲等待時限,err爲錯誤信息。
爲防止任務因得不到信號量而處於長期的等待狀態,函數OSSemPend
允許用參數timeout設置一個等待時間的限制,當任務等待的時間超過timeout時可以結束等待狀態而 進入就緒狀態。如果參數timeout被設置爲0,則表明任務的等待時間爲無限長。
3)發送信號量函數
任務獲得信號量,並在訪問共享資源結束以後要釋放信號量,釋放信號量也叫做發送信號量,發送信號通過OSSemPost
函數實現 。OSSemPost 函數在對信號量的計數器操作之前,首先要檢查是否還有等待該信號量的任務。如果沒有,就把信號量計數器OSEventCnt加一;如果有,則調用調度器OS_Sched( )去運行等待任務中優先級別最高的任務。函數OSSemPost的原型爲:
INT8U OSSemPost(OS_EVENT *pevent);
其中,pevent爲信號量指針,該函數在調用成功後,返回值爲OS_ON_ERR,否則會根據具體錯誤返回OS_ERR_EVENT_TYPE、OS_SEM_OVF。
4)刪除信號量
應用程序如果不需要某個信號量,可以調用函數OSSemDel
來刪除該信號量,該函數的原型爲:
OS_EVENT *OSSemDel (OS_EVENT *pevent,INT8U opt, INT8U *err);
其中,pevent爲要刪除的信號量指針,opt爲刪除條件選項,err爲錯誤信息。
下面是關於郵箱的函數
1)創建郵箱
創建郵箱通過函數OSMboxCreate
實現,該函數原型爲:
OS_EVENT *OSMboxCreate (void *msg);
函數中的參數msg爲消息的指針,函數的返回值爲消息郵箱的指針。
調用函數OSMboxCreate需先定義msg的初始值。在一般的情況下,這個初始值爲NULL;但也可以事先定義一個郵箱,然後把這個郵箱的指針作爲參數傳遞到函數OSMboxCreate 中,使之一開始就指向一個郵箱。
2) 向郵箱發送消息函數
任務可以通過調用函數OSMboxPost
向消息郵箱發送消息,這個函數的原型爲:
INT8U OSMboxPost (OS_EVENT *pevent,void *msg);
其中pevent爲消息郵箱的指針,msg爲消息指針。
3) 請求郵箱函數
當一個任務請求郵箱時需要調用函數OSMboxPend
,這個函數的主要作用就是查看郵箱指針OSEventPtr是否爲NULL,如果不是NULL就把郵箱中的消息指針返回給調用函數的任務,同時用OS_NO_ERR通過函數的參數err通知任務獲取消息成功;如果郵箱指針OSEventPtr是NULL,則使任務進入等待狀態,並引發一次任務調度。
函數OSMboxPend的原型爲:
void *OSMboxPend (OS_EVENT *pevent, INT16U timeout, INT8U *err);
其中pevent爲請求郵箱指針,timeout爲等待時限,err爲錯誤信息。
4) 查詢郵箱狀態函數
任務可以通過調用函數OSMboxQuery
查詢郵箱的當前狀態。該函數原型爲:
INT8U OSMboxQuery(OS_EVENT *pevent,OS_MBOX_DATA *pdata);
其中pevent爲消息郵箱指針,pdata爲存放郵箱信息的結構。
5) 刪除郵箱函數
在郵箱不再使用的時候,我們可以通過調用函數OSMboxDel
來刪除一個郵箱,該函數原型爲:
OS_EVENT *OSMboxDel(OS_EVENT *pevent,INT8U opt,INT8U *err);
其中pevent爲消息郵箱指針,opt爲刪除選項,err爲錯誤信息。
2.UCOSII實驗代碼
下面是一個STM32mini板實例:
主要功能:程序初始時,LED0燈閃爍,表示任務1在運行;串口顯示錶示任務2在運行。
通過信號量和郵箱,實現KEY0控制LED0任務(任務1)的掛起,KEY1實現串口顯示任務(任務2)的刪除,並且實現LED1燈的亮滅;WK_UP實現LED0任務(任務1)的恢復。
實驗結果:只有窗口顯示部分
main.c文件
#include "led.h"
#include "delay.h"
#include "sys.h"
#include "usart.h"
#include "includes.h"
#include "key.h"
#include "usart.h"
//START 任務
//設置任務優先級
#define START_TASK_PRIO 10 ///開始任務的優先級爲最低
//設置任務堆棧大小
#define START_STK_SIZE 128
//任務任務堆棧
OS_STK START_TASK_STK[START_STK_SIZE];
//任務函數
void start_task(void *pdata);
//LED0任務
//設置任務優先級
#define LED0_TASK_PRIO 7
//設置任務堆棧大小
#define LED0_STK_SIZE 64
//任務堆棧
OS_STK LED0_TASK_STK[LED0_STK_SIZE];
//任務函數
void led0_task(void *pdata);
//LED1任務
//設置任務優先級
#define LED1_TASK_PRIO 6
//設置任務堆棧大小
#define LED1_STK_SIZE 64
//任務堆棧
OS_STK LED1_TASK_STK[LED1_STK_SIZE];
//任務函數
void led1_task(void *pdata);
//key傳遞函數
//設置任務優先級
#define KEY_TASK_PRIO 5
//設置任務堆棧大小
#define KEY_STK_SIZE 64
//任務堆棧
OS_STK KEY_TASK_STK[KEY_STK_SIZE];
//任務函數
void key_task(void *pdata);
//按鍵掃描任務
//設置任務優先級
#define SCAN_TASK_PRIO 4
//設置任務堆棧大小
#define SCAN_STK_SIZE 64
//任務堆棧
OS_STK SCAN_TASK_STK[SCAN_STK_SIZE];
//任務函數
void scan_task(void *pdata);
//串口顯示任務
#define FLOAT_TASK_PRIO 8
//設置任務堆棧大小
#define FLOAT_STK_SIZE 128
//任務堆棧
//如果任務中使用printf來打印浮點數據的話一點要8字節對齊
__align(8) OS_STK FLOAT_TASK_STK[FLOAT_STK_SIZE];
//任務函數
void float_task(void *pdata);
OS_EVENT * msg_key; //按鍵郵箱時間塊指針
OS_EVENT * sem_led0; //LED0信號量指針
OS_EVENT * sem_led1; //LED1信號量指針
OS_EVENT * sem_print;//WK_UP信號量指針
int main(void)
{
delay_init(); //延時初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中斷分組配置
uart_init(115200); //串口波特率設置
LED_Init(); //LED初始化
KEY_Init();
OSInit(); //UCOS初始化
OSTaskCreate(start_task,(void*)0,(OS_STK*)&START_TASK_STK[START_STK_SIZE-1],START_TASK_PRIO); //創建開始任務
OSStart(); //開始任務
}
//開始任務
void start_task(void *pdata)
{
OS_CPU_SR cpu_sr=0;
pdata = pdata;
msg_key=OSMboxCreate((void *)0);//創建消息郵箱
sem_led0=OSSemCreate(0);
sem_led1=OSSemCreate(0);//創建信號量
sem_print=OSSemCreate(0);
OS_ENTER_CRITICAL(); //進入臨界區(無法被中斷打斷)
OSTaskCreate(led0_task,(void *)0,(OS_STK*)&LED0_TASK_STK[LED0_STK_SIZE-1],LED0_TASK_PRIO);
OSTaskCreate(led1_task,(void *)0,(OS_STK*)&LED1_TASK_STK[LED1_STK_SIZE-1],LED1_TASK_PRIO);
OSTaskCreate(key_task,(void *)0,(OS_STK*)&KEY_TASK_STK[KEY_STK_SIZE-1],KEY_TASK_PRIO);
OSTaskCreate(scan_task,(void *)0,(OS_STK*)&SCAN_TASK_STK[SCAN_STK_SIZE-1],SCAN_TASK_PRIO);
OSTaskCreate(float_task,(void *)0,(OS_STK*)&FLOAT_TASK_STK[FLOAT_STK_SIZE],FLOAT_TASK_PRIO);
OSTaskSuspend(START_TASK_PRIO); //掛起起始任務.
OS_EXIT_CRITICAL(); //退出臨界區(可以被中斷打斷)
}
//LED0任務
void led0_task(void *pdata)
{
//u8 err;
while(1)
{
// OSSemPend(sem_led0,0,&err);
LED0=0;
delay_ms(500);
LED0=1;
delay_ms(500);
};
}
//LED1任務
void led1_task(void *pdata)
{
u8 err;
while(1)
{
OSSemPend(sem_led1,0,&err);
LED1=0;
delay_ms(500);
LED1=1;
delay_ms(500);
}
}
//按鍵掃描任務
void key_task(void *pdata)
{
int key=0;
u8 err;
while(1)
{
key=(int)OSMboxPend(msg_key,10,&err);
switch(key)
{
case KEY0_PRES://發送信號量0
OSSemPost(sem_led0);
OSTaskSuspend(LED0_TASK_PRIO);//led0任務掛起
break;
case KEY1_PRES://發送信號量1
OSSemPost(sem_led1);
OSTaskDel(FLOAT_TASK_PRIO);//刪除串口顯示任務
break;
case WKUP_PRES:
//OSSemPost(sem_print);
// OSSemPost(sem_led1);
OSTaskResume(LED0_TASK_PRIO);//led1任務掛起後恢復
break;
}
}
}
//串口顯示任務
void float_task(void *pdata)
{
//u8 err;
OS_CPU_SR cpu_sr=0;
while(1)
{
// OSSemPend(sem_print,0,&err);
OS_ENTER_CRITICAL(); //進入臨界區(關閉中斷)
printf("串口顯示程序正在運行:\r\n\n"); //串口打印結果
printf("這是任務2:\r\n\n"); //串口打印結果
OS_EXIT_CRITICAL(); //退出臨界區(開中斷)
delay_ms(500);
}
}
//按鍵掃描任務
void scan_task(void *pdata)
{
u8 key;
while(1)
{
key=KEY_Scan(0);
if(key)OSMboxPost(msg_key,(void*)key);//發送消息
delay_ms(10);
}
}
key.c文件
#include "key.h"
#include "delay.h"
//按鍵初始化函數
//PA0.15和PC5 設置成輸入
void KEY_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOC,ENABLE);//使能PORTA,PORTC時鐘
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);//關閉jtag,使能SWD,可以用SWD模式調試
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;//PA15
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //設置成上拉輸入
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA15
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;//PC5
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //設置成上拉輸入
GPIO_Init(GPIOC, &GPIO_InitStructure);//初始化GPIOC5
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;//PA0
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA0設置成輸入,默認下拉
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.0
}
//按鍵處理函數
//返回按鍵值
//mode:0,不支持連續按;1,支持連續按;
//返回值:
//0,沒有任何按鍵按下
//KEY0_PRES,KEY0按下
//KEY1_PRES,KEY1按下
//WKUP_PRES,WK_UP按下
//注意此函數有響應優先級,KEY0>KEY1>WK_UP!!
u8 KEY_Scan(u8 mode)
{
static u8 key_up=1;//按鍵按鬆開標誌
if(mode)key_up=1; //支持連按
if(key_up&&(KEY0==0||KEY1==0||WK_UP==1))
{
delay_ms(10);//去抖動
key_up=0;
if(KEY0==0)return KEY0_PRES;
else if(KEY1==0)return KEY1_PRES;
else if(WK_UP==1)return WKUP_PRES;
}else if(KEY0==1&&KEY1==1&&WK_UP==0)key_up=1;
return 0;// 無按鍵按下
}
led.c文件
#include "led.h"
//初始化PA8和PD2爲輸出口.並使能這兩個口的時鐘
//LED IO初始化
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOD, ENABLE); //使能PA,PD端口時鐘
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //LED0-->PA.8 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推輓輸出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度爲50MHz
GPIO_Init(GPIOA, &GPIO_InitStructure); //根據設定參數初始化GPIOA.8
GPIO_SetBits(GPIOA,GPIO_Pin_8); //PA.8 輸出高
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //LED1-->PD.2 端口配置, 推輓輸出
GPIO_Init(GPIOD, &GPIO_InitStructure); //推輓輸出 ,IO口速度爲50MHz
GPIO_SetBits(GPIOD,GPIO_Pin_2); //PD.2 輸出高
}
工程目錄