簡(jiǎn)單整理了自己在本學(xué)期學(xué)校的“物聯(lián)網(wǎng)技術(shù)與應(yīng)用”課程的課程大作業(yè)中所實(shí)現(xiàn)的一個(gè)小原型羡滑。
該原型實(shí)現(xiàn)了利用esp8266自帶的WIFI模塊,借助Node MCU開(kāi)發(fā)板讀取pulse sensor心率傳感器的數(shù)據(jù)野瘦,通過(guò)MQTT通信協(xié)議萄金,在手機(jī)上查看心率值建瘫,并且顯示在OLED屏上。同時(shí)通過(guò)MQTT協(xié)議荤牍,連接了開(kāi)發(fā)板的傳感器案腺、EMQ服務(wù)器、手機(jī)之間可以相互訂閱参淫、發(fā)布相關(guān)topic的消息救湖。
需要用到:
esp8266開(kāi)發(fā)板
杜邦線、USB線
Pulse Sensor心率傳感器
0.96寸OLED顯示屏
Arduino IDE
EMQ (MQTT消息服務(wù)器)
一涎才、傳感器相關(guān)準(zhǔn)備
用杜邦線將傳感器按照以下的對(duì)應(yīng)關(guān)系連接起來(lái):
Pulse Sensor心率傳感器 esp8266
S A0引腳(ADC)
+ 3.3V
- GND
Pulse Sensor心率傳感器如下圖所示:
網(wǎng)上關(guān)于Pulse Sensor心率傳感器也可以搜到一些鞋既,這里就不再多說(shuō)啦,可以參考這個(gè)博客:史上最全脈搏心率傳感器PulseSensor資料(電路圖+中文說(shuō)明書(shū)+最全源代碼)
Pulse Sensor官方的github庫(kù): PulseSensorPlayground
在Arduino->管理庫(kù)->庫(kù)管理器 搜索PulseSensor Playground下載并安裝,安裝成功后在 Arduino->文件->示例 可以看到如下圖所示的官方示例代碼耍铜。
連接好:
二邑闺、OLED相關(guān)準(zhǔn)備
使用的是0.96寸的OLED屏:
將OLED屏也連接到esp8266開(kāi)發(fā)板上:
三、Arduino下載安裝相關(guān)庫(kù)
在Arduino庫(kù)管理器里搜索并安裝以下的將會(huì)用到的庫(kù)
四棕兼、MQTT相關(guān)知識(shí)
MQTT菜鳥(niǎo)教程:MQTT 入門(mén)介紹
五陡舅、搭建EMQ
EMQ官網(wǎng):https://www.emqx.io/cn/products/broker
官網(wǎng)安裝配置文檔:https://docs.emqx.io/broker/v3/cn/
搭建成功后,在瀏覽器輸入控制臺(tái)地址:http://127.0.0.1:18083
輸入賬號(hào)密碼即可進(jìn)入EMQ控制臺(tái):
六伴挚、Arduino部分
Arduino->工具里開(kāi)發(fā)板要選NodeMCU1.0靶衍,且端口要選對(duì)
實(shí)現(xiàn)代碼 (計(jì)算心率算法+MQTT+OLED控制):
extern "C" {
#include "user_interface.h"
}
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <Wire.h>
// Only needed for Arduino 1.6.5 and earlier
#include "SSD1306Wire.h"
// legacy include: `#include "SSD1306.h"`
SSD1306Wire display(0x3c, D4, D5);
//esp8266通過(guò)傳感器采集脈搏模擬信號(hào)灾炭,經(jīng)過(guò)AD轉(zhuǎn)換為數(shù)字電壓 并計(jì)算心率值
//Signal,信號(hào)颅眶,持有原始模擬輸入數(shù)據(jù)引腳0蜈出,每2ms更新一次 int
//IBI: 相鄰兩次脈搏的時(shí)間間隔(單位:ms) int
//BPM(beats per minute):心率,一分鐘內(nèi)的心跳次數(shù) int
//(且BPM = 60 / IBI)是根據(jù)之前 10個(gè)IBI值的 平均值
//Pulse脈沖涛酗,當(dāng)檢測(cè)到心跳時(shí) 為true铡原,其他時(shí)候?yàn)閒alse。它控制LED引腳13商叹。 boolean
//QS 當(dāng)找到Pulse并更新BPM時(shí) 為true燕刻。必須重置。 boolean
const char* ssid = "";//熱點(diǎn)名
const char* password = "";//熱點(diǎn)密碼
const char* mqtt_server = "";//MQTT服務(wù)器ip
WiFiClient espClient;
PubSubClient client(espClient);
long lastMsg = 0;
char msg[8];
int value = 0;
#define N 10
volatile int BPM; // 保持引腳0剖笙,每2ms更新一次
volatile int Signal; // 保存原始數(shù)據(jù)
volatile int IBI = 600; // 相鄰兩次心跳之間的時(shí)間間隔是唯一確定的
volatile boolean Pulse = false; // 脈沖,當(dāng)監(jiān)測(cè)到一個(gè)心跳時(shí)為"True"卵洗,否則為"False"
volatile boolean QS = false; // 當(dāng)Arduoino監(jiān)測(cè)到心跳時(shí)為true
volatile int Rate[N]; // 設(shè)置數(shù)組保存最后10次IBI數(shù)據(jù)
volatile unsigned long CurrBeatTime = 0; // 用于確定脈沖時(shí)間
volatile unsigned long LastBeatTime = 0; // 用于監(jiān)測(cè)IBI
volatile int P = 500; // 用于尋找脈搏波峰值
volatile int T = 500; // 用于發(fā)現(xiàn)脈搏波中的波谷
volatile int Threshold = 512; //設(shè)置閥值
volatile int Amplifier = 100; //放大器
int PulseSensorPin = 17;
int FadePin = 4;
int FadeRate = 0;
String tmp="";
void setupTimer(int m /* msec */) {
timer0_isr_init();
timer0_attachInterrupt(timer0_ISR);
timer0_write(ESP.getCycleCount() + 80000L * m); // 80MHz/1000 == 1msec
}
unsigned long getCurrentTime() {
return ESP.getCycleCount() / 80000L;
}
void timer0_ISR(void) {
noInterrupts();
Signal = system_adc_read();
CurrBeatTime = getCurrentTime(); // msec
unsigned long interval = CurrBeatTime - LastBeatTime;
// 找出脈搏波的波谷
if ((Signal < Threshold) && (interval >(IBI * 3) / 5)) { // 通過(guò)等待3/5的IBI,以避免二色噪聲
if (Signal < T) { // T 是波谷
T = Signal; // 脈搏波最低點(diǎn)跟蹤
//Serial.println("T:" + String(T));
}
}
//找出脈搏波的波峰
if (Signal > Threshold && Signal > P) { // 閾值條件有助于避免噪音
P = Signal; // P 是波峰
//Serial.println("P:" + String(P));
}
// 開(kāi)始尋找心跳
// 每當(dāng)有脈沖時(shí)枯途,信號(hào)就會(huì)增值忌怎。
if (interval > 250 /* ms */) { // 避免高頻率噪音
//檢查信號(hào)是否超過(guò)閥值
if ((Signal > Threshold) && !Pulse && (interval > (IBI * 3) / 5)) {
Pulse = true; // 當(dāng)感覺(jué)這有心跳時(shí)設(shè)Pulse flag
IBI = interval;
if (Rate[0] < 0) { // first time 第一次
Rate[0] = 0;
LastBeatTime = getCurrentTime();
setupTimer(10);
noInterrupts();
return;
}
else if (Rate[0] == 0) { // second time 第二次
for (int i = 0; i < N; ++i) { // 以獲得啟動(dòng)時(shí)實(shí)際的BPM
Rate[i] = IBI;
}
}
// 保持最后10個(gè)IBI值的累計(jì)總數(shù)
word running_total = 0; // 清零runningTotal
for (int i = 0; i < N - 1; ++i) { // 心率數(shù)組中的移位數(shù)據(jù)
Rate[i] = Rate[i + 1]; // 降低最早的IBI值
running_total += Rate[i]; // 加上9個(gè)最早的IBI值 一共10個(gè),平均值得到BPM
}
Rate[N - 1] = IBI; // 將最新的IBI添加到心率數(shù)組中
running_total += IBI; // 將最新的IBI添加到運(yùn)行總數(shù)runningTotal中
running_total /= N; // 取10個(gè)IBI值的平均值=BPM
BPM = 60000 / running_total; // 一分鐘內(nèi)的心跳次數(shù)就是BPM
QS = true; // 設(shè)置量化標(biāo)記Quantified Self flag
LastBeatTime = getCurrentTime();
}
}
// 檢查信號(hào)是否在閥值以下
if ((Signal < Threshold) && Pulse) {
Pulse = false;
Amplifier = P - T;
Threshold = Amplifier / 2 + T; // 修改閥值
P = Threshold;
T = Threshold;
}
// 如果沒(méi)有信號(hào)超過(guò)2.5秒 檢查
if (interval > 2500 /* ms */) {
Threshold = 512;
P = 500;
T = 500;
LastBeatTime = getCurrentTime();
for (int i = 0; i < N; ++i) {
Rate[i] = -1;
}
}
setupTimer(10);
interrupts();
}
void setup() {
pinMode(BUILTIN_LED, OUTPUT);
pinMode(A0, INPUT);
Serial.begin(115200);
setup_wifi();
client.setServer(mqtt_server, 1883);
client.setCallback(callback);
pinMode(FadePin, OUTPUT);
analogWriteRange(255);
noInterrupts();
setupTimer(10);
interrupts();
LastBeatTime = getCurrentTime(); // msec
Serial.println();
display.init();
display.clear();
}
void setup_wifi() {
delay(10);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
}
void callback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("] ");
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
tmp+=(char)payload[i];
}
Serial.println();
displayData();
tmp="";
}
void displayData(){
display.clear();
display.drawString(0,0,"[receive]:");
display.drawString(0,20,tmp);
display.display();
delay(1000);
}
void reconnect() {
while (!client.connected()) {
Serial.print("MQTT connection...");
if (client.connect("ESP8266Client")) {
client.subscribe("Topic"); //訂閱主題Topic
}
else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
delay(5000);
}
}
}
void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();
if (QS) {
FadeRate = 255;
Serial.print("BPM: ");
Serial.println(BPM);
snprintf(msg, 8, "BPM:%d", BPM);
client.publish("Topic", msg);//發(fā)布主題Topic
QS = false;
}
FadeRate -= 15;
FadeRate = constrain(FadeRate, 0, 255);
analogWrite(FadePin, FadeRate);
delay(20);
}
程序?qū)懞煤罄乙模瑢㈤_(kāi)發(fā)板上電,并連接到電腦上孽惰,將程序燒制進(jìn)去晚岭,保持手指放在心率傳感器的正面,打開(kāi)串口監(jiān)視器(波特率選擇115200)勋功,當(dāng)成功連接到MQTT服務(wù)器時(shí)坦报,就可以看到串口輸出以下數(shù)據(jù),并可以在OLED上顯示(程序訂閱和發(fā)布的主題都是Topic狂鞋,所以自己能收到自己發(fā)的)
此時(shí)可以打開(kāi)EMQ的控制臺(tái)片择,利用EMQ的WebSocket工具,訂閱主題Topic便可以查看收到的消息數(shù)據(jù)骚揍,也可以發(fā)送Topic主題的消息字管,其可以顯示在OLED屏上。
七信不、iOS/Android手機(jī)端MQTTClient
相關(guān)MQTTClient的應(yīng)用各大應(yīng)用商城都可以搜到很多嘲叔,可以選擇下載一個(gè)進(jìn)行調(diào)試,查看效果抽活;也可以自己寫(xiě)一個(gè)簡(jiǎn)單的安卓MQTTClient程序硫戈,實(shí)現(xiàn)在安卓手機(jī)端查看心率值,前提是手機(jī)下硕、開(kāi)發(fā)板丁逝、部署了MQTT服務(wù)器的電腦在同一網(wǎng)絡(luò)下汁胆。 以下是自己寫(xiě)的一個(gè)簡(jiǎn)單的安卓MQTTClient客戶端的demo,可以訂閱霜幼、發(fā)布Topic主題的消息嫩码,在安卓手機(jī)上以Toast的形式顯示接收到的心率值。
安卓MQTTClient部分代碼: