硬件需求:
树莓派、L9110模块、OLED显示屏、HC-SR04超声波模块、温度传感器、SG90舵机、马达及轮子、清洁桶。
架构:
远程控制服务端:树莓派。
远程控制客户端:linux虚拟机。
原理:
利用到网络编程、多线程、文件操作–日志模块,通过在linux虚拟机上远程控制小车,实现前进,后退,拐弯,避障等功能,利用超声波模块检测手是否靠近,然后通过sg90舵机模块将盖子打开,手拿开让盖子盖下;通过温度传感器检测温度,将温度显示在led模块上。
模块功能实现:
1.oled模块
const unsigned char zi[];
void oled() {
int fd;
unsigned char yi[4][16] = {" ", //第一行
" ", //第二行
" ", //第三行
"made in zyt " //第四行
}; //显示内容
fd = wiringPiI2CSetup(0x3c); // i2c初始化 0x3c是oled的从机地址
wiringPiI2CWriteReg8(fd, 0x00, 0xa1); //图像反了修改成0xa0
wiringPiI2CWriteReg8(fd, 0x00, 0xc8); //行输出反了修改成0xc0
wiringPiI2CWriteReg8(fd, 0x00, 0x8d); //允许电荷泵
wiringPiI2CWriteReg8(fd, 0x00, 0x14);
wiringPiI2CWriteReg8(fd, 0x00, 0xa6); //想反相显示改成0xa7
wiringPiI2CWriteReg8(fd, 0x00, 0x21); //重置列地址
wiringPiI2CWriteReg8(fd, 0x00, 0x00);
wiringPiI2CWriteReg8(fd, 0x00, 0x7f);
wiringPiI2CWriteReg8(fd, 0x00, 0xaf); //开显示
char zt1, zt2;
for (zt1 = 0; zt1 < 8; zt1++) {
wiringPiI2CWriteReg8(fd, 0x00, 0xb0 + zt1);
for (zt2 = 0; zt2 < 128; zt2++) wiringPiI2CWriteReg8(fd, 0x40, 0x00);
}
struct tm *ptr;
time_t lt;
lt = time(<);
ptr = localtime(<);
sprintf(yi[0], "wendu is %f\n", getwendu());
strftime(yi[1], 16, "%m/%d %a", ptr); //月/日 周几
strftime(yi[2], 16, "%R %p", ptr); //时:分 am或pm
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]);
}
}
2.日志模块
void log_open(char *buf) {
fd = fopen(buf, "a+");
if (fd == NULL) {
printf("打开失败\n");
exit(-1);
}
}
void log_write(const char *pszFormat, ...) {
va_list args;
va_start(args, pszFormat);
vfprintf(fd, pszFormat, args);
va_end(args);
fflush(fd);
}
void log_close() {
fclose(fd);
}
3.超声波模块
原理:通过计算超声波从发射到返回的时间从而得出距离,测试距离=(高电平时间声速(340M/S))/2;。
float getdis()
{
pinMode(Trig,OUTPUT);
pinMode(Echo,INPUT);
struct timeval t1;
struct timeval t2;
long start;
long stop;
float dis;
digitalWrite(Trig,LOW);
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;
stop = t2.tv_sec * 1000000 + t2.tv_usec;
dis = (float)(stop - start) / 1000000 * 34000 / 2;
return dis;
}
4.l9110驱动电机模块
原理:4个IO口分别控制两个电机的正反转,若要使小车向前走,即将GPIO1A、2A拉高即可。
void left()
{
digitalWrite(GPIO1A,HIGH);
digitalWrite(GPIO1B,LOW);
digitalWrite(GPIO2A,LOW);
digitalWrite(GPIO2B,LOW);
}
void right()
{
digitalWrite(GPIO1A,LOW);
digitalWrite(GPIO1B,LOW);
digitalWrite(GPIO2A,HIGH);
digitalWrite(GPIO2B,LOW);
}
void back()
{
digitalWrite(GPIO1A,LOW);
digitalWrite(GPIO1B,HIGH);
digitalWrite(GPIO2A,LOW);
digitalWrite(GPIO2B,HIGH);
}
void ahead()
{
digitalWrite(GPIO1A,HIGH);
digitalWrite(GPIO1B,LOW);
digitalWrite(GPIO2A,HIGH);
digitalWrite(GPIO2B,LOW);
}
void initxiaoche()
{
digitalWrite(GPIO1A,LOW);
digitalWrite(GPIO1B,LOW);
digitalWrite(GPIO2A,LOW);
digitalWrite(GPIO2B,LOW);
}
5.温度传感器
从/sys/bus/w1/devices/28-031697799236/w1_slave 文件中读取温度的值。
float getwendu() {
FILE *fd;
float temp;
char *p;
char readbuf[256];
fd = fopen("/sys/bus/w1/devices/28-031697799236/w1_slave", "r");
if (fd == NULL) {
printf("fopen wendu failed\n");
return -1;
}
if (fread(readbuf, sizeof(readbuf), 1, fd) < 0) {
printf("fread wendu failed\n");
return -1;
}
p = strstr(readbuf, "t=");
if (p == NULL) {
printf("find \"t=\" failed\n");
return -1;
}
p = p + 2;
temp = atof(p) / 1000;
// printf("wendu is %0.3f\n",temp);
// delay(1000);
fclose(fd);
return temp;
}
网络编程相关代码:
int mark;
int fd;
struct sockaddr_in addr;
fd = socket(AF_INET,SOCK_STREAM,0);
if(fd < 0)
{
log_write("server socket failed\n");
return -1;
}
memset(&addr,0,sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = SERVER_PORT;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
int on = 1;
setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
int b;
b = bind(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
if(b<0)
{
log_write("server bind failed\n");
return -1;
}
listen(fd,10);
mark = accept(fd,NULL,NULL);
if(mark < 0)
{
log_write("accept failed\n");
return -1;
}else{
log_write("accept success\n");
}
struct sockaddr_in addr;
int mark;
int fd;
fd = socket(AF_INET,SOCK_STREAM,0);
if(fd == -1)
{
log_write("cilent socket failed\n");
}
memset(&addr,0,sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = SERVER_PORT;
inet_aton("172.20.10.11",&addr.sin_addr);
mark=connect(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
if(mark < 0)
{
log_write("cilent connect failed\n");
}
线程创建:
int crt;
pthread_t t1;
pthread_t t2;
pthread_t t3;
crt = pthread_create(&t1,NULL,led,(void *)&mark);
if(crt < 0)
{
log_write("create t1 failed\n");
return -1;
}
crt = pthread_create(&t2,NULL,duoji,(void *)&mark);
if(crt < 0)
{
log_write("create t2 failed\n");
return -1;
}
crt = pthread_create(&t3,NULL,dianji,(void *)&mark);
if(crt < 0)
{
log_write("create t3 failed\n");
return -1;
}
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_join(t3,NULL);
void *led()
{
while(1)
{
oled();
delay(5000);
}
}
void *duoji ()
{
int pin = 1;
int i;
softPwmCreate (pin, 5, RANGE);
while(1)
{
if(getdis()<10)
{
softPwmWrite(pin,25);
delay(1000);
}
if(getdis()>10)
{
softPwmWrite(pin,5); //将pwm输出复写为使舵机转到0
delay(1000);
}
}
exit(0);
}
void *dianji(void *arg)
{
int data = *(int *)arg;
char *buf = malloc(32);
while(1)
{
recv(data,buf,sizeof(buf),0);
log_write("read a from data :%s",buf);
switch (*buf)
{
case 'w': ahead();
break;
case 's': back();
break;
case 'a': left();
break;
case 'd': right();
break;
case 'q': initxiaoche();
break;
default:
break;
}
memset(buf,0,sizeof(buf));
}
}
遇到的问题:
在编程时,L9110模块驱动电机这一部分给我造成了很大的困扰,即只能驱动两个电机正转反转,而不能驱动单个轮子转动,在排查了一系列问题之后,初步判断是L9110模块的问题。
多线程的原理:
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序文本、程序的全局内存和堆内存、栈以及文件描述符。 典型的UNIX/Linux进程可以看成只有一个控制线程:一个进程在同一时刻只做一件事情。有了多个控制线程后,在程序设计时可以把进程设计成在同一时刻做不止一件事,每个线程各自处理独立的任务。
相关API