STM32实现LM19温度精准测量

参考资料

ADC采样:

【stm32-hal】ADC模拟-数字转换技术

电压转温度公式:

LM19温度传感器数据手册

一、LM19核心要点

  1. 供电:2.7~ 5.5V可测-55~ +130℃;2.4~ 2.7V仅-30~+130℃,STM32常用3.3V供电
  2. 输出 :模拟电压信号,温度越高输出电压越小
    抛物线公式: V O = − 3.88 × 10 − 6 T 2 − 0.0115 T + 1.8639 V_O=-3.88×10^{-6}T^2-0.0115T+1.8639 VO=−3.88×10−6T2−0.0115T+1.8639
    测温反推: T = − 1481.96 + 2.1962 × 10 6 + 1.8639 − V O 3.88 × 10 − 6 T=-1481.96+\sqrt{2.1962×10^6+\frac{1.8639-V_O}{3.88×10^{-6}}} T=−1481.96+2.1962×106+3.88×10−61.8639−VO
  3. 引脚(TO92) :1=VCC、2=VOUT、3=GND

二、硬件接线

LM19引脚 STM32 备注
V+(1脚) 3.3V 严禁5V超压可选
GND(3脚) GND 共地
VOUT(2脚) STM32 ADC输入引脚(PA0/PA1等)
外围推荐:电源端加0.1μF去耦电容;长线干扰大时:VOUT串200Ω+1μF到GND滤波

注意:LM19最大输出约2.485V(-55℃)<3.3V,可直接接入3.3V量程ADC

三、STM32软件思路(HAL库为例)

1.配置

  1. 开启ADC时钟,配置IO为模拟输入
  2. ADC配置:单次/连续转换、12位分辨率、内部参考电压3.3V
  3. ADC采样值换算电压:
    V o u t ( V ) = A D C v a l × 3.3 4095 V_{out}(V)=\frac{ADC_{val}×3.3}{4095} Vout(V)=4095ADCval×3.3

2.关键代码逻辑

c 复制代码
//1.ADC读取原始值
uint16_t adc_val=HAL_ADC_GetValue(&hadc1);
//2.换算输出电压(V)
float Vo = adc_val * 3.3f / 4095.0f;
//3.带入LM19反算公式求温度
float temp = -1481.96f + sqrt(2196200.0f + (1.8639f-Vo)/3.88e-6f);

3.简化方案(小量程近似线性)

-30~100℃适用: V O = − 0.01177 × T + 1.8605 V_O = -0.01177×T+1.8605 VO=−0.01177×T+1.8605

T = ( 1.8605 − V O ) / 0.01177 T=(1.8605-V_O)/0.01177 T=(1.8605−VO)/0.01177

计算简单、单片机运算快,日常测温优先用此式。

四、误差优化

  1. 采样滤波:连续采10~20次ADC取平均,降低噪声
  2. 自发热忽略:LM19静态电流≤10μA,自升温<0.02℃,无需补偿
  3. 电源稳压:STM32 3.3V不稳会带来ADC误差,必要时校准ADC参考电压

五、示例:常用温度对照(快速校验)

温度 Vo电压
0℃ 1.8639V
25℃ 1.574V
30℃ 1.515V
100℃ 0.675V

六、代码

根据【stm32-hal】ADC模拟-数字转换技术中配置ADC

  1. main()
c 复制代码
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "adc.h"
#include "gpio.h"
#include <math.h>

/* Private includes ----------------------------------------------------------*/


/**
  * @brief  The application entry point.
  * @retval int
  */
	int value = 0;
	float voltage =0.0;
	float temp =0.0;

//ADC多次采样平均值,times:采样次数
uint16_t Get_Adc_Average(uint8_t times)
{
    uint32_t adc_sum = 0;
    for(uint8_t i=0; i<times; i++)
    {
       // HAL_ADC_PollForConversion(&hadc1,HAL_MAX_DELAY);//已开启连续转换模式,只用写一次,可注释
        adc_sum += HAL_ADC_GetValue(&hadc1);
    }
    return adc_sum / times;
}

//LM19电压转温度
float LM19_GetTemp(float Vo)
{
    return -1481.96f + sqrtf(2196200.0f + (1.8639f - Vo)/3.88e-6f);
}

	
int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_ADC1_Init();
  /* USER CODE BEGIN 2 */
	HAL_ADCEx_Calibration_Start(&hadc1);
	HAL_ADC_Start(&hadc1);
	HAL_ADC_PollForConversion(&hadc1,HAL_MAX_DELAY);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */
    value = Get_Adc_Average(20);  //20次平均
    voltage = value * 3.3f / 4095.0f;
    temp = LM19_GetTemp(voltage);
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}
  1. adc.c
c 复制代码
/* Includes ------------------------------------------------------------------*/
#include "adc.h"

/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

ADC_HandleTypeDef hadc1;

/* ADC1 init function */
void MX_ADC1_Init(void)
{

  /* USER CODE BEGIN ADC1_Init 0 */

  /* USER CODE END ADC1_Init 0 */

  ADC_ChannelConfTypeDef sConfig = {0};

  /* USER CODE BEGIN ADC1_Init 1 */

  /* USER CODE END ADC1_Init 1 */

  /** Common config
  */
  hadc1.Instance = ADC1;
  hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
  hadc1.Init.ContinuousConvMode = ENABLE;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.NbrOfConversion = 1;
  if (HAL_ADC_Init(&hadc1) != HAL_OK)
  {
    Error_Handler();
  }

  /** Configure Regular Channel
  */
  sConfig.Channel = ADC_CHANNEL_15;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN ADC1_Init 2 */

  /* USER CODE END ADC1_Init 2 */

}

void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(adcHandle->Instance==ADC1)
  {
  /* USER CODE BEGIN ADC1_MspInit 0 */

  /* USER CODE END ADC1_MspInit 0 */
    /* ADC1 clock enable */
    __HAL_RCC_ADC1_CLK_ENABLE();

    __HAL_RCC_GPIOC_CLK_ENABLE();
    /**ADC1 GPIO Configuration
    PC5     ------> ADC1_IN15
    */
    GPIO_InitStruct.Pin = GPIO_PIN_5;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

  /* USER CODE BEGIN ADC1_MspInit 1 */

  /* USER CODE END ADC1_MspInit 1 */
  }
}

void HAL_ADC_MspDeInit(ADC_HandleTypeDef* adcHandle)
{

  if(adcHandle->Instance==ADC1)
  {
  /* USER CODE BEGIN ADC1_MspDeInit 0 */

  /* USER CODE END ADC1_MspDeInit 0 */
    /* Peripheral clock disable */
    __HAL_RCC_ADC1_CLK_DISABLE();

    /**ADC1 GPIO Configuration
    PC5     ------> ADC1_IN15
    */
    HAL_GPIO_DeInit(GPIOC, GPIO_PIN_5);

  /* USER CODE BEGIN ADC1_MspDeInit 1 */

  /* USER CODE END ADC1_MspDeInit 1 */
  }
}

/* USER CODE BEGIN 1 */

/* USER CODE END 1 */
  1. 时钟配置

将原来的

c 复制代码
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

改成

c 复制代码
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
  PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC;
  PeriphClkInit.AdcClockSelection = RCC_ADCPCLK2_DIV6;//ADCPrescaler = DIV6 → ADC_CLK=72/6=12MHz
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
  {
    Error_Handler();
  }
}

否则,测到的电压偏大,温度偏低

核心原因:ADC时钟变了 → ADC采样精度变 → 电压算错 → 温度飘

STM32F1 ADC最大时钟不能超过14MHz,这是关键限制:

1、原来配置
c 复制代码
ADCPrescaler = DIV2
SYSCLK=HSE*9=72M → APB2=72M → ADC_CLK =72/2=36MHz

超标(>14M),ADC工作异常、采样跳变不准,温度乱飘。

2、修改后
c 复制代码
ADCPrescaler = DIV6 → ADC_CLK=72/6=12MHz

12MHz<14MHz,ADC进入标准正常工作区间,采样真实电压,温度自然变准

快速总结
  1. DIV2:36M超规格 → ADC采样失真,温度错误
  2. DIV6:12M合规 → ADC读数真实,温度正确
配套优化(进一步稳温度)

ADC采样时间从1.5cycle改成长采样:

c 复制代码
sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;

七、现象

打开调试器可以观察到,temp等数据在不断变化。

相关推荐
不做无法实现的梦~1 小时前
常见编译,烧录和调试工具介绍
stm32·嵌入式硬件
济6171 小时前
【ROS2 Humble 开发专栏】Ubuntu22.04 基于 OpenCV 实现颜色阈值分割与目标坐标定位|附完整工程源码
嵌入式硬件·嵌入式·ros2·机器人开发·机器人方向
txh05071 小时前
串口数据调试-直观表示
嵌入式硬件·学习
半条-咸鱼1 小时前
【STM32】底层 CPU 流程、NVIC 优先级、GPIO 编程与事件(EVT)区别
stm32·单片机·嵌入式硬件
csg11071 小时前
工厂智能化改造(五):橡塑卷材边缘对齐与纠偏——一个典型非标案例
单片机·嵌入式硬件·物联网·自动化
振南的单片机世界2 小时前
地址总线定“找谁”,数据总线定“搬多少”
arm开发·stm32·单片机
yong99902 小时前
手机蓝牙发送指令STM32串口接收控制 LED 亮灭
stm32·单片机·智能手机
来点抹茶吗2 小时前
U-Boot、内核移植与根文件系统构建(BeagleBone Green Gateway&AM335X)
linux·嵌入式硬件·ubuntu·debian
嵌入式小站11 小时前
STM32 零基础可移植教程 17:USART + DMA + IDLE,串口不定长接收怎么做
stm32·单片机·嵌入式硬件