RISC-V MCU+病房系统
时间:2022-08-28 18:00:01
RISC-V MCU 病房系统报告
团队名称: 一模一样
团队成员:李玉峰、李树燊、欧平源
目录
摘要
一、设计概述
1.1设计目的
1.2应用领域
1.3主要技术特点
1.4主要创新点
二、系统组成及设计方法
2.1整体介绍
2.2介绍硬件及其模块
2.2.1 CH32V307VCT6主控板
2.2.2 MAX30102心率传感器模块
2.2.3 LMT170体温传感器模块
三、完成情况及实验结果
四、总结
五、参考文献
六、附录
6.1主函数main.c程序
6.2 LED.c
6.3 AHT-10.c
6.4 IIC.c
6.2程序流程框图
摘要
随着可穿戴医疗设备在生命体征监测中的应用越来越多,医疗机构也迅速注意到便携式医疗设备和可穿戴医疗设备在疾病预防、咨询、术后恢复、饮食调节等方面的重要价值,因此智能病房项目在医院、康复中心逐渐迅速启动……
一、设计概述
1.1设计目的
为了满足现代医疗需求,为医院设计了快速便携的智能病房终端,可以测量患者的体温,可以测试患者的心率,同时收集病房的实时温湿度和显示治疗,方便医务人员更快地完成数据收集,减轻工作负担,更及时地报告。
1.2应用领域
在医院、诊所、急救中心,医务人员可以快速诊断发热症状患者的体征状态和温度,以及住院病房日常检查的数据测量和收集。
1.3主要技术特点
主控采用了CH32V307VCT6.采集和使用国产高速微处理器的心率MAX30102年,医用级用于体温测量LMT70体温测量芯片和环境传感器AHT-10实时采集环境温度,尺寸小,测量时间段小,体积小。
1.4主要创新点
与传统病房的一些监测仪器一起,该系统便携,可携式外出使用,也可在病房使用,具有快速诊断患者生命基础的体征,如测量体温,不再需要用汞温度针等待,只需夹在患者腋窝实时测量当前患者体温,心率测量只需将脉搏压在传感器上几秒钟即可显示心率,无需安装重型仪器即可快速采集测量。让医护人员在不接触患者和操作设备的情况下,准确监测患者的身体状况,帮助医护人员第一时间发现患者的病情变化,实现生命体征异常风险预警,为医生诊断和治疗患者提供科学的数据支持,大大降低了医护人员的工作强度,提高了工作效率。
二、系统组成及设计方法
2.1整体介绍
这个系统主要由CH32V307VCT6核心板、AHT-10温湿度传感器模块MAX30102心率传感器模块LMT由70体温传感器模块组成。通过CH32V307VCT6芯片驱动AHT-10温湿度传感器模块MAX30102心率传感器模块LMT由70体温传感器模块组成的70体温传感器模块AHT-10温湿度传感器模块MAX30102心率传感器模块LMT70体温传感器模块回传自己测量的数据CH32V307VCT6芯片,再通过LCD每个模块返回的数据实时显示屏幕。如2-1图所示,系统框图。
2-1系统整体框图
2.2介绍硬件及其模块
2.2.1 CH32V307VCT6主控板
CH32V307VCT6引脚原理图
2.2.2 MAX30102心率传感器模块
Maxim MAX30102传感器是一种集成脉搏血氧仪和心率监测器模块。MAX30102是脉搏血氧仪和心率检测仪的集成生物传感器模块。它集成了红光LED红外光LED,光电境光抑制的低噪声电子电路光电探测器。
MAX30102采用PPG光电容积脉冲波描(PhotoPlethysmoGraphy)测量数据,微控制器在处理这些数据后获得心率血氧值,然后通过I2C或UART接口输出大大降低了传感器的使用难度和主控资源的占用。传感器还配备了相应的上位机,可以通过计算机直接读取数据。可应用可穿戴设备和辅助医疗设备。MAX如2-3图所示,30102心率传感器。
特点:配备集成算法的微控制器,可通过计算机上位机直接读取数据。
参数:
● 供电电压:3.3-5V
● 工作电流:<15mA
● 通讯方式:I2C/UART
● I2C地址:0x57
● 串口波特率:96000
● -40工作温度范围℃~85℃
● 产品尺寸:25.5*32mm
图2-3 MAX30102心率传感器
2.2.3 LMT170体温传感器模块
LMT70 该模块采用德州仪器(TI)的 LMT70 模拟输出温度传感器的芯片设计,LMT70 它是一种超小、高精度、低功耗的互补金属氧化物半导体,具有输出导脚 (CMOS) 模拟温度传感器。LMT70几乎适用于物联网等所有高精度、低功耗的经济高效温度感知应用 (IoT) 传感器节点、医疗温度计、高精度仪表和电池供电设备。 LMT70 也是RTD 和高精度 NTC/PTC 理想的热敏电阻替代品。LMT70具有优异的温度匹配性能,两个相邻的相一卷带中取出 LMT70 温差最大 0.1°C。因此,对于需要计算热量传递的能量计量应用,LMT70是理想的解决方案。可以用来测试人体表面体温,例如手被,额头等,实施监测人体体温。LMT70体温传感器如2-4图所示。
特点:功耗低,测温精度高。
参数:
● 电压:2.0V~5.5V (推荐 3.3V)
● 通信方式:模拟量输出
图2-4 LMT70体温传感器
三、完成情况及实验结果
通过实际测试,该系统可以正常运行,并通过核心板上的按钮发送指令MAX30102心率模块测量当前人体心率和血氧浓度,显示返回测量数据,实时显示当前人体心率和血氧浓度,实时测量显示室温湿度和按钮控制室灯。实验结果如图3-1所示。
图3-1 实验结果图
四、总结
经过多天的团队斗争,系统的主要功能终于成功完成,系统正常运行。在生产过程中,我们遇到了很多问题,困扰了我们的团队很长一段时间,但最终通过咨询信息,团队讨论,逐一解决这些问题。列出遇到的几个问题,首先是串口数据的收发。我们预计将指令发送到传感器模块,然后传感器模块在收到指令后收集数据,并将收集到的数据返回到芯片并通过LCD显示出来。然而,我们发现一个单独的串口无法实现我们想要的功能。最后,通过串口3将两个串口发送到传感器。传感器收集数据并返回数据,然后将数据传输到串口1并返回芯片。第二,按键中断,我们发现按键中断函数,LCD显示屏卡住了,因为指令是通过按钮发送到传感器的,指令都是用来发送的while循环发送,判断不接收数据就会循环等待。后来,我们改变了一种方式,使用它DMA,就能正常,LCD与按钮不冲突。
五、参考文献
[1]CH32V307EVT 应用例程
[2]MK_LMT70模块使用说明书.pdf
[3]MAX30102芯片资料.pdf
[4]CH32F20xDS0.PDF
[5]CH32FV2x_V3xRM.PDF
[6]SCH_openCH_CH32V307_Board.pdf
六、附录
6.1主函数main.c程序
/* *@Note */ #include "debug.h" #include "AHT_10.h" #include "lcd.h" #include "LED.h" u16 TxBuf[1024]; s16 Calibrattion_Val = 0;/* Global define */
#define RXBUF_SIZE 1024 // DMA buffer size
#define size(a) (sizeof(a) / sizeof(*(a)))
#define up 1
#define down 2
#define left 3
#define right 4
#define sel 5
#define sw1 6
#define sw2 7
/* Global Variable */
u8 TxBuffer[] = " ";
u8 RxBuffer[RXBUF_SIZE]={
0};
/* Global Variable */
//初始化ADC
//这里我们仅以规则通道为例
//我们默认将开启通道0~3
void Adc_Init(void)
{
ADC_InitTypeDef ADC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |RCC_APB2Periph_ADC1 , ENABLE ); //使能ADC1通道时钟
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M
//PA1 作为模拟通道输入引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模拟输入引脚
GPIO_Init(GPIOA, &GPIO_InitStructure);
ADC_DeInit(ADC1); //复位ADC1,将外设 ADC1 的全部寄存器重设为缺省值
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC工作模式:ADC1和ADC2工作在独立模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE; //模数转换工作在单通道模式
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //模数转换工作在单次转换模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //转换由软件而不是外部触发启动
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADC数据右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1; //顺序进行规则转换的ADC通道的数目
ADC_Init(ADC1, &ADC_InitStructure); //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器
ADC_Cmd(ADC1, ENABLE); //使能指定的ADC1
ADC_ResetCalibration(ADC1); //使能复位校准
while(ADC_GetResetCalibrationStatus(ADC1)); //等待复位校准结束
ADC_StartCalibration(ADC1); //开启AD校准
while(ADC_GetCalibrationStatus(ADC1)); //等待校准结束
// ADC_SoftwareStartConvCmd(ADC1, ENABLE); //使能指定的ADC1的软件转换启动功能
}
//获得ADC值
//ch:通道值 0~3
u16 Get_Adc(u8 ch)
{
//设置指定ADC的规则组通道,一个序列,采样时间
ADC_RegularChannelConfig(ADC1,ch, 1, ADC_SampleTime_239Cycles5 ); //ADC1,ADC通道,采样时间为239.5周期
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //使能指定的ADC1的软件转换启动功能
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));//等待转换结束
return ADC_GetConversionValue(ADC1); //返回最近一次ADC1规则组的转换结果
}
u16 Get_Adc_Average(u8 ch,u8 times)
{
u32 temp_val=0;
u8 t;
for(t=0;t<times;t++)
{
temp_val+=Get_Adc(ch);
delay_ms(5);
}
return temp_val/times;
}
void GPIO_INIT(){
GPIO_InitTypeDef GPIO_InitTypdefStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOE,ENABLE);
GPIO_InitTypdefStruct.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;
GPIO_InitTypdefStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitTypdefStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOE, &GPIO_InitTypdefStruct);
GPIO_InitTypdefStruct.GPIO_Pin = GPIO_Pin_0;
GPIO_InitTypdefStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitTypdefStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitTypdefStruct);
GPIO_InitTypdefStruct.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_13;
GPIO_InitTypdefStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitTypdefStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOD, &GPIO_InitTypdefStruct);
}
/******************************************************************************* * Function Name : Basic_Key_Handle * Description : Basic Key Handle * Input : None * Return : 0 = no key press * 1 = key press down *******************************************************************************/
uint8_t Basic_Key_Handle( void ) //按键函数
{
uint8_t keyval = 0;
if( ! GPIO_ReadInputDataBit( GPIOE, GPIO_Pin_4 ) )
{
Delay_Ms(20);
if( ! GPIO_ReadInputDataBit( GPIOE, GPIO_Pin_4 ) )
{
keyval = sw1;
}
}
else {
if( ! GPIO_ReadInputDataBit( GPIOE, GPIO_Pin_5 ) )
{
Delay_Ms(20);
if( ! GPIO_ReadInputDataBit( GPIOE, GPIO_Pin_5 ) )
{
keyval = sw2;
}
}
else {
if( ! GPIO_ReadInputDataBit( GPIOE, GPIO_Pin_1 ) )
{
Delay_Ms(20);
if( ! GPIO_ReadInputDataBit( GPIOE, GPIO_Pin_1 ) )
{
keyval = up;
}
}
else {
if( ! GPIO_ReadInputDataBit( GPIOE, GPIO_Pin_2 ) )
{
Delay_Ms(20);
if( ! GPIO_ReadInputDataBit( GPIOE, GPIO_Pin_2 ) )
{
keyval = down;
}
}
else {
if( ! GPIO_ReadInputDataBit( GPIOE, GPIO_Pin_3 ) )
{
Delay_Ms(20);
if( ! GPIO_ReadInputDataBit( GPIOE, GPIO_Pin_3 ) )
{
keyval = right;
}
}
else {
if( ! GPIO_ReadInputDataBit( GPIOD, GPIO_Pin_6 ) )
{
Delay_Ms(20);
if( ! GPIO_ReadInputDataBit( GPIOD, GPIO_Pin_6 ) )
{
keyval = left;
}
}
else {
if( ! GPIO_ReadInputDataBit( GPIOD, GPIO_Pin_13 ) )
{
Delay_Ms(20);
if( ! GPIO_ReadInputDataBit( GPIOD, GPIO_Pin_13 ) )
{
keyval = sel;
}
}
}
}
}
}
}
}
return keyval;
}
void USARTx_CFG(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
// RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD,ENABLE);
// RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_AFIO,ENABLE);//重映射串口3
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);
GPIO_PinRemapConfig(GPIO_FullRemap_USART3,ENABLE);
/* USART2 TX-->A.2 RX-->A.3 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //设置PD8为复用推挽输出
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //设置PD9为浮空输入
GPIO_Init(GPIOD, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = 9600; // 波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 数据位 8
USART_InitStructure.USART_StopBits = USART_StopBits_1; // 停止位 1
USART_InitStructure.USART_Parity = USART_Parity_No; // 无校验
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 无硬件流控
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; //使能 RX 和 TX
USART_Init(USART3, &USART_InitStructure);
DMA_Cmd(DMA1_Channel3, ENABLE); //开启接收 DMA
USART_Cmd(USART3, ENABLE); //开启USART
}
/******************************************************************************* * Function Name : DMA_INIT * Description : Configures the DMA. * 描述 : DMA 初始化 * Input : None * Return : None *******************************************************************************/
void DMA_INIT(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
// TX DMA 初始化
DMA_DeInit(DMA1_Channel2);
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&USART3->DATAR); // DMA 外设基址,需指向对应的外设
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)TxBuffer; // DMA 内存基址,指向发送缓冲区的首地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; // 方向 : 外设 作为 终点,即 内存 -> 外设
DMA_InitStructure.DMA_BufferSize = 0; // 缓冲区大小,即要DMA发送的数据长度,目前没有数据可发
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址自增,禁用
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 内存地址自增,启用
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 外设数据位宽,8位(Byte)
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // 内存数据位宽,8位(Byte)
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // 普通模式,发完结束,不循环发送
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; // 优先级最高
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // M2P,禁用M2M
DMA_Init(DMA1_Channel2, &DMA_InitStructure);
// RX DMA 初始化,环形缓冲区自动接收
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)RxBuffer; // 接收缓冲区
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // 方向 : 外设 作为 源,即 内存 <- 外设
DMA_InitStructure.DMA_BufferSize = RXBUF_SIZE; // 缓冲区长度为 RXBUF_SIZE
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 循环模式,构成环形缓冲区
DMA_Init(DMA1_Channel3, &DMA_InitStructure);
}
/******************************************************************************* * Function Name : uartWrite * Input : char * data data to send 要发送的数据的首地址 * uint16_t num number of data 数据长度 * Return : RESET UART6 busy,failed to send 发送失败 * SET send success 发送成功 *******************************************************************************/
FlagStatus uartWrite(char * data , uint16_t num) //q
{
//如上次发送未完成,返回
if(DMA_GetCurrDataCounter(DMA1_Channel2) != 0){
return RESET;
}
DMA_ClearFlag(DMA1_FLAG_TC7);
DMA_Cmd(DMA1_Channel2, DISABLE ); // 关 DMA 后操作
DMA1_Channel2->MADDR = (uint32_t)data; // 发送缓冲区为 data
DMA_SetCurrDataCounter(DMA1_Channel2,num); // 设置缓冲区长度
DMA_Cmd(DMA1_Channel2, ENABLE); // 开 DMA
return SET;
}
/******************************************************************************* * Function Name : uartWriteStr * Input : char * str string to send * Return : RESET UART busy,failed to send 发送失败 * SET send success 发送成功 *******************************************************************************/
FlagStatus uartWriteStr(char * str) //1
{
uint16_t num = 0;
while(str[num])num++; // 计算字符串长度
return uartWrite(str,num); //w
}
/******************************************************************************* * Function Name : uartReadreceive * Description : read some bytes from receive buffer 从接收缓冲区读出一组数据 * Input : char * buffer buffer to storage the data 用来存放读出数据的地址 * uint16_t num number of data to read 要读的字节数 * Return : int number of bytes read 返回实际读出的字节数 *******************************************************************************/
uint16_t rxBufferReadPos = 0; //接收缓冲区读指针
uint32_t uartRead(char * buffer , uint16_t num)
{
uint16_t rxBufferEnd = RXBUF_SIZE - DMA_GetCurrDataCounter(DMA1_Channel3); //计算 DMA 数据尾的位置
uint16_t i = 0;
if (rxBufferReadPos == rxBufferEnd){
// 无数据,返回
return 0;
}
while (rxBufferReadPos!=rxBufferEnd && i < num){
buffer[i] = RxBuffer[rxBufferReadPos];
i++;
rxBufferReadPos++;
if(rxBufferReadPos >= RXBUF_SIZE){
// 超出缓冲区,回零
rxBufferReadPos = 0;
}
}
return i;
}
/******************************************************************************* * Function Name : uartReadByte * Description : read one byte from UART buffer 从接收缓冲区读出 1 字节数据 * Input : None * Return : char read data 返回读出的数据(无数据也返回0) *******************************************************************************/
char uartReadByte()
{
char ret;
uint16_t rxBufferEnd = RXBUF_SIZE - DMA_GetCurrDataCounter(DMA1_Channel3);
if (rxBufferReadPos == rxBufferEnd){
// 无数据,返回
return 0;
}
ret = RxBuffer[rxBufferReadPos];
rxBufferReadPos++;
if(rxBufferReadPos >= RXBUF_SIZE){
// 超出缓冲区,回零
rxBufferReadPos = 0;
}
return ret;
}
/******************************************************************************* * Function Name : uartAvailable * Description : get number of bytes Available to read from the UART buffer 获取缓冲区中可读数据的数量 * Input : None * Return : uint16_t number of bytes Available to read 返回可读数据数量 *******************************************************************************/
uint16_t uartAvailable()
{
uint16_t rxBufferEnd = RXBUF_SIZE - DMA_GetCurrDataCounter(DMA1_Channel3);//计算 DMA 数据尾的位置
// 计算可读字节
if (rxBufferReadPos <= rxBufferEnd){
return rxBufferEnd - rxBufferReadPos;
}else{
return rxBufferEnd +RXBUF_SIZE -rxBufferReadPos;
}
}
/******************************************************************************* * Function Name : main * Description : Main program. * Input : None * Return : None *******************************************************************************/
int main(void)
{
char buffer[1024]={
"\0"};
float temperature, humidity;//温湿度
u16 adcx;
float temp;
float tem;
int num;
LED_Init(); //LED初始化
Delay_Init(); //延时初始化
USART_Printf_Init(9600);//串口初始化
lcd_init(); //LCD初始化
GPIO_INIT(); //IO初始化
lcd_set_color(BLACK,BLUE);
lcd_show_string(30, 0, 32,"Ward system");
// delay_ms(100);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
printf("SystemClk:%d\r\n",SystemCoreClock);
printf("EX