项目:心肺复苏按压仪+沁恒赤兔ch32v307+嵌入式开发

心肺复苏按压仪+2021嵌入式芯片设计大赛嵌入式芯片与系统设计竞赛

  1. 简述:本项目基于沁恒赤兔ch32v307vct6开发板,项目实现:lcd屏幕显示、心电传感器控制 压力传感器模块HX711与读取数据,CH9141蓝牙传输 串口通信 蓝牙app设计心电图显示等、环形fifo缓存、lc298n继电器控制电机等
  2. 源码下载:https://download.csdn.net/download/qq_43029779/86490280
  3. B站演示视频:https://www.bilibili.com/video/BV1iY411M7jj?vd_source=ba3c1dd6bd8eb83604725933a8f1a7d6

第一部分 设计概述
1.1 设计目的
在当前人们生活压力大,心脏骤停严重威胁着人类的生命健康。当一个人因某种原因如心跳骤停和呼吸骤停,导致心功能和肺功能丧失而处于死亡状态时,人们利用一些急救方法使其恢复心跳和呼吸,促使血液含氧流经各脏器,以挽救患者生命。
令人遗憾的是,传统的徒手心肺复苏(Cardiopulmonary Resuscitation, CPR)时到达心脏和脑的血流非常少。研究指出,徒手心肺复苏仅能提供相当于正常生理情况下10%20%的血流给心脏,20%30%的血流给脑。且一般使用心肺复苏急救时间在30分钟以上,单人任难以持续完成救援。医学界对于高质量的心肺复苏技术具有急切需求,加上传统徒手心肺复苏的固有局限性,激发了能增加循环血量的新技术的热潮。
机械式心肺复苏,即采用心肺复苏机械设备进行心肺复苏,此类设备通常称做心肺复苏机。 CPR机械设备设计的初衷是增加心脏骤停患者心脏和脑的血流,并为后续的除颤、静脉用药、血管重建等起到桥梁承接作用。
1.2应用领域
医疗器械向数字化、智能化转型是大势所趋。国外心肺复苏仪器价格昂贵,不利于产品普及。海外品牌的智能心肺复苏设备因其成熟的功能和稳定的性能长期占据我国大部分市场,仅具有简单机械按压功能的智能心肺复苏设备价格可达近10万。带有按压深度反馈、生理信号检测、数据存储共享等功能的智能心肺复苏设备的价格通常更加昂贵,我们的作品恰好弥补了人们在这方面的医疗需求。
1.3主要技术特点
本产品目标是:
(a)能够自动按照频率和深度按压心肺,增加循环血流,并且操作人员可以根据患者请款改变频率和按压深度;
项目:心肺复苏按压仪+沁恒赤兔ch32v307+嵌入式开发_第1张图片

图1 样例照片
(b)能够通过显示屏实时交互信息;
©能够通过传感器采集按压频率按压深度等信息;
(d)能够存储数据上传到个人电脑,显示该患者信息,连接蓝牙可以通过蓝牙小程序查询患者心肺复苏相关信息。
1.4 关键性能指标
正常心肺复苏中,按压频率应为每分钟80次;
按压下陷深度3厘米;
按压与放松所需时间相等;
1.5 方案选择
方案一:系统的机械结构,关键部件是储气瓶、单作用气缸、步进电机和电磁阀。
项目:心肺复苏按压仪+沁恒赤兔ch32v307+嵌入式开发_第2张图片

方案二:直接通过步进电机控制按压频率和深度。
方案二更易于实现,选择方案二。

第二部分 系统组成即功能说明
2.1 整体说明
本作品通过沁恒开发的CH32V307作为主控板,心肺复苏系统的电路板要完成的功能就是能够接受使用者的意图、输出显示系统的工作状态、数据存储、控制步进电机和继电器的动作以及与计算机通信。
通过开发以下模块实现信息:
(a)触屏输入与液晶显示模块(人机对话界面,本系统的输入采用触摸屏,可触控输入按压频率深度等、LCD(液态晶体显示)屏幕显示采集到的实时信息);
(b)继电器控制步进电机(驱动电机按压)(通过控制继电器完控制电机按压动作);
(e)串口通信模块(要将已存储的数据上传到个人电脑,还需有与计算机通信的串口单元,将数据传输到电脑);
(f)蓝牙小程序模块(智慧医疗:医师可以通过蓝牙连接获取心肺复苏期间患者信息,实现智慧医疗)。
2.2 各模块介绍
2.2.1 LCD显示模块
本次实验所使用的开发板提供了一块1.33寸240*240分辨率的LCD屏,FSMC控制。通过预先加载字库,可以实现字体、图像的显示。本实验将储存在buffer数组中的心率、血氧饱和度等数据以及HX711传感器输出的压力值输出到屏幕上。
2.2.2 继电器控制电机模块
电机通断通过继电器控制,以免电机对按压部位造成伤害。lc298n调研部分
项目:心肺复苏按压仪+沁恒赤兔ch32v307+嵌入式开发_第3张图片

①双5V输入
在A、B两处都接入一个5V电压,G接GND,AGB上方的板载5V使能要用跳线帽接起来。这种方法电压比较小,轮子可能转不起来,也无法进行PWM调速。不推荐!
②单12V输入
在A处接入一个7-12V电压,G接GND,B是一个5V输出,AGB上方的板载5V使能要用跳线帽接起来。没听错输入就变输出了,可以用来给单片机供电那种(但不太好用),这种方法很好用,转速可以,也可以进行PWM调速,最常用的方法就是它。
③单大于12V输入
在A处接入一个大于12V电压,G接GND,B是一个5V输出,AGB上方的板载5V使能的跳线帽接要拔掉。这种方法电压太大可能会烧坏玩具电机等小型电机,顺便摧毁单片机,不推荐!
3、通道使能
①PWM调速
如果你想用PWM调速,那就把通道使能脚接入单片机的PWM波输出脚。ENA控制输出A,ENB控制输出B。
②非PWM调速
不用PWM调速,就单纯想让电机转动,就给使能脚一个高电平即可,可以通过跳线帽将其与高电平输出脚相连,如果给使能脚低电平的话,电机将无法转动。

2.2.3 压力传感器模块HX711
HX711是一款高精度24位A/D转换器芯片,常见于电子秤称重中。该芯片集成了稳压电源、片内时钟振荡器等外围电路,具有集成度高、响应速度快、抗干扰性强的特点。该芯片与MCU芯片的接口和编程都十分简单,所有控制信号都由管脚驱动,无需对芯片内部的寄存器编程。HX711有两个输入通道:通道A的可编程增益为128或64,对应的满额度差分输入信号幅值分别为±20mV或±40mV。通道B则为固定的32增益,用于系统参数检测。芯片内提供的稳压电源可以直接向外部传感器和芯片内的A/D 转换器提供电源,系统板上无需另外的模拟电源。芯片内的时钟振荡器不需要任何外接器件。上电自动复位功能简化了开机的初始化过程。HX711芯片引脚图如下图1所示:
项目:心肺复苏按压仪+沁恒赤兔ch32v307+嵌入式开发_第4张图片

图 1 HX711引脚图

本设计通过使用HX711传感器,附着在心肺复苏按压仪按压端并感知其压力大小。当压力达到给定的阈值时,按压仪的电机会停止工作。
在本次设计中,我们把传感器连接到HX711的A通道,VCC接沁恒赤菟OpenCH CH32V307VCT6开发板的3.3V VCC引脚,GND共地,DT(DOUT)为脉冲输出脚、数据端口,SCK为脉冲(时钟)输入脚,具体接线方法如下图2所示:
项目:心肺复苏按压仪+沁恒赤兔ch32v307+嵌入式开发_第5张图片
图 2 HX711接线图

2.2.4 CH9141蓝牙传感器模块
在这里插入图片描述
图3 CH9141芯片
CH9141是一款蓝牙串口透传芯片,芯片样式如图3所示。芯片支持广播模式、主机模式、从机模式,支持BLE4.2。该芯片支持串口AT配置和在从机模式下的蓝牙通信配置,提供通用GPIO、同步GPIO、ADC采集功能,串口波特率最高1Mbps。蓝牙从机模式下可设置蓝牙名称、厂商信息等参数,可通过APP或者串口命令配置,效果如图4所示。芯片支持串口透传功能,默认出厂波特率为115200bit/s。本实验蓝牙模块的波特率选择38400bit/s。将生命体征监测模块产生的实时数据包以数组buffer[76]的形式储存起来,并且通过蓝牙将连接在开发板上的生命体征监测模块收集到的数据(心率、血氧饱和度等)传输到相应APP上,并在APP上绘制出图像。
项目:心肺复苏按压仪+沁恒赤兔ch32v307+嵌入式开发_第6张图片

图 4 CH9141 AT配置示意
2.2.5串口控制生命体征模块
如图所示,生命体征模块可以通过串口传输脉搏波形,血压血氧等数据,再通过串口DMA将数据暂存入FIFO,把数据通过蓝牙传输给手机app,以便医生实时监控病人生命体征。
项目:心肺复苏按压仪+沁恒赤兔ch32v307+嵌入式开发_第7张图片

2.2.6 蓝牙app部分
主要工作:微信开发者工具学习、蓝牙小程序开发
成果:理解代码中各部分功能,能够对界面进行设计修改,并发布上线;
已生成能与板子连接的小程序
后续工作:理解所用传感器数据传输格式,并通过app接收显示
预期功能
项目:心肺复苏按压仪+沁恒赤兔ch32v307+嵌入式开发_第8张图片

点击“连接”按钮与周围的蓝牙设备进行配对,自动接收规定格式的信号,并显示对应的波形图、峰峰值、频率、THDx、五次谐波的归一化幅值

正常运转时,“连接”按钮下方会显示“send”

发送信号的格式:“send,THDx值,2次谐波值,3次谐波值,4次谐波值,5次谐波值,待画波形幅度值,待画波形频率值,待画波形(长度不限),end ” 举个例子:“send,3.11,0.7,0.6,0.5,0.4,200,1000,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,end”

具体实现
开发环境
开发语言: java
开发软件:Android Studio 2020.3.1

各个模块
这里展示一些核心代码,布局 连接按钮

  Button:id = connectButton, text=“连接”

正常接收时,连接按钮下方显示send
  ScrollView:id = receiveScrolView
显示text
  TextView: id = textView7, text=“波形图”
  TextView: id = textViewxA, text="峰峰值: "
  TextView: id = textViewxF, text="频率: "
  TextView: id = textViewTHDx, text="THDx: "
  TextView: id = textView3, text=“归一化幅值图”
  TextView: id = textViewj, text=“基波归一化幅值: 1”
  TextView: id = textViewx3, text="3次谐波归一化幅值: "
  TextView: id = textViewx4, text="4次谐波归一化幅值: "
  TextView: id = textViewx5, text="5次谐波归一化幅值: "
画布
  com.example.btcontroller.LineChartView:id = lineChartView
  com.example.btcontroller.LineChartView:id = lineChartView2

2.2.7电机部分实现
项目:心肺复苏按压仪+沁恒赤兔ch32v307+嵌入式开发_第9张图片

电机为可调速电机,通过简单的继电器控制即可实现继电器控制电机通断,进而控制电机运动。
电机为压控电机,运动时只需改变电机的电压即可改变电机运转的速度,因为此我们采用一个滑动电阻器来控制改变电机的电压。

2.2.8 uart与FIFO回环传输部分
UART(Universal Asynchronous Receiver-Transmitter)是采用异步串行通信方式的通用异步收发传输器,在发送数据时将并行数据转换为串行数据,在接收数据时将串行数据转换为并行数据。发送和接收的一帧数据由起始位、数据位、奇偶校验位和停止位组成。
串口通信的速率用波特率表示,单位为bps(位/秒),常用的波特率由9600、19200、38400、57600、115200。

UART串口通信实验内容
UART串口通信实验内容位:当上位机通过串口调试工具发送数据给FPGA,FPGA通过串口接收数据并将接收到的数据发送给上位机,完成串口数据环回。当上位机未发送数据时候,FPGA每隔1s产生Hello World!发送给上位机并通过串口调试工具显示。
FIFO的必要性。在进行UART通信时,中断方式比轮询方式要简便且效率高。但是,如果没有收发FIFO,则每传输一个数据(5~8位)都要中断处理一次,效率仍然不高。如果有了收发FIFO,则可以在连续收发若干个数据(可多至14个)后才产生一次中断,然后一起处理。这就大大提高了收发效率。
  接收超时问题。如果没有接收超时功能,则在对方已经发送完毕而接收FIFO未填满时并不会触发中断(FIFO满才会触发中断),结果造成最后接收的有效数据得不到处理的问题。有了接收超时功能后,如果接收FIFO未填满而对方发送已经停,则在不超过3个数据的接收时间内就会触发超时中断,因此数据会照常得到处理。
  发送时,只要发送FIFO不满,数据只管往里连续放,放完后就直接退出发送子程序。随后,FIFO真正发送完成后会自动产生中断,通知主程序说:我已经完成真正的发送。
接收时,如果对方是连续不间断发送,则填满FIFO后会以中断的方式通知主程序说:现在有一批数据来了,请处理。
  如果对方是间断性发送,也不要紧,当间隔时间过长时(2~3个字符传输时间),也会产生中断,这次是超时中断,通知主程序说:对方可能已经发送完毕,但FIFO未满,也请处理。

主要函数如下所示:
/*******************************************************************************
* Function Name  :  uartWriteBLE
* Description    :  send data to BLE via UART7          向蓝牙模组发送数据
* Input          :  char * data          data to send   要发送的数据的首地址
*                   uint16_t num         number of data 数据长度
* Return         :  RESET                UART7 busy,failed to send  发送失败
*                   SET                  send success               发送成功
*******************************************************************************/
FlagStatus uartWriteBLE(char * data , uint16_t num)
{
    //如上次发送未完成,返回
    if(DMA_GetCurrDataCounter(DMA2_Channel8) != 0){
        return RESET;
    }

    DMA_ClearFlag(DMA2_FLAG_TC8);
    DMA_Cmd(DMA2_Channel8, DISABLE );           // 关 DMA 后操作
    DMA2_Channel8->MADDR = (uint32_t)data;      // 发送缓冲区为 data
    DMA_SetCurrDataCounter(DMA2_Channel8,num);  // 设置缓冲区长度
    DMA_Cmd(DMA2_Channel8, ENABLE);             // 开 DMA
    return SET;
}

/*******************************************************************************
* Function Name  :  uartWriteBLEstr
* Description    :  send string to BLE via UART7    向蓝牙模组发送字符串
* Input          :  char * str          string to send
* Return         :  RESET                UART7 busy,failed to send  发送失败
*                   SET                  send success               发送成功
*******************************************************************************/
FlagStatus uartWriteBLEstr(char * str)
{
    uint16_t num = 0;
    while(str[num])num++;           // 计算字符串长度
    return uartWriteBLE(str,num);
}


/*******************************************************************************
* Function Name  :  uartReadBLE
* 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       返回实际读出的字节数
*******************************************************************************/

uint32_t uartReadBLE(char * buffer , uint16_t num)
{
    uint16_t rxBufferEnd = RXBUF_SIZE - DMA_GetCurrDataCounter(DMA2_Channel9); //计算 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  :  uartReadByteBLE
* Description    :  read one byte from UART buffer  从接收缓冲区读出 1 字节数据
* Input          :  None
* Return         :  char    read data               返回读出的数据(无数据也返回0)
*******************************************************************************/
char uartReadByteBLE()
{
    char ret;
    uint16_t rxBufferEnd = RXBUF_SIZE - DMA_GetCurrDataCounter(DMA2_Channel9);//计算 DMA 数据尾的位置
    if (rxBufferReadPos == rxBufferEnd){
        // 无数据,返回
        return 0;
    }
    ret = RxBuffer[rxBufferReadPos];
    rxBufferReadPos++;
    if(rxBufferReadPos >= RXBUF_SIZE){
        // 超出缓冲区,回零
        rxBufferReadPos = 0;
    }
    return ret;
}
/*******************************************************************************
* Function Name  :  uartAvailableBLE
* Description    :  get number of bytes Available to read from the UART buffer  获取缓冲区中可读数据的数量
* Input          :  None
* Return         :  uint16_t    number of bytes Available to readd              返回可读数据数量
*******************************************************************************/
uint16_t uartAvailableBLE()
{
    uint16_t rxBufferEnd = RXBUF_SIZE - DMA_GetCurrDataCounter(DMA2_Channel9);//计算 DMA 数据尾的位置
    // 计算可读字节
    if (rxBufferReadPos <= rxBufferEnd){
        return rxBufferEnd - rxBufferReadPos;
    }else{
        return rxBufferEnd +RXBUF_SIZE -rxBufferReadPos;
    }
}

char* Int2String(int num,char *str)//10进制
{
    int i = 0;//指示填充str
    if(num<0)//如果num为负数,将num变正
    {
        num = -num;
        str[i++] = '-';
    }
    //转换
    do
    {
        str[i++] = num%10+48;//取num最低位 字符0~9的ASCII码是48~57;简单来说数字0+48=48,ASCII码对应字符'0'
        num /= 10;//去掉最低位
    }while(num);//num不为0继续循环

    str[i] = '\0';

    //确定开始调整的位置
    int j = 0;
    if(str[0]=='-')//如果有负号,负号不用调整
    {
        j = 1;//从第二位开始调整
        ++i;//由于有负号,所以交换的对称轴也要后移1位
    }
    //对称交换
    for(;j<i/2;j++)
    {
        //对称交换两端的值 其实就是省下中间变量交换a+b的值:a=a+b;b=a-b;a=a-b;
        str[j] = str[j] + str[i-1-j];
        str[i-1-j] = str[j] - str[i-1-j];
        str[j] = str[j] - str[i-1-j];
    }

    return str;//返回转换后的值
}

2.2.9 Gpio与uart

外部中断优势:使用外部中断来处理按键动作不仅响应速度是最快最及时的,还不需要一直跑循环占用计算资源,因此我们选择使用外部中断来处理按键 Wake_Up 的动作。
所有的 GPIO 口都可以被配置外部中断输入通道,但一个外部中断输入通道最多只能映射到一个 GPIO 引脚上,且外部中断通道的序号必须和 GPIO 端口的位号一致,比如 PA1(或 PB1、PC1、PD1、PE1 等)只能映射到 EXTI1 上,且 EXTI1 只能接受 PA1、PB1、PC1、PD1 或 PE1 等其中之一的映射,两方都是一对一的关系。
项目:心肺复苏按压仪+沁恒赤兔ch32v307+嵌入式开发_第10张图片

1.值得注意的是,CH32V307 虽有 16 个 GPIO 外部中断输入通道,但对应的中断服务函数只有 7 个:
1.通道 0、1、2、3、4 各一个
2.通道 59 共用一个
3.通道 1015 共用一个
项目:心肺复苏按压仪+沁恒赤兔ch32v307+嵌入式开发_第11张图片

实现过程:通过 资料 -> openCH 赤菟开发板开发资源v1.0.0 中的引脚定义可以得出,按下 Wake_Up 按键后,引脚 PA0 处会得到高电平,另外,PE11 引脚处连接了 LED1。
项目:心肺复苏按压仪+沁恒赤兔ch32v307+嵌入式开发_第12张图片

借鉴官方例程 CH32V307EVT 的 EXAM -> GPIO -> GPIO_Toggle 中初始化 GPIO 的代码,完成初始化 PE11 的代码。官方例程 CH32V307EVT 的 EXAM -> GPIO -> GPIO_Toggle 中初始化 GPIO 的代码:

主要代码如下:
/*******************************************************************************
* Function Name  : USARTx_CFG
* Description    : Initializes the USART peripheral.
* 描述    :   串口初始化
* Input          : None
* Return         : None
*******************************************************************************/
void UART3_CFG(void)
{
    GPIO_InitTypeDef  GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    //开启时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);

    /* USART3 CK(SCK)-->D10  RX(DT)-->D9 */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_Init(GPIOD, &GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;           //RX
    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_Rx; //使能 RX

    USART_Init(USART3, &USART_InitStructure);
    USART_Cmd(USART3, ENABLE);                                        //开启UART
}
/* Global define */
#define RXBUF_SIZE 1024     // DMA buffer size
#define size(a)   (sizeof(a) / sizeof(*(a)))

/* Global Variable */
u8 TxBuffer[] = " ";
u8 RxBuffer[RXBUF_SIZE]={0};


/*******************************************************************************
* Function Name  : USARTx_CFG
* Description    : Initializes the USART peripheral.
* 描述    :   串口初始化
* Input          : None
* Return         : None
*******************************************************************************/
void UART2_CFG(void)
{
    GPIO_InitTypeDef  GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    //开启时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART7, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);

    /* USART2 TX-->A2  RX-->A3 */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;           //RX,输入上拉
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    USART_InitStructure.USART_BaudRate = 38400;                    // 波特率
    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(USART2, &USART_InitStructure);
    DMA_Cmd(DMA1_Channel6, ENABLE);                                  //开启接收 DMA
    USART_Cmd(USART2, ENABLE); //开启UART

    /* USART7 TX-->C2  RX-->C3 */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOC, &GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;           //RX,输入上拉
    GPIO_Init(GPIOC, &GPIO_InitStructure);

    USART_InitStructure.USART_BaudRate = 115200;                    // 波特率
    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(UART7, &USART_InitStructure);
    DMA_Cmd(DMA2_Channel9, ENABLE);                                  //开启接收 DMA
    USART_Cmd(UART7, ENABLE);                                        //开启UART
}

/*******************************************************************************
* 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_DMA2, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

    // TX DMA 初始化
    DMA_DeInit(DMA2_Channel8);
    DMA_DeInit(DMA1_Channel7);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&UART7->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(DMA2_Channel8, &DMA_InitStructure);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&USART2->DATAR);        // DMA 外设基址,需指向对应的外设
    DMA_InitStructure.DMA_MemoryBaseAddr = (u32)RxBuffer;                   // DMA 内存基址,指向发送缓冲区的首地址。未指定合法地址就启动 DMA 会进 hardfault
    DMA_Init(DMA1_Channel7, &DMA_InitStructure);

    // RX DMA 初始化,环形缓冲区自动接收
    DMA_DeInit(DMA2_Channel9);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&UART7->DATAR);
    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(DMA2_Channel9, &DMA_InitStructure);
    DMA_DeInit(DMA1_Channel6);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&USART2->DATAR);
    DMA_Init(DMA1_Channel6, &DMA_InitStructure);
}


/*******************************************************************************
* Function Name  : GPIO_CFG
* Description    : Initializes GPIOs.
* 描述    :   GPIO 初始化
* Input          : None
* Return         : None
*******************************************************************************/
void GPIO_CFG(void)
{
    GPIO_InitTypeDef  GPIO_InitStructure;
    // CH9141 配置引脚初始化
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
    /* BLE_sleep --> C13  BLE_AT-->A7 */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_Init(GPIOC, &GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
}

uint16_t rxBufferReadPos = 0;       //接收缓冲区读指针
uint16_t uartAvailable()
{
    uint16_t rxBufferEnd = RXBUF_SIZE - DMA_GetCurrDataCounter(DMA1_Channel6);//计算 DMA 数据尾的位置
    // 计算可读字节
    if (rxBufferReadPos <= rxBufferEnd){
        return rxBufferEnd - rxBufferReadPos;
    }else{
        return rxBufferEnd +RXBUF_SIZE -rxBufferReadPos;
    }
}


uint32_t uartRead(char * buffer , uint16_t num)
{
    uint16_t rxBufferEnd = RXBUF_SIZE - DMA_GetCurrDataCounter(DMA1_Channel6); //计算 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;
}


/*********************************************************************
 * @fn      GPIO_Toggle_INIT
 *
 * @brief   Initializes GPIOA.0
 *
 * @return  none
 *//**@Note
 GPIO例程:
 PA0推挽输出。
*/
void GPIO_Toggle_INIT(void)
{
    GPIO_InitTypeDef GPIO_InitStructure = {0};

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
}

第三部分完成情况及性能参数
本作品通过沁恒开发的CH32V307作为主控板,心肺复苏按压仪完成以下功能:
(a)串口通信模块:通过串口发送指令控制心电采集模块,并通过串口DMA传输反馈信号;
(b)触屏输入与液晶显示模块:人机对话界面,本系统的输入采用触摸屏,可触控输入按压频率深度等、LCD(液态晶体显示)屏幕显示采集到的实时信息;
©压力传感模块:通过采集压力来判断是否压力超负荷,若有超过一定压力值则在lcd显示报警;
(d)蓝牙小程序模块(智慧医疗:医师可以通过蓝牙连接获取心肺复苏期间患者信息,实现智慧医疗);
(e)电机按压模块:通过继电器控制电机正常按压。

3.1 压力传感器HX711调试效果
我们在网上搜集有关资料,找到适用于STM32单片机的HX711驱动程序代码。在将代码进行必要改动以适配沁恒开发板并下载烧录后,用手指按压HX711的应变片,发现读数并不稳定,如图5所示。继续搜集资料,发现需要将HX711接收到的数据通过惯性滤波算法和平均值滤波算法进行滤波,然后得到稳定的输出。两种算法程序的流程图如图6、7所示。在加入滤波代码后,问题得到解决。详细驱动代码和滤波算法代码位于第五部分。
项目:心肺复苏按压仪+沁恒赤兔ch32v307+嵌入式开发_第13张图片

图5 HX711初始调试结果显示
项目:心肺复苏按压仪+沁恒赤兔ch32v307+嵌入式开发_第14张图片
图6 惯性滤波算法程序流程图
项目:心肺复苏按压仪+沁恒赤兔ch32v307+嵌入式开发_第15张图片

图7 滑动平均值滤波算法程序流程图
称重的主要函数如下所示
//****************************************************
//读取HX711
//****************************************************
unsigned long HX711_Read(void)  //增益128
{
    unsigned long data=0;
    unsigned char i;

  GPIO_WriteBit(GPIOD, GPIO_Pin_9,SET);//HX711_DOUT=1;
  GPIO_WriteBit(GPIOD, GPIO_Pin_10,RESET);

   Delay_ms(1);

  while(GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_9)== SET);
    for(i=0;i<24;i++)
    {
        GPIO_WriteBit(GPIOD, GPIO_Pin_10,SET);

        Delay_ms(1);
            data=data<<1;
            GPIO_WriteBit(GPIOD, GPIO_Pin_10,RESET);

          if(GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_9)== SET)
               data++;

    }

     GPIO_WriteBit(GPIOD, GPIO_Pin_10,SET);
     data=data^0x800000;//第25个脉冲下降沿来时,转换数据

     Delay_ms(1);
     GPIO_WriteBit(GPIOD, GPIO_Pin_10,RESET);



    return (data);
}

//****************************************************
//称重
//****************************************************
unsigned long Get_Weight()
{
    Weight_Shiwu = HX711_Read();
    Weight_Shiwu = Weight_Shiwu - Weight_Maopi;     //获取净重
    if(Weight_Shiwu > 0)
    {
        Weight_Shiwu = (unsigned int)((float)Weight_Shiwu/GapValue);    //计算实物的实际重量


        if(Weight_Shiwu > 5000)     //超重报警
        {
            Flag_ERROR = 1;
        }
        else
        {
            Flag_ERROR = 0;
        }
    }
    else
    {
        Weight_Shiwu = 0;
        Flag_ERROR = 1;             //负重报警
    }
    return Weight_Shiwu;
}

//****************************************************
//获取毛皮重量
//****************************************************
void Get_Maopi()
{
    Weight_Maopi = HX711_Read();
}


float guanxing_filter(void)//惯性滤波算法
{
    unsigned long new_value;
    //extern Weight_Shiwu;
    new_value=Get_Weight();
    return (a*new_value+(1-a)*new_value);
}

unsigned long huadong_filter(float weight)//平均值滤波算法
{
    float sum=0;
    int j=0;
    value_butf[ii++]=weight;
    if(HH==ii) ii=0;
    for(j=0;j<HH;j++)
    {
        sum+=value_butf[j];
    }
    return (unsigned long)(sum/((float)HH));
}

3.2 蓝牙串口传输效果
在将蓝牙驱动程序下载烧录进开发板后,我们将生命体征监测模块发出的一段数据包用串口调试助手发送至开发板蓝牙的串口。数据包内容由76个2位16进制数组成:
FF 8E 8B 8A 8D 92 9B A5 AD B3 AF A7 9E 9B A2 B8 DB 01 25 3A 3D 2F 13 F7 DE CE C8 C6 C5 C1 BB B4 AF AC AD B0 B3 B2 B0 AC A9 A8 A8 A9 AA AA A7 A3 9F 9C 99 96 92 90 92 9C B3 D4 FD 22 3B 3F 31 14 F6 4A 63 4C C0 12 00 5A 39 1F FE 01
数据包各段含义如下图8所示:
项目:心肺复苏按压仪+沁恒赤兔ch32v307+嵌入式开发_第16张图片

图 8数据包各段含义说明

我们用蓝牙调试APP接收数据,接收效果如下图9所示:
项目:心肺复苏按压仪+沁恒赤兔ch32v307+嵌入式开发_第17张图片

图 9 蓝牙调试APP接收数据效果
3.3 LCD显示屏显示效果
如下图10所示,屏幕上显示的是心率、血氧饱和度、收缩压、舒张压和HX711模块感应到的压力值等数据,每0.5s刷新一次。
项目:心肺复苏按压仪+沁恒赤兔ch32v307+嵌入式开发_第18张图片

图 10 LCD显示屏显示效果
3.4心电信息APP显示:
在官方提供的蓝牙APP基础上进行改造,使APP能够实时显示心电传感器测得的信息,包括心率、血氧、舒张压等,并能将接收的数据绘制成脉搏波。
接收到的数据为每1.28秒76字节的数据包,其中2-65字节表示脉搏波,66表示心率,67表示血氧,68表示微循环,72表示收缩压,73表示舒张压。
实现了对传输过来数据包的解析和显示,初始界面显示心率血氧等信息,点击脉搏波按钮后,将动态绘制脉搏波的波形图,效果如下。
项目:心肺复苏按压仪+沁恒赤兔ch32v307+嵌入式开发_第19张图片
项目:心肺复苏按压仪+沁恒赤兔ch32v307+嵌入式开发_第20张图片

第四部分总结
4.1 主要创新点
徒手心肺复苏急救存在操作难度大且持续的操作对医护人员的体力提出巨大挑战。本作品采用沁恒平台嵌入式系统开发智能心肺复苏按压仪,它能够替代徒手心肺复苏。增加二维码扫码上传信息,可以帮助主治医生获取患者被救助信息,实现智慧医疗。
4.2 可拓展之处
(1)可以将步进电机替换为更加精准的医疗电机;
(2)可以将心电采集模块替换为更加精准的采集设备;
4.3 心得体会
比赛让我们了解了沁恒开发板的芯片硬件架构,训练了我们硬件开发的思维,开拓了我们硬件的创意思维。

物品 价格
电子元器件–继电器 14.58
赤兔开发板 183
多用电表 12.75
杜邦线 12.18
电子秤传感器 10.89
HX711模块 3.69
健康传感器 105
合计 342.09


第五部分主要代码

#include "ch32v30x_tim.h"
#include "ch32v30x_rcc.h"
#include "debug.h"
#include "lcd.h"
#include 
/* Global typedef */

/* Global define */
#define a 0.4
#define HH 3
/* Global Variable */
int ii=0;
float value_butf[HH]={0};
//HX711驱动程序

unsigned long Weight_Maopi = 0;
long Weight_Shiwu = 0;
FlagStatus Flag_ERROR = 0;

//校准参数
//因为不同的传感器特性曲线不是很一致,因此,每一个传感器需要矫正这里这个参数才能使测量值很准确。
//当发现测试出来的重量偏大时,增加该数值。
//如果测试出来的重量偏小时,减小改数值。
//该值可以为小数
#define GapValue 430

//****************************************************
//延时函数
//****************************************************

void Delay_ms(uint8_t time)
{
  uint8_t i;
    while(time--){
    for(i=0;i<113;i++);
    }

}



/*******************************************************************************
* CH9141 BLE 串口透传例程
* 赤菟开发板上 UART7 CH9141 串口透传模块
* 本例程演示使用 DMA 通过 UART7 与 CH9141 通信
*
* 也可以用手机或电脑连接 CH9141 进行通信。
*
* 用手机端连接时,需要通过蓝牙调试软件与CH9141通信
* 注意 CH9141 透传服务的 UUID 为 0000fff0,其中 CH9141的 TX 为 0000fff1,RX 为 0000fff2
* 配置不正确可以连接但不能通信
*
* 例程中 uartWriteBLE(), uartWriteBLEstr() 是非阻塞的。
* 调用这些函数发送时,若上一次发送尚未完成,将不等待而直接返回
*
* 安卓平台调试 APP
* BLEAssist 沁恒官方的 BLE 调试 APP,配置比较详细,适合复杂调试 http://www.wch.cn/downloads/BLEAssist_ZIP.html
* 蓝牙调试器 XLazyDog 开发,适合简单调试、遥控调试 https://blog.csdn.net/XLazyDog/article/details/99584735

*******************************************************************************/
#include "debug.h"

/*******************************************************************************
* Function Name  : main
* Description    : Main program.
* Input          : None
* Return         : None
*******************************************************************************/
int main(void)
{
    unsigned long weight_final;
    float weight1;
    int i=0;//定义adc采集的数据


    int heart,SPO2,SBP,DBP;//心电有关数据
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    Delay_Init();
    USART_Printf_Init(38400);


    DMA_INIT();
    UART2_CFG(); /* USART INIT */
//
    USART_DMACmd(USART2,USART_DMAReq_Tx|USART_DMAReq_Rx,ENABLE);  //开启 USART2 DMA 接收和发送
    USART_DMACmd(UART7,USART_DMAReq_Tx|USART_DMAReq_Rx,ENABLE);
    unsigned char buff=0X8A;               //开机延时发送的启动命令
    USART_SendData(USART2, buff);
    //int period = 500; // 串口回传间隔, 单位为毫秒
//
    GPIO_CFG();
    GPIO_WriteBit(GPIOA, GPIO_Pin_7,RESET); //进入 AT
    GPIO_WriteBit(GPIOC, GPIO_Pin_13,SET); //enable CH9141
    Delay_Ms(1000);

    GPIO_Toggle_INIT();

    lcd_init();
    lcd_set_color(WHITE, BLACK);
    UART3_CFG(); /* USART INIT */
    Delay_Ms(3000);      //延时,等待传感器稳定
    Get_Maopi();                //称毛皮重量

    while(1){

            if(weight_final>100)
            {
                GPIO_WriteBit(GPIOA, GPIO_Pin_1, (i == 0) ? (i = Bit_SET) : (i = Bit_RESET));
            }
            Delay_Ms(10);
               Delay_Ms(10);
               Delay_Ms(10);
               weight1=guanxing_filter();
               weight_final=huadong_filter(weight1);

               lcd_clear(WHITE);//Background Color
               FORE_COLOR=BLACK;//Character Color
               lcd_show_string(70, 160, 24, "Weight:");
               lcd_show_num(160,160, weight_final, 4, 24);
               lcd_show_string(210,160, 24, "g");


               lcd_show_string(70, 40, 24, "heart:");
              lcd_show_num(140,40,heart,9,24);
              lcd_show_string(70, 70, 24, "SPO2:");
              lcd_show_num(130,70,SPO2,9,24);
              lcd_show_string(70, 100, 24, "SBP:");
              lcd_show_num(125,100,SBP,9,24);
              lcd_show_string(70, 130, 24, "DBP:");
              lcd_show_num(125,130,DBP,9,24);
              Delay_Ms(500);

        unsigned char H[3],SP[3],SB[3],D[3];
        while(!uartAvailable());    //等待RX数据
                    //处理(把接收到的数据发送出去)
            //      char c = uartReadByte();
            //      uartWriteBlocking(&c,1);
        char buffer[76];
        Delay_Ms(50);           //等一会儿,攒点数据;如果数据量大,接收缓冲会溢出
        int num1 = uartAvailable();  //获取可读字节数
        if (num1 > 0 ){
               uartRead(buffer , num1); //把数据从缓冲区读到 buffer 里
               heart=buffer[65];
               SPO2=buffer[66];
               SBP=buffer[71];
               DBP=buffer[72];

               uartWriteBLE(buffer , 76);

               }
        Int2String(heart, H);
        Int2String(SPO2, SP);
        Int2String(SBP, SB);
        Int2String(DBP, D);
        Delay_Ms(100);

        GPIO_WriteBit(GPIOA, GPIO_Pin_7,SET); // 退出AT。可用手机或电脑连接CH9141,测试数据收发


    }

你可能感兴趣的:(嵌入式硬件,经验分享)