任务介绍
本项目实现了Funpack第八期活动的任务二——环境监测站。
具体的待监测的参数包括:
-
周边环境温度(精度:±0.1°C, ±0.1°F)
-
周边环境湿度(精度:±1%)
-
大气压强(精度:±0.1kPa, ±0.1psi)
-
日照强度(用于判断白天/夜晚)
-
周边平均噪声(精度:±1dB)
项目采用一块0.96’的OLED显示屏进行数据显示。
硬件搭建
首先介绍本期活动的主角 Arduino Nano 33 BLE Sense开发板,这块开发板是Arduino系列中非常亮眼的一款开发板。主要原因是它在仅有Arduino Nano相当的体积下,同时配备了ARM Cortex-M4内核的微处理器和温湿度、光强、麦克风等众多实用的传感器,是一块非常优秀的开发板。
本期活动所需要的传感器外设都已在板卡上配备完全,除去主控板外,所需要的硬件就是一块OLED显示屏,我使用了另一块物联网底板上的屏幕作为数据显示器。
最后用杜邦线连接 Arduino Nano 33 BLE Sense 开发板与OLED模块的IIC通信接口和3.3V电源接口,项目的硬件部分就搭建完成了。
程序分析
接下来我们详细分析本次的代码。由于Arduino的生态非常完整,所以开发板上的各种外设基本都能够找到对应的库,以及简单的例程。
我使用的开发环境是VSCode+PlatformIO,相比旧版的Arduino IDE,VSCode的代码编辑能力非常强,并且最重要的是,扩展PlatformIO可以实现增量编译。再也不会面临旧版Arduino IDE的一次编译就要几分钟的情况,可以极大地提高开发效率。
首先详解主程序:
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Arduino_HTS221.h>
#include <Arduino_LPS22HB.h>
#include <Arduino_APDS9960.h>
#include <arm_math.h>
#include "Nano33BLEMicrophoneRMS.h"
#include <Scheduler.h>
#define MICROPHONE_BUFFER_SIZE_IN_WORDS (256U)
/*****************************************************************************/
/*GLOBAL Data */
/*****************************************************************************/
Nano33BLEMicrophoneRMSData microphoneData;
int16_t refBuf[32] = {0};
float temperature, humidity;
float barometricPressure;
int colourR, colourG, colourB, colourC;
#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);
void loop2(void);
void loop3(void);
void setup()
{
pinMode(LED_BUILTIN, OUTPUT);
display.clearDisplay();
display.setTextSize(1); //设置字体大小
display.setTextColor(WHITE); //设置字体颜色白色
// by default, we'll generate the high voltage from the 3.3v line internally! (neat!)
display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // initialize with the I2C addr 0x3D (for the 128x64)
if (!HTS.begin())
{
display.println("Failed to initialize humidity temperature sensor!");
goto error_code;
}
if (!BARO.begin())
{
display.println("Failed to initialize preasure sensor!");
goto error_code;
}
APDS.setGestureSensitivity(50);
if (!APDS.begin())
{
display.println("Failed to initialize APDS sensor!");
goto error_code;
}
APDS.setLEDBoost(0);
MicrophoneRMS.begin();
Serial.begin(115200);
Scheduler.startLoop(loop2);
return;
error_code:
display.display();
while (1)
;
}
void loop()
{
static int num = 0;
display.clearDisplay();
display.setCursor(0, 0); //设置字体的起始位置
temperature = HTS.readTemperature();
humidity = HTS.readHumidity();
barometricPressure = BARO.readPressure();
display.print("temp: ");
display.print(temperature);
display.println("'C");
display.print("humi: ");
display.print(humidity);
display.println("%");
display.print("pres: ");
display.print(barometricPressure);
display.println("kPa");
if (APDS.colorAvailable())
{
APDS.readColor(colourR, colourG, colourB, colourC);
}
display.print("R: ");
display.print(colourR);
display.print(" G: ");
display.println(colourG);
display.print("B: ");
display.print(colourB);
display.print(" C: ");
display.println(colourC);
if (colourC < 600)
{
display.println("state: night");
}
else
{
display.println("state: day");
}
uint16_t db = 0;
if (MicrophoneRMS.pop(microphoneData) && microphoneData.RMSValue != 0)
{
db = (uint16_t)20 * log10f(microphoneData.RMSValue + 0.0001);
}
display.print("sound: ");
display.print(db);
display.println("dB");
display.print("loops: ");
display.println(num++);
display.display(); //把缓存的都显示
delay(50);
}
void loop2()
{
static int blink = 0;
digitalWrite(LED_BUILTIN, blink = 1 - blink);
delay(1000);
}
首先通过包含头文件引入相关的库,随后在初始化函数中进行OLED显示器和各类传感器的初始化。随后进入主循环。利用各传感器库函数提供的API读取各传感器的数值,并在OLED屏幕上显示出来。
#include "Nano33BLEMicrophoneRMS.h"
#include <PDM.h>
#include <arm_math.h>
/*****************************************************************************/
/*MACROS */
/*****************************************************************************/
/* This value was also used in the PDM example, seems like a good enough reason to
* continue using it. With this value and 16kHz sampling frequency, the RMS sampling
* period will be 16mS */
#define MICROPHONE_BUFFER_SIZE_IN_WORDS (256U)
/*****************************************************************************/
/*GLOBAL Data */
/*****************************************************************************/
const uint32_t MICROPHONE_BUFFER_SIZE_IN_BYTES_C = (MICROPHONE_BUFFER_SIZE_IN_WORDS * sizeof(int16_t));
/* MP34DT05 Microphone data buffer with a bit depth of 16. Also a variable for the RMS value */
static int16_t microphoneBuffer[MICROPHONE_BUFFER_SIZE_IN_WORDS];
static rtos::Semaphore bufferReadySemaphore;
/*****************************************************************************/
/*CLASS MEMBER FUNCTION IMPLEMENTATION */
/*****************************************************************************/
/**
* @brief
* This member function implementation should do everything requred to
* initialise the sensor this class is designed for. Immediately after
* this function is executed, the RTOS will begin periodically reading
* values from the sensor.
*
* @param none
* @return none
*/
void Nano33BLEMicrophoneRMS::init()
{
/* PDM setup for MP34DT05 microphone */
/* configure the data receive callback to transfer data to local buffer */
PDM.onReceive(Nano33BLEMicrophoneRMS::PDM_callback);
/* Initialise single PDM channel with a 16KHz sample rate (only 16kHz or 44.1kHz available */
if (!PDM.begin(1, 16000))
{
/* Something went wrong... Put this thread to sleep indefinetely. */
osSignalWait(0x0001, osWaitForever);
}
else
{
/* Gain values can be from 0 to 80 (around 38db). Check out nrf_pdm.h
* from the nRF528x-mbedos core to confirm this.
*/
/*
* This has to be done after PDM.begin() is called as begin() always
* sets the gain as the default PDM.h value (20).
*/
PDM.setGain(80);
}
}
/**
* @brief
* This member function implementation should do everything requred to
* read one reading from the sensor this class is designed for. This
* function is put inside an endless while loop so will be called
* endlessly, therefore a sleep should be called at the end of the
* function. The sleep period should be defined by the READ_PERIOD_MS
* defined at the start of this file.
*
* @param none
* @return none
*/
void Nano33BLEMicrophoneRMS::read(void)
{
/*
* Place the implementation required to read the sensor
* once here.
*/
bufferReadySemaphore.acquire();
Nano33BLEMicrophoneRMSData data;
arm_rms_q15((q15_t*)microphoneBuffer, MICROPHONE_BUFFER_SIZE_IN_WORDS, (q15_t*)&data.RMSValue);
data.timeStampMs = millis();
push(data);
}
void Nano33BLEMicrophoneRMS::PDM_callback(void)
{
// query the number of bytes available
int bytesAvailable = PDM.available();
if(bytesAvailable == MICROPHONE_BUFFER_SIZE_IN_BYTES_C)
{
PDM.read(microphoneBuffer, MICROPHONE_BUFFER_SIZE_IN_BYTES_C);
bufferReadySemaphore.release();
}
}
Nano33BLEMicrophoneRMS MicrophoneRMS;
这里需要特殊说明的是周边平均噪声的测量,这里需要一定的公式计算与转换,才能获得相应的数值。特别是均方根的计算,程序使用一段长度为256的数组缓冲区。来存储麦克风测量的原始数值。并通过ARM Cortex-M4提供的DSP库函数计算均方根数值,将数值代入噪声的分贝计算公式,就可以得到平均噪声。
Tips:程序中使用了CMSIS官方的DSP库,故可以利用ARM Cortex-M4内置的DSP与FPU单元进行加速计算,但在本文档编写时,最新版的framework-arduino-mbed库缺失相应的库文件,会导致编译失败。可以从Github官方CMSIS库中下载libarm_cortexM4l_math.a库文件,放到本地的framework-arduino-mbed\variants\ARDUINO_NANO33BLE\libs目录下,并在编译参数中加入build_flags = -l arm_cortexM4l_math,就可以正常使用硬件加速了。
功能展示
心得体会
很荣幸参加本期的Funpack活动,这也是我第一参加Funpack系列的活动。Arduino Nano 33 BLE Sense 是我接触到的第一块高性能Arduino开发板,在学习和编程的过程中,我感受到国内外创客群体对Arduino系列开发板的热爱以及对Arduino生态的贡献。虽然使用Arduino可以享受到快速调库、打造原型的酣畅淋漓的感觉,但真正热爱编程的程序员不应止步于此,Arduino库的源代码中蕴含着非常优秀的面向对象的编程思想和软件架构,这也是我们应该深入学习的。同时,应如苏老师所说,放宽视野,充分学习外文的优秀技术资源,取长补短,快速提升技术水平。
最后,感谢硬禾学堂和得捷电子的大力支持,以及交流群中小伙伴们的奇思妙想,祝愿Funpack系列活动越办越好!