ADC(Analog-Digital Converter),意即模拟-数字转换器,简称模数转换器。ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁。
DAC:数字到模拟的桥梁(PWM控制灯的亮度和电机旋转的速度,DAC的使用只要是在信号发生器、音频解码芯片等
PWM:数字到模拟的桥梁,例如PWM控制灯的亮度和电机旋转的速度,PWM只有完全导通和完全断开两种状态,在这两种状态都没有功率损耗,故直流电机调速这种大功率的应用场景,使用PWM来等效模拟量,是比DAC更好的选择,PWM电路更简单,更常用。
普通的AD转换流程为:先启动一次转换,之后读值,依次循环。
STM32的ADC可以将要转换的通道列为一组,每一次连续转换多个值。
规则组用于常规使用,注入组一般用于突发事件。
一般可以用于测量光线强度、温度这些值
如果光线高于某个预值、低于某个预值,或者温度高于某个预值、低于某个预值时,就会执行一些操作
模拟开门狗可以监测指定的某些通道,当AD值高于他设定的上域值,或者低于下域值时。就会申请中断,然后就可以在中断函数里,执行相应的操作。
下图所示的即为逐次逼近型ADC的工作原理图(ADC0809)。STM32中的ADC的结构与此类似
通道 | AD1 | AD2 | AD3 |
---|---|---|---|
通道0 | PA0 | PA0 | PA0 |
通道1 | PA1 | PA1 | PA1 |
通道2 | PA2 | PA2 | PA2 |
通道3 | PA3 | PA3 | PA3 |
通道4 | PA4 | PA4 | PF6 |
通道5 | PA5 | PA5 | PF7 |
通道6 | PA6 | PA6 | PF8 |
通道7 | PA7 | PA7 | PF9 |
通道8 | PB0 | PB0 | PF10 |
通道9 | PB1 | PB1 | |
通道10 | PC0 | PC0 | PC0 |
通道11 | PC1 | PC1 | PC1 |
通道12 | PC2 | PC2 | PC2 |
通道13 | PC3 | PC3 | PC3 |
通道14 | PC4 | PC4 | |
通道15 | PC5 | PC5 | |
通道16 | 温度传感器 | ||
通道17 | 内部参考电压 |
本节课程使用的STM32F103C8T6没有PC0到PC5的引脚,故也就不存在通道10到通道15。
单次转换非扫描模式、连续转换非扫描模式、单次转换扫描模式、连续转换扫描模式
一次转换完成后,立刻开始下一次的转换
在扫描模式下还可有一种模式——间断模式,作用是在扫描过程中每隔几个转换,就暂停一次,需要再次触发才能继续
此表为规则组的触发源,也就是上图第2部分,有来自定时器的信号、引脚或定时器的信号(具体是引脚和定时器,需要用AFIO重映射来确定)最后的软件控制位就是软件触发。这些触发信号的选择,可以通过上图1设置表右边的寄存器完成,使用库函数的话只需一个函数给个参数即可。
数据右对齐,即作为转换结果的12位数据向右靠,高位补0;
数据左对齐,即作为转换结果的12位数据向左靠,低位补0。
在使用时通常使用数据右对齐,这样在读取时直接读取寄存器即可。
如果选择左对齐直接读取,得到的数据会比实际的数据大16倍。
当对分辨率的要求不高时(对电压仅作大概的判断即可)可以采用左对齐,将数据寄存器的高8位取出,就相当于舍弃了转换结果的4位的精度,12位的ADC退化位为8位的ADC
ADC有一个内置自校准模式。校准可大幅减小因内部电容器组的变化而造成的精准度误差。校准期间, 在每个电容器上都会计算出一个修正码(数字值), 这个码用于消除在随后的转换中每个电容器上产生的误差
建议在每次上电后执行一次校准启动校准前, ADC必须处于关电状态超过至少两个ADC时钟周期由于校准过程是固定的,对于使用者而言只需要在初始化后加上几条代码即可!
ADC外围电路的设计给出以下三个电路图:
图1:电位器产生一个可调的电压的电路
中间的滑动端可以输出一个0~3.3伏可调的电压输出来,上滑时电压增大,下滑时电压减小,若阻值太小,电阻就会比较费电
图2:分压方法输出传感器组织的电路
- 传感器输出电压的电路,例如光敏电阻、热敏电阻、红外接头管、麦克风等都可以等效为一个可变电阻
- 那电阻阻值得通过和一个固定电阻串联分压,当传感器阻值变大时,下拉作用变弱,输出端受上拉电阻的作用,电压就会升高
- 固定电阻建议选择和传感器阻值相近的电阻,才可以得到一个位于中间电压区域比较好的输出
- 此处传感器和固定电阻的位置也调换,输出电压的极性就反过来了
图3:简单的电压转换电路
想测一个0-5V的VIN电压,到那时ADC只能接收0~3.3V的电压,就可以搭建此类电路。使用电阻分压,上面阻值17K,下面阻值33K,加一起50K,中间的电压就是VIN/50K*33K,得到的电压范围就是0-3.3伏,就可以进入ADC转换了。想要其他范围(如5V、10V)的VIN电压可类似操作,如果电压过高就不建议使用这种电路了,可能比较危险,高电压采集最好使用专用芯片,比如隔离放大器等,做到高低电压隔离保证电路安全。
该配置函数定义存放在stm32f10x_rcc.h文件中,用来配置ADCCLK分频器。它可以对APB2的72MHz时钟选择2、4、6、8分频,输出到ADCCLK。
1.void RCC_ADCCLKConfig(uint32_t RCC_PCLK2)
// 恢复ADC缺省配置
void ADC_DeInit(ADC_TypeDef* ADCx);
// ADC初始化
void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);
// ADC配置结构体初始化
void ADC_StructInit(ADC_InitTypeDef* ADC_InitStruct);// ADC上电工作函数,即开关控制函数
void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);// ADC开启DMA输出信号
void ADC_DMACmd(ADC_TypeDef* ADCx, FunctionalState NewState);// ADC中断输出控制函数
void ADC_ITConfig(ADC_TypeDef* ADCx, uint16_t ADC_IT, FunctionalState NewState);// 下面4个函数用于ADC工作前的校准操作,在ADC初始化完成后依次调用即可
// ADC复位校准
void ADC_ResetCalibration(ADC_TypeDef* ADCx);
// ADC获取复位校准状态
FlagStatus ADC_GetResetCalibrationStatus(ADC_TypeDef* ADCx);
// ADC开始校准
void ADC_StartCalibration(ADC_TypeDef* ADCx);
// ADC获取开始校准状态
FlagStatus ADC_GetCalibrationStatus(ADC_TypeDef* ADCx);// ADC软件触发转换,给CR2的SWSTART置1(开始转换后立即自动清0)
void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
// ADC获取软件触发状态,获取CR2的SWSTART(开始转换规则通道)位
// 不能用它判断转换是否结束,一般不用,了解即可
FlagStatus ADC_GetSoftwareStartConvStatus(ADC_TypeDef* ADCx);// ADC规则组通道配置,给转换序列的每个位置填写指定的通道
void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);// ADC外部触发转换控制(是否允许外部触发转换)
void ADC_ExternalTrigConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);// ADC获取转换值,获取AD转换的数据寄存器
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);// ADC获取双模式转换值,读取双ADC模式下ADC的转换结果
uint32_t ADC_GetDualModeConversionValue(void);// ADC温度传感器、内部参考电压控制,开启内部的两个转换通道
void ADC_TempSensorVrefintCmd(FunctionalState NewState);// 下面的函数与操作标志位寄存器状态有关
// ADC获取标志位状态,可通过获取EOC标志位判断转换是否结束
FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);
// 清除标志位
void ADC_ClearFlag(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);
// 获取中断标志位
ITStatus ADC_GetITStatus(ADC_TypeDef* ADCx, uint16_t ADC_IT);
// 清除中断挂起位
void ADC_ClearITPendingBit(ADC_TypeDef* ADCx, uint16_t ADC_IT);
(1)双ADC工作模式选择,ADC_Mode,其中分别为:
#define ADC_Mode_Independent ((uint32_t)0x00000000)//独立模式
#define ADC_Mode_RegInjecSimult ((uint32_t)0x00010000)//同步规则和同步注入模式
#define ADC_Mode_RegSimult_AlterTrig ((uint32_t)0x00020000)//同步规则和交替触发模式
#define ADC_Mode_InjecSimult_FastInterl ((uint32_t)0x00030000)//同步注入和快速交叉模式
#define ADC_Mode_InjecSimult_SlowInterl ((uint32_t)0x00040000)//同步注入和慢速交叉模式
#define ADC_Mode_InjecSimult ((uint32_t)0x00050000)//同步注入模式
#define ADC_Mode_RegSimult ((uint32_t)0x00060000)//同步规则模式
#define ADC_Mode_FastInterl ((uint32_t)0x00070000)//快速交叉模式
#define ADC_Mode_SlowInterl ((uint32_t)0x00080000)//慢速交叉模式
#define ADC_Mode_AlterTrig ((uint32_t)0x00090000)//交替触发模式
(2)扫描模式选择,ADC_ScanConvMode,ENABLE表示多通道扫描模式,否则为单通道。
(3)连续转换模式选择,ADC_ContinuousConvMode,ENABLE表示连续转换模式,否则为单次转换模式。
(4)ADC转换触发方式选择,ADC_ContinuousConvMode,其中ADC_ExternalTrigConv_None为不使用外部触发,即使用软件触发方式。
(5)ADC转换数据对齐方式选择,ADC_DataAlign,可以左对齐或右对齐
(6) 顺序进行规则转换的ADC通道的数目设置,ADC_NbrOfChannel,可以设置1~16个通道
// 下面两个函数用来配置STM32中ADC的间断模式
// 配置每隔几个通道间断依次
void ADC_DiscModeChannelCountConfig(ADC_TypeDef* ADCx, uint8_t Number);
// 开启间断模式
void ADC_DiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
1.AD.c(单次转换非扫描)
#include "stm32f10x.h" // Device header
void AD_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//配置ADCCLK分频器,对APB2的72MHz时钟选择2、4、6、8分频,输入到ADCCLKRCC_ADCCLKConfig(RCC_PCLK2_Div6);//分频后等于72MHz/6=12MHzGPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//模拟输入(ADC的专属模式)在AIN,GPIO口无效,断开GPIO口,防止GPIO口的输入输出对模拟电压造成干扰GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);//ADC规则组通道配置,给序列的每个位置填写指定的通道,就是填写点菜菜单的过程//第一个参数是ADCx,第二个是ADC指定的通道(通道0-17)//第三个是写在序列几的位置,然后第四个指定通道的采样时间//ADC_SampleTime_55Cycles5表示55.5个ADCCLK的周期ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);//ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 2, ADC_SampleTime_55Cycles5);//ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 3, ADC_SampleTime_55Cycles5);//通道可以重复,序列不要重复,需要的话可以多写几个,这是填充菜单的方法ADC_InitTypeDef ADC_InitStructure;ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//工作模式,独立模式:ADC1和ADC2各转各的ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//右对齐ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//不使用外部触发转换,即软件触发ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//单次 //ENABLE:连续转换ADC_InitStructure.ADC_ScanConvMode = DISABLE;//非扫描ADC_InitStructure.ADC_NbrOfChannel = 1;//总共需要扫描多少个通道ADC_Init(ADC1, &ADC_InitStructure);//中断和看门狗如果需要可以在此处定义//ADC上电ADC_Cmd(ADC1, ENABLE);ADC_ResetCalibration(ADC1);//复位校准//为1时,开始复位校准,复位校准完后,该位就会由硬件自动清0while (ADC_GetResetCalibrationStatus(ADC1) == SET);//等待复位校准完成ADC_StartCalibration(ADC1);//开始校准while (ADC_GetCalibrationStatus(ADC1) == SET);//等待校准完成/*ADC_SoftwareStartConvCmd(ADC1, ENABLE); //软件触发(连续转换只需要初始化一次即可,所以软件触发的函数可以挪到初始化函数) */
}uint16_t AD_GetValue(void)
{// 1. 软件触发开启转换ADC_SoftwareStartConvCmd(ADC1, ENABLE); //软件触发// 2. 等待转换完成(获取标志位状态,等待EOC标志位置1)/*连续转换非扫描:不需要判断标志位while这句可删掉while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);//转换结束,EOC置1// 转换未完成则等待(55.5T + 12.5T = 68T,结果大概为5.6us)// 3. 读取ADC数据寄存器并返回return ADC_GetConversionValue(ADC1);// 读取之后会自动清除EOC标志位
}
2、改为连续转换非扫描
好处:无需不断触发,不需要等待转换完成
- ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;//连续
- 连续转换只需要初始化一次即可,所以软件触发的函数可以挪到初始化函数最后ADC_SoftwareStartConvCmd(ADC1, ENABLE); //软件触发 在初始化完成后触发一次即可
- 且在AD_GetValue函数中,不需要判断标志位
while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);//转换结束,EOC置1 这一句可以删除
程序如上图所示
3、main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"/*
电位器即滑动变阻器,用电位器产生0~3.3V连续变化的模拟电压信号,然后接到STM32的PA0口上,
之后用STM32内部的ADC读取电压数据,显示在屏幕上
屏幕第一行:模拟数据
屏幕第二行:处理过后显示的电压值
往左拧电位器,AD值减小,对应的电压减小,反之则反
ADC是12位的,AD结果最大值是4095,也就是2^12-1,对应的电压是3.3V
GPIO只能读取高低电平 ,而ADC可以对高低电平之间的任意电压进行量化,最终用一个变量表示
*/
uint16_t ADValue;
float Voltage;
int main(void)
{OLED_Init();AD_Init();OLED_ShowString(1, 1, "ADValue:");OLED_ShowString(2, 1, "Volatge:0.00V");while(1){ADValue = AD_GetValue();Voltage = (float)ADValue/4095*3.3;//数字与电压映射关系,ADValue为uint16类型除以4095会舍弃小数部分,先强转为floatOLED_ShowNum(1,9,ADValue,4);//目前的OLED没有显示浮点数的功能,但可用显示整数的借用OLED_ShowNum(2,9,Voltage,1);//显示整数部分OLED_ShowNum(2,11,(uint16_t)(Voltage*100)%100,2);//显示小数部分,浮点数不可%取余所以先强转为整型Delay_ms(100);//限制OLED刷新速度}}
1、思路
在每次触发转换之前,手动更改一下列表第一个位置的通道
比如第一次转换,在序列1先写入通道0,之后触发、等待、读值
第二次转换,在序列1把通道0改成通道1,之后触发、等待、读值
第三次转换,在序列1改成通道2等等
2、AD.c
#include "stm32f10x.h" // Device header//(单次转换非扫描)void AD_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//配置ADCCLK分频器,对APB2的72MHz时钟选择2、4、6、8分频,输入到ADCCLKRCC_ADCCLKConfig(RCC_PCLK2_Div6);//分频后等于72MHz/6=12MHzGPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//模拟输入(ADC的专属模式)在AIN,GPIO口无效,断开GPIO口,防止GPIO口的输入输出对模拟电压造成干扰GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);ADC_InitTypeDef ADC_InitStructure;ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//工作模式,独立模式:ADC1和ADC2各转各的ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//右对齐ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//不使用外部触发转换,即软件触发ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//单次 //ENABLE:连续转换ADC_InitStructure.ADC_ScanConvMode = DISABLE;//非扫描ADC_InitStructure.ADC_NbrOfChannel = 1;//总共需要扫描多少个通道ADC_Init(ADC1, &ADC_InitStructure);//中断和看门狗如果需要可以在此处定义//ADC上电ADC_Cmd(ADC1, ENABLE);ADC_ResetCalibration(ADC1);//复位校准//为1时,开始复位校准,复位校准完后,该位就会由硬件自动清0while (ADC_GetResetCalibrationStatus(ADC1) == SET);//等待复位校准完成ADC_StartCalibration(ADC1);//开始校准while (ADC_GetCalibrationStatus(ADC1) == SET);//等待校准完成/*ADC_SoftwareStartConvCmd(ADC1, ENABLE); //软件触发(连续转换只需要初始化一次即可,所以软件触发的函数可以挪到初始化函数) */
}uint16_t AD_GetValue(uint8_t ADC_Channel)
{ADC_RegularChannelConfig(ADC1,ADC_Channel, 1, ADC_SampleTime_55Cycles5);// 把通道作为参数填入序列1中,通道的采样周期是55.5个ADCCLK的周期// 1. 软件触发开启转换ADC_SoftwareStartConvCmd(ADC1, ENABLE); //软件触发// 2. 等待转换完成(获取标志位状态,等待EOC标志位置1)/*连续转换非扫描:不需要判断标志位while这句可删掉while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);//转换结束,EOC置1// 转换未完成则等待(55.5T + 12.5T = 68T,结果大概为5.6us)// 3. 读取ADC数据寄存器并返回return ADC_GetConversionValue(ADC1);// 读取之后会自动清除EOC标志位
}
3、main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"/*
DO是数字输出
AO是模拟量输出
*/
uint16_t AD0,AD1,AD2,AD3;//表示四个ADC输入通道的转换结果的接收变量int main(void)
{OLED_Init();AD_Init();OLED_ShowString(1, 1, "AD0:");OLED_ShowString(2, 1, "AD1:");while(1){AD0 = AD_GetValue(ADC_Channel_0); AD1 = AD_GetValue(ADC_Channel_1); //根据需要也可设置映射关系,再进行显示OLED_ShowNum(1,5,AD0,4);OLED_ShowNum(2,5,AD1,4);Delay_ms(100);//限制OLED刷新速度}}
本文发布于:2024-01-30 20:02:27,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170661615222483.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |