当前位置: 首页 > news >正文

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转运

相关文章:

STM32之DMA

简介 • DMA &#xff08; Direct Memory Access &#xff09;直接存储器存取 &#xff08;可以直接访问STM32内部存储器&#xff0c;如SRAM、程序存储器Flash和寄存器等&#xff09; •DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输&#xff0c;无须CPU干预&a…...

解决前端二进制流下载的文件(例如:excel)打不开的问题

1. 现在后端请求数据后&#xff0c;返回了一个二进制的数据&#xff0c;我们要把它下载下来。 这是响应的数据&#xff1a; 2. 这是调用接口的地方&#xff1a; uploadOk(){if(this.files.length 0){return this.$Message.warning("请选择上传文件&#xff01;&#xff…...

动态规划算法(1)--矩阵连乘和凸多边形剖分

目录 一、动态数组 1、创建动态数组 2、添加元素 3、删除修改元素 4、访问元素 5、返回数组长度 6、for each遍历数组 二、输入多个数字 1、正则表达式 2、has.next()方法 三、矩阵连乘 1、什么是矩阵连乘&#xff1f; 2、动态规划思路 3、手推m和s矩阵 4、完…...

通过Nginx重新认识HTTP错误码

文章目录 概要一、HTTP错误码1.1、1xx1.2、2xx1.3、3xx1.4、4xx1.5、5xx 二、Nginx对常见错误处理三、参考资料 概要 在web开发过程中&#xff0c;通过HTTP错误码快速定位问题是一个非常重要的技能&#xff0c;同时Nginx是非常常用的一个实现HTTP协议的服务&#xff0c;因此本…...

某房产网站登录RSA加密分析

文章目录 1. 写在前面2. 抓包分析3. 扣加密代码4. 还原加密 1. 写在前面 今天是国庆节&#xff0c;首先祝福看到这篇文章的每一个人节日快乐&#xff01;假期会老的这些天一直在忙事情跟日常带娃&#xff0c;抽不出一点时间来写东西。夜深了、娃也睡了。最近湖南开始降温了&…...

深度学习:基于长短时记忆网络LSTM实现情感分析

目录 1 LSTM网络介绍 1.1 LSTM概述 1.2 LSTM网络结构 1.3 LSTM门机制 1.4 双向LSTM 2 Pytorch LSTM输入输出 2.1 LSTM参数 2.2 LSTM输入 2.3 LSTM输出 2.4 隐藏层状态初始化 3 基于LSTM实现情感分析 3.1 情感分析介绍 3.2 数据集介绍 3.3 基于pytorch的代码实现 3…...

selenium使用已经获取的cookies登录网站报错unable to set cookie的处理方式

用selenium半手动登录github获取其登录cookies后&#xff0c;保存到一个文件gtb_cookies.txt中。 然后用selenium使用这个cookies文件&#xff0c;免登录上github。但是报错如下&#xff1a;selenium.common.exceptions.UnableToSetCookieException: Message: unable to set co…...

初阶数据结构(四)带头双向链表

&#x1f493;博主csdn个人主页&#xff1a;小小unicorn ⏩专栏分类&#xff1a;数据结构 &#x1f69a;代码仓库&#xff1a;小小unicorn的代码仓库&#x1f69a; &#x1f339;&#x1f339;&#x1f339;关注我带你学习编程知识 带头双向链表 链表的相关介绍初始化链表销毁链…...

2022年9月及10月

9月 1.Halcon12的HObject和Hobject halcon12 可以用HObject&#xff0c;也可以用Hobject&#xff0c;用法都一样 包括HalconCpp.h 如果附加目录中&#xff1a; C:\Program Files\MVTec\HALCON-12.0\include\halconcpp\ 在前面&#xff0c;则用 HalconCpp::HObject 如果附加目录…...

Vmware安装

title: “Vmware安装” createTime: 2021-11-22T09:53:2908:00 updateTime: 2021-11-22T09:53:2908:00 draft: false author: “name” tags: [“VMware”,“安装”,“linux”] categories: [“install”] description: “测试的” linux安装VMware Workstation16 1.安装包 …...

RSA算法

算法简介 RSA是一种非对称加密方式。发送者把明文通过公钥加密后发送出去&#xff0c;接受者把密文通过私钥解密得到明文。 算法过程 生成公钥和私钥 选取两个质数p和q&#xff0c;np*q。n的长度就是密钥长度。φ(n)(p-1)*(q-1)φ(n)为n的欧拉函数。找到1-φ(n)间与φ(n)互质的…...

计算机竞赛 深度学习手势识别 - yolo python opencv cnn 机器视觉

文章目录 0 前言1 课题背景2 卷积神经网络2.1卷积层2.2 池化层2.3 激活函数2.4 全连接层2.5 使用tensorflow中keras模块实现卷积神经网络 3 YOLOV53.1 网络架构图3.2 输入端3.3 基准网络3.4 Neck网络3.5 Head输出层 4 数据集准备4.1 数据标注简介4.2 数据保存 5 模型训练5.1 修…...

Spring的Ordered

Ordered Java中的Ordered接口是Spring框架中的一个接口&#xff0c;用于表示对象的顺序。它定义了一个方法getOrder()&#xff0c;用于获取对象的顺序值&#xff0c;值越小的对象越先被处理。 Ordered接口是Spring框架中的一个接口&#xff0c;用于定义组件的加载顺序。当一个…...

前端两年半,CSDN创作一周年

文章目录 一、机缘巧合1.1、起因1.2、万事开头难1.3、 何以坚持&#xff1f; 二、收获三、日常四、憧憬 五、总结 一、机缘巧合 1.1、起因 最开始接触CSDN&#xff0c;还是因为同专业的同学&#xff0c;将计算机实验课的实验题&#xff0c;记录总结并发在了专业群里。后来正式…...

定时任务管理平台青龙 QingLong

一、关于 QingLong 1.1 QingLong 介绍 青龙面板是支持 Python3、JavaScript、Shell、Typescript 多语言的定时任务管理平台&#xff0c;支持在线管理脚本和日志等。其功能丰富&#xff0c;能够满足大部分需求场景&#xff0c;值得一试。 主要功能 支持多种脚本语言&#xf…...

java多线程相关介绍

1. 线程的创建和启动 在 Java 中创建线程有两种方式。一种是继承 Thread 类并重写其中的 run() 方法&#xff0c;另一种是实现 Runnable 接口并重写其中的 run() 方法。创建完线程对象后&#xff0c;调用 start() 方法可以启动线程。 2. 线程的状态 Java 的线程在不同阶段会处于…...

css复合选择器

交集选择器 紧紧挨着 <template><div><p class"btn">Click me</p><button class"btn" ref"myButton" click"handleClick">Click me</button></div> </template> <style> but…...

USART串口协议

通信接口 •通信的目的&#xff1a;将一个设备的数据传送到另一个设备&#xff0c;扩展硬件系统 • 通信协议&#xff1a;制定通信的规则&#xff0c;通信双方按照协议规则进行数据收发 全双工&#xff1a;指通信双方能够同时进行双向通信&#xff0c;一般来说&#xff0c;全双…...

picoctf_2018_shellcode

picoctf_2018_shellcode Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x8048000) RWX: Has RWX segments32位&#xff0c;啥都没开 这个看着挺大的&#xff0c;直接来个ROPchain&#xff0c;…...

Apache Derby的使用

Apache Derby是关系型数据库&#xff0c;可以嵌入式方式运行&#xff0c;也可以独立运行&#xff0c;当使用嵌入式方式运行时常用于单元测试&#xff0c;本篇我们就使用单元测试来探索Apache Derby的使用 一、使用IDEA创建Maven项目 打开IDEA创建Maven项目&#xff0c;这里我…...

leetcode 图相关的题

图 图相关知识有leetcode207课程表1(有环判断)以及210 课程表2(拓扑排序). 链表遍历 def dfs(n):print(n)dfs(n)二叉树遍历 def dfs(n):print(n)dfs(n.left)dfs(n.right)多叉树遍历 dfs(root) def dfs(n):for node in n.nodes:dfs(node)图遍历 visited [False] * n_node…...

程序员们,我们能工作到65岁吗?

软件开发人员的职业生涯可以持续多久&#xff1f;这是大多数认真考虑成为专业程序员的人不禁想知道的事情。 在谈论这样一个要求很高的职业时&#xff0c;这是一个非常自然的问题。没有人愿意花费数年时间学习一项技能&#xff0c;这些技能将在几年内不再相关&#xff0c;或者当…...

【洛谷 P1996】约瑟夫问题 题解(队列+模拟+循环)

约瑟夫问题 题目描述 n n n 个人围成一圈&#xff0c;从第一个人开始报数,数到 m m m 的人出列&#xff0c;再由下一个人重新从 1 1 1 开始报数&#xff0c;数到 m m m 的人再出圈&#xff0c;依次类推&#xff0c;直到所有的人都出圈&#xff0c;请输出依次出圈人的编号。…...

字符串函数与内存函数讲解

文章目录 前言一、字符串函数1.求字符串长度strlen 2.长度不受限制的字符串函数(1)strcpy(2)strcat(3)strcmp 3.长度受限制的字符串函数(1)strncpy(2)strncat(3)strncmp 4.字符串查找(1)strstr(2)strtok 5.错误信息报告(1)strerror(2)perror 二、内存函数1.memcpy2.memmove3.me…...

c语言系统编程之多进程

程序与进程的区别&#xff1f; 程序是静态的未运行的二进制文件&#xff0c;存储在磁盘中 进程是已经运行的二进制文件&#xff0c;存储在内存中 进程的内存划分图有哪几部分&#xff1f; 堆&#xff08;存储malloc和calloc出来的空间&#xff09;、栈&#xff08;局部变量…...

前端还是后端:探讨Web开发的两大街区

前端还是后端&#xff1a;探讨Web开发的两大街区 一、引言二、两者的对比分析技能要求和专业知识职责和工作内容项目类型和应用领域就业前景和市场需求 三、技能转换和跨领域工作四、全栈开发结语 一、引言 Web开发领域涉及到前端开发和后端开发这两个不同而又互为补充的领域。…...

JavaScript中如何确定this的值?如何指定this的值?

&#x1f380;JavaScript中的this 在绝大多数情况下&#xff0c;函数的调用方法决定了this的值&#xff08;运行时绑定&#xff09;。this不能在执行期间被赋值&#xff0c;并且在每次函数呗调用时this的值也可能会不同。 &#x1f37f;如何确定this的值&#xff1a; 在非严格…...

ubuntu下源码编译方式安装opencv

基础条件 ubuntu 20.04 opencv 3.4.3 opencv 源码编译的安装步骤 第一步&#xff0c; 首先clone源码 git clone https://github.com/opencv/opencv.git第二步&#xff0c;依赖包&#xff0c;执行下面的命令 sudo apt-get install build-essential sudo apt-get install cmak…...

spring boot整合常用redis客户端(Jedis、Lettuce、RedisTemplate、Redisson)常见场景解决方案

Java操作redis有三种客户端供选择&#xff1a;Jedis、Lettuce、Redisson。 在实际项目中运用最多的客户端还是Redisson、RedisTemplate&#xff1b;其中RedisTemplate并非是一个新的redis客户端实现&#xff0c;RedisTemplate是Spring Data Redis中提供的封装好的redis操作模板…...

HarmonyOS之运行Hello World

目录 下载与安装DevEco Studio 配置环境 创建项目 认识DevEco Studio界面 运行Hello World 了解基本工程目录 工程级目录 模块级目录...

子页网站设计/百度电脑版下载官网

1、每一排是一个单独的单刀双掷开关&#xff0c;中间的是公共的&#xff0c;可以用SW-DPDT先是只要确定中间的是公共就行了&#xff0c;至于到底哪边是开哪边是关这个问题可以留到焊接的时候再去解决。 2、以一个USB供电单片机的实例来说明 这是一个USBslot&#xff0c;其中1 P…...

域名推荐网站/谷歌关键词搜索工具

/// /// 获取某一列的所有值/// /// 列数据类型/// 数据表/// 列名/// public static List GetColumnValues(DataTable dtSource,string filedName){return (from r in dtSource.AsEnumerable() select r.Field("ID")).ToList();}方法调用&#xff1a;获取字段ID的所…...

常州北京网站建设/腾讯云域名注册官网

1热爱冲浪的你遇到过404吗作为一个每日网上冲浪的小可爱&#xff0c;你最崩溃的瞬间是什么&#xff1f;没错&#xff01;对于小V来讲&#xff0c;最可怕的事情不是没电而是4&#xff01;0&#xff01;4&#xff01;毕竟网络出了问题&#xff0c;小V就不能跟大家愉快的玩耍了说到…...

做微商能利用的网站有哪些/深圳seo优化排名

登录&#xff1a; ssh -i ~/.ssh/证书 用户名端口号 不行就&#xff1a; chmod 600 ~/.ssh/证书 再登录&#xff1a; 登录成功后输入(打印全部的debug信息)&#xff1a; mosquitto_sub -t “#” 齐活 链接: Mac 通过 ssh 远程登录服务器(密钥对方式&#xff09; 链接: chm…...

wordpress新窗口打开/关键词优化有哪些作用

位图或者位向量可以表示一系列序列集合&#xff0c;比如&#xff1a;可用一个20位长的字符串来表示一个所有元素都小于20的简单的非负整数集合。例如可用如下字符串表示集合 {1,2,3,5,8,13}&#xff1a; 0 1 1 1 0 1 0 0 1 0 0 0 0 1 0 0 0 0 0 0 集合中为1的代表整数集合中的该…...

大连集团网站建设/关键词排名批量查询

2019独角兽企业重金招聘Python工程师标准>>> 在lvmcache初步分析 - 1 中&#xff0c;我们主要从DM层框架的角度&#xff0c;简单看了下dmcache是怎样被放进这个通用框架的。这一篇&#xff0c;将尝试分析dmcache的设计。 谈设计前&#xff0c;我们需要清楚dmcache要…...