基于STM32设计的云端健康管理系统(采用阿里云物联网平台)

1. 前言

近几年随着科技的进步和智能化浪潮的到来,智能穿戴设备也在飞速火爆发展,各种健康智能手环,智能手表、智能跑鞋、智能眼镜纷纷上市,并出现了很多针对个人家庭的健康管理设备。比如: 智能血压计、智能心率检测、脂肪秤、智能体重秤等等,都带上了智能、健康各种标签。

可穿戴设备,即直接穿在身上,或是整合到用户的衣服或配件的一种便携式设备。可穿戴设备不仅仅是一种硬件设备,更是通过软件支持以及数据交互、云端交互来实现强大的功能,可穿戴设备将会对生活、感知带来很大的转变。

当前采用STM32加上各种外设传感器配合物联网平台设计一个健康管理设备,通过ESP8266+MQTT协议将数据传输到物联网平台,实现云端健康管理。

物联网平台采用阿里云,下面是设计的界面与设备的整体效果:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2. 设计需求

设备需要支持以下功能:

(1)人体温度测量
(2)运动监测、计步功能
(3)睡眠监测
(4)心率测量

STM32采集这些传感器数据之后,进行处理,在本地OLED显示屏上完成显示;再通过ESP8266将数据传递到物联网平台,完成云端数据展示。

人体温度测量超过规定阈值时、心率测量超过规定阈值、睡眠监测睡眠时长如超出8h,云端以及本地0led屏发出文字提醒并且蜂鸣器报警。

3. 硬件选型

3.1 MAX30102血氧传感器

image-20220321180309060

MAX30102是一个集成的脉搏血氧仪和心率监测仪生物传感器的模块。它集成了一个红光LED和一个红外光LED、光电检测器、光器件,以及带环境光抑制的低噪声电子电路。MAX30102采用一个1.8V电源和一个独立的5.0V用于内部LED的电源,应用于可穿戴设备进行心率和血氧采集检测,佩戴于手指、耳垂和手腕等处。标准的I2C兼容的通信接口可以将采集到的数值传输给Arduino、KL25Z、51、STM32等单片机进行心率和血氧计算。此外,该芯片还可通过软件关断模块,待机电流接近为零,实现电源始终维持供电状态。

接线如下:

产品名称      MAX30102心率模块
LED峰值波长   660nm/880nm
LED供电电压   3.3~5V
检测信号类型  光反射信号(PPG)
输出信号接口   I2C接口
通信接口电压   1.8~3.3V5V(可选)

原理说明:

光溶积法: 利用人体组织在血管搏动时造成透光率不同来进行脉搏和血氧饱和度测量的;

光源: 采用对动脉血中氧合血红蛋白(Hb02)-和血红蛋白(Hb)有选择性的特定波长的发光极管透光率转化为电信号:动脉搏动充血容积委化导致够束光的透光率发生改变,此时由光电变换接收经人体组织反身光线,转变为电信号并将其放大输出。

引脚说明:

VIN:主电源电源输入端,1.8V-5V 3位焊盘:选择总线上的拉电平,取决于引脚主控电压,可选1.8V端或者3.3v端(此端包含3.3V及以上)
SCL:接IIC总线的时钟
SDA:接IIC总线的数据
NT: MAX30102芯片的中断引脚
RD: MAX30102芯片的 RED LED接地端,一般不接
IRD: MAX30102芯片的RLED接地端,一般不接
GND:接地线

PulseSensor 是一款用于脉搏心率测量的光电反射式模拟传感器。将其佩戴于手指、耳垂等处,通过导线连接可将采集到的模拟信号传输给 Arduino 等单片机用来转换为数字信号,再通过 arduino 等单片机简单计算后就可以得到心率数值,此外还可将脉搏波形通过串口上传到电脑显示波形。 PulseSensor 是一款开源硬件, 目前国外官网上已有其对应的 arduino 程序和上位机 Processing 程序, 其适用于心率方面的科学研究和教学演示,也非常适合用于二次开发。
在这里插入图片描述
特别提醒:传感器背面是电子元件,请不要用手指直接接触, 以免静电或汗液造成背面器件损坏。 可以在背面粘贴黑色粘扣, 正面粘贴透明膜来保护传感器。
在这里插入图片描述
传感器的接口一共 3 个, 如上图红框内所示。 请大家千万不要根据线的颜色来自行推测, 而要根据电路板的背面标识来分辨。
红框中的 3 根线,标有 S 的为模拟信号输出线(最左边) ; 标有+的为电源输入线(中间);标有-的为地线(最右边) 。
总结一下:
S → 脉搏信号输出(要接单片机 AD 接口)

  • → 5v(或 3.3v)电源输入
  • → GND 地

3.2 MPU6050

image-20220321180624048

MPU6050特点:

(1)高性能三轴加速度+三轴陀螺仪的六轴传感器模块MPU6050芯片;

(2)可利用自带的数字运动处理器(DMP)硬件加速引擎,通过主IKC接口,向应用端输出姿态解算后的数据,使用InvenSense公司提供的运动处理资料库,实现姿态解算,降低了运动处理运算对操作系统的负荷同时大大降低了开发难度;

(3)体积小,自带温度传感器;

(4)支持IIC从机地址设置和中断;

(5)兼容3.3V/5V系统;

3.3 STM32开发板

STM32F103RCT6的芯体规格是32位,速度是72MHz,程序存储器容量是256KB,程序存储器类型是FLASH,RAM容量是48K。

3.4 ESP8266 WIFI

image-20220310112928724

ESP8266是一款物联网WiFi芯片,基于ESP8266可以开发物联网串口WiFi模块,像SKYLAB的WG219/WG229专为移动设备和物联网应用设计,可将用户的物理设备连接到WiFi无线网络上,进行互联网或局域网通信,实现联网功能。

ESP8266,专为移动设备、可穿戴电子产品和物联网应用而设计,通过多项专有技术实现了超低功耗。ESP8266EX 具有的省电模式适用于各种低功耗应用场景。

ESP8266内置超低功耗 Tensilica L106 32 位 RISC 处理器,CPU 时钟速度最高可达 160 MHz,支持实时操作系统 (RTOS) 和 Wi-Fi 协议栈,可将高达 80% 的处理能力留给应用编程和开发。

3.5 GY-MCU90615V2体温传感器

image-20220321181050674

通信协议:

串口发送命令字节:
(1)、串口通信参数(默认波特率值9600 bps,可通过软件设定)
波特率:9600 bps     校验位:N   数据位:8   停止位:1
波特率:115200 bps   校验位:N   数据位:8   停止位:12)、模块输入命令,由外部控制器发送至GY-MCU90615模块(十六进制)

1、帧头:0xa5
指令格式:帧头+指令+校验和(8bit)(如自动读取温度指令=0xA5+0x45+0xEA2、命令指令: 
连续输出指令:
0xA5+0x45+0xEA----------------温度数据(模块返回数据类型为0x45)

查询输出指令:
0xA5+0x15+0xBA ---------------温度数据(模块返回数据类型为0x45)

配置指令:(掉电重启后生效)  
波特率配置:
0xA5+0xAE+0x53 ---------------9600(默认)
0xA5+0xAF+0x54 ---------------115200
上电是否自动发送温度数据配置:
0xA5+0x51+0xF6---------------上电后自动输出温度数据(默认)
0xA5+0x52+0xF7---------------上电后不自动输出温度数据

通信协议
串口接收:
(1)、串口通信参数(默认波特率值9600 bps,可通过软件设定)
波特率:9600 bps     校验位:N   数据位:8   停止位:1
波特率:115200 bps   校验位:N   数据位:8   停止位:12)、模块输出格式,每帧包含9个字节(十六进制):
①.Byte0:   0x5A        帧头标志 
②.Byte1:   0x5A        帧头标志 
③.Byte2:   0X45        本帧数据类型(0X45:温度数据)
④.Byte3:   0x04       数据量(以下4个数据2组为例)
⑤.Byte4:   0x00~0xFF   数据18位
⑥.Byte5:   0x00~0xFF   数据18位
⑦.Byte6:   0x00~0xFF   数据28位
⑧.Byte7:   0x00~0xFF   数据28位
⑨.Byte8:   0x00~0xFF   校验和(前面数据累加和,仅留低8位) 
    
(3)、数据计算方法
温度计算方法 :
温度=8<<88位(结果为实际角度乘以100)
例:发送指令:A5 45 EA   ,接收到一帧数据:
<5A- 5A- 45- 04- 0C- 78- 0D- 19- A7 >
表示TO(有符号16bit,表示目标温度):TO=0x0C78/100=31.92 ℃
表示TA(有符号16bit,表示环境温度):TO=0x0D19/100=33.53 ℃

使用方法
 该模块为串口输出数据,使用者通过串口连接后,发送输出指令,例如0xA5+0x45+0xEA给模块,模块将连续输出温度数据;如想通过查询输出可发送0xA5+0x15+0xBA给模块,每发送一次,模块将返回一次温度数据,查询频率应低于10hz,如需高于10hz请使用连续输出模式,即发送0xA5+0x45+0xEA指令;

image-20220325095259728

3.6 OLED显示屏

image-20220321181209155

特点:

OLED是有机发光二极管又称为有机激光显示、OLED显示技术具有自发光的特性、采用非常薄的有机材料涂层、和玻璃基板、当有电流通过时、这些有机材料就会发光、而且OLED显示屏幕可视角大、功耗低、OLED由于同时具备自发光、不需背光源(只是供电不会亮的、驱动程序和接线正确才会点亮)对比度高、厚度薄视角广、反应速度快、可用于挠曲面板、使用温度范围广、结构及制程等优异之特性、先接触的1286屏都是LCD的、需要背光、功耗较高、而OLED的功耗低、更加适合小系统、由于两者发光材料的不同、在不同的环境中、OLED的显示效果好、模块供电可以是3.3v也可以是5V、不需要修改模块电路、OLED屏具有多个控制指令、可以控制oLED的亮度、对比度、开关升压电路等指令、操作方便、功能丰富、可显示汉字、ASCH、图案等、同时为了方便应用在产品上、预留4个M3固定孔、方便用户固在机壳上。

3.7 母对母杜邦线

image-20220321181308284

三、阿里云物联网云平台

3.1 创建产品

官网地址: https://iot.console.aliyun.com/lk/summary/new

没有账号需要先注册账号,实名认证之后登录。

(1)选择公共实例

image-20220325103751708

(2)创建产品

image-20220325103846191

(3)配置产品信息

image-20220325104007164

3.2 创建设备

(1)点击添加设备

产品下可以创建多个具体设备,设备也可以使用接口让设备端自动创建,下面采用网页手动添加的方式,添加设备。

image-20220325104054515

(2)点击添加设备

image-20220325104208540

(3)填入设备的信息

image-20220325104331824

(4)保存设备信息

创建成功后复制设备证书信息,方便后续生成MQTT登录参数使用。

{
    
    
  "ProductKey": "a1cMlEwEwjg",
  "DeviceName": "healthy_iot_dev",
  "DeviceSecret": "9203ea85a963b40ace7686d01046598f"
}

image-20220325104348831

(5)创建成功

image-20220325104441883

(6)查看设备证书信息

如果刚才没有复制保存,可以在设备详情页面查看设备证书。

image-20220325104637517

image-20220325104646474

3.3 创建自定义属性

云端的产品、设备创建完毕之后,那么本地具体的设备肯定会向云端设备上传数据,那上传的数据用什么字段保存,数据是什么类型,这些需要提前创建,这样约定好之后,设备端才可以正常的上传数据上来。

(1)找到功能定义选项页面

image-20220325104951447

image-20220325105011278

(2)添加自定义功能

image-20220325105107285

根据自己传感器的数据属性添加即可。

(3)添加心率属性

image-20220325105436552

(4)添加血氧饱和度属性

image-20220325105808471

(5)添加人体体温属性

image-20220325110221286

(5)添加行走的步数

image-20220325110338213

(6)添加完毕发布上线

image-20220325110418993

image-20220325110442921

(4)完成

image-20220325110458872

3.4 查看物模型

接下来设备端的数据上报就要参考物模型的格式来完成数据拼接。

image-20220325110702812

(1)完整物模型

{
    
    
  "schema": "https://iotx-tsl.oss-ap-southeast-1.aliyuncs.com/schema.json",
  "profile": {
    
    
    "version": "1.0",
    "productKey": "a1cMlEwEwjg"
  },
  "properties": [
    {
    
    
      "identifier": "HeartRate",
      "name": "心率",
      "accessMode": "r",
      "desc": "心率",
      "required": false,
      "dataType": {
    
    
        "type": "int",
        "specs": {
    
    
          "min": "1",
          "max": "500",
          "step": "1"
        }
      }
    },
    {
    
    
      "identifier": "BloodOxygen",
      "name": "血氧饱和度",
      "accessMode": "rw",
      "desc": "血氧饱和度",
      "required": false,
      "dataType": {
    
    
        "type": "int",
        "specs": {
    
    
          "min": "1",
          "max": "100",
          "unit": "%",
          "unitName": "百分比",
          "step": "1"
        }
      }
    },
    {
    
    
      "identifier": "temperature",
      "name": "人体体温",
      "accessMode": "r",
      "desc": "人体体温",
      "required": false,
      "dataType": {
    
    
        "type": "int",
        "specs": {
    
    
          "min": "1",
          "max": "100",
          "unit": "°C",
          "unitName": "摄氏度",
          "step": "1"
        }
      }
    },
    {
    
    
      "identifier": "steps",
      "name": "行走步数",
      "accessMode": "r",
      "desc": "行走步数",
      "required": false,
      "dataType": {
    
    
        "type": "int",
        "specs": {
    
    
          "min": "1",
          "max": "100000",
          "unit": "stepCount",
          "unitName": "步",
          "step": "1"
        }
      }
    }
  ],
  "events": [
    {
    
    
      "identifier": "post",
      "name": "post",
      "type": "info",
      "required": true,
      "desc": "属性上报",
      "method": "thing.event.property.post",
      "outputData": [
        {
    
    
          "identifier": "HeartRate",
          "name": "心率",
          "dataType": {
    
    
            "type": "int",
            "specs": {
    
    
              "min": "1",
              "max": "500",
              "step": "1"
            }
          }
        },
        {
    
    
          "identifier": "BloodOxygen",
          "name": "血氧饱和度",
          "dataType": {
    
    
            "type": "int",
            "specs": {
    
    
              "min": "1",
              "max": "100",
              "unit": "%",
              "unitName": "百分比",
              "step": "1"
            }
          }
        },
        {
    
    
          "identifier": "temperature",
          "name": "人体体温",
          "dataType": {
    
    
            "type": "int",
            "specs": {
    
    
              "min": "1",
              "max": "100",
              "unit": "°C",
              "unitName": "摄氏度",
              "step": "1"
            }
          }
        },
        {
    
    
          "identifier": "steps",
          "name": "行走步数",
          "dataType": {
    
    
            "type": "int",
            "specs": {
    
    
              "min": "1",
              "max": "100000",
              "unit": "stepCount",
              "unitName": "步",
              "step": "1"
            }
          }
        }
      ]
    }
  ],
  "services": [
    {
    
    
      "identifier": "set",
      "name": "set",
      "required": true,
      "callType": "async",
      "desc": "属性设置",
      "method": "thing.service.property.set",
      "inputData": [
        {
    
    
          "identifier": "BloodOxygen",
          "name": "血氧饱和度",
          "dataType": {
    
    
            "type": "int",
            "specs": {
    
    
              "min": "1",
              "max": "100",
              "unit": "%",
              "unitName": "百分比",
              "step": "1"
            }
          }
        }
      ],
      "outputData": []
    },
    {
    
    
      "identifier": "get",
      "name": "get",
      "required": true,
      "callType": "async",
      "desc": "属性获取",
      "method": "thing.service.property.get",
      "inputData": [
        "HeartRate",
        "BloodOxygen",
        "temperature",
        "steps"
      ],
      "outputData": [
        {
    
    
          "identifier": "HeartRate",
          "name": "心率",
          "dataType": {
    
    
            "type": "int",
            "specs": {
    
    
              "min": "1",
              "max": "500",
              "step": "1"
            }
          }
        },
        {
    
    
          "identifier": "BloodOxygen",
          "name": "血氧饱和度",
          "dataType": {
    
    
            "type": "int",
            "specs": {
    
    
              "min": "1",
              "max": "100",
              "unit": "%",
              "unitName": "百分比",
              "step": "1"
            }
          }
        },
        {
    
    
          "identifier": "temperature",
          "name": "人体体温",
          "dataType": {
    
    
            "type": "int",
            "specs": {
    
    
              "min": "1",
              "max": "100",
              "unit": "°C",
              "unitName": "摄氏度",
              "step": "1"
            }
          }
        },
        {
    
    
          "identifier": "steps",
          "name": "行走步数",
          "dataType": {
    
    
            "type": "int",
            "specs": {
    
    
              "min": "1",
              "max": "100000",
              "unit": "stepCount",
              "unitName": "步",
              "step": "1"
            }
          }
        }
      ]
    }
  ]
}

(2)精简物模型

{
    
    
  "properties": [
    {
    
    
      "identifier": "HeartRate",
      "dataType": {
    
    
        "type": "int"
      }
    },
    {
    
    
      "identifier": "BloodOxygen",
      "dataType": {
    
    
        "type": "int"
      }
    },
    {
    
    
      "identifier": "temperature",
      "dataType": {
    
    
        "type": "int"
      }
    },
    {
    
    
      "identifier": "steps",
      "dataType": {
    
    
        "type": "int"
      }
    }
  ]
}

3.5 创建web可视化界面

地址: https://iot.console.aliyun.com/lk/related-services

(1)找到web应用开发入口

image-20220325112130632

image-20220325112201161

(2)新建项目

image-20220325112523817

image-20220325112335047

image-20220325112354712

image-20220325112405087

(3)查看项目

image-20220325112600018

(4)新建web应用

image-20220325112623899

image-20220325112640911

(5)配置数据源

以仪表盘为例,拖一个仪表盘组件到页面,选中仪表盘,配置数据源。

image-20220325114037424

关联产品

image-20220325114056477

image-20220325114113858

关联自己创建的产品。

image-20220325114135016

关联成功

image-20220325114155046

回到刚才的数据源页面,继续配置,选中设备,配置设备。

image-20220325114303482

image-20220325114327418

image-20220325114341214

关联成功。

image-20220325114358619

回到刚才配置数据源页面,刷新设备列表,继续配置。

image-20220325114422992

根据自己仪表盘要显示的数据进行关联。

image-20220325114450517

(6)完成页面配置

根据自己需求,配置页面样式,选择数据源。

因为目前刚创建完设备还没有上传数据激活,所以页面上选上数据源错误,这是正常。

image-20220325115433454

(7)发布应用

image-20220325130148440

image-20220325130205646

image-20220325130241460

3.6 生成MQTT设备登录信息

(1)MQTT登录地址

关于MQTT协议登录所需要的参数官方说明文档: https://help.aliyun.com/document_detail/140507.html?spm=a2c4g.11186623.6.571.1e417544OGPj2y

MQTT登录域名的格式:
${
    
    YourProductKey}.iot-as-mqtt.${
    
    YourRegionId}.aliyuncs.com

其中:
${
    
    YourProductKey}:请替换为设备所属产品的ProductKey
${
    
    YourRegionId}:请替换为物联网平台设备所在地域代码。

下面是阿里云国内的服务器地域和可用区详情:

 
地域名称	所在城市	Region ID	可用区数量
华北 1	青岛	cn-qingdao	    2
华北 2	北京	cn-beijing	    10
华北 3	张家口	cn-zhangjiakou	3
华北 5	呼和浩特	cn-huhehaote	2
华北 6	乌兰察布	cn-wulanchabu	3
华东 1	杭州	cn-hangzhou	    8
华东 2	上海	cn-shanghai  	8
华南 1	深圳	cn-shenzhen   	6
华南 2	河源	cn-heyuan	    2
华南 3	广州	cn-guangzhou	2
西南 1	成都	cn-chengdu	    2

端口号是:1883

经过上面的格式解释,我的阿里云服务器登录的域名就是(选择的是上海服务器):a1cMlEwEwjg.iot-as-mqtt.cn-shanghai.aliyuncs.com

域名对应的IP地址(动态解析出来的):  101.133.196.97

在线解析域名网站:https://site.ip138.com/a1cMlEwEwjg.iot-as-mqtt.cn-shanghai.aliyuncs.com/

image-20220325131621769

(2)MQTT登录参数

(1) MQTT_ClientID
固定格式:${
    
    ClientID}|securemode=${
    
    Mode},signmethod=${
    
    SignMethod}|。

参数说明:
${
    
    ClientId}:  设备ID,一般填设备的硬件编号。我这里就直接填当前的设备名称,后面的密码里也要填这个ID,必须一样就行。(设备名称就是创建设备的时候复制出来3个参数里的设备名称)
securemode=3:TCP直连模式,无需设置SSL/TLS信息。
securemode=2:TLS直连模式,需要设置SSL/TLS信息。
${
    
    SignMethod}:算法类型,支持hmacmd5和hmacsha1。

示例:
当前我的设备名称是:healthy_iot_dev ,选择TCP直连模式,选择hmacsha1算法类型。

那么我的ClientID就是:
healthy_iot_dev|securemode=3,signmethod=hmacsha1|

(2) MQTT_UserName
固定格式:${
    
    DeviceName}&${
    
    ProductKey}
参数解释:
${
    
    DeviceName} 是设备的名称(就是创建设备的时候复制出来3个参数里的设备名称)
${
    
    ProductKey} 是设备的ProductKey(就是创建设备的时候复制出来3个参数里的ProductKey)

示例:
当前我的设备名称是:healthy_iot_dev ,我的ProductKey是:a1cMlEwEwjg

那么我的UserName就是:
healthy_iot_dev&a1cMlEwEwjg

(3) MQTT_PassWord
下载密码生成小工具:https://help.aliyun.com/document_detail/140507.html?spm=a2c4g.11186623.6.571.1e417544OGPj2y#section-dai-o6u-deh

image-20220325132240837

计算MQTT签名-密码:https://help.aliyun.com/document_detail/292635.htm?spm=a2c4g.11186623.0.0.5aaf3686v36VUs#section-jx3-u57-pmm

image-20220325132253301

image-20220325132405260

根据设备证书信息,填入参数,然后生成MQTT登录密匙。

image-20220325132556178

生成的信息如下:这3个数据就是接下来,MQTT登录使用的密匙信息

mqttClientId:  healthy_iot_dev|securemode=2,signmethod=hmacsha1,timestamp=1648185853325|

username:  healthy_iot_dev&a1cMlEwEwjg

password: F4F3DA2461A8CD6BF5ACF024D065BB77A75DFFCA

(3)主题订阅、发布测试

在产品页面可以看到主题格式:https://iot.console.aliyun.com/product/productDetail/a1cMlEwEwjg/func?current=2

image-20220325133828855

发布主题:
/sys/a1cMlEwEwjg/healthy_iot_dev/thing/event/property/post

上报属性消息的格式:  
{
    
    "method":"thing.event.property.post","params":{
    
    "HeartRate":60,"BloodOxygen":80,"temperature":37,"steps":12345}}


订阅主题:
/sys/a1cMlEwEwjg/healthy_iot_dev/thing/service/property/set

3.7 软件模拟设备登录

(1)设备登录

软件下载地址:https://download.csdn.net/download/xiaolong1126626497/18784012

填入设备的登录信息。

image-20220325134729197

(2)查看设备状态

打开网页的设备页面查看,设备已经在线了。

image-20220325134429926

image-20220325134450863

(3)查看上报的数据

物模型页面查看设备上传的数据。

image-20220325134956520

(4)查看网页数据

项目主页:https://studio.iot.aliyun.com/p/a123qInUN2Bpr14w/project/detail

image-20220325141836833

image-20220325141954327

四、设备端开发

如果需要完整的项目资料可以去这里下载: https://download.csdn.net/download/xiaolong1126626497/85895120

4.1 设备运行效果

在这里插入图片描述

在这里插入图片描述

4.2 设备硬件接线说明

这里列出当前设备使用到的硬件连线说明。

1)MAX30102--心率与血氧采集模块:(测量心率与血氧饱和度)-IIC协议
VCC------3.3V
GND------GND
SCL------PC0
SDA------PC1
IM-------PC2

(20.96寸SPI接口的OLED显示屏:
VCC----------3.3V
GND----------GND
D0-SCL-------PC8
D1-SDA-------PC9
RES-RST------PC10
DC-----------PB7
CS-----------PB8


(3)MPU6050 六轴陀螺仪(计步、睡眠监测)
SCL----------PC3
SDA----------PC4
AD0----------PC6
VCC----------3.3V
GND----------GND


(4)GY-MCU90615--红外测温模块
VCC----------3.3V
GND----------GND
PA2-TX-------RX
PA3-RX-------TX


(5)ESP8266-WIFI (连接阿里云物联网服务器)
ATK-ESP8266串口WIFI模块与STM32的串口3相连接。
PA10(TX)--RXD 模块接收脚
PA11(RX)--TXD 模块发送脚
GND---GND 地
VCC---VCC 电源(3.3V~5.0V)


(6)板载LED灯接线
LED1---PA8
LED2---PD2
BEEP---PA6 --------需要外接蜂鸣器


(7)板载按键接线
K0---PA0 
K1---PC5 
K2---PA15

4.3 keil工程布局

在mian.c里首先完成了各个传感器的初始化配置,然后进入到while循环,在循环不断的检测按键,如果有按键按下就切换显示页面,查看其他页面的数据。在while里固定1秒钟的频率向MQTT物联网服务器上传一次数据。

image-20220325174035967

image-20220325174055543

image-20220325174207221

4.4 设备main.c核心代码

#include "stm32f10x.h"
#include "led.h"
#include "key.h"
#include "delay.h"
#include "usart.h"
#include <stdio.h>
#include "exti.h"
#include "timer.h"
#include "oled.h"
#include "rtc.h"
#include "temperature.h"
#include "mpu6050.h"
#include "stepAlgorithm.h"
#include "BMP.h"
#include <stdio.h>
#include <sys.h>
#include "esp8266.h"
#include "mqtt.h"
#include "max30102.h" 
#include "myiic.h"
#include "algorithm.h"

//阿里云物联网服务器的设备信息
#define MQTT_ClientID "healthy_iot_dev|securemode=2,signmethod=hmacsha1,timestamp=1648185853325|"
#define MQTT_UserName "healthy_iot_dev&a1cMlEwEwjg"
#define MQTT_PassWord "F4F3DA2461A8CD6BF5ACF024D065BB77A75DFFCA"

//订阅与发布的主题
#define SET_TOPIC  "/sys/a1cMlEwEwjg/healthy_iot_dev/thing/service/property/set"  //订阅
#define POST_TOPIC "/sys/a1cMlEwEwjg/healthy_iot_dev/thing/event/property/post"  //发布

//设置连接的路由器信息
#define CONNECT_WIFI  "Xiaomi_meizi6"   //将要连接的路由器名称 --不要出现中文、空格等特殊字符
#define CONNECT_PASS "12170307yu"       //将要连接的路由器密码

#define CONNECT_SERVER_IP "a1cMlEwEwjg.iot-as-mqtt.cn-shanghai.aliyuncs.com"   //服务器IP地址或者域名
#define CONNECT_SERVER_PORT 1883            //服务器端口号

char mqtt_message[200];//上报数据缓存区


u32 PagCnt=0;  //延时计数

/*计步函数*/
void App_calStepHandler(void);
void OLEDPag1(void);
void OLEDPag2(void);
void OLEDPag3(void);
void OLEDPag4(void);

//传感器采集的数据
int HeartRate=60;
int BloodOxygen=66;
float temperature=37.8;
int steps=88;
float GY_MCU90615V2_buff[2];//温度显示。T[0]=真实温度  T[1]=环境温度


uint32_t aun_ir_buffer[500]; //IR LED sensor data
int32_t n_ir_buffer_length;    //data length
uint32_t aun_red_buffer[500];    //Red LED sensor data
int32_t n_sp02; //SPO2 value
int8_t ch_spo2_valid;   //indicator to show if the SP02 calculation is valid
int32_t n_heart_rate;   //heart rate value
int8_t  ch_hr_valid;    //indicator to show if the heart rate calculation is valid
uint8_t uch_dummy;

#define MAX_BRIGHTNESS 255

void dis_DrawCurve(u32* data,u8 x);


int main()
{
    
    
    //variables to calculate the on-board LED brightness that reflects the heartbeats
	uint32_t un_min, un_max, un_prev_data;  
    u32 time_cnt=0;
    u8 esp8266_state=0;
    u32 i;
    u8 temp[6];
	int Tmp,stat=0;
    int32_t n_brightness;
	float f_temp;
    u8 dis_hr=0,dis_spo2=0;
    u8 str[100];
    
	u8 cnt=1;              	//开机显示第一页
	BeepInit();            	//初始化蜂鸣器
    JTAG_Set(SWD_ENABLE);	//禁止JTAG,从而PA15可以做普通IO使用,否则PA15不能做普通IO!!!
	LedInit();             	//初始化LED灯
	KeyInit();             	//初始化按键
	Usart1Init(72,115200); 	//初始化串口1
    USART3_Init(115200);    //ESP8266-WIFI
    TIMER3_Init(72,20000);  //超时时间20ms
	OLED_Init();           	//初始化OLED
	OLEDPag1();            	//显示页面1
	TemPeratureInit();      //初始化红外线温度监测模块
	RTC_Init();              //RTC初始化
	stat=MPU_Init();      //初始化MPU6050陀螺仪
    printf("MPU6050=%d\n",stat);              //MPU6050
    WatchInfo_init();  //设置身高、体重用于计算卡路里消耗
	max30102_init();    //初始化心率血氧传感器
    
    Timer4Init(2500,720);  //计步算法处理-20ms一次
	OLEDPag1();               //显示页面1
	printf("系统正常!\r\n");
	
    for(i=0;i<5;i++)
    {
    
    
        if(ESP8266_Init()==0)
        {
    
    
            esp8266_state=1;
            break;
        }
        else
        {
    
    
            esp8266_state=0;
            printf("ESP8266硬件检测错误.\n");  
        }
    }
    
   if(esp8266_state)
   {
    
    
       printf("准备连接服务器....\r\n");
        //非加密端口
        printf("WIFI:%d\n",ESP8266_STA_TCP_Client_Mode(CONNECT_WIFI,CONNECT_PASS,CONNECT_SERVER_IP,CONNECT_SERVER_PORT,1));
         
        //2. MQTT协议初始化	
        MQTT_Init(); 

        //3. 连接服务器  
        for(i=0;i<5;i++)
        {
    
    
            if(MQTT_Connect(MQTT_ClientID,MQTT_UserName,MQTT_PassWord)==0)
            {
    
    
                esp8266_state=1;
                break;
            }
            esp8266_state=0;
            printf("服务器连接失败,正在重试...\n");
            delay_ms(500);
        }
        printf("服务器连接成功.\n");

        //3. 订阅主题
        if(MQTT_SubscribeTopic(SET_TOPIC,0,1))
        {
    
    
           printf("主题订阅失败.\n");
        }
        else
        {
    
    
           printf("主题订阅成功.\n");
        } 
   }
   
   
    un_min=0x3FFFF;
	un_max=0;
	
	n_ir_buffer_length=500; //buffer length of 100 stores 5 seconds of samples running at 100sps
	//read the first 500 samples, and determine the signal range
    for(i=0;i<n_ir_buffer_length;i++)
    {
    
    
        while(MAX30102_INT==1);   //wait until the interrupt pin asserts
        
		max30102_FIFO_ReadBytes(REG_FIFO_DATA,temp);
		aun_red_buffer[i] =  (long)((long)((long)temp[0]&0x03)<<16) | (long)temp[1]<<8 | (long)temp[2];    // Combine values to get the actual number
		aun_ir_buffer[i] = (long)((long)((long)temp[3] & 0x03)<<16) |(long)temp[4]<<8 | (long)temp[5];   // Combine values to get the actual number
            
        if(un_min>aun_red_buffer[i])
            un_min=aun_red_buffer[i];    //update signal min
        if(un_max<aun_red_buffer[i])
            un_max=aun_red_buffer[i];    //update signal max
    }
	un_prev_data=aun_red_buffer[i];
	//calculate heart rate and SpO2 after first 500 samples (first 5 seconds of samples)
    maxim_heart_rate_and_oxygen_saturation(aun_ir_buffer, n_ir_buffer_length, aun_red_buffer, &n_sp02, &ch_spo2_valid, &n_heart_rate, &ch_hr_valid); 
	
    
    
	while(1)
	{
    
    
		  Tmp=GetKeyVal(0);
		  /*按键控制翻页*/
		   if(Tmp)
			{
    
    
                printf("按键按下:%d\r\n",Tmp);
                if(cnt>=4)cnt=0;
                cnt++;
                OLED_Clear();
			}
            
			if(cnt==1)
			{
    
    
				OLEDPag1();    //公司名称
			}
			if(cnt==2)
			{
    
      
				TIM2->CR1|=1<<0;        //使能定时器,使能位一般都是最后才开启
				OLEDPag2();             //心率--检测仪
			}
			else
			{
    
    
				TIM2->CR1&=~(1<<0);        //使能定时器,使能位一般都是最后才开启
			}
			
			if(cnt==3)
			{
    
    
				OLEDPag3();    //行走-计步器
			}
			if(cnt==4)
			{
    
    
				OLEDPag4();    //温度显示
			}	
            
            
            // 接收WIFI返回的数据
            if(USART3_RX_FLAG)
            {
    
    
                USART3_RX_BUFFER[USART3_RX_CNT]='\0';
                
                printf("UART3收到数据.....\r\n");
                //向串口打印服务器返回的数据
                for(i=0;i<USART3_RX_CNT;i++)
                {
    
    
                    printf("%c",USART3_RX_BUFFER[i]);
                } 
            }
            
            un_min=0x3FFFF;
            un_max=0;
            
            //dumping the first 100 sets of samples in the memory and shift the last 400 sets of samples to the top
            for(i=100;i<500;i++)
            {
    
    
                aun_red_buffer[i-100]=aun_red_buffer[i];
                aun_ir_buffer[i-100]=aun_ir_buffer[i];
                
                //update the signal min and max
                if(un_min>aun_red_buffer[i])
                un_min=aun_red_buffer[i];
                if(un_max<aun_red_buffer[i])
                un_max=aun_red_buffer[i];
            }
            //take 100 sets of samples before calculating the heart rate.
            for(i=400;i<500;i++)
            {
    
    
                un_prev_data=aun_red_buffer[i-1];
                while(MAX30102_INT==1);
                max30102_FIFO_ReadBytes(REG_FIFO_DATA,temp);
                aun_red_buffer[i] =  (long)((long)((long)temp[0]&0x03)<<16) | (long)temp[1]<<8 | (long)temp[2];    // Combine values to get the actual number
                aun_ir_buffer[i] = (long)((long)((long)temp[3] & 0x03)<<16) |(long)temp[4]<<8 | (long)temp[5];   // Combine values to get the actual number
            
                if(aun_red_buffer[i]>un_prev_data)
                {
    
    
                    f_temp=aun_red_buffer[i]-un_prev_data;
                    f_temp/=(un_max-un_min);
                    f_temp*=MAX_BRIGHTNESS;
                    n_brightness-=(int)f_temp;
                    if(n_brightness<0)
                        n_brightness=0;
                }
                else
                {
    
    
                    f_temp=un_prev_data-aun_red_buffer[i];
                    f_temp/=(un_max-un_min);
                    f_temp*=MAX_BRIGHTNESS;
                    n_brightness+=(int)f_temp;
                    if(n_brightness>MAX_BRIGHTNESS)
                        n_brightness=MAX_BRIGHTNESS;
                }
                //send samples and calculation result to terminal program through UART
                if(ch_hr_valid == 1 && n_heart_rate<120)//**/ ch_hr_valid == 1 && ch_spo2_valid ==1 && n_heart_rate<120 && n_sp02<101
                {
    
    
                    dis_hr = n_heart_rate;
                    dis_spo2 = n_sp02;
                }
                else
                {
    
    
                    dis_hr = 0;
                    dis_spo2 = 0;
                }
//                    printf("HR=%i, ", n_heart_rate); 
//                    printf("HRvalid=%i, ", ch_hr_valid);
//                    printf("SpO2=%i, ", n_sp02);
//                    printf("SPO2Valid=%i\r\n", ch_spo2_valid);
            }
            maxim_heart_rate_and_oxygen_saturation(aun_ir_buffer, n_ir_buffer_length, aun_red_buffer, &n_sp02, &ch_spo2_valid, &n_heart_rate, &ch_hr_valid);

            if(dis_hr == 0 && dis_spo2 == 0)  //**dis_hr == 0 && dis_spo2 == 0
            {
    
    
                sprintf((char *)str,"HR:--- SpO2:--- ");//**HR:--- SpO2:--- 
            }
            else
            {
    
    
                sprintf((char *)str,"HR:%3d SpO2:%3d ",dis_hr,dis_spo2);//**HR:%3d SpO2:%3d 
            }
            
            //心率数据
            HeartRate=dis_hr;
            
            //血氧浓度
            BloodOxygen=dis_spo2;
            
            //采集体温
            GetTemInfo(GY_MCU90615V2_buff); //获取温度值
            temperature=GY_MCU90615V2_buff[0];
  
            //获取步数
            steps=userSportsInfo.stepCount/5.0;
            
            //判断心率是否超过设定的阀值
            if(HeartRate>200)
            {
    
    
                BEEP=1; //开启蜂鸣器
                printf("警告心率超过正常阀值.\r\n");
            }
            else
            {
    
    
                BEEP=0; //关闭蜂鸣器
            }
            
            //判断体温是否超过阀值
            if(temperature>40)
            {
    
    
                BEEP=1; //开启蜂鸣器
                printf("警告体温超过正常阀值.\r\n");
            }
            else
            {
    
    
                BEEP=0; //关闭蜂鸣器
            }

           
            //判断是否处于佩戴状态
            if(HeartRate>50 && BloodOxygen>30)
            {
    
    
                //根据心率的波动范围+行走的步数来综合判断是否处于睡眠状态
                //判断是否超过了8小时
                
            }

                
            //默认1秒钟的频率向物联网服务器上传一次数据
            time_cnt++;
            delay_ms(10);
            if(time_cnt>100)
            {
    
    
                time_cnt=0;
                LED1=!LED1;
                //MQTT上报数据
                sprintf(mqtt_message,"{\"method\":\"thing.event.property.post\",\"id\":\"1234567890\",\"params\":{\"HeartRate\":%d,\"BloodOxygen\":%d,\"temperature\":%f,\"steps\":%d},\"version\":\"1.1.1\"}",
                HeartRate,BloodOxygen,temperature,steps);
                MQTT_PublishData(POST_TOPIC,mqtt_message,0);
                
                //向串口打印血氧浓度、心率数据、体温、步数
                sprintf(mqtt_message,"HeartRate:%d,BloodOxygen:%d,temperature:%f,steps:%d\r\n",
                HeartRate,BloodOxygen,temperature,steps);
                printf("%s\r\n",mqtt_message);
            }    
	}
}


/*
功能:OLED第一页显示内容
*/
void OLEDPag1(void)
{
    
    
	//页首符号
	OLED_DrawBMP(0,0,11,1,SignalTower);
	OLED_DrawBMP(111,0,128,1,Battery);
	
	//产品名称
	OLED_ShowCHinese(28,2,0);//
	OLED_ShowCHinese(46,2,1);//
	OLED_ShowCHinese(64,2,2);//
	OLED_ShowCHinese(82,2,3);//

	//年月日
	OLED_ShowNum(8*(0+3),4,RTC_Timer.year,4,16);  //年  2017
	OLED_ShowChar(8*(4+3),4,'-');
	OLED_ShowNum(8*(5+3),4,RTC_Timer.month,2,16); //月 03
	OLED_ShowChar(8*(7+3),4,'-');
	OLED_ShowNum(8*(8+3),4,RTC_Timer.day,2,16);   //日
	//时分秒
	OLED_ShowNum(8*0,6,RTC_Timer.hour,2,16);   		//时
	OLED_ShowChar(8*2,6,':');
	OLED_ShowNum(8*3,6,RTC_Timer.minute,2,16); 		//分
	OLED_ShowChar(8*5,6,':');
	OLED_ShowNum(8*6,6,RTC_Timer.sec,2,16); 			//秒
	OLED_ShowString(8*9,6,"Week:");
	OLED_ShowNum(8*14,6,RTC_Timer.week,1,16); 		//星期
}


void OLEDPag2(void)
{
    
    
	//页首符号
	OLED_DrawBMP(0,0,11,1,SignalTower);
	OLED_DrawBMP(111,0,128,1,Battery);
	
	//心率--检测仪
	OLED_ShowCHinese(8*9,2,9); 
	OLED_ShowCHinese(8*11,2,10);	
	
	/*
	健康成人的心率为60~100次/分,大多数为60~80次/分,
	女性稍快;3岁以下的小儿常在100次/分以上;老年人偏慢。	
	*/	
	
	OLED_ShowNum(8*9,5,HeartRate,3,16);
	
	//心率符号
	OLED_DrawBMP(8*3,2,8*(3+4),6,Heart);
}


void OLEDPag3(void)
{
    
    
	//页首符号
	OLED_DrawBMP(0,0,11,1,SignalTower);
	OLED_DrawBMP(111,0,128,1,Battery);
	
	//行走-计步器
	OLED_ShowCHinese(8*5,2,16);
	OLED_ShowCHinese(8*7,2,4);
	OLED_ShowCHinese(8*9,2,17);
	
	//userSportsInfo.calories;
	//userSportsInfo.distance;
	//App_calStepHandler(); //计步
	
    //步数显示
	//OLED_ShowNum(8*6,4,userSportsInfo.stepCount,3,16);
	//OLED_ShowNum(8*6,6,userSportsInfo.distance,3,16);  //路程
	u8 buff_b[10];
	sprintf((char*)buff_b,"%d",userSportsInfo.stepCount/5);
	strcat((char *)buff_b,"step");
	OLED_ShowString(8*6,4,buff_b); //行走步数
	
	u8 buff_m[10];
	sprintf((char*)buff_m,"%0.1f",userSportsInfo.distance/5);
	strcat((char *)buff_m,"m");
	OLED_ShowString(8*6,6,buff_m); //行走路程
	
	 PagCnt++;
     DelayMs(1);
	
	if(PagCnt==300)
	{
    
    
        //跑步图片
        OLED_DrawBMP(0,2,18,6,OnFoot_1);	 
	}
	
	if(PagCnt==600)
	{
    
    
        OLED_DrawBMP(0,2,18,6,Nothing);
        OLED_DrawBMP(0,2,17,6,OnFoot_2);
        PagCnt=0;		
	}
}


void OLEDPag4(void)
{
    
    
	
	u32 a,b;
	
	//页首符号
	OLED_DrawBMP(0,0,11,1,SignalTower);
	OLED_DrawBMP(111,0,128,1,Battery);
	
	//温度计
	OLED_DrawBMP(0*8,2,8*(0+4),6,Temperature);
	
	//体温-检测仪
	OLED_ShowCHinese(8*6,0,6);
	OLED_ShowCHinese(8*8,0,7);
    

	if(tem_flag)
	{
    
    
			tem_flag=0;
			a=GY_MCU90615V2_buff[0];        //整数部分
			b=(((float)GY_MCU90615V2_buff[0]-a)*100);     //小数部分
			//printf("%d.%d\r\n",a,b);

			OLED_ShowString(8*5,2,"T0:");
			OLED_ShowString(8*5,4,"T1:");
			//体温显示--体温
			OLED_ShowNum(8*8,2,a,2,16);  //----------体温------------
			OLED_ShowChar(8*10,2,'.');//点
			OLED_ShowNum(8*11,2,b,2,16); 
			OLED_ShowString(8*13,2,"T");

			a=GY_MCU90615V2_buff[1];        //整数部分
			b=(((float)GY_MCU90615V2_buff[1]-a)*100);     //小数部分
			//体温显示--物理温度
			OLED_ShowNum(8*8,4,a,2,16);   //----------室温------------
			OLED_ShowChar(8*10,4,'.');    //点
			OLED_ShowNum(8*11,4,b,2,16); 
			OLED_ShowString(8*13,4,"T");
	}
}


/**********************************************************************************************************
* 函数名:     App_calStepHandler
* 功能描述:	  计步处理
* 作者:		    Jahol Fan  
* 参数说明:	  none
* 返回值说明:	none
* 修改记录: 
**********************************************************************************************************/
void App_calStepHandler(void)
{
    
    
//	u8 err;
//	accValue_t accValue;
//	personInfo_t personInfo;
//	static u32 step_count_old_Val;
//	static u8 time_cnt;
//	static Timer timeStamp;											//重命名结构体
//	short aacx,aacy,aacz;												//加速度传感器原始数据
//	static u8 tempSecond;								  			//保存秒钟暂态量
//	
// 	MPU_Get_Accelerometer(&aacx,&aacy,&aacz);		//得到加速度传感器数据
//	
//	if(tempSecond != timeStamp.sec)					    //秒更新
//	{
    
    
//	  tempSecond = timeStamp.sec;
//	  timeStamp.twentyMsCount = 0 ;							//20ms计数变量清零
//	}
//	else												  							//秒不更新,1秒等于50*20ms
//	{
    
    
//	  timeStamp.twentyMsCount++;								//20ms计数变量++
//	}
//	
//	timeStamp=RTC_Timer; 												//获取时间
//	
//	//将三轴数据转换为以g为单位的数据
//	accValue.accX = ((float)(int)aacx/16384) *10;
//	accValue.accY = ((float)(int)aacy/16384) *10;
//	accValue.accZ = ((float)(int)aacz/16384) *10; 
//	
//	step_count_old_Val = userSportsInfo.stepCount;
//	
//	userSportsInfo = *onSensorChanged(&accValue,&timeStamp,&personInfo); //调用计数算法

//	if(step_count_old_Val != userSportsInfo.stepCount)
//	{
    
    
//	  time_cnt++;
//	}
//	  
//	if(time_cnt == 1)
//	{
    
    
//		time_cnt = 0; 
//	} 

  u8 err;
	accValue_t accValue;
	static u32 step_count_old_Val;
  static u8 time_cnt;
	static timeStamp_t timeStamp;
	short aacx,aacy,aacz;									//加速度传感器原始数据
	static u8 tempSecond;								  //保存秒钟暂态量
	Timer *rtcTime; 							        //获取年月日时分秒
	//sportsInfo_t *sportsInfo;
	//sportsInfo = Pedometer_getSportsInfo();
	MPU_Get_Accelerometer(&aacx,&aacy,&aacz);		 //得到加速度传感器数据
	rtcTime = &RTC_Timer; 					   //获取当前RTC的值
	
	if(tempSecond != timeStamp.second)					 //秒更新
	{
    
    
	  tempSecond = timeStamp.second;
	  timeStamp.twentyMsCount = 0 ;//20ms计数变量清零
	}
	else												  //秒不更新,1秒等于50*20ms
	{
    
    
	  timeStamp.twentyMsCount ++;//20ms计数变量++
	}
	
	timeStamp.hour	 = rtcTime->hour;
	timeStamp.minute = rtcTime->minute;
	timeStamp.second = rtcTime->sec;
	
	
	//将三轴数据转换为以g为单位的数据
	accValue.accX = ((float)(int)aacx/16384) *10;
	accValue.accY = ((float)(int)aacy/16384) *10;
	accValue.accZ = ((float)(int)aacz/16384) *10; 
	step_count_old_Val = userSportsInfo.stepCount;
	userSportsInfo = *onSensorChanged(&accValue,&timeStamp,WatchInfo_getUserInfo(&err)); //调用计数算法
	
	if(step_count_old_Val != userSportsInfo.stepCount)
	{
    
    
	  time_cnt++;
	}
	  
	  if(time_cnt == 1)
	  {
    
    
		  time_cnt = 0;
	  } 
}

猜你喜欢

转载自blog.csdn.net/xiaolong1126626497/article/details/125830964