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.

相关推荐
readhere5 小时前
从ZYNQ7000开发板,学会启动嵌入式Linux系统——入门学习:EBAZ4205篇目录
linux·启动·嵌入式linux·zynq7000·ebaz4205
wuyk5555 小时前
防逆向基础:在量产固件中关闭调试接口并设置读保护(RDP)的正确姿势
stm32·单片机·嵌入式硬件·性能优化·重构
打工仔折腾 AI6 小时前
在 Ubuntu ARM64 部署 Q1 V3.0 MuJoCo 模型:加载、单步推理与执行器响应解析
linux·运维·人工智能·python·深度学习·ubuntu·性能优化
yyuuuzz6 小时前
跨境站点日志时区混乱的排查记录
运维·服务器·网络·云计算·aws·1024程序员节
‎ദ്ദിᵔ.˛.ᵔ₎6 小时前
关于Linux 线程局部存储的认识
linux·运维
GlobalInfo6 小时前
全球及中国风电叶片检测机器人行业研究报告(2026版):市场现状、行业前景与占有率分析
运维·网络·人工智能·机器人
隔窗听雨眠8 小时前
KingbaseES平替全链路实践:从迁移评估到可持续运维
运维
skywalk81638 小时前
登到192.168.1.5那台机器,看看今天白天,大约从早晨到晚上,都卡住了。看看因为什么卡住,为什么卡住而没有杀死那个导致卡死的进程,为什么
运维·系统·freebsd
运维行者_8 小时前
网络监控工具怎么选?从设备监控到智能运维的能力清单
运维·服务器·开发语言·网络·人工智能·支持向量机·php
小小de风呀8 小时前
de风——【从零开始学习Linux】(九):进程基础 —— 从冯诺依曼体系到 fork 创建进程
linux·学习