4. ADC数据采集HIL测试

任务目标

在HIL平台上通过DAC输出已知电压至STM32的ADC输入引脚,验证ADC转换精度与采样率是否满足设计要求。测试分为静态精度测试(多点电压比对)与动态采样率测试(已知频率正弦波采集),最终通过串口打印结果并判定PASS/FAIL。

硬件连接与HIL配置

  • HIL通道映射:HIL的DAC0输出接STM32的PA0(ADC1_IN0);HIL的DIO0接STM32的PB0(用于触发同步采样)
  • STM32配置:ADC1通道0,12位分辨率,采样时间设为最大(810.5周期),由定时器TRGO触发连续采样
  • HIL脚本:使用Python API控制DAC输出电压与读取STM32回传数据

STM32端代码(ADC采集与串口上报)

/* main.c 核心部分 - 使用HAL库 */
ADC_HandleTypeDef hadc1;
TIM_HandleTypeDef htim2;
UART_HandleTypeDef huart2;

#define ADC_BUF_SIZE 1024
uint16_t adc_buf[ADC_BUF_SIZE];
uint8_t tx_buf[64];

void Error_Handler(void) {
    while(1) { /* 错误处理 */ }
}

static void MX_ADC1_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.DiscontinuousConvMode = DISABLE;
    hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T2_TRGO;
    hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    hadc1.Init.NbrOfConversion = 1;
    if (HAL_ADC_Init(&hadc1) != HAL_OK) Error_Handler();
    sConfig.Channel = ADC_CHANNEL_0;
    sConfig.Rank = 1;
    sConfig.SamplingTime = ADC_SAMPLETIME_810CYCLES;
    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) Error_Handler();
}

static void MX_TIM2_Init(uint32_t freq_hz) {
    /* 定时器触发频率 = 采样率 */
    TIM_OC_InitTypeDef sConfigOC = {0};
    htim2.Instance = TIM2;
    htim2.Init.Prescaler = 84 - 1;          /* 84MHz/84 = 1MHz */
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.Period = (1000000 / freq_hz) - 1;
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    if (HAL_TIM_Base_Init(&htim2) != HAL_OK) Error_Handler();
    sConfigOC.OCMode = TIM_OCMODE_PWM1;
    sConfigOC.Pulse = (htim2.Init.Period + 1) / 2;
    sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
    if (HAL_TIM_OC_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1) != HAL_OK) Error_Handler();
    HAL_TIM_OC_Start(&htim2, TIM_CHANNEL_1);
}

void start_adc_dma_collection(void) {
    HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buf, ADC_BUF_SIZE);
    HAL_TIM_Base_Start(&htim2);
}

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {
    if (hadc->Instance == ADC1) {
        /* 计算平均值与标准差 */
        uint32_t sum = 0;
        uint32_t sum_sq = 0;
        for (int i = 0; i < ADC_BUF_SIZE; i++) {
            sum += adc_buf[i];
            sum_sq += (uint32_t)adc_buf[i] * adc_buf[i];
        }
        float mean = (float)sum / ADC_BUF_SIZE;
        float variance = ((float)sum_sq / ADC_BUF_SIZE) - (mean * mean);
        float stddev = sqrtf(variance);

        /* 通过串口发送: 平均值,标准差,最小值,最大值 */
        uint16_t min_val = 4095, max_val = 0;
        for (int i = 0; i < ADC_BUF_SIZE; i++) {
            if (adc_buf[i] < min_val) min_val = adc_buf[i];
            if (adc_buf[i] > max_val) max_val = adc_buf[i];
        }
        int len = snprintf((char*)tx_buf, sizeof(tx_buf),
            "%.2f,%.2f,%u,%u\r\n", mean, stddev, min_val, max_val);
        HAL_UART_Transmit(&huart2, tx_buf, len, 100);
        HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buf, ADC_BUF_SIZE); /* 连续采集 */
    }
}

HIL上位机Python脚本(测试控制与判定)

import hil
import serial
import time
import math

# 连接HIL设备
hil_dev = hil.HIL()
hil_dev.connect("192.168.1.100")  # HIL设备IP

# 配置DAC0输出范围 0-3.3V
hil_dev.dac[0].configure(0, 3.3)

# 串口配置(与STM32通信)
ser = serial.Serial('COM5', 115200, timeout=1)
time.sleep(0.1)

def read_stm32_result():
    """读取STM32返回的统计结果"""
    line = ser.readline().decode().strip()
    if line:
        parts = line.split(',')
        if len(parts) == 4:
            return {
                'mean': float(parts[0]),
                'stddev': float(parts[1]),
                'min': int(parts[2]),
                'max': int(parts[3])
            }
    return None

def voltage_to_lsb(voltage):
    """电压转LSB(12位ADC,3.3V参考)"""
    return int(round(voltage / 3.3 * 4095))

# ========== 测试1:静态精度测试 ==========
print("=== 静态精度测试 ===")
test_voltages = [0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.3]
static_pass = True

for v in test_voltages:
    hil_dev.dac[0].write(v)
    time.sleep(0.05)  # 等待稳定
    
    # 清空串口缓冲
    ser.reset_input_buffer()
    time.sleep(0.1)
    
    result = read_stm32_result()
    if result is None:
        print(f"  {v:.2f}V: 无响应")
        static_pass = False
        continue
    
    expected_lsb = voltage_to_lsb(v)
    actual_lsb = result['mean']
    error = abs(actual_lsb - expected_lsb)
    error_mv = error * 3300.0 / 4095.0
    
    # 判定标准:误差 ≤ 3 LSB(约2.4mV)
    status = "PASS" if error <= 3 else "FAIL"
    if error > 3:
        static_pass = False
    print(f"  {v:.2f}V: 期望={expected_lsb} LSB, 实测={actual_lsb:.1f} LSB, "
          f"误差={error_mv:.2f}mV [{status}]")

# ========== 测试2:动态采样率测试 ==========
print("\n=== 采样率测试(1kHz正弦波) ===")
sample_rate = 10000  # 10kHz采样率
sine_freq = 1000     # 1kHz正弦波

# 配置STM32定时器采样率(通过复位后默认配置,此处仅验证)
# 实际采样率由STM32固件决定,此处通过波形重建验证

# 生成正弦波数据点(HIL DAC更新率需远高于采样率)
dac_update_rate = 100000  # 100kHz DAC更新率
samples_per_cycle = dac_update_rate // sine_freq
t = [i / dac_update_rate for i in range(samples_per_cycle)]
sine_wave = [1.65 + 1.0 * math.sin(2 * math.pi * sine_freq * ti) for ti in t]

# 连续输出正弦波并采集
hil_dev.dac[0].write_waveform(sine_wave, dac_update_rate)
time.sleep(0.5)  # 采集0.5秒数据

# 读取多组数据用于频率分析
collected = []
for _ in range(20):
    result = read_stm32_result()
    if result:
        collected.append(result['mean'])
    time.sleep(0.01)

hil_dev.dac[0].write(0)  # 停止输出

if len(collected) >= 10:
    # 通过过零检测估算频率
    zero_crossings = 0
    for i in range(1, len(collected)):
        if (collected[i-1] - 1.65) * (collected[i] - 1.65) < 0:
            zero_crossings += 1
    
    # 每组数据是1024个采样点,采样率10kHz
    total_samples = len(collected) * 1024
    total_time = total_samples / sample_rate
    measured_freq = zero_crossings / (2 * total_time)
    
    freq_error = abs(measured_freq - sine_freq) / sine_freq * 100
    rate_pass = freq_error < 2.0  # 频率误差<2%
    print(f"  理论频率: {sine_freq} Hz, 实测频率: {measured_freq:.1f} Hz, "
          f"误差: {freq_error:.2f}% [{'PASS' if rate_pass else 'FAIL'}]")
else:
    print("  数据不足,无法分析")
    rate_pass = False

# ========== 总结 ==========
print("\n=== 测试总结 ===")
print(f"静态精度测试: {'PASS' if static_pass else 'FAIL'}")
print(f"采样率测试: {'PASS' if rate_pass else 'FAIL'}")
print(f"总体结果: {'PASS' if (static_pass and rate_pass) else 'FAIL'}")

hil_dev.disconnect()
ser.close()

运行与验证

  1. 编译烧录:使用STM32CubeIDE编译工程,通过ST-Link烧录至目标板
  2. 启动HIL脚本:确保HIL设备已上电并连接网络,运行python hil_test.py
  3. 观察输出:脚本依次执行静态精度测试与采样率测试,每项打印PASS/FAIL状态
  4. 通过标准:静态测试中所有电压点误差≤3 LSB;动态测试中实测频率与设定值偏差<2%

常见问题

  • 串口无数据:检查STM32端UART引脚与HIL脚本中串口号是否匹配,确认波特率一致
  • ADC读数跳变:检查PA0引脚是否悬空或受到干扰,确认HIL DAC输出阻抗匹配
  • 采样率不准确:验证定时器时钟配置,确认APB1时钟为84MHz且定时器分频正确