当前位置: 首页 > news >正文

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。

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

//串口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;//复用串口1std_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,具体的解释如下:

/**
* @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通道,具体示例如下:

//使用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的接收缓冲区,将其余的数据转存至到备份区域,这就避免了传输过程中造成的数据丢失问题。

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

        具体配置如下:

/**
* @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的RXdma_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;//一次传输1bytedma_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对应的中断服务函数如下:

/*-------------------------------------------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对应的无阻塞性收发初始化内容具体如下:

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实现的串口无阻塞性收发已经实现。

http://www.dtcms.com/a/274088.html

相关文章:

  • CentOS 安装 JDK+ NGINX+ Tomcat + Redis + MySQL搭建项目环境
  • Redis5.0.5 漏洞
  • redis的一些疑问
  • windows下安装 redis
  • Redis全栈技术导航:从基础架构到实战案例的完整指南
  • 创客匠人:AI 时代创始人 IP 打造与知识变现的范式迁移
  • 什么是IP关联?跨境卖家如何有效避免IP关联?
  • LeetCode--43.字符串相乘
  • 软件过程模型核心特征与开发流程对照表
  • Android Glide使用与底层机制详解
  • 上位机知识篇---安装包架构
  • imx6ull-系统移植篇2—— U-Boot 命令使用(上)
  • Java 中线程通信方式笔记
  • tailwindCSS === 使用插件自动类名排序
  • ssm框架整合全攻略:从环境搭建到功能实现
  • 什么是Podman?能否替代Docker?Podman快速入门
  • dockerfile 笔记
  • STM32-DAC数模转换
  • 将英语转化为语音 英文转音频 英语转语音朗读
  • 嵌入式八股文之 GPIO
  • RISC-V:开源芯浪潮下的技术突围与职业新赛道 (三)RISC-V架构深度解剖(下)
  • FPGA实现SDI转LVDS视频发送,基于GTX+OSERDES2原语架构,提供2套工程源码和技术支持
  • Spring注解IoC与JUnit整合实战
  • MyBatis-Plus通用中等、大量数据分批查询和处理
  • 蔚来测开一面:HashMap从1.7开始到1.8的过程,既然都解决不了并发安全问题,为什么还要进一步解决环形链表的问题?
  • XPath 语法【Web 自动化-定位方法】
  • [java][springboot]@PostConstruct的介绍和用法
  • 机器学习基础知识【 激活函数、损失函数、优化器、 正则化、调度器、指标函数】
  • AI加持的开源知识库新秀:PandaWiki,如何用它打造智能化文档系统?
  • 「日拱一码」024 机器学习——防止过拟合