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.pswp.cn/pingmian/88406.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/88406.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

飞算 JavaAI 体验:重塑 Java 开发的智能新范式

飞算 JavaAI 体验:重塑 Java 开发的智能新范式引言:正文:一、工程化代码生成:从 "片段拼接" 到 "模块交付"1.1 传统工具的局限与突破1.2 代码质量验证二、智能重构引擎:从 "问题修复" 到…

深入理解JVM的垃圾收集(GC)机制

引言首先我们来介绍垃圾收集的概念,什么是垃圾收集?垃圾收集 (Garbage Collection,GC),顾名思义就是释放垃圾占用的空间,防止内存爆掉。有效的使用可以使用的内存,对内存堆中已经死亡…

【笔记】国标-机动车辆及挂车分类

源于:GB/T 15089-2001机动车辆及挂车分类 1.L类:两轮或三轮车辆2.M类:四轮载客车辆3.N类:四轮载货车辆4.O类:挂车5.G类:其他

VLLM部署DeepSeek-LLM-7B-Chat 模型

一、部署环境准备1. 基础环境要求操作系统:Linux(推荐欧拉系统、Ubuntu 等)Python 版本:3.8 及以上依赖工具:pip、git、curl可选依赖:GPU 环境:NVIDIA GPU(支持 CUDA 11.7&#xff0…

翱翔的智慧之翼:Deepoc具身智能如何赋能巡检无人机“读懂”工业现场

翱翔的智慧之翼:Deepoc具身智能如何赋能巡检无人机“读懂”工业现场在百米高的风力发电机叶片顶端,在蜿蜒数十公里的高压输电线旁,在油气管道穿越的崇山峻岭之上,一架四旋翼无人机正精准地悬停着,它的“眼睛”&#xf…

Java大厂面试实录:谢飞机的电商场景技术问答(Spring Cloud、MyBatis、Redis、Kafka、AI等)

Java大厂面试实录:谢飞机的电商场景技术问答(Spring Cloud、MyBatis、Redis、Kafka、AI等)本文模拟知名互联网大厂Java后端岗位面试流程,以电商业务为主线,由严肃面试官与“水货”程序员谢飞机展开有趣的对话&#xff…

Kotlin基础

前言 Decrement(递减) → 将一个值减 1 的操作 Predicate(谓词) → 返回布尔值(逻辑值)的函数 Reference(引用) → 允许使用自定义名称与对象交互 Runtime(运行时&…

预防DNS 解析器安全威胁

DNS 是互联网的重要基础,例如 Web 访问、email 服务在内的众多网络服务都和 DNS 息息相关,DNS 的安全则直接关系到整个互联网应用能否正常使用。 DNS 解析器的作用是将用户输入的域名转换为对应的 IP 地址,以便计算机能够准确地定位并连接到…

Windows下VScode配置FFmpeg开发环境保姆级教程

相关准备 提前在本地开发环境中配置好mingw64或者msys2开发工具集。 安装VScode软件。 下载Windows版本的FFmpeg相关库 下载地址:https://ffmpeg.org/download.html 下载步骤:如下图。 下载后的文件:包含了可执行文件ffmpeg、ffpl…

Lecture #19 : Multi-Version Concurrency Control

CMU15445课程笔记多版本并发控制 多版本并发控制讲的是Mvcc。 即维护单个逻辑对象的多个物理版本, 这样当一个事务读取某个对象的时候不会阻塞其他事务写入该对象; 反之亦然。 但是Mvcc不保护写写冲突, 对于这种情况, 可能需要其两…

imx6ul Qt运行qml报错This plugin does not support createPlatformOpenGLContext!

imx6ul运行qml的Qt程序报错This plugin does not support createPlatformOpenGLContext!1、开发环境2、问题复现3、解决办法第一种方法第二种方法4、结论1、开发环境 主板:imx6ul Qt版本:5.9.6 文件系统:buildroot 问题描述:现需…

软考中项系统集成第 5 章:软件工程全流程考点拆解,备考逻辑清晰

备考系统集成项目管理工程师的小伙伴们,福利来啦!今天开始为大家带来《系统集成项目管理工程师(第 3 版)》考点的思维导图,今天带来的是第5章。第 5 章聚焦软件工程,涵盖软件工程定义、软件需求、软件设计、…

ICLR 2025 | InterpGN:时间序列分类的透明革命,Shapelet+DNN双引擎驱动!

在Rensselaer理工学院、Stony Brook大学与IBM Research的合作下,本文聚焦于如何在时间序列分类任务中兼顾性能与可解释性。传统深度学习模型虽然准确率高,却常被诟病为“黑盒”,难以赢得如医疗等高风险领域的信任。为此,作者提出了…

使用ENO将您的JSON对象生成HTML显示

ENO 是简单易用,性能卓越,自由灵活开源的 WEB 前端组件;实现 JSON 与 HTML 互操作的 JavaScript 函数库。没有任何其它依赖,足够轻量。 WEBPack NPM 工程安装。 npm install joyzl/eno 然后在JS中引用 import "joyzl/eno…

7.12 卷积 | 最小生成树 prim

lc1900.模拟比赛算出两个指定选手最早和最晚能在第几轮碰到。还是建议dfs捏模拟比赛,找出两个特定选手(firstPlayer和secondPlayer)最早和最晚相遇的轮次。1. 定义了一个“选手”结构体,包含两个属性a(战斗力&#xff…

LVS-NAT模式配置

目录 1、负载调度器配置 配置IP地址 安装ipvsadm 开启路由转发功能 加载ip_vs模块 启动ipvsadm服务 配置负载分配策略 查看验证 2、web节点配置 3、测试 1、负载调度器配置 配置IP地址 增加一块网卡 cd /etc/sysconfig/network-scripts/ cp ifcfg-ens192 ifcfg-ens…

中国银联豪掷1亿采购海光C86架构服务器

近日,中国银联国产服务器采购大单正式敲定,基于海光C86架构的服务器产品中标,项目金额超过1亿元。接下来,C86服务器将用于支撑中国银联的虚拟化、大数据、人工智能、研发测试等技术场景,进一步提升其业务处理能力、用户…

web网页,在线%食谱推荐系统%分析系统demo,基于vscode,uniapp,vue,java,jdk,springboot,mysql数据库

经验心得两业务单,项目前端在VSCode、HBuilder环境下整合Uniapp、Vue。后端使用Java、SpringBoot和MySQL,使用这些技术栈咱们就可以搭建在线食谱推荐与分析功能的系统,技术栈虽涉及前后端及数据库跨度不小,但咱们拆分模块去开发它难度就会变小…

MCP架构:AI时代的标准化上下文交互协议

本文深入解析Model Context Protocol(MCP)架构的创新设计,这是一种由Anthropic提出的标准化协议,旨在解决大型语言模型(LLM)与外部工具和数据源交互的碎片化问题。MCP采用客户端-服务器架构,通过…

机器学习数据集加载全攻略:从本地到网络

目录 一、加载内置数据集 1.1 Iris鸢尾花数据集 1.2 其他常用内置数据集 二、加载网络数据集 2.1 20 Newsgroups数据集 三、加载本地数据集 3.1 使用pandas加载CSV文件 3.2 处理常见问题 四、数据加载最佳实践 五、总结 在机器学习项目中,数据的加载是第一…