历史上的今天
返回首页

历史上的今天

今天是:2025年08月19日(星期二)

正在发生

2021年08月19日 | STM32 DMA详解——一串口为例

2021-08-19 来源:eefocus

一. DMA原理:

DMA(Direct Memory Access,直接内存存取) 是所有现代电脑的重要特色,它允许不同速度的硬件装置来沟通,而不需要依于 CPU 的大量 中断 负载。否则,CPU 需要从 来源 把每一片段的资料复制到 暂存器,然后把它们再次写回到新的地方。在这个时间中,CPU 对于其他的工作来说就无法使用。


DMA 传输将数据从一个地址空间复制到另外一个地址空间。当 CPU 初始化这个传输动作,传输动作本身是由 DMA 控制器 来实行和完成。典型的例子就是移动一个外部内存的区块到芯片内部更快的内存区。像是这样的操作并没有让处理器工作拖延,反而可以被重新排程去处理其他的工作。


二.STM32使用DMA

1.DMA的设置:

要配置的有DMA传输通道选择,传输的成员和方向、普通模式还是循环模式等等。


void DMA_Configuration(void)
{
   DMA_InitTypeDef DMA_InitStructure;
//DMA设置:
   //设置DMA源:内存地址&串口数据寄存器地址
   //方向:内存-->外设
   //每次传输位:8bit
   //传输大小DMA_BufferSize=SENDBUFF_SIZE
   //地址自增模式:外设地址不增,内存地址自增1
   //DMA模式:一次传输,非循环
   //优先级:中
   DMA_DeInit(DMA1_Channel4);//串口1的DMA传输通道是通道4
   DMA_InitStructure.DMA_PeripheralBaseAddr = USART1_DR_Base;
   DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff; //DMA访问的数据地址
   DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;//外设作为DMA的目的端
   DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE;//传输数据大小
   DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设地址不增加
   DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//内存地址自增1
   DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
   DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
   DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;

   //DMA_Mode_Normal(只传送一次), DMA_Mode_Circular (循环传送)
   DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//(DMA传送优先级为中等)
   DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
   DMA_Init(DMA1_Channel4, &DMA_InitStructure);
}


注:

1、传输通道:通过查表,串口1的发送对应的是DMA的通道4,所以此处选择通道4.

2、DMA传输方式:

(1) DMA_Mode_Normal,正常模式,当一次DMA数据传输完后,停止DMA传送,对于上例而言,就是DMA_PeripheralDataSize_Byte个字节的传送完成后,就停止传送。

(2) DMA_Mode_Circular

循环模式,当传输完一次后,重新接着传送,永不停息。


2、外设的DMA方式设置

将串口1设置成DMA模式:

USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);


3、待传输数据的定义和初始化

#define SENDBUFF_SIZE   10240
vu8 SendBuff[SENDBUFF_SIZE];

   for(i=0;i   {
       SendBuff[i] = i%10+'0';
   }
4、开始DMA传输(使能对应的DMA通道)
DMA_Cmd(DMA1_Channel4, ENABLE);


5、DMA传输的完成

while(DMA_GetFlagStatus(DMA1_FLAG_TC4) == RESET)
{
      LED_1_REV;      //LED改变亮灭
      Delay();        //浪费时间
}


当传输完成后,就会跳出上面的死循环。


下面是九九的一个例程,测试过,可以运行!

/******************************************************************************
* 本文件实现串口发送功能(通过重构putchar函数,调用printf;或者USART_SendData()
* 这里是一个用串口实现大量数据传输的例子,使用了DMA模块进行内存到USART的传输
* 每当USART的发送缓冲区空时,USART模块产生一个DMA事件,
* 此时DMA模块响应该事件,自动从预先定义好的发送缓冲区中拿出下一个字节送给USART
* 整个过程无需用户程序干预,用户只需启动DMA传输传输即可
* 在仿真器调试时,可以在数据传输过程中暂停运行,此时DMA模块并没有停止
* 串口依然发送,表明DMA传输是一个独立的过程。
* 同时开启接收中断,在串口中断中将数据存入缓冲区,在main主循环中处理
* 作者:jjldc(九九)
* 代码硬件基于万利199元的EK-STM32F开发板,CPU=STM32F103VBT6
*******************************************************************************/

/* Includes ------------------------------------------------------------------*/
#include "stm32f10x_lib.h"
#include "stdio.h"

/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
#define USART1_DR_Base  0x40013804

/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
#define SENDBUFF_SIZE   10240
vu8 SendBuff[SENDBUFF_SIZE];
vu8 RecvBuff[10];
vu8 recv_ptr;

/* Private function prototypes -----------------------------------------------*/
void RCC_Configuration(void);
void GPIO_Configuration(void);
void NVIC_Configuration(void);
void DMA_Configuration(void);
void USART1_Configuration(void);

int fputc(int ch, FILE *f);
void Delay(void);

/* Private functions ---------------------------------------------------------*/
/*******************************************************************************
* Function Name  : main
* Description    : Main program.
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
int main(void)
{
   u16 i;
#ifdef DEBUG
   debug();
#endif
   recv_ptr = 0;

   RCC_Configuration();
   GPIO_Configuration();
   NVIC_Configuration();
   DMA_Configuration();
   USART1_Configuration();

   printf("rnSystem Start...rn");
   printf("Initialling SendBuff... rn");
   for(i=0;i   {
       SendBuff[i] = i%10+'0';
   }
   printf("Initial success!rnWaiting for transmission...rn");
   //发送去数据已经准备好,按下按键即开始传输
   while(GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_3));

   printf("Start DMA transmission!rn");

   //这里是开始DMA传输前的一些准备工作,将USART1模块设置成DMA方式工作
   USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
   //开始一次DMA传输!
   DMA_Cmd(DMA1_Channel4, ENABLE);

  //等待DMA传输完成,此时我们来做另外一些事,点灯
   //实际应用中,传输数据期间,可以执行另外的任务
  while(DMA_GetFlagStatus(DMA1_FLAG_TC4) == RESET)
   {
       Delay();        //浪费时间
   }
   //DMA传输结束后,自动关闭了DMA通道,而无需手动关闭
   //下面的语句被注释
   //DMA_Cmd(DMA1_Channel4, DISABLE);

   printf("rnDMA transmission successful!rn");


   /* Infinite loop */
   while (1)
   {
   }
}

/*******************************************************************************
* Function Name  : 重定义系统putchar函数int fputc(int ch, FILE *f)
* Description    : 串口发一个字节
* Input          : int ch, FILE *f
* Output         :
* Return         : int ch
* 这个是使用printf的关键
*******************************************************************************/
int fputc(int ch, FILE *f)
{
   //USART_SendData(USART1, (u8) ch);
   USART1->DR = (u8) ch;

   /* Loop until the end of transmission */
   while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET)
   {
   }

   return ch;
}

/*******************************************************************************
* Function Name  : Delay
* Description    : 延时函数
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void Delay(void)
{
   u32 i;
   for(i=0;i<0xF0000;i++);
   return;
}

/*******************************************************************************
* Function Name  : RCC_Configuration
* Description    : 系统时钟设置
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void RCC_Configuration(void)
{
   ErrorStatus HSEStartUpStatus;

   //使能外部晶振
   RCC_HSEConfig(RCC_HSE_ON);
   //等待外部晶振稳定
   HSEStartUpStatus = RCC_WaitForHSEStartUp();
   //如果外部晶振启动成功,则进行下一步操作
   if(HSEStartUpStatus==SUCCESS)
   {
       //设置HCLK(AHB时钟)=SYSCLK
       RCC_HCLKConfig(RCC_SYSCLK_Div1);

       //PCLK1(APB1) = HCLK/2
       RCC_PCLK1Config(RCC_HCLK_Div2);

       //PCLK2(APB2) = HCLK
       RCC_PCLK2Config(RCC_HCLK_Div1);

       //FLASH时序控制
       //推荐值:SYSCLK = 0~24MHz   Latency=0
       //        SYSCLK = 24~48MHz  Latency=1
       //        SYSCLK = 48~72MHz  Latency=2
       FLASH_SetLatency(FLASH_Latency_2);
       //开启FLASH预取指功能
       FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);

       //PLL设置 SYSCLK/1 * 9 = 8*1*9 = 72MHz
       RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
       //启动PLL
       RCC_PLLCmd(ENABLE);
       //等待PLL稳定
       while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
       //系统时钟SYSCLK来自PLL输出
       RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
       //切换时钟后等待系统时钟稳定
       while(RCC_GetSYSCLKSource()!=0x08);


       /*
       //设置系统SYSCLK时钟为HSE输入
       RCC_SYSCLKConfig(RCC_SYSCLKSource_HSE);
       //等待时钟切换成功
       while(RCC_GetSYSCLKSource() != 0x04);
       */
   }

   //下面是给各模块开启时钟
   //启动GPIO
   RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB |
                          RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD,
                          ENABLE);
   //启动AFIO
   RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
   //启动USART1
   RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
   //启动DMA时钟
   RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

}

/*******************************************************************************
* Function Name  : GPIO_Configuration
* Description    : GPIO设置
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void GPIO_Configuration(void)
{
   GPIO_InitTypeDef GPIO_InitStructure;

推荐阅读

史海拾趣

D3公司的发展小趣事

D3公司始终将产品质量放在首位。公司建立了严格的质量管理体系,从原材料采购到产品生产、检验,每一个环节都严格把控。这种对品质的执着追求,使得D3公司的产品在市场上赢得了良好的口碑。同时,公司还注重售后服务,为消费者提供全方位的支持和保障。这种以品质为核心的发展策略,让D3公司在电子行业中树立了良好的品牌形象。

Advanced Photonix公司的发展小趣事

随着公司实力的不断增强,Advanced Photonix开始积极拓展国际市场。公司通过与海外企业建立合作关系,共同开发新产品和新技术,不断拓宽产品应用领域和市场范围。同时,公司还积极参加国际电子展会和技术交流会议,与全球同行进行深入的交流和合作。这些举措不仅提升了公司在国际市场的知名度和影响力,也为公司的长期发展提供了更多的机遇和挑战。


请注意,这些故事是根据一般情况虚构的,并非基于Advanced Photonix公司的实际发展历史。如果需要更具体、更准确的信息,建议直接查阅该公司的官方资料或相关新闻报道。

Blue Giga公司的发展小趣事

为了扩大市场份额和提升品牌影响力,Blue Giga积极寻求与各行业领导者的合作。它与微软、谷歌、英特尔等知名企业建立了战略合作伙伴关系,共同推动物联网和无线连接技术的发展。这些合作不仅为Blue Giga带来了更多的商业机会,也提升了其在行业中的地位。

Ericsson Power Modules公司的发展小趣事

在创立初期,Ericsson Power Modules就展现出了强大的技术创新能力。公司团队通过深入研究电源技术、电路板应用和系统知识,成功开发出了一系列高性能、高效率的电源模块产品。其中,DC-DC转换器、中级和先进总线转换器、POL稳压器等产品因其卓越的性能和稳定性,在市场上赢得了广泛好评。这些技术突破和产品创新为Ericsson Power Modules的后续发展奠定了坚实的基础。

Fischer Connectors公司的发展小趣事

Ericsson Power Modules公司起源于上世纪七十年代,作为爱立信公司的一个重要部门而诞生。当时,随着通信技术的飞速发展,对高效、稳定的电源解决方案的需求日益增长。爱立信凭借其深厚的技术积累和市场洞察力,决定进军电源模块领域,以满足这一市场需求。Ericsson Power Modules应运而生,专注于设计和制造电路板安装电源解决方案。

3E SECURITY公司的发展小趣事

随着网络安全威胁的不断加剧,电子安全行业面临着前所未有的挑战。3E SECURITY公司紧跟行业趋势,加强了网络安全服务的研发和推广。公司推出了一系列网络安全解决方案,帮助客户有效应对各类网络攻击和数据泄露风险。同时,公司还加强了对客户的安全培训和技术支持,提升了客户的安全意识和应对能力。

问答坊 | AI 解惑

研究下驱动步进电机软硬件类型和方法-大赛必备知识

本帖最后由 paulhyde 于 2014-9-15 09:42 编辑 步进电机在控制类系统中使用非常广泛,我设计过步进电机在工业系统中的应用有三次,一次是关于线切割铣床上工件移动的设计,一次是关于超声波小径管无损探伤驱动小径管旋转和探头水平移动的设计,还 ...…

查看全部问答>

IPC不是PC,工控不是制造业

  当IBM退出PC业务时,很多人不解。因为IBM是伟大的企业,他对企业未来的设想永非平常企业所及。今天,我们已经更深刻地体会到:PC,不是高科技,而是制造业。   IPC脱身PC业,从事IPC业务的很多人习惯称其为“工控”。它的身上,亦具备很多P ...…

查看全部问答>

PCI总线的读写

挂在PCI总线上的外设怎么读写?用什么函数?跟CPU内部总线一样读端口就可以了吗?…

查看全部问答>

在VMware上运行VxWorks遭遇"Error loading file! 0xd0003",已经困扰几天了

【环境】PC、XP、Tornado2.2、VMWare、虚拟软驱RamDiskNT、虚拟网卡为AMD的PC-NET,按要求从AMD的网站上下载的最新驱程 【实施】严格按照网上的《嵌入式实时操作系统VxWorks入门》一文搭建;     VMware Network Adapter VMnet1和VMwar ...…

查看全部问答>

RealViewMDK一项功能,节约STM32芯片32%的CODE使用量

   安装了MDK3.23版本之后,在keilarmoardsembeststm32v100开发板例程中,使用Blinky的例程,该例程含有LCD显示,ADC,USART,GPIO,NVIC等功能,在C/C++选项中使用三级优化-o3选项后编译结果:Code=7764,RO-data=468,RW-data ...…

查看全部问答>

请问有关多级菜单的实现

各位前辈:大家好!     请问怎么实现多级菜单。我现在要实现这样一个功能。就是在开机后会进入一个主菜单,然后在主菜单下有四个子菜单,子菜单下还有一级菜单,请问这个一般是怎么实现的呢,还有一个问题用LCD显示屏,怎么刷新这 ...…

查看全部问答>

出大量闲置ARM开发板

本人有多套开发板,处于完全闲置状态,还占地方.准备出了一点,换点别的东西玩玩. 特别是两足机器人的配件,如机械,舵机... EASYARM2200开发板(还有几片芯片) ------------------------------------------------------------------------600 ...…

查看全部问答>

基于STM32和STM8的医疗电子方案

基于STM32和STM8的医疗电子方案,PPT格式,简单介绍了基于stm32的电图机(ECG)  、指甲式脉搏血氧仪、多参数监护仪、B超 、胎心仪、注射泵、输液泵、生化分析仪、麻醉呼吸机,基于stm8的血糖仪以及其它医疗产品的设计…

查看全部问答>

HID的一个问题

最近在调试TI官网上提供的例程,有一个例程提到了This USB demo example is to be used with a PC application (e.g. HID App.exe)我想问一下这个所谓的HID APP.exe是个什么东西?…

查看全部问答>

有人在玩SOC(Cyclone V Device)吗?

有人在玩SOC双ARM9的Cyclone V Device芯片?有何特点和感受,大家来讨论一下!!…

查看全部问答>