ARM裸机学习9——ADC模块详解与应用实践

目录

[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. 关键问题与优化)

6.结果与分析

[5. 常见问题与优化](#5. 常见问题与优化)

6、核心要点


摘要:本文基于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_SetPinMuxIOMUXC_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)**​

校准是保证精度的必要步骤,必须在复位后执行:

  • 流程

    1. 清除校准失败标志(ADC1->GS |= (1 << 1))。

    2. 启动校准(ADC1->GC |= (1 << 7)),等待CAL位清零(代码中while循环)。

    3. 检查CALF标志:若为0则成功(代码返回1),否则失败。

  • 注意事项

    • 校准期间禁止写入寄存器或进入低功耗模式,否则会中止流程。

    • 校准耗时约14000 ADCK周期(代码中未显式处理超时,需添加超时判断)。

3. **数据采集函数 (get_adc_value)**​

启动转换并读取结果:

  • 启动转换

    ADCx_HC0写入通道号(代码中ADC1->HC[0] = 0x01),切换通道会触发新转换。

  • 等待完成

    轮询ADCx_HSCOCO位(代码中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 = <&reg_3v3>)和通道映射。

完整流程总结

  1. 配置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位数)*参考电压

相关推荐
电子科技圈2 小时前
SmartDV展示AI & HPC连接与存储IP解决方案,以解锁下一代算力芯片和节点的“速度密码”
网络·数据库·人工智能·嵌入式硬件·aigc·边缘计算
CET中电技术2 小时前
“事后维护”到“主动预防”排除故障,CET中电技术为电动机的安全运行保驾护航
单片机·嵌入式硬件
辰哥单片机设计2 小时前
STM32智能鞋柜(机智云)
stm32·单片机·嵌入式硬件
xuxie992 小时前
N18 RTC
单片机·嵌入式硬件·实时音视频
惶了个恐2 小时前
嵌入式硬件第九弹——ARM(5)
arm开发·单片机·嵌入式硬件·arm·硬件工程
嵌入式×边缘AI:打怪升级日志2 小时前
IMX6ULL 的 LED 操作方法
stm32·单片机·嵌入式硬件
三佛科技-134163842123 小时前
家用电子血压计方案开发MCU控制芯片
单片机·嵌入式硬件·物联网·智能家居·pcb工艺
仰泳之鹅3 小时前
【stm32进阶】定时器+ADC+DMA+乒乓缓冲区
stm32·单片机·嵌入式硬件
进击的小头3 小时前
第4篇:嵌入式处理器内核全解析:ARM Cortex-M_R_A系列核心差异与选型指南
arm开发·单片机·嵌入式硬件