一、项目背景
非常荣幸能再次参加DFROBOT在新年举办的义教信息科技教学活动实践案例征集活动。之前已经参与了行空板K10(以下均简称K10)的内测,对K10是又爱又激动,首先是K10高度集成了多款传感器(如果单独购买,绝对需要更多花费),我感觉K10=ESP32 S3主控板+离线人工智能+显示屏+多媒体+摄像头+……。K10实在是功能强大,特别适合中小学信息科技和创客教育,所以在年前我采购了几十块K10,用来支持部分学校和我的工作室成员,期待老师们能在自己的手上发挥出K10更大的功效。
通过学习《义务教育信息科技课程教学指南》,我挑选了义务教育信息科技八年级第22课:健康生活新设备的内容,作为本项目的主题,打算使用K10与其他传感器,实现人们身体的各项数据的采集和记录,通过物联网服务器进行数据交互。
二、硬件清单
器材 |
作用 |
行空板K10 |
获取心率、血氧数据,接收物联网数据、显示数据 |
掌控:Microbit扩展板 |
为行空板K10提供扩展接口 |
ESP32 |
获取身高、体重数据,并发送到物联网服务器 |
超声波传感器 |
测量身高 |
HX711称重传感器 |
测量体重 |
MAX30102传感器 |
测量心率和血氧 |
行空板M10 |
物联网服务器(siot v1.0) |
三、项目实现
K10作为本项目的主角,负责采集部分数据,同时将整个项目中的获得的数据和分析的数据,呈现在它的显示屏上。ESP32主控板作为数据采集的辅助(主要是K10的扩展接口没搞定才出的下策),将用来采集身高和体重的数据,并发送到物联网服务器(行空板M10)上。为了提高挑战的难度,本项目将全部采用MicroPython语言,而不选用Mind+,同时更容易与教材中采用的Python语言接轨。
为了更容易呈现本项目的实现思路,我制作了以下的思维导图。
图1 项目思维导图
这是项目硬件全家福
图2 项目硬件全家福
单击可看
K10上Micropython固件烧录教程。假如你使用Thonny编写程序,那么这个软件本身就具备烧录固件的功能,方法是先单击菜单“运行>配置解释器”
图3 配置解释器菜单命令
然后在弹出的对话框中选择“解释器”,根据下图步骤进行选择
图4 配置解释器对话框
单击上图第4步后,弹出如下对话框,根据步骤依次操作即可。
图5 选择固件开始烧录
如果无法烧录,可能是K10没有进入烧录模式,在烧录前,拔下USB线,然后按住K10上的BOOT键再插入USB线,然后放掉BOOT键。
1. 身高测量实现
在八年级信息科技教材第22课《健康生活新设备》中,提到了“身高记录仪”这一设备,其原理是利用了超声波传感器测距的功能来实现身高的测量。
超声波测量原理
原理概述:超声波是一种频率高于 20000Hz 的声波,它在空气中传播时,遇到障碍物会反射回来。利用超声波的这个特性,仪器向人体头顶发射超声波信号,同时开始计时,当超声波遇到人体头顶反射回来被仪器接收时停止计时。根据超声波在空气中的传播速度以及传播时间,就可以计算出仪器到人体头顶的距离,再结合仪器本身的安装高度等参数,就能得出人体的身高。
具体公式:
假设超声波从发射到接收的时间为 t,超声波在空气中的传播速度为 v(通常在常温下约为 340m/s),那么仪器到人体头顶的距离
(因为超声波走了一个往返路程)。然后用仪器安装高度 H 减去 d 就得到身高 h = H - d。
图6 身高计算示意图
优缺点:
优点是测量速度快、非接触式测量,对人体没有伤害,操作简单便捷;缺点是容易受到外界环境因素的影响,如空气流动、温度变化等,可能会导致测量结果出现一定误差。例如,温度变化会影响超声波在空气中的传播速度,从而影响测量精度。
硬件连接
本项目我采用了掌控/micro:bit扩展板,以便连接超声波传感器,我想扩展板的IO口还是挺丰富的,因此通过穷举可用的IO口,发现大多数都不能使用,侦测到P0和P1口相对ESP32 S3的IO口为1和2。我就觉得奇怪,为什么这么多的IO接口不能使用?后来通过Mind+软件发现外接传感器还只能使用P0和P1口。我又查阅了K10的原理图,发现ESP32 S3的大多数IO口都被板载传感器用掉了。同时由于该款扩展板并不是为K10量身定制的,因此只有P0和P1是可用的,而IO35-37虽然没有被占用,但不一定在这款扩展板中引出这些IO引脚。
图7 K10引脚原理图
因此我只能将超声波传感器的Trig和Echo引脚接到扩展板的P0(IO1)和P1(IO2)上,另外要注意的是,K10要插在扩展板的Micro:bit一侧。
参考程序:
MicroPython超声波测量身高的参考程序如下:
from machine import Pin
import time
def get_distance():
# 发送触发信号
trig.value(0)
time.sleep_us(2)
trig.value(1)
time.sleep_us(10)
trig.value(0)
# 等待回声信号
while echo.value() == 0:
pulse_start = time.ticks_us()
while echo.value() == 1:
pulse_end = time.ticks_us()
# 计算脉冲持续时间
pulse_duration = pulse_end - pulse_start
# 计算距离(单位:厘米)
distance = pulse_duration * 0.0343 / 2
return distance
# 定义超声波触发引脚和回声引脚
trig = Pin(1, Pin.OUT)
echo = Pin(2, Pin.IN)
while True:
dist = get_distance() #获得测量结果存入变量dist中
print(dist)
time.sleep(1)
测试效果
接下来我将超声波传感器固定在头顶的吊灯上,然后用较长的线连接到K10。测量时的情况如下图所示。
图8 测量身高
通过测量,发现测得的值要比真实的身高稍微多2厘米左右,估计是头发的原因,因此我们可以在程序中进行一下修正。这里不得不提一句,对于传感器获得的数据,我们一般需要进行一些处理,常见的处理有修正、清洗和取平均值等。
到这里为止,我们的项目已经初见雏形,可以实现身高数据的获取,接下来我想将超声波传感器获得的数据显示在K10屏幕上,结果发现在原有程序的基础上导入unihiker_k10的screen库后,与上面的超声波传感器程序有冲突,而里面提供的超声波传感器模块ultrasonic不能使用(指定接口提示无效的IO口),另外因为目前不清楚unihiker_k10这个模块里面一些函数的使用方法,因此我另外找了一个开源的超声波传感器hc_sr04驱动模块(驱动下载见最后参考资料),发现可以正常使用。参考程序如下:
from unihiker_k10 import screen
from hcsr04 import HCSR04
import time
screen.init(dir=2) #代码中不调用时默认为2
sensor = HCSR04(trigger_pin=1, echo_pin=2)
while True:
dist = sensor.distance_cm() #获得测量结果存入变量dist中
print(dist)
screen.draw_text(text="Height: "+str(round(dist,1))+" cm",line=1)
screen.show_draw()
time.sleep(1)
2. 体重测量实现
压力传感器原理
我们经常可以在商场、医院等地方看到人身称重设备,当人站上设备几秒钟后,设备通过显示屏或语音将人身重量反馈给大家,其实这些装备通常使用压力传感器来实现体重测量。这些装置中常用的压力传感器类型是应变片式压力传感器。其工作原理基于应变效应,当外力作用于弹性元件(如金属弹性体)时,弹性元件会发生形变,粘贴在弹性元件上的应变片也会随之发生形变。应变片是一种将机械应变转换为电阻变化的敏感元件,其电阻值会根据形变量的大小而改变。生活中利用压力传感器实现称重除了人体称重之外,还有货物电子秤、工业称重系统、公路上的汽车衡等。
称重实现
本项目HX711传感器实现称重,是一款专为高精度电子秤而设计的 24 位 A/D(模拟 - 数字)转换器芯片,常与压力传感器(如应变式压力传感器)配合使用,用于将压力传感器输出的模拟信号转换为数字信号,以便微控制器进行处理和显示重量值。
我尝试的压力传感器称重范围分别是1KG、5KG和20KG,考虑到本次只是为了实现称重实验,因此没有采用更大重量的压力传感器(因为真正实现人体称重需要定制压力传感器的站立平台)。
请记录HX711连接到ESP32的两个引脚的编号,本程序分别是DT连接到IO1,SLK(或SCK)连接到IO2。
图9 HX711与K10连接
参考程序
### 这个程序是最后测得还可以的程序。
from machine import Pin
import time
# HX711 引脚定义
PIN_DT = 1 # 数据引脚
PIN_SCK = 2 # 时钟引脚
# 初始化 HX711
class HX711:
def __init__(self, dout, pd_sck):
self.dout = Pin(dout, Pin.IN)
self.pd_sck = Pin(pd_sck, Pin.OUT)
self.pd_sck.value(0)
self.offset = 0
self.scale = 1
def read(self):
# 等待数据准备好
while self.dout.value() == 1:
pass
# 读取 24 位数据
data = 0
for _ in range(24):
self.pd_sck.value(1)
time.sleep_us(1)
data = (data << 1) | self.dout.value()
self.pd_sck.value(0)
time.sleep_us(1)
# 设置增益为 128
for _ in range(1):
self.pd_sck.value(1)
time.sleep_us(1)
self.pd_sck.value(0)
time.sleep_us(1)
# 将 24 位数据转换为有符号整数
if data & 0x800000:
data -= 0x1000000
return data
def read_average(self, times=3):
sum = 0
for _ in range(times):
sum += self.read()
return sum / times
def get_value(self, times=3):
return self.read_average(times) - self.offset
def get_units(self, times=3):
return self.get_value(times) / self.scale
def tare(self, times=3):
self.offset = self.read_average(times)
def set_scale(self, scale):
self.scale = scale
def set_offset(self, offset):
self.offset = offset
# 初始化 HX711 对象
hx = HX711(PIN_DT, PIN_SCK)
# 校准
hx.tare() # 去皮,设置零点
hx.set_scale(1144.2) # 设置比例因子,需要根据实际情况调整
print(hx.offset)
# 主循环
while True:
value = hx.get_units()
print("Weight: {:.2f} g".format(value))
time.sleep(1)
初次运行需要进行一次去皮操作,什么是去皮,比如一个电子秤上没有放任何东西,那应该称重的结果是0,但有时候要称一些不容易放的物品,比如白糖,比如大米,那肯定有盛这些物体的容器,一般我们可以先称容器+物品,然后再称容器,得到物品的净重。而电子秤的去皮操作可以先将容器放在电子秤上,使其重量为0,这样当放上要称重的物品,就可以直接得到净重了。
另外,去皮后,还要称一个标准物(如砝码等),我因为手头没有砝码,所以这里采用了手机,因为手机查到出厂标准重量(我还特地还买了一个电子秤,以验证重量,可是花了血本了),然后会得到一个称得的数值,需要将这个数值,与手机的标准重量进行一下换算,得到的换算结果就是比例因子,然后就可以开始称重了。
图10 验证标准物重量准备去皮
请注意:以上超声波传感器和称重传感器只能单独接在K10扩展板的P0和P1接口,不能同时使用。
3. 物联网实现
因为本项目要采集二个数据,分别是身高和体重,而K10外接扩展板只能使用两个I/O口,因此我将超声波传感器和称重传感器接在ESP32上,然后通过网络连接物联网服务器,与K10进行数据交互。
流程是这样的:当按下K10的A键,实现测量身高,按下B键,实现称重,将结果显示在K10的屏幕上。同时数据也会记录在物联网服务器上。
这里我采用行空板M10作为物联网服务器,采用的是SIoT v1.0版本,行空板M10连接电脑后,通过浏览器地址栏输入10.1.2.3进行访问,然后依次点击左侧的“应用开关”,然后在右侧单击“启动服务”(下图已经是启动的状态,所以显示的是“停止服务”),然后单击下面的“打开页面”进入SIoT服务管理界面。另外,我已经将行空板M10上的SIoT版本进行了升级,所以可以在V1和V2之间切换,本项目采用V1版本。
图11 启用行空板M10上的SIoT服务
打开SIoT服务后,在弹出的登录界面输入用户名siot密码dfrobot登录,然后出现管理界面,单击设备列表,可以看到我已经创建了三个设备
图12 创建三个消息主题
可以通过右侧的“发送消息”,输入主题和消息来进行测试,一旦发送,即创建主题。
图13 通过发送消息创建主题
当然,我们也可以通过代码来创建主题,在通过网络成功连接物联网服务器的前提下,通过下面两条语句来发送消息(主题未找到会创建主题),物联网服务器连接下面会说明。
mqttclient.publish(topic='siot/height',content= 'on')
mqttclient.publish(topic='siot/weight',content= 'on')
1. K10上通过A/B按钮发送指令消息
我们首先在K10上编写程序,实现按下K10上的A键和B键往物联网服务器的siot/projects主题发送消息,按A键发送height表示开始量身高,按下B键发送消息weight表示开始称重。
K10上编写如下程序:
from unihiker_k10 import screen,tf_card,button
from unihiker_k10 import wifi,mqttclient
import time
#初始化屏幕 设置方向为(0-3)
screen.init(dir=2)#代码中不调用时默认为2
screen.clear()
wifi.connect(ssid="zaiba",psd="密码",timeout=50000) #尝试连接wifi网络。可以不写参数名称。timeout为可选参数,表示连接超时时长,默认超时时间为10000毫秒
screen.draw_text(text="Start connecting WIFI...")
screen.show_draw()
time.sleep(1)
while not wifi.status():
pass
screen.clear()
screen.draw_text(text="wifi connected. "+wifi.info())
screen.show_draw()
time.sleep(1)
screen.clear()
mqttclient.connect(server= "192.168.183.24",
port=1883,
client_id="",
user= "siot" ,
psd= "dfrobot") #阻塞运行,默认超时时间为3秒
mqttclient.connected() #返回连接状态
bt_a=button(button.a)#初始化板载按键传感器 A
bt_b=button(button.b)#初始化板载按键传感器 B
def button_a_released():
#将摄像头画面显示到屏幕上
print('start testing Height')
screen.draw_text('start testing Height',x=20,y=10)
screen.show_draw()
mqttclient.publish(topic='siot/projects',content= 'height')
def button_b_released():
print('start testing Weight')
screen.draw_text('start testing Weight',x=20,y=10)
screen.show_draw()
mqttclient.publish(topic='siot/projects',content= 'weight')
bt_a.event_released = button_a_released
bt_b.event_released = button_b_released
#如主题已创建则注释掉下面两条语句
mqttclient.publish(topic='siot/height',content= 'on')
mqttclient.publish(topic='siot/weight',content= 'on')
while True:
pass
#time.sleep(1)
运行程序,按下K10上的A键或B键,就会发送消息到物联网服务器,以下是物联网服务器管理页面中我们看到的接收到的消息:
图14 接收到的消息
2. ESP32根据接收到的消息执行相应的测量程序
ESP32联网后,接收到物联网服务器上的消息,执行相应的程序,即接收到消息“height”则启动测量身高的程序,接收到消息“weight”后执行称重的程序。然后将测量的结果,发送到物联网服务器相应的主题,身高发送到主题siot/height,体重发送到主题siot/weight。
from hcsr04 import HCSR04
from umqtt.simple import MQTTClient
import machine
import time
# 初始化 HX711
class HX711:
def __init__(self, dout, pd_sck):
self.dout = Pin(dout, Pin.IN)
self.pd_sck = Pin(pd_sck, Pin.OUT)
self.pd_sck.value(0)
self.offset = 0
self.scale = 1
def read(self):
# 等待数据准备好
while self.dout.value() == 1:
pass
# 读取 24 位数据
data = 0
for _ in range(24):
self.pd_sck.value(1)
time.sleep_us(1)
data = (data << 1) | self.dout.value()
self.pd_sck.value(0)
time.sleep_us(1)
# 设置增益为 128
for _ in range(1):
self.pd_sck.value(1)
time.sleep_us(1)
self.pd_sck.value(0)
time.sleep_us(1)
# 将 24 位数据转换为有符号整数
if data & 0x800000:
data -= 0x1000000
return data
def read_average(self, times=3):
sum = 0
for _ in range(times):
sum += self.read()
return sum / times
def get_value(self, times=3):
return self.read_average(times) - self.offset
def get_units(self, times=3):
return self.get_value(times) / self.scale
def tare(self, times=3):
self.offset = self.read_average(times)
def set_scale(self, scale):
self.scale = scale
def set_offset(self, offset):
self.offset = offset
def WIFIconnect(): #无线链接
import network
ssid='zaiba' #无线名称
password='密码' #修改为无线密码
station=network.WLAN(network.STA_IF)
if station.isconnected() == True:
print("WiFi already connected")
print(station.ifconfig())
return
station.active(True)
station.connect(ssid,password)
while station.isconnected() == False:
pass
print("Connection successful")
print(station.ifconfig())
WIFIconnect()
# SIoT 服务器配置
SIOT_SERVER = "192.168.183.24" # SIoT 服务器地址
SIOT_PORT = 1883 # MQTT 默认端口
CLIENT_ID = "esp32_client" # 客户端 ID
SIOT_USER = "siot"
SIOT_PASSWORD = "dfrobot"
TOPIC_SUB = "siot/projects" # 订阅的主题
TOPIC_PUB1 = "siot/height" # 发布的主题
TOPIC_PUB2 = "siot/weight" # 发布的主题
# 回调函数:当收到消息时触发
def on_message(topic, msg):
print("Received message:", msg.decode())
if msg == b"height":
print("sStart measure height")
sensor = HCSR04(trigger_pin=27, echo_pin=26) # 初始化超声波传感器
distance = round(sensor.distance_cm(),2)
client.publish(TOPIC_PUB1, str(distance)) # 发送响应消息
elif msg == b"weight":
print("Start measure weight")
hx = HX711(16, 17)
get_weight = rount(hx.get_units(),2)
client.publish(TOPIC_PUB2, str(get_weight)) # 发送响应消息
# 连接 SIoT 服务器
try:
client = MQTTClient(CLIENT_ID, SIOT_SERVER, SIOT_PORT, SIOT_USER, SIOT_PASSWORD)
client.set_callback(on_message) # 设置回调函数
client.connect()
print("Connected to SIoT server")
client.subscribe(TOPIC_SUB) # 订阅主题
print(f"Subscribed to topic: {TOPIC_SUB}")
except Exception as e:
print("Failed to connect to SIoT server:", e)
machine.reset()
# 主循环:等待消息
try:
while True:
client.check_msg() # 检查是否有新消息
time.sleep(1) # 稍微延迟,避免占用过多 CPU
except Exception as e:
print("Error:", e)
client.disconnect()
machine.reset()
3. K10上显示测量结果
通过esp32上连接的传感器,将测量的身高和体重数据发送到物联网服务器,然后K10再接收这两个数据,另外再计算出BMI指数,并显示在屏幕上。之前已经编写了一部分K10上的程序(根据按键发送相应的命令消息),对这个程序进行修改和完善。
from unihiker_k10 import screen,tf_card,button
from unihiker_k10 import wifi,mqttclient
import time
#初始化屏幕 设置方向为(0-3)
screen.init(dir=2)#代码中不调用时默认为2
screen.clear()
wifi.connect(ssid="zaiba",psd="密码",timeout=50000) #尝试连接wifi网络。可以不写参数名称。timeout为可选参数,表示连接超时时长,默认超时时间为10000毫秒
screen.draw_text(text="Start connecting WIFI...")
screen.show_draw()
time.sleep(1)
while not wifi.status():
pass
screen.clear()
screen.draw_text(text="wifi connected. "+wifi.info())
screen.show_draw()
time.sleep(1)
screen.clear()
def received_height():
global height
height=float(mqttclient.message(topic='siot/height'))
def received_weight():
global weight
weight=float(mqttclient.message(topic='siot/weight'))
mqttclient.connect(server= "192.168.183.24", # 行空板M10连网后的IP地址
port=1883,
client_id="",
user= "siot" ,
psd= "dfrobot")
mqttclient.connected() #返回连接状态
bt_a=button(button.a)#初始化板载按键传感器 A
bt_b=button(button.b)#初始化板载按键传感器 B
def button_a_released():
#将摄像头画面显示到屏幕上
print('start testing Height')
screen.draw_text('start testing Height',x=20,y=10)
screen.show_draw()
mqttclient.publish(topic='siot/projects',content= 'height')
def button_b_released():
print('start testing Weight')
screen.draw_text('start testing Weight',x=20,y=10)
screen.show_draw()
mqttclient.publish(topic='siot/projects',content= 'weight')
bt_a.event_released = button_a_released
bt_b.event_released = button_b_released
#如主题已创建则注释掉下面两条语句
# mqttclient.publish(topic='siot/height',content= 'on')
# mqttclient.publish(topic='siot/weight',content= 'on')
mqttclient.received (topic='siot/height', #对应主题收到消息时,回调函数
callback=received_height) #通过callback指定回调函数
mqttclient.received (topic='siot/weight', #对应主题收到消息时,回调函数
callback=received_weight) #通过callback指定回调函数
screen.draw_text(text="Body Health",x=50,y=40)
height=weight=bmi=0
bmi_text=""
while True:
print(height,weight)
screen.draw_text(text="Height: "+str(height),x=20,y=100)
screen.draw_text(text="Weight: "+str(weight),x=20,y=130)
if height!=0 and weight!=0:
bmi=weight/(height*height)*10000
if bmi<18.5:
bmi_text="Underweight"
elif 18.5<=bmi<=23.9:
bmi_text="Normal weight"
elif 23.9<bmi<=27.9:
bmi_text="Overweight"
elif bmi>28:
bmi_text="Fat"
screen.draw_text(text="BMI: "+str(round(bmi,1)),x=20,y=160)
screen.draw_text(text="Health: "+bmi_text,x=20,y=190)
screen.show_draw()
time.sleep(1)
# screen.draw_text(text="Blood oxygen: "+str(blood),x=20,y=250)
# screen.draw_text(text="Pulse: "+str(pulse),x=20,y=280)
四、拓展反思
功能扩展:检测心率和血氧
心率和血氧的检测也是衡量人体健康的一个重要指标,通过MAX30102传感器可以方便获得人体的心率和血氧指标,适用于制作穿戴检测设备。这里我发现K10具备板载I2C接口(Gravity接口),但我手头的MAX30102传感器是普通的引脚,因此我稍微转接了一下,以适应板载Gravity接口。
图15 MAX30102传感器连接到K10的I2C接口
同时我在GITHUB上找来MAX30102传感器的驱动库,另外在编写程序的时候需要注意,通常I2C在ESP32上的两个数据引脚是21和22,但通过仔细查看K10引脚原理图,发现SCL是48,SDA是47,需要注意。
以下是在K10上编写获取心率和血氧的参考程序,大家可以结合上面的K10主程序进行修改,将血氧和心率值也显示在K10的屏幕上。
from machine import sleep, SoftI2C, Pin, Timer
from utime import ticks_diff, ticks_us
from max30102 import MAX30102, MAX30105_PULSE_AMP_MEDIUM
BEATS = 0 # 存储心率
SPO2 = 0 # 存储血氧值
FINGER_FLAG = False # 默认表示未检测到手指
def display_info(t):
# 如果没有检测到手指,那么就不显示
if FINGER_FLAG is False:
return
print('心率: ', BEATS, "bpm")
print('血氧: ', SPO2, "%")
def calculate_spo2(red_reading, ir_reading):
"""
计算血氧饱和度(SpO2)
:param red_reading: 红光信号值
:param ir_reading: 红外光信号值
:return: 血氧饱和度(SpO2)
"""
# 计算红光和红外光的 AC 和 DC 分量
red_ac = max(red_reading) - min(red_reading)
red_dc = sum(red_reading) / len(red_reading)
ir_ac = max(ir_reading) - min(ir_reading)
ir_dc = sum(ir_reading) / len(ir_reading)
# 计算 R 值(用于 SpO2 计算)
r_value = (red_ac / red_dc) / (ir_ac / ir_dc)
# 计算血氧饱和度(SpO2)
spo2 = 104 - 17 * r_value
return round(spo2, 2) # 保留两位小数
def main():
global BEATS, SPO2, FINGER_FLAG # 如果需要对全局变量修改,则需要global声明
# 创建I2C对象(检测MAX30102)
i2c = SoftI2C(sda=Pin(47), scl=Pin(48), freq=400000) # Fast: 400kHz, slow: 100kHz
# 创建传感器对象
sensor = MAX30102(i2c=i2c)
# 检测是否有传感器
if sensor.i2c_address not in i2c.scan():
print("没有找到传感器")
return
elif not (sensor.check_part_id()):
# 检查传感器是否兼容
print("检测到的I2C设备不是MAX30102或者MAX30105")
return
else:
print("传感器已识别到")
print("使用默认配置设置传感器")
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() # Starting time of the acquisition
MAX_HISTORY = 32
history_red = [] # 存储红光信号历史数据
history_ir = [] # 存储红外光信号历史数据
beats_history = []
beat = False
while True:
sensor.check()
if sensor.available():
# FIFO 先进先出,从队列中取数据。都是整形int
red_reading = sensor.pop_red_from_storage()
ir_reading = sensor.pop_ir_from_storage()
if red_reading < 1000:
print('No finger')
FINGER_FLAG = False # 表示没有放手指
continue
else:
FINGER_FLAG = True # 表示手指已放
# 计算心率
history_red.append(red_reading)
history_ir.append(ir_reading)
# 为了防止列表过大,这里取列表的后32个元素
history_red = history_red[-MAX_HISTORY:]
history_ir = history_ir[-MAX_HISTORY:]
# 提取必要数据
minima, maxima = min(history_red), max(history_red)
threshold_on = (minima + maxima * 3) // 4 # 3/4
threshold_off = (minima + maxima) // 2 # 1/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:] # 只保留最大30个元素数据
BEATS = round(sum(beats_history) / len(beats_history), 2) # 四舍五入
# 计算血氧值
SPO2 = calculate_spo2(history_red, history_ir)
if beat and red_reading < threshold_off:
beat = False
if __name__ == '__main__':
# 1. 创建定时器
timer = Timer(1)
# 2. 设置定时器的回调函数,每1秒钟调用1次display_info函数(用来显示数据)
timer.init(period=1000, mode=Timer.PERIODIC, callback=display_info)
# 3. 调用主程序,用来检测数据
main()
测试中有待解决的问题
在测试中,我发现目前有几个问题需要解决
- K10的内含模块的使用手册有待加强,因为我通过help('modules')和dir(模块名)命令可以获得模块名称和内含函数,但使用时需要提供几个参数,具体是否返回值等,不是特别清楚。比如unihiker_k10自带了HCSR04和ultrasonic这两个库,但不知道如何去使用。
图16 罗列K10的unihiker_k10库
- K10屏幕上显示汉字需要进一步研究,有朋友提供了一些方法,我下次试试。
- K10用完了大多数ESP32 S3的引脚,期待能出一款带引脚HUB的扩展板,板载I2C接口应该也可以引出接上I2C的HUB。
五、参考资料