1.1项目背景及目标
医疗科室呼号显示屏系统的部署,显著提升了医院门诊部的运营效率,有效地解决了患者排队等候时的拥挤和无序问题,从而显著减少了患者的等待时间,优化了整体的就医体验。这一智能化医疗辅助设施的应用,不仅提高了医院的服务质量,而且改善了医院的整体形象,实现了对医疗资源的高效分配与管理。通过该系统,我们成功实现了以下目标: 1,大幅降低患者排队等候时间,全面提升就医环境质量。以往,在患者数量众多的情况下,就诊过程中患者不得不在诊室门口排队等候,这不仅影响了医生的正常工作秩序,还让患者承受着长时间的等待压力。 2,引入科室呼号显示屏系统后,患者可以轻松地在候诊区的屏幕上查看自己的排队信息,从而避免了在诊室外的无效等待。 3,该系统的投入使用,有效遏制了医疗服务中的混乱现象,如插队、拥挤等,为医院创造了一个安静、有序的就医环境,同时也为医护人员提供了一个更加高效、舒适的工作空间。 换言之,科室呼号显示屏系统的引入,不仅极大地提升了医院的服务水平,还进一步增强了患者对医疗服务的满意度,为医院的现代化管理和智慧医疗服务体系建设奠定了坚实的基础。
1.2程序设计流程图
1.3程序效果演示
1.3.1主界面
1.3.2科室选择挂号模块
1.3.3医生信息模块
1.3.4过/叫号模块
1.3.5退出系统模块
1.4核心代码解析
1.4.1主函数
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <stdlib.h>
#include "funtion.h"
#include "font.h"
/*
叫挂界面 callAndHangInterface
界面 interface
科室选择 departmentSelection
退出界面 exit
医生信息 doctorInformation
内科:Internal_Medicine
外科:surgical
急诊:emergency
*/
int main(int argc, char const *argv[])
{
Scan_Main_face();
return 0;
}
1.4.2映射函数
// 获取LCD映射地址
void *mmap_lcd()
{
// 1.打开LCD 设备
int fd = open("/dev/fb0", O_RDWR);
if (fd < 0)
{
perror("打开LCD失败\n");
return NULL;
}
// 2.映射LCD设备
void *p = mmap(NULL, 800 * 480 * 4, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (p == MAP_FAILED)
{
perror("映射失败\n");
return NULL;
}
else
{
printf("映射LCD成功\n");
}
close(fd);
return p;
}
// 展示照片
//函数说明: ch 为要展示照片的路径地址
//返回值: 成功返回 0, 失败返回 -1
int show(char *ch)
{
if (ch == NULL)
{
printf("ch 为 NULL\n");
return -1;
}
// 1.打开bmp图片文件
int fd = open(ch, O_RDWR);
if (fd < 0)
{
perror("打开文件失败\n");
return -1;
}
// 2.读取文件头
BITMAPFILEHEADER head;
read(fd, &head, 14);
// 3.读取文件信息头
BITMAPINFOHEADER info;
read(fd, &info, 40);
// 定义像素缓存区 800 480 24 / 8
char rgb[info.biWidth * info.biHeight * info.biBitCount / 8];
// 4.读取图像的像素数据
read(fd, rgb, sizeof(rgb));
// 5.内存映射
int(*lcd)[800] = mmap_lcd();
// 把char 类型的rgb 转换为 int 类型的color
int color[info.biHeight][info.biWidth];
// 指向 rgb 数据
char *p = rgb;
for (int y = 0; y < info.biHeight; y++)
{
for (int x = 0; x < info.biWidth; x++)
{
char b = *p++;
char g = *p++;
char r = *p++;
color[y][x] = b | g << 8 | r << 16 | 0 << 24;
}
}
// 6.把rgb 数据放入 lcd 映射地址
for (int y = 479; y >= 0; y--)
{
for (int x = 0; x < 800; x++)
{
lcd[479 - y][x] = color[y][x];
}
}
//关闭映射
if (munmap(lcd, 800 * 480 * 4) == -1) {
perror("munmap 失败");
exit(EXIT_FAILURE);
}
else
{
printf("关闭映射\n");
}
// 关闭打开后的文件
close(fd);
return 0;
}
//展示照片(任意位置)
//函数参数说明: offset_x 和 offset_y 为屏幕上的位置
int show_anywhere(char *ch, int offset_x, int offset_y)
{
if (ch == NULL)
{
printf("ch 为 NULL\n");
return -1;
}
// 1.打开bmp图片文件
int fd = open(ch, O_RDWR);
if (fd < 0)
{
perror("打开文件失败\n");
return -1;
}
// 2.读取文件头
BITMAPFILEHEADER head;
read(fd, &head, 14);
// 3.读取文件信息头
BITMAPINFOHEADER info;
read(fd, &info, 40);
// 定义像素缓存区 800 480 24 / 8
char rgb[info.biWidth * info.biHeight * info.biBitCount / 8];
// 4.读取图像的像素数据
read(fd, rgb, sizeof(rgb));
// 5.内存映射
int(*lcd)[800] = mmap_lcd();
// 把char 类型的rgb 转换为 int 类型的color
int color[info.biHeight][info.biWidth];
// 指向 rgb 数据
char *p = rgb;
for (int y = 0; y < info.biHeight; y++)
{
for (int x = 0; x < info.biWidth; x++)
{
char b = *p++;
char g = *p++;
char r = *p++;
color[y][x] = b | g << 8 | r << 16 | 0 << 24;
}
}
// 6.把rgb 数据放入 lcd 映射地址
for (int y = offset_y; y < info.biHeight + offset_y; y++)
{
for (int x = offset_x; x < info.biWidth + offset_x; x++)
{
lcd[y][x] = color[info.biHeight - y + offset_y][x - offset_x];
}
}
//关闭映射
if (munmap(lcd, 800 * 480 * 4) == -1) {
perror("munmap 失败");
exit(EXIT_FAILURE);
}
else
{
printf("关闭映射\n");
}
// 关闭打开后的文件
close(fd);
return 0;
}
1.4.3获取触摸屏位置数据函数
//获取触摸屏的xy并判断按下松开状态
//函数返回值:成功为 0 失败为 -1
int get_event_data(int *x, int *y)
{
//打开触摸屏文件
int fd = open("/dev/input/event0", O_RDWR);
if (fd < 0)
{
perror("open fd error");
return -1;
}
//读取触摸屏中的数据
while (1)
{
struct input_event ev;
read(fd, &ev, sizeof(ev));
//获取x的值
if (ev.type == EV_ABS && ev.code == ABS_X)
{
//进行等比例校准
*x = ev.value * 800 / 1024;
printf("x = %d ", *x);
}
//获取y的值
if (ev.type == EV_ABS && ev.code == ABS_Y)
{
*y = ev.value * 480 / 600;
printf("y = %d\n", *y);
}
//检测触摸屏的按下和松开(把触摸屏看作一个按钮)
if (ev.type == EV_KEY)
{
//判断按下
if (ev.value == 1)
{
printf("触摸屏按下 %d\n", ev.value);
}
if (ev.value == 0)
{
printf("触摸屏松开 %d\n", ev.value);
break;
}
}
}
close(fd);
return 0;
}
1.4.4显示文字模块
//显示文字模块
/*函数说明:
test : 要输出的内容
test_size: 字体大小
width: 输出框的宽度
height: 输出框的高度
x,y : 在输出框的起始位置
px, py: 在LCD上的起始位置
color: 颜色数据
maxWidth: 最大宽度, 用于换行
*/
void display_Text(char *text, int test_size, unsigned int width, unsigned int height, int x, int y, int px, int py, unsigned int color, int maxWidth)
{
// 1.映射LCD 设备
int(*lcd)[800] = NULL;
lcd = mmap_lcd();
// 2.加载字库
font *f = fontLoad("/usr/share/fonts/DroidSansFallback.ttf");
// 3.设置字体的大小
fontSetSize(f, test_size);
// 4.创建一块输出框
bitmap *bm = createBitmap(width, height, 4);
// getColor(A, B, G, R) 设置颜色
// 5.把汉字放入输出框
fontPrint(f, bm, x, y, text, color, maxWidth);
show_font_to_lcd((void *)lcd, px, py, bm);
//关闭字库
fontUnload(f);
//关闭映射
if (munmap(lcd, 800 * 480 * 4) == -1) {
perror("munmap 失败");
exit(EXIT_FAILURE);
}
else
{
printf("关闭映射\n");
}
}
1.4.5各个功能实现模块
//科室选择
void department_Selection()
{
//获取x y 的值
int x, y;
//获取内科医生信息
node *head_Internal = new_department_Internal_Doctors_list();
node *temp_Internal = head_Internal->next;
//获取外科医生信息
node *head_surgical = new_department_surgical_Doctors_list();
node *temp_surgical = head_surgical->next;
//获取急诊医生信息
node *head_emergency = new_department_emergency_Doctors_list();
node *temp_emergency = head_emergency->next;
while (1)
{
show("/picture/departmentSelection.bmp");
get_event_data(&x, &y);
//内科按钮
if (x >= 220 && x <= 560 && y >= 100 && y <= 170)
{
printf("内科响应了\n");
show("/picture/funtion_1_sourse.bmp");
department_Selection_Model(head_Internal, temp_Internal);
continue;
}
//外科按钮
if (x >= 220 && x <= 560 && y >= 215 && y <= 275)
{
printf("外科响应了\n");
show("/picture/funtion_1_sourse.bmp");
department_Selection_Model(head_surgical, temp_surgical);
continue;
}
//急诊按钮
if (x >= 220 && x <= 560 && y >= 320 && y <= 400)
{
printf("急诊响应了\n");
show("/picture/funtion_1_sourse.bmp");
department_Selection_Model(head_emergency, temp_emergency);
continue;
}
//返回首页按钮
if (x >= 55 && x <= 190 && y >= 370 && y <= 450)
{
printf("返回首页响应了\n");
break;
}
}
}
//医生信息模块
void doctor_Information_Module(node *head, node *temp)
{
int x, y;
while (1)
{
//打印医生信息
show(temp->data);
display_Text_profile(temp->profile);
display_Text_professionalExcellence(temp->professionalExcellence);
//获取坐标值
get_event_data(&x, &y);
//检测 “上一张” 按钮
if (x >= 35 && x <= 160 && y >= 380 && y <= 425)
{
if (temp->prev == head)
{
printf("已经到顶啦\n");
display_Test_prompt(" 已经到顶啦");
if (check_Ok_Button(x, y))
{
temp = head->next; // 始终指向第一个节点
continue;
}
}
else
{
temp = temp->prev;
}
printf("上一张响应\n");
}
//检测 “返回主页” 按钮
if (x >= 300 && x <= 470 && y >= 380 && y <= 425)
{
printf("返回主页响应\n");
break;
}
//检测 “下一张” 按钮
if (x >= 600 && x <= 750 && y >= 380 && y <= 425)
{
if (temp->next == head)
{
printf("已经到底啦\n");
display_Test_prompt(" 已经到底啦");
if (check_Ok_Button(x, y))
{
continue;
}
}
else
{
temp = temp->next;
}
printf("下一张响应\n");
}
}
}
//呼/挂号模块
void call_And_Hang_Interface(node *head_patients, node **temp_patients, queue *m, char **ch_ok, char **ch_pass)
{
//监控页面控件
while (1)
{
printf("这是第三个按钮\n");
show("/picture/callAndHangInterface.bmp"); //展示框架
// show_anywhere("/picture/surgical_01.bmp", 53, 63); //头像
// display_Name("华佗", 120, 267); //展示名字
// display_Doctor_Department("外科", 120, 297); //展示科室
//展示等待人员信息
show_Information_Waiting_People(m);
//展示正在就诊和过号人员
display_Name_chang_size(*ch_ok, 35, 330, 128);
display_Name_chang_size((*temp_patients)->name, 35, 330, 295);
//监控页面按钮
int key_num = check_call_And_Hang_Interface_button();
//返回首页 按钮响应
if (key_num == 1)
{
printf("返回首页响应\n");
break;
}
//过 按钮响应
if (key_num == 2)
{
printf(" 过 响应\n");
*ch_pass = pop(m);
if (*ch_pass == NULL)
{
// *ch_pass = " ";
// display_Name_chang_size(*ch_pass, 35, 330, 295);
continue;
}
else
{
//尾插到链表中
*temp_patients = new_Node(NULL, NULL, NULL, *ch_pass, NULL);
insert_end(head_patients, *temp_patients);
// //过号患者
// display_Name_chang_size(temp_patients->name, 35, 330, 295);
}
}
//勾 按钮响应
if (key_num == 3)
{
printf(" 勾 响应\n");
*ch_ok = pop(m);
if (*ch_ok == NULL)
{
*ch_ok = " ";
display_Name_chang_size(*ch_ok, 35, 330, 128);
}
else
{
//正在就诊
display_Name_chang_size(*ch_ok, 35, 330, 128);
}
}
//上一位 按钮响应
if (key_num == 4)
{
printf(" 上一位 响应\n");
//到顶了
if ((*temp_patients)->prev == head_patients)
{
printf("已经到顶啦\n");
display_Test_prompt(" 已经到顶啦");
if (check_Ok_Button())
{
(*temp_patients) = head_patients->next; // 始终指向第一个节点
continue;
}
}
//展示上一位过号患者
(*temp_patients) = (*temp_patients)->prev;
}
//下一位 按钮响应
if (key_num == 5)
{
printf(" 下一位 响应\n");
//到顶了
if ((*temp_patients)->next == head_patients)
{
printf("已经到底啦\n");
display_Test_prompt(" 已经到底啦");
if (check_Ok_Button())
{
continue;
}
}
//展示上一位过号患者
(*temp_patients) = (*temp_patients)->next;
}
}
}
/*
退出系统模块
确认退出 返回 1
取消退出 返回 2
*/
int exit_funtion()
{
printf("这是第四个按钮\n");
display_Test_prompt_two_button("确定退出吗");
int key_num = check_Ok_Button_two_button();
//确认退出
if (key_num == 1)
{
show("/picture/exit.bmp");
return 1;
}
//取消退出
if (key_num == 2)
{
return 2;
}
}
1.5难点总结
- 内存映射是一个重大难点,要通过open函数打开LCD设备,调用mmap函数映射LCD设备,再通过强制类型转换把映射地址强制转换为 int (*)[800]
- 图像处理也是一大重大难点,本次项目我主要处理的是bmp图像,并且bmp图片 = 文件头(14)个字节 + 位图信息头(40)个字节 + 像素数据,不过在网上或者GPT都可以获得图像处理的接口,最后把图像显示在LCD屏就大功告成了
- 触摸屏的处理同样也是一大难点,通过open函数打开触摸屏文件,通过read函数读取触摸屏中的数据,其中要注意读取到的数据要与LCD屏幕进行比例匹配
- 最后的就是医生信息的存储、文字库的运用以及各种的图片素材的存储,这些都是文件IO的基本功,难点还是可以接受的