一、项目概述

本项目基于STM32F103C8T6 + FreeRTOS(CMSIS-RTOS v1),实现NTC热敏电阻温度采集与OLED显示。系统采用软件定时器500ms周期触发采样,通过任务通知唤醒温度任务,完成ADC采样、NTC温度换算、5点滑动平均滤波后,经长度为1的队列发送至OLED任务显示。

核心设计要求:

  • 软件定时器回调仅发送任务通知,不执行任何采样或刷新操作
  • 温度任务完成ADC读取、换算、滤波并发队
  • 断线或ADC越界时OLED显示Sensor ERR,系统不崩溃
  • 手摸NTC时Raw先变化,AVG平滑跟随

二、硬件接线

STM32引脚连接对象说明
PA0NTC分压中点NTC接VCC,10k电阻接GND
PB6OLED SCL软件模拟I2C
PB7OLED 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三行通过
手捏NTCRaw先变,AVG平滑跟随通过
拔掉PA0显示Sensor ERR通过
插回PA0自动恢复通过
编译0 Error 0 Warning通过

七、设计总结

  1. 定时器回调只发通知,耗时操作全在任务里做
  2. 静态分配开定时器必须补vApplicationGetTimerTaskMemory
  3. NTC分压方向要和公式对应,标称点附近容易看不出问题
  4. 断线检测用时间分片:平时AIN,检测瞬间切下拉
  5. 环形缓冲滑动平均O(1)更新,启动阶段按实际样本数平均
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐