CIU32L051系列 DMA串口无阻塞性收发的实现

1.CIU32L051 DMA的通道映射

由于华大CIU32L051的DMA外设资源有限,DMA只有两个通道可供使用,对应的通道映射图如下:

2.UART对应的引脚分布及其复用映射

CIU32L051对应的UART对应的引脚映射图如下,这里博主为了各位方便查找,就直接全拿进来了:

3.USART1作为无阻塞性收发的串口

根据第二章的图片可以看到,串口1对应的IO口为PA1,PA2,PA11,PA12等等,这里为了方便,博主直接拿usart的例程中的PA11,PA12分别作为USART1_TX,USART1_RX。

对应串口的配置程序如下:

cs 复制代码
//串口1    GPIO的配置
static void UART1_GPIO_Configure(void)
{
    /* GPIO外设时钟使能 */  
    std_rcc_gpio_clk_enable(RCC_PERIPH_CLK_GPIOA);
    
    std_gpio_init_t usart_gpio_init={0};

    /* GPIO引脚配置  
        PA12    ------> RX  
        PA11    ------> TX  
    */    
    
    usart_gpio_init.pin = GPIO_PIN_11|GPIO_PIN_12;
    usart_gpio_init.mode = GPIO_MODE_ALTERNATE;//io复用模式
    usart_gpio_init.output_type = GPIO_OUTPUT_PUSHPULL;//复用推挽输出
    usart_gpio_init.pull = GPIO_PULLUP;//上拉输入
    usart_gpio_init.alternate = GPIO_AF1_USART1;//复用串口1
    std_gpio_init(GPIOA, &usart_gpio_init);
	
	NVIC_SetPriority(USART1_IRQn, 0);//这里配置串口1的中断配置器,这里可以不需要使用
    NVIC_EnableIRQ(USART1_IRQn);
}

//串口1 结构体的配置
static void usart1_init(uint32_t baud)
{
    /* USART1时钟使能 */
    std_rcc_apb2_clk_enable(RCC_PERIPH_CLK_USART1);
    
    std_usart_init_t usart_init={0};
    
    usart_init.direction = USART_DIRECTION_SEND_RECEIVE;//这里是使能了串口的发送和接收
    usart_init.baudrate = baud;//波特率
    usart_init.wordlength = USART_WORDLENGTH_8BITS;//数据长度
    usart_init.stopbits = USART_STOPBITS_1;//停止位
    usart_init.parity = USART_PARITY_NONE;//奇偶校验
    usart_init.hardware_flow = USART_FLOWCONTROL_NONE;//无硬件流使能
    /* USART初始化 */   
    if(STD_OK != std_usart_init(USART1,&usart_init))
    {
        /* 波特率配置不正确处理代码 */
        while(1);
    }
	
    std_usart_enable(USART1);
}

4.串口1无阻塞性发送的实现及代码实现

根据第一章的内容可以得知,串口1的TX和RX分别对应的是DMA的通道1和通道0。具体如下:

4.1配置串口1 DMA无阻塞性发送的实现

配置代码具体如下:

此处有配置DMA的传输模式为DMA_BLOCK_TRANSFER,具体的解释如下:

cs 复制代码
/**
* @brief  DMA通道1初始化
* @retval 无
*/
static void dma_init(void)
{
	std_dma_init_t dma_init_param={0};
    /* DMA外设时钟使能 */
    std_rcc_ahb_clk_enable(RCC_PERIPH_CLK_DMA);
    
    /* dma_init_param 结构体初始化 */
    dma_init_param.dma_channel = DMA_CHANNEL_1;
    dma_init_param.dma_req_id = DMA_REQUEST_USART1_TX;//这里是指DMA请求的触发条件
    dma_init_param.transfer_type = DMA_BLOCK_TRANSFER;//配置DMA 的传输模式
    dma_init_param.src_addr_inc = DMA_SRC_INC_ENABLE;//使能源地址递增
    dma_init_param.dst_addr_inc = DMA_DST_INC_DISABLE;//DMA目的地址自增使能或禁止
    dma_init_param.data_size = DMA_DATA_SIZE_BYTE;//DMA传输数据宽度,字节、半字或字
    dma_init_param.mode = DMA_MODE_NORMAL;//DMA工作模式为单次传输
    
    /* DMA初始化 */
    std_dma_init(&dma_init_param);
	
    /* 使能传输完成中断 */
    std_dma_interrupt_enable(dma_init_param.dma_channel,DMA_INTERRUPT_TF);//这里使能的传输完成中断,当DMA将对应目标的数据搬运完成后会将传输完成的标记置为1
    
    /* NVIC初始化 */
    NVIC_SetPriority(DMA_Channel1_IRQn, 0);
    NVIC_EnableIRQ(DMA_Channel1_IRQn); 
}

/**
* @brief  DMA配置函数    目的是更新DMA传输的数据源和目标地址
* @param  source DMA 传输源地址    指的是发送缓冲区的地址
* @param  number DMA 传输字符数    这个参数就是指的是你发送缓冲区对应的大小
* @retval 无
*/
void bsp_usart_dma_config(uint8_t *source,uint32_t number)
{
    std_dma_config_t dma_config = {0};

    /* 配置DMA 源地址、目的地址和传输数据大小,并使能DMA */
    dma_config.src_addr = (uint32_t)source;
    dma_config.dst_addr = (uint32_t)&USART1->TDR;//目标地址为串口1的发送数据寄存器
    dma_config.data_number = number;
    dma_config.dma_channel = DMA_CHANNEL_1;
    std_dma_start_transmit(&dma_config); 
}

void En_Dma_Channel(void)//这里在进行对DMA通道的使能
{
	std_dma_enable(DMA_CHANNEL_1);
}


void Dis_Dma_Channel(void)//这里在进行对DMA通道的失能
{
	std_dma_disable(DMA_CHANNEL_1);
}

上述代码中,DMA的通道1触发传输的条件是触发了串口1的发送,即当发送缓冲区的数据非空时,DMA将会把发送缓冲区的数据搬运至串口1的发送数据寄存器中,串口则会通过发送数据寄存器将数据转发。

由于,DMA的工作模式设置的是单次传输的模式,由此当DMA传输第一次完成后,需要对对DMA的数据源和目的地址进行更新,并重新使能对应的DMA通道,具体示例如下:

cs 复制代码
//使用DMA通道1	实现串口非阻塞性发送    这个函数的构造,就相当于是无阻塞性的UART_SENDDATA("DEBUG",strlen("DEBUG"))这种发送函数,只不过原本串口的发送函数是阻塞性的
void UART1_DMA_Send_Buf(uint8_t *buf,size_t len)
{
	Dis_Dma_Channel();//更新数据源前需要先关闭DMA对应的通道
	bsp_usart_dma_config(buf,len);//此函数则是更新了DMA对应通道的数据源,此函数在之前的配置代码中有对应的详细内容
	std_dma_interrupt_enable(DMA_CHANNEL_1,DMA_INTERRUPT_TF); //这里开启对应的传输完成中断,若是对应的中断服务函数中没有关闭传输完成中断,此处内容可以省略
	En_Dma_Channel();//重新开启DMA对应的通道
}


//DMA通道1对应的中断服务函数
/*-------------------------------------------functions------------------------------------------*/
/**
* @brief  DMA通道中断服务函数
* @retval 无
*/
//传输完成中断
void DMA_Channel1_IRQHandler(void)
{
    if(std_dma_get_flag(DMA_FLAG_TF1))
    {
        std_dma_interrupt_disable(DMA_CHANNEL_1,DMA_INTERRUPT_TF); //这个可以不用关闭
		SEGGER_RTT_printf(0, "DMA DATA SEND FINISH\r\n");//这就是RRT的输出的一个调试信息
        std_dma_clear_flag(DMA_FLAG_TF1);//清除传输完成的标记
    }
}

上述内容是对应的串口 DMA无阻塞性的发送的实现。

4.2串口1 无阻塞性接收的实现

无阻塞性接收数据的实现,需要用到DMA双缓冲区备份。为什么要用双缓冲区备份呢?

理由如下:

由于串口接收到的数据是不定长的数据,使用的DMA传输数据时容易造成可能由于数据过长导致缓冲区溢出的情况,以及在接收完成数据后由于数据处理花费的时间导致数据丢失。

DMA提供了一个传输完成一半的中断提示,由此可以通过数据传输完成一半的时,在中断服务函数中更换DMA的接收缓冲区,将其余的数据转存至到备份区域,这就避免了传输过程中造成的数据丢失问题。

此方式花费了原本双倍的内存换来了更高性能的传输,也避免了传输过程中的数据丢失问题。

具体配置如下:

cs 复制代码
/**
* @brief  DMA配置函数    目的是更新串口1RX对应DMA的数据源和目的地址
* @param  distination DMA传输目的地址
* @param  number DMA传输字符数
* @retval 无
*/
void USART1_DMA_RX_config(uint8_t *distination,uint32_t number)
{
    std_dma_config_t dma_config = {0};

	if(distination == buf_hu1)//此处内容用于中断服务函数中更新对应的缓冲区
	{
		Set_USART1_Buf_Flag();
	}
	else if(distination == buf_hu2)
	{
		Clear_USART1_Buf_Flag();
	}


    /* 配置DMA 源地址、目的地址和传输数据大小,并使能DMA */
    dma_config.src_addr = (uint32_t)&USART1->RDR;//串口数据接收寄存器
    dma_config.dst_addr = (uint32_t)distination;
    dma_config.data_number = number;
    dma_config.dma_channel = DMA_CHANNEL_0; 
    std_dma_start_transmit(&dma_config);   
}


/**
* @brief  DMA通道0初始化
* @retval 无
*/
void UART1_Dma_RX_init(void)
{
    std_dma_init_t dma_init_param = {0};
    
    /* DMA外设时钟使能 */
    std_rcc_ahb_clk_enable(RCC_PERIPH_CLK_DMA);
    
    /* dma_init_param 结构体初始化 */
    dma_init_param.dma_channel = DMA_CHANNEL_0;//通道0对应串口1的RX
    dma_init_param.dma_req_id = DMA_REQUEST_USART1_RX;//dma请求是串口1的接收,意思是当串口1    的RDR寄存器不为空时将RDR寄存器的数据搬运置,缓冲区中
    dma_init_param.transfer_type = DMA_BLOCK_TRANSFER;
    dma_init_param.src_addr_inc = DMA_SRC_INC_DISABLE;//使能数据源地址递增
    dma_init_param.dst_addr_inc = DMA_DST_INC_ENABLE;//使能目标地址递增
    dma_init_param.data_size = DMA_DATA_SIZE_BYTE;//一次传输1byte
    dma_init_param.mode = DMA_MODE_CIRCULAR;//循环接收,不需要对DMA进行重新配置
    
    /* DMA初始化 */
    std_dma_init(&dma_init_param);
	USART1_DMA_RX_config(buf_hu1,256);//这设置了串口1对应DMA通道0的缓冲区
	
	/* 使能传输完成中断 */
    std_dma_interrupt_enable(DMA_CHANNEL_0,DMA_INTERRUPT_TF);//使能dma通道0对应的接收完成中断  
	std_dma_interrupt_enable(DMA_CHANNEL_0,DMA_INTERRUPT_TH);  //使能dma通道0对应的接收一半中断  
	/* NVIC初始化 */
    NVIC_SetPriority(DMA_Channel0_IRQn, 0);    
    NVIC_EnableIRQ(DMA_Channel0_IRQn);
}

void Set_USART1_Buf_Flag(void)
{
	ATdevs.uart1_buf_Flag = 1;
}

uint8_t Get_USART1_Buf_Flag(void)
{
	return ATdevs.uart1_buf_Flag;
}

void Clear_USART1_Buf_Flag(void)
{
	ATdevs.uart1_buf_Flag = 0;
}

/*
	DMA 双缓冲区实现串口DMA的无阻塞性接收数据
		配合DMA的传输一半的中断进行使用
	当触发传输一半的中断后,此时需要更换对应的缓冲区,再下次传输一半数据前,是我们处理原本数据的有效时间
		要求是MCU的处理速度要比DMA的传输速度要快一半
*/
void DMA_USART_RX_CallBlack(void)
{
	std_dma_disable(DMA_CHANNEL_0);
	
	if(Get_USART1_Buf_Flag())
	{
		memset(buf_hu2,0,USART1_BUF_SIZE);//更换缓冲区前清除需要更换的缓冲区数据
		USART1_DMA_RX_config(buf_hu2,USART1_BUF_SIZE);//更新数据源
		SEGGER_RTT_printf(0,"dma buf is modify buf_hu2 success\r\n");
	}
	else
	{
		memset(buf_hu1,0,USART1_BUF_SIZE);
		USART1_DMA_RX_config(buf_hu1,USART1_BUF_SIZE);
		SEGGER_RTT_printf(0,"dma buf is modify buf_hu1 success\r\n");
	}
	
	std_dma_enable(DMA_CHANNEL_0);
}

上述内容是配置了USART1_RX对应的DMA通道,以及更换数据源的实现。

关于DMA通道0对应的中断服务函数如下:

cs 复制代码
/*-------------------------------------------functions------------------------------------------*/
/**
* @brief  DMA通道中断服务函数
* @retval 无
*/
//传输完成中断
void DMA_Channel0_IRQHandler(void)
{
    if(std_dma_get_flag(DMA_FLAG_TF0))
    {
		std_dma_clear_flag(DMA_FLAG_TF0);        
    }
	
	if(std_dma_get_flag(DMA_INTERRUPT_TH))//DMA传输一半完成的中断
    {
		std_dma_clear_flag(DMA_INTERRUPT_TH);

		DMA_USART_RX_CallBlack();//传输一半后更换目的地址
    }
}

USART1对应的无阻塞性收发初始化内容具体如下:

cs 复制代码
void Configure_UART1_DMA0_TX_AND_RX(void)
{
	dma_init();//这是DMA结构体相关的初始化
	UART1_Dma_RX_init();//此处是关于DMA实现的无阻塞性接收的配置
	UART1_GPIO_Configure();
	usart1_init(115200);
	std_usart_dma_tx_enable(USART1);//这里必须要使能
	std_usart_dma_rx_enable(USART1);//这里必须要使能
}

5.测试效果

测试环境:

RT-thread NANO系统下,开辟了一个周期为2秒的周期定时器,在周期定时器的回调中进行无阻塞性的发送。

另外,通过STM32F103的最小系统板一直给CIU32L051发送数据。

效果图如下:

由此可见,基于DMA实现的串口无阻塞性收发已经实现。

相关推荐
yuanManGan18 分钟前
Linux基本指令(一)
linux·运维·服务器
珹洺1 小时前
Linux操作系统从入门到实战(十)Linux开发工具(下)make/Makefile的推导过程与扩展语法
linux·运维·服务器
星哥说事1 小时前
Linux管理不用记命令!Linux安装可视化管理工具Cockpit安装使用
linux
花海如潮淹3 小时前
物联网设备管理工具实战:用AR运维镜击穿6.8天修复魔咒
运维·物联网·ar
不会敲代码的XW3 小时前
LVS(Linux Virtual Server)详细笔记(理论篇)
linux·笔记·lvs
nbsaas-boot3 小时前
如何设计一个合理的 Java Spring Boot 项目结构
java·运维·云计算
开开心心就好3 小时前
电脑桌面整理工具,一键自动分类
运维·服务器·前端·智能手机·pdf·bash·symfony
路飞雪吖~3 小时前
【Linux】线程创建&&等待&&终止&&分离
linux·开发语言
♛暮辞4 小时前
centos 安装java 环境
java·linux·centos
不脱发的程序猿4 小时前
嵌入式Linux:进程间通信机制
linux