【嵌入式Linux项目】基于Linux的全志H616开发板智能垃圾桶项目

目录

一、功能需求

二、涵盖的知识点

1、wiringPi库下的相关硬件操作函数调用

2、线程(未使用互斥锁和条件)

 3、父子进程

4、网络编程(socket套接字)

5、进程间通信(共享内存和信号量)

三、开发环境

1、硬件:

2、软件:

3、引脚分配:

四、代码

1、服务端代码:server.c

2、客户端代码:client.c

五、编译和运行

六、视频功能展示


一、功能需求

  • 靠近时,垃圾桶开盖2秒,2秒后关盖
  • 垃圾桶开盖、关盖带滴滴声(蜂鸣器发声)
  • 垃圾桶开盖超过10秒,滴滴声报警
  • 通过Socket网络编程,开发板运行服务端,上位机运行客户端。实现Socket客户端发送指令远程打开和关闭垃圾桶

二、涵盖的知识点

1、wiringPi库下的相关硬件操作函数调用

包括wiringPi库的初始化,蜂鸣器、超声波测距、sg90舵机的输入输出引脚配置和高低电平设置,超声波测距中的时间函数和距离测算,sg90舵机中的PWM信号控制、Linux定时器和信号处理。

2、线程(未使用互斥锁和条件)

服务端创建了三个线程:超声波测距、sg90舵机、socket命令。(下面括号中为对于线程函数名)

  • 超声波测距(*ultrasonic):在while循环中每隔0.5s计算一次距离。
  • sg90舵机(*sg90):在while循环中每隔20ms通过所测距或客户端指令,执行垃圾桶开盖/关盖。
  • socket命令(*socketCmd):完成共享内存和信号量的创建,在while循环中每隔0.5s查看客户端是否发出指令,若服务端收到指令(open or close),则执行垃圾桶开盖/关盖。

 3、父子进程

在main函数中,父进程完成设备初始化和socket服务端搭建。while循环中,父进程阻塞在accept函数处,等待多个客户端接入;子进程实现对共享内存和信号量的获取,在while循环中读取客户端发出的指令,并将指令写到共享内存。

4、网络编程(socket套接字)

通过Socket网络编程,开发板运行服务端,上位机运行客户端。实现Socket客户端发送指令远程打开和关闭垃圾桶。

5、进程间通信(共享内存和信号量)

父子进程间的通信通过共享内存来完成,信号量用于实现进程间的互斥与同步。

                                                             图1 cmd指令流向图

三、开发环境

1、硬件:

Orangepi Zero2 全志H616开发板,超声波测距模块,蜂鸣器,sg90舵机,一台电脑

2、软件:

MobaXterm、Ubuntu linux操作系统(虚拟机)

3、引脚分配:

在MobaXterm命令控制终端输入gpio readall可以查看开发板上的所有引脚。蜂鸣器、超声波测距和sg90舵机的引脚接线在下图框出。

图2 引脚分配

四、代码

1、服务端代码:server.c

#include <stdio.h>
#include <sys/time.h>
#include <wiringPi.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>      
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
 
#define Trig 9
#define Echo 10
#define BEEP 0 //设置针脚0为蜂鸣器的控制引脚
#define SG90Pin 6
#define OPEN  1
#define CLOSE 2

static int curAngle;//当前角度
static int prevAngel;//上一个角度
static int i 			    = 0;
static int j 			    = 5;
static double distance 		= 0;
static int 	  openCnt 		= 0;
static int    openSocket 	= 0;
static int    closeSocket 	= 0;
char buf[128];

struct Msg
{
    int type;
    char cmd[128];
};

/**************信号量**************/
union semun {
    int              val;    /* Value for SETVAL */
    struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
    unsigned short  *array;  /* Array for GETALL, SETALL */
    struct seminfo  *__buf;  /* Buffer for IPC_INFO */
};

void pGetKey(int id)
{
	struct sembuf set;
	set.sem_num = 0;//信号量编号 不写也可以 默认是0
	set.sem_op = -1;//把钥匙 -1
	set.sem_flg=SEM_UNDO;//设置为等待
	semop(id, &set, 1);//在semop函数中取钥匙
	//printf("getkey\n");
}

void vPutBackKey(int id)
{
	struct sembuf set;
	set.sem_num = 0;
	set.sem_op = 1;//把钥匙 +1 (锁的数量+1)
	set.sem_flg=SEM_UNDO;
	semop(id, &set, 1);
	//printf("put back the key\n");
}

/**************服务器控制指令**************/
void *socketCmd(){
	//父进程 共享内存和信号量创建
	int shmid;
	int semid;
	char *shmaddr;//指向共享内存
	int retCmp = 0;

	//key是16进制整数,读写的key相同就会访问同一个共享内存 
	key_t key1;
	key_t key2;
	key1 = ftok(".",1);//“."为当前路径 key值由ftok中两个参数确定,与shmr.c中的key值相同,shmid也相同
	key2 = ftok(".",2);
	
	//共享内存 存储空间大小以 兆为单位对齐
	//创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
	//1兆,可读可写的权限
	shmid = shmget(key1,1024*1,IPC_CREAT|0666);
	if(shmid == -1){
		printf("shmget fail\n");
		exit(-1);
	}
	//shmat 共享内存映射:将共享内存挂载到进程的存储空间(连接共享内存到当前进程的地址空间)
	//成功返回0,失败返回-1
	//获取的共享内存ID,0为 linux内核为我们自动安排共享内存,0为映射进来的共享内存为可读可写
	//void *shmat(int shmid, const void *shmaddr, int shmflg);
	shmaddr = shmat(shmid,0,0);//参数 0 0默认即可
	 
	//信号量集合中有一个信号量  1为信号量集中信号量的个数
	semid = semget(key2, 1, IPC_CREAT|0666);//获取/创建信号量
	union semun initsem;
	initsem.val = 1;//一开始无锁的状态(无钥匙);若是1,则有锁 
	//0代表 操作第0个信号量 ,只有1个信号量 ,SETVAL是设置信号量的初值
	semctl(semid, 0, SETVAL, initsem);//初始化信号量
                  //SETVAL设置信号量的值,设置为inisem
		
	while(1){
		//初始化作用:
		//(1)避免程序多次运行,shmaddr是同一个内存空间,这样初始值为open/close
		//(2)执行open开盖5s后,客户端再输入open,仍可执行
		pGetKey(semid);
		strcpy(buf, "\0");
		strcpy(shmaddr, "\0");
		vPutBackKey(semid);
		
		strcpy(buf, shmaddr);
		usleep(500000);//每隔0.5s检查
		retCmp = strcmp(buf, shmaddr);//返回值为0 表示buf和shmaddr内容相同
		if(retCmp != 0){//客户端输入了指令 open or close
			if(!strcmp("open", shmaddr)){//open 开盖5s
				openSocket = 1;
				printf("*******cmd open******\n");
			}
			if(!strcmp("close", shmaddr)){//close
				if(openSocket == 1){//在客户端执行open指令前提下(5s内),再执行close
					closeSocket = 1;
					printf("*******cmd close******\n");
				}			
			}//只有以上两种情况,if语句中的表达式 更具可读性,故第二种情况不用else
		}	
	}
	//销毁锁
	semctl(semid,0,IPC_RMID);
	//卸载(断开)共享内存(退出连接):成功返回0,失败返回-1
	shmdt(shmaddr);
	//将共享内存释放:成功返回0,失败返回-1
	shmctl(shmid, IPC_RMID, 0);//保持默认
}



/******************蜂鸣器******************/
void beepInit(){
	pinMode(BEEP, OUTPUT);//设置IO口的输入输出,输出
	digitalWrite(BEEP, HIGH);
}

void beepOn(){
    digitalWrite(BEEP, LOW);  // low输出低电平,蜂鸣器响
}

void beepOff(){
    digitalWrite(BEEP, HIGH);  // high输出高电平,蜂鸣器不响
}

/******************超声波******************/
void ultrasonicInit(){
    pinMode(Trig, OUTPUT);
    pinMode(Echo, INPUT);
}

double getDistance(){
    double dis;
    struct timeval start;
    struct timeval stop;
    //pinMode(Trig, OUTPUT);
    //pinMode(Echo, INPUT);
    digitalWrite(Trig ,LOW);
    usleep(5);
    digitalWrite(Trig ,HIGH);
    usleep(10);
    digitalWrite(Trig ,LOW);

	//超声波未发出前,1号引脚一直处于低电平0,一但发出声波则高电平,跳出循环
    while(!digitalRead(Echo));
    gettimeofday(&start,NULL);
	//超声波回来瞬间,Echo引脚变为低电平,跳出循环
	while(digitalRead(Echo));
    gettimeofday(&stop,NULL);
	
	//秒*10^6转换成微秒
    long diffTime = 1000000*(stop.tv_sec-start.tv_sec)+(stop.tv_usec -start.tv_usec);
    //printf("diffTime = %ld\n",diffTime);
    dis = (double)diffTime/1000000 * 34000 / 2;//单位厘米
    return dis;
}

void *ultrasonic(){
	while(1){
		usleep(500000);//每0.5s计算一次距离
		distance = getDistance();
		//printf("distance = %lf cm\n",distance);
	}
}

/*******************舵机******************/
void signal_handler(int signum)
{
	//curAngle: 1-0° 2-45° 3-90° 4-135°
	if(i <= curAngle){
		digitalWrite(SG90Pin, HIGH);
	}else{
		digitalWrite(SG90Pin, LOW);
	} 
	if(i == 40){
		i = 0;
	} 
	i++;
}

void sg90Init(){
	struct itimerval itv;
	curAngle = 4;//角度初始化 关盖
	prevAngel = 4;
	pinMode(SG90Pin, OUTPUT);
	//设定定时时间,每500um触发一次SIGALRM信号
	itv.it_interval.tv_sec = 0;
	itv.it_interval.tv_usec = 500;
	//设定开始生效,启动定时器的时间
	itv.it_value.tv_sec = 1;
	itv.it_value.tv_usec = 0;
	//设定定时方式
	if( -1 == setitimer(ITIMER_REAL, &itv, NULL)){
		perror("error");
		exit(-1);
	} 
	//信号处理
	signal(SIGALRM, signal_handler);
}

void openLid(){
	curAngle = 1;
	if(curAngle != prevAngel){//条件成立:关盖->开盖
		beepOn();usleep(200000);
		beepOff();
	}
	prevAngel = curAngle;
	usleep(20000);
}

void closeLid(){
	curAngle = 4;
	if(curAngle != prevAngel){//条件成立:开盖->关盖
		beepOn();usleep(200000);
		beepOff();usleep(100000);
		beepOn();usleep(200000);
		beepOff();
	}
	prevAngel = curAngle;
	usleep(20000);
}

void *sg90(){
	while(1){
		j = 5;
		usleep(20000);
		if(distance < 10.0 || openSocket == 1){
			//printf("=====开盖=====\n");
			if(openSocket == 1){//开盖5s
				openLid();
				while(j--){//j=5 相当于sleep(5)
					sleep(1);	
					if(closeSocket == 1){//客户端发送了close指令
						goto closeInterrupt;
					}
				}
				openSocket = 0;			
			}
			else{
				openLid();
				sleep(2);
				openCnt++;
				if(openCnt == 5){//连续开盖到达10秒,报警
					while(distance < 10.0){
						beepOn();
						usleep(200000);
						beepOff();
						usleep(100000);
					}
				}
			}			
		}
		else{
			//printf("=====关盖=====\n");
		closeInterrupt:
			openSocket = 0;
			closeSocket = 0;
			openCnt = 0;
			closeLid();	
		}
	}
}

void deviceInit(){
	// == -1 说明库的初始化失败
    if(wiringPiSetup() == -1){
        fprintf(stderr,"%s","initWringPi error");
        exit(-1);
    }
	
	beepInit();
    ultrasonicInit();
    sg90Init();
	sleep(1);
	/*线程创建成功后,线程ID存放在此
	pthread_t ultrasonicID;
	pthread_t sg90ID;

	wiringPi库下的线程创建
int piThreadCreate (void *(*fn)(void *)){
	pthread_t myThread ;
	return pthread_create (&myThread, NULL, fn, NULL) ;
}
*/	
	int retUltrasonic = piThreadCreate(ultrasonic);	
	int retSg90 = piThreadCreate(sg90);
	int retSocketCmd = piThreadCreate(socketCmd);
}

/***********运行格式 sudo ./server IP地址 端口号************/
int main(int argc, char **argv)
{
	int s_fd;
	int c_fd;
	int n_read;
	struct sockaddr_in s_addr;
	struct sockaddr_in c_addr;
	struct Msg msg;
	
	if(argc != 3){
		printf("The number of parameters does not match\n");
		exit(-1);
	}

	deviceInit();
	memset(&s_addr,0,sizeof(struct sockaddr_in));
	memset(&c_addr,0,sizeof(struct sockaddr_in));
	
	//1.socket
	s_fd = socket(AF_INET, SOCK_STREAM, 0);
	if(s_fd == -1){
		perror("socket");
		exit(-1);
	}
	//将协议类型,IP地址,端口号信息放在结构体重
	s_addr.sin_family = AF_INET;
	s_addr.sin_port = htons(atoi(argv[2]));//ASCII 转换成 int
	inet_aton(argv[1],&s_addr.sin_addr);
	
	//2. bind
	bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
	
	//3. listen
	listen(s_fd,10);
	
	//4. accept
	int clen = sizeof(struct sockaddr_in);
	
	while(1){
		c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);
		if(c_fd == -1){
			perror("accept");
		}
		printf("get connect: %s\n",inet_ntoa(c_addr.sin_addr));
		if(fork() == 0){	
			//子进程 获取共享内存和信号量
			int shmid;
			int semid;
			char *shmaddr;//指向共享内存
			key_t key1;
			key_t key2;
			
			key1 = ftok(".",1);//“."为当前路径 key值由ftok中两个参数确定,与shmr.c中的key值相同,shmid也相同
			key2 = ftok(".",2);
			shmid = shmget(key1,1024*1,0);
			if(shmid == -1){
				printf("shmget fail\n");
				exit(-1);
			}
			shmaddr = shmat(shmid,0,0);//参数 0 0默认即可
			
			semid = semget(key2, 1, IPC_CREAT|0666);	
			union semun initsem;
			initsem.val = 1;//一开始无锁的状态(无钥匙);若是1,则有锁
			semctl(semid, 0, SETVAL, initsem);

			while(1){
				memset(msg.cmd, 0, sizeof(msg.cmd));
				n_read = read(c_fd, &msg, sizeof(msg));	
				if(n_read == 0){
					printf("client out\n");
					break;
				}else if(n_read > 0){
					printf("server get msg:%s\n",msg.cmd);
					//msg_cmd_handler(msg);//指令一来就调用函数
					pGetKey(semid);
					strcpy(shmaddr, msg.cmd);//指令放入共享内存
					vPutBackKey(semid);
				}
			}
			shmdt(shmaddr);
		}
	}
	close(c_fd);
	close(s_fd);
	return 0;
}

2、客户端代码:client.c

#include <stdio.h>
#include <sys/types.h>    
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>

#define OPEN  0
#define CLOSE 1
#define QUIT 2
#define ERROR 3

struct Msg
{
    int type;
    char cmd[128];
};

int get_cmd_type(char *cmd)
{
	if(!strcmp("open",cmd))    return OPEN;
	if(!strcmp("close",cmd))   return CLOSE;
	if(!strcmp("quit",cmd))   return QUIT;
	return ERROR;
}

void msg_handler(struct Msg msg, int c_fd)
{

	switch(get_cmd_type(msg.cmd)){
		case OPEN:
		case CLOSE:
			write(c_fd,msg,sizeof(msg));;
			break;
		case QUIT:
			exit(1);
		case ERROR:
			printf("wrong cmd\n");
			break;
	}
}

/***********运行格式 sudo ./client IP地址 端口号************/
int main(int argc, char **argv)
{
	int c_fd;
	int n_read;
	struct sockaddr_in c_addr;
	struct Msg msg;

	memset(&c_addr,0,sizeof(struct sockaddr_in));

	if(argc != 3){
		printf("The number of parameters does not match\n");
		exit(-1);
	}

	//1. socket
	c_fd = socket(AF_INET, SOCK_STREAM, 0);
	if(c_fd == -1){
		perror("socket");
		exit(-1);
	}
	c_addr.sin_family = AF_INET;
	c_addr.sin_port = htons(atoi(argv[2]));
	inet_aton(argv[1],&c_addr.sin_addr);

	//2.connect	
	if(connect(c_fd, (struct sockaddr *)&c_addr,sizeof(struct sockaddr)) == -1){
		perror("connect");
		exit(-1);
	}
	
	while(1){
		//子进程 写指令
		if(fork()==0){
			while(1){
				memset(msg.cmd,0,sizeof(msg.cmd));
				msg.type = 1;
				printf(">");
				gets(msg.cmd);//open close alarm
				msg_handler(msg,c_fd);
			}
		}
		
		//父进程读服务端的数据 
		while(1){
		//	memset(msg,0,sizeof(msg));
			n_read = read(c_fd, &msg, sizeof(msg));
			if(n_read == 0){
				printf("server is out,quit\n");
				exit(-1);
			}		
			printf("\n%s\n",msg.cmd);
			//printf("nread=%d\n",n_read);		
		}
	}
	return 0;
}

五、编译和运行

编译服务端和客户端代码:

gcc server.c -lwiringPi -lwiringPiDev -lpthread -lm -lcrypt -lrt -o server
gcc client.c -lwiringPi -lwiringPiDev -lpthread -lm -lcrypt -lrt -o client

运行服务端和客户端代码:

sudo ./server 127.0.0.1 9999
sudo ./client 127.0.0.1 9999

需要注意的是,开发板与上位机需要同一网段才可通信,在运行程序前先配置好ip地址。

六、视频功能展示

全志H616垃圾桶功能展示

猜你喜欢

转载自blog.csdn.net/qq_43460230/article/details/131604233