基于HW-508 on Arduino Nano 的温湿度/距离检测兼播放器系统(待施工 图补)

说明书:基于AruduinoNano的
多功能传感装置
制作人:24级6班 刘君睿

第一部分 前言
第二部分 材料准备&环境配置
第三部分 Arduino初探&电路排布
第四部分 程序设计&调试优化说明
第五部分 成果演示
第六部分 思考&拓展
第七部分 总结

第一部分 前言
本学期暑假,为培养科技创新能力,发展综合学科思维,我利用家中现有的单片机资源组装了一个基础的测温测距套件,并结合竞赛课上学习的信息学奥林匹克竞赛程序设计知识,使用编译器软件完成了相关程序代码的编写,并上传到单片机中,最后对其硬件进行整合,制成一枚可用的多功能传感装置。
该装置体积小巧易携带,使用MiniUSB数据线从电子设备下载数据,拓展板使用DC接口供电。该设备使用温湿度传感器获取当前室温(050℃)/体感温度、环境湿度(20%90%);设备前端的超声波传感器可以在约400cm范围内对前方物体进行至高精准至小数点后6位的测距。上述数据均用2行16字符的液晶屏进行可视化,一枚无源蜂鸣器HW-508负责播放提示音(设备具有简易的开机音效)同时该设备使用两枚按钮调节按键音效、温度类别(详见第四部分)、屏幕刷新率、测距灵敏度、测距精度等六个参数,同时可执行开关屏操作。
本人从初步了解Arduino单片机到彻底完成制作该装置大约耗时一周。组成的该装置的各硬件材料设备来源于家中(早年购入),所需的编译器软件、电路设计与设备的软件调控相关知识均来源于Arduino单片机官网论坛和本人高中阶段的信息学奥赛程序设计知识储备,说明书中的内容和设计的所有代码也均为本人亲自编写。
特别鸣谢24级3班付琦睿同学在初探单片机阶段和代码内存优化阶段对本人的学术支持。

第二部分 材料准备&环境配置
一、硬件准备(图1.1)
Arduino Nano单片机及拓展板(通常二者拼在一起)
MiniUSB数据线
DHT11温湿度传感器(3Pin)
HC-SR04超声波传感器(4Pin)
1602液晶显示屏(4Pin)
双引脚按钮、三引脚按钮(2Pin+3Pin)
引脚连接Pin线(共16Pin)

1.1
第二部分 材料准备&环境配置
二、软件环境配置(图1.2-1.5)
1.Win11环境下,从Arduino官网下载ArduinoIDE和Arduino Nano芯片(CH340/CH341)专用驱动,同时完成编译器的安装。
2.使用MiniUSB数据线连接ArduinoNano和电脑,之后安装单片机驱动,将电脑连接的USB口转为COM端口。
3.运行ArduinoIDE,在左上角的“工具”选项总的开发板一项选择“Arduino Nano”,选择单片机连接的端口,在处理器一栏选择“ATmega328P(Old Bootloader)”至此,软件环境基本配置完毕。
1.2
1.3
1.4
1.5
第三部分 Arduino初探
一、编程语言异同(图2.1-2.4)
于我个人而言,Arduino的软件环境学习不算困难。通过实际上手可以知道,ArduinoIDE编译器所提供的语言环境与C++语言基本相同,基本语法(如变量,循环,函数等)与C++完全通用。对于有一定C++程序设计学习经历的我来说,代码的编写较好上手。同时,ArduinoIDE囊括了C++大部分函数库,(对于Arduino的实际应用而言似乎只涉及数学部分),因此在程序开头无需导入相关库,只需在编译器内提前下载并导入运行各类硬件所需的库。
C++ 言中的主函数main对应着Arduino中setup()和loop()两个函数,不同于main函数强制要求返回int类型(竞赛中也可用signed,进而不返回值),两个主函数均为void类型。其中setup函数仅在设备启动时运行一次,内部多为硬件接口初始化代码。而loop函数则在setup函数运行完毕后无限制循环运行。
因为Arduino以硬件控制为主,所以在程序中几乎不会用到标准的输入/输出,而是使用硬件库中的输入/输出函数。(注:标准输入输出printf/scanf仍为语言关键词,命名时应规避)
代码编写调试完毕后,先点击左上角对号按钮进行“验证”——也就是编译。如没有报错问题,可以点击旁边的左箭头按钮,将代码上传到单片机中,上传完毕后,在通电状态下,单片机便会开始运行,届时断开单片机与计算机的连接也不会有什么影响。值得注意的是,因为单片机自身并不充裕的储存和运存大小,因此编写代码时,相比程序的正确性,我们更应关注程序运行占用的内存大小和代码自身长度所占用的储存大小。(详见第四部分)
第三部分 电路排布
二、硬件信息(图2.5)
相比于软件,Arduino的硬件部分更易掌握。单片机及其拓展板和其他硬件上有若干种引脚,我们利用Pin线将其对应的引脚相连(亦存在Pin板插口式电路板),每个板件独享一条并联线路,单片机才能在通电时正常运行。
单片机(含拓展板)本体有若干引脚,大部分板件常用的对应引脚有GND引脚(接地),VCC引脚(供电)和控制引脚(数字引脚)。后者通常分为An类型和Dn类型,通常An类型接口用于模拟输入,Dn类型接口用于模拟输出/串口通信。
一些无需供电的板件(如按钮)没有VCC引脚。
大部分板件可以在An或Dn类别内自行选择数字引脚,只需硬件安装完毕后在编译器内定义好对应的引脚。
对于本设备使用到的DHT11和HC-SR04传感器,因为其从外界读取数据并输入到单片机,所以除固定GND和VCC引脚外,数字引脚选择输入类型(An)为宜(选择Dn引脚仍可工作,但经过实测,性能将大幅下降)注意到HC-SR04传感器有“Echo”和“Trig”两个控制引脚,应用Pin线将其与两个数字引脚相连。
除此之外,两个按钮及用于输出音效的蜂鸣器应选择输出类型(Dn)引脚作为数字引脚。
最终电路排布方案如下:
单片机本体连接拓展板,HC-SR04传感器连接A3,A2引脚,HW-508蜂鸣器连接D4引脚,双引脚、三引脚按钮分别连接D5,D6引脚(GND和VCC引脚连接处均在数字引脚的对应位置)
特别地,对于1602屏幕,它的数字引脚固定为A4和A5(分别对应屏幕引脚上的SDA、SCL引脚)

2.1

2.2

2.3

2.4

2.5

第四部分 程序设计
一、库
装置程序的编写同竞赛中的语言程序设计一样,应考虑程序所需的依赖库。
1.库的导入
程序设计的数学运算所需函数库无需导入,只是导入硬件运行所依赖的库(在第三部分中已经说明)
对于1602显示屏,需在ArduinoIDE编译器软件内下载LiquidCrystal库(程序中应导入LiquidCrystal_I2C库)
对于温湿度传感器,应下载并在程序中导入DHT库。
其它板件无需依赖库。
2.自定义库
为了简化主程序,提升主程序可读性,我们可以将程序中用到的变量或者宏定义放入自订库中,库名自取,不与已有库重名即可。
程序需要蜂鸣器播放音效,因为HW-508为无源蜂鸣器,不能自行发声,故需要自定义音频驱动信号,我们将其放入<beat.h>:
音符所需库准备完毕,我们可以将各参数变量放进一个自订库<Asuka.h>里,这样主程序中是需要放入库的引入、运算函数和主函数以及必要的初始化声明。
连个库内各宏定义和变量的作用详见注释。

//定义需要的三个八度

define Do 523

define Re 587

define Mi 659

define Fa 698

define Sol 784

define La 880

define Si 988

define Dol 262

define Rel 294

define Mil 330

define Fal 349

define Soll 392

define Lal 440

define Sil 494

define Doh 1046

define Reh 1175

define Mih 1318

define Fah 1397

define Solh 1568

define Lah 1760

define Sih 1976

short note[3][7] = { { Dol, Rel, Mil, Fal, Soll, Lal, Sil }, { Do, Re, Mi, Fa, Sol, La, Si }, { Doh, Reh, Mih, Fah, Solh, Lah, Sih } };//封装为一个二维数组(也可直接用频率定义) 
short sun[11][2]  = {//存储第一句伴奏(第一句后的两个音将作为两种提示音被调用) 
 { note[0][5], 429 }, { note[1][0], 429 }, { note[1][4], 429 }, { note[1][0], 429 }, { note[0][3], 429 }, { note[0][4], 214 }, { note[0][5], 214 }, { note[1][4], 429 }, { note[1][0], 429 },
 { note[1][0], 100 },
 { note[1][4],100 },
};//第一位存储音调,第二位存储间隔时间
beat.h的内容
const uint8_t tPin  PROGMEM= A2;//超声波传感器的Trig引脚 
const uint8_t ePin  PROGMEM= A3;//超声波传感器的Echo引脚 
const uint8_t bPin  PROGMEM= 4;//蜂鸣器引脚 
const uint8_t b1Pin PROGMEM = 6;//双引脚蜂鸣器引脚 
const uint8_t b2Pin PROGMEM = 7;//三引脚蜂鸣器引脚 
const uint8_t dPIN  PROGMEM= A1;//温湿度传感器引脚 
const uint8_t dTYPE = DHT11;//声明温湿度传感器类型:DHT11 
float dist;//距离 
uint8_t so;//屏幕显示状态:0熄屏/1亮屏 
uint8_t set;//界面选项状态:0功能界面/ 1设置界面 
uint8_t smod;//设置选项:1~5 
uint8_t si;//按键音效:0(静音),1(低音调),2(高音调) 
uint8_t te;//温度模式:1室温/2体感温度 
uint8_t hz;//刷新率档位:0-6 
uint8_t sen;//灵敏度档位:0-4 
uint8_t p;//测距精度 
int a,b;//播放音专用变量 
uint8_t fi[3] = {0,9,10};//播放档位 
int ms[7]= {200,125,100,50,25,20,10};//刷新间隔(ms) 
float od=-114514;//距离缓存(用于灵敏判定) 
float mis[5]= {0,0.5,1,5,10};//灵敏度限制量 
float temperature,humidity;//温度,湿度 
String k;//输出专用字符串 
//以下字符数组用来在屏幕上可视化 
String mod[6]= {"","SoundMode:","TemType:","FrameRate:","SenRate:","Precision:"};
String SM[3]= {"Off","Low","High"};
String TT[3]= {"","Envi","Body"};
String FR[7]= {"5hz","8hz","10hz","20hz","40hz","50hz","100hz"};
String SR[5]= {"RealTime","VeryHigh","High","Low","VeryLow"};
Asuka.h的内容

二、程序构造
依赖库导入完毕,接下来应该将程序按板件分为若干模块,分别完成各个板件的功能实现代码。
1.内容显示
导入LiquidCrystal_I2C库后,先声明一些必要参数:
LiquidCrystal_I2C lcd(0x27, 16, 2); //地址、行位、列位(这里一位代指一字符)
之后就可以编写主函数内代码:
lcd.init();//初始化         
  lcd.backlight();// 开启屏幕背光 
  lcd.clear();//清空屏幕原有内容
  lcd.setCursor(0, 0);//设定输出位置(0,0)第一行从头输出/(0,1)第二行从头输出
根据LiquidCrystal_I2C库的内置函数,理论上我们可以直接用lcd.print()输出内容,但经过实测,如上代码只能输出变量中的第一位内容(无论输出字符串还是数字)故我们应自行编写输出代码,用于输出多位内容,以输出字符串为例:
void lcdout(String a) {
  for (int i = 0; i <= a.length(); i++) lcd.print(a[i]);
}
在主函数中的调用代码:lcdout(a);//a为某字符串变量
显示模块代码编写完毕。
2.音效播放
为使用无源蜂鸣器播放声音,我们需要使用内置的tone函数(无需导入依赖库)如需停止播放,则需调用noTone函数。通常在tone和noTone函数见会加入delay函数来设定播放持续时间:
tone(a,b);//引脚a所连蜂鸣器持续播放bhz的声音
delay(c);//等待c毫秒,既持续播放c毫秒
noTone(a);//引脚a所连蜂鸣器停止播放
音效播放基础代码编写完毕,我们将其和<beat.h>库结合,用于播放音乐。导入<beat.h>库后,我们编写一段播放函数:
void sunday(int be,int ed){//起始和结束播放位置
  for (int i = be; i <=ed; i++) { 
    tone(bPin, sun[i][0]);//播放预设音调
    delay(sun[i][1]);//持续播放预设时间
  }
  delay(400);
  noTone(bPin);//停止播放
}
在主函数中我们调用sunday(a,b);//a,b分别为始末位置即可。
至此,程序的基本框架已经构建完毕,导入两个传感器所用库并调用内置的代码即可完成程序。
三、最终程序
代码如下,详见注释:
//I'm into C****z**.

include <LiquidCrystal_I2C.h>

include <DHT.h>

include <beat.h>

include <Asuka.h>

LiquidCrystal_I2C lcd(0x27, 16, 2);//声明地址、行、列
DHT dht(dPIN, dTYPE);//声明传感器接口、传感器类型
void sunday(uint8_t be, uint8_t ed) {  //音乐部分
  for (uint8_t i = be; i <= ed; i++) {
    a = sun[i][0], b = sun[i][1];
    tone(bPin, a);
    delay(b);
  }
  delay(400);
  noTone(bPin);
}
void lcdout() {  //字符串输出
  for (uint8_t i = 0; i < k.length(); i++) { lcd.print(k[i]); }
}
float hi() {  //计算体表温度
  return (-8.78469475556 + 1.61139411 * temperature + 2.33854883889 * humidity - 0.14611605 * temperature * humidity - 0.012308094 * temperature * temperature - 0.0164248277778 * humidity * humidity + 0.002211732 * temperature * temperature * humidity + 0.00072546 * temperature * humidity * humidity - 0.000003582 * temperature * temperature * humidity * humidity);
}
void t() {                                 //温度
  temperature = dht.readTemperature();     //室温
  humidity = dht.readHumidity() / 100.00;  //湿度
  if (te == 2) temperature = hi();
  k = String(temperature, 1);
  k += "`C";
}
void h() {  //湿度
  humidity = dht.readHumidity();
  k = String(humidity, 0);
  k += '%';
}
void dis() {
  digitalWrite(tPin, LOW);
  delayMicroseconds(2);
  digitalWrite(tPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(tPin, LOW);
  dist = pulseIn(ePin, HIGH)*17.0/1000.00;
  if (fabs(dist - od) <= mis[sen]) dist = od;
  od = dist;
  if (dist >=400) k = "TooFar/Close";  //超距判定
  else {
    k = String(dist, p);
    k += "cm";
  }
}
void setup() {
  lcd.init();  //初始化屏幕
  lcd.backlight();
  so = 1;   //亮屏
  set = 0;  //非设置
  si = 0;   //默认静音
  sen = 0;  //完全灵敏
  p = 3;    //测距3位
  te = 1;   //1:真实温度;2:体表温度
  hz = 3;   //默认刷新间隔4档(50ms/20hz)
  lcd.clear();
  lcd.setCursor(0, 0);
  dht.begin();            //初始化传感器
  pinMode(bPin, OUTPUT);  //初始化蜂鸣器
  Serial.begin(9600);     //初始化测距模块
  pinMode(tPin, OUTPUT);
  pinMode(ePin, INPUT);
  pinMode(b1Pin, INPUT_PULLUP);  //初始化1按钮
  pinMode(b2Pin, INPUT_PULLUP);  //初始化2按钮
  k = "Multi-Function";
  lcdout();  //开机动画、音乐
  lcd.setCursor(0, 1);
  k = "Sensor by Liu.Jr";
  lcdout();
  sunday(0, 8);
}
void loop() {
  lcd.clear();
  if (digitalRead(b1Pin) == LOW && !set) {  //功能界面下熄屏
    if (!so) {                              //当前熄屏
      if (si) sunday(fi[si], fi[si]);
      noTone(bPin);
      lcd.backlight(), so = 1;
    } else {  //当前亮屏
      if (si) sunday(fi[si], fi[si]);
      noTone(bPin);
      lcd.clear();
      k = "Screen Off";
      lcdout();
      lcd.noBacklight();
      so = 0;
      delay(ms[hz]);
    }
    delay(200);
  }
  if (so == 1) {                      //非熄屏
    if (digitalRead(b2Pin) == LOW) {  //设置判定
      if (si) sunday(fi[si], fi[si]);
      noTone(bPin);
      delay(300);
      if (digitalRead(b2Pin) == LOW) {  //长按0.5s,进退设置选项
        if (!set) set = 1, smod = 1;
        else set = 0;
        delay(300);
      } else if (set) {  //短按,切换设置模式(进入设置选项前提下)
        if (smod == 5) smod = 1;
        else smod++;
      }
    }
    if (!set) {  //功能界面
      lcd.clear();
      lcd.setCursor(0, 0);
      k = "Dis:";
      lcdout();
      dis();
      lcdout();
      lcd.setCursor(0, 1);
      k = "T:";
      lcdout();
      t();
      lcdout();
      k = "   ";
      lcdout();
      k = "H:";
      lcdout();
      h();
      lcdout();
      delay(ms[hz]);
    } else {  //设置界面
      lcd.clear();
      //渲染
      lcd.setCursor(0, 0);
      k = "Set";
      lcdout();
      lcd.print(smod);
      lcd.print("😊;
      lcd.setCursor(0, 1);
      k = mod[smod];
      lcdout();
      if (smod == 1) k = SM[si], lcdout();        //静音选项
      else if (smod == 2) k = TT[te], lcdout();   //温度选项
      else if (smod == 3) k = FR[hz], lcdout();   //刷新率
      else if (smod == 4) k = SR[sen], lcdout();  //灵敏度
      else lcd.print(p), k = "bit", lcdout();
      delay(ms[hz]);
      //调参
      if (digitalRead(b1Pin) == LOW) {  //短按调参
        if (si) sunday(fi[si], fi[si]);
        noTone(bPin);
        if (smod == 1) si = (si == 2 ? 0 : si + 1);          //静音
        else if (smod == 2) te = (te == 1 ? 2 : 1);          //温度
        else if (smod == 3) hz = (hz == 6 ? 0 : hz + 1);     //刷新率
        else if (smod == 4) sen = (sen == 4 ? 0 : sen + 1);  //灵敏度
        else if (smod == 5) p = (p == 6 ? 0 : p + 1);        //精度
        delay(125);                                   //强制8hz
      }
    }
  } else {
    lcd.clear();
    lcd.setCursor(0, 0);
    k = "Screen Off";
    lcdout();
    delay(ms[hz]);
  }
}

第五部分 成果展示
1.开机
接电后程序自动开机,播放开机音效,并显示“Multi-function
sensor bs Junsi”(图3.1),持续约1s后进入功能界面。
3.1
2.功能界面(图3.2)
第一行显示字符串“Dis:”附有从传感器到远处目标物体的实时距离(单位:cm)
第二行显示字符串“T:”附有温湿度传感器测出的温度(默认室温,精准到小数点后一位)和字符串“H:”附有当前湿度(精准到百分比整数)
3.2
3.熄屏
当且仅当屏幕显示功能界面时,点按二元按钮(红色按钮)即可关闭屏幕背光,屏幕显示字符“ScreenOff”(图3.3),再次点按屏幕开启背光,并显示功能界面.
3.2
2.设置界面
当且仅当屏幕显示功能界面时,长按三元按钮(黑色按钮)约1s,进入设置界面,直接进入第一项设置(共五项)。
第一项设置(图3.3):按钮音设置,点按二元按钮可在Off(关闭提示音),Low(低音调),High(高音调)之间切换,默认为Off(图3.4,之后选项的切换图略)。
3.3

3.4
点按三元按钮切换到第二项设置(图3.5):温度选项设置,点按二元按钮可在Envi(测室内实际温度),Body(测室内体感温度)之间切换,(其中体感温度使用简化的热指数公式计算,公式如代码所示)默认测室内实际温度。 3.5
点按三元按钮切换到第三项设置(图3.6):屏幕刷新率设置,点按二元按钮可在5hz,8hz,10hz,20hz,40hz,50hz,100hz之间切换,默认20hz(刷新率过低会导致输入采集不及时,按键操作无效,刷新率过高会导致屏幕快速闪烁,并且渲染不完全,不建议低于8hz,高于50hz)。
3.6
点按三元按钮切换到第四项设置(图3.7):测距灵敏度设置,点按二元按钮可在Realtime,VeryHigh,High,Low,VeryLow之间切换,默认Realtime.
Realtime状态下,测距模块实时输出当前测试结果,
VeryHigh状态下,当测出距离与上一帧测出距离相差大于0.5cm时,输出当前距离,否则输出上一帧测出距离;
High状态下,当测出距离与上一帧测出距离相差大于1cm时,输出当前距离,否则输出上一帧测出距离;
Low状态下,当测出距离与上一帧测出距离相差大于5cm时,输出当前距离,否则输出上一帧测出距离;
VeryLow状态下,当测出距离与上一帧测出距离相差大于10cm时,输出当前距离,否则输出上一帧测出距离。
3.7
点按三元按钮切换到第五项设置(图3.8):测距精度设置,点按二元按钮可在0bit6bit之间切换,测出距离会保留06位小数输出,默认保留3位。
3.8
再次点按三元按钮切回到第一项设置,往后操作在第一项到第五项设置间循环切换。

第六部分 思考&拓展
多功能装置制作完毕,下面对制作过程中的各类问题进行归纳分析。
1.测距模块的准确度问题
经过实测,超声波传感器可以在约400cm范围内对前方物体进行准确测距,但是在测试过程中仍会出现数据突然进行短暂大幅度波动的情况,这会导致我们设置的灵敏度显示功能一定程度上失效。
2.温湿度模块的准确度问题
室内温度的测量结果可能高于实际情况下的室温,误差在1摄氏度左右,影响较大。但经过分析,不准确的结果可能受限于DHT11传感器硬件性能。
3.较高刷新率下的频闪问题
在刷新率>40hz时,屏幕在功能界面会出现比较明显的频闪现象,且第二行的部分数据可能显示异常,会出现亮度较低的问题。这可能是由于数据计算与内容渲染同时进行,因为数据获取和计算缓解耗时较大导致在显示间隔内没有充足时间将一帧完全渲染。这一现象可能受限于设备芯片的计算性能,而频闪问题则是屏幕本身的性质所导致。
4.实用性问题
本设备拥有测距和测温湿度的功能,从客观上来讲功能不够丰富,而选项内容相比之下较为细致,有头重脚轻,外强中干的问题,颇有“金玉其外败絮其中”之感。至于距离、温度、湿度三个数据,基本上较为准确,能够满足日常需求。该装置较为轻便,在测距方面,日常中可以替代皮尺等测距工具用于日常生活方面。在温湿度测量方面,也可以作为摆件代替一般的温湿度剂。

第七部分 总结
本多功能装置能够实现说明的若干功能,具有一定实用性,可以作为实际工具运用到生活中。
在制作本装置的过程中,本人也学习了一些单片机设备相关知识以及ArduinoIDE编译器软件的使用方法,程序设计能力也有所提升,创新思维也得到培养,相信这些进步会对我的课内学习有所帮助。

posted @ 2025-08-15 14:48  Junsi  阅读(7)  评论(0)    收藏  举报