STM32之DMA

简介

DMA Direct Memory Access )直接存储器存取
(可以直接访问STM32内部存储器,如SRAM、程序存储器Flash和寄存器等)
DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源(外设指的是外设的寄存器)
12 个独立可配置的通道: DMA1 7 个通道), DMA2 5 个通道)
每个通道都支持软件触发和特定的硬件触发
(特定的:每个DMA通道的硬件触发源不一样)
(软件触发:会一股脑的把这批数据以最快的速度全部转运完成)
(硬件触发:硬件触发一次,转运一次)
(存储器到存储器的转运一般使用软件触发,外设到存储器的数据转运一般使用硬件触发)
STM32F103C8T6 DMA 资源: DMA1 7 个通道)

 存储器映像

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

RAM:随机存储器,是一种易失性、掉电丢失的存储器

DMA框图 

使用DMA进行数据转运可以归为一类问题—— 从某个地址取内容,然后再放到另一个地址去

        为了高效有条理地访问存储器,设计了一个总线矩阵,其左端是主动单元,拥有存储器的访问权,右边是被动单元,它们的存储器只能被左边的主动单元读写。

        主动单元中内核有DCode和系统总线,可以访问右边的存储器。DCode专门访问Flash。

        DMA需要转运数据,所以DMA也有访问的主动权,DMA1、DMA2和以太网都有各自的DMA总线。DMA1有七个通道,各个通道可以分别设置它们转运数据的源地址和目的地址,这样它们就可以各自独立地工作了。

       虽然多个通道可以独立转运数据,但是因为DMA的总线只有一条,所以的通道都只能分时复用这一条DMA总线,如果产生冲突,那就会由仲裁器,根据通道的优先级决定先转运哪一条通道的数据。(另外总线矩阵中也有一个仲裁器,如果DMA和CPU都要访问同一个目标,那么DMA就会暂停CPU的访问,以防止冲突)。

    因为DMA作为一个外设,它也有相对应的配置寄存器——AHB从设备。所以DMA即是总线矩阵的主动单元,可以读写各自存储器,也是AHB总线上的被动单元。   

 DMA请求:即触发,这条线路右边的触发源是各个外设,所以这个DMA请求就是DMA的硬件触发源(比如ADC转换完成,需要触发DMA转运数据时,就会通过这条线路,向DMA发出硬件触发信号)

基本结构

 起始地址:有外设端的起始地址和存储器端的起始地址,决定里的数据从哪里来到哪里去的

数据宽度:指定一次转运要按多大的数据宽度来进行,可以选择字节Byte(8位,uint8_t)、半字HalfWord(16位,uint16_t)和字Word(32位,uint32_t)

地址是否自增:指定一次转运完成后,下一次转运是不是要把地址移动到下一个位置去,相当于指针p++(比如ADC扫描模式,用DMA进行数据转运,外设地址是ADC_DR寄存器,寄存器这边显然地址是不用自增的,如果自增,下一次转运就跑到别的寄存器那里去了;存储器这边地址就需要自增了,每转运一次都需要往后挪个位置,不然就会导致下次再转把上次的数据覆盖掉)

传出计数器:是个自减的计数器,输入一个数字,每传输一次数据,计数就自减,当计数为0时,停止转换;

自动重装器:(即转换完一轮是否需要再来一轮)当传输计数器计数为0时,自动重装器就可以决定是否从计数的初始置重新开始计数,不重装,就是正常的单次模式,如果重装,则是循环模式。如果是ADC扫描模式+连续转换,为了配合ADC,则DMA也需要使用循环模式;

触发控制:分为硬件触发和软件触发,有M2M这个参数控制选择哪一种。

软件触发:以最快的速度连续不断地触发DMA,争取最快速把传输计数器清零,完成这一轮转换,不可以和自动重装器(循环模式)搭配使用,因为这样下去就是无限循环触发DMA,运用在存储器到存储器转运的情况

硬件触发:可以选择ADC、串口、定时器等,一般与外设有关的转运才会使用硬件触发,因为外设的转运(ADC转换完成,串口接收到信号等等)都有一个时机,当达到这个时机时触发DMA的转运。

DMA转运的条件

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

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

3、必须有触发信号

(当传输计数器为0时,无论怎么样都不能再触发DMA了,想要再次触发,就必须关闭开关控制,即给DMA_Cmd一个DISABLE信号,关闭DMA,再给传输计数器写入一个大于0的数,再打开开关控制,才能再次进行DMA转运,需要注意的是当传输计数器为0时,且开关控制在ENABLE状态下,是不能给传输计数器写入值的,需要关闭开关才可以写入)

DMA请求

硬件触发必须使用对应的通道,软件触发则随意

那么一个通道如何判断是哪个外设发出的请求信号呢,会有相关函数初始化相关外设的通道

数据宽度与对齐

总结:大数据转到小的里去,舍弃高位,小数据转到大的里去,高位补0

举个栗子

数据转运+DMA

需要把DataA的数据转运到DataB上去

应该如何配置DMA的数据

首先外设地址填DataA,存储器地址填DataB;数据宽度皆为uint8_t;地址是否自增:是;

传输计数器给7;不需要自动重装,软件触发(因为是存储器到存储器的转运,不需要等待时机)

 ADC扫描模式+DMA

扫描模式下,ADC会自动转换从序列1到最后的序列的数据,不需要地址自增,而DMA储存器需要地址自增;因为ADC转换完一个通道后既不会置标志位,也不会触发中断,按理说是无法体现“时机”的,但是据研究表明ADC转换完一个序列的数据后,会自动触发DMA的数据转运,所以这里选择硬件触发。ADC的扫描模式不使用DMA,功能会受到很大的限制。

 拓展

利用结构体访问寄存器的地址(原理详见江科大P247min)

比如要访问ADC1的DR寄存器

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"int main(void)
{OLED_Init();//要输入地址信息就需要强制转换为该类型OLED_ShowHexNum(1, 1, (uint32_t)&ADC1->DR, 8);while(1){}
}

OLED上显示地址为:4001244C,这个地址是固定的,可以通过数据手册查出来的。

先打开数据手册的2.3存储器映像,可查到ADC1的地址起始位为0x40012400

然后再打开11.12.15ADC寄存器地址映像,可查到ADC_DR的偏移量为4C

综合可得到ADC1_DR的地址为0x4001244C。

如果想查某个寄存器的地址,可以先通过查存储器映像,查出其起始地址,然后再在外设的寄存器总表中查其偏移量,即可计算得出其地址了。

代码实操

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);

 DMA设置当前数据寄存器(即给传输计数器写数据的)

void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber); 

 DMA获取当前数据寄存器(返回传输计数器的值)

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);

按照下图编写代码

先建立一个数组作为传输的数据,建立另一个数组用于接收数据

 再建立一个在System中建立模块(因为DMA不涉及外围硬件电路)

1、RCC开启DMA时钟

	//开启时钟,DMA是AHB总线的设备RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

2、调用DMA_Init初始化各个参数

//DMA初始化DMA_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;//传输方向//SRC-从外设站点传输到存储器站点,DST-从存储器传输到外设站点DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//缓冲器大小(传输计数器)DMA_InitStructure.DMA_BufferSize = BufferSize;//传输模式(是否启用自动重装)(不自动重装)DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//是否是存储器到存储器(硬件触发还是软件触发)//Enable-软件触发,Disable-硬件触发DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;//优先级(一个随便选DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//y决定是哪个DMA,x决定是DMA的哪个通道DMA_Init(DMA1_Channel1, &DMA_InitStructure);

3、开关控制

	DMA_Cmd(DMA1_Channel1, ENABLE);

(还可以调用中断函数)

得初始化函数

#include "stm32f10x.h"                  // Device header
//(传出数据的数组,传入数据的数组,传输数据的数量)
void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t BufferSize)
{//开启时钟,DMA是AHB总线的设备RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//DMA初始化DMA_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;//传输方向//SRC-从外设站点传输到存储器站点,DST-从存储器传输到外设站点DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//缓冲器大小(传输计数器)DMA_InitStructure.DMA_BufferSize = BufferSize;//传输模式(是否启用自动重装)(不自动重装)DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//是否是存储器到存储器(硬件触发还是软件触发)//Enable-软件触发,Disable-硬件触发DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;//优先级(一个随便选DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//y决定是哪个DMA,x决定是DMA的哪个通道DMA_Init(DMA1_Channel1, &DMA_InitStructure);DMA_Cmd(DMA1_Channel1, ENABLE);
}

在主函数中调用一下测试结果

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyDMA.h"uint8_t DataA[]={0x01, 0x02, 0x03, 0x04};//需要转运的数据
uint8_t DataB[4];//用于存储数据的数组int main(void)
{OLED_Init();//转运前OLED_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);OLED_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);//转运后OLED_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);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);while(1){}
}

第一二行分别是转运前的DataA和DataB,第三四行分别是转运前的DataA和DataB 

 测试得出转运结果没问题

当需要转运的数据发生变化时,需要重新转运则就会需要重新给传输计数器写入新的值,这还需要关闭DMA的开关,再打开其开关。

得出传输函数

void MyDMA_Transfer(void)
{//关闭DMA,写入传输计数器的值DMA_Cmd(DMA1_Channel1, DISABLE);DMA_SetCurrDataCounter(DMA1_Channel1, Size);//开启DMADMA_Cmd(DMA1_Channel1, ENABLE);//获取标志位状态(即是否完成传输)while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);//清除标志位(方便下次继续传输)DMA_ClearFlag(DMA1_FLAG_TC1);
}

同时也需要修改主函数

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyDMA.h"uint8_t DataA[]={0x01, 0x02, 0x03, 0x04};//需要转运的数据
uint8_t DataB[4];//用于存储数据的数组int main(void)
{OLED_Init();OLED_ShowString(1, 1, "DataA:");OLED_ShowString(3, 1, "DataB:");MyDMA_Init((uint32_t)DataA, (uint32_t)DataB, 4);//显示两个数组的地址OLED_ShowHexNum(1, 8, (uint32_t)DataA, 8);OLED_ShowHexNum(3, 8, (uint32_t)DataB, 8);while(1){//模拟数据的变化for(int i=0;i<4;i++){DataA[i]++;}	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);Delay_ms(1000);MyDMA_Transfer();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);Delay_ms(1000);}
}

这样DataA模拟了数据会变化的情况,这样就实现了每过一秒DataA的数据都+1,然后每过一秒转运一次DataA的数据到DataB中。

DMA+AD多通道

单次转换+扫描模式

在AD多通道的代码基础上做修改

在ADC使能之前加入DMA初始化的代码

部分需要稍作修改

在配置完GPIO口后添加

	//选择规则组的输入通道ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5);

再把ADC修改为扫描模式,扫描前4个通道 

	//扫描模式(ENABLE-扫描模式 or DISABLE-非扫描模式)ADC_InitStructure.ADC_ScanConvMode = ENABLE;//通道数目(指定在扫描模式下指定用到几个通道0~16)//在非扫描模式下,填任何数值都没用ADC_InitStructure.ADC_NbrOfChannel = 4;

 DMA初始化——首先外设站点的起始地址改为ADC1的DR寄存器(存储数据的寄存器)

再改为以半字为单位传输,外设站点的自增改为Disable

再建立一个数组接收传输的数据,并强制转换其地址填入存储器的起始地址中,同样的改为以半字为单位输入,保持地址自增

传输计数器给4(因为接入的一个电位器和三个传感器作为实验对象)

软件触发改为硬件触发(ADC1触发)

	//DMA初始化DMA_InitTypeDef DMA_InitStructure;//外设站点的起始地址、数据宽度、是否自增//ADC1的地址DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;//以半字为单位传输DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//非自增DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//存储器的起始地址、数据宽度、是否自增DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ADCValue;//存储数据的数组DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_HalfWord;DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//传输方向//SRC-从外设站点传输到存储器站点,DST-从存储器传输到外设站点DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//缓冲器大小(传输计数器)DMA_InitStructure.DMA_BufferSize = 4;//传输模式(是否启用自动重装)(不自动重装)DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//是否是存储器到存储器(硬件触发还是软件触发)//Enable-软件触发,Disable-硬件触发DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//优先级(一个随便选DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//y决定是哪个DMA,x决定是DMA的哪个通道DMA_Init(DMA1_Channel1, &DMA_InitStructure);//打开DMADMA_Cmd(DMA1_Channel1, ENABLE);

 但是在打开DMA后,并不能立即配合ADC传输数据,因为通道一还有其他外设,所以需要添加一个在ADC中开启DMA输出信号的函数

void ADC_DMACmd(ADC_TypeDef* ADCx, FunctionalState NewState);

 

ADC_DMACmd(ADC1, ENABLE);

再修改一下获取转换值的函数

void AD_GetValue(void)
{//关闭DMA,写入传输计数器的值DMA_Cmd(DMA1_Channel1, DISABLE);DMA_SetCurrDataCounter(DMA1_Channel1, 4);//开启DMADMA_Cmd(DMA1_Channel1, ENABLE);//软件触发(启动)ADC_SoftwareStartConvCmd(ADC1, ENABLE);//获取标志位状态(即是否完成传输)while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);//清除标志位(方便下次继续传输)DMA_ClearFlag(DMA1_FLAG_TC1);
}

然后再在主函数中调用,查看结果

#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){AD_GetValue();OLED_ShowNum(1, 5, ADValue[0], 4);OLED_ShowNum(2, 5, ADValue[1], 4);OLED_ShowNum(3, 5, ADValue[2], 4);OLED_ShowNum(4, 5, ADValue[3], 4);Delay_ms(100);}
}

 此时会出现BUG,表现为两个值不稳定,两个值为0,这是因为我们在配置DMA时,把外设站点的输出单位直接复制到了存储器的输出单位中

	DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_HalfWord;

此时就会出现显示BUG,而且这个BUG需要非常细心的寻找才可能找到

改回来后显示就没问题了。

AD.c

#include "stm32f10x.h"                  // Device headeruint16_t ADValue[4];void AD_Init(void)
{//开启时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);//DMA是AHB总线的设备RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//6分频RCC_ADCCLKConfig(RCC_PCLK2_Div6);//配置GPIO口GPIO_InitTypeDef GPIO_InitStructure;//AIN模拟输入//在AIN模式下GPIO口无效,即断开GPIO口//防止GPIO输入输出对模拟电压造成干扰(ADC专属模式)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);//选择规则组的输入通道ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5);//初始化ADC(单次转换,扫描模式)ADC_InitTypeDef ADC_InitStructure;//ADC工作模式(独立模式)ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//数据对齐(右对齐)ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//外部触发转换选择(外部触发源选择)(None,内部软件触发)ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//连续转换模式(ENABLE-连续模式 or DISABLE-非连续模式)ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//扫描模式(ENABLE-扫描模式 or DISABLE-非扫描模式)ADC_InitStructure.ADC_ScanConvMode = ENABLE;//通道数目(指定在扫描模式下指定用到几个通道0~16)ADC_InitStructure.ADC_NbrOfChannel = 4;ADC_Init(ADC1, &ADC_InitStructure);//DMA初始化DMA_InitTypeDef DMA_InitStructure;//外设站点的起始地址、数据宽度、是否自增//ADC1的地址DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;//以半字为单位传输DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//非自增DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//存储器的起始地址、数据宽度、是否自增DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ADValue;//存储数据的数组DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//传输方向//SRC-从外设站点传输到存储器站点,DST-从存储器传输到外设站点DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//缓冲器大小(传输计数器)DMA_InitStructure.DMA_BufferSize = 4;//传输模式(是否启用自动重装)(不自动重装)DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//是否是存储器到存储器(硬件触发还是软件触发)//Enable-软件触发,Disable-硬件触发DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//优先级(一个随便选DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//y决定是哪个DMA,x决定是DMA的哪个通道DMA_Init(DMA1_Channel1, &DMA_InitStructure);//打开DMADMA_Cmd(DMA1_Channel1, ENABLE);//开启ADC到DMA输出信号ADC_DMACmd(ADC1, ENABLE);//开启ADC电源ADC_Cmd(ADC1, ENABLE);//校准//复位校准ADC_ResetCalibration(ADC1);//获取复位校准状态//标志位为1时,表示正在进行复位校准//标志位为0时,表示复位校准结束,则我们要保证复位校准成功//当复位校准未完成就一直循环等待其完成while(ADC_GetResetCalibrationStatus(ADC1) == SET);//开始校准ADC_StartCalibration(ADC1);//获取开始校准状态while(ADC_GetCalibrationStatus(ADC1) == SET);
}void AD_GetValue(void)
{//关闭DMA,写入传输计数器的值DMA_Cmd(DMA1_Channel1, DISABLE);DMA_SetCurrDataCounter(DMA1_Channel1, 4);//开启DMADMA_Cmd(DMA1_Channel1, ENABLE);//软件触发(启动)ADC_SoftwareStartConvCmd(ADC1, ENABLE);//获取标志位状态(即是否完成传输)while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);//清除标志位(方便下次继续传输)DMA_ClearFlag(DMA1_FLAG_TC1);
}

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){AD_GetValue();OLED_ShowNum(1, 5, ADValue[0], 4);OLED_ShowNum(2, 5, ADValue[1], 4);OLED_ShowNum(3, 5, ADValue[2], 4);OLED_ShowNum(4, 5, ADValue[3], 4);Delay_ms(100);}
}

多次转换+扫描模式

只需把ADC改为多次扫描,DMA自动重装,把软件触发直接放到初始化函数中,不需要GetValue函数也可执行

#include "stm32f10x.h"                  // Device headeruint16_t ADValue[4];void AD_Init(void)
{//开启时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);//DMA是AHB总线的设备RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//6分频RCC_ADCCLKConfig(RCC_PCLK2_Div6);//配置GPIO口GPIO_InitTypeDef GPIO_InitStructure;//AIN模拟输入//在AIN模式下GPIO口无效,即断开GPIO口//防止GPIO输入输出对模拟电压造成干扰(ADC专属模式)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);//选择规则组的输入通道ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5);//初始化ADC(单次转换,扫描模式)ADC_InitTypeDef ADC_InitStructure;//ADC工作模式(独立模式)ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//数据对齐(右对齐)ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//外部触发转换选择(外部触发源选择)(None,内部软件触发)ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//连续转换模式(ENABLE-连续模式 or DISABLE-非连续模式)ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;//扫描模式(ENABLE-扫描模式 or DISABLE-非扫描模式)ADC_InitStructure.ADC_ScanConvMode = ENABLE;//通道数目(指定在扫描模式下指定用到几个通道0~16)ADC_InitStructure.ADC_NbrOfChannel = 4;ADC_Init(ADC1, &ADC_InitStructure);//DMA初始化DMA_InitTypeDef DMA_InitStructure;//外设站点的起始地址、数据宽度、是否自增//ADC1的地址DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;//以半字为单位传输DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//非自增DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//存储器的起始地址、数据宽度、是否自增DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ADValue;//存储数据的数组DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//传输方向//SRC-从外设站点传输到存储器站点,DST-从存储器传输到外设站点DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//缓冲器大小(传输计数器)DMA_InitStructure.DMA_BufferSize = 4;//传输模式(是否启用自动重装)(不自动重装)DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;//是否是存储器到存储器(硬件触发还是软件触发)//Enable-软件触发,Disable-硬件触发DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//优先级(一个随便选DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//y决定是哪个DMA,x决定是DMA的哪个通道DMA_Init(DMA1_Channel1, &DMA_InitStructure);//打开DMADMA_Cmd(DMA1_Channel1, ENABLE);//开启ADC到DMA输出信号ADC_DMACmd(ADC1, ENABLE);//开启ADC电源ADC_Cmd(ADC1, ENABLE);//校准//复位校准ADC_ResetCalibration(ADC1);//获取复位校准状态//标志位为1时,表示正在进行复位校准//标志位为0时,表示复位校准结束,则我们要保证复位校准成功//当复位校准未完成就一直循环等待其完成while(ADC_GetResetCalibrationStatus(ADC1) == SET);//开始校准ADC_StartCalibration(ADC1);//获取开始校准状态while(ADC_GetCalibrationStatus(ADC1) == SET);//软件触发(启动)ADC_SoftwareStartConvCmd(ADC1, ENABLE);}

这样就也可以实现目标

同时还可以再优化代码,添加一个定时器,定时器触发ADC,ADC触发DMA转运

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

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

相关文章

pytorch函数reshape()和view()的区别及张量连续性

目录 1.view() 2.reshape() 3.引用和副本&#xff1a; 4.区别 5.总结 在PyTorch中&#xff0c;tensor可以使用两种方法来改变其形状&#xff1a;view()和reshape()。这两种方法的作用是相当类似的&#xff0c;但是它们在实现上有一些细微的区别。 1.view() view()方法是…

古记事法:Windows 下 16 位汇编环境搭建指南(DOSBox-X 篇)

文章目录 参考环境DOSBox-XWOWWindows On Windows 产生的原因Windows On Windows 的工作原理WOW16 的结束与 WOW64 的未来 在现代操作系统中运行 16 位应用程序DOSBox-X 16 位汇编环境的搭建应用准备挂载自动挂载dosbox-x.conf配置工具 参考 项目描述搜索引擎Bing、GoogleAI 大…

二极管的直流等效电路和微变等效电路

二级管的主要参数 1.IF&#xff08;最大整流的电流&#xff09; 二极管长期工作做能够通过电流的平均最大值&#xff1a;物理意义&#xff1a;功率电流值。 2.UR 二极管最高反向工作电压 需要留有裕度&#xff0c;通常能达到一半的裕度&#xff1b;UR不能等于UBR。 3.IR 未击穿…

自动驾驶技术:现状与未来

自动驾驶技术&#xff1a;现状与未来 文章目录 引言自动驾驶技术的现状自动驾驶技术的挑战自动驾驶技术的未来结论结论 2023星火培训【专项营】Apollo开发者社区布道师倾力打造&#xff0c;包含PnC、新感知等的全新专项课程上线了。理论与实践相结合&#xff0c;全新的PnC培训不…

傅里叶系列 P1 的定价选项

如果您想了解更多信息&#xff0c;请查看第 2 部分和第 3 部分。 一、说明 这是第一篇文章&#xff0c;我将帮助您获得如何使用这个新的强大工具来解决金融中的半分析问题并取代您的蒙特卡洛方法的直觉。 我们都知道并喜欢蒙特卡洛数字积分方法&#xff0c;但是如果我告诉你你可…

机器人中的数值优化(二十)——函数的光滑化技巧

本系列文章主要是我在学习《数值优化》过程中的一些笔记和相关思考&#xff0c;主要的学习资料是深蓝学院的课程《机器人中的数值优化》和高立编著的《数值最优化方法》等&#xff0c;本系列文章篇数较多&#xff0c;不定期更新&#xff0c;上半部分介绍无约束优化&#xff0c;…

嵌入式Linux应用开发-基础知识-第十九章驱动程序基石②

嵌入式Linux应用开发-基础知识-第十九章驱动程序基石② 第十九章 驱动程序基石②19.3 异步通知19.3.1 适用场景19.3.2 使用流程19.3.3 驱动编程19.3.4 应用编程19.3.5 现场编程19.3.6 上机编程19.3.7 异步通知机制内核代码详解 19.4 阻塞与非阻塞19.4.1 应用编程19.4.2 驱动编程…

Unity HDRP Custom Pass 实现场景雪地效果

先使用Shader Graph连一个使用模型法线添加雪地的shader&#xff0c;并赋给一个material。 1.1 先拿到模型世界坐标下的顶点法线&#xff0c;简单处理一下&#xff0c;赋给透明度即可。 给场景添加Custom Pass&#xff0c;剔除不需要的层级。 1.在Hierarchy界面中&#xff…

用于自然语言处理的 Python:理解文本数据

一、说明 Python是一种功能强大的编程语言&#xff0c;在自然语言处理&#xff08;NLP&#xff09;领域获得了极大的普及。凭借其丰富的库集&#xff0c;Python 为处理和分析文本数据提供了一个全面的生态系统。在本文中&#xff0c;我们将介绍 Python for NLP 的一些基础知识&…

SSM - Springboot - MyBatis-Plus 全栈体系(十六)

第三章 MyBatis 三、MyBatis 多表映射 2. 对一映射 2.1 需求说明 根据 ID 查询订单&#xff0c;以及订单关联的用户的信息&#xff01; 2.2 OrderMapper 接口 public interface OrderMapper {Order selectOrderWithCustomer(Integer orderId); }2.3 OrderMapper.xml 配置…

讲讲项目里的仪表盘编辑器(三)布局组件

布局容器处理 看完前面两章的讲解&#xff0c;我们对仪表盘系统有了一个大概的理解。接着我们讲讲更深入的应用。 上文讲解的编辑器只是局限于平铺的组件集。而在编辑器中&#xff0c;还会有一种组件是布局容器。它允许其他组件拖拽进入在里面形成自己的一套布局。典型的有分页…

Python基础语法(1)

目录 一、常量和表达式 二、变量和类型 2.1 变量是什么 2.2 变量的语法 2.2.1 定义变量 2.2.2 使用变量 2.3 变量的类型 2.3.1 整数 2.3.2 浮点数(小数) 2.3.3 字符串 2.3.4 布尔 2.3.5 其他 2.4 为什么要有这么多类型 2.4.1 类型决定了数据在内存中占据多大空间…

【STM32 CubeMX】移植u8g2(一次成功)

文章目录 前言一、下载u8g2源文件二、复制和更改文件2.1 复制文件2.2 修改文件u8g2_d_setup文件u8g2_d_memory 三、编写oled.c和oled.h文件3.1 CubeMX配置I2C3.2 编写文件oled.holed.c 四、测试代码main函数测试代码 总结 前言 在本文中&#xff0c;我们将介绍如何在STM32上成…

[C++随想录] 优先级队列

优先级队列 基本使用题目训练 基本使用 priority_queue, 优先级队列, 又叫做双端队列, 头文件也是 <queue> 别看它叫做队列, 其实它是一个 堆 补充一下概念: 大根堆 — — 每一棵树的父节点比它的孩子都大小跟堆 — — 每一棵树的父节点比它的孩子都小 &#x1f447;&…

Golang语法、技巧和窍门

Golang简介 命令式语言静态类型语法标记类似于C&#xff08;但括号较少且没有分号&#xff09;&#xff0c;结构类似Oberon-2编译为本机代码&#xff08;没有JVM&#xff09;没有类&#xff0c;但有带有方法的结构接口没有实现继承。不过有type嵌入。函数是一等公民函数可以返…

设计模式10、外观模式Facade

解释说明&#xff1a;外观模式&#xff08;Facade Pattern&#xff09;又称为门面模式&#xff0c;属于结构型模式 Faade 为子系统中的一组接口提供了一个统一的高层接口&#xff0c;该接口使得子系统更加容易使用 外观&#xff08;Facade)角色&#xff1a;为多个子系统对外提供…

Sql注入(手工注入思路、绕过、防御)

一、Sql注入思路 1、判断注入点 在GET参数、POST参数、以及HTTP头部等&#xff0c;包括Cookie、Referer、XFF(X-Forwarded-for)、UA等地方尝试插入代码、符号或语句&#xff0c;尝试是否存在数据库参数读取行为&#xff0c;以及能否对其参数产生影响&#xff0c;如产生影响则…

信创办公–基于WPS的EXCEL最佳实践系列 (数据整理复制粘贴)

信创办公–基于WPS的EXCEL最佳实践系列 &#xff08;数据整理复制粘贴&#xff09; 目录 应用背景操作步骤1、数据查找与替换2、复制或粘贴数据3、使用自动填充工具4、将数据拆分到多列5、应用数字格式 应用背景 数据的整理复制粘贴等在日常的工作中经常使用。本章内容主要学习…

设计模式 - 享元模式

目录 一. 前言 二. 实现 一. 前言 享元模式&#xff08;Flyweight Pattern&#xff09;是一种结构型设计模式&#xff0c;它主要解决的问题是创建大量相似对象时的内存开销问题。该模式通过共享具有相同状态的对象来减少内存使用量。 享元模式的思想是&#xff1a;当需要创建…

嵌入式Linux应用开发-基础知识-第十九章驱动程序基石③

嵌入式Linux应用开发-基础知识-第十九章驱动程序基石③ 第十九章 驱动程序基石③19.5 定时器19.5.1 内核函数19.5.2 定时器时间单位19.5.3 使用定时器处理按键抖动19.5.4 现场编程、上机19.5.5 深入研究&#xff1a;定时器的内部机制19.5.6 深入研究&#xff1a;找到系统滴答 1…