赞
踩
当串口需要发送多个字节的数据时,使用数据包的形式来发送和接收是更加方便的,还可以更好的区分各个字节数据所对应的内容。例如需要发送X,Y,Z来控制陀螺仪传感器的X,Y,Z,就可以使用数据包的格式。
发送一连串数据时,无法精准的确定此数据对应哪一位,所以需要分割数据来使其位置一一对应。或者限制数据的大小来避免数据与包头包尾重复。
1. 使用包头加包尾把数据给包裹起来
在数据与包头包尾有重复时,尽量使用固定包长的方法,以免错误的识别到包头或包尾导致数据错误。
2. 使用一位标志位来确定数据开始
和普通的数据发送一致,使用一个数组来接收这个数据包,然后使用相关的发送函数即可。
每接收一个字节的数据,程序就会进入一次中断然后退出中断,所以每个数据的接收是独立的,所以要针对不同的状态进行对应的判断和进行相关的偏移,使这些数据关联起来,也就是状态机思路。
如图,判断包头为状态s=1,判断成功使状态s=2,则接收数据状态,接收满4个数据后,使状态s=3,判断包尾,判断成功后进入下一轮。
使按下按键可以发送递增数据包,将接收的数据包与发送的数据包都显示到OLED上
串口配置Serial.c
#include "stm32f10x.h" // Device header #include <stdio.h> #include <stdarg.h> uint8_t TxData[4]; // 发送数据包 uint8_t RxData[4]; // 接收数据包 uint8_t RxFlag; void Serial_Init(void){ // 开启时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); // 初始化引脚 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; // A9 发送数据 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 50Hz翻转速度 GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; // A10 接收数据 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 50Hz翻转速度 GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化串口配置 USART_InitTypeDef USART_InitStructure; USART_InitStructure.USART_BaudRate = 9600; // 串口波特率 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 不使用流控 USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; // 串口模式,发送+接收 USART_InitStructure.USART_Parity = USART_Parity_No; // 无校验 USART_InitStructure.USART_StopBits = USART_StopBits_1; // 选择一位停止位 USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 不需要校验位,八位字长 USART_Init(USART1,&USART_InitStructure); // 开启中断 USART_ITConfig(USART1,USART_IT_RXNE,ENABLE); //初始化NVIC NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 分组 NVIC_InitTypeDef NVIC_InitStructure; // 中断通道 NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; // 中断通道使能 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 抢占优先级 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 响应优先级 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_Init(&NVIC_InitStructure); // USART1使能 USART_Cmd(USART1,ENABLE); } // 发送函数 void USART_SendByte(uint8_t Byte){ USART_SendData(USART1,Byte); // 等待写入完成,写入完成之后会将标志位自动清0 while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET); } // 发送数组函数 void USART_SendArray(uint8_t *Array,uint16_t Length){ uint8_t i = 0; for(i=0;i<Length;i++){ USART_SendData(USART1,Array[i]); // 等待写入完成,写入完成之后会将标志位自动清0 while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET); } } // 发送字符串函数 void USART_SendString(uint8_t *String){ uint8_t i = 0; for(i=0;String[i]!='\0';i++){ USART_SendData(USART1,String[i]); // 等待写入完成,写入完成之后会将标志位自动清0 while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET); } } // 返回X的Y次方 uint32_t Serial_Pow(uint32_t X,uint32_t Y){ uint32_t Result = 1; while(Y--){ Result *= X; } return Result; } // 发送数字函数 void USART_SendNum(uint32_t Num,uint16_t Length){ uint8_t i = 0; for(i=0;i<Length;i++){ USART_SendByte(Num / Serial_Pow(10,Length-i-1) % 10 + 0x30); // 等待写入完成,写入完成之后会将标志位自动清0 while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET); } } //重定向fputc函数,fputc是printf函数的底层,printf通过不停的调用fputc来达到输出的效果 //重定向到串口 int fputc(int ch,FILE *f){ USART_SendByte(ch); return ch; } // 封装使用sprintf输出到串口 void Serial_Printf(char *format, ...) { char String[100]; va_list arg; // 可变参数列表 va_start(arg, format); // 从format开始接收可变参数 vsprintf(String, format, arg); va_end(arg); USART_SendString((uint8_t*)String); } // 获取RxFlag uint8_t USART_GetRxFlag(void){ if(RxFlag == 1){ RxFlag = 0; return 1; } return 0; } void USART_SendPacket(void){ USART_SendByte(0xff); // 发送包头 USART_SendArray(TxData,4); // 发送数据 USART_SendByte(0xfe); // 发送包尾 } //中断函数 void USART1_IRQHandler(void){ static uint8_t RxStatus = 0; // 数据包状态 static uint8_t RxCount = 0; if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET){ uint8_t RXDATA = USART_ReceiveData(USART1); // 获取接收到的数据 if(RxStatus == 0){ if(RXDATA == 0xff){ RxStatus = 1; } }else if(RxStatus == 1){ RxData[RxCount] = RXDATA; RxCount++; if(RxCount >= 4){ RxCount = 0; RxStatus = 2; } }else if(RxStatus == 2){ if(RXDATA == 0xfe){ RxStatus = 0; RxFlag = 1; } } } }
按键button.c
#include "stm32f10x.h" // Device header #include "Delay.h" /** * @brief 初始化Button相关端口 * @param 无 * @retval 无 */ void Button_Init(void){ // 初始化时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输出,按下为0 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 50Hz翻转速度 GPIO_Init(GPIOB, &GPIO_InitStructure); // 使这两个端口默认高电平,不然初始化后默认是低电平 GPIO_SetBits(GPIOB, GPIO_Pin_1); } /** * @brief 返回所按按键值 * @param 无 * @retval KeyNum 按键值 */ uint8_t Key_Num(void){ uint8_t KeyNum = 0; if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1)==0){ Delay_ms(20); while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1)==0); Delay_ms(20); KeyNum = 1; } if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11)==0){ Delay_ms(20); while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11)==0); Delay_ms(20); KeyNum = 11; } return KeyNum; }
主函数main.c
#include "stm32f10x.h" // Device header #include "Delay.h" #include "OLED.h" #include "Serial.h" #include "button.h" uint8_t KeyNum; int main(void) { OLED_Init(); OLED_ShowString(1, 1, "TxData:"); OLED_ShowString(3, 1, "RxData:"); Button_Init(); Serial_Init(); TxData[0] = 0x01; TxData[1] = 0x02; TxData[2] = 0x03; TxData[3] = 0x04; //USART_SendPacket(); while (1) { KeyNum = Key_Num(); if(KeyNum == 1){ TxData[0] ++; TxData[1] ++; TxData[2] ++; TxData[3] ++; USART_SendPacket(); OLED_ShowHexNum(2,1,TxData[0],2); OLED_ShowHexNum(2,4,TxData[1],2); OLED_ShowHexNum(2,7,TxData[2],2); OLED_ShowHexNum(2,10,TxData[3],2); } if(USART_GetRxFlag() == 1){ OLED_ShowHexNum(4,1,RxData[0],2); OLED_ShowHexNum(4,4,RxData[1],2); OLED_ShowHexNum(4,7,RxData[2],2); OLED_ShowHexNum(4,10,RxData[3],2); } } }
主要功能为接收字符串,通过特定的字符串来实现led的点亮和关闭,并把相关信息显示在OLED上
led配置led.c
#include "stm32f10x.h" // Device header /** * @brief 初始化LED相关端口,让LED所在端口可以被直接赋值 * @param 无 * @retval 无 */ void LED_Init(void){ // 初始化时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 配置LED所在端口 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 通用推挽输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 50Hz翻转速度 GPIO_Init(GPIOA, &GPIO_InitStructure); // 使这两个端口默认高电平,不然初始化后默认是低电平 GPIO_SetBits(GPIOA, GPIO_Pin_1); } /** * @brief LED1亮 * @param 无 * @retval 无 */ void LED1_On(void){ GPIO_ResetBits(GPIOA, GPIO_Pin_1); } /** * @brief LED1关 * @param 无 * @retval 无 */ void LED1_Off(void){ GPIO_SetBits(GPIOA, GPIO_Pin_1); } /** * @brief LED2亮 * @param 无 * @retval 无 */ void LED2_On(void){ GPIO_ResetBits(GPIOA, GPIO_Pin_2); } /** * @brief LED2关 * @param 无 * @retval 无 */ void LED2_Off(void){ GPIO_SetBits(GPIOA, GPIO_Pin_2); } /** * @brief LED1取反 * @param 无 * @retval 无 */ void LED1_Reverse(void){ // 读取端口状态,根据状态取反 if(GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_1) == 0) GPIO_SetBits(GPIOA, GPIO_Pin_1); else GPIO_ResetBits(GPIOA, GPIO_Pin_1); } /** * @brief LED2取反 * @param 无 * @retval 无 */ void LED2_Reverse(void){ // 读取端口状态,根据状态取反 if(GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_2) == 0) GPIO_SetBits(GPIOA, GPIO_Pin_2); else GPIO_ResetBits(GPIOA, GPIO_Pin_2); }
串口配置serial.c
#include "stm32f10x.h" // Device header #include <stdio.h> #include <stdarg.h> char RxData[100]; // 接收数据包 uint8_t RxFlag; void Serial_Init(void){ // 开启时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); // 初始化引脚 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; // A9 发送数据 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 50Hz翻转速度 GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; // A10 接收数据 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 50Hz翻转速度 GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化串口配置 USART_InitTypeDef USART_InitStructure; USART_InitStructure.USART_BaudRate = 9600; // 串口波特率 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 不使用流控 USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; // 串口模式,发送+接收 USART_InitStructure.USART_Parity = USART_Parity_No; // 无校验 USART_InitStructure.USART_StopBits = USART_StopBits_1; // 选择一位停止位 USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 不需要校验位,八位字长 USART_Init(USART1,&USART_InitStructure); // 开启中断 USART_ITConfig(USART1,USART_IT_RXNE,ENABLE); //初始化NVIC NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 分组 NVIC_InitTypeDef NVIC_InitStructure; // 中断通道 NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; // 中断通道使能 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 抢占优先级 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 响应优先级 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_Init(&NVIC_InitStructure); // USART1使能 USART_Cmd(USART1,ENABLE); } // 发送函数 void USART_SendByte(uint8_t Byte){ USART_SendData(USART1,Byte); // 等待写入完成,写入完成之后会将标志位自动清0 while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET); } // 发送数组函数 void USART_SendArray(uint8_t *Array,uint16_t Length){ uint8_t i = 0; for(i=0;i<Length;i++){ USART_SendData(USART1,Array[i]); // 等待写入完成,写入完成之后会将标志位自动清0 while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET); } } // 发送字符串函数 void USART_SendString(uint8_t *String){ uint8_t i = 0; for(i=0;String[i]!='\0';i++){ USART_SendData(USART1,String[i]); // 等待写入完成,写入完成之后会将标志位自动清0 while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET); } } // 返回X的Y次方 uint32_t Serial_Pow(uint32_t X,uint32_t Y){ uint32_t Result = 1; while(Y--){ Result *= X; } return Result; } // 发送数字函数 void USART_SendNum(uint32_t Num,uint16_t Length){ uint8_t i = 0; for(i=0;i<Length;i++){ USART_SendByte(Num / Serial_Pow(10,Length-i-1) % 10 + 0x30); // 等待写入完成,写入完成之后会将标志位自动清0 while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET); } } //重定向fputc函数,fputc是printf函数的底层,printf通过不停的调用fputc来达到输出的效果 //重定向到串口 int fputc(int ch,FILE *f){ USART_SendByte(ch); return ch; } // 封装使用sprintf输出到串口 void Serial_Printf(char *format, ...) { char String[100]; va_list arg; // 可变参数列表 va_start(arg, format); // 从format开始接收可变参数 vsprintf(String, format, arg); va_end(arg); USART_SendString((uint8_t*)String); } //中断函数 void USART1_IRQHandler(void){ static uint8_t RxStatus = 0; // 数据包状态 static uint8_t RxCount = 0; if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET){ uint8_t RXDATA = USART_ReceiveData(USART1); // 获取接收到的数据 if(RxStatus == 0){ if(RXDATA == '@' && RxFlag == 0){ RxStatus = 1; RxCount = 0; // 初始化之前的数据长度 } }else if(RxStatus == 1){ if(RXDATA == '\r') RxStatus = 2; else{ RxData[RxCount] = RXDATA; RxCount++; } }else if(RxStatus == 2){ if(RXDATA == '\n'){ RxStatus = 0; RxFlag = 1; RxData[RxCount] = '\0'; // 加入字符串结束标志位 } } USART_ClearITPendingBit(USART1, USART_IT_RXNE); // 清除标志位 } }
主函数mian.c
#include "stm32f10x.h" // Device header #include "Delay.h" #include "OLED.h" #include "Serial.h" #include "LED.h" #include <string.h> int main(void) { OLED_Init(); Serial_Init(); LED_Init(); OLED_ShowString(1, 1, "TxData:"); OLED_ShowString(3, 1, "RxData:"); while (1) { if(RxFlag == 1){ OLED_ShowString(4,1," "); // 显示前擦除这行 OLED_ShowString(4,1,RxData); if(strcmp(RxData,"LED_ON")==0){ LED1_On(); USART_SendString((uint8_t*)"LED_ON_OK\r\n"); OLED_ShowString(2,1," "); OLED_ShowString(2,1,"LED_ON_OK"); } else if(strcmp(RxData,"LED_OFF")==0){ LED1_Off(); USART_SendString((uint8_t*)"LED_OFF_OK\r\n"); OLED_ShowString(2,1," "); OLED_ShowString(2,1,"LED_OFF_OK"); } else{ USART_SendString((uint8_t*)"ERR_CMD\r\n"); OLED_ShowString(2,1," "); OLED_ShowString(2,1,"ERR_CMD"); } RxFlag = 0; } } }
这里没有使用函数封装标志位自动清除,在许多读写操作进行时可能因为读取速度太慢导致数据接收错位,所以在主函数中手动把标志位清0,保证把每个数据包接收完毕后才可以开始下一次接收,同时把serial.c的包头判断时加入标志位判断即if(RXDATA == ‘@’ && RxFlag == 0)。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。