STM32F103 ------ DMA 与 串口
什么是DMA
DMA,全称Direct Memory Access,即直接存储器访问。
DMA传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。
用法一
接收串口数据,使用到的中断:串口空闲中断
STM32的串口有监测总线是否处于空闲的功能,我们可以使用这个功能,当数据传输完总线变成空闲状态时产生中断,来对收到的数据进行处理。因此整个过程就变成:当一堆数据开始传输,DMA默默地把数据搬运到内存中,当这堆数据传输完成,总线变成空闲状态时,马上产生中断,在中断服务程序中去做相应处理。
下面的例子,DMA设置的模式是正常模式(非循环存储);如果设置成循环存储,就可以防止接收到非常大的数据流,从而避免开辟很大的存储或者数组越界,当然数据流太大数据会丢失。
#define DMA_Rec_Len 10 //数据缓冲区大小 u8 value[DMA_Rec_Len]; void uart_init_DMA_IN(u32 bound) { //GPIO端口设置 GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; DMA_InitTypeDef DMA_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能USART1,GPIOA时钟 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能DMA传输 // RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);//使能USART2时钟 USART_DeInit(USART1); //复位串口1 //USART1_TX PA.9 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出 GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PA9 //USART1_RX A.10 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入 GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PA10 //Usart1 NVIC 配置 NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级3 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能 NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器 //USART 初始化设置 USART_InitStructure.USART_BaudRate = bound;//一般设置为9600; USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式 USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位 USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制 USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式 USART_Init(USART1, &USART_InitStructure); //初始化串口 USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);//开启空闲中断 USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE); //使能串口1 DMA接收 USART_Cmd(USART1, ENABLE); //使能串口 //相应的DMA配置 DMA_DeInit(DMA1_Channel5); //将DMA的通道5寄存器重设为缺省值 串口1对应的是DMA通道5 DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART1->DR; //DMA外设ADC基地址 DMA_InitStructure.DMA_MemoryBaseAddr = (u32)value; //DMA内存基地址 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //数据传输方向,从外设读取发送到内存 DMA_InitStructure.DMA_BufferSize = DMA_Rec_Len; //DMA通道的DMA缓存的大小 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址寄存器递增 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //数据宽度为8位 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //工作在正常缓存模式 DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道 x拥有中优先级 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输 DMA_Init(DMA1_Channel5, &DMA_InitStructure); //根据DMA_InitStruct中指定的参数初始化DMA的通道 DMA_Cmd(DMA1_Channel5, ENABLE); //正式驱动DMA传输 }
void USART1_IRQHandler(void) //串口1中断服务程序 { char i; if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET) { USART_ReceiveData(USART1);//读取数据 注意:这句必须要,否则不能够清除中断标志位。 Usart1_Rec_Cnt = DMA_Rec_Len-DMA_GetCurrDataCounter(DMA1_Channel5); //算出接本帧数据长度 //***********帧数据处理函数************//
for(i=0;i<Usart1_Rec_Cnt;i++) { USART_SendData(USART1,value[i]); while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET); }//*************************************// USART_ClearITPendingBit(USART1, USART_IT_IDLE); //清除中断标志 DMA_Cmd(DMA1_Channel5,DISABLE); //重新设置传输数据长度 DMA_SetCurrDataCounter(DMA1_Channel5,DMA_Rec_Len); DMA_Cmd(DMA1_Channel5,ENABLE); //开始下一次DMA } }
用法二
接收串口数据,使用到的中断:串口空闲中断、DMA半满中断、DMA满中断
DMA设置成循环存储数据到内存(DMA rx buf),三个中断触发时都从内存(DMA rx buf)读取数据。
产生半满中断CPU从内存(DMA rx buf)读取前半段数据到内存(rx fifo),保证CPU有足够的时间读取数据,不会造成DMA覆盖了未读出的数据;
产生满中断从内存(DMA rx buf)读取后半段数据到内存(rx fifo),此时DMA正在把数据写入前半段;
最后一部分数据可能不足一半,不能通过半满或者满中断读取数据,通过串口空闲中断通知CPU读取剩余的部分数据。
相比于用法一的优点:保证大数据量的串口数据,不用开很大的内存给DMA用,CPU有足够的时间读取内存数据,避免被DMA新数据覆盖。
用法三
发送串口数据,使用到的中断:DMA传输完成中断
CPU往待发送数据缓冲区(tx fifo)存放数,检查DMA状态是否为空闲,空闲则把要发送数据时把数据拷贝到内存(DMA tx buf),启动DMA发送串口数据,产生DMA传输完成中断时检查待发送数据缓冲区(tx fifo)是否还有数据待发送,有则重复之前的步骤,即拷贝数据,启动DMA。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
2017-07-17 TCP ------ TCP创建服务器中出现的套接字