Bootstrap

10.关于DMA

其实我是顺着上一篇ADC的思路来的,本来我也没想用过DMA来传数据,但是无奈使用ADC必须要用到只能较为浅显的
研究一下,其实DMA广泛应用于ADC 和 DAC 还有一些其他的外设中 ,应用十分广泛!!!
好吧,马上开始主题。


/*****************************************************************************************************/

关于DMA就简单介绍一种直接模式,这种模式最为常用,下面是手册对DMA的介绍,可以参照下

直接存储器访问 (DMA) 用于在外设与存储器之间以及存储器与存储器之间提供高速数据传
输。可以在无需任何 CPU 操作的情况下通过 DMA 快速移动数据。这样节省的 CPU 资源可
供其它操作使用。
两个 DMA 控制器总共有 16 个数据流(每个控制器 8 个),每一个 DMA 控制器都用于管理
一个或多个外设的存储器访问请求。每个数据流总共可以有多达 8 个通道(或称请求)。每
个通道都有一个仲裁器,用于处理 DMA 请求间的优先级。

很明显我们可以得出使用DMA有几个好处,首先是可以快速传输数据,其次可以节省资源,再者,
控制通道比较多,使用起来比较方便,差不多这几个好处明显够了。

简单介绍下,首先DMA有两个,一个用于存储器访问,是DMA1,然后另一个是用于外设访问的,当然是DMA2了
然后每个DMA都有八个通道,称为流,Stream 是的,然后每个流有多达8个通道,是不是感觉很复杂?
然后每个数据流可以配置为从存储器到外设,从外设到存储器,还有一个超级不经常用的从存储器到存储器
反正跟我是没用过,为什么要定义这个呢?是在于传输时候方向的,这个DMA也是单方向的所以必须定义
不同的方向,对应不同的方向使用不同的DMA,我想我应该是讲清楚了的。
1.首先是通道选择
  每个数据流都与一个 DMA 请求相关联,此 DMA 请求可以从 8 个可能的通道请求中选出。
  此选择由 DMA_SxCR 寄存器中的 CHSEL[2:0] 位控制。来自外设的 8 个请求(TIM、 ADC、 SPI、 I2C 等)
  独立连接到每个通道,具体的连接取决于产品实现情况。
  在官方手册里面有两个表,一个是DMA1的另一个是DMA2的,对应有各种外设对应的流和通道,所以你完全没必要去
  记得DMA1到底是从存储器到外设还是外设到存储器,根本就不用记,只需要知道你现在要用到那个外设,然后直接
  去表里面查找相应的外设的流与同道就可以了,然后在寄存器里面有个需要设置的,到底是存储器到外设还是
  外设到存储器,如果是在DMA1里面就是存储器到外设,另一个就是外设到存储器,是这样的,还有记得你使用的
  外设在表格中对应的流与通道,后面设置是需要用到的。
2.源、目标和传输模式

  1.外设到存储器模式
    在直接模式下(当 DMA_SxFCR 寄存器中的 DMDIS 值为“0”时),不使用 FIFO 的阈值
    级别控制:每完成一次从外设到 FIFO 的数据传输后,相应的数据立即就会移出并存储到目
    标中。
  2.存储器到外设模式
    使能这种模式(将 DMA_SxCR 寄存器中的 EN 位置 1)时,数据流会立即启动传输,从源完全填充 FIFO。
    在直接模式下(当 DMA_SxFCR 寄存器中的 DMDIS 值为“0”时),不使用 FIFO 的阈值级
    别。一旦使能了数据流, DMA 便会预装载第一个数据,将其传输到内部 FIFO。一旦外设请
    求数据传输, DMA 便会将预装载的值传输到配置的目标。然后,它会使用要传输的下一个数
    据再次重载内部空 FIFO。预装载的数据大小为 DMA_SxCR 寄存器中 PSIZE 位字段的值
3.指针递增
  就是说根据 DMA_SxCR 寄存器中 PINC 和 MINC 位的状态,外设和存储器指针在每次传输后可以自动向后递增或保持常量。
  如果你需要从外设到存储器存储100个数据,然后对于内部存储器可以设置为指针递增,意思就是当你设置
  两者之间的地址后,其实就是指针指向首地址,传送完第一个数据后,存储器指针会自动加1,以便继续存储接下来的数据
  就是这样,很简单的。但是外设就不需要,因为是外设提供给源源不断的数据的。
4.循环模式
  这个感觉和AD里面的多次转换一个样的,
  循环模式可用于处理循环缓冲区和连续数据流(例如 ADC 扫描模式)。可以使用 DMA_SxCR?寄存器中的 CIRC 位使能此特性。
  当激活循环模式时,要传输的数据项的数目在数据流配置阶段自动用设置的初始值进行加载,并继续响应 DMA 请求
  也就是说不断传输就是了,对于AD内部使用扫描模式这个正好和那个搭配,简直完美,数据不断的穿过来,我这个不断的
  传过去
5.可编程数据宽度、封装/解封、字节序
  1.看名字其实是这么难懂,其实说白了就是需要发送的数目,发送字节和接收字节的宽度,
  2.要传输的数据项数目必须在使能数据流之前编程到 DMA_SxNDTR(要传输数据项数目位,NDT)中,
  3.直接模式下(DMA_SxFCR 寄存器中的 DMDIS = 0) ,不能进行数据封装/解封。这种情
    况下,不允许源与目标的传输数据宽度不同,二者必须相等,并由 DMA_SxCR 中的 PSIZE
    位定义, MSIZE 位的状态是“无关”。
  数据封装/解封 意思就是如果发送和接收的宽度不同,首先得在使用FIFO模式下,然后系统会自动将两者转换
  但是在直接模式下是必须要求发送和接收的数据宽度必须相同的,然后需要设置发送多少数据,这些都是必须的
6.直接模式
  1.默认情况下, FIFO 以直接模式操作(将 DMA_SxFCR 中的 DMDIS 位置 1),不使用 FIFO
    阈值级别。如果在每次 DMA 请求之后,系统需要至/自存储器的立即和单独传输,这种模式 非常有用。
  2.意思就是使用直接模式更加快,不会缓存,来一个数据我就发过去,

7.DMA 传输完成
  传输完成的话可以设置中断,直接进去中断处理函数去处理
8.流控制器
  1.这也是一个很容易混淆的概念,控制要传输的数据数目的实体称为流控制器。就是说谁是主谁就控制需要发送
   多少个数据,那么谁就是流控制器,一般来说都是存储器作为流控制器,这是因为如果外设在做流控制器的话
   存储器并不知道对方到底传过来多少数据然后也不好控制,因此最好把存储器作为流控制器,
  2.流控制器可以是:
    DMA 控制器:在这种情况下,要传输的数据项的数目在使能 DMA 数据流之前由软件编
                程到 DMA_SxNDTR 寄存器。
    外设源或目标:当要传输的数据项的数目未知时属于这种情况。当所传输的是最后的数据时,
                  外设通过硬件向 DMA 控制器发出指示。仅限能够发出传输结束信号的外设支持此功能,
                  也就是 SDIO
9.这里有一份DMA配置的流程可以参考一下
  配置 DMA 数据流 x(其中 x 是数据流编号)时应遵守下面的顺序:

  1.如果使能了数据流,通过重置 DMA_SxCR 寄存器中的 EN 位将其禁止,然后读取此位
    以确认没有正在进行的数据流操作。将此位写为 0 不会立即生效,因为实际上只有所有
    当前传输都已完成时才会将其写为 0。 当所读取 EN 位的值为 0 时,才表示可以配置数
    据流。因此在开始任何数据流配置之前,需要等待 EN 位置 0。应将先前的数据块 DMA
    传输中在状态寄存器(DMA_LISR 和 DMA_HISR)中置 1 的所有数据流专用的位置 0,
    然后才可重新使能数据流。
  2. 在 DMA_SxPAR 寄存器中设置外设端口寄存器地址。外设事件发生后,数据会从此地址
     移动到外设端口或从外设端口移动到此地址。
  3. 在 DMA_SxMA0R 寄存器(在双缓冲区模式的情况下还有 DMA_SxMA1R 寄存器)中设
     置存储器地址。外设事件发生后,将从此存储器读取数据或将数据写入此存储器。
  4. 在 DMA_SxNDTR 寄存器中配置要传输的数据项的总数。每出现一次外设事件或每出现
     一个节拍的突发传输,该值都会递减。
  5. 使用 DMA_SxCR 寄存器中的 CHSEL[2:0] 选择 DMA 通道(请求)。
  6. 如果外设用作流控制器而且支持此功能,请将 DMA_SxCR 寄存器中的 PFCTRL 位置 1。
  7. 使用 DMA_SxCR 寄存器中的 PL[1:0] 位配置数据流优先级。  
  8. 配置 FIFO 的使用情况(使能或禁止,发送和接收阈值)。
  9. 配置数据传输方向、外设和存储器增量 / 固定模式、单独或突发事务、外设和存储器数
     据宽度、循环模式、双缓冲区模式和传输完成一半和/ 或全部完成,和/或 DMA_SxCR
     寄存器中错误的中断。
  10. 通过将 DMA_SxCR 寄存器中的 EN 位置 1 激活数据流。
     一旦使能了流,即可响应连接到数据流的外设发出的任何 DMA 请求。
     一旦在 AHB 目标端口上传输了一半数据,传输一半标志 (HTIF) 便会置 1,如果传输一半中
     断使能位 (HTIE) 置 1,还会生成中断。传输结束时,传输完成标志 (TCIF) 便会置 1,如果
     传输完成中断使能位 (TCIE) 置 1,还会生成中断。

10. 寄存器

    前面四个寄存器是以DMA1或者DMA2为指向的,前两个是状态寄存器,后面两个是清除状态寄存器
    是分开的需要注意,然后 后面的寄存器都是以 类似 DMA2_Stream3为指向的,前面是DMA1/2,后面是哪一个流

    接下来简单介绍一下需要注意的寄存器
    1.LISR   
    2.HISR
    3.LIFCR
    4.HIFCR

    5.CR
      在这里面可以配置通道,以及优先级,和数据位,推荐当数据位数不够的时候使用强制转换,避免
      因为双方数据位不对出现各种各样的BUG,然后可以设置指针递增模式和循环模式,还有传输方向
      很容易设置的,然后还有关键谁是流控制器,以及DMA使能位
    6.NDTR   
      这个是数据项数寄存器,就是说需要发送多少个数据,还有这里对循环模式也做了很明显的介绍

      传输完成后,此寄存器保持为零(数据流处于正常模式时),或者在以下情况下自动以先前编程的值重载:
      — 以循环模式配置数据流时。
      — 通过将 EN 位置“1”来重新使能数据流时
    7.PAR 
      外设地址寄存器,注意这里需要写入的是地址而不是值
      DMA2_Stream3->PAR = (uint32_t)&ADC2->DR; 注意取地址,然后注意PAR寄存器位宽是32位的当不够时需要转换
    8.M0AR
      存储器0的地址
      把需要存放数据的地方写进寄存器里面 

11. 这里还需要特别注意的一点是之前在设置DMA的一般步骤里面的第一条提过的,是在设置DMA的时候确保DMA没有
    这点特别重要,不然后面的值都写不进去,你可以查看官方手册里面寄存器下面写的都是基于DMA不使能的条件
    所以在设置之前首先确保DMA没有使能,然后确保相应的标志位被清零,然后才能进行相应的配置
    差不多就是这样了! 
    关于DMA的简易理解!!!   
;