用dw制作购物网站首页,运城网站建设公司有多少,凉州区住房和城乡建设局网站,数字创意设计包括哪些行业简介 • DMA #xff08; Direct Memory Access #xff09;直接存储器存取 #xff08;可以直接访问STM32内部存储器#xff0c;如SRAM、程序存储器Flash和寄存器等#xff09; •DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输#xff0c;无须CPU干预 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发出硬件触发信号
基本结构 起始地址有外设端的起始地址和存储器端的起始地址决定里的数据从哪里来到哪里去的
数据宽度指定一次转运要按多大的数据宽度来进行可以选择字节Byte8位uint8_t、半字HalfWord16位,uint16_t和字Word32位,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.hint 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决定是哪个DMAx决定是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决定是哪个DMAx决定是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.huint8_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.huint8_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 i0;i4;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中。
DMAAD多通道
单次转换扫描模式
在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决定是哪个DMAx决定是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.hint 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决定是哪个DMAx决定是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.hint 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决定是哪个DMAx决定是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);}这样就也可以实现目标
同时还可以再优化代码添加一个定时器定时器触发ADCADC触发DMA转运