[stm32] NRF24L01+USART搞定有线和无线通信
前言
一般进行远程监控时,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函数说完,整个工程的细节也就大致能了解了~
int main(void){
uint8_t a=;//LED高低电压控制
/* System Clocks Configuration */
RCC_Configuration(); //系统时钟设置
/*嵌套向量中断控制器
说明了USART1抢占优先级级别0(最多1位) ,和子优先级级别0(最多7位) */
NVIC_Configuration(); //中断源配置
/*对控制LED指示灯的IO口进行了初始化,将端口配置为推挽上拉输出,口线速度为50Mhz。PA9,PA10端口复用为串口1的TX,RX。
在配置某个口线时,首先应对它所在的端口的时钟进行使能。否则无法配置成功,由于用到了端口B, 因此要对这个端口的时钟
进行使能,同时由于用到复用IO口功能用于配置串口。因此还要使能AFIO(复用功能IO)时钟。*/
GPIO_Configuration(); //端口初始化
SPI2_NRF24L01_Init(); //SPI2及NRF24L01接口初始化
USART_Config(USART1); //串口1初始化
/*NRF24L01设置为接收模式*/
RX_Mode();
while ()
{
if(usart_rec_flag==) //判断是否收到一帧有效数据
{
usart_rec_flag=;
NRF_Send_Data(TxBufferRF,sizeof(TxBufferRF));
if(a==){GPIO_SetBits(GPIOB, GPIO_Pin_5);a=;} //LED1 明暗闪烁
else{GPIO_ResetBits(GPIOB, GPIO_Pin_5);a=;}
}
if(rf_rec_flag==)
{
rf_rec_flag=;
for(i=;i<;i++)//发送字符串
{
USART_SendChar(USART1,TxBufferUSART[i]);
// Delay(0x0000ff00);
}
}
}
}
第4行RCC初始化主要是系统时钟和外设时钟配置,这里注意要使能RCC_APB2Periph_USART1,当时忘了使能这个结果串口出现异常,我还以为是初始化和中断向量什么的弄错了呢,浪费了很长时间。
/*--------------------------------------------------------------------------------------
系统时钟配置为72MHZ+外设时钟配置*/
void RCC_Configuration(void){
SystemInit();
RCC_APB2PeriphClockCmd( RCC_APB2Periph_USART1 |RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB |RCC_APB2Periph_AFIO , ENABLE);
}
第7行中断向量初始化设置,主要是设置串口接收中断和NRF24L01中断的,这样设置好了之后当串口中断被触发时其对应的中断子程序将被执行(这个科班的大概都知道这里就不多说了),所以我们就要在stm32f10x_it.c里实现他们各自的中断子程序了(这个一会再详细介绍,咱们先把整个框架了解下)。另外说一句,这里的的优先级组将影响主优先级和子优先级数量具体请参考stm32f10X_的固件库的NVIC.
void NVIC_Configuration(void){
/* 结构声明*/
NVIC_InitTypeDef NVIC_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
/* 优先级组 1 */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
/* Enable the USART1 Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //设置串口1中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = ; //抢占优先级 0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = ; //子优先级为0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //NRF24L01 中断响应
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = ; //抢占优先级 0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = ; //子优先级为1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能
NVIC_Init(&NVIC_InitStructure);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0); //NRF24L01 IRQ PA0
EXTI_InitStructure.EXTI_Line = EXTI_Line0; //NRF24L01 IRQ PA0
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //EXTI中断
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE; //使能
EXTI_Init(&EXTI_InitStructure);
}
第11行的GPIO初始化,主要是对通用IO口的属性设置和初始化,这里一定要对串口所需的A9和A10配置好!
void GPIO_Configuration(void){
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED1控制--PB5
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //USART1 TX
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure); //A端口
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //USART1 RX
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //复用开漏输入
GPIO_Init(GPIOA, &GPIO_InitStructure); //A端口
}
第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):
/****************************************************************************
* 名 称:NRF_Send_Data(uint8_t* data_buffer, uint8_t Nb_bytes)
* 功 能:将保存在接收缓存区的32字节的数据通过NRF24L01+发送出去
* 入口参数:data_buffer 待发送数据
Nb_bytes 待发送数据长度
* 出口参数:无
* 说 明:数据小于32,把有效数据外的空间用0填满
* 调用方法:RX_Mode();
****************************************************************************/
void NRF_Send_Data(uint8_t* data_buffer, uint8_t Nb_bytes)
{
uchar i=;
MODE_CE(); //NRF 模式控制 SPI_RW_Reg(WRITE_REG1+STATUS,0xff); //设置状态寄存器初始化
SPI_RW_Reg(0xe1,); //清除TX FIFO寄存器
SPI_RW_Reg(0xe2,); //清除RX FIFO寄存器
TX_Mode(); //设置为发送模式
delay_ms();
if(Nb_bytes<){ //当接收到的USB虚拟串口数据小于32,把有效数据外的空间用0填满
for(i=Nb_bytes;i<;i++) data_buffer[i]=;
}
MODE_CE();
SPI_Write_Buf(WR_TX_PLOAD, data_buffer, TX_PLOAD_WIDTH); //发送32字节的缓存区数据到NRF24L01
MODE_CE(); //保持10us以上,将数据发送出去
}
第13行是USART初始化,包括波特率、数据位、停止位等~
void USART_Config(USART_TypeDef* USARTx){
USART_InitStructure.USART_BaudRate = ; //速率9600bps
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //数据位8位
USART_InitStructure.USART_StopBits = USART_StopBits_1; //停止位1位
USART_InitStructure.USART_Parity = USART_Parity_No; //无校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无硬件流控
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
/* Configure USART1 */
USART_Init(USARTx, &USART_InitStructure); //配置串口参数函数
/* Enable USART1 Receive and Transmit interrupts */
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //使能接收中断
USART_ITConfig(USART1, USART_IT_TXE, ENABLE); //使能发送缓冲空中断
/* Enable the USART1 */
USART_Cmd(USART1, ENABLE);
}
同样的类似于NRF24L01一旦初始化之后,其数据接收一般采用中断方式、数据发送一般采用直接发送的方式。所以在中断向量里也要设置,也要在it.c中实现其接收中断子函数。其发送直接调用stm32f10的固件库函数(这里我稍加封装了下):其实就是发送一个data之后要监听是否发送完成才能进行下次发送~
void USART_SendChar(USART_TypeDef* USARTx,uint8_t data){
USART_SendData(USARTx,data);
while(USART_GetFlagStatus(USARTx,USART_FLAG_TC)==RESET);
}
接下来进入while循环,不断进行监听看是否有串口接收标志位置1或者无线模块接收标志位置1,如果有表明相应的有数据从该通道传送过来。当是从串口传来的数据表明数据是从上位机发送来的数据,并且想把该数据通过2.4G发送出去,所以这里调用:NRF_Send_Data(TxBufferRF,sizeof(TxBufferRF));将数据发送出去;当数据是从2.4G通道中传过来的,表明数据是从下位机传送过来的想给上位机,于是调用串口发送函数将数据发送给上位机:USART_SendChar(USART1,TxBufferUSART[i]);

看了上图适配器端的数据交换过程就明白了串口中断和无线中断大致要干的事了,这里我就不多介绍,看看下面的代码就明白了(在stm32f10x_it.c中),要再次提醒的是无论是串口还是无线其接收都是采用中断,而发送采用循环直接发送,他们的中断和中断向量有关并要在stm32f10x_it.c里实现相应的中断子程序~
/******************************************************************************/
/* STM32F10x Peripherals Interrupt Handlers */
/******************************************************************************/ /**
* @brief This function handles USART1 global interrupt request.
* @param None
* @retval : None
*/
void USART1_IRQHandler(void) //串口1 中断服务程序
{
unsigned int i;
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //判断读寄存器是否非空
{ RxBufferUSART[RxCounter1++] = USART_ReceiveData(USART1); //将读寄存器的数据缓存到接收缓冲区里 if(RxBufferUSART[RxCounter1-]==0x0d&&RxBufferUSART[RxCounter1-]==0x0a) //判断结束标志是否是0x0d 0x0a
{
for(i=; i< RxCounter1; i++) TxBufferRF[i] = RxBufferUSART[i]; //将接收缓冲器的数据转到发送缓冲区,准备转发
usart_rec_flag=; //接收成功标志
TxBufferRF[RxCounter1]=; //发送缓冲区结束符
TxCounter1=RxCounter1;
RxCounter1=;
}
} if(USART_GetITStatus(USART1, USART_IT_TXE) != RESET) //这段是为了避免STM32 USART 第一个字节发不出去的BUG
{
USART_ITConfig(USART1, USART_IT_TXE, DISABLE); //禁止发缓冲器空中断,
}
}
/*******************************************************************************
* Function Name : EXTI0 中断函数
* Description : NRF24L01中断服务程序
* Input : None
* Output : None
* Return : None
*******************************************************************************/
void EXTI0_IRQHandler(void){
u8 i=;
u8 status;
if(EXTI_GetITStatus(EXTI_Line0) != RESET) //判断是否产生了EXTI0中断
{
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==){ //判断是否是PA0线变低
status=SPI_Read(READ_REG1+STATUS); // 读取状态寄存其来判断数据接收状况
if(status & 0x40) // 判断是否接收到数据
{
//GPIO_ResetBits(GPIOB, GPIO_Pin_5);
SPI_Read_Buf(RD_RX_PLOAD,rx_buf,TX_PLOAD_WIDTH); //从接收缓冲区里读出数据
for(i=; i<; i++)TxBufferUSART[i] = rx_buf[i]; //向USB 端点1的缓冲区里放置数据
rf_rec_flag=;
}
else if((status &0x10)>){ //发射达到最大复发次数
SPI_RW_Reg(0xe1,); //清除发送缓冲区
RX_Mode(); //进入接收模式
}
else if((status &0x20)>){ //发射后收到应答
GPIO_SetBits(GPIOB, GPIO_Pin_5);
SPI_RW_Reg(0xe1,); //清除发送缓冲区
RX_Mode(); //进入接收模式
}
SPI_RW_Reg(WRITE_REG1+STATUS, status); //清除07寄存器标志
}
EXTI_ClearITPendingBit(EXTI_Line0); //清除EXTI0上的中断标志
}
}
串口和无线接收中断子程序
下位机部分
上面说过一般具有远程通信能力的嵌入式系统其下位机部分往往要干很多事,这里我们采用stm32作为MCU并搭载uc-OS实时操作系统负责任务调度,同时采用7寸TFT彩屏和uc-GUI设计可视化人机交互界面。其中任务包括主任务、界面任务和触摸任务,主任务负责建立其他任务,界面任务中将包含整个人机交互界面的界面刷新逻辑,触摸任务负责获取触摸位置数据获取~

这里我们还是得从main函数先说起:首先在main函数中进行相关初始化,然后建立主任务并启动uc-OS内核;接着在主任务中调用App_TaskCreate(); 分别建立界面任务和触摸任务(如下每个任务的建立类似,要给出指向任务代码的指针、任务执行时传递给任务的参数的指针,分配给这个任务的栈信息,任务优先级等)。这样当任务建立好之后,其执行权就由操作系统调度了~
static void App_TaskCreate(void)
{
/* 建立用户界面任务 */
OSTaskCreateExt(AppTaskUserIF, //指向任务代码的指针
(void *), //任务开始执行时,传递给任务的参数的指针
(OS_STK *)&AppTaskUserIFStk[APP_TASK_USER_IF_STK_SIZE-], //分配给任务的堆栈的栈顶指针 从顶向下递减
APP_TASK_USER_IF_PRIO, //分配给任务的优先级
APP_TASK_USER_IF_PRIO, //预备给以后版本的特殊标识符,在现行版本同任务优先级
(OS_STK *)&AppTaskUserIFStk[], //指向任务堆栈栈底的指针,用于堆栈的检验
APP_TASK_USER_IF_STK_SIZE, //指定堆栈的容量,用于堆栈的检验
(void *), //指向用户附加的数据域的指针,用来扩展任务的任务控制块
OS_TASK_OPT_STK_CHK|OS_TASK_OPT_STK_CLR); //选项,指定是否允许堆栈检验,是否将堆栈清0,任务是否要
//进行浮点运算等等。 /* 建立触摸驱动任务 */
OSTaskCreateExt(AppTaskKbd,
(void *),
(OS_STK *)&AppTaskKbdStk[APP_TASK_KBD_STK_SIZE-],
APP_TASK_KBD_PRIO,
APP_TASK_KBD_PRIO,
(OS_STK *)&AppTaskKbdStk[],
APP_TASK_KBD_STK_SIZE,
(void *),
OS_TASK_OPT_STK_CHK|OS_TASK_OPT_STK_CLR); }
这里以界面任务为例:因为我们在建立界面任务时已经指定其任务代码指针AppTaskUserIF,所以这里来写其对应的函数(也就是说这里是界面任务的入口)。从下面的代码可以看出进入界面任务时首先对uc-GUI进行初始化,然后进入死循环不断执行Fun()函数(有人会疑惑:这里while死循环不就只能死在这里吗?怎么执行其他任务呢?哈哈,这就是具有操作系统和不具有操作系统的不同啦~虽然这里是while死循环,但是当OS要把CPU占有权分给其他任务时就会把当前执行的任务的信息压入其对应的栈空间,当再次要把CPU分配给该任务时,则把栈里保存的上次执行的情况拿出来继续执行,从而实现抢占与多任务的效果!)
static void AppTaskUserIF (void *p_arg)
{
(void)p_arg;
GUI_Init(); //ucgui初始化
while()
{
Fun(); //界面主程序
}
}
所以接下来我们主要看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的运行逻辑啦!
void Fun(void) {
GUI_CURSOR_Show(); //打开鼠标图形显示
/* 建立对话框时,包含了资源列表,资源数目, 并且指定了用于动作响应的回调函数 */
hWin = GUI_CreateDialogBox(aDialogCreate, GUI_COUNTOF(aDialogCreate), _cbCallback, , , );
FRAMEWIN_SetFont(hWin, &GUI_FontComic18B_1); //对话框字体设置
FRAMEWIN_SetClientColor(hWin, GUI_BLACK); //对话框的窗体颜色是黑色
memcpy(tx_buf, "1234567890abcdefghij!@#$%^&*()-=", ); //将长度为32字节的发送字符串拷贝到发送缓冲区,
memcpy(rx_buf, "", ); //将接收缓存区清空
/* 获得文本框句柄 */
text1 = WM_GetDialogItem(hWin, GUI_ID_TEXT0); //获得对话框里GUI_ID_TEXT0项目(文本框Send Text Area)的句柄
text2 = WM_GetDialogItem(hWin, GUI_ID_TEXT1); //获得对话框里GUI_ID_TEXT1项目(文本框Receive Text Area)的句柄
text3 = WM_GetDialogItem(hWin, GUI_ID_TEXT2); //获得对话框里GUI_ID_TEXT2项目(文本框2M BPS)的句柄
text4 = WM_GetDialogItem(hWin, GUI_ID_TEXT3); //获得对话框里GUI_ID_TEXT3项目(文本框1M BPS)的句柄
text6 = WM_GetDialogItem(hWin, GUI_ID_TEXT5); //获得对话框里GUI_ID_TEXT5项目(文本框250K BPS)的句柄
text5 = WM_GetDialogItem(hWin, GUI_ID_TEXT4); //获得对话框里GUI_ID_TEXT4项目(状态字符文本框)的句柄
/* 设置文本框字体 */
TEXT_SetFont(text1,pFont); //设置对话框里文本框Send Text Area的字体
TEXT_SetFont(text2,pFont); //设置对话框里文本框Receive Text Area的字体
TEXT_SetFont(text3,pFont18); //设置对话框里文本框2M BPS的字体
TEXT_SetFont(text4,pFont18); //设置对话框里文本框1M BPS的字体
TEXT_SetFont(text6,pFont18); //设置对话框里文本框250K BPS的字体
TEXT_SetFont(text5,pFont); //设置对话框里状态字符文本框的字体
/* 设置文本框颜色 */
TEXT_SetTextColor(text1,GUI_GREEN); //设置对话框里文本框Send Text Area的字体颜色
TEXT_SetTextColor(text2,GUI_GREEN ); //设置对话框里文本框Receive Text Area的字体颜色
TEXT_SetTextColor(text3,GUI_YELLOW); //设置对话框里文本框2M BPS的字体颜色
TEXT_SetTextColor(text4,GUI_YELLOW); //设置对话框里文本框1M BPS的字体颜色
TEXT_SetTextColor(text6,GUI_YELLOW); //设置对话框里文本框250K BPS的字体颜色
TEXT_SetTextColor(text5,GUI_YELLOW); //设置对话框里状态字符文本框的字体颜色
TEXT_SetBkColor(text5,GUI_BLUE); //设置对话框里状态字符文本框的背景颜色
/* 编辑框相关 */
edit1 = WM_GetDialogItem(hWin, GUI_ID_EDIT1); //获得对话框里GUI_ID_EDIT1项目(编辑框 发送字符串显示区)的句柄
EDIT_SetFont(edit1,pFont18); //设置对话框里编辑框 发送字符串显示区的字体
EDIT_SetText(edit1,(const char *)tx_buf); //设置对话框里编辑框 发送字符串显示区的字符串
edit2 = WM_GetDialogItem(hWin, GUI_ID_EDIT2); //获得对话框里GUI_ID_EDIT2项目(编辑框 接收字符串显示区)的句柄
EDIT_SetFont(edit2,pFont18); //设置对话框里编辑框 接收字符串显示区的字体
EDIT_SetText(edit2,(const char *)rx_buf); //设置对话框里编辑框 接收字符串显示区的字符串
/* 按钮相关 */
bt[]=WM_GetDialogItem(hWin,GUI_ID_BUTTON0); //获得对话框里GUI_ID_BUTTON0项目(按键SEND)的句柄
bt[]=WM_GetDialogItem(hWin, GUI_ID_BUTTON2); //获得对话框里GUI_ID_BUTTON2项目(按键CLEAR)的句柄
BUTTON_SetFont(bt[],pFont); //设置对话框里按键SEND的字体
BUTTON_SetFont(bt[],pFont); //设置对话框里按键CLEAR的字体
BUTTON_SetTextColor(bt[],,GUI_WHITE); //设置对话框里按键SEND未被按下的字体颜色
BUTTON_SetTextColor(bt[],,GUI_WHITE); //设置对话框里按键CLEAR未被按下的字体颜色
/* List相关 */
nrf_Pipe=; //NRF24L01初始发射通道设置为0
list1 = WM_GetDialogItem(hWin, GUI_ID_LISTBOX0); //获得对话框里GUI_ID_LISTBOX0项目(列表框-通道选择)的句柄
LISTBOX_SetText(list1, _apListBox); //设置对话框里列表框-通道选择里的条目
LISTBOX_SetFont(list1,pFont18); //设置对话框里列表框-通道选择的字体
LISTBOX_SetSel(list1,nrf_Pipe); //设置对话框里列表框-通道选择的焦点选择
SCROLLBAR_CreateAttached(list1, SCROLLBAR_CF_VERTICAL); //设置对话框里列表框-通道选择的卷动方向为下拉
/* Radio按钮相关 */
rd0 = WM_GetDialogItem(hWin, GUI_ID_RADIO0); //获得对话框里GUI_ID_RADIO0项目(点选框-速率选择)的句柄
nrf_baud=; //NRF24L01速率 初始为2MPS
RADIO_SetValue(rd0,); //设置对话框里点选框-速率选择的焦点选择
RX_Mode(); //NRF24L01进入接收模式
/* 获得slider部件的句柄 */
slider1 = WM_GetDialogItem(hWin, GUI_ID_SLIDER1);
slider2 = WM_GetDialogItem(hWin, GUI_ID_SLIDER2);
slider3 = WM_GetDialogItem(hWin, GUI_ID_SLIDER3);
slider4 = WM_GetDialogItem(hWin, GUI_ID_SLIDER4);
slider5 = WM_GetDialogItem(hWin, GUI_ID_SLIDER5);
/* 设置slider部件的取值范围-8-8*/
SLIDER_SetRange(slider1,-,);
SLIDER_SetRange(slider2,-,);
SLIDER_SetRange(slider3,-,);
SLIDER_SetRange(slider4,-,);
SLIDER_SetRange(slider5,-,);
/* 设置slider部件的值*/
SLIDER_SetValue(slider1,);
SLIDER_SetValue(slider2,);
SLIDER_SetValue(slider3,);
SLIDER_SetValue(slider4,);
SLIDER_SetValue(slider5,);
/* 获取文本框句柄 */
text_speed1 = WM_GetDialogItem(hWin, GUI_ID_TEXT_SPEED1);
text_speed2 = WM_GetDialogItem(hWin, GUI_ID_TEXT_SPEED2);
text_speed3 = WM_GetDialogItem(hWin, GUI_ID_TEXT_SPEED3);
text_speed4 = WM_GetDialogItem(hWin, GUI_ID_TEXT_SPEED4);
text_speed5 = WM_GetDialogItem(hWin, GUI_ID_TEXT_SPEED5);
/* 设置文本框字体 */
TEXT_SetFont(text_speed1,pFont18);
TEXT_SetFont(text_speed2,pFont18);
TEXT_SetFont(text_speed3,pFont18);
TEXT_SetFont(text_speed4,pFont18);
TEXT_SetFont(text_speed5,pFont18);
/* 设置文本框颜色 */
TEXT_SetTextColor(text_speed1,GUI_YELLOW);
TEXT_SetTextColor(text_speed2,GUI_YELLOW);
TEXT_SetTextColor(text_speed3,GUI_YELLOW);
TEXT_SetTextColor(text_speed4,GUI_YELLOW);
TEXT_SetTextColor(text_speed5,GUI_YELLOW);
speed_change_flag=;
while ()
{
if(Rx_Succ==){ //当NRF24L01接收到有效数据
EDIT_SetText(edit2,(const char *)rx_buf); //将接收缓冲区的字符写入到接收字符编辑框内
TEXT_SetText(text5,(const char *)status_buf); //将状态文本缓冲区的字符写入到状态文本框内
Rx_Succ=;
// for(i=0;i<sizeof(rx_buf);i++)
// USART_SendChar(USART1,rx_buf[i]);
}
if(speed_change_flag!=)
{
speed_change_flag=;
USART_SendChar(USART1,control_data);
}
WM_Exec(); //刷新屏幕
}
}
Fun()
/****************************************************************************
* 名 称:static void _cbCallback(WM_MESSAGE * pMsg)
* 功 能:ucgui回调函数,是作为对话框动作响应的函数
* 入口参数:无
* 出口参数:无
* 说 明:
* 调用方法:
****************************************************************************/
static void _cbCallback(WM_MESSAGE * pMsg) {
int NCode, Id;
switch (pMsg->MsgId) {
case WM_NOTIFY_PARENT: //通知父窗口有事件在窗口部件上发生
Id = WM_GetId(pMsg->hWinSrc); //获得对话框窗口里发生事件的部件的ID
NCode = pMsg->Data.v; //通知代码
switch (NCode) {
case WM_NOTIFICATION_RELEASED: //窗体部件动作被释放
if (Id == GUI_ID_BUTTON2) { //按键CLEAR被松开
memcpy(status_buf, "", ); //清空状态文本缓冲区
memcpy(rx_buf, "", ); //清空接收文本缓冲区
TEXT_SetText(text5,(const char *)status_buf); //清空状态文本框
EDIT_SetText(edit2,(const char *)rx_buf); //清空接收字符编辑框
memcpy(tx_buf, "", ); //清空发送文本缓冲区
NRF24L01_TXBUF(tx_buf,); //将发送字符缓冲区的字符通过NRF24L01发送出去
}
else if (Id == GUI_ID_BUTTON0) { //按键SEND 被松开
memcpy(tx_buf, "1234567890abcdefghij!@#$%^&*()-=", ); //将32字节的文本拷贝到发送文本缓冲区
memcpy(rx_buf, "", ); //清空接收文本缓冲区
memcpy(status_buf, "", ); //清空状态文本缓冲区
EDIT_SetText(edit2,(const char *)rx_buf); //清空接收字符编辑框
NRF24L01_TXBUF(tx_buf,); //将发送字符缓冲区的字符通过NRF24L01发送出去
memcpy(tx_buf, "", ); //清空发送文本缓冲区
TEXT_SetText(text5,(const char *)status_buf); //清空状态文本框
}
else if (Id == GUI_ID_RADIO0) { //NRF24L01无线速率点选框点选动作完成
nrf_baud= RADIO_GetValue(rd0); //获得速率表示值
RX_Mode(); //进入接收模式
}
else if (Id == GUI_ID_LISTBOX0){ //NRF24L01无线通道选择动作
nrf_Pipe= LISTBOX_GetSel(list1); //获得NRF24LL01无线通道表示值
RX_Mode(); //进入接收模式
}else if(Id == GUI_ID_SLIDER1){ //slider1 的值被改变
speed1=SLIDER_GetValue(slider1);//获得slider1的值
if(speed1>){
speed_show[]='+';
speed_show[]=''+speed1;
control_data=+speed1;
}else if(speed1<){
speed_show[]='-';
speed_show[]=''-speed1;
control_data=-speed1;
}else{
speed_show[]=' ';
speed_show[]='';
control_data=;
}
// USART_SendChar(USART1,control_data);
TEXT_SetText(text_speed1,speed_show);
speed_change_flag=;
}else if(Id == GUI_ID_SLIDER2){ //slider2 的值被改变
speed2=SLIDER_GetValue(slider2);//获得slider2的值
if(speed2>){
speed_show[]='+';
speed_show[]=''+speed2;
control_data=++speed2;
}else if(speed2<){
speed_show[]='-';
speed_show[]=''-speed2;
control_data=+-speed2;
}else{
speed_show[]=' ';
speed_show[]='';
control_data=;
}
TEXT_SetText(text_speed2,speed_show);
speed_change_flag=;
}else if(Id == GUI_ID_SLIDER3){ //slider3 的值被改变
speed3=SLIDER_GetValue(slider3);//获得slider3的值
if(speed3>){
speed_show[]='+';
speed_show[]=''+speed3;
control_data=++speed3;
}else if(speed3<){
speed_show[]='-';
speed_show[]=''-speed3;
control_data=+-speed3;
}else{
speed_show[]=' ';
speed_show[]='';
control_data=;
}
TEXT_SetText(text_speed3,speed_show);
speed_change_flag=;
}else if(Id == GUI_ID_SLIDER4){ //slider4 的值被改变
speed4=SLIDER_GetValue(slider4);//获得slider4的值
if(speed4>){
speed_show[]='+';
speed_show[]=''+speed4;
control_data=++speed4;
}else if(speed4<){
speed_show[]='-';
speed_show[]=''-speed4;
control_data=+-speed4;
}else{
speed_show[]=' ';
speed_show[]='';
control_data=;
}
TEXT_SetText(text_speed4,speed_show);
speed_change_flag=;
}else if(Id == GUI_ID_SLIDER5){ //slider5 的值被改变
speed5=SLIDER_GetValue(slider5);//获得slider5的值
if(speed5>){
speed_show[]='+';
speed_show[]=''+speed5;
control_data=++speed5;
}else if(speed5<){
speed_show[]='-';
speed_show[]=''-speed5;
control_data=+-speed5;
}else{
speed_show[]=' ';
speed_show[]='';
control_data=;
}
TEXT_SetText(text_speed5,speed_show);
speed_change_flag=;
}
break;
default: break;
}
default:
WM_DefaultProc(pMsg); //默认程序来处理消息
break;
}
}
窗口回调函数
/* 定义了对话框资源列表 */
static const GUI_WIDGET_CREATE_INFO aDialogCreate[] = {
//建立窗体, 大小是800X480 原点在0,0
{ FRAMEWIN_CreateIndirect, "http://beautifulzzzz", ,,, , , FRAMEWIN_CF_ACTIVE },
{ BUTTON_CreateIndirect, "SEND", GUI_ID_BUTTON0, , , , }, { BUTTON_CreateIndirect, "CLEAR", GUI_ID_BUTTON2, , , , },
{ EDIT_CreateIndirect, "", GUI_ID_EDIT1, , , , , EDIT_CF_LEFT, },
{ EDIT_CreateIndirect, "", GUI_ID_EDIT2, , , , , EDIT_CF_LEFT, }, //建立TEXT控件,起点是窗体的X,X,大小XXY 文字左对齐
{ TEXT_CreateIndirect, "Send Text Area", GUI_ID_TEXT0, , , , , TEXT_CF_LEFT },
{ TEXT_CreateIndirect, "Receive Text Area ", GUI_ID_TEXT1, , , , , TEXT_CF_LEFT }, { TEXT_CreateIndirect, "2M bps", GUI_ID_TEXT2, , , , , TEXT_CF_LEFT },
{ TEXT_CreateIndirect, "1M bps", GUI_ID_TEXT3, , , , , TEXT_CF_LEFT },
{ TEXT_CreateIndirect, "250K bps", GUI_ID_TEXT5, , , , , TEXT_CF_LEFT }, { TEXT_CreateIndirect, "", GUI_ID_TEXT4, , , , , TEXT_CF_LEFT }, { RADIO_CreateIndirect, "Receive Mode", GUI_ID_RADIO0, , , , , RADIO_TEXTPOS_LEFT,}, { LISTBOX_CreateIndirect, "", GUI_ID_LISTBOX0, , , , , , }, //建立滑块
{ SLIDER_CreateIndirect, NULL, GUI_ID_SLIDER1, , , , , , },
{ SLIDER_CreateIndirect, NULL, GUI_ID_SLIDER2, , , , , , },
{ SLIDER_CreateIndirect, NULL, GUI_ID_SLIDER3, , , , , , },
{ SLIDER_CreateIndirect, NULL, GUI_ID_SLIDER4, , , , , , },
{ SLIDER_CreateIndirect, NULL, GUI_ID_SLIDER5, , , , , , },
//建立滑块对应的text
{ TEXT_CreateIndirect, "", GUI_ID_TEXT_SPEED1, , , , , TEXT_CF_LEFT },
{ TEXT_CreateIndirect, "", GUI_ID_TEXT_SPEED2, , , , , TEXT_CF_LEFT },
{ TEXT_CreateIndirect, "", GUI_ID_TEXT_SPEED3, , , , , TEXT_CF_LEFT },
{ TEXT_CreateIndirect, "", GUI_ID_TEXT_SPEED4, , , , , TEXT_CF_LEFT },
{ TEXT_CreateIndirect, "", GUI_ID_TEXT_SPEED5, , , , , TEXT_CF_LEFT },
};
还要回过头说说我们的USART和NRF24L01,他们的初始化要看main函数中的BSP_Init();函数,该函数负相关硬件的初始化设置(中文意思是板级支持包初始化函数,因为uc-OS可以并不只限于stm32单片机,所以这里要根据不同平台进行相应的设置)。该函数位于bsp.c函数中,其作用相当于将以前我们在main函数中进行的相关硬件初始化单独拿出来封装成一个函数而已~但是,串口和无线对应的中断接收程序却有点不一样,因为这里是操作系统,所以在每个中断子程序前要调用OS_ENTER_CRITICAL();保存当前的全局中断标志,然后OSIntNesting++;中断嵌套深度加1,最后调用OS_EXIT_CRITICAL();恢复全局中断标志进入正常的中断处理,此外在中断响应函数最后要调用OSIntExit(); 检测如果有更高优先级的任务就绪了,则执行一次任务切换。
/*******************************************************************************
* Function Name : USART1_IRQHandler
* Description : This function handles USART1 global interrupt request.
* Input : None
* Output : None
* Return : None
*******************************************************************************/
void USART1_IRQHandler(void)
{
unsigned int i;
OS_CPU_SR cpu_sr;
OS_ENTER_CRITICAL(); //保存全局中断标志,关总中断 Tell uC/OS-II that we are starting an ISR
OSIntNesting++;
OS_EXIT_CRITICAL(); //恢复全局中断曛? if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //判断读寄存器是否非空
{
RxBufferUSART[RxCounter1++] = USART_ReceiveData(USART1); //将读寄存器的数据缓存到接收缓冲区里
if(RxBufferUSART[RxCounter1-]==0x0d&&RxBufferUSART[RxCounter1-]==0x0a) //判断结束标志是否是0x0d 0x0a
{
for(i=; i< RxCounter1; i++) TxBufferRF[i] = RxBufferUSART[i]; //将接收缓冲器的数据转到发送缓冲区,准备转发
usart_rec_flag=; //接收成功标志
TxBufferRF[RxCounter1]=; //发送缓冲区结束符
TxCounter1=RxCounter1;
RxCounter1=;
}
}
if(USART_GetITStatus(USART1, USART_IT_TXE) != RESET) //这段是为了避免STM32 USART 第一个字节发不出去的BUG
{
USART_ITConfig(USART1, USART_IT_TXE, DISABLE); //禁止发缓冲器空中断,
}
OSIntExit(); //在os_core.c文件里定义,如果有更高优先级的任务就绪了,则执行一次任务切换
}
/////////////////////////////
void EXTI0_IRQHandler(void)
{
unsigned char status,i;
OS_CPU_SR cpu_sr;
OS_ENTER_CRITICAL(); //保存全局中断标志,关总中断 Tell uC/OS-II that we are starting an ISR
OSIntNesting++;
OS_EXIT_CRITICAL(); //恢复全局中断标志 if(EXTI_GetITStatus(EXTI_Line0) != RESET) //判断是否产生了EXTI0中断
{
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==){ //判断是否是PA0线变低
status=SPI_Read(READ_REG1+STATUS); // 读取状态寄存其来判断数据接收状况
if(status & 0x40) // 判断是否接收到数据
{
SPI_Read_Buf(RD_RX_PLOAD,rx_buf,TX_PLOAD_WIDTH); //从接收缓冲区里读出数据
for(i=; i<; i++){ //向USB 端点1的缓冲区里放置数据
TxBufferUSART[i] = rx_buf[i];
}
rf_rec_flag=;
if((status&0x0e)<=0x0a){
nrf_Pipe_r=(status&0x0e)>>; //读出是在哪个通道接收的
}
else nrf_Pipe_r=;
Rx_Succ=; //读取数据完成标志
/* 根据读出的接收通道号,将相应信息写入状态文本缓冲区 */
if(nrf_Pipe_r==) memcpy(status_buf, "Pipe 0 Recive OK! ", );
else if(nrf_Pipe_r==) memcpy(status_buf, "Pipe 1 Recive OK! ", );
else if(nrf_Pipe_r==) memcpy(status_buf, "Pipe 2 Recive OK! ", );
else if(nrf_Pipe_r==) memcpy(status_buf, "Pipe 3 Recive OK! ", );
else if(nrf_Pipe_r==) memcpy(status_buf, "Pipe 4 Recive OK! ", );
else if(nrf_Pipe_r==) memcpy(status_buf, "Pipe 5 Recive OK! ", );
}
else if((status &0x10)>){ //发射达到最大复发次数
SPI_RW_Reg(0xe1,); //清除发送缓冲区
RX_Mode(); //进入接收模式
Rx_Succ=;
/* 根据发送通道,将相应信息写入状态文本缓冲区 */
if(nrf_Pipe==) memcpy(status_buf, "Pipe 0 NO ACK! ", );
else if(nrf_Pipe==) memcpy(status_buf, "Pipe 1 NO ACK! ", );
else if(nrf_Pipe==) memcpy(status_buf, "Pipe 2 NO ACK! ", );
else if(nrf_Pipe==) memcpy(status_buf, "Pipe 3 NO ACK! ", );
else if(nrf_Pipe==) memcpy(status_buf, "Pipe 4 NO ACK! ", );
else if(nrf_Pipe==) memcpy(status_buf, "Pipe 5 NO ACK! ", );
}
else if((status &0x20)>){ //发射后收到应答
SPI_RW_Reg(0xe1,); //清除发送缓冲区
RX_Mode(); //进入接收模式
Rx_Succ=;
/* 根据发送通道,将相应信息写入状态文本缓冲区 */
if(nrf_Pipe==) memcpy(status_buf, "Pipe 0 Send OK! ", );
else if(nrf_Pipe==) memcpy(status_buf, "Pipe 1 Send OK! ", );
else if(nrf_Pipe==) memcpy(status_buf, "Pipe 2 Send OK! ", );
else if(nrf_Pipe==) memcpy(status_buf, "Pipe 3 Send OK! ", );
else if(nrf_Pipe==) memcpy(status_buf, "Pipe 4 Send OK! ", );
else if(nrf_Pipe==) memcpy(status_buf, "Pipe 5 Send OK! ", );
} SPI_RW_Reg(WRITE_REG1+STATUS, status); //清除07寄存器标志
}
EXTI_ClearITPendingBit(EXTI_Line0); //清除EXTI0上的中断标志
}
OSIntExit(); //在os_core.c文件里定义,如果有更高优先级的任务就绪了,则执行一次任务切换
}
串口和无线中断子程序
最后说明
对于纯玩软件的小伙伴,这里涉及的东西有点多,不必细究,看看了解即可。但是对于初学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闪烁、串口、滑块、文本编辑框简单例程
[stm32] NRF24L01+USART搞定有线和无线通信的更多相关文章
- 100天搞定机器学习|Day22 机器为什么能学习?
前情回顾 机器学习100天|Day1数据预处理 100天搞定机器学习|Day2简单线性回归分析 100天搞定机器学习|Day3多元线性回归 100天搞定机器学习|Day4-6 逻辑回归 100天搞定机 ...
- 【开源】简单4步搞定QQ登录,无需什么代码功底【无语言界限】
说17号发超简单的教程就17号,qq核审通过后就封装了这个,现在放出来~~ 这个是我封装的一个开源项目:https://github.com/dunitian/LoTQQLogin ————————— ...
- 对百度WebUploader开源上传控件的二次封装,精简前端代码(两句代码搞定上传)
前言 首先声明一下,我这个是对WebUploader开源上传控件的二次封装,底层还是WebUploader实现的,只是为了更简洁的使用他而已. 下面先介绍一下WebUploader 简介: WebUp ...
- 教你怎么半天搞定Docker
首先,不要把docker想的那么高大,它不就是先做个镜像,然后通过docker像虚拟机一样跑起来嘛...docker其实在真实业务场景中还是非常有局限性的.Dockerfile脚本也没那么好写,有些应 ...
- 用ORM的思想操作XML文档,一个对象就搞定不要太简单。滚蛋吧!XmlDocument、XmlNode、Xml***……
大家有没有这样的感受,一涉及XML文档操作就得百度一遍.是不是非!常!烦!.各种类型,各种方法,更别提为了找到一个节点多费劲.本来想写个XML操作的工具方法,写了两行一想既然XML文档是有规律的,如果 ...
- JS组件系列——又一款MVVM组件:Vue(一:30分钟搞定前端增删改查)
前言:关于Vue框架,好几个月之前就听说过,了解一项新技术之后,总是处于观望状态,一直在犹豫要不要系统学习下.正好最近有点空,就去官网了解了下,看上去还不错的一个组件,就抽空研究了下.最近园子里vue ...
- JS组件系列——BootstrapTable+KnockoutJS实现增删改查解决方案(三):两个Viewmodel搞定增删改查
前言:之前博主分享过knockoutJS和BootstrapTable的一些基础用法,都是写基础应用,根本谈不上封装,仅仅是避免了html控件的取值和赋值,远远没有将MVVM的精妙展现出来.最近项目打 ...
- 如何让两个div在同一行显示?一个float搞定
最近在学习div和css,遇到了一些问题也解决了很多以前以为很难搞定的问题.比如:如何让两个div显示在同一行呢?(不是用table表格,table对SE不太友好)其实,<div> 是一个 ...
- 3小时搞定一个简单的MIS系统案例Northwind,有视频、有源代码下载、有真相
一.瞎扯框架.架构 楼主自从1998年从C语言.MASM.Foxbase开始学计算机开始接触这个行当16年以来,2001年干第一份与程序.软件.然后是各种屌的东西开始,差不多干了13年了,这13年来, ...
随机推荐
- 一个Email
Email 1.接受表单数据并用单独变量保存起来,判断用户协议,这个是必须同意的.2.判断验证码,利用验证码类Captcha.3.判断用户名,密码,邮箱规则,利用Verify类验证.4.判断唯一性,邮 ...
- JQuery初体验
虽然做b/s也有一年半了,但是还没怎么认真的去看JQuery,趁自己生病的这几天,恶补一下JQuery方面的知识,保持学习的态度,内容很简单,聊以自慰一下>_<.废话不多说,直接上代码了. ...
- java-关于浏览器的判断
一.判断是手机还是电脑浏览器 public final static String[] agent = { "Android", "iPhone", " ...
- pygame 练习之 PIE game (以及简单图形训练)
简单的大饼游戏,掌握pygame中直线以及圆弧的画法,以及对输入的响应. import math import pygame, sys from pygame.locals import * pyga ...
- beanstalkd----协议
Beanstalkd中文协议 总括 beanstalkd协议基于ASCII编码运行在tcp上.客户端连接服务器并发送指令和数据,然后等待响应并关闭连接.对于每个连接,服务器按照接收命令的序列依次处理并 ...
- 理解Java Integer的缓存策略
转载自http://www.importnew.com/18884.html 本文将介绍 Java 中 Integer 缓存的相关知识.这是 Java 5 中引入的一个有助于节省内存.提高性能的特性. ...
- 使用hibernate可以优化的地方
a. 在查询字符串中,应该总是使用jdbc的占位符?,或使用使用命名参数:,不要自查询中使用字符串值来代替非常量值. b. Flush会影响性能,频繁刷新影响性能,尽量减少不必要的刷新. c. ...
- nginx日常运维
pid丢失办法: 1.查找nginx进程ID ps -ef | grep nginx 2.将进程ID写入pid > /tmp/nginx.pid 3.重启nginx
- Spring MVC 流程图(转)
Spring MVC工作流程图 图一 图二 Spring工作流程描述 1. 用户向服务器发送请求,请求被Spring 前端控制Servelt DispatcherServle ...
- 尝试u3d中将代码与编辑器分离
最近与朋友交流,他一直是做端游,最近接触了u3d以后无法忍受代码与配置文件,美术资源全部纠缠在一起的状况,于是一直在琢磨怎么将编辑器与代码彻底分离. 自己也抽空研究一下,碰到一些问题先记录下来. 首先 ...