【ArduinoNiclaVision】开箱贴 https://forum.eepw.com.cn/thread/398517/1
【ArduinoNiclaVision】过程贴 https://forum.eepw.com.cn/thread/398519/1
一、项目介绍:
项目的初衷是做一款便携式物联网设备,能够检测生理指标如心率,借助板载的Wi-Fi模组,把数据上传到云平台如ThingSpeak。板子具有IIC接口,扩展了OLED屏幕。板载了IMU传感器,实现了摔倒检测功能。项目的实现情况达到了预期效果。具体项目实现思路
Arduino NIcla Vision
具有IIC接口,以此驱动OLED与MAX30102,完成获取心率数据与屏幕显示数据。
板载了WiFi,结合ThingSpeak云平台,在步骤1中获取到心率数据后,每20s上传到云平台一次。(20s是因为免费账号的限制)
板载了IMU单元,扩展功能为实现摔倒检测,当这种情况发生时,屏幕会有显示。
二、硬件介绍:
主控Arduino Nicla Vison:
Nicla Vision 是一款创新的 Arduino IoT Cloud 兼容开发板,专为智能项目设计。它集成了强大的 STM32H747AII6 双 ARM® Cortex® M7/M4 IC 处理器,配备 2MP 彩色摄像头支持 TinyML,以及智能 6 轴运动传感器、集成麦克风和距离传感器。这款开发板设计紧凑,尺寸仅为 22.86 x 22.86 毫米,易于集成到各种项目中。它支持通过 WiFi 和蓝牙® 低功耗技术进行无线连接,兼容 MicroPython,并且可以独立运行,非常适合电池供电的应用场景。

屏幕:SEEED SSD1315 0.96寸 OLED屏幕
OLED: 128*64分辨率屏幕,7bit地址为0x3C。使用Adafruit的SSD1306库即可驱动。

心率传感器:
MAX30102是一个集成的脉搏血氧仪和心率监测仪生物传感器的模块。它集成了多个LED、光电检测器、光器件,以及带环境光抑制的低噪声电子电路。MAX30102提供完备的系统方案,使移动及可穿戴设备的设计过程变得轻松。
MAX30102采用一个1.8V电源和一个独立的3.3V用于内部LED的电源,标准的I2C兼容的通信接口。可通过软件关断模块,待机电流为零,实现电源始终维持供电状态。

三、软件流程图:

四、具体实现细节与关键代码:
请参考:【ArduinoNiclaVision】过程贴 https://forum.eepw.com.cn/thread/398519/1
五、功能展示:发布在B站
关于摔倒的检测:

六、技术难点与心得:
Aruino Nicla Vision本身是一款非常紧凑的开发板,但功能非常强大。本次体验只探索了其中的一小部分功能。在测试WiFi模块功能的时候,总是烧录失败,后来发现需要预先烧录一个固件才可以。耗费了点时间。其次MAX30102的输出有时候波动比较大,后续可以考虑使用相关的滤波算法来获取更加准确的输出。
在开发的过程中也遇到了一些问题,都记录在了过程贴中。整个系统的集成是比较顺利的,得益于Arduino 完善的开发生态。
再次感谢EEPW与得捷提供的本次体验机会,希望这个活动越办越好!
源码:
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Arduino_LSM6DSOX.h>
#include "MAX30105.h"
#include "heartRate.h"
#include <WiFi.h>
#include "secrets.h"
#include "ThingSpeak.h" // always include thingspeak header file after other header files and custom macros
#include <PubSubClient.h>
//----------------------------OLED Related Start -------------------------------------------------------------------
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
MAX30105 particleSensor;
const byte RATE_SIZE = 4; //Increase this for more averaging. 4 is good.
byte rates[RATE_SIZE]; //Array of heart rates
byte rateSpot = 0;
long lastBeat = 0; //Time at which the last beat occurred
float beatsPerMinute;
int beatAvg;
//----------------------------MQTT Related Start -------------------------------------------------------------------
char ssid[] = SECRET_SSID; // your network SSID (name)
char pass[] = SECRET_PASS; // your network password
int keyIndex = 0; // your network key Index number (needed only for WEP)
WiFiClient client;
unsigned long myChannelNumber = SECRET_CH_ID;
const char* myWriteAPIKey = SECRET_WRITE_APIKEY;
//MQTT
PubSubClient MqttClient(client); // 定义PubSubClient的实例
const char* mqtt_server = "test.mosquitto.org"; // hivemq 的信息中转服务
const unsigned short mqtt_server_port = 1883;
const char* pubTOPIC = "digikey/heartbeat"; // 发布信息主题
const char* pubTOPICSpO2 = "digikey/spo2"; // 发布信息主题
const char* client_id = "smartband"; // 标识当前设备的客户端编号
uint8_t data[2];
unsigned char temp[10];
const long interval = 20000;
unsigned long previousMillis = 0;
void printWifiStatus() {
/* -------------------------------------------------------------------------- */
// print the SSID of the network you're attached to:
Serial.print("SSID: ");
Serial.println(WiFi.SSID());
// print your board's IP address:
IPAddress ip = WiFi.localIP();
Serial.print("IP Address: ");
Serial.println(ip);
// print the received signal strength:
long rssi = WiFi.RSSI();
Serial.print("signal strength (RSSI):");
Serial.print(rssi);
Serial.println(" dBm");
}
void setup() {
Serial.begin(115200);
// Wait for display
delay(500);
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println(F("SSD1306 allocation failed"));
for (;;)
; // Don't proceed, loop forever
}
// Show initial display buffer contents on the screen --
// the library initializes this with an Adafruit splash screen.
display.display();
delay(2000); // Pause for 2 seconds
display.clearDisplay();
display.setTextSize(1); // Normal 1:1 pixel scale
display.setTextColor(SSD1306_WHITE); // Draw white text
display.setCursor(0, 0); // Start at top-left corner
display.println(F("Hello, world!"));
display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); // Draw 'inverse' text
display.println(3.141592);
display.setTextSize(2); // Draw 2X-scale text
display.setTextColor(SSD1306_WHITE);
display.print(F("0x"));
display.println(0xDEADBEEF, HEX);
display.display();
delay(2000);
//begin
// Initialize sensor
if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) //Use default I2C port, 400kHz speed
{
Serial.println("MAX30105 was not found. Please check wiring/power. ");
while (1)
;
}
Serial.println("Place your index finger on the sensor with steady pressure.");
particleSensor.setup(); //Configure sensor with default settings
particleSensor.setPulseAmplitudeRed(0x0A); //Turn Red LED to low to indicate sensor is running
particleSensor.setPulseAmplitudeGreen(0); //Turn off Green LED
//end
//Init IMU:
if (!IMU.begin()) {
Serial.println("Failed to initialize IMU!");
while (1)
;
}
Serial.print("Accelerometer sample rate = ");
Serial.print(IMU.accelerationSampleRate());
Serial.println(" Hz");
Serial.println();
Serial.println("Acceleration in g's");
Serial.println("X\tY\tZ");
pinMode(LED_BUILTIN, OUTPUT);
//WiFi.mode(WIFI_STA);
// Connect or reconnect to WiFi
if (WiFi.status() != WL_CONNECTED) {
Serial.print("Attempting to connect to SSID: ");
Serial.println(SECRET_SSID);
while (WiFi.status() != WL_CONNECTED) {
WiFi.begin(ssid, pass); // Connect to WPA/WPA2 network. Change this line if using open or WEP network
Serial.print(".");
delay(5000);
}
Serial.println("\nConnected.");
}
Serial.println("You're connected to the network");
Serial.println(ssid);
printWifiStatus();
Serial.println();
ThingSpeak.begin(client); // Initialize ThingSpeak
MqttClient.setServer(mqtt_server, mqtt_server_port); //设定MQTT服务器与使用的端口,1883是默认的MQTT端口
Serial.println("UART, ThingSpeak, MQTT init done");
}
void reconnect() {
while (!MqttClient.connected()) {
Serial.print("Attempting MQTT connection...");
// Attempt to connect
if (MqttClient.connect(client_id)) {
Serial.println("connected");
// 连接成功时订阅主题
//MqttClient.subscribe(TOPIC);
} else {
Serial.print("failed, rc=");
Serial.print(MqttClient.state());
Serial.println(" try again in 5 seconds");
// Wait 5 seconds before retrying
delay(5000);
}
}
}
long lastMsg = 0;
char msg[50];
int value = 0;
float x, y, z;
void loop() {
static uint16_t tempData = 0;
unsigned long currentMillis = millis();
long irValue = particleSensor.getIR();
if (checkForBeat(irValue) == true) {
//We sensed a beat!
long delta = millis() - lastBeat;
lastBeat = millis();
beatsPerMinute = 60 / (delta / 1000.0);
if (beatsPerMinute < 255 && beatsPerMinute > 20) {
rates[rateSpot++] = (byte)beatsPerMinute; //Store this reading in the array
rateSpot %= RATE_SIZE; //Wrap variable
//Take average of readings
beatAvg = 0;
for (byte x = 0; x < RATE_SIZE; x++)
beatAvg += rates[x];
beatAvg /= RATE_SIZE;
}
}
Serial.print("IR=");
Serial.print(irValue);
Serial.print(", BPM=");
Serial.print(beatsPerMinute);
Serial.print(", Avg BPM=");
Serial.print(beatAvg);
if (irValue < 50000)
Serial.print(" No finger?");
Serial.println();
if (IMU.accelerationAvailable()) {
IMU.readAcceleration(x, y, z);
if (fabs(x) >= 0.2) {
Serial.println("***********Falling Down. Warning!****************");
Serial.print(x);
Serial.print('\t');
Serial.print(y);
Serial.print('\t');
Serial.println(z);
//Write to OLED:
display.clearDisplay();
display.setTextSize(2); // Normal 1:1 pixel scale
display.setTextColor(SSD1306_WHITE); // Draw white text
display.setCursor(0, 20); // Start at top-left corner
display.print(F("Fall Down"));
display.display();
}
}
/**************************MQTT******************************/
if ((currentMillis - previousMillis >= interval) && ((beatAvg >= 50) && (beatAvg <= 150))) {
previousMillis = currentMillis;
//Write to OLED:
display.clearDisplay();
display.setTextSize(1); // Normal 1:1 pixel scale
display.setTextColor(SSD1306_WHITE); // Draw white text
display.setCursor(0, 0); // Start at top-left corner
display.print(F("Heart/Min: "));
//display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); // Draw 'inverse' text
display.println(beatAvg);
// display.setTextSize(2); // Draw 2X-scale text
// display.setTextColor(SSD1306_WHITE);
// display.print(F("0x"));
// display.println(0xDEADBEEF, HEX);
display.display();
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
Serial.println("Sending message to topic: ");
// set the fields with the values
ThingSpeak.setField(1, beatAvg);
ThingSpeak.setField(2, irValue);
ThingSpeak.setStatus("HeartBeat_SpO2");
int x = ThingSpeak.writeFields(myChannelNumber, myWriteAPIKey);
if (x == 200) {
Serial.print("------------Channel update successful----Value: ");
//Serial.print(tempValtoIOT);
Serial.println(" -------------------.");
} else {
Serial.println("Problem updating channel. HTTP error code " + String(x));
}
if (!MqttClient.connected()) {
reconnect();
}
MqttClient.loop();
//byte outmsg_heart = data[0];
snprintf(msg, 50, "%ld bps", beatAvg);
if (MqttClient.publish(pubTOPIC, msg)) {
Serial.println("Publish Topic:");
Serial.println(pubTOPIC);
Serial.println("Publish message:");
Serial.println(msg);
} else {
Serial.println("Message Publish Failed.");
}
snprintf(msg, 50, "%ld %%", irValue);
if (MqttClient.publish(pubTOPICSpO2, msg)) {
Serial.println("Publish Topic:");
Serial.println(pubTOPICSpO2);
Serial.println("Publish message:");
Serial.println(msg);
} else {
Serial.println("Message Publish Failed.");
}
}
}
我要赚赏金
