嘿,我的小可爱们!
在《蓝牙串口通信》这一章中,小编带着大家编写了蓝牙串口通信程序,测试了蓝牙通信正常。由于我后来又找到了《蓝牙调试器》软件,功能强大,可自定义控件,所以下面将编写通信协议,通过这款软件,来实现数据采集和蓝牙控制。而我们在上一章中,完成了蓝牙监控界面的设计,所以这一章中,我们开始讲解程序的编写。
首先,我们要将数据包设置成结构体的形式,便于后面的操作和管理,同时定义接收数据堆栈和发送数据堆栈。之所以这样做,是因为通信协议规定,数据包必须包括起始字节、数据字节、校验字节和结束字节,这样做的目的就是确保数据传输的正确性和稳定性。关于通信方面的知识,我以后用到的话,还会介绍的,例如ESP8266等模块。
/*定义串口发送数据堆栈*/
typedef struct
{
u8 head;//定义起始字节
u8 getdata[8];//定义采集数据,包括温度(1字节)、湿度(1字节)、是否报警(1字节)、电机运行状态(1字节)、空气质量(2字节)和测量距离(2字节)
u8 verify;//定义校验字节
u8 tail;//定义结束字节
}TX_STACK;//定义串口发送数据堆栈
/*定义串口接收数据堆栈*/
typedef struct
{
u8 head;//定义起始字节
u8 control[10];//定义阈值存放区,温湿度上下限(4字节),空气质量、安全距离(4字节),取消报警和启停电机(2字节)
u8 verify;//定义校验字节
u8 tail;//定义结束字节
u8 ptr;//定义接收数据指针
u8 lock_flag;//定义数据接收完毕标志位
}RX_STACK;//定义串口接收数据堆栈
TX_STACK tx_stack;//定义发送数据缓存区
RX_STACK rx_stack;//定义接收数据缓存区
关于蓝牙串口的配置代码,在前面已经讲述过。下面,我们分为两个模块来讲解。
(1)数据采集,实时显示
这一部分,是单片机发送数据,通过蓝牙传输给手机,在手机界面上显示单片机采集到的数据。
/*发送堆栈初始化*/
void tx_init(void)
{
u8 i;
tx_stack.head = 0xa5;//起始字节
for(i=0; i<8; i++)
{
tx_stack.getdata[i] = 0x00;//采集数据
}
tx_stack.verify = 0x00;
tx_stack.tail = 0x5a;//结束字节
}
我们先将发送堆栈初始化,然后通过下面的发送程序发送出去。
/*发送采集数据*/
void send_collected_data(void)
{
u8 i;
//发送起始字节
USART_SendData(USART2,tx_stack.head);
while(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET);
//发送采集数据
for(i=0; i<8; i++)
{
USART_SendData(USART2, tx_stack.getdata[i]);
while(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET);
tx_stack.verify += tx_stack.getdata[i];
}
//发送校验字节
USART_SendData(USART2,tx_stack.verify);
while(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET);
//发送结束字节
USART_SendData(USART2,tx_stack.tail);
while(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET);
tx_init();
}
在发送采集数据函数里,我们根据协议,依次发送起始字节、采集数据字节、校验字节和结束字节。这样,在蓝牙软件里,就会对单片机发送过来的数据进行分析处理。首先,判断数据包是否符合通信协议,如果符合(起始字节、结束字节等正确,数据结构正确)。再对接收的数据进行校验,如果校验成功,则数据有效,蓝牙软件就会显示对应的数据,包括文本、图表等形式。如果校验不成功,则数据失效,不予处理。(注意:这里只要单片机依次发数据即可,判断数据包是否符合协议以及数据校验等操作是上位机软件程序完成的)
temp = distance/100;
tx_stack.getdata[0] = temperature;
tx_stack.getdata[1] = humidity;
tx_stack.getdata[2] = cancel;
tx_stack.getdata[3] = state;
tx_stack.getdata[4] = value%256;
tx_stack.getdata[5] = value/256;
tx_stack.getdata[6] = temp%256;
tx_stack.getdata[7] = temp/256;
send_collected_data();
在主程序里,对发送数据包的每个元素采集到的数值,再调用该发送程序即可。
(2)蓝牙控制,参数调整
这一部分,是单片机接收程序,单片机接收手机发出的指令,并做相关的处理。
我们先要在串口中断函数里编写数据包接收语句,相较于发送(此项目采集数据,实时性强),接收不是每时每刻都进行的,用中断的方式可能会更好些。在接收中断函数里,我们只要依次接收数据,将数据存放在相应的接收数据结构体里即可。
void USART2_IRQHandler(void)
{
static u8 i = 0;
if(USART_GetITStatus(USART2, USART_IT_RXNE)!=RESET)
{
rev_data = USART_ReceiveData(USART2); //接收数据
if(rx_stack.lock_flag == UNLOCK)
{
switch(i)
{
case 0:rx_stack.head = rev_data;i++;break;
case 1:rx_stack.control[rx_stack.ptr++] = rev_data;i++;break;
case 2:rx_stack.control[rx_stack.ptr++] = rev_data;i++;break;
case 3:rx_stack.control[rx_stack.ptr++] = rev_data;i++;break;
case 4:rx_stack.control[rx_stack.ptr++] = rev_data;i++;break;
case 5:rx_stack.control[rx_stack.ptr++] = rev_data;i++;break;
case 6:rx_stack.control[rx_stack.ptr++] = rev_data;i++;break;
case 7:rx_stack.control[rx_stack.ptr++] = rev_data;i++;break;
case 8:rx_stack.control[rx_stack.ptr++] = rev_data;i++;break;
case 9:rx_stack.control[rx_stack.ptr++] = rev_data;i++;break;
case 10:rx_stack.control[rx_stack.ptr++] = rev_data;i++;break;
case 11:rx_stack.verify = rev_data;i++;break;
case 12:rx_stack.tail = rev_data;i++;break;
default:rx_stack.lock_flag = LOCKED;i=0;break;
}
}
}
USART_ClearITPendingBit(USART2, USART_IT_RXNE); //清除接收中断标志
}
接收手机发送的数据包后,我们要对数据包进行解析。当然在解析之前,先要判断数据能不能校验成功,如果校验成功,则接收的数据正确,做相关的处理;否则,接收的数据错误,不做任何处理。这里,我们需要判断起始字节、结束字节(固定值0xa5和0x5a)是否符合协议,以及校验字节是否和数据字节之和低8位是否相等。这里,由于采用软件里的自定义控件进行数据命令的发送,所以起始字节和结束字节可不必校验(肯定是固定值0xa5和0x5a)。如果是通过文本框发送数据,那么就要校验这两个字节(手动输入,不能确保)。
/*发送蓝牙控制数据*/
void send_control_data(void)
{
u8 sum = 0,i;
if(rx_stack.lock_flag == LOCKED)
{
for(i=0; i<10; i++)
{
sum += rx_stack.control[i];
}
/*检验成功*/
if(rx_stack.verify==sum)
{
temper_H = rx_stack.control[0];
temper_L = rx_stack.control[1];
humid_H = rx_stack.control[2];
humid_L = rx_stack.control[3];
cancel = rx_stack.control[4];
motor_flag = rx_stack.control[5];
air_upperlimit = rx_stack.control[6]+rx_stack.control[7]*256;
length = rx_stack.control[8]+rx_stack.control[9]*256;
}
else
{
;//校验失败,不操作
}
rx_stack.lock_flag = UNLOCK;
rx_init();
}
}
当然,我们在此之前需要对接收数据堆栈进行初始化,然后再主函数中调用解析函数即可。
/*接收堆栈初始化*/
void rx_init(void)
{
rx_stack.head = 0x00;//起始字节
rx_stack.control[0] = temper_H;//数据接收控制4字节
rx_stack.control[1] = temper_L;
rx_stack.control[2] = humid_H;
rx_stack.control[3] = humid_L;
rx_stack.control[4] = cancel;
rx_stack.control[5] = motor_flag;
rx_stack.control[6] = air_upperlimit%256;
rx_stack.control[7] = air_upperlimit/256;
rx_stack.control[8] = length%256;
rx_stack.control[9] = length/256;
rx_stack.verify = 0x00;//数据校验字节
rx_stack.tail = 0x00;//结束字节
rx_stack.lock_flag = UNLOCK;
rx_stack.ptr = 0x00;
}
到这里,我们的蓝牙串口通信协议就编写完成了,当然过程肯定没有这么简单,不信你也可以试试。此时,我们连接好蓝牙,打开之前的多功能时钟工程,就可以在工程界面里看到实时采集的数据,并且不断的更新。同时,你也可以设置相关的参数,UI界面也会随着你的操作而会发生变化,单片机也会做相关的处理,是不是很神奇,是不是很有趣,那就一起来开发多功能时钟吧。