中国有句古话叫:“凡事谋定而后动”。我们在编写程序之前同样要认真的“谋划”一下。先来在脑子里想想我们要做的这个自动躲避障碍小车都要实现哪些功能模块,然后再逐个的用程序来实现它们。其实也很简单,小车程序主要有4个模块构成:网络服务模块、引擎模块、电机驱动模块和测距模块:
1、网络服务模块:主要功能为创建一个网络服务,使用12003端口(端口号可以是任意的)来接收手机端的命令。当服务启动后会以阻塞方式等待网络数据包,当有来自网络的数据包时,服务器将读取数据包的内容,如果数据包格式正确则,执行用户命令;如果格式错误则放弃此数据包。
2、引擎模块:是负责小车行驶的核心模块。它向外部提供接口,可以接收用户对小车所发送的命令。同时它也负责分析用户命令,根据用户命令计算出小车目前行驶时左右侧电机的速度值,并调用驱动程序接口对小车电机进行调速。另外引擎模块中还需要实现自动行驶功能,并通过调用测距驱动来判断前方障碍物的距离,根据距离来进行自动躲避。
3、电机驱动模块:即为L298N芯片的驱动程序,可以使小车左右侧电机向前、向后、停止、调速等。
4、测距驱动模块:即为HC-SR04声波测距芯片的驱动程序,可以测量出前方障碍物与其之间的距离。
下面我们开始编写小车程序,首先定义一些常用的数据类型:
#define true (1) #define false (0) #define null ((void *)0) typedef unsigned char boolean; typedef char s8; typedef unsigned char u8; typedef signed short s16; typedef unsigned short u16; typedef signed int s32; typedef unsigned int u32; typedef signed long long s64; typedef unsigned long long u64;
首先我们要实现一个网络服务程序。当然我们只需要一部手机就可以摇控我们的玩具小车,所以我们需要实现一个“循环服务器”即可实现这个功能,但是下面代码中所实现的是一个多任务并发服务器(这是我在另外一个项目中开发的并发型服务器,为了节省时间而直接拿过来使用的),这对我们的“谋划”其实并没有什么太大的影响。下面我们来看一下Linux中用C语言编写网络服务程序的过程:
注意,流程图中只有开始,没有结束。我们希望小车在加电后启动树莓派的操作系统并且执行服务程序后不再退出,因为小车的所有动作均来源于用户命令,用户需要实时通过手机端向小车发送命令,所以服务程序是一个永循环,不结束。下面我们来看一下服务模块的程序代码:
boolean server_start() { //允许新客户端接入信号量 sem_init(&server.sem_new_accept, 0, config.max_accept_count); //套接字 server.socket_descriptor = socket(AF_INET, SOCK_STREAM, 0); //服务地址与端口 memset(&server.servaddr, 0, sizeof(server.servaddr)); server.servaddr.sin_family = AF_INET; server.servaddr.sin_addr.s_addr = htonl(INADDR_ANY); server.servaddr.sin_port = htons(config.server_port); //绑定套接字 int bind_status = bind(server.socket_descriptor, (struct sockaddr*) &server.servaddr, sizeof(server.servaddr)); //开始监听 int listen_id = listen(server.socket_descriptor, config.max_client_count); if (listen_id == -1) { printf("[ ERROR ] Listen host connection.\n"); return false; } //循环创建新的空闲连接 while (true) { //最多允许接入新客户端数 sem_wait(&server.sem_new_accept); //创建新的等待连接 pthread_create(&server.thread_conn, null, (void*) &server_create_connection, null); } printf("Server Stop.\n"); return true; } //创建新的等待连接 boolean server_create_connection() { //等待接入 int socket = -1; for (int i = 0; socket <= 0 && i < SOCLET_NONBLOCK_COUNT; i++) { socket = accept(server.socket_descriptor, (struct sockaddr*) NULL, NULL); usleep(SOCLET_NONBLOCK_USLEEP); } //一但有客户端接入,释放new_appept信号量,允许创建一个新的空闲连接 sem_post(&server.sem_new_accept); if (socket == -1) { server_close_socket(socket); return false; } pthread_mutex_lock(&mlock); list_insert(&list, (void *) pthread_self(), (void *) (u64) socket); pthread_mutex_unlock(&mlock); //控制 ctl_get_data(socket); //关闭套接字 server_close_socket(socket); return true; } //关闭套接字 void server_close_socket(int socket) { close(socket); sem_post(&server.sem_connection); pthread_exit(null); } //控制小车命令 void ctl_get_data(int socket) { s_ctl sctl; int recv_size = -1; for (int i = 0; recv_size < 0 && i < 100; i++) { //读取网络数据 recv_size = recv(socket, &sctl, 1024, 0); usleep(100); } //大小不正确 if (recv_size < sizeof(sctl)) { return; } //模式不正确 if (!(sctl.mode == 0 || sctl.mode == 1)) { return; } //命令不正确 if (!(sctl.ctl_rotate_type == 0 || sctl.ctl_rotate_type == 1 || sctl.ctl_rotate_type == 2)) { return; } //使能半径,速度最大值 if (sctl.ctl_radius > 100) { sctl.ctl_radius = 100; } if (sctl.ctl_radius < 0) { sctl.ctl_radius = 0; } //向引擎发送命令 engine_control_set(sctl.mode, sctl.ctl_direct, sctl.ctl_rotate_type, sctl.ctl_radius); sctl.mode = 0; sctl.ctl_direct = 0; sctl.ctl_rotate_type = 0; sctl.ctl_radius = 0; }
至此,服务器程序部分编写结束,这些代码只是示例,由于篇幅原因不能加入完整的代码,希望朋友们多读多理解。完整代码将在最后提供。
Copyright © 2015-2023 问渠网 辽ICP备15013245号