DAY63 IMX6ULL ADC Driver Development

IMX6ULL ADC Driver Development

1. Sorting Out Core Concepts of ADC

1.1 What is ADC?

ADC (Analog-to-Digital Converter) is a core module that converts continuously varying analog voltage signals into discrete digital signals, serving as a bridge for digital systems (MCU/processor) to interact with analog signals from the physical world.

1.2 Core Terminology

Terminology Core Explanation
Analog Signal A continuously varying physical quantity (e.g., voltage, temperature) in the physical world, acting as the input source of ADC
Digital Signal A discrete, discontinuous signal that can be directly parsed by MCU (e.g., values from 0 to 4095)
Sensor A device that converts physical quantities (light, temperature, pressure, etc.) into analog electrical signals (voltage/current)

2. Hardware Analysis of IMX6ULL ADC

2.1 Schematic Diagram Analysis

The ADC module of IMX6ULL is integrated inside the SOC, with the following core hardware associations:

  • Core Board (IMX6ULL_CORE_V2.0): The ADC reference voltage pin ADC_VREFH provides a voltage reference for conversion;
  • Bottom Board (IMX6ULL_MINI_V2.2): GPIO_1 (pin 7) of the P4 module is associated with the ADC channel, acting as the hardware carrier for actual signal acquisition.

2.2 Key Information from Reference Manual

(1) ADC External Signal Mapping

ADC1 of IMX6ULL includes 10 input channels, with the core channel-pin mapping as follows (only key ones listed):

Signal Description Corresponding Pin Direction
ADC_VREFH ADC high reference voltage ADC_VREFH Input
ADC1_IN0 ADC1 channel 0 input GPIO1_IO00 Input
ADC1_IN1 ADC1 channel 1 input GPIO1_IO01 Input
... ... ... ...
ADC1_IN7 ADC1 channel 7 input GPIO1_IO07 Input
(2) Clock Configuration

The ADC input clock (ADICLK) supports 3 options, which determine the conversion rate:

ADICLK Value Clock Source
00 IPG clock
01 IPG clock divided by 2
11 Asynchronous clock (ADACK)
(3) Core Registers (Key for Development)
Register Core Bit Fields Function Description
ADCx_HC0 ADCH (b0-b4) Selects the acquisition channel; switching the channel triggers one conversion
AIEN (b7) Conversion complete interrupt enable (disabled by default)
ADCx_HS COCO0 (b0) Conversion complete flag (1 = completed, 0 = not completed)
ADCx_R0 CDATA (b0-b11) Stores the conversion result (lower 12 bits valid, range 0~4095)
ADCx_GC CAL (b7) Starts automatic calibration (write 1 to start, read 0 when completed)
ADCx_GS CALF (b1) Calibration failure flag (0 = success, 1 = failure; needs to be cleared by writing 1)

3. Practical ADC Driver Development

3.1 ADC Calibration (Mandatory Step)

The ADC of IMX6ULL must undergo automatic calibration before use to eliminate hardware offset errors. The core code is as follows:

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

/**
 * @brief ADC automatic calibration
 * @param adc_base: ADC base address (e.g., ADC1_BASE)
 * @return 0: Calibration successful, -1: Calibration failed
 */
int adc_calibrate(ADC_Type *adc_base)
{
    // 1. Start calibration: Write 1 to the CAL bit
    adc_base->GC |= (1 << 7);
    
    // 2. Wait for calibration completion: CAL bit reads 0 when completed
    while((adc_base->GC & (1 << 7)) != 0);
    
    // 3. Check calibration failure flag
    if(adc_base->GS & (1 << 1))
    {
        adc_base->GS |= (1 << 1); // Clear the failure flag
        return -1;
    }
    return 0;
}

Principle Explanation: Calibration is a core step for ADC to ensure conversion accuracy, and the hardware automatically completes offset compensation. It is necessary to wait for the CAL bit to return to 0. If CALF is set to 1, it indicates calibration failure, and the flag needs to be cleared and calibration re-performed.

3.2 ADC Single Sampling

After calibration, perform single sampling on the specified channel. The core code is as follows:

c 复制代码
/**
 * @brief ADC single sampling (12-bit resolution)
 * @param adc_base: ADC base address
 * @param channel: Sampling channel (0-9)
 * @return Sampled value (0~4095, corresponding to 0~ADC_VREFH voltage)
 */
unsigned short adc_single_sample(ADC_Type *adc_base, unsigned char channel)
{
    unsigned short adc_value = 0;
    
    // 1. Configure the sampling channel: Disable interrupt, set target channel
    adc_base->HC0 = (0 << 7) | (channel & 0x1F);
    
    // 2. Wait for conversion completion: Poll the COCO0 bit
    while((adc_base->HS & 0x01) == 0);
    
    // 3. Read the conversion result: Retain only valid lower 12 bits of data
    adc_value = adc_base->R0 & 0xFFF;
    
    return adc_value;
}

Principle Explanation : Writing the channel number to ADCx_HC0 triggers one conversion automatically by the ADC. Poll the COCO0 bit until it is set to 1, indicating conversion completion. The lower 12 bits of ADCx_R0 are the final digital quantity, corresponding to the proportional value of the analog voltage.

3.3 Mean Filter Optimization (Dedicated to Photosensitive Sensors)

The signal collected by the photosensitive sensor is susceptible to environmental interference, and mean filtering is used to reduce noise. The core code is as follows:

c 复制代码
#define SAMPLE_COUNT 10  // Number of sampling times, adjustable as needed

/**
 * @brief ADC mean filter sampling (improves photosensitive signal stability)
 * @param adc_base: ADC base address
 * @param channel: Sampling channel
 * @return Filtered sampled value
 */
unsigned short adc_average_sample(ADC_Type *adc_base, unsigned char channel)
{
    unsigned int sum = 0;
    unsigned char i = 0;
    
    // 1. Accumulate multiple sampling values
    for(i = 0; i < SAMPLE_COUNT; i++)
    {
        sum += adc_single_sample(adc_base, channel);
        // Short delay to avoid excessively high sampling frequency
        for(volatile int j=0; j<1000; j++);
    }
    
    // 2. Calculate the average value to filter out random noise
    return (unsigned short)(sum / SAMPLE_COUNT);
}

Principle Explanation: Collect N samples (10 in the example) continuously from the same channel, accumulate them and take the average value, which can effectively offset random interference in a single sampling and improve the stability of the photosensitive signal.

4. Extended Practice: I2C Temperature Reading and Code Optimization

4.1 LM75 Temperature Sensor Reading (I2C Method)

LM75 is a temperature sensor with an I2C interface. The core code for reading temperature is as follows:

c 复制代码
unsigned char rcv_buffer[2] = {0}; // I2C receive buffer

/**
 * @brief Read LM75 temperature value (0.5℃ precision)
 * @return Temperature value (unit: ℃)
 */
float get_temp_value(void)
{
    unsigned short t = 0;
    struct I2C_Msg msg = {
        .dev_addr = 0x48,  // LM75 device address
        .reg_addr = 0x00,  // Temperature register address
        .reg_len = 1,      // Register address length (1 byte)
        .data = rcv_buffer,// Receive data buffer
        .len = 2,          // Number of bytes to read (temperature register is 16-bit)
        .dir = I2C_read    // I2C read direction
    };
    
    // Execute I2C data transfer
    i2c_transfer(I2C1, &msg);
    
    // Splice data and convert to actual temperature
    t |= (rcv_buffer[0] << 8) | (rcv_buffer[1] << 0);
    t = t >> 7;  // Retain valid temperature bits (upper 9 bits)
    return t * 0.5;  // 0.5℃ precision conversion
}

Principle Explanation: The temperature register of LM75 is 16-bit, with the upper 9 bits as valid temperature bits (including sign). After shifting right by 7 bits and multiplying by 0.5, the digital quantity can be converted into the actual temperature value (e.g., a value of 10 corresponds to 5℃).

4.2 I2C Multi-Byte Register Address Compatibility Optimization

The register address of an I2C device may be multi-byte (e.g., 16-bit/32-bit), so it is necessary to be compatible with the transmission of addresses of different lengths. The optimized code is as follows:

c 复制代码
/**
 * @brief Send multi-byte register address (compatible with different I2C devices)
 * @param base: I2C base address
 * @param reg_addr: Register address
 * @param reg_len: Address length (number of bytes)
 * @return 0: Success, non-0: Failure
 */
int i2c_send_reg_addr(I2C_Type *base, unsigned int reg_addr, unsigned int reg_len)
{
    int status = 0;
    int i = reg_len - 1;
    
    // Send address byte by byte from high to low
    for (; i >= 0; i--)
    {
        base->I2DR = (reg_addr >> (8 * i)) & 0XFF; // Extract current byte
        status = i2c_wait_iif(base); // Wait for transmission completion
        if (status != 0) goto stop;  // Jump to stop process if failed
    }
    return 0;
    
stop:
    base->I2CR &= ~(1 << 5); // Stop I2C transmission
    return status;
}

Principle Explanation: According to the register address length (reg_len), extract the address from high to low byte by byte through shift and AND operations, and write them into the I2C data register (I2DR) one by one to ensure compatibility with register addresses of different byte lengths.

4.3 FPU Enable (Improve Floating-Point Operation Efficiency)

Temperature calculation involves floating-point operations (e.g., *0.5), so it is necessary to enable the FPU (Floating-Point Unit) of IMX6ULL. The core assembly code is as follows:

armasm 复制代码
enable_fpu:
    // 1. Read CPACR register, enable CP10/CP11 (FPU access permission)
    mrc     p15, 0, r0, c1, c0, 2   
    orr     r0, r0, #(0xF << 20)    
    mcr     p15, 0, r0, c1, c0, 2   
    
    // 2. Set the EN bit of FPEXC to enable FPU
    mov     r0, #0x40000000         
    vmsr    fpexc, r0               
    
    // 3. Clear FPSCR flags to initialize floating-point status
    mov     r0, #0x00000000         
    vmsr    fpscr, r0               
    
    bx      lr                      // Function return

Principle Explanation: The CP10/CP11 bits of the CPACR register control FPU access permission; setting them to 1 allows floating-point operations. The EN bit of FPEXC is the main FPU enable bit, and enabling it improves floating-point operation efficiency by several times.

相关推荐
Web3探索者18 小时前
可视化服务器管理和传统命令行区别是什么?新手教程:Linux 运维到底该用图形界面还是 SSH 命令行?
linux·ssh
zylyehuo20 小时前
Linux系统中网线与USB网络共享冲突
linux
荣--1 天前
一键部署不是为了省时间 —— 它是把"买来的 PaaS"变成"自己的平台"的拐点
运维·zabbix·工程化·一键部署·平台化·边界设计
江华森1 天前
动手实战学 Docker — 从零到集群编排完全指南
运维
Avan_菜菜2 天前
FRP 内网穿透完整实战:从 HTTP 映射到 HTTPS 自签代理
运维·nginx·https
Sokach10152 天前
Linux Shell 脚本从零到能用:一个新手的一天学习总结
linux
FreakStudio3 天前
W55MH32L-EVB 上手测评:硬件 TCP/IP 加持的以太网单片机,MicroPython 零门槛开发
python·单片机·嵌入式·大学生·面向对象·并行计算·电子diy·电子计算机
SelectDB3 天前
Litefuse 开源并推出单进程轻量模式,25 秒就能跑起来的 Agent 可观测与评估平台
运维·后端·自动化运维
AlfredZhao3 天前
Docker 容器时区不对,`timedatectl` 不存在怎么办?
linux·timezone
zzzzzz3104 天前
9K Star 炸裂开源!这个 C 语言写的代码知识图谱,把 Linux 内核索引压缩到了 3 分钟
linux·服务器·sql