【FFH】OpenHarmony设备开发(二)-基于TCP遥控小车 原创 精华

X丶昕雪
发布于 2022-9-3 00:50
浏览
3收藏

基于TCP开发遥控小车

本文主要介绍在hi3861使用TCP进行控制小车,在使用TCP遥控小车之前,需要连接好wifi,连接wifi的步骤可以参考此文章: OpenHarmony设备开发-WIFI连接
TCP传输控制协议,是一种提供可靠的数据传输的协议,是有连接的,按序传输数据的,面向字节流的,可靠稳定的协议.
TCP通讯设备分为了客户端和服务端,在本文中,在windows上建立服务端,hi3861作为客户端,两者连接手机的wifi进行TCP通讯(一定要在同一网络内)。下文主要介绍hi3861作为客户端建立tcp连接的方法.

客户端代码介绍

  • 客户端在连接TCP之前一定要确保连上wifi,而hi3861连接wifi需要亿点点时间,因此建议连接wifi后sleep()几秒钟,让hi3861连接上wifi.
  1. 首先创建Socket对象,创建客户端套接字对象
    • socket()本次只需要关注参数一和参数二,参数一表示ipv4,参数二表示使用面向连接的套接字(TCP)
    • 返回值:若创建失败,返回-1
int socks;
socks=socket(AF_INET, SOCK_STREAM, 0);
  1. 创建sockaddr_in结构体对象,该对象存放服务端地址的参数,并初始化该对象的成员
    struct sockaddr_in sock_addr = {0};
    sock_addr.sin_family = AF_INET;     //ipv4
    sock_addr.sin_port = PP_HTONS(8888);        //IP端口
    sock_addr.sin_addr.s_addr = inet_addr("192.168.xx.xx"); //ip地址
  1. connect()连接函数,其功能是完成一个有连接协议的连接过程.上面两步创建的结构体对象都会在这一步使用到,这一步也是TCP连接最为关键的一步
    • 返回值:若连接成功便返回0,失败则返回-1
    • 参数一:指定数据发送的套接字
    • 参数二:指定服务端的地址,使用sockaddr_in结构体对象
    • 参数三:参数二结构体的长度
      connect(socks, (struct sockaddr *)&sock_addr, sizeof(sock_addr));
      上述步骤便完成了hi3861客户端和服务端的连接,我们接下来便可以快乐地和服务端进行收发数据,基于该无线连接的收发数据,我们可以做出各种有意思有趣的东西.
  2. API接口
  • 发送数据:
    函数:lwip_write(int s, const void *dataptr, size_t size);
    参数一:套接字对象,参数二:发送的数据,参数三:数据长度
    样例:
static const char *buf = "Hello! I'm HI3861 TCP client!!!!!!";
lwip_write(socks, buf, strlen(buf));
  • 接收数据:
    函数:lwip_read(int fildes, void *buf, size_t nbyte);
    参数一:套接字对象,参数二:接收的数据,参数三:数据长度
    样例:
char recv_buf[64];
(void)memset_s(recv_buf, sizeof(recv_buf), 0, sizeof(recv_buf));
lwip_read(socks, recv_buf, sizeof(recv_buf) - 1);
  • 超时机制:
    在TCP连接中,recv等函数默认为阻塞模式,就是没有数据传输时便会一直停留在函数里,我们有时不希望一直停在这个函数内,便需要一种超时机制setsockopt(),到达一定时间后即使没有数据也会退出函数.
    函数:int setsockopt(int s, int level, int optname, void* optval, socklen_t* optlen);
    • 参数一: 指向一个打开的套接口描述字

    • 参数二:指定选项代码的类型
      |选项代码的类型|含义|
      | ---- | ---- |
      |SOL_SOCKET| 基本套接口|
      |IPPROTO_IP| IPv4套接口|
      |IPPROTO_IPV6|IPv6套接口|
      |IPPROTO_TCP|TCP套接口|

    • 参数三:选项名称
      SO_RCVTIMEO:设置接收超时时间

    • 参数四:选项值

    • 参数五:参数的长度
      样例:

struct timeval recev_timeout;
recev_timeout.tv_sec = 5;
recev_timeout.tv_usec = 0;
/* 5S Timeout */
ret_a = setsockopt(socks, SOL_SOCKET, SO_RCVTIMEO, recev_timeout, sizeof(recev_timeout));

该函数还有其它许多用法,但本文不过多介绍,需要了解更多可以baidu一下

关键代码

头文件以及一些宏定义:

// tcp
#include <stdio.h>
#include "lwip/sockets.h"
char recv_buf[64];
// io
#include "iot_gpio.h"
#include <hi_gpio.h>
#include <hi_io.h>
#define GPIO0 0
#define GPIO1 1
#define GPIO9 9
#define GPIO10 10

主函数中的代码:

//连接好wifi后
/**********************************************/
    sleep(5);
    
    struct sockaddr_in sock_addr = {0};

    int s;

    sock_addr.sin_family = AF_INET;
    sock_addr.sin_port = PP_HTONS(_PROT_);
    sock_addr.sin_addr.s_addr = inet_addr(IP_argv[0]);

    s = socket(AF_INET, SOCK_STREAM, 0);
    if (s < 0)
    {
        printf("socket false\n");
    }
    int ret_a;
    ret_a = connect(s, (struct sockaddr *)&sock_addr, sizeof(sock_addr));
    if (ret_a != 0)
    {
        printf("connect false\n");
    }

    my_car_init();      //小车电机的IO口初始化
    while (1)
    {
        (void)memset_s(recv_buf, sizeof(recv_buf), 0, sizeof(recv_buf));
        ret_a = lwip_read(s, recv_buf, sizeof(recv_buf) - 1);       //等待接收服务端发送数据
        printf("server:%d!\n", recv_buf[0]);
	//判断接收的数据是何按键,49对应前进键,50对应后退键,51左键,52右键
        if (recv_buf[0] == 49)
        {
            car_forward();
            printf("car_forward!\n");
        }
        else if (recv_buf[0] == 50)
        {
            car_stop();
            printf("car stop!");
        }
        else if (recv_buf[0] == 51)
        {
            car_left();
        }
        else if (recv_buf[0] == 52)
        {
            car_right();
        }
    }

小车控制的代码:

#define GPIO0 0
#define GPIO1 1
#define GPIO9 9
#define GPIO10 10
void my_car_init(void)
{
    //IO口初始化
    IoTGpioInit(GPIO0);
    IoTGpioInit(GPIO1);
    IoTGpioInit(GPIO10);
    IoTGpioInit(GPIO9);
    //IO口复用
    hi_io_set_func(GPIO0, HI_IO_FUNC_GPIO_0_GPIO); //在hi_io.h里有定义
    hi_io_set_func(GPIO1, HI_IO_FUNC_GPIO_1_GPIO);
    hi_io_set_func(GPIO10, HI_IO_FUNC_GPIO_10_GPIO);
    hi_io_set_func(GPIO9, HI_IO_FUNC_GPIO_9_GPIO);
    //IO口功能设置,输入or输出
    IoTGpioSetDir(GPIO0, 1);    //1为output输出
    IoTGpioSetDir(GPIO1, 1);
    IoTGpioSetDir(GPIO10, 1);
    IoTGpioSetDir(GPIO9, 1);
    //IO口输出电平
    IoTGpioSetOutputVal(GPIO0, IOT_GPIO_VALUE1);        //VALUE1为高电平,VALUE0为低电平
    IoTGpioSetOutputVal(GPIO1, IOT_GPIO_VALUE1);
    IoTGpioSetOutputVal(GPIO9, IOT_GPIO_VALUE1);
    IoTGpioSetOutputVal(GPIO10, IOT_GPIO_VALUE1);
}

void car_stop(void)
{
    IoTGpioSetOutputVal(GPIO0, IOT_GPIO_VALUE1); //左轮
    IoTGpioSetOutputVal(GPIO1, IOT_GPIO_VALUE1); //左轮
    IoTGpioSetOutputVal(GPIO9, IOT_GPIO_VALUE1); //右轮
    IoTGpioSetOutputVal(GPIO10, IOT_GPIO_VALUE1); //右轮
}

void car_forward(void)
{
    IoTGpioSetOutputVal(GPIO0, IOT_GPIO_VALUE1);
    IoTGpioSetOutputVal(GPIO1, IOT_GPIO_VALUE0);
    IoTGpioSetOutputVal(GPIO9, IOT_GPIO_VALUE1);
    IoTGpioSetOutputVal(GPIO10, IOT_GPIO_VALUE0);
}

void car_left(void)
{
    IoTGpioSetOutputVal(GPIO0, IOT_GPIO_VALUE0);
    IoTGpioSetOutputVal(GPIO1, IOT_GPIO_VALUE0);
    IoTGpioSetOutputVal(GPIO9, IOT_GPIO_VALUE1);
    IoTGpioSetOutputVal(GPIO10, IOT_GPIO_VALUE0);
}

void car_right(void)
{
    IoTGpioSetOutputVal(GPIO0, IOT_GPIO_VALUE1);
    IoTGpioSetOutputVal(GPIO1, IOT_GPIO_VALUE0);
    IoTGpioSetOutputVal(GPIO9, IOT_GPIO_VALUE1);
    IoTGpioSetOutputVal(GPIO10, IOT_GPIO_VALUE1);
}

win10中的服务端:
编译gcc -o server server.c -lwsock32
运行软件./server IP地址 端口

#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#include <windows.h>
#include <conio.h>
int key;

int startup(int _port, const char *_ip)
{
    WSADATA ws;
    WSAStartup(MAKEWORD(2, 2), &ws);
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
    {
        perror("socket");
        exit(1);
    }

    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_port = htons(_port);
    local.sin_addr.s_addr = inet_addr(_ip);
    int len = sizeof(local);

    if (bind(sock, (struct sockaddr *)&local, len) < 0) //绑定
    {
        perror("bind");
        exit(2);
    }

    if (listen(sock, 5) < 0) //允许连接的最大数量为5,监听
    {
        perror("listen");
        exit(3);
    }

    return sock;
}

int main(int argc, const char *argv[])
{
    int listen_sock = startup(atoi(argv[2]), argv[1]); //初始化

    //用来接收客户端的socket地址结构体
    struct sockaddr_in remote;
    int len = sizeof(struct sockaddr_in);

    while (1)
    {
        int sock = accept(listen_sock, (struct sockaddr *)&remote, &len); //连接
        if (sock < 0)
        {
            perror("accept");
            continue;
        }
        printf("get a client, ip:%s, port:%d\n", inet_ntoa(remote.sin_addr), ntohs(remote.sin_port));
        while (1)
        {
            key = _getch();
            if (key > 31 && key < 127) /*如果不是特殊键*/
            {
                printf("按了 %c 键\n", key);
                continue;
            }
            key = _getch();
            if (key == 72)
            {
                send(sock, "1", 2, 0);
                printf("按了 %c 上键\n", key);
            }
            else if (key == 80)
            {
                send(sock, "2", 2, 0);
                printf("按了 %c 下键\n", key);
            }
            else if (key == 75)
            {
                send(sock, "3", 2, 0);
                printf("按了 %c 左键\n", key);
            }
            else if (key == 77)
            {
                send(sock, "4", 2, 0);
                printf("按了 %c 右键\n", key);
            }
     
        }
    }
    return 0;
}

效果图

【FFH】OpenHarmony设备开发(二)-基于TCP遥控小车-鸿蒙开发者社区
【FFH】OpenHarmony设备开发(二)-基于TCP遥控小车-鸿蒙开发者社区

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
4
收藏 3
回复
举报
6条回复
按时间正序
/
按时间倒序
FFH杞人
FFH杞人

大佬666!

回复
2022-9-4 15:30:39
红叶亦知秋
红叶亦知秋

好奇hi3861连wifi遇到了啥困苦,单纯的网络难连接吗?

回复
2022-9-5 10:05:11
啃论文俱乐部的欧sir
啃论文俱乐部的欧sir

代码躺在附件和自己电脑,都是很浪费的。

建议研究下提交到OpenHarmony的知识体系代码仓里。

怎么参加代码提交,可以去翻一下“战码先锋”相关回播,一大堆课程在做讲解。

1
回复
2022-9-5 14:51:30
mb5f6a88475c6f9
mb5f6a88475c6f9 回复了 啃论文俱乐部的欧sir
代码躺在附件和自己电脑,都是很浪费的。 建议研究下提交到OpenHarmony的知识体系代码仓里。 怎么参加代码提交,可以去翻一下“战码先锋”相关回播,一大堆课程在做讲解。

有道理

回复
2022-9-5 15:09:13
X丶昕雪
X丶昕雪 回复了 啃论文俱乐部的欧sir
代码躺在附件和自己电脑,都是很浪费的。 建议研究下提交到OpenHarmony的知识体系代码仓里。 怎么参加代码提交,可以去翻一下“战码先锋”相关回播,一大堆课程在做讲解。

感谢提示

回复
2022-9-5 23:20:03
X丶昕雪
X丶昕雪 回复了 红叶亦知秋
好奇hi3861连wifi遇到了啥困苦,单纯的网络难连接吗?

网络不难连接,我们的设备连接wifi时后的一小段时间才真正连上,我认为是wifi连接中的关联步骤需要一些时间

回复
2022-9-5 23:23:10
回复
    相关推荐