前言
一般進行遠程監控時,2.4G無線通信是充當遠程數據傳輸的一種方法。這時就需要在現場部分具備無線數據發送裝置,而在上位機部分由於一般只有串口,所以將采集到的數據送到電腦里又要在上位機端設計一個數據接收的適配器。這里基於stm32分別設計了現場部分和適配器部分,這里只是基本通信功能實現的講解,一些復雜的技術比如加密、可靠等要根據具體的應用來設計~

總體說明
這里采用stm32作為MCU,采用nRF24L01作為2.4G通信模塊。其中適配器中僅僅采用了USART和NRF24L01兩個主要部分,負責將下位機通過2.4G發送過來的數據通過串口發送給上位機,或者將上位機的通過串口傳來的數據通過2.4G發送給下位機來實現遠程監控(沒有采用uc-os操作系統,也沒有界面,要用串口和上位機相連);其中下位機比較復雜,因為一般下位機是一個集成的系統,包括從各種傳感器收集數據、向各種類型的驅動電路發送控制命令、將數據輸給打印機或顯示器、和無線通信或有線通信設備進行互相通信來實現數據傳輸等,這里的下位機比較簡單:采用uc-os實時操作系統+uc-gui負責界面顯示,外接7寸TFT液晶顯示屏,和適配器類似也包括USART和NRF24L01通信部分,但是因為有了操作系統和可視化交互界面,所以也有點不同,接下來開始介紹。
適配器部分
這里介紹的流程是以main函數為基准,廣度拓寬知識點,最后main函數說完,整個工程的細節也就大致能了解了~
1 int main(void){ 2 uint8_t a=0;//LED高低電壓控制 3 /* System Clocks Configuration */ 4 RCC_Configuration(); //系統時鍾設置 5 /*嵌套向量中斷控制器 6 說明了USART1搶占優先級級別0(最多1位) ,和子優先級級別0(最多7位) */ 7 NVIC_Configuration(); //中斷源配置 8 /*對控制LED指示燈的IO口進行了初始化,將端口配置為推挽上拉輸出,口線速度為50Mhz。PA9,PA10端口復用為串口1的TX,RX。 9 在配置某個口線時,首先應對它所在的端口的時鍾進行使能。否則無法配置成功,由於用到了端口B, 因此要對這個端口的時鍾 10 進行使能,同時由於用到復用IO口功能用於配置串口。因此還要使能AFIO(復用功能IO)時鍾。*/ 11 GPIO_Configuration(); //端口初始化 12 SPI2_NRF24L01_Init(); //SPI2及NRF24L01接口初始化 13 USART_Config(USART1); //串口1初始化 14 /*NRF24L01設置為接收模式*/ 15 RX_Mode(); 16 17 while (1) 18 { 19 if(usart_rec_flag==1) //判斷是否收到一幀有效數據 20 { 21 usart_rec_flag=0; 22 NRF_Send_Data(TxBufferRF,sizeof(TxBufferRF)); 23 if(a==0){GPIO_SetBits(GPIOB, GPIO_Pin_5);a=1;} //LED1 明暗閃爍 24 else{GPIO_ResetBits(GPIOB, GPIO_Pin_5);a=0;} 25 } 26 if(rf_rec_flag==1) 27 { 28 rf_rec_flag=0; 29 for(i=0;i<32;i++)//發送字符串 30 { 31 USART_SendChar(USART1,TxBufferUSART[i]); 32 // Delay(0x0000ff00); 33 } 34 } 35 } 36 }
第4行RCC初始化主要是系統時鍾和外設時鍾配置,這里注意要使能RCC_APB2Periph_USART1,當時忘了使能這個結果串口出現異常,我還以為是初始化和中斷向量什么的弄錯了呢,浪費了很長時間。
1 /*-------------------------------------------------------------------------------------- 2 系統時鍾配置為72MHZ+外設時鍾配置*/ 3 void RCC_Configuration(void){ 4 SystemInit(); 5 RCC_APB2PeriphClockCmd( RCC_APB2Periph_USART1 |RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB |RCC_APB2Periph_AFIO , ENABLE); 6 }
第7行中斷向量初始化設置,主要是設置串口接收中斷和NRF24L01中斷的,這樣設置好了之后當串口中斷被觸發時其對應的中斷子程序將被執行(這個科班的大概都知道這里就不多說了),所以我們就要在stm32f10x_it.c里實現他們各自的中斷子程序了(這個一會再詳細介紹,咱們先把整個框架了解下)。另外說一句,這里的的優先級組將影響主優先級和子優先級數量具體請參考stm32f10X_的固件庫的NVIC.
1 void NVIC_Configuration(void){ 2 /* 結構聲明*/ 3 NVIC_InitTypeDef NVIC_InitStructure; 4 EXTI_InitTypeDef EXTI_InitStructure; 5 6 /* 優先級組 1 */ 7 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); 8 9 /* Enable the USART1 Interrupt */ 10 NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //設置串口1中斷 11 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //搶占優先級 0 12 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //子優先級為0 13 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能 14 NVIC_Init(&NVIC_InitStructure); 15 16 17 NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //NRF24L01 中斷響應 18 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //搶占優先級 0 19 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //子優先級為1 20 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能 21 NVIC_Init(&NVIC_InitStructure); 22 23 GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0); //NRF24L01 IRQ PA0 24 25 EXTI_InitStructure.EXTI_Line = EXTI_Line0; //NRF24L01 IRQ PA0 26 EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //EXTI中斷 27 EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿觸發 28 EXTI_InitStructure.EXTI_LineCmd = ENABLE; //使能 29 EXTI_Init(&EXTI_InitStructure); 30 }
第11行的GPIO初始化,主要是對通用IO口的屬性設置和初始化,這里一定要對串口所需的A9和A10配置好!
1 void GPIO_Configuration(void){ 2 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED1控制--PB5 3 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽輸出 4 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 5 GPIO_Init(GPIOB, &GPIO_InitStructure); 6 7 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //USART1 TX 8 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //復用推挽輸出 9 GPIO_Init(GPIOA, &GPIO_InitStructure); //A端口 10 11 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //USART1 RX 12 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //復用開漏輸入 13 GPIO_Init(GPIOA, &GPIO_InitStructure); //A端口 14 }
第12行的SPI2_NRF24L01_Init();主要是驅動NRF24L01的接口初始化,因為NRF24L01采用的是SPI通信,所以這里免不了SPI的設置和相關操作了,不過幸好都封裝好了~像以前在51上做SPI就得自己模擬SPI,沒有示波器調試起來甚是坑~此外這里我已經把NRF24L01的整個驅動都封裝在NRF24L01.c這個文件里了,當想用的時候只要在中斷向量里設置其中斷接收函數,並在it.c里實現其接收函數;一般主函數里用到的是其初始化函數SPI2_NRF24L01_Init();和 RX_Mode();,當在過程中想利用NRF24L01向外發數據時只要調用函數void NRF_Send_Data(uint8_t* data_buffer, uint8_t Nb_bytes):
1 /**************************************************************************** 2 * 名 稱:NRF_Send_Data(uint8_t* data_buffer, uint8_t Nb_bytes) 3 * 功 能:將保存在接收緩存區的32字節的數據通過NRF24L01+發送出去 4 * 入口參數:data_buffer 待發送數據 5 Nb_bytes 待發送數據長度 6 * 出口參數:無 7 * 說 明:數據小於32,把有效數據外的空間用0填滿 8 * 調用方法:RX_Mode(); 9 ****************************************************************************/ 10 void NRF_Send_Data(uint8_t* data_buffer, uint8_t Nb_bytes) 11 { 12 uchar i=0; 13 MODE_CE(0); //NRF 模式控制 14 15 SPI_RW_Reg(WRITE_REG1+STATUS,0xff); //設置狀態寄存器初始化 16 SPI_RW_Reg(0xe1,0); //清除TX FIFO寄存器 17 SPI_RW_Reg(0xe2,0); //清除RX FIFO寄存器 18 TX_Mode(); //設置為發送模式 19 delay_ms(1); 20 if(Nb_bytes<32){ //當接收到的USB虛擬串口數據小於32,把有效數據外的空間用0填滿 21 for(i=Nb_bytes;i<32;i++) data_buffer[i]=0; 22 } 23 MODE_CE(0); 24 SPI_Write_Buf(WR_TX_PLOAD, data_buffer, TX_PLOAD_WIDTH); //發送32字節的緩存區數據到NRF24L01 25 MODE_CE(1); //保持10us以上,將數據發送出去 26 }
第13行是USART初始化,包括波特率、數據位、停止位等~
1 void USART_Config(USART_TypeDef* USARTx){ 2 USART_InitStructure.USART_BaudRate = 9600; //速率9600bps 3 USART_InitStructure.USART_WordLength = USART_WordLength_8b; //數據位8位 4 USART_InitStructure.USART_StopBits = USART_StopBits_1; //停止位1位 5 USART_InitStructure.USART_Parity = USART_Parity_No; //無校驗位 6 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //無硬件流控 7 USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收發模式 8 9 /* Configure USART1 */ 10 USART_Init(USARTx, &USART_InitStructure); //配置串口參數函數 11 12 13 /* Enable USART1 Receive and Transmit interrupts */ 14 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //使能接收中斷 15 USART_ITConfig(USART1, USART_IT_TXE, ENABLE); //使能發送緩沖空中斷 16 17 /* Enable the USART1 */ 18 USART_Cmd(USART1, ENABLE); 19 }
同樣的類似於NRF24L01一旦初始化之后,其數據接收一般采用中斷方式、數據發送一般采用直接發送的方式。所以在中斷向量里也要設置,也要在it.c中實現其接收中斷子函數。其發送直接調用stm32f10的固件庫函數(這里我稍加封裝了下):其實就是發送一個data之后要監聽是否發送完成才能進行下次發送~
1 void USART_SendChar(USART_TypeDef* USARTx,uint8_t data){ 2 USART_SendData(USARTx,data); 3 while(USART_GetFlagStatus(USARTx,USART_FLAG_TC)==RESET); 4 }
接下來進入while循環,不斷進行監聽看是否有串口接收標志位置1或者無線模塊接收標志位置1,如果有表明相應的有數據從該通道傳送過來。當是從串口傳來的數據表明數據是從上位機發送來的數據,並且想把該數據通過2.4G發送出去,所以這里調用:NRF_Send_Data(TxBufferRF,sizeof(TxBufferRF));將數據發送出去;當數據是從2.4G通道中傳過來的,表明數據是從下位機傳送過來的想給上位機,於是調用串口發送函數將數據發送給上位機:USART_SendChar(USART1,TxBufferUSART[i]);

看了上圖適配器端的數據交換過程就明白了串口中斷和無線中斷大致要干的事了,這里我就不多介紹,看看下面的代碼就明白了(在stm32f10x_it.c中),要再次提醒的是無論是串口還是無線其接收都是采用中斷,而發送采用循環直接發送,他們的中斷和中斷向量有關並要在stm32f10x_it.c里實現相應的中斷子程序~
1 /******************************************************************************/ 2 /* STM32F10x Peripherals Interrupt Handlers */ 3 /******************************************************************************/ 4 5 /** 6 * @brief This function handles USART1 global interrupt request. 7 * @param None 8 * @retval : None 9 */ 10 void USART1_IRQHandler(void) //串口1 中斷服務程序 11 { 12 unsigned int i; 13 if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //判斷讀寄存器是否非空 14 { 15 16 RxBufferUSART[RxCounter1++] = USART_ReceiveData(USART1); //將讀寄存器的數據緩存到接收緩沖區里 17 18 if(RxBufferUSART[RxCounter1-2]==0x0d&&RxBufferUSART[RxCounter1-1]==0x0a) //判斷結束標志是否是0x0d 0x0a 19 { 20 for(i=0; i< RxCounter1; i++) TxBufferRF[i] = RxBufferUSART[i]; //將接收緩沖器的數據轉到發送緩沖區,准備轉發 21 usart_rec_flag=1; //接收成功標志 22 TxBufferRF[RxCounter1]=0; //發送緩沖區結束符 23 TxCounter1=RxCounter1; 24 RxCounter1=0; 25 } 26 } 27 28 if(USART_GetITStatus(USART1, USART_IT_TXE) != RESET) //這段是為了避免STM32 USART 第一個字節發不出去的BUG 29 { 30 USART_ITConfig(USART1, USART_IT_TXE, DISABLE); //禁止發緩沖器空中斷, 31 } 32 } 33 /******************************************************************************* 34 * Function Name : EXTI0 中斷函數 35 * Description : NRF24L01中斷服務程序 36 * Input : None 37 * Output : None 38 * Return : None 39 *******************************************************************************/ 40 void EXTI0_IRQHandler(void){ 41 u8 i=0; 42 u8 status; 43 if(EXTI_GetITStatus(EXTI_Line0) != RESET) //判斷是否產生了EXTI0中斷 44 { 45 if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==0){ //判斷是否是PA0線變低 46 status=SPI_Read(READ_REG1+STATUS); // 讀取狀態寄存其來判斷數據接收狀況 47 if(status & 0x40) // 判斷是否接收到數據 48 { 49 //GPIO_ResetBits(GPIOB, GPIO_Pin_5); 50 SPI_Read_Buf(RD_RX_PLOAD,rx_buf,TX_PLOAD_WIDTH); //從接收緩沖區里讀出數據 51 for(i=0; i<32; i++)TxBufferUSART[i] = rx_buf[i]; //向USB 端點1的緩沖區里放置數據 52 rf_rec_flag=1; 53 } 54 else if((status &0x10)>0){ //發射達到最大復發次數 55 SPI_RW_Reg(0xe1,0); //清除發送緩沖區 56 RX_Mode(); //進入接收模式 57 } 58 else if((status &0x20)>0){ //發射后收到應答 59 GPIO_SetBits(GPIOB, GPIO_Pin_5); 60 SPI_RW_Reg(0xe1,0); //清除發送緩沖區 61 RX_Mode(); //進入接收模式 62 } 63 SPI_RW_Reg(WRITE_REG1+STATUS, status); //清除07寄存器標志 64 } 65 EXTI_ClearITPendingBit(EXTI_Line0); //清除EXTI0上的中斷標志 66 } 67 }
下位機部分
上面說過一般具有遠程通信能力的嵌入式系統其下位機部分往往要干很多事,這里我們采用stm32作為MCU並搭載uc-OS實時操作系統負責任務調度,同時采用7寸TFT彩屏和uc-GUI設計可視化人機交互界面。其中任務包括主任務、界面任務和觸摸任務,主任務負責建立其他任務,界面任務中將包含整個人機交互界面的界面刷新邏輯,觸摸任務負責獲取觸摸位置數據獲取~

這里我們還是得從main函數先說起:首先在main函數中進行相關初始化,然后建立主任務並啟動uc-OS內核;接着在主任務中調用App_TaskCreate(); 分別建立界面任務和觸摸任務(如下每個任務的建立類似,要給出指向任務代碼的指針、任務執行時傳遞給任務的參數的指針,分配給這個任務的棧信息,任務優先級等)。這樣當任務建立好之后,其執行權就由操作系統調度了~
1 static void App_TaskCreate(void) 2 { 3 /* 建立用戶界面任務 */ 4 OSTaskCreateExt(AppTaskUserIF, //指向任務代碼的指針 5 (void *)0, //任務開始執行時,傳遞給任務的參數的指針 6 (OS_STK *)&AppTaskUserIFStk[APP_TASK_USER_IF_STK_SIZE-1], //分配給任務的堆棧的棧頂指針 從頂向下遞減 7 APP_TASK_USER_IF_PRIO, //分配給任務的優先級 8 APP_TASK_USER_IF_PRIO, //預備給以后版本的特殊標識符,在現行版本同任務優先級 9 (OS_STK *)&AppTaskUserIFStk[0], //指向任務堆棧棧底的指針,用於堆棧的檢驗 10 APP_TASK_USER_IF_STK_SIZE, //指定堆棧的容量,用於堆棧的檢驗 11 (void *)0, //指向用戶附加的數據域的指針,用來擴展任務的任務控制塊 12 OS_TASK_OPT_STK_CHK|OS_TASK_OPT_STK_CLR); //選項,指定是否允許堆棧檢驗,是否將堆棧清0,任務是否要 13 //進行浮點運算等等。 14 15 /* 建立觸摸驅動任務 */ 16 OSTaskCreateExt(AppTaskKbd, 17 (void *)0, 18 (OS_STK *)&AppTaskKbdStk[APP_TASK_KBD_STK_SIZE-1], 19 APP_TASK_KBD_PRIO, 20 APP_TASK_KBD_PRIO, 21 (OS_STK *)&AppTaskKbdStk[0], 22 APP_TASK_KBD_STK_SIZE, 23 (void *)0, 24 OS_TASK_OPT_STK_CHK|OS_TASK_OPT_STK_CLR); 25 26 }
這里以界面任務為例:因為我們在建立界面任務時已經指定其任務代碼指針AppTaskUserIF,所以這里來寫其對應的函數(也就是說這里是界面任務的入口)。從下面的代碼可以看出進入界面任務時首先對uc-GUI進行初始化,然后進入死循環不斷執行Fun()函數(有人會疑惑:這里while死循環不就只能死在這里嗎?怎么執行其他任務呢?哈哈,這就是具有操作系統和不具有操作系統的不同啦~雖然這里是while死循環,但是當OS要把CPU占有權分給其他任務時就會把當前執行的任務的信息壓入其對應的棧空間,當再次要把CPU分配給該任務時,則把棧里保存的上次執行的情況拿出來繼續執行,從而實現搶占與多任務的效果!)
1 static void AppTaskUserIF (void *p_arg) 2 { 3 (void)p_arg; 4 GUI_Init(); //ucgui初始化 5 while(1) 6 { 7 Fun(); //界面主程序 8 } 9 }
所以接下來我們主要看Fun.c里的Fun函數:雖然代碼有點長,但是很好理解,其核心思路就是建立整個界面並對界面中的每個控件進行相關設置同時獲得其句柄,在最后又進入了while死循環,在循環中不斷檢測2.4G是否接受到數據(和適配器端類似也是中斷子程序中收數據然后置接收標志為1的),然后根據從2.4G收到的數據來刷新文本顯示區;下面一個if判斷speed_change_flag是否有效來向串口發送相應的數據。那么我們的問題又來了:這個speed_change_flag是在哪里被改變的呢?這個我們就要參看窗口回調函數了!這里的窗口回調函數是窗口動作響應函數(就像安卓開發里的按鈕監聽或MFC里的按鈕點擊事件等),一旦窗口里的控件有相應的觸發動作就會調用該函數,並把事件類型封裝在WM_MESSAGE里傳過來,在該函數里對該消息進行解析並作出相應的動作即可(非常像Win32!!!我懷疑做這個uc-GUI的人有copy微軟的嫌疑,♪(^∇^*)隨便猜測,如有雷同,純屬巧合)。這樣我們就很容易找到send按鈕的監聽用於將數據通過NRF24L01發送出去的相關操作,也就明白了滑動條監聽用來改變speed1~5.上面說了這么多,少了介紹整個界面是怎么建立的了~其實整個窗體的布局都要放在一個結構體里,然后在fun()函數里調用hWin = GUI_CreateDialogBox(aDialogCreate, GUI_COUNTOF(aDialogCreate), _cbCallback, 0, 0, 0);根據定義的窗口資源和回調函數進行窗體的建立~這樣我們就圓滿地理解了stm32基於uc-OS並搭載uc-GUI的運行邏輯啦!
1 void Fun(void) { 2 GUI_CURSOR_Show(); //打開鼠標圖形顯示 3 4 /* 建立對話框時,包含了資源列表,資源數目, 並且指定了用於動作響應的回調函數 */ 5 hWin = GUI_CreateDialogBox(aDialogCreate, GUI_COUNTOF(aDialogCreate), _cbCallback, 0, 0, 0); 6 7 FRAMEWIN_SetFont(hWin, &GUI_FontComic18B_1); //對話框字體設置 8 FRAMEWIN_SetClientColor(hWin, GUI_BLACK); //對話框的窗體顏色是黑色 9 memcpy(tx_buf, "1234567890abcdefghij!@#$%^&*()-=", 32); //將長度為32字節的發送字符串拷貝到發送緩沖區, 10 memcpy(rx_buf, "", 32); //將接收緩存區清空 11 12 /* 獲得文本框句柄 */ 13 text1 = WM_GetDialogItem(hWin, GUI_ID_TEXT0); //獲得對話框里GUI_ID_TEXT0項目(文本框Send Text Area)的句柄 14 text2 = WM_GetDialogItem(hWin, GUI_ID_TEXT1); //獲得對話框里GUI_ID_TEXT1項目(文本框Receive Text Area)的句柄 15 text3 = WM_GetDialogItem(hWin, GUI_ID_TEXT2); //獲得對話框里GUI_ID_TEXT2項目(文本框2M BPS)的句柄 16 text4 = WM_GetDialogItem(hWin, GUI_ID_TEXT3); //獲得對話框里GUI_ID_TEXT3項目(文本框1M BPS)的句柄 17 text6 = WM_GetDialogItem(hWin, GUI_ID_TEXT5); //獲得對話框里GUI_ID_TEXT5項目(文本框250K BPS)的句柄 18 text5 = WM_GetDialogItem(hWin, GUI_ID_TEXT4); //獲得對話框里GUI_ID_TEXT4項目(狀態字符文本框)的句柄 19 /* 設置文本框字體 */ 20 TEXT_SetFont(text1,pFont); //設置對話框里文本框Send Text Area的字體 21 TEXT_SetFont(text2,pFont); //設置對話框里文本框Receive Text Area的字體 22 TEXT_SetFont(text3,pFont18); //設置對話框里文本框2M BPS的字體 23 TEXT_SetFont(text4,pFont18); //設置對話框里文本框1M BPS的字體 24 TEXT_SetFont(text6,pFont18); //設置對話框里文本框250K BPS的字體 25 TEXT_SetFont(text5,pFont); //設置對話框里狀態字符文本框的字體 26 /* 設置文本框顏色 */ 27 TEXT_SetTextColor(text1,GUI_GREEN); //設置對話框里文本框Send Text Area的字體顏色 28 TEXT_SetTextColor(text2,GUI_GREEN ); //設置對話框里文本框Receive Text Area的字體顏色 29 TEXT_SetTextColor(text3,GUI_YELLOW); //設置對話框里文本框2M BPS的字體顏色 30 TEXT_SetTextColor(text4,GUI_YELLOW); //設置對話框里文本框1M BPS的字體顏色 31 TEXT_SetTextColor(text6,GUI_YELLOW); //設置對話框里文本框250K BPS的字體顏色 32 TEXT_SetTextColor(text5,GUI_YELLOW); //設置對話框里狀態字符文本框的字體顏色 33 TEXT_SetBkColor(text5,GUI_BLUE); //設置對話框里狀態字符文本框的背景顏色 34 35 /* 編輯框相關 */ 36 edit1 = WM_GetDialogItem(hWin, GUI_ID_EDIT1); //獲得對話框里GUI_ID_EDIT1項目(編輯框 發送字符串顯示區)的句柄 37 EDIT_SetFont(edit1,pFont18); //設置對話框里編輯框 發送字符串顯示區的字體 38 EDIT_SetText(edit1,(const char *)tx_buf); //設置對話框里編輯框 發送字符串顯示區的字符串 39 edit2 = WM_GetDialogItem(hWin, GUI_ID_EDIT2); //獲得對話框里GUI_ID_EDIT2項目(編輯框 接收字符串顯示區)的句柄 40 EDIT_SetFont(edit2,pFont18); //設置對話框里編輯框 接收字符串顯示區的字體 41 EDIT_SetText(edit2,(const char *)rx_buf); //設置對話框里編輯框 接收字符串顯示區的字符串 42 43 /* 按鈕相關 */ 44 bt[0]=WM_GetDialogItem(hWin,GUI_ID_BUTTON0); //獲得對話框里GUI_ID_BUTTON0項目(按鍵SEND)的句柄 45 bt[1]=WM_GetDialogItem(hWin, GUI_ID_BUTTON2); //獲得對話框里GUI_ID_BUTTON2項目(按鍵CLEAR)的句柄 46 BUTTON_SetFont(bt[0],pFont); //設置對話框里按鍵SEND的字體 47 BUTTON_SetFont(bt[1],pFont); //設置對話框里按鍵CLEAR的字體 48 BUTTON_SetTextColor(bt[0],0,GUI_WHITE); //設置對話框里按鍵SEND未被按下的字體顏色 49 BUTTON_SetTextColor(bt[1],0,GUI_WHITE); //設置對話框里按鍵CLEAR未被按下的字體顏色 50 51 /* List相關 */ 52 nrf_Pipe=0; //NRF24L01初始發射通道設置為0 53 list1 = WM_GetDialogItem(hWin, GUI_ID_LISTBOX0); //獲得對話框里GUI_ID_LISTBOX0項目(列表框-通道選擇)的句柄 54 LISTBOX_SetText(list1, _apListBox); //設置對話框里列表框-通道選擇里的條目 55 LISTBOX_SetFont(list1,pFont18); //設置對話框里列表框-通道選擇的字體 56 LISTBOX_SetSel(list1,nrf_Pipe); //設置對話框里列表框-通道選擇的焦點選擇 57 SCROLLBAR_CreateAttached(list1, SCROLLBAR_CF_VERTICAL); //設置對話框里列表框-通道選擇的卷動方向為下拉 58 59 /* Radio按鈕相關 */ 60 rd0 = WM_GetDialogItem(hWin, GUI_ID_RADIO0); //獲得對話框里GUI_ID_RADIO0項目(點選框-速率選擇)的句柄 61 nrf_baud=0; //NRF24L01速率 初始為2MPS 62 RADIO_SetValue(rd0,0); //設置對話框里點選框-速率選擇的焦點選擇 63 RX_Mode(); //NRF24L01進入接收模式 64 65 /* 獲得slider部件的句柄 */ 66 slider1 = WM_GetDialogItem(hWin, GUI_ID_SLIDER1); 67 slider2 = WM_GetDialogItem(hWin, GUI_ID_SLIDER2); 68 slider3 = WM_GetDialogItem(hWin, GUI_ID_SLIDER3); 69 slider4 = WM_GetDialogItem(hWin, GUI_ID_SLIDER4); 70 slider5 = WM_GetDialogItem(hWin, GUI_ID_SLIDER5); 71 /* 設置slider部件的取值范圍-8-8*/ 72 SLIDER_SetRange(slider1,-8,8); 73 SLIDER_SetRange(slider2,-8,8); 74 SLIDER_SetRange(slider3,-8,8); 75 SLIDER_SetRange(slider4,-8,8); 76 SLIDER_SetRange(slider5,-8,8); 77 /* 設置slider部件的值*/ 78 SLIDER_SetValue(slider1,0); 79 SLIDER_SetValue(slider2,0); 80 SLIDER_SetValue(slider3,0); 81 SLIDER_SetValue(slider4,0); 82 SLIDER_SetValue(slider5,0); 83 /* 獲取文本框句柄 */ 84 text_speed1 = WM_GetDialogItem(hWin, GUI_ID_TEXT_SPEED1); 85 text_speed2 = WM_GetDialogItem(hWin, GUI_ID_TEXT_SPEED2); 86 text_speed3 = WM_GetDialogItem(hWin, GUI_ID_TEXT_SPEED3); 87 text_speed4 = WM_GetDialogItem(hWin, GUI_ID_TEXT_SPEED4); 88 text_speed5 = WM_GetDialogItem(hWin, GUI_ID_TEXT_SPEED5); 89 /* 設置文本框字體 */ 90 TEXT_SetFont(text_speed1,pFont18); 91 TEXT_SetFont(text_speed2,pFont18); 92 TEXT_SetFont(text_speed3,pFont18); 93 TEXT_SetFont(text_speed4,pFont18); 94 TEXT_SetFont(text_speed5,pFont18); 95 /* 設置文本框顏色 */ 96 TEXT_SetTextColor(text_speed1,GUI_YELLOW); 97 TEXT_SetTextColor(text_speed2,GUI_YELLOW); 98 TEXT_SetTextColor(text_speed3,GUI_YELLOW); 99 TEXT_SetTextColor(text_speed4,GUI_YELLOW); 100 TEXT_SetTextColor(text_speed5,GUI_YELLOW); 101 102 speed_change_flag=0; 103 104 while (1) 105 { 106 if(Rx_Succ==1){ //當NRF24L01接收到有效數據 107 EDIT_SetText(edit2,(const char *)rx_buf); //將接收緩沖區的字符寫入到接收字符編輯框內 108 TEXT_SetText(text5,(const char *)status_buf); //將狀態文本緩沖區的字符寫入到狀態文本框內 109 Rx_Succ=0; 110 // for(i=0;i<sizeof(rx_buf);i++) 111 // USART_SendChar(USART1,rx_buf[i]); 112 } 113 if(speed_change_flag!=0) 114 { 115 speed_change_flag=0; 116 USART_SendChar(USART1,control_data); 117 } 118 WM_Exec(); //刷新屏幕 119 } 120 }
1 /**************************************************************************** 2 * 名 稱:static void _cbCallback(WM_MESSAGE * pMsg) 3 * 功 能:ucgui回調函數,是作為對話框動作響應的函數 4 * 入口參數:無 5 * 出口參數:無 6 * 說 明: 7 * 調用方法: 8 ****************************************************************************/ 9 static void _cbCallback(WM_MESSAGE * pMsg) { 10 int NCode, Id; 11 switch (pMsg->MsgId) { 12 case WM_NOTIFY_PARENT: //通知父窗口有事件在窗口部件上發生 13 Id = WM_GetId(pMsg->hWinSrc); //獲得對話框窗口里發生事件的部件的ID 14 NCode = pMsg->Data.v; //通知代碼 15 switch (NCode) { 16 case WM_NOTIFICATION_RELEASED: //窗體部件動作被釋放 17 if (Id == GUI_ID_BUTTON2) { //按鍵CLEAR被松開 18 memcpy(status_buf, "", 20); //清空狀態文本緩沖區 19 memcpy(rx_buf, "", 32); //清空接收文本緩沖區 20 TEXT_SetText(text5,(const char *)status_buf); //清空狀態文本框 21 EDIT_SetText(edit2,(const char *)rx_buf); //清空接收字符編輯框 22 memcpy(tx_buf, "", 32); //清空發送文本緩沖區 23 NRF24L01_TXBUF(tx_buf,32); //將發送字符緩沖區的字符通過NRF24L01發送出去 24 } 25 else if (Id == GUI_ID_BUTTON0) { //按鍵SEND 被松開 26 memcpy(tx_buf, "1234567890abcdefghij!@#$%^&*()-=", 32); //將32字節的文本拷貝到發送文本緩沖區 27 memcpy(rx_buf, "", 32); //清空接收文本緩沖區 28 memcpy(status_buf, "", 20); //清空狀態文本緩沖區 29 EDIT_SetText(edit2,(const char *)rx_buf); //清空接收字符編輯框 30 NRF24L01_TXBUF(tx_buf,32); //將發送字符緩沖區的字符通過NRF24L01發送出去 31 memcpy(tx_buf, "", 32); //清空發送文本緩沖區 32 TEXT_SetText(text5,(const char *)status_buf); //清空狀態文本框 33 } 34 else if (Id == GUI_ID_RADIO0) { //NRF24L01無線速率點選框點選動作完成 35 nrf_baud= RADIO_GetValue(rd0); //獲得速率表示值 36 RX_Mode(); //進入接收模式 37 } 38 else if (Id == GUI_ID_LISTBOX0){ //NRF24L01無線通道選擇動作 39 nrf_Pipe= LISTBOX_GetSel(list1); //獲得NRF24LL01無線通道表示值 40 RX_Mode(); //進入接收模式 41 }else if(Id == GUI_ID_SLIDER1){ //slider1 的值被改變 42 speed1=SLIDER_GetValue(slider1);//獲得slider1的值 43 if(speed1>0){ 44 speed_show[0]='+'; 45 speed_show[1]='0'+speed1; 46 control_data=8+speed1; 47 }else if(speed1<0){ 48 speed_show[0]='-'; 49 speed_show[1]='0'-speed1; 50 control_data=16-speed1; 51 }else{ 52 speed_show[0]=' '; 53 speed_show[1]='0'; 54 control_data=0; 55 } 56 // USART_SendChar(USART1,control_data); 57 TEXT_SetText(text_speed1,speed_show); 58 speed_change_flag=1; 59 }else if(Id == GUI_ID_SLIDER2){ //slider2 的值被改變 60 speed2=SLIDER_GetValue(slider2);//獲得slider2的值 61 if(speed2>0){ 62 speed_show[0]='+'; 63 speed_show[1]='0'+speed2; 64 control_data=32+8+speed2; 65 }else if(speed2<0){ 66 speed_show[0]='-'; 67 speed_show[1]='0'-speed2; 68 control_data=32+16-speed2; 69 }else{ 70 speed_show[0]=' '; 71 speed_show[1]='0'; 72 control_data=0; 73 } 74 TEXT_SetText(text_speed2,speed_show); 75 speed_change_flag=1; 76 }else if(Id == GUI_ID_SLIDER3){ //slider3 的值被改變 77 speed3=SLIDER_GetValue(slider3);//獲得slider3的值 78 if(speed3>0){ 79 speed_show[0]='+'; 80 speed_show[1]='0'+speed3; 81 control_data=64+8+speed3; 82 }else if(speed3<0){ 83 speed_show[0]='-'; 84 speed_show[1]='0'-speed3; 85 control_data=64+16-speed3; 86 }else{ 87 speed_show[0]=' '; 88 speed_show[1]='0'; 89 control_data=0; 90 } 91 TEXT_SetText(text_speed3,speed_show); 92 speed_change_flag=1; 93 }else if(Id == GUI_ID_SLIDER4){ //slider4 的值被改變 94 speed4=SLIDER_GetValue(slider4);//獲得slider4的值 95 if(speed4>0){ 96 speed_show[0]='+'; 97 speed_show[1]='0'+speed4; 98 control_data=96+8+speed4; 99 }else if(speed4<0){ 100 speed_show[0]='-'; 101 speed_show[1]='0'-speed4; 102 control_data=96+16-speed4; 103 }else{ 104 speed_show[0]=' '; 105 speed_show[1]='0'; 106 control_data=0; 107 } 108 TEXT_SetText(text_speed4,speed_show); 109 speed_change_flag=1; 110 }else if(Id == GUI_ID_SLIDER5){ //slider5 的值被改變 111 speed5=SLIDER_GetValue(slider5);//獲得slider5的值 112 if(speed5>0){ 113 speed_show[0]='+'; 114 speed_show[1]='0'+speed5; 115 control_data=128+8+speed5; 116 }else if(speed5<0){ 117 speed_show[0]='-'; 118 speed_show[1]='0'-speed5; 119 control_data=128+16-speed5; 120 }else{ 121 speed_show[0]=' '; 122 speed_show[1]='0'; 123 control_data=0; 124 } 125 TEXT_SetText(text_speed5,speed_show); 126 speed_change_flag=1; 127 } 128 break; 129 default: break; 130 } 131 default: 132 WM_DefaultProc(pMsg); //默認程序來處理消息 133 break; 134 } 135 }
1 /* 定義了對話框資源列表 */ 2 static const GUI_WIDGET_CREATE_INFO aDialogCreate[] = { 3 //建立窗體, 大小是800X480 原點在0,0 4 { FRAMEWIN_CreateIndirect, "http://beautifulzzzz", 0,0,0, 800, 480, FRAMEWIN_CF_ACTIVE }, 5 { BUTTON_CreateIndirect, "SEND", GUI_ID_BUTTON0, 0, 395, 200, 55 }, 6 7 { BUTTON_CreateIndirect, "CLEAR", GUI_ID_BUTTON2, 200, 395, 200, 55 }, 8 { EDIT_CreateIndirect, "", GUI_ID_EDIT1, 0, 190, 400, 65, EDIT_CF_LEFT, 50 }, 9 { EDIT_CreateIndirect, "", GUI_ID_EDIT2, 0, 290, 400, 65, EDIT_CF_LEFT, 50 }, 10 11 //建立TEXT控件,起點是窗體的X,X,大小XXY 文字左對齊 12 { TEXT_CreateIndirect, "Send Text Area", GUI_ID_TEXT0, 1, 160, 400, 25, TEXT_CF_LEFT }, 13 { TEXT_CreateIndirect, "Receive Text Area ", GUI_ID_TEXT1, 1, 263, 400, 25, TEXT_CF_LEFT }, 14 15 { TEXT_CreateIndirect, "2M bps", GUI_ID_TEXT2, 23, 22, 140, 25, TEXT_CF_LEFT }, 16 { TEXT_CreateIndirect, "1M bps", GUI_ID_TEXT3, 23, 42, 140, 25, TEXT_CF_LEFT }, 17 { TEXT_CreateIndirect, "250K bps", GUI_ID_TEXT5, 23, 62, 140, 25, TEXT_CF_LEFT }, 18 19 { TEXT_CreateIndirect, "", GUI_ID_TEXT4, 0, 120, 400, 25, TEXT_CF_LEFT }, 20 21 { RADIO_CreateIndirect, "Receive Mode", GUI_ID_RADIO0, 3, 33, 40, 52, RADIO_TEXTPOS_LEFT,3}, 22 23 { LISTBOX_CreateIndirect, "", GUI_ID_LISTBOX0, 134, 13, 130, 90, 0, 0 }, 24 25 //建立滑塊 26 { SLIDER_CreateIndirect, NULL, GUI_ID_SLIDER1, 440, 60, 320, 25, 0, 0 }, 27 { SLIDER_CreateIndirect, NULL, GUI_ID_SLIDER2, 440, 120, 320, 25, 0, 0 }, 28 { SLIDER_CreateIndirect, NULL, GUI_ID_SLIDER3, 440, 180, 320, 25, 0, 0 }, 29 { SLIDER_CreateIndirect, NULL, GUI_ID_SLIDER4, 440, 240, 320, 25, 0, 0 }, 30 { SLIDER_CreateIndirect, NULL, GUI_ID_SLIDER5, 440, 300, 320, 25, 0, 0 }, 31 //建立滑塊對應的text 32 { TEXT_CreateIndirect, "0", GUI_ID_TEXT_SPEED1, 770, 60, 25, 25, TEXT_CF_LEFT }, 33 { TEXT_CreateIndirect, "0", GUI_ID_TEXT_SPEED2, 770, 120, 25, 25, TEXT_CF_LEFT }, 34 { TEXT_CreateIndirect, "0", GUI_ID_TEXT_SPEED3, 770, 180, 25, 25, TEXT_CF_LEFT }, 35 { TEXT_CreateIndirect, "0", GUI_ID_TEXT_SPEED4, 770, 240, 25, 25, TEXT_CF_LEFT }, 36 { TEXT_CreateIndirect, "0", GUI_ID_TEXT_SPEED5, 770, 300, 25, 25, TEXT_CF_LEFT }, 37 };
還要回過頭說說我們的USART和NRF24L01,他們的初始化要看main函數中的BSP_Init();函數,該函數負相關硬件的初始化設置(中文意思是板級支持包初始化函數,因為uc-OS可以並不只限於stm32單片機,所以這里要根據不同平台進行相應的設置)。該函數位於bsp.c函數中,其作用相當於將以前我們在main函數中進行的相關硬件初始化單獨拿出來封裝成一個函數而已~但是,串口和無線對應的中斷接收程序卻有點不一樣,因為這里是操作系統,所以在每個中斷子程序前要調用OS_ENTER_CRITICAL();保存當前的全局中斷標志,然后OSIntNesting++;中斷嵌套深度加1,最后調用OS_EXIT_CRITICAL();恢復全局中斷標志進入正常的中斷處理,此外在中斷響應函數最后要調用OSIntExit(); 檢測如果有更高優先級的任務就緒了,則執行一次任務切換。
1 /******************************************************************************* 2 * Function Name : USART1_IRQHandler 3 * Description : This function handles USART1 global interrupt request. 4 * Input : None 5 * Output : None 6 * Return : None 7 *******************************************************************************/ 8 void USART1_IRQHandler(void) 9 { 10 unsigned int i; 11 OS_CPU_SR cpu_sr; 12 OS_ENTER_CRITICAL(); //保存全局中斷標志,關總中斷 Tell uC/OS-II that we are starting an ISR 13 OSIntNesting++; 14 OS_EXIT_CRITICAL(); //恢復全局中斷曛? 15 16 if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //判斷讀寄存器是否非空 17 { 18 RxBufferUSART[RxCounter1++] = USART_ReceiveData(USART1); //將讀寄存器的數據緩存到接收緩沖區里 19 if(RxBufferUSART[RxCounter1-2]==0x0d&&RxBufferUSART[RxCounter1-1]==0x0a) //判斷結束標志是否是0x0d 0x0a 20 { 21 for(i=0; i< RxCounter1; i++) TxBufferRF[i] = RxBufferUSART[i]; //將接收緩沖器的數據轉到發送緩沖區,准備轉發 22 usart_rec_flag=1; //接收成功標志 23 TxBufferRF[RxCounter1]=0; //發送緩沖區結束符 24 TxCounter1=RxCounter1; 25 RxCounter1=0; 26 } 27 } 28 if(USART_GetITStatus(USART1, USART_IT_TXE) != RESET) //這段是為了避免STM32 USART 第一個字節發不出去的BUG 29 { 30 USART_ITConfig(USART1, USART_IT_TXE, DISABLE); //禁止發緩沖器空中斷, 31 } 32 OSIntExit(); //在os_core.c文件里定義,如果有更高優先級的任務就緒了,則執行一次任務切換 33 } 34 ///////////////////////////// 35 void EXTI0_IRQHandler(void) 36 { 37 unsigned char status,i; 38 OS_CPU_SR cpu_sr; 39 OS_ENTER_CRITICAL(); //保存全局中斷標志,關總中斷 Tell uC/OS-II that we are starting an ISR 40 OSIntNesting++; 41 OS_EXIT_CRITICAL(); //恢復全局中斷標志 42 43 if(EXTI_GetITStatus(EXTI_Line0) != RESET) //判斷是否產生了EXTI0中斷 44 { 45 if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==0){ //判斷是否是PA0線變低 46 status=SPI_Read(READ_REG1+STATUS); // 讀取狀態寄存其來判斷數據接收狀況 47 if(status & 0x40) // 判斷是否接收到數據 48 { 49 SPI_Read_Buf(RD_RX_PLOAD,rx_buf,TX_PLOAD_WIDTH); //從接收緩沖區里讀出數據 50 for(i=0; i<32; i++){ //向USB 端點1的緩沖區里放置數據 51 TxBufferUSART[i] = rx_buf[i]; 52 } 53 rf_rec_flag=1; 54 if((status&0x0e)<=0x0a){ 55 nrf_Pipe_r=(status&0x0e)>>1; //讀出是在哪個通道接收的 56 } 57 else nrf_Pipe_r=0; 58 Rx_Succ=1; //讀取數據完成標志 59 /* 根據讀出的接收通道號,將相應信息寫入狀態文本緩沖區 */ 60 if(nrf_Pipe_r==0) memcpy(status_buf, "Pipe 0 Recive OK! ", 20); 61 else if(nrf_Pipe_r==1) memcpy(status_buf, "Pipe 1 Recive OK! ", 20); 62 else if(nrf_Pipe_r==2) memcpy(status_buf, "Pipe 2 Recive OK! ", 20); 63 else if(nrf_Pipe_r==3) memcpy(status_buf, "Pipe 3 Recive OK! ", 20); 64 else if(nrf_Pipe_r==4) memcpy(status_buf, "Pipe 4 Recive OK! ", 20); 65 else if(nrf_Pipe_r==5) memcpy(status_buf, "Pipe 5 Recive OK! ", 20); 66 } 67 else if((status &0x10)>0){ //發射達到最大復發次數 68 SPI_RW_Reg(0xe1,0); //清除發送緩沖區 69 RX_Mode(); //進入接收模式 70 Rx_Succ=1; 71 /* 根據發送通道,將相應信息寫入狀態文本緩沖區 */ 72 if(nrf_Pipe==0) memcpy(status_buf, "Pipe 0 NO ACK! ", 20); 73 else if(nrf_Pipe==1) memcpy(status_buf, "Pipe 1 NO ACK! ", 20); 74 else if(nrf_Pipe==2) memcpy(status_buf, "Pipe 2 NO ACK! ", 20); 75 else if(nrf_Pipe==3) memcpy(status_buf, "Pipe 3 NO ACK! ", 20); 76 else if(nrf_Pipe==4) memcpy(status_buf, "Pipe 4 NO ACK! ", 20); 77 else if(nrf_Pipe==5) memcpy(status_buf, "Pipe 5 NO ACK! ", 20); 78 } 79 else if((status &0x20)>0){ //發射后收到應答 80 SPI_RW_Reg(0xe1,0); //清除發送緩沖區 81 RX_Mode(); //進入接收模式 82 Rx_Succ=1; 83 /* 根據發送通道,將相應信息寫入狀態文本緩沖區 */ 84 if(nrf_Pipe==0) memcpy(status_buf, "Pipe 0 Send OK! ", 20); 85 else if(nrf_Pipe==1) memcpy(status_buf, "Pipe 1 Send OK! ", 20); 86 else if(nrf_Pipe==2) memcpy(status_buf, "Pipe 2 Send OK! ", 20); 87 else if(nrf_Pipe==3) memcpy(status_buf, "Pipe 3 Send OK! ", 20); 88 else if(nrf_Pipe==4) memcpy(status_buf, "Pipe 4 Send OK! ", 20); 89 else if(nrf_Pipe==5) memcpy(status_buf, "Pipe 5 Send OK! ", 20); 90 } 91 92 SPI_RW_Reg(WRITE_REG1+STATUS, status); //清除07寄存器標志 93 } 94 EXTI_ClearITPendingBit(EXTI_Line0); //清除EXTI0上的中斷標志 95 } 96 OSIntExit(); //在os_core.c文件里定義,如果有更高優先級的任務就緒了,則執行一次任務切換 97 }
最后說明
對於純玩軟件的小伙伴,這里涉及的東西有點多,不必細究,看看了解即可。但是對於初學stm32,尤其是還在為stm32控制NRF24L01不通的同學,這個還是挺有用滴~下面有工程的鏈接,里面有些注釋不規范,一切以我博客里說的為准哦~
鏈接
上述工程keil代碼:http://pan.baidu.com/s/1mgqowQ0
本文鏈接:http://www.cnblogs.com/zjutlitao/p/4242734.html
上述工程GitHub鏈接:https://github.com/beautifulzzzz/stm32/tree/master/stm32_USART%2BNRF24L01
可能有用1:[stm32][ucos] 1、基於ucos操作系統的LED閃爍、串口通信簡單例程
可能有用2:[stm32][ucos][ucgui] 2、LED閃爍、串口、滑塊、文本編輯框簡單例程
