#include "ntc.h" #include "gd32f4xx_adc.h" #include "gd32f4xx_rcu.h" #include "gd32f4xx_gpio.h" #include "board_config.h" #include /** * @brief 配置 NTC 引脚(PC5 模拟输入)与 ADC0 常规通道。 */ static void ntc_gpio_adc_config(void) { /* 使能 GPIOC 与 ADC0 时钟 */ rcu_periph_clock_enable(NTC_GPIO_RCU); rcu_periph_clock_enable(NTC_ADC_RCU); /* PC5 模拟输入模式 */ gpio_mode_set(NTC_GPIO_PORT, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, NTC_GPIO_PIN); /* ADC0 独立模式,常规通道软件触发、单次转换、右对齐 */ adc_sync_mode_config(ADC_SYNC_MODE_INDEPENDENT); adc_special_function_config(NTC_ADC, ADC_CONTINUOUS_MODE, DISABLE); adc_special_function_config(NTC_ADC, ADC_INSERTED_CHANNEL, DISABLE); adc_external_trigger_config(NTC_ADC, ADC_ROUTINE_CHANNEL, EXTERNAL_TRIGGER_DISABLE); adc_data_alignment_config(NTC_ADC, ADC_DATAALIGN_RIGHT); adc_channel_length_config(NTC_ADC, ADC_ROUTINE_CHANNEL, 1U); adc_routine_channel_config(NTC_ADC, 0U, NTC_ADC_CHANNEL, NTC_SAMPLETIME); adc_enable(NTC_ADC); adc_calibration_enable(NTC_ADC); } void ntc_init(void) { ntc_gpio_adc_config(); } /** * @brief 触发一次转换并读取 12bit 原始值。 */ static uint16_t ntc_read_raw_once(void) { adc_flag_clear(NTC_ADC, ADC_FLAG_EOC); adc_software_trigger_enable(NTC_ADC, ADC_ROUTINE_CHANNEL); /* 等待转换完成(EOC) */ while (SET != adc_flag_get(NTC_ADC, ADC_FLAG_EOC)) {} return (uint16_t)adc_routine_data_read(NTC_ADC); } float ntc_read_temperature(void) { uint16_t samples[NTC_SAMPLE_COUNT]; uint32_t sum = 0U; /* 采集 NTC_SAMPLE_COUNT 个样本 */ for (uint8_t i = 0; i < NTC_SAMPLE_COUNT; i++) { samples[i] = ntc_read_raw_once(); } /* 插入排序(16 元素,开销可忽略),为修剪均值做准备 */ for (uint8_t i = 1; i < NTC_SAMPLE_COUNT; i++) { uint16_t key = samples[i]; int8_t j = (int8_t)(i - 1); while (j >= 0 && samples[j] > key) { samples[j + 1] = samples[j]; j--; } samples[j + 1] = key; } /* 修剪均值:去掉每端 NTC_TRIM_COUNT 个极值(野值/毛刺)后取平均 */ for (uint8_t i = NTC_TRIM_COUNT; i < (NTC_SAMPLE_COUNT - NTC_TRIM_COUNT); i++) { sum += samples[i]; } float adc_val = (float)sum / (float)(NTC_SAMPLE_COUNT - 2U * NTC_TRIM_COUNT); /* 分压点电压:上拉 R_PULL 在 VREF 侧,NTC 接 GND 侧 */ float voltage = adc_val * NTC_VREF / NTC_ADC_MAX; /* 开路/短路判定:电压贴近电源轨说明 NTC 支路没有形成分压通路 */ if (voltage >= 0.97f * NTC_VREF) { return -273.15f; /* 开路(PC5 被上拉拉满,NTC 未连通) */ } if (voltage <= 0.03f * NTC_VREF) { return -274.15f; /* 短路(PC5 被拉到 GND) */ } /* 反解 NTC 阻值:V = VREF * R / (R_PULL + R) => R = R_PULL * V / (VREF - V) */ float r_ntc = NTC_R_PULL * voltage / (NTC_VREF - voltage); /* B 值公式:1/T = 1/T25 + ln(R/R25)/B */ float t_k = 1.0f / (1.0f / NTC_T25 + logf(r_ntc / NTC_R25) / (float)NTC_B_VALUE); return t_k - 273.15f; } /******************************************************************************/ /* 后台采样 + 全局最新值:各消费者(debug 打印 / M1 / M2 上报)共享一次采样 */ /******************************************************************************/ static float s_ntc_last_temp = -273.15f; /* 最新温度(初值 = 开路标记) */ static uint16_t s_ntc_sample_divider = 0; /* 主循环 10ms 圈分频计数 */ /* 滑动中值滤波(V0.1.11):真机读数毛刺可持续 1.4s+(曾抓到 14 帧连续 OPEN),会穿透控制环的斜率限幅并触发猛拉。最近 NTC_MEDIAN_N 个样本取 中值——单点/短串毛刺被完全剔除,滞后仅 ~0.2s(远小于低通滤波)。 故障标记值(-273/-274)参与排序:偶发 OPEN 被中值滤掉,连续 >=3 帧才 成为中值(等效又一层去抖),故障判定语义不变。 */ #define NTC_MEDIAN_N 5U static float s_ntc_win[NTC_MEDIAN_N]; static uint8_t s_ntc_win_idx = 0; static uint8_t s_ntc_win_fill = 0; void ntc_sample_task(void) { /* 主循环周期 10ms:按 NTC_SAMPLE_PERIOD_MS 分频触发一次采样 */ if (++s_ntc_sample_divider >= (NTC_SAMPLE_PERIOD_MS / 10U)) { s_ntc_sample_divider = 0; float v = ntc_read_temperature(); s_ntc_win[s_ntc_win_idx] = v; s_ntc_win_idx = (uint8_t)((s_ntc_win_idx + 1U) % NTC_MEDIAN_N); if (s_ntc_win_fill < NTC_MEDIAN_N) { s_ntc_win_fill++; } /* 插入排序后取中值(5 点,开销可忽略) */ float tmp[NTC_MEDIAN_N]; for (uint8_t i = 0; i < s_ntc_win_fill; i++) { tmp[i] = s_ntc_win[i]; } for (uint8_t i = 1; i < s_ntc_win_fill; i++) { float key = tmp[i]; int8_t j = (int8_t)(i - 1); while (j >= 0 && tmp[j] > key) { tmp[j + 1] = tmp[j]; j--; } tmp[j + 1] = key; } s_ntc_last_temp = tmp[s_ntc_win_fill / 2U]; } } float ntc_get_temperature(void) { return s_ntc_last_temp; }