串口数据交换简介:
串口在接收数据时,就涉及到,数据长度的问题,如果双方约定好数据的长度,那么只要开一个固定大小的数据缓冲区就可以了,要是一方不清楚数据的长度,这就需要用到不定长数据的接收了。
以下是个人了解的三种不定长数据接收的方案
1、固定帧头帧尾,然后采用普通串口中断接收的方式接收并判断数据是否接收结束。
2、使用软件超时判断,例如只要超过10ms没接收到数据,就认为这一帧数据接收完毕。
3、使用串口+dma空闲中断,相较于前两种方案呢,这个方式就是用硬件超时判断,不需要软件判断。这种方式相较于前两种效率更高。
数据包编码发送:
双方约定好以固定的数据包格式
例如下面代码采用的格式<name1:123,name2:-245,name3:1.45,>其中‘<’、‘>’为数据包的帧头和帧尾,name表示一个数据的名称冒号和‘,’之间表示的是该数据值。
数据包数据解析:
当上位机发送一串数据包后,单片机可以提取其中一个所需要用到的数据,那么只需要在数据包中查找对应的名称,再提取出名称后面跟着的数据值即可。
例如name1:123,使用strstr()函数查找子字符串,在保存下该字符串后面‘:’和‘,’之间的数据再通过atof()转化为浮点型的格式返回。
程序:
方案一程序:
bluetooth.c文件
#include "Bluetooth.h"
#include "usart.h"
#include "string.h"
#include <stdlib.h>
#include <stdbool.h>
#include "OLED.h"
/***********************/
/* 宏 */
/***********************/
/*==================================*/
#define Usart huart6
#define USART_ USART6
#define buffersize 100
/*==================================*/
uint8_t message = NULL;
char message_char[buffersize] = {NULL};
char *message_flag = &message_char[0];
bool send_state = false;
/**
* @brief 蓝牙初始化
* @param 无
* @retval 无
*/
void Bluetooth_Init(void)
{
HAL_UART_Receive_IT(&Usart, &message, 1);
}
/**
* @brief 串口中断函数
* @param 串口
* @retval 无
*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART_)
{
if((char)message == '<')
{
message_flag = &message_char[0];
*message_flag = (char)message;
message_flag++;
send_state = 0;
}
else if((char)message == '>')
{
*message_flag = (char)message;
send_state = 1;
}
else
{
*message_flag = (char)message;
message_flag++;
if(message_flag == &message_char[buffersize])message_flag = &message_char[0];
}
HAL_UART_Receive_IT(&Usart, &message, 1);
}
}
/**
* @brief 数据提取函数
* @param 总数据包
* @param 数据包中要解析的数据名
* @retval 数据值
*/
float String_FindNum(char main_string[], char child_string[])
{
char *p = strstr(main_string, child_string);
char num_char[10];
if(p == NULL)return CantFind;
else
{
p = p + strlen(child_string) + 1;
for(uint8_t i = 0; i<10; i++)
{
if(*p != ',')
{
num_char[i] = *p;
p++;
}
else
{
break;
}
}
return atof(num_char);
}
}
/**
* @brief 获取消息
* @param 无
* @retval 消息地址
*/
char* GetMessage(void)
{
return message_char;
}
/**
* @brief 获取读取消息状态
* @param 无
* @retval 是否读取结束
*/
bool GetSendState(void)
{
return send_state;
}
/**
* @brief 设置读取消息状态
* @param 是否读取
* @retval 无
*/
void SetSendState(bool state)
{
send_state = state;
}
bluetooth.h文件
#ifndef __Bluetooth_H
#define __Bluetooth_H
#include "usart.h"
#include <stdbool.h>
#define CantFind 2147483647
void Bluetooth_Init(void);
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
float String_FindNum(char main_string[], char child_string[]);
char* GetMessage(void);
bool GetSendState(void);
void SetSendState(bool state);
#endif
具体使用方法:
/*蓝牙调节PID参数*/
void Bluetooth_Operate(void)
{
if(GetSendState() == true)//判断数据是否发送完毕
{
float data = String_FindNum(GetMessage(),"P");//提取P参数
if(data != CantFind)LeftSpeedPID.Kp = data; //设置P参数
data = String_FindNum(GetMessage(),"I");
if(data != CantFind)LeftSpeedPID.Ki = data;
data = String_FindNum(GetMessage(),"D");
if(data != CantFind)LeftSpeedPID.Kd = data;
SetSendState(false);//此句用于避免一帧数据多次修改,使一次数据帧仅执行一次读取操作
}
}
其中CantFind为定义的一个数值,当没查找到该名称时返回该数值。
方案三程序:
MyUsartAgreement.c文件
#include "MyUsartAgreement.h"
#include "usart.h"
#include "string.h"
#include <stdlib.h>
#include <stdbool.h>
#include "NoBlockATCommand.h"
uint8_t message_buffer[buffersize];
char message_charbuffer[buffersize] = {NULL};
bool send_state = false;
/**
* @brief 串口初始化
* @param 无
* @param 无
* @retval 无
*/
void UsartReceive_Init(void)
{
HAL_UARTEx_ReceiveToIdle_DMA(&Usart, message_buffer, buffersize);
}
/**
* @brief 串口IDLE中断回调
* @param 无
* @param 无
* @retval 无
*/
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
if(huart->Instance == USART_)
{
for(uint16_t i = 0; i < Size; i++)
{
message_charbuffer[i] = message_buffer[i];
}
SetSendState(true);
HAL_UARTEx_ReceiveToIdle_DMA(&Usart, message_buffer, buffersize);
}
}
//串口接收错误中断回调(上电时会误触发串口中断,报OVER RUN错误,此时会关闭中断,需要在错误中断中重新启动中断)
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART_)
{
HAL_UARTEx_ReceiveToIdle_DMA(&Usart, message_buffer, buffersize);
}
}
/**
* @brief 数据提取函数
* @param 总数据包
* @param 数据包中要解析的数据名
* @retval 数据值
*/
float String_FindNum(char main_string[], char child_string[])
{
char *p = strstr(main_string, child_string);
char num_char[10];
if(p == NULL)return CantFind;
else
{
p = p + strlen(child_string) + 1;
for(uint8_t i = 0; i<10; i++)
{
if(*p != ',')
{
num_char[i] = *p;
p++;
}
else
{
break;
}
}
return atof(num_char);
}
}
/**
* @brief 获取消息
* @param 无
* @retval 消息地址
*/
char* GetMessage(void)
{
return message_charbuffer;
}
/**
* @brief 获取读取消息状态
* @param 无
* @retval 是否读取结束
*/
bool GetSendState(void)
{
return send_state;
}
/**
* @brief 设置读取消息状态
* @param 是否读取
* @retval 无
*/
void SetSendState(bool state)
{
send_state = state;
}
MyUsartAgreement.h文件
#ifndef __MyUsartAgreement_H
#define __MyUsartAgreement_H
#include "usart.h"
#include <stdbool.h>
#define CantFind 2147483647
/***********************/
/* 宏 */
/***********************/
/*==================================*/
#define Usart huart1
#define USART_ USART1
#define buffersize 100
/*==================================*/
void UsartReceive_Init(void);
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
float String_FindNum(char main_string[], char child_string[]);
char* GetMessage(void);
bool GetSendState(void);
void SetSendState(bool state);
#endif
该方案需开启串口接收DMA
VOFA上位机
该上位机提供许多控件,方便通过操作图形化界面中的控件实现数据的修改,并且可以直观的打印数据波形。
上位机数据格式:
单片机方发送至上位机时需使用以下格式。
上位机发送命令:
命令的作用是为控件赋予指令使得操控控件时可以以该命令的方式发送,新增命令右键编辑命令发送的内容 。
常用控件:
波形控件:右键波形控件添加x轴和y轴表示的数据
要使用prinf函数向上位机打印需重定向
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart6, (uint8_t *)&ch, 1, 0xffff);
return ch;
}
记得包含头文件#include <stdio.h>和勾选下列框内选项
滑条控件:右键控件绑定原先设置的命令
可以拖动滑条调参,也可以使用同时发送并修改多个数据