补充:
红色
部分为重要部分;
绿色
为解析或者补充,如果红色前有绿色,代表文章后有
相关知识
;
蓝色
为代码,如果蓝色内部有红色如上;
紫色
为自我理解与认知不一定正确。
自定义协议
功能字
0x01:控制灯
0x02:获取灯的状态
0x03发送灯的状态
0x04:获取DHT11的温度
0x05:获取DHT11的湿度
0x06:发送DHT11的温度
0x07:发送DHT11的湿度
补充部分知识点
异或相同返回0不同返回1
0与任何数异或都等于那个数本身
uint8_t 0~255
static
静态变量在局部定义后等同于全局变量
volatile 可以防止语句被优化省略掉
如何解析协议呢?
我们可以发现这里可以利用状态机,进行状态的存储与使用。
首先检测字节0;
检测成功后检测字节1;(当然如果没有检测到回到起始点检测字节0)(检测成功后要将所有存储在buf中,因为最后要完成校验)
检测数据长度(这里是可能发生改变,包括后面我们可以设置一个变量,对这里的长度进行存储)
…………
最后进行校验
(这里使用了接收的中断,如果不会可以观看串口那期,中断尽可能少操作重复操作可以放在while循环中)
第一步解析:(这里的解析过程是写在接收中断的回调函数中)
HAL_UART_Receive_IT(&huart1, &rec_data, 1); //每次在串口1接收一位数时进入中断回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
//编写
回调函数 参数为进入函数的串口指针,就是使用了那个串口
{
if(huart ==&huart1)
{
rec_buf[rec_index]=rec_data;
//以buf为数组存储,以index为下标,data为存储在buf中的元素,每来一个填入一个下标前进
rec_index++;
switch(frame_state)
//检测当前的状态,通过每一个状态进行每一个选项(这里要枚举提前设置每一个状态)
{
case FRAME_HEAD_1:
//头1
if(rec_buf[0]==0xAA)
{
frame_state=FRAME_HEAD_2;
}
else
{
rec_index=0;
}
break;
case FRAME_HEAD_2:
//头2
if(rec_buf[1]==0x55)
{
frame_state=FRAME_LENGTH;
}
else
{
rec_index=0;
frame_state=FRAME_HEAD_1;
}
break;
case FRAME_LENGTH:
//这里记录了
rec_buf[2]也就是数据位的长度
rec_len=rec_buf[2];
frame_state=FRAME_DATA;
break;
case FRAME_DATA:
//这里模拟了数据位的完成,具体内容可以在主函数while里面书写,中断函数尽量不要有太多的运算
data_len++;
if( data_len>=rec_buf[2])
{
data_len=0;
frame_state=FRAME_CHECK;
}
break;
case FRAME_CHECK:
//完成后一帧的协议就已经完全的输入其中了但是没有进行校验,也是可以放在主函数中运行,设置的标记代表检测完成
rec_compelete_flag=1;
frame_state=FRAME_HEAD_1;
rec_index=0;
break;
}
HAL_UART_Receive_IT(&huart1, &rec_data, 1); //再一次进入中断回调函数这是形成循环的关键
}
}
uint8_t cal_XORSum(uint8_t *buf,uint16_t size) //这里单独写了一个函数用于校验,不要忘记了在前面声明函数否则无法使用
{
uint8_t tmp=0;
for(uint16_t i=0;i<size;i++)
{
tmp^=buf[i];
}
return tmp;
}
接下来完成功能(写在主函数中)
0x01:控制灯:可以通过代码来进行反推协议如果不会可以参考下面的图(标记的判断与复位被我省略了要注意(就是判1与回0))
if(rec_buf[rec_len+3]==cal_XORSum(rec_buf,rec_len+3))
{
if(rec_buf[3]==0x01&&rec_buf[4]==0x01)
{
if(rec_buf[5]==0x01)
{
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_RESET);
}
else
{
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_SET);
}
}
else if(rec_buf[3]==0x01&&rec_buf[4]==0x00)
{
if(rec_buf[5]==0x01)
{
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5,GPIO_PIN_RESET);
}
else
{
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5,GPIO_PIN_SET);
}
}
0x02:获取灯的状态(这里要和发送灯的状态一起看)(这个过程是电脑对单片机发出指令)
在写完解析后想要获取状态只需对应协议发送向对应的命令即可如:
AA 55 02 02 00 FF
AA 55 02 02 01 FE
0x03发送
灯的状态
(这个过程是单片机回复电脑)
if(rec_buf[3]==0x02&&rec_buf[4]==0x00)
{
uint32_t state =0;
uint8_t data[7]={0};
//这里注意要新建立一个数组用于储存单片机发回来的数据不能使用,电脑向单片机发送数据使用的数组;
state=HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_5);
data[0]=0xAA;
data[1]=0x55;
data[2]=0x03;
data[3]=0x03;
data[4]=0x00;
data[5]=!state;
data[6]=cal_XORSum(data,data[2]+3);
HAL_UART_Transmit(&huart1,data,7,0xFF);
//通过函数将存在data数组中的数据传输到串口1上,注意 不一定一定要连接在电脑上的,其他的设备也是可以使用的。
}
else if(rec_buf[3]==0x02&&rec_buf[4]==0x01)
{
uint32_t state =0;
uint8_t data[7]={0};
state=HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_5);
data[0]=0xAA;
data[1]=0x55;
data[2]=0x03;
data[3]=0x03;
data[4]=0x01;
data[5]=!state;
data[6]=cal_XORSum(data,data[2]+3);
HAL_UART_Transmit(&huart1,data,7,0xFF);
}
与上同理:
AA 55 01 04 FA
AA 55 01 05 FB
湿度与温度 (当然如果想要完成此功能需要导入DHT11的驱动前期
嵌入式的学习之DHT11-CSDN博客是有的,而且导入后不要忘记初始化)
if(rec_buf[3]==0x04)
{
uint8_t data[7]={0};
DHT11_Read(&shidu,&wendu);
data[0]=0xAA;
data[1]=0x55;
data[2]=0x03;
data[3]=0x06;
data[4]=shidu;
data[5]=0x00;
data[6]=cal_XORSum(data,data[2]+3);
HAL_UART_Transmit(&huart1,data,7,0xFF);
}
else if(rec_buf[3]==0x05)
{
uint8_t data[7]={0};
DHT11_Read(&shidu,&wendu);
data[0]=0xAA;
data[1]=0x55;
data[2]=0x03;
data[3]=0x07;
wendu=wendu*100;
data[4]=(int)wendu/100;
data[5]=(int)wendu%100;
data[6]=cal_XORSum(data,data[2]+3);
HAL_UART_Transmit(&huart1,data,7,0xFF);
}