STM32 DMA操作

https://blog.csdn.net/u014754841/article/details/79525637?utm_medium=distribute.pc_relevant.none-task-blog-title-7&spm=1001.2101.3001.4242

https://blog.csdn.net/faihung/article/details/78748033

写一个DMA驱动的主要工作包括:DMA通道申请、DMA中断申请、控制寄存器设置、挂入DMA等待队列、清除DMA中断、释放DMA通道

DMA的定义

直接存储器存取(Direct Memory Access,DMA)是计算机科学中的一种内存访问技术。它允许某些电脑内部的硬体子系统(电脑外设),可以独立地直接读写系统存储器,而不需绕道 CPU。在同等程度的CPU负担下,DMA是一种快速的数据传送方式。它允许不同速度的硬件装置来沟通,而不需要依于 CPU的大量中断请求。

DMA有什么用?

DMA是一种无需CPU的参与就可以让外设和系统内存之间进行双向数据传输的硬件机制。

直接存储器存取用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU的干预,通过DMA数据可以快速地移动。这就节省了CPU的资源来做其他操作。

有多少个DMA资源?

有两个DMA控制器,DMA1有7个通道,DMA2有5个通道。

数据从什么地方送到什么地方?
    • 外设到SRAM(I2C/UART等获取数据并送入SRAM); 
      SRAM的两个区域之间;
    • 外设到外设(ADC读取数据后送到TIM1控制其产生不同的PWM占空比);
    • SRAM到外设(SRAM中预先保存的数据送入DAC产生各种波形);

    • 还有一些目前还搞不清楚的。

DMA可以传递多少数据?

传统的DMA的概念是用于大批量数据的传输,但是我理解,在STM32中,它的概念被扩展了,也许更多的时候快速是其应用的重点。数据可以从1~65535个。

DMA控制器与仲裁器

现在越来越多的单片机采用DMA技术,提供外设和存储器之间或者存储器之间的高速数据传输。当 CPU 初始化这个传输动作,传输动作本身是由 ==DMA 控制器== 来实行和完成。STM32就有一个DMA控制器,它有7个通道,每个通道专门用来管理一个或多个外设对存储器访问的请求,还有一个==仲裁器==来协调各个DMA请求的优先权。

DMA 控制器和Cortex-M3核共享系统数据总线执行直接存储器数据传输。当CPU和DMA同时访问相同的目标(RAM或外设)时,DMA请求可能会停止 CPU访问系统总线达若干个周期,总线仲裁器执行循环调度,以保证CPU至少可以得到一半的系统总线(存储器或外设)带宽。

在发生一个事件后,外设发送一个请求信号到DMA控制器。DMA控制器根据通道的优先权处理请求。当DMA控制器开始访问外设的时候,DMA控制器立即发送给外设一个应答信号。当从DMA控制器得到应答信号时,外设立即释放它的请求。一旦外设释放了这个请求,DMA控制器同时撤销应答信号。如果发生更多的请求时,外设可以启动下次处理。

总之,每个DMA传送由3个操作组成: 
1. 从外设数据寄存器或者从DMA_CMARx寄存器指定地址的存储器单元执行加载操作。 
2. 存数据到外设数据寄存器或者存数据到DMA_CMARx寄存器指定地址的存储器单元。 
3. 执行一次DMA_CNDTRx寄存器的递减操作。该寄存器包含未完成的操作数目。

1.USART主要特性
1.1.全双工的,异步通信
1.2.NRZ标准格式
1.3.分数波特率发生器系统
    1.3.1发送和接收共用的可编程波特率,最高达4.5Mbits/s
1.4.可编程数据字长度(8位或9位)
1.5.可配置的停止位-支持1或2个停止位
1.6.LIN主发送同步断开符的能力以及LIN从检测断开符的能力
    1.6.1当USART硬件配置成LIN时,生成13位断开符;检测10/11位断开符
1.7.发送方为同步传输提供时钟
1.8.IRDA SIR 编码器解码器
    1.8.1.在正常模式下支持3/16位的持续时间
1.9.智能卡模拟功能
    1.9.1.智能卡接口支持ISO7816-3标准里定义的异步智能卡协议
    1.9.2.智能卡用到的0.5和1.5个停止位
1.10.单线半双工通信
1.11.可配置的使用DMA的多缓冲器通信
    1.11.1.在SRAM里利用集中式DMA缓冲接收/发送字节
1.12.单独的发送器和接收器使能位
1.13.检测标志
    1.13.1.接收缓冲器满
    1.13.2.发送缓冲器空
    1.13.3.传输结束标志
1.14.校验控制
    1.14.1.发送校验位
    1.14.2.对接收数据进行校验
1.15.四个错误检测标志

1.15.1.溢出错误
1.15.2.噪音错误
1.15.3.帧错误
1.15.4.校验错误

1.16.10个带标志的中断源
    1.16.1.CTS改变
    1.16.2.LIN断开符检测
    1.16.3.发送数据寄存器空
    1.16.4.发送完成
    1.16.5.接收数据寄存器满
    1.16.6.检测到总线为空闲
    1.16.7.溢出错误
    1.16.8.帧错误
    1.16.9.噪音错误

    1.16.10.校验错误
1.17.多处理器通信 -- 如果地址不匹配,则进入静默模式
1.18.从静默模式中唤醒(通过空闲总线检测或地址标志检测)
1.19.两种唤醒接收器的方式:地址位(MSB,第9位),总线空闲

2.USART功能概述
接口通过三个引脚与其他设备连接在一起(见下图)。任何USART双向通信至少需要两个脚:接收数据输入(RX)和发送数据输出(TX)。
RX:接收数据输入。通过过采样技术来区别数据和噪音,从而恢复数据。
TX:发送数据输出。当发送器被禁止时,输出引脚恢复到它的I/O端口配置。当发送器被激活,并且不发送数据时,TX引脚处于高电平。在单线和智能卡模式里,此I/O口被同时用于数据的发送和接收。
2.1.总线在发送或接收前应处于空闲状态
2.2.一个起始位
2.3.一个数据字(8或9位),最低有效位在前
2.4.0.5,1,1.5, 2个的停止位,由此表明数据帧的结束
2.5.使用分数波特率发生器 —— 12位整数和4位小数的表示方法。
2.6.一个状态寄存器(USART_SR)
2.7.数据寄存器(USART_DR)
2.8.一个波特率寄存器(USART_BRR), 12位的整数和4位小数
2.9.一个智能卡模式下的保护时间寄存器(USART_GTPR) 
在同步模式中需要下列引脚:
CK:发送器时钟输出。此引脚输出用于同步传输的 时钟, (在Start位和Stop位上没有时钟脉冲,软件可选地,可以在最后一个数据位送出一个时钟脉冲)。数据可以在RX上同步被接收。这可以用来控制带有移位寄存器的外部设备(例如LCD驱动器)。时钟相位和极性都是软件可编程的。在智能卡模式里,CK可以为智能卡提供时钟。
在IrDA模式里需要下列引脚:
IrDA_RDI: IrDA模式下的数据输入;
IrDA_TDO: IrDA模式下的数据输出。
下列引脚在硬件流控模式中需要:
nCTS: 清除发送,若是高电平,在当前数据传输结束时阻断下一次的数据发送;

nRTS: 发送请求,若是低电平,表明USART准备好接收数据。

注:1.DMA2仅存在于大容量产品和互联型产品。
       2.SPI/I2S3、UART4、TIM5、TIM6、TIM7和DAC的DMA请求仅存在于大容量产品和互联型产品。

       3.ADC3、SDIO和TIM8的DMA请求仅存在于大容量产品。

每次DMA传送由3个操作组成:
1.从外设数据寄存器或者从当前外设/存储器地址寄存器指示的存储器地址取数据,第一次传输时的开始地址是DMA_CPARx或DMA_CMARx寄存器指定的外设基地址或存储器单元。
2.存数据到外设数据寄存器或者当前外设/存储器地址寄存器指示的存储器地址,第一次传输时的开始地址是DMA_CPARx或DMA_CMARx寄存器指定的外设基地址或存储器单元。
3.执行一次DMA_CNDTRx寄存器的递减操作,该寄存器包含未完成的操作数目。

通道配置过程 
下面是配置DMA通道x的过程(x代表通道号): 
1. 在DMA_CPARx寄存器中设置外设寄存器的地址。发生外设数据传输请求时,这个地址将是数据传输的源或目标。 
2. 在DMA_CMARx寄存器中设置数据存储器的地址。发生外设数据传输请求时,传输的数据将从这个地址读出或写入这个地址。 
3. 在DMA_CNDTRx寄存器中设置要传输的数据量。在每个数据传输后,这个数值递减。 
4. 在DMA_CCRx寄存器的PL[1:0]位中设置通道的优先级。 
5. 在DMA_CCRx寄存器中设置数据传输的方向、循环模式、外设和存储器的增量模式、外设和存储器的数据宽度、传输一半产生中断或传输完成产生中断。 
6. 设置DMA_CCRx寄存器的ENABLE位,启动该通道。 

一旦启动了DMA通道,它既可响应连到该通道上的外设的DMA请求。当传输一半的数据后,半传输标志(HTIF)被置1,当设置了允许半传输中断位(HTIE)时,将产生一个中断请求。在数据传输结束后,传输完成标志(TCIF)被置1,当设置了允许传输完成中断位(TCIE)时,将产生一个中断请求。

中断
每个DMA通道都可以在DMA传输过半、传输完成和传输错误时产生中断。为应用的灵活性考虑,通过设置寄存器的不同位来打开这些中断。

DMA中断请求

注意:在大容量产品中,DMA2通道4和DMA2通道5的中断被映射在同一个中断向量上。在互联型产品中,DMA2通道4和DMA2通道5的中断分别有独立的中断向量。所有其他的DMA通道都有自己的中断向量。

 

DMA请求映像
DMA1控制器
从外设(TIMx[x=1、2、 3、 4]、ADC1、 SPI1、 SPI/I2S2、I2Cx[x=1、 2]和USARTx[x=1、2、 3])产生的7个请求,通过逻辑或输入到DMA1控制器,这意味着同时只能有一个请求有效。参见下图的DMA1请求映像。外设的DMA请求,可以通过设置相应外设寄存器中的控制位,被独立地开启或关闭。

DMA1请求映像

仲裁器根据通道请求的优先级来启动外设/存储器的访问。优先级分为两个等级:软件(4个等级:最高、高、中等、低)、硬件(有较低编号的通道比拥有较高编号的通道有较高的优先权)。

可以在DMA传输过半、传输完成和传输错误时产生中断。

STM32中DMA的不同中断(传输完成、半传输、传输完成)通过“线或”方式连接至NVIC,需要在中断例程中进行判断。

进行DMA配置前,不要忘了在RCC设置中使能DMA时钟。STM32的DMA控制器挂在AHB总线上。

DMA总共有7个通道,各个通道的DMA映射关系如下:

各个通道的DMA1请求一览

DMA2控制器
从外设(TIMx[5、 6、 7、 8]、 ADC3、 SPI/I2S3、 UART4、DAC通道1、 2和SDIO)产生的5个请求,经逻辑或输入到DMA2控制器,这意味着同时只能有一个请求有效。参见下图的DMA2请求映像。外设的DMA请求,可以通过设置相应外设寄存器中的DMA控制位,被独立地开启或关闭。
注意: DMA2控制器及相关请求仅存在于大容量产品和互联型产品中。

DMA2请求映像

各个通道的DMA2请求一览

DMA的传输标志位(CHTIFx、CTCIFx、CGIFx)由硬件设置为“1”,但需要软件清零,在中断服务程序中清除。当CGIFx(全局中断标志位)清零后,CHTIFx 和 CTCIFx均清零。

过程:怎样启用DMA?首先,众所周知的是初始化,任何设备启用前都要对其进行初始化,要对模块初始化,还要先了解该模块相应的结构及其函数,以便正确的设置;由于DMA较为复杂,我就只谈谈DMA的基本结构和和常用函数,这些都是ST公司提供在库函数中的。

1、 下面代码是一个标准DMA设置,当然实际应用中可根据实际情况进行裁减:

DMA_DeInit(DMA_Channel1);

上面这句是给DMA配置通道,根据ST提供的资料,STM3210Fx中DMA包含7个通道(CH1~CH7),也就是说可以为外设或memory提供7座“桥梁”(请允许我使用桥梁一词,我觉得更容易理解,哈哈,别“拍砖”呀!);

DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;

上面语句中的DMA_InitStructure是一个DMA结构体,在库中有声明了,当然使用时就要先定义 了;DMA_PeripheralBaseAddr是该结构体中一个数据成员,给DMA一个起始地址,好比是一个buffer起始地址,数据流程是:外设 寄存器à DMA_PeripheralBaseAddàmemory中变量空间(或flash中数据空间等),ADC1_DR_Address是我定义的一个地址 变量;

DMA_InitStructure.DMA_MemoryBaseAddr = (u32)ADC_ConvertedValue;

上面这句很显然是DMA要连接在Memory中变量的地址,ADC_ConvertedValue是我自己在memory中定义的一个变量;

DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;

上面的这句是设置DMA的传输方向,就如前面我所说的,DMA可以双向传输,也可以单向传输,这里设置的是单向传输,如果需要双向传输:把DMA_DIR_PeripheralSRC改成DMA_DIR_PeripheralDST即可。

DMA_InitStructure.DMA_BufferSize = 2;

上面的这句是设置DMA在传输时缓冲区的长度,前面有定义过了buffer的起始地址:ADC1_DR_Address ,为了安全性和可靠性,一般需要给buffer定义一个储存片区,这个参数的单位有三种类型:Byte、HalfWord、word,我设置的2个 half-word(见下面的设置);32位的MCU中1个half-word占16 bits。

DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;

上面的这句是设置DMA的外设递增模式,如果DMA选用的通道(CHx)有多个外设连接,需要使用外设递增模式:DMA_PeripheralInc_Enable;我的例子里DMA只与ADC1建立了联系,所以选用DMA_PeripheralInc_Disable

DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;

上面的这句是设置DMA的内存递增模式,DMA访问多个内存参数时,需要使用DMA_MemoryInc_Enable,当DMA只访问一个内存参数时,可设置成:DMA_MemoryInc_Disable。

DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;

上面的这句是设置DMA在访问时每次操作的数据长度。有三种数据长度类型,前面已经讲过了,这里不在叙述。

DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;

与上面雷同。在此不再说明。

DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;

上面的这句是设置DMA的传输模式:连续不断的循环模式,若只想访问一次后就不要访问了(或按指令操作来反问,也就是想要它访问的时候就访问,不要它访问的时候就停止),可以设置成通用模式:DMA_Mode_Normal

DMA_InitStructure.DMA_Priority = DMA_Priority_High;

上面的这句是设置DMA的优先级别:可以分为4级:VeryHigh,High,Medium,Low.

DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

上面的这句是设置DMA的2个memory中的变量互相访问的

DMA_Init(DMA_Channel1,&DMA_InitStructure);

前面那些都是对DMA结构体成员的设置,在次再统一对DMA整个模块做一次初始化,使得DMA各成员与上面的参数一致。

DMA_Cmd(DMA_Channel1,ENABLE);

至此,整个DMA总算设置好了,但是,DMA通道又是怎样与外设联系在一起的呢?

要使DMA与外设建立有效连接,这不是DMA自身的事情,是各个外设的事情,每个外设都有 一个xxx_DMACmd(XXXx,Enable )函数,如果使DMA与ADC建立有效联系,就使用ADC_DMACmd(ADC1,Enable); (这里我启用了ADC中的ADC1模块)。

一个简单的例子 transfer a word data buffer from FLASH memory to embedded SRAM memory. 
在V3.1.2库的位置

 1 STM32F10x_StdPeriph_Lib_V3.1.2\Project\STM32F10x_StdPeriph_Examples\DMA\FLASH_RAM
 2 
 3 
 4 DMA_DeInit(DMA1_Channel6);
 5   //peripheral base address
 6 DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)SRC_Const_Buffer;
 7   //memory base address   
 8 DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)DST_Buffer;
 9   //数据传输方向    Peripheral is source               
10 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
11 //缓冲区大小 Number of data to be transferred (0 up to 65535).数据传输数目     
12 DMA_InitStructure.DMA_BufferSize = BufferSize;
13    // the Peripheral address register is incremented       
14 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
15   //the memory address register is incremented
16 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
17 //the Peripheral data width       
18 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; 
19 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
20 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
21 DMA_InitStructure.DMA_Priority = DMA_Priority_High;
22 //the DMAy Channelx will be used in memory-to-memory transfer
23 //DMA通道的操作可以在没有外设请求的情况下进行,这种操作就是存储器到存储器模式。
24 DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;   
25 DMA_Init(DMA1_Channel6, &DMA_InitStructure);
26 
27 
28 DMA_ITConfig(DMA1_Channel6, DMA_IT_TC, ENABLE);
29 
30 
31 
32 DMA_Cmd(DMA1_Channel6, ENABLE);

外设的DMA请求映像

要使DMA与外设建立有效连接,这不是DMA自身的事情,是各个外设的事情,每个外设都有 一个

xxx_DMACmd(XXXx,Enable )函数,如果使DMA与ADC建立有效联系,就使用 ADC_DMACmd

(ADC1,Enable); (这里我启用了ADC中的ADC1模块)。

DMA_DeInit(DMA1_Channel1);
DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&AD_Value;   
//u16  AD_Value[2];   不加&应该也可以  数组名 代表地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = 2;      //############## 改了
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;  //##############     改了
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);


DMA_Cmd(DMA1_Channel1, ENABLE);


ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = ENABLE;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 2;      //##############     改了
ADC_Init(ADC1, &ADC_InitStructure);
//内部温度传感器  添加这一句 

ADC_TempSensorVrefintCmd(ENABLE);
//##############     改了

//################ Channel 10(电位器)
ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 1, ADC_SampleTime_13Cycles5);
//###### 内部温度传感器  Channel 16 ###################
ADC_RegularChannelConfig(ADC1, ADC_Channel_16, 2, ADC_SampleTime_55Cycles5);

  使能ADC1的DMA请求映像
  ADC_DMACmd(ADC1, ENABLE);


ADC_Cmd(ADC1, ENABLE);

   //使用之前一定要校准
ADC_ResetCalibration(ADC1);

while(ADC_GetResetCalibrationStatus(ADC1));


ADC_StartCalibration(ADC1);

while(ADC_GetCalibrationStatus(ADC1));


ADC_SoftwareStartConvCmd(ADC1, ENABLE);

 

posted @ 2020-12-14 16:18  Sean_hn  阅读(595)  评论(0)    收藏  举报