数据帧结构
帧头:数据传输的开始标志,一般是判断两次,如果两次都判断正确再继续接受数据,如果判断错误则重新开始接受数据
字节长度:数据字节长度,除去帧头帧尾和校验位
数据:
功能码:
校验位:所有数据的总和,0XAA+0XAA+0X20+0X01+0X00=0X57
确定是要发送什么数据,比如要点灯,功能码就表示灯
功能内容:功能码所需要执行/接收的数据
通信协议测试代码
自己写的通信协议测试代码,通过串口调试助手发送数据给stm32(串口1)这段代码里没有校验位和数据长度,如果帧头帧尾接收正确,就数据接收正确,开始处理数据,检测校验和只是固定传输某些数据,对于可变数据的传输不检查校验和,检查帧头帧尾就可以。
#define E_START 0 //准备状态
#define E_OK 1 //成功
#define E_FRAME_HEADER_ERROR 2 //帧头错误
#define E_FRAME_RTAIL_ERROR 3 //帧尾错误
#define LINE_LEN 4 //数据长度 0--3
#define HEAD_FRAME 0xD8
#define BACK_FRAME 0xEE
uint8_t temp_buff[LINE_LEN]; //主机用于接收数据的BUFF
uint8_t uart_flag; //接收数据标志位
uint8_t data_n;
void get_slave_data(uint8_t data) //只管帧头帧尾的数据是否正确,帧尾数据正确之后再处理中间数据
{
static uint8_t uart_num = 0;
temp_buff[uart_num++] = data;
if(uart_num==1) //接收到的第一个数据 0xD8
{
if(HEAD_FRAME != temp_buff[0]) //帧头错误
{
RED_ON;
uart_flag = E_FRAME_HEADER_ERROR;
uart_num=0;//数据位清零
}
}
else if(uart_num==2) //接收到的第二个数据
{
}
else if(uart_num==3) //接收到的第三个数据
{
}
else if(uart_num==4) //接收到的第四个数据 帧尾0xEE
{
if(BACK_FRAME==temp_buff[LINE_LEN-1]) //如果帧尾正确
{
GREEN_ON;
uart_flag = E_OK; //接收标志成功
data_n=temp_buff[2]-1;
}
else //接收失败
{
uart_flag = E_FRAME_RTAIL_ERROR; //帧尾错误
}
uart_num=0;
}
USART_SendData(DEBUG_USARTx, uart_flag);
}
//数据处理
void data_process(void)
{
if(uart_flag ==E_OK) //数据接收成功
{
if(data_n==2)
{
GREEN_ON;
}
else if(data_n==1)
{
RED_ON;
}
else BLUE_ON;
}
}
// 串口中断服务函数
extern uint8_t uart_flag;
extern uint8_t data_n;
#define E_START 0 //准备状态
#define E_OK 1 //成功
voidUSART1_IRQHandler(void)
{
uint8_t ucTemp;
if(USART_GetITStatus(DEBUG_USARTx,USART_IT_RXNE)!=RESET)
{
ucTemp = USART_ReceiveData(DEBUG_USARTx);//获取串口数据
// USART_SendData(DEBUG_USART1x,ucTemp);
get_slave_data(ucTemp);//将每一个串口的数据存入temp_buff中,判断帧头帧尾
data_process();
}
}
串口通信编程要点
1:上位机发送数据时要勾选hex显示,或者是以16进制发送,以16进制接收
因为底层的数据都是以字节流模式传输,数据会被分解成一个一个字节,一个字节是8位,如果选择ASCII传输,那么接收端收到的就是ASCII码,
选择ASCII发送就代表你要发送的是字符串,这时候程序就会一位一位地读,比如你写了1234,在字节流中传递的就是1234对应的ASCII码,31,32,33,34(十六进制的)。比较而言,在Hex发送模式下,写了1234,会被发送的就是12,34,如果是01020304那就是01,02,03,04。这个时候,你写ab就会发送相应的ASCII码61,62,其他字符同理。
选择Hex发送就代表你要发送的内容是纯数字,由程序完成String到Int再到Byte的转化。
所以你应该保证每个你要发送的数都是两位的,如果是7就应该写07,因为程序会每两位每两位地读。如果你选择了Hex发送,而输入的又是字符,比如你写了ab,那么就会被程序读为16进制的AB。这就是不同的概念了,无论你选择什么方式显示都不能得到原来的ab了。
2:配置串口,配置串口的io口的初始化,以及串口的初始化,以及嵌套串口中断NVIC
3: 串口中断处理函数:
先将收到的数据用一个变量来接收,之后在串口数据处理函数中用一个buf缓存数组来接收处理数据,判断帧尾uart_flag = E_OK必须加,要不然uart_flag
会出错误,一直执行在uart_num ==1 里面
当接收标志位为正确时开始解析数据:
串口通信从机数据发送:
1:初始化串口
2:将数据封装处理,如果数据时16位的需要分开处理,将高低八位分别发送,接收端合并
3: 通过串口将数据发送出去,
数据处理:
stm32f4串口数据接收解析代码:
1:串口初始化配置
void UART1_Init(uint32_t BaudRate)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
//开启io口对应时钟
RCC_AHB1PeriphClockCmd(UART1_CLK , ENABLE);
//开启串口对应时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
//外围设备的特殊功能
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);
//TX RX-- 复用推挽输出
GPIO_InitStructure.GPIO_Pin = UART1_PIN_TX | UART1_PIN_RX;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_Init(UART1_PORT,&GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = BaudRate;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_BaudRate = BaudRate;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发一体
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_Init(USART1, &USART_InitStructure);
USART_ITConfig(USART1, USART_IT_RXNE,ENABLE); //串口接收中断开启
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
USART_Cmd(USART1,ENABLE);
}
2:串口接收中断:
3:串口数据效验
void data_test(int rxdata)
{
static int count = 0;
TmpData[count++] = rxdata; //数据存入数组
if(count == 1) //帧头
{
if(TmpData[0] == 0x01)
{
LED0_ON;
flag_num = 1;
BEEN_OFF;
}
else
{
BEEN_ON;
flag_num = 0;
count = 0; //重新开始接收
}
}
if(count == 4) //帧尾
{
flag_num = 4; // 这里必须加!!!!!!!!!!!!!!!!!
if(TmpData[3] == 0x07) //数据
{
LED3_ON;
flag_num == 4;
}
else
{
flag_num = 0;
count = 0; //重新开始接收
BEEN_ON;
}
count = 0;
}
}
4:串口数据处理:
串口数据发送数据类型问题:
串口数据是一个字节一个字节发送接收的,每一数据帧包含起始位,功能字,数据位,奇偶校验位,停止位。串口数据的发送是一个字节一个字节发送的,也就是串口每次只能发送和接收八位数据,那么如果要发送一些十六进制的数据就需要位操作来实现数据的发送以及合并。
以发送一个十六位数为例:
Data = 65530 这个数据是16位的数据(FFFA),就要拆成两部分发送
Low_Data = Data & 0xFF 取出低八位 Low_Data = FA
High_D = Data >> 8 将数据右移八位,高8位移到低8位 High_D = FF
Low_Data和High_D 都调用数据发送函数发送出去
数据接收
High_D = High_D << 8 将高位数据重新移到高位 FF00
Data_temp = High_D | Low_Data 数据重新合并 FFFA
数据拆分发送
数据接收合并
一些数据发送函数的解读:
数据的发送和接收都离不开USART的DR寄存器,这个寄存器由两个寄存器组成,包含了数据的发送与接收,兼具读和写两个功能
数据发送 USARTx->DR = (Data & (uint16_t)0x01FF);
数据接收 (uint16_t)(USARTx->DR & (uint16_t)0x01FF);
数据是以字节的形式在硬件流中传输 一个字节是8bit
发送一个字节:
接收一个字节:
所有的数据发送函数的扩充都是在这两个函数的基础上改进的
发送一个字节封装函数,与标准库功能参数一样。
发送8位的数组,按字节循环发送
发送字符串,和数组发送类似,按字节循环发送
发送一个16位数,分别取出高低八位,分别发送