一.实验要求
设计一个基于物联网技术的智慧病房管理系统。假设医院住院部的一层病房(走廊两边病房平行分布),病房数量最多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 数据库里。
设计要求:
1、写出系统设计方案,画出系统功能图
2、画出病房stm32f103控制模块的电路原理图。
3、设计各功能模块的模拟实现软件,给出主要源代码
4、总结本系统方案的技术优势和代码特点
建议(加分项):
采用 RTOS(ucos、RTthread-nano等)做多任务软件框架。
使用 cubemx 完成基本端口参数选择和设计。
采用 stm32f103 内部 flash 记录数据,或者其他设计或实现的亮点。
日期读取功能,利用 stm32 内部日历芯片。
上位机功能只需要设计,不须实现。串口,modbus 数据接收可用串口助手等工具软件。但如果有能力设计上位机代码,也有奖励分。
二.
1.系统设计方案
RTOS 系统移植
温湿度模块数据获取
通过定时器定时启动呼吸灯
接收按键信号并进行消抖
血氧与心率检测
modbus 发送数据信息给上位机
使用 I2C 读取 AHT20 模块
TIM 定时器与 PWM 呼吸灯
按键信息接收与消抖
UART 转 485 接口与 modbus 通信
MAX30102 模块心率与血氧测量
2.系统配置
RCC 配置
SYS 配置
USART1 与 DMA 配置
I2C 配置
TIM3 配置
DMA 配置
TIM2 与 PWM 配置
NVIC 配置
GPIO 配置
时钟配置
引脚配置
RTOS 配置
引入 RT-Thread
外部按钮配置
温湿度获取
引入头文件
#include “AHT20.h”
温湿度传感器进行初始化
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”);
}
RTOS 进程设计
引入头文件
#include “rtthread.h”
#include “main.h”
#include “i2c.h”
#include “usart.h”
#include “gpio.h”
#include “stdio.h”
#include “AHT20.h”
设计进程
//初始化线程函数
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");
}
}
主函数
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 */
}
按钮操作
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&&(KEY10))
{
HAL_Delay(10);//去抖动
key_up=0;
if(KEY10)return KEY1_PRES;
}
else if(KEY1==1)key_up=1;
return 0;//无按键按下
}
定时开关灯
配置 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 初始化
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();
}
}
三.总结
本次实验运用了本学期本课程所学到的很多重要内容例如RTC的使用,串口通信等。通过本次大作业,也算是对前面所学内容的总结与加深。使我对知识的了解更加深化,并能灵活加以运用