参考资料:
1-STM32F4xx中文参考手册1.pdf
2-STM32F407_405英文数据手册(没有中文).pdf
3.野火STM32F407_霸天虎V2开发板原理图2025.pdf
什么是ADC?
ADC全称:Analog to Digital Converter,即模数转换器。
对于F407而言,有3个独立的ADC1、2、3,分辨率有12/10/8/6位可选,每个ADC具有18个通道,其中外部通道16个。F103上分辨率只有12位,F429、F7与F407一样分辨率都有4个档位可选,分辨率决定了转换的时间,如某些场合不需要ADC的转换精度那么高,就可以选择分辨率低一些的档位。
ADC功能框图

①电压输入范围
为什么ADC的采集范围是0~3.3V?ADC的输入电压范围是:Vref-≤Vin≤Vref+,由Vref-、Vref+、Vdda、Vssa这四个外部引脚决定。在设计原理图时一般将Vssa和Vref-接地,Vref+和Vdda接3.3V,则得到ADC的输入电压范围是0~3.3V。如果我们想让输入的电压范围变宽,可以测量负电压或更高的正电压,可以在外部加一个电压调理电路,把需要转换的电压降压或抬升到0~3.3V,这样ADC就能测量了。举个例子,使ADC可以测量:-10V~10V,我们设计一个电路如下图:

-10V~10V电压从IN处进入,经过3个电阻进行分压,得到中间点(OUT),OUT的范围就变成了0~3.3V。它们的线性关系怎么求?根据基尔霍夫定律(KCL),节点流入的电流等于流出的电路,则(Vin-Vout)/R2+(3V3-Vout)/R1=Vout/R3,带入电阻值得Vout=(Vin+10)/6,将-10和10分别带入Vin就可明白这个线性关系。
②输入通道
每个ADC具有18个通道,其中外部通道有16个。见下图分布(这个图可以从哪里找到?):

那我们能够使用的外部通道就是16*3=48个吗?并不是。通过观察上表,0~3通道全部共用(4个),4~9通道ADC1和ADC2共用(6个),10~13通道全部共用(4个),14~15通道ADC1和ADC2共用(2个),ADC3没有与ADC1&ADC2共用的(6个+2个),所以只能是24个外部通道可以同时使用。(虽然说有这么多通道,但是霸天虎V2开发板是一个评估板,力求能够验证所有功能,所以大部分的IO基本上已经被复用,能留出来给ADC的就不多了,PB0、1、PC2、PA6,这4个IO口没有用到其它地方,可以供ADC使用,当然用使用过的IO口也可以,但是采集到的信号可能会有杂波,如果你还是想使用的话可以把它接到0或者3.3V验证ADC可以采集到的两个极限电压,极限电压正确的话程序八九不离十也就没毛病!)
③规则通道和注入通道
外部的16个通道在转换的时候又分为规则通道和注入通道,其中规则通道最多有16路(刚好对应外部的16个GPIO),注入通道最多有4路(任意选择)。有什么区别?如何使用?规则通道:很规矩,通常就用这个;注入通道:就是插入,与中断程序类似,它是在规则通道转换的时候强行插入要转换的通道(也就是说它只在规则通道存在时才有)。
假如通道0~16都用ADC1转换,他们的转换顺序怎么确定?


序列寄存器就是决定每个通道的转换顺序的。上面2图分别表示规则通道和注入通道的转换顺序,分别对应规则序列寄存器和注入序列寄存器。
注入序列寄存器需要注意的是:如果JL小于4,设置通道的转换顺序就与规则序列寄存器的不一样,第一次转换的不是JSQ14:0,而是JSQ(4-JL),如果JL=00(1个转换),那么转换的顺序就是从JSQ(4-0)4:0即JSQ(4)4:0开始,而不是从JSQ(1)4:0开始。当JL=4时,转换顺序跟SQR(规则序列寄存器)一样。(通俗理解,1个转换--JSQ4,2个转换--从JSQ3~JSQ4,3个转换--从JSQ2~JSQ4,4个转换--从JSQ1~JSQ4)。
④触发源
也就是告诉ADC开始采集的命令,就叫触发。分为软件触发和外部触发。规则通道,外部触发(有EXTI_11和多个定时器),EXTI_11通过电平触发,IO口出现低电平或者高电平就可以进行转换了,还有定时器(如框图),具体选择哪个是通过ADC_CR2的EXTSEL3:0选择,选择之后能不能起作用,由ADC_CR2的EXTEN决定(注入通道的触发原理类似,由框图举一反三)。还有软件触发,也是使用的最多的,是通过ADC_CR2的ADON(先打开),再打开SWSTART(开始转换规则通道)或JSWSTART(开始转换注入通道)。
ADC的精度由分辨率决定,也要看ADC的转换时间,如果ADC转换时间慢,转换的精度再高也没有用。转换的时间就与时钟相关。
⑤转换时间
Tconv=采样时间+12个周期(12位)。分辨率多少位固定的转换周期就是多少个。采样时间可以自己设置,最小是3个时钟周期。框图中的ADCCLK从哪里来?具体是多少呢?ADC_CLK也叫ADC模拟电路时钟,最大值是36M(可以看数据手册:5.3.21章节),ADCx(x=1,2,3)挂载到APB2上由PCLK2提供,还可以进行分频(由ADC通用控制寄存器ADC_CCR的ADCPRE
位设置2/4/6/8分频)。已知F407的PCLK2=84M(不知道的可以去STM32之RCC系统时钟控制(芯片的心脏控制器)-CSDN博客),要想满足最大值是36M,2分频不满足,4/6/8分频可以满足,但是分频值越大,转换时间越长,可选4分频(转换时间最短),即ADC_CLK=PCLK2/4=84/4=21M,若想要满足最大值36M,反推,分频2分频,那么PCLK2就是72M,SYSCLK就是144M,可以做到转换时间最优。可以通过降低ADC的分辨率来执行快速转换。每种分辨率的最小转换时间如下:
12位:3+12=15 ADCCLK周期
10位:3+10=13 ADCCLK周期
8位:3+8=11 ADCCLK周期
6位:3+6=9 ADCCLK周期
采样时间为什么要设3个时钟周期?ADC需要若干个ADC_CLK周期完成对输入的模拟量进行采样,采样的周期数可通过ADC采样时间寄存器ADC_SMPR1(控制通道10~18)和ADC_SMPR2(控制通道0~9)中的SMP2:0位设置,每个通道可以分别用不同的时间采样,采样周期最小是3个(由寄存器可选参数决定),即想要最快采样,应该设为3个周期,这里的周期指的是1/ADC_CLK。
采样率,决定了ADC能采集多大频率的信号(参考数据手册:5.3.21章节)
快速计算最短转换时间:Tconv=采样时间+12个周期(12位),PCLK2=84M,ADC_CLK=84/4=21M,Tconv=3+12=15周期=15*(1/21M)=15/21M=15/21000000(s)=15/21000(ms)=15/21(μs)=0.71μs。
μs级别属于中等速度,ms属于低速,更快的有ns的ADC更快。
转换方式:单次转换,采一个点就结束,要想采第二个点的话就要继续使能。连续转换就是,采集完一个点又会继续采集第二个点,会一直采集下去。扫描模式:针对多通道,比如有3个通道,每轮分别采集通道1、2、3,又继续第二轮采集1、2、3,...(扫描也有单次和连续之分,这里讲的是连续,单次可参考上面的介绍)。
⑥注入/规则数据寄存器
转换时间算好了,那么ADC转换数据完成之后,数据放到哪里呢?一切转换就绪之后,ADC转换后的数据根据转换组的不同,规则组的数据放在ADC_DR,注入组的数据放到JDRx。如果使用双重或者三重模式那规则组的数据就存放到通用规则寄存器ADC_CDR里(关于双重或三重后面会讲到)。那么ADC_DR与JDRx有什么不同?

由上图可知,ADC_DR:16位有效,用于存放独立模式(单个ADC即ADCx,x=1、2、3)转换完成的数据,对齐方式由ADC_CR2:ALIGN位决定,可以左/右对齐,一般使用右对齐。要注意的是ADC_DR寄存器只有一个,不同通道的ADC转换数据如果不及时取走的话数据就会覆盖。独立模式时取ADC_DR里面数据的方式有两种:一是中断,比如通道1转换完成之后,产生一个EOC(end of complete),中断里面把数据读走,当采集通道2的数据的时候放进去就不会覆盖通道1的数据,这种方式比较笨,一般不用;二是DMA,可以事先定义一个数组,比如3个通道,那就定义一个具有3个成员的数组,1通道转换完成之后数据传到ADC_DR,采用DMA传到数组的第一个成员里面,其它同理,也就是说,ADC_DR一产生数据就通过DMA传到数组中,这样的话就不会造成数据混乱(具体做法,参考附件程序)。

由上图可知,ADC_JDRx(x=1,2,3,4):16位有效,用于存放注入通道转换完成的数据,对齐方式由ADC_CR2:ALIGN位决定,可以左/右对齐,一般使用右对齐。不过它有四个这样的寄存器,最多只能有4个注入通道,每个通道对应一个寄存器。

由上图可知。ADC_CDR:32位有效,必须是双重或三重ADC能使用,必须配合DMA使用。(后面有讲解)
⑦中断
是由两个寄存器配合完成的。看门狗是用来保护通道里面输入的模拟信号的范围,它有两个寄存器,一个是上限一个是下限,比如通道是测量0~3.3V的电压范围,已知我们要测量的信号是1~2V的,那么低于1V和高于2V想要报警产生中断的话,可以使用模拟看门狗来检测电压。那么模拟看门狗如何保护多个通道?如下图:

最后,怎么根据采集回来的数据量算出模拟量?
模拟量的输入范围是:0~3.3V,分辨率使用12位,那么数字量对应模拟量的最小精度就是3.3/2的12次方,那么数字量比如是x的话,则模拟量Y=(3.3/2的12次方)*x。
双重/三重采集时数据怎么传输的?
多重ADC的各种模式,由ADC_CCR:MULTI4:0决定。如下:

0000表示单独模式,单独使用一个ADC;
双重由多种模式,使用的非常多的是规则同时。
三重模式使用最多的是交替触发。
使用多重ADC时必须使用DMA传输,由ADC_CCR:DMA决定。
多重模式的DMA请求有3种:
DMA模式1,每次产生一个DMA请求,一有数据,就产生一个DMA请求;DMA模式2就是有两个数据可以用的时候可以产生一个DMA请求,DMA模式3与DMA模式2一样,但是3只能用于分辨率为6位和8位,模式2可以用于12位和10位。(例程都会涉及)
ADC的各种模式:

独立模式:只有一个ADC工作,可以是ADC1,2,3,可以是单通道,也可以是双通道,转化你的数据放在ADC_DR数据寄存器,可以采用中断或者DMA接收数据。采集和转换原理图如下:

双重规则同步模式:规则是只适用于规则通道,注入通道不行,同步指的是两个或三个ADC同时采集多个通道,比如示波器的双通道同时采集。(注意不要在同步模式时采集同一个通道)
多重交替模式:交替模式只能用于规则组,通常只有一个通道,在一个ADC转换的时候,另外一个ADC又开始采集,这样可以提高ADC的采样率,最高为6M;数据传输时,使用DMA模式2,当有两个数据项可用时(32bit),生成一个DMA请求。
ADC结构体(基于固件库)
cpp
typedef struct
{
uint32_t ADC_Resolution; //配置ADC的分辨率,可选12、10、8、6位 ADC_CR1:RES[1:0]
FunctionalState ADC_ScanConvMode;//配置是否使用扫描模式,单通道时DISABLE,多通道时ENABLE ADC_CR1:SCAN
FunctionalState ADC_ContinuousConvMode;//配置连续模式(ENABLE)还是单次转换(DISABLE) ADC_CR2:CONT
uint32_t ADC_ExternalTrigConvEdge;//外部触发极性选择 可选上升沿,下降沿、上升下降均可,禁止 ADC_CR2:EXTEN/JEXTEN
uint32_t ADC_ExternalTrigConv; //外部触发选择,一般使用软件触发 ADC_CR2:EXTSEL/JEXTSEL
uint32_t ADC_DataAlign; // 转换结果数据对齐方式,一般右对齐
uint8_t ADC_NbrOfConversion; //需要转换通道数目 ADC_SQR1:L/ADC_JSQR1:JL
}ADC_InitTypeDef;
cpp
typedef struct
{
uint32_t ADC_Mode; //模式选择 独立 双重 三重 ADC_CCR:MULTI
uint32_t ADC_Prescaler;//分频系数 可选2,4,6,8 ADC_CCR:ADCPRE
uint32_t ADC_DMAAccessMode;//DMA模式,双重或三重才配置 ADC_CCR:DMA
uint32_t ADC_TwoSamplingDelay;//2个采样阶段之间的延迟,仅适用于双重或三重交错模式,ADC_CCR:DELAY
}ADC_CommonInitTypeDef;
做实验是验证理论的唯一方式也是最有效的方式
实验一 独立模式-单通道(中断&DMA)
由于要使用中断,NVIC必不可少,以及对ADC的初始化也必不可少。
由于这里是对滑动变阻器的变化测量模拟电压(接PB0),从而实现AD转换,所以我们将整个初始化函数定义为滑动变阻器的初始化,里面包含3个函数,即ADC的GPIO初始化、ADC结构体初始化、以及NVIC初始化,以及中断函数,分别为:
cpp
static void Rheostat_ADC_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RHEOSTAT_ADC_GPIO_CLK, ENABLE);//RCC_AHB1Periph_GPIOB
GPIO_InitStructure.GPIO_Pin = RHEOSTAT_ADC_GPIO_PIN;//GPIO_Pin_0
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//模拟输入
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ; //不上拉不下拉
GPIO_Init(RHEOSTAT_ADC_GPIO_PORT, &GPIO_InitStructure); //GPIOB
}
cpp
static void Rheostat_ADC_Mode_Config(void)
{
ADC_InitTypeDef ADC_InitStructure;
ADC_CommonInitTypeDef ADC_CommonInitStructure;
RCC_APB2PeriphClockCmd(RHEOSTAT_ADC_CLK , ENABLE);//RCC_APB2Periph_ADC1
// -------------------ADC Common 结构体 参数 初始化------------------------
// 独立ADC模式
ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;//也就是选择ADC1,2,3中的其中一个
// 时钟为fpclk x分频
ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;//4分频,ADC都是挂载到APB2上的,所以这里4分频后ADC频率就是APB2CLK/4=21M
// 禁止DMA直接访问模式
ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;//三重或双重时才配置
// 采样时间间隔
ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_20Cycles; //三重或双重时才配置
ADC_CommonInit(&ADC_CommonInitStructure);
// -------------------ADC Init 结构体 参数 初始化--------------------------
ADC_StructInit(&ADC_InitStructure);//初始化默认值
// ADC 分辨率
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;//12位
// 禁止扫描模式,多通道采集才需要
ADC_InitStructure.ADC_ScanConvMode = DISABLE;//这里使用ADC1的8,就一个通道
// 连续转换
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //转换完成继续转换
//禁止外部边沿触发
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
//外部触发通道,本例子使用软件触发,此值随便赋值即可
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1;
//数据右对齐
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
//转换通道 1个
ADC_InitStructure.ADC_NbrOfConversion = 1;
ADC_Init(RHEOSTAT_ADC, &ADC_InitStructure);//ADC1
//---------------------------------------------------------------------------
// 配置 ADC 通道转换顺序为1,第一个转换,采样时间为56个时钟周期
ADC_RegularChannelConfig(RHEOSTAT_ADC, RHEOSTAT_ADC_CHANNEL, 1, ADC_SampleTime_56Cycles);//ADC_Channel_8
// ADC 转换结束产生中断,在中断服务程序中读取转换值
ADC_ITConfig(RHEOSTAT_ADC, ADC_IT_EOC, ENABLE);//使能ADC的EOC中断
// 使能ADC
ADC_Cmd(RHEOSTAT_ADC, ENABLE);
//开始adc转换,软件触发
ADC_SoftwareStartConv(RHEOSTAT_ADC);
}
cpp
// 配置中断优先级
static void Rheostat_ADC_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);//这里为了验证放到这里,一般建议放到main函数中
NVIC_InitStructure.NVIC_IRQChannel = Rheostat_ADC_IRQ;//ADC_IRQn
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
cpp
void Rheostat_Init(void)
{
Rheostat_ADC_GPIO_Config();
Rheostat_ADC_Mode_Config();
Rheostat_ADC_NVIC_Config();
}
cpp
extern __IO uint16_t ADC_ConvertedValue;// ADC转换的电压值通过MDA方式传到SRAM
// ADC 转换完成中断服务程序
void Rheostat_ADC_INT_FUNCTION(void) //ADC_IRQHandler
{
if(ADC_GetITStatus(RHEOSTAT_ADC,ADC_IT_EOC)==SET)
{
//读取ADC的转换值
ADC_ConvertedValue = ADC_GetConversionValue(RHEOSTAT_ADC);
}
ADC_ClearITPendingBit(RHEOSTAT_ADC,ADC_IT_EOC);
}
那么在主函数中我们怎么验证呢?
cpp
// ADC转换的电压值通过中断方式传到SRAM
__IO uint16_t ADC_ConvertedValue = 0;
// 局部变量,用于保存转换计算后的电压值
float ADC_Vol;
static void Delay(__IO uint32_t nCount); //简单的延时函数
int main(void)
{
/*初始化USART 配置模式为 115200 8-N-1,中断接收*/
Debug_USART_Config();
Rheostat_Init();
while (1)
{
ADC_Vol =(float) ADC_ConvertedValue/4096*(float)3.3; // float变量ADC_Vol,读取转换的AD值
printf("\r\n The current AD value = 0x%04X \r\n", ADC_ConvertedValue); //转换值
printf("\r\n The current AD value = %f V \r\n",ADC_Vol); //对应的电压值
Delay(0xffffee); //简单延时函数
}
}
static void Delay(__IO uint32_t nCount) //简单的延时函数
{
for(; nCount != 0; nCount--);
}
知识点涉及:
1.所有的GPIO都是挂载到AHB1总线上的【中文参考手册:2.3章节】
2.为什么是PB0?开发板原理图:可调电阻部分
3.GPIO的输入模式为什么是模拟输入?通过可调电阻过来的信号就是模拟信号,我们这个实验也是ADC(即将输入进来的模拟电压转换成数字值)
4.当使用GPIO输入模式时速度和输出模式可以不配置,SPEED不用指定是因为仅在输出模式下有效。
5.为什么GPIO的GPIO_PuPd 设置为既不上拉也不下拉,是因为是模拟输入。
6.结构体初始化黄金法则:永远先调用xxx_StructInit(),再修改需要定制的参数,最后调用xxx_Init()完成配置。(这里初始化ADC时就用了这个法则)
7.采用中断方式的原理是:比如通道x转换完成之后,会产生一个EOC(end of complete),需要在中断里面把数据读走(本实验就是采用这个方法),关于中断的介绍不明白的可以看这里STM32之中断(以EXTI为例)-CSDN博客
8.在main函数中0x%04x是什么意思?输出至少4位十六进制数字,不足4位时左边用0填充。这里说明一下,由于我们ADC转换采用12位,固件库函数得到的转换值是以16bit显示的,如果用16进制显示的话,并且采用右对齐,我们这时输出4位的16进制数的话,即0x____中的第一位始终是0的,因为这个位置是不会转换值的(好好领悟)。
上实验修改成独立模式单通道DMA模式,怎么改?(常用DMA,对DMA不熟的看往期教程STM32之DMA--直接存储区访问-CSDN博客)
使用DMA时即将外设-->存储器,也就是P-->M,查表的得到只能使用DMA2的流0的通道0。具体关于DMA2的初始化如下:
cpp
static void Rheostat_ADC_Mode_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
ADC_CommonInitTypeDef ADC_CommonInitStructure;
// ------------------DMA Init 结构体参数 初始化--------------------------
// ADC1使用DMA2,数据流0,通道0,这个是手册固定死的
// 开启DMA时钟
RCC_AHB1PeriphClockCmd(RHEOSTAT_ADC_DMA_CLK, ENABLE); //DMA都是位于AHB1中的
// 外设基址为:ADC 数据寄存器地址
DMA_InitStructure.DMA_PeripheralBaseAddr = RHEOSTAT_ADC_DR_ADDR;//这是ADC1的规则数据寄存器的地址
// 存储器地址,实际上就是一个内部SRAM的变量
DMA_InitStructure.DMA_Memory0BaseAddr = (u32)&ADC_ConvertedValue;
// 数据传输方向为外设到存储器
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
// 缓冲区大小为,指一次传输的数据量
DMA_InitStructure.DMA_BufferSize = 1;
// 外设寄存器只有一个,地址不用递增
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
// 存储器地址固定
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
// 外设数据大小为半字,即两个字节
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
// 存储器数据大小也为半字,跟外设数据大小相同
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
// 循环传输模式
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
// DMA 传输通道优先级为高,当使用一个DMA通道时,优先级设置不影响
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
// 禁止DMA FIFO ,使用直连模式
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
// FIFO 大小,FIFO模式禁止时,这个不用配置
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
// 选择 DMA 通道,通道存在于流中
DMA_InitStructure.DMA_Channel = RHEOSTAT_ADC_DMA_CHANNEL; //通道0
//初始化DMA流,流相当于一个大的管道,管道里面有很多通道
DMA_Init(RHEOSTAT_ADC_DMA_STREAM, &DMA_InitStructure);
// 使能DMA流
DMA_Cmd(RHEOSTAT_ADC_DMA_STREAM, ENABLE);
// 开启ADC时钟
RCC_APB2PeriphClockCmd(RHEOSTAT_ADC_CLK , ENABLE);
// -------------------ADC Common 结构体 参数 初始化------------------------
// 独立ADC模式
ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
// 时钟为fpclk x分频
ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;
// 禁止DMA直接访问模式
ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
// 采样时间间隔
ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_20Cycles;
ADC_CommonInit(&ADC_CommonInitStructure);
// -------------------ADC Init 结构体 参数 初始化--------------------------
ADC_StructInit(&ADC_InitStructure);
// ADC 分辨率
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
// 禁止扫描模式,多通道采集才需要
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
// 连续转换
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
//禁止外部边沿触发
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
//外部触发通道,本例子使用软件触发,此值随便赋值即可
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1;
//数据右对齐
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
//转换通道 1个
ADC_InitStructure.ADC_NbrOfConversion = 1;
ADC_Init(RHEOSTAT_ADC, &ADC_InitStructure);
//---------------------------------------------------------------------------
// 配置 ADC 通道转换顺序为1,第一个转换,采样时间为56个时钟周期
ADC_RegularChannelConfig(RHEOSTAT_ADC, RHEOSTAT_ADC_CHANNEL, 1, ADC_SampleTime_56Cycles);
// 使能DMA请求 after last transfer (Single-ADC mode)
ADC_DMARequestAfterLastTransferCmd(RHEOSTAT_ADC, ENABLE);
// 使能ADC DMA
ADC_DMACmd(RHEOSTAT_ADC, ENABLE);
// 使能ADC
ADC_Cmd(RHEOSTAT_ADC, ENABLE);
//开始adc转换,软件触发
ADC_SoftwareStartConv(RHEOSTAT_ADC);
}
每个ADC都有自己独立的数据寄存器。(从外设的地址可以看出来,这里ADC1的规则数据寄存器ADC_DR在固件库中的地址是((u32)ADC1+0x4c),0x4c是偏移地址其中ADC1就是表示ADC1的基地址,当然ADC2也有自己的基地址)。
实验二 独立模式-多通道采集-DMA
编程流程:(下面的流程对照着程序理解会更透彻)
1.初始化配置ADC的GPIO输入模式为模拟输入;
2.使能ADC时钟和DMA时钟;
3.配置DMA从规则数据寄存器传输到我们指定的存储区;
4.配置通用ADC为独立模式、4分频;
5.设置ADC为12位分辨率、启动扫描、连续转换、不需要外部触发;
6.设置ADC的转换通道顺序以及采样时间;
7.使能DMA请求,DMA在AD转换完成后自动传输数据到指定的存储区;
8.启动ADC转换;
9.使能软件触发ADC转换。
本实验使用ADC1的多通道,其中PB0--通道8--电位器,PB1--通道9--外接光敏电阻(这里只接极限电压),PA6--通道6--使用杜邦线接极限电压。与实验一编程流程类似:
GPIO初始化:
cpp
static void Rheostat_ADC_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/*==========通道1 PB0接的电位器(滑动变阻器)=========*/
// 使能 GPIO 时钟
RCC_AHB1PeriphClockCmd(RHEOSTAT_ADC_GPIO_CLK1,ENABLE);
// 配置 IO
GPIO_InitStructure.GPIO_Pin = RHEOSTAT_ADC_GPIO_PIN1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
//不上拉不下拉
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
GPIO_Init(RHEOSTAT_ADC_GPIO_PORT1, &GPIO_InitStructure);
/*==========通道2 PB1通过调帽外接光敏电阻==============*/
// 使能 GPIO 时钟
RCC_AHB1PeriphClockCmd(RHEOSTAT_ADC_GPIO_CLK2,ENABLE);
// 配置 IO
GPIO_InitStructure.GPIO_Pin = RHEOSTAT_ADC_GPIO_PIN2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
//不上拉不下拉
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
GPIO_Init(RHEOSTAT_ADC_GPIO_PORT2, &GPIO_InitStructure);
/*================通道3 PA6使用杜邦线=================*/
// 使能 GPIO 时钟
RCC_AHB1PeriphClockCmd(RHEOSTAT_ADC_GPIO_CLK3,ENABLE);
// 配置 IO
GPIO_InitStructure.GPIO_Pin = RHEOSTAT_ADC_GPIO_PIN3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
//不上拉不下拉
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
GPIO_Init(RHEOSTAT_ADC_GPIO_PORT3, &GPIO_InitStructure);
}
DMA初始化、通用ADC初始化、ADC初始化、设置转换顺序、使能DMA请求并打开ADC的DMA、使能ADC并软件触发ADC:
cpp
static void Rheostat_ADC_Mode_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
ADC_CommonInitTypeDef ADC_CommonInitStructure;
// ------------------DMA Init 结构体参数 初始化--------------------------
// ADC1使用DMA2,数据流0,通道0,这个是手册固定死的
// 开启DMA时钟
RCC_AHB1PeriphClockCmd(RHEOSTAT_ADC_DMA_CLK, ENABLE); //DMA2位于AHB1上
// 外设基址为:ADC 数据寄存器地址
DMA_InitStructure.DMA_PeripheralBaseAddr = RHEOSTAT_ADC_DR_ADDR;//ADC1的规则数据寄存器
// 存储器地址,实际上就是一个内部SRAM的变量
DMA_InitStructure.DMA_Memory0BaseAddr = (u32)ADC_ConvertedValue;
// 数据传输方向为外设到存储器
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
// 缓冲区大小为,指一次传输的数据量
DMA_InitStructure.DMA_BufferSize = RHEOSTAT_NOFCHANEL; //3 DMA传输一轮需要传输多少个数据
// 外设寄存器只有一个,地址不用递增
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
// 存储器地址递增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
// // 外设数据大小为半字,即两个字节
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //16位
// 存储器数据大小也为半字,跟外设数据大小相同
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
// 循环传输模式
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;//采集完一轮后继续传输下一轮
// DMA 传输通道优先级为高,当使用一个DMA通道时,优先级设置不影响
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
// 禁止DMA FIFO ,使用直连模式
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
// FIFO 大小,FIFO模式禁止时,这个不用配置
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
// 选择 DMA 通道,通道存在于流中
DMA_InitStructure.DMA_Channel = RHEOSTAT_ADC_DMA_CHANNEL; //通道0
//初始化DMA流,流相当于一个大的管道,管道里面有很多通道
DMA_Init(RHEOSTAT_ADC_DMA_STREAM, &DMA_InitStructure);//DMA2流0
// 使能DMA流
DMA_Cmd(RHEOSTAT_ADC_DMA_STREAM, ENABLE);
// 开启ADC时钟
RCC_APB2PeriphClockCmd(RHEOSTAT_ADC_CLK , ENABLE);//ADC位于APB2
// -------------------ADC Common 结构体 参数 初始化------------------------
// 独立ADC模式
ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
// 时钟为fpclk x分频
ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;
// 禁止DMA直接访问模式
ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
// 采样时间间隔
ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_20Cycles;
ADC_CommonInit(&ADC_CommonInitStructure);
// -------------------ADC Init 结构体 参数 初始化--------------------------
ADC_StructInit(&ADC_InitStructure);//先初始化
// ADC 分辨率
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
// 扫描模式,多通道采集需要
ADC_InitStructure.ADC_ScanConvMode = ENABLE;
// 连续转换
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
//禁止外部边沿触发
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
//外部触发通道,本例子使用软件触发,此值随便赋值即可
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1;
//数据右对齐
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
//转换通道 3个
ADC_InitStructure.ADC_NbrOfConversion = RHEOSTAT_NOFCHANEL;
ADC_Init(RHEOSTAT_ADC, &ADC_InitStructure);
//---------------------------------------------------------------------------
// 配置 ADC 通道转换顺序和采样时间周期
ADC_RegularChannelConfig(RHEOSTAT_ADC, RHEOSTAT_ADC_CHANNEL1, 1,
ADC_SampleTime_3Cycles);
ADC_RegularChannelConfig(RHEOSTAT_ADC, RHEOSTAT_ADC_CHANNEL2, 2,
ADC_SampleTime_3Cycles);
ADC_RegularChannelConfig(RHEOSTAT_ADC, RHEOSTAT_ADC_CHANNEL3, 3,
ADC_SampleTime_3Cycles);
// 使能DMA请求 after last transfer (Single-ADC mode)
ADC_DMARequestAfterLastTransferCmd(RHEOSTAT_ADC, ENABLE);
// 使能ADC DMA
ADC_DMACmd(RHEOSTAT_ADC, ENABLE);
// 使能ADC
ADC_Cmd(RHEOSTAT_ADC, ENABLE);
//开始adc转换,软件触发
ADC_SoftwareStartConv(RHEOSTAT_ADC);
}
封装到一个函数中:
cpp
void Rheostat_Init(void)
{
Rheostat_ADC_GPIO_Config();
Rheostat_ADC_Mode_Config();
}
这里定义了有3个成员的数组(16位),在main函数中赋值,转换,并输出:
cpp
// ADC转换的电压值通过MDA方式传到SRAM
extern __IO uint16_t ADC_ConvertedValue[RHEOSTAT_NOFCHANEL];//3个成员的16位数组 RHEOSTAT_NOFCHANEL=3
// 局部变量,用于保存转换计算后的电压值
float ADC_ConvertedValueLocal[RHEOSTAT_NOFCHANEL]={0};
static void Delay(__IO uint32_t nCount) //简单的延时函数
{
for(; nCount != 0; nCount--);
}
int main(void)
{
/*初始化USART 配置模式为 115200 8-N-1,中断接收*/
Debug_USART_Config();
Rheostat_Init();
while (1)
{
ADC_ConvertedValueLocal[0] =(float) ADC_ConvertedValue[0]/4096*(float)3.3;
ADC_ConvertedValueLocal[1] =(float) ADC_ConvertedValue[1]/4096*(float)3.3;
ADC_ConvertedValueLocal[2] =(float) ADC_ConvertedValue[2]/4096*(float)3.3;
printf("\r\n CH1_PB0 value = %f V \r\n",ADC_ConvertedValueLocal[0]);
printf("\r\n CH2_PB1 value = %f V \r\n",ADC_ConvertedValueLocal[1]);
printf("\r\n CH3_PA6 value = %f V \r\n",ADC_ConvertedValueLocal[2]);
printf("\r\n\r\n");
Delay(0xffffff);
}
}
最后使用串口输出。值得注意的是:使用了独立模式的3个通道,DMA外设地址是规则数据寄存器,只有一个,对这3个通道是轮换使用,其中我们设置接收的存储区设置为有3个成员的数组(uint16_t类型,这是根据传过来的数据的位数决定的,规则数据寄存器只有低16位有效,虽然是16位,由于后面设置ADC的分辨率为12位其实只有12位是有转换值的),后面的外设宽度和存储器宽度也就设为了半字(2字节即16位),DMA的BufferSize也就是一次传输的数据量,设置为数组长度也就是3,就是一次传输3个数据,根据后面的DMA模式为连续,也就是说传输的这3个数据会轮询的传到这个数组中,每次覆盖原来的值。在配置ADC初始化的时候,将扫描模式使能即可。最后配置ADC的转换顺序,这里将宏定义的通道1也就是PB0对应的通道8,设置为第1个转换(这里配置的是规则序列寄存器的值),其他两个通道设置类似。最后使能下ADC DMA的发送请求,这是独立模式下的,操作的是ADC_CR2寄存器的DDS位,再使能ADC DMA功能,操作的是ADC_CR2寄存器的DMA位。最后使能ADC,操作的是ADC_CR2寄存器的ADON位,最后开启软件触发,操作的是ADC_CR2寄存器的SWSTART位。另外还有一个转换通道也设置为3个,是因为这是独立模式下ADC,我们使用了ADC1下的3个通道,所以设置为3.
实验三 双重模式-多通道-规则同步
以下图片来源于固件库的例程(ADC部分):

规则指的是规则通道,同步指的是ADC1和ADC2是同时采集的。如上图,ADC1采集通道10和通道11,ADC2采集通道11和通道12。ADC1对通道10进行采样的时候,这里采样周期为3(一个表格代表一个周期),同时ADC2也在采集自己的通道11,ADC1转换完是CH10data,ADC2转换完是CH11data,此时产生DMA请求,在使用双重和三重ADC时必须配合DMA使用。DMA模式有3种,规则同步使用DMA模式1,也就是说一有数据就产生一个DMA请求,然后将数据传输到预定义好的数组里面,双重时首先传输ADC1,然后传输ADC2,这里定义了4个成员的数组,传输顺序为ADC1CH10-->ADC2CH11-->ADC1CH11-->ADC2CH12(如上图),其中前两个数据是第一次采集同时产生的数据,ADC1和ADC2都是多通道采集,并行同时的,后两个数据时第二次采集同时产生的数据。
我们做的实验采用的是双重模式,单通道。
编程要点:
1.初始化ADC的GPIO(多/单通道)
2.配置ADC初始化结构体(两个ADC都要配置)
3.配置通道的转换顺序、配置DMA(是否3个ADC的DMA都要配置?)、触发ADC转换(主ADC转换即可)
4.编写main函数
当使用双重模式时,数据进行DMA传输的时候,传输的是ADC_CDR里面的数据。ADC1转换的时候首先是将转换的数据放到自己的ADC1_DR里面的,ADC2同理,当一产生DMA请求的时候,ADC1_DR先传到ADC_CDR里面,再从ADC_CDR里面传到预先定义好的变量里面,当产生第二个请求时,ADC2和ADC1同时准备好的,但是传输分两次传输,使用DMA模式1,第二次DMA传输,ADC2_DR先传到ADC_CDR里面,再从ADC_CDR里面传到预先定义好的变量里面。
DMA传输的时候使用ADC_CDR但是真正同时采集数据,数据还是放到他们各自的规则数据寄存器,只是说在传输的时候需要放到中转的ADC_CDR里面,然后再通过DMA控制器搬到预先定义好的变量中。
双重只能是ADC1和ADC2,不能是ADC1和ADC3或者ADC2和ADC3,此时ADC1是主,ADC2是从,当选择DMA控制器时必须选择主ADC,不能使用ADC2的。
具体程序如下:
宏定义:
cpp
/***********************宏定义************************/
// PB0 通过调帽接电位器
#define ADC_GPIO_PORT1 GPIOB
#define ADC_GPIO_PIN1 GPIO_Pin_0
#define ADC_GPIO_CLK1 RCC_AHB1Periph_GPIOB
// PB1 通过调线接极限电压测试
#define ADC_GPIO_PORT2 GPIOB
#define ADC_GPIO_PIN2 GPIO_Pin_1
#define ADC_GPIO_CLK2 RCC_AHB1Periph_GPIOB
//ADC_CDR地址
#define ADC_CDR_ADDR (uint32_t)(ADC1_BASE+0x300+0x08)
//ADC DMA宏定义
#define ADC_DMA_CLK RCC_AHB1Periph_DMA2
#define ADC_DMA_CHANNEL DMA_Channel_0
#define ADC_DMA_STREAM DMA2_Stream0
#define ADC1_CLK RCC_APB2Periph_ADC1
#define ADC1_CHANNEL ADC_Channel_8
#define ADC2_CLK RCC_APB2Periph_ADC2
#define ADC2_CHANNEL ADC_Channel_9
函数定义:
cpp
#include "./adc/bsp_adc.h"
__IO uint16_t ADC_ConvertedValue[2];
static void ADC_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(ADC_GPIO_CLK1, ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStructure.GPIO_Pin = ADC_GPIO_PIN1;
GPIO_Init(ADC_GPIO_PORT1, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = ADC_GPIO_PIN2;
GPIO_Init(ADC_GPIO_PORT2, &GPIO_InitStructure);
}
static void ADC_DMA_Config()
{
DMA_InitTypeDef DMA_InitStructure;
/* ADC1使用DMA2,数据流0,通道0 手册定死的 */
RCC_AHB1PeriphClockCmd(ADC_DMA_CLK, ENABLE);
DMA_InitStructure.DMA_Channel = ADC_DMA_CHANNEL;
DMA_InitStructure.DMA_PeripheralBaseAddr = ADC_CDR_ADDR;
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)ADC_ConvertedValue;
DMA_InitStructure.DMA_BufferSize = 2;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
//禁止DMA FIFO,使用直连模式 关于FIFO的不用配置 不起作用
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
DMA_Init(ADC_DMA_STREAM, &DMA_InitStructure);//初始化DMA结构体
DMA_Cmd(ADC_DMA_STREAM, ENABLE);//使能DMA
}
static void ADC_Mode_Config()
{
ADC_CommonInitTypeDef ADC_CommonInitStructure;
ADC_InitTypeDef ADC_InitStructure;
RCC_APB2PeriphClockCmd(ADC1_CLK|ADC2_CLK, ENABLE);
ADC_CommonInitStructure.ADC_Mode = ADC_DMAAccessMode_1;//DMA模式1
ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DualMode_RegSimult;//规则同步模式
ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;//4分频时钟
ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_10Cycles;//3重模式才用到
ADC_CommonInit(&ADC_CommonInitStructure); //ADC共用的,只需要初始化1次
ADC_StructInit(&ADC_InitStructure);
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;//分辨率
ADC_InitStructure.ADC_ScanConvMode = DISABLE;//禁止扫描模式后,采集多通道才使用
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;//连续转换
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;//禁止外部边沿触发
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1;//外部触发通道,本例使用软件触发,此值随便赋值即可
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//数据对齐
ADC_InitStructure.ADC_NbrOfConversion = 1;//转换通道
ADC_Init(ADC1, &ADC_InitStructure);//初始化ADC1
//配置ADC1转换通道,顺序为1,采样时间为3个时钟周期
ADC_RegularChannelConfig(ADC1, ADC1_CHANNEL, 1, ADC_SampleTime_3Cycles);
ADC_Init(ADC2, &ADC_InitStructure);//初始化ADC2
//配置ADC2转换通道,顺序为1,采样时间为3个时钟周期
ADC_RegularChannelConfig(ADC2, ADC2_CHANNEL, 1, ADC_SampleTime_3Cycles);
//使能DMA1请求
ADC_MultiModeDMARequestAfterLastTransferCmd(ENABLE);//控制CCR的DDS位
//ADC_DMACmd(ADC1, ENABLE);//使能主ADC的DMA传输
ADC_Cmd(ADC1, ENABLE);//使能ADC1
ADC_Cmd(ADC2, ENABLE);//使能ADC2
ADC_SoftwareStartConv(ADC1);//开始主ADC转换
//ADC_SoftwareStartConv(ADC2);//写不写都成
}
void ADC_Config(void)
{
ADC_GPIO_Config();
ADC_DMA_Config();
ADC_Mode_Config();
}
mian函数:
cpp
extern __IO uint16_t ADC_ConvertedValue[2];
float ADC_ConvertedValueLocal[2];
static void Delay(__IO uint32_t nCount); //简单的延时函数
int main(void)
{
/*初始化USART 配置模式为 115200 8-N-1,中断接收*/
Debug_USART_Config();
ADC_Config();
while(1)
{
Delay(0xffffee);
ADC_ConvertedValueLocal[0] = (float) ((uint16_t)ADC_ConvertedValue[0]*3.3/4096);
ADC_ConvertedValueLocal[1] = (float) ((uint16_t)ADC_ConvertedValue[1]*3.3/4096);
printf("\r\n The Current AD value = 0x%08X \r\n", ADC_ConvertedValue[0]);
printf("\r\n The Current AD value = 0x%08X \r\n", ADC_ConvertedValue[1]);
printf("\r\n The Current AD1 value = %f V \r\n", ADC_ConvertedValueLocal[0]);
printf("\r\n The Current AD2 value = %f V \r\n", ADC_ConvertedValueLocal[1]);
}
}
static void Delay(__IO uint32_t nCount) //简单的延时函数
{
for(; nCount != 0; nCount--);
}
实验四 三重模式-单通道-交替采集
交替模式只能用于规则组(通常为1个通道),提高采样率,两个ADC之间的延时最少是6个时钟周期。

这里至少需要11个周期才能够获取到数据,如果有两路ADC对同一通道进行采集,首先ADC1进行采集,然后转换,进过6个时钟周期之后ADC2采集,开始转换,这样ADC1和ADC2采集的数据都是针对第一个通道的。这时两次采集的数据间隔提高到5个周期,提升了2倍,如果是3个ADC采集的话将会更快。ADC1准备好了,当ADC2也准备好了,就会产生一个32位的DMA请求,把ADC1和ADC2的数据放到CDR寄存器,低位放ADC1,高位放ADC2,组合成32位数据,通过DMA传到定义好的SRAM。这使用的是DMA2模式。也就是当有2个数据可用的时候就产生一个DMA请求
我们做的实验采用的是三重模式,单通道,交替模式。
编程要点:
1.初始化ADC的GPIO(单通道)
2.配置ADC初始化结构体(3个ADC都要配置)
3.配置通道的转换顺序、配置DMA(是否3个ADC的DMA都要配置?)、触发ADC转换(主ADC转换即可)
4.编写main函数
这里要选择3个ADC都有的GPIO通道。这里用的是PC2。当我们使用多重模式时,ADC1都是主,所以只需要选择主通道的流控制器就可。3重ADC时,DMA模式2,每当有两个数据项可用时,就会生成一个DMA请求。如下图:

产生3个请求时,已经传输完一轮,ADC也采集完一轮,那么就会接着下一轮采集,对于32位的变量来看,低16位首先是ADC1、3、2。
具体程序如下:
宏定义:
cpp
/***********************宏定义************************/
// PC2
#define ADC_GPIO_PORT1 GPIOC
#define ADC_GPIO_PIN1 GPIO_Pin_2
#define ADC_GPIO_CLK1 RCC_AHB1Periph_GPIOC
//ADC_CDR地址
#define ADC_CDR_ADDR (uint32_t)(ADC1_BASE+0x300+0x08)
//ADC DMA宏定义
#define ADC_DMA_CLK RCC_AHB1Periph_DMA2
#define ADC_DMA_CHANNEL DMA_Channel_0
#define ADC_DMA_STREAM DMA2_Stream0
//ADC序号宏定义
#define ADC1_CLK RCC_APB2Periph_ADC1
#define ADC1_CHANNEL ADC_Channel_12
#define ADC2_CLK RCC_APB2Periph_ADC2
#define ADC2_CHANNEL ADC_Channel_12
#define ADC3_CLK RCC_APB2Periph_ADC3
#define ADC3_CHANNEL ADC_Channel_12
函数定义:
cpp
__IO uint16_t ADC_ConvertedValue[3];
static void ADC_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(ADC_GPIO_CLK1, ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStructure.GPIO_Pin = ADC_GPIO_PIN1;
GPIO_Init(ADC_GPIO_PORT1, &GPIO_InitStructure);
}
static void ADC_DMA_Config()
{
DMA_InitTypeDef DMA_InitStructure;
/* ADC1使用DMA2,数据流0,通道0 手册定死的 */
RCC_AHB1PeriphClockCmd(ADC_DMA_CLK, ENABLE);
DMA_InitStructure.DMA_Channel = ADC_DMA_CHANNEL;
DMA_InitStructure.DMA_PeripheralBaseAddr = ADC_CDR_ADDR;
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)ADC_ConvertedValue;
DMA_InitStructure.DMA_BufferSize = 3;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;//字
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;//字
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
//禁止DMA FIFO,使用直连模式 关于FIFO的不用配置 不起作用
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
DMA_Init(ADC_DMA_STREAM, &DMA_InitStructure);//初始化DMA结构体
DMA_Cmd(ADC_DMA_STREAM, ENABLE);//使能DMA
}
static void ADC_Mode_Config()
{
ADC_CommonInitTypeDef ADC_CommonInitStructure;
ADC_InitTypeDef ADC_InitStructure;
RCC_APB2PeriphClockCmd(ADC1_CLK|ADC2_CLK|ADC3_CLK, ENABLE);
ADC_CommonInitStructure.ADC_Mode = ADC_TripleMode_Interl;//触发模式
ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_2;//DMA模式2
ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;//4分频时钟
ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_6Cycles;//3重模式才用到
ADC_CommonInit(&ADC_CommonInitStructure); //ADC共用的,只需要初始化1次
ADC_StructInit(&ADC_InitStructure);
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;//分辨率
ADC_InitStructure.ADC_ScanConvMode = DISABLE;//禁止扫描模式后,采集多通道才使用
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;//连续转换
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;//禁止外部边沿触发
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1;//外部触发通道,本例使用软件触发,此值随便赋值即可
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//数据对齐
ADC_InitStructure.ADC_NbrOfConversion = 1;//转换通道
ADC_Init(ADC1, &ADC_InitStructure);//初始化ADC1
//配置ADC1转换通道,顺序为1,采样时间为3个时钟周期
ADC_RegularChannelConfig(ADC1, ADC1_CHANNEL, 1, ADC_SampleTime_3Cycles);
ADC_Init(ADC2, &ADC_InitStructure);//初始化ADC2
//配置ADC2转换通道,顺序为1,采样时间为3个时钟周期
ADC_RegularChannelConfig(ADC2, ADC2_CHANNEL, 1, ADC_SampleTime_3Cycles);
ADC_Init(ADC3, &ADC_InitStructure);//初始化ADC2
//配置ADC2转换通道,顺序为1,采样时间为3个时钟周期
ADC_RegularChannelConfig(ADC3, ADC3_CHANNEL, 1, ADC_SampleTime_3Cycles);
//使能DMA1请求
ADC_MultiModeDMARequestAfterLastTransferCmd(ENABLE);//控制CCR的DDS位
//ADC_DMACmd(ADC1, ENABLE);//针对单个DMA的
ADC_Cmd(ADC1, ENABLE);//使能ADC1
ADC_Cmd(ADC2, ENABLE);//使能ADC2
ADC_Cmd(ADC3, ENABLE);//使能ADC3
ADC_SoftwareStartConv(ADC1);//开始主ADC转换
//ADC_SoftwareStartConv(ADC2);//写不写都成
//ADC_SoftwareStartConv(ADC3);//写不写都成
}
void ADC_Config(void)
{
ADC_GPIO_Config();
ADC_DMA_Config();
ADC_Mode_Config();
}
main函数:
cpp
extern __IO uint16_t ADC_ConvertedValue[3];
float ADC_ConvertedValueLocal[3];
static void Delay(__IO uint32_t nCount); //简单的延时函数
int main(void)
{
/*初始化USART 配置模式为 115200 8-N-1,中断接收*/
Debug_USART_Config();
ADC_Config();
while(1)
{
Delay(0xffffee);
ADC_ConvertedValueLocal[0] = (float) ((uint16_t)ADC_ConvertedValue[0]*3.3/4096);
ADC_ConvertedValueLocal[1] = (float) ((uint16_t)ADC_ConvertedValue[1]*3.3/4096);
ADC_ConvertedValueLocal[2] = (float) ((uint16_t)ADC_ConvertedValue[2]*3.3/4096);
printf("\r\n The Current AD value = 0x%08X \r\n", ADC_ConvertedValue[0]);
printf("\r\n The Current AD value = 0x%08X \r\n", ADC_ConvertedValue[1]);
printf("\r\n The Current AD value = 0x%08X \r\n", ADC_ConvertedValue[2]);
printf("\r\n The Current AD1 value = %f V \r\n", ADC_ConvertedValueLocal[0]);
printf("\r\n The Current AD3 value = %f V \r\n", ADC_ConvertedValueLocal[1]);
printf("\r\n The Current AD2 value = %f V \r\n", ADC_ConvertedValueLocal[2]);
}
}
static void Delay(__IO uint32_t nCount) //简单的延时函数
{
for(; nCount != 0; nCount--);
}
拓展实验--内部温度传感器ADC测量与换算(独立模式单通道)
STM32多数型号(F1/F4/L0系列)内置温度传感器,用于检测芯片内部的温度。以F407为例,其连接到ADC1的通道16(需要使能ADC_CCR的TSVREFE位),测量的范围是:


由数据手册可知:
Average slope(平均斜率):温度每升高1℃,Vsensor输出电压大约上升2.5mV;
V25:在25℃时的电压值为0.76V;
根据线性关系我手写以下两个方法(在附件程序中也有体现):

实验程序见附件。