这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 活动中心 » 板卡试用 » 【ArduinoNiclaVision】成果贴

共1条 1/1 1 跳转至

【ArduinoNiclaVision】成果贴

工程师
2025-12-28 18:26:04     打赏

【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

  1. 具有IIC接口,以此驱动OLED与MAX30102,完成获取心率数据与屏幕显示数据。

  2. 板载了WiFi,结合ThingSpeak云平台,在步骤1中获取到心率数据后,每20s上传到云平台一次。(20s是因为免费账号的限制)

  3. 板载了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,并且可以独立运行,非常适合电池供电的应用场景。

image.png

屏幕:SEEED SSD1315 0.96寸 OLED屏幕

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

image.png

心率传感器:

MAX30102是一个集成的脉搏血氧仪和心率监测仪生物传感器的模块。它集成了多个LED、光电检测器、光器件,以及带环境光抑制的低噪声电子电路。MAX30102提供完备的系统方案,使移动及可穿戴设备的设计过程变得轻松。

MAX30102采用一个1.8V电源和一个独立的3.3V用于内部LED的电源,标准的I2C兼容的通信接口。可通过软件关断模块,待机电流为零,实现电源始终维持供电状态。

image.png


三、软件流程图:

image.png

四、具体实现细节与关键代码:

请参考:【ArduinoNiclaVision】过程贴 https://forum.eepw.com.cn/thread/398519/1


五、功能展示:发布在B站

关于摔倒的检测:

A35C24BE-8D17-41CE-82B6-C93D15217399-6213-000000B789F4D5B0.gif


六、技术难点与心得:

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.");
    }
  }
}




共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]