基于HAL库的STM32串口DMA环形缓冲收发实例
首先在此感谢开源项目,以及大佬们的无私奉献,让每一个逐梦人能够免费学习,再次感谢!
发布只为记录,记性不够,笔记来凑。记得点赞哦
具体实现原理讲起来确实挺复杂,不过用起来还是很NICE的!可以直接移植!
1、STM32CubeMax配置
1.1、选择单片机型号
2、配置时钟和串口
或者直接在HCLK位置输入72,点击OK自动配置
这个地方第四步,模式选择MODE。发送选择正常NOMAL.接收RX选择循环模式,第五步,外设地址不自增,存储器地址自增勾选
数字长度选择字节模式byte
此处必须使能UART,原因后面会提到
然后点击生成文件就行。如果用的keil,则直接点击Open Project,如果用的VSCODE,选择打开文件夹Open folder
到这个页面后点击MDK_ARM,就是生成的文件
然后返回上一层,右击,选择打开方式,为通过code打开,
右击,选择新建文件夹
新建文件夹BSP 和bsp_usart.h和bsp_usart.c
将文件夹拖到目录栏
将刚才的两个文件夹路径放进来
至此,所有的构建操作已经全部完成,剩下的就是程序编辑
为了方便打印调试,引用printf打印输出,引入fputc,添加头文件#include “stdio.h”
#include "stdio.h"
int fputc(int ch, FILE *f)
{
// while((USART1->SR&0X40)==0);//循环发送,直到发送完毕
// USART1->DR = (unsigned char) ch;
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 100);
return ch;
}
huart1结构体句柄由系统自动生成
保姆级的提示到这一步结束,要是不会用就直接调用吧,下来可能就是关键步骤,不会这么详细的讲,至于如何使用这些引入的函数,点击这个链接
定义数组,用来存放发送的数据
#define UART1_RX_RB_LEN (128u)
uint8_t usart1_tx_rb_data[128];
uint8_t usart1_rx_rb_data[UART1_RX_RB_LEN];
绑定结构体指针BUFF和定义的实体数组
lwrb_init(&usart1_tx_rb, usart1_tx_rb_data, ARRAY_LEN(usart1_tx_rb_data));
lwrb_init(&usart1_rx_rb, usart1_rx_rb_data, ARRAY_LEN(usart1_rx_rb_data));
2、发送环节
为了移植方便,使底层逻辑不暴露,引入发送函数,方便外部调用
void USART1_SendData(const uint8_t *p_data, uint16_t len)
{
lwrb_write(&usart1_tx_rb, p_data, len); /* Write data to TX buffer for loopback */
USART1_Start_DmaTx();
}
void USART1_Sendstring(const char *str)
{
lwrb_write(&usart1_tx_rb, str, strlen(str)); /* Write data to TX buffer for loopback */
USART1_Start_DmaTx();
}
真正的发送函数处理
uint8_t USART1_Start_DmaTx(void)
{
uint32_t primask;
uint8_t started = 0;
primask = __get_PRIMASK();
__disable_irq();
if (usart1_tx_dma_current_len == 0
&& (usart1_tx_dma_current_len = lwrb_get_linear_block_read_length(&usart1_tx_rb)) > 0)
{
__HAL_DMA_DISABLE(&hdma_usart1_tx);
__HAL_DMA_CLEAR_FLAG(&hdma_usart1_tx
, __HAL_DMA_GET_TC_FLAG_INDEX(&hdma_usart1_tx));
__HAL_DMA_CLEAR_FLAG(&hdma_usart1_tx
, __HAL_DMA_GET_HT_FLAG_INDEX(&hdma_usart1_tx));
__HAL_DMA_CLEAR_FLAG(&hdma_usart1_tx
, __HAL_DMA_GET_TE_FLAG_INDEX(&hdma_usart1_tx));
__HAL_DMA_CLEAR_FLAG(&hdma_usart1_tx
, __HAL_DMA_GET_GI_FLAG_INDEX(&hdma_usart1_tx));
HAL_UART_Transmit_DMA(&huart1
, (uint8_t*)lwrb_get_linear_block_read_address(&usart1_tx_rb)
, (uint16_t)usart1_tx_dma_current_len);
started = 1;
}
__set_PRIMASK(primask);
return started;
}
发送完成后触发中断,跳过当前已经有的长度,然后再次进入发送状态,判断缓冲区中是否还存在数据,如果有进入IF语句,如果没有,返回started = 0;此时发送形成一个循环发送和检测
void USART1_TxTcCb(UART_HandleTypeDef *huart)
{
lwrb_skip(&usart1_tx_rb, usart1_tx_dma_current_len);/* Skip sent data, mark as read */
usart1_tx_dma_current_len = 0; /* Clear length variable */
USART1_Start_DmaTx(); /* Start sending more data */
}
3、接收环节
HAL_UARTEx_ReceiveToIdle_DMA(&huart1, usart1_rx_dma_buffer, ARRAY_LEN(usart1_rx_dma_buffer));
利用此函数可以接受数据,直至触发IDLE空闲中断,具体细节点击函数查看,不在此赘述
此时会产生三种情况,接受一半中断。接受溢出中断和接收完成后空闲中断
如果自己注册回调函数则,会调用自己的回调函数,不然则为系统默认回调函数
不会的可以参考此篇注册串口回调函数
//注册回调事件
HAL_UART_RegisterRxEventCallback(&huart1, USART1_RxEventCb);
HAL_UART_RegisterCallback(&huart1, HAL_UART_TX_COMPLETE_CB_ID, USART1_TxTcCb);
产生回调后,进入回调函数,可以自定义一些回调处理和动作
//接受半个缓冲区完成回调函数
void USART1_DMA_RxHtCb( DMA_HandleTypeDef * p_hdma)
{
UNUSED(p_hdma);
USART1_RxEventCheck();
}
//接收完成回调函数
void USART1_DMA_RxTcCb( DMA_HandleTypeDef * p_hdma)
{
UNUSED(p_hdma);
USART1_RxEventCheck();
usart1_rx_flag = 1;
}
/*!< UART Reception Event Callback */
void USART1_RxEventCb(struct __UART_HandleTypeDef *huart, uint16_t pos)
{
UNUSED(huart);
UNUSED(pos);
USART1_RxEventCheck();
}
对回调事件进行检查分析,看是那种情况,具体参考大佬文章
共有这么几种情况:
-
情况A:缓冲区为空
W == R = 0 == 0
-
情况B:缓冲区将字节保存为
W - R = 4 - 0 = 4``W > R
-
Case C : 缓冲区已满or or
W == R - 1``7 == 0 - 1``7 = (0 - 1 + S) % S = (0 - 1 + 8) % 8 = (-1 + 8) % 8 = 7
-
R
并且W
可以保存S
不同的值,从0
到,即模数S - 1``S
- 缓冲区将字节保存为
W - R = 7 - 0 = 7``W > R
-
-
情况D:缓冲区将字节保存为
S - (R - W) = 8 - (5 - 3) = 6``R > W
-
情况E:缓冲区已满为( ) 并保存字节
W == R - 1``4 = 5 - 1``S - (R - W) = 8 - (5 - 4) ) = 7
}
void USART1_RxEventCheck(void)
{
static uint16_t old_pos;
uint16_t pos;
/* Calculate current position in buffer and check for new data available */
//检查缓冲区中已用长度
pos = ARRAY_LEN(usart1_rx_dma_buffer) - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
if (pos != old_pos)
{ /* Check change in received data */
if (pos > old_pos)
{ /* Current position is over previous one */
/*
* Processing is done in "linear" mode.
*
* Application processing is fast with single data block,
* length is simply calculated by subtracting pointers
*
* [ 0 ]
* [ 1 ] <- old_pos |------------------------------------|
* [ 2 ] | |
* [ 3 ] | Single block (len = pos - old_pos) |
* [ 4 ] | |
* [ 5 ] |------------------------------------|
* [ 6 ] <- pos
* [ 7 ]
* [ N - 1 ]
*/
lwrb_write(&usart1_rx_rb, &usart1_rx_dma_buffer[old_pos], pos - old_pos);
}
else
{
/*
* Processing is done in "overflow" mode..
*
* Application must process data twice,
* since there are 2 linear memory blocks to handle
*
* [ 0 ] |---------------------------------|
* [ 1 ] | Second block (len = pos) |
* [ 2 ] |---------------------------------|
* [ 3 ] <- pos
* [ 4 ] <- old_pos |---------------------------------|
* [ 5 ] | |
* [ 6 ] | First block (len = N - old_pos) |
* [ 7 ] | |
* [ N - 1 ] |---------------------------------|
*/
lwrb_write(&usart1_rx_rb, &usart1_rx_dma_buffer[old_pos], ARRAY_LEN(usart1_rx_dma_buffer) - old_pos);
if (pos > 0)
{
lwrb_write(&usart1_rx_rb, &usart1_rx_dma_buffer[0], pos);
}
}
old_pos = pos; /* Save current position as old for next transfers */
usart1_rx_flag = 1;
}
}
当标志位置一后,对读取到的数据进行处理,此时传进来的数据需要我们自行进行拼接,比如上述情况E,发送的数据存到了两端,这样需要先将后面的读出来,再加上前面的
void USART1_ProcessData(void)
{
uint8_t data[UART1_RX_RB_LEN];
uint16_t len1 = lwrb_get_linear_block_read_length(&usart1_rx_rb);
printf("len1 = %d\r\n", len1);
if (len1 > 0)
{
printf("lwrb_read(&usart1_tx_rb, data, len1) = %d\r\n"
, lwrb_read(&usart1_rx_rb, data, len1));
uint16_t len2 = lwrb_get_linear_block_read_length(&usart1_rx_rb);
printf("len2 = %d\r\n", len2);
if (len2 > 0)
{
printf("lwrb_read(&usart1_tx_rb, &data[len1], len2) = %d\r\n"
, lwrb_read(&usart1_rx_rb, &data[len1], len2));
}
// 进行data进行处理
// 处理usart1_rx_rb的一些东西
lwrb_write(&usart1_tx_rb
, data
, len1 + len2); /* Write data to TX buffer for loopback */
USART1_Start_DmaTx();
}
}
再main函数中对标志位进行处理,
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
HAL_Delay(50);
USART1_Init();
printf("stm32f103rct6_uart_dma_loopback_test!\r\n");
// uint8_t str[] = "USART1_Init\r\n";
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
if (usart1_rx_flag == 1)
{
usart1_rx_flag = 0;
// printf("usart1_rx_flag = 1\r\n");
USART1_ProcessData();
}
HAL_Delay(1000);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
下面附上BSP_USART.c完整代码,
#include "bsp_usart.h"
#include "usart.h"
#include "dma.h"
#define UART1_RX_DMA_BUFFER_LEN (20u)
#define UART1_RX_RB_LEN (128u)
lwrb_t usart1_rx_rb;// Ring buffer instance for TX data
lwrb_t usart1_tx_rb;// Ring buffer instance for TX data
uint8_t usart1_rx_dma_buffer[UART1_RX_DMA_BUFFER_LEN];
uint8_t usart1_rx_rb_data[UART1_RX_RB_LEN];// Ring buffer data array for RX DMA
uint8_t usart1_tx_rb_data[128];// Ring buffer data array for TX DMA
volatile size_t usart1_tx_dma_current_len;// Length of currently active TX DMA transfer
volatile uint8_t usart1_rx_flag;
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 100);
return ch;
}
uint8_t USART1_Start_DmaTx(void)
{
uint32_t primask;
uint8_t started = 0;
primask = __get_PRIMASK();
__disable_irq();
if (usart1_tx_dma_current_len == 0
&& (usart1_tx_dma_current_len = lwrb_get_linear_block_read_length(&usart1_tx_rb)) > 0)
{
__HAL_DMA_DISABLE(&hdma_usart1_tx);
__HAL_DMA_CLEAR_FLAG(&hdma_usart1_tx
, __HAL_DMA_GET_TC_FLAG_INDEX(&hdma_usart1_tx));
__HAL_DMA_CLEAR_FLAG(&hdma_usart1_tx
, __HAL_DMA_GET_HT_FLAG_INDEX(&hdma_usart1_tx));
__HAL_DMA_CLEAR_FLAG(&hdma_usart1_tx
, __HAL_DMA_GET_TE_FLAG_INDEX(&hdma_usart1_tx));
__HAL_DMA_CLEAR_FLAG(&hdma_usart1_tx
, __HAL_DMA_GET_GI_FLAG_INDEX(&hdma_usart1_tx));
HAL_UART_Transmit_DMA(&huart1
, (uint8_t*)lwrb_get_linear_block_read_address(&usart1_tx_rb)
, (uint16_t)usart1_tx_dma_current_len);
started = 1;
}
__set_PRIMASK(primask);
return started;
}
void USART1_DMA_RxHtCb( DMA_HandleTypeDef * p_hdma)
{
UNUSED(p_hdma);
USART1_RxEventCheck();
}
//接收完成回调函数
void USART1_DMA_RxTcCb( DMA_HandleTypeDef * p_hdma)
{
UNUSED(p_hdma);
USART1_RxEventCheck();
usart1_rx_flag = 1;
}
void USART1_RxEventCb(struct __UART_HandleTypeDef *huart, uint16_t pos)
{
UNUSED(huart);
UNUSED(pos);
USART1_RxEventCheck();
}
// 串口1发送完成回调
void USART1_TxTcCb(UART_HandleTypeDef *huart)
{
lwrb_skip(&usart1_tx_rb, usart1_tx_dma_current_len); /* Skip sent data, mark as read */
usart1_tx_dma_current_len = 0; /* Clear length variable */
USART1_Start_DmaTx(); /* Start sending more data */
}
void USART1_Init(void)
{
lwrb_init(&usart1_tx_rb, usart1_tx_rb_data, ARRAY_LEN(usart1_tx_rb_data));
lwrb_init(&usart1_rx_rb, usart1_rx_rb_data, ARRAY_LEN(usart1_rx_rb_data));
HAL_UARTEx_ReceiveToIdle_DMA(&huart1, usart1_rx_dma_buffer, ARRAY_LEN(usart1_rx_dma_buffer));
HAL_UART_RegisterRxEventCallback(&huart1, USART1_RxEventCb);
HAL_UART_RegisterCallback(&huart1, HAL_UART_TX_COMPLETE_CB_ID, USART1_TxTcCb);
}
// void USART1_RxEventCheck(uint16_t pos)
void USART1_RxEventCheck(void)
{
static uint16_t old_pos;
uint16_t pos;
/* Calculate current position in buffer and check for new data available */
//检查缓冲区中已用长度
pos = ARRAY_LEN(usart1_rx_dma_buffer) - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
// printf("old_pos = %d, pos = %d\r\n", old_pos, pos);
if (pos != old_pos)
{ /* Check change in received data */
if (pos > old_pos)
{ /* Current position is over previous one */
/*
* Processing is done in "linear" mode.
*
* Application processing is fast with single data block,
* length is simply calculated by subtracting pointers
*
* [ 0 ]
* [ 1 ] <- old_pos |------------------------------------|
* [ 2 ] | |
* [ 3 ] | Single block (len = pos - old_pos) |
* [ 4 ] | |
* [ 5 ] |------------------------------------|
* [ 6 ] <- pos
* [ 7 ]
* [ N - 1 ]
*/
lwrb_write(&usart1_rx_rb, &usart1_rx_dma_buffer[old_pos], pos - old_pos);
}
else
{
/*
* Processing is done in "overflow" mode..
*
* Application must process data twice,
* since there are 2 linear memory blocks to handle
*
* [ 0 ] |---------------------------------|
* [ 1 ] | Second block (len = pos) |
* [ 2 ] |---------------------------------|
* [ 3 ] <- pos
* [ 4 ] <- old_pos |---------------------------------|
* [ 5 ] | |
* [ 6 ] | First block (len = N - old_pos) |
* [ 7 ] | |
* [ N - 1 ] |---------------------------------|
*/
lwrb_write(&usart1_rx_rb, &usart1_rx_dma_buffer[old_pos], ARRAY_LEN(usart1_rx_dma_buffer) - old_pos);
if (pos > 0)
{
lwrb_write(&usart1_rx_rb, &usart1_rx_dma_buffer[0], pos);
}
}
old_pos = pos; /* Save current position as old for next transfers */
usart1_rx_flag = 1;
}
}
void USART1_ProcessData(void)
{
uint8_t data[UART1_RX_RB_LEN];
uint16_t len1 = lwrb_get_linear_block_read_length(&usart1_rx_rb);
printf("len1 = %d\r\n", len1);
if (len1 > 0)
{
printf("lwrb_read(&usart1_tx_rb, data, len1) = %d\r\n"
, lwrb_read(&usart1_rx_rb, data, len1));
uint16_t len2 = lwrb_get_linear_block_read_length(&usart1_rx_rb);
printf("len2 = %d\r\n", len2);
if (len2 > 0)
{
printf("lwrb_read(&usart1_tx_rb, &data[len1], len2) = %d\r\n"
, lwrb_read(&usart1_rx_rb, &data[len1], len2));
}
// 进行data进行处理
// 处理usart1_rx_rb的一些东西
lwrb_write(&usart1_tx_rb
, data
, len1 + len2); /* Write data to TX buffer for loopback */
USART1_Start_DmaTx();
}
}
void USART1_SendData(const uint8_t *p_data, uint16_t len)
{
lwrb_write(&usart1_tx_rb, p_data, len); /* Write data to TX buffer for loopback */
USART1_Start_DmaTx();
}
void USART1_Sendstring(const char *str)
{
lwrb_write(&usart1_tx_rb, str, strlen(str)); /* Write data to TX buffer for loopback */
USART1_Start_DmaTx();
}
BSP_USART_H文件文章来源:https://www.toymoban.com/news/detail-693584.html
#ifndef __BSP_USART_H__
#define __BSP_USART_H__
#include "main.h"
#include "stdio.h"
#include "lwrb.h"
#define ARRAY_LEN(x) (sizeof(x) / sizeof((x)[0]))
extern UART_HandleTypeDef huart1;
extern DMA_HandleTypeDef hdma_usart1_rx;
extern DMA_HandleTypeDef hdma_usart1_tx;
extern volatile size_t usart1_tx_dma_current_len;
extern volatile uint8_t usart1_rx_flag;
void USART1_Init(void);
void USART1_ProcessData(void);
void USART1_SendData(const uint8_t *p_data, uint16_t len);
void USART1_Sendstring(const char* str);
#endif // ! __BSP_USART_H__
lwrb文件链接文章来源地址https://www.toymoban.com/news/detail-693584.html
#ifndef __BSP_USART_H__
#define __BSP_USART_H__
#include "main.h"
#include "stdio.h"
#include "lwrb.h"
#define ARRAY_LEN(x) (sizeof(x) / sizeof((x)[0]))
extern UART_HandleTypeDef huart1;
extern DMA_HandleTypeDef hdma_usart1_rx;
extern DMA_HandleTypeDef hdma_usart1_tx;
extern volatile size_t usart1_tx_dma_current_len;
extern volatile uint8_t usart1_rx_flag;
void USART1_Init(void);
void USART1_ProcessData(void);
void USART1_SendData(const uint8_t *p_data, uint16_t len);
void USART1_Sendstring(const char* str);
#endif // ! __BSP_USART_H__
lwrb文件[链接](https://github.com/MaJerle/lwrb)
git[下载链接](https://github.com/MaJerle/lwrb.git)
到了这里,关于stm32串口+DMA环形缓冲收发保姆级的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!