RFID Course Design—Intelligent Residential Security System

Attachment: complete implementation of the code

【Problem Description】

With the enhancement of safety awareness, community security system has gradually entered the homes of ordinary people. Monitoring solutions are also emerging one after another. Combined with the current popular communication technology of the Internet of Things, the intelligent security system combined with RFID has gradually become the mainstream.

This project is based on multimedia technology and network technology, combined with the mainstream embedded Cortex-A series high-end processing platform, combined with RFID technology, network video monitoring, etc. to realize intelligent security monitoring, which is more efficient than traditional intelligent security monitoring methods, cost and Lower power consumption.

【basic requirements】

The project should have the following functions:

1) Realize the user registration function according to RFID, and record some basic information of users, such as name, unit number, room number, etc.

2) Access control function: users need to swipe their cards to enter and leave the community, and the access control is automatically opened.

3) Video monitoring function: use the USB camera to take real-time images of the entrance of the community, and transmit them to the development board screen for display.

It has important innovation value.

【Hardware Solution】

1) GEC6818 development board

2) Low frequency RFID module

3) USB camera - V4L2

【Software Solution】

VMware virtual machine + Ubuntu

【Key Technology】

1) Use of Linux operating system

2) Application development of low frequency RFID

3) File I/O

4) ARM development board UI design

5) ARM development board touch screen application development

6) v4l2 camera driver

【Additional items】

1) Increase the function of logout of the lost access control card

2) Use TCP/IP network protocol to realize remote video monitoring

Generally speaking, it is necessary to write server code and client code. The time reserved for this course design is only 4 days. My time can only be relatively good, so all functions are kept simple.

First of all, let's look at the requirements of 1) to realize the user registration function and record some basic information of users, such as name, unit number, room number, etc. I can use arrays, linked lists, and even databases to solve it. My first thought is to use linked lists.

First create a structure Users

struct Users
{
	int number;			  // 编号
	int class;			  // 房间号
	unsigned int card_id; // 卡号
	char name[10];		  // 住户姓名
};

Then create a linked list

struct Users userDatabase[100]; // 用户链表,最多存储100个用户

Write the function 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");
}

The logout logic is to assign 0 to the members in the linked list

// 注销用户
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");
}

Because it is the head of household database established in the linked list, it needs a sign to check whether there is registration, and if it is not registered, it is set to 0

int isRegistered = 0

After completing the registration ++, once you have registered, you can directly enter and read out the 13-digit hexadecimal number, which is the detection of the low-frequency card. Because the low-frequency card reads out a hexadecimal number, we need to perform a hexadecimal conversion if we want to understand it.

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;
				}
			}	

Of course, we also need to initialize the serial port one (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) Access control function: users need to swipe their cards to enter and leave the community, and the access control is automatically opened.   

Derived from the experiments taught by the teacher in class, the driver code can be written but it is not necessary. Here, switch the BMP picture on the LCD screen to display the switch of the access control. "1_close.bmp", "2_open.bmp"

The first step is to initialize the 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;
}

LCD module of GEC6818 displays BMP picture function

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);
}

At this time, based on the analysis, we can also come up with drawing a rectangular button to realize - cancel the head of household card according to the card number

That is, the drawing point function and the upgraded drawing rectangle function

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);
		}
	}
}

Call it in the main function and map it to the LCD screen

The business logic is

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) There is a detailed introduction in my last article, just read the previous article V4L2 https://blog.csdn.net/qq_52708261/article/details/130753794?spm=1001.2014.3001.5502

4) If the head of household card is lost, cancel the head of household card.

Let me tell you a joke here. When I first started writing business code, I canceled the head of account card by swiping the card. After one night, I still felt that there was no problem. When I thought about it the next day, I lost it. How can I swipe the card to cancel? Ha ha ha ha.

Later, it was decided to log out the user card by touching the screen event.

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;
}

This function must be a complete event from touching the screen with the finger to releasing it, otherwise it will be stuck, that is to say, a parent-child process needs to be written to carry out the entire logic flow. The child process reads the coordinates touched by the screen and communicates through the unnamed pipe

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);  //非阻塞模式

But in fact, we need three processes to implement, the child process initializes the server, the parent process creates the parent-child process, the child process reads the screen to obtain the screen position, and the parent process executes the entire logic

There is a huge error between the pixel coordinates displayed on the LCD and the touch of the actual LCD screen. I need to find the approximate position of the button by touching the print function. Finally, if you look at my complete code, you will find that I have a lot of printf, that is At the beginning, I ignored this question, which made my debugging process very cumbersome.

//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);
}

Finally, try out the approximate range based on the rectangular buttons to achieve

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 protocol for remote monitoring

Because TCP can guarantee the integrity of the transmitted data, the transmission speed is not bad, and it can basically be completed.

        Choose one GEC6818 board as the server side and the other as the client side. The server side will be responsible for capturing video frames and transmitting the video frames to the client side through the TCP/IP protocol. On the server side, create a TCP/IP server program and use Socket programming to receive connection requests from clients. Once the client is successfully connected, the server uses the v4l2 library to capture video frames from the camera.
Send the captured video frames to the client through Socket. Video frames can be converted into byte streams, and then byte stream data can be sent using Socket. On the client side, create a TCP/IP client program and use Socket programming to connect to the server side.
Once the connection is successful, the client receives the video frame data sent from the server.
        On the client side, use the v4l2 library to process the received video frame data. The received byte stream data can be converted back to the video frame format, and operations such as displaying and saving can be performed.

First let's look at the 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;  	
}

The function Server_Init(const char *server_addr, int server_port) calls recv_data(), handle_connection(), and the parameter in it is the release pointer, so the parameter is passed in this way in the child process, fill in your own ip address in Server_Init and ensure that the two development The boards are under the same network segment. The command is ifconfig     

If not set, you need to set the command yourself as 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);
}

A thread is created in the handle_connection() function, which can realize one-to-many transmission information. Here I only have one.

Because V4L2 is a characteristic of outgoing data, see the previous article. At first, I chose to transmit the raw YUYV data captured by V4L2, but there was always a segment error. Later, I realized that V4L2 is not a standard output of 4 frames. Afterwards, the RGB[640*480*3] converted by transmission is directly adopted.

On the server side, only need to receive and display

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;
}

Both of these use threads so the instructions are

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

Complete code implementation

server:

/*************************************************************************
	> 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;
	}
}

client

/*************************************************************************
	> 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);
}

 

 

 

Guess you like

Origin blog.csdn.net/qq_52708261/article/details/130822617