目录
一、功能需求
二、涵盖的知识点
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垃圾桶功能展示