心跳率是監(jiān)測(cè)任何人健康狀況的最重要參數(shù)。在可穿戴設(shè)備的現(xiàn)代時(shí)代,有很多設(shè)備可以測(cè)量心跳、血壓、腳步聲、燃燒的卡路里和許多其他東西。這些設(shè)備內(nèi)部有脈沖傳感器來感應(yīng)脈搏率。今天,我們還將使用帶有PIC微控制器的脈沖傳感器來計(jì)算每分鐘的心跳次數(shù)和心跳間隔,這些值將進(jìn)一步顯示在16x2字符LCD上。我們將在本項(xiàng)目中使用PIC16F877A PIC微控制器。我們已經(jīng)將脈沖傳感器與Arduino連接起來,用于患者監(jiān)測(cè)系統(tǒng)。
必需組件
PIC16F877A 微控制器
20 兆赫晶體
33pF電容 2個(gè)
4.7k 電阻器 1 個(gè)
16x2 字符液晶顯示器
10K 電位器,用于 LCD 的對(duì)比度控制
SEN-11574 脈沖傳感器
魔術(shù)貼帶
5V電源適配器
面包板和連接線
脈沖傳感器 SEN-11574
為了測(cè)量心跳,我們需要一個(gè)脈搏傳感器。在這里,我們選擇了SEN-11574脈沖傳感器,它可以在網(wǎng)上或線下商店輕松買到。我們使用此傳感器是因?yàn)橹圃焐烫峁┝耸纠a,但那是Arduino代碼。我們?yōu)镻IC微控制器轉(zhuǎn)換了該代碼。
該傳感器非常小,非常適合通過耳垂或指尖讀取心跳。它的直徑為 0.625“,圓形 PCB 側(cè)的厚度為 0.125”。
該傳感器提供模擬信號(hào),傳感器可由3V或5V驅(qū)動(dòng),傳感器的電流消耗為4 mA,非常適合移動(dòng)應(yīng)用。傳感器配有三根電線,末端帶有 24 英寸長(zhǎng)的連接電纜和 berg 公接頭。此外,傳感器還配有魔術(shù)貼指帶,可將其佩戴在指尖上。
脈沖傳感器原理圖也由制造商提供,也可在 sparkfun.com 上購(gòu)買。
傳感器原理圖由光學(xué)心率傳感器、降噪RC電路或濾波器組成,如原理圖所示。R2、C2、C1、C3 和運(yùn)算放大器 MCP6001 用于可靠的放大模擬輸出。
用于心跳監(jiān)測(cè)的其他傳感器很少,但SEN-11574脈沖傳感器廣泛用于電子項(xiàng)目。
脈沖傳感器與PIC微控制器接口的電路圖
在這里,我們已經(jīng)連接了脈沖傳感器跨 2德·微控制器的引腳單位。由于傳感器提供模擬數(shù)據(jù),我們需要通過進(jìn)行必要的計(jì)算將模擬數(shù)據(jù)轉(zhuǎn)換為數(shù)字信號(hào)。
20Mhz的晶體振蕩器通過兩個(gè)陶瓷33pF電容器連接在微控制器單元的兩個(gè)OSC引腳上。液晶屏通過微控制器的RB端口連接。
PIC16F877A 心跳監(jiān)護(hù)儀代碼說明
對(duì)于初學(xué)者來說,代碼有點(diǎn)復(fù)雜。制造商提供了SEN-11574傳感器的示例代碼,但它是為Arduino平臺(tái)編寫的。我們需要轉(zhuǎn)換微芯片 PIC16F877A 的計(jì)算結(jié)果。本項(xiàng)目結(jié)束時(shí)將提供完整的代碼,并附有演示視頻。并且可以從此處下載支持的C文件。
我們的代碼流相對(duì)簡(jiǎn)單,我們使用開關(guān)大小寫來執(zhí)行步驟。根據(jù)制造商的說法,我們需要每 2 毫秒從傳感器獲取數(shù)據(jù)。因此,我們使用了計(jì)時(shí)器中斷服務(wù)例程,它將每 2 毫秒觸發(fā)一個(gè)函數(shù)。
switch語句中的代碼流將如下所示:
案例1:讀取ADC
案例2:計(jì)算心跳和IBI
情況 3:在液晶屏上顯示心跳和 IBI
案例 4:空閑(不執(zhí)行任何操作)
在定時(shí)器中斷功能中,我們將程序的狀態(tài)更改為情況 1:每 2 毫秒讀取一次 ADC。
因此,在 main 函數(shù)中,我們定義了程序狀態(tài)和所有開關(guān)情況。
void main() {
system_init();
main_state = READ_ADC;
while (1) {
switch (main_state) {
case READ_ADC:
{
adc_value = ADC_Read(0); // 0 is the channel number
main_state = CALCULATE_HEART_BEAT;
break;
}
case CALCULATE_HEART_BEAT:
{
calculate_heart_beat(adc_value);
main_state = SHOW_HEART_BEAT;
break;
}
case SHOW_HEART_BEAT:
{
if (QS == true) { // A Heartbeat Was Found
// BPM and IBI have been Determined
// Quantified Self "QS" true when Arduino finds a heartbeat
QS = false; // reset the Quantified Self flag for next time
// 0.9 used for getting better data. actually should not be used
BPM = BPM * 0.9;
IBI = IBI / 0.9;
lcd_com(0x80);
lcd_puts("BPM:- ");
lcd_print_number(BPM);
lcd_com(0xC0);
lcd_puts("I.B.I:- ");
lcd_print_number(IBI);
}
}
main_state = IDLE;
break;
case IDLE:
{
break;
}
default:
{
}
}
}
}
我們使用 PIC16F877A 的兩個(gè)硬件外設(shè):定時(shí)器0 和 ADC。
在 timer0.c 文件中,
TMR0 = (uint8_t)(tmr0_mask & (256-(((2 *_XTAL_FREQ)/(256*4))/1000)));
此計(jì)算提供 2 毫秒計(jì)時(shí)器中斷。計(jì)算公式為
// TimerCountMax - (((delay(ms) * Focs(hz)) / (PreScale_Val * 4)) / 1000)
如果我們看到timer_isr函數(shù),它是-
void timer_isr() {
main_state = READ_ADC;
}
在此函數(shù)中,程序狀態(tài)每 2ms 更改為 READ_ADC。
然后,CALCULATE_HEART_BEAT函數(shù)取自 Arduino 示例代碼。
void calculate_heart_beat(int adc_value) {
Signal = adc_value;
sampleCounter += 2; // keep track of the time in mS with this variable
int N = sampleCounter - lastBeatTime; // monitor the time since the last beat to avoid noise
// find the peak and trough of the pulse wave
if (Signal < thresh && N > (IBI / 5)*3) { // avoid dichrotic noise by waiting 3/5 of last IBI
if (Signal < T) { // T is the trough
T = Signal; // keep track of lowest point in pulse wave
}
}
………….
………………………..
此外,下面給出了完整的代碼,并通過注釋進(jìn)行了很好的解釋。這種心跳傳感器數(shù)據(jù)可以進(jìn)一步上傳到云端,并從任何地方通過互聯(lián)網(wǎng)進(jìn)行監(jiān)控,從而使其成為基于物聯(lián)網(wǎng)的心跳監(jiān)測(cè)系統(tǒng)。
/*
* File: main.c
* Author: Sourav Gupta
* By:- circuitdigest.com
* Created on September 30, 2018, 2:26 PM
*/
// PIC16F877A Configuration Bit Settings
// 'C' source line config statements
// CONFIG
#pragma config FOSC = HS // Oscillator Selection bits (HS oscillator)
#pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF // Power-up Timer Enable bit (PWRT disabled)
#pragma config BOREN = ON // Brown-out Reset Enable bit (BOR enabled)
#pragma config LVP = OFF // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3/PGM pin has PGM function; low-voltage programming enabled)
#pragma config CPD = OFF // Data EEPROM Memory Code Protection bit (Data EEPROM code protection off)
#pragma config WRT = OFF // Flash Program Memory Write Enable bits (Write protection off; all program memory may be written to by EECON control)
#pragma config CP = OFF // Flash Program Memory Code Protection bit (Code protection off)
#include
#include
#include
#include
#include
#include "supporing_cfilelcd.h"
#include "supporing_cfileeusart1.h"
#include "supporing_cfileadc.h"
#include "supporing_cfiletmr0.h"
/*
Hardware related definition
*/
#define _XTAL_FREQ 200000000 //Crystal Frequency, used in delay
/*
Program Flow related definition
*/
#define READ_ADC 1
#define CALCULATE_HEART_BEAT 2
#define SHOW_HEART_BEAT 3
#define IDLE 0
#define DEFAULT -1
volatile int rate[10]; // array to hold last ten IBI values
volatile unsigned long sampleCounter = 0; // used to determine pulse timing
volatile unsigned long lastBeatTime = 0; // used to find IBI
volatile int P = 512; // used to find peak in pulse wave, seeded
volatile int T = 512; // used to find trough in pulse wave, seeded
volatile int thresh = 530; // used to find instant moment of heart beat, seeded
volatile int amp = 0; // used to hold amplitude of pulse waveform, seeded
volatile bool firstBeat = true; // used to seed rate array so we startup with reasonable BPM
volatile bool secondBeat = false; // used to seed rate array so we startup with reasonable BPM
volatile int BPM; // int that holds raw Analog in 0. updated every 2mS
volatile int Signal; // holds the incoming raw data
volatile int IBI = 600; // int that holds the time interval between beats! Must be seeded!
volatile bool Pulse = false; // "True" when User's live heartbeat is detected. "False" when not a "live beat".
volatile bool QS = false; // becomes true when finds a beat.
int main_state = -1;
int adc_value = 0;
int tune = 0;
/*
Other Specific definition
*/
void system_init(void);
void calculate_heart_beat(int adc_value) {
Signal = adc_value;
sampleCounter += 2; // keep track of the time in mS with this variable
int N = sampleCounter - lastBeatTime; // monitor the time since the last beat to avoid noise
// find the peak and trough of the pulse wave
if (Signal < thresh && N > (IBI / 5)*3) { // avoid dichrotic noise by waiting 3/5 of last IBI
if (Signal < T) { // T is the trough?
T = Signal; // keep track of lowest point in pulse wave
}
}
if (Signal > thresh && Signal > P) { // thresh condition helps avoid noise
P = Signal; // P is the peak
} // keep track of highest point in pulse wave
// NOW IT'S TIME TO LOOK FOR THE HEART BEAT
// signal surges up in value every time there is a pulse
if (N > 250) { // avoid high frequency noise
if ((Signal > thresh) && (Pulse == false) && (N > (IBI / 5)*3)) {
Pulse = true; // set the Pulse flag when we think there is a pulse
IBI = sampleCounter - lastBeatTime; // measure time between beats in mS
lastBeatTime = sampleCounter; // keep track of time for next pulse
if (secondBeat) { // if this is the second beat, if secondBeat == TRUE
secondBeat = false; // clear secondBeat flag
int i;
for (i = 0; i <= 9; i++) { // seed the running total to get a realisitic BPM at startup?
rate[i] = IBI;
}
}
if (firstBeat) { // if it's the first time we found a beat, if firstBeat == TRUE
firstBeat = false; // clear firstBeat flag
secondBeat = true; // set the second beat flag
//pulse_tmr_handle = bsp_harmony_start_tmr_cb_periodic(PULSE_CHECK_TIME_INTERVAL, 0, pulse_read_cb); // enable interrupts again
return; // IBI value is unreliable so discard it
}
// keep a running total of the last 10 IBI values
uint16_t runningTotal = 0; // clear the runningTotal variable
int i;
for (i = 0; i <= 8; i++) { // shift data in the rate array?
rate[i] = rate[i + 1]; // and drop the oldest IBI value
runningTotal += rate[i]; // add up the 9 oldest IBI values
}
rate[9] = IBI; // add the latest IBI to the rate array
runningTotal += rate[9]; // add the latest IBI to runningTotal
runningTotal /= 10; // average the last 10 IBI values
BPM = 60000 / runningTotal; // how many beats can fit into a minute? that's BPM!
QS = true; // set Quantified Self flag
// QS FLAG IS NOT CLEARED INSIDE THIS ISR
}
}
if (Signal < thresh && Pulse == true) { // when the values are going down, the beat is over?
Pulse = false; // reset the Pulse flag so we can do it again
amp = P - T; // get amplitude of the pulse wave
thresh = amp / 2 + T; // set thresh at 50% of the amplitude
P = thresh; // reset these for next time
T = thresh;
}
if (N > 2500) { // if 2.5 seconds go by without a beat
thresh = 530; // set thresh default
P = 512; // set P default
T = 512; // set T default
lastBeatTime = sampleCounter; // bring the lastBeatTime up to date
firstBeat = true; // set these to avoid noise
secondBeat = false; // when we get the heartbeat back
}
}
void main() {
system_init();
main_state = READ_ADC;
while (1) {
switch (main_state) {
case READ_ADC:
{
adc_value = ADC_Read(0);
main_state = CALCULATE_HEART_BEAT;
break;
}
case CALCULATE_HEART_BEAT:
{
calculate_heart_beat(adc_value);
main_state = SHOW_HEART_BEAT;
break;
}
case SHOW_HEART_BEAT:
{
if (QS == true) { // A Heartbeat Was Found
// BPM and IBI have been Determined
// Quantified Self "QS" true when arduino finds a heartbeat
QS = false; // reset the Quantified Self flag for next time
// 0.9 used for getting better data. actually should not be used
//BPM = BPM * 0.9;
// IBI = IBI / 0.9;
//IBI = IBI * 2;
// tune = BPM / 2;
//lcd_com(0x01);
lcd_com(0x80);
lcd_puts("BPM:- ");
lcd_print_number(BPM);
lcd_puts (" ");
lcd_com(0xC0);
lcd_puts("I.B.I:- ");
lcd_print_number(IBI);
lcd_puts (" ");
}
}
main_state = IDLE;
break;
case IDLE:
{
break;
}
default:
{
}
}
}
}
/*
This Function is for system initializations.
*/
void system_init(void){
TRISB = 0x00;
lcd_init(); // This will initialize the lcd
TMR0_Initialize();
TMR0_StartTimer();
INTERRUPT_GlobalInterruptEnable();
INTERRUPT_PeripheralInterruptEnable();
ADC_Init();
}
/*
* Custom timer callback function
*/
void timer_isr() {
main_state = READ_ADC;
}
void interrupt INTERRUPT_InterruptManager (void)
{
// interrupt handler
if(INTCONbits.TMR0IE == 1 && INTCONbits.TMR0IF == 1)
{
TMR0_ISR();
}
}
-
微控制器
+關(guān)注
關(guān)注
48文章
7454瀏覽量
150853 -
傳感器
+關(guān)注
關(guān)注
2545文章
50445瀏覽量
751049 -
PIC
+關(guān)注
關(guān)注
8文章
507瀏覽量
87427
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論