(三)stm32之串口通信DMA传输完成中断
一、DMA功能简介
首先唠叨一下DMA的基本概念,DMA的出现大大减轻了CPU的工作量。在硬件系统中,主要由CPU(内核)、外设、内存(SRAM)、总线等结构组成,数据经常要在内存和外设之间,外设和外设之间转移。例如:CPU需要处理从外设采集回来的数据,CPU需要先将数据从ADC外设的寄存器读取到内存中(变量)去,然后进行运算处理,这是一般的解决方法。CPU的资源是非常宝贵的,我们可以设法把转移的工作交给其他部件来完成,CPU把更多的资源用于数据运算和中断响应上,如此DMA便登场了。DMA正是为CPU分担数据转移工作,因为DMA的存在,CPU才被解放出来,它可以在数据转移的同时进行数据运算,相应中断,大大提高了效率。
二、DMA的主要特性
三、DMA中断特性
四、DMA之串口通信
我们实现一个简单的功能,在DMA中处理串口通信,把数据转移的工作交给DMA,DMA把数据从内存(数组)到外设(串口)的转移,在main函数中不断进行闪灯操作,这样我们可以看到DMA在工作的时候CPU也在工作。非常有必要复习一下DMA的对应关系,我们知道stm32总共有2个DMA控制器(DMA1有7个通道,DMA2有5个通道),每个通道专门用来管理来自一个或多个外设对存储器访问的请求,还有一个仲裁器来协调DMA请求的优先级(优先级分:很高、高、中等、低),这可不是随便对应的。
1、LED初始化程序如下:
void LED_GPIO_Config(void)
{
/*定义一个GPIO_InitTypeDef类型的结构体*/
GPIO_InitTypeDef GPIO_InitStructure;
/*开启LED的外设时钟*/
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE);
/*选择要控制的GPIOB引脚*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
/*设置引脚模式为通用推挽输出*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
/*设置引脚速率为50MHz */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
/*调用库函数,初始化GPIOB0*/
GPIO_Init(GPIOB, &GPIO_InitStructure);
/* 关闭所有led灯 */
GPIO_SetBits(GPIOB, GPIO_Pin_14);
}
这个地方地方没什么要注意的,唯一要注意的就是输入输出模式,我们按需求这样配就好了。
2、串口初始化
void USART3_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
/* config USART3 clock */
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB , ENABLE);
RCC_APB1PeriphClockCmd( RCC_APB1Periph_USART3, ENABLE);
/* USART1 GPIO config */
/* Configure USART1 Tx (PA.09) as alternate function push-pull */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
/* Configure USART1 Rx (PA.10) as input floating */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOB, &GPIO_InitStructure);
/* USART1 mode config */
USART_InitStructure.USART_BaudRate = 38400;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
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(USART3, &USART_InitStructure);
USART_Cmd(USART3, ENABLE);
}
3、DMA初始化
void USART3_DMA_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
/*开启DMA时钟*/
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
//NVIC_Config(); //配置DMA中断
//NVIC_Configuration();
/*设置DMA源:串口数据寄存器地址*/
DMA_InitStructure.DMA_PeripheralBaseAddr = USART3_DR_Base;
/*内存地址(要传输的变量的指针)*/
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff;
/*方向:从内存到外设*/
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
/*传输大小DMA_BufferSize=SENDBUFF_SIZE*/
DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE;
/*外设地址不增*/
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
/*内存地址自增*/
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
/*外设数据单位*/
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
/*内存数据单位 8bit*/
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
/*DMA模式:不断循环*/
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ;
//DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
/*优先级:中*/
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
/*禁止内存到内存的传输 */
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
/*配置DMA1的2通道*/
DMA_Init(DMA1_Channel2, &DMA_InitStructure);
//DMA_ITConfig(DMA1_Channel2,DMA_IT_TC,ENABLE); //配置DMA发送完成后产生中断
/*使能DMA*/
DMA_Cmd (DMA1_Channel2,ENABLE);
}
在这里我们要注意以下几点:
(1)DMA_InitStructure.DMA_PeripheralBaseAddr = USART3_DR_Base;这里对应USART数据寄存器地址,这个地址我们是这样定义的:#define USART3_DR_Base 0x40004804,这个值是怎么算出来的呢?我们可以查看stm32存储器映射表:
USART3的起始地址是0x40004800,我们查看stm32串口数据寄存器偏移地址为0x04
因此我们可以计算到USART3数据寄存器地址为0x40004804
(2)我们数据传输方向内存(变量)到外设(串口),所以DMA方向为内存到外设
(3)DMA传输模式有两种:DMA_Mode_Normal(普通模式),DMA只传输一次;DMA_Mode_Circular(循环模式),DMA循环传输,比如在AD采集时要配置成循环模式。
4、主函数
int main(void)
{
/* USART1 config 115200 8-N-1 */
USART3_Config();
USART3_DMA_Config();
LED_GPIO_Config();
printf("\r\n usart3 DMA TX 测试 \r\n");
{
uint16_t i;
/*填充将要发送的数据*/
for(i=0;i<SENDBUFF_SIZE;i++)
{
SendBuff[i] = 'A';
}
}
/* USART1 向 DMA发出TX请求 */
USART_DMACmd(USART3, USART_DMAReq_Tx, ENABLE);
/* 此时CPU是空闲的,可以干其他的事情 */
//例如同时控制LED
for(;;)
{
LED1(ON);
Delay(0xFFFFF);
LED1(OFF);
Delay(0xFFFFF);
}
}
这个函数很简单,我们很容易就可以实现,达到效果,这里就不贴图片了。
五、串口通信DMA传输完成中断
我们知道DMA可以在传输过半,传输完成,传输错误时产生中断。我们实现的功能是,DMA工作在普通模式下即只传输一次,LED灯初始化是关闭的,DMA传输完成后产生一个中断,在中断中我们做点灯操作。这个程序调了一天才调了出来,并不是因为它很难,而是有一些要注意的地方没有注意到,从而到时耽误了好长时间才调出来。不过有错误就会有进步嘛。
我先贴出正确的代码,然后在讨论我犯的错误,由于和上一个程序好多都是一样的,这里我们只贴出不同的地方。
(1)DMA初始化程序:
void USART3_DMA_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
/*开启DMA时钟*/
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
//NVIC_Config(); //配置DMA中断
NVIC_Configuration();
/*设置DMA源:串口数据寄存器地址*/
DMA_InitStructure.DMA_PeripheralBaseAddr = USART3_DR_Base;
/*内存地址(要传输的变量的指针)*/
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff;
/*方向:从内存到外设*/
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
/*传输大小DMA_BufferSize=SENDBUFF_SIZE*/
DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE;
/*外设地址不增*/
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
/*内存地址自增*/
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
/*外设数据单位*/
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
/*内存数据单位 8bit*/
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
/*DMA模式:不断循环*/
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ;
//DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
/*优先级:中*/
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
/*禁止内存到内存的传输 */
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
/*配置DMA1的2通道*/
DMA_Init(DMA1_Channel2, &DMA_InitStructure);
DMA_ITConfig(DMA1_Channel2,DMA_IT_TC,ENABLE); //配置DMA发送完成后产生中断
/*使能DMA*/
DMA_Cmd (DMA1_Channel2,ENABLE); }
注意我们在这里打开了DMA传输完成中断。
(2)NVIC初始化
static void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure; /* Configure one bit for preemption priority */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
(3)中断处理程序
我们在stm32f10x_it.c中编写我们的中断处理程序:
void DMA1_Channel2_IRQHandler(void)
{
if(DMA_GetITStatus(DMA1_IT_TC2))
{
LED1(ON);
DMA_ClearITPendingBit(DMA1_IT_GL2); //清除全部中断标志
}
}
我们也可以这样写中断处理程序:
void DMA1_Channel2_IRQHandler(void)
{
if(DMA_GetFlagStatus(DMA1_FLAG_TC2))
{
LED1(ON);
DMA_ClearFLAG(DMA1_FLAG_TC2); //清除全部中断标志
}
}
这两种写法都行,我们在库开发文档可以查看。都代表DMA的通道2传输完成中断。
(4)主函数
int main(void)
{
/* USART1 config 115200 8-N-1 */
USART3_Config();
USART3_DMA_Config();
LED_GPIO_Config();
printf("\r\n usart3 DMA TX 测试 \r\n");
{
uint16_t i;
/*填充将要发送的数据*/
for(i=0;i<SENDBUFF_SIZE;i++)
{
SendBuff[i] = 'A';
}
}
/* USART1 向 DMA发出TX请求 */
USART_DMACmd(USART3, USART_DMAReq_Tx, ENABLE);
/* 此时CPU是空闲的,可以干其他的事情 */
//例如同时控制LED
for(;;)
{
}
}
这样我们实验便可以看到,LED灯初始化是关闭的,当串口发送完40000字节的‘A’后,LED等亮。
(5)补充
原意是测试DMA发送完成中断指的是每次指定字节发送完成后便产生一个中断还是最终都传输完成触发一次中断,刚开始中断处理函数写的程序如下:
void DMA1_Channel2_IRQHandler(void)
{
uint16_t n = 0;
if(DMA_GetFlagStatus(DMA1_FLAG_TC2))
{
n = ~n;
if(n) LED1(ON);
else LED1(OFF);
DMA_ClearFlag(DMA1_FLAG_TC2); //清除全部中断标志
}
}
通过测试,我发现LED灯并没有像试想的那样每次发送完成后便触发一次中断,然后灯会间隔闪烁,而实际是第一次传输完成后灯点亮,之后就一直保持亮的状态。刚开始我还以为DMA只会触发第一次中断,后来仔细分析后才发现了问题。正确的代码应该如下。
void DMA1_Channel2_IRQHandler(void)
{
static uint16_t n = ;
if(DMA_GetFlagStatus(DMA1_FLAG_TC2))
{
n = ~n;
if(n) LED1(ON);
else LED1(OFF);
DMA_ClearFlag(DMA1_FLAG_TC2); //清除全部中断标志
}
}
在这里n是一个局部变量,如果不定义成静态变量,每次出中断时后n所占的内存(栈)便会释放,这样再次进入后n还是会初始化为0.与我们要达到的效果不符。因此,在这里我们把它指定为静态变量,那么内存就不会释放,它会保持上一次的的值,修改之后达到了效果,每次传输完成3000个字节后灯的状态就会改变一次。
在这里我们整理一下变量:
全局动态变量:作用范围为整个工程,不释放内存,会保持上一次的值。
全局静态变量:作用范围为当前文件,不释放内存,会保持上一次的值。
局部动态变量:作用范围为当前函数,每次函数执行结束释放内存,不会保持上一次的值。
局部静态变量:作用范围为当前函数,不释放内存,会保持上一次的值。
(三)stm32之串口通信DMA传输完成中断的更多相关文章
- STM32的串口通信
本篇文章主要讲解一个在开发过程中经常使用到的一个外设---串口. 串口是绝大多数 MCU 中不可或缺的一个外设,同时也是我们开发中经常使用的一种调试手段,所以在STM32的学习中,串口的配置使用也是必 ...
- stm32的DMA传输一半中断
这里本想做一个录音程序 硬件很简单: MIC(麦克风)放大滤波电路---->stm32的ADC----->DMA通道----->一个数组缓存------->通过FATFS的 ...
- STM32之串口通信
一.RS232通信协议 1.概念 个人计算机上的通讯接口之一,由电子工业协会(Electronic Industries Association,EIA) 所制定的异步传输标准接口. 2.电气特性 逻 ...
- 串口通信DMA中断
这是以前学32的时候写的,那时候学了32之后感觉32真是太强大了,比51强的没影.关于dma网上有许多的资料,亲们搜搜,这里只贴代码了,其实我也想详详细细地叙述一番,但是自己本身打字就慢,还有好多事情 ...
- STM32 在串口通信时运用MODBUS协议
最近一个项目用到了MODBUS协议,就学习了一下,这里做一下记录以免后续忘记. 要用到MODBUS肯定要先知道是MOBUS协议,这里呢我们就又要先理解协议的含义了. 所谓的协议是什么?就是互相之间的约 ...
- Stm32串口通信(USART)
Stm32串口通信(UART) 串口通信的分类 串口通信三种传递方式 串口通信的通信方式 串行通信的方式: 异步通信:它用一个起始位表示字符的开始,用停止位表示字符的结束.其每帧的格式如下: 在一帧格 ...
- STM32基础分析——USART的DMA模式
有关USART的DMA传输模式,其基本的概念和配置,网上有很多博客和教程都有,这里不再赘述,只是记录一下比较容易忽视而造成调试不通的问题. 1. 串口发送和接收分属两个DMA通道 一般方式操作串口时, ...
- RS232串口通信详解
串口是计算机上一种非常通用的设备通信协议. ---------------------------------串口的引脚定义: 9芯 信号方向来自 缩写 描述 1 调制解调器 CD 载波检测 2 调制 ...
- STM32学习笔记:【004】USART串口通信
版本:STM32F429 Hal库v1.10 串口通信能够实现两块电路之间不同的通信,在开发中作为打印调试也是一门利器(printf重定向). 补充一点小知识: 1. weak修饰符修饰的函数,说明这 ...
随机推荐
- 使用System.arraycopy()实现数组之间的复制
System提供了一个静态方法arraycopy(),我们可以使用它来实现数组之间的复制. 其函数原型是: public static void arraycopy(Object src, int s ...
- js的作用域
全局代码和两个函数都会形成一个作用域,通过函数是在哪个作用域下创建的来确定作用域的上下级关系.作用域最大的用处是隔离变量,不同作用域下同名变量不会有冲突.
- Android动画的使用总结
1.补间动画(透明渐变.平移.旋转.缩放.组合) 方法一:通过xml文件设置 1-1:创建:res/anim 1-2:java代码写调用 Animation a = AnimationUtils.lo ...
- HttpClient的使用方法
使用httpClient发送请求.接收响应很简单.一般需要以下几个步骤. 第一:创建HttpClient对象: 第二:创建请求方法的实例,并指定请求URL.如果要发送GET请求,创建HttpGet对象 ...
- Moo University - Financial Aid
Moo University - Financial Aid Time Limit: 1000MS Memory Limit: 30000K Total Submissions: 6020 Accep ...
- Stars(树状数组或线段树)
Stars Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 37323 Accepted: 16278 Description A ...
- 山东理工大学第七届ACM校赛-学区房问题 分类: 比赛 2015-06-26 10:23 89人阅读 评论(0) 收藏
Time Limit: 1000ms Memory limit: 65536K 有疑问?点这里^_^ 题目描述 铁牌狗在学区B有一套面积为S1平方米的房子,现在他为了让后代进化成金牌狗,决定在学区A购 ...
- Poj(1797) Dijkstra对松弛条件的变形
题目链接:http://poj.org/problem?id=1797 题意:从路口1运货到路口n,最大的运货重量是多少?题目给出两路口间的最大载重. 思路:j加到s还是接到K下面,取两者的较大者,而 ...
- 3094 寻找sb4
3094 寻找sb4 时间限制: 1 s 空间限制: 32000 KB 题目等级 : 黄金 Gold 题解 查看运行结果 题目描述 Description sb有一天和sml吵架了,她 ...
- spark记录
Filtering multiple values in multiple columns: In the case where you're pulling data from a database ...