本周我们做了智能清洁桶项目,该项目主要实现以下功能
避障小车模式:
1.避障移动
清洁桶模式:
1.手伸过去,自动把盖子打开
2.屏幕显示状态
3.检测湿温度
4.远程控制,tcpip发送控制命令
涉及到的模块有:
1.树莓派主控
2.oled显示屏
3.超声波模块
4.湿温度传感器
5.S90舵机
6.马达和轮子
7.清洁桶
架构:
远程控制服务度:
树莓派
远程控制客户端:
linux虚拟机
模块对应功能
1.oled显示屏
(1)温湿度
(2)时间
(3)树莓派IP
(4) made in zcc
2.超声波模块
1.识别手扔垃圾的动作
2.识别障碍物
3.温湿度传感器
(1).读取当前环境的温湿度
4.SG90舵机
(1)打开清洁桶的盖子
5.马达和轮子
(1)通过热熔胶黏在筒底部
设计到的知识点:
1.多线程及锁
2.tcpip网络编程
3.文件操作-日志模块
以上是需要实现的具体功能,接下来我描述下我具体实现的方法:
1.实现oled显示屏:
初始化oled屏:
unsigned char yi[4][16] = {
"Hello world! ",
" ",
" 172.20.10.10 ",
" made in zcc "
};
const unsigned char zi[];
void init(void)
{
wiringPiSetup();
fd = wiringPiI2CSetup(0x3c);
wiringPiI2CWriteReg8(fd, 0x00, 0xa1);
wiringPiI2CWriteReg8(fd, 0x00, 0xc8);
wiringPiI2CWriteReg8(fd, 0x00, 0x8d);
wiringPiI2CWriteReg8(fd, 0x00, 0x14);
wiringPiI2CWriteReg8(fd, 0x00, 0xa6);
wiringPiI2CWriteReg8(fd, 0x00, 0x21);
wiringPiI2CWriteReg8(fd, 0x00, 0x00);
wiringPiI2CWriteReg8(fd, 0x00, 0x7f);
wiringPiI2CWriteReg8(fd, 0x00, 0xaf);
}
void qingping(){
char zt1,zt2;
for(zt1=0;zt1<8;zt1++)
{
wiringPiI2CWriteReg8(fd,0x00,0xb0+zt1);
for(zt2=0;zt2<128;zt2++) wiringPiI2CWriteReg8(fd,0x40,0x00);
}
}void ascii(void)//显示ASCII码8*16{
int zt;
char zt3,zt4;
for(zt3=0;zt3<4;zt3++)
{
wiringPiI2CWriteReg8(fd,0x00,0xb0+(zt3*2));
for(zt4=0;zt4<16;zt4++)
for(zt=0;zt<8;zt++)
wiringPiI2CWriteReg8(fd,0x40,zi[yi[zt3][zt4]*16+zt]);
wiringPiI2CWriteReg8(fd,0x00,0xb0+(zt3*2)+1);
for(zt4=0;zt4<16;zt4++)
for(zt=0;zt<8;zt++)
wiringPiI2CWriteReg8(fd,0x40,zi[yi[zt3][zt4]*16+zt+8]);
}
}
获取时间:
void shijian(void)//当前时间{
struct tm *ptr;
time_t lt;
lt=time(<);
ptr=localtime(<);
strftime(yi[1],16,"%m/%d %a",ptr); //月/日 周几
strftime(yi[2],16,"%R %p",ptr);//时:分 am或pm
}
温度显示在led屏
void wd(){
temp=wsd();
char tmp[128];
gcvt(temp,4,tmp);
strcpy(yi[3],tmp);
}
void oled(){
init();
qingping();
while(1)
{
shijian();
wd();
ascii();
delay(100);
}
}
温度的实现
float wsd(){
char path[50] = "/sys/bus/w1/devices/";
char rom[20];
char buf[100];
DIR *dirp;
struct dirent *direntp;
int fd =-1;
char *temp;
float value;
system("sudo modprobe w1-gpio");
system("sudo modprobe w1-therm");
if((dirp = opendir(path)) == NULL)
{
printf("opendir error\n");
// return NULL;
}
while((direntp = readdir(dirp))!=NULL){
if(strstr(direntp->d_name,"28-03169779dec0"))
{
strcpy(rom,direntp->d_name);
printf(" rom: %s\n",rom);
}
}
closedir(dirp);
strcat(path,rom);
strcat(path,"/w1_slave");
// while(1)
// {
if((fd = open(path,O_RDONLY)) < 0)
{
printf("open error\n");
// return NULL;
}
if(read(fd,buf,sizeof(buf)) < 0)
{
printf("read error\n");
// return NULL;
}
temp = strchr(buf,'t');
sscanf(temp,"t=%s",temp);
value = atof(temp)/1000;
return value;
sleep(1);
// }
}
超声波模块的实现:
初始化超声波模块:
void initsrc(){
pinMode(Echo,INPUT);
pinMode(Trig,OUTPUT);
pinMode(7,OUTPUT);
}
超声波模块实现的具体算法:
float disinitcsb(){
struct timeval t1;
struct timeval t2;
float dis;
long start,exit;
digitalWrite(Trig,LOW);
delayMicroseconds(2);
digitalWrite(Trig,HIGH);
delayMicroseconds(10);
digitalWrite(Trig,LOW);
while(!(digitalRead(Echo)==1));
gettimeofday(&t1, NULL);
while (!(digitalRead(Echo)==0));
gettimeofday(&t2,NULL);
start=t1.tv_sec*1000000 + t1.tv_usec;
exit=t2.tv_sec*1000000 + t2.tv_usec;
dis = (float)(exit - start)/1000000 * 34000/2;
return dis;
}
void csb(){
float dis=0;
wiringPiSetup();
initsrc();
while(1){
dis=disinitcsb();
printf("dis=%0.2f\n",dis);
delay(1000);
}
}
识别手扔垃圾动作:
手扔垃圾的操作主要是舵机和超声波模块的结合,当超声波模块的dis值小于20时,说明手已靠近超声波模块,这时候执行舵机操作。
舵机操作实现步骤如下:
初始化舵机模块:
int pwmPin = 1;
void initdj(){
wiringPiSetup();
pinMode (pwmPin, PWM_OUTPUT);
pwmSetMode (PWM_MODE_MS);
pwmSetRange (2000);
pwmSetClock (192);
}
实现舵机模块:
void dj(void *arg){
pwmWrite(pwmPin, 155);
delay(1000);
pwmWrite(pwmPin, 200);
}
在完成舵机模块的功能后,在超声波模块调用舵机功能则能实现手扔垃圾操作。
while(1){
dis=disinitcsb();
printf("dis=%0.2f\n",dis);
if(dis<20){
dj();
}
delay(1000);
}
识别障碍物:
识别障碍物功能是在垃圾桶走动时遇到障碍物即停下的操作,实现起来其实很简单,首先先实现让轮子跑的功能,实现过程如下:
初始化:
void initqd(){
int i;
// wiringPiSetup();
pinMode(0,OUTPUT);
pinMode(2,OUTPUT);
pinMode(29,OUTPUT);
pinMode(28,OUTPUT);
}
实现向前怕跑的功能:
void go(){
digitalWrite(0,LOW);
digitalWrite(2,HIGH);
digitalWrite(29,HIGH);
digitalWrite(28,LOW);
printf("xiangqian\n");
}
实现向后跑的功能:
void down(){
digitalWrite(0,HIGH);
digitalWrite(2,LOW);
digitalWrite(29,LOW);
digitalWrite(28,HIGH);
}
实现停止功能:
void tz(void *arg){
digitalWrite(0,HIGH);
digitalWrite(2,HIGH);
digitalWrite(29,HIGH);
digitalWrite(28,HIGH);
}
在具有这三个的功能后,就能控制马达的运作,然后我们配合超声波模块实现,实现步骤如下
while(1){
dis=disinitcsb();
printf("dis=%0.2f\n",dis);
if(dis<20){
dj();
}
delay(1000);
}
实现远程控制服务端:
在树莓派建立服务端:
要实现该功能需要在服务端建立与客户端相同的端口号以及接受者来自客户端的IP,具体实现步骤如下:
创建连接:
1.socket 创建
s_fd = socket(AF_INET, SOCK_STREAM, 0);
if (s_fd == -1) {
log_write("socket failed,%d\n", s_fd);
exit(-1);
}
2.bind,获取ip 和 port
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(8990);
// inet_aton("192.168.60.129",&s_addr.sin_addr);
s_addr.sin_addr.s_addr = htonl(INADDR_ANY);
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(8080);
// serveraddr.sin_port=SERVER_PORT;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
ret = bind(s_fd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr>
if (ret < 0) {
log_write("bind failed, %d\n", ret);
return -1;
}
3.listen 启用监听模式
ret = listen(s_fd, 10);
4.accept 接收网络,返回已经完成3次握手的socket
int clen = sizeof(struct sockaddr);
c_fd = accept(s_fd, (struct sockaddr *)&c_addr, &clen);
if (c_fd == -1) {
log_write("accept failed, %d\n", c_fd);
exit(-1);
} else {
log_write("accept connect success!\n"); //连接成功
}
这样即可在服务端建立与客户端的连接,接着在客户端输入要连接的服务端的IP以及相同的端口号,具体实现步骤如下:
//1.socket 创建
c_fd = socket(AF_INET,SOCK_STREAM,0);
if(c_fd == -1){
log_write(“socket failed,%d\n”,c_fd);
exit(-1);
}
//2.获取ip 和 port
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(8080);
inet_aton("172.20.10.10",&serveraddr.sin_addr);
//3.连接服务器
ret = connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockadd>
if(ret < 0){
log_write("connect failed, %d\n",ret);
return -1;
}
这样既能实现服务端与客户端相连,
服务器和客户端项链后我们就要开始做相应的操作了,首先是先建立两个模式:即避障模式和清洁桶模式,实现过程如下
当接受到客户端传来的命令后,首先判断接受到的I的值是否为1,若为1,则进入模式选择,若为0,则不进入是否退出模式。
g_running = 1;
while (g_running) {
// 1. 接收到客户端命令
ret = my_recv(sock, (char *)msg_recv, sizeof(struct Msg));
log_write("recv %d,cmd %d\n",ret,msg_recv->cmd);
if(msg_recv->i==1){
mingling(msg_recv,sock);
}
else{
log_write("recv %d\n", ret);
2. handle cmd处理客户端命令
memset(msg_send, 0, sizeof(struct Msg));
handle_cmd2(msg_recv, msg_send);
3. 发送处理结果给客户端
ret = my_send(sock, (char *)msg_send, sizeof(struct Msg));
log_write("send %d\n", ret);
}
}
当i等于1时,将接受到的内容发送到mingling()函数,mingling()函数如下:
void mingling(struct Msg *in_cmd,int sock){
switch(in_cmd->cmd){
case FTP_CMD_MS1:
ms1(sock);
break;
case FTP_CMD_MS1:
ms2(sock);
break;
}
}
switch命令判断当接受的命令是ms1时,调用ms1函数;ms2时,调用ms2函数。
ms1函数是实现避障模式的函数,具体代码如下:
void ms1(int sock){
struct Msg *msg_recv = NULL;
struct Msg *msg_send = NULL;
msg_recv = (struct Msg *)malloc(sizeof(struct Msg));
msg_send = (struct Msg *)malloc(sizeof(struct Msg));
memset(msg_send,0,sizeof(struct Msg));
memset(msg_recv,0,sizeof(struct Msg));
my_send(sock, (char *)msg_send, sizeof(struct Msg));
while(1){
memset(msg_send,0,sizeof(struct Msg));
memset(msg_recv,0,sizeof(struct Msg));
my_recv(sock, (char *)msg_recv, sizeof(struct Msg));
if(FTP_CMD_GO == msg_recv->cmd){
go();
printf("hello\n");
my_send(sock, (char *)msg_send, sizeof(struct Msg));
log_write("out_cmd->cmd is %d\n",msg_send->cmd);
}
else if(FTP_CMD_DOWN == msg_recv->cmd){
qd1();
my_send(sock, (char *)msg_send, sizeof(struct Msg));
log_write("out_cmd->cmd is %d\n",msg_send->cmd);
}
else if(FTP_CMD_TZ == msg_recv->cmd){
tz();
my_send(sock, (char *)msg_send, sizeof(struct Msg));
log_write("out_cmd->cmd is %d\n",msg_send->cmd);
}
else if(FTP_CMD_QUIT == msg_recv->cmd){
break;
}
}
}
ms2函数如下:
void ms2(int sock){
pthread_t t1;
pthread_t t2;
struct Msg *msg_recv = NULL;
struct Msg *msg_send = NULL;
msg_recv = (struct Msg *)malloc(sizeof(struct Msg));
msg_send = (struct Msg *)malloc(sizeof(struct Msg));
memset(msg_send,0,sizeof(struct Msg));
memset(msg_recv,0,sizeof(struct Msg));
my_send(sock, (char *)msg_send, sizeof(struct Msg));
while(1){
memset(msg_send,0,sizeof(struct Msg));
memset(msg_recv,0,sizeof(struct Msg));
my_recv(sock, (char *)msg_recv, sizeof(struct Msg));
if(FTP_CMD_CSB == msg_recv->cmd){
csb();
my_send(sock, (char *)msg_send, sizeof(struct Msg));
log_write("out_cmd->cmd is %d\n",msg_send->cmd);
}
else if(FTP_CMD_QUIT == msg_recv->cmd){
break;
}
}
}
即可实现清洁桶和避障两个模式
接着我总结一下就是在这次实验中遇到的一些要思考的问题:例如,在我执行清洁桶模式时,要怎么实现显示屏和舵机一起实现的功能,即实验两个线程同时执行即能实现。还有就是遇障停止的问题,即给csb模块和go模块上锁,在遇障时,将go线程处于wait模式,执行csb模块的dj功能,当dis值>20时,则将dj模块阻塞,进入go线程。我在这次项目中遇到的主要问题就是由于我多次调用函数实现功能,没有注意到树莓派被不停初始化,导致部分功能无法实现,这是我主要需要注意的点。