IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> 串口DMA发送&接收不定长数据--易移植和修改的C语言代码 -> 正文阅读

[嵌入式]串口DMA发送&接收不定长数据--易移植和修改的C语言代码

一、前言

实际项目应用中,对于串口通信速率很快的接收,在不知道多少字节为一帧数据时,处理不好会存在数据的丢失。对于一次性要发送很长的数据,一直让CPU去处理,会造成其他功能处理进入瘫痪。针对这类场景,编写了一份便于移植的应用层代码,对串口数据的收发,提供一个处理框架。

二、设计概述

这套源码属于应用层,驱动层需根据不同平台来实现,后面以stm32 + cubemx来实现驱动层。整个应用层有4个文件,分别是dev_uart.c、dev_uart.h、dataqueue.c和dataqueue.h。可实现多个串口的设备注册和对应帧数据的读取。

2.1 DMA接收不定长数据,完成分帧概述

需要对应芯片的串口和DMA,支持以下功能

  • DMA循环模式(普通模式和循环模式区别参考文末链接2)
  • DMA半传输中断
  • DMA完成传输中断
  • 串口空闲中断

2.2 DMA发送固定长度数据概述

  • DMA普通模式
  • 每次发送调用官方库函数DMA发送

三、功能效果

3.1 DMA方式接收不定长数据,然后通过DMA发送回显

可以看到截图有如下结果。

  • 发送的字节数等于接收的字节数
  • 1ms定时连续发送了2次不同的数据(后面数据多了个6),回显结果对比没有出现错误
  • 不定长的数据接收和数据的fifo处理正常(总fifo设置为250字节,多次快速读写后正常)

在这里插入图片描述
在这里插入图片描述

3.2 DMA方式同时进行发送和接收

设计逻辑,接收到不定长数据后,统计接收长度。每10ms通过DMA发送10字节数据,发送10次后,回显接收到的长度和已发送的长度。
下图结果说明

  • DMA发送的过程中,不会干扰DMA数据的接收,即不需要考虑发送和接收不能同时进行。(DMA发送和接收搬运数据是不同的硬件,只是CPU去下发指令和搬运数据时,发送和接收不能同时进行)

![在这里插入图片描述](https://img-blog.csdnimg.cn/7f6d2c67e67c4f6bb8cd17d71734f1c9.png在这里插入图片描述

四、详细设计

4.1 数据结构

数据结构一共有2个,以下是串口设备的抽象,实现里面的指针和大小设置、驱动层调用相应的接口,即可实现以下功能

  • 不丢数据的不定长数据接收,通过空闲中断分数据帧
  • DMA方式发送和接收,大大减轻处理器的负担
  • 应用层直接获取数据帧
typedef struct uart_device
{	
	uint8_t *name;	  /* 名称*/
	uint8_t name_size;	  /* 名称长度*/
	uint8_t *rx_frame_start;	  /* 帧数据保存的起始地址*/
	uint8_t *dmarx_buf;	      /* dma接收缓存 */

	uint16_t rx_current_size;   /* 接收帧当前大小 */	
	uint16_t rx_max_size;	  /* 接收帧最大大小 */

	uint16_t dmarx_buf_size;   /* dma接收缓存大小*/
	uint16_t last_dmarx_size;  /* dma上一次接收数据大小 */
		
	struct data_queue fifo;   /*分帧的数据队列结构,如用操作系统消息队列,可不引用fifo相关*/
	int (*recv_fifo_write)(struct data_queue *queue,uint8_t *data_ptr,uint16_t size);/* 接收fifo写入指针 */
	int (*recv_fifo_read)(struct data_queue *queue,uint8_t *data_ptr,uint16_t *size,uint16_t max_size);/* 接收fifo读取指针 */
	void (*recv_fifo_reset)(struct data_queue *queue);/* 应用层如果有异常出错,可尝试清空fifo */
	
    void (*send)(uint8_t *data,uint16_t size); /* 发送指针,实现后才可用uart_dma_tx */

	int (*dmarx_remain_size)(void);/* DMA剩余要接收字节,必须实现 */
}uart_device_t;

以下是数据帧队列处理方式的数据结构,可直接使用或者用操作系统对应的消息队列去处理。
数据帧队列的实现主要思想是用2个环形缓存,一个记录数据的内容,一个记录数据帧的长度。

struct data_queue
{
	uint8_t *Ring_Buff;
	uint8_t magic;
	uint8_t frame_r_index;
	uint8_t frame_w_index;
	uint8_t frame_max_num;
	uint8_t frame_recv_num;
	uint16_t *frame_size_fifo;

    uint16_t Ring_Buff_Head;           
    uint16_t Ring_Buff_Tail;
	uint16_t Ring_Buff_size;
};

4.2 应用层接口

应用层接口一共有2个,发送和接收。
发送如下,其中定义了一个确定传入的发送buff是互斥的宏,即发送结果出来前不对buff进行修改,裸机可以定义该宏。
如果用了操作系统,在很多任务都有调用发送接口,比较复杂的时候,要加上操作系统的互斥量来上锁和解锁,有序的对uart_tx_frame_buff内存访问。

/**
 * @brief  调用函数指针对应的DMA发送函数
 * @param  serial:串口对象结构体、data:传输数据的起始地址、size:传输长度
 * @retval none(可在发送的驱动层,添加错误信息汇报uart_dma_error_report接口)
 */
void uart_dma_tx(struct uart_device *serial,uint8_t *data,uint16_t size)
{
#if defined(CONFIRM_SEND_BUFF_MUTUALLY_EXCLUSIV)
	uart_send_lock();
    for(int i = 0; i < size;++i)
	uart_tx_frame_buff[i] = data[i];
	serial->send(uart1_tx_frame_buff,size);
	uart_send_unlock();
#else
	serial->send(data,size);
#endif
}

接收如下,调用对应的接收fifo处理,在空闲中断的驱动里面,调用数据队列的写fifo数据,然后读取的时候就会用队列的方式返回写入的fifo数据。

/**
 * @brief  从串口的数据fifo读出数据
 * @param  serial:串口对象结构体、data:传输数据的起始地址、size:传输长度
 * @retval frame data size
 */
int uart_dma_rx(struct uart_device *serial,uint8_t *data,uint16_t *size)
{
	return serial->recv_fifo_read(&serial->fifo,data,size);
}

4.3 需实现的驱动接口

需要实现的驱动接口一共有5个或更多

void uart1_device_init(struct uart_device *serial);
void uart_dmarx_half_done_isr(struct uart_device *serial);
void uart_dmarx_done_isr(struct uart_device *serial);
void uart_dmarx_idle_isr(struct uart_device *serial);
void uart_dma_error_report(struct uart_device *serial,uint8_t *data,uint16_t size);

4.3.1 uart1_device_init是对应串口设备的初始化,参考以下代码

  • 定义分配内存和大小,以及函数指针指向对应的函数地址。
  • 在硬件驱动文件里面,传入uart1_device_init对应的struct uart_device的结构体,进行初始化
  • 如该单片机需要几个串口设备,编写uart2_device_init驱动接口,然后在驱动文件里面,传入uart2_device_init对应的struct uart_device的结构体,进行初始化
//串口一帧最大接收字节
#define UART1_RX_FRAME_BUF_SIZE   512
//dma循环接收的缓存大小
#define	UART1_DMA_RX_BUF_SIZE		256
//串口所有未数据的最大缓存大小
#define UART1_RX_FIFO_BUF_SIZE        256
//帧数据缓存大小,最大存储的帧标记数量
#define UART1_RX_FRAME_MAX_NUM        10
//对应设备名称的最大字节
#define UART1_NAME_MAX_SIZE 		    6   
 
static uint8_t uart1_rx_frame_buff[UART1_RX_FRAME_BUF_SIZE]; 
static uint8_t uart1_dmarx_buff[UART1_DMA_RX_BUF_SIZE];
static uint8_t uart1_rx_ring_buff[UART1_RX_FIFO_BUF_SIZE]; 
static uint16_t uart1_frame_max[UART1_RX_FRAME_MAX_NUM];
static uint8_t uart1_name[UART1_NAME_MAX_SIZE] = {"uart1"};

/**
 * @brief 串口1设备初始化 
 * @param  
 * @retval 
 */
void uart1_device_init(struct uart_device *serial)
{
	serial->name = uart1_name;
	serial->name_size = UART1_NAME_MAX_SIZE - 1;//大小不需要\0
	serial->rx_frame_start = uart1_rx_frame_buff;
	serial->dmarx_buf = uart1_dmarx_buff;
	serial->dmarx_buf_size = UART1_DMA_RX_BUF_SIZE;
	serial->last_dmarx_size = 0;
	serial->rx_current_size = 0;
	serial->rx_max_size = UART1_RX_FRAME_BUF_SIZE;
	serial->send = UART1_Send;//bsp驱动文件里面实现发送的函数名称
	serial->dmarx_remain_size = usart1_dma_rx_remain_size;//bsp驱动里面实现dma剩余数据的函数名称

	if(0==data_queue_init(&serial->fifo,uart1_rx_ring_buff,UART1_RX_FIFO_BUF_SIZE,uart1_frame_max,UART1_RX_FRAME_MAX_NUM))
	{
		serial->recv_fifo_write = data_queue_push;
		serial->recv_fifo_read = data_queue_pop;
		serial->recv_fifo_reset = data_queue_reset;
    }
	else
	{
		serial->recv_fifo_write = NULL;
		serial->recv_fifo_read = NULL;
		serial->recv_fifo_reset = NULL;
	}
}

4.3.2 uart_dmarx_half_done_isr

当指定DMA的接收长度后,如30字节,在收到第15字节的时候,单片机就会有dma半传输中断信号,等MCU有空去处理时候,获取当前剩余要搬运的字节。假设MCU比较慢去响应中断,此时dma已经搬运了18个字节,那么会得出dma剩余要搬运12个字节,MCU快速将数据搬运到别的内存空间。

void uart_dmarx_half_done_isr(struct uart_device *serial)
{
  	uint16_t recv_total_size;
  	uint16_t recv_size;
	
	recv_total_size = serial->dmarx_buf_size - serial->dmarx_remain_size();
	
	recv_size = recv_total_size - serial->last_dmarx_size;
	
	if( recv_size + serial->rx_current_size < serial->rx_max_size)
	{
		for(int i = 0;i < recv_size;++i)
		serial->rx_frame_start[serial->rx_current_size++] = serial->dmarx_buf[serial->last_dmarx_size++];
	}
}

在对应的驱动中断里面,获取中断标志,调用该API,实现举例如下

uart_device_t serial_1;
char debug_buff[50];
void DMA1_Channel5_IRQHandler(void)
{
    if( __HAL_DMA_GET_FLAG(&hdma_usart1_rx,DMA_FLAG_HT5))
	{
		__HAL_DMA_CLEAR_FLAG(&hdma_usart1_rx,DMA_FLAG_HT5);
		uart_dmarx_half_done_isr(&serial_1);
	}
    else if( __HAL_DMA_GET_FLAG(&hdma_usart1_rx,DMA_FLAG_TC5))
	{
		__HAL_DMA_CLEAR_FLAG(&hdma_usart1_rx,DMA_FLAG_TC5);
		uart_dmarx_done_isr(&serial_1);
	}	
	else if( __HAL_DMA_GET_FLAG(&hdma_usart1_rx,DMA_FLAG_TE5))
	{
		__HAL_DMA_CLEAR_FLAG(&hdma_usart1_rx,DMA_FLAG_TE5);
		uint16_t len;
		len = sprintf(debug_buff,"uart1 DMA recv errorCode:%d\r\n",hdma_usart1_rx.ErrorCode);
		uart_dma_error_report(&serial_1,(uint8_t *)debug_buff,len);		
	}
	
  /* USER CODE END DMA1_Channel5_IRQn 0 */
  HAL_DMA_IRQHandler(&hdma_usart1_rx);
  /* USER CODE BEGIN DMA1_Channel5_IRQn 1 */

  /* USER CODE END DMA1_Channel5_IRQn 1 */
}

4.3.3 uart_dmarx_done_isr

在DMA接收到指定长度后,会有一个dma接收完成中断,如果是dma循环模式,接收完成后,dma外设会将接收地址修改为一开始配置的接收起始地址,计数从0开始,相等与软件再配置一次dma普通模式接收。
这样即使处理器还没空去相应DMA的中断时候,DMA外设也会继续搬运数据,而不会丢失数据,结合半传输和完成传输的处理,让MCU有容错的时间去搬运数据。所以使用这种方式,能保证数据传输在很快的速率下,也不丢数据。

void uart_dmarx_done_isr(struct uart_device *serial)
{
  	uint16_t recv_size;
	
	recv_size = serial->dmarx_buf_size - serial->last_dmarx_size;

	if(recv_size + serial->rx_current_size < serial->rx_max_size)
	{
		for(int i = 0;i < recv_size;++i)
		serial->rx_frame_start[serial->rx_current_size++] = serial->dmarx_buf[serial->last_dmarx_size++];
	}

	serial->last_dmarx_size = 0;
}

4.3.4 uart_dmarx_idle_isr

  • 串口的空闲中断是实现分帧的标志,读取完dma接收的字节,再写入到数据队列中,即可完成分包。
  • 如果串口空闲中断调试有bug,可尝试用硬件定时器去分包,如30毫秒,没有dma的中断促发,就认为接收完了一帧,有dma中断到来时,将定时器计时清零。
  • 如果是有协议包头的数据通信,可不实现数据队列,直接在dma半传输完成、dma传输完成、空闲中断写入fifo即可,应用层去读取fifo查找包头解析数据。
void uart_dmarx_idle_isr(struct uart_device *serial)
{
  	uint16_t recv_total_size;
  	uint16_t recv_size;
  	
	recv_total_size = serial->dmarx_buf_size - serial->dmarx_remain_size();
	
	recv_size = recv_total_size - serial->last_dmarx_size;
	
	if( recv_size + serial->rx_current_size < serial->rx_max_size)
	{
		for(int i = 0;i < recv_size;++i)
		serial->rx_frame_start[serial->rx_current_size++] = serial->dmarx_buf[serial->last_dmarx_size++];
	}
	
	if(serial->recv_fifo_write != NULL && serial->rx_current_size > 0) serial->recv_fifo_write(&serial->fifo,serial->rx_frame_start,serial->rx_current_size);
	
	serial->rx_current_size= 0;	
}

4.3.5 uart_dma_error_report

dma发送和接收传输的汇报接口,就是打印串口设备名称,和传输的汇报内容。
可以在dma的发送和接收里面,获取错误标志,进行添加信息打印。

void uart_dma_error_report(struct uart_device *serial,uint8_t *data,uint16_t size)
{
	printf("%.*s Error Info :%.*s",serial->name_size,serial->name,size,data);
}

发送端的错误可以如下,接收端的错误在4.3.2部分有举例

uart_device_t serial_1;
char debug_buff[50];
void UART1_Send(uint8_t *pData, uint16_t Size)
{
	if(HAL_OK != HAL_UART_Transmit_DMA(&huart1,pData,Size))
	{
		uint16_t len;	
		len = sprintf(debug_buff,"uart1 DMA send HAL errorCode:%d\r\n",hdma_usart1_tx.ErrorCode);
		uart_dma_error_report(&serial_1,(uint8_t *)debug_buff,len);		
	}
}

void DMA1_Channel4_IRQHandler(void)
{
  /* USER CODE BEGIN DMA1_Channel4_IRQn 0 */
	
	if( __HAL_DMA_GET_FLAG(&hdma_usart1_tx,DMA_FLAG_TE4))
	{
		__HAL_DMA_CLEAR_FLAG(&hdma_usart1_tx,DMA_FLAG_TE4);
		uint16_t len;	
		len = sprintf(debug_buff,"uart1 DMA send errorCode:%d\r\n",hdma_usart1_tx.ErrorCode);
		uart_dma_error_report(&serial_1,(uint8_t *)debug_buff,len);		
	}

  /* USER CODE END DMA1_Channel4_IRQn 0 */
  HAL_DMA_IRQHandler(&hdma_usart1_tx);
  /* USER CODE BEGIN DMA1_Channel4_IRQn 1 */

  /* USER CODE END DMA1_Channel4_IRQn 1 */
}

4.4 发送实现逻辑

发送的实现逻辑,根据官方的SDK来设计的,阅读stm32的相关源码发现,HAL_UART_Transmit_DMA接口是配置dma的发送,结合初始化DMA通道的时候,置为普通模式,不配置为循环模式,就可实现调用一次就发送一次。HAL_UART_Transmit_DMA接口里面还开启了DMA半传输中断、DMA完成中断、DMA错误中断。因为传入的buff应用层设计为不可改变,所以对DMA半传输中断、DMA完成中断没有运用,可修改源码关闭这两个中断,只开错误中断。
DMA完成中断,可作为一个发送成功标志,告诉应用层发送成功了,可以对传入发送buff进行修改。

4.5 接收实现逻辑

接收实现的逻辑可参考链接1的文章,思路和文章作者一致,只是作了一些数据结构、驱动配合以及应用层上进行简化改动。
核心逻辑个人表诉如下

  • DMA半传输中断,处理器有空去处理时,记录当前收到的数据大小,DMA完成中断到来时,总共要接收的字节减去上一次dma处理的数据大小,就是要拷贝的剩余数据,此时算完成了一次DMA的普通接收,DMA循环模式下,相当于无限进行DMA的普通接收,执行半中断和完成中断的触发。
  • DMA接收完成中断处理,要对上一次dma处理的数据清零,其实就是将要拷贝数据的指针,指向dma的缓存接收buff的首地址,还一个作用是,让空闲和半传输中断回到最初的模式处理,不会出错统计要接收的字节大小。
  • 空闲中断的触发的时候,说明串口没有数据输入了,此时获取dma要接收的总大小减去dma剩余要传输的大小的值,并减去上一次dma收到的大小,就是剩余要拷贝的字节内容。对于半传输和空闲中的处理,可能场景图示如下
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/8684dce13e374bf38e284a42085b7c84.png
    在这里插入图片描述

五、stm32芯片 +keil + cubemx配置例程

举例是stm32L431的芯片配置,对stm32的芯片配置都类似。

5.1 配置时钟


在这里插入图片描述

5.2 配置串口DMA接收

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5.3 keil工程生成配置

在这里插入图片描述
在这里插入图片描述

5.4 驱动层适配

在这里插入图片描述
在这里插入图片描述

char debug_buff[50];
uart_device_t serial_1;

  uart1_device_init(&serial_1);
  
  //此HAL库会开启DMA半传输中断、完成中断、错误中断	
  HAL_UART_Receive_DMA(&huart1,serial_1.dmarx_buf,serial_1.dmarx_buf_size);
 
  //开启空闲中断
  __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); 

在这里插入图片描述

//重定向一下printf对应的底层接口,让uart_dma_error_report里面的打印可用,keil工程必须要勾选一下 Use MicroLIB
int fputc(int ch, FILE *f)
{
  HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xff);
  return ch;
}

/**
  * @brief This function handles USART1 global interrupt.
  */
void USART1_IRQHandler(void)
{
	if( __HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE))
	{
		__HAL_UART_CLEAR_IDLEFLAG(&huart1);
		uart_dmarx_idle_isr(&serial_1);	
	}

  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */

  /* USER CODE END USART1_IRQn 1 */
}

int usart1_dma_rx_remain_size(void)/* 接收回调 */
{
	return __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
}

void UART1_Send(uint8_t *pData, uint16_t Size)
{
	
	if(HAL_OK != HAL_UART_Transmit_DMA(&huart1,pData,Size))
	{
		uint16_t len;
		
		len = sprintf(debug_buff,"uart1 DMA send HAL errorCode:%d\r\n",hdma_usart1_tx.ErrorCode);
		uart_dma_error_report(&serial_1,(uint8_t *)debug_buff,len);		
	}
}

void DMA1_Channel4_IRQHandler(void)
{
  /* USER CODE BEGIN DMA1_Channel4_IRQn 0 */
	
	if( __HAL_DMA_GET_FLAG(&hdma_usart1_tx,DMA_FLAG_TE4))
	{
		__HAL_DMA_CLEAR_FLAG(&hdma_usart1_tx,DMA_FLAG_TE4);
		uint16_t len;	
		len = sprintf(debug_buff,"uart1 DMA send errorCode:%d\r\n",hdma_usart1_tx.ErrorCode);
		uart_dma_error_report(&serial_1,(uint8_t *)debug_buff,len);		
	}

  /* USER CODE END DMA1_Channel4_IRQn 0 */
  HAL_DMA_IRQHandler(&hdma_usart1_tx);
  /* USER CODE BEGIN DMA1_Channel4_IRQn 1 */

  /* USER CODE END DMA1_Channel4_IRQn 1 */
}

/**
  * @brief This function handles DMA1 channel5 global interrupt.
  */
void DMA1_Channel5_IRQHandler(void)
{
    if( __HAL_DMA_GET_FLAG(&hdma_usart1_rx,DMA_FLAG_HT5))
	{
		__HAL_DMA_CLEAR_FLAG(&hdma_usart1_rx,DMA_FLAG_HT5);
		uart_dmarx_half_done_isr(&serial_1);
	}
    else if( __HAL_DMA_GET_FLAG(&hdma_usart1_rx,DMA_FLAG_TC5))
	{
		__HAL_DMA_CLEAR_FLAG(&hdma_usart1_rx,DMA_FLAG_TC5);
		uart_dmarx_done_isr(&serial_1);
	}	
	else if( __HAL_DMA_GET_FLAG(&hdma_usart1_rx,DMA_FLAG_TE5))
	{
		__HAL_DMA_CLEAR_FLAG(&hdma_usart1_rx,DMA_FLAG_TE5);
		uint16_t len;
		len = sprintf(debug_buff,"uart1 DMA recv errorCode:%d\r\n",hdma_usart1_rx.ErrorCode);
		uart_dma_error_report(&serial_1,(uint8_t *)debug_buff,len);		
	}
	
  /* USER CODE END DMA1_Channel5_IRQn 0 */
  HAL_DMA_IRQHandler(&hdma_usart1_rx);
  /* USER CODE BEGIN DMA1_Channel5_IRQn 1 */

  /* USER CODE END DMA1_Channel5_IRQn 1 */
}

在这里插入图片描述

5.5 应用层简单使用举例

在maic.c里面定义读取的帧的内存和记录帧大小的全局变量,如下

uint8_t recv_buff[1024];
uint16_t recv_size;

在main.c的whlie(1)添加如下代码,即可实现数据回显。

	if(uart_dma_rx(&serial_1,recv_buff,&recv_size,sizeof(recv_buff)))
	{
	   //printf("len = %d,data = %.*s\r\n",recv_size,recv_size,recv_buff);
		uart_dma_tx(&serial_1,recv_buff,10);
	}

六、源码文件

6.1 dev_uart.c

#include <stddef.h>
#include <stdint.h>
#include "dev_uart.h"
#include "usart.h"
//#include "bsp_uart.h"

//Confirm the send buff transmitted by the application layer and do not change it until the send is completed.Can be undefined.
//#define UNCONFIRM_SEND_BUFF_MUTUALLY_EXCLUSIV


#ifdef UNCONFIRM_SEND_BUFF_MUTUALLY_EXCLUSIV
#define UART1_TX_BUF_SIZE 		    1024
static uint8_t uart_tx_frame_buff[UART1_TX_BUF_SIZE];
void uart_send_lock(void)
{
	__disable_irq();
}
void uart_send_unlock(void)
{
	__enable_irq();
}
#endif

//串口一帧最大接收字节
#define UART1_RX_FRAME_BUF_SIZE   512
//dma循环接收的缓存大小
#define	UART1_DMA_RX_BUF_SIZE		256
//串口所有未数据的最大缓存大小
#define UART1_RX_FIFO_BUF_SIZE        256
//帧数据缓存大小,最大存储的帧标记数量
#define UART1_RX_FRAME_MAX_NUM        10
//对应设备名称的最大字节
#define UART1_NAME_MAX_SIZE 		    6   
 
static uint8_t uart1_rx_frame_buff[UART1_RX_FRAME_BUF_SIZE]; 
static uint8_t uart1_dmarx_buff[UART1_DMA_RX_BUF_SIZE];
static uint8_t uart1_rx_ring_buff[UART1_RX_FIFO_BUF_SIZE]; 
static uint16_t uart1_frame_max[UART1_RX_FRAME_MAX_NUM];
static uint8_t uart1_name[UART1_NAME_MAX_SIZE] = {"uart1"};


void uart1_device_init(struct uart_device *serial)
{
	serial->name = uart1_name;
	serial->name_size = UART1_NAME_MAX_SIZE - 1;//大小不需要\0
	serial->rx_frame_start = uart1_rx_frame_buff;
	serial->dmarx_buf = uart1_dmarx_buff;
	serial->dmarx_buf_size = UART1_DMA_RX_BUF_SIZE;
	serial->last_dmarx_size = 0;
	serial->rx_current_size = 0;
	serial->rx_max_size = UART1_RX_FRAME_BUF_SIZE;
	serial->send = UART1_Send;//bsp驱动文件里面实现发送的函数名称
	serial->dmarx_remain_size = usart1_dma_rx_remain_size;//bsp驱动里面实现dma剩余数据的函数名称

	if(0==data_queue_init(&serial->fifo,uart1_rx_ring_buff,UART1_RX_FIFO_BUF_SIZE,uart1_frame_max,UART1_RX_FRAME_MAX_NUM))
	{
		serial->recv_fifo_write = data_queue_push;
		serial->recv_fifo_read = data_queue_pop;
		serial->recv_fifo_reset = data_queue_reset;
    }
	else
	{
		serial->recv_fifo_write = NULL;
		serial->recv_fifo_read = NULL;
		serial->recv_fifo_reset = NULL;
	}
}

void uart_dmarx_half_done_isr(struct uart_device *serial)
{
  	uint16_t recv_total_size;
  	uint16_t recv_size;
	
	recv_total_size = serial->dmarx_buf_size - serial->dmarx_remain_size();
	
	recv_size = recv_total_size - serial->last_dmarx_size;
	
	if( recv_size + serial->rx_current_size < serial->rx_max_size)
	{
		for(int i = 0;i < recv_size;++i)
		serial->rx_frame_start[serial->rx_current_size++] = serial->dmarx_buf[serial->last_dmarx_size++];
	}
}

void uart_dmarx_done_isr(struct uart_device *serial)
{
  	uint16_t recv_size;
	
	recv_size = serial->dmarx_buf_size - serial->last_dmarx_size;

	if(recv_size + serial->rx_current_size < serial->rx_max_size)
	{
		for(int i = 0;i < recv_size;++i)
		serial->rx_frame_start[serial->rx_current_size++] = serial->dmarx_buf[serial->last_dmarx_size++];
	}

	serial->last_dmarx_size = 0;
}


void uart_dmarx_idle_isr(struct uart_device *serial)
{
  	uint16_t recv_total_size;
  	uint16_t recv_size;
	
	recv_total_size = serial->dmarx_buf_size - serial->dmarx_remain_size();
	
	recv_size = recv_total_size - serial->last_dmarx_size;
	
	if( recv_size + serial->rx_current_size < serial->rx_max_size)
	{
		for(int i = 0;i < recv_size;++i)
		serial->rx_frame_start[serial->rx_current_size++] = serial->dmarx_buf[serial->last_dmarx_size++];
	}
	
	if(serial->recv_fifo_write != NULL && serial->rx_current_size > 0) serial->recv_fifo_write(&serial->fifo,serial->rx_frame_start,serial->rx_current_size);
	
	serial->rx_current_size= 0;	
}

/**
 * @brief  调用函数指针对应的DMA发送函数
 * @param  serial:串口对象结构体、data:传输数据的起始地址、size:传输长度
 * @retval none(可在发送的驱动层,添加错误信息汇报uart_dma_error_report接口)
 */
void uart_dma_tx(struct uart_device *serial,uint8_t *data,uint16_t size)
{
#if defined(UNCONFIRM_SEND_BUFF_MUTUALLY_EXCLUSIV)
	uart_send_lock();
    for(int i = 0; i < size;++i)
	uart_tx_frame_buff[i] = data[i];
	serial->send(uart_tx_frame_buff,size);
	uart_send_unlock();
#else
	serial->send(data,size);
#endif
}


/**
 * @brief  从串口的数据fifo读出数据
 * @param  
 * @retval frame data size
 */
int uart_dma_rx(struct uart_device *serial,uint8_t *data,uint16_t *size,uint16_t max_size)
{
	return serial->recv_fifo_read(&serial->fifo,data,size,max_size);
}

void uart_dma_error_report(struct uart_device *serial,uint8_t *data,uint16_t size)
{
	printf("%.*s Error Info :%.*s",serial->name_size,serial->name,size,data);
}

6.2 dev_uart.h

#ifndef _DEV_UART_H_
#define _DEV_UART_H_

#include <stdint.h>
#include "dataqueue.h"

typedef struct uart_device
{	
	uint8_t *name;	  /* 名称*/
	uint8_t name_size;	  /* 名称长度*/
	uint8_t *rx_frame_start;	  /* 帧数据保存的起始地址*/
	uint8_t *dmarx_buf;	      /* dma接收缓存 */

	uint16_t rx_current_size;   /* 接收帧当前大小 */	
	uint16_t rx_max_size;	  /* 接收帧最大大小 */

	uint16_t dmarx_buf_size;   /* dma接收缓存大小*/
	uint16_t last_dmarx_size;  /* dma上一次接收数据大小 */
		
	struct data_queue fifo;   /*分帧的数据队列结构,如用操作系统消息队列,可不引用fifo相关*/
	int (*recv_fifo_write)(struct data_queue *queue,uint8_t *data_ptr,uint16_t size);/* 接收fifo写入指针 */
	int (*recv_fifo_read)(struct data_queue *queue,uint8_t *data_ptr,uint16_t *size,uint16_t max_size);/* 接收fifo读取指针 */
	void (*recv_fifo_reset)(struct data_queue *queue);/* 应用层如果有异常出错,可尝试清空fifo */
	
    void (*send)(uint8_t *data,uint16_t size); /* 发送指针,实现后才可用uart_dma_tx */

	int (*dmarx_remain_size)(void);/* DMA剩余要接收字节,必须实现 */
}uart_device_t;



void uart_dma_tx(struct uart_device *serial,uint8_t *data,uint16_t size);
int uart_dma_rx(struct uart_device *serial,uint8_t *data,uint16_t *size,uint16_t max_size);


void uart1_device_init(struct uart_device *serial);
void uart_dmarx_half_done_isr(struct uart_device *serial);
void uart_dmarx_done_isr(struct uart_device *serial);
void uart_dmarx_idle_isr(struct uart_device *serial);
void uart_dma_error_report(struct uart_device *serial,uint8_t *data,uint16_t size);



#endif 

6.3 dataqueue.c

#include "dataqueue.h"
#include <stddef.h>
#include <stdio.h>

#define INITOK 0x55


void data_queue_lock(void)
{
	__disable_irq();
}

void data_queue_unlock(void)
{
	__enable_irq();
}

int data_queue_init(struct data_queue *queue,uint8_t *ring_buff_ptr,uint16_t ring_buff_size,uint16_t *frame_size_ptr,uint16_t frame_max_num)
{
	if(queue == NULL || ring_buff_ptr == NULL || frame_size_ptr == NULL) return -1;
   
	queue->Ring_Buff = ring_buff_ptr;

	queue->frame_r_index = 0;
	queue->frame_w_index = 0;
	queue->frame_max_num = frame_max_num;
	queue->frame_recv_num = 0;

	queue->frame_size_fifo = frame_size_ptr;
	queue->Ring_Buff_Head = 0;
	queue->Ring_Buff_Tail = 0;
	queue->Ring_Buff_size = ring_buff_size;

	queue->magic	= INITOK;

	return 0;
}


int data_queue_push(struct data_queue *queue,uint8_t *data_ptr,uint16_t size)
{
	if(queue->magic	!= INITOK) return 0;

	if(queue->frame_recv_num >= queue->frame_max_num || size > queue->Ring_Buff_size) 
	{
		printf("Error: data queue is full!\r\n");
		return 0;
	} 

	uint8_t *p = data_ptr;	
	uint16_t i;

	data_queue_lock();

	queue->frame_size_fifo[queue->frame_w_index] = size;

	if(++queue->frame_w_index >= queue->frame_max_num) queue->frame_w_index = 0;

	if(queue->Ring_Buff_Tail + size < queue->Ring_Buff_size)
	{	
		for(i = 0; i < size; ++i)		
		queue->Ring_Buff[queue->Ring_Buff_Tail++] = *p++;
	}
	else
	{
		uint16_t len = queue->Ring_Buff_size - queue->Ring_Buff_Tail;	

		for(i = 0; i < len; ++i)		
		queue->Ring_Buff[queue->Ring_Buff_Tail++] = *p++;

		queue->Ring_Buff_Tail = 0;		
		for(i = len; i < size; ++i)		
		queue->Ring_Buff[queue->Ring_Buff_Tail++] = *p++;
	}	
	queue->frame_recv_num++;
	data_queue_unlock();
    return size;
}

int data_queue_pop(struct data_queue *queue,uint8_t *data_ptr,uint16_t *size,uint16_t max_size)
{
	if(queue->magic	!= INITOK) return 0;

	if(queue->frame_recv_num == 0) return 0;
	
	data_queue_lock();
	uint8_t *p = data_ptr;	
	uint16_t i;
	
	*size = queue->frame_size_fifo[queue->frame_r_index];
	
	if(++queue->frame_r_index >= queue->frame_max_num) queue->frame_r_index = 0;
	
	if(*size > max_size)
	{
		printf("Error: Not enough data memory to read fifo data!\r\n");
		return 0;
	}

	if(queue->Ring_Buff_Head + *size < queue->Ring_Buff_size)
	{	
		for(i = 0; i < *size; ++i)		
		*p++ = queue->Ring_Buff[queue->Ring_Buff_Head++];
	}
	else
	{
		uint16_t len = queue->Ring_Buff_size - queue->Ring_Buff_Head;	

		for(i = 0; i < len; ++i)		
		*p++ = queue->Ring_Buff[queue->Ring_Buff_Head++];

		queue->Ring_Buff_Head = 0;		

		for(i = len; i < *size; ++i)		
		*p++ = queue->Ring_Buff[queue->Ring_Buff_Head++];

	}	
	queue->frame_recv_num--;
	
	data_queue_unlock();
    return *size;
}



void data_queue_reset(struct data_queue *queue)
{
    queue->frame_r_index = 0;
	queue->frame_w_index = 0;
	queue->frame_recv_num = 0;
	queue->Ring_Buff_Head = 0;
	queue->Ring_Buff_Tail = 0;
}

6.4 dataqueue.h

/*
 * Copyright (c) 2006-2018, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 */
#ifndef DATAQUEUE_H__
#define DATAQUEUE_H__

#include <stdint.h>

struct data_queue
{
	uint8_t *Ring_Buff;
	uint8_t magic;
	uint8_t frame_r_index;
	uint8_t frame_w_index;
	uint8_t frame_max_num;
	uint8_t frame_recv_num;
	uint16_t *frame_size_fifo;

    uint16_t Ring_Buff_Head;           
    uint16_t Ring_Buff_Tail;
	uint16_t Ring_Buff_size;
};

typedef struct data_queue data_queue_t;

/**
 * DataQueue for DeviceDriver
 */

int data_queue_init(struct data_queue *queue,uint8_t *ring_buff_ptr,uint16_t ring_buff_size,uint16_t *frame_size_ptr,uint16_t frame_max_num);
int data_queue_push(struct data_queue *queue,uint8_t *data_ptr,uint16_t size);
int data_queue_pop(struct data_queue *queue,uint8_t *data_ptr,uint16_t *size,uint16_t max_size);
void data_queue_reset(struct data_queue *queue);
#endif

七、参考资料

链接1: 一个严谨的STM32串口DMA发送&接收(1.5Mbps波特率)机制
链接2: STM32 DMA 循环模式DMA_Mode_Circular详解

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2022-09-25 23:18:15  更:2022-09-25 23:19:10 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 20:53:14-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码