简介: 该睡眠呼吸机屏幕界面采用5.6英寸STONE串口屏幕,内置背光,色彩丰富,配备串口接口便于与主控制板通信,触摸屏输入方式省去了键盘,使整个呼吸机外观更加简洁大方。可调节的压力值和延迟时间采用滑动调节方式,操作更加便捷。
记住“遇见沃弗斯”?如图(1)所示,在这篇长篇故事中,作者Roku Mu Xue讲述了孤独的老人李肯辰、他的女儿大宝和狗狗杜宝的故事。李肯辰最初并不喜欢狗狗杜宝, 他的女儿离家到北方工作,只有狗杜包一直陪伴着他;但狗总是半夜把他叫醒,后来得知老人李肯辰患有阵发性睡眠呼吸暂停综合征,狗杜包叫醒他是为了救他的命!

如果你没有这么聪明的狗狗,可以使用天使睡眠呼吸机(图2)。它可以通过在睡眠中持续施加正压(CPAP模式)打开阻塞的气道,帮助预防睡眠呼吸暂停——不同体重可调节压力值,详见图(3)。你还可以设置15-60分钟的延迟压力提升(演示时可使用秒代替分钟以直观观察效果),确保在入睡后再逐步提升气压,避免高气压突然启动影响睡眠。


该睡眠呼吸机的显示界面采用5.6英寸STONE串口触摸屏,带背光,色彩丰富,并配备串口便于与主控制板通信,触摸输入消除了键盘,使整个呼吸机外观更加简洁大方。可调节的压力值和延迟时间为滑动选项——图(4),操作更便捷。此外,延迟时间倒计时和压力表显示值在专用界面中显示——图(5),直观清晰。开发过程记录如下。


描述
- 人机界面(HMI)和控制创建说明
- 界面下载
- 硬件连接和命令测试
- 调试代码
1、呼吸机屏幕设计和控制创建说明
如图 (2) 所示,通过“确定”按钮进入呼吸机参数调整界面,上下滑动调整左侧的空气压力值,同时上下滑动调整右侧的延迟增压时间。点击“启动”按钮开始增压,或延迟增压,红色“停止”按钮可随时退出或停止增压。按下 “压力表”按钮进入压力显示和时间延迟倒计时显示界面。
可在按钮属性页面调整参数:round_rsdius: 50,以实现圆形和弧形按钮。图(6)展示了“停止”按钮的属性截图。

字体在 STONE 显示屏上会以小写形式显示,而在 PC 上会显示得更大,因此请在 PC 上将字体大小设计得尽可能大,以免下载到屏幕后显得过小,导致需要多次调整字体大小。
文本选择器设计时,中间高亮部分的字体比两侧更大,颜色为红色以突出显示,例如ts11属性参数如图(7)所示。实际效果如图(4)所示。
图(7) 气压选择器参数设计

指针仪表 gauge_pointer 控件的图标样式在图 (8) 中选择, 指针的锚点由 anchor_x、anchor_y 设置,范围为 0.0 – 1.0。角度设置,当前在 PC -140 指向空气压力 0,在屏幕上,原始位置在中间 0 度,需要程序命令重新指定。
图 (2) 和图 (3) 界面中,涉及控件名称、范围及编程所需内容以统一方式展示,便于编程时快速查阅。
编程控件名称
图 (2) 参数调整界面:
启动按钮名称 = b11
停止按钮名称 = b12
压力表按钮名称 = b13
延迟压力提升选择按钮名称 = cb1
压力选择名称 = ts11 范围 1-20
延迟时间选择名称 = ts12 范围 1-60
图 (3) 压力和倒计时显示屏:
压力表指针名称 = gp1 压力值 0: -140, 20: -110
分钟倒计时名称 = l21
秒倒计时名称 = l22
2、接口下载
点击主菜单下的“调试”—“下载”,选择呼吸机文件夹,系统将生成一个与项目同名的子文件夹(每次下载前需将最后生成的文件夹重命名,否则会返回错误);
检查 STONE 串口屏幕背面的拨动开关至 “设备”位置,使用USB通信电缆直接连接电脑与显示屏的USB接口,电脑将弹出新盘符,将刚生成的项目同名子文件夹/default/复制到 显示屏存储目录下的“raw”文件夹。
3、硬件连接与命令测试
编程前需验证待用命令。按图(9)连接,检查STONE反馈,通过串口助手验证命令有效性。
本项目涉及五种控制类型:
- 按钮
- 标签
- 文本选择器
- 指针仪表
- 复选框
我们将逐一测试并验证它们。
图 (9) 串口助手测试适配器板跳线图
按钮相关:
0x1001 按钮键值主动发送
键值(数据段的最后一个字节):
0x01 按下按钮
0x02 松开按钮(按钮触发点击事件完成)
示例:
参数屏幕的“开始”按钮(名称 = b11)被按下以主动发送命令。
ST<0x10 0x01 0x00 0x04 b11 0x01>ET
串行助手返回十六进制数据: 验证通过
53 54 3C 10 01 00 04 62 31 31 01 3E 45 54 5C 0B
按钮释放(按钮点击动作完成) 激活命令 —- 作为一种方式 62 31 31 02 解码可行!
ST<0x10 0x01 0x00 0x04 b11 0x02>ET
串口助手返回十六进制数据:验证通过
53 54 3C 10 01 00 04 62 31 31 02 3E 45 54 18 0B
注:按钮 b11(ASCII 码 0x62 0x31 0x31)。
标签相关:
set_text 设置标签显示的文本
示例:
设置文本: (最小倒计时名称 = l21)
ST<{“cmd_code”: “set_text”, “type”: “label”, “widget”: “l21”, ‘text’: “29”}>ET 验证顶部屏幕是否正常
文本选择器相关:
0x1081 文本选择器值已发送 (整数类型 主动发布:选择器调整后立即发布)
数据格式:
值:数据段的最后四个字节
示例:(延迟提升时间最小选择器控制名称 = ts12,选项:1-60,当前值 16,当值为 20 时重新选择)
串行助手接收的十六进制数据为。
53 54 3C 10 81 00 08 74 73 31 32 00 00 00 14 3E 45 54 F8 4E OK
屏幕主动给出指令如下:
ST<0x10 0x81 0x00 0x08 ts12 0x00 0x00 0x00 0x00 0x14 >ET ts11 当前值:20 = 0x14
与指针相关的参数:
set_angle 设置指针的旋转角度(整数类型)
示例:(气压计指针名称 = gp1)
设置指针角度参数:以下操作已验证通过
ST<{“cmd_code”: “set_angle”, “type”: “gauge_pointer”, “widget”: ‘gp1’, “angle”:-140}>ET
ST<{“cmd_code”: “set_angle”, “type”: “gauge_pointer”, “widget”: “gp1”, “angle”:30}>ET
ST<{“cmd_code”: “set_angle”, “type”: “gauge_pointer”, “widget”: ‘gp1’, “angle”:30}>ET
ST<{“cmd_code”: “set_angle”, “type”: “指针仪表”, “widget”: “gp1”, “angle”:130}>ET 指针“, ”widget“: ‘gp1’, ”angle”:130}>ET
ST<{“cmd_code”: “set_angle”, “type”: “gauge_pointer”, “widget”: “gp1”, “angle”:-110}>ET
ST<{“cmd_code”: “set_angle”, “type”: “gauge_pointer”, “widget”: ‘gp1’, “angle”:0}>ET
ST<{“cmd_code”: “set_angle”, “type”: “gauge_pointer”, “widget”: ‘gp1’, “angle”:270}>ET
复选框相关:
当状态发生变化时,多选按钮会主动发送。
0x1020 复选按钮值变化后主动发送
示例:(延迟增益选择旋钮名称 = cb1)
值更改时接收的“未选中”串行助手。
53 54 3C 10 20 00 04 63 62 31 00 3E 45 54 AC 46
等同于指令:
ST<0x10 0x20 0x00 0x0D cb1 0x00 >ET 未选中
当值发生变化时,“已选中”串行助手接收。
53 54 3C 10 20 00 04 63 62 31 01 3E 45 54 50 47
等效于指令:
ST<0x10 0x20 0x00 0x0D cb1 0x01 >ET selected
经过上述命令测试,下一步可以放心地编程实现该功能。当然,首先仍需确保硬件连接正确。我们使用 ESP32 开发板实现与 STONE 串口屏幕的通信。
如图 (10) 所示,NodeMCU-32S 开发板的 TX0、RX0 和 GND 需与 STONE 屏幕适配器板对应的引脚连接,以完成 HMI 信息交互。
STONE屏幕接口为232信号,通过适配板进入MCU接口需使用TX和RX信号,以实现电平匹配。
图(10)左侧为NodeMCU-32S开发板,蓝色(TX0)、红色(RX0)引脚连接至适配板跳线位置(蓝色跳线移除后),黑色为GND。

4、调试代码
本项目重点在于按钮解码、多选按钮及文本选择器的处理,以及气压计指针驱动算法、倒计时计时器和延迟升压功能的实现。
启动气压延迟升压过程, ESP32板蓝色LED将闪烁提示,气压显示接口的气压指针将模拟均匀升压至目标气压位置(表中16cmH2O对应160),延迟倒计时将以每秒递减的方式动态显示 (为直观设置1分钟倒计时,实际使用时建议设置15分钟至60分钟)。
以下录制代码已验证,请参考视频查看运行效果。
/*
Frank 2022年5月3日为睡眠呼吸机开发
使用NodeMCU-32s ESP32在Arduino 1.8.13环境下
使用深圳STONE HMI
整数类型:int(-32768,32767)无符号整数:unsigned int(0,65535)
按钮:
启动—“b11”—0x62 0x31 0x31
停止—“b12”—0x
文本选择器:
压力—‘ts11’—0x74 0x73 0x31 0x31
延迟时间–“ts12”—
复选框:
延迟增强—“cb1”—0x63 0x62 0x31
标签:
分钟—“l21”—
秒 – ‘l22’ –
指针:—“gp1”–
*/
//——睡眠通气机使用———-2022.05.03——
int delayTimeMin = 1; //延迟压力增压的最小值
int delayTimeSec = 0;
int inPressure = 20; // cmH2O 设置
int nowPressure = 0; // 当前 cmH2O
int start = 0; // 1 = 开始,0 = 停止
int dealyedPressure = 1; // 1 = 选择器,0 = 不延迟
int minShow,secShow,pointerShow;
float m1,m2,m3;
int i1,i2,i3;
//——时间计数器使用——
int timeON = 0; // 0 = 关闭,1 = 打开
int ii = 0;
unsigned int time_1s=0;
unsigned int time_1m=0;
unsigned int time_1h=0;
int iii = 0;
int LED_blue = 2; // IO2,控制LED点亮(高电平为点亮)
void setup() {
Serial.begin(115200);
pinMode(LED_blue, OUTPUT);
}
void loop() {
int inChar;
// 读取串口发送的信息并简单解码:
if (Serial.available() > 0) {inChar = Serial.read();}
if (inChar == 0x62) // 2 个按钮
{
if (Serial.available() > 0){inChar = Serial.read();}
if (inChar == 0x31)
{
if (Serial.available() > 0){ inChar = Serial.read();}
如果 (inChar == 0x31)
{
如果 (Serial.available() > 0){ inChar = Serial.read();}
如果 (inChar == 0x02)
{
如果(dealyedPressure == 1 && start == 0)
{
time_1s = 0;
time_1m = 0;
timeON = 1;
}
start = 1; //62 31 31 02 表示启动成功!
}
} else if (inChar == 0x32)
{
if (Serial.available() > 0){ inChar = Serial.read();}
if (inChar == 0x02)
{
start = 0; //62 31 32 02 表示停止成功!
时间开启 = 0;
}
}
}
} else if (inChar == 0x63) // 1 个复选框
{
if (Serial.available() > 0){inChar = Serial.read();}
if (inChar == 0x62)
{
if (Serial.available() > 0){ inChar = Serial.read();}
如果 (inChar == 0x31)
{
如果 (Serial.available() > 0){ inChar = Serial.read();}
如果 (inChar == 0x00)
{
dealyedPressure = 0; // 0 = 不延迟
timeON = 0;
} else if(inChar == 0x01)
{
dealyedPressure = 1; // 1 = 选择器,
if(start == 1)
{
time_1s = 0;
time_1m = 0;
timeON = 1;
}
}
}
}
}else if(inChar == 0x74) //2 个文本选择器
{
if (Serial.available() > 0){inChar = Serial.read();}
如果 (inChar == 0x73)
{
如果 (Serial.available() > 0){ inChar = Serial.read();}
如果 (inChar == 0x31)
{
如果 (Serial.available() > 0){ inChar = Serial.read();}
如果 (inChar == 0x31) // 74 73 31 31 是过程
{
如果 (Serial.available() > 0){ inChar = Serial.read();}
如果 (inChar == 0x00)
{
如果 (Serial.available() > 0) { inChar = Serial.read(); }
如果 (inChar == 0x00)
{
如果 (Serial.available() > 0) { inChar = Serial.read(); }
如果 (inChar == 0x00)
{
如果 (Serial.available() > 0) { inChar = Serial.read(); }
inPressure = inChar;
}
}
}
}else if (inChar == 0x32) // 74 73 31 32 是延迟最小值
{
if (Serial.available() > 0){ inChar = Serial.read();}
if (inChar == 0x00)
{
如果 (Serial.available() > 0) { inChar = Serial.read(); }
如果 (inChar == 0x00)
{
如果 (Serial.available() > 0) { inChar = Serial.read(); }
如果 (inChar == 0x00)
{
如果 (Serial.available() > 0) { inChar = Serial.read(); }
延迟时间最小值 = inChar;
}
}
}
}
}
}
}
//———显示——- 分钟——– 秒———
如果 (time_1m < delayTimeMin)
{
minShow = delayTimeMin – time_1m – 1;
secShow = 59 – time_1s;
} else
{
timeON = 0;
}
if(start == 1 && dealyedPressure == 1)
{
Serial.print(“ST<{\”cmd_code\“:\”set_text\“,\”type\“:\”label\“,\”widget\“:\”l21\“,\”text\“:\””);
Serial.print(minShow);
Serial.println(“\”}>ET“);
Serial.print(”ST<{\“cmd_code\”:\“set_text\”,\“type\”:\“label\”,\“widget\”:\“l22\”,\“text\”:\“”);
Serial.print(secShow);
Serial.println(“\”}>ET“);
}else //stop or dealyedPressure == 0
{
Serial.print(”ST<{\“cmd_code\”:\“set_text\”,\“type\”:\“label\”,\“widget\”:\“l21\”,\“text\”:\“”);
Serial.print(delayTimeMin);
Serial.println(“\”}>ET“);
Serial.print(”ST<{\“cmd_code\”:\“set_text\”,\“type\”:\“label\”,\“widget\”:\“l22\”,\“text\”:\“”);
串口打印(delayTimeSec);
串口打印(“”}>ET”);
}
//———–显示——压力——–
如果(delayedPressure == 0 || timeON == 0)
{
pointerShow = inPressure*17 -145;
}else
{
i1 = time_1m*60 + time_1s;
m1 = (float)delayTimeMin * 60.0;
m2 = (float)i1 / m1;
m3 = (float)inPressure * 17.0 * m2;
pointerShow = int(m3) – 145;
}
if(start == 1)
{
Serial.print(“ST<{\”cmd_code\“:\”set_angle\“,\”type\“:\”gauge_pointer\“,\”widget\“:\”gp1\“,\”angle\“:”);
Serial.print(pointerShow);
Serial.println(“}>ET”);
}else //stop
{
Serial.println(“ST<{\”cmd_code\“:\”set_angle\“,\”type\“:\”gauge_pointer\“,\”widget\“:\”gp1\“,\”angle\“:-145}>ET”);
}
//——————时间++ ——–开始———
if(时间ON == 1)
{
延迟(10);
ii += 1;
if(ii > 29) //秒++
{
ii = 0;
时间_1s += 1;
if(时间_1s >= 60) //分钟++
{
time_1s = 0;
time_1m += 1;
if(time_1m >= 60) //hour++
{
time_1m = 0;
time_1h += 1;
}
}
if(iii == 0){
iii = 1;
digitalWrite(LED_blue, HIGH); // 打开 LED(HIGH 表示电压为高电平)
}else
{
iii = 0;
digitalWrite(LED_blue, LOW); // 通过将电压设为低电平关闭 LED
}
}
}
//——————时间递增 ——–结束———
}
解码说明。
气压计指针驱动算法涉及整数与浮点数之间的运算,需严格遵循以下运算格式规范,否则将无法获得正确结果。