Funpack第八期-基于Arduino Nano 33 BLE Sense和树莓派Zero W的环境监测站
将Arduino Nano 33 BLE Sense的传感器数据通过BLE发送给树莓派Zero W进行处理与显示
标签
嵌入式系统
网络与通信
氢化脱氯次氯酸
更新2021-05-20
1198

内容介绍

1 平台介绍

1.1 Arduino Nano 33 BLE Sense开发板

Arduino公司最新推出的NANO 33 BLE是一款基于nRF52840 SoC ARM-32位处理器的微型开发板,其主控芯片集成了蓝牙低功耗(BLE)。NANO 33 BLE不仅保留了与经典款NANO同样的尺寸与管脚,且在此基础上配有多种高性能传感器(角速度,加速度,压力,温湿度,距离,光感,姿态)等,在实现完全兼容的条件下增加了无限多种的组合玩法,可以迅速实现并验证转瞬即逝的灵感火花,是一款所有创客都梦寐以求的伴侣。

Arduino系列的开发板可以分为入门级和进阶级,而本次活动中介绍的Nano 33 BLE Sense毫无疑问是进阶系列中最受创客喜爱的开发板。它不仅继承了Arduino家族中近乎完美的生态基因,而且在保留硬件小巧轻便的基础上,提升了板卡的性能,可延展性与互动性,因此绝对是一款可以陪伴所有电子创客走得最长,最远,最久的伙伴。以下我们列举了Nano 33的几个亮点:

  • 首屈一指的Arduino开源硬件生态,可以最快速完成原型搭建,验证和测试
  • 具有多种物理传感器,能感应加速度,角速度,磁场,温湿度,距离,压强
  • 带有语音,颜色,手势等识别功能,可用于机器学习项目的开发
  • 可实现蓝牙低功耗无线通讯
  • DIP加邮票孔封装,同时满足面包板和扩展板搭配的使用需求

FvgEnQKVE47hFwo-YIko69UHJxI2

Fh4_2hyaAjtOg6xf8JbUQvHR1aKW

1.2 开发环境:Arduino IDE

Arduino Software IDE是一套以Java编写的跨平台应用软件。Arduino Software IDE源自于Processing编程语言以及Wiring计划的集成开发环境。它是被设计于介绍程序编写给艺术家和不熟悉程序设计的人们,且包含了一个拥有语法高亮、括号匹配、自动缩进和一键编译并将可执行文件烧写入Arduino硬件中的编辑器。

Arduino Software IDE使用与C语言和C++相仿的编程语言,并且提供了包含常见的输入/输出函数的Wiring软件库。在使用GNU toolchain编译和链接后,Arduino Software IDE提供了一个程序“avrdude”用来转换可执行档成为能够烧写入Arduino硬件的固件。

1.3 上位机

上位机使用Raspberry Pi Zero W,操作系统为基于Linux的Raspberry Pi OS,使用Python进行编程。为了与开发板通过蓝牙进行连接,调用bluepy实现linux上BLE的接口。

 

2 任务及实现方案

2.1 本期任务

利用NANO-33 BLE的传感器,搭建一个小型环境监测站用于监测户外环境。待监测的参数包括:

  • 周边环境温度(精度:±0.1°C, ±0.1°F)
  • 周边环境湿度(精度:±1%)
  • 大气压强(精度:±0.1kPa, ±0.1psi)
  • 日照强度(用于判断白天/夜晚)
  • 周边平均噪声(精度:±1dB)

任务的数据信息反馈方式可采用以下任意一种方式:

  1. 通过对开发板外接显示屏显示
  2. 通过蓝牙在电脑端口显示
  3. 通过手机APP

 

2.2 方案简介

Nano 33 BLE读取板上的各个传感器数值,通过蓝牙发送给上位机Raspberry Pi Zero W,上位机接收传感器信息后将其打印在终端中。

FnCK1Y9jBsMSoK-RUOsxG6Atpl5P

2.3 程序使用说明

Nano 33 BLE开发板烧录并运行funpack8.ino程序。

在上位机程序(funpack8.py)设置Nano 33 BLE开发板的MAC地址:(可以用手机APP查看)

mac_addr = "e8:70:5d:09:76:a8"  # MAC address of Arduino Nano 33 BLE Sense

在终端中运行程序:

python3 funpack8.py

若树莓派与Nano 33 BLE开发板蓝牙连接成功,Nano 33 BLE开发板上的LED会被点亮,同时终端中打印连接成功信息并开始打印传感器读数。程序运行中键入Ctrl+C断开蓝牙连接并结束程序,Nano 33 BLE开发板上的LED熄灭。

此外,若Arduino程序中的DEBUG宏定义被取消注释,Arduino会通过串口发送连接信息和传感器的读数,便于调试。但该情况下必须在电脑中打开串口后Arduino才会进行传感器与蓝牙的初始化,否则Arduino不会进行蓝牙广播与传感器读取。

 

3 Arduino程序实现过程

3.1 模块初始化

程序首先设置板载LED管脚为输出模式,对使用的传感器(温湿度传感器、压力传感器、颜色传感器、麦克风)和蓝牙进行初始化:

  #ifdef DEBUG
  // wait for serial connection before BLE advertise
  Serial.begin(9600);
  while (!Serial);
  #endif

  pinMode(ledPin, OUTPUT); // use the LED pin as an output
  delay(10);

  // initial all sensors
  if (!HTS.begin()) {
    #ifdef DEBUG
    Serial.println("Failed to initialize humidity temperature sensor!");
    #endif
    while (1);
  }

  if (!BARO.begin()) {
    #ifdef DEBUG
    Serial.println("Failed to initialize pressure sensor!");
    #endif
    while (1);
  }

  if (!APDS.begin()) {
    #ifdef DEBUG
    Serial.println("Error initializing APDS9960 sensor.");
    #endif
    while (true); // Stop forever
  }

  // Configure the data receive callback
  PDM.onReceive(onPDMdata);
  if (!PDM.begin(1, 16000)) {
    #ifdef DEBUG
    Serial.println("Failed to start PDM!");
    #endif
    while (1);
  }

  // BLE initialization
  if (!BLE.begin()) {
    #ifdef DEBUG
    Serial.println("starting BLE failed!");
    #endif
    while (1);
  }

3.2 蓝牙设置

设置蓝牙设备名称为Funpack 8:

 // set the local name peripheral advertises
  BLE.setLocalName("Funpack 8");

设置GATT环境感知服务(UUID: 181A)并将该服务进行广播:

BLEService senseService("181A"); // create service
  // set the UUID for the service this peripheral advertises
  BLE.setAdvertisedService(senseService);
  // add service
  BLE.addService(senseService);

设置各个特征,每个特征都有读取和通知两种操作:

// create switch characteristic and allow remote device to read and write
BLEShortCharacteristic tempCharacteristic("2A6E", BLERead | BLENotify);
BLEUnsignedShortCharacteristic humidityCharacteristic("2A6F", BLERead | BLENotify);
BLEUnsignedLongCharacteristic pressureCharacteristic("2A6D", BLERead | BLENotify);
BLEByteCharacteristic dayNightCharacteristic("183B", BLERead | BLENotify);
BLEUnsignedShortCharacteristic soundPressureCharacteristic("555a0002-0019-467a-9538-01f0652c74e8", BLERead | BLENotify);

// create descriptors in light and sound characteristics
BLEDescriptor dayNightDescriptor("2901", "day(1) night(0)");
BLEDescriptor soundPressureDescriptor("2901", "sound pressure");
  // add characteristics to the service
  senseService.addCharacteristic(tempCharacteristic);
  senseService.addCharacteristic(humidityCharacteristic);
  senseService.addCharacteristic(pressureCharacteristic);
  senseService.addCharacteristic(dayNightCharacteristic);
  senseService.addCharacteristic(soundPressureCharacteristic);

  // add descriptor to the corresponding characteristic
  dayNightCharacteristic.addDescriptor(dayNightDescriptor);
  soundPressureCharacteristic.addDescriptor(soundPressureDescriptor);

设置蓝牙连接和断开连接的回调函数。蓝牙连接成功时点亮LED,通过串口输出蓝牙客户端(电脑、手机等)蓝牙MAC地址(调试模式);蓝牙连接断开时熄灭LED,并通过串口输出连接断开信息(调试模式)。

  // assign event handlers for connected, disconnected to peripheral
  BLE.setEventHandler(BLEConnected, blePeripheralConnectHandler);
  BLE.setEventHandler(BLEDisconnected, blePeripheralDisconnectHandler);
void blePeripheralConnectHandler(BLEDevice central) {
  // central connected event handler
  #ifdef DEBUG
  Serial.print("Connected event, central: ");
  Serial.println(central.address());
  #endif
  digitalWrite(ledPin, HIGH);  // led on when connected
}

void blePeripheralDisconnectHandler(BLEDevice central) {
  // central disconnected event handler
  #ifdef DEBUG
  Serial.print("Disconnected event, central: ");
  Serial.println(central.address());
  #endif
  digitalWrite(ledPin, LOW);  // led off when disconnected
}

最后打开蓝牙广播,等待连接:

  // start advertising
  BLE.advertise();
  #ifdef DEBUG
  Serial.println(("Bluetooth device active, waiting for connections..."));
  #endif

3.3 主循环

在loop函数中每隔1s更新并发送传感器读数,并调用poll来处理BLE事件,如蓝牙连接等。

void loop() {
  // poll for BLE events
  BLE.poll(1000);
  while (BLE.connected()) {
    // update sensor values every 1s
    lowPowerBleWait(1000);
    updateSensorValues();
  }
}

在updateSensorValues函数中对传感器进行读取,通过蓝牙进行发送:

void updateSensorValues(void)
{
  // read all the sensor values
  float temperature = HTS.readTemperature();
  float humidity    = HTS.readHumidity();
  float pressure    = BARO.readPressure();
  uint16_t sound = 20*log10(getSoundAverage()*10);
  static int r = 0, g = 0, b = 0, a = 0;
  // check if a color reading is available
  if (APDS.colorAvailable()) {
    APDS.readColor(r, g, b, a);
  }

  #ifdef DEBUG
  // print each of the sensor values
  Serial.print("Temperature = ");
  Serial.print(temperature);
  Serial.println(" °C");

  Serial.print("Humidity    = ");
  Serial.print(humidity);
  Serial.println(" %");
  
  Serial.print("Pressure    = ");
  Serial.print(pressure);
  Serial.println(" kPa");

  Serial.print("Light      =  ");
  Serial.println(a);

  Serial.print("Sound      =  ");
  Serial.println(sound);

  // print an empty line
  Serial.println();
  #endif

  // update characteristics
  tempCharacteristic.writeValue((short)(temperature * 100));
  humidityCharacteristic.writeValue((unsigned short)(humidity * 100));
  pressureCharacteristic.writeValue((unsigned long)(pressure * 10000));
  dayNightCharacteristic.writeValue((byte)(a > 10 ? 0x01 : 0x00));
  soundPressureCharacteristic.writeValue(sound);
}

其中有些信息为满足BLE特征的数据格式,需要对传感器读取的原始信息进行转换。例如温度特征(UUID:2A6E)数据格式为short型,单位为0.01℃。所以需要将传感器原始数据乘100再转换为short型才能发送。

如果使用自定义的128位UUID,则不必遵循特定的格式。但使用上述16位的标准UUID可以在手机APP(如nrf connect)上被识别为温度特征并直接显示摄氏温度,而不是显示未知特征并直接显示原始数据。使用了16位UUID的温度、湿度和压力特征可以直接在手机上显示:

FuRL2V96KF70cLAPtb-em_gVHCt4

任务要求的白天/黑夜检测使用二元传感器(UUID:183B)来进行表示,其中1代表白天,0代表黑夜。声压特征没有对应的16位UUID,只能用128位UUID来表示。上述两个特征在nrf connect中不能被识别,所以在它们下面添加用户描述(user description)进行说明。

FuxBFk72qvvHtI2iLVEnnhm27LLw

4 上位机程序实现过程

4.1 蓝牙连接

树莓派通过Nano 33 BLE开发板的蓝牙MAC地址与其进行连接:

mac_addr = "e8:70:5d:09:76:a8"  # MAC address of Arduino Nano 33 BLE Sense
# Connect to Arduino by MAC address.
p = Peripheral(mac_addr,"public")
print('Connected to Arduino nano 33 BLE Sense! Reading sensor values...')

4.2 特征读取

通过特征的UUID(与Arduino中设置的相同)读取特征信息:

# Get characteristics by UUID.
ch_temperature = p.getCharacteristics(uuid=temperature_uuid)[0]
ch_humidity = p.getCharacteristics(uuid=humidity_uuid)[0]
ch_pressure = p.getCharacteristics(uuid=pressure_uuid)[0]
ch_light = p.getCharacteristics(uuid=light_uuid)[0]
ch_sound = p.getCharacteristics(uuid=sound_uuid)[0]

4.3 通知订阅

BLE的通知(Notify)可以在特征值发生变化时将其发送给客户端,在本项目中Arduino每1s会更新和通知传感器信息,上位机需要订阅通知来获得最新的传感器信息。首先需要开启通知,开启通知需要在对应特征的通知句柄(特征句柄+1)中写入0x01:

# Enable notifications by writing 0x01 to notification handles.
p.writeCharacteristic(ch_temperature.getHandle()+1, b"\x01\x00")
p.writeCharacteristic(ch_humidity.getHandle()+1, b"\x01\x00") 
p.writeCharacteristic(ch_pressure.getHandle()+1, b"\x01\x00")
p.writeCharacteristic(ch_light.getHandle()+1, b"\x01\x00") 
p.writeCharacteristic(ch_sound.getHandle()+1, b"\x01\x00") 

然后需要创建一个委托对象(delegate)用来接收通知。创建时需要向委托对象传递各特征句柄,用来判断当通知到来时是哪个特征的通知:

# Set delegate.
p.setDelegate(MyDelegate(ch_temperature.getHandle(), ch_humidity.getHandle(), ch_pressure.getHandle(), ch_light.getHandle(), ch_sound.getHandle()))
class MyDelegate(DefaultDelegate):
    def __init__(self, temperature_handle, humidity_handle, pressure_handle, light_handle, sound_handle):
        DefaultDelegate.__init__(self)
        self.temperature_handle = temperature_handle
        self.humidity_handle = humidity_handle
        self.pressure_handle = pressure_handle
        self.light_handle = light_handle
        self.sound_handle = sound_handle

    # Called when a notification is received from Arduino.
    def handleNotification(self, cHandle, data):
        if cHandle == self.temperature_handle:
            temperature = struct.unpack('<h', data)[0] / 100
            print('temperature:', temperature, "C")
        if cHandle == self.humidity_handle:
            humidity = struct.unpack('<H', data)[0] / 100
            print('humidity:', humidity, "%")
        if cHandle == self.pressure_handle:
            pressure = struct.unpack('<L', data)[0] / 10
            print('pressure_handle:', pressure, "Pa")
        if cHandle == self.light_handle:
            light = struct.unpack('<B', data)[0]
            if light:
                print('Day')
            else:
                print('Night')
        if cHandle == self.sound_handle:
            sound = struct.unpack('<H', data)[0]
            print('sound intensity:', sound, "dB\n")

随后程序进入循环等待通知。当通知到来时handleNotification被自动调用,打印传感器读数:

while True: 
    try:
        if p.waitForNotifications(2.0):  # Waiting for notifications to come.
            continue 
        print("Waiting...")  # No notification in 2 seconds.

    except KeyboardInterrupt:  # Ctrl+C to disconnect.
        print('Disconnecting...')
        p.disconnect()
        print('Disconnected.')
        break

5 心得体会

本次活动基于Arduino平台,Arduino便捷灵活、方便上手、开源度高,广受全世界的创客所欢迎。通过本次任务,我也体会到了Arduino的优越性,利用其强大的库函数和例程,可以节省编写各种驱动的时间,从而快速高效地完成任务。

此外,上位机树莓派也是一个自由度极高的开源平台,可以在树莓派上安装智能家居平台home assistant,并将Arduino Nano 33 BLE Sense接入home assistant,通过互联网随时随地监控传感器信息,还可以实现与其他智能设备的联动等。不过由于时间和精力原因我没有继续进行下去,感兴趣的同学可以尝试一下~

// create switch characteristic and allow remote device to read
BLEShortCharacteristic tempCharacteristic("2A6E", BLERead | BLENotify);
BLEUnsignedShortCharacteristic humidityCharacteristic("2A6F", BLERead | BLENotify);
BLEUnsignedLongCharacteristic pressureCharacteristic("2A6D", BLERead | BLENotify);
BLEByteCharacteristic dayNightCharacteristic("183B", BLERead | BLENotify);
BLEUnsignedShortCharacteristic soundPressureCharacteristic("555a0002-0019-467a-9538-01f0652c74e8", BLERead | BLENotify);

// create descriptors in light and sound characteristics
BLEDescriptor dayNightDescriptor("2901", "day(1) night(0)");
BLEDescriptor soundPressureDescriptor("2901", "sound pressure");

附件下载

funpack8.py
树莓派python程序
Arduino工程.zip
Arduino工程

团队介绍

团队成员
氢化脱氯次氯酸

评论

0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号