久久久久久久999_99精品久久精品一区二区爱城_成人欧美一区二区三区在线播放_国产精品日本一区二区不卡视频_国产午夜视频_欧美精品在线观看免费

 找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

搜索
查看: 27599|回復: 24
打印 上一主題 下一主題
收起左側(cè)

STM32三導聯(lián)藍牙心電監(jiān)護儀設(shè)計,C#上位機顯示波形 附源碼與電路

  [復制鏈接]
跳轉(zhuǎn)到指定樓層
樓主
ID:402025 發(fā)表于 2018-9-25 10:05 | 只看該作者 回帖獎勵 |倒序瀏覽 |閱讀模式
題  目:  基于stm32的無線藍牙心電監(jiān)護儀

視頻:


摘要
   近些年,隨著物質(zhì)生活水平的提高,人們開始逐漸關(guān)注自身健康,隨之而來的就是市場上出現(xiàn)了各種各樣的健康相關(guān)的電子消費品。心電圖信號(ECG)是人體健康相關(guān)的一項很重要的參數(shù),通過對ECG信號的分析可以找出人體出現(xiàn)的各類心臟疾病,同時通過ECG信號還可以對健康人群起到監(jiān)護預防作用,近些年市場上出現(xiàn)比較多的此類電子消費品。但是市場上的心電監(jiān)測設(shè)備存在價格比較昂貴,同時在質(zhì)量上也不能得到保證。本設(shè)計旨在設(shè)計出一種價格相對低廉,但同具有較高的測量精度的心電信號檢測系統(tǒng),該系統(tǒng)同時具有測量信號分析信號存儲信號的功能,同時在使用上便捷簡單。本設(shè)計使用AD8232作為模擬前端來采集放大處理人體的心電信號,使用STM32F103VET6作為主控芯片來對模擬前端的信號進行進一步濾波處理,最終使用上位機將處理后的信號進行分析保存,在硬件端可以通過簡單的按鍵來控制檢測系統(tǒng)的使用,并且通過OLED顯示屏來顯示相應(yīng)的數(shù)據(jù)。本系統(tǒng)實現(xiàn)了硬件與上位機配合使用的方式,當然也可以僅僅使用硬件端做簡單的信息采集,正文部分從內(nèi)容上細分為硬件配置和軟件設(shè)計兩部分。硬件部分詳細論證了各模塊的實現(xiàn)原理、器件選型和參數(shù)配置,軟件部分仔細分析了系統(tǒng)功能的實現(xiàn)邏輯、控制思路及算法流程。系統(tǒng)使用STM32F103VET6作為最主要的處理單元,可以對信號進行快速的濾波處理,保證信號的實時上傳,使用AD采樣配合DMA傳輸,可以快速的將信號采集出來,同時模擬前端自身具有的信號放大以及濾波的能力可以更好的保證信號的精度。經(jīng)過多次的實際測量以及對多人進行心電數(shù)據(jù)采樣后,分析得出該系統(tǒng)在信號的質(zhì)量以及精度上能得到很好的保證,同時也做到了使用便捷,具有較高的實際價值。

1.        引言
伴隨著人口老齡化問題的日趨嚴重,心臟類疾病的發(fā)病率越來越高,人們對于心電監(jiān)護類消費品的需求逐漸加大,心電監(jiān)護儀是一種新一代的醫(yī)用電子儀器用戶在家中使用它就可以完成心電圖的測量。它與監(jiān)護診斷儀器不同,心電監(jiān)護儀必須長時間實時監(jiān)護病人的心電圖,才能檢測出心率變化趨勢,保存失常的心電圖信號,為醫(yī)務(wù)人員提供治療的參考依據(jù),起到緩解并移除病情的作用。
市面上普遍存在的心率測量方式為通過光線照射毛細管來檢測心率,雖然這種方式可以有效的測量出人的心率,但無法提供心電圖形的測量。另一方面現(xiàn)階段市場上很多能買到心率儀器普遍價格高昂,不是很能被大眾接受,但同時那些廉價的心率儀存在質(zhì)量差,信號測不準,數(shù)據(jù)不準確等多種問題,因此本設(shè)計可以提供一種價格便宜數(shù)據(jù)準確且同時具有較強的數(shù)據(jù)分析功能的心電信號采集系統(tǒng)。
在本設(shè)計中,心電信號的采集系統(tǒng)主要分為模擬前端對數(shù)據(jù)的采集,微處理器對信號進行采樣濾波上傳,上位機部分對心率、心電波形的計算以及存儲。在本設(shè)計中模擬前端采用ADI公司的一款用于心電以及其它生物電測量的集成信號調(diào)理芯片AD8232,該芯片集成了儀表放大器、增益放大器、右腿驅(qū)動電路還具有導聯(lián)脫落檢測功能可實現(xiàn)高共模抑制能力。本設(shè)計采用AD8232芯片作為信號采集模塊的模擬前端,用于對人體心電信號的采集。
在考慮到本設(shè)計需要較強的信號處理能力因此微處理器采用的是STM32F103VET6,使用該芯片的原因是應(yīng)為該處理器兼具了較強性能以及便宜的價格,該芯片主要用于對數(shù)據(jù)采樣,并且進行IIR濾波將處理好的數(shù)據(jù)通過串口上傳到上位機,同時還可以將波形數(shù)據(jù)通過顯示模塊顯示出來。本設(shè)計最后一部分為上位機,上位機使用C#語言編寫,主要實現(xiàn)串口讀取、數(shù)據(jù)顯示、數(shù)據(jù)處理保存等功能。基于以上的分析本設(shè)計采用AD8232與STM32F103VET6的搭配設(shè)是符合設(shè)計要求的。
2.        系統(tǒng)方案
本設(shè)計詳細分析了各種MCU包括STC89C52、STM32F103VET6、STM32F407、MSP430等多種MCU之間的優(yōu)缺點,同時分析了市場上使用的ADS1298,AD8232,ADAS1000三種ECG前端方案進行了分析,包括性能,價格等多個點進行綜合分析。最終選擇了STM32F103和AD8232配合的方案作為本設(shè)計的主要方案,該組合同時保證了性能與較經(jīng)濟的價格。同時在藍牙模塊的選擇上使用了常用的HC-05藍牙芯片來用于與上位機的通信。在數(shù)據(jù)的顯示上最終選擇了SSD1306 OLED來實現(xiàn)本設(shè)計硬件端波形顯示以及相關(guān)數(shù)據(jù)顯示功能。為了保證本設(shè)計的獨立供電使用了XL6009 DC-DC升壓模塊來將3.7V鋰電池電壓提升到5V來為系統(tǒng)供電。在本設(shè)計中模擬前端AD8232作為最主要的信號采集前端,在本文后將做詳細的介紹,同樣作為MCU使用的STM32F103VET6也要做詳細的討論。作為顯示的OLED顯示屏由于是最直接的顯示系統(tǒng)的顯示效果他的選擇也將會影響到用戶體驗,因此本文也將討論多種現(xiàn)有的方案并在其中選擇最佳的方案。
3.        系統(tǒng)硬件設(shè)計
3.1 系統(tǒng)原理框圖設(shè)計
    本次設(shè)計的基于STM32的心電信號采集系統(tǒng)由四個模塊組成:STM32F103VET6主控模塊、OLED顯示屏模塊,藍牙模塊,AD8232模擬前端模塊。在本設(shè)計中首先通過三路導聯(lián)獲取人體的心電信號,三路導聯(lián)線分別與人體的左胸、右胸、右腿連接。然后通過將信號輸入到AD8232模擬前端中進行信號放大,將信號放大1100倍后通過模擬輸出將信號傳輸?shù)絊TM32的AD端口,STM32通過12位AD將模擬信號轉(zhuǎn)換為數(shù)字信號,然后通過程序?qū)崿F(xiàn)的高通濾波以及50HZ陷波器對信號進行濾波處理,處理完成后將信號通過與STM32串口連接的藍牙模塊傳輸?shù)缴衔粰C中,同時信號波形顯示在與STM32連接的OLED顯示模塊上并計算出此時人體的心率數(shù)據(jù),當上位機接收到信號后對信號進行SG平滑濾波,最終處理完的信號可以通過上位機顯示出波形,計算相關(guān)的心率參數(shù)并且能夠保存在文件中。本設(shè)計的基本框圖如圖3-1所示。

系統(tǒng)總體原理框圖

3.2 系統(tǒng)主要元器件介紹
3.2.1 STM32F103VET6主控芯片
MCU芯片選用的是意法半導體公司的STM32F103VET6。該芯片的具有ARM 32位 Cortex-M3 CPU,片上128k Flash,20kRAM,12位精度的片內(nèi)ADC轉(zhuǎn)換器,以及串口等外設(shè)。Cortex-M3 采用 ARM V7 構(gòu)架,不僅支持 Thumb-2 指令集,而且擁有很多新特性。較之ARM7 TDMI,Cortex-M3 擁有更強勁的性能。STM32擁有超多的外設(shè)并且具有極高的集成度。同時STM32有杰出的功耗控制。STM32各個外設(shè)都配置了獨立時鐘開關(guān),可以通過關(guān)閉相應(yīng)外設(shè)的時鐘來降低功耗。對于學習與設(shè)計來說STM32有極低的開發(fā)成本,STM32程序仿真過程十分簡單可以通過串口直接下載程序,同時配合JTAG仿真器可以方便的實時仿真調(diào)試代碼。
本設(shè)計使用的STM32F103VET6擁有72MHZ工作頻率、256k閃存(FLASH)空間、高達64K的SRAM,同時支持定時器、ADC、SPI、USB、IIC和UART等多種外設(shè),可以在簡單的電路上完成信號采集與數(shù)據(jù)濾波等運算,圖3-2為STM32F103VET6內(nèi)部資源圖。模擬信號的采樣使用了ADC1的第10通道,兩次采樣率為250HZ,轉(zhuǎn)換精度為12bit,ADC的觸發(fā)時鐘配置為TIM2輸出,通過DMA方式進行外設(shè)到內(nèi)存數(shù)據(jù)傳輸,采用DMA乒乓結(jié)構(gòu)進行數(shù)據(jù)存儲和處理。同時使用STM32F103VET6實現(xiàn)了0.3HZ高通濾波器以及50HZ數(shù)字陷波器。在數(shù)據(jù)上傳方面使用STM32的串口1將數(shù)據(jù)發(fā)送到上位機。
圖3-2 STM32F103內(nèi)部資源圖

     在本設(shè)計中,使用STM32F103VET6作為主控制器,使用STM32F103VET6的AD端口來讀取模擬前端的信號,使用UART1和藍牙模塊連接后實現(xiàn)數(shù)據(jù)的上傳,同時OLED模塊使用5個IO口與STM32F103VET6連接后通過STM32F103VET6的控制實現(xiàn)波形以及其他數(shù)據(jù)的顯示。通過其他的IO口還可以實現(xiàn)按鍵與LED燈顯示。
圖3-3 STM32F103VET6引腳配置圖

本設(shè)計總共使用了STM32F103VET6 100根引腳中的15根,其引腳配置圖如圖3-3所示,其中各引腳功能如表3-1所示:

表3-1 STM32F103VET6引腳配置功能
接口名稱
接口功能
NRST
系統(tǒng)復位
PB3
OLED模塊CS口
PB4
OLED模塊DC口
PB5
OLED模塊RST口
PB6
OLED模塊DIN口
PB7
OLED模塊CLK口
PCO
ADC1的通道10模擬前端信號輸入
PA10
UART1_RX
PA9
UART1_TX
PB0
LED1
PC3
LED2
PC4
LED3
PA0
按鍵1
PC13
按鍵2
PA1
按鍵3

3.2.2 AD8232模擬前端芯片
              AD8232是一款用于心電信號測量及其他生物電測量的集成信號調(diào)理模塊。該芯片可以在有運動或遠程電極放置產(chǎn)生的噪聲的情況下提取、放大及過濾微弱的生物電信號。該芯片使得模數(shù)轉(zhuǎn)換器(ADC)或嵌入式微控制器(MCU)能夠便捷的采集輸出信號,AD8232模擬前端模塊由AD8232芯片和輔助電路構(gòu)成,由于心電信號的頻率范圍為0.5~100 HZ,幅度范圍為0~4 mV,屬于低頻微弱小信號。同時心電信號中混雜著諸多干擾,如肌電噪聲、工頻干擾、基線漂移以及運動偽跡等,所以心電信號采集模塊需在有效提取出微弱的心電信號的同時將對各種噪聲起到最大的抑制。心電信號的前端放大模塊由AD8232以及外圍電路構(gòu)成,實現(xiàn)了心電信號的輸出,圖3-4為AD8232內(nèi)部濾波器結(jié)構(gòu)圖。

圖3-4 AD8232內(nèi)部結(jié)構(gòu)圖
3.2.3 SSD1306 OLED顯示模塊
OLED顯示屏模塊128*64 OLED顯示屏組成,該OLED使用SSD1306作為主控芯片,與MCU采用IIC接口通信,OLED無需背光、顯示單元能自發(fā)光具有很好的顯示效果。OLED顯示模塊一共有7個管腳,其中兩個為電源VCC與GND,一個為復位口,還有四個管腳為OLED模塊的控制管腳,使用四線串行模式來進行圖像的顯示,圖3-5為SSD1306內(nèi)部結(jié)構(gòu)圖。
圖3-5 SSD1306內(nèi)部結(jié)構(gòu)圖
3.2.4 HC-05藍牙模塊

    考慮到心電信號采集系統(tǒng)的便攜性,因此本設(shè)計使用無線的方式來進行數(shù)據(jù)的傳輸,經(jīng)過多種藍牙模塊的比較最終選擇HC-05藍牙模塊,HC-05是一款高性能的主從一體藍牙串口模塊,可以和各種自帶藍牙功能的外部設(shè)備連接包括手機、電腦等職能終端設(shè)備,HC-05藍牙模塊支持的通信頻率為4800~1382400,并且可以同時兼容5V或3.3V單片機系統(tǒng),在本設(shè)計中能夠方便的通過TX、RX接口與STM32F103VET6連接。本模塊使用非常靈活、方便。本設(shè)計中電腦通過與藍牙模塊配對連接后可以在電腦上實現(xiàn)COM口從而實現(xiàn)串口數(shù)據(jù)傳輸。藍牙模塊設(shè)置波特率為115200,同時通過AT指令將工作模式配置為從機模式以便于PC連接。

圖3-6  HC-05藍牙模塊引腳圖
3.2.5 XL6009 DC-DC電源模塊
XL6009 是一款使用開關(guān)電流的升壓模塊。該模塊使用XL6009E1為核心芯片,性能遠超LM2577。XL6009升壓模塊成本低,性能更卓越,XL6009擁有超寬輸入電壓3V~32V,最佳工作電壓范圍是5~32V,同時內(nèi)置4A高效MOSFET開關(guān)管,電源的轉(zhuǎn)換效率高達94%,XL6009還具有400KHz超高開關(guān)頻率,使用小容量的濾波電容即能達到非常好的效果,紋波更小,因此能夠為硬件系統(tǒng)提供穩(wěn)定可靠的供電,圖3-7為內(nèi)部結(jié)構(gòu)圖。
圖3-7  XL6009芯片內(nèi)部結(jié)構(gòu)圖

3.3 系統(tǒng)模塊電路設(shè)計
3.3.1 AD8232模擬前端電路設(shè)計
心電信號的頻率范圍為0.5~100 Hz,幅度范圍為0~4 mV,屬于低頻微弱小信號。同時心電信號中混雜著諸多干擾,如肌電噪聲、工頻干擾、基線漂移以及運動偽跡等,所以心電信號采集模塊需在有效提取出微弱的心電信號的同時將對各種噪聲起到最大的抑制。因為STM32的AD檢測電壓為0-3.3V所以需要對原始信號進行1100倍的放大,同時由于原始信號中有較多低頻干擾所以需要對信號進行0.5Hz高通濾波。心電信號的前端放大模塊由AD8232以及外圍電路構(gòu)成,實現(xiàn)了模塊模擬輸出心電信號和導聯(lián)脫落檢測功能。在AD8232與右腿連接的導聯(lián)接口圖3-8中U22處在輸入端具有1nF柵極電容和499kΩ電阻。這在每個輸入端上構(gòu)成一個簡單的RC濾波器來實現(xiàn)低通濾波器,無需增加外部元件便可降低高頻時的整流,在左右胸的導聯(lián)端圖3-8中U44、U42中也使用同樣的方式來獲得更好的輸入信號。
為了保證輸出信號的質(zhì)量還可向隔直電路中添加額外的節(jié)點,以進一步抑制低頻信號,即圖3-8中HPSENSE、HPDRIVE、IAOUT、SW。該電路拓撲結(jié)構(gòu)的另一個好處是,它允許利用較小的R和C值來提供較小的截止頻率,并且可使用圖3-8中電阻R77 來控制濾波器的Q,從而實現(xiàn)窄帶帶通濾波(針對心率監(jiān)測)或最大帶通平坦度(針對心臟監(jiān)護)。在這種拓撲結(jié)構(gòu)中,濾波器衰減在極低頻率下會變回單極點滾降。由于初始滾降為40 dB/十倍頻程,因此變回20 dB/十倍頻程對濾波器的帶外低頻信號抑制性能影響不大。根據(jù)
R75=R76=10MΩ ,C24=C2=0.22UF ,R77=0.14×R75計算得到截止頻率為:
                                               (3-1)
圖3-4所示的儀表放大器由兩個匹配良好的跨導放大器(GM1和GM2)、隔直放大器(HPA)和一個由C1和一個運算放大器構(gòu)成的積分器組成。跨導放大器GM1產(chǎn)生一個與其輸入電壓成比例的電流。達到反饋要求時,跨導放大器GM2的輸入端上即會出現(xiàn)大小相等的電壓,從而匹配GM1產(chǎn)生的電流。這種差異會產(chǎn)生誤差電流,該電流由電容C1進行積分。所得到的電壓出現(xiàn)在儀表放大器的輸出端。該放大器的反饋由GM2通過兩個獨立的路徑施加:兩個電阻對輸出信號進行分頻,以設(shè)置100的總增益,而隔直放大器則對與基準電平的任何偏差進行積分。因此,GM1輸入端上高達±300 mV的直流失調(diào)會以相同幅度但相位相反的方式出現(xiàn)在GM2的輸入端,始終不會導致目標信號出現(xiàn)飽和。為了獲得失真最小的ECG波形,AD8232配置為使用一個0.5 Hz雙極點高通濾波器,后接一個雙極點、40 Hz、低通濾波器。除40 Hz濾波功能以外,運算放大器級的增益還配置為11,因此系統(tǒng)總增益為11×100=1100倍。
圖3-8  AD8232模擬前端模塊電路圖
3.2.2 按鍵控制以及LED電路設(shè)計

    本設(shè)計需要通過三個按鍵來實現(xiàn)硬件端的模式選擇,以及相關(guān)功能的切換,使用實體按鍵可以做到操作方便可靠,可以通過簡單的顯示引導即可讓用戶知道該如何操作硬件,將STM32F103VET6的PA0、PA1、PC13配置成GPIO_INPUT模式來實現(xiàn)按鍵檢測,按鍵通過一個上拉電阻與STM32F103VET6的IO口連接。同樣為了使用戶很清晰的知道硬件系統(tǒng)工作在什么模式,通過三個LED燈的亮滅來實時的呈現(xiàn)硬件系統(tǒng)的兩種工作模式以及是否在檢測ECG數(shù)據(jù),將STM32F103VET6的PB0、PC3、PC4配置成GPIO_OUTPUT模式,同時將IO與一個510R的限流電阻串聯(lián)連接到STM32F103的IO口上。當IO口輸出低電平時LED亮,當IO口輸出高電平時LED熄滅,圖3-9與圖3-10為按鍵與LED連接引腳圖。

圖3-9 按鍵電路

圖3-10 LED燈電路

3.3.3 OLED顯示模塊電路設(shè)計

OLED顯示模塊通過四線串行模式與STM32F103VET6連接,一共需要5根數(shù)據(jù)線,其中OLED顯示模塊的CS、DC、RES、DIN、CLK分別與STM32F103VET6的PB3、PB4、PB5、PB6、PB7連接,在這5根線中,RES為OLED的復位信號線,CS為片選信號,DC為數(shù)據(jù)/指令控制管腳,DIN為串行數(shù)據(jù)傳輸腳,CLK為數(shù)據(jù)時鐘,同時OLED模塊使用3.3V電壓供電。OLED模塊電路連接圖如圖3-11所示。

3.3.4 藍牙模塊與MCU連接電路設(shè)計

藍牙模塊一共有四個管腳VCC、GND、TX、RX,其中VCC連接3.3V電源,GND與STM32F103VET6的GND連接,藍牙模塊的TX與STM32F103VET6的UART1_RX連接也就是與PA10管腳連接,藍牙模塊的RX管腳與UART1_TX即管腳PA9。通過上述連接可以實現(xiàn)將ECG數(shù)據(jù)使用MCU的串口發(fā)送到上位機實現(xiàn)上位機部分需要完成的功能。藍牙模塊電路連接圖如圖3-12所示。


圖3-11 OLED模塊連接圖

圖3-12 藍牙模塊連接圖


3.3.5 電源模塊
本設(shè)計使用的電源是3.7V鋰電池,但是stm32供電所需要的電壓為5V與3.3V,因此使用XL6009 DC-DC升壓模塊,XL6009是一款開關(guān)電源高性能升壓模塊,該模塊使用了XL6009E1作為核心芯片可以實現(xiàn)3V-32V的電壓輸入以及5V-32V的電壓輸出,通過可調(diào)電阻R1可以調(diào)節(jié)模塊的輸出電壓,該模塊可以用于將電壓提升到5V,XL6009電路連接圖如圖3-13所示。
由于STM32F103VET6的供電電壓為3.3V因此需要將XL6009升壓模塊升壓后的5V電壓的電壓穩(wěn)壓到3.3V為系統(tǒng)提供供電,ASM1117的工作原理和普通的78系列線性穩(wěn)壓器或LM317線性穩(wěn)壓器相同,通過對輸出電壓采樣,然后反饋到調(diào)節(jié)電路去調(diào)節(jié)輸出級調(diào)整管的阻抗。使用型號為AMS117-3.3V的穩(wěn)壓芯片配合輸入與輸出端兩組濾波電容即可構(gòu)成本設(shè)計所需要的3.3V穩(wěn)壓電路,電路輸入為5V輸出為3.3V,AMS1117電路連接圖如圖3-14所示。
圖3-13 XL6009 DC-DC升壓模塊電路圖

圖3-14 AMS1117-3.3V穩(wěn)壓電路圖
3.3.6 整體電路連接
在完成各個模塊的設(shè)計后,需要將各個模塊通過相關(guān)的接口與STM32連接起來,首先是AD8232模擬前端模塊的信號輸出口與STM32的AD1通道10連接,即與PC0引腳連接,藍牙模塊與STM32的UART1連接,電源模塊連接STM32的VCC與GND,同時OLED與STM32的PB3-PB7連接,同時LED燈以及按鍵分別與STM32的相關(guān)IO口連接。在完成以上連接有即完成了整個硬件平臺的搭建,在確保硬件系統(tǒng)可以正常運行的時候還需要使用單獨編寫的程序來分別測試每一個硬件部分的功能是否正常。當所有硬件模塊都能正常工作后,硬件系統(tǒng)的設(shè)計全部完成,圖3-15為系統(tǒng)整體電路連接圖。
圖3-15系統(tǒng)整體電路連接圖
4.        系統(tǒng)軟件設(shè)計
心電信號的采集的主要依靠STM32來處理,因此在STM32上需要完成大部份的數(shù)據(jù)采集與處理,其中包括AD采樣,數(shù)字濾波器以及數(shù)據(jù)上傳。在系統(tǒng)上電后首先需要通過一個按鍵來選擇系統(tǒng)的工作模式,包括上位機模式以及本地模式,選擇本地模式后,系統(tǒng)系統(tǒng)可以脫離上位機單獨工作,實現(xiàn)心率波形的測量顯示以及心率計算,在本地模式中可以通過兩個按鍵分別控制測量的開始與結(jié)束以及切換測量后的顯示數(shù)據(jù);當選擇上位機模式后,系統(tǒng)將所有采集到的數(shù)據(jù)通過藍牙傳輸?shù)缴衔粰C,由上位機負責對數(shù)據(jù)的處理。
數(shù)據(jù)采樣的過程主要通過AD進行,由于要保證采樣與數(shù)據(jù)處理的同時快速進行,需要使用兩路DMA輪流傳輸(DMA-存儲器直接訪問,這是指一種高速的數(shù)據(jù)傳輸操作,允許在外部設(shè)備和存儲器之間直接讀寫數(shù)據(jù),既不通過CPU,也不需要CPU干預),數(shù)據(jù)通過AD采樣后通過兩路DMA輪流送往兩個緩存區(qū),同時當一個緩存區(qū)數(shù)據(jù)存滿后開始對該緩存區(qū)的數(shù)據(jù)進行數(shù)字濾波,這個過程包括50HZ陷波以及0.5HZ高通濾波,當完成這兩個步驟后將數(shù)據(jù)通過藍牙上傳并且清空該緩存區(qū),然后開始等待另一個緩存區(qū)存滿,如此循環(huán)進行數(shù)據(jù)的采樣處理以及發(fā)送,圖4-1為該過程的程序框圖。
4.2 子程序算法流程分析
4.2.1 模塊初始化子程序設(shè)計
程序的初始化過程主要是對幾個主要模塊的接口以及STM32F103VET6的工作模式進行選擇,首先對STM32F103VET6的UART1進行初始化,波特率設(shè)置為115200、無校驗位、數(shù)據(jù)位為8位,停止位為1位;其次是對與OLED連接的接口進行初始化,包括使能PD端口時鐘,端口設(shè)置為推挽輸出以及對OLED顯示的亮度,對比度等進行初始化;之
后進行ADC的初始化包括ADC端口初始化,模式初始化以及定時器與DMA中斷的初始化,配置ADC的采樣率為250HZ;完成以上初始化后需要對濾波使用到的TTR濾波器的基本參數(shù)進行初始化;最后完成的是按鍵與LED燈的GPIO初始化,與按鍵連接的IO口設(shè)置為上拉輸入模式引腳速率為10MHz,與LED連接的引腳設(shè)置為推挽輸出模式引腳速率為50MHZ,圖4-2為系統(tǒng)初始化過程。


圖4-1 系統(tǒng)整體程序框圖

圖4-2 系統(tǒng)初始化過程
圖4-3 ADC采樣過程
4.2.2 ADC配置以及DMA傳輸配置程序
模擬信號的采樣使用了ADC1的第10通道,兩次采樣時間間隔為14個時鐘周期,轉(zhuǎn)換精度為12bit,ADC的觸發(fā)時鐘配置為TIM2輸出,采樣率為250HZ,通過DMA方式進行外設(shè)到內(nèi)存數(shù)據(jù)傳輸,采用DMA乒乓結(jié)構(gòu)進行數(shù)據(jù)存儲和處理,也就是兩路DMA輪流接收AD采樣獲得的數(shù)據(jù)當其中一路的數(shù)據(jù)緩沖區(qū)存滿后切換到另一路繼續(xù)存儲,而期間進行對前一路緩沖區(qū)數(shù)據(jù)測濾波處理工作,這樣交替進行采樣濾波可以很大的提高系統(tǒng)的工作速率。具體的采樣過程如圖4-3所示。
定時器配置PWM輸出關(guān)鍵代碼:
  1. TIM_TimeBaseStructure.TIM_Period = 400;      
  2. TIM_TimeBaseStructure.TIM_Prescaler = 720 - 1;
  3. TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
  4. TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
  5. TIM_OCInitStructure.TIM_Pulse = 200;
  6. TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
  7. TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
  8. TIM_OCInitStructure.TIM_Pulse = 200;
  9. TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
  10. TIM_CtrlPWMOutputs(TIM2, ENABLE);
  11. (2)配置DMA外設(shè)到內(nèi)存?zhèn)鬏?br />
  12. DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;
  13. DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&ADC_ConvertedValue;
  14. (3)用定時器配置ADC采樣率
  15. ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_CC2 ;
  16. //Timer觸發(fā)            
  17. ADC_ExternalTrigConvCmd(ADC1, ENABLE);   //使用外部中斷觸發(fā)
復制代碼


4.2.3 數(shù)字濾波器設(shè)計程序
1. ECG噪聲來源
工頻干擾:工頻干擾是由電力系統(tǒng)引起的一種干擾,由50hz及其諧波構(gòu)成的一種干擾,幅值約為ECG(心電信號)的50%。
基線漂移:呼吸作用引起的,源于被測對象在測試過程中呼吸時電機與人體皮膚間的阻抗儀器放大的熱噪聲等干擾引起的,頻率約為0.15-0.3hz。呼吸作用時ECG幅值有15%的變化。
肌電干擾:來源于人體的肌肉顫抖,肌電干擾產(chǎn)生毫伏級電勢,可視為瞬間發(fā)生的高斯零均值帶限噪聲。
2. 50HZ陷波器設(shè)計
工頻干擾可以用陷波器進行處理,過濾50hz信號,而基線漂移則通過高通濾波器消除0.5hz以下的頻率。常用固定頻率的設(shè)計有IIR濾波器和FIR濾波器兩種,其中FIR濾波器具有良好的線性相位。但是在同等性能條件下階數(shù)比IIR濾波器高,運算量大。故本設(shè)計的工頻陷波和高通濾波均采用IIR濾波器來設(shè)計。
IIR濾波器的設(shè)計方法有脈沖響應(yīng)不變法和雙線性變換法,但過程比較復雜,借助MATLAB的FDATOOL工具包可以直接生成相關(guān)參數(shù)。再通過直接II型翻譯成C語言形式應(yīng)用于STM32平臺,圖4-4為FDATOOL工具配置界面。

圖4-4 FDATOOLl工具界面
導出濾波系數(shù)得到轉(zhuǎn)移函數(shù):
                            (4-1)
圖4-5濾波器效果
圖4-6濾波器直接II型實現(xiàn)框圖

根據(jù)濾波器直接II型實現(xiàn)框圖(圖4-6)可以得到C語言代碼:
const float IIR_50Notch_A[3] = {
                1,
              -0.5577124773,
                0.8047954884
};
x0=ADC_ConvertedValueLocal;  //輸入ADC采集到的信號
w0[0]=IIR_50Notch_A[0]*x0-IIR_50Notch_A[1]*w0[1]-IIR_50Notch_A[2]*w0[2];
y0=IIR_50Notch_B[0]*w0[0]+IIR_50Notch_B[1]*w0[1]+IIR_50Notch_B[2]*w0[2];
w0[2]=w0[1];
w0[1]=w0[0];

3. 0.5HZ高通濾波器設(shè)計
數(shù)字高通濾波器能有效抑制呼吸作用導致的基線漂移,同樣采用Matlab的Fdatool工具箱設(shè)計0.5hz二階IIR高通濾波器,導出濾波系數(shù),圖4-7為FDATOOL工具配置界面。

圖4-7 fdatool工具設(shè)計界面
得到響應(yīng)函數(shù):
                                     (4-2)
C語言代碼:
  1. const float IIR_50Notch_B[3] = {
  2.     0.9023977442,
  3.               -0.5577124773,
  4.                 0.9023977442
  5. };
  6. w1[0]=0.991153*(IIR_High_A[0]*x1-IIR_High_A[1]*w1[1]-IIR_High_A[2]*w1[2]);
  7. y1=IIR_High_B[0]*w1[0]+IIR_High_B[1]*w1[1]+IIR_High_B[2]*w1[2];
  8. w1[2]=w1[1];
  9. w1[1]=w1[0];
復制代碼

4. 數(shù)字濾波器Matlab仿真
為了使STM32上設(shè)計的濾波器有好的效果,需要在Matlab上提前實現(xiàn)50Hz陷波器,0.5Hz高通濾波器以及SG平滑濾波,通過Matlab仿真可以提前有效的了解濾波器的實際效果,圖4-8為MATLAB仿真的最終結(jié)果。
Matlab代碼:
  1. clc
  2. clear all
  3. fs=250;           %數(shù)字濾波器的采樣頻率fs=250hz
  4. f=50;             %50hz的正弦信號
  5. t=0:1/fs:4;      
  6. %==========================================
  7. s=importdata('test.txt') ; %測試數(shù)據(jù)
  8. s=s(1:1000);
  9. figure
  10. subplot(411)
  11. plot(s);
  12. title('原始數(shù)據(jù)')
  13. IIR_B=[0.90239774423695518,-0.55771247730967288,0.90239774423695518];
  14. IIR_A=[1,-0.55771247730967288,0.80479548847391036];
  15. w01=0;
  16. w02=0;
  17. w03=0;
  18. y0=zeros(1,1000);
  19. for i=1:1000
  20.     w01=s(i)-IIR_A(2)*w02-IIR_A(3)*w03;
  21.     y0(i)=IIR_B(1)*w01+IIR_B(2)*w02+IIR_B(3)*w03;
  22.     w03=w02;
  23.     w02=w01;
  24. end
  25. subplot(412)
  26. plot(y0);
  27. title('50HZ陷波器+0.5HZ高通濾波器濾波之后');
  28. y0=y0(:);
  29. A=y0;
  30. %長度為5,階數(shù)為5的S-G平滑濾波器
  31. N=11;
  32. d=3;
  33. M=(N-1)/2;
  34. for m=-M:M
  35.     for i=0:d
  36.         S(m+M+1,i+1)=m^i;
  37.     end
  38. end
復制代碼



圖4-8 matlab代碼運行效果

4.2.4 模式切換及OLED顯示程序

為了做到很好的交互體驗,通過三個按鍵K1、K2、K3負責對系統(tǒng)的工作狀態(tài)進行控制,同時使用三個LED燈LED1、LED2、LED3來顯示系統(tǒng)的工作狀態(tài)。K1負責切換系統(tǒng)的工作模式,當按下K1時通過改變標志位MODE_FLAG的值可以實現(xiàn)系統(tǒng)在上位機模式與本地模式間切換,同時通過LED1與LED2的亮滅來顯示狀態(tài),當系統(tǒng)工作在上位機模式是只需要進行數(shù)據(jù)的采樣以及濾波,完成后將數(shù)據(jù)發(fā)送即可;當系統(tǒng)工作在本地模式時通過KEY2來實現(xiàn)心電信號檢測的開始,按下KEY1檢測開始,系統(tǒng)進行10000個參數(shù)的采樣同時LED3亮,完成后LED3熄滅并將這些參數(shù)濾波并且以波形的方式顯示在OLED顯示模塊上,K3是用于切換波形顯示與心率,心率的計算方式與上位機使用的方式相同,在下文會詳細介紹。OLED顯示模塊主要用來顯示波形以及其他的參數(shù),在OLED顯示函數(shù)中主要用到個函數(shù)OLED_Clear()用于清除屏幕顯示,OLED_ShowString()用于顯示字符,OLED_ShowNum()用于顯示數(shù)字,LCD_DrawLine()用于顯示波形時進行兩個點之間的畫線操作,下圖4-9詳細的表述了系統(tǒng)切換模式時的工作原理。

圖4-9 系統(tǒng)模式切換框圖

4.3 上位機設(shè)計
4.3.1 串口數(shù)據(jù)接收程序
上位機為獲取心電數(shù)據(jù)必須要使用某種數(shù)據(jù)傳送方式,考慮到數(shù)據(jù)傳送的速度以及其后期設(shè)備的擴展,使用UART串口是最好的選擇,考慮到數(shù)據(jù)較多因此串口通信的波特率為115200,同時為了便于數(shù)據(jù)的解析定義了數(shù)據(jù)發(fā)送的數(shù)據(jù)包格式(#+0000&)其中首位和末位為校驗位第二位為數(shù)據(jù)的正負,第三到第六位為數(shù)據(jù),由于實際數(shù)據(jù)較小這里將數(shù)據(jù)放大1000倍傳輸,在STM32端將數(shù)據(jù)轉(zhuǎn)換成數(shù)據(jù)包格式并且發(fā)送,上位機將數(shù)據(jù)接收并解析成有效的心電數(shù)據(jù)并顯示。
上位機接收數(shù)據(jù)使用了WINFORM的SERIALPORT控件,該控件可以提供對串口的配置、打開關(guān)閉、以及數(shù)據(jù)的接收和發(fā)送。在串口接收數(shù)據(jù)時存在進程鎖死的問題因此需要使用多線程來防止鎖死,圖4-10與圖4-11分別描述了串口的配置界面與接收的數(shù)據(jù)。
串口接收函數(shù):
  1. private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
  2. {
  3.             string serialReadString, text;
  4.             string DataSymb;
  5.             string DataMain;
  6.             serialReadString = serialPort1.ReadExisting();
  7.             this.textBoxData.Invoke
  8.                 (
  9.                 new MethodInvoker
  10.                     (
  11.                       delegate
  12.                       {
  13.                           try
  14.                           {
  15.                               this.textBoxData.AppendText(serialReadString);
  16.                               text = textBoxData.Text;
  17.                               if (text.Length > 1)
  18.                               {
  19.                                   if (DataEnable == true)
  20.                                   {
  21.                                       TextDataTotal += serialReadString;
  22.                                   }
  23.                                   else if(DataEnable == false)
  24.                                   {
  25.                                       //TextDataTotal = "";
  26.                                   }
  27.                               }
  28.                           }
  29.                           catch { }
  30.                       }
  31.                     )
  32.                 );
  33. }
復制代碼


圖4-10串口配置界面
圖4-11串口接收數(shù)據(jù)

4.3.2 波形顯示控件設(shè)計
心電數(shù)據(jù)最直觀的體現(xiàn)方式便通過波形圖顯示出來,波形圖可以很直觀的將心電狀態(tài)每一個區(qū)域的特點體現(xiàn)出來,在上位機中使用chart控件來顯示波形,該控件的原理是讀取一個鏈表中的數(shù)據(jù)并顯示,通過對串口上傳獲得的數(shù)據(jù)進行解析把解析后的數(shù)據(jù)存入鏈表這樣便可以做到波形顯示,同時該控件還可以實現(xiàn)波形的放大、拖動、實時顯示等多種功能。由于需要顯示心電數(shù)據(jù)在SG平滑濾波前后的波形特征因此需要兩條波形同時顯示。從圖中4-12可以看出波形顯示達到了很好地效果。
波形顯示的過程程序:
  1. public List<float> x1 = new List<float>();              //新建四個鏈表用于存兩條
  2. public List<float> y1 = new List<float>();              //波形的數(shù)據(jù)
  3. public List<float> x2 = new List<float>();
  4. public List<float> y2 = new List<float>();
  5. x1.Clear();                                             //鏈表初始化
  6. y1.Clear();
  7. x2.Clear();
  8. y2.Clear();
  9. zGraph1.f_ClearAllPix();
  10. zGraph1.f_reXY();
  11. zGraph1.f_InitMode(Pengpai.UI.ZGraph.GraphStyle.DefaultMoveMode);
  12. zGraph1.f_LoadOnePix(x1, y1, Color.Red, 2);
  13. zGraph1.f_AddPix(x2, y2, Color.Blue, 2);
  14. for (int i = 0; i < DataCalcTime; i++)                        //循環(huán)顯示
  15. {
  16.     DrawLineSG(i, EcgDataNew[ i]);
  17. }
復制代碼


圖4-12波形顯示效果

4.3.3 數(shù)據(jù)處理及保存
1. SG平滑濾波
SG平滑濾波:Savitzky-Golay濾波器(通常簡稱為S-G濾波器)最初由Savitzky和Golay于1964年提出,發(fā)表于Analytical Chemistry 雜志。之后被廣泛地運用于數(shù)據(jù)流平滑除噪,是一種在時域內(nèi)基于局域多項式最小二乘法擬合的濾波方法。這種濾波器最大的特點在于在濾除噪聲的同時可以確保信號的形狀、寬度不變。對IIR濾波后的數(shù)據(jù)進行SG平滑處理。在本設(shè)計中硬件端發(fā)送的數(shù)據(jù)雖然經(jīng)過了0.5HZ高通濾波和50HZ陷波但是信號還是存在一定的毛刺,為了得到平滑的信號需要對信號進行平滑濾波。在本設(shè)計中使用長度為11階數(shù)為5的SG平滑濾波,濾波器需要先在Matlab平臺上面進行仿真,通過對樣本數(shù)據(jù)的處理,可以驗證SG平滑算法的效果,再將其在上位機上實現(xiàn)。
C#代碼:
  1. _Matrix_Calc m_c = new _Matrix_Calc();
  2. _Matrix s = new _Matrix(N, D + 1);
  3. _Matrix s_tran = new _Matrix(D + 1, N);
  4. _Matrix F = new _Matrix(4, 4);
  5. _Matrix F_inv = new _Matrix(4, 4);
  6. _Matrix b1 = new _Matrix(N, 4);
  7. _Matrix b2 = new _Matrix(N, N);
  8. s.init_matrix();
  9. s_tran.init_matrix();
  10. F.init_matrix();
  11. F_inv.init_matrix();
  12. b1.init_matrix();
  13. b2.init_matrix();
  14. for (m = -M; m <= M; m++)           //得到矩陣s
  15. {
  16. for (i = 0; i <= D; i++)
  17. {
  18.          s.write(m + M, i, (float)Math.Pow(m, i));
  19. }
  20. }
  21. m_c.transpos(ref s,ref s_tran);
  22. m_c.multiply(ref s_tran, ref s, ref F);
  23. m_c.inverse(ref F, ref F_inv);
  24. m_c.multiply(ref s, ref F_inv , ref b1);
  25. m_c.multiply(ref b1, ref s_tran , ref b2);
  26. for (int i = 0; i <= M; i++)        //第0到M,一共M+1個點
  27. {
  28.        for (int j = 0; j < N; j++)
  29.        EcgDataNew[ i] = EcgDataNew[ i] + B.read(j, i) * EcgDataOld[j];
  30. }
  31. for (int i = M + 1; i <= L - M - 2; i++)  //第M+1到L-M-2,一共L-2M-2個點
  32. {
  33.         for (int j = 0; j < N; j++)
  34.         EcgDataNew[ i] = EcgDataNew[ i] + B.read(j, M) * EcgDataOld[i - M + j - 1];
  35.   }
  36. for (int i = 0; i <= M; i++)       //第L-M-1到L-1個點,一共M+1個點
  37. {
  38.         for (int j = 0; j < N; j++)
  39.         EcgDataNew[L - M + i - 1] = EcgDataNew[L - M + i - 1] + B.read(j, M + i) * EcgDataOld[L - N + j];
  40. }
復制代碼


下圖為SG平滑濾波效果,通過圖4-13可以清晰的發(fā)現(xiàn)濾波前測試信號上擁有很多毛刺但是通過濾波毛刺基本消失波形達到了理想的效果。

圖4-13紅色為原始信號藍色為濾波后的信號

2. 心率計算
心率是通過人體心電信號可以得出的一項重要的數(shù)據(jù),在MCU以及上位機對數(shù)據(jù)進行綜合處理之后得到了可以用于分析的心電波形數(shù)據(jù),在心率的計算上使用了動態(tài)取閾值的方法,通過對一段采樣數(shù)據(jù)做分段取極大極小值后再對該數(shù)據(jù)取平均后得到的數(shù)據(jù)為心率的平均極值,然后取平均極值的3/5作為該采樣數(shù)據(jù)的閾值,分別為最小閾和最大閾,再分別對該段數(shù)據(jù)取最大最小值,用閾值與最大最小數(shù)據(jù)對比做對比,當最大值大于最大閾后繼續(xù)判斷如果此時最小值小于最小閾計數(shù)+1,同時最大最小值清零,判斷繼續(xù)直到將整段數(shù)據(jù)遍歷一遍。這樣就能得到該段數(shù)據(jù)的心跳次數(shù)。由圖4-14可以看出在12S內(nèi)程序計算出的心率次數(shù)與實際的心率次數(shù)相同。
心率計算函數(shù):
  1. private void GetBMP()
  2. {
  3.             float bmpnummax = 0;
  4.             float bmpnummin = 0;
  5.             float bmpavemax = 0;
  6.             float bmpavemin = 0;
  7.             float bmpfamax = 0;
  8.             float bmpfamin = 0;
  9.             bool tipmax = true;
  10.             bool tipmin = true;
  11.             int BMP = 0;
  12.             for (int i = 0; i < txtline; i++)
  13.             {
  14.                 if (EcgTxtDataNew[ i] >= bmpnummax)
  15.                     bmpnummax = EcgTxtDataNew[ i];
  16.                 if (EcgTxtDataNew[ i] <= bmpnummin)
  17.                     bmpnummin = EcgTxtDataNew[ i];
  18.             }
  19.                         bmpmax.Text = bmpnummax.ToString();
  20.             bmpmin.Text = bmpnummin.ToString();
  21.             bmpfamax = bmpnummax * 3 / 5;
  22.             bmpfamin = bmpnummin * 3 / 5;
  23.             bmpnummax = 0;
  24.             bmpnummin = 0;         
  25.             for (int i = 0; i < txtline; i++)
  26.             {
  27.                 if (EcgTxtDataNew[ i] >= bmpnummax)// && tipmax == true)
  28.                 {
  29.                     bmpnummax = EcgTxtDataNew[ i];
  30.                 }
  31.                 if (EcgTxtDataNew[ i] <= bmpnummin)//&& tipmin == true)
  32.                 {
  33.                     bmpnummin = EcgTxtDataNew[ i];
  34.                 }
  35.                 if (bmpnummax > bmpfamax)
  36.                 {
  37.                     if(EcgTxtDataNew[ i]<bmpfamin)
  38.                     {
  39.                         BMP++;
  40.                         bmpnummax = 0;
  41.                         bmpnummin = 0;
  42.                     }
  43.                 }
  44.             }
  45.             BMP = (int)(60 * (float)BMP / ((float)txtline * 0.004));
  46.             HeartBit.Text = BMP.ToString();
  47. }
復制代碼


圖4-14 12s測試數(shù)據(jù)得到的心率

3. 數(shù)據(jù)保存與讀取
在上位機上有一個按鍵專門給用于對數(shù)據(jù)進行保存,當點擊該數(shù)據(jù)后可以將該數(shù)據(jù)存儲在以保存時間命名的txt文件中,同時,在對應(yīng)的文件讀取控件中添加該文件以便于今后的讀取。下圖中點擊保存按鍵可以將當前的波形保存為txt文件同樣點擊菜單欄中的讀取文件按鈕可以將波形再次讀取出來這樣有利于后期對數(shù)據(jù)的分析。如圖4-15中可以通過數(shù)據(jù)菜單中的保存與讀取按鈕來進行數(shù)據(jù)存儲操作。
圖4-15數(shù)據(jù)保存與讀取功能
評測與結(jié)論
5.1 硬件設(shè)計制作與系統(tǒng)調(diào)試
在完成對心電信號采集系統(tǒng)的綜合設(shè)計論證之后便開始了硬件的設(shè)計過程,首先需要設(shè)計的是AD8232芯片和他的外圍電路,通過參考官方的文檔在官方的推薦電路的基礎(chǔ)上完成了本設(shè)計模擬前端模塊的設(shè)計。
在完成模擬前端的設(shè)計以及制作后開始了對模擬前端效果的測試,但是在初次的幾次測試中效果很不理想,有大量的干擾導致心電信號并不能很好的體現(xiàn)出來,在綜合分析了多種可能情況后的出的結(jié)論為:1.測試電路搭建太簡陋使用了較多杜邦線2.沒有使用數(shù)字濾波。在檢查了原因后我對測試電路做出了改進。在通過硬件改進以及數(shù)字濾波器的使用后,心電信號的到了改善,從圖5-1與圖5-2中可以看出軟件濾波的效果。

圖5-1 AD8232模塊原始信號

圖5-2軟件濾波后的信號

在硬件的設(shè)計過程中還有很重要的一部分就是對MCU的選擇,由于我在長期的學習過程中對STM32比較熟悉以及STM32在硬件性能上有著較大的優(yōu)勢。因此我選用我比較熟悉的STM32F103VET6來設(shè)計心電信號采集系統(tǒng),STM32的硬件直接選用了最小系統(tǒng)版,由于要使用AD和串口因此主要使用了串口1的PA9、PA10和AD的PC0,PA9,PA10與藍牙模塊的TX、RX連接PX0與模擬前端的OUT口連接。
在電源上由于STM32最小系統(tǒng)板上有AMS1117用于5V-3.3V所以電源的話采用5V即可,考慮到便攜性的要求我使用3.7V鋰電池通過升壓模塊將電壓提升到5V供硬件系統(tǒng)使用。在早期沒有使用藍牙模塊進行無線信號傳輸那時使用的是usb線連接來傳輸數(shù)據(jù),當時接收到的數(shù)據(jù)中總是有一個干擾信號始終無法去除,在多方資料的查找過程成中我發(fā)現(xiàn)對于醫(yī)療設(shè)備來說USB電源是一個很大的干擾源,必須進行隔離,但是由于后期使用鋰電池供電并不會產(chǎn)生USB干擾這個問題也被鋰電池的使用解決了,對比圖5-3與圖5-4的波形來看使用USB電源不僅給信號帶來了一個干擾波形還使得信號產(chǎn)生了較大的基線漂移。
圖5-3 使用USB電源的心電波形
圖5-4 隔離USB電源的心電波形
5.2 軟件設(shè)計與調(diào)試
軟件的設(shè)計主要分為兩個部分,主要是STM32端的程序編寫以及上位機的編寫,STM32程序編寫主要有AD采樣,數(shù)字濾波,串口數(shù)據(jù)發(fā)送這幾個方面,AD采樣由于之前做過類似項目直接使用當時的程序即可,但需在采樣率上要做相關(guān)調(diào)整,STM32負責的重點為數(shù)字濾波器的設(shè)計,這一部分在第三部分程序設(shè)計中已經(jīng)做了詳細的講解。在數(shù)字濾波器過程中還是出現(xiàn)各種問題,主要有前期由于沒有考慮到使用MATLAB仿真導致在濾波器的參數(shù)上設(shè)定的并不是很到位。之后通過多次調(diào)整才完成了濾波器的設(shè)計。其次STM32還需要完成與上位機的通信在數(shù)據(jù)包的設(shè)定上也要做好校驗以防止數(shù)據(jù)丟失。
上位機在實現(xiàn)過程中由于之前有過相關(guān)的設(shè)計經(jīng)驗并沒有遇到太大的困難,上位機的完成是在逐步添加功能的過程中進行的最初僅僅實現(xiàn)了串口數(shù)據(jù)的接收功能,然后在此基礎(chǔ)上實現(xiàn)了多種功能,最終上位機實現(xiàn)了一鍵自動心率檢測,數(shù)據(jù)保存,以及各種逐步測試功能,自動檢測功能為最后添加的功能,設(shè)計初期由于需要測試并沒有考慮到心電信號測量的簡便,在設(shè)計接近完成后發(fā)現(xiàn)這個功能尤其重要。圖5-5為上位機最終完成后的界面。
圖5-5上位機最終完成界面

5.3 系統(tǒng)綜合調(diào)試
綜合測試實在軟硬件完成并且做過相關(guān)測試的前提下進行的,測試的第一步為保證數(shù)據(jù)能否接收,在上位機模式中使用串口調(diào)試助手直接接收數(shù)據(jù)并且使用MATLAB讀取數(shù)據(jù)確認數(shù)據(jù)的正確性,從下圖的結(jié)果來看硬件部分可以將正常的將數(shù)據(jù)通過串口發(fā)送并且從波形中不難看出這是正常的心電信號,圖5-6為MATLAB顯示接收到的數(shù)據(jù)。

圖5-6 MATLAB顯示接收到的數(shù)據(jù)
在完成了最基礎(chǔ)的數(shù)據(jù)接收及其準確性驗證后,開始對上位機的各項功能進行驗證,首先要保證上位機能準確的將數(shù)據(jù)包解析出來并且通過波形顯示控件準確的將波形顯示出來,下圖為最初版的上位機對數(shù)據(jù)接收以及顯示的驗證。上位機上的測量時間為測試時間但是在實際的心率計算過程中并未使用該數(shù)據(jù)作為標準,考慮到數(shù)據(jù)發(fā)送的延遲以及該時間的準確性,通過采樣率作為標準來計算心率最為準確。由圖5-7可知系統(tǒng)可以正常的讀取硬件發(fā)送的心率波形數(shù)據(jù)。
圖5-7上位機讀取心電數(shù)據(jù)
其次是對SG平滑濾波效果的驗證,通過串口模擬器在電腦上虛擬出兩個模擬串口將上位機與串口調(diào)試助手相連,通過串口調(diào)試助手產(chǎn)生一組隨機數(shù)據(jù)發(fā)送給上位機,以此來驗證SG平滑濾波的效果,通過驗證平滑濾波的效果是符合要求的。由圖5-8可以看出通過平滑濾波處理后的波形比原波形流暢許多。
圖5-8 SG平滑濾波效果
完成以上兩部分的測試,便可以正式對上位機的核心功信號采集以及數(shù)據(jù)分析保存功能進行測試,波形的測試標準為上位機處理過后的波形可以清晰的顯示人體的心電信號信號干擾較小可以準確的分析出心電信號的特征區(qū)域,從圖5-9與圖5-10中測試信號已經(jīng)可以清楚的顯示它的心電信號的基本波形包括P波、QRS波群和T波。
圖5-9標準心電信號
圖5-10采集獲得的心電信號
在本地模式中,需要測試硬件端是否能在脫離上位機的情況下完成心率的測量以及波形的顯示,通過按鍵K1可以將系統(tǒng)在本地模式與上位機模式間進行切換,在本地模式中按K2進行心率測量,在完成設(shè)計后,首先多按鍵的功能進行驗證,在確定系統(tǒng)可以正常進行模式切換并且LED燈可以正確顯示不同的狀態(tài)后開始驗證系統(tǒng)是否能夠測量心率,在按下K2時系統(tǒng)會進行10000次采樣完成后將波形顯示出來,在測試初期經(jīng)常遇到波形顯示到一半后系統(tǒng)卡死經(jīng)過多次檢驗發(fā)現(xiàn)是由于數(shù)據(jù)顯示位置超出了OLED顯示范圍導致出錯,經(jīng)過改正后顯示正常,硬件端的心率計算方式和上位機相同,在硬件端也能正常工作,下圖5-11中的五張小圖是OLED顯示模塊的不同狀態(tài)下的界面,包括心電波形,心率以及心率狀態(tài)是否正常。
圖5-11 OLED顯示模塊界面


心率準確性的計算上由于沒有標準的心率設(shè)備,但是可以通過自己摸著胸口來測量自己的心率,在測試中我通過三次1分鐘心率直接摸胸口讀取得到我的當前心率為每分鐘72次,接下來使用心電信號采集系統(tǒng)來測量心率,由圖5-12可知上位機得出的心率為每分鐘72次符合之前的結(jié)果,當然在多次測量后心率此時誤差在1-2次,符合設(shè)計的要求。通過多次測試還發(fā)現(xiàn)在心率測量前需要盡量保持穿著較少的衣物,最準確的測量方式為左右胸以及右腹,當然通過左右手以及右腿也能得到理想的波形但是此種方式得到的波形在穿著衣物時效果并不好尤其是冬天衣物摩擦會產(chǎn)生較大的干擾。
圖5-12自動測量的測量結(jié)果
測量數(shù)據(jù)完成后點擊上位機的保存按鍵可以將數(shù)據(jù)存儲到本地,這樣便于后期的提取分析,在多次測試后保存的文件可以順利地讀取。
完成以上幾個步驟后系統(tǒng)的測試基本完成,之后需要通過多次的數(shù)據(jù)測試來保證系統(tǒng)的穩(wěn)定性,同時也需要多位同學配合來測試心電信號。同時解決了一些細微錯誤比如上位機容易崩潰和stm32偶爾無法上傳數(shù)據(jù)等問題后心電信號采集系統(tǒng)的設(shè)計總體完成。
表5-1 六人心律檢測數(shù)據(jù)
姓名
測試心律
實際心律
徐XX
75
75
楊XX
69
70
曾XX
73
72
王XX
77
77
鄭XX
70
70



單片機源程序如下:
  1. #include "stm32f10x.h"
  2. #include "usart1.h"
  3. #include "adc.h"
  4. #include "math.h"
  5. #include "change.h"
  6. #include "oled.h"
  7. #include "bsp_led.h"
  8. #include "bsp_key.h"

  9. extern __IO u16 BUF1[100];  //緩沖1區(qū)
  10. extern __IO u16 BUF2[100];  //緩沖2區(qū)

  11. /*********************************狀態(tài)標志位******************************/
  12. u8 MODE_FLAG=0;
  13. u8 MODE_FLAG_CAL=0;
  14. u8 MODE1_START_FLAG=0;
  15. u8 MODE1_FINISH_FLAG=0;
  16. u8 MODE1_CHANGE_FLAG=0;
  17. u8 MODE1_CHANGE_STATE_FLAG=0;

  18. /*********************************ECG數(shù)據(jù)*********************************/
  19. int ECG_DATA[10500];
  20. u16 ECG_NUM=0;
  21. u16 BMP_ECG=0;

  22. /**********************************DMA標志位******************************/
  23. #define BUF_Entrance 5
  24. #define BUF1_store_Finish 0
  25. #define BUF1_filter_Finish 1
  26. #define BUF2_store_Finish 2
  27. #define BUF2_filter_Finish 3

  28. extern __IO u8 Data_flag;
  29. extern __IO u8 DMA_flag;     

  30. #define L 100
  31. #define N 11
  32. #define M 5

  33. __IO u16 i=0,j;

  34. /*********************************濾波器數(shù)組*******************************/
  35. __IO float buf_N[N]={0};                   //SG平滑濾波數(shù)組
  36. __IO float IIR_Filter_Data[100];           //緩存數(shù)組,儲存濾波之后的時域信號 float類型
  37. __IO float SG_Filter_Data[100];            //SG平滑之后數(shù)據(jù)
  38. __IO int   Send_int_Data[100];             //轉(zhuǎn)換為int類型數(shù)據(jù)
  39. __IO char  Send_char_Data[5];              //發(fā)送數(shù)組


  40. __IO float w0[3]={0};
  41. __IO float w1[3]={0};

  42. __IO float x0=0,x1=0;
  43. __IO float y0=0,y1=0;



  44. /*******************************SG_Filter*****************************/
  45. //N=11 D=2
  46. const float B[N][N]={
  47.     {  0.580420,  0.377622,  0.209790,  0.076923, -0.020979, -0.083916, -0.111888, -0.104895, -0.062937,  0.013986,  0.125874},
  48.         {  0.377622,  0.278322,  0.193007,  0.121678,  0.064336,  0.020979, -0.008392, -0.023776, -0.025175, -0.012587,  0.013986},
  49.         {  0.209790,  0.193007,  0.173893,  0.152448,  0.128671,  0.102564,  0.074126,  0.043357,  0.010256, -0.025175, -0.062937},
  50.         {  0.076923,  0.121678,  0.152448,  0.169231,  0.172028,  0.160839,  0.135664,  0.096503,  0.043357, -0.023776, -0.104895},
  51.         { -0.020979,  0.064336,  0.128671,  0.172028,  0.194406,  0.195804,  0.176224,  0.135664,  0.074126, -0.008392, -0.111888},
  52.         { -0.083916,  0.020979,  0.102564,  0.160839,  0.195804,  0.207459,  0.195804,  0.160839,  0.102564,  0.020979, -0.083916},
  53.         { -0.111888, -0.008392,  0.074126,  0.135664,  0.176224,  0.195804,  0.194406,  0.172028,  0.128671,  0.064336, -0.020979},
  54.         { -0.104895, -0.023776,  0.043357,  0.096503,  0.135664,  0.160839,  0.172028,  0.169231,  0.152448,  0.121678,  0.076923},
  55.         { -0.062937, -0.025175,  0.010256,  0.043357,  0.074126,  0.102564,  0.128671,  0.152448,  0.173893,  0.193007,  0.209790},
  56.         {  0.013986, -0.012587, -0.025175, -0.023776, -0.008392,  0.020979,  0.064336,  0.121678,  0.193007,  0.278322,  0.377622},
  57.         {  0.125874,  0.013986, -0.062937, -0.104895, -0.111888, -0.083916, -0.020979,  0.076923,  0.209790,  0.377622,  0.580420}
  58. };



  59. /******************************50hz_IIR_Fnotch_Filter**************************/
  60. const float IIR_50Notch_B[3] = {
  61.     0.9023977442,
  62.          -0.5577124773,
  63.           0.9023977442
  64. };

  65. const float IIR_50Notch_A[3] = {
  66.           1,
  67.          -0.5577124773,
  68.           0.8047954884
  69. };



  70. /*****************************0.3Hz_IIR__High_Filter****************************/
  71. const float Gain=0.99468273;
  72. const float IIR_High_B[3]={
  73.          1,
  74.         -2,
  75.          1   
  76. };

  77. const float IIR_High_A[3]={
  78.          1,
  79.         -1.9833718117,
  80.          0.9839372834
  81. };



  82. void IIR_Reset()
  83. {
  84.    for(i=0;i<3;i--)
  85.           {
  86.      w0[i]=0;
  87.                  w1[i]=0;
  88.    }
  89.           x0=0;
  90.           y0=0;
  91.           x1=0;
  92.           y1=0;         
  93. }



  94. void DelayMs(unsigned int ms)
  95. {
  96.         unsigned int i;
  97.         while(ms--)
  98.         {
  99.                 for(i=0;i<7200;i++);
  100.         }
  101. }


  102. void First_filter_buf1(void)
  103. {
  104.          for(i=0;i<100;i++)
  105.          {
  106.                         x0=(float)BUF1[i]/4096*3.3-1.23;   // 讀取轉(zhuǎn)換的AD值        
  107.                         x0=-x0;   //電壓數(shù)值翻轉(zhuǎn)
  108.                  
  109.                         w0[0]=IIR_50Notch_A[0]*x0-IIR_50Notch_A[1]*w0[1]-IIR_50Notch_A[2]*w0[2];
  110.                         y0=IIR_50Notch_B[0]*w0[0]+IIR_50Notch_B[1]*w0[1]+IIR_50Notch_B[2]*w0[2];
  111.                         
  112.                         w1[0]=IIR_High_A[0]*y0-IIR_High_A[1]*w1[1]-IIR_High_A[2]*w1[2];
  113.                         y1=Gain*(IIR_High_B[0]*w1[0]+IIR_High_B[1]*w1[1]+IIR_High_B[2]*w1[2]);
  114.                         
  115.                         IIR_Filter_Data[i]=y1;         //緩存數(shù)組儲存濾波之后的信號
  116.                         
  117.                         w0[2]=w0[1];
  118.                         w0[1]=w0[0];
  119.                         w1[2]=w1[1];
  120.                         w1[1]=w1[0];            
  121.         }
  122.         for(i=0;i<=M;i++)            //第0到M,一共M+1個點
  123.   {
  124.                 for(j=0;j<N;j++)
  125.                 {
  126.            SG_Filter_Data[i]=SG_Filter_Data[i]+B[j][i]*IIR_Filter_Data[j];
  127.                 }
  128.   }
  129.         for(i=0;i<N;i++)            //將數(shù)據(jù)放入緩存數(shù)組中
  130.         {
  131.      buf_N[0]=buf_N[1];
  132.                  buf_N[1]=buf_N[2];
  133.                  buf_N[2]=buf_N[3];
  134.                  buf_N[3]=buf_N[4];
  135.                  buf_N[4]=buf_N[5];
  136.                  buf_N[5]=buf_N[6];
  137.                  buf_N[6]=buf_N[7];
  138.                  buf_N[7]=buf_N[8];
  139.                  buf_N[8]=buf_N[9];      //數(shù)據(jù)移位
  140.                  buf_N[9]=buf_N[10];
  141.            buf_N[10]=IIR_Filter_Data[i];        
  142.   }
  143.         for(i=M+1;i<L-M;i++)            //第M+1到L-1,一共L-M-1個點
  144.   {
  145.                
  146.     for(j=0;j<N;j++)
  147.                 {
  148.       SG_Filter_Data[i]=SG_Filter_Data[i]+B[j][M]*buf_N[j];
  149.                 }
  150.                 buf_N[0]=buf_N[1];
  151.                 buf_N[1]=buf_N[2];
  152.                 buf_N[2]=buf_N[3];
  153.                 buf_N[3]=buf_N[4];
  154.                 buf_N[4]=buf_N[5];
  155.                 buf_N[5]=buf_N[6];
  156.                 buf_N[6]=buf_N[7];
  157.                 buf_N[7]=buf_N[8];
  158.                 buf_N[8]=buf_N[9];     //數(shù)據(jù)移位
  159.                 buf_N[9]=buf_N[10];
  160.                 buf_N[10]=IIR_Filter_Data[i+M];
  161.         }
  162. }        


  163. void Else_filter_buf1(void)
  164. {
  165.           for(i=0;i<100;i++)
  166.          {
  167.                         x0=(float)BUF1[i]/4096*3.3-1.23;   // 讀取轉(zhuǎn)換的AD值        
  168.                         x0=-x0;   //電壓數(shù)值翻轉(zhuǎn)
  169.                         
  170.                         w0[0]=IIR_50Notch_A[0]*x0-IIR_50Notch_A[1]*w0[1]-IIR_50Notch_A[2]*w0[2];
  171.                         y0=IIR_50Notch_B[0]*w0[0]+IIR_50Notch_B[1]*w0[1]+IIR_50Notch_B[2]*w0[2];
  172.                         
  173.                         w1[0]=IIR_High_A[0]*y0-IIR_High_A[1]*w1[1]-IIR_High_A[2]*w1[2];
  174.                         y1=Gain*(IIR_High_B[0]*w1[0]+IIR_High_B[1]*w1[1]+IIR_High_B[2]*w1[2]);
  175.                         
  176.                         IIR_Filter_Data[i]=y1;         //緩存數(shù)組儲存濾波之后的信號
  177.                         
  178.                         w0[2]=w0[1];
  179.                         w0[1]=w0[0];
  180.                         w1[2]=w1[1];
  181.                         w1[1]=w1[0];            
  182.          }
  183.         for(i=0;i<L;i++)            //第0到M,一共M+1個點
  184.   {
  185.                   buf_N[0]=buf_N[1];
  186.                   buf_N[1]=buf_N[2];
  187.                   buf_N[2]=buf_N[3];
  188.                   buf_N[3]=buf_N[4];
  189.                   buf_N[4]=buf_N[5];
  190.                   buf_N[5]=buf_N[6];
  191.                   buf_N[6]=buf_N[7];
  192.                   buf_N[7]=buf_N[8];
  193.                   buf_N[8]=buf_N[9];    //數(shù)據(jù)移位
  194.                   buf_N[9]=buf_N[10];
  195.             buf_N[10]=IIR_Filter_Data[i];        
  196.       for(j=0;j<N;j++)
  197.                   {
  198.         SG_Filter_Data[i]=SG_Filter_Data[i]+B[j][M]*buf_N[j];
  199.                   }
  200.         }
  201. }        

  202. void First_filter_buf2(void)
  203. {
  204.          for(i=0;i<100;i++)
  205.          {
  206.                         x0=(float)BUF2[i]/4096*3.3-1.23;   // 讀取轉(zhuǎn)換的AD值        
  207.                         x0=-x0;   //電壓數(shù)值翻轉(zhuǎn)
  208.                  
  209.                         w0[0]=IIR_50Notch_A[0]*x0-IIR_50Notch_A[1]*w0[1]-IIR_50Notch_A[2]*w0[2];
  210.                         y0=IIR_50Notch_B[0]*w0[0]+IIR_50Notch_B[1]*w0[1]+IIR_50Notch_B[2]*w0[2];
  211.                         
  212.                         w1[0]=IIR_High_A[0]*y0-IIR_High_A[1]*w1[1]-IIR_High_A[2]*w1[2];
  213.                         y1=Gain*(IIR_High_B[0]*w1[0]+IIR_High_B[1]*w1[1]+IIR_High_B[2]*w1[2]);
  214.                         
  215.                         IIR_Filter_Data[i]=y1;         //緩存數(shù)組儲存濾波之后的信號
  216.                         
  217.                         w0[2]=w0[1];
  218.                         w0[1]=w0[0];
  219.                         w1[2]=w1[1];
  220.                         w1[1]=w1[0];            
  221.         }
  222.         for(i=0;i<=M;i++)            //第0到M,一共M+1個點
  223.   {
  224.                 for(j=0;j<N;j++)
  225.                 {
  226.       SG_Filter_Data[i]=SG_Filter_Data[i]+B[j][i]*IIR_Filter_Data[j];
  227.                 }
  228.   }
  229.         for(i=0;i<N;i++)            //將數(shù)據(jù)放入緩存數(shù)組中
  230.         {
  231.      buf_N[0]=buf_N[1];
  232.                  buf_N[1]=buf_N[2];
  233.                  buf_N[2]=buf_N[3];
  234.                  buf_N[3]=buf_N[4];
  235.                  buf_N[4]=buf_N[5];
  236.                  buf_N[5]=buf_N[6];
  237.                  buf_N[6]=buf_N[7];
  238.                  buf_N[7]=buf_N[8];
  239.                  buf_N[8]=buf_N[9];      //數(shù)據(jù)移位
  240.                  buf_N[9]=buf_N[10];
  241.            buf_N[10]=IIR_Filter_Data[i];        
  242.   }
  243.         for(i=M+1;i<L-M;i++)            //第M+1到L-1,一共L-M-1個點
  244.   {
  245.                
  246.     for(j=0;j<N;j++)
  247.                 {
  248.       SG_Filter_Data[i]=SG_Filter_Data[i]+B[j][M]*buf_N[j];
  249.                 }
  250.                 buf_N[0]=buf_N[1];
  251.                 buf_N[1]=buf_N[2];
  252.                 buf_N[2]=buf_N[3];
  253.                 buf_N[3]=buf_N[4];
  254.                 buf_N[4]=buf_N[5];
  255.                 buf_N[5]=buf_N[6];
  256.                 buf_N[6]=buf_N[7];
  257.                 buf_N[7]=buf_N[8];
  258.                 buf_N[8]=buf_N[9];     //數(shù)據(jù)移位
  259.                 buf_N[9]=buf_N[10];
  260.                 buf_N[10]=IIR_Filter_Data[i+M];
  261.         }
  262. }        


  263. void Else_filter_buf2(void)
  264. {
  265.           for(i=0;i<100;i++)
  266.          {
  267.                         x0=(float)BUF2[i]/4096*3.3-1.23;   // 讀取轉(zhuǎn)換的AD值        
  268.                         x0=-x0;   //電壓數(shù)值翻轉(zhuǎn)
  269.                         
  270.                         w0[0]=IIR_50Notch_A[0]*x0-IIR_50Notch_A[1]*w0[1]-IIR_50Notch_A[2]*w0[2];
  271.                         y0=IIR_50Notch_B[0]*w0[0]+IIR_50Notch_B[1]*w0[1]+IIR_50Notch_B[2]*w0[2];
  272.                         
  273.                         w1[0]=IIR_High_A[0]*y0-IIR_High_A[1]*w1[1]-IIR_High_A[2]*w1[2];
  274.                         y1=Gain*(IIR_High_B[0]*w1[0]+IIR_High_B[1]*w1[1]+IIR_High_B[2]*w1[2]);
  275.                         
  276.                         IIR_Filter_Data[i]=y1;         //緩存數(shù)組儲存濾波之后的信號
  277.                         
  278.                         w0[2]=w0[1];
  279.                         w0[1]=w0[0];
  280.                         w1[2]=w1[1];
  281.                         w1[1]=w1[0];            
  282.          }
  283.         for(i=0;i<L;i++)            //第0到M,一共M+1個點
  284.   {
  285.                   buf_N[0]=buf_N[1];
  286.                   buf_N[1]=buf_N[2];
  287.                   buf_N[2]=buf_N[3];
  288.                   buf_N[3]=buf_N[4];
  289.                   buf_N[4]=buf_N[5];
  290.                   buf_N[5]=buf_N[6];
  291.                   buf_N[6]=buf_N[7];
  292.                   buf_N[7]=buf_N[8];
  293.                   buf_N[8]=buf_N[9];    //數(shù)據(jù)移位
  294.                   buf_N[9]=buf_N[10];
  295.             buf_N[10]=IIR_Filter_Data[i];        
  296.       for(j=0;j<N;j++)
  297.                   {
  298.         SG_Filter_Data[i]=SG_Filter_Data[i]+B[j][M]*buf_N[j];
  299.                   }
  300.         }
  301. }        

  302. void send_data(unsigned char ascii_code)
  303. {
  304.   USART_SendData(USART1,ascii_code);
  305.   while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET){}                                                               
  306. }

  307. void getBMP(int length)
  308. {
  309.         float bmpnummax = 0;
  310.     float bmpnummin = 0;
  311.     float bmpavemax = 0;
  312.     float bmpavemin = 0;
  313.     float bmpfamax = 0;
  314.     float bmpfamin = 0;
  315.         float BMP=0;
  316.         
  317.         for (i = 0; i < length; i++)
  318.     {
  319.                 if (ECG_DATA[i] >= bmpavemax)
  320.                     bmpavemax = ECG_DATA[i];
  321.                 if (ECG_DATA[i] <= bmpavemin)
  322.                     bmpavemin = ECG_DATA[i];
  323.                 if (i == length / 5)
  324.                 {
  325.                     bmpnummax = bmpnummax + bmpavemax / 5;
  326.                     bmpnummin = bmpnummin + bmpavemin / 5;
  327.                     bmpavemax = 0;
  328.                     bmpavemin = 0;
  329.                 }
  330.                 if (i == length / 5 * 2)
  331.                 {
  332.                     bmpnummax = bmpnummax + bmpavemax / 5;
  333.                     bmpnummin = bmpnummin + bmpavemin / 5;
  334.                     bmpavemax = 0;
  335.                     bmpavemin = 0;
  336.                 }
  337.                 if (i == length / 5 * 3)
  338.                 {
  339.                     bmpnummax = bmpnummax + bmpavemax / 5;
  340.                     bmpnummin = bmpnummin + bmpavemin / 5;
  341.                     bmpavemax = 0;
  342.                     bmpavemin = 0;
  343.                 }
  344.                 if (i == length / 5 * 4)
  345.                 {
  346.                     bmpnummax = bmpnummax + bmpavemax / 5;
  347.                     bmpnummin = bmpnummin + bmpavemin / 5;
  348.                     bmpavemax = 0;
  349.                     bmpavemin = 0;
  350.                 }
  351.                 if (i == length - 1)
  352.                 {
  353.                     bmpnummax = bmpnummax + bmpavemax / 5;
  354.                     bmpnummin = bmpnummin + bmpavemin / 5;
  355.                     bmpavemax = 0;
  356.                     bmpavemin = 0;
  357.                 }
  358.    
  359.         }
  360.             bmpfamax = bmpnummax * 3 / 5;
  361.             bmpfamin = bmpnummin * 3 / 5;
  362.             bmpnummax = 0;
  363.             bmpnummin = 0;

  364.             for (i = 0; i < length; i++)
  365.             {
  366.                 if (ECG_DATA[i] >= bmpnummax)// && tipmax == true)
  367.                 {
  368.                     bmpnummax = ECG_DATA[i];
  369.                 }
  370.                 if (ECG_DATA[i] <= bmpnummin)//&& tipmin == true)
  371.                 {
  372.                     bmpnummin = ECG_DATA[i];
  373.                 }
  374.                 if (bmpnummax > bmpfamax)
  375.                 {
  376.                     if (ECG_DATA[i] < bmpfamin)
  377.                     {
  378.                         BMP++;
  379.                         bmpnummax = 0;
  380.                         bmpnummin = 0;
  381.                     }
  382.                 }
  383.             }
  384.                         BMP = (60 * BMP / ((float)length * 0.004));
  385.                         BMP_ECG=BMP;
  386. //            BMP = (int)(60 * (float)BMP / ((float)length * 0.004));
  387. }

  388. int main(void)
  389. {        
  390.         u8 ID=1;
  391.         Data_flag=0;
  392.         USART1_Config();
  393.         
  394.         OLED_Init();                        //初始化OLED
  395.         
  396.         ADC1_Init();
  397.         DMA_flag=BUF_Entrance;    //初始化DMA緩沖區(qū)切換標識位
  398.         IIR_Reset();
  399.          
  400.         OLED_Clear();
  401.         
  402.         /* config the led */
  403.         LED_GPIO_Config();
  404.         LED1_OFF;
  405.         LED2_OFF;
  406.         LED3_OFF;
  407.         /*config key*/
  408.         Key_GPIO_Config();        
  409.         
  410.         OLED_ShowString(24,0,ASCII16x16,"ECG SYSTEM");
  411.         OLED_ShowString(12,3,ASCII16x16,"K1:LOCAL MODE");
  412.         OLED_ShowString(12,5,ASCII16x16,"K1:UPPER MODE");
  413.         
  414.         while(1)
  415.         {
  416. //                if( Key_Scan(GPIOA,GPIO_Pin_1) == KEY_ON  )
  417. //                {
  418. //                                LED1_TOGGLE;                        
  419. //                }
  420.                
  421.                 if( Key_Scan(GPIOA,GPIO_Pin_0) == KEY_ON  )
  422.                 {
  423.                         MODE_FLAG=~MODE_FLAG;
  424.                         MODE1_CHANGE_FLAG =0;
  425.                         MODE1_FINISH_FLAG=0;
  426.                         if(MODE_FLAG==0)
  427.                         {
  428.                                 /*LED1反轉(zhuǎn)*/
  429.                                 LED1_ON;
  430.                                 LED2_OFF;
  431.                                 LED3_OFF;
  432.                                 MODE_FLAG_CAL=1;
  433.                                 OLED_Clear();
  434.                                 OLED_ShowString(24,0,ASCII16x16,"LOCAL MODE");
  435.                                 OLED_ShowString(32,4,ASCII16x16,"K2:START");
  436.                                 MODE1_START_FLAG =0;
  437.                         }
  438.                         else
  439.             {
  440.                                 /*LED1反轉(zhuǎn)*/
  441.                                 LED1_OFF;
  442.                                 LED2_ON;
  443.                                 LED3_OFF;
  444.                                 MODE_FLAG_CAL=2;
  445.                                 OLED_Clear();
  446.                                 OLED_ShowString(24,0,ASCII16x16,"UPPER MODE");
  447.                                 OLED_ShowString(12,4,ASCII16x16,"CONNECTING...");
  448.                                 MODE1_START_FLAG =0;
  449.                         }
  450.                 }
  451.                
  452.                 if(MODE_FLAG_CAL==1)
  453.                 {
  454.                         if( Key_Scan(GPIOC,GPIO_Pin_13) == KEY_ON  )
  455.                         {
  456.                                 MODE1_START_FLAG =1;
  457.                                 MODE1_FINISH_FLAG=0;
  458.                                 LED3_ON;
  459.                                 OLED_Clear();
  460.                        OLED_ShowString(8,2,ASCII16x16,"RECEIVING DATA");        
  461.                 OLED_ShowString(24,4,ASCII16x16,"WAITING...");                                
  462.                         }
  463.                         
  464. //                        if(ECG_NUM >8000&&MODE1_START_FLAG ==1)
  465. //                        {
  466. //                           send_data('A');
  467. //                        }
  468.                         
  469.                         if(MODE1_START_FLAG ==1)
  470.                         {
  471.                                                         /****************獲取標志位,1管道儲存完成,處理管道1數(shù)據(jù)******************/
  472.                                   if(DMA_flag==BUF1_store_Finish)            
  473.                                   {
  474.                                           if(ID==1)
  475.                                           {                                                         
  476.                                                  First_filter_buf1();       //處理緩沖區(qū)1的數(shù)據(jù)                        
  477.                                                  for(i=0;i<L-M;i++)
  478.                                                  {
  479.                                                         Send_int_Data[i]=(int)(SG_Filter_Data[i]*1000);
  480.                                                         int_to_char(Send_int_Data[i],Send_char_Data);
  481.                                                          
  482.                                                         ECG_DATA [ECG_NUM]=Send_int_Data[i];
  483.                                                         ECG_NUM++;        
  484.                             if(ECG_NUM>10000)
  485.                             {
  486.                                                                 MODE1_START_FLAG =0;
  487.                                                         }                                                               
  488.                                                   }                                                  
  489.                                                for(i=10;i<100;i++)           //清零操作
  490.                                                    {
  491.                                                            IIR_Filter_Data[i]=0;
  492.                                                            SG_Filter_Data[i]=0;
  493.                                                 }
  494.                                                    DMA_flag=BUF1_filter_Finish;
  495.                                                    ID++;
  496.                                             }
  497.                                                 else
  498.                                                 {
  499.                                                    Else_filter_buf1();       //處理緩沖區(qū)1的數(shù)據(jù)                        
  500.                                                    for(i=0;i<L;i++)
  501.                                                    {        
  502.                                                            Send_int_Data[i]=(int)(SG_Filter_Data[i]*1000);
  503.                                                            int_to_char(Send_int_Data[i],Send_char_Data);
  504.                                                            
  505.                                                            ECG_DATA [ECG_NUM]=Send_int_Data[i];
  506.                                                            ECG_NUM++;         
  507.                                if(ECG_NUM>10000)
  508.                                {
  509.                                                                   MODE1_START_FLAG =0;
  510.                                                            }                                                           
  511.                                                         }           
  512.                                                 for(i=0;i<L;i++)      //清零操作
  513.                                                     {
  514.                                                             IIR_Filter_Data[i]=0;
  515.                                                                 SG_Filter_Data[i]=0;
  516.                                                 }
  517.                                                     DMA_flag=BUF1_filter_Finish;
  518.                                                         ID++;   
  519.                                         }
  520.                                  }
  521.                            /****************獲取標志位,2管道儲存完成,處理管道2數(shù)據(jù)******************/         
  522.                                  if(DMA_flag==BUF2_store_Finish)        //緩沖區(qū)2存儲滿
  523.                                  {
  524.                                          if(ID==1)
  525.                                          {
  526.                                                  First_filter_buf2();                //處理緩沖區(qū)2的數(shù)據(jù)                                                
  527.                                                  for(i=0;i<L;i++)
  528.                                                  {        
  529.                                                          Send_int_Data[i]=(int)(SG_Filter_Data[i]*1000);
  530.                                                          int_to_char(Send_int_Data[i],Send_char_Data);
  531.                                                          
  532.                                                          ECG_DATA [ECG_NUM]=Send_int_Data[i];
  533.                                                          ECG_NUM++;   
  534.                              if(ECG_NUM>10000)
  535.                              {
  536.                                                                 MODE1_START_FLAG =0;
  537.                                                          }                                                         
  538.                                                   }                        
  539.                                                   for(i=0;i<L;i++)      //清零操作
  540.                                                   {
  541.                                                           IIR_Filter_Data[i]=0;
  542.                                                       SG_Filter_Data[i]=0;
  543.                                                   }
  544.                                                   DMA_flag=BUF2_filter_Finish;        
  545.                                                   ID++;
  546.                                       }
  547.                                           else
  548.                                           {
  549.                                                    Else_filter_buf2();                //處理緩沖區(qū)2的數(shù)                                                                          
  550.                                                    for(i=0;i<100;i++)
  551.                                                    {        
  552.                                                            Send_int_Data[i]=(int)(SG_Filter_Data[i]*1000);                       
  553.                                                            int_to_char(Send_int_Data[i],Send_char_Data);        
  554.                                                          
  555.                                 ECG_DATA [ECG_NUM]=Send_int_Data[i];
  556.                                                             ECG_NUM++;        
  557.                                 if(ECG_NUM>10000)
  558.                                 {
  559.                                                                     MODE1_START_FLAG =0;
  560.                                                               }                                                           
  561.                                                          }                                       
  562.                                                 for(i=0;i<100;i++)      //清零操作
  563.                                                     {
  564.                                                            IIR_Filter_Data[i]=0;
  565.                                                    SG_Filter_Data[i]=0;
  566.                                                  }
  567.                                                 DMA_flag=BUF2_filter_Finish;        
  568.                                                         ID++;
  569.                                            }               
  570.                                   }
  571.                         }        
  572.                         
  573. /******************************顯示波形************************************/
  574.             if(ECG_NUM>10000&&MODE1_START_FLAG ==0)
  575.                         {
  576.                                 OLED_Clear();
  577.                                 LED3_OFF;
  578.                                 
  579.                                 getBMP(10000);
  580.                                 
  581.                                 for(i=0;i<10000;i++)
  582.                                 {
  583.                                         ECG_DATA [i]=-(ECG_DATA [i]/32-32);
  584.                                         if(ECG_DATA [i]<0)
  585.                                                 ECG_DATA [i]=0;
  586.                                         else if(ECG_DATA [i]>63)
  587.                                                 ECG_DATA [i]=63;
  588.                                 }
  589.                                 
  590.                                 for(i=0;i<10000-512;i+=100)
  591.                                 {
  592.                                         OLED_Clear();
  593.                                         for(j=0;j<512;j++)
  594.                                         {
  595.                                                 LCD_DrawLine(j/4,ECG_DATA [j+i],(j+1)/4,ECG_DATA [j+i+1]);
  596.                                         }
  597.                                         delay_ms(50);                                
  598.                                 }
  599.                                 ECG_NUM=0;
  600.                                 MODE1_FINISH_FLAG=1;
  601.                                 MODE1_CHANGE_STATE_FLAG=0;
  602.                         }
  603.                         
  604.                         if( Key_Scan(GPIOA,GPIO_Pin_1) == KEY_ON&&MODE1_FINISH_FLAG ==1)
  605.                         {
  606.                                 MODE1_CHANGE_STATE_FLAG=~MODE1_CHANGE_STATE_FLAG;
  607.                 MODE1_CHANGE_FLAG =1;                                
  608.                         }
  609.                         
  610.                         if(MODE1_CHANGE_FLAG ==1&&MODE1_FINISH_FLAG==1)
  611.                         {
  612.                 MODE1_CHANGE_FLAG =0;
  613.                                 if(MODE1_CHANGE_STATE_FLAG==0)
  614.                                 {
  615.                                         for(i=0;i<10000-512;i+=100)
  616.                                         {
  617.                                                 OLED_Clear();
  618.                                                 for(j=0;j<512;j++)
  619.                                                 {
  620.                                                         LCD_DrawLine(j/4,ECG_DATA [j+i],(j+1)/4,ECG_DATA [j+i+1]);
  621.                                                 }
  622.                                                 delay_ms(50);                                
  623.                                         }
  624.                                 }
  625.                                 else
  626.                                 {
  627.                                         OLED_Clear();
  628.                                     OLED_ShowString(32-16,0,ASCII16x16,"ECG DATA");
  629.                                     OLED_ShowString(24-16,2,ASCII16x16,"NUM:");
  630.                                     OLED_ShowString(24-16,4,ASCII16x16,"STATE:");
  631. //                                        getBMP(10000);
  632.                                        
  633.                                         //OLED_ShowNum(24+32,2,BMP_ECG,4,16);
  634.                                        
  635.                                         if(BMP_ECG>400)
  636.                                         {
  637.                                                 OLED_ShowString(24+32-16,2,ASCII16x16,"ERROR");
  638.                                         }
  639.                                         else if(BMP_ECG<=400&&BMP_ECG>=0)
  640.                                         {
  641.                                                 OLED_ShowNum(24+32-16,2,BMP_ECG,4,16);
  642.                                         }
  643.                                        
  644.                                         if(BMP_ECG>50&&BMP_ECG<300)
  645.                                         {
  646.                                                 OLED_ShowString(24+48-16,4,ASCII16x16,"SAFE");
  647.                                         }
  648.                                         else if(BMP_ECG<=50&&BMP_ECG>=0)
  649.                                         {
  650.                                                 OLED_ShowString(24+48-16,4,ASCII16x16,"DANGERL");
  651.                                         }
  652.                                         else if(BMP_ECG>=300&&BMP_ECG<=400)
  653.                                         {
  654.                                                 OLED_ShowString(24+48-16,4,ASCII16x16,"DANGERH");
  655.                                         }
  656.                                     else if(BMP_ECG>400)
  657.                                         {
  658.                                                 OLED_ShowString(24+48-16,4,ASCII16x16,"ERROR");
  659.                                         }                                       
  660.                                         //OLED_ShowNum(u8 x,u8 y,u16 num,u8 len,u8 size);
  661.                                 }
  662.                         }
  663.                         
  664.                 }
  665.                
  666.                
  667.                
  668.                 if(MODE_FLAG_CAL==2)
  669.                 {
  670.                         /****************獲取標志位,1管道儲存完成,處理管道1數(shù)據(jù)******************/
  671.                   if(DMA_flag==BUF1_store_Finish)            
  672.           {
  673.                   if(ID==1)
  674.                   {
  675.                                                 
  676.                                   First_filter_buf1();       //處理緩沖區(qū)1的數(shù)據(jù)                                 
  677.                                   for(i=0;i<L-M;i++)
  678.                                   {
  679. ……………………

  680. …………限于本文篇幅 余下代碼請從51黑下載附件…………
復制代碼

所有資料51hei提供下載:
完整的word格式設(shè)計報告51黑下載地址:
基于stm32的無線藍牙心電監(jiān)護儀_論文.doc (3.99 MB, 下載次數(shù): 284)
電路圖.zip (144.54 KB, 下載次數(shù): 284)
程序源碼.zip (4.3 MB, 下載次數(shù): 369)

分享到:  QQ好友和群QQ好友和群 QQ空間QQ空間 騰訊微博騰訊微博 騰訊朋友騰訊朋友
收藏收藏19 分享淘帖 頂2 踩
回復

使用道具 舉報

沙發(fā)
ID:402025 發(fā)表于 2018-9-25 10:05 | 只看該作者
單導心電的電路和源碼
回復

使用道具 舉報

板凳
ID:1 發(fā)表于 2018-9-26 03:42 | 只看該作者
好資料,51黑有你更精彩!!!
回復

使用道具 舉報

地板
ID:44453 發(fā)表于 2018-12-5 11:40 | 只看該作者
看的好心動,可惜沒有黑幣了
回復

使用道具 舉報

5#
ID:485969 發(fā)表于 2019-3-7 02:46 | 只看該作者
很有用!
回復

使用道具 舉報

6#
ID:182535 發(fā)表于 2019-3-20 23:53 | 只看該作者
好文章
回復

使用道具 舉報

7#
ID:499249 發(fā)表于 2019-3-27 16:21 | 只看該作者
寫的好有邏輯,給我很多提示,太感謝啦
回復

使用道具 舉報

8#
ID:444897 發(fā)表于 2019-5-1 22:34 | 只看該作者
2539871219 發(fā)表于 2019-3-27 16:21
寫的好有邏輯,給我很多提示,太感謝啦

請問為什么這個上位機程序怎么打不開?我用的是vs 2017
回復

使用道具 舉報

9#
ID:512514 發(fā)表于 2019-5-18 11:23 | 只看該作者
好資料,51黑有你更精彩!!!
回復

使用道具 舉報

10#
ID:541521 發(fā)表于 2019-5-18 17:09 | 只看該作者
I  II  III 心電導聯(lián)如何切換呢,電路不完整
回復

使用道具 舉報

11#
ID:68875 發(fā)表于 2019-6-16 09:55 | 只看該作者
good topic
回復

使用道具 舉報

12#
ID:255292 發(fā)表于 2019-12-4 14:08 | 只看該作者
上位機我用VS2013打不開?請教咋回事
回復

使用道具 舉報

13#
ID:492919 發(fā)表于 2019-12-6 12:53 | 只看該作者
學習一下
回復

使用道具 舉報

14#
ID:414712 發(fā)表于 2019-12-20 11:09 | 只看該作者
單導心電的電路和源碼
回復

使用道具 舉報

15#
ID:347034 發(fā)表于 2020-2-25 10:35 | 只看該作者
學習一下
回復

使用道具 舉報

16#
ID:700537 發(fā)表于 2020-3-1 14:39 | 只看該作者
zhangkaijin 發(fā)表于 2019-5-1 22:34
請問為什么這個上位機程序怎么打不開?我用的是vs 2017

我也是,用2017打不開,請問你現(xiàn)在解決了嗎?
回復

使用道具 舉報

17#
ID:710769 發(fā)表于 2020-3-18 12:09 | 只看該作者
請問有沒有單導聯(lián)的設(shè)計
回復

使用道具 舉報

18#
ID:661510 發(fā)表于 2020-3-18 15:18 | 只看該作者
好資料,學到了很多
回復

使用道具 舉報

19#
ID:700459 發(fā)表于 2020-3-19 07:32 | 只看該作者
thank you so much
回復

使用道具 舉報

20#
ID:604438 發(fā)表于 2021-4-28 10:24 | 只看該作者
衛(wèi)佐 發(fā)表于 2020-3-1 14:39
我也是,用2017打不開,請問你現(xiàn)在解決了嗎?

你好,問題解決了嗎
回復

使用道具 舉報

21#
ID:913561 發(fā)表于 2021-5-5 11:07 | 只看該作者
有測試數(shù)據(jù)的下載方式嗎
回復

使用道具 舉報

22#
ID:818895 發(fā)表于 2021-5-5 12:33 | 只看該作者
是不是比較實用。
回復

使用道具 舉報

23#
ID:1072611 發(fā)表于 2023-4-18 22:30 | 只看該作者
值得收藏
回復

使用道具 舉報

24#
ID:1127175 發(fā)表于 2024-6-27 16:08 | 只看該作者
寫得很不錯,買點原件組起來玩一下試一試,也能學習下各部分電路怎么做的,嵌入式的代碼基本都是調(diào)用庫嗎?還是有改底層呢?如果還有后續(xù)的話,希望能多討論吖,也想知道3導聯(lián)和5導聯(lián)的區(qū)別是啥,設(shè)計上哪里不同。
回復

使用道具 舉報

25#
ID:1127175 發(fā)表于 2025-1-11 20:35 | 只看該作者
感謝樓主的分享,是很好的一個項目學習資料,但是我們知道現(xiàn)在的心電導聯(lián)都是3/5/12導聯(lián)的,那么5導聯(lián)和12導聯(lián)又有哪些差別呢?信號處理上又有哪些不一樣?
回復

使用道具 舉報

您需要登錄后才可以回帖 登錄 | 立即注冊

本版積分規(guī)則

手機版|小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術(shù)交流QQ群281945664

Powered by 單片機教程網(wǎng)

快速回復 返回頂部 返回列表
主站蜘蛛池模板: 久久精品二区亚洲w码 | 国产精品乱码一区二区三区 | 国产精品国产三级国产aⅴ原创 | 免费一级欧美在线观看视频 | 最近日韩中文字幕 | 91高清在线观看 | 毛片免费视频 | 欧美色综合一区二区三区 | 91麻豆精品国产91久久久久久 | 亚洲国产一区在线 | 欧美性受| 亚洲一区二区在线 | 在线一区二区三区 | 亚洲高清在线 | 91久久| 999免费网站 | 97日日碰人人模人人澡分享吧 | 在线观看成人小视频 | 欧美一级淫片免费视频黄 | 97精品国产一区二区三区 | 99久久精品国产一区二区三区 | 日本精品视频 | 欧美4p| 日本福利视频免费观看 | 欧州一区| 欧美 日韩 在线播放 | 99免费在线观看视频 | 日韩视频一区二区在线 | 我要看免费一级毛片 | 国产在线精品一区二区 | 亚洲高清在线观看 | 亚洲一区二区三区四区五区午夜 | 国产精品视频一区二区三区四蜜臂 | 黄色成人在线观看 | 久久99视频 | 亚洲热在线视频 | 日韩中文字幕在线观看 | 免费成人高清在线视频 | 国产精品福利久久久 | 日韩中文在线观看 | 成人黄色三级毛片 |