?一、開發環境介紹
WIFI模塊: ESP8266 --可選的。直接使用串口有線傳輸給上位機也可以。
上位機: C++(QT) 設計的。 支持PC機電腦、Android手機顯示。
與上位機的傳輸協議: 支持串口傳輸、WIFI網絡傳輸兩種。 如果是PC就可以直接連接串口傳輸數據,如果不方便可以直接通過WIFI---TCP協議傳輸。
二、PulseSensor心率模塊介紹
PulseSensor 是一款用于脈搏心率測量的光電反射式模擬傳感器。
可以將其佩戴于手指、耳垂、手腕等處,通過杜邦線--導線將引腳連接到單片機,可將采集到的模擬信號傳輸給單片機,單片機配置ADC用來轉換為數字信號,再通過單片機簡單計算后就可以得到心率數值;為了方便聯動健康管理系統,也方便自己了解自己的心率,可將脈搏波形通過串口、WIFI等方式上傳到電腦、手機顯示波形,然后根據提前配置的參數,結合算法確定是否正常。
PulseSensor 是一款開源硬件, 目前國外官網上已有其對應的單片機程序,也附帶有對應的上位機Processing 程序, 比較適用于心率方面的科學研究和教學演示,也非常適合用于二次開發;上位機也可以自己開發,根據自己的需求定制,達到自己想要的功能。
傳感器的接口一共 3 個,
其中標有S的為模擬信號輸出線
標有+的為電源輸入線(中間);
標有-的為地線。
總結一下:
S → 脈搏信號輸出(要接單片機 AD 接口)
+ → 5v(或 3.3v)電源輸入
- → GND 地
傳感器的硬件參數介紹:
電路板直徑: 16mm
電路板厚度: 1.2mm
LED 峰值波長: 515nm
供電電壓: 3.3~5v
檢測信號類型:光反射信號(PPG)
輸出信號類型:模擬信號
信號放大倍數: 330 倍
輸出信號大小: 0~VCC
電流大小: ~4ma(5v 下)
傳統的測量方法介紹:
傳統的脈搏測量方法主要有三種:
一是從心電信號中提取;
二是從測量血壓時壓力傳感器測到的波動來計算脈率;
三是光電容積法。前兩種方法提取信號都會限制病人的活動,如果長時間使用會增加病人生理和心理上的不舒適感。而光電容積法脈搏測量作為監護測量中最普遍的方法之一,其具有方法簡單、佩戴方便、可靠性高等特點。
光電容積法的基本原理是利用人體組織在血管搏動時造成透光率不同來進行脈搏測量的。其使用的傳感器由光源和光電變換器兩部分組成,通過綁帶或夾子固定在病人的手指或耳垂上。光源一般采用對動脈血中氧和血紅蛋白有選擇性的一定波長(500nm~700nm)的發光二極管。當光束透過人體外周血管,由于動脈搏動充血容積變化導致這束光的透光率發生改變,此時由光電變換器接收經人體組織反射的光線,轉變為電信號并將其放大和輸出。由于脈搏是隨心臟的搏動而周期性變化的信號,動脈血管容積也周期性變化,因此光電變換器的電信號變化周期就是脈搏率。
根據相關文獻和實驗結果, 560nm 波長左右的波可以反映皮膚淺部微動脈信息,適合用來提取脈搏信號。
本傳感器采用了峰值波長為 515nm 的綠光 LED,型號為 AM2520,而光接收器采用了 APDS-9008, 這是一款環境光感受器,感受峰值波長為 565nm,兩者的峰值波長相近,靈敏度較高。此外,由于脈搏信號的頻帶一般在 0.05~200Hz 之間, 信號幅度均很小,一般在毫伏級水平,容易受到各種信號干擾。在傳感器后面使用了低通濾波器和由運放 MCP6001 構成的放大器,將信號放大了 330 倍,同時采用分壓電阻設置直流偏置電壓為電源電壓的 1/2,使放大后的信號可以很好地被單片機的 AD 采集到。
整個心率傳感器的結構如下圖:
由于傳感器使用的是固定倍數的放大器, 而人體生理信號是微弱信號,細微的差異會導致放大后的信號產生巨大的差別。 所以下圖的示波器顯示的波形只是理想情況下的波形,每個人的實際效果會略有區別。
?三、STM32的控制代碼
STM32的采集代碼比較簡單,因為就只需要配置對應引腳的ADC功能采集即可。
可以采集10次,去掉最大值最小值取平均值,拿到最終結果再傳遞給上位機顯示。
3.1 ADC的配置代碼
/*
函數功能: 初始化ADC1
硬件連接: PA1 --ADC1的通道1
配置的模式:模擬輸入
*/
void ADC1_Init(void)
{
/*1. 配置GPIO口*/
RCC->APB2ENR|=1<<2; //開啟PA時鐘
GPIOA->CRL&=0xFFFFFF0F;
GPIOA->CRL|=0x00000000;
/*2. 配置ADC相關寄存器*/
RCC->APB2ENR|=1<<9;//開啟ADC1時鐘
RCC->APB2RSTR|=1<<9; //開啟ADC1復位時鐘
RCC->APB2RSTR&=~(1<<9);//關閉ADC1復位時鐘
RCC->CFGR&=~(0x3<<14); //清除ADC的時鐘配置
RCC->CFGR|=0x2<<14; //配置6分頻
ADC1->CR2|=1<<20; //開啟外部事件轉換
ADC1->CR2|=0x7<<17; //SW開關方式控制ADC轉換(作為外部事件)
ADC1->SMPR2|=0x7<<3; //配置通道1的采樣時間
ADC1->CR2|=1<<0;//開啟ADC并啟動轉換
ADC1->CR2|=1<<3;//開啟ADC校準初始化
while(ADC1->CR2&1<<3){}//等待初始化完成
ADC1->CR2|=1<<2;//開啟ADC校準
while(ADC1->CR2&1<<2){} //等待ADC校準完成
}
/*
函數功能: 根據傳入的通道編號獲取一次該通道的ADC值
*/
u16 ADC1_GetData(u8 ch)
{
ADC1->SQR3&=0xFFFFFFE0; //0xE0-->11100000 //清除原來的通道編號
ADC1->SQR3|=ch<<0; //配置現在即將轉換的通道號
ADC1->CR2|=1<<22; //開啟一次ADC規則通道轉換
while(!(ADC1->SR&1<<1)){} //等待轉換完成
return ADC1->DR; //讀出ADC的結果值
}
3.2 ESP8?266 WIFI 配置代碼
#include "esp8266.h"
/*
函數功能:向ESP82668266發送命令
函數參數:
cmd:發送的命令字符串
ack:期待的應答結果,如果為空,則表示不需要等待應答
waittime:等待時間(單位:10ms)
返 回 值:
0,發送成功(得到了期待的應答結果)
1,發送失敗
*/
u8 ESP8266_SendCmd(u8 *cmd,u8 *ack,u16 waittime)
{
u8 res=0;
USART3_RX_STA=0;
USART3_RX_CNT=0;
UsartStringSend(USART3,cmd);//發送命令
if(ack&&waittime) //需要等待應答
{
while(--waittime) //等待倒計時
{
DelayMs(10);
if(USART3_RX_STA)//接收到期待的應答結果
{
if(ESP8266_CheckCmd(ack))
{
res=0;
//printf("cmd->ack:%s,%s\r\n",cmd,(u8*)ack);
break;//得到有效數據
}
USART3_RX_STA=0;
USART3_RX_CNT=0;
}
}
if(waittime==0)res=1;
}
return res;
}
/*
函數功能:ESP8266發送命令后,檢測接收到的應答
函數參數:str:期待的應答結果
返 回 值:0,沒有得到期待的應答結果
其他,期待應答結果的位置(str的位置)
*/
u8* ESP8266_CheckCmd(u8 *str)
{
char *strx=0;
if(USART3_RX_STA) //接收到一次數據了
{
USART3_RX_BUF[USART3_RX_CNT]=0;//添加結束符
strx=strstr((const char*)USART3_RX_BUF,(const char*)str); //查找是否應答成功
//printf("RX=%s",USART3_RX_BUF);
}
return (u8*)strx;
}
/*
函數功能:向ESP8266發送指定數據
函數參數:
data:發送的數據(不需要添加回車)
ack:期待的應答結果,如果為空,則表示不需要等待應答
waittime:等待時間(單位:10ms)
返 回 值:0,發送成功(得到了期待的應答結果)luojian
*/
u8 ESP8266_SendData(u8 *data,u8 *ack,u16 waittime)
{
u8 res=0;
USART3_RX_STA=0;
UsartStringSend(USART3,data);//發送數據
if(ack&&waittime) //需要等待應答
{
while(--waittime) //等待倒計時
{
DelayMs(10);
if(USART3_RX_STA)//接收到期待的應答結果
{
if(ESP8266_CheckCmd(ack))break;//得到有效數據
USART3_RX_STA=0;
USART3_RX_CNT=0;
}
}
if(waittime==0)res=1;
}
return res;
}
/*
函數功能:ESP8266退出透傳模式
返 回 值:0,退出成功;
1,退出失敗
*/
u8 ESP8266_QuitTrans(void)
{
while((USART3->SR&0X40)==0); //等待發送空
USART3->DR='+';
DelayMs(15); //大于串口組幀時間(10ms)
while((USART3->SR&0X40)==0); //等待發送空
USART3->DR='+';
DelayMs(15); //大于串口組幀時間(10ms)
while((USART3->SR&0X40)==0); //等待發送空
USART3->DR='+';
DelayMs(500); //等待500ms
return ESP8266_SendCmd("AT\r\n","OK",20);//退出透傳判斷.
}
/*
函數功能:獲取ESP8266模塊的連接狀態
返 回 值:0,未連接;1,連接成功.
*/
u8 ESP8266_ConstaCheck(void)
{
u8 *p;
u8 res;
if(ESP8266_QuitTrans())return 0; //退出透傳
ESP8266_SendCmd("AT+CIPSTATUS\r\n",":",50); //發送AT+CIPSTATUS指令,查詢連接狀態
p=ESP8266_CheckCmd("+CIPSTATUS\r\n:");
res=*p; //得到連接狀態
return res;
}
/*
函數功能:獲取ip地址
函數參數:ipbuf:ip地址輸出緩存區
*/
void ESP8266_GetWanip(u8* ipbuf)
{
u8 *p,*p1;
if(ESP8266_SendCmd("AT+CIFSR\r\n","OK",50))//獲取WAN IP地址失敗
{
ipbuf[0]=0;
return;
}
p=ESP8266_CheckCmd(""");
p1=(u8*)strstr((const char*)(p+1),""");
*p1=0;
sprintf((char*)ipbuf,"%s",p+1);
}
四、QT設計的上位機代碼
4.1 軟件運行效果圖
軟件有兩個版本: 1. 網絡版本 2. 串口版本
網絡版本主要是通過TCP協議傳輸數據顯示,串口版本直接通過串口傳輸。
?
?4.2 widget.cpp代碼
代碼較多,這里就主UI的部分代碼。#include "widget.h"
#include "ui_widget.h"
#define AppFontName "Microsoft YaHei"
#define AppFontSize 9
#define TextColor QColor(255,255,255)
#define Plot_NoColor QColor(0,0,0,0)
//曲線1的顏色
#define HeartRate_Plot_DotColor QColor(236,110,0)
#define HeartRate_Plot_LineColor QColor(246,98,0)
#define HeartRate_Plot_BGColor QColor(246,98,0,80)
//曲線2的顏色
#define HeartRate_Plot_DotColor_2 Qt::blue
#define HeartRate_Plot_LineColor_2 Qt::blue
#define HeartRate_Plot_BGColor_2 Qt::blue
#define TextWidth 1
#define LineWidth 2
#define DotWidth 5
//一個刻度里的小刻度數量--太小的話顯示的時間會重疊
#define HeartRate_Plot_Count 5
//Y軸最大范圍值
#define HeartRate_Plot_MaxY 3000
/*
* 設置QT界面的樣式
*/
void Widget::SetStyle(const QString &qssFile) {
QFile file(qssFile);
if (file.open(QFile::ReadOnly)) {
QString qss = QLatin1String(file.readAll());
qApp->setStyleSheet(qss);
QString PaletteColor = qss.mid(20,7);
qApp->setPalette(QPalette(QColor(PaletteColor)));
file.close();
}
else
{
qApp->setStyleSheet("");
}
}
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
/*服務器線程*/
//開始信號
connect(this,SIGNAL(StartServerThread()),&tcp_server_class,SLOT(run()));
//日志信號
connect(&tcp_server_class,SIGNAL(LogSend(QString)),this,SLOT(Log_Display(QString)));
//移動到線程
tcp_server_class.moveToThread(&tcp_server_thread);
tcp_server_thread.start(); //啟動線程
StartServerThread(); //創建服務器
this->setWindowTitle("萬邦易嵌-健康監控管家");
//波形圖界面初始化
InitForm();
InitPlot();
HeartRate_InitPlot();
HeartRate_LoadPlot();
SetStyle(":/blue.css");
//開始加載數據
plot_timer->start(100);
}
Widget::~Widget()
{
delete ui;
}
//日志顯示
void Widget::Log_Display(QString text)
{
QPlainTextEdit *plainTextEdit_log=ui->plainTextEdit_log;
//設置光標到文本末尾
plainTextEdit_log->moveCursor(QTextCursor::End, QTextCursor::MoveAnchor);
//當文本數量超出一定范圍就清除
if(plainTextEdit_log->toPlainText().size()>1024*4)
{
plainTextEdit_log->clear();
}
plainTextEdit_log->insertPlainText(text);
//移動滾動條到底部
QScrollBar *scrollbar = plainTextEdit_log->verticalScrollBar();
if(scrollbar)
{
scrollbar->setSliderPosition(scrollbar->maximum());
}
}
//查看服務器狀態
void Widget::on_toolButton_server_stat_clicked()
{
QString text="TCP服務器IP地址列表:\n";
QList list = QNetworkInterface::allAddresses();
for(int i=0;isocketDescriptor()==-1)
{
text+="設備未連接\n";
}
else
{
text+="設備連接成功\n";
}
}
else
{
text+="設備未連接\n";
}
text+="數據協議:\n";
text+="A:心電數據1,B:新電數據2,C:運動步數,D:運動距離,E:體表溫度\n";
text+="例如: "A:1633215,B:1833215,C:45,D:28,E:66.55"";
QMessageBox::about(this,"狀態信息",text);
}
//窗口關閉事件
void Widget::closeEvent(QCloseEvent *event)
{
tcp_server_thread.quit();
tcp_server_thread.wait();
}
void Widget::InitForm()
{
//初始化隨機數種子
QTime time = QTime::currentTime();
qsrand(time.msec() + time.second() * 1000);
//初始化動態曲線定時器
plot_timer = new QTimer(this);
connect(plot_timer, SIGNAL(timeout()), this, SLOT(HeartRate_LoadPlot()));
plots.append(ui->plot2);
}
void Widget::InitPlot()
{
//設置縱坐標名稱
plots.at(0)->yAxis->setLabel("心電數據(單位:%)");
//設置縱坐標范圍
plots.at(0)->yAxis->setRange(0, HeartRate_Plot_MaxY);
//設置支持鼠標移動縮放波形界面
plots.at(0)->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom);
//設置背景顏色
#if 1
foreach (QCustomPlot *plot, plots)
{
//設置字體大小
QFont font = QFont(AppFontName, AppFontSize - 2);
plot->legend->setFont(font);
plot->xAxis->setLabelFont(font);
plot->yAxis->setLabelFont(font);
plot->xAxis->setTickLabelFont(font);
plot->yAxis->setTickLabelFont(font);
//設置坐標顏色/坐標名稱顏色
plot->yAxis->setLabelColor(TextColor);
plot->xAxis->setTickLabelColor(TextColor);
plot->yAxis->setTickLabelColor(TextColor);
plot->xAxis->setBasePen(QPen(TextColor, TextWidth));
plot->yAxis->setBasePen(QPen(TextColor, TextWidth));
plot->xAxis->setTickPen(QPen(TextColor, TextWidth));
plot->yAxis->setTickPen(QPen(TextColor, TextWidth));
plot->xAxis->setSubTickPen(QPen(TextColor, TextWidth));
plot->yAxis->setSubTickPen(QPen(TextColor, TextWidth));
//設置畫布背景色
QLinearGradient plotGradient;
plotGradient.setStart(0, 0);
plotGradient.setFinalStop(0, 350);
plotGradient.setColorAt(0, QColor(80, 80, 80));
plotGradient.setColorAt(1, QColor(50, 50, 50));
plot->setBackground(plotGradient);
//設置坐標背景色
QLinearGradient axisRectGradient;
axisRectGradient.setStart(0, 0);
axisRectGradient.setFinalStop(0, 350);
axisRectGradient.setColorAt(0, QColor(80, 80, 80));
axisRectGradient.setColorAt(1, QColor(30, 30, 30));
plot->axisRect()->setBackground(axisRectGradient);
//設置圖例提示位置及背景色
plot->axisRect()->insetLayout()->setInsetAlignment(0, Qt::AlignTop | Qt::AlignRight);
plot->legend->setBrush(QColor(255, 255, 255, 200));
plot->replot();
}
#endif
}
void Widget::HeartRate_InitPlot()
{
plots.at(0)->addGraph();
plots.at(0)->graph(0)->setName("心電數據1");
plots.at(0)->graph(0)->setPen(QPen(HeartRate_Plot_LineColor, LineWidth));
plots.at(0)->graph(0)->setScatterStyle(
QCPScatterStyle(QCPScatterStyle::ssCircle,
QPen(HeartRate_Plot_DotColor, LineWidth),
QBrush(HeartRate_Plot_DotColor), DotWidth));
//設置動態曲線的橫坐標格式及范圍
plots.at(0)->xAxis->setTickLabelType(QCPAxis::ltDateTime);
plots.at(0)->xAxis->setDateTimeFormat("HH:mm:ss");
plots.at(0)->xAxis->setAutoTickStep(true);
plots.at(0)->xAxis->setTickStep(0.5);
plots.at(0)->xAxis->setRange(0, HeartRate_Plot_Count, Qt::AlignRight);
plots.at(0)->addGraph();//相當于添加一條新的曲線
plots.at(0)->graph(1)->setName("心電數據2");
plots.at(0)->graph(1)->setPen(QPen(HeartRate_Plot_LineColor_2, LineWidth));
plots.at(0)->graph(1)->setScatterStyle(
QCPScatterStyle(QCPScatterStyle::ssCircle,
QPen(HeartRate_Plot_DotColor_2, LineWidth),
QBrush(HeartRate_Plot_DotColor_2), DotWidth));
//設置是否需要顯示曲線的圖例說明
foreach (QCustomPlot *plot, plots)
{
plot->legend->setVisible(true);
plot->replot();
}
//得到數據指針
mData_0 = plots.at(0)->graph(0)->data();
mData_1 = plots.at(0)->graph(1)->data();
}
void addToDataBuffer(QCPDataMap *mData,double x, double y)
{
QCPData newData;
newData.key = x;
newData.value = y;
mData->insert(x, newData);
}
//加載曲線數據
void Widget::HeartRate_LoadPlot()
{
int i;
bool flag=false;
for(i=0;i<5;i++)
{
//得到秒單位的時間
HeartRate_plot_key = QDateTime::currentDateTime().toMSecsSinceEpoch() / 1000.0;
//心電數據1
HeartRate_plot_value=uart_queue_data.read_queueA();
if(HeartRate_plot_value>0)
{
flag=true;
addToDataBuffer(mData_0,HeartRate_plot_key,HeartRate_plot_value);
}
//心電數據2
HeartRate_plot_value=uart_queue_data.read_queueB();
if(HeartRate_plot_value>0)
{
flag=true;
addToDataBuffer(mData_1,HeartRate_plot_key,HeartRate_plot_value);
}
}
if(flag)
{
plots.at(0)->xAxis->setRange(HeartRate_plot_key, HeartRate_Plot_Count , Qt::AlignRight);
plots.at(0)->rescaleAxes(false); //設置圖表完全可見
plots.at(0)->replot();
}
/*
A:心電數據1,B:新電數據2,C:運動步數,D:運動距離,E:體表溫度
例如: "A:1633215,B:1833215,C:45,D:28,E:66.55"
*/
int val=uart_queue_data.read_queueC();
if(val>0)
{
ui->lcdNumber_bumber->display(val);
}
val=uart_queue_data.read_queueD();
if(val>0)
{
ui->lcdNumber_len->display(val);
}
double tmp_val=uart_queue_data.read_queueE();
if(tmp_val>0)
{
ui->lcdNumber_temp->display(tmp_val);
}
}
void Widget::on_toolButton_src_data_clicked()
{
ui->stackedWidget->setCurrentIndex(0);
}
void Widget::on_toolButton_image_data_clicked()
{
ui->stackedWidget->setCurrentIndex(1);
}
void Widget::on_toolButton_clear_clicked()
{
mData_0->clear();
mData_1->clear();
}
void Widget::on_commandLinkButton_clicked()
{
QDesktopServices::openUrl(QUrl("https://blog.csdn.net/xiaolong1126626497/article/details/116694318"));
}
();i++)>
審核編輯:湯梓紅
-
傳感器
+關注
關注
2548文章
50740瀏覽量
752140 -
STM32
+關注
關注
2266文章
10876瀏覽量
354925 -
檢測儀
+關注
關注
5文章
4073瀏覽量
42213
發布評論請先 登錄
相關推薦
評論