西南交通大学 电子技术课程设计 心率血氧测试仪
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------>操作系统(Windows、MacOS、Linux等)-------0101010100---->硬件电路
所以,我们也能够大体整理出在ESP32上的运行流程
Python源代码----print("haha")----->特殊的Python解释器-------0101010010---->硬件电路
注意:ESP32上没有运行操作系统
硬件:ESP32
特殊的Python解释器:MicroPython(一种针对于硬件做了特殊处理的Python解释器)
IDE:Thonny(类似Pycharm这个也是个IDE,它对ESP32支持更好 所以选择它)
2.2 下载&安装Thonny
-
Windows(
64位电脑,win8,win10,win11用户)https://doc.itprojects.cn/A0001.micropython.esp32/02.download/03.thonny/thonny-4.0.1.exe
-
Windows(
32位电脑,win7用户)https://doc.itprojects.cn/A0001.micropython.esp32/02.download/03.thonny/thonny-py38-4.0.1.exe
-
Mac(苹果电脑,下载后自带驱动):
https://doc.itprojects.cn/A0001.micropython.esp32/02.download/03.thonny/thonny-4.0.1.pkg
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

在弹出的框中点击下图所示位置
2.6 烧录MicroPython到EPS32

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

会再次弹出1个框

在上图序号1处选择,串口。只要安装了我提供的驱动,那么会看到如下的名字(COM后的序号不一样没事)
注意:ESP32此时要链接USB开机

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

点击“安装”,会在上图3处看到进度,当出现“Done”时表示完成,关闭窗口即可
此时,已经将MicroPython烧录到ESP32中
会再次弹出1个框

在上图序号1处选择,串口。只要安装了我提供的驱动,那么会看到如下的名字(COM后的序号不一样没事)
注意:ESP32此时要链接USB开机
在上图序号2处选择下载的MicroPython固件

点击“安装”,会在上图3处看到进度,当出现“Done”时表示完成,关闭窗口即可
此时,已经将MicroPython烧录到ESP32中
这个方法有时候会出现bug,因为好像是从0x0001000开始烧录有可能烧录失败
那么就得使用乐鑫官方的烧录工具进行烧录
详细看这篇博客https://blog.csdn.net/qq_30895747/article/details/138012468?spm=1001.2101.3001.10138.3
2.7Thonny与ESP32交互

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

表示此时可以与ESP32交互了,可以写个简单的python语句试试看
2.8 编写Python代码让EPS32执行
2.8.1 开启左侧目录

此时会看左侧的目录

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


在左侧目录树中看到

2.8.3运行Python文件

到此为止,我们就搭建好了整个流程
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进行操作
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云盘
&spm=1001.2101.3001.5002&articleId=140395597&d=1&t=3&u=792c6728dc564b03bd699f3f81ecfb93)
1990

被折叠的 条评论
为什么被折叠?



