Arduino STM32用NTC热敏电阻 OLED显示屏制作温度计

1.材料清单

STM32F103C8T6开发板(黑色板)、NTC热敏电阻、12864OLED显示屏(四脚)、microUSB数据线、导线及面包板

2.电路连接

NTC热敏电阻连接电路:GND->10k电阻->PB1->NTC热敏电阻->3.3V
OLED显示屏连接电路:
 * GND->GND
 * VCC->3.3V
 * SCL->PB6
 * SCL->PB7

3.NTC热敏电阻测温

连接好电路后,先来读取热敏电阻的阻值,并发送到串口。

以上是NTC热敏电阻的接口电路,其中输出电压V_out连接PB1,VCC=3.3V。每次热敏电阻检测到温度变化时,输出电压都会随之变化。通常,我们使用分压器,其公式如下:

V_out = VCC ⋅[R_平衡 / (R_NTC + R_平衡)]

但是,我们不希望V_out作为答案,我们想要热敏电阻的阻值R_NTC。对上式整理得:

R_NTC = R_平衡 ⋅ (VCC / V_out - 1)

从上式可以看出我们需要测量电压输出和电源电压,这就是ADC的用武之地。ADC可以将电压表示为一定范围内的数字,所以上式最终结果如下:

R_NTC = R_平衡 ⋅(D_max / D_测量 - 1)

这在数学上是成功的,因为无论我们如何表示电压(以伏特或数字为单位),这些单位抵消了分数中的分子和分母,留下无量纲数。在那之后,乘以一个阻值,以欧姆为单位得出答案。

在STM32F103C8T6中ADC的范围是0~4095,所以D_max=4095,D_测量为PB1引脚读取到的值,R_平衡可通过万用表精准测得,由此我们便可以求得R_NTC的值。那么,R_NTC与温度的关系是怎样的呢?

幸运的是我们可以查得NTC热敏电阻的拟合曲线方程如下:

其中,R0为T0温度(单位:K)下的阻值,beta为常数,三个变量均由厂家给出。 

所以如果已知热敏电阻阻值,我们便可以通过上式求得对应的温度。

Let's get started!

/* 
   ===热敏电阻演示代码===
   为了消除噪声读数,采样ADC几次,然后平均样本以获得更稳定的测量值,用readThermistor函数实现。
   http://www.thermistors.cn/news/293.html
*/ 
const int sampleNumber = 10; //采样次数
const double balanceR = 9700.0; //参考电阻阻值,越精确越好
const double ADC_max = 4095.0; 
/*使用beta方程计算阻值。*/ 
const double beta = 3950.0; //商家给出的电阻对应25°C下的bata值
const double roomTemp = 298.15; //以开尔文为单位的室温25°C
const double roomTempR = 10000.0; //NTC热敏电阻在室温25°C下具有典型的电阻
double currentTemperature = 0; //保存当前温度

const int thermistorPin = PB1; // ADC采样电阻分压器的输出引脚

void setup(){
  //设置串口窗口消息的端口速度
  Serial.begin(9600);
} 
void loop(){ 
  currentTemperature = readThermistor();
//  Serial.print("当前温度:"); 
  Serial.println(currentTemperature);
//  Serial.println("°C");
  delay(3000);  
} 

/* 
  函数功能:读取模拟引脚,如下所示。
  通过模数转换将电压信号转换为数字表示。但是,这样做了多次,因此我们可以对其进行平均以消除测量误差。
  然后使用该平均数来计算热敏电阻的电阻。此后,电阻用于计算热敏电阻的温度。最后,温度转换为摄氏度。
  有关此过程的详细信息和一般理论,请参阅allaboutcircuits.com文章。
  原理图:

         (地面)----====-------  | ---------====--------3.3V 
                R_balance      |      R_thermistor 
                               | 
                             ADC引脚
*/ 

double readThermistor(){
  double rThermistor = 0; //保存热敏电阻的电阻值
  double tKelvin = 0; //以开尔文温度保存温度
  double tCelsius = 0; //以摄氏温度保存温度
  double adcAverage = 0; //保存平均电压测量值
  double adcSamplesi = 0 ;//保存当前采样值
  for(int i = 0; i<sampleNumber; i ++)
  { 
     adcSamplesi = analogRead(thermistorPin); //从引脚和存储
//     Serial.println(adcSamplesi);
     delay(10); //等待10毫秒
     adcAverage += adcSamplesi; //添加所有样本
  }
  adcAverage /= sampleNumber; //平均值w= sum/sampleNumber
//  Serial.println(adcAverage);
  /*公式计算热敏电阻的电阻。*/ 
  rThermistor = balanceR *((ADC_max / adcAverage) -  1); 
  tKelvin =(beta * roomTemp)/ 
             (beta +(roomTemp * log(rThermistor / roomTempR)));  
  tCelsius = tKelvin  -  273.15; //将开尔文转换为摄氏温度
  return tCelsius;//以摄氏度返回温度
}

串口输出效果:

由于ADC的偶然误差,每次测得的数值会有变化,将测得结果绘制图像如下:

故采用均值滤波来平滑所测的结果,由readThermistor中for循环实现。

MATLAB均值滤波代码

clear;clc;
load('tempData.mat');
n=length(data);
sum = 0;
% res = zeros(n,1);
x=1:n-100;
for i =1:n-100
    for j=1:100
        sum = sum+data(i+j);
    end
    res(i,1)=sum/100;
    sum=0;
end
figure(1);
clf
plot(x, data(1:n-100), '.-b', x, res, '-.r','linewidth',3);
set(gca,'FontSize',12); set(gcf,'Color','White');
xlabel('时间'); ylabel('温度(℃)');
legend('滤波前', '滤波后');

滤波前后对比图如下,可见均值滤波可以很好的去除噪点,能够提高OLED显示的稳定性! 

4.OLED显示温度

温度℃的显示用zimoV2.2软件取模,软件下载网盘链接:

链接:https://pan.baidu.com/s/1G7McxB00iCCCeBg9yUUg5w
提取码:qg3d

 这里还需要下载Adafruit GFX和Adafruit SSD1306两个库。

Arduino IDE操作方法:项目>>加载库>>管理库...,查找安装

加上OLED显示屏的完整程序:

/*NTC OLED显示屏制作温度计
 * NTC热敏电阻连接:GND->10k电阻->PB1->NTC热敏电阻->3.3V
 * OLED屏连接:
 * GND->GND
 * VCC->3.3V
 * SCL->PB6
 * SCL->PB7
*/
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306_STM32.h>
#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);

const int sampleNumber = 100; //采样次数,const对象放在只读内存中
const double balanceR = 9982.0; //参考电阻阻值,越精确越好
const double ADC_max = 4095.0; 
/*使用beta方程计算阻值。*/ 
const double beta = 3950.0; //商家给出的电阻对应25°C下的bata值
const double roomTemp = 298.15; //以开尔文为单位的室温25°C
const double roomTempR = 10000.0; //NTC热敏电阻在室温25°C下具有典型的电阻
double currentTemperature = 0; //保存当前温度

const int thermistorPin = PB1; // ADC采样电阻分压器的输出引脚


//*--文字: ℃--*/
static const unsigned char PROGMEM strC[] =
{

/*--  宽度x高度=40x35  --*/
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x07,0xC0,0x0C,0x00,0x00,0x0F,0xC0,0xFF,0xC0,0x00,0x1C,0xE3,
0xFF,0xE4,0x00,0x1C,0xE7,0xC0,0x7E,0x00,0x1C,0xE7,0x00,0x3E,0x00,0x0F,0xCF,0x00,
0x1E,0x00,0x07,0x9E,0x00,0x0E,0x00,0x00,0x1E,0x00,0x0E,0x00,0x00,0x3C,0x00,0x0E,
0x00,0x00,0x3C,0x00,0x0E,0x00,0x00,0x3C,0x00,0x04,0x00,0x00,0x3C,0x00,0x00,0x00,
0x00,0x3C,0x00,0x00,0x00,0x00,0x3C,0x00,0x00,0x00,0x00,0x3C,0x00,0x00,0x00,0x00,
0x3C,0x00,0x00,0x00,0x00,0x3C,0x00,0x00,0x00,0x00,0x3C,0x00,0x00,0x00,0x00,0x1C,
0x00,0x00,0x00,0x00,0x1E,0x00,0x06,0x00,0x00,0x1E,0x00,0x0C,0x00,0x00,0x0F,0x00,
0x1C,0x00,0x00,0x07,0x80,0x78,0x00,0x00,0x07,0xE1,0xF0,0x00,0x00,0x01,0xFF,0xE0,
0x00,0x00,0x00,0xFF,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};

#if (SSD1306_LCDHEIGHT != 64)
#error("Height incorrect, please fix Adafruit_SSD1306.h!");
#endif

void setup()
{
  Serial.begin(9600);
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // initialize with the I2C addr 0x3D (for the 128x64)
  display.display();
  delay(1000);
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.display();
   // init done
  delay(1000);
}
void loop()
{
 //*-- 以下开始显示内容 --*/
 //清理1306屏幕,准备显示:
  display.clearDisplay();

  // temp&Duty 显示://
  display.setTextSize(3);               //设置字体大小
  display.setTextColor(WHITE);          //设置字体颜色白色
  
  //中文字符显示
  display.setCursor(2,25);            //设置字体的起始位置
  currentTemperature = readThermistor();
  display.println(currentTemperature);                  //输出字符
  display.drawBitmap(98,18, strC, 40, 35, 1);    //℃,宽*高点阵
  
  display.display();                   //把缓存都显示
}

double readThermistor(){
  double rThermistor = 0; //保存热敏电阻的电阻值
  double tKelvin = 0; //以开尔文温度保存温度
  double tCelsius = 0; //以摄氏温度保存温度
  double adcAverage = 0; //保存平均电压测量值
  double adcSamplesi = 0 ;//保存当前采样值
  for(int i = 0; i<sampleNumber; i ++)
  { 
     adcSamplesi = analogRead(thermistorPin); //从引脚和存储
//     Serial.println(adcSamplesi);
     delay(10); //等待10毫秒
     adcAverage += adcSamplesi; //添加所有样本
  }
  adcAverage /= sampleNumber; //平均值w= sum/sampleNumber
//  Serial.println(ADC_max / adcAverage -  1);
  /*公式计算热敏电阻的电阻。*/ 
  rThermistor = balanceR *((ADC_max / adcAverage) -  1); 
  tKelvin =(beta * roomTemp)/ 
             (beta +(roomTemp * log(rThermistor / roomTempR)));  
  tCelsius = tKelvin  -  273.15; //将开尔文转换为摄氏温度
  return tCelsius;//以摄氏度返回温度
}

 5.实现效果

6.误差分析

由于beta值、R0、T0、ADC测量值、R_平衡均会有测量误差 ,所以需要对参数进行校准。又因为R_平衡为个人测量结果,测量误差较大,对结果影响最大。这里笔者采用体温计对体温测量,得到较精确的体温36.8℃,再用STM32得到此时的R_NTC值,通过上面的公式反推出R_平衡。

参考链接

http://www.thermistors.cn/news/293.html

发布了16 篇原创文章 · 获赞 26 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/weixin_42268054/article/details/104369968