在2019年暑假,大一的我参加了学校组织的为期15天的实训,实训内容就是树莓派小车的基本功能的实现,包括移动,拍照,录像等。
下面我将对本次实训经行一个简单的回顾
首先我们需要一下材料
树莓派,小车零件,杜邦线,电池盒,L298N电机驱动模块,TF卡/SD卡
然后按照电路图将小车的零部件一个一个组装起来即可
唯一需要注意的是引脚的对应
如图所示,程序里的引脚的标号为黄色框里的编号,横向对应红色框里的物理接口
下图为组装好的小车
那么要如何实现电脑端与小车的通信呢,这时候就需要用到socket编程了,它可以通过IP地址实现两地之间的通信
套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合
首先我们要实现最基本的本地通信
下面附上最基本的本地通信的代码
客户端:
#include <iostream>
#include<cstdio>
#include<string.h>
#include<WINSOCK2.H>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
int initser(int port,char*ip,int* psockFd)
{
WORD sockVersion = MAKEWORD(2, 2);
WSADATA wsaData;
if (WSAStartup(sockVersion, &wsaData) != 0)
{
return 0;
}
int sockFd = socket(AF_INET, SOCK_STREAM, 0);
if (sockFd < 0)
{
printf("socket error\n");
return -1;
}
else
printf("create socket success\n");
struct sockaddr_in serAddr;
memset(&serAddr, 0, sizeof(serAddr));
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(port);
serAddr.sin_addr.S_un.S_addr = inet_addr(ip);
int res = connect(sockFd, (struct sockaddr*) & serAddr, sizeof(serAddr));
if (sockFd < 0)
{
printf("connect error\n");
return -1;
}
else
printf("connect success\n");
*psockFd = sockFd;
}
int main()
{
int sockFd = 0;
char ip[100] = { "192.168.101.63" };//这里填自己本机所使用的IP地址
initser(5000, ip, &sockFd);//端口的数字可以自己决定,不过要保证客户端与服务器一致且一般较大
char buf[100] = { 0 };
gets_s(buf);
int res = send(sockFd, buf, sizeof(buf), 0);//发送信息
if (res < 0)
{
printf("send err\n");
return -1;
}
printf("send data success [%s]\n", buf);
closesocket(sockFd);
return 0;
}
服务器:
#include <iostream>
#include <stdio.h>
#include <WinSock2.h>
using namespace std;
//使用ws2_32.lib静态库
#pragma comment(lib,"ws2_32.lib")
int initSer(int port, int* psockFd)
{
WORD sockVersion = MAKEWORD(2, 2);
WSADATA wsadata;
if (WSAStartup(sockVersion, &wsadata) != 0)
{
return 0;
}
int sockFd = socket(AF_INET, SOCK_STREAM, 0);
if (sockFd < 0)
{
cout << "socket fail!" << endl;
return -1;
}
else
cout << "socket success!" << endl;
struct sockaddr_in serAddr;
memset(&serAddr, 0, sizeof(serAddr));
serAddr.sin_family = AF_INET;
serAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//让操作系统指派IP,即目前使用的IP
serAddr.sin_port = htons(port);
int res = bind(sockFd, (struct sockaddr*) & serAddr, sizeof(serAddr));
if (res < 0)
{
cout << "bind fail!" << endl;
return -1;
}
else
cout << "bind success!" << endl;
res = listen(sockFd, 10);
if (res < 0)
{
cout << "listen fail!" << endl;
return -1;
}
else
cout << "listen success!" << endl;
int newsockFd = accept(sockFd, NULL, NULL);
if (sockFd < 0)
{
cout << "accept fail!" << endl;
return -1;
}
else
cout << "accept success!" << endl;
*psockFd = newsockFd;
return 0;
}
int main()
{
int sockFd = 0;
initSer(5000, &sockFd);//端口的数字可以自己决定,不过要保证客户端与服务器一致且一般较大
char buf[100] = { 0 };
int res = recv(sockFd, buf, sizeof(buf), 0);//接收信息
if (res < 0)
{
printf("recv err\n");
return -1;
}
printf("recv data success [%s]\n", buf);
closesocket(sockFd);
return 0;
}
上面的代码虽然能实现通信,但是只能通信一次,就算加了while也只能按照一定的规律来通信,所以我们这里为了实现随发随收,就需要加上多线程
只需将发送与接收写在两个不同的线程中即可
下面附上代码
客户端:
#include <iostream>
#include<cstdio>
#include<string.h>
#include<WINSOCK2.H>
#pragma comment(lib,"ws2_32.lib")
#define PORT 5000
using namespace std;
HANDLE hMutex = NULL;
DWORD WINAPI Fun1(LPVOID lpparamter)
{
int newsockFd = *(int*)lpparamter;
while (1)
{
char buf[100] = { 0 };
memset(buf, '\0', sizeof(buf));
int res = recv(newsockFd, buf, 100, 0);//接收
if (res < 0)
{
cout << "recv fail!" << endl;
return -1;
}
else
printf("receive message[%s]\n", buf);
}
return 0L;
}
DWORD WINAPI Fun2(LPVOID lpparamter)
{
int newsockFd = *(int *)lpparamter;
while (1)
{
char buf[100] = { 0 };
gets_s(buf);
int res = send(newsockFd, buf, sizeof(buf), 0);//发送
if (res < 0)
{
printf("send err\n");
return -1;
}
printf("send success [%s]\n", buf);
}
return 0L;
}
int main()
{
WORD sockVersion = MAKEWORD(2, 2);
WSADATA wsaData;
if (WSAStartup(sockVersion, &wsaData) != 0)
{
return 0;
}
int sockFd = socket(AF_INET, SOCK_STREAM, 0);
if (sockFd < 0)
{
printf("socket error\n");
return -1;
}
else
printf("create socket success\n");
struct sockaddr_in serAddr;
memset(&serAddr, 0, sizeof(serAddr));
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(PORT);
serAddr.sin_addr.S_un.S_addr = inet_addr("172.16.13.62");
int res = connect(sockFd, (struct sockaddr*) & serAddr, sizeof(serAddr));
if (sockFd < 0)
{
printf("connect error\n");
return -1;
}
else
printf("connect success\n");
//创建线程
HANDLE hand1 = CreateThread(NULL, 0, Fun2, &sockFd , 0, NULL);
HANDLE hand2 = CreateThread(NULL, 0, Fun1, &sockFd , 0, NULL);
WaitForSingleObject(hand1, INFINITE);
closesocket(sockFd);
return 0;
}
服务器:
#include <iostream>
#include <WinSock2.h>
using namespace std;
#define PORT 5000
//使用ws2_32.lib静态库
#pragma comment(lib,"ws2_32.lib")
HANDLE hMutex = NULL;
DWORD WINAPI Fun1(LPVOID lpparamter)
{
int newsockFd = *(int*)lpparamter;
while(1)
{
char buf[100] = { 0 };
memset(buf, '\0', sizeof(buf));
int res = recv(newsockFd, buf, 100, 0);//接收
if (res < 0)
{
cout << "recv fail!" << endl;
return -1;
}
else
printf("receive message[%s]\n", buf);
}
return 0L;
}
DWORD WINAPI Fun2(LPVOID lpparamter)
{
int newsockFd = *(int*)lpparamter;
while(1)
{
char buf[100] = { 0 };
gets_s(buf);
int res = send(newsockFd, buf, sizeof(buf), 0);//发送
if (res < 0)
{
printf("send err\n");
return -1;
}
printf("send success [%s]\n", buf);
}
return 0L;
}
int main()
{
//windows下需要加载的SOCKET库
WORD sockVersion = MAKEWORD(2, 2);
WSADATA wsadata;
if (WSAStartup(sockVersion, &wsadata) != 0)
{
return 0;
}
int sockFd = socket(AF_INET, SOCK_STREAM, 0);
if (sockFd < 0)
{
cout << "socket fail!" << endl;
return -1;
}
else
cout << "socket success!" << endl;
struct sockaddr_in serAddr;
memset(&serAddr, 0, sizeof(serAddr));
serAddr.sin_family = AF_INET;
serAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
serAddr.sin_port = htons(PORT);
int rec = bind(sockFd, (struct sockaddr*) & serAddr, sizeof(serAddr));
if (rec < 0)
{
cout << "bind fail!" << endl;
return -1;
}
else
cout << "bind success!" << endl;
rec = listen(sockFd, 10);
if (rec < 0)
{
cout << "listen fail!" << endl;
return -1;
}
else
cout << "listen success!" << endl;
int newsockFd = accept(sockFd, NULL, NULL);
if (sockFd < 0)
{
cout << "accept fail!" << endl;
return -1;
}
else
cout << "accept success!" << endl;
//创建线程
HANDLE hand1 = CreateThread(NULL, 0, Fun1, &newsockFd, 0, NULL);
HANDLE hand2 = CreateThread(NULL, 0, Fun2, &newsockFd, 0, NULL);
WaitForSingleObject(hand1, INFINITE);
closesocket(newsockFd);
closesocket(sockFd);
return 0;
}
至此就实现了实时的发送接收
然后我们就可以尝试与小车进行通信了
我们首先需要与小车建立连接来将服务器端的代码送进去(这里也可以把小车作为客户端,一样可行,有兴趣的话可以自己去尝试)
这里我们需要下面两个工具来连接小车
https://pan.baidu.com/s/1f7WXga7jDd45v9jeusdQuA 提取码2w7m
首先打开树莓派,连上WIFI
然后打开下载好的Putty在hostname框输入自己本机的IP地址
接着点击OPEN即可
接着输入自己的登陆用户名及密码
也可以在下面的 Saved Sessions里面保存自己的IP地址,方便使用
到这里其实就连上树莓派了
然后可以用FileZilla来经行数据传输
在文件->站点管理器一栏输入自己的IP以及用户名密码然后连接即可
这时就可以对树莓派的内的文件经行操作了
接下来就可以尝试将服务器文件放到树莓派之中
需要注意的是小车内部是linux环境
所以需要做一些修改
首先把下面这段删掉,因为这是WINDOWS下需要加载的库
WORD sockVersion = MAKEWORD(2, 2);
WSADATA wsadata;
还有就是
serAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
改成
serAddr.sin_addr.s_addr = htonl(INADDR_ANY);
然后就是头文件的修改
直接百度找到对应的替换的头文件即可
或者直接用这个
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace cv;
using namespace std;
至此就可以与小车通信了
首先需要在小车里配好WiringPi库和OPENCV库,我的小车是提前装好的,具体操作可以自行百度,同时电脑上也需配好OPENCV库以方便调试
为了方便操作,客户端建议直接用MFC来写
代码我就直接放网盘了
https://pan.baidu.com/s/1wYwt1_OyDaCdooHXbyMfzw 提取码y5y0
其中的OPENCV部分是我同学写的,所以我就不详细展开了。。。
还有在Linux下编译时编译的指令后面还要加上WiringPi来加载WiringPi库
至此就可以控制小车啦
这可以说是我第一次对失误经行操作,以前都是干巴巴的写代码,这也让我再一次找到了编程的乐趣,这样的实训多我来说还是收获颇丰
我的个人博客 amazingz6.github.io
我的CSDN https://blog.csdn.net/qq_44105654
我的简书 https://www.jianshu.com/u/607ef08e5825
我的github https://github.com/AmazingZ6?tab=repositories