目录
一、ADC功能框图
1.电压输入范围
2.输入通道
3.转换顺序
4.触发源
5.转换时间
6.数据寄存器
二、ADC初始化结构体介绍
三、ADC常用固件库函数
四、实验设计
1.独立模式-单通道-中断读取
2.独立模式-(单)多通道-必须采用DMA读取
3.双重模式-多通道-规则同步
简介:STM32单片机中含有
1-三个独立的ADC 1 / 2 / 3
2-分辨率为12位
3-每个ADC具有18个通道,其中外部通道16个,可测量16个外部和2个内部信号源。
例如:温度传感器/VREFINT内部通道:温度传感器和通道ADC1_IN16相连接,内部参照电压VREFINT和ADC1_IN17相连接。可以按注入或规则通道对这两个内部通道进行转换。
输入电压:VREF- ≤ VIN ≤ VREF+
决定输入电压的引脚:VREF-、VREF+、VDDA、VSSA
参考负电压VREF接到VSSA,VSSA接地;参考正电压VREF+接到VDDA,VDDA接3V3,得到ADC 的输入电压范围为: 0~3.3V。
由于MINI板子的芯片用的是64脚的,所以就没有VREF-、VREF+两个引脚,输入电压范围只由VDDA、VSSA确定。只有100脚以上的芯片才有这两个引脚。
由于板上引脚资源紧张,我们只留下通道11:PC1作为单独的ADC通道,不与其他任何外设相关。
在使用ADC转换时,VAR和PC1排针要短接,我们可以把蜂鸣器与A15的排针拔掉去用。
外部的16个通道在转换的时候又分为规则通道和注入通道,其中规则通道最多有16路,注入通道最多有4路。
通道的分类:
规则通道:很规矩的意思,我们平时一般使用的就是这个通道。
注入通道:注入,可以理解为插入,插队的意思。它是一种在规则通道转换的时候强行插入要转换的一种。这点跟中断程序很像,规则通道正在执行过程中注入通道插入执行。所以,注入通道只有在规则通道存在时才会出现。
1-规则序列寄存器
如果我们需要转换3个通道,SQR1寄存器的低4位取值就是3,要设置第3个通道为第一个转换的通道,SQR3寄存器的SQ1位取值就是3,设置第2个通道为第三个转换的通道,SQR3寄存器的SQ3位取值就是2。
2-注入序列寄存器
我们设置1、5、6、7作为注入通道,通道7个作为第一个转换,JSQR寄存器的JSQ1位就该置7;通道1作为最后一个转换,JSQR寄存器的JSQ4位就该置1.以上这是4个注入通道的时候,如果注入通道小于4个,就需要按图片上面的配置。
1、软件触发:ADC_CR2控制寄存器2 :ADON/SWST(规则通道)
ART/JSWSTART(注入通道)
2、外部事件触发:内部定时器/外部IO
选择:ADC_CR2 :EXTSEL[2:0]和 JEXTSEL[2:0]
激活:ADC_CR2 :EXTEN 和 JEXTEN
转换时间:Tconv = 采样时间 + 12.5 个固定的周期
周期指的就是ADC_CLK:ADC模拟电路时钟,最大值为14M,由PCLK2 (PCLK2=72M)提供,还可分频:2/4/6/8,分频因子由RCC_CFGR控制器的ADCPRE[1:0]设置。通常我们使用6分频=12M.
采样时间由ADC 采样时间寄存器 ADC_SMPR1和ADC_SMPR2控制,通道0-9是寄存器2,通道10-17由寄存器1控制。
数字时钟:RCC_APB2ENR,用于访问寄存器
一切准备就绪后, ADC 转换后的数据根据转换组的不同,规则组的数据放在ADC_DR 寄存器,注入组的数据放在JDRx。
ADC1和ADC2的数据共用这一个寄存器
1-16位有效,用于存放独立模式转换完成数据
2- ADC_CR2 :ALIGN位控制左对齐还是右对齐,一般采取右对齐
3-该寄存器只有一个,多通道采集的是最好使用DMA,数据一旦产生就接着搬运走。
1-16位有效,用于存放注入通道转换完成数据
2- ADC_CR2 :ALIGN
3-有4个这样的寄存器
1-ADC_MODE:模式选择,ADC_CR1寄存器的DUALMOD位
独立模式使用最多:单独的使用ADC1/2/3。
规则同步模式:规则通道,两个ADC同时使用,一主一从,两个ADC同时采集转换数据。采集到的数据存放在ADC规则数据寄存器内(ADC_DR)
交替触发模式:两个ADC,一个采集数据的时候另一个进行数据转换,提高工作项效率。
2-ADC_ScanConvMode:扫描模式,ADC_CR1寄存器的SCAN位
0:关闭扫描模式;1:使用扫描模式。用于多通道采集(采集多个信号源)的情况下。
3-ADC_ContinuousConvMode:连续转换模式,ADC_CR2寄存器的CON位
0:单次转换模式;1:连续转换模式。单次转换:采集一个点就结束,连续转换:不断对外部信号源进行采集并且转换为数字信号量。
4-ADC_ExternalTrigConv:外部触发转换选择,ADC_CR2寄存器的EXTTRIG和EXTSEL[2:0]位
确定好要转换多少个通道,一切就绪之后,我们就要给ADC一个信号,让它开始转换。这个信号可以用软件触发,也可以由外部触发。
外部触发:下图中的11和15引脚,也就是板子上的所有11和15引脚发生的电平变化就可以产生一个事件,这个事件可以触发ADC进行转换。
我们通常采用软件触发。
5-ADC_DataAlign:数据对齐格式,ADC_CR2寄存器的ALIGN位。我们通常使用右对齐。
6-ADC_NbrOfChannel:转换的通道数,配置规则序列寄存器和注入序列寄存器。
规则序列寄存器(ADC_SQR1)
注入序列寄存器(ADC_JSQR)
ADC_Init(); 初始化结构体成员
RCC_ADCCLKConfig(); ADC时钟配置 配置采样时钟的分频因子
ADC_RegularChannelConfig();规则通道配置 配置采样时间,转换顺序
ADC_Cmd();
ADC_SoftwareStartConvCmd(); 软件触发
ADC_ExternalTrigConvCmd(); 外部触发
ADC_DMACmd(); DMA相关
首先是初始化及配置端口引脚。一个是配置ADC用到的GPIO,然后配置、初始化ADC结构体,最后配置NVIC,处理中断。
static void ADCx_GPIO_Config(void)//配置 ADC 用到的 GPIO
{
GPIO_InitTypeDef GPIO_InitStructure;
// 打开 ADC IO端口时钟
ADC_GPIO_APBxClock_FUN ( ADC_GPIO_CLK, ENABLE );
// 配置 ADC IO 引脚模式
// 必须为模拟输入
GPIO_InitStructure.GPIO_Pin = ADC_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
//只有在 GPIO口 用作输出的时候才需要配置速度,输入不需要
// 初始化 ADC IO
GPIO_Init(ADC_PORT, &GPIO_InitStructure);
}
static void ADC_Mode_Config(void)//配置、初始化 ADC 结构体
{
ADC_InitTypeDef ADC_InitStructure;
// 打开ADC时钟
ADC_APBxClock_FUN ( ADC_CLK, ENABLE );
// ADC 模式配置
// 只使用一个ADC,属于独立模式
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//配置为独立模式
// 禁止扫描模式,多通道才要,单通道不需要
ADC_InitStructure.ADC_ScanConvMode = DISABLE ;
// 连续转换模式
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
// 不用外部触发转换,软件开启即可
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
// 转换结果右对齐
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
// 转换通道1个
ADC_InitStructure.ADC_NbrOfChannel = 1;
// 初始化ADC
ADC_Init(ADCx, &ADC_InitStructure);
/*配置采样周期*/
// 配置ADC时钟为PCLK2的8分频,即9MHz
RCC_ADCCLKConfig(RCC_PCLK2_Div8);
// 配置 ADC 通道转换顺序和采样时间
ADC_RegularChannelConfig(ADCx,ADC_CHANNEL,1,ADC_SampleTime_55Cycles5);
/*数据转换结束产生中断*/
// ADC 转换结束产生中断,在中断服务程序中读取转换值
ADC_ITConfig(ADCx, ADC_IT_EOC, ENABLE);
// 开启ADC ,并开始转换
ADC_Cmd(ADCx, ENABLE);
// 初始化ADC 校准寄存器
ADC_ResetCalibration(ADCx);
// 等待校准寄存器初始化完成
while(ADC_GetResetCalibrationStatus(ADCx));
// ADC开始校准
ADC_StartCalibration(ADCx);
// 等待校准完成
while(ADC_GetCalibrationStatus(ADCx));
// 由于没有采用外部触发,所以使用软件触发ADC转换
ADC_SoftwareStartConvCmd(ADCx, ENABLE);
}
static void ADCx_NVIC_Config(void)//配置 NVIC,处理中断
{
NVIC_InitTypeDef NVIC_InitStructure;
// 优先级分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
// 配置中断优先级
NVIC_InitStructure.NVIC_IRQChannel = ADC_IRQ;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
然后再写中断程序,检测到产生中断之后,读取ADC的转换值。
void ADC_IRQHandler(void)
{
if (ADC_GetITStatus(ADCx,ADC_IT_EOC)==SET)//检测是否产生了中断
{
// 读取ADC的转换值
ADC_ConvertedValue = ADC_GetConversionValue(ADCx);
}
ADC_ClearITPendingBit(ADCx,ADC_IT_EOC);
}
主函数里面,对转换出来的模拟量进行转换,转换成外部采集到的模拟量。
电压的模拟范围是0-3.3V,数字量的模拟范围:2的12次方 = 4096。3.3 / 4096 = 模拟量对应数字量的最小精度。在此基础上再乘转换出来的模拟量 就等于外部采集的模拟量。
采用DMA高效的读取转换的数据。
我们用通道10-15做实验,这些通道对应的引脚是PC0-PC5,用到的时钟就是C端口的时钟。但是ADC2没有DMA功能,我们只能用ADC1做实验。
先采集通道一的数据,然后进行数据转换,数据放到规则数据寄存器里面。但由于我们是多通道采集数据,如果不及时读走数据就会被覆盖,所以数据放到规则数据寄存器里就应该立马被读走。我们可以预先定义一个数组,DMA立刻将规则数据寄存器里的数据读到数组里面去(我们用到0-5 6个通道,所以数组里面有6个数据)。读取完通道一的数据之后,立刻采集通道二的数据…所有通道的数据读取完之后,就循环再读一遍。
先是配置ADC所用的GPIO;再初始化DMA和ADC的结构体。
static void ADCx_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 打开 ADC IO端口时钟
ADC_GPIO_APBxClock_FUN ( ADC_GPIO_CLK, ENABLE );
// 配置 ADC IO 引脚模式
GPIO_InitStructure.GPIO_Pin = ADC_PIN1|
ADC_PIN2|
ADC_PIN3|
ADC_PIN4|
ADC_PIN5|
ADC_PIN6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
// 初始化 ADC IO
GPIO_Init(ADC_PORT, &GPIO_InitStructure);
}
static void ADCx_Mode_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
// 打开DMA时钟
RCC_AHBPeriphClockCmd(ADC_DMA_CLK, ENABLE);
// 打开ADC时钟
ADC_APBxClock_FUN ( ADC_CLK, ENABLE );
// 复位DMA控制器
DMA_DeInit(ADC_DMA_CHANNEL);
// 配置 DMA 初始化结构体
// 外设基址为:ADC 数据寄存器地址
DMA_InitStructure.DMA_PeripheralBaseAddr = ( u32 ) ( & ( ADC_x->DR ) );
// 存储器地址
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)ADC_ConvertedValue;
// 数据源来自外设
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
// 缓冲区大小,应该等于数据目的地的大小
DMA_InitStructure.DMA_BufferSize = NOFCHANEL;
// 外设寄存器只有一个,地址不用递增
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
// 存储器地址递增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
// 外设数据大小为半字,即两个字节
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_InitStructure.DMA_M2M = DMA_M2M_Disable;
// 初始化DMA
DMA_Init(ADC_DMA_CHANNEL, &DMA_InitStructure);
// 使能 DMA 通道
DMA_Cmd(ADC_DMA_CHANNEL , ENABLE);
// ADC 模式配置
// 只使用一个ADC,属于单模式
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
// 扫描模式
ADC_InitStructure.ADC_ScanConvMode = ENABLE ;
// 连续转换模式
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
// 不用外部触发转换,软件开启即可
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
// 转换结果右对齐
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
// 转换通道个数
ADC_InitStructure.ADC_NbrOfChannel = NOFCHANEL;
// 初始化ADC
ADC_Init(ADC_x, &ADC_InitStructure);
// 配置ADC时钟N狿CLK2的8分频,即9MHz
RCC_ADCCLKConfig(RCC_PCLK2_Div8);
// 配置ADC 通道的转换顺序和采样时间
ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL1, 1, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL2, 2, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL3, 3, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL4, 4, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL5, 5, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL6, 6, ADC_SampleTime_55Cycles5);
// 使能ADC DMA 请求
ADC_DMACmd(ADC_x, ENABLE);
// 开启ADC ,并开始转换
ADC_Cmd(ADC_x, ENABLE);
// 初始化ADC 校准寄存器
ADC_ResetCalibration(ADC_x);
// 等待校准寄存器初始化完成
while(ADC_GetResetCalibrationStatus(ADC_x));
// ADC开始校准
ADC_StartCalibration(ADC_x);
// 等待校准完成
while(ADC_GetCalibrationStatus(ADC_x));
// 由于没有采用外部触发,所以使用软件触发ADC转换
ADC_SoftwareStartConvCmd(ADC_x, ENABLE);
}
注意:用作ADC采集的IO必须没有复用,否则采集电压会有影响。
ADC的I/O使用的模式使用的模式是模拟输入,既不上拉,也不下拉,采集到的数值完全取决于你想采集的模拟量。
双ADC进行采集,ADC1作为主,ADC2作为从,一个通道采集。二者的采集与转换都是同时进行的。ADC1的数据放在规则数据寄存器的低16位,ADC2的数据放在规则数据寄存器的高16位。数据放好之后,DMA会把数据读走,读到预先定义好的数组内(与上面一样),数组要定义为32位,之前只有一个ADC,所以只需要16位的数组即可。
先是配置ADC所用的GPIO;再初始化DMA和ADC1和2的结构体。
static void ADCx_GPIO_Config(void)//ADC 的GPIO配置、初始化
{
GPIO_InitTypeDef GPIO_InitStructure;
// ADCx_1 GPIO 初始化
ADCx_1_GPIO_APBxClock_FUN ( ADCx_1_GPIO_CLK, ENABLE );
GPIO_InitStructure.GPIO_Pin = ADCx_1_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(ADCx_1_PORT, &GPIO_InitStructure);
// ADCx_2 GPIO 初始化
ADCx_1_GPIO_APBxClock_FUN ( ADCx_2_GPIO_CLK, ENABLE );
GPIO_InitStructure.GPIO_Pin = ADCx_2_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(ADCx_2_PORT, &GPIO_InitStructure);
}
static void ADCx_Mode_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
// 打开DMA时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
// 打开ADC时钟
ADCx_1_APBxClock_FUN ( ADCx_1_CLK, ENABLE );
ADCx_2_APBxClock_FUN ( ADCx_2_CLK, ENABLE );
/* ------------------DMA模式配置---------------- */
// 复位DMA控制器
DMA_DeInit(ADC_DMA_CHANNEL);
// 配置 DMA 初始化结构体
// 外设基址为:ADC 数据寄存器地址
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&( ADCx_1->DR ));
// 存储器地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ADC_ConvertedValue;
// 数据源来自外设
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
// 缓冲区大小,应该等于数据目的地的大小
DMA_InitStructure.DMA_BufferSize = NOFCHANEL;
// 外设寄存器只有一个,地址不用递增
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
// 存储器地址递增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
// 外设数据大小
DMA_InitStructure.DMA_PeripheralDataSize =
DMA_PeripheralDataSize_Word;
// 内存数据大小,跟外设数据大小相同
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
// 循环传输模式
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
// DMA 传输通道优先级为高,当使用一个DMA通道时,优先级设置不影响
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
// 禁止存储器到存储器模式,因为是从外设到存储器
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
// 初始化DMA
DMA_Init(ADC_DMA_CHANNEL, &DMA_InitStructure);
// 使能 DMA 通道
DMA_Cmd(ADC_DMA_CHANNEL , ENABLE);
/* ----------------ADCx_1 模式配置--------------------- */
// 双ADC的规则同步
ADC_InitStructure.ADC_Mode = ADC_Mode_RegSimult;
// 扫描模式
ADC_InitStructure.ADC_ScanConvMode = ENABLE ;
// 连续转换模式
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
// 不用外部触发转换,软件开启即可
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
// 转换结果右对齐
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
// 转换通道个数
ADC_InitStructure.ADC_NbrOfChannel = NOFCHANEL;
// 初始化ADC
ADC_Init(ADCx_1, &ADC_InitStructure);
// 配置ADC时钟N狿CLK2的8分频,即9MHz
RCC_ADCCLKConfig(RCC_PCLK2_Div8);
// 配置ADC 通道的转换顺序和采样时间
ADC_RegularChannelConfig(ADCx_1, ADCx_1_CHANNEL, 1,
ADC_SampleTime_239Cycles5);
// 使能ADC DMA 请求
ADC_DMACmd(ADCx_1, ENABLE);
/* ----------------ADCx_2 模式配置--------------------- */
// 双ADC的规则同步
ADC_InitStructure.ADC_Mode = ADC_Mode_RegSimult;
// 扫描模式
ADC_InitStructure.ADC_ScanConvMode = ENABLE ;
// 连续转换模式
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
// 不用外部触发转换,软件开启即可
ADC_InitStructure.ADC_ExternalTrigConv =
ADC_ExternalTrigConv_None;
// 转换结果右对齐
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
// 转换通道个数
ADC_InitStructure.ADC_NbrOfChannel = NOFCHANEL;
// 初始化ADC
ADC_Init(ADCx_2, &ADC_InitStructure);
// 配置ADC时钟为PCLK2的8分频,即9MHz
RCC_ADCCLKConfig(RCC_PCLK2_Div8);
// 配置ADC 通道的转换顺序和采样时间
ADC_RegularChannelConfig(ADCx_2, ADCx_2_CHANNEL, 1,
ADC_SampleTime_239Cycles5);
/* 使能ADCx_2的外部触发转换 */
ADC_ExternalTrigConvCmd(ADC2, ENABLE);
/* ----------------ADCx_1 校准--------------------- */
// 开启ADC ,并开始转换
ADC_Cmd(ADCx_1, ENABLE);
// 初始化ADC 校准寄存器
ADC_ResetCalibration(ADCx_1);
// 等待校准寄存器初始化完成
while(ADC_GetResetCalibrationStatus(ADCx_1));
// ADC开始校准
ADC_StartCalibration(ADCx_1);
// 等待校准完成
while(ADC_GetCalibrationStatus(ADCx_1));
/* ----------------ADCx_2 校准--------------------- */
// 开启ADC ,并开始转换
ADC_Cmd(ADCx_2, ENABLE);
// 初始化ADC 校准寄存器
ADC_ResetCalibration(ADCx_2);
// 等待校准寄存器初始化完成
while(ADC_GetResetCalibrationStatus(ADCx_2));
// ADC开始校准
ADC_StartCalibration(ADCx_2);
// 等待校准完成
while(ADC_GetCalibrationStatus(ADCx_2));
// 由于没有采用外部触发,所以使用软件触发ADC转换
ADC_SoftwareStartConvCmd(ADCx_1, ENABLE);
}
注意:为什么ADC2要使用外部触发?
在双重采集模式下,ADC1是主,ADC2是从,在内部,ADC1会给ADC2一个触发信号,这个是在双重模式下硬件自动启动的。如果想让此启动成功,就要把ADC2外部触发的开关打开(此信号是ADC1给它的,相当于外部给它的)。
在实验中,我们可以不用跳线帽,我们用到了ADC1(PC1)和ADC2(PC4),我们观察ADC1的数据,可以用杜邦线把PC1和3V3连接起来;观察ADC2数据的时候,可以用杜邦线把PC4(液晶屏下面那个引脚)和3V3连接起来。