目录
一、功能需求
二、涵盖的知识点
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 2static 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;//信号量编号 不写也可以 默认是0set.sem_op = -1;//把钥匙 -1set.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设置信号量的值,设置为inisemwhile(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 closeif(!strcmp("open", shmaddr)){//open 开盖5sopenSocket = 1;printf("*******cmd open******\n");}if(!strcmp("close", shmaddr)){//closeif(openSocket == 1){//在客户端执行open指令前提下(5s内),再执行closecloseSocket = 1;printf("*******cmd close******\n");} }//只有以上两种情况,if语句中的表达式 更具可读性,故第二种情况不用else} }//销毁锁semctl(semid,0,IPC_RMID);//卸载(断开)共享内存(退出连接):成功返回0,失败返回-1shmdt(shmaddr);//将共享内存释放:成功返回0,失败返回-1shmctl(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){//开盖5sopenLid();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.sockets_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 转换成 intinet_aton(argv[1],&s_addr.sin_addr);//2. bindbind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));//3. listenlisten(s_fd,10);//4. acceptint 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 3struct 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. socketc_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 alarmmsg_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垃圾桶功能展示