![](https://img-blog.csdnimg.cn/direct/0677856ee1fe4bb2bda07dbb271503b4.png)
![](https://img-blog.csdnimg.cn/direct/1f47ad198d71445eae68d3178fb8a414.png)
![](https://img-blog.csdnimg.cn/direct/ea59b88f754041cc91dc78124483fd1b.png)
#include "stm32f10x.h" // Device header
//旋转按钮
void AD_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//数据手册ADC最高支持14Mhz,只能选择6,8分频
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//ADC模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//ADC独立模式
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//数据对齐模式选择
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//外部触发源选择,none不选择外部触发
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;//连续模式选择
ADC_InitStructure.ADC_ScanConvMode = ENABLE;//扫描模式选择
ADC_InitStructure.ADC_NbrOfChannel = 1;//序列扫描个数
ADC_Init(ADC1, &ADC_InitStructure);
ADC_Cmd(ADC1, ENABLE);//ADC上电
ADC_ResetCalibration(ADC1);//复位校准
while (ADC_GetResetCalibrationStatus(ADC1) == SET);//返回复位状态
ADC_StartCalibration(ADC1);//开始校准
while (ADC_GetCalibrationStatus(ADC1) == SET);
}
uint16_t AD_GetValue()
{
ADC_RegularChannelConfig(ADC1, ADC_Channel_8, 1, ADC_SampleTime_55Cycles5);//规则组配置(ADCX,CH,序列,采样时间)
ADC_SoftwareStartConvCmd(ADC1, ENABLE);//开始AD转换
while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
return ADC_GetConversionValue(ADC1);
}
触发模式选择连续自动触发和触发一次,扫描模式决定扫描到第几个序列,注入组有4个16位寄存器,规则组有1个16位寄存器,每次存不下自动覆盖数据
ADC只能接收0-3.3v电压,利用硬件电路改变电压
ADC多通道独立单个ADC模式
#include "stm32f10x.h" // Device header
//旋转按钮
void AD_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//数据手册ADC最高支持14Mhz,只能选择6,8分频
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//ADC模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//ADC独立模式单ADC
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//数据对齐模式选择
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//外部触发源选择,none不选择外部触发
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;//连续模式选择
ADC_InitStructure.ADC_ScanConvMode = ENABLE;//扫描模式选择
ADC_InitStructure.ADC_NbrOfChannel = 1;//序列扫描个数
ADC_Init(ADC1, &ADC_InitStructure);
ADC_Cmd(ADC1, ENABLE);//ADC上电
ADC_ResetCalibration(ADC1);//复位校准
while (ADC_GetResetCalibrationStatus(ADC1) == SET);//返回复位状态
ADC_StartCalibration(ADC1);//开始校准
while (ADC_GetCalibrationStatus(ADC1) == SET);
}
uint16_t AD_GetValue()
{
ADC_RegularChannelConfig(ADC1, ADC_Channel_8, 1, ADC_SampleTime_55Cycles5);//规则组配置(ADCX,CH,序列,采样时间)
ADC_SoftwareStartConvCmd(ADC1, ENABLE);//开始AD转换
while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
return ADC_GetConversionValue(ADC1);
}
DMA应用
外设(外设存储器) : 一般是外设的数据寄存器DR , 比如ADC的数据寄存器、串口的数据寄存器等等
存储器 : 这里存储器,指的就是是运行内存SRAM和程序存储器Flash , 是我们存储变量数组和程序代码的地方
外设和存储器的数据传输---------使用特定的硬件触发(每隔DMA通道的触发源不一样)
存储器和存储器之间的数据传输------软件触发
方向 : 决定了数据的方向 外设--->储存器 或者 储存器---->外设 或者 储存器---->储存器
Flash是只读储存器, 所以在储存器--->储存器的传输方向的时候 :只能选择 Flash-------------->SRAM方向的传输
X.DMA_DIR=指定外设站点(外设寄存器)为数据源还是目的地
传输计数器: 用来指定,我总共需要转运几次的, 这个传输计数器是一个自减计数器
比如给它写一个5, 那DMA就只能进行5次数据转运 , 转运过程中,每转运一次,计数器的数就会减1, 当传输计数器减到0之后,DMA就不会再进行数据转运了 , 它减到0之后,之前自增的地址,也会恢复到起始地址的位置
X.DMA_BufferSize=传输计数器
自动重装器 : 传输计数器减到0之后 , 是否要恢复到最初的值
比如最初传输计数器给5, 如果不使用自动重装器,那转运5次后,DMA就结束了. 如果使用自动重装器,那转运5次, 计数器减到0后,就会立即重装到初始值5, 决定了转运的模式
不重装,就是正常的单次模式
重装,就是循环模式
X.DMA_Mode=自动重装器
起始地址 : 决定了数据从那里来到那里去的
因为stm32是32位的单片机,他的一个内存单元是32位的, 所以起始地址填入的都为32位
数据宽度 : 指定一次转运要按多大的数据宽度来进行
字节Byte(uint8_t)、半字HalfWord(uint16_t)和字Word(uint32_t)
软件触发和循环模式,不能同时用
软件触发 : 软件触发的执行逻辑是,以最快的速度,连续不断地触发DMA, 早日把传输计数器清零,完成这一轮的转换
软件触发就是尽快把传输计数器清零 . 循环模式是清零后自动重装 , 如果同时用的话,那DMA就停不下来了
4: DMA转运的条件
1: 开关控制,DMA _Cmd函数必须使能
2: 传输计数器必须大于0
3: 触发源,必须有触发信号
触发一次,转运一次,传输计数器自减一次 , 当传输计数器等于0,且没有自动重装时 , 无论是否触发,DMA都不会再进行转运了, 此时就需要DMA_Cmd函数,给DISABLE,关闭DMA. 再为传输计数器写入一个大于0的数, 再DMA_Cmd,给ENABLE,开启DMA才可以正常工作.
注意,写传输计数器时,必须要先关闭DMA,再进行, 不能在DMA开启时,写传输计数器
5:DMA请求
特定的硬件触发 : 每个通道的硬件触发源都是不同的 , 需要用ADC1来触发的话一那就必须选择通道1; 定时器2的更新事件来触发的话,那就必须选择通道2
因为每个通道的硬件触发源都不同 , 如果你想使用某个硬件触发源的话, 就必须使用它所在的通道
软件触发 : 使用软件触发的话,那通道就可以任意选择了. 每个通道的软件触发都是一样的
在stm32f10x dmah文件中配置----MDA初始化
void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct);
在stm32f10x dmah文件中配置----开启MDA
void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);
在stm32f10x dmah文件中配置----数据寄存器设置
void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber);
uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);
DMA_SetCurrDataCounter : 设置当前数据寄存器 , 就是给这个传输计数器写数据的, 和配置DMA中X.DMA_Buffersize参数相似
DMA_GetCurrDataCounter : 获取当前数据寄存器, 这个函数就是返回传输计数器的值 , 在转运完成后把标志位值1
void DMA_ClearFlag(uint32_t DMAy_FLAG)--DMA_ClearFlag : 清除中断标志位
6-存储器到存储器(软件触发)
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyDMA.h"
uint16_t MyDMA_Size;
void MyMDA_init(uint32_t AddrA, uint32_t AddrB, uint16_t Size){
MyDMA_Size=Size;
//开启RCC--DMA是AHB总线的设备
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
/*
我们使用的STM32型号为: STMF103
参数1的第一个选择 : 互连型设备--互联型是STM23F 105/107的型号
参数1的第一个选择 : 其他设备---103选择这个
*/
//配置DMA
DMA_InitTypeDef DMA_initstruct;
//外设备站点的3个参数
//因为stm32是32位的单片机,他的一个内存单元是32位的
DMA_initstruct.DMA_PeripheralBaseAddr=AddrA; //外设的起始地址--要求:32位
DMA_initstruct.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Byte;外设的数据宽度---我们选择以字节的方式传输(uint8_t)
DMA_initstruct.DMA_PeripheralInc=DMA_PeripheralInc_Enable;//外设的地址是否自增--自增
//储存器的3个参数
DMA_initstruct.DMA_MemoryBaseAddr=AddrB;//储存器的起始地址--要求:32位
DMA_initstruct.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte;//储存器的数据宽度--我们选择以字节的方式传输(uint8_t)
DMA_initstruct.DMA_MemoryInc=DMA_MemoryInc_Enable;//储存器的地址是否自增--自增
//传输方向
DMA_initstruct.DMA_DIR=DMA_DIR_PeripheralSRC;//指定外设站点(外设寄存器)为数据源还是目的地---数据源
//缓冲区大小---传输计数器
DMA_initstruct.DMA_BufferSize=Size;//传输几次
//传输模式----是否使用自动重装
DMA_initstruct.DMA_Mode=DMA_Mode_Normal;//正常模式 //传输计数器到0直接停止
//选择触发模式---硬件触发或者软件触发
DMA_initstruct.DMA_M2M=DMA_M2M_Enable; //使用软件触发
//优先级
DMA_initstruct.DMA_Priority=DMA_Priority_Medium;
DMA_Init(DMA1_Channel1,&DMA_initstruct);
//开启MDA
DMA_Cmd(DMA1_Channel1,DISABLE);
}
void MyDMA_Transfer(void)
{
//需要给我传输寄存器重新赋值,首先要使CMD失能
DMA_Cmd(DMA1_Channel1, DISABLE);
/*
设置当前数据寄存器 , 就是给这个传输计数器写数据的
和配置DMA中X.DMA_BufferSize参数相似
*/
DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size);
DMA_Cmd(DMA1_Channel1, ENABLE);
//DMA1_FLAG_TC1---转运完成标志位 转运完成后置1
while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);
DMA_ClearFlag(DMA1_FLAG_TC1);//清除转运完成标志位
}
uint8_t dataA[]={0x01,0x02,0x03,0x04};
uint8_t dataB[]={0,0,0,0};
int main(void)
{
OLED_Init();
MyMDA_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);
OLED_ShowHexNum(3, 8, (uint32_t)dataB, 8);
while (1)
{ dataA[0]++;
dataA[1]++;
dataA[2]++;
dataA[3]++;
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);
}
}
7-ADC的DMA转运(硬件触发)
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"
uint16_t AD_Value[4];
void AD_init(void){
//RCC开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
/*
我们使用的STM32型号为: STMF103
参数1的第一个选择 : 互连型设备--互联型是STM23F 105/107的型号
参数1的第一个选择 : 其他设备---103选择这个
*/
//配置ADCCLK
//APB2时钟72MHz时钟信号然后通过ADC预分频器进行分频,得到ADCCLK钟信号
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//72Mhz/6=12Mhz
//配置GPIO
GPIO_InitTypeDef GPIO_initstruct;
GPIO_initstruct.GPIO_Mode=GPIO_Mode_AIN; //模拟输入,可以理解为ADC的专属模式
GPIO_initstruct.GPIO_Pin=GPIO_Pin_0 |GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;
GPIO_initstruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_initstruct);
/*ADC_Channel_0 --通道o
1----1~16的范围规则组第几个序列
ADC_SampleTime_55Cycles5-----指定通道的采样时间
*/
//选择AD转化器----我们选择规则组的输入通道 因为启动了应该组,所以是下面改为扫描模式
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_initstruct;
ADC_initstruct.ADC_ContinuousConvMode=ENABLE;//选择是连续转换还是单次转换--连续
ADC_initstruct.ADC_DataAlign=ADC_DataAlign_Right; //数据对齐---右对齐
ADC_initstruct.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;//触发控制的触发源---不使用外部触发,使用内部软件触发
ADC_initstruct.ADC_Mode=ADC_Mode_Independent;//ADC的工作模式---独立模式
ADC_initstruct.ADC_NbrOfChannel=4; //通道数目--指定在扫描模式下,总共会用到几个通道
ADC_initstruct.ADC_ScanConvMode=ENABLE;//可以选择是扫描模式还是非扫描模式---扫描模式
ADC_Init(ADC1,&ADC_initstruct);
//ADC在完成后面数据写在DR寄存器中
//配置DMA
//外设备站点的3个参数
//因为stm32是32位的单片机,他的一个内存单元是32位的
DMA_InitTypeDef DMA_initstruct;
DMA_initstruct.DMA_PeripheralBaseAddr=(uint32_t)&ADC1->DR; //外设的起始地址--要求:32位
DMA_initstruct.DMA_PeripheralDataSize=DMA_PeripheralDataSize_HalfWord;外设的数据宽度---我们选择以字节的方式传输(uint16_t)
DMA_initstruct.DMA_PeripheralInc=DMA_PeripheralInc_Disable;//外设的地址是否自增--不自增
// //储存器的3个参数
DMA_initstruct.DMA_MemoryBaseAddr=(uint32_t)AD_Value;//储存器的起始地址--要求:32位
DMA_initstruct.DMA_MemoryDataSize=DMA_MemoryDataSize_HalfWord;//储存器的数据宽度--我们选择以字节的方式传输(uint16_t)
DMA_initstruct.DMA_MemoryInc=DMA_MemoryInc_Enable;//储存器的地址是否自增--自增
//传输方向
DMA_initstruct.DMA_DIR=DMA_DIR_PeripheralSRC;//指定外设站点(外设寄存器)为数据源还是目的地---数据源
//缓冲区大小---传输计数器
DMA_initstruct.DMA_BufferSize=4;//传输几次
//传输模式----是否使用自动重装
DMA_initstruct.DMA_Mode=DMA_Mode_Circular;//循环模式 //传输计数器到0直接停止 ,/单次数模式 //DMA_Mode_Circular--循环模式 DMA_Mode_Normal
//选择触发模式---硬件触发或者软件触发
DMA_initstruct.DMA_M2M=DMA_M2M_Disable; //使用硬件触发
//优先级
DMA_initstruct.DMA_Priority=DMA_Priority_Medium;
DMA_Init(DMA1_Channel1,&DMA_initstruct);
//开启MDA
DMA_Cmd(DMA1_Channel1,ENABLE);
//开启ADC到DMA的输出
ADC_DMACmd(ADC1,ENABLE);
//开启ADC
ADC_Cmd(ADC1,ENABLE);
//校准ADC
//复位校准
ADC_ResetCalibration(ADC1);//---把CR2_RSTCAL_Set这一位置一
//等待复位校准完成--ADC_GetResetCalibrationStatus作用:返回复位校准的状态
while (ADC_GetResetCalibrationStatus(ADC1)==SET); //SET=1
/*获取的是这个CR2_RSTCAL_Set的标志位 ,该位由软件设置并由硬件清除
在校准寄存器被初始化后该位将被清除,所以该位的用法就是:
你软件置该位为1,那硬件就会开始复位校准 , 当复位校准完成后,该位就会由硬件自动清0
*/
//开始校准
ADC_StartCalibration(ADC1);
//获取校准状态
while(ADC_GetCalibrationStatus(ADC1)==SET);
ADC_SoftwareStartConvCmd(ADC1,ENABLE);
}
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);
OLED_ShowNum(2, 5, AD_Value[1], 4);
OLED_ShowNum(3, 5, AD_Value[2], 4);
OLED_ShowNum(4, 5, AD_Value[3], 4);
Delay_ms(100);
}
}
把ADC1通道数据通过规则组一个16位寄存器连续 扫描模式读取通道(1-4)依次进入规则组寄存器,由ADC触发DMA转运数据循环读取转运到AD_Value刷新数据,每次扫描一次DMA转运一次存储器地址每次递增