stm32学习笔记:DMA

 每个DMA通道硬件触发源是不一样的,要使用某个外设的硬件触发源,就必须使用它连接的那个通道

12个独立可配置的通道:DMA1(7个通道),DMA2(5个通道)

每个通道都支持软件触发和特定的硬件触发

C8T6 DMA资源:DMA1

 ( 比如想把Flash里的一批数据转运到SRAM里,需要软件触发,使用软件触发之后,DMA会以最快都速度把这批数据转运)如果进行外设到存储器的转运,就不能一股脑地转运了,因为外设的数据是有一定的时机的,这时就需要用硬件触发   (比如转运ADC的数据,就需要每个ADC通道转换完成后,硬件触发一次DMA再转运)触发一次转运一次,这样数据才是正确的,才是我们想要的效果,所以存储器到存储器的数据转运一般使用软件触发,外设到存储器要硬件触发)

运算器和控制器一般会合在一起,叫做CPU,所以计算机的核心关键部分就是CPU和存储器

存储器的重要知识点:存储的内容和存储器的地址。外设也是存储器

ROM: 只读存储器,是一种非易失性,掉电不丢失的存储器

RAM:随机存储器,易失,掉电丢失

ROM 分为3块,分别为程序存储器Flash(主闪存),系统存储器和选项字节

RAM:运行内存SRAM,外设寄存器,内核外设寄存器。

1.寄存器是一种特殊的存储器,一方面,CPU可以对寄存器进行读写,就像读写运行内存一样。另一方面,寄存器的每一位背后,都连接了一根导线,可以用于控制外设电路的状态
2.比如:置引脚的高低电平,导通和断开开关,切换数据选择器,或者多位结合起来,当作计数器,数据寄存器等。所以寄存器是连接软件和硬件的桥梁,软件读写寄存器,就相当于再控制硬件的执行。
3.所以使用DMA进行数据转运就相当于:从某个地址取内容,再放到另一个地址里。为了高效有条理地访问存储器,STM32设计了总线矩阵,左端是主动单元,也就是存储器的访问权。右边是被动单元,只能被左边的主动单元读取。主动单元包括内核的DCode和系统总线,可以访问右边的存储器,DCode专门访问Flash,系统总线访问其他东西。
4.由于DMA要访问数据,所以DMA也必须要有访问的主动权主动单元除了内核CPU,剩下的就是DMA了,每个DMA通道可以分别设置他们转运数据的源地址和目的地址,这样他们就可以各自独立的进行工作了。
5.仲裁器的作用:虽然每个通道可以独立进行设置,但是DMA总线只有一条,所以所有的通道都只能分时复用这一条DMA总线。如果产生了冲突,就会由仲裁器,根据通道的优先级决定谁先用谁后用。
6.在总线矩阵里,也会有仲裁器,如果DMA和CPU都要访问同一个目标,那么DMA就会暂停CPU的访问,防止冲突。不过。此时总线仲裁器仍然会保证CPU得到一般的总线带宽,使CPU能够正常的工作。
7.AHB从设备:DMA自身的寄存器,因为DMA作为一个外设,它自己也会有相应的配置寄存器,上面连接在总线右边的AHB总线上,所以DMA既是总线矩阵的主动单元,可以读写各种存储器,也是AHB总线上的被动单元

 

  1.  DMA请求就是触发的意思,右边的触发源就是各个外设,DMA请求就是DMA的硬件触发源,比如ADC转换完成,串口就受到数据等。
  2. 需要触发DMA转运数据的时候就通过DMA请求线路向DMA发出硬件触发信号,之后DMA就可以执行数据转运的动作了。

DMA的各个部分和作用:

DMA总线:用于访问各个存储器,内部的多个通道可以进行独立的数据转运。

仲裁器:用于调度各个通道,防止产生冲突

AHB从设备:用于配置DMA参数

DMA请求:用于硬件触发DMA的数据转运

DMA基本结构 

画圈的是数据转换的站点,左边是外设寄存器站点,右边是存储器站点包括Flash和SRAM。

STM32存储器一般特指Flash和SRAM,不包含外设寄存器,外设寄存器一般直接称为外设,所以就是外设到存储器,存储器到存储器这样来描述。

 外设和存储器参数的作用:

外设和存储器起始地址作用:决定了数据从哪里来,到哪里去。

数据宽度:作用是指定一次转运要按多大的数据宽度来进行,可以选择字节Byte,半字HalhWord和字Word。字节是8位,半字是16位

地址是否自增作用:指定一次转运完成后,下一次转运要把地址移动到下一个位置去,相当于指针P++ 。比如ADC扫描模式,用DMA进行数据转运,外设地址是ADC_DR寄存器,寄存器的地址不用自增,不然就会移位到其他寄存器里面了。存储器地址需要自增,每转运一次,就往后移动一位,不然会覆盖上一个数据。

进行存储器到存储器的转运,就需要把其中一个存储器的地址放在外设的这个站点。只要在存储器里写Flash或SRAM的地址,他就会去这两个地方找数据。

站点虽然叫外设存储器,并不是说只能写寄存器的地址只是一个名字而已。

 传输计数器:用来指定总共需要转运几次,它是自减计数器,比如你给他写个5,那DMA就只能进行5次数据转运,转运过程中,每转运一次,计数器的数就会减1。当传输计数器减到0后,DMA不再进行数据转运。 另外,它减到0之后,之前自增的地址,也会恢复到起始地址的位置,以方便之后DMA开始新一轮的转运。
 

自动重装器:

作用:传输计数器减到0之后,是否要恢复到最初的值,比如最初的值是5,如果不使用自动重装器,那么转运5次之后就DMA结束。使用自动重装器,转运5次,计数器减到0之后,计数器就会立即重装到初始值5. 不重装,就是单次模式,重装就是循环模式

比如说要转运一个数组,一般是单次模式,转运一轮就结束了。如果是ADC扫描模式+连续转换,为了配合ADC。DMA也需要使用循环模式。和ADC差不多,都是指定一轮工作完成后,是不是立即开始下一轮工作
 

DMA触发控制:

决定DMA在什么时机进行转运,触发源有硬件触发和软件触发,具体选择哪个有M2M参数决定。

M2M就是(Memory to Memory)即存储器到存储器,选择1DMA就会选择软件触发

软件触发执行逻辑:以最快的速度,连续不断地触发DMA,争取快速完成传输计数器清零,完成这一轮的转换工作,给0就是硬件触发。

触发源可以选择:ADC,串口,定时器等,一般都是与外设有关的转运,需要一定的时机,比如ADC转换完成,串口收到数据,定时时间到等。所以需要使用硬件触发,在硬件达到这些时机时,传一个信号过来,触发DMA进行转运。

开关控制:

使用 DMA_Cmd函数,给DMA使能后,DMA准备就绪,可以进行转运

DMA转运的条件:

1.开关控制,DMA_Cmd必须使能

2.传输计数器必须大于0

3.触发源必须有触发信号,触发一次,转运一次,传输计数器自减一次。当传输计数器等于0,且没有自动重装时,无论是否触发,DMA都不会进行转运,此时需要DMA_Cmd给DISABLE关闭DMA再为传输计数器写入一个大于0的数,再DMA_Cmd,给ENABLE,开启DMA才能继续工作。在写传输计数器时,必须要先关闭DMA,在进行写入,不能开启时写入EN=0时不工作

硬件触发注意事项:使用相应的硬件触发需要选择对应的通道,不然就触发不了,选择哪个触发源由对应的外设是否开启DMA输出决定的。

比如要使用ADC1,就需要使用ADC_DMACmd函数进行使能,必须使用这个库函数开启ADC1这一路输出才有效。使用定时器3,会有TIM_DMACmd函数用来进行DMA输出控制,使用哪一个外设触发源,取决于把哪个通道开启了,全开启理论上都可以使用

开关控制开启之后,这七个触发源进入到仲裁器进行优先级判断,最终产生内部DMA1请求,优先级判断与中断类似,序号越小优先级越高,也可以在程序中配置优先级。

DMA进行转运的三个条件

DMA进行转运的三个条件:

1、开关控制,DMA_Cmd必须使能

2、传输计数器必须大于0

3、触发源,必须有触发信号。
注意写传输计数器时,必须要先关闭DMA,在进行,不能再DMA开启时,写传输计数器。

//在里面需要重新给传输计数器赋值,传输计数器赋值,必须要先给DMA失能,DMA_Cmd(DMA1_Channel1, DISABLE);//给传输计数器赋值DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size);//使能,此时DMA再次进行转运DMA_Cmd(DMA1_Channel1, ENABLE);

每个通道都有一个数据选择器,可以选择硬件触发和软件触发。EN并不是数据选择器的控制位,而是决定这个选择器要不要工作。EN=0,数据选择器不工作,EN=1,数据选择器工作。软件触发后面跟(M2M)的意思是当M2M位=1时,选择软件触发。每个通道的硬件触发源都不同,若想使用某个硬件触发源的话,必须使用它所在的通道,这就是硬件触发的注意事项。如你要使用ADC1来触发,就必须选择通道1.如果使用软件触发,那么通道就可以任意选择。

数据宽度与对齐:

如果转运的数据宽度一样,就是正常的一个个转运,如果数据宽度不一样,

比如源宽度是8位,目标宽度是16位,传输B0时,需要在前面高位补上0,传输结果就是00B0.

比如源宽度是16位,目标宽度是8位,传输B1B0时,需要把高位舍去,传输结果就是B0.

 

 具体工作流程:左边是ADC扫描模式的执行流程,在这里有7个通道,触发一次后,7个通道依次进行AD转换,然后将转换结果都放到ADC_DR数据寄存器里面,ADC是连续扫描,那DMA就可以使用自动重装,在ADC启动下一轮转换的时候,DMA也启动下一轮的转换。ADC和DMA同步工作。ADC扫描,在每隔单独的通道转换完成后,没有任何标志位,也不会触发中断。所以我们程序不太好判断某一个通道转换完成的时机是什么时候,但他会产生DMA请求,去触发DMA转运。

DMA库函数

void DMA_DeInit(DMA_Channel_TypeDef* DMAy_Channelx);  //恢复缺省配置
void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct);  //初始化
void DMA_StructInit(DMA_InitTypeDef* DMA_InitStruct);   //结构体初始化
void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);  //使能
void DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT, FunctionalState NewState);  //中断输出使能
void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber); //设置当前数据寄存器,给这个传输计数器写数据
uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);//获取当前数据寄存器,返回传输计数器的值,查看还剩多少数据没有转运
FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);//获取标志位
void DMA_ClearFlag(uint32_t DMAy_FLAG);  //清除标志位
ITStatus DMA_GetITStatus(uint32_t DMAy_IT);  //获取中断状态
void DMA_ClearITPendingBit(uint32_t DMAy_IT);//清除中断挂起位

初始化后立刻转运

MyDMA.c

#include "stm32f10x.h"                  // Device headeruint16_t MyDMA_Size;void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
{MyDMA_Size = Size;//RCC开启DMA的时钟,DMA是AHB总线的设备,所以用AHB开启时钟的函数RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//初始化DMADMA_InitTypeDef DMA_InitStructure;DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA;  //外设站点DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;  //数据宽度DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;  //是否自增DMA_InitStructure.DMA_MemoryBaseAddr = AddrB;  //存储器站点DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;  //数据宽度DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;  //是否自增DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;  //传输方向DMA_InitStructure.DMA_BufferSize = Size;  //缓存区大小,传输计数器DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;   //传输模式,是否使用自动重装,注意,循环模式和软件触发不能够同时使用,如果同时使用,DMA就会连续触发DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;  //选择是否是存储器到存储器,其实就是选择硬件触发还是软件触发,这里选择软件触发DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;  //优先级DMA_Init(DMA1_Channel1, &DMA_InitStructure);   //DMA1_Channel1选择是哪个DMA,也选择了DMA的哪个通道(软件触发,所以任意通道都可以)//因为这里是存储器到存储器,所以通道可任意选择//此处选择DMA1,通道1DMA_Cmd(DMA1_Channel1, DISENABLE);/*如果选择的是硬件出发,记得在对应的外设调用xxx_DMACmd,开启触发信号的输出*//*如果你需要DMA的中断,就调用DMA_ITConfig,开启中断输出,再在NVIC,配置相应的中断通道,然后写中断服务函数*//*在运行的过程中,如果转运完成,传输计数器清零,这是想再给传输计数器赋值,则需要DMA失能,写传输计数器,DMA使能*/
}
/*DMA转运的三个条件
1、开关控制,DMA_Cmd必须使能2、传输计数器必须大于03、触发源,必须有触发信号。
*/
void MyDMA_Transfer(void)
{DMA_Cmd(DMA1_Channel1, DISABLE);DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size);DMA_Cmd(DMA1_Channel1, ENABLE);while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);DMA_ClearFlag(DMA1_FLAG_TC1);
}

MyDMA.h

#ifndef __MYDMA_H
#define __MYDMA_Hvoid MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size);
void MyDMA_Transfer(void);#endif
main.c
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyDMA.h"//DataA,作为待转运的原数据
uint8_t DataA[] = {0x01, 0x02, 0x03, 0x04};
//DataB,作为转运数据的目的地(全局变量默认初始值为0)
uint8_t DataB[] = {0, 0, 0, 0};int main(void)
{OLED_Init();//显示dataAOLED_ShowHexNum(1, 1, DataA[0], 2);OLED_ShowHexNum(1, 4, DataA[1], 2);OLED_ShowHexNum(1, 7, DataA[2], 2);OLED_ShowHexNum(1, 10, DataA[3], 2);//显示dataBOLED_ShowHexNum(2, 1, DataB[0], 2);OLED_ShowHexNum(2, 4, DataB[1], 2);OLED_ShowHexNum(2, 7, DataB[2], 2);OLED_ShowHexNum(2, 10, DataB[3], 2);MyDMA_Init((uint32_t)DataA, (uint32_t)DataB, 4);  //因为需要传输4个数据,所以为4//转运后//显示dataAOLED_ShowHexNum(3, 1, DataA[0], 2);OLED_ShowHexNum(3, 4, DataA[1], 2);OLED_ShowHexNum(3, 7, DataA[2], 2);OLED_ShowHexNum(3, 10, DataA[3], 2);//显示dataBOLED_ShowHexNum(4, 1, DataB[0], 2);OLED_ShowHexNum(4, 4, DataB[1], 2);OLED_ShowHexNum(4, 7, DataB[2], 2);OLED_ShowHexNum(4, 10, DataB[3], 2);while (1){}
}

程序现象

转运多次


初始化后立刻转运的代码,并且转运一次之后,DMA停止工作,如果DataA的数据变化,需要再转运一次,怎么做呢? 此时需要给传输计数器赋值,调用一次MyDMA_Transfer(void),就再次启动一次DMA转运,在函数里面需要重新给传输计数器赋值,传输计数器赋值,必须要先给DMA失能,然后给传输计数器赋值,DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size);,然后给DMA使能。开始转运,但需要等待转运完成, while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);,然后清除标志位。

MyDMA.c

#include "stm32f10x.h"                  // Device header//全局变量
uint16_t MyDMA_Size;void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
{//将Size赋值给全局变量MyDMA_Size = Size;//RCC开启DMA的时钟,DMA是AHB总线的设备,所以用AHB开启时钟的函数RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//初始化DMADMA_InitTypeDef DMA_InitStructure;DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA;  //外设站点DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;  //数据宽度DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;  //是否自增DMA_InitStructure.DMA_MemoryBaseAddr = AddrB;  //存储器站点DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;  //数据宽度DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;  //是否自增DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;  //传输方向DMA_InitStructure.DMA_BufferSize = Size;  //缓存区大小,传输计数器DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;   //传输模式,是否使用自动重装DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;  //选择是否是存储器到存储器,其实就是选择硬件触发还是软件触发DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;  //优先级DMA_Init(DMA1_Channel1, &DMA_InitStructure);   //DMA1_Channel1选择是哪个DMA,也选择了DMA的哪个通道(软件触发,所以任意通道都可以)//不让DMA初始化后就立刻进行转运,而是等调用Transfer函数后才进行转运DMA_Cmd(DMA1_Channel1, DISENABLE);/*如果选择的是硬件出发,记得在对应的外设调用xxx_DMACmd,开启触发信号的输出*//*如果你需要DMA的中断,就调用DMA_ITConfig,开启中断输出,再在NVIC,配置相应的中断通道,然后写中断服务函数*//*在运行的过程中,如果转运完成,传输计数器清零,这是想再给传输计数器赋值,则需要DMA失能,写传输计数器,DMA使能*/
}
/*DMA转运的三个条件
1、开关控制,DMA_Cmd必须使能2、传输计数器必须大于03、触发源,必须有触发信号。
*///转运多次,需要给传输计数器重新赋值
//调用该函数,就再次启动一次DMA转运
//调用一次,再转运一次
void MyDMA_Transfer(void)
{//在里面需要重新给传输计数器赋值,传输计数器赋值,必须要先给DMA失能,DMA_Cmd(DMA1_Channel1, DISABLE);//给传输计数器赋值DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size);//使能,此时DMA再次进行转运DMA_Cmd(DMA1_Channel1, ENABLE);//等待转运完成,TC1为转运完成标志位while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);//标志位置1后需要手动清除DMA_ClearFlag(DMA1_FLAG_TC1);
}

MyDMA.h

#ifndef __MYDMA_H
#define __MYDMA_Hvoid MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size);
void MyDMA_Transfer(void);#endif
main.c
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyDMA.h"//DataA,作为待转运的原数据
uint8_t DataA[] = {0x01, 0x02, 0x03, 0x04};
//DataB,作为转运数据的目的地(全局变量默认初始值为0)
uint8_t DataB[] = {0, 0, 0, 0};int main(void)
{OLED_Init();//将原数组和目的数组的地址传递进函数,转运数据长度为4MyDMA_Init((uint32_t)DataA, (uint32_t)DataB, 4);OLED_ShowString(1, 1, "DataA");OLED_ShowString(3, 1, "DataB");OLED_ShowHexNum(1, 8, (uint32_t)DataA, 8);//之所以要强转类型,是因为DataA是指针类型OLED_ShowHexNum(3, 8, (uint32_t)DataB, 8);while (1){//第一步,自增,变化一下原数组DataA的测试数据/*在给 DataA 数组的元素递增的操作中,每次循环都对 DataA 数组的不同元素进行加一操作,即 DataA[0]++、DataA[1]++、DataA[2]++、DataA[3]++。这样做的目的是为了改变 DataA 数组中的值,以便在后续的代码中能够观察到数据传输的效果。通过递增操作,每次循环 DataA 数组的不同元素的值都会增加,这样在每次数据传输之前和之后都可以通过 OLED 显示来观察到数据的变化。*/DataA[0] ++;DataA[1] ++;DataA[2] ++;DataA[3] ++;//显示DataA和DataB(转运前)OLED_ShowHexNum(2, 1, DataA[0], 2);OLED_ShowHexNum(2, 4, DataA[1], 2);OLED_ShowHexNum(2, 7, DataA[2], 2);OLED_ShowHexNum(2, 10, DataA[3], 2);OLED_ShowHexNum(4, 1, DataB[0], 2);OLED_ShowHexNum(4, 4, DataB[1], 2);OLED_ShowHexNum(4, 7, DataB[2], 2);OLED_ShowHexNum(4, 10, DataB[3], 2);//延时1秒,方便观看Delay_ms(1000);//第三步 调用MyDMA_Transfer()函数,使用DMA进行数据转运MyDMA_Transfer();//显示DataA和DataB,检测数据是不是从DataA转运到DataB(转运后)OLED_ShowHexNum(2, 1, DataA[0], 2);OLED_ShowHexNum(2, 4, DataA[1], 2);OLED_ShowHexNum(2, 7, DataA[2], 2);OLED_ShowHexNum(2, 10, DataA[3], 2);OLED_ShowHexNum(4, 1, DataB[0], 2);OLED_ShowHexNum(4, 4, DataB[1], 2);OLED_ShowHexNum(4, 7, DataB[2], 2);OLED_ShowHexNum(4, 10, DataB[3], 2);//延时1秒,方便观看Delay_ms(1000);}
}

程序现象

ADC+DMA应用(两个不同的方法,最后实现同一功能)

ADC扫描模式 DMA数据转运
(使用ADC的扫描模式来实现多通道采集,然后使用DMA进行数据转运)

开启ADC到DMA的输出 

ADC单次扫描+DMA单次转运模式

AD.c
#include "stm32f10x.h"                  // Device headeruint16_t AD_Value[4];/* ADC扫描模式 + DMA数据转运 */
void AD_Init(void)
{//开启ADC1时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);//开启GPIOA时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//RCC开启DMA的时钟,DMA是AHB总线的设备,所以用AHB开启时钟的函数RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//该函数用来配置ADCCLK分频器的,他可以对APB2的72Mhz时钟选择2、4、6、8分频,输入到ADCCLKRCC_ADCCLKConfig(RCC_PCLK2_Div6);//PA0被初始化成模拟输入的引脚GPIO_InitTypeDef GPIO_InitStructure;//模拟输入模式,在AIN模式下,GPIO口是无效的,断开GPIO,防止GPIO口的输入输出对模拟电压造成干扰GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);//菜单上的1~4号空位,我填上了0~3这四个通道ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);  //通道0放到序列1ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);  //通道1放到序列2ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);  //通道2放到序列3ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5);  //通道3放到序列4//用结构体初始化ADCADC_InitTypeDef ADC_InitStructure;ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;  // 独立模式ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;  //右对齐ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;  // 不使用外部触发,也就是使用内部软件触发ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //单次转换还是连续转换ADC_InitStructure.ADC_ScanConvMode = ENABLE;  //扫描ADC_InitStructure.ADC_NbrOfChannel = 4;  //通道数目,我点了4个菜,你看前4个位置就可以了ADC_Init(ADC1, &ADC_InitStructure);/*配置DMA*//* DMA 可以想象为服务员 ADC厨师把菜做好后,DMA这个服务员要尽快把菜端出来,防止覆盖  *///初始化DMADMA_InitTypeDef DMA_InitStructure;DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;  //(端菜的)源头地址DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;  //数据宽度(我们想要DR寄存器低16位的数据)//外设寄存器只有一个,地址不用递增DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;  //不自增,始终转运同一个位置的数据DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;  //存储器站点DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;  //数据宽度DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;  //存储器地址自增DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;  //传输方向,外设站点是源DMA_InitStructure.DMA_BufferSize = 4;  //缓存区大小,传输计数器DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;   //传输模式,是否使用自动重装//触发源为ADC1,厨师每个菜做好了,教我一下,我再去端菜,这样才是合适的时机DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;  //选择是否是存储器到存储器,其实就是选择硬件触发还是软件触发,此处选择硬件触发DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;  //优先级//必须使用DMA1的通道1DMA_Init(DMA1_Channel1, &DMA_InitStructure);   //DMA1_Channel1选择是哪个DMA,也选择了DMA的哪个通道//开启ADC到DMA的输出ADC_DMACmd(ADC1, ENABLE);//不让DMA初始化后就立刻进行转运,而是等调用Transfer函数后才进行转运DMA_Cmd(DMA1_Channel1, ENABLE);//传输计数器不为零//DMA使能//但是触发源有信号,目前不满足,因为这里是硬件触发,ADC还没启动,不会有触发信号,所以dma是能后不会立刻工作//开启ADC电源ADC_Cmd(ADC1, ENABLE);//对ADC进行校准ADC_ResetCalibration(ADC1);  //复位校准while (ADC_GetResetCalibrationStatus(ADC1) == SET);  //等待复位校准完成(0位复位校准完成,1为初始化复位校准)ADC_StartCalibration(ADC1);  //开始校准while (ADC_GetCalibrationStatus(ADC1) == SET);  //等待校准完成/*至此,ADC初始化已经完成,ADC处于准备就绪的状态*/
}//因为ADC到DMA通道设定好了,而且自动运行,不需要判断标志位,使用DMA后,不需要其他方式转运
//此函数:调用该函数,ADC开始转换,连续扫描4个通道,DMA也同步进行转运,AD转换结果依次放在上面的AD_Value数组里
void AD_GetValue(void)
{//在里面需要重新给传输计数器赋值,传输计数器赋值,必须要先给DMA失能,DMA_Cmd(DMA1_Channel1, DISABLE);//给传输计数器赋值DMA_SetCurrDataCounter(DMA1_Channel1, 4);//使能,此时DMA再次进行转运DMA_Cmd(DMA1_Channel1, ENABLE);//ADC还是单次模式,还需要软件触发ADC转换ADC_SoftwareStartConvCmd(ADC1, ENABLE);//最后等待ADC转换和DMA转运完成,转运总是在转换之后,等待ADC转换完成的代码就不需要了//等待转运完成,TC1为转运完成标志位while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);//标志位置1后需要手动清除DMA_ClearFlag(DMA1_FLAG_TC1);}
AD.h
#ifndef __AD_H
#define __AD_H//把数据存到SRAM数组里,外部可调用
extern uint16_t AD_Value[4];void AD_Init(void);
void AD_GetValue(void);#endif
main.c
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"int main(void)
{OLED_Init();AD_Init();OLED_ShowString(1, 1, "AD0:");OLED_ShowString(2, 1, "AD1:");OLED_ShowString(3, 1, "AD2:");OLED_ShowString(4, 1, "AD3:");while (1){//调用getvalue,之后数据就直接跑搭配AD_Value数组里AD_GetValue();//显示结果OLED_ShowNum(1,5,AD_Value[0],4);  //通道1OLED_ShowNum(2,5,AD_Value[1],4);  //通道2OLED_ShowNum(3,5,AD_Value[2],4);  //通道3OLED_ShowNum(4,5,AD_Value[3],4);  //通道4Delay_ms(100);  //让他刷新慢些}
}

ADC连续扫描+DMA循环转运

总结:ADC连续扫描+DMA循环转运,此时硬件外设已经实现了相互配合和高度自动化,各种操作都是硬件自己完成,极大地减轻了软件负担,软件什么都不需要做,也不需要进行任何中断。硬件自动就把活干完。

AD.c
#include "stm32f10x.h"                  // Device headeruint16_t AD_Value[4];/* ADC扫描模式 + DMA数据转运 */
void AD_Init(void)
{//开启ADC1时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);//开启GPIOA时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//RCC开启DMA的时钟,DMA是AHB总线的设备,所以用AHB开启时钟的函数RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//该函数用来配置ADCCLK分频器的,他可以对APB2的72Mhz时钟选择2、4、6、8分频,输入到ADCCLKRCC_ADCCLKConfig(RCC_PCLK2_Div6);//PA0被初始化成模拟输入的引脚GPIO_InitTypeDef GPIO_InitStructure;//模拟输入模式,在AIN模式下,GPIO口是无效的,断开GPIO,防止GPIO口的输入输出对模拟电压造成干扰GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);//菜单上的1~4号空位,我填上了0~3这四个通道ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);  //通道0放到序列1ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);  //通道1放到序列2ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);  //通道2放到序列3ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5);  //通道3放到序列4//用结构体初始化ADCADC_InitTypeDef ADC_InitStructure;ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;  // 独立模式ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;  //右对齐ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;  // 不使用外部触发,也就是使用内部软件触发ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //单次转换还是连续转换ADC_InitStructure.ADC_ScanConvMode = ENABLE;  //扫描ADC_InitStructure.ADC_NbrOfChannel = 4;  //通道数目,我点了4个菜,你看前4个位置就可以了ADC_Init(ADC1, &ADC_InitStructure);/*配置DMA*//* DMA 可以想象为服务员 ADC厨师把菜做好后,DMA这个服务员要尽快把菜端出来,防止覆盖  *///初始化DMADMA_InitTypeDef DMA_InitStructure;DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;  //(端菜的)源头地址DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;  //数据宽度(我们想要DR寄存器低16位的数据)DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;  //不自增,始终转运同一个位置的数据DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;  //存储器站点DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;  //数据宽度DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;  //存储器地址自增DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;  //传输方向,外设站点是源DMA_InitStructure.DMA_BufferSize = 4;  //缓存区大小,传输计数器DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;   //传输模式,是否使用自动重装//触发源为ADC1,厨师每个菜做好了,教我一下,我再去端菜,这样才是合适的时机DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;  //选择是否是存储器到存储器,其实就是选择硬件触发还是软件触发,此处选择硬件触发DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;  //优先级//必须使用DMA1的通道1DMA_Init(DMA1_Channel1, &DMA_InitStructure);   //DMA1_Channel1选择是哪个DMA,也选择了DMA的哪个通道//开启ADC到DMA的输出ADC_DMACmd(ADC1, ENABLE);//不让DMA初始化后就立刻进行转运,而是等调用Transfer函数后才进行转运DMA_Cmd(DMA1_Channel1, ENABLE);//开启ADC电源ADC_Cmd(ADC1, ENABLE);//对ADC进行校准ADC_ResetCalibration(ADC1);  //复位校准while (ADC_GetResetCalibrationStatus(ADC1) == SET);  //等待复位校准完成(0位复位校准完成,1为初始化复位校准)ADC_StartCalibration(ADC1);  //开始校准while (ADC_GetCalibrationStatus(ADC1) == SET);  //等待校准完成/*至此,ADC初始化已经完成,ADC处于准备就绪的状态*///ADC触发后,ADC连续转换,DMA循环转运,两者一直在工作,始终把最新的转换结果刷新到SRAM数组里ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}
AD.h
#ifndef __AD_H
#define __AD_H//把数据存到SRAM数组里,外部可调用
extern uint16_t AD_Value[4];void AD_Init(void);#endif
main.c
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"int main(void)
{OLED_Init();AD_Init();OLED_ShowString(1, 1, "AD0:");OLED_ShowString(2, 1, "AD1:");OLED_ShowString(3, 1, "AD2:");OLED_ShowString(4, 1, "AD3:");//循环转换、扫描模式while (1){//显示结果OLED_ShowNum(1,5,AD_Value[0],4);  //通道1OLED_ShowNum(2,5,AD_Value[1],4);  //通道2OLED_ShowNum(3,5,AD_Value[2],4);  //通道3OLED_ShowNum(4,5,AD_Value[3],4);  //通道4Delay_ms(100);  //让他刷新慢些}
}

程序现象:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/237437.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Sqoop与其他数据采集工具的比较分析

比较Sqoop与其他数据采集工具是一个重要的话题,因为不同的工具在不同的情况下可能更适合。在本博客文章中,将深入比较Sqoop与其他数据采集工具,提供详细的示例代码和全面的内容,以帮助大家更好地了解它们之间的差异和优劣势。 Sq…

定时器问题(vue的问题)

我在a页面写一个定时,让他每秒钟打印一个1,然后跳转到b页面,此时可以看到,定时器依然在执行。这样是非常消耗性能的。如下图所示: 解决方法1 首先我在data函数里面进行定义定时器名称: data() {return {t…

Python中使用execfile实现R中的source功能,避免重复加载包导入函数

R中避免重复 在R中要加载多个包,进行全局的配置,定义全局变量,我们可以下一个config.R文件,在分析的代码开头source一下这个config.R文件即可避免每次都要加载包的冗余代码 using是之前定义的一个函数,作用是一次性加…

基于MAP算法的Turbo译码 -- 公式推导

到此为止,讲完了turbo译码器的子译码器基于MAP算法的译码过程。但在实际使用中,很少直接使用MAP算法进行译码。而是使用改进的LOG-MAP和MAX-LOG-MAP算法进行译码,因此译码的整体流程,包括外信息的计算以及先验信息的获取等。都在后…

canvas设置圆锥形渐变

查看专栏目录 canvas示例教程100专栏,提供canvas的基础知识,高级动画,相关应用扩展等信息。canvas作为html的一部分,是图像图标地图可视化的一个重要的基础,学好了canvas,在其他的一些应用上将会起到非常重…

慢 SQL 的优化思路

分析慢 SQL 如何定位慢 SQL 呢? 可以通过 slow log 来查看慢SQL,默认的情况下,MySQL 数据库是不开启慢查询日志(slow query log)。所以我们需要手动把它打开。 查看下慢查询日志配置,我们可以使用 show …

飞鱼CRM接入第三方系统 飞鱼API对接详细教程

场景描述 在白码低代码开发平台中,是支持外部crm系统的线索通过接口流入到白码系统里面,换而言之,只要外部的系统有线索api接口,白码系统可以接收线索并在白码系统上进行后续操作。本文以飞鱼crm系统为例,讲解如何接收…

二分图最大匹配——匈牙利算法详解

文章目录 零、前言一、红娘牵线二、二分图最大匹配2.1概念2.2交替路2.3增广路2.4匈牙利算法2.4.1算法原理2.4.2算法示例2.4.3代码实现 3.OJ练习3.1模板3.2棋盘覆盖3.3車的放置 零、前言 关于二分图的基本知识见:二分图及染色法判定 一、红娘牵线 一位红娘近日遇到一…

螺旋数字矩阵 - 华为OD统一考试

OD统一考试(C卷) 分值: 100分 题解: Java / Python / C++ 题目描述 疫情期间,小明隔离在家,百无聊赖,在纸上写数字玩。他发明了一种写法: 给出数字个数n和行数m (0 < n <= 999,0 < m <= 999),从左上角的1开始,按照顺时针螺旋向内写方式,依次写出2,3……

网络安全工具:通过监控分析日志数据保护企业网络

由于混合工作模式的兴起以及业务运营向云环境的迁移&#xff0c;企业网络变得更加分散和复杂&#xff0c;仅安装外围安全解决方案只会创建一个基本的防御层&#xff0c;系统、服务器和其他网络实体会生成记录所有网络活动的日志。集中式日志管理系统可以帮助管理员自动监控网络…

使用numpy处理图片——灰阶影像

大纲 载入图像灰阶处理lightnessaverageluminosity 灰阶&#xff08;Gray scale&#xff09;影像是每个像素只有一个采样颜色的图像。 载入图像 import numpy as np import PIL.Image as Imageimg Image.open(lena.png) data np.array(img)灰阶处理 我们有三种方法来生成这…

《ARM Linux内核源码剖析》读书笔记——0号进程(init_task)的创建时机

最近在读《ARM Linux内核源码剖析》&#xff0c;一直没有看到0号进程&#xff08;init_task进程)在哪里创建的。直到看到下面这篇文章才发现书中漏掉了set_task_stack_end_magic(&init_task)这行代码。 下面这篇文章提到&#xff1a;start_kernel()上来就会运行 set_task_…

迅腾文化用网络集成化生态系统助力品牌之路的坚实后盾

商业竞争激烈&#xff0c;品牌不仅是企业的标志和形象&#xff0c;更是其核心价值和竞争力的体现。然而&#xff0c;企业在品牌推广过程中面临着诸多如缺乏有效的渠道管理、品牌形象模糊以及竞争激烈的市场环境等。这些阻碍着企业的品牌发展和市场占有率的提升。本文将通过企业…

在centos系统安装mqtt

在CentOS系统上安装MQTT&#xff0c;通常意味着要安装一个MQTT代理&#xff08;broker&#xff09;&#xff0c;比如Mosquitto。下面是在CentOS上安装Mosquitto的步骤&#xff1a; 添加EPEL仓库&#xff1a; 由于Mosquitto可能不在CentOS默认的Yum仓库中&#xff0c;你可能需要…

国标GB28181视频监控EasyCVR平台:视频集中录制存储/云端录像功能及操作介绍

安防视频监控系统EasyCVR视频综合管理平台&#xff0c;采用了开放式的网络结构&#xff0c;可以提供实时远程视频监控、视频录像、录像回放与存储、告警、语音对讲、云台控制、平台级联、磁盘阵列存储、视频集中存储、云存储等丰富的视频能力&#xff0c;同时还具备权限管理、设…

Python新年文字烟花简单代码

简单的Python新年烟花代码示例&#xff1a; import random import timedef create_firework():colors [红色, 橙色, 黄色, 绿色, 蓝色, 紫色]flashes [爆裂, 闪光, 旋转, 流星, 喷射]color random.choice(colors)flash random.choice(flashes)print(f"发射一枚{color…

html5基础入门

html5基础语法与标签 前言前端开发零基础入门介绍前端开发行业介绍&#xff1a;大前端时代&#xff1a;前端开发主要技术介绍学习方法IDE简介vscode快捷键&#xff1a; 总结 HTML语法与基础标签互联网基本原理HTTP协议&#xff08;请求、响应&#xff09;什么是前端、后端&…

GitHub Copilot的使用方法和快捷按键

GitHub Copilot是GitHub与OpenAI合作开发的一款人工智能编码助手。它基于GPT&#xff08;Generative Pre-trained Transformer&#xff09;模型&#xff0c;可以为你提供代码补全、建议和生成的功能 使用方法&#xff1a; 安装插件&#xff1a; 首先&#xff0c;确保你的开发环…

免费简单好用的 webshell 在线检测:支持 php、jsp、asp等多格式文件

话不多说&#xff0c;直接上图上链接&#xff1a;https://rivers.chaitin.cn/?share3d4f2e8aaec211eea5550242c0a8170c 还是比较好用的&#xff0c;支持 PHP、JSP 文件 webshell 检测&#xff0c;看官方解释文档&#xff0c;引擎使用静态文本特征、骨架哈希、静态语义分析、动…

Windows 项目从0到1的部署

目录 一. 安装jdk 1.1 安装jdk 1.2 配置jdk的环境配置jdk 1.3 配置成功 二. 配置tomcat 2.1 启动tomcat 2.2 防火墙设置 三. 安装MySQL 3.1 安装步骤 3.2 内部连接 3.3 外部连接 四. 部署项目 4.1 项目部署 4.2 修改mysql的用户密码 一. 安装jdk 这里给大家准备好了jdk和…