本文总结下串口通信的一些小技巧
首先我们还是先配置一个串口,一般正常情况下都默认是可以的
上面参数的意思:
那我们先来看下串口通信具体是什么样的,串口的数据包组成如下:
我们在来看下用串口发下数据到逻辑i分析仪里面读取的数据,相关说明我已经在图片中标出来了
这是我串口发送的数据
那我们再来看下波特率,这里我们选的波特率是115200,那就是1秒钟可以发送115200byte的数据,那一个数据的时间可以计算如下所示:
而我们可以在逻辑分析仪中测量下这个时间的准确性,可以看到是非常准确的(废话,这用的不是硬件串口吗,哈哈哈)
这样就可以帮我么更清楚的认识串口了!
通信过程中一般都是发送字符串偏多,因为字符串就是char类型的,可以直接匹配串口的数据,方便转化,所以一个常用的小工具就是数字转字符串了!
u32 Pow(u32 num,u8 count)
{
u8 i;
u32 result = 0;
if(count == 0)
{
num = 1;
return num;
}
else
{
result = num;
for(i = 0;i<count-1;i++)
{
result *= num;
}
}
return result;
}
void NumToString(u32 num,u8 numbit,u8 *string)
{
u8 i=0,z=0;
u32 temp = 0;
u8 changestart = 0;
u8 j = numbit-1;
for(i = 0;i<numbit;i++)
{
temp = num/Pow(10,j);
if(changestart)
{
string[z++] = temp+0x30;
}
if((temp>0)&&(changestart == 0))
{
string[z++] = temp+0x30;
changestart = 1;
}
temp = Pow(10,j);
num %= temp;
j--;
}
string[z] = 0;
}
那么如果是浮点数怎么办呢,就只能是先转为整数在处理了,就是先把他变为整数,再用这个函数来处理!
更好的办法还可以采用官方函数的方法,可以无视整数和浮点数的差异,该函数的用法如下所示,因为后面一个参数是格式化的数据,那%d就是整形数据,%f就是单精度浮点型了。
这也是比较常用的一种数据类型,有很多时候接收到的数据是以字符串的方式发过来的,我们要获取其中的整形数据,就可以用这个方法,flag是分隔符,比如一组数据13,23,23这样就是以,为分隔符了!
int32_t str2int(uint8_t * str, uint8_t flag, uint8_t no)
{
uint8_t No = 1;
uint8_t * Str = str;
uint8_t NumTemp[TempIntLen];
while(No!=no)
{
if(*Str == flag)
No++;
Str++;
}
No = 0;
while(*Str != flag && *Str != '\r' && *Str != '\n' && *Str != '\0' && No < (TempIntLen - 1))
{
NumTemp[No] = *Str;
Str++;
No++;
}
NumTemp[No] = '\0';
return atoi(NumTemp);
}
这个就是获取其中的浮点型数据了,flag是分隔符
void str2double(uint8_t * str, uint8_t flag, uint8_t no, double * Output)
{
uint8_t No = 1;
uint8_t * Str = str;
uint8_t NumTemp[TempDoubleLen];
uint8_t NumTemp_int[TempDoubleLen];
double OutputNum;
while(No!=no)
{
if(*Str == flag)
No++;
Str++;
}
No = 0;
while(*Str != flag && *Str != '\r' && *Str != '\n' && *Str != '\0' && No < (TempDoubleLen - 1))
{
NumTemp[No] = *Str;
Str++;
No++;
}
NumTemp[No] = '\0';
NumTemp[(TempDoubleLen - 1)] = 0;
No = 0;
while(NumTemp[NumTemp[(TempDoubleLen - 1)]] != '\0' && NumTemp[(TempDoubleLen - 1)] < (TempDoubleLen - 1))
{
if(NumTemp[NumTemp[(TempDoubleLen - 1)]] == '.')
{
NumTemp[(TempDoubleLen - 1)]++;
NumTemp_int[(TempDoubleLen - 1)] = NumTemp[(TempDoubleLen - 1)];
}
NumTemp_int[No] = NumTemp[NumTemp[(TempDoubleLen - 1)]];
No++;
NumTemp[(TempDoubleLen - 1)]++;
}
NumTemp_int[No]='\0';
NumTemp[(TempDoubleLen - 1)] = NumTemp_int[(TempDoubleLen - 1)]++;
OutputNum = (double)atoi(NumTemp_int);
while(NumTemp[NumTemp[(TempDoubleLen - 1)]] != '\0')
{
OutputNum /= 10;
NumTemp[(TempDoubleLen - 1)] ++;
}
*Output = OutputNum;
}
以上四种转换方法的测试如下所示:
int num=1253423456;
u8 str[20]= {0};
char str1[]="112,233,41";
char str2[]="112.32,233.34,343.45";
double dat[3];
int main(void)
{
NumToString(num,10,str);
printf("%s\n",str);
printf("%d\n%d\n%d\n",str2int(str1,',',1),str2int(str1,',',2),str2int(str1,',',3));
str2double(str2,',',1,dat);
str2double(str2,',',2,dat+1);
str2double(str2,',',3,dat+2);
printf("%f\n%f\n%f",dat[0],dat[1],dat[2]);
}
在需要接收大量数据的情况下这个是比较常用的,比如解析物联网开发时候云下发的json命令,一般数据梁都比较大而且不是固定长度的数据,这个时候这个东西就变得很有必要了
开启串口,并设置参数,一般情况默认即可
配置中断和DMA
配置DMA发送和接收函数模式
初始化中开启串口空闲中断
对中断函数进行处理
空闲中断处理函数如下所示
源代码如下:
UART_DMA.c
/*
* UART_DMA.c
*
* Created on: Mar 18, 2022
* Author: LX
*/
#include "UART_DMA.h"
#include "string.h"
#include "stdarg.h"
#include "stdio.h"
#define USART_DMA_TX_BUFFER_MAXIMUM 128 // DMA缓冲区大小
#define USART_DMA_RX_BUFFER_MAXIMUM 128 // DMA缓冲区大小
extern DMA_HandleTypeDef hdma_usart1_rx;
extern DMA_HandleTypeDef hdma_usart1_tx;
extern UART_HandleTypeDef huart1; //UART句柄
uint8_t usart1_rx_buffer[USART_DMA_RX_BUFFER_MAXIMUM]; //串口1的DMA接收缓冲区
uint8_t usart1_tx_buffer[USART_DMA_TX_BUFFER_MAXIMUM]; //串口1的DMA发送缓冲区
uint16_t usart1_rx_len; //DMA一次空闲中断接收到的数据长度
uint8_t receive_data[USART_DMA_RX_BUFFER_MAXIMUM]; //DMA接收数据缓存区
void HAL_UART_ReceiveIdle(UART_HandleTypeDef *huart)
{
//当触发了串口空闲中断
if((__HAL_UART_GET_FLAG(huart, UART_FLAG_IDLE) != RESET))
{
if(huart->Instance == USART1)
{
__HAL_UART_CLEAR_IDLEFLAG(huart);
HAL_UART_DMAStop(huart);
usart1_rx_len = USART_DMA_RX_BUFFER_MAXIMUM - (__HAL_DMA_GET_COUNTER(&hdma_usart1_rx));
memcpy(receive_data, usart1_rx_buffer, USART_DMA_RX_BUFFER_MAXIMUM);
memset(usart1_rx_buffer,0,128);
while (HAL_UART_Receive_DMA(&huart1,(uint8_t *)usart1_rx_buffer, USART_DMA_RX_BUFFER_MAXIMUM)!=HAL_OK);
// Debug_printf("%s",receive_data); // 是否添加回环检测函数
}
//下面添加其他串口的处理函数
}
}
void UART1_TX_DMA_Send(uint8_t *buffer, uint16_t length)
{
while(HAL_DMA_GetState(&hdma_usart1_tx) != HAL_DMA_STATE_READY);
//while(__HAL_DMA_GET_COUNTER(&hdma_usart1_tx));
__HAL_DMA_DISABLE(&hdma_usart1_tx);
HAL_UART_Transmit_DMA(&huart1, buffer, length);
}
void Debug_printf(const char *format, ...)
{
uint32_t length = 0;
va_list args;
va_start(args, format);
length = vsnprintf((char *)usart1_tx_buffer, sizeof(usart1_tx_buffer), (char *)format, args);
UART1_TX_DMA_Send(usart1_tx_buffer, length);
}
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1)
{
__HAL_DMA_CLEAR_FLAG(&hdma_usart1_tx, DMA_FLAG_TC4);
// HAL_UART_DMAStop(&huart1);
}
}
UART_DMA.h
#include "main.h"
void HAL_UART_ReceiveIdle(UART_HandleTypeDef *huart);
void UART1_TX_DMA_Send(uint8_t *buffer, uint16_t length);
void Debug_printf(const char *format, ...);
下面可以对数据接收进行暴力测试:
在串口助手中设置高频率的大量数据进行发送,并在单片机端设置数据回传,测试结果如下:
正常情况下回环接收
周期加大会开始出现错误
经过测试我得到的结论大概如下: