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()
运行与验证
- 编译烧录:使用STM32CubeIDE编译工程,通过ST-Link烧录至目标板
- 启动HIL脚本:确保HIL设备已上电并连接网络,运行
python hil_test.py - 观察输出:脚本依次执行静态精度测试与采样率测试,每项打印PASS/FAIL状态
- 通过标准:静态测试中所有电压点误差≤3 LSB;动态测试中实测频率与设定值偏差<2%
常见问题
- 串口无数据:检查STM32端UART引脚与HIL脚本中串口号是否匹配,确认波特率一致
- ADC读数跳变:检查PA0引脚是否悬空或受到干扰,确认HIL DAC输出阻抗匹配
- 采样率不准确:验证定时器时钟配置,确认APB1时钟为84MHz且定时器分频正确