基于Zynq-Zybo开发板及rtl8188无线网卡的远程舵机控制系统设计(四)——socket应用开发

回顾

再上一章中,我们进行了rtl8188的驱动编译,以及舵机驱动的编写和编译。有了上一章的成果,就已经搭建好了嵌入式Linux系统的硬件环境。现在,就可以在这个硬件环境下进行软件编程了。

在系统分析中介绍过,本系统通过socket(套接字)来传递舵机的控制信息(旋转角度)。那么,就先介绍一下嵌入式Linux系统下的网络编程原理吧。

Linux网络编程原理

概述

Linux下的网络编程通过socket编程实现。Socket,即套接口,既是一种特殊的I/O,也是一种文件描述符。一个通信实体的socket由一个三元组定义,即:协议族,网络地址和传输层端口。通信双方的一个连接是用网络五元组来标识的,它是由双方相同协议族的两个本地三元组合成的,包括:协议族,本地网络地址、本地端口、远程网络地址和远程端口。

Socket的英文原义是“孔”或“插座”。作为BSD UNIX的进程通信机制,取后一种意思。通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。在Internet上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。Socket正如其英文原义那样,像一个多孔插座。一台主机犹如布满各种插座的房间,每个插座有一个编号,有的插座提供220伏交流电, 有的提供110伏交流电,有的则提供有线电视节目。 客户软件将插头插到不同编号的插座,就可以得到不同的服务。

socket编程原理

下图表示了TCP协议下socket的工作原理和流程:
socket工作流程
从服务器看,socket编程要结果以下步骤:
1.建立套接口
2.将本地端口与另一个端口联系起来
3.建立套接口队列
4.等待连接
5.接收信息
6.关闭套接口

所以,在实际编程中,先建立套接口,这一步骤可以用socket函数来实现,具体如下:

if( (s = socket(AF_INET, SOCK_STREAM, 0)) == -1 )

这段代码中使用到的socket函数的原型为:

int socket (int domain, int type, int protocol);

domain指协议族,type指套接口类型,protocol指对应套接字应使用哪个协议。返回值为套接字句柄。若返回-1,则表明建立失败。这里,AF_INET指用于跨机器间的通信。SOCK_STREAM提供TCP的套接字。在本程序中,用sockaddr_in 类型的参数servaddr来存储本地的协议族,网络地址及端口。下面对其进行初始化:

memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(12345);

其协议族依然为AF_INET。INADDR为0.0.0.0,即本机地址,htonl将主机字节顺序转换 为网络字节顺序(有的机器高为在前,有的低位在前)。在手机端,将通信的端口号设置为12345,故在此处与手机匹配,将端口号也设置为12345。Htons将主机字节转换为网络字节顺序。
接下来使用bind函数将本机的一个端口与套接口连起来,代码如下:

if( bind(s, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){
    
    
	printf("bind socket error: %s(errno:%d)\n",strerror(errno),errno);
	exit(0);
}

bind函数的第一个参数为创立的套接字句柄。第二个参数为存储本机IP地址和端口号的数据结构。第三个参数为第二个参数的长度。若成功调用,返回0,否则返回-1。
接下来用listen函数建立套接口队列,代码如下:

if( listen(s, 10) == -1){
    
    
	printf("listen socket error: %s(errno:%d)\n",strerror(errno),errno);
	exit(0);
}

这里s为套接口的句柄,10表示socket队列的最大连接数。正确调用返回0,否则返回-1。

接下来进行轮询,来检测是否有连接,代码如下:

while(1){
    
    
	if((connfd = accept(s, (struct sockaddr*)NULL, NULL)) == -1){
    
    
		printf("accept socket error: %s(errno: %d)",strerror(errno),errno);
		continue;
	}
	n = recv(connfd, buff, MAXLINE, 0);
	buff[n] = '\0';
	printf("recv msg from client: %s\n", buff);
	close(connfd);
}

首先,调用等待连接函数accept。accept第一个参数为套接口句柄,第二个参数存储客户程序的IP地址及端口号,这里不做限定,故为NULL。第三个参数为第二个参数的长度。Accept函数为阻塞调用。当无连接时,阻塞进程,有连接时,若成功,返回一个新连接的套接口描述句柄,否则返回-1。
连接之后,用recv函数向buff中写入数据。Recv返回值为接收到字符串长度。接收之后在最后加上字符串的结束符。技术之后关闭这个新连接的套接口。
最后关闭开始时建立的套接口s。

socket舵机控制应用介绍

了解完socket编程的原理后,就可以编写“socket接收+舵机控制”的应用了。首先声明,从手机APP发来的socket内包含的是范围从1-180的字符串信息,代表了舵机的旋转角度。在知道这点的情况下,开始进行编程。

socket接收应用源码设计

在socket接收应用中,需要完成两个内容:
1.接收socket,并通过调用socket的API获得socket内部的字符串信息;
2.将字符串信息转化成int整形数格式,便于之后的舵机控制。

在上一节中,已经详细介绍过socket编程的原理流程,按照之前介绍的原理依次调用socket的API,就可以获得socket中的字符串信息。这里对接收socket并获得信息的过程不再讲述,读者可以参考附录中的源码来加深对socket编程的理解。

至于将字符串转化成int整形数格式,这里用到了函数int atoi(const char *nptr)。这个函数定义于stdlib.h中,专门用于把ascii字符串转化成int整形数。具体代码如下:

     angle_value = atoi ( buff ); //将字符串转换位数字                          
     printf("Angle Value: %d\n\n",angle_value);
                      
     if ( angle_value < 0 || angle_value > 180 ){
    
    //角度校验         
     	printf( "angle format error\n" );            
     }            
     else{
    
                    
     	zrcar_servo0_set( servo_dev, angle_value );            
     }

首先用atoi函数把接收到的字符串转换为int型数据,然后做一次校验。当角度大于180或者小于0时报错,而在角度在正常范围内时,调用舵机控制函数zrcar_servo0_set,这个函数会在下一节中具体介绍到。

舵机控制源码设计

这里会用到上一章讲到的舵机驱动中的相关函数,在这里不花费时间赘述,请大家参考上一章内容辅助理解
在上一章中,舵机已经在Linux系统中被抽象为设备文件zrcar_servo_dev。现在,我们就需要通过对这个文件进行修改,从而改变PWM波生成模块的工作状态。先从设计一个基本的设备文件读取函数开始介绍:
(1)舵机设备文件读取操作:
下面这个函数是一段是对舵机设备文件的最基本的读取操作:

int zrcar_servo_init(char *servo_dev){
    
         
	int fd = open(servo_dev,O_RDWR);          
	TEST(fd > 0,"servo device init failed!");        
	close(fd);
	return 0;
}

该函数的输入char *servo_dev是舵机设备文件的路径(在本次设计中该文件路径为:/dev/zrcar_servo_dev)。之后调用open函数。open函数会调用在驱动函数中注册的接口函数,使用上一章中定义的函数servo_open,打开舵机设备。之后用TEST函数校验舵机设备是否能成功打开,如果不能打开,就打印错误提示信息。最后关闭文件。
这个函数没有对设备做任何控制,只是用来验证是否可以打开舵机设备文件。
接下来,将会介绍如何通过对舵机设备文件进行修改来控制PWM波的占空比
(2)舵机设备文件修改操作:
下面这个函数用来对舵机设备文件做修改:

int zrcar_servo0_set(char *servo_dev, int angle){
    
    
	int fd = open( servo_dev, O_RDWR );
	TEST(fd > 0,"servo device open failed!\n");
	angle = 500 + 2000/180.0 * angle;
	ioctl(fd,SERVO0_SET,angle);
	close(fd);
	return 0;
}

首先用open函数打开舵机设备文件,
之后控制舵机设备,使用函数ioctl,该函数可以调用在上一章的舵机驱动中定义的舵机控制接口函数:

static long servo_ioctl(struct file *file, unsigned int cmd, unsigned long arg)

使用该函数,应用程序就可以通过驱动程序访问舵机外设,控制舵机角度。
由于舵机角度控制ip核只负责产生PWM波,周期固定为20ms,占空比由传入的参数unsigned long arg控制,因此需要将舵机的角度0-180线性映射到500-2500(即高电平时间0.5ms~2.5ms),所以需要对角度用下面的公式进行换算:

arg = 500+2000/180.0*angle。

最后关闭舵机设备文件。这样,就可以修改输出的PWM波的占空比,从而控制舵机的旋转角度了。

源码编译

该应用程序编写完毕后,将该文件中的函数与socket程序结合在一起,编写MakeFile文件:

CROSS_COMPLIE = arm-linux-gnueabihf-
ARCH = arm
CC = $(CROSS_COMPILE)gcc
LD = $(CROSS_COMPILE)ld
CFLAGS = -g
all: socketServer.o servo_ctrl.o
	${CC} -o socketServer socketServer.o servo_ctrl.o
clean:
	rm ./*.o socketServer

其中CROSS_COMPLIE指定交叉编译工具,ARCH为程序运行CPU的架构,使用CC= arm-linux-gnueabihf-gcc,保证使用的是交叉编译工具,而不是电脑X86的gcc编译工具。这样,就可以在Linux系统下编译得到适用于Zybo开发板上的arm-v7架构处理器的可执行文件socketServer了。

最终成果

通过设计Zybo开发板上的“socket应用+舵机控制”应用,成功地把实验板上的无线网卡和舵机联系了起来,现在可以通过向rtl8188的指定端口发送1-180内的字符串信息来控制舵机的转动了。

附录

socket接收+舵机控制应用源码下载 提取码:df8u

下章预告

现在,已经完成了Zybo开发板上的全部软硬件设计内容。剩下的内容就是Android端的socket发送APP开发以及最终的测试环节了,这两部分的内容会在下一章详细介绍。

猜你喜欢

转载自blog.csdn.net/qq_36745999/article/details/92799954