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主使能位,开启后浮点运算效率提升数倍。