1.Arduino板代码
1.调用库,初始化
#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/wdt.h>
#include <Wire.h>
#include <SPI.h>
#include <Max44009.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include <SoftwareSerial.h>
#define SEALEVELPRESSURE_HPA (1013.25)
#define uv_sensor A3
#define rain_sensor A1
SoftwareSerial WindSerial(2, 3); // RX, TX
Adafruit_BME280 bme;
Max44009 myLux(0x4A);
unsigned char Request[8] = { 0x02, 0x03, 0x00, 0x00, 0x00, 0x01, 0x84, 0x39 }; //风速测量命令
unsigned char Request1[8] = { 0x01, 0x03, 0x00, 0x00, 0x00, 0x02, 0xC4, 0x0B }; //风向测量命令
unsigned char Request2[8] = { 0x03, 0x03, 0x00, 0x00, 0x00, 0x01, 0x85, 0xE8 }; //雨量测量命令
String LoRaMessage = "";
String StationName = "NUS_Soochow_Weather_Station|";
//Initial counter
int ct = 0;
//Watchdog counter
int count = 1;
volatile int f_wdt = 0;
//AverageFilter
float lux_filter[5];
float temperature_filter[5];
float pressure_filter[5];
float altitude_filter[5];
float humidity_filter[5];
float windspd_filter[5];
int winddir_filter[5];
这些代码是针对气象站进行的初始化定义,其中有初始化库,对类的定义,变量的定义,数组的定义,其中这个对对象初始化的部分,Max44009 myLux(0x4A),就是把地址已经给了I2C通信协议的初始化。
2.setup
void setup() {
Serial.begin(9600);
WindSerial.begin(4800);
Wire.begin();
pinMode(6, INPUT);
pinMode(A3, INPUT);
pinMode(4, OUTPUT);
pinMode(5, OUTPUT);
digitalWrite(4, LOW);
digitalWrite(5, HIGH);
if (!myLux.isConnected()) {
Serial.println("ER01"); //no lux
while (1) delay(10);
}
if (!bme.begin(0x76)) {
Serial.println(F("ER02")); //no bme
while (1) delay(10);
}
bme.setSampling(Adafruit_BME280::MODE_FORCED,
Adafruit_BME280::SAMPLING_X1, // temperature
Adafruit_BME280::SAMPLING_X1, // pressure
Adafruit_BME280::SAMPLING_X1, // humidity
Adafruit_BME280::FILTER_OFF);
myLux.clrContinuousMode();
//set_sleep_mode(SLEEP_MODE_PWR_DOWN);
//sleep_enable();
// 0=16ms, 1=32ms,2=64ms,3=128ms,4=250ms,5=500ms
// 6=1 sec,7=2 sec, 8=4 sec, 9= 8sec
setup_watchdog(6);
//OFFACDDC();
Sleep_avr();
}
这段代码是Arduino程序中的setup()
函数,用于初始化和配置微控制器的硬件和库。以下是对代码中每部分的详细介绍和解释:
1. 串口通信初始化
Serial.begin(9600);
- 功能:初始化Arduino的硬件串口通信,波特率设置为9600bps。这允许Arduino通过串口与计算机通信,用于调试信息的输出。
WindSerial.begin(4800);
- 功能:初始化名为
WindSerial
的SoftwareSerial
对象,波特率设置为4800bps。这允许Arduino通过软件模拟的串口与风速风向传感器通信。
2. I2C通信初始化
Wire.begin();
- 功能:初始化Arduino的I2C通信协议。这通常用于与I2C接口的传感器(如BME280和Max44009)通信。
3. 引脚配置
pinMode(6, INPUT);
pinMode(A3, INPUT);
pinMode(4, OUTPUT);
pinMode(5, OUTPUT);
- 功能:配置引脚模式。
- 引脚6和A3被设置为输入模式,可能用于读取传感器数据。
- 引脚4和5被设置为输出模式,可能用于控制其他设备或指示灯。
digitalWrite(4, LOW);
digitalWrite(5, HIGH);
- 功能:设置引脚4为低电平,引脚5为高电平。这可能是用于初始化一些设备的电源状态或其他控制信号。
4. 传感器连接检查
if (!myLux.isConnected()) {
Serial.println("ER01"); // no lux
while (1) delay(10);
}
- 功能:检查Max44009光强传感器是否连接成功。如果未连接,输出错误信息"ER01"并通过无限循环停止程序执行。
if (!bme.begin(0x76)) {
Serial.println(F("ER02")); // no bme
while (1) delay(10);
}
- 功能:尝试与BME280温湿度气压传感器建立通信。传感器的I2C地址是0x76。如果通信失败,输出错误信息"ER02"并通过无限循环停止程序执行。
5. 传感器配置
bme.setSampling(Adafruit_BME280::MODE_FORCED,
Adafruit_BME280::SAMPLING_X1, // temperature
Adafruit_BME280::SAMPLING_X1, // pressure
Adafruit_BME280::SAMPLING_X1, // humidity
Adafruit_BME280::FILTER_OFF);
- 功能:配置BME280传感器的采样模式。
MODE_FORCED
:强制模式,每次读取都会进行新的测量。SAMPLING_X1
:采样设置,这里设置为X1,表示最大的采样值。
myLux.clrContinuousMode();
- 功能:将Max44009光强传感器从连续模式切换到单次测量模式。
6. 看门狗定时器配置
setup_watchdog(6);
- 功能:配置看门狗定时器,超时时间为1秒(根据注释中的设置)。看门狗定时器用于重置微控制器,防止程序卡死。
7. 睡眠模式配置
//set_sleep_mode(SLEEP_MODE_PWR_DOWN);
//sleep_enable();
- 功能:这部分代码被注释掉了。如果启用,将会设置微控制器进入深度睡眠模式,以节省能量。
SLEEP_MODE_PWR_DOWN
是其中一种模式。
Sleep_avr();
- 功能:调用
Sleep_avr()
函数使Arduino进入睡眠模式。这需要相应的函数定义来实现具体的睡眠逻辑。
总的来说,setup()
函数负责初始化硬件、配置传感器、设置通信协议和错误处理,确保所有设备在进入主循环loop()
之前正确配置和准备就绪。
3.Loop函数
void loop() {
if (f_wdt >= 2) {
//ONACDDC();
//delay(1000);
while (ct < 5) {
WeatherMeasure();
ct++;
}
if (count <= 100) {
WeatherMeasure();
WeatherReport();
count++;
}
else {
count = 1;
}
delay(500);
//Serial.println("Data sent to ESP32C3 successfully");
//防止过于快速地进入睡眠
f_wdt = 0;
//OFFACDDC();
Sleep_avr();
}
}
这段代码是一个Arduino的loop()
函数,它使用了看门狗定时器(WDT)来管理程序的执行流程和周期性任务。以下是对代码的详细解释:
1. 看门狗定时器的检查
if (f_wdt >= 2) {
- 功能:检查全局变量
f_wdt
的值是否大于或等于2。f_wdt
通常是由看门狗定时器中断(WDT_vect
)增加的。每次看门狗定时器超时并触发中断时,f_wdt
的值会增加。 - 目的:确保程序在看门狗定时器超时之前完成了必要的操作。
2. 测量和报告天气数据
while (ct < 5) {
WeatherMeasure();
ct++;
}
- 功能:循环执行
WeatherMeasure()
函数,直到ct
(计数器)达到5。WeatherMeasure()
函数用于收集当前的天气数据。 - 目的:批量收集天气数据,为后续的报告做准备。
if (count <= 100) {
WeatherMeasure();
WeatherReport();
count++;
}
else {
count = 1;
}
- 功能:
- 如果
count
小于或等于100,再次调用WeatherMeasure()
和WeatherReport()
函数,然后增加count
。 - 如果
count
大于100,将其重置为1。
- 如果
- 目的:控制数据收集和报告的频率。每收集5次数据后,报告一次数据。
3. 延迟和防止快速睡眠
delay(500);
- 功能:在继续下一次循环之前,程序暂停500毫秒。
- 目的:提供足够的时间间隔,防止程序过于频繁地执行,也可以看作是一种节流操作。
4. 重置看门狗定时器和进入睡眠模式
f_wdt = 0;
- 功能:将
f_wdt
重置为0。 - 目的:重置看门狗定时器的计数器,防止看门狗定时器超时触发重启。
Sleep_avr();
- 功能:调用
Sleep_avr()
函数,使Arduino进入睡眠模式。 - 目的:减少Arduino的功耗,节省能源。
总结
这段代码的主要目的是通过看门狗定时器来控制数据收集和报告的周期性。通过在loop()
函数中使用看门狗定时器,确保程序在规定的时间内完成数据收集和报告任务,并在完成任务后进入睡眠状态以节省能源。
这种设计模式适用于需要低功耗和周期性数据报告的应用场景,如气象站、环境监测等。通过合理配置看门狗定时器和睡眠模式,可以在保证系统稳定性的同时,最大限度地减少能源消耗。
4.相关函数
void WeatherMeasure() {
int windir = windDirection();//Wind Direction
float windsp = averageFilter(windspd_filter, WindSpeed()); //Wind Speed
float lux = averageFilter(lux_filter, myLux.getLux());//Light intensity
int uv = UV_Voltage();//UV intensity
float rain_mm = rainfall_mm();
bme.takeForcedMeasurement();//Temp/Pres/Hum/DewPt
float temperature = averageFilter(temperature_filter, bme.readTemperature());
float pressure = averageFilter(pressure_filter, bme.readPressure() / 100.0F);
float humidity = averageFilter(humidity_filter, bme.readHumidity());
LoRaMessage = String(temperature) + "~" + String(pressure) + "!" + String(humidity)
+ "@" + String(lux) + "#" + String(uv) + "$" + String(windsp)
+ "%" + String(windir) + "^" + String(rain_mm) + "N" + String(count);
}
void WeatherReport() {
Serial.println(LoRaMessage);
}
void OFFACDDC() { //关闭ACD和ADC
ACSR |= _BV(ACD);
ADCSRA = 0;
}
void ONACDDC() { //开启ACD和ADC
ACSR &= ~_BV(ACIE);
ACSR &= ~_BV(ACD);
ADCSRA |= _BV(ADEN);
ADCSRA |= _BV(ADIF);
}
void setup_watchdog(int ii) { //看门狗设置
byte bb;
if (ii > 9) ii = 9;
bb = ii & 7;
if (ii > 7) bb |= (1 << 5);
bb |= (1 << WDCE);
MCUSR &= ~(1 << WDRF);
// start timed sequence
WDTCSR |= (1 << WDCE) | (1 << WDE);
// set new watchdog timeout value
WDTCSR = bb;
WDTCSR |= _BV(WDIE);
}
ISR(WDT_vect) { //看门狗唤醒执行函数
f_wdt++;
}
void Sleep_avr(){
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
sleep_mode();
}
float averageFilter(float data[], float in_data) {
float sum = 0;
for (int i = 0; i < 4; i++) {
data[i] = data[i + 1];
sum = sum + data[i];
}
data[4] = in_data;
sum = sum + data[4];
return (sum / 5);
}
int windDirection() {
String windata = "";
for (int i = 0; i < 8; i++) { // 发送测温命令
WindSerial.write(Request1[i]); // write输出
}
delay(50);
while (WindSerial.available()) { //从串口中读取数据
unsigned char in = ((unsigned)WindSerial.read()); // read读取
windata += in;
windata += ',';
}
int commaPosition = -1;
String info[9]; // 用字符串数组存储
for (int i = 0; i < 9; i++) {
commaPosition = windata.indexOf(',');
if (commaPosition != -1) {
info[i] = windata.substring(0, commaPosition);
windata = windata.substring(commaPosition + 1, windata.length());
} else {
if (windata.length() > 0) { // 最后一个会执行这个
info[i] = windata.substring(0, commaPosition);
}
}
}
int data = info[4].toInt();
int sum = 0;
for (int i = 0; i < 4; i++) {
winddir_filter[i] = winddir_filter[i + 1];
sum = sum + winddir_filter[i];
}
winddir_filter[4] = data;
sum = sum + winddir_filter[4];
return (sum / 5);
}
float WindSpeed() {
String windata = "";
for (int i = 0; i < 8; i++) { // 发送测温命令
WindSerial.write(Request[i]); // write输出
}
delay(50);
while (WindSerial.available()) { //从串口中读取数据
unsigned char in = ((unsigned)WindSerial.read()); // read读取
windata += in;
windata += ',';
}
//windata = windata.substring(16, 32);
//Serial.println(windata);
int commaPosition = -1;
String info[9]; // 用字符串数组存储
for (int i = 0; i < 9; i++) {
commaPosition = windata.indexOf(',');
if (commaPosition != -1) {
info[i] = windata.substring(0, commaPosition);
windata = windata.substring(commaPosition + 1, windata.length());
} else {
if (windata.length() > 0) {
info[i] = windata.substring(0, commaPosition);
}
}
}
return (((info[3].toInt() << 8) | info[4].toInt()) / (float)10);
}
float rainfall_mm() {
String raindata = "";
for (int i = 0; i < 8; i++) { // 发送测温命令
WindSerial.write(Request2[i]); // write输出
}
delay(50);
while (WindSerial.available()) { //从串口中读取数据
unsigned char in = ((unsigned)WindSerial.read()); // read读取
raindata += in;
raindata += ',';
}
int commaPosition = -1;
String info[9];
for (int i = 0; i < 9; i++) {
commaPosition = raindata.indexOf(',');
if (commaPosition != -1) {
info[i] = raindata.substring(0, commaPosition);
raindata = raindata.substring(commaPosition + 1, raindata.length());
} else {
if (raindata.length() > 0) {
info[i] = raindata.substring(0, commaPosition);
}
}
}
//Serial.println(((info[3].toInt() << 8) | info[4].toInt()) / (float)10);
return (((info[3].toInt() << 8) | info[4].toInt()) / (float)10);
}
int UV_Voltage() {
int sensorValue = 0;
long sum = 0;
for (int i = 0; i < 1024; i++) {
sensorValue = analogRead(A3);
sum = sensorValue + sum;
delay(1);
}
//Serial.println(sensorValue);
sum = sum / 1024;
sum = sum * 4980.0 / 1024;
return sum;
}
这段代码是Arduino程序的一部分,包含了多个函数,用于从传感器收集数据、处理数据、通过串口报告数据以及管理看门狗定时器和电源。以下是对每个函数的详细解释:
WeatherMeasure()
函数
这个函数用于从各种传感器收集天气数据,并对某些数据进行平均滤波处理。
void WeatherMeasure() {
int windir = windDirection(); // 风向
float windsp = averageFilter(windspd_filter, WindSpeed()); // 风速
float lux = averageFilter(lux_filter, myLux.getLux()); // 光照强度
int uv = UV_Voltage(); // UV 强度
float rain_mm = rainfall_mm(); // 降雨量
bme.takeForcedMeasurement(); // 触发 BME280 传感器进行一次测量
float temperature = averageFilter(temperature_filter, bme.readTemperature()); // 温度
float pressure = averageFilter(pressure_filter, bme.readPressure() / 100.0F); // 气压
float humidity = averageFilter(humidity_filter, bme.readHumidity()); // 湿度
// 构建 LoRa 消息字符串
LoRaMessage = String(temperature) + "~" +
String(pressure) + "!" +
String(humidity) + "@" +
String(lux) + "#" +
String(uv) + "$" +
String(windsp) + "%" +
String(windir) + "^" +
String(rain_mm) + "N" +
String(count);
}
- 功能:从风速风向传感器、光照传感器、UV传感器、雨量传感器、BME280(温度、气压、湿度)传感器收集数据,并对风速、温度、压力、湿度数据进行平均滤波处理。
- 目的:获取当前环境的气象数据,并构建一条包含所有数据的LoRa消息字符串。
WeatherReport()
函数
这个函数用于通过串口报告天气数据。
void WeatherReport() {
Serial.println(LoRaMessage);
}
- 功能:通过串口打印
LoRaMessage
字符串,该字符串包含了所有测量的气象数据。 - 目的:允许用户通过串口监视器查看收集到的天气数据。
OFFACDDC()
和 ONACDDC()
函数
这两个函数用于开启和关闭模拟比较器和模拟数字转换器(ADC)。
void OFFACDDC() { // 关闭ACD和ADC
ACSR |= _BV(ACD);
ADCSRA = 0;
}
void ONACDDC() { // 开启ACD和ADC
ACSR &= ~_BV(ACIE);
ACSR &= ~_BV(ACD);
ADCSRA |= _BV(ADEN);
ADCSRA |= _BV(ADIF);
}
- 功能:
OFFACDDC()
关闭ACD和ADC,ONACDDC()
开启ACD和ADC。 - 目的:在不需要进行模拟信号比较或数字转换时关闭这些功能,可以节省能源。
setup_watchdog()
函数
这个函数用于设置看门狗定时器。
void setup_watchdog(int ii) { // 看门狗设置
byte bb;
if (ii > 9) ii = 9;
bb = ii & 7;
if (ii > 7) bb |= (1 << 5);
bb |= (1 << WDCE);
MCUSR &= ~(1 << WDRF);
WDTCSR |= (1 << WDCE) | (1 << WDE);
WDTCSR = bb;
WDTCSR |= _BV(WDIE);
}
- 功能:配置看门狗定时器的超时时间,并启用看门狗定时器。
- 目的:确保程序在超时时间内完成必要的操作,防止程序卡死。
ISR(WDT_vect)
函数
这是看门狗定时器中断服务例程。
ISR(WDT_vect) { // 看门狗唤醒执行函数
f_wdt++;
}
- 功能:每次看门狗定时器超时并触发中断时,增加
f_wdt
的值。 - 目的:用于跟踪看门狗定时器超时的次数。
Sleep_avr()
函数
这个函数用于使Arduino进入睡眠模式。
void Sleep_avr(){
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
sleep_mode();
}
- 功能:配置Arduino进入深度睡眠模式,以节省能源。
- 目的:在不需要进行数据收集和处理时,降低Arduino的功耗。
averageFilter()
函数
这个函数用于对传感器数据进行平均滤波处理。
float averageFilter(float data[], float in_data) {
float sum = 0;
for (int i = 0; i < 4; i++) {
data[i] = data[i + 1];
sum = sum + data[i];
}
data[4] = in_data;
sum = sum + data[4];
return (sum / 5);
}
- 功能:使用移动平均滤波算法计算5个连续数据的平均值。
- 目的:平滑传感器数据,减少随机噪声。
其他函数
windDirection()
:读取风向传感器数据,并进行平均滤波处理。WindSpeed()
:读取风速传感器数据。rainfall_mm()
:读取雨量传感器数据。UV_Voltage()
:读取UV传感器数据,并计算电压值。
这些函数共同构成了一个完整的数据收集和报告流程,通过周期性地唤醒Arduino,收集传感器数据,并通过串口报告,最后进入睡眠模式以节省能源。
2.ESP32板代码
1.初始化
#include <WiFi.h>
#include <TFT_eSPI.h>
#include <SPI.h>
#include <HardwareSerial.h>
HardwareSerial MySerial(1);
String apikey = "NOPHHZME1H3V6RZZ"; //apikey
int count = 1; //数数计数器
int ct = 0; //清屏计数器
int ct1 = 0; //接收但未上传云端计数器
const char* ssid = "NUSRI-STU"; //local wifi
const char* password = "Nusri311";
const char* server = "api.thingspeak.com"; //cloud server
//String comdata = "26~1020!65@200#55$5%0^10";
String comdata = "";
String temperature;
String pressure;
String humidity;
String winddirection;
String windspeed;
String rainfall;
String lux;
String uv;
String LoRaMessage = "";
WiFiClient client;
TFT_eSPI tft = TFT_eSPI();
定义了thinkingspeak的API接口,wifi的信息。以及其他的初始化
2.setup函数
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
MySerial.begin(9600, SERIAL_8N1, 1, 0);
tft.init();
tft.fillScreen(TFT_BLACK); //屏幕颜色
tft.setCursor(00, 00, 1); //设置起始坐标(10, 10),2 号字体
tft.setTextColor(TFT_WHITE); //设置字体颜色
tft.setTextSize(1); //设置文字的大小 (1~7)
WiFi.mode(WIFI_STA); //station mode
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
WiFi.begin(ssid, password);
delay(1000);
}
tft.println("WiFi connected..!");
tft.print("Got IP: ");
tft.println(WiFi.localIP());
fillData();
//MySerial.println("Start");
}
这段代码是Arduino的setup()
函数,用于初始化和配置硬件以及网络连接。以下是对代码中每部分的详细解释:
1. 串口通信初始化
Serial.begin(9600);
- 功能:初始化Arduino板载的硬件串口,波特率设置为9600bps,用于与计算机通信,便于调试。
MySerial.begin(9600, SERIAL_8N1, 1, 0);
- 功能:初始化名为
MySerial
的软件串口,波特率设置为9600bps,配置为8位数据位,无校验位,1位停止位。这里使用的是SERIAL_8N1
宏定义,代表串口数据格式。1
和0
分别代表RX和TX引脚。 - 目的:允许Arduino通过软件串口与其他设备(如传感器、模块等)通信。
2. TFT屏幕初始化
tft.init();
- 功能:初始化TFT屏幕。
- 目的:确保屏幕准备好显示内容。
tft.fillScreen(TFT_BLACK);
- 功能:将TFT屏幕填充为黑色。
- 目的:为屏幕提供统一的背景颜色。
tft.setCursor(00, 00, 1);
- 功能:设置TFT屏幕上的光标起始位置为(0, 0),并选择字体大小为1。
- 目的:准备在屏幕的指定位置开始显示文本。
tft.setTextColor(TFT_WHITE);
- 功能:设置TFT屏幕上显示文本的颜色为白色。
- 目的:确保文本在黑色背景的屏幕上清晰可见。
tft.setTextSize(1);
- 功能:设置TFT屏幕上文本的大小为1(通常是最小尺寸)。
- 目的:调整文本大小以适应屏幕。
3. WiFi连接
WiFi.mode(WIFI_STA);
- 功能:设置WiFi模式为Station模式,即客户端模式。
- 目的:使Arduino可以连接到WiFi网络。
WiFi.begin(ssid, password);
- 功能:开始连接到指定的WiFi网络,使用提供的SSID和密码。
- 目的:使Arduino能够通过WiFi网络与互联网通信。
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
WiFi.begin(ssid, password);
delay(1000);
}
- 功能:检查WiFi连接状态,如果未连接,则在串口打印点符号,并每隔1秒尝试重新连接。
- 目的:确保Arduino成功连接到WiFi网络。
4. 更新TFT屏幕显示
tft.println("WiFi connected..!");
- 功能:在TFT屏幕上打印“WiFi connected…!”。
- 目的:向用户显示WiFi连接状态。
tft.print("Got IP: ");
tft.println(WiFi.localIP());
- 功能:在TFT屏幕上打印分配给Arduino的本地IP地址。
- 目的:向用户显示连接到WiFi网络后分配的IP地址。
5. 填充数据
fillData();
- 功能:调用
fillData()
函数,可能用于初始化或填充显示数据。 - 目的:准备显示内容。
总结
这段代码的主要目的是初始化硬件(串口、TFT屏幕)、连接到WiFi网络,并在屏幕上显示连接状态和IP地址。通过这些步骤,Arduino设备被配置为可以通过WiFi网络与互联网通信,并准备好显示信息。
3.loop函数
void loop() {
int pos1, pos2, pos3, pos4, pos5, pos6, pos7, pos8;
while (MySerial.available() > 0) {
comdata += char(MySerial.read());
delay(2);
}
if (comdata.length() > 0) {
tft.print(comdata);
tft.print("No.");
tft.print(count);
count++;
ct++;
pos1 = comdata.indexOf('~');
pos2 = comdata.indexOf('!');
pos3 = comdata.indexOf('@');
pos4 = comdata.indexOf('#');
pos5 = comdata.indexOf('$');
pos6 = comdata.indexOf('%');
pos7 = comdata.indexOf('^');
pos8 = comdata.indexOf('N');
temperature = comdata.substring(0, pos1);
pressure = comdata.substring(pos1 + 1, pos2);
humidity = comdata.substring(pos2 + 1, pos3);
lux = comdata.substring(pos3 + 1, pos4);
uv = comdata.substring(pos4 + 1, pos5);
windspeed = comdata.substring(pos5 + 1, pos6);
winddirection = comdata.substring(pos6 + 1, pos7);
rainfall = comdata.substring(pos7 + 1, pos8);
//bool sgn = client.connect(server, 80);
//while (!sgn)
//{
// sgn = client.connect(server, 80);
// delay(100);
//}
client.connect(server, 80);
delay(200);
client.connect(server, 80);
delay(200);
if (client.connect(server, 80)) // "184.106.153.149" or api.thingspeak.com
{
String postStr;
postStr = apikey;
postStr += "&field1=";
postStr += String(temperature);
postStr += "&field2=";
postStr += String(pressure);
postStr += "&field3=";
postStr += String(humidity);
postStr += "&field4=";
postStr += String(lux);
postStr += "&field5=";
postStr += String(uv);
postStr += "&field6=";
postStr += String(windspeed);
postStr += "&field7=";
postStr += String(winddirection);
postStr += "&field8=";
postStr += String(rainfall);
postStr += "r\n";
client.print("POST /update HTTP/1.1\n");
client.print("Host: api.thingspeak.com\n");
client.print("Connection: close\n");
client.print("X-THINGSPEAKAPIKEY: " + apikey + "\n");
client.print("Content-Type: application/x-www-form-urlencoded\n");
client.print("Content-Length: ");
client.print(postStr.length());
client.print("\n\n");
client.print(postStr);
tft.print(" Sent");
ct1++;
delay(200);
}
client.stop();
tft.print(" er:");
tft.println(count - ct1 - 1);
tft.println();
comdata = "";
}
if (ct >= 4) {
tft.fillScreen(TFT_BLACK);
tft.setCursor(0, 0, 1);
ct = 0;
tft.println(count - 1);
}
}
double dewPointFast(double celsius, double humidity) {
double a = 17.271;
double b = 237.7;
double temp = (a * celsius) / (b + celsius) + log(humidity * 0.01);
double Td = (b * temp) / (a - temp);
return Td;
}
void fillData() {
int startX = 10;
int startY = 10;
int cellWidth = 60;
int cellHeight = 30;
// Example data
String data[3][4] = {
{"Temp", "25", "26", "27"},
{"Humidity", "50", "55", "52"},
{"Pressure", "1000", "1010", "1020"}
};
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
int textX = startX + i * cellWidth + 10;
int textY = startY + j * cellHeight + 10;
tft.setCursor(textX, textY);
tft.print(data[i][j]);
}
}
}
loop()
函数
这个函数是Arduino程序的主要循环,不断重复执行。
void loop() {
// ...
while (MySerial.available() > 0) {
comdata += char(MySerial.read());
delay(2);
}
// ...
}
- 功能:从
MySerial
(可能是一个传感器或其他设备)读取数据,直到没有更多数据可读,读取的数据存储在comdata
字符串中。
if (comdata.length() > 0) {
// ...
}
- 功能:检查
comdata
是否有数据,如果有,执行以下操作。
tft.print(comdata);
tft.print("No.");
tft.print(count);
count++;
ct++;
- 功能:在TFT屏幕上打印接收到的数据和序号,然后增加序号计数器。
pos1 = comdata.indexOf('~');
// ...
rainfall = comdata.substring(pos7 + 1, pos8);
- 功能:使用indexOf()查找特定分隔符的位置,然后使用substring()提取传感器数据。
client.connect(server, 80);
- 功能:尝试通过TCP连接到服务器(可能是一个Web服务,如ThingSpeak)。
if (client.connect(server, 80)) {
// ...
}
- 功能:如果连接成功,构建一个HTTP POST请求,将数据发送到服务器。
tft.print(" Sent");
ct1++;
- 功能:在TFT屏幕上显示"Sent",并增加成功发送计数器。
client.stop();
tft.print(" er:");
tft.println(count - ct1 - 1);
- 功能:断开与服务器的连接,并在TFT屏幕上显示错误计数。
comdata = "";
- 功能:清空
comdata
字符串,为下一次数据接收做准备。
if (ct >= 4) {
tft.fillScreen(TFT_BLACK);
tft.setCursor(0, 0, 1);
ct = 0;
tft.println(count - 1);
}
- 功能:每四次循环后,清空TFT屏幕,重置光标位置,并打印当前计数。
dewPointFast()
函数
这个函数用于快速计算露点温度。
double dewPointFast(double celsius, double humidity) {
// ...
}
- 功能:使用露点计算公式,根据摄氏温度和相对湿度计算露点温度。
fillData()
函数
这个函数用于在TFT屏幕上显示数据。
void fillData() {
// ...
}
- 功能:定义数据单元格的位置和大小,然后在TFT屏幕上显示示例数据。
总结
loop()
函数处理从串行设备接收数据,将数据显示在TFT屏幕上,尝试将数据发送到服务器,并跟踪成功和失败的发送次数。dewPointFast()
函数提供了一个快速计算露点温度的方法。fillData()
函数用于在TFT屏幕上显示静态数据。这段代码可能是一个环境监测项目的一部分,用于收集和显示温度、湿度、压力等数据,并将数据上传到远程服务器。
3.其他概念
1.看门狗
本质上是为了应对硬件卡死的可能发生,通过初始化,和定义看门狗函数的计数,和计时,在函数中加入看门狗从而可以监控整个系统不让他卡死或者出现其他异常,出现则直接重启。
2.I2C和SPI的传输原理
1.SPI
在Arduino平台上使用SPI总线协议确实相对简单,因为Arduino已经为SPI通信提供了抽象化的库,您不需要直接处理底层的通信细节。下面是SPI通信中每条线的具体作用:
-
SCK (Serial Clock):时钟线,由主设备(Arduino)控制,用于同步数据传输。数据在SCK的时钟信号边沿(上升沿或下降沿)上发送或接收。
-
MOSI (Master Out Slave In):主设备数据输出,从设备数据输入线。Arduino通过这条线向从设备发送数据。
-
MISO (Master In Slave Out):主设备数据输入,从设备数据输出线。Arduino通过这条线从从设备接收数据。
-
CS (Chip Select):片选线,用于激活目标从设备,使其知道数据是发给它的。通常,CS引脚在没有数据传输时保持高电平,当需要与特定从设备通信时,将其拉低。
在代码中,您需要做以下事情:
-
正确连接线:确保Arduino的SCK、MOSI、MISO引脚与从设备的对应引脚正确连接,CS引脚连接到从设备指定的CS引脚。
-
初始化SPI:在Arduino代码中通过
SPI.begin()
函数初始化SPI通信。 -
选中从设备:在数据传输前,通过将CS引脚置为低电平来选中目标从设备。
-
发送和接收数据:使用
SPI.transfer()
函数发送数据,并可接收从设备返回的数据。 -
取消选中从设备:数据传输完成后,将CS引脚置回高电平,取消选中从设备。
这里是一个简化的示例代码:
#include <SPI.h>
const int CS_PIN = 10; // 定义CS引脚
void setup() {
SPI.begin(); // 初始化SPI总线
pinMode(CS_PIN, OUTPUT); // 设置CS引脚为输出
digitalWrite(CS_PIN, HIGH); // 初始状态为不选中从设备
}
void loop() {
digitalWrite(CS_PIN, LOW); // 选中从设备
byte data = SPI.transfer(0xFF); // 发送0xFF并接收数据
digitalWrite(CS_PIN, HIGH); // 取消选中从设备
// 使用接收到的数据...
}
在这个示例中,SPI.transfer(0xFF)
会发送一个字节0xFF
到从设备,并同时返回从设备发送回来的数据。您不需要编写任何额外的代码来处理时钟或数据同步,因为SPI库已经为您处理了这些细节。
在实际使用中,您需要查阅从设备的文档来了解如何通过SPI与之通信,包括合适的时钟频率、数据模式、要发送的命令以及如何解读返回的数据。这些信息通常在从设备的硬件手册或数据表中提供。
2.I2C
I2C(Inter-Integrated Circuit)协议是一种非常流行的串行通信协议,用于连接微控制器和各种外围设备,如传感器和显示器。I2C协议只需要两根线:一根是数据线(SDA),另一根是时钟线(SCL)。这两根线可以连接多个设备,形成I2C总线。
I2C协议工作原理
- 起始条件:当主机设备需要与一个从设备通信时,它产生一个起始条件,即SDA线上的电平从高到低变化,同时SCL线保持低电平。
- 地址和读写位:起始条件后,主机发送从设备的地址,以及一个读写位(0表示写,1表示读)。
- 应答位:从设备接收到地址后,会返回一个应答信号。
- 数据传输:如果是写操作,主机发送数据字节;如果是读操作,从设备发送数据字节。每个字节后都有一个应答位。
- 停止条件:数据传输完成后,主机产生一个停止条件,即SDA线上的电平从低到高变化,同时SCL线保持高电平。
在Arduino中的使用
Arduino提供了简单的库来支持I2C通信,你只需要连接SDA和SCL线到Arduino的相应引脚(A4为SDA,A5为SCL在Arduino Uno上)。
示例:读取I2C温湿度传感器(DHT12)
以下是使用I2C协议读取温湿度传感器DHT12的示例代码:
#include <Wire.h>
#include <DHT.h>
#define DHTPIN 2 // 连接到Arduino的数字引脚2
#define DHTTYPE DHT12 // DHT12传感器
DHT dht(DHTPIN, DHTTYPE);
void setup() {
Serial.begin(9600);
Wire.begin(); // 加入I2C总线
dht.begin();
}
void loop() {
// 读取温度和湿度
float h = dht.readHumidity();
float t = dht.readTemperature();
// 检查读取是否成功
if (isnan(h) || isnan(t)) {
Serial.println("Failed to read from DHT sensor!");
return;
}
// 打印结果
Serial.print("Humidity: ");
Serial.print(h);
Serial.print(" %\t");
Serial.print("Temperature: ");
Serial.print(t);
Serial.println(" *C ");
delay(2000); // 等待两秒再次读取
}
代码解释
-
库的引入:
#include <Wire.h>
:引入Arduino的I2C库。#include <DHT.h>
:引入DHT传感器库。
-
定义引脚和传感器类型:
#define DHTPIN 2
:定义DHT12传感器连接到数字引脚2。#define DHTTYPE DHT12
:定义传感器类型为DHT12。DHT dht(DHTPIN, DHTTYPE)
:创建一个DHT传感器对象。
-
初始化:
Serial.begin(9600)
:初始化串口通信。Wire.begin()
:初始化I2C总线。dht.begin()
:初始化DHT传感器。
-
主循环:
float h = dht.readHumidity()
:读取湿度。float t = dht.readTemperature()
:读取温度。isnan(h) || isnan(t)
:检查读取的数据是否有效。Serial.print
:将读取的数据打印到串口。
-
延时:
delay(2000)
:等待两秒再次读取数据。
这个示例展示了如何在Arduino中使用I2C协议读取温湿度传感器DHT12的数据。通过I2C库,Arduino可以轻松地与各种I2C设备进行通信。
#include <DHT.h>
#include <Wire.h>
// 定义两个DHT12传感器的引脚号
#define DHTPIN1 2 // 第一个DHT12传感器连接到数字引脚2
#define DHTPIN2 4 // 第二个DHT12传感器连接到数字引脚4
#define DHTTYPE DHT12 // DHT12传感器类型
// 创建两个DHT传感器对象
DHT dht1(DHTPIN1, DHTTYPE);
DHT dht2(DHTPIN2, DHTTYPE);
void setup() {
Serial.begin(9600);
Wire.begin(); // 加入I2C总线
dht1.begin();
dht2.begin();
}
void loop() {
// 读取第一个DHT12传感器的温度和湿度
float h1 = dht1.readHumidity();
float t1 = dht1.readTemperature();
// 读取第二个DHT12传感器的温度和湿度
float h2 = dht2.readHumidity();
float t2 = dht2.readTemperature();
// 检查读取是否成功
if (isnan(h1) || isnan(t1) || isnan(h2) || isnan(t2)) {
Serial.println("Failed to read from DHT sensor(s)!");
return;
}
// 打印第一个传感器的结果
Serial.print("Sensor 1: Humidity: ");
Serial.print(h1);
Serial.print(" %\tTemperature: ");
Serial.print(t1);
Serial.println(" *C ");
// 打印第二个传感器的结果
Serial.print("Sensor 2: Humidity: ");
Serial.print(h2);
Serial.print(" %\tTemperature: ");
Serial.print(t2);
Serial.println(" *C ");
delay(2000); // 等待两秒再次读取
}
这个代码就展示了如果是两个相同的传感器如何处理,也就是说在实例化的时候,就要考虑这些问题。
3.ADC
就是模拟转数字,模拟电压,从传感器或其他模拟信号源获取连续变化的电压值,并将其转换为离散的数字值。将外部世界的模拟信号转换为计算机或数字电路可以处理的数字信号。ADC在各种电子系统中广泛应用,如传感器数据读取、音频信号处理等。
4.代码问题模拟
问题1:您在实现WiFi连接时遇到过哪些问题?
回答:在实现WiFi连接时,最常见的问题是连接不稳定或连接失败。为了解决这个问题,我使用了WiFi.begin
函数在循环中尝试连接,直到成功为止。同时,我在循环中添加了Serial.print(".")
来监控连接进度。
问题2:您如何处理从串口接收的数据?
回答:我使用了一个循环来读取MySerial
串口的数据,直到没有更多数据可读。为了避免数据丢失,我在读取数据后添加了一个小的延迟。
问题3:您如何解析接收到的数据?
回答:我使用indexOf
来找到数据中不同部分的分隔符,然后使用substring
来提取每个传感器的值。
问题4:您在上传数据到云端时遇到过哪些问题?
回答:在上传数据时,我遇到了连接服务器失败的问题。为了解决这个问题,我尝试了多次连接,并在两次连接尝试之间添加了延迟。
问题5:您如何确保数据正确上传到云端?
回答:我通过检查client.connect(server, 80)
的返回值来确保连接成功。如果连接成功,我构建了一个HTTP POST请求,将数据发送到云端。
问题6:您如何跟踪上传失败的数据?
回答:我使用了一个计数器ct1
来跟踪成功上传的数据。在循环结束时,我计算了未上传的数据数量,并在TFT屏幕上显示。
问题7:您在代码中使用了看门狗定时器,这是出于什么考虑?
回答:我使用看门狗定时器来防止程序在执行过程中卡死。如果程序在一定时间内没有响应,看门狗定时器会重置Arduino。
问题8:您如何确保屏幕内容的更新不会太频繁?
回答:我使用了一个计数器ct
来跟踪屏幕更新的次数。当达到一定次数后,我清空屏幕并重置计数器。
问题9:您在设计系统时如何考虑节能?
回答:为了节能,我使用了Sleep_avr
函数使Arduino进入睡眠模式。同时,我关闭了ADC来减少功耗。
问题10:您如何处理传感器数据的滤波?
回答:我使用了一个简单的移动平均滤波算法averageFilter
来平滑传感器数据。
问题11:您在代码中如何处理错误和异常?
回答:我通过检查传感器连接状态和读取数据的有效性来处理错误。如果发现错误,我会在串口打印错误信息,并在TFT屏幕上显示。
问题12:您如何测试整个系统?
回答:我通过将系统部署在实际环境中,并监控串口输出和TFT屏幕显示来测试系统。我还尝试了断开WiFi连接和传感器连接,以确保系统能够正确处理这些情况。
这些问题和答案涵盖了代码实现、问题解决、系统设计考虑等方面,展示了您在项目开发过程中的思考和实践。
好的,我将从面试官的角度,针对你的代码进行提问,涵盖实际操作中可能遇到的问题以及解决方法。以下是一些可能的提问:
问题13. 关于 Wi-Fi 连接
问题: 在 Wi-Fi 连接过程中,WiFi.begin()
和 WiFi.status()
的使用方法是否存在过问题?如果有,如何解决?
答案: 在连接 Wi-Fi 时,常见的问题是由于网络延迟或信号不强导致 WiFi.status()
一直无法返回 WL_CONNECTED
状态。为了解决这个问题,我在代码中使用了一个 while
循环,确保不断尝试重新连接 Wi-Fi 直到连接成功。同时,我也增加了适当的 delay(1000)
来减少连接请求频率,以防止网络过载。
问题14. 关于 comdata
的数据解析
问题: 在串口数据 comdata
解析时,如何保证数据是完整的并且各字段能够正确解析?
答案: 我使用了 indexOf()
函数来定位每个数据字段的分隔符。例如,comdata
的不同部分用 ~
, !
, @
等符号分隔,我通过 substring()
函数来获取每一段数据,并确保在处理时的字符串不会越界。此外,我通过检查 comdata.length()
来确保只有在接收到完整数据时才进行解析和上传操作。
问题15. 关于 Thingspeak
上传的连接稳定性
问题: 在向 Thingspeak 服务器上传数据时,你的连接是否出现过问题?如何解决?
答案: 是的,在向服务器发送数据时,连接不稳定是常见问题。为解决这个问题,我在代码中使用了多次 client.connect()
,即使第一次连接失败,也可以在之后的尝试中成功建立连接。此外,我通过增加延时 delay(200)
来避免频繁连接请求导致的服务器拒绝。
问题16. TFT 显示屏上的数据更新
问题: 当数据较频繁地更新到 TFT 屏幕时,如何避免屏幕的闪烁或残影?
答案: 为了避免屏幕闪烁,我使用了 tft.fillScreen(TFT_BLACK)
清空屏幕,并且通过 ct
计数器每 4 次更新后才清屏,以避免频繁刷新屏幕产生的闪烁问题。另外,我使用了 setCursor()
来确保文字从正确的起始位置显示,减少覆盖现象。
问题17. 关于平均滤波器的实现
问题: 在数据采集时,你使用了平均滤波器,请解释如何设计和实现它,并说明其作用?
答案: 我实现了一个简单的滑动窗口平均滤波器,用来平滑风速、温度、湿度等传感器数据,减小噪声的影响。具体实现上,我创建了一个大小为 5 的数组,每次读取新数据时,旧数据会移位并将新数据加入数组,随后计算数组中的平均值,作为最终输出数据。这有助于消除因传感器抖动带来的误差,特别是风速和方向的测量。
问题18. 关于节能与低功耗设计
问题: AVR 微控制器部分有用到低功耗设计,请解释看门狗定时器 (WDT) 和 AVR 睡眠模式的作用,以及如何唤醒设备?
答案: 为了节省电力,我使用了看门狗定时器和睡眠模式。当设备进入低功耗状态时,会调用 Sleep_avr()
将设备置于 SLEEP_MODE_PWR_DOWN
模式。在此模式下,设备几乎不消耗电能,只有看门狗定时器能够唤醒它。我使用 ISR(WDT_vect)
中断服务程序来处理看门狗定时器超时唤醒的操作,并在设备每次唤醒后执行数据采集任务。
问题19. 关于传感器通信
问题: 在读取风速、风向和雨量传感器的数据时,采用了什么协议和方法?如何处理通信错误?
答案: 我使用了基于串口通信的 MODBUS 协议,通过发送特定的命令(如 Request
、Request1
)来请求传感器的数据。每个命令对应不同的传感器,返回的数据会通过串口缓冲区读取并解析。如果通信中出现错误(如返回的数据不完整或格式错误),我会通过检查返回数据的长度和格式来判断,并在解析时进行容错处理,确保设备在异常情况下不会崩溃。
问题20. 关于 BME280 和 Max44009 传感器的初始化
问题: 你是如何确保 BME280 和 Max44009 传感器正确初始化并避免初始化失败的?
答案: 我在 setup()
中通过 bme.begin()
和 myLux.isConnected()
来检查传感器是否正确连接。如果初始化失败,我使用 Serial.println("ER01")
或 Serial.println("ER02")
输出错误信息并停止程序执行,防止设备在没有获取正确传感器数据的情况下继续运行。此外,我在 Wire.begin()
函数中初始化 I2C 总线,确保与传感器的通信能够正常进行。
这些问题和答案能够帮助你从多个方面理解代码在实际操作中的表现以及可能遇到的挑战。
5.总体毕设问题模拟
作为面试官,我会询问您在开发LoRa无线气象站项目过程中可能遇到的挑战、您采取的解决措施以及您从中学到了什么。以下是一些可能的面试问题及相应的回答:
问题1:您在项目中遇到的最大技术挑战是什么?
回答:在项目中,最大的技术挑战是如何确保LoRa通信模块在城市环境中的稳定传输。为了解决这个问题,我们进行了一系列的实验室和户外测试,包括不同天线在不同距离下的传输成功率测试,以及在开启和关闭确认应答(ACK)机制下的传输成功率测试。
问题2:您如何解决气象站的持续供电问题?
回答:为了确保气象站在户外环境中能够持续运行,我们设计了一个独立太阳能供电系统。我们选择了一种20W的多晶硅太阳能板,并配合MPPT(最大功率点跟踪)控制器,以及一个62Wh的锂离子电池来存储能量。
问题3:在测试过程中,您如何评估数据的准确性?
回答:我们使用专业的气象仪器与我们的传感器数据进行对比测试,并根据中国国家标准(GB/T 35221-2017)对数据准确性进行评估。测试结果表明,我们的气象站在温度、相对湿度、气压、风速和风向方面达到了标准。
问题4:在项目实施过程中,您是否遇到了任何意外的困难?
回答:在项目实施过程中,我们遇到了一些挑战,例如在调试ESP32网关节点时,TFT屏幕出现了显示错误。我们通过优化代码逻辑和调整数据发送顺序来解决这个问题。
问题5:您在设计PCB时遇到了哪些问题,又是如何解决的?
回答:在设计PCB时,我们遇到了设备间的干扰问题,导致程序运行不稳定。为了解决这个问题,我们在PCB 2.0版本中增加了地孔和去耦电容,并且重新布局了传感器和LoRa模块,以提高系统的抗干扰能力。
问题6:您在数据可视化方面做了哪些工作?
回答:我们使用MATLAB和thingspeak.com平台对收集到的数据进行可视化和分析。我们开发了多个数据可视化程序,包括温度、湿度、风速等气象参数的图表和曲线。
问题7:您在项目中使用了哪些质量控制措施?
回答:为了确保数据的准确性和系统的可靠性,我们进行了一系列的测试,包括传输成功率、电源管理、数据准确性测试,并根据测试结果不断调整和优化系统。
问题8:在未来的工作中,您打算如何改进这个系统?
回答:未来的改进方向包括增加更多种类的传感器以收集更全面的气象数据,探索使用人工智能算法来提高天气预报的准确性,以及优化电源管理系统以延长系统的电池寿命。
问题9:您在项目中学到了哪些最重要的课程或技能?
回答:我学到了LoRa通信技术、气象站传感器的集成、电源管理、数据可视化技能,以及如何进行系统测试和性能评估。
问题10:您如何协调团队成员以确保项目按时完成?
回答:我通过定期组织会议、分配任务、监督进度和解决团队成员遇到的问题来协调团队成员的工作。
这些问题和答案旨在展示您在项目开发过程中的挑战、解决策略和学习成果。
针对你的论文《LoRa Based Wireless Weather Station》,我将从面试官的角度提出一些可能会问到的问题,并给出相应的答案,这些问题会涵盖实际操作中可能遇到的挑战和解决方案。
问题11. LoRa 的长距离传输性能
问题: 在使用 LoRa 技术进行长距离传输时,你是否遇到过信号传输成功率低的问题?你是如何解决的?
答案: 是的,在远距离(尤其是超过 10 公里)传输时,信号传输成功率有所下降。为了解决这个问题,我们对比了不同类型的天线(如 433MHz 12DBi Rod 天线和 Sucker 天线)的传输效果。我们发现 Rod 天线在长距离传输中的成功率相对更高。在超过 9 公里的距离下,我们还使用了带有 ACK(Acknowledgment)的传输方法来提高短距离的成功率。
问题12. 能源管理和自动供电系统
问题: 在使用太阳能板进行供电时,你遇到过哪些问题?如何优化电力管理系统?
答案: 在能源管理方面,我们使用了多晶硅太阳能电池板和 MPPT(最大功率点追踪)控制器,以优化太阳能的利用效率。然而,太阳能板的实际输出功率不总是稳定的。因此,我们采用了 MPPT 控制器来调节输入电压和电流,从而确保电池能够高效充电。通过使用锂电池作为储能设备,我们可以确保气象站在持续阴天的情况下也能运行。
问题13. LoRa 模块的干扰问题
问题: 在使用 LoRa 模块时,是否遇到过信号干扰或者数据传输失败的问题?你是如何处理的?
答案: 确实,在 PCB 设计的初期,我们的 LoRa 模块和其他传感器之间存在干扰,导致信号传输不稳定。为了减少干扰,我们在 PCB 2.0 版本中增加了多个接地过孔和电容,优化了电源引脚的滤波,并对 LoRa 模块和其他敏感器件进行了隔离。这些措施有效提升了信号传输的稳定性。
问题14. MATLAB 中数据可视化
问题: 在使用 MATLAB 进行数据可视化时,是否遇到过性能或数据误差问题?你是如何优化的?
答案: 在使用 MATLAB 进行气象数据的可视化时,我们发现传感器偶尔会返回 NaN(非数字)数据,导致程序出现错误。例如,温度数据可能因为传感器噪声出现±0.1℃的浮动。为此,我们引入了平均滤波算法,对数据进行平滑处理,减少噪声的影响。此外,通过删除 NaN 数据,我们确保了数据在可视化过程中的准确性。
问题15. ACK 方法在 LoRa 中的使用
问题: 在 LoRa 的数据传输过程中,你是如何提升传输成功率的?特别是在使用 ACK 方法时有什么效果?
答案: 在短距离传输中,使用 ACK 方法可以显著提升传输成功率。在我们的测试中,未开启 ACK 时的传输成功率在较长距离下会显著下降,而开启 ACK 后,短距离(1km 到 6.6km)传输的成功率接近 100%。不过,随着距离增加,ACK 方法的效果会有所减弱。这表明 ACK 主要在短距离传输中有用,长距离传输需要考虑其他优化策略。
问题16. 传感器数据的校准和准确性
问题: 在数据采集过程中,如何确保传感器的数据准确性?是否进行过校准?
答案: 是的,为确保传感器的数据准确性,我们在测试前对所有传感器进行了校准,并使用专业设备对比实际测量值和传感器采集的数据。在一段时间的运行后,我们比较了传感器数据与标准仪器的数据,发现我们的系统在温度、湿度等方面的数据误差符合中国国家标准 GB/T 35221-2017 的要求。
问题17. 硬件设计中的改进
问题: 在设计 PCB 和硬件外壳时,遇到了哪些挑战?如何进行改进?
答案: 在 PCB 1.0 版本中,我们遇到了不同传感器之间的电磁干扰问题,导致程序偶尔崩溃。为解决此问题,我们在 PCB 2.0 中重新设计了线路,缩短了电路路径,并添加了零欧姆电阻以改善接地。在外壳设计方面,我们使用 SolidWorks 设计了具备防水和抗风结构的外壳,以确保设备在户外恶劣天气条件下能正常工作。
通过这些问题和解答,你可以更好地理解论文中的设计逻辑以及实际操作中可能遇到的问题和解决方案。
6.其他问题
如果面试官问你具体在这篇论文中做了哪些工作,你可以从以下几个方面进行回答,突出你在设计、开发、测试等各环节中的贡献。对于团队分工和平衡的问题,由于这是你单独完成的项目,你可以强调你在项目管理、时间安排、以及如何处理不同任务方面的能力。
1. 你的具体工作
你可以分几个主要的部分来回答:
-
系统设计与架构:
你可以提到你是如何设计整个系统架构的,包括传感器端和接收端的功能划分。你设计了自供电系统、嵌入式系统和 LoRa 通信模块,并将它们集成在一起。特别是你在如何让系统高效收集、传输气象数据上花费了很多精力,确保系统可以在户外环境中长期运行。 -
硬件部分:
强调你在传感器的选择与集成上的工作。例如,你选择了 Bosch 的 BME280 作为温湿度和气压传感器,Max44009 作为光照传感器,以及 PR-3000 系列风速风向传感器。你还设计并优化了 PCB,解决了初期的干扰问题,使各个传感器与 LoRa 模块能够更稳定地通信。 -
软件开发与数据处理:
你负责开发 Arduino 代码来收集和处理传感器数据,并通过 LoRa 模块传输至接收端。你还使用了 ESP32 作为网关节点,通过 WiFi 将数据上传至云平台。你实现了数据的实时可视化,使用了 MATLAB 对数据进行分析和处理,包括数据的平滑处理、误差校正等。 -
测试与优化:
你进行了多次室外测试,评估系统的传输成功率、功耗管理和数据准确性。你还根据测试结果优化了系统的传输参数,比如使用 ACK 方法来提高短距离传输的成功率,以及通过调整天线设计来优化长距离传输的性能。
2. 如何平衡团队与分工问题(针对个人完成项目的情况)
-
独立完成项目的组织与管理:
尽管这是一个个人完成的项目,但你可以强调自己如何在没有团队分工的情况下,合理安排和管理整个项目的进度。你可以说你分阶段完成了硬件设计、软件开发、以及后期的测试和调优,并根据项目的复杂性制定了详细的时间表。 -
任务优先级管理:
强调你在处理多个任务时,如何设定优先级。例如,你在硬件设计完成后,先确保各个传感器能够准确工作,再进入软件开发阶段,最后通过多轮测试来优化系统性能。你可以展示你在面对复杂任务时的解决问题的能力和决策能力。 -
跨领域工作能力:
由于你一个人负责硬件、软件和系统测试等所有环节,你可以突出自己在多领域(如电路设计、嵌入式编程、数据分析等)中解决问题的能力,并展示你对整个系统的全面掌控力。 -
协作与反馈:
虽然项目是你独立完成的,但你也可以提到你在整个过程中如何与导师保持沟通,获得反馈和建议,并根据他们的指导不断改进项目。你可以提到如何通过定期汇报进度、获得改进意见来提高项目的质量。
通过这样回答,你不仅展示了自己在整个项目中的独立能力,还强调了你在项目管理、跨学科协作和自我驱动能力方面的优势。