基于STM32+FreeRTOS的NTC温度采集系统:软件定时器触发与断线检测设计
·
一、项目概述
本项目基于STM32F103C8T6 + FreeRTOS(CMSIS-RTOS v1),实现NTC热敏电阻温度采集与OLED显示。系统采用软件定时器500ms周期触发采样,通过任务通知唤醒温度任务,完成ADC采样、NTC温度换算、5点滑动平均滤波后,经长度为1的队列发送至OLED任务显示。
核心设计要求:
- 软件定时器回调仅发送任务通知,不执行任何采样或刷新操作
- 温度任务完成ADC读取、换算、滤波并发队
- 断线或ADC越界时OLED显示Sensor ERR,系统不崩溃
- 手摸NTC时Raw先变化,AVG平滑跟随
二、硬件接线
| STM32引脚 | 连接对象 | 说明 |
|---|---|---|
| PA0 | NTC分压中点 | NTC接VCC,10k电阻接GND |
| PB6 | OLED SCL | 软件模拟I2C |
| PB7 | OLED SDA | 软件模拟I2C |
| PA9 | 串口TX | 调试输出 |
NTC参数:10kΩ标称电阻,B值3950,R0=10kΩ@25°C。
三、系统架构
软件定时器(500ms自动重装)
回调:xTaskNotifyGive(tempTask) ← 仅通知
│ 任务通知
▼
温度任务(栈512字,优先级Normal)
① ulTaskNotifyTake 等待通知
② ADC采样PA0
③ 越界检测 + 断线检测
④ NTC Beta公式换算
⑤ 5点滑动平均
⑥ xQueueOverwrite 写入队列
│ 队列长度1
▼
OLED任务(栈256字,优先级BelowNormal)
xQueueReceive 接收数据
显示:ADC原始值 / Raw温度 / AVG温度
关键配置:configTOTAL_HEAP_SIZE=8192,温度任务栈512字,OLED任务栈256字。
四、关键问题与解决方案
4.1 软件定时器静态分配配置
开启configUSE_TIMERS后链接报错vApplicationGetTimerTaskMemory未定义。原因是工程用静态分配,开定时器后需要提供定时器服务任务的TCB和栈空间。
在FreeRTOSConfig.h添加:
#define configUSE_TIMERS 1
#define configTIMER_TASK_PRIORITY (tskIDLE_PRIORITY + 1)
#define configTIMER_TASK_STACK_DEPTH 128
#define INCLUDE_xTaskNotifyGive 1
freertos.c实现回调:
static StaticTask_t xTimerTaskTCBBuffer;
static StackType_t xTimerStack[configTIMER_TASK_STACK_DEPTH];
void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTCB,
StackType_t **ppxStack,
uint32_t *ppxSize)
{
*ppxTCB = &xTimerTaskTCBBuffer;
*ppxStack = &xTimerStack[0];
*ppxSize = configTIMER_TASK_STACK_DEPTH;
}
4.2 NTC分压方向与换算公式匹配
室温正常但手捏NTC温度反而下降。代码公式对应NTC接GND,实际接线是NTC接VCC。25°C时两边电阻都约10k看不出问题,偏离后方向错误暴露。
修正公式:
R_NTC = 10000.0f * (4095.0f - adc) / adc;
T_kelvin = 1.0f / (1.0f/298.15f + logf(R_NTC/10000.0f)/3950.0f);
temp_c = T_kelvin - 273.15f;
4.3 ADC断线检测设计
拔掉PA0线后OLED不显示ERR。PA0是模拟输入高阻态,悬空时电压随机飘,ADC读到中间值不触发越界。
直接改下拉输入能触发ERR,但内部40k下拉和外部10k并联改变分压比,温度不准。
最终方案:平时保持模拟输入,采样后短暂切下拉读数字电平,判断完立刻切回:
static uint8_t SensorIsDisconnected(void)
{
GPIO_InitTypeDef g = {0};
g.Pin = GPIO_PIN_0;
g.Mode = GPIO_MODE_INPUT;
g.Pull = GPIO_PULLDOWN;
HAL_GPIO_Init(GPIOA, &g);
for (volatile uint32_t i = 0; i < 2000; i++) __NOP();
uint8_t disconnected =
(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET);
g.Mode = GPIO_MODE_ANALOG;
g.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &g);
return disconnected;
}
正常接线PA0约1.5V读到高,悬空被下拉到0V读到低。检测仅数微秒,不影响分压。
五、核心代码
软件定时器回调:
static void vSamplingTimerCallback(TimerHandle_t xTimer)
{
(void)xTimer;
xTaskNotifyGive(temperatureTaskHandle);
}
温度任务:
static void StartTemperatureTask(void *argument)
{
SensorData_t data;
(void)argument;
for (;;) {
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
data.adc_raw = ADC_Read_Value();
data.sensor_err = (data.adc_raw < 50 || data.adc_raw > 4045)
|| SensorIsDisconnected();
if (data.sensor_err) {
data.temp_raw = data.temp_avg = 0;
ma_cnt = 0; ma_sum = 0; ma_idx = 0;
} else {
data.temp_raw = NTC_Convert(data.adc_raw);
ma_sum -= ma_buf[ma_idx];
ma_buf[ma_idx] = data.temp_raw;
ma_sum += data.temp_raw;
ma_idx = (ma_idx + 1) % 5;
if (ma_cnt < 5) ma_cnt++;
data.temp_avg = ma_sum / ma_cnt;
}
xQueueOverwrite(sensorQueueHandle, &data);
}
}
六、测试结果
| 测试项 | 预期结果 | 实际结果 |
|---|---|---|
| 正常接线 | 显示ADC/Raw/AVG三行 | 通过 |
| 手捏NTC | Raw先变,AVG平滑跟随 | 通过 |
| 拔掉PA0 | 显示Sensor ERR | 通过 |
| 插回PA0 | 自动恢复 | 通过 |
| 编译 | 0 Error 0 Warning | 通过 |
七、设计总结
- 定时器回调只发通知,耗时操作全在任务里做
- 静态分配开定时器必须补vApplicationGetTimerTaskMemory
- NTC分压方向要和公式对应,标称点附近容易看不出问题
- 断线检测用时间分片:平时AIN,检测瞬间切下拉
- 环形缓冲滑动平均O(1)更新,启动阶段按实际样本数平均
更多推荐


所有评论(0)