树莓派小车基本功能的实现

在2019年暑假,大一的我参加了学校组织的为期15天的实训,实训内容就是树莓派小车的基本功能的实现,包括移动,拍照,录像等。
下面我将对本次实训经行一个简单的回顾

文章目录

  • 1.小车的组装
  • 2.SOCKET编程
  • 3.与小车连接
  • 4.控制小车

1.小车的组装

首先我们需要一下材料
树莓派,小车零件,杜邦线,电池盒,L298N电机驱动模块,TF卡/SD卡
然后按照电路图将小车的零部件一个一个组装起来即可
唯一需要注意的是引脚的对应
树莓派小车基本功能的实现_第1张图片
如图所示,程序里的引脚的标号为黄色框里的编号,横向对应红色框里的物理接口

下图为组装好的小车

树莓派小车基本功能的实现_第2张图片

2.SOCKET编程

那么要如何实现电脑端与小车的通信呢,这时候就需要用到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;
}

至此就实现了实时的发送接收
然后我们就可以尝试与小车进行通信了

3.与小车连接

我们首先需要与小车建立连接来将服务器端的代码送进去(这里也可以把小车作为客户端,一样可行,有兴趣的话可以自己去尝试)
这里我们需要下面两个工具来连接小车
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;

至此就可以与小车通信了

4.控制小车

首先需要在小车里配好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

你可能感兴趣的:(树莓派小车基本功能的实现)