附:代码的完整实现
【问题描述】
随着安全意识的增强,小区安防系统也慢慢的走入我们寻常百姓家。监控方案也是层出不穷,结合当前热门的物联网通信技术,结合RFID的智能安防系统逐渐成为主流。
本项目以多媒体技术及网络技术为基础,结合主流的嵌入式Cortex-A系列高端处理平台,结合RFID技术,网络视频监测等实现智能安防监测,比传统的智能安防监测方式效率更高,成本与功耗更低。
【基本要求】
该项目应具有以下功能:
1)根据RFID实现用户注册功能,记录一些用户的基本信息,如姓名、单元号、房间号等。
2)门禁功能:用户进入和离开小区需要刷卡,自动打开门禁。
3)视频监控功能: 利用USB摄像头,拍摄小区门口的实时图像,传输到开发板屏幕显示。
有重要创新价值。
【硬件方案】
1)GEC6818开发板
2)低频RFID模块
3)USB摄像头——V4L2
【软件方案】
VMware虚拟机 + Ubuntu
【关键技术】
1)Linux操作系统的使用
2)低频RFID的应用开发
3)文件IO
4)ARM开发板UI设计
5)ARM开发板触摸屏应用开发
6)v4l2摄像头驱动
【附加分项】
1)增加门禁卡遗失注销功能
2)利用TCP/IP网络协议,实现远程视频监控
总的来说是要写服务器代码和客户端代码,此次课程设计预留的时间就4天 ,我的时间只能相对做好,所以一切功能从简。
首先我们先看1)要求,实现用户注册功能,记录一些用户的基本信息,如姓名、单元号、房间号等。我可以用数组,链表,甚至数据库等技术来解决,我最先想到是用链表解决。
先创建一个结构体Users
struct Users
{
int number; // 编号
int class; // 房间号
unsigned int card_id; // 卡号
char name[10]; // 住户姓名
};
接着创建链表
struct Users userDatabase[100]; // 用户链表,最多存储100个用户
编写函数registerUser
// 登记用户
void registerUser(unsigned int cardId)
{
struct Users newUser;
// 填写用户信息
newUser.card_id = cardId;
printf("请输入户主编号: ");
scanf("%d", &newUser.number);
printf("请输入户主房间号: ");
scanf("%d", &newUser.class);
printf("请输入户主姓名: ");
scanf("%s", newUser.name);
// 将新用户添加到用户链表中
userDatabase[userCount] = newUser;
userCount++;
printf("户主信息注册成功!\n");
}
注销逻辑也就是将链表中的成员赋0
// 注销用户
void revokeUser(unsigned int cardId)
{
for (int i = 0; i < userCount; i++)
{
if (userDatabase[i].card_id == cardId)
{
userDatabase[i].card_id = 0;
userDatabase[i].number = 0;
userDatabase[i].class = 0;
memset(userDatabase[i].name, 0, sizeof(userDatabase[i].name));
printf("住户卡已注销!\n");
return;
}
}
printf("未找到该住户卡号,无法注销!\n");
}
因为是在链表中建立的户主数据库,所以就需要一个检查是否有注册的标志,未注册置0
int isRegistered = 0
完成注册++,已注册即直接进入读出13位16进制数,也就是低频卡的检测。因为低频卡读出是16进制的数,我们要读懂则需进行一个进制转化
int binary(int num)
{
int i = 0, j;
while (num)
{
if (num >= 16)
{
j = num / 16;
i += j;
}
else
{
i += num;
break;
}
num = num % 16;
i = i * 10;
}
return i;
}
if (r == 13)
{
if (buf[0] != 0x02 || buf[12] != 0x03)
{
printf("rfid read error\n");
}
else
{
printf("rfid read ok\n");
for (i = 5; i < 11; i++)
{
if ((buf[i] - '0') >= 10)
{
dat[i - 5] = binary(buf[i] - '0' - 1);
}
else
dat[i - 5] = buf[i] - '0';
}
for (i = 0; i < 13; i++)
{
if ((buf[i] - '0') >= 10)
{
buf[i] = buf[i] - 1;
}
}
cardId = dat[0]*16*16*16*16*16 + dat[1]*16*16*16*16 + dat[2]*16*16*16 + dat[3]*16*16 + dat[4]*16 + dat[5];
printf("id = %ld\n", cardId);
h = dat[0]*16 + dat[1];
l = dat[2]*16*16*16 + dat[3]*16*16 + dat[4]*16 + dat[5];
// 检查用户是否已注册的标志
int isRegistered = 0;
for (int i = 0; i < userCount; i++)
{
if (userDatabase[i].card_id == cardId)
{
isRegistered = 1;
printf("户主已经注册户主卡!\n");
currentImage = (currentImage + 1) % 3; // 加载多张图像索引
LCD_DrawBMP(640, 0, imageNames[currentImage]); // 加载下一张BMP图片
LCD_DrawRect(700, 420, 100, 60, 0x00FF00);
usleep(500000);
break;
}
}
if (!isRegistered)
{
printf("这张住户卡未注册,是否注册户主卡?(y/n): ");
char choice;
scanf(" %c", &choice);
if (choice == 'y' || choice == 'n')
{
registerUser(cardId);
}
else
{
continue;
}
}
}
}
if (!isRegistered)
{
printf("这张住户卡未注册,是否注册户主卡?(y/n): ");
char choice;
scanf(" %c", &choice);
if (choice == 'y' || choice == 'n')
{
registerUser(cardId);
}
else
{
continue;
}
}
当然我们也要进行串口一的初始化(GEC6818)
int uart1_init()
{
int uart1_fd = open("/dev/ttySAC1", O_RDWR); // 打开串口1设备文件
if (uart1_fd == -1)
{
perror("open error:");
return -1;
}
struct termios myserial;
// 清空结构体
memset(&myserial, 0, sizeof(myserial));
// O_RDWR
myserial.c_cflag |= (CLOCAL | CREAD);
// 设置控制模式状态,本地连接,接受使能
// 设置 数据位
myserial.c_cflag &= ~CSIZE; // 清空数据位
myserial.c_cflag &= ~CRTSCTS; // 无硬件流控制
myserial.c_cflag |= CS8; // 数据位:8
myserial.c_cflag &= ~CSTOPB; // //1位停止位
myserial.c_cflag &= ~PARENB; // 不要校验
cfsetospeed(&myserial, B9600); // 设置波特率,B9600是定义的宏
cfsetispeed(&myserial, B9600);
/* 刷新输出队列,清楚正接受的数据 */
tcflush(uart1_fd, TCIFLUSH);
/* 改变配置 */
tcsetattr(uart1_fd, TCSANOW, &myserial);
return uart1_fd;
}
2)门禁功能:用户进入和离开小区需要刷卡,自动打开门禁。
结合老师上课所教的实验衍生而来,驱动代码可以编写但没必要。这里采用在LCD屏上切换BMP图片来显示门禁的开关。"1_close.bmp", "2_open.bmp"
第一步初始化LCD
void *lcd_init()
{
lcd_fd = open("/dev/fb0", O_RDWR);
if (lcd_fd == -1)
{
perror("open lcd_file error\n");
return MAP_FAILED;
}
plcd = (int *)mmap(NULL, 800 * 480 * 4, PROT_READ | PROT_WRITE, MAP_SHARED, lcd_fd, 0);
return plcd;
}
GEC6818的LCD模块显示BMP图片函数
void LCD_DrawBMP(int x, int y, const char *bmpname)
{
unsigned char buf[4];
int fd = open(bmpname, O_RDONLY);
if (fd == -1)
{
perror("open bmp failed");
return;
}
// 读模数
lseek(fd, 0, SEEK_SET);
read(fd, 0, SEEK_SET);
read(fd, buf, 2);
if (buf[0] != 0x42 || buf[1] != 0x4D)
{
printf("this picture is not bmp!\n");
return;
}
// 读位图宽度
int bmp_w = 0;
lseek(fd, 0x12, SEEK_SET);
read(fd, &bmp_w, 4);
// 读取位图高度
int bmp_h = 0;
lseek(fd, 0x16, SEEK_SET);
read(fd, &bmp_h, 4);
// 读取图片色深
int bmp_colordepth = 0;
lseek(fd, 0x1C, SEEK_SET);
read(fd, &bmp_colordepth, 2);
printf("bmp:%ld * %ld * %ld\n", bmp_w, bmp_h, bmp_colordepth);
lseek(fd, 0x1C, SEEK_SET);
read(fd, &bmp_colordepth, 2);
printf("bmp:%ld * %ld * %ld\n", bmp_w, bmp_h, bmp_colordepth);
// 读取像素数组内容,并通过画点函数画出
lseek(fd, 54, SEEK_SET);
int i, j;
for (i = 0; i < bmp_h; i++)
{
for (j = 0; j < bmp_w; j++)
{
int color = 0;
read(fd, &color, bmp_colordepth / 8);
lcd_draw_point(x + j, y + (bmp_h > 0 ? (bmp_h - 1 - i) : i), color); // 位图高度为正数时,会上下颠倒存放数据
}
lseek(fd, (4 - bmp_colordepth / 8 * bmp_w % 4) % 4, SEEK_CUR); // 跳过无用数据
}
close(fd);
}
此时我们根据分析也可以想出画一个矩形按键实现——按卡号注销户主卡
也就是画点函数以及升级的画矩形函数
void lcd_draw_point(int x, int y, int color)
{
*(plcd + y * 800 + x) = color;
}
// LCD画矩形
void LCD_DrawRect(int x, int y, int width, int height, unsigned int color)
{
int i, j;
for (i = y; i < y + height; i++)
{
for (j = x; j < x + width; j++)
{
lcd_draw_point(j, i, color);
}
}
}
在main函数中调用映射到LCD屏上即可
业务逻辑为
LCD_DrawBMP(640, 0, imageNames[currentImage]);
LCD_DrawRect(700, 420, 100, 60, 0x00FF00);
int isRegistered = 0;
if(r == 13)
for (int i = 0; i < userCount; i++)
{
if (userDatabase[i].card_id == cardId)
{
isRegistered = 1;
printf("户主已经注册户主卡!\n");
currentImage = (currentImage + 1) % 3; // 加载多张图像索引
LCD_DrawBMP(640, 0, imageNames[currentImage]); // 加载下一张BMP图片
LCD_DrawRect(700, 420, 100, 60, 0x00FF00);
usleep(500000);
break;
}
}
}
3)在我的上一篇文章有详细介绍,直接看上篇V4L2https://blog.csdn.net/qq_52708261/article/details/130753794?spm=1001.2014.3001.5502
4)在户主卡丢失的情况下,注销户主卡。
这里讲一个笑话,在我刚开始编写业务代码的时候,我是以通过刷卡的方式注销户主卡的,一晚上过后还觉得没什么问题,第二天一想,我都丢了怎么刷卡注销啊,哈哈哈哈。
后面,就决定通过,触及屏幕事件来注销用户卡。
int get_coordinate(int *x, int *y)
{
struct input_event et;
int fd = open("/dev/input/event0", O_RDONLY);
if (fd == -1)
{
perror("open event0 failed");
return -1;
}
while (1)
{
int r = read(fd, &et, sizeof(et));
if (r == sizeof(et))
{
if (et.type == EV_ABS && et.code == ABS_X) // 保存x坐标
{
*x = et.value;
}
if (et.type == EV_ABS && et.code == ABS_Y) // 保存y坐标
{
*y = et.value;
}
if (et.type == EV_KEY && et.code == BTN_TOUCH && et.value == 0) // 手指离开屏幕
{
close(fd);
return 0;
}
}
}
return 0;
}
这个函数必须以手指触及屏幕,再到松开为一个完整事件,否则就会卡死,也就是说需要写一个父子进程,以进行整个逻辑流程。子进程读取屏幕触及的坐标并通过无名管道通信
int fd[2]; //fd[0]管道读端标志 fd[1]管道写标志
pid_t pid; //用于存储进程的标识符
unlink("myfifo");
if(mkfifo("myfifo",0777) == -1) //创建mgfifo管道文件并赋权限
{
perror("pipe create error");
exit(1);
}
pid = fork(); //创建子进程
if (pid == -1)
{
perror("fork");
exit(1);
}
else
{
pid = fork(); //创建子进程子进程用于检测触摸坐标并将坐标写入管道
if (pid == -1)
{
perror("fork");
exit(1);
}
if (pid == 0)
{
fd[1] = open("myfifo",O_RDWR);
while (1)
{
}
close(fd[1]); // 子进程退出时关闭写端
exit(0);
}
}
fd[0] = open("myfifo",O_RDWR | O_NONBLOCK); //非阻塞模式
但实际上,我们是需要三个进程来实现的,子进程初始化服务器,父进程在创建父子进程,子进程读屏幕获取的屏幕的位置,父进程在执行整个逻辑
LCD的显示的像素点坐标和实际LCD屏的触及是有极大误差的我需要通过触及打印函数来找到按键的大概位置,最后你看我的完整代码,你会发现我有很多的printf,就是一开始我忽略掉了这个问提才导致我调试过程很繁琐。
//printf("parent hello world\n");
int r = read(uart1_fd, buf, 13);
memset(tx_buf,0,10);//初始化tx_buf数组
size = read(fd[0],tx_buf,10);
if(size > 0)
{
sscanf(tx_buf,"%d %d",&x,&y);
printf("x = %d,y = %d\n",x,y);
}
最后根据矩形按键试出大概范围实现
if (x > 900 && x < 1050 && y > 500 && y < 600)
{
unsigned int revokeUser_id;
printf("请输入你要注销的卡号\n");
scanf("%u", &revokeUser_id);
revokeUser(revokeUser_id);
x = 0;
y = 0;
printf("注销户主卡成功");
}
5)TCP/IP协议实现远程监控
因为TCP可以保证传输数据的完整性,传输速度大差不差,基本可以完成。
选择一个GEC6818板作为服务器端,另一个作为客户端。服务器端将负责捕获视频帧,并通过TCP/IP协议将视频帧传输给客户端。在服务器端,创建一个TCP/IP服务器程序,使用Socket编程接收来自客户端的连接请求。一旦客户端连接成功,服务器端使用v4l2库捕获摄像头的视频帧。
将捕获到的视频帧通过Socket发送给客户端。可以将视频帧转换为字节流形式,然后使用Socket发送字节流数据。在客户端,创建一个TCP/IP客户端程序,使用Socket编程连接到服务器端。
一旦连接成功,客户端接收从服务器端发送过来的视频帧数据。
在客户端,使用v4l2库来处理接收到的视频帧数据。可以将接收到的字节流数据转换回视频帧格式,并进行显示、保存等操作。
首先我们先看服务器
void *recv_data(void *arg)
{
int *p = arg;
char buf_server[256]={0}; //服务器端缓存数组
while(1)
{
int r = read(*p,buf_server,256);
if(r > 0)
{
printf("r:%d buf_server:%s\n",r,buf_server);
}
}
}
//连接客户端,输入并发送数据
int handle_connection(int connfd)
{
char rgb[640*480*3];
fd2[1] = open("myfifo2",O_RDWR);
pthread_t tid;
int r = pthread_create(&tid, NULL,recv_data, (void *)&connfd);
if(r == -1)
{
perror("pthread_create failed");
return -1;
}
while(1)
{
int len = read(fd2[1],rgb,sizeof(rgb));
if(len > 0)
{
int r =write(connfd,rgb,strlen(rgb));
//printf("r=%d buf=%s,size=%d\n",r,rgb,strlen(rgb));
}
//printf("%s %d\n",__FUNCTION__,__LINE__);
//write(connfd,"hello client",12);
// printf("hello client\n");
}
}
//初始化服务器
int Server_Init(const char *server_addr,int server_port)
{
int sockfd;
/*step1: 创建一个套接字(SOCK_STREAM)*/
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd == -1)
{
perror("socket failed");
return -1;
}
/*step2: 生成一个服务器的地址,并bind*/
struct sockaddr_in sAddr;
memset(&sAddr,0,sizeof(sAddr));//把结构体清空
sAddr.sin_family = AF_INET;//IPV4协议
// sAddr.sin_port = htons(server_port);//指定端口号
// sAddr.sin_addr.s_addr = inet_addr(server_addr);//指定IP地址
sAddr.sin_port = htons(8887);//指定端口号
sAddr.sin_addr.s_addr = inet_addr("192.168.31.99");//指定IP地址
int r = bind(sockfd, (struct sockaddr *)&sAddr,sizeof(sAddr));
if(r == -1)
{
perror("bind failed");
return -1;
}
/*step3: listen 设置监听数*/
r = listen(sockfd, 10);
if(r == -1)
{
perror("listen failed");
return -1;
}
/*step4:等待被连接*/
while(1)
{
struct sockaddr_in cAddr;//用来保存客户端的地址
socklen_t addrlen = sizeof(cAddr);
int connfd = accept(sockfd, (struct sockaddr *)&cAddr, &addrlen);
if(connfd > 0)
{
printf("connection form %s[%d]\n",inet_ntoa(cAddr.sin_addr),ntohs(cAddr.sin_port));
pid_t pid = fork();
if(pid > 0)//父进程直接关闭连接
{
close(connfd);
}
else if(pid == 0)//子进程来提供服务
{
handle_connection(connfd);
exit(0);
}
}
}
return sockfd;
}
Server_Init(const char *server_addr,int server_port)这个函数调用recv_data(),handle_connection(),而且其中的参数为放行指针,所以子进程中这样传参,在Server_Init中填写自己的ip地址并确保两个开发板在同一个网段下。命令为ifconfig
若没有设置,则需自己设置命令为ifconfig eth0 192.168.xx.xx
if (pid == 0)
{
int sockfd = Server_Init(argv[1], atoi(argv[2]));
if(sockfd == -1)
{
printf("Server_Init failed\n");
return -1;
}
close(fd[1]); // 子进程退出时关闭写端
exit(0);
}
handle_connection()函数中则创了一个线程,可以实现一对多的传输信息,这里我只有一即可。
因为V4L2是传出数据的特性,见上篇。最先开始我选择传输V4L2 所捕获YUYV原始数据,但总会出现段错误,后来才知晓V4L2并非标准输出4帧。后直接采用传输转换的RGB[640*480*3]。
服务器端,只需要接收显示即可
int Client_Init(const char *server_addr, int server_port)
{
int sockfd;
/*step1: 创建一个套接字(SOCK_STREAM)*/
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1)
{
perror("socket failed");
return -1;
}
/*step2: 生成一个服务器的地址*/
struct sockaddr_in sAddr;
memset(&sAddr, 0, sizeof(sAddr)); // 把结构体清空
sAddr.sin_family = AF_INET; // IPV4协议
// sAddr.sin_port = htons(server_port);//指定端口号
// sAddr.sin_addr.s_addr = inet_addr(server_addr);//指定IP地址
sAddr.sin_port = htons(8887); // 指定端口号
sAddr.sin_addr.s_addr = inet_addr("192.168.31.99"); // 指定IP服务器地址
/*step3: 主动去连接一个套接字(服务器)*/
int r = connect(sockfd, (struct sockaddr *)&sAddr, sizeof(sAddr));
if (r == -1)
{
perror("connect failed");
return -1;
}
return sockfd;
}
这两个都用到了线程所以指令为
china@ubuntu:22278$ arm-linux-gcc tcp_server.c -o tcp_server -lpthread
china@ubuntu:22278$ arm-linux-gcc tcp_client.c -o tcp_client -lpthread
完整代码实现
服务器:
/*************************************************************************
> File Name: tcp_server.c
> Author: volcano.eth
> Mail: [email protected]
> Created Time: 2023年05月10日 星期四 15时46分08秒
************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/mman.h>
#include <linux/input.h>
#include <linux/videodev2.h>
#include <pthread.h>
struct Users
{
int number; // 编号
int class; // 房间号
unsigned int card_id; // 卡号
char name[10]; // 住户姓名
};
unsigned char buf[13]; // 存储卡号的数组
unsigned int dat[6]; // 16转10进制所输出的卡号
struct Users userDatabase[100]; // 用户链表,最多存储100个用户
int userCount = 0; // 用户计数器
int fd2[2]; //fd2[0]管道读端标志 fd2[1]管道写标志
// 登记用户
void registerUser(unsigned int cardId)
{
struct Users newUser;
// 填写用户信息
newUser.card_id = cardId;
printf("请输入户主编号: ");
scanf("%d", &newUser.number);
printf("请输入户主房间号: ");
scanf("%d", &newUser.class);
printf("请输入户主姓名: ");
scanf("%s", newUser.name);
// 将新用户添加到用户数据库
userDatabase[userCount] = newUser;
userCount++;
printf("户主信息注册成功!\n");
}
// 注销用户
void revokeUser(unsigned int cardId)
{
for (int i = 0; i < userCount; i++)
{
if (userDatabase[i].card_id == cardId)
{
userDatabase[i].card_id = 0;
userDatabase[i].number = 0;
userDatabase[i].class = 0;
memset(userDatabase[i].name, 0, sizeof(userDatabase[i].name));
printf("住户卡已注销!\n");
return;
}
}
printf("未找到该住户卡号,无法注销!\n");
}
// 16进制转换
int binary(int num)
{
int i = 0, j;
while (num)
{
if (num >= 16)
{
j = num / 16;
i += j;
}
else
{
i += num;
break;
}
num = num % 16;
i = i * 10;
}
return i;
}
int uart1_init()
{
int uart1_fd = open("/dev/ttySAC1", O_RDWR); // 打开串口1设备文件
if (uart1_fd == -1)
{
perror("open error:");
return -1;
}
struct termios myserial;
// 清空结构体
memset(&myserial, 0, sizeof(myserial));
// O_RDWR
myserial.c_cflag |= (CLOCAL | CREAD);
// 设置控制模式状态,本地连接,接受使能
// 设置 数据位
myserial.c_cflag &= ~CSIZE; // 清空数据位
myserial.c_cflag &= ~CRTSCTS; // 无硬件流控制
myserial.c_cflag |= CS8; // 数据位:8
myserial.c_cflag &= ~CSTOPB; // //1位停止位
myserial.c_cflag &= ~PARENB; // 不要校验
cfsetospeed(&myserial, B9600); // 设置波特率,B9600是定义的宏
cfsetispeed(&myserial, B9600);
/* 刷新输出队列,清楚正接受的数据 */
tcflush(uart1_fd, TCIFLUSH);
/* 改变配置 */
tcsetattr(uart1_fd, TCSANOW, &myserial);
return uart1_fd;
}
int *plcd; // 用于存储屏幕缓冲区的首地址,以便后续对屏幕进行读写操作
int *lcd_p; // 指向屏幕缓冲区的特定位置
int lcd_fd; // 用于存储屏幕设备文件的文件描述符
void *lcd_init()
{
lcd_fd = open("/dev/fb0", O_RDWR);
if (lcd_fd == -1)
{
perror("open lcd_file error\n");
return MAP_FAILED;
}
plcd = (int *)mmap(NULL, 800 * 480 * 4, PROT_READ | PROT_WRITE, MAP_SHARED, lcd_fd, 0);
return plcd;
}
void lcd_draw_point(int x, int y, int color)
{
*(plcd + y * 800 + x) = color;
}
void LCD_DrawBMP(int x, int y, const char *bmpname)
{
unsigned char buf[4];
int fd = open(bmpname, O_RDONLY);
if (fd == -1)
{
perror("open bmp failed");
return;
}
// 读模数
lseek(fd, 0, SEEK_SET);
read(fd, 0, SEEK_SET);
read(fd, buf, 2);
if (buf[0] != 0x42 || buf[1] != 0x4D)
{
printf("this picture is not bmp!\n");
return;
}
// 读位图宽度
int bmp_w = 0;
lseek(fd, 0x12, SEEK_SET);
read(fd, &bmp_w, 4);
// 读取位图高度
int bmp_h = 0;
lseek(fd, 0x16, SEEK_SET);
read(fd, &bmp_h, 4);
// 读取图片色深
int bmp_colordepth = 0;
lseek(fd, 0x1C, SEEK_SET);
read(fd, &bmp_colordepth, 2);
printf("bmp:%ld * %ld * %ld\n", bmp_w, bmp_h, bmp_colordepth);
lseek(fd, 0x1C, SEEK_SET);
read(fd, &bmp_colordepth, 2);
printf("bmp:%ld * %ld * %ld\n", bmp_w, bmp_h, bmp_colordepth);
// 读取像素数组内容,并通过画点函数画出
lseek(fd, 54, SEEK_SET);
int i, j;
for (i = 0; i < bmp_h; i++)
{
for (j = 0; j < bmp_w; j++)
{
int color = 0;
read(fd, &color, bmp_colordepth / 8);
lcd_draw_point(x + j, y + (bmp_h > 0 ? (bmp_h - 1 - i) : i), color); // 位图高度为正数时,会上下颠倒存放数据
}
lseek(fd, (4 - bmp_colordepth / 8 * bmp_w % 4) % 4, SEEK_CUR); // 跳过无用数据
}
close(fd);
}
// 获取手指触摸位置
int get_coordinate(int *x, int *y)
{
struct input_event et;
int fd = open("/dev/input/event0", O_RDONLY);
if (fd == -1)
{
perror("open event0 failed");
return -1;
}
while (1)
{
int r = read(fd, &et, sizeof(et));
if (r == sizeof(et))
{
if (et.type == EV_ABS && et.code == ABS_X) // 保存x坐标
{
*x = et.value;
}
if (et.type == EV_ABS && et.code == ABS_Y) // 保存y坐标
{
*y = et.value;
}
if (et.type == EV_KEY && et.code == BTN_TOUCH && et.value == 0) // 手指离开屏幕
{
close(fd);
return 0;
}
}
}
return 0;
}
// 关闭LCD屏
int uninit_lcd()
{
close(lcd_fd);
if (munmap(lcd_p, 800 * 480 * 4) == -1)
{
return -1;
}
return 0;
}
// LCD画矩形
void LCD_DrawRect(int x, int y, int width, int height, unsigned int color)
{
int i, j;
for (i = y; i < y + height; i++)
{
for (j = x; j < x + width; j++)
{
lcd_draw_point(j, i, color);
}
}
}
typedef struct
{
char *start;
size_t length;
} buffer_t;
buffer_t buffer[4]; // 映射所需的素材缓存在数组中
buffer_t current; // 保存当前输出的一帧
unsigned int sign3 = 0;
//在yuyv2rgb0被调用
int yuyv2rgb(int y, int u, int v)
{
unsigned int pixel24 = 0;
unsigned char *pixel = (unsigned char *)&pixel24;
int r, g, b;
static int ruv, guv, buv;
if (sign3)
{
sign3 = 0;
ruv = 1159 * (v - 128);
guv = 380 * (u - 128) + 813 * (v - 128);
buv = 2018 * (u - 128);
}
r = (1164 * (y - 16) + ruv) / 1000;
g = (1164 * (y - 16) - guv) / 1000;
b = (1164 * (y - 16) + buv) / 1000;
if (r > 255)
r = 255;
if (g > 255)
g = 255;
if (b > 255)
b = 255;
if (r < 0)
r = 0;
if (g < 0)
g = 0;
if (b < 0)
b = 0;
pixel[0] = r;
pixel[1] = g;
pixel[2] = b;
return pixel24;
}
//YUYV转rgb
int yuyv2rgb0(unsigned char *yuv, unsigned char *rgb, unsigned int width, unsigned int height)
{
unsigned int in, out;
int y0, u, y1, v;
unsigned int pixel24;
unsigned char *pixel = (unsigned char *)&pixel24;
unsigned int size = width * height * 2;
for (in = 0, out = 0; in < size; in += 4, out += 6)
{
y0 = yuv[in + 0];
u = yuv[in + 1];
y1 = yuv[in + 2];
v = yuv[in + 3];
sign3 = 1;
pixel24 = yuyv2rgb(y0, u, v);
rgb[out + 0] = pixel[0];
rgb[out + 1] = pixel[1];
rgb[out + 2] = pixel[2];
pixel24 = yuyv2rgb(y1, u, v);
rgb[out + 3] = pixel[0];
rgb[out + 4] = pixel[1];
rgb[out + 5] = pixel[2];
}
return 0;
}
//接收客户端发送的数据, 读取connfd存在buf_server
void *recv_data(void *arg)
{
int *p = arg;
char buf_server[256]={0}; //服务器端缓存数组
while(1)
{
int r = read(*p,buf_server,256);
if(r > 0)
{
printf("r:%d buf_server:%s\n",r,buf_server);
}
}
}
//连接客户端,输入并发送数据
int handle_connection(int connfd)
{
char rgb[640*480*3];
fd2[1] = open("myfifo2",O_RDWR);
pthread_t tid;
int r = pthread_create(&tid, NULL,recv_data, (void *)&connfd);
if(r == -1)
{
perror("pthread_create failed");
return -1;
}
while(1)
{
int len = read(fd2[1],rgb,sizeof(rgb));
if(len > 0)
{
int r =write(connfd,rgb,strlen(rgb));
//printf("r=%d buf=%s,size=%d\n",r,rgb,strlen(rgb));
}
//printf("%s %d\n",__FUNCTION__,__LINE__);
//write(connfd,"hello client",12);
// printf("hello client\n");
}
}
//初始化服务器
int Server_Init(const char *server_addr,int server_port)
{
int sockfd;
/*step1: 创建一个套接字(SOCK_STREAM)*/
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd == -1)
{
perror("socket failed");
return -1;
}
/*step2: 生成一个服务器的地址,并bind*/
struct sockaddr_in sAddr;
memset(&sAddr,0,sizeof(sAddr));//把结构体清空
sAddr.sin_family = AF_INET;//IPV4协议
// sAddr.sin_port = htons(server_port);//指定端口号
// sAddr.sin_addr.s_addr = inet_addr(server_addr);//指定IP地址
sAddr.sin_port = htons(8887);//指定端口号
sAddr.sin_addr.s_addr = inet_addr("192.168.31.99");//指定IP地址
int r = bind(sockfd, (struct sockaddr *)&sAddr,sizeof(sAddr));
if(r == -1)
{
perror("bind failed");
return -1;
}
/*step3: listen 设置监听数*/
r = listen(sockfd, 10);
if(r == -1)
{
perror("listen failed");
return -1;
}
/*step4:等待被连接*/
while(1)
{
struct sockaddr_in cAddr;//用来保存客户端的地址
socklen_t addrlen = sizeof(cAddr);
int connfd = accept(sockfd, (struct sockaddr *)&cAddr, &addrlen);
if(connfd > 0)
{
printf("connection form %s[%d]\n",inet_ntoa(cAddr.sin_addr),ntohs(cAddr.sin_port));
pid_t pid = fork();
if(pid > 0)//父进程直接关闭连接
{
close(connfd);
}
else if(pid == 0)//子进程来提供服务
{
handle_connection(connfd);
exit(0);
}
}
}
return sockfd;
}
int main(int argc, char *argv[])
{
int uart1_fd = uart1_init(); // uart1_fd串口文件描述符用于结束循环
unsigned int cardId = 0; // 输出用户的卡号
int h = 0; // 韦根号逗号前
int l = 0; // 韦根号逗号后
int i,size;
int x, y;
char tx_buf[10]; //缓存LCD的xy坐标数组
int currentImage = 0; // 跟踪当前图片索引
const char *imageNames[] = {"1_close.bmp", "2_open.bmp"}; // 为图片数组中的图片命名
lcd_init();
// 操作映射内存,让屏幕显示图像,按钮
LCD_DrawBMP(640, 0, imageNames[currentImage]);
LCD_DrawRect(700, 420, 100, 60, 0x00FF00);
// 打开摄像头
int fd_v4l2 = open("/dev/video7", O_RDWR); // 根据secureCRT确定设备
if (fd_v4l2 == -1)
{
perror("open");
exit(-1);
}
// 获取功能参数
struct v4l2_capability cap = {};
int res = ioctl(fd_v4l2, VIDIOC_QUERYCAP, &cap);
if (res == -1)
{
perror("ioctl cap");
exit(-1);
}
// 先确定摄像头功能可以使用
if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)
{
printf("is a capture device!\n");
}
else
{
printf("is not a capture device!\n");
exit(-1);
}
// 获取摄像头支持的格式
struct v4l2_fmtdesc fmt = {};
fmt.index = 0;
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 获取摄像头格式
while ((res = ioctl(fd_v4l2, VIDIOC_ENUM_FMT, &fmt)) == 0)
{
printf("pixformat = %c %c %c %c,description = %s\n",
fmt.pixelformat & 0xff,
(fmt.pixelformat >> 8) & 0xff,
(fmt.pixelformat >> 16) & 0xff,
(fmt.pixelformat >> 24) & 0xff,
fmt.description);
fmt.index++;
}
// 设置采集通道
int index = 0; // 使用通道0
res = ioctl(fd_v4l2, VIDIOC_S_INPUT, &index);
if (res == -1)
{
perror("ioctl_s_input");
exit(-1);
}
// 设置摄像头采集格式
struct v4l2_format format = {};
format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
format.fmt.pix.width = 640;
format.fmt.pix.height = 480;
format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; // YUYV
format.fmt.pix.field = V4L2_FIELD_NONE;
res = ioctl(fd_v4l2, VIDIOC_S_FMT, &format);
if (res == -1)
{
perror("ioctl s_fmt");
exit(-1);
}
// 申请缓存空间
struct v4l2_requestbuffers req = {};
req.count = 4;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
res = ioctl(fd_v4l2, VIDIOC_REQBUFS, &req);
if (res == -1)
{
perror("ioctl reqbufs");
exit(-1);
}
// 分配映射入队
size_t q, max_len = 0;
for (q = 0; q < 4; q++)
{
struct v4l2_buffer buf_v4l2 = {};
buf_v4l2.index = q; // 0~3展现4帧图片
buf_v4l2.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf_v4l2.memory = V4L2_MEMORY_MMAP;
res = ioctl(fd_v4l2, VIDIOC_QUERYBUF, &buf_v4l2);
if (res == -1)
{
perror("ioctl querybuf");
exit(-1);
}
// 判读并记录最大长度,以适配各个图帧
if (buf_v4l2.length > max_len)
max_len = buf_v4l2.length;
// 映射
buffer[q].length = buf_v4l2.length;
buffer[q].start = mmap(NULL, buf_v4l2.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd_v4l2, buf_v4l2.m.offset);
if (buffer[q].start == MAP_FAILED)
{
perror("mmap");
exit(-1);
}
// 入队
res = ioctl(fd_v4l2, VIDIOC_QBUF, &buf_v4l2);
if (res == -1)
{
perror("ioctl qbuf");
exit(-1);
}
}
// 申请临时缓冲区
current.start = malloc(max_len);
if (current.start == NULL)
{
perror("malloc");
exit(-1);
}
// 启动摄像头
enum v4l2_buf_type buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
res = ioctl(fd_v4l2, VIDIOC_STREAMON, &buf_type);
if (res == -1)
{
perror("ioctl streamon");
exit(-1);
}
// 延时
sleep(1);
// RGB缓冲区
char rgb[640*480*3];
int fd[2]; //fd[0]管道读端标志 fd[1]管道写标志
pid_t pid; //用于存储进程的标识符
unlink("myfifo");
if(mkfifo("myfifo",0777) == -1) //创建mgfifo管道文件并赋权限
{
perror("pipe create error");
exit(1);
}
unlink("myfifo2");
if(mkfifo("myfifo2",0777) == -1) //创建mgfifo2管道文件并赋权限
{
perror("pipe2 create error");
exit(1);
}
pid = fork(); //创建子进程
if (pid == -1)
{
perror("fork");
exit(1);
}
// 子进程初始化服务器
if (pid == 0)
{
int sockfd = Server_Init(argv[1], atoi(argv[2]));
if(sockfd == -1)
{
printf("Server_Init failed\n");
return -1;
}
close(fd[1]); // 子进程退出时关闭写端
exit(0);
}
// 父进程用于检测触摸坐标并将坐标写入管道
else
{
pid = fork(); //创建子进程子进程用于检测触摸坐标并将坐标写入管道
if (pid == -1)
{
perror("fork");
exit(1);
}
if (pid == 0)
{
fd[1] = open("myfifo",O_RDWR);
while (1)
{
printf("child hello world1\n");
char buf[256];
memset(buf, 0, sizeof(buf));
sscanf(buf, "%d %d", &x, &y); // 解析坐标
get_coordinate(&x, &y);
sprintf(buf, "%d %d\n", x, y); // 将获取到的坐标写入缓冲区
write(fd[1], buf, strlen(buf)); // 写入管道
}
close(fd[1]); // 子进程退出时关闭写端
exit(0);
}
fd[0] = open("myfifo",O_RDWR | O_NONBLOCK); //非阻塞模式
fd2[0] = open("myfifo2",O_RDWR );
while (1)
{
struct v4l2_buffer buf_v4l2 = {};
buf_v4l2.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf_v4l2.memory = V4L2_MEMORY_MMAP;
// 出队
res = ioctl(fd_v4l2, VIDIOC_DQBUF, &buf_v4l2);
if (res == -1)
{
perror("ioctl dqbuf");
}
// 拷贝数据
memcpy(current.start, buffer[buf_v4l2.index].start, buf_v4l2.bytesused);
current.length = buf_v4l2.bytesused;
// 入队
res = ioctl(fd_v4l2, VIDIOC_QBUF, &buf_v4l2);
if (res == -1)
{
perror("ioctl qbuf");
}
// YUYV转RGB
yuyv2rgb0(current.start, rgb, 640, 480);
write(fd2[0],rgb,strlen(rgb));
//显示到LCD屏上
int n, m;
for (m = 0; m < 480; m++)
{
for (n = 0; n < 640; n++)
{
*(plcd + m*800 + n) = rgb[3 * (m*640 + n)] << 16 | rgb[3 * (m*640 + n) + 1] << 8 | rgb[3 * (m*640 + n) + 2];
}
}
//printf("parent hello world\n");
int r = read(uart1_fd, buf, 13);
memset(tx_buf,0,10);//初始化tx_buf数组
size = read(fd[0],tx_buf,10);
if(size > 0)
{
sscanf(tx_buf,"%d %d",&x,&y);
printf("x = %d,y = %d\n",x,y);
}
if (x > 900 && x < 1050 && y > 500 && y < 600)
{
unsigned int revokeUser_id;
printf("请输入你要注销的卡号\n");
scanf("%u", &revokeUser_id);
revokeUser(revokeUser_id);
x = 0;
y = 0;
printf("注销户主卡成功");
}
if (r == 13)
{
if (buf[0] != 0x02 || buf[12] != 0x03)
{
printf("rfid read error\n");
}
else
{
printf("rfid read ok\n");
for (i = 5; i < 11; i++)
{
if ((buf[i] - '0') >= 10)
{
dat[i - 5] = binary(buf[i] - '0' - 1);
}
else
dat[i - 5] = buf[i] - '0';
// printf("dat=%d\n",dat[i-5]);
}
for (i = 0; i < 13; i++)
{
if ((buf[i] - '0') >= 10)
{
buf[i] = buf[i] - 1;
// printf("buf=%x\n",buf[i]-'0');
}
}
cardId = dat[0]*16*16*16*16*16 + dat[1]*16*16*16*16 + dat[2]*16*16*16 + dat[3]*16*16 + dat[4]*16 + dat[5];
printf("id = %ld\n", cardId);
h = dat[0]*16 + dat[1];
l = dat[2]*16*16*16 + dat[3]*16*16 + dat[4]*16 + dat[5];
// 检查用户是否已注册的标志
int isRegistered = 0;
for (int i = 0; i < userCount; i++)
{
if (userDatabase[i].card_id == cardId)
{
isRegistered = 1;
printf("户主已经注册户主卡!\n");
currentImage = (currentImage + 1) % 3; // 加载多张图像索引
LCD_DrawBMP(640, 0, imageNames[currentImage]); // 加载下一张BMP图片
LCD_DrawRect(700, 420, 100, 60, 0x00FF00);
usleep(500000);
break;
}
}
if (!isRegistered)
{
printf("这张住户卡未注册,是否注册户主卡?(y/n): ");
char choice;
scanf(" %c", &choice);
if (choice == 'y' || choice == 'n')
{
registerUser(cardId);
}
else
{
continue;
}
}
}
}
}
// 关闭摄像头采集
buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
res = ioctl(fd_v4l2, VIDIOC_STREAMOFF, &buf_type);
if (res == -1)
{
perror("ioctl streamoff");
exit(-1);
}
// 解除映射
for (i = 0; i < 4; i++)
{
munmap(buffer[i].start, buffer[i].length);
}
free(current.start);
sleep(1); // 延时一下
close(fd_v4l2);
close(fd[0]); // 父进程退出时关闭读端
close(uart1_fd);
uninit_lcd();
return 0;
}
}
客户端
/*************************************************************************
> File Name: tcp_client.c
> Author: volcano.eth
> Mail: [email protected]
> Created Time: 2023年05月18日 星期四 21时30分16秒
************************************************************************/
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <linux/videodev2.h>
#include <sys/mman.h>
#include <linux/input.h>
#include <netinet/in.h>
int *plcd; // 用于存储屏幕缓冲区的首地址,以便后续对屏幕进行读写操作
int *lcd_p; // 指向屏幕缓冲区的特定位置
int lcd_fd; // 用于存储屏幕设备文件的文件描述符
char client_buf[640*480*3];
typedef struct
{
char *start;
size_t length;
} buffer_t;
buffer_t buffer[4]; // 映射所需的素材缓存在数组中
buffer_t current; //保存服务器发送帧的数据
void *lcd_init()
{
lcd_fd = open("/dev/fb0", O_RDWR);
if (lcd_fd == -1)
{
perror("open lcd_file error\n");
return MAP_FAILED;
}
plcd = (int *)mmap(NULL, 800 * 480 * 4, PROT_READ | PROT_WRITE, MAP_SHARED, lcd_fd, 0);
return plcd;
}
// 关闭LCD屏
int uninit_lcd()
{
close(lcd_fd);
if (munmap(lcd_p, 800 * 480 * 4) == -1)
{
return -1;
}
return 0;
}
int Client_Init(const char *server_addr, int server_port)
{
int sockfd;
/*step1: 创建一个套接字(SOCK_STREAM)*/
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1)
{
perror("socket failed");
return -1;
}
/*step2: 生成一个服务器的地址*/
struct sockaddr_in sAddr;
memset(&sAddr, 0, sizeof(sAddr)); // 把结构体清空
sAddr.sin_family = AF_INET; // IPV4协议
// sAddr.sin_port = htons(server_port);//指定端口号
// sAddr.sin_addr.s_addr = inet_addr(server_addr);//指定IP地址
sAddr.sin_port = htons(8887); // 指定端口号
sAddr.sin_addr.s_addr = inet_addr("192.168.31.99"); // 指定IP服务器地址
/*step3: 主动去连接一个套接字(服务器)*/
int r = connect(sockfd, (struct sockaddr *)&sAddr, sizeof(sAddr));
if (r == -1)
{
perror("connect failed");
return -1;
}
return sockfd;
}
//接收数据
void *recv_data(void *arg)
{
int *p = arg;
memset(client_buf,0,1000);
while (1)
{
int r = read(*p,client_buf,sizeof(client_buf));
if (r > 0)
{
printf("r:%d buf:%s,sixe=%d\n", r,client_buf,strlen(client_buf));
}
printf("%s %d\n",__FUNCTION__,__LINE__);
}
}
int main(int argc, char *argv[])
{
pthread_t tid;
int sockfd = Client_Init(argv[1], atoi(argv[2]));
if (sockfd == -1)
{
printf("client_Init failed\n");
return -1;
}
lcd_init();
// // 延时
sleep(1);
// RGB缓冲区
char rgb[640*480*3];
while (1)
{
int r = read(sockfd,rgb,sizeof(rgb));
if (r > 0)
{
// 显示到LCD屏上
int n, m;
for (m = 0; m < 480; m++)
{
for (n = 0; n < 640; n++)
{
*(plcd + m*800 + n) = rgb[3*(m*640 + n)] << 16 | rgb[3*(m*640 + n) + 1] << 8 | rgb[3* (m*640 + n) + 2];
}
}
}
}
close(sockfd);
}