这次做的项目是基于树莓派的智能垃圾桶,整个项目使用了超声波模块、DS18B20温度传感器、L9110S桥两路直流电机驱动板、SG90舵机、oled显示屏,这个项目也加入了网络编程,可以通过远程来控制垃圾桶进行行走。接下来通过各个模块来进行原理说明。
使用超声波模块和SG90舵机模块实现的功能是当我们手碰到垃圾桶的口时,利用超声波来检测,当检测的距离小于所设定的距离后,利用舵机将盖子打开。
超声波原理:之前有通过C51单片机来实现超声波模块,那超声波在树莓派上的使用同样也是一样的,利用超声波在空气中的传播速度为已知,测量声波在发射后遇到障碍物反射回来的时间,根据发射和接收的时间差计算出发射点到障碍物的实际距离。树莓派向Tri脚发送一个10us的脉冲,接收到树莓派发送的脉冲信号,开始发送超声波 ,并把 Echo置为高电平。 然后准备接收返回的超声波。接收到返回的超声波 时,把 Echo 置为低电平。Echo 高电平持续的时间就是超声波从发射到返回所经过的时间间隔 。
舵机原理:当在20ms内,连续地通过gpio模拟输出高电平(即PWM波),在0.5ms~2.5ms时间内,舵机就能线性地转角0度—180度。SG90一共三根线,红线接5V电源,棕线GND。黄线为数据控制线,该线接到GPIO上,这里我们是wiringPI模式下的1端口。
void *chaoshenbo(void *arg){
init_chaoshengbo();
float dis;
int degree;
int i;
degree=(200+5)/180.0*20.0;
while(1){
dis=distance();
if(dis>0&&dis<10){ //距离小于10时候,舵机转动,打开盖子
softPwmCreate(pin,15,RANGE);
delay(1000);
softPwmWrite(pin,5);
delay(2000);
softPwmWrite(pin,degree);
delay(1000);
softPwmWrite(pin,15);
}
}
}
这边的oled代码:https://download.csdn.net/download/qq_30801535/10189091
在这份代码的基础,对oled的代码进行分装了,并加入一个写入代码
void o_write(char *str,int i){ //参数i为行数
sprintf(yi[i],"%s",str);
}
sudo apt-get install -y i2c-tools//安装i2c,来帮我们驱动oled
sudo raspi-config //打开树莓派配置,选择5 Interfacing Options。选择P5 I2C回车激活I2C。
sudo i2cdetect -y 1 //查看oled的i2c地址
void *led(void *arg){
o_init();
char *str="wendu: ";
float temp;
char *buf=(char *)malloc(128);
pthread_create(&t3,NULL,chaoshenbo,NULL); //加入超声波模块功能的线程
log_creat("test.txt");
log_time();
qingping(); //清屏
while (1) {
shijian();//显示时间
temp= get_temp(); //获取温度
sprintf(buf,"Temp:%0.3f",temp);//转化成自付出阿布
o_write(buf,2);//写入
ascii();//ascii转化显示
delay(1000);
}
log_destroy();
}
利用树莓派上的GPIO口来获取传感器所检测的温度。首先要对树莓派进行一些配置。
sudo raspi-config // 进入interfacingoptions,允许单总线接口
enable 1-wire interface
sudo modprobe w1-gpio(加载设备)
sudo modprobe w1-therm(加载设备)
cd /sys/bus/w1/devices/
ls
cd 28-00000xxxxxx //28-00000xxxxxx为温度传感器传感器版本
cat w1_slave//查看所检测的温度
如何通过代码获取温度呢?这里我们要使用到open函数,通过文件的读取来获取温度并显示在我们的显示屏上。
//部分代码
#define BUFSIZE 128
float get_temp(){
float temp;
int fd;
int ret;
int i;
int j;
char buf[BUFSIZE];
char tempbuf[5];
fd=open("/sys/bus/w1/devices/28-03159779c101/w1_slave",O_RDONLY);
if(-1==fd){
log_write("open device file failed,fd=%d\n",fd); //写入日志文件
exit(-1);
}
while(1){
ret=read(fd,buf,BUFSIZE);
if(0==ret){
log_write("read success,ret=%d\n");
break;
}
if(-1==ret){
log_write("read failed,ret=%d\n",ret);
}
}
for(i=0;i
原理:使用Raspberry Pi可轻松实现直流电机。我们使用L9110SQ电机驱动器来控制直流电机,它允许电机向前或向后移动。对IN1、IN2、IN3、IN4引脚进行高低电平的设置来实现电机正向和反向的转动。
//车行走的代码
void *run(void *arg) {
char flag;
int c_fd=*(int *)arg;
int ret;
init();//端口初始化
log_creat("service.txt");
pthread_create(&t2,NULL,led,NULL);//加入显示屏显示的线程
while(1){
ret = my_recv(c_fd, (char *)&flag, sizeof(char));
if (-1 == ret) {
log_write("server recevice failed,ret=%d\n", ret);
} else {
log_write("recevice %d \n", ret);
}
switch (flag) {
case 'w':
stop();
delay(50);
forword();
break;
case 's':
stop();
delay(50);
back();
break;
case 'd':
stop();
delay(100);
right();
break;
case 'a':
stop();
delay(100);
left();
break;
case ' ':
stop();
break;
default:
break;
}
}
log_destroy();
}
这个项目最重要的就是网络编程,我们要编写一个服务端和客户端,树莓派作为服务端,虚拟机作为客户端(这时候的虚拟机和树莓派要在同一个局域网下才可以进行通信),我们通过虚拟机的输入来控制垃圾桶的行走。同时每个功能模块我们将通过多线程来实现。
多线程的原理:指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。
这边我编写的服务器和客户端的接收和发送是以一个字节进行发送和接收,有一定的局限性,只能使用于这个项目,后期会进行改进,当发送w时,车向前走,s-后退,a-左转,d-右转
//服务端构建部分代码
int main() {
int s_fd;
int c_fd;
int ret;
log_creat("service.txt");
log_time();
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
memset(&s_addr, 0, sizeof(struct sockaddr_in));
memset(&c_addr, 0, sizeof(struct sockaddr_in));
s_fd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == s_fd) {
log_write("create socket failed,ret=%d\n", s_fd);
}
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(PORT);//port为端口号8080
s_addr.sin_addr.s_addr = htonl(INADDR_ANY); //任意的ip都可以接入这个服务器
ret = bind(s_fd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr_in));
if (-1 == ret) {
log_write("bind failed,ret=%d\n", ret);
}
ret = listen(s_fd, 10);
if (-1 == ret) {
log_write("listen failed,ret=%d\n", ret);
}
int on=1;
setsockopt(ret,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
while (1) {
int c_len = sizeof(struct sockaddr_in);
c_fd = accept(s_fd, (struct sockaddr *)&c_addr, (socklen_t *)&c_len);
if (-1 == c_fd) {
log_write("accept failed,ret=%d\n", c_fd);
}
printf("get the car ip:%s\n", inet_ntoa(c_addr.sin_addr));
wiringPiSetup();
pthread_create(&t1,NULL,run,(void *)&c_fd);//加入车子行走的线程
}
log_destroy();
return 0;
}
//客户端构建部分代码
int main() {
int c_fd;
char flag;
int ret;
struct sockaddr_in c_addr;
memset(&c_addr, 0, sizeof(struct sockaddr_in));
log_creat("client.txt");
log_time();
c_fd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == c_fd) {
log_write("client connect failed,ret=%d\n", ret);
}
c_addr.sin_family = AF_INET;
c_addr.sin_port = htons(PORT);
inet_aton(SEVERADDR, &c_addr.sin_addr);
ret = connect(c_fd, (struct sockaddr *)&c_addr, sizeof(struct sockaddr));
if (-1 == ret) {
log_write("client connect failed,ret=%d\n", ret);
}
log_destroy();
while (1) {
log_creat("client.txt");
flag=handle();
ret = send(c_fd, (char *)&flag, sizeof(char),0);
if (-1 == ret) {
log_write("client send failed,ret=%d\n");
}else{
log_write("send %d\n",ret);
}
log_destroy();
}
}
在做这次这个项目,虽然说是一个小项目,但是它让我更好掌握了对于嵌入式的一些基本开发。这次的智能垃圾桶是一个基本的软件和硬件相互结合的项目,通过这个项目也更加掌握了对于网络的编程,虽然有些地方还会忘记,后面需要时不时巩固一下