设计一个基于物联网技术的智慧病房管理系统。假设医院住院部的一层病房(走廊两边病房平行分布),病房数量最多60间,每间病房3个床位,编号从1~180号。每间病房可采用的设备如下:STM32F103开发板1块,房间温湿度采集模块1套(I2C接口,AHT20模块),房间自动灯光开关控制器(以PWM方式控制,每天早上7点渐亮,晚上22点渐灭),病人脉搏&血氧检测仪3套(UART接口输出脉搏+血氧的数字值),床头紧急呼叫按键开关3个(按下呼叫)。
每间病房的STM32F103开发板通过UART转485接口,以mobus组网方式,连接到护士监控室的PC电脑上(上位机)。PC电脑上可接收每间病房的温湿度数据(周期为5分钟)、床头紧急呼叫信号、病人脉搏血氧数据(正常状态下30分钟一次采集;当脉搏超过120或血氧值低于90时切换到危重状态下,实时采集),显示在屏幕上并且保存到MySQL数据库里。
这里 RTOS 代码修改,AHT20 驱动代码移植可以参考博客内容:
读取ATH20(DTH20)温度传感器
RT-Thread Nano移植
下面主要介绍使用和按键相关内容
首先我们需要引入温湿度驱动头文件:
#include "AHT20.h"
然后我们需要在进程中对温湿度传感器进行初始化,这里在初始化之后,不能立即获取温湿度,需要等待 2 秒或以上,确保数据正确性:
MX_GPIO_Init();
MX_I2C1_Init();
MX_USART1_UART_Init();
uint32_t CT_data[2]={0,0}; // 用于获取温湿度数据
volatile int c1,t1;
rt_thread_delay(50);
AHT20_Init();
rt_thread_delay(2500);
然后我们需要获取经过 CRC 验证的温湿度数据,这里直接调用驱动函数即可:
while(1)
{
AHT20_Read_CTdata_crc(CT_data); //经过CRC校验,读取AHT20的温度和湿度数据 推荐每隔大于1S读一次
c1 = CT_data[0]*1000/1024/1024; //计算得到湿度值c1(放大了10倍)
t1 = CT_data[1]*2000/1024/1024-500;//计算得到温度值t1(放大了10倍)
printf("正在检测");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
printf("\r\n");
printf("温度:%d%d.%d",t1/100,(t1/10)%10,t1%10); // 这里需要对温度进行计算后才能得到我们需要的温度值
printf("湿度:%d%d.%d",c1/100,(c1/10)%10,c1%10); // 这里同样需要对适度进行计算
printf("\r\n");
printf("等待");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
printf("\r\n");
}
首先新建 app_rt_thread.c 文件,然后引入头文件:
#include "rtthread.h"
#include "main.h"
#include "i2c.h"
#include "usart.h"
#include "gpio.h"
#include "stdio.h"
#include "AHT20.h"
然后我们需要设计进程,这里主进程先设置为串口发送,子进程设置两个,一个是获取温度参数,另一个是控制 PC13 引脚上的板载 LED ,后续会根据系统进程对进行设计:
//初始化线程函数
void MX_RT_Thread_Init(void)
{
//初始化LED1线程
rt_thread_init(&led1_thread,"led1",led1_task_entry,RT_NULL,&rt_led1_thread_stack[0],sizeof(rt_led1_thread_stack),3,20);
//开启线程调度
rt_thread_startup(&led1_thread);
//初始化USART1线程
rt_thread_init(&usart1_thread,"usart1",usart1_task_entry,RT_NULL,&rt_usart1_thread_stack[0],sizeof(rt_usart1_thread_stack),3,20);
//开启线程调度
rt_thread_startup(&usart1_thread);
}
//主任务
void MX_RT_Thread_Process(void)
{
printf("Hello RT_Thread!!!\r\n");
rt_thread_delay(2000);
}
//LED1任务
void led1_task_entry(void *parameter)
{
while(1)
{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13, GPIO_PIN_RESET);
rt_thread_delay(500);
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13, GPIO_PIN_SET);
rt_thread_delay(500);
}
}
//读取温度任务
void usart1_task_entry(void *parameter)
{
MX_GPIO_Init();
MX_I2C1_Init();
MX_USART1_UART_Init();
uint32_t CT_data[2]={0,0}; //
volatile int c1,t1;
rt_thread_delay(50);
AHT20_Init();
rt_thread_delay(2500);
while(1)
{
AHT20_Read_CTdata_crc(CT_data); //经过CRC校验,读取AHT20的温度和湿度数据 推荐每隔大于1S读一次
c1 = CT_data[0]*1000/1024/1024; //计算得到湿度值c1(放大了10倍)
t1 = CT_data[1]*2000/1024/1024-500;//计算得到温度值t1(放大了10倍)
printf("正在检测");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
printf("\r\n");
printf("温度:%d%d.%d",t1/100,(t1/10)%10,t1%10); // 这里需要对温度进行计算后才能得到我们需要的温度值
printf("湿度:%d%d.%d",c1/100,(c1/10)%10,c1%10); // 这里同样需要对适度进行计算
printf("\r\n");
printf("等待");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
printf("\r\n");
}
}
点击查看完整 app_rt_thread.c 代码
然后我们在主函数中引入 RT-Thread 必要的头文件并引用函数:
#include "rtthread.h"
extern void MX_RT_Thread_Init(void);
extern void MX_RT_Thread_Process(void);
最后我们直接在主函数中对进程进行初始化,并运行即可,这里直接给出完整主函数:
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_I2C1_Init();
MX_TIM3_Init();
MX_USART1_UART_Init();
MX_TIM2_Init();
/* USER CODE BEGIN 2 */
MX_RT_Thread_Init(); // 初始化线程
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
MX_RT_Thread_Process(); // 执行主进程
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
首先我们设计按钮在 PA3 引脚,然后在设置 PA4 引脚上外接一个 LED ,这里先令按钮按下反转 PA4 引脚电平,即按下小灯泡点亮,再次按下,小灯泡熄灭,这里直接设计为一个新进程:
struct rt_thread btnclick_thread;
rt_uint8_t rt_btnclick_thread_stack[128];
void btnclick_task_entry(void *parameter);
void btnclick_task_entry(void *parameter){
while(1){
switch(KEY_Scan(0))
{
case KEY1_PRES:
HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_4);
break;
default:
break;
}
}
}
初始化线程:
//初始化线程
rt_thread_init(&btnclick_thread,"btnclick",btnclick_task_entry,RT_NULL,&rt_btnclick_thread_stack[0],sizeof(rt_btnclick_thread_stack),3,20);
//开启线程调度
rt_thread_startup(&btnclick_thread);
但是我们不进行按键消抖,这里按键非常容易误触,所以这里进行按键消抖:
#define KEY1 HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_3) //读取按键1
#define KEY1_PRES 1 //KEY1按下
uint8_t KEY_Scan(uint8_t mode)
{
static uint8_t key_up=1;//按键松开标志位
if(key_up&&(KEY1==0))
{
HAL_Delay(10);//去抖动
key_up=0;
if(KEY1==0)return KEY1_PRES;
}
else if(KEY1==1)key_up=1;
return 0;//无按键按下
}
这里我们主要实现能够定时开关灯,并且能够获取如下信息:
病人按下紧急呼叫信号
定时获取病人脉搏血氧数据
这里要求我们能够实现在早上 7 点开灯,晚上 22 点灭灯,所以我们就要设计 RTC 系统时间与日历读取,确定我们当前的时间与日期,然后判断时间数,最终通过 PWM 实现灯的渐亮与渐灭
这里首先我们需要配置 RTC:
这里先设置两个变量用于获取时间和日期:
RTC_DateTypeDef Date;
RTC_TimeTypeDef Time;
然后我们可以通过时间进行判断从而实现开关灯的效果:
void ledauto_task_entry(void *parameter){
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_2);
while(1){
HAL_RTC_GetTime(&hrtc,&Time,RTC_FORMAT_BIN);
if(Time.Hours == 7&&Time.Minutes == 0&&Time.Seconds == 0){
printf("开灯!");
for(uint16_t i=1;i<500;i++){
htim2.Instance->CCR2 = i;
rt_thread_delay(5);
}
}
else if(Time.Hours == 22&&Time.Minutes == 0&&Time.Seconds == 0){
printf("关灯!");
for(uint16_t i=499;i>=1;i--){
htim2.Instance->CCR2 = i;
rt_thread_delay(5);
}
}
}
}
前面在博客中已经介绍了如何通过 RTC 读取日历和时间:
RTC时间读取
这里我们只需要照做就可以,但是,由于开发板掉电,时间会重置,所以这里设计时间掉电保存,如果使用外接 RTC 并有电池,则会在掉电后继续计时,这里我设计将代码直接写道 RTC 初始化的函数中即可,完整初始化代码如下:
void MX_RTC_Init(void)
{
/* USER CODE BEGIN RTC_Init 0 */
/* USER CODE END RTC_Init 0 */
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef DateToUpdate = {0};
/* USER CODE BEGIN RTC_Init 1 */
__HAL_RCC_BKP_CLK_ENABLE();// 开启后背区域时钟
__HAL_RCC_PWR_CLK_ENABLE();// 开启电源时钟
/* USER CODE END RTC_Init 1 */
/** Initialize RTC Only
*/
hrtc.Instance = RTC;
hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND;
hrtc.Init.OutPut = RTC_OUTPUTSOURCE_NONE;
if (HAL_RTC_Init(&hrtc) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN Check_RTC_BKUP */
if(HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR1)!= 0x5051)
{
/* USER CODE END Check_RTC_BKUP */
/** Initialize RTC and set the Time and Date
*/
sTime.Hours = 0x7;
sTime.Minutes = 0x0;
sTime.Seconds = 0x0;
if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD) != HAL_OK)
{
Error_Handler();
}
DateToUpdate.WeekDay = RTC_WEEKDAY_THURSDAY;
DateToUpdate.Month = RTC_MONTH_JANUARY;
DateToUpdate.Date = 0x12;
DateToUpdate.Year = 0x23;
if (HAL_RTC_SetDate(&hrtc, &DateToUpdate, RTC_FORMAT_BCD) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN RTC_Init 2 */
__HAL_RTC_SECOND_ENABLE_IT(&hrtc,RTC_IT_SEC); //开启RTC时钟秒中断
datebuff = DateToUpdate; //把日期数据拷贝到自己定义的data中
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, 0x5051);//向指定的后备区域寄存器写入数据
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR2, (uint16_t)datebuff.Year);
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR3, (uint16_t)datebuff.Month);
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR4, (uint16_t)datebuff.Date);
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR5, (uint16_t)datebuff.WeekDay);
}
else
{
datebuff.Year = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR2);
datebuff.Month = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR3);
datebuff.Date = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR4);
datebuff.WeekDay = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR5);
DateToUpdate = datebuff;
if (HAL_RTC_SetDate(&hrtc, &DateToUpdate, RTC_FORMAT_BIN) != HAL_OK)
{
Error_Handler();
}
__HAL_RTC_SECOND_ENABLE_IT(&hrtc,RTC_IT_SEC); //开启RTC时钟秒中断
}
getRealTime();
/* USER CODE END RTC_Init 2 */
}
然后我们需要在运行时,实时获取一次时间:
void getRealTime(void)
{
HAL_RTC_GetTime(&hrtc, &time, RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc, &datebuff, RTC_FORMAT_BIN);
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR2, (uint16_t)datebuff.Year);
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR3, (uint16_t)datebuff.Month);
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR4, (uint16_t)datebuff.Date);
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR5, (uint16_t)datebuff.WeekDay);
}
void HAL_RTCEx_RTCEventCallback(RTC_HandleTypeDef *hrtc)
{
if(hrtc->Instance == RTC)
{
getRealTime();
}
}
智慧病房是以院内物联网为基础的辅助诊疗及智能护理一体化解决方案。以智能改造医院病房,通过先进的物联网技术,让患者与医务人员、智能医疗设备紧密联接。通过对临床数据采集、筛选、整合,形成医疗大数据,为临床诊疗、护理提供智能辅助决策。提高医疗资源的利用率,更好地保障医疗安全,更好地以患者为中心,提供有品质的医疗服务。