esp32-micropython-血氧心率测试仪(swjtu-hanser_05)

西南交通大学 电子技术课程设计 心率血氧测试仪

1.方案设计

设计一个基于ESP32的血氧心率测试仪是一个相当复杂和有挑战性的项目,涉及到多个方面的工程和医学知识。

1.1. 硬件选型与布局

主控制器:基于ESP32-S3-N16R8的双USB开发板

传感器:血氧浓度和心率检测的传感器模块MAX30102

显示屏:基于sh1106的1.3寸蓝色oled显示屏

电源管理:考虑到设备的便携性,需要一个适当的电源管理电路,可能包括锂电池充电电路和稳压器或者直接采用USB供电

1.2. 软件开发

选用MicroPython对ESP32进行开发

MicroPython环境配置:将MicroPython固件烧录到ESP32开发板上,并配置开发环境。

传感器驱动:使用现有的MicroPython驱动程序来读取MAX30102传感器的数据。

数据处理:实现数据的采集、处理和分析,从传感器获取的数据包括反射光强度和心率等数据。

显示与用户界面:设计简单直观的用户界面,通过OLED显示屏或LCD显示屏显示血氧饱和度和心率等测量结果。

1.3. 算法与校准

信号处理算法:针对传感器采集到的光强度数据,开发合适的算法计算血氧饱和度和心率。

校准:确保测量结果的准确性和可靠性,可能需要根据标准设备进行校准。

1.4.设计流程

准备工作:选定硬件和传感器,获取ESP32开发板并准备开发环境。

硬件连接:按照传感器和显示屏的引脚定义,连接到ESP32开发板上。

软件开发:配置MicroPython环境,并验证基本功能。

开发传感器驱动和数据处理模块。

设计并实现用户界面和数据显示模块。

测试与调试:对整个系统进行测试,包括传感器数据的准确性、显示屏功能以及用户界面的友好程度。

优化与改进:根据测试结果进行必要的优化和改进,确保设备的稳定性和性能。

校准:进行设备的校准工作。

2.软件和硬件方面

2.1ESP32怎样运行Python?

联想 pc机的流程

Python源代码----print("hahah")----->Python解释器------010101001------>操作系统(WindowsMacOSLinux等)-------0101010100---->硬件电路

所以,我们也能够大体整理出在ESP32上的运行流程

Python源代码----print("haha")----->特殊的Python解释器-------0101010010---->硬件电路

注意:ESP32上没有运行操作系统

硬件:ESP32

特殊的Python解释器:MicroPython(一种针对于硬件做了特殊处理的Python解释器)

IDE:Thonny(类似Pycharm这个也是个IDE,它对ESP32支持更好 所以选择它)

2.2 下载&安装Thonny

2.3 下载MicroPython

下载地址:MicroPython - Python for microcontrollers

也可以直接采用我上传的资源包中的固件

2.4下载ESP32驱动

为了能够将程序下载(烧录)到ESP32中,需要安装驱动

下载地址:https://doc.itprojects.cn/A0001.micropython.esp32/02.download/02.driver/esp32usbDriver.zip

下载后安装即可

 2.5烧录MicroPython到EPS32

1652846232274

在弹出的框中点击下图所示位置

2.6 烧录MicroPython到EPS32

1652846232274

在弹出的框中点击下图所示位置

1652846254262

会再次弹出1个框

1652846304030

在上图序号1处选择,串口。只要安装了我提供的驱动,那么会看到如下的名字(COM后的序号不一样没事)

注意:ESP32此时要链接USB开机

1652846366993

在上图序号2处选择下载的MicroPython固件

1652846547244

点击“安装”,会在上图3处看到进度,当出现“Done”时表示完成,关闭窗口即可

此时,已经将MicroPython烧录到ESP32中

会再次弹出1个框

1652846304030

在上图序号1处选择,串口。只要安装了我提供的驱动,那么会看到如下的名字(COM后的序号不一样没事)

注意:ESP32此时要链接USB开机

在上图序号2处选择下载的MicroPython固件

1652846547244

点击“安装”,会在上图3处看到进度,当出现“Done”时表示完成,关闭窗口即可

此时,已经将MicroPython烧录到ESP32中

这个方法有时候会出现bug,因为好像是从0x0001000开始烧录有可能烧录失败

那么就得使用乐鑫官方的烧录工具进行烧录

详细看这篇博客https://blog.csdn.net/qq_30895747/article/details/138012468?spm=1001.2101.3001.10138.3

2.7Thonny与ESP32交互

1652846660533

按上图操作,会看到如下界面

1652846712037

表示此时可以与ESP32交互了,可以写个简单的python语句试试看

2.8 编写Python代码让EPS32执行

2.8.1 开启左侧目录

1652846775978

此时会看左侧的目录

1652846811623

2.8.2编写python文件

默认只有1个boot.py文件,不要再这个里面乱些东西,新建一个python文件,例如helloworld.py

1652846897492

1652846916156

在左侧目录树中看到

1652846939334

2.8.3运行Python文件

1652846965220

到此为止,我们就搭建好了整个流程

3.代码测试(要点及难点)

3.1.MAX30102传感器数据的读取

3.1.1心率数据的读取:

引入库和全局变量:

通过from machine import sleep, SoftI2C, Pin, Timer等语句引入需要的库和模块。

定义了全局变量BEATS(存储心率)和FINGER_FLAG(手指是否检测到)。

main函数:

首先初始化I2C接口和MAX30102传感器。

检测传感器是否正常连接和兼容。配置传感器的采样率、FIFO平均值和LED的功率。

进入主循环,不断检测传感器数据。

数据采集和处理:

使用sensor.pop_red_from_storage()和sensor.pop_ir_from_storage()从传感器的FIFO队列中获取红光和红外光的数据。

根据红光数据的强度判断是否检测到手指,如果红光强度低于阈值,则认为没有放置手指。

如果检测到手指,则将红光数据加入历史记录中,并根据一定的算法计算心率(bpm)。

心率计算算法:

使用历史数据的最小值和最大值计算阈值。根据阈值判断心跳的开始和结束,并计算心率。

定时器设置:

创建定时器对象,并设置定时器的周期为1秒,回调函数为display_info,用来定时显示心率信息。

3.1.2血氧浓度数据

使用SoftI2C和MAX30102类通过I2C接口初始化MAX30102传感器。初始化包括设置传感器的采样率、FIFO平均值和LED功率。

数据获取循环:

在main()函数中,脚本持续检测传感器数据(red_reading和ir_reading)。然后对这些数据进行处理,计算心率(BEATS)和可能的血氧饱和度(SPO2)。

血氧饱和度计算(SPO2)涉及维护最近传感器读数的列表(red_list和ir_list),并将这些列表传递给处理函数(calc_hr_and_spo2)。该函数返回hr、hrb、sp和spb,其中sp表示计算得到的SpO2值。

3.1.3心率与血氧的纠正算法

心率监测部分:

在history列表中存储红光传感器的读数。

通过保持列表的最新32个元素来避免列表过大。

通过取历史数据的最小值和最大值,计算阈值threshold_on和threshold_off。

检测心跳,当红光传感器的读数高于阈值threshold_on时,判定为有心跳。

当检测到心跳时,计算心率,并将数据存入beats_history列表中,只保留最大30个元素的数据。计算心率的平均值(BEATS)。

血氧水平监测部分:

在red_list和ir_list列表中分别存储红光和红外传感器的读数。

保持red_list和ir_list列表中最新的100个元素。调用calc_hr_and_spo2函数计算血氧值,并在条件满足时更新SPO2值。

3.2.基于sh1106的1.3寸蓝色oled显示屏的显示

3.2.1英文显示

英文显示直接调用官方的开源库函数进行显示

但是github上面直接找到的代码不能直接用,端口稍微有点问题,已经修改好了直接用

3.2.2中文显示

使用官方的驱动,首先生成中文的点阵图,让后将生成的文字数据在oled上显示

原理:一个汉字用点阵形式显示一般16*16才能正常显示。用二进制来存储这个汉字的话也就是一行2个字节.16行一共32个字节

但是micropython对中文的支持少,默认编码就是utf8,一个汉字转完是3个bytes的,但是因为使用esp32这个芯片,所以必须转成2个bytes的编码,像GBK、GB2312、UNICODE之类的,这里采用的是micropython utf-8转gb2312进行操作

这一部分是参考的https://blog.csdn.net/knswd/article/details/104544130?ops_request_misc=&request_id=&biz_id=102&utm_term=python%E7%94%9F%E6%88%90%E6%B1%89%E5%AD%97%E7%82%B9%E9%98%B5&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-1-104544130.142^v100^pc_search_result_base8&spm=1018.2226.3001.4187

https://blog.csdn.net/jd3096/article/details/130257320?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522172083801516800182111718%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=172083801516800182111718&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-130257320-null-null.142^v100^pc_search_result_base8&utm_term=micropython%E4%B8%AD%E6%96%87%E8%BD%ACgb2312&spm=1018.2226.3001.4187

3.3.整合代码

通过不断检测传感器数据并进行数据处理,实现了对心率和血氧饱和度的实时监测,并在OLED屏幕上展示相关数据

引入和初始化操作:

代码开始时通过引入所需模块,如Timer,创建了一个OLED控制器实例。

传感器设置和数据处理:

在main()函数中,进行了传感器的设置和初始化,配置传感器的采样率、FIFO平均值和LED强度等参数。主循环中不断检测传感器数据,包括红光和红外传感器读数,用来判断手指状态并计算心率和血氧饱和度。根据红光传感器读数的波动情况,判断是否检测到心跳信号,并根据心跳信号计算心率值。存储历史数据,计算平均值,并根据历史值更新心率和血氧饱和度数据。

显示部分:

display_info()函数负责在OLED屏幕上显示心率和血氧饱和度数据,根据数据量的变化动态调整显示内容。

OLEDController()函数负责在OLED屏幕上显示课程设计名称与小组成员

数据处理函数:

calc_hr_and_spo2()函数用于计算心率和血氧饱和度值。

定时器和回调函数:

使用Timer对象设置了定时器,每隔一定时间触发display_info()函数,用于更新OLED屏幕上的数据显示。

4.部分代码

4.1 主程序

from machine import SoftI2C, Pin, Timer, sleep
from uio import StringIO
from utime import ticks_diff, ticks_us
from max30102 import MAX30102, MAX30105_PULSE_AMP_MEDIUM
from hrcalc import calc_hr_and_spo2
import sh1106
from input_chinese import OLEDController
import time

# 创建 OLED 控制器实例,并指定 SCL 和 SDA 引脚,以及字体大小
oled_controller = OLEDController(scl_pin=1, sda_pin=2, font_size=2)

# 调用显示中文字符的方法,指定字符串和显示位置,并保持显示5秒钟
oled_controller.display_chinese_on_oled("血氧检测仪", 0, 0)
time.sleep(1)

oled_controller.display_chinese_on_oled("田钟恒2234", 0, 20)
time.sleep(1)

oled_controller.display_chinese_on_oled("张艺严2215", 0, 40)
time.sleep(5)

#正式的代码 
BEATS = 0
FINGER_FLAG = False
SPO2 = 0
HREAT_LIST = []
SPO2_LIST = []
OLED = None

def create_oled():
    global OLED
    i2c_oled = SoftI2C(scl=Pin(1), sda=Pin(2))
    oled_width = 128
    oled_height = 64
    OLED = sh1106.SH1106_I2C(oled_width, oled_height, i2c_oled)
    OLED.fill(0)
    OLED.text('Put your finger', 0, 30)
    OLED.show()

def display_info(t):
    global HREAT_LIST, SPO2_LIST, OLED, BEATS, SPO2
    
    if not FINGER_FLAG:
        HREAT_LIST.clear()
        SPO2_LIST.clear()
        BEATS = SPO2 = 0
        OLED.fill(0)
        OLED.text('Put your finger', 0, 30)
        OLED.show()
        return
    
    bmp_str = 'BPM:%3d' % int(BEATS)
    spo2_str = 'SPO2:%3d' % int(SPO2)
    
    if len(HREAT_LIST) < 30:
        HREAT_LIST.append(BEATS)
        HREAT_LIST = HREAT_LIST[-30:]
        bmp_str += " <--%d" % (30 - len(HREAT_LIST))
    elif len(SPO2_LIST) < 10:
        SPO2_LIST.append(SPO2)
        SPO2_LIST = SPO2_LIST[-10:]
        spo2_str += " <--%d" % (10 - len(SPO2_LIST))
    
    OLED.fill(0)
    OLED.text(bmp_str, 0, 10)
    OLED.text(spo2_str, 0, 30)
    OLED.show()
    
def main():
    global BEATS, FINGER_FLAG, SPO2
    
    i2c = SoftI2C(sda=Pin(5), scl=Pin(4), freq=490000)
    sensor = MAX30102(i2c=i2c)
    
    if sensor.i2c_address not in i2c.scan():
        print("Sensor not found")
        return
    elif not sensor.check_part_id():
        print("Device is not MAX30102 or MAX30105")
        return
    else:
        print("Sensor found")
        
      
    
    sensor.setup_sensor()
    sensor.set_sample_rate(400)
    sensor.set_fifo_average(8)
    sensor.set_active_leds_amplitude(MAX30105_PULSE_AMP_MEDIUM)
    
    t_start = ticks_us()
    MAX_HISTORY = 32
    history = []
    beats_history = []
    beat = False
    red_list = []
    ir_list = []
    
    while True:
        sensor.check()
        if sensor.available():
            red_reading = sensor.pop_red_from_storage()
            ir_reading = sensor.pop_ir_from_storage()
            
            if red_reading < 1000:
                print('No finger detected')
                FINGER_FLAG = False
                continue
            else:
                FINGER_FLAG = True
            
            if len(HREAT_LIST) < 30:
                history.append(red_reading)
                history = history[-MAX_HISTORY:]
                
                minima, maxima = min(history), max(history)
                threshold_on = (minima + maxima * 3) // 4
                threshold_off = (minima + maxima) // 2
                
                if not beat and red_reading > threshold_on:
                    beat = True
                    t_us = ticks_diff(ticks_us(), t_start)
                    t_s = t_us / 1000000
                    f = 1 / t_s
                    bpm = f * 60
                    if bpm < 500:
                        t_start = ticks_us()
                        beats_history.append(bpm)
                        beats_history = beats_history[-MAX_HISTORY:]
                        BEATS = round(sum(beats_history) / len(beats_history), 2)
                
                if beat and red_reading < threshold_off:
                    beat = False
            
            elif len(SPO2_LIST) < 10:
                red_list.append(red_reading)
                ir_list.append(ir_reading)
                
                red_list = red_list[-100:]
                ir_list = ir_list[-100:]
                
                if len(red_list) == 100 and len(ir_list) == 100:
                    hr, hrb, sp, spb = calc_hr_and_spo2(red_list, ir_list)
                    if hrb and spb and sp != -999:
                        SPO2 = int(sp)

if __name__ == '__main__':
    
    create_oled()
    #display_initial_numbers()
    
    timer = Timer(1)
    timer.init(period=1000, mode=Timer.PERIODIC, callback=display_info)
    main()

4.2 sh1106驱动

#
# MicroPython SH1106 OLED driver, I2C and SPI interfaces
#
# The MIT License (MIT)
#
# Copyright (c) 2016 Radomir Dopieralski (@deshipu),
#               2017-2021 Robert Hammelrath (@robert-hh)
#               2021 Tim Weber (@scy)
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# Sample code sections for ESP8266 pin assignments
# ------------ SPI ------------------
# Pin Map SPI
#   - 3v - xxxxxx   - Vcc
#   - G  - xxxxxx   - Gnd
#   - D7 - GPIO 13  - Din / MOSI fixed
#   - D5 - GPIO 14  - Clk / Sck fixed
#   - D8 - GPIO 4   - CS (optional, if the only connected device)
#   - D2 - GPIO 5   - D/C
#   - D1 - GPIO 2   - Res
#
# for CS, D/C and Res other ports may be chosen.
#
# from machine import Pin, SPI
# import sh1106
 
# spi = SPI(1, baudrate=1000000)
# display = sh1106.SH1106_SPI(128, 64, spi, Pin(5), Pin(2), Pin(4))
# display.sleep(False)
# display.fill(0)
# display.text('Testing 1', 0, 0, 1)
# display.show()
#
# --------------- I2C ------------------
#
# Pin Map I2C
#   - 3v - xxxxxx   - Vcc
#   - G  - xxxxxx   - Gnd
#   - D2 - GPIO 5   - SCK / SCL
#   - D1 - GPIO 4   - DIN / SDA
#   - D0 - GPIO 16  - Res
#   - G  - xxxxxx     CS
#   - G  - xxxxxx     D/C
#
# Pin's for I2C can be set almost arbitrary
#
# from machine import Pin, I2C
# import sh1106
#
# i2c = I2C(scl=Pin(5), sda=Pin(4), freq=400000)
# display = sh1106.SH1106_I2C(128, 64, i2c, Pin(16), 0x3c)
# display.sleep(False)
# display.fill(0)
# display.text('Testing 1', 0, 0, 1)
# display.show()
 
from micropython import const
import utime as time
import framebuf
from machine import Pin
 
 
# a few register definitions
_SET_CONTRAST        = const(0x81)
_SET_NORM_INV        = const(0xa6)
_SET_DISP            = const(0xae)
_SET_SCAN_DIR        = const(0xc0)
_SET_SEG_REMAP       = const(0xa0)
_LOW_COLUMN_ADDRESS  = const(0x00)
_HIGH_COLUMN_ADDRESS = const(0x10)
_SET_PAGE_ADDRESS    = const(0xB0)
 
 
class SH1106(framebuf.FrameBuffer):
 
    def __init__(self, width, height, external_vcc, rotate=0):
        self.width = width
        self.height = height
        self.external_vcc = external_vcc
        self.flip_en = rotate == 180 or rotate == 270
        self.rotate90 = rotate == 90 or rotate == 270
        self.pages = self.height // 8
        self.bufsize = self.pages * self.width
        self.renderbuf = bytearray(self.bufsize)
        self.pages_to_update = 0
 
        if self.rotate90:
            self.displaybuf = bytearray(self.bufsize)
            # HMSB is required to keep the bit order in the render buffer
            # compatible with byte-for-byte remapping to the display buffer,
            # which is in VLSB. Else we'd have to copy bit-by-bit!
            super().__init__(self.renderbuf, self.height, self.width,
                             framebuf.MONO_HMSB)
        else:
            self.displaybuf = self.renderbuf
            super().__init__(self.renderbuf, self.width, self.height,
                             framebuf.MONO_VLSB)
 
        # flip() was called rotate() once, provide backwards compatibility.
        self.rotate = self.flip
        self.init_display()
 
    def init_display(self):
        self.fill(0)
        self.show()
        self.poweron()
        # rotate90 requires a call to flip() for setting up.
        self.flip(self.flip_en)
 
    def poweroff(self):
        self.write_cmd(_SET_DISP | 0x00)
 
    def poweron(self):
        self.write_cmd(_SET_DISP | 0x01)
        if self.delay:
            time.sleep_ms(self.delay)
 
    def flip(self, flag=None, update=True):
        if flag is None:
            flag = not self.flip_en
        mir_v = flag ^ self.rotate90
        mir_h = flag
        self.write_cmd(_SET_SEG_REMAP | (0x01 if mir_v else 0x00))
        self.write_cmd(_SET_SCAN_DIR | (0x08 if mir_h else 0x00))
        self.flip_en = flag
        if update:
            self.show(True) # full update
 
    def sleep(self, value):
        self.write_cmd(_SET_DISP | (not value))
 
    def contrast(self, contrast):
        self.write_cmd(_SET_CONTRAST)
        self.write_cmd(contrast)
 
    def invert(self, invert):
        self.write_cmd(_SET_NORM_INV | (invert & 1))
 
    def show(self, full_update = False):
        # self.* lookups in loops take significant time (~4fps).
        (w, p, db, rb) = (self.width, self.pages,
                          self.displaybuf, self.renderbuf)
        if self.rotate90:
            for i in range(self.bufsize):
                db[w * (i % p) + (i // p)] = rb[i]
        if full_update:
            pages_to_update = (1 << self.pages) - 1
        else:
            pages_to_update = self.pages_to_update
        #print("Updating pages: {:08b}".format(pages_to_update))
        for page in range(self.pages):
            if (pages_to_update & (1 << page)):
                self.write_cmd(_SET_PAGE_ADDRESS | page)
                self.write_cmd(_LOW_COLUMN_ADDRESS | 2)
                self.write_cmd(_HIGH_COLUMN_ADDRESS | 0)
                self.write_data(db[(w*page):(w*page+w)])
        self.pages_to_update = 0
 
    def pixel(self, x, y, color=None):
        if color is None:
            return super().pixel(x, y)
        else:
            super().pixel(x, y , color)
            page = y // 8
            self.pages_to_update |= 1 << page
 
    def text(self, text, x, y, color=1):
        super().text(text, x, y, color)
        self.register_updates(y, y+7)
 
    def line(self, x0, y0, x1, y1, color):
        super().line(x0, y0, x1, y1, color)
        self.register_updates(y0, y1)
 
    def hline(self, x, y, w, color):
        super().hline(x, y, w, color)
        self.register_updates(y)
 
    def vline(self, x, y, h, color):
        super().vline(x, y, h, color)
        self.register_updates(y, y+h-1)
 
    def fill(self, color):
        super().fill(color)
        self.pages_to_update = (1 << self.pages) - 1
 
    def blit(self, fbuf, x, y, key=-1, palette=None):
        super().blit(fbuf, x, y, key, palette)
        self.register_updates(y, y+self.height)
 
    def scroll(self, x, y):
        # my understanding is that scroll() does a full screen change
        super().scroll(x, y)
        self.pages_to_update =  (1 << self.pages) - 1
 
    def fill_rect(self, x, y, w, h, color):
        super().fill_rect(x, y, w, h, color)
        self.register_updates(y, y+h-1)
 
    def rect(self, x, y, w, h, color):
        super().rect(x, y, w, h, color)
        self.register_updates(y, y+h-1)
 
    def register_updates(self, y0, y1=None):
        # this function takes the top and optional bottom address of the changes made
        # and updates the pages_to_change list with any changed pages
        # that are not yet on the list
        start_page = max(0, y0 // 8)
        end_page = max(0, y1 // 8) if y1 is not None else start_page
        # rearrange start_page and end_page if coordinates were given from bottom to top
        if start_page > end_page:
            start_page, end_page = end_page, start_page
        for page in range(start_page, end_page+1):
            self.pages_to_update |= 1 << page
 
class SH1106_I2C(SH1106):
    def __init__(self, width, height, i2c, addr=0x3c,
                 rotate=0, external_vcc=False, delay=0):
        self.i2c = i2c
        self.addr = addr
        self.temp = bytearray(2)
        self.delay = delay
        super().__init__(width, height, external_vcc, rotate)
 
    def write_cmd(self, cmd):
        self.temp[0] = 0x80  # Co=1, D/C#=0
        self.temp[1] = cmd
        self.i2c.writeto(self.addr, self.temp)
 
    def write_data(self, buf):
        self.i2c.writeto(self.addr, b'\x40'+buf)
 
 
class SH1106_SPI(SH1106):
    def __init__(self, width, height, spi, dc, cs=None,
                 rotate=0, external_vcc=False, delay=0):
        dc.init(dc.OUT, value=0)
        if cs is not None:
            cs.init(cs.OUT, value=1)
        self.spi = spi
        self.dc = dc
        self.cs = cs
        self.delay = delay
        super().__init__(width, height, external_vcc, rotate)
 
    def write_cmd(self, cmd):
        if self.cs is not None:
            self.cs(1)
            self.dc(0)
            self.cs(0)
            self.spi.write(bytearray([cmd]))
            self.cs(1)
        else:
            self.dc(0)
            self.spi.write(bytearray([cmd]))
 
    def write_data(self, buf):
        if self.cs is not None:
            self.cs(1)
            self.dc(1)
            self.cs(0)
            self.spi.write(buf)
            self.cs(1)
        else:
            self.dc(1)
            self.spi.write(buf)

4.3utf-8转gb2312

class gb2312(object):
    def __init__(self):
        self.f = open('utf2gb2312.bin', 'r', encoding='utf-8')

    def b2i(self, byte):  # bytes转int
        r = 0
        for i in range(len(byte)):
            r = (r << 8) + byte[i]
        return r

    def i2b(self, num):  # int转bytes
        num = int(num, 16)
        return num.to_bytes(2, 'big')

    def one_char(self, char):  # 将一个字符转化成gb2312
        utf_byte = char.encode('utf-8')
        r = self.B_S(0, 7296, self.b2i(utf_byte))
        gb2312_byte = self.i2b(r)
        # print(gb2312_byte)
        return gb2312_byte

    def strs(self, st):  # 将字符串转化成gb2312
        r = b''
        for s in st:
            # print(s.encode('utf-8'))
            if len(s.encode('utf-8')) <= 1:
                r += s.encode('utf-8')
            else:
                r += self.one_char(s)
        return r

    def B_S(self, low, high, m):  # 二分查找
        if 0 <= low <= high <= 7296:
            mid = (low + high) // 2
            self.f.seek(mid * 12)
            data = self.f.read(12)
            utf = data[0:6]
            if int(utf, 16) < m:
                return self.B_S(mid + 1, high, m)
            elif int(utf, 16) > m:
                return self.B_S(low, mid - 1, m)
            else:
                return data[7:-1]

    def __del__(self):
        self.f.close()
        
fontbyte = gb2312()
# r = fontbyte.strs('你')
# print(r)
        


4.4点阵字显示

import binascii
import gb2312
import framebuf
from machine import Pin, I2C
import sh1106

class OLEDController:
    def __init__(self, scl_pin=22, sda_pin=21, font_size=2):
        self.scl_pin = scl_pin
        self.sda_pin = sda_pin
        self.font_size = font_size  # 添加字体大小变量
        self.i2c = I2C(0, scl=Pin(self.scl_pin), sda=Pin(self.sda_pin))
        self.oled = sh1106.SH1106_I2C(128, 64, self.i2c)
        self.buf = bytearray(128 * 64 // 8)
        self.fb = framebuf.FrameBuffer(self.buf, 128, 64, framebuf.MONO_HLSB)
        self.KEYS = [0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01]
        self.rect_list = [[] for _ in range(16)]
        self.font_file = "HZK16"

    def display_char(self, char, x, y):
        try:
            self.display_chinese(char, x, y)
        except:
            self.fb.text(char, x, y)

        self.oled.blit(self.fb, 0, 0)
        self.oled.show()

    def display_chinese(self, char, x, y):
        self.rect_list = [[] for _ in range(16)]

        get_gb2312 = gb2312.fontbyte.strs(char)
        hex_str = binascii.hexlify(get_gb2312).decode('utf-8')
        area = eval('0x' + hex_str[:2]) - 0xA0
        index = eval('0x' + hex_str[2:]) - 0xA0
        offset = (94 * (area-1) + (index-1)) * 32

        font_rect = None
        with open(self.font_file, "rb") as f:
            f.seek(offset)
            font_rect = f.read(32)

        for k in range(len(font_rect) // 2):
            row_list = self.rect_list[k]
            for j in range(2):
                for i in range(8):
                    asc = font_rect[k * 2 + j]
                    flag = asc & self.KEYS[i]
                    row_list.append(flag)

        # 计算新的字体大小
        new_font_size = self.font_size // 2
        for row in range(len(self.rect_list)):
            for col in range(len(self.rect_list[0])):
                if self.rect_list[row][col]:
                    self.fb.fill_rect(x + col * new_font_size, y + row * new_font_size, new_font_size, new_font_size, 1)

    def display_chinese_on_oled(self, text, x=0, y=0):
        for index, char in enumerate(text):
            self.display_char(char, x + index * self.font_size*8, y)


# 创建 OLED 控制器实例
#oled_controller = OLEDController()

# 调用示例
#oled_controller.display_chinese_on_oled("你好世界word", 0, 0)

5本作品使用说明

对设备接入USB供电,设备开机并依次显示项目名称以及成员名称5s左右后进入功能界面,当手指未按压在心率血氧传感器上时,屏幕显示put your finger, 当手指按压在心率血氧传感器上时,屏幕上显示心率与血氧浓度的实时数据并开始倒计时读秒,读秒结束后即可显示测量的最终结果,当手指在中途离开传感器时,屏幕显示put your finger并清除数据等待下一次按压,当手指测量完成后离开传感器时,屏幕显示put your finger并清除数据等待下一次测量

6资源下载

环境的配置的教程来自网络,直接转载的

所有代码和资料已经打包好了,下载即可,如果对你有帮助,点个关注,谢谢

esp32-micropython-血氧心率测试仪(swjtu hanser_05).rar官方版下载丨最新版下载丨绿色版下载丨APP下载-123云盘

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值