目录
三、STM32F103C8T6中ADC的时钟树以及内部结构框图
一、前言:
学习笔记!!!
参考:视频B站铁头山羊2.1.【电池】使用ADC采集电池电压(1)_哔哩哔哩_bilibili
B站江科大STM32[7-1] ADC模数转换器_哔哩哔哩_bilibili
二、硬件接线:
解释:12V电压输入为3节串联的18650(理论最大电压为4.2V*3=12.6V),经过电阻串联分压到单片机PB1(ADC9)引脚可接受的电压(3.3V内),测量R3上的电压间接测量,理论最大为10/(20+20+10)*12.6 = 2.52V经过线性标定即可反推电池电压。
三、STM32F103C8T6中ADC的时钟树以及内部结构框图
部分介绍:内部18个通道:16个IO口+1个温度传感器+VREFINT内部参考电压,进入到模拟多路开关再进入到模数转换器,即逐次比较的过程。转换的12位数据结果会放置到16位数据寄存器里,读取即可获得转换值。
-
触发ADC转换的信号:①软件触发,在程序中调用一条代码,就可以启动转换了。②硬件触发,就是这里的这些触发源(EXTI_11/EXTI15开始连接的两控制器)。
-
参考电压:芯片的VDDA和VSSA在电路中默认接了VREF,因此没有VREF±引脚。同电源0~3.3V
-
ADCCLK:来源于外设总线预分频器,最大14MHz,因此把时钟源分频时候不能弄超了。
-
数据寄存器:存数据,DMA数据搬运
注意转换可以多通道进行,并且分成了规则/注入两个通道组。
四、STM32F103C8T6中的两个10路的ADC
ADC12_IN0的意思是ADC1和ADC2的IN0都是在PA0上的,即引脚全都是相同的。因此ADC1和ADC2可以同时运行,即双ADC模式。比如可以配合组成同步或者交叉运行(对着一个通道交叉的采样,增大采样的频率)。
规则组四个模式
参数:单次/连续、扫描/非扫描,不细述
规则组测ADC编程思路(单次、非扫描)
- 开启时钟,开启ADC1和GPIOB1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //开启ADC1的时钟(PA0的ADC1)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOA的时钟(模拟输入)
-
设置ADC时钟分频
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHz -
GPIO初始化
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //GPIO 无效
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //将PA0引脚初始化为模拟输入 -
规则组通道配置
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); //规则组序列1的位置,PA0的ADC1配置为通道0,采样时间越大,转换越慢越稳定反之则反,ADC_SampleTime_55Cycles5表示55.5个采样周期
若还要加入其它通道则:ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 2, ADC_SampleTime_55Cycles5); //规则组序列2的位置,配置通道3
-
ADC初始化
ADC_InitTypeDef ADC_InitStructure; //定义结构体变量
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //模式,选择独立模式,即单独使用ADC1
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; //扫描模式,失能,只转换规则组的序列1这一个位置(即非扫描模式单通道)
ADC_InitStructure.ADC_NbrOfChannel = 1; //通道数,为1,仅在扫描模式下,才需要指定大于1的数,在非扫描模式下,只能是1
ADC_Init(ADC1, &ADC_InitStructure); //将结构体变量交给ADC_Init,配置ADC1 -
ADC使能
ADC_Cmd(ADC1, ENABLE); //使能ADC1,ADC开始运行 -
ADC校准
ADC_ResetCalibration(ADC1);//复位校准 //固定流程,内部有电路会自动执行校准
while (ADC_GetResetCalibrationStatus(ADC1) == SET); //等待复位校准完成(会自动清0)
ADC_StartCalibration(ADC1); //开始校准
while (ADC_GetCalibrationStatus(ADC1) == SET); //等待开始校准完成(ADC_SoftwareStartConvCmd(ADC1, ENABLE);//软件触发AD转换一次,若设置为连续转换则放在这里)
-
触发ADC
单次非扫描模式软件触发
uint16_t AD_GetValue(void)
{
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //软件触发AD转换一次
while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); //等待EOC标志位完成,即等待AD转换结束(读取ADC_DR时会自动复位)规则组转换完成标志位:ADC_FLAG_EOC等待时间= 5.6us
采样周期(刚刚设置的55.5)+固定的转换周期12.5 =68个周期
68个周期 ÷ 【72(主频) ÷ 6(分频)】=5.6us
return ADC_GetConversionValue(ADC1); //读数据寄存器(EOC复位),得到AD转换的结果
}连续非扫描模式软件触发只需要触发一次,且由于连续触发不需要看EOC标志位完成
五、注入组测ADC编程思路(单次、非扫描)

我这里是使用1ms定时器2结合定时器2的中断函数累计10次后(10ms)再软件触发ADC中断采样,当然这样子是多余的操作,这里同时学习一下定时器中断
.1 硬件初始化配置
-
TIM2定时器配置:
- 时钟源:APB1总线时钟(默认72MHz)。
- 预分频器(PSC):设置为1,分频后时钟为36MHz(
72MHz / (1+1) = 36MHz
)。 - 自动重装载值(ARR):设置为36000-1,使TIM2每1ms产生一次更新中断(
36,000 / 36MHz = 1ms
)。 - 中断配置:使能更新中断,设置抢占优先级为1,子优先级为1。
-
ADC1配置:
- 时钟分频:ADC时钟配置为12MHz(
72MHz / 6
)。 - GPIO配置:PB1引脚设为模拟输入模式(ADC通道9)。
- 注入通道设置:单次转换模式,采样时间7.5个时钟周期(约0.625μs)。
- 中断配置:使能注入转换完成中断(JEOC),抢占优先级0(最高),子优先级2。
- 时钟分频:ADC时钟配置为12MHz(
2. 中断服务逻辑
-
TIM2中断(TIM2_IRQHandler):
- 每1ms触发一次,累计10次后(10ms)软件触发ADC注入转换。
- 清除中断标志,避免重复触发。
-
ADC中断(ADC1_2_IRQHandler):
- 读取注入通道的ADC值,存入环形缓冲区
adc_buffer
。 - 当缓冲区填满(
FILTER_WINDOW_SIZE=10
)时,置位filter_ready
标志。
- 读取注入通道的ADC值,存入环形缓冲区
3. 数据采集与滤波处理(可有可无)
- 缓冲区管理:
- 使用
adc_buffer
存储最近10次ADC采样值,索引buffer_index
循环更新。
- 使用
- 混合滤波算法(Hybrid_Filter):
- 数据复制:将缓冲区数据复制到临时数组,避免操作原始数据。
- 中值滤波:冒泡排序临时数组,剔除最高和最低的25%数据(即保留中间50%)。
- 移动平均:对保留的数据计算平均值,进一步平滑噪声。
- 输出:返回滤波后的ADC值,提高数据稳定性。
4. 电压计算与校准
-
公式转换:
- 滤波后的ADC值转换为电压: Vbat=(filtered_adc4095×3.3V×5)−0.22VVbat=(4095filtered_adc×3.3V×5)−0.22V
- 分压比:硬件采用5:1分压(例如16.5V电池分压后为3.3V)。
- 误差校准:减去0.22V补偿硬件偏差。
-
数据获取:
- 主循环中检查
filter_ready
标志,触发滤波计算并更新Vbat
。
- 主循环中检查
六、最终效果:
没插STlink的时候为12.18V,插了或者是直接拿电压表测是11.96V,不知道啥原因,所以我直接用最简单的方法减去0.22V补偿硬件偏差。
添加效果:电池电量显示
代码在:亿只搞学习的空大白/STM32F103C8T6平衡车
电源电压变化缓慢,实测两种效果差不多,但是第二种方法操作性多且采样时间一定,受主程序影响小,但是难度也高。