有关USART的DMA传输模式,其基本的概念和配置,网上有很多博客和教程都有,这里不再赘述,只是记录一下比较容易忽视而造成调试不通的问题。

1. 串口发送和接收分属两个DMA通道

一般方式操作串口时,读写数据都是只操作DR(数据寄存器),虽然它是由两个寄存器组成的,一个给发送用(TDR),一个给接收用(RDR),但是用户只能操作DR寄存。而DMA模式下,串口发送和接收分属两个DMA通道,需要单独配置。

分别配置的代码如下:

static void USART1_Tx_DMA_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel4_IRQn; // 配置DMA1_Channel4中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure); DMA_DeInit(USART_TX_DMA_CHANNEL);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // 开启DMA时钟
DMA_InitStructure.DMA_PeripheralBaseAddr = USART_DR_ADDRESS; // 设置DMA源地址:串口数据寄存器地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)sendbuff; // 内存地址(要传输的变量的指针)
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; // 方向:从内存到外设
DMA_InitStructure.DMA_BufferSize = CMD_NUM; // 传输大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址不增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 内存地址自增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 外设数据单位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // 内存数据单位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // DMA一次模式
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; // 优先级:中
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // 禁止内存到内存的传输
DMA_Init(USART_TX_DMA_CHANNEL, &DMA_InitStructure); // 配置DMA通道DMA1_Channel4 DMA_ITConfig(USART_TX_DMA_CHANNEL,DMA_IT_TC,ENABLE);
DMA_Cmd (USART_TX_DMA_CHANNEL,DISABLE); // 关闭DMA
} static void USART1_Rx_DMA_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
//注意,接收没使用接收DMA中断
// NVIC_InitTypeDef NVIC_InitStructure;
//
// NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel5_IRQn;
// NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
// NVIC_InitStructure.NVIC_IRQChannelSubPriority = 4;
// NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
// NVIC_Init(&NVIC_InitStructure); DMA_DeInit(USART_RX_DMA_CHANNEL);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // 开启DMA时钟
DMA_InitStructure.DMA_PeripheralBaseAddr = USART_DR_ADDRESS; // 设置DMA源地址:串口数据寄存器地址*/
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)rx_cmd; // 内存地址(要传输的变量的指针)
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // 方向:外设到内存
DMA_InitStructure.DMA_BufferSize = CMD_NUM; // 传输大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址不增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 内存地址自增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 外设数据单位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // 内存数据单位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // DMA一次模式
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; // 优先级:中
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // 禁止内存到内存的传输
DMA_Init(USART_RX_DMA_CHANNEL, &DMA_InitStructure); // 配置DMA通道DMA1_Channel5 // DMA_ITConfig(USART_RX_DMA_CHANNEL,DMA_IT_TC,ENABLE);
DMA_Cmd (USART_RX_DMA_CHANNEL,ENABLE); // 使能DMA
}

注意:在串口的基本配置当中要打开DMA传输模式,函数如下:

    USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);                    // 开启串口发送DMA
USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE); // 开启串口接收DMA

2. 间隔单次传输

将DMA传输模式设置为Normal(一次传输),传输完成需要再次传输时,需要再次向DMA通道的传输数量寄存器(CNDTR)写入要传输的字节数。但是,在写入前,需要关闭DMA,写完CNDTR后再打开。

2.1 串口DMA发送

我的设计方法是在初始化的时候,默认先关闭发送DMA,在需要串口发送数据时,先配置CNDTR,再打开DMA,发送完成后进入中断函数,再关闭DMA。

void DMA1_Channel4_IRQHandler(void)
{
DMA_ClearFlag(DMA1_FLAG_TC4);
DMA_Cmd(USART_TX_DMA_CHANNEL,DISABLE);
} *********
//代码片段
DMA_SetCurrDataCounter(DMA1_Channel4,(uint16_t)CMD_NUM); // 关于DMA单次传输,这条非常重要
DMA_Cmd (USART_TX_DMA_CHANNEL,ENABLE);

2.2 串口DMA接收

设计方法是:不启用DMA接收通道中断,而使用串口传输中断,在串口中断函数中对DMA处理。注意,一般串口中断我们采用的是接收中断USART_IT_RXNE,接收一次即中断一次。在DMA模式下要使用空闲中断USART_IT_IDLE,空闲中断是在检测到接收数据后,在数据总线上的一个字节时间内,如果没有接收到新的数据,则触发空闲中断,它是在串口的RXNE位被置位之后才开始检测。简单理解是,连续的一串数据发送完成之后,才触发空闲中断。

串口的CR1寄存器的IDLE位被硬件置1,必须采用软件将IDLE位清零才能避免反复进入空闲中断。具体的做法是先读取状态寄存器USART_SR,再读取数据寄存器USART_DR,完成后自动清除。需要注意的是,不能采用库函数USART_ClearFlag()或者USART_ClearItPending()来清除IDEL标志,因为这两个函数接收的中断标志位仅包括:

  • USART_FLAG_CTS: CTS Change flag (not available for UART4 and UART5).

  • USART_FLAG_LBD: LIN Break detection flag.

  • USART_FLAG_TC: Transmission Complete flag.

  • USART_FLAG_RXNE: Receive data register not empty flag.

同理,关闭DMA后,重置传输字节数,再开启DMA(因为串口一直要监测接收数据)。串口中断函数基础代码如下:

void USART1_IRQHandler(void)
{
uint32_t temp = 0; if(USART_GetITStatus(USART1,USART_IT_IDLE)!=RESET)
{
// temp = USART_GetITStatus(USART1,USART_IT_IDLE); // 在判断时已经读取过一次
temp = USART_ReceiveData(USART1); // 必须添加这条语句
DMA_Cmd(USART_RX_DMA_CHANNEL,DISABLE);
DMA_SetCurrDataCounter(DMA1_Channel5,(uint16_t)CMD_NUM);
DMA_Cmd(USART_RX_DMA_CHANNEL,ENABLE);
}
}

3. 疑问

实际上这里面还有一些隐含方式方法,感兴趣的可以尝试一下,欢迎分享。

  • 现在采用的是串口中断来处理接收问题,是否可以采用DMA接收中断来处理数据接收?就如同DMA发送中断来处理发送数据一样。

4. 参考文献

  1. 《STM32F10X参考手册》
  2. 《32位基于ARM微控制器STM32F101xx与STM32F103xx 固件函数库》
  3. STM32的串口空闲中断
  4. STM32的串口采用DMA方式接收数据测试
  5. STM32使用串口IDLE中断的两种接收不定长数据的方式

STM32基础分析——USART的DMA模式的更多相关文章

  1. STM32 ADC多通道转换DMA模式与非DMA模式两种方法(HAL库)

    一.非DMA模式(转) 说明:这个是自己刚做的时候百度出来的,不是我自己做出来的,因为感觉有用就保存下来做学习用,原文链接:https://blog.csdn.net/qq_24815615/arti ...

  2. 使用STM32的USART的同步模式Synchronous调戏SPI【usart模拟spi理论】

    [原创出品§转载请注明出处] 出处:http://www.cnblogs.com/libra13179/p/7064321.html 什么东西?? 我们先来看我们平常看到SPI的时序图(呵呵,要是忘记 ...

  3. 使用STM32的USART的同步模式Synchronous调戏SPI[2] 【实现spi 9bit】

    [原创出品§转载请注明出处] 出处:http://www.cnblogs.com/libra13179/p/7064533.html 上回说道使用USART的来模拟SPI通讯.说说一下我什么写这个的原 ...

  4. STM32的USART DMA传输(转)

    源:STM32的USART DMA传输 问题描述: 我有一个需求,AD采得一定数目的数据之后,由串口DMA发出,由于AD使用双缓冲,所以每次开始DMA的时候都需要重新设置开始的内存地址以及传输的数目( ...

  5. (三)stm32之串口通信DMA传输完成中断

    一.DMA功能简介 首先唠叨一下DMA的基本概念,DMA的出现大大减轻了CPU的工作量.在硬件系统中,主要由CPU(内核).外设.内存(SRAM).总线等结构组成,数据经常要在内存和外设之间,外设和外 ...

  6. stm32串口USART 硬件流控 --学习笔记

    流控的概念源于 RS232 这个标准,在 RS232 标准里面包含了串口.流控的定义.大家一定了解,RS232 中的"RS"是Recommend Standard 的缩写,即&qu ...

  7. STM32(11)——DMA

    简介: DMA:Direct Memory Access,直接存储器访问.DMA传输数据从一个地址空间复制到另外一个地址空间.当CPU初始化这个传输动作,传输动作本身就是DMA控制器来实现和完成.典型 ...

  8. stm32定时器时钟以及中间对齐模式

    在永磁同步电机的控制中,需要对电机的三相定子施加一定的电压,才能控制电机转动.现在用的较多的是SVPWM(SVPWM的具体原理会在后面另写一篇博客说明),要想产生SVPWM波形,需要控制的三相电压呈如 ...

  9. STM32串行通信USART解说笔记

    STM32串行通信USART程序例举链接:http://blog.csdn.net/dragon12345666/article/details/24883111 1.STM32串行通信USART的相 ...

随机推荐

  1. 鼠标相关操作(Cursor类及相关API)

    Cursor.visible:属性,显示或者隐藏鼠标.  Cursor.lockState = CursorLockMode.Locked:锁定鼠标到游戏窗口的中心. (CursorLockMode: ...

  2. Android Studio 升级到3.0 提示 java.lang.NoClassDefFoundError

    Android Studio 升级到3.0 提示 java.lang.NoClassDefFoundError 这个问题折腾了2个小时,最后解决了,Stack Overflow 上也有一次类似的问题, ...

  3. [对smartMenu.js改进] 解决右键菜单栏在边缘弹出后,移出视图区域无法操作的问题

    当用户在视图边缘(如右下角)右键召唤菜单栏的时候,菜单仍然从选中元素的右下角弹出,这时二级菜单栏一般都离开了视图区域,用户无法进一步操作. 这个问题挺常见的,原作者的留言板: 但是作者应该是已经不再维 ...

  4. 学习爬虫的day02 (用线程去爬虫 提高速度)

    通过lxml的方式去分析数据,将爬到的数据放到file中的html中代码如下# 用线程去爬虫 from urllib.request import Request from urllib.reques ...

  5. 深入浅出多线程——ReentrantLock (二)

    深入浅出多线程——ReentrantLock (一)文章中介绍了该类的基本使用,以及在源码的角度分析lock().unlock()方法.这次打算在此基础上介绍另一个极为重要的方法newConditio ...

  6. 基于文本图形(ncurses)的文本搜索工具 ncgrep

    背景 作为一个VIM党,日常工作开发中,会经常利用grep进行关键词搜索,以快速定位到文件.如图: 利用grep进行文本搜索 但是,这一过程会有两个效率问题: 展示的结果无法进行直接交互,需要手动粘贴 ...

  7. Linux中nginx手动安装

    本分类下有一个环境一键安装.那这背后发生了什么呢?咱们手动使用源码进行安装. 1.首先保证有一个能联网的centos. 2.百度 ningx 官网   点download  http://nginx. ...

  8. 【new File(String Path)加载资源问题】

    2017-12-17   15:07:38  [原创-wx] 一.我们在用IO流加载资源的时候,创建文件资源 1 File file = New File("String Path" ...

  9. Docker笔记四:Elasticsearch实例部署

    在运行或启动elasticsearch容器前,先在宿主机上执行 sysctl -w vm.max_map_count=262144: 解决" max virtual memory areas ...

  10. 读《淘宝技术这十年》 总结下web架构的发展

    关键词就两 分布式 缓存 分布式 数据库,应用服务器等的多节点部署,数据库的读写分离,剥离文件系统 缓存 数据缓存 静态页面缓存 php时代 最初LAMP起步 并将数据库做读写分离,拆分为主库+从库 ...