0 前言
🔥 这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕设题目缺少创新和亮点,往往达不到毕业答辩的要求,这两年不断有学弟学妹告诉学长自己做的项目系统达不到老师的要求。
为了大家能够顺利以及最少的精力通过毕设,学长分享优质毕业设计项目,今天要分享的是
🚩 毕业设计 stm32手部动作捕捉与生理参数监测系统
🥇学长这里给一个题目综合评分(每项满分5分)
- 难度系数:3分
- 工作量:3分
- 创新点:5分
🧿 选题指导, 项目分享:
1 简介
基于AB32VG1单片机的手部动作捕捉与生理参数监测硬件平台,本项目基于中科蓝讯AB32VG1开发版以及RT-Thread软件框架,利用集成前端倾角传感器MPU6050与双通道血氧检测传感器MAX30100,设计了一套完整的可采集手部位姿、血氧饱和度、温度、心率等参数的硬件平台。 该硬件平台,具有且不限于以下应用场景: 手功能康复评估(医疗):对手部残疾或手指受伤术后康复患者,可以借助对指节温度、血氧饱和度、手指可活动范围(ROM)等客观指标进行可复现、高精度的手功能康复评估。 人机交互(VR/MR/MetaVerse):借助六轴陀螺仪对操作者手部姿势的高准确度还原,形成更自然、操作信息更丰富的人机交互手段。 动作捕捉(影视、游戏):同样借助六轴陀螺仪对操作者手部姿势的高准确度还原,应用于游戏、电影、动画制作等需要对真人手部动作捕捉的场景。 低成本多通道模拟开关的设计,大幅降低物料成本和MCU的IO占用,配套以自行设计的小尺存传感器电路板,整套设备成本控制在200元以内,结合基于RT-Thread框架的开源软件包的使用,十分方便复现。
2 主要器件
- 中科蓝讯的AB32VG1单片机
- RT-Thread物联网操作系统
- 前端倾角传感器MPU6050
- 双通道血氧检测传感器MAX30100
- OLED显示模块
- Invensense官方的 eMPL姿态解算库
- 可采集手部位姿、血氧饱和度、温度、心率等参数
3 实现效果
4 设计原理
4.1 硬件设计
一、指节传感器电路
为了完成对手部动作的完整捕捉,以及考虑到医疗用途中患者手部受伤状况的复杂性。
传感器部分采取了独立、并行的硬件设计,分别布置在人手指末端的五个指节处,并可以根据需求进行放置位置的移动。
传感器电路主要完成以下四个参数的采集:指节位姿、血氧饱和度、心率以及温度。
指节位姿信息的采集
目前市面上主要流行的方法有应变电阻、电位器以及IMU的测量方案,考虑到在医疗场景中使用时,方便佩戴以及体积小巧是很重要的考虑因素,因此选择了使用IMU的测量方案,这里我们选择了MPU6050的六轴集成传感模块。
MPU6050模块
简介:
MPU6050内部整合了三轴MEMS陀螺仪、三轴MEMS加速度计以及一个可扩展的数字运动处理器DMP(Digital Motion Processor),而且还可以连接一个第三方数字传感器(如磁力计),这样的话,就可以通过IIC接口输出一个9轴信号(链接第三方数字传感器才可以输出九轴信号,否则只有六轴信号)。更加方便的是,有了DMP,可以结合InvenSense公司提供的运动处理资料库,实现姿态解算。通过自带的DMP,可以通过IIC接口输出9轴融合演算的数据,大大降低了运动处理运算对操作系统的负荷,同时也降低了开发难度。其实,简单一句话说,陀螺仪就是测角速度的,加速度传感器就是测角加速度的,二者数据通过算法就可以得到PITCH、YAW、ROLL角了。
血氧饱和度以及心率的测量
由于考虑到医疗安全的角度,且对精度没有十分准确的要求,我们最好采用非侵入式的监测手段,而PPG(光电容积脉搏波)是一种很成熟的非侵入式监测方案,大致原理是通过人体血液中氧合血红蛋白与还原血红蛋白,对不同波长入射光的投、反射系数不同,通过对特定波长反射光光强的量化,即可得到PPG信号,单通道的PPG信号我们可以获得心率和血压信息,而双通道(双波长)的PPG信号则可以获得血氧饱和度的信息,最终考虑到传感器集成体积和成本,选择了MAX30100集成传感器进行血氧饱和度、心率以及温度信息采集。
MAX30102
MAX30102是一个集成的脉搏血氧仪和心率监测仪生物传感器的模块。它集成了一个红光LED和一个红外光LED、光电检测器、光器件,以及带环境光抑制的低噪声电子电路。MAX30102采用一个1.8V电源和一个独立的5.0V用于内部LED的电源,应用于可穿戴设备进行心率和血氧采集检测,佩戴于手指、耳垂和手腕等处。标准的2C兼容的通信接口可以将采集到的数值传输给Arduino、KL25Z等单片机进行心率和血氧计算。此外,该芯片还可通过软件关断模块,待机电流接近为零,实现电源始终维持供电状态。正因为其优异的性能,该芯片被大量应用在了三星 Galaxy S7 手机。与前代产品 MAX30100 相比 (MAX30100 目前已经停产淘汰 ) , MAX30102 集成了玻璃盖可以有效排除外界和内部光干扰,拥有最优可靠的性能。
传统的脉搏测量方法主要有三种:
- 一是从心电信号中提取;
- 二是从测量血压时压力传感器测到的波动来计算脉率;
- 三是光电容积法。
前两种方法提取信号都会限制病人的活动,如果长时间使用会增加病人生理和心理上的不舒适感。而光电容积法脉搏测量作为监护测量中最普遍的方法之一,其具有方法简单、佩戴方便、可靠性高等特点。 光电容积法的基本原理是利用人体组织在血管搏动时造成透光率不同来进行脉搏和血 氧饱和度测量的。其使用的传感器由光源和光电变换器两部分组成,通过绑带或夹子固定 在病人的手指、手腕或耳垂上。光源一般采用对动脉血中氧合血红蛋白( HbO2 )和血红蛋 白( Hb )有选择性的特定波长的发光二极管(一般选用 660nm 附近的红光和 900nm 附近的 红外光)。当光束透过人体外周血管,由于动脉搏动充血容积变化导致这束光的透光率发 生改变,此时由光电变换器接收经人体组织反射的光线,转变为电信号并将其放大和输 出。由于脉搏是随心脏的搏动而周期性变化的信号,动脉血管容积也周期性变化,因此光 电变换器的电信号变化周期就是脉搏率。同时根据血氧饱和度的定义,其表示为:
MAX30102 本身集成了完整的发光 LED 及驱动部分,光感应和 AD 转换部分,环境光干 扰消除及数字滤波部分,只将数字接口留给用户,极大地减轻了用户的设计负担。用户只 需要使用单片机通过硬件 I2C或者模拟I2C接口来读取 MAX30102 本身的FIFO ,就可以得到转换后的光强度数值,通过编写相应算法就可以得到心率值和血氧饱和度。
电源设计及通信接口
MPU6050为3.3V供电,MAX30100同时需要3.3V和1.8V的供应电压,因此设计了3.3V和1.8V两路LDO。通信接口方面,两个集成传感器都使用IIC通信的方式。考虑到模块的更换方便与外观简洁,设计了Type-c的端口进行供电与数据传输。
传感器电路原理图与布局示意图如下:
传感器电路原理图
传感器电路立体图
电路布局示意图(背离人体面)
电路布局示意图(朝向人体面)
实物焊接效果如下:
二、前端数据电路(AB32VG1开发板拓展版)
完成了传感器电路部分的介绍,接下来介绍的是我们为了完成此次设计,结合中科蓝讯AB32VG1开发板设计的拓展版,AB32VG1丰富的开发资源与合理的可拓展硬件设计,为拓展设计提供了极大便利。
此次拓展版的设计,主要考虑到以下两个方面的需求:
1. AB32VG1开发板的供电电流(500mA)可能无法支持5路传感器+开发板的同时工作。
2. 前端传感器出现IIC地址重叠
因此,为了满足这些需求,我们为拓展版设计了以下三个方面的模块:
电源管理模块 ,主要包括12V直流输入、DC-
DC降压、LDO稳压,为开发板和前端传感器提供电源供应与基准电压,后续还可根据需要加入电池管理与充电IC。
多路模拟开关模块 ,利用TI的CD4051多路模拟开关,在采集对应通道的传感器数据时,进行特定选通,较低成本的解决了IIC地址复用问题。
OLED显示模块 ,方便设备在脱机使用时,显示一些本地信息和调试。
拓展板电路原理图及布局示意如下:
![QQ截图20211213023021.png](https://file.elecfans.com/web2/M00/26/C9/pYYBAGG-
MGWAc4lqAAM6wRIwO08171.png)
拓展板原理图
拓展板立体图
拓展板布局示意图
实物焊接效果如下:
最后是此次设计基于的开发板AB32VG1,厂商提供了详细的开发板资料、datasheet与例程,可以参考此次大赛的首页或公司首页获取这些资料。
焊接完成后,完成整体组装,因时间紧迫,尚未来得及设计外壳,后续视情况添加。
软件说明
在硬件部分完成设计后,进行了软件框架的设计与编写调试。
首先说明一些本次设计引用到的开源代码与RT-Thread相关软件包:Invensense官方提供的 eMPL姿态解算库
(主要进行IIC读写函数以及少量系统函数的移植)、 MAX30102的软件包 (与MAX30100驱动部分有差异,需自己修改)
软件部分流程如下图所示:
传感器初始化部分: 各通道轮流初始化,全部工作寄存器配置完成后,查询各传感器ID及状态,确保正常工作。
传感器数据采集 :
这里采用信号量的方式进行各采集线程的同步,主要考虑到多路模拟开关只在各线程采集进入时切换通道,若中途发生线程优先级抢占或轮转,会采集到错误通道数据。
数据上传及查询:
原先的设计是增加串口上传线程,进行数据上报,但是由于时间紧张,上位机显示部分尚未完工,因此目前先采取MSH命令行查询的方式,验证采集数据的正确性。
演示效果
最终佩戴至人体如图(后续会增加感器外壳,增加安全性):
各通道初始化(红光为MAX30100光电传感器光源):
血氧、心率数据采集(单通道显示):
以及原本计划编写手部实时捕捉的上位机显示部分尚未完工,因此暂时借用匿名四轴上位机进行数据显示,需要按照匿名上位机的格式进行数据发送(拇指):
多指节数据借助msh命令行输出进行验证(指节2、3的roll角度变化较为明显):
5 部分核心代码
/** \file max30102.cpp ******************************************************
*
* Project: MAXREFDES117#
* Filename: max30102.cpp
* Description: This module is an embedded controller driver for the MAX30102
*
* Revision History:
*\n 1-18-2016 Rev 01.00 GL Initial release.
*\n
*/
#include "max30102.h"
#include "myiic.h"
#define max30102_WR_address 0xAE
bool maxim_max30102_write_reg(uint8_t uch_addr, uint8_t uch_data)
/**
* \brief Write a value to a MAX30102 register
* \par Details
* This function writes a value to a MAX30102 register
*
* \param[in] uch_addr - register address
* \param[in] uch_data - register data
*
* \retval true on success
*/
{
/* 第1步:发起I2C总线启动信号 */
i2c_Start();
/* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
i2c_SendByte(max30102_WR_address | I2C_WR); /* 此处是写指令 */
/* 第3步:发送ACK */
if (i2c_WaitAck() != 0)
{
goto cmd_fail; /* EEPROM器件无应答 */
}
/* 第4步:发送字节地址 */
i2c_SendByte(uch_addr);
if (i2c_WaitAck() != 0)
{
goto cmd_fail; /* EEPROM器件无应答 */
}
/* 第5步:开始写入数据 */
i2c_SendByte(uch_data);
/* 第6步:发送ACK */
if (i2c_WaitAck() != 0)
{
goto cmd_fail; /* EEPROM器件无应答 */
}
/* 发送I2C总线停止信号 */
i2c_Stop();
return true; /* 执行成功 */
cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */
/* 发送I2C总线停止信号 */
i2c_Stop();
return false;
}
bool maxim_max30102_read_reg(uint8_t uch_addr, uint8_t *puch_data)
/**
* \brief Read a MAX30102 register
* \par Details
* This function reads a MAX30102 register
*
* \param[in] uch_addr - register address
* \param[out] puch_data - pointer that stores the register data
*
* \retval true on success
*/
{
/* 第1步:发起I2C总线启动信号 */
i2c_Start();
/* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
i2c_SendByte(max30102_WR_address | I2C_WR); /* 此处是写指令 */
/* 第3步:发送ACK */
if (i2c_WaitAck() != 0)
{
goto cmd_fail; /* EEPROM器件无应答 */
}
/* 第4步:发送字节地址, */
i2c_SendByte((uint8_t)uch_addr);
if (i2c_WaitAck() != 0)
{
goto cmd_fail; /* EEPROM器件无应答 */
}
/* 第6步:重新启动I2C总线。下面开始读取数据 */
i2c_Start();
/* 第7步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
i2c_SendByte(max30102_WR_address | I2C_RD); /* 此处是读指令 */
/* 第8步:发送ACK */
if (i2c_WaitAck() != 0)
{
goto cmd_fail; /* EEPROM器件无应答 */
}
/* 第9步:读取数据 */
{
*puch_data = i2c_ReadByte(); /* 读1个字节 */
i2c_NAck(); /* 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) */
}
/* 发送I2C总线停止信号 */
i2c_Stop();
return true; /* 执行成功 返回data值 */
cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */
/* 发送I2C总线停止信号 */
i2c_Stop();
return false;
}
bool maxim_max30102_init(void)
/**
* \brief Initialize the MAX30102
* \par Details
* This function initializes the MAX30102
*
* \param None
*
* \retval true on success
*/
{
if(!maxim_max30102_write_reg(REG_INTR_ENABLE_1, 0xc0)) // INTR setting
return false;
if(!maxim_max30102_write_reg(REG_INTR_ENABLE_2, 0x00))
return false;
if(!maxim_max30102_write_reg(REG_FIFO_WR_PTR, 0x00)) //FIFO_WR_PTR[4:0]
return false;
if(!maxim_max30102_write_reg(REG_OVF_COUNTER, 0x00)) //OVF_COUNTER[4:0]
return false;
if(!maxim_max30102_write_reg(REG_FIFO_RD_PTR, 0x00)) //FIFO_RD_PTR[4:0]
return false;
if(!maxim_max30102_write_reg(REG_FIFO_CONFIG, 0x6f)) //sample avg = 8, fifo rollover=false, fifo almost full = 17
return false;
if(!maxim_max30102_write_reg(REG_MODE_CONFIG, 0x03)) //0x02 for Red only, 0x03 for SpO2 mode 0x07 multimode LED
return false;
if(!maxim_max30102_write_reg(REG_SPO2_CONFIG, 0x2F)) // SPO2_ADC range = 4096nA, SPO2 sample rate (400 Hz), LED pulseWidth (411uS)
return false;
if(!maxim_max30102_write_reg(REG_LED1_PA, 0x17)) //Choose value for ~ 4.5mA for LED1
return false;
if(!maxim_max30102_write_reg(REG_LED2_PA, 0x17)) // Choose value for ~ 4.5mA for LED2
return false;
if(!maxim_max30102_write_reg(REG_PILOT_PA, 0x7f)) // Choose value for ~ 25mA for Pilot LED
return false;
return true;
}
bool maxim_max30102_read_fifo(uint32_t *pun_red_led, uint32_t *pun_ir_led)
/**
* \brief Read a set of samples from the MAX30102 FIFO register
* \par Details
* This function reads a set of samples from the MAX30102 FIFO register
*
* \param[out] *pun_red_led - pointer that stores the red LED reading data
* \param[out] *pun_ir_led - pointer that stores the IR LED reading data
*
* \retval true on success
*/
{
uint32_t un_temp;
uint8_t uch_temp;
*pun_ir_led = 0;
*pun_red_led = 0;
maxim_max30102_read_reg(REG_INTR_STATUS_1, &uch_temp);
maxim_max30102_read_reg(REG_INTR_STATUS_2, &uch_temp);
/* 第1步:发起I2C总线启动信号 */
i2c_Start();
/* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
i2c_SendByte(max30102_WR_address | I2C_WR); /* 此处是写指令 */
/* 第3步:发送ACK */
if (i2c_WaitAck() != 0)
{
printf("read fifo failed");
goto cmd_fail; /* EEPROM器件无应答 */
}
/* 第4步:发送字节地址, */
i2c_SendByte((uint8_t)REG_FIFO_DATA);
if (i2c_WaitAck() != 0)
{
goto cmd_fail; /* EEPROM器件无应答 */
}
/* 第6步:重新启动I2C总线。下面开始读取数据 */
i2c_Start();
/* 第7步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
i2c_SendByte(max30102_WR_address | I2C_RD); /* 此处是读指令 */
/* 第8步:发送ACK */
if (i2c_WaitAck() != 0)
{
goto cmd_fail; /* EEPROM器件无应答 */
}
un_temp = i2c_ReadByte();
i2c_Ack();
un_temp <<= 16;
*pun_red_led += un_temp;
un_temp = i2c_ReadByte();
i2c_Ack();
un_temp <<= 8;
*pun_red_led += un_temp;
un_temp = i2c_ReadByte();
i2c_Ack();
*pun_red_led += un_temp;
un_temp = i2c_ReadByte();
i2c_Ack();
un_temp <<= 16;
*pun_ir_led += un_temp;
un_temp = i2c_ReadByte();
i2c_Ack();
un_temp <<= 8;
*pun_ir_led += un_temp;
un_temp = i2c_ReadByte();
i2c_Ack();
*pun_ir_led += un_temp;
*pun_red_led &= 0x03FFFF; //Mask MSB [23:18]
*pun_ir_led &= 0x03FFFF; //Mask MSB [23:18]
/* 发送I2C总线停止信号 */
i2c_Stop();
return true;
cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */
/* 发送I2C总线停止信号 */
i2c_Stop();
return false;
}
bool maxim_max30102_reset()
/**
* \brief Reset the MAX30102
* \par Details
* This function resets the MAX30102
*
* \param None
*
* \retval true on success
*/
{
if(!maxim_max30102_write_reg(REG_MODE_CONFIG, 0x40))
return false;
else
return true;
}