跳转至

ADC与DAC编程

概述

ADC(Analog-to-Digital Converter,模数转换器)和DAC(Digital-to-Analog Converter,数模转换器)是连接模拟世界和数字世界的重要接口。

ADC基础

ADC参数

参数 说明
分辨率 位数,如8位、12位、16位
采样率 每秒采样次数(SPS)
输入范围 可测量的电压范围
精度 测量值与真实值的偏差
通道数 可测量的模拟通道数量

分辨率计算

Text Only
1
2
3
4
分辨率 = 满量程电压 / (2^位数 - 1)

例:12位ADC,参考电压3.3V
分辨率 = 3.3V / 4095 ≈ 0.8mV

Linux ADC编程

使用IIO子系统

C
#include <stdio.h>
#include <stdlib.h>

int read_adc(int channel)
{
    char path[64];
    char buf[16];
    int value;
    
    // 读取原始值
    snprintf(path, sizeof(path), 
             "/sys/bus/iio/devices/iio:device0/in_voltage%d_raw", 
             channel);
    FILE *f = fopen(path, "r");
    if (!f) return -1;
    fgets(buf, sizeof(buf), f);
    fclose(f);
    
    value = atoi(buf);
    
    // 读取缩放因子
    snprintf(path, sizeof(path),
             "/sys/bus/iio/devices/iio:device0/in_voltage%d_scale",
             channel);
    f = fopen(path, "r");
    if (f) {
        fgets(buf, sizeof(buf), f);
        fclose(f);
        float scale = atof(buf);
        return (int)(value * scale);
    }
    
    return value;
}

// 转换为电压
float adc_to_voltage(int raw, int bits, float vref)
{
    return (raw * vref) / ((1 << bits) - 1);
}

使用libiio

C
#include <iio.h>

struct iio_context *ctx;
struct iio_device *dev;
struct iio_channel *ch;

// 连接到本地IIO
ctx = iio_create_local_context();

// 获取ADC设备
dev = iio_context_find_device(ctx, "adc0");

// 获取通道
ch = iio_device_find_channel(dev, "voltage0", false);

// 启用通道
iio_channel_enable(ch);

// 读取数据
int buf[100];
iio_device_read(dev, buf, sizeof(buf));

STM32 ADC编程

HAL库单次转换

C
#include "stm32f4xx_hal.h"

ADC_HandleTypeDef hadc1;

void ADC_Init(void)
{
    ADC_ChannelConfTypeDef sConfig = {0};
    
    hadc1.Instance = ADC1;
    hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
    hadc1.Init.Resolution = ADC_RESOLUTION_12B;
    hadc1.Init.ScanConvMode = DISABLE;
    hadc1.Init.ContinuousConvMode = DISABLE;
    hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    hadc1.Init.NbrOfConversion = 1;
    HAL_ADC_Init(&hadc1);
    
    sConfig.Channel = ADC_CHANNEL_0;
    sConfig.Rank = 1;
    sConfig.SamplingTime = ADC_SAMPLETIME_480CYCLES;
    HAL_ADC_ConfigChannel(&hadc1, &sConfig);
}

uint16_t read_adc(void)
{
    HAL_ADC_Start(&hadc1);
    HAL_ADC_PollForConversion(&hadc1, 100);
    uint16_t value = HAL_ADC_GetValue(&hadc1);
    HAL_ADC_Stop(&hadc1);
    return value;
}

多通道扫描模式

C
uint16_t adc_values[4];

void ADC_Multi_Init(void)
{
    hadc1.Init.ScanConvMode = ENABLE;
    hadc1.Init.NbrOfConversion = 4;
    
    // 配置多个通道
    ADC_ChannelConfTypeDef sConfig = {0};
    
    sConfig.Channel = ADC_CHANNEL_0;
    sConfig.Rank = 1;
    HAL_ADC_ConfigChannel(&hadc1, &sConfig);
    
    sConfig.Channel = ADC_CHANNEL_1;
    sConfig.Rank = 2;
    HAL_ADC_ConfigChannel(&hadc1, &sConfig);
    
    // ... 其他通道
}

void read_all_channels(void)
{
    HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_values, 4);
}

中断方式

C
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
    if (hadc->Instance == ADC1) {
        uint16_t value = HAL_ADC_GetValue(hadc);
        // 处理ADC值
    }
}

void start_adc_interrupt(void)
{
    HAL_ADC_Start_IT(&hadc1);
}

ESP32 ADC编程

C
#include "driver/adc.h"

void adc_init(void)
{
    // 配置ADC1
    adc1_config_width(ADC_WIDTH_BIT_12);
    adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_11);
}

int read_adc(void)
{
    return adc1_get_raw(ADC1_CHANNEL_0);
}

// 使用ESP-IDF高级API
#include "esp_adc/adc_oneshot.h"

adc_oneshot_unit_handle_t adc1_handle;

void adc_oneshot_init(void)
{
    adc_oneshot_unit_init_cfg_t init_config = {
        .unit_id = ADC_UNIT_1,
    };
    adc_oneshot_new_unit(&init_config, &adc1_handle);
    
    adc_oneshot_chan_cfg_t config = {
        .atten = ADC_ATTEN_DB_11,
        .bitwidth = ADC_BITWIDTH_12,
    };
    adc_oneshot_config_channel(adc1_handle, ADC_CHANNEL_0, &config);
}

int read_adc_oneshot(void)
{
    int adc_raw;
    adc_oneshot_read(adc1_handle, ADC_CHANNEL_0, &adc_raw);
    return adc_raw;
}

DAC编程

Linux DAC编程

C
// 通过IIO写入DAC值
void write_dac(int value)
{
    char path[64];
    snprintf(path, sizeof(path),
             "/sys/bus/iio/devices/iio:device0/out_voltage0_raw");
    
    FILE *f = fopen(path, "w");
    if (f) {
        fprintf(f, "%d", value);
        fclose(f);
    }
}

STM32 DAC编程

C
#include "stm32f4xx_hal.h"

DAC_HandleTypeDef hdac;

void DAC_Init(void)
{
    DAC_ChannelConfTypeDef sConfig = {0};
    
    hdac.Instance = DAC;
    HAL_DAC_Init(&hdac);
    
    sConfig.DAC_Trigger = DAC_TRIGGER_NONE;
    sConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE;
    HAL_DAC_ConfigChannel(&hdac, &sConfig, DAC_CHANNEL_1);
}

void set_dac(uint16_t value)
{
    // 12位DAC,值范围0-4095
    HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, value);
    HAL_DAC_Start(&hdac, DAC_CHANNEL_1);
}

// 输出正弦波
void output_sine(void)
{
    const uint16_t sine_table[100] = { /* 正弦表 */ };
    int i = 0;
    
    while (1) {
        HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, sine_table[i]);
        i = (i + 1) % 100;
        HAL_Delay(10);
    }
}

ESP32 DAC编程

C
#include "driver/dac.h"

void dac_init(void)
{
    dac_output_enable(DAC_CHANNEL_1);
}

void set_dac(uint8_t value)
{
    // 8位DAC,值范围0-255
    dac_output_voltage(DAC_CHANNEL_1, value);
}

电压转换工具函数

C
// ADC值转电压
float adc_to_voltage(uint16_t adc_value, float vref, uint8_t bits)
{
    return (adc_value * vref) / ((1 << bits) - 1);
}

// 电压转ADC值
uint16_t voltage_to_adc(float voltage, float vref, uint8_t bits)
{
    return (uint16_t)((voltage / vref) * ((1 << bits) - 1));
}

// ADC值转温度(热敏电阻)
float adc_to_temperature(uint16_t adc_value, float vref, uint8_t bits)
{
    float vout = adc_to_voltage(adc_value, vref, bits);
    float r_thermistor = 10000.0 * vout / (vref - vout);  // 10K上拉
    
    // B值方程
    float b_value = 3950.0;
    float t0 = 298.15;  // 25°C in Kelvin
    float r0 = 10000.0;
    
    float temp = 1.0 / (1.0/t0 + 1.0/b_value * log(r_thermistor/r0));
    return temp - 273.15;  // 转换为摄氏度
}

常见ADC芯片

MCP3008(SPI接口)

C
uint16_t mcp3008_read(uint8_t channel)
{
    uint8_t tx[3] = {
        0x01,
        (0x80 | (channel << 4)),
        0x00
    };
    uint8_t rx[3] = {0};
    
    cs_low();
    spi_transfer(tx, rx, 3);
    cs_high();
    
    return ((rx[1] & 0x03) << 8) | rx[2];
}

ADS1115(I2C接口)

C
// 配置ADS1115
void ads1115_config(uint8_t channel)
{
    uint16_t config = 0x8483;  // 单次转换,通道0,±4.096V,128SPS
    config |= (channel << 12);
    
    uint8_t data[3] = {0x01, config >> 8, config & 0xFF};
    i2c_write(0x48, data, 3);
}

// 读取ADS1115
int16_t ads1115_read(void)
{
    uint8_t data[2];
    i2c_write(0x48, (uint8_t[]){0x00}, 1);
    i2c_read(0x48, data, 2);
    
    return (data[0] << 8) | data[1];
}

参考资料