學習目標 : 利用faya電子積塊組出能偵測心率的裝置
學習時間 : 150min
使用模組 : (1) faya brickNano
(2) faya 心跳偵測模組
(3) faya 串列8x8點矩陣模組
(4) faya 串列七段顯示模組
(5) faya 蜂鳴器模組
工具 : 樂高積木(相容)
====================相關知識====================
這篇文章中所用到的模組使用方式及相關知識整理如下,需進一步了解時可點進去參考:
模組介紹 : faya brickNano
模組介紹 : faya 心跳偵測模組
模組介紹 : faya 串列8x8點矩陣模組
模組介紹 : faya 串列七段顯示模組
模組介紹 : faya 蜂鳴器模組
======================開箱======================
心動時刻(型號NTG-508)是fayalab的第八款創意組合作品,此作品的目標是利用現有的faya模組,配合Arduino程式,組出一個能夠量測心率(BPM - Beats Per Minute, 每分鐘心跳數 )的裝置,當偵測到心跳訊號時,8x8點矩陣會閃爍一次愛心型狀,並在蜂鳴器產生一次嗶聲,在穩定的狀態下,大約偵測6~7次的心跳就會估計出受測者的心率。
盒內包含了製作心跳時刻所需的全部模組及附件:
1. brickNano 2. 8x8點矩陣模組 3. 心跳偵測模組 4. 串列七段顯示模組 5. 蜂鳴器模組
6. 積木柱子 7. 積木蓋子 8. 電源線(B) 9. micro USB傳輸線
10. 跳線組 11. 積木組 12. 積木底板
===================範例實作=====================
目標:
(1) 利用faya電子積塊,量測每分鐘心跳
(2) 量測到心跳訊號時,8x8點矩陣顯示心跳圖案
(3) 量測到心跳訊號時,蜂鳴器產生嗶聲
(4) 七段顯示器顯示每分鐘心跳數 BPM (Beats Per Minute)
組裝:
首先把 [1.faya brickNano] [2.串列8x8點矩陣模組] [3.心跳偵測模組] [4.串列七段顯示模組] [5. 蜂鳴器模組]的四個角落裝上[6.積木柱子]。積木柱子如果塞起來鬆鬆的沒關西,下個步驟透過積木蓋子就可以將柱子扣穩。
積木柱子的組裝過程可參考[功能介紹 : faya電子積塊與LEGO積木的結合]
接著把[2.串列8x8點矩陣模組] [3.心跳偵測模組] [4.串列七段顯示模組] [5. 蜂鳴器模組]的四個角落裝上[7. 積木蓋子],把[1.faya brickNano]的四個角若裝上1x1的積木。
積木蓋子的組裝過程可參考[小技巧 : 積木柱子太鬆時如何處理?]
接下來示範線路的連接和積木的組裝,我們這次要將五個模組以立體結構的方式組在16x16的積木平板上,大家可以按照自己喜愛的方式堆疊積木,但這邊建議先按照底下的範例跟著做一次,熟悉系統運作的方式後,再按照自己的喜好修改造型。如果照著底下的範例,組完後會長這個樣子:
安裝faya brickNano、心跳偵測模組、串列七段顯示模組:
首先利用[11. 積木包] 裡面的4x2,2x2積木,安裝在[12. 積木底板]上,位置如下所示
接下來安裝電源線,我們利用兩條[8. 電源線(B)]連結模組間的電源座,如下圖所示,將brickNano右邊的電源座連接到串列七段顯示模組,brickNano左邊的電源座連接到心跳偵測模組,連接的詳細原理和說明可參考這篇文章 或簡易版。
完成後,我們理一下電源線,將其繞到模組下方,保持美觀,然後利用[10.跳線組]完成Arduino和模組之間的訊號連接
心跳感測模組:
Arduino_D2 ===> 心跳偵測模組_Signal
串列七段顯示模組:
Arduino_D7 ===> 串列七段顯示模組_DIN
Arduino_D6 ===> 串列七段顯示模組_CLK
Arduino_D5 ===> 串列七段顯示模組_LOAD
同樣的我們把訊號線繞到模組下方保持美觀
安裝串列8x8點矩陣模組:
接著準備往上搭積木以建立立體結構,在這之前,先用3條黃色跳線將數位腳位D12、D11、D10拉出來,未來要連接上方的串列8x8點矩陣顯示模組,另外用一條綠色跳線將數位腳D3拉出來,未來要連接上方蜂鳴器模組。
拉完訊號線後,利用2片2x6平板,安裝在brickNano上方的1x1積木上
接著在2x6平板積木上安裝4顆1x2積木,如下所示
然後在上方覆蓋一片6x8平板積木
完成後,將 [2.串列8x8點矩陣模組]安裝在6x8平板上方,如下所示
接下來連接8x8點矩陣顯示模組和心跳偵測模組間的電源線,如下圖所示
然後利用剛剛引出的黃色跳線,連接Arduinoi和串列8x8點矩陣間的訊號線
串列8x8點矩陣模組:
Arduino_D12 ===> 串列8x8點矩陣模組_DIN
Arduino_D11 ===> 串列8x8點矩陣模組_CLK
Arduino_D10 ===> 串列8x8點矩陣模組_LOAD
完成訊號線的連接後,將訊號線整理壓平,才不會擋到稍後積木的安裝
安裝蜂鳴器模組:
首先利用兩片2x6平板,安裝在6x8平板兩側
接著在尾端上方安裝一片2x10平板
然後在2x10平板上方放置兩個2x2積木,位置如下圖所示
再將一片2x6平板壓在上方
完成以上步驟後,將主體擺一邊,拿出一片2x6平板和兩顆1x4積木,做出以下組合
接著在其上下方兩側各裝上兩顆90度的積木,如下所示
再將[5. 蜂鳴器模組]的積木柱子安裝在側面的四個積木凸點
完成後,將以上立體結構安裝在剛剛作品主體上,位置如下所示
接著連接蜂鳴器模組和串列七段顯示模組間的電源線,如下圖所示
然後利用剛剛引出的綠色跳線,連接Arduinoi和蜂鳴器模組間的訊號線,由下圖中我們可以看到此訊號線穿過模組下方的空間往上連接。
蜂鳴器模組:
Arduino_D3 ===> 蜂鳴器模組_BZ
完成主體:
最後我們利用手邊剩下的積木做一些裝飾,這些裝飾不會影響功能,大家可以自由發揮,讓主體看起來不單調,以下是從各個角度檢視我們完成的樣子。
正面視角:
背面視角:
左方視角:
右方視角
側上方視角
範例程式:
底下為本作品的範例程式,程式中會用到LedControl的函式庫,可以點此下載,下載後解壓縮至Arduino下的Libraries資料夾,重新開啟Arduino IDE後會自動載入。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 2017/9/27 | |
// Faya-Nugget 範例程式 (HeartRateDetector_1.ino) | |
// 單元:心跳時刻 | |
// 網址:https://fayalab.blogspot.com/2018/09/heart-rate-detector.html | |
// 目標: (1) 利用faya電子積塊,量測每分鐘心跳 | |
// (2) 量測到心跳訊號時8x8點矩陣顯示心跳圖案 | |
// (3) 量測到心跳訊號時蜂鳴器產生嗶聲 | |
// (4) 七字節顯示器顯示每分鐘心跳數 BPM (Beats Per Minute) | |
// 接線: Arduino ==> faya模組 | |
// D12 ==> DIN (點矩陣顯示模組) | |
// D11 ==> CLK (點矩陣顯示模組) | |
// D10 ==> LOAD (點矩陣顯示模組) | |
// D7 ==> DIN (7段顯示器模組) | |
// D6 ==> CLK (7段顯示器模組) | |
// D5 ==> LOAD (7段顯示器模組) | |
// D3 ==> BZ (蜂鳴器模組) | |
// D2 ==> Signal (心跳偵測模組) | |
#include <LedControl.h> //引入LED Control 函式庫 | |
//定義Arduino與串列七字節模組的連接腳位與參數 | |
LedControl faya7seg=LedControl(7,6,5,1); // DIN, CLK, LOAD, 串列數量 to 串列七字節 | |
//定義Arduino與串列8x8點矩陣模組的連接腳位與參數 | |
LedControl faya8x8=LedControl(12,11,10,1); // DIN, CLK, LOAD, 串列數量 to 串列點矩陣 | |
#define irPulseIn 2 // 連接心跳感應模組輸入腳位 | |
#define bzOut 3 // 連接蜂鳴器模組輸出腳位 | |
boolean heartFull[8][8] = //定義形狀 - 實心愛心 | |
{ | |
{0,0,0,0,0,0,0,0}, | |
{0,1,1,0,0,1,1,0}, | |
{1,1,1,1,1,1,1,1}, | |
{1,1,1,1,1,1,1,1}, | |
{1,1,1,1,1,1,1,1}, | |
{0,1,1,1,1,1,1,0}, | |
{0,0,1,1,1,1,0,0}, | |
{0,0,0,1,1,0,0,0} | |
}; | |
boolean heartEmpty[8][8] = //定義形狀 - 空心愛心 | |
{ | |
{0,0,0,0,0,0,0,0}, | |
{0,1,1,0,0,1,1,0}, | |
{1,0,0,1,1,0,0,1}, | |
{1,0,0,0,0,0,0,1}, | |
{1,0,0,0,0,0,0,1}, | |
{0,1,0,0,0,0,1,0}, | |
{0,0,1,0,0,1,0,0}, | |
{0,0,0,1,1,0,0,0} | |
}; | |
int input, lastInput; // 輸入狀態, 前一筆輸入狀態 | |
int pulseTime, lastTime; // 心跳時間點, 前一筆心跳時間點 | |
int heartPeriod, heartRate; // 兩心跳間時間 (mS), 心率 (BPM) | |
int avgHeartRate = 72; // 平均心率 | |
const int maxCount = 7; // 儲存心率資料筆數 | |
int H_array[maxCount] = {72,72,72,72,72,72,72}; // 矩陣用來儲存心率 | |
int count = 1; // 用來記錄儲存次數 | |
void setup() | |
{ | |
Serial.begin(9600); // 設定串列埠傳輸速率 | |
pinMode(irPulseIn, INPUT); // 紅外線偵測連接到輸入腳位 (D2) | |
pinMode(bzOut, OUTPUT); // 輸出腳位 (D3) 連接到蜂鳴器 | |
// 初始化七字節顯示器 | |
faya7seg.shutdown(0,false); // 第0個元件不要進入省電模式 | |
faya7seg.setIntensity(0,8); // 第0個的元件亮度8 (0~15) | |
faya7seg.clearDisplay(0); // 清除第0個元件 | |
show4digits(0); // 輸出數字0000 | |
// 初始化8x8點矩陣顯示器 | |
faya8x8.shutdown(0,false); // 第0個元件不要進入省電模式 | |
faya8x8.setIntensity(0,8); // 第0個的元件亮度8 (0~15) | |
faya8x8.clearDisplay(0); // 清除第0個元件 | |
matrixEmpty(); // 輸出空心愛心圖形 | |
} | |
void loop() | |
{ | |
heartRateGet(); // 取得單次心率 | |
rollingAverage(); // 計算平均心率 | |
show4digits(avgHeartRate); // 顯示平均心率 | |
} | |
void heartRateGet() //取得單次心率 | |
{ | |
input = digitalRead(irPulseIn); //讀取輸入狀態 | |
if ((input != lastInput)&&(input == HIGH)) //上緣觸發 | |
{ | |
pulseTime = millis(); // 紀錄偵測到心跳時的起始時間 | |
heartPeriod = pulseTime - lastTime; // 計算兩心跳間的時間間隔 | |
lastTime = pulseTime; // 更新最後一次偵測到心跳的時間 | |
heartRate = 60000/heartPeriod; // 心率 (每分鐘心跳數 BPM) = 60 / 心跳間隔時間(ms) = 60000 / 心跳間隔時間 (s) | |
if(heartRate > 150 || heartRate < 40) // 太高或太低的心率視為無效 | |
{ | |
heartRate = avgHeartRate; // 用平均心率取代 | |
} | |
else | |
{ | |
showSerialMonitor(); //顯示心率於串列埠傳輸視窗 | |
tone(bzOut,800,100); // 蜂鳴器短beep | |
matrixFull(); // 8x8點矩陣秀出實心愛心 | |
delay(100); // 延遲100ms | |
matrixEmpty(); // 8x8點矩陣秀出空心愛心 | |
} | |
} | |
lastInput = input; // 更新input值 | |
} | |
void rollingAverage() | |
{ | |
if(H_array[count-1] != heartRate && abs(avgHeartRate-heartRate)<20) // 當偵測到心率改變,且改變不能太大 | |
{ | |
if(count == maxCount) //記錄到最後一筆資料時 | |
{ | |
count=0; //歸回第一筆重新記錄 | |
} | |
else | |
{ | |
H_array[count] = heartRate; //紀錄心率資料 | |
count = count+1; //指向下一筆資料儲存區 | |
} | |
} | |
avgHeartRate = (H_array[0]+H_array[1]+H_array[2]+H_array[3]+H_array[4]+H_array[5]+H_array[6])/maxCount; // 取最近七次有效量測的平均值 | |
} | |
void showSerialMonitor() //顯示心率資料於串列埠監控視窗 | |
{ | |
Serial.print(heartRate); // 單次心率 | |
Serial.println("BPM (One Time)"); | |
Serial.print(H_array[0]); // 儲存於第一筆的心率資料 | |
Serial.print("\t"); | |
Serial.print(H_array[1]); // 儲存於第二筆的心率資料 | |
Serial.print("\t"); | |
Serial.print(H_array[2]); // 儲存於第三筆的心率資料 | |
Serial.print("\t"); | |
Serial.print(H_array[3]); // 儲存於第四筆的心率資料 | |
Serial.print("\t"); | |
Serial.print(H_array[4]); // 儲存於第五筆的心率資料 | |
Serial.print("\t"); | |
Serial.print(H_array[5]); // 儲存於第六筆的心率資料 | |
Serial.print("\t"); | |
Serial.println(H_array[6]); // 儲存於第七筆的心率資料 | |
Serial.print(avgHeartRate); // 平均心率 | |
Serial.println("BPM (Average)"); | |
Serial.println(); | |
} | |
void show4digits(int number) //顯示數字於串列七字節顯示模組 | |
{ | |
int num1,num2,num3,num4; | |
num1=number/1000; //千位數 | |
num2=(number%1000)/100; //百位數 | |
num3=(number%100)/10; //十位數 | |
num4=number%10; //個位數 | |
faya7seg.setDigit(0,3,num1,false); //點亮千位數 | |
faya7seg.setDigit(0,2,num2,false); //點亮百位數 | |
faya7seg.setDigit(0,1,num3,false); //點亮十位數 | |
faya7seg.setDigit(0,0,num4,false); //點亮個位數 | |
} | |
void matrixFull() //8x8點矩陣顯示實心愛心 | |
{ | |
for(int i=0; i<=7; i++) | |
{ | |
for (int j=7; j>=0; j--) | |
{ | |
faya8x8.setLed(0,i,j,heartFull[i][j]); | |
} | |
} | |
} | |
void matrixEmpty() //8x8點矩陣顯示空心愛心 | |
{ | |
for(int i=0; i<=7; i++) | |
{ | |
for (int j=7; j>=0; j--) | |
{ | |
faya8x8.setLed(0,i,j,heartEmpty[i][j]); | |
} | |
} | |
} |
備註:
- L81~L83: 程式主要由此三行組成,必須重複執行,因此存於Loop()迴圈。
- L81: heartRateGet() - 用來獲取並計算單次心率,並且顯示心跳圖案於8x8點矩陣模組與顯示聲音於蜂鳴器模。
- L82 : rollingAverage() - 利用滾動式平均法取得較穩定的平均心率值
- L83 : show4digits() - 將平均心率值秀在七段顯示器。
L86~109: heartRateGet()副程式:
- 心跳感應模組的原理可參考之前的文章 [模組介紹 : faya 心跳偵測模組]
-L89: if ((input != lastInput)&&(input == HIGH)); 偵測數位腳位上源觸發時的語法,第一個條件判斷邏輯準位改變時,第二個條件判斷狀態為HIGH時。每次的上緣信號代表一次的心跳。
-L91~94 : 每次偵到上源訊號時,記錄當下時間,算出兩次上源觸發的時間差後,取倒數算出頻率,並且轉換單位變成每分鐘的心跳數。
-L95~98 : 量測時心跳訊號時,有時會因為周邊雜訊或者身體的移動,造成不合哩的心率數值,我們利用此段程式過濾不合理的心率,並用穩定的平均心率值取代。
-L99~106 : 當量測到合理的心跳數值時,緊接著於串列埠監控視窗輸出量測統計表、一個短嗶聲、顯示實心與空心的愛心圖案,讓量測者可以察覺已完成一次有效量測。
L111~126: rollingAverage()副程式:
- L58: 設立了一個可存七筆資料的矩陣,儲存每次量測到的心率,七筆資料用來計算滾動式平均值
- L113: 通過兩個條件才將資料存放在矩陣中,第一個條件是量到的心率有變化時,第二個條件是變化量不能太大,例如原本的心率是72 BPM,下一次的心率上升到110 BPM,這是不合理的心率值,需要濾掉。
- L115~118 : 資料存到最大筆數時,回到第一筆重新儲存
- L120~123 : 儲存有效資料至矩陣中,並指定下一筆資料存放區
- L125 : 每次更新一筆心率資料後,立刻算出最近七筆心率的平均值
L128~148: showSerialMonitor()副程式:
- 第一行秀出單次的心律
- 第二行秀出最近七次的有效心率
- 第三行秀出算出的滾動式平均心率
L150~162: show4digits(int)副程式:
- L22、L67~70、L150~162 : 於串列七段顯示模組顯示指定數字,相關用語法解釋可參照之前的文章 : [模組介紹 : faya 串列七段顯示模組]
L128~148: matrixFull() / matrixEmpty()副程式:
- L24、L29~51、L74~86、L164~184: 在串列8x8點矩陣模組顯示指定圖案,相關語法解釋可參照之前的文章 : [模組介紹 :faya 串列8x8點矩陣模組]
實心愛心圖的點矩陣配置:
空心愛心圖的點矩陣配置:
結果演示:
影片中可看出從穩定量測開始,大約經過7次量測後,心率值達到穩定狀態,量測結果和Garmin的心率錶幾乎一致,介於62~64BPM之間。
量測技巧:
- 請以坐姿量測,左右手食指皆可量測,量測時,手軸靠在桌上,手指輕壓感測器表面,身體和手指不要移動並保持穩定的壓力,可獲得穩定的測結果,大約經過6~7次的量測,會得到準確的心率值。
- 如量測出來的數值怪怪的,可用示波器觀察波型,或者直接旋轉心跳偵測模組的可變電阻改變gain值再試試看,通常可變電阻指向一點鐘方向可以獲得滿不錯的效果。
- 由於每個人的皮膚厚度不同,量測不到訊號時,可以試著將量測點挪到不同的指腹位置,直到獲得穩定量測結果為止。
以下是我們開發此創意組合時的結論與經驗,大家可以參考參考
- 我們利用心跳時信號產生的上源觸發,計算兩次心跳間的間隔,再算出每分鐘頻率,如果只取一次的量測值,由於手的移動或其他干擾,每次計算出的輸出數值會有很大的變化,因此我們加入了滾動式平均法,當量測到有效的心率,就和前6筆有效資料總合起來並取平均值,如此一來的輸出資料會比較平滑。
- 我們透過程式濾掉了不合理的心率值,增加了輸出值的準確性。
歡迎大家在底下留言或到我們的粉絲團留言喔!
====================================
fayalab 粉絲團
FB本篇留言版
沒有留言:
張貼留言