目录
[1. ADC基础概念](#1. ADC基础概念)
[2. i.MX6ULL ADC核心特性](#2. i.MX6ULL ADC核心特性)
[3. 关键寄存器解析](#3. 关键寄存器解析)
[3.1 控制寄存器(ADCx_HC0)](#3.1 控制寄存器(ADCx_HC0))
[3.2 配置寄存器(ADCx_CFG)](#3.2 配置寄存器(ADCx_CFG))
[3.3 通用控制寄存器(ADCx_GC)](#3.3 通用控制寄存器(ADCx_GC))
[4. ADC模块函数开发流程](#4. ADC模块函数开发流程)
[1. 初始化函数 (adc1_init)](#1. 初始化函数 (adc1_init))
[2. 校准函数 (adc1_calibration)](#2. 校准函数 (adc1_calibration))
[3. 数据采集函数 (get_adc_value)](#3. 数据采集函数 (get_adc_value))
[4. 滤波优化 (get_average_value)](#4. 滤波优化 (get_average_value))
[5. 关键问题与优化](#5. 关键问题与优化)
[5. 常见问题与优化](#5. 常见问题与优化)
摘要:本文基于NXP i.MX6ULL处理器的参考手册及开发实践,深入解析其内置ADC模块的架构特性、寄存器配置及驱动开发要点,并提供关键代码实现参考。
1. ADC基础概念
- ADC概念:
ADC(模拟-数字转换器)将连续模拟信号(如电压)转换为离散数字信号。i.MX6ULL采用**逐次逼近型(SAR)**架构,通过二分法逐步逼近输入电压值。其分辨率可选8/10/12位,转换结果范围对应0~255、0~1023或0~4095。
-
ADC的分辨率:指adc的位数。常见的分辨率有8位,10位,12位,16位
-
基准电压:是ADC进行转换的参考标准(如3.3V, 5v)
-
ADC的工作原理:ADC将输入的模拟电压值与基准电压进行比较,从而确定对应的数字值。具体步骤为:
Ⅰ.输入:将连续模拟信号输入ADC
Ⅱ.采样与保持 : 在特定时刻捕获并保持电压值
Ⅲ.量化: 将电压值归入最接近的离散电平
Ⅳ.编码: 将离散电平转换为二进制数字代码
Ⅵ.输出结果:输出数字信号
-
输出结果v=adc采样值/2^(adc位数)*参考电压
2. i.MX6ULL ADC核心特性
-
通道配置:
支持最多16个单端外部模拟输入通道(如ADC1_IN0~ADC1_IN15),复用至GPIO引脚(如GPIO1_IO00~GPIO1_IO09)。
-
性能参数:
-
最高采样率:1 MS/s(每秒百万次采样)
-
精度:最高10位有效位数(ENOB)
-
内置硬件平均器与比较器,支持自动校准。
-
-
时钟源:
支持ipg_clk、ipg_clk/2及专用低功耗时钟ADACK(默认20MHz)。
3. 关键寄存器解析
3.1 控制寄存器(ADCx_HC0)
-
通道选择(ADCH位):
写入通道号(0~15)启动单次转换,切换通道会触发新转换。
-
中断使能(AIEN位):
置1时转换完成触发中断。
3.2 配置寄存器(ADCx_CFG)
-
时钟分频(ADIV位):
可选1/2/4/8分频,确保ADCK频率在2.5~30 MHz范围内。
-
采样时间配置(ADLSMP+ADSTS位):
组合控制采样周期(2~24个ADCK周期),适应不同阻抗信号源。
3.3 通用控制寄存器(ADCx_GC)
-
校准使能(CAL位):
置1启动校准,复位后必须执行以保证精度。
-
硬件平均使能(AVGE位):
启用多采样值自动平均。
4. ADC模块函数开发流程
i.MX6ULL的ADC采用逐次逼近型(SAR)架构,支持8/10/12位分辨率,需重点关注初始化、校准、数据采集及滤波设计。开发例程分为四个核心阶段:
1. **初始化函数 (adc1_init)**
初始化是ADC工作的前提,需配置引脚模式、时钟源和寄存器:
引脚配置:
将GPIO设为模拟模式(如GPIO1_IO01)。代码中
IOMUXC_SetPinMux和IOMUXC_SetPinConfig设置复用功能与电气特性(禁用上拉/下拉),确保信号无干扰。寄存器设置:
ADC1->CFG:配置时钟分频(ADIV位)、采样时间(ADLSMP+ADSTS位)。例如,代码中设置ADIV=2(4分频)和ADSTS=3(长采样模式),适应高阻抗信号源。
ADC1->GC:使能ADC(ADCO=1),但校准前需关闭硬件平均和比较功能。关键点:
时钟源可选ipg_clk或ADACK(默认20MHz),需确保ADCK频率在2.5--30 MHz范围内。
2. **校准函数 (adc1_calibration)**
校准是保证精度的必要步骤,必须在复位后执行:
-
流程:
-
清除校准失败标志(
ADC1->GS |= (1 << 1))。 -
启动校准(
ADC1->GC |= (1 << 7)),等待CAL位清零(代码中while循环)。 -
检查
CALF标志:若为0则成功(代码返回1),否则失败。
-
-
注意事项:
-
校准期间禁止写入寄存器或进入低功耗模式,否则会中止流程。
-
校准耗时约14000 ADCK周期(代码中未显式处理超时,需添加超时判断)。
-
3. **数据采集函数 (get_adc_value)**
启动转换并读取结果:
启动转换:
向
ADCx_HC0写入通道号(代码中ADC1->HC[0] = 0x01),切换通道会触发新转换。等待完成:
轮询
ADCx_HS的COCO位(代码中while ((ADC1->HS & (1 << 0)) == 0))。读取结果:
从
ADCx_R0读取12位数据(ADC1->R[0] & 0x0FFF)。转换时间:
典型值约700 ns(8位模式+40MHz时钟),受采样周期和时钟分频影响。
4. **滤波优化 (get_average_value)**
裸机程序需软件滤波提升稳定性:
中值滤波:
采集100个样本 → 冒泡排序 → 剔除头尾20个极值。
均值处理:
计算中间60个样本的平均值(
sum/60),避免整数除法精度损失(代码中转为浮点计算)。电压转换:
最终结果按公式转换:
电压值 = (ADC值 × 3.3V) / 4096。
5. 关键问题与优化
误差来源:
量化误差(固定)、引脚漏电流(硬件布局优化)、噪声干扰(增加滤波)。
低功耗场景:
进入STOP模式前需清除数据阻塞标志,避免意外唤醒系统。
Linux驱动扩展:
需在设备树配置参考电压(如
vref-supply = <®_3v3>)和通道映射。
完整流程总结:
- 配置GPIO为模拟模式 → 2. 初始化ADC时钟/寄存器 → 3. 执行校准 → 4. 启动转换并读取原始值 → 5. 中值滤波+均值处理 → 6. 转换为电压值输出。
完整代码:
cpp
//main.c
#include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"
#include "interrupt.h"
#include "clock.h"
#include "epit.h"
#include "gpt.h"
#include "uart.h"
#include "stdio.h"
#include "string.h"
#include "adc.h"
int main(void)
{
clock_init();
system_interrupt_init();
led_init();
beep_init();
gpt1_init();
uart1_init();
adc1_init();
float value = 0.0;
while (1)
{
#if 0
value = get_volt_value();
#endif
value = get_average_value();
int m = (int)(value * 1000) / 1000;
int n = (int)(value * 1000) % 1000;
printf("volt = %d.%03d\n", m, n); //%03d 3位数,不足3位前面补0,防止区域得85,显示3.85而不是3.085
delay_ms(100);
}
return 0;
}
cpp
//adc.c
#include "adc.h"
#include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"
#include "stdio.h"
int adc1_calibration(void)
{
ADC1->GS |= (1 << 1);
ADC1->GC |= (1 << 7);
while ((ADC1->GC & (1 << 7)) != 0);
return ((ADC1->GS & (1 << 1)) == 0);
}
void adc1_init(void)
{
//复用功能
IOMUXC_SetPinMux(IOMUXC_GPIO1_IO01_GPIO1_IO01, 0);
//电气特性
IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO01_GPIO1_IO01, IOMUXC_SW_PAD_CTL_PAD_PKE(1));
IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO01_GPIO1_IO01, IOMUXC_SW_PAD_CTL_PAD_PUE(0));
//ADC
ADC1->CFG = 0;
unsigned int t = ADC1->CFG;
t |= (2 << 2);
t |= (3 << 0);
ADC1->CFG = t;
ADC1->GC = 0;
ADC1->GC |= (1 << 0);
printf(adc1_calibration() ? "Calibration completed normally." : "Calibration failed.");
}
unsigned short get_adc_value(void)
{
ADC1->HC[0] = 0x1F;
ADC1->HC[0] = 0x01;
while ((ADC1->HS & (1 << 0)) == 0);
return (unsigned short)(ADC1->R[0] & 0x0FFF);
}
float get_volt_value(void)
{
return get_adc_value() * 3.3 / 4096;
}
float get_average_value(void)
{
unsigned int temp[100];
unsigned long sum = 0; // 使用更宽的类型避免溢出
unsigned int temp_swap; // 用于交换的临时变量
int i = 0;
int j = 0;
// 初始化数组
for (i = 0; i < 100; i++)
{
temp[i] = get_adc_value();
}
// 冒泡排序
for (i = 0; i < 99; i++)
{
for (j = 0; j < 99 - i; j++)
{
if (temp[j] > temp[j + 1]) // 大的往后排
{
temp_swap = temp[j];
temp[j] = temp[j + 1];
temp[j + 1] = temp_swap;
}
}
}
// 计算temp数组第20-第80个元素的平均值 (去掉了头尾各20个极值)
for (i = 20; i < 80; i++)
{
sum += temp[i];
}
// 直接使用浮点数计算,避免整数除法精度损失
float average = (float)sum / 60.0f;
float value = average * 3.3f / 4096.0f;
return value;
}
6.结果与分析

滤波方法:本裸机程序采用中值滤波+均值处理提升了结果的稳定性。
补充:常用的滤波方法
5. 常见问题与优化
- 在实际应用中如何选择ADC?
- (1)首先看量程:确保你要测量的信号电压范围在ADC的量程之内。如果信号太小,需要先用运放放大;如果信号太大,需要用电阻分压。
- (2)然后看分辨率:根据你需要的测量精细度选择位数。例如,测量锂电池电压(3.0V-4.2V),一个12位的ADC(有4096个等级)可能就足够了。
- (3)最后看精度:在对测量结果的绝对准确性要求极高的场合(如精密仪器、科学测量),必须仔细研究数据手册中的精度指标了偏移误差、增益误差、INL、DNL),而不能只看它的位数。高精度的ADC价格也更高。
- 转换时间计算:
短时间配置(8位模式+40MHz时钟)典型值约700 ns。
-
误差来源:
量化误差、引脚漏电流、噪声干扰等需通过硬件布局与校准抑制。
6、核心要点
①ADC概念:模数转换器
②基准电压是ADC进行转换的参考标准(如3.3V, 5v)
③ADC的工作原理:ADC将输入的模拟电压值与基准电压进行比较,从而确定对应的数字值。
具体步骤为:
Ⅰ.输入:将连续模拟信号输入ADC
Ⅱ.采样与保持 : 在特定时刻捕获并保持电压值
Ⅲ.量化: 将电压值归入最接近的离散电平
Ⅳ.编码: 将离散电平转换为二进制数字代码
Ⅵ.输出结果:输出数字信号
④ADC的分辨率指adc的位数。常见的分辨率有8位,10位,12位,16位
⑤输出结果v=adc采样值/2^(adc位数)*参考电压

