63、IMX6ULL ADC驱动开发

IMX6ULL ADC驱动开发

一、ADC核心概念梳理

1.1 什么是ADC?

ADC(Analog-to-Digital Converter,模数转换器)是将连续变化的模拟电压信号 转换为离散数字信号的核心模块,是数字系统(MCU/处理器)与物理世界模拟信号交互的桥梁。

1.2 核心术语

术语 核心说明
模拟信号 物理世界中连续变化的物理量(如电压、温度),是ADC的输入源
数字信号 离散、不连续的信号,可直接被MCU解析(如0~4095的数值)
传感器 将物理量(光、温度、压力等)转换为模拟电信号(电压/电流)的器件

二、IMX6ULL ADC硬件解析

2.1 原理图分析

IMX6ULL的ADC模块集成在SOC内部,核心硬件关联如下:

  • 核心板(IMX6ULL_CORE_V2.0):ADC参考电压引脚ADC_VREFH,为转换提供电压基准;
  • 底板(IMX6ULL_MINI_V2.2):P4模块GPIO_1(7号引脚)关联ADC通道,是实际信号采集的硬件载体。

2.2 参考手册关键信息

(1)ADC外部信号映射

IMX6ULL的ADC1包含10个输入通道,核心通道与引脚映射如下(仅列关键):

Signal 描述 对应引脚 方向
ADC_VREFH ADC高参考电压 ADC_VREFH 输入
ADC1_IN0 ADC1通道0输入 GPIO1_IO00 输入
ADC1_IN1 ADC1通道1输入 GPIO1_IO01 输入
... ... ... ...
ADC1_IN7 ADC1通道7输入 GPIO1_IO07 输入
(2)时钟配置

ADC输入时钟(ADICLK)支持3种选择,决定转换速率:

ADICLK值 时钟源
00 IPG时钟
01 IPG时钟分频2
11 异步时钟(ADACK)
(3)核心寄存器(开发关键)
寄存器 核心位段 功能说明
ADCx_HC0 ADCH(b0-b4) 选择采集通道,切换通道触发一次转换
AIEN(b7) 转换完成中断使能(默认禁用)
ADCx_HS COCO0(b0) 转换完成标志(1=完成,0=未完成)
ADCx_R0 CDATA(b0-b11) 存储转换结果(低12位有效,范围0~4095)
ADCx_GC CAL(b7) 启动自动校准(写1启动,读0完成)
ADCx_GS CALF(b1) 校准失败标志(0=成功,1=失败,需写1清零)

三、ADC驱动开发实战

3.1 ADC校准(必选步骤)

IMX6ULL的ADC使用前需完成自动校准,消除硬件偏移误差,核心代码如下:

c 复制代码
#include "imx6ull.h"

/**
 * @brief ADC自动校准
 * @param adc_base: ADC基地址(如ADC1_BASE)
 * @return 0: 校准成功, -1: 校准失败
 */
int adc_calibrate(ADC_Type *adc_base)
{
    // 1. 启动校准:向CAL位写1
    adc_base->GC |= (1 << 7);
    
    // 2. 等待校准完成:CAL位读0表示完成
    while((adc_base->GC & (1 << 7)) != 0);
    
    // 3. 检查校准失败标志
    if(adc_base->GS & (1 << 1))
    {
        adc_base->GS |= (1 << 1); // 清零失败标志
        return -1;
    }
    return 0;
}

原理说明:校准是ADC保证转换精度的核心步骤,硬件自动完成偏移补偿;需等待CAL位归0,若CALF置1说明校准失败,需清零标志并重新校准。

3.2 ADC单次采样

校准完成后,对指定通道进行单次采样,核心代码如下:

c 复制代码
/**
 * @brief ADC单次采样(12位分辨率)
 * @param adc_base: ADC基地址
 * @param channel: 采样通道(0-9)
 * @return 采样值(0~4095,对应0~ADC_VREFH电压)
 */
unsigned short adc_single_sample(ADC_Type *adc_base, unsigned char channel)
{
    unsigned short adc_value = 0;
    
    // 1. 配置采样通道:关闭中断,设置目标通道
    adc_base->HC0 = (0 << 7) | (channel & 0x1F);
    
    // 2. 等待转换完成:轮询COCO0位
    while((adc_base->HS & 0x01) == 0);
    
    // 3. 读取转换结果:仅保留低12位有效数据
    adc_value = adc_base->R0 & 0xFFF;
    
    return adc_value;
}

原理说明 :向ADCx_HC0写入通道号后,ADC自动触发一次转换;轮询COCO0位直到置1,说明转换完成;ADCx_R0的低12位是最终数字量,对应模拟电压的比例值。

3.3 均值滤波优化(光敏传感专用)

光敏传感器采集的信号易受环境干扰,通过均值滤波降低噪声,核心代码如下:

c 复制代码
#define SAMPLE_COUNT 10  // 采样次数,可根据需求调整

/**
 * @brief ADC均值滤波采样(提升光敏信号稳定性)
 * @param adc_base: ADC基地址
 * @param channel: 采样通道
 * @return 滤波后的采样值
 */
unsigned short adc_average_sample(ADC_Type *adc_base, unsigned char channel)
{
    unsigned int sum = 0;
    unsigned char i = 0;
    
    // 1. 多次采样累加
    for(i = 0; i < SAMPLE_COUNT; i++)
    {
        sum += adc_single_sample(adc_base, channel);
        // 短延时,避免采样频率过高
        for(volatile int j=0; j<1000; j++);
    }
    
    // 2. 计算平均值,滤除随机噪声
    return (unsigned short)(sum / SAMPLE_COUNT);
}

原理说明:对同一通道连续采集N次(示例为10次),累加后取平均值,可有效抵消单次采样的随机干扰,提升光敏信号的稳定性。

四、拓展实战:I2C温度读取与代码优化

4.1 LM75温度传感器读取(I2C方式)

LM75是I2C接口的温度传感器,读取温度的核心代码如下:

c 复制代码
unsigned char rcv_buffer[2] = {0}; // I2C接收缓冲区

/**
 * @brief 读取LM75温度值(精度0.5℃)
 * @return 温度值(单位:℃)
 */
float get_temp_value(void)
{
    unsigned short t = 0;
    struct I2C_Msg msg = {
        .dev_addr = 0x48,  // LM75设备地址
        .reg_addr = 0x00,  // 温度寄存器地址
        .reg_len = 1,      // 寄存器地址长度(1字节)
        .data = rcv_buffer,// 接收数据缓冲区
        .len = 2,          // 读取字节数(温度寄存器为16位)
        .dir = I2C_read    // I2C读方向
    };
    
    // 执行I2C数据传输
    i2c_transfer(I2C1, &msg);
    
    // 拼接数据并转换为实际温度
    t |= (rcv_buffer[0] << 8) | (rcv_buffer[1] << 0);
    t = t >> 7;  // 保留温度有效位(高9位)
    return t * 0.5;  // 0.5℃精度转换
}

原理说明:LM75的温度寄存器为16位,高9位为温度有效位(含符号);右移7位后乘以0.5,即可将数字量转换为实际温度值(如数值10对应5℃)。

4.2 I2C多字节寄存器地址兼容优化

I2C设备的寄存器地址可能为多字节(如16位/32位),需兼容不同长度的地址发送,优化代码如下:

c 复制代码
/**
 * @brief 发送多字节寄存器地址(兼容不同I2C设备)
 * @param base: I2C基地址
 * @param reg_addr: 寄存器地址
 * @param reg_len: 地址长度(字节数)
 * @return 0: 成功, 非0: 失败
 */
int i2c_send_reg_addr(I2C_Type *base, unsigned int reg_addr, unsigned int reg_len)
{
    int status = 0;
    int i = reg_len - 1;
    
    // 从高位到低位逐字节发送地址
    for (; i >= 0; i--)
    {
        base->I2DR = (reg_addr >> (8 * i)) & 0XFF; // 提取当前字节
        status = i2c_wait_iif(base); // 等待发送完成
        if (status != 0) goto stop;  // 失败则跳转到停止流程
    }
    return 0;
    
stop:
    base->I2CR &= ~(1 << 5); // 停止I2C传输
    return status;
}

原理说明:根据寄存器地址长度(reg_len),通过移位+与运算逐字节提取高位到低位的地址,逐个写入I2C数据寄存器(I2DR),确保适配不同字节长度的寄存器地址。

4.3 FPU使能(提升浮点运算效率)

温度计算涉及浮点运算(如*0.5),需使能IMX6ULL的FPU(浮点单元),核心汇编代码如下:

armasm 复制代码
enable_fpu:
    // 1. 读取CPACR寄存器,使能CP10/CP11(FPU访问权限)
    mrc     p15, 0, r0, c1, c0, 2   
    orr     r0, r0, #(0xF << 20)    
    mcr     p15, 0, r0, c1, c0, 2   
    
    // 2. 设置FPEXC的EN位,使能FPU
    mov     r0, #0x40000000         
    vmsr    fpexc, r0               
    
    // 3. 清零FPSCR标志位,初始化浮点状态
    mov     r0, #0x00000000         
    vmsr    fpscr, r0               
    
    bx      lr                      // 函数返回

原理说明:CPACR寄存器的CP10/CP11位控制FPU访问权限,置1后允许浮点运算;FPEXC的EN位是FPU主使能位,开启后浮点运算效率提升数倍。

相关推荐
C++ 老炮儿的技术栈2 小时前
CMFCEditBrowseCtrl用法一例
c语言·开发语言·c++·windows·qt·visual studio code
DLGXY2 小时前
STM32标准库——控制驱动LED灯、蜂鸣器(四)
stm32·单片机·嵌入式硬件
我是大咖2 小时前
C 语言笔记: const 指针 + 堆内存申请
c语言·开发语言
春日见2 小时前
三分钟安装window Docker,并与Ubuntu(WSL)建立连接
linux·人工智能·windows·驱动开发·机器学习·docker·容器
A-code2 小时前
嵌入式UI刷新:观察者模式实战
stm32·单片机·mcu·物联网·51单片机
纳祥科技2 小时前
NX6802,4路音频DAC芯片,具备90dB 动态范围 -90 dB THD+N
单片机·音视频
HABuo3 小时前
【linux基础I/O(一)】文件系统调用接口&文件描述符详谈
linux·运维·服务器·c语言·c++·ubuntu·centos
拾光Ծ3 小时前
【Linux】一切皆文件:深入理解文件与文件IO
linux·c语言·运维开发·系统编程·重定向·linux开发·文件io
windows_63 小时前
MISRA C:2004 逐条分析
c语言