许多科创比赛中经常会有其他设备与STM32串口通讯的需求,比如可能需要Openmv / K210向STM32串口发送坐标的情况。下面我将介绍一种基于HAL库的串口DMA不定长数据收发和数据解读的方案。
(示例) 平台: STM32 Cube IDE
1.选择好芯片、配置好时钟和debug模式后,使能要用到的串口。
2.使能该串口的收发收发DMA:
3.使能串口全局中断,并生成工程文件。
本部分代码修改自xia0816大佬写的《真正实现了STM32 HAL串口不定长数据的接收发送功能(DMA方式,不用限定单次接收长度和添加结束标志)》
#include "UART_DMA.h"
#include
#include
#include
uint8_t RxBuffer[UART_RX_BUF_SIZE] = {
0};
uint8_t TxBuffer[UART_RX_BUF_SIZE] = {
0};
uint8_t sendCompleteSign = 1;
uint8_t TxLen = 0;
void DataProcess(void)
{
//在这里加入数据处理的函数
}
//到USARTx_IRQHandler中添加,如:
//void USART1_IRQHandler(void)
//{
// /* USER CODE BEGIN USART1_IRQn 0 */
// if(__HAL_UART_GET_FLAG(&USB_Huart,UART_FLAG_IDLE))
// {
// HAL_UART_IdleCallback(&USB_Huart);
// }
//
// /* USER CODE END USART1_IRQn 0 */
// HAL_UART_IRQHandler(&huartx);
//}
void HAL_UART_IdleCallback(UART_HandleTypeDef *huart)
{
__HAL_UART_CLEAR_IDLEFLAG(huart);
{
HAL_UART_DMAStop(huart);
ProcessData();
StartUartRxDMA();
}
}
void ProcessData()
{
uint32_t len = 0;
//得到已经接收了多少个字节 = 总共要接收的字节数 - ?NDTR F1为CNDTR F4为NDTR
#ifdef __STM32F1xx_HAL_H
len = UART_RX_BUF_SIZE - USB_Huart.hdmarx->Instance->CNDTR;
#define ProcessDataOK
#endif
#ifdef __STM32F4xx_HAL_H
len = UART_RX_BUF_SIZE - USB_Huart.hdmarx->Instance->NDTR;
#define ProcessDataOK
#endif
#ifndef ProcessDataOK
增加所用芯片的版本
#endif
if(len > 0)
{
if(sendCompleteSign == 1)
{
#if UART_RXTX_Switch
memset((void *)TxBuffer, 0, sizeof(TxBuffer));
memcpy(TxBuffer, RxBuffer, len);
TxLen = len;
StartUartTxDMA(); //串口回显
#endif
{
//在这里面加入数据处理的函数
DataProcess();
}
}
}
}
void USB_DMA_printf(const char *format,...)
{
uint32_t length;
va_list args;
va_start(args, format);
length = vsnprintf((char*)TxBuffer, sizeof(TxBuffer)+1, (char*)format, args);
va_end(args);
HAL_UART_Transmit_DMA(&USB_Huart,TxBuffer,length);
}
void USB_printf(const char *format,...)
{
uint32_t length;
va_list args;
va_start(args, format);
length = vsnprintf((char*)TxBuffer, sizeof(TxBuffer)+1, (char*)format, args);
va_end(args);
HAL_UART_Transmit(&USB_Huart,TxBuffer,length,0xFFFF);
}
/**
* @brief Tx Transfer completed callbacks.
* @param huart Pointer to a UART_HandleTypeDef structure that contains
* the configuration information for the specified UART module.
* @retval None
*/
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
/* Prevent unused argument(s) compilation warning */
// UNUSED(huart);
if(huart == &USB_Huart)
{
sendCompleteSign = 1;
}
/* NOTE: This function should not be modified, when the callback is needed,
the HAL_UART_TxCpltCallback could be implemented in the user file
*/
}
/**
* @brief Rx Transfer completed callbacks.
* @param huart Pointer to a UART_HandleTypeDef structure that contains
* the configuration information for the specified UART module.
* @retval None
*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
/* Prevent unused argument(s) compilation warning */
// UNUSED(huart);
if(huart == &USB_Huart)
{
ProcessData();
StartUartRxDMA();
}
/* NOTE: This function should not be modified, when the callback is needed,
the HAL_UART_RxCpltCallback could be implemented in the user file
*/
}
uint8_t UartTxData(UART_HandleTypeDef *huart, uint8_t *buf, const uint32_t len)
{
HAL_StatusTypeDef status;
uint8_t ret = 1;
if(sendCompleteSign == 0 || len == 0)
{
return 0;
}
sendCompleteSign = 0;
status = HAL_UART_Transmit_DMA(huart, (uint8_t*)buf, len);
if(HAL_OK != status)
{
ret = 0;
}
return ret;
}
//启动DMA发送
uint8_t StartUartTxDMA()
{
return UartTxData(&USB_Huart, TxBuffer, TxLen);
}
uint8_t UartRxData(UART_HandleTypeDef *huart, uint8_t *buf, const uint32_t len)
{
HAL_StatusTypeDef status;
uint8_t ret = 1;
status = HAL_UART_Receive_DMA(huart, (uint8_t*)buf, len);
if(HAL_OK != status)
{
ret = 0;
}
else
{
/* 开启空闲接收中断 */
__HAL_UART_ENABLE_IT(huart, UART_IT_IDLE);
}
return ret;
}
//启动DMA接收
uint8_t StartUartRxDMA()
{
return UartRxData(&USB_Huart, RxBuffer, UART_RX_BUF_SIZE);
}
void ProcessData()中可能需要视所用芯片情况作部分修改,目前只测试过STM32F103VET6和STM32F411CEU6
//得到已经接收了多少个字节 = 总共要接收的字节数 - ?NDTR F1为CNDTR F4为NDTR
#ifdef __STM32F1xx_HAL_H
len = UART_RX_BUF_SIZE - USB_Huart.hdmarx->Instance->CNDTR;
#define ProcessDataOK
#endif
#ifdef __STM32F4xx_HAL_H
len = UART_RX_BUF_SIZE - USB_Huart.hdmarx->Instance->NDTR;
#define ProcessDataOK
#endif
#ifndef ProcessDataOK
增加所用芯片的版本
#endif
#ifndef UART_DMA_UART_DMA_H_
#define UART_DMA_UART_DMA_H_
#include "main.h"
extern UART_HandleTypeDef huart1; //修改为所用串口
#define USB_Huart huart1 //修改为所用串口
#define UART_RX_BUF_SIZE 128
#define UART_RXTX_Switch 1 //串口回显开关
/*
要在Cube中开串口全局中断和收发DMA
*/
extern uint8_t RxBuffer[UART_RX_BUF_SIZE];
extern uint8_t TxBuffer[UART_RX_BUF_SIZE];
extern uint8_t TxLen;
void USB_DMA_printf(const char *format,...); //printf DMA方式
void USB_printf(const char *format,...); //printf 普通方式
uint8_t UartTxData(UART_HandleTypeDef *huart, uint8_t *buf, const uint32_t len);
uint8_t StartUartRxDMA(); //接收DMA初始化
uint8_t StartUartTxDMA(); //不需要自己调用
void ProcessData(); //在里面添加数据处理函数
void HAL_UART_IdleCallback(UART_HandleTypeDef *huart); //到USARTx_IRQHandler中添加
#endif /* UART_DMA_UART_DMA_H_ */
需要到stm32f1xx_it.c中的USARTx_IRQHandler添加几句话
//...
/* USER CODE BEGIN Includes */
#include "../UART_DMA/UART_DMA.h"
/* USER CODE END Includes */
//...
//...
/**
* @brief This function handles USART1 global interrupt.
*/
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
if(__HAL_UART_GET_FLAG(&USB_Huart,UART_FLAG_IDLE))
{
HAL_UART_IdleCallback(&USB_Huart);
}
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
//...
启动串口DMA接收
//...
/* USER CODE BEGIN 2 */
StartUartRxDMA();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
//...
进入debug跑起来,将接收区缓存RxBuffer加入 现场表达式
//...
uint8_t RxBuffer[UART_RX_BUF_SIZE] = {
0};
uint8_t TxBuffer[UART_RX_BUF_SIZE] = {
0};
uint8_t sendCompleteSign = 1;
uint8_t TxLen = 0;
//...
在ProcessData()中的该处打上断点。
打开串口调试助手,选择好参数后发送一段测试字符串,可以发现该字符串已成功存入缓冲区。
随后又成功将数据通过DMA回显
至此串口DMA收发已成功实现。
而源文件中附有的USB_DMA_printf()和USB_printf()分别为DMA方式的printf和普通的printf
void USB_DMA_printf(const char *format,...)
{
uint32_t length;
va_list args;
va_start(args, format);
length = vsnprintf((char*)TxBuffer, sizeof(TxBuffer)+1, (char*)format, args);
va_end(args);
HAL_UART_Transmit_DMA(&USB_Huart,TxBuffer,length);
}
void USB_printf(const char *format,...)
{
uint32_t length;
va_list args;
va_start(args, format);
length = vsnprintf((char*)TxBuffer, sizeof(TxBuffer)+1, (char*)format, args);
va_end(args);
HAL_UART_Transmit(&USB_Huart,TxBuffer,length,0xFFFF);
}
效果如下:
为了进一步处理数据,下面介绍字符串数字提取的方案。
实测double数据直接传参数据会出错,故采取了指针的方式。
/*
* NumAndStr.c
*
* Created on: Mar 15, 2021
* Author: 乙酸氧铍
*/
#include "../NumAndStr/NumAndStr.h"
#include
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);
}
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;
}
/*
* NumAndStr.h
*
* Created on: Mar 15, 2021
* Author: 乙酸氧铍
*/
#ifndef NUMANDSTR_NUMANDSTR_H_
#define NUMANDSTR_NUMANDSTR_H_
#include "main.h"
#define TempDoubleLen 18
#define TempIntLen 11
/*
str:数字字符串首地址
flag:分隔符
no:第no个数字 从1开始计
Output: 小数存放地址
*/
extern int32_t str2int(uint8_t * str, uint8_t flag, uint8_t no);
extern void str2double(uint8_t * str, uint8_t flag, uint8_t no, double * Output);
#endif /* NUMANDSTR_NUMANDSTR_H_ */
str:数字字符串首地址
flag:分隔符
no:第no个数字 从1开始计
Output: 小数存放地址
修改UART_DMA.c中的DataProcess()函数
#include "../NumAndStr/NumAndStr.h" //包含头文件
int32_t a,b,c;
double d,e,f;
void DataProcess(void)
{
//在这里加入数据处理的函数
a = str2int(RxBuffer, ' ', 1);
b = str2int(RxBuffer, ' ', 2);
c = str2int(RxBuffer, ' ', 3);
str2double(RxBuffer, ' ', 4, &d);
str2double(RxBuffer, ' ', 5, &e);
str2double(RxBuffer, ' ', 6, &f);
}
进入debug模式,监视变量a、b、c、d、e、f,使用串口调试助手再次发送一段测试字符串
效果如图所示:
可以看到六个数据都已成功存入对应的变量中,并成功回显。
且多次测试都能成功解读
(示例) 平台: MaixPy IDE、K210 Maix Bit
K210 串口测试程序
延时500ms时
import utime
from board import board_info
from Maix import freq
from fpioa_manager import fm
from machine import UART
import random
fm.register(9,fm.fpioa.UART1_TX)
fm.register(10,fm.fpioa.UART1_RX)
UART_USB = UART(UART.UART1, 115200, 8, None, 1, timeout = 1000, read_buf_len = 128)
while(True):
Tube_X = random.randint(-200,200)
Tube_Y = random.randint(-200,200)
Tube_Angle = random.random()
print('%d %d %f'%(Tube_X, Tube_Y, Tube_Angle))
UART_USB.write('%d %d %f\r\n'%(Tube_X, Tube_Y, Tube_Angle))
utime.sleep_ms(500)
修改UART_DMA.c中的DataProcess()函数
int32_t Tube_X = 0, Tube_Y = 0;
double Tube_Angle = 0;
void DataProcess(void)
{
//在这里加入数据处理的函数
Tube_X = str2int(RxBuffer, ' ', 1);
Tube_Y = str2int(RxBuffer, ' ', 2);
str2double(RxBuffer, ' ', 3, &Tube_Angle);
}
#...
while(True):
Tube_X = random.randint(-200,200)
Tube_Y = random.randint(-200,200)
Tube_Angle = random.random()
print('%d %d %f'%(Tube_X, Tube_Y, Tube_Angle))
UART_USB.write('%d %d %f\r\n'%(Tube_X, Tube_Y, Tube_Angle))
utime.sleep_ms(15)
#...
本文介绍了一种STM32 串口DMA收发并解读的方案,对CPU要求较小,只需自己选择分隔符号,不需要设计复杂的通信协议就能得到对应位置的数据,应该可以应用到使用STM32的多种科创比赛项目中去。