Chapter 07 · ARM 嵌入式 C 开发
ADC 与 DAC
理解逐次逼近型 ADC 原理与精度,掌握多通道 DMA 采集、滤波算法,读取 NTC 温度传感器,输出正弦波。
理解逐次逼近型 ADC 原理与精度,掌握多通道 DMA 采集、滤波算法,读取 NTC 温度传感器,输出正弦波。
/** * @brief ADC1 三通道 DMA 循环采集 * CH0(PA0): 外部电压,CH1(PA1): 电位器,CH17: 片内温度传感器 */ ADC_HandleTypeDef hadc1; #define ADC_CH_NUM 3 volatile uint16_t adc_values[ADC_CH_NUM]; /* DMA 写入此数组 */ void ADC1_DMA_Init(void) { ADC_ChannelConfTypeDef sConfig = {0}; __HAL_RCC_ADC1_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); /* 配置 PA0、PA1 为模拟输入 */ GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1; GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; /* 模拟模式:关闭施密特触发器 */ GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /* ADC 基本参数 */ hadc1.Instance = ADC1; hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; /* 84/4=21MHz */ hadc1.Init.Resolution = ADC_RESOLUTION_12B; hadc1.Init.ScanConvMode = ENABLE; /* 扫描多通道 */ hadc1.Init.ContinuousConvMode = ENABLE; /* 持续转换 */ hadc1.Init.DiscontinuousConvMode = DISABLE; hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion = ADC_CH_NUM; /* 3个通道 */ hadc1.Init.DMAContinuousRequests = ENABLE; /* DMA循环模式 */ hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV; HAL_ADC_Init(&hadc1); /* 配置三个通道的采样时间和序列顺序 */ sConfig.SamplingTime = ADC_SAMPLETIME_56CYCLES; 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); sConfig.Channel = ADC_CHANNEL_TEMPSENSOR; sConfig.SamplingTime = ADC_SAMPLETIME_480CYCLES; /* 温度传感器需长采样 */ sConfig.Rank = 3; HAL_ADC_ConfigChannel(&hadc1, &sConfig); /* 启动 DMA 循环采集 */ HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_values, ADC_CH_NUM); } /* 读取电压值(mV)*/ uint32_t ADC_GetVoltage_mV(uint8_t ch) { return ((uint32_t)adc_values[ch] * 3300) / 4095; }
#include "math.h" #define NTC_SERIES_R 10000.0f /* 串联电阻 10kΩ */ #define NTC_R25 10000.0f /* 25°C 标称阻值 10kΩ */ #define NTC_B 3950.0f /* B 系数(查手册)*/ #define VREF 3.3f #define ADC_MAX 4095.0f /** * NTC 分压电路:VCC → R_series → NTC → GND * ADC 测量 R_series 和 NTC 之间的电压(即 NTC 两端电压) * * R_NTC = R_series × V_NTC / (VCC - V_NTC) * = R_series × adc / (ADC_MAX - adc) * * Steinhart-Hart 简化(B 值方程): * 1/T = 1/T25 + (1/B) × ln(R/R25) * T = 1 / (1/T25 + ln(R/R25)/B) (K 转 °C 减 273.15) */ float NTC_ReadTemp_C(uint16_t adc_raw) { float r_ntc = NTC_SERIES_R * (float)adc_raw / (ADC_MAX - (float)adc_raw); float inv_t = (1.0f / (25.0f + 273.15f)) + (1.0f / NTC_B) * logf(r_ntc / NTC_R25); return (1.0f / inv_t) - 273.15f; } /* ── 均值滤波(消除 ADC 噪声)────────────────────── */ #define AVG_N 16 uint16_t ADC_AverageFilter(uint16_t *samples, uint8_t n) { uint32_t sum = 0; for (uint8_t i = 0; i < n; i++) sum += samples[i]; return (uint16_t)(sum / n); } /* ── 一维卡尔曼滤波 ─────────────────────────────── */ typedef struct { float x; /* 状态估计值 */ float p; /* 估计误差协方差 */ float q; /* 过程噪声协方差(系统噪声,越小越信任模型)*/ float r; /* 测量噪声协方差(越小越信任测量值)*/ float k; /* 卡尔曼增益 */ } Kalman_t; void Kalman_Init(Kalman_t *kf, float q, float r, float init_val) { kf->x = init_val; kf->p = 1.0f; kf->q = q; kf->r = r; } float Kalman_Update(Kalman_t *kf, float measurement) { kf->p += kf->q; /* 预测:增加不确定性 */ kf->k = kf->p / (kf->p + kf->r); /* 计算卡尔曼增益 */ kf->x += kf->k * (measurement - kf->x); /* 更新估计值 */ kf->p *= (1.0f - kf->k); /* 更新协方差 */ return kf->x; }
#include "math.h" #define SINE_POINTS 100 /* 正弦波一个周期的采样点数 */ uint16_t sine_table[SINE_POINTS]; /* 预计算正弦查找表 */ void Sine_TableInit(void) { for (int i = 0; i < SINE_POINTS; i++) { float rad = (2.0f * 3.14159f * i) / SINE_POINTS; /* 将 -1~1 映射到 0~4095(12位DAC) */ sine_table[i] = (uint16_t)((sinf(rad) + 1.0f) * 2047.5f); } } /** * DAC1 + DMA + TIM6 触发 → 自动输出正弦波 * 频率 = TIM6 触发频率 / SINE_POINTS * 例:TIM6 触发 = 10kHz → 正弦波 = 100Hz */ void DAC_Sine_Init(void) { /* PA4 为模拟输出(DAC1 CH1) */ GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitStruct.Pin = GPIO_PIN_4; GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /* DAC 启动 DMA(TIM6 TRGO 触发,循环模式)*/ /* 详细初始化代码由 CubeMX 生成 */ HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, (uint32_t*)sine_table, SINE_POINTS, DAC_ALIGN_12B_R); /* 12位右对齐 */ }
STM32F4 的 ADC1 通道 16(CH16)连接片内温度传感器,可测量芯片结温(不是环境温度)。转换公式:Temp = ((V_SENSE - V25) / Avg_Slope) + 25,其中 V25 ≈ 0.76V,Avg_Slope ≈ 2.5mV/°C(参考具体芯片数据手册)。精度约 ±1.5°C,适合监控芯片是否过热。
AVDD 和 AGND 必须与数字电源分开走线,否则数字噪声会耦合到 ADC。VREF+ 和 VREF- 引脚附近需要 100nF + 1μF 去耦电容。ADC 输入端加 RC 低通滤波器(如 1kΩ + 100nF)可以滤除高频噪声。多通道同时采集时,通道间的信号切换会产生串扰,高精度应用建议减慢扫描速度或对每通道多次采样取平均。