注册 登录
电子工程世界-论坛 返回首页 EEWORLD首页 频道 EE大学堂 下载中心 Datasheet 专题
xusiwei1236的个人空间 https://home.eeworld.com.cn/space-uid-1305707.html [收藏] [复制] [分享] [RSS]
日志

【先楫HPM6750测评】RT-Thread SPI驱动和WiFi联网

已有 531 次阅读2022-6-12 17:15 |个人分类:国产MCU

本篇将会介绍——如何使用RT-Thread Studio让HPM6750EVKMINI开发板通过RW007 WiFi模块实现联网,并介绍其背后的原理,以及使用iperf对网速进行网速测试。本篇的最后,将演示WiFi网络联通之后,将会编写一个简单联网获取百度首页的示例,并进行测试。

创建RT-Thread项目

开始本篇实验前,需要搭建RT-Thread开发环境,具体可以参考我之前发的文章。(可以在EEWorld我的日志页面找到:xusiwei1236的日志 - 电子工程世界-论坛 (eeworld.com.cn)

使用RT-Thread Studio创建名为hpm_net_test的项目:

为项目添加RW007支持

打开RT-Thread Settings

项目创建成功后,打开项目的RT-Thread Settings界面:

可以看到,默认情况下常见的项目SPI驱动已经打开了。

 

BSP中的SPI1驱动也已经打开了:

添加RW007软件包

在RT-Thread Settings界面,点击通过“添加软件包”按钮,会弹出RT-Thread Package Center界面:

在中间的搜索框种输入RW007,回车,可以找到RW007驱动程序软件包:

点击界面“添加”按钮,即可将RW007软件包添加到当前项目的包配置中了,此时软件包并没有真正下载下来。点完添加按钮后,界面回到了RT-Thread Settings,此时按Ctrl+S保存,则会开始下载。下载过程中,控制台子窗口中可以看到一些日志输出:

稍等片刻,可以看到控制台中间有“RW007 v2.0.1 is downloaded successfully.”输出。此时rw007软件包已经成功下载到当前项目中了,具体代码位于packages子目录下:

配置RW007驱动

在RT-Thread Settings界面,中将鼠标移动到RW007组件上,会弹出悬浮菜单:

点击悬浮菜单中的“配置项”,即可进入RW007软件包的配置界面:

可以看到,默认有一个RW007 for stm32的配置,就是说RW007默认包含了STM32的驱动。

这里我们需要修改的就是这个example driver port配置项,点击下拉菜单改为不使用示例驱动:

选中后,记得Ctrl+S保存配置。

 

编译、烧录、运行项目

在RT-Thread Studio中Ctrl+B或按“锤子”按钮,即可开始编译项目。编译完成后,可以看到控制台输出了RAM和Flash占用:

此时,将开发板连接到PC,并使用串口助手或者其他终端工具,连接到新增的串口上。

再到RT-Thread Studio中,使用“下载”按钮或Ctrl+Alt+D即可进行烧录(或者直接进行调试也可以)。

烧录完成后,可以看到串口终端上有输出:

可以看到,输出了RT-Thread版本信息和RW007模组的序列号以及固件版本信息。这里能够看到RW007模组的固件版本信息,其实HPM6750芯片和RW007模组之间可以已经正常通信了。

 

WiFi测试

接下来,我们进行一些简单的WiFi测试。

添加RW007组件后,默认会打开RT-Thread的WiFi驱动框架,而RT-Thread的WiFi驱动框架中同时带有一个测试命令——wifi(对就是这么直接)。

我们可以在RT-Thread的finsh交互环境中使用help查看当前已有哪些命令:

可以看到有一个wifi命令。

 

接下来我们查看wifi命令的使用方式:

扫描测试

尝试扫描周围的WiFi热点:

可以看到,成功扫描到了周围的WiFi热点。

 

连接测试

尝试连接其中的一个热点:

然而,不幸的是,发生异常了。

不过,从这里的几个warning打印信息可以看到,应该是因为tcpip线程栈溢出导致的。

 

调大tcpip线程栈大小

接下来,我们通过RT-Thread Settings修改tcpip线程栈的大小。

同样,首先打开RTT Settings界面,鼠标指针放到LwIP组件图标上:

打开配置项,找到RT_LWIP_TCPTHREAD_STACKSIZE配置项,并将其修改为4096:

界面下方可以看到这个LwIP线程栈大小的配置项名称为**RT_LWIP_TCPTHREAD_STACKSIZE。**至于这里为什么要改这个配置项,没有在RT-Thread用过LwIP的同学可能会疑惑。其实,这里可以根据线程名“tcpip”,一路搜索代码,首先可以找到创建名为tcpip线程的代码位置,然后可以找到线程栈大小参数的来源。这里是搜索结果:

PS:因为默认使用的是lwip 2.0.3版本,所以这里只搜索了lwip-2.0.3的代码。
 

重新测试

配置修改完成后,Ctrl+S保存,重新编译项目、烧录、运行,这次能够成功连接WiFi热点了:

可以看到,已经成功通过DHCP从热点获取到IP地址了。

 

网络测试

RT-Thread网络组件

前面提到,添加了RW007软件包后,会开启RT-Thread的WiFi驱动框架;同时,也会开启系统中网络协议相关的组件,主要包括套接字抽象层(SAL)、网络接口层、轻量级TCP/IP堆栈(LwIP),如下图所示。

其中,LwIP的默认版本用的是v2.0.3,也可以切换为其他版本(RT-Thread系统中同时提供了LwIP的好几个版本可供选择)。

 

RT-Thread网络组件相关的命令

RT-Thread系统网络相关组件打开后,将会向finsh中注册几个命令用于测试,具体包括:ifconfig、ping、netstat、dns等,可以在help的输出中找到:

ping测试

有IP地址了,我们可以用ping命令测试一下能不能访问baidu.com:

可以看到,能够成功ping通baidu.com了。

使用baidu.com的域名能够访问,说明DNS整个流程都是OK的,同时网路协议也是没问题的。

 

socket测试

可以ping通baidu之后,我们就可以进行业务开发了。这里以一个简单的使用socket获取baidu首页为例(其实,更简单的方法是直接使用web_client组件):

/*
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2022-05-08     xusiwei1236       the first version
 */
#include <rtthread.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netdb.h>

#define DEFAULT_HOST "example.com"
#define DEFAULT_PORT 80

#define CONTENT_LENGTH "Content-Length:"
#define HEADER_END_MARK "\\r\\n\\r\\n"

uint32_t get_host_addr(const char *host)
{
    uint32_t dest = 0;
    struct hostent *he;

    he = gethostbyname(host);
    if (he && he->h_addr_list && he->h_addr_list[0]) {
        dest = ((struct in_addr *)(he->h_addr_list[0]))->s_addr;
    }
    return dest;
}

#define close(fd) closesocket(fd)

int fetch(int argc, char* argv[])
{
    char* host = DEFAULT_HOST;
    int port = DEFAULT_PORT;
    int sockfd = -1;
    int retval = 0;
    int recved = 0;
    int content_start = 0;
    int content_length = 0;
    struct sockaddr_in server_addr = {0};
    static char request[256];
    static char response[4096];

    if (argc > 1) host = argv[1];
    if (argc > 2) port = atoi(argv[2]);

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        rt_kprintf("create socket failed!\\n");
        return -1;
    }
    rt_kprintf("create socket success!\\n");

    rt_memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);
    server_addr.sin_addr.s_addr = get_host_addr(host);

    // IP转为 “点分十进制” 格式
    inet_ntop(AF_INET, &server_addr.sin_addr, response, sizeof(response));
    rt_kprintf("server IP: %s\\n", response);

    rt_kprintf("connect to server...\\n");
    retval = connect(sockfd, (const struct sockaddr *)&server_addr,
              sizeof(server_addr));
    if (retval < 0) {
        rt_kprintf("connect failed!\\n");
        close(sockfd);
        return -1;
    }

    rt_snprintf(request, sizeof(request),
            "GET / HTTP/1.1\\r\\n"
            "Host: %s\\r\\n"
            "User-Agent: curl/7.81.0\\r\\n"
            "Accept: */*\\r\\n"
            "\\r\\n", host);

    rt_kprintf("send request...\\n");
    retval = send(sockfd, request, rt_strlen(request), 0);
    if (retval < 0) {
        rt_kprintf("send failed!\\n");
        close(sockfd);
        return -1;
    }
    rt_kprintf("%d bytes sent\\n", retval);

    rt_kprintf("recv response...\\n");
    recved = 0;
    while ((retval = recv(sockfd, &response[recved], sizeof(response) - recved, 0)) > 0) {
        if (content_length == 0) {
            char* content_length_pos = rt_strstr(response, CONTENT_LENGTH);
            if (content_length_pos) {
                content_length = atoi(content_length_pos + rt_strlen(CONTENT_LENGTH));
                rt_kprintf("found %s %d!\\n", CONTENT_LENGTH, content_length);
            }
        }
        if (content_start == 0) {
            char* header_end = rt_strstr(response, HEADER_END_MARK);
            if (header_end) {
                content_start = header_end + rt_strlen(HEADER_END_MARK) - response;
                rt_kprintf("content_start: %d\\n", content_start);
            }
        }
        recved += retval;
        rt_kprintf("recved: %d %d %d\\n", recved, content_start, content_length);
        if (content_length && content_start && recved - content_start >= content_length) {
            rt_kprintf("fully recved!\\n");
            break;
        }
    }
    response[recved] = '\\0';

    rt_kprintf("==== Response Header ====:\\n");
    for (int i = 0; i < content_start; i++) {
        rt_kprintf("%c", response);
    }

    rt_kprintf("==== Response Content ====:\\n");
    for (int i = content_start; i < recved; i++) {
        rt_kprintf("%c", response);
    }

    if (retval < 0) {
        rt_kprintf("recv failed!\\n");
        close(sockfd);
        return -1;
    }

    shutdown(sockfd, SHUT_RDWR);
    close(sockfd);
    return 0;
}
MSH_CMD_EXPORT(fetch, "fetch home page of a site");

这是一段使用裸socket实现的简单HTTP客户端,依次进行了请求发送、回复接收和回复解析的过程,测试结果:

网络带宽测试

添加netutils软件包

RT-Thread的netutils组件中提供了iperf命令,可以用于测试网络带宽;

和前面类似的方法,为项目添加netutils组件:

打开“配置项”后,打开iperf的配置项:

修改配置后,Ctrl+S保存。

重新编译、烧录、运行项目,help的输出可以看到多了iperf命令。

 

iperf命令参数

在RT-Thread的finsh中运行iperf,默认输出帮助信息:

可以看到iperf的命令参数使用方法。

需要注意的是:

  1. RT-Thread的iperf命令实现中,对参数的顺序由要求,如果使用过程中发现参数报错,需要查看源码定位原因;
  2. RT-Thread的iperf不支持持续时间选项,一般是先启动,后通过stop选项停止的方式控制测试时长;

 

PC端的iperf

PC端的iperf可以到iperf项目官网下载:javascript:;

我使用的mobaxterm,里面自带了iperf命令,所以就不单独下载了:

进行iperf测试

进行iperf测试之前,需要注意:

  1. 最好用PC创建热点,用无线路由器也行,但是需要确保信号强度足够;
  2. 确保开发板和PC直接的距离不要太远,否则WiFi信号较弱,测试的结果可能会偏小;
  3. 最好在WiFi热点较少的环境下进行测试,否则测出的结果数据也会偏小;

下面进行测试,测试步骤如下:

  1. 在PC上,创建热点,例如名为rtt,密码为12345678
  2. 在PC上,启动iperf服务端:iperf -s -p 5678
  3. 在PC上,使用ipconfig/ifconfig命令查看热点的IP地址,例如我在Win10上创建的热点,IP地址是:192.168.137.1
  4. 在开发板上,连接PC启动热点:wifi join rtt 12345678
  5. 在开发板上,查看IP地址是否已成功分配:ifconfig,另外,可以通过ping命令测试开发板和PC直接IP是否可达
  6. 在开发板上,启动iperf客户端:iperf -c 192.168.137.1 -p 5678
    • 启动后,可以通过ps命令查看正在运行的线程
  7. 一段时间后,在开发板上,停止iperf客户端:iperf --stop
  8. 开发板上iperf停止后,PC端应该可以看到iperf的输出;

开发板上整个过程的输出如下:

PC端输出:
可以看到带宽为7.45Mbps

 

iperf测试小结

实际上,影响WiFi带宽测试结果数据的因素很多。我们这里,其中,起决定性的的主要由以下几个方面:

  1. RW007模组本身支持的最高WiFi传输速率;
  2. RW007模组的SPI接口支持的最高工作频率;
  3. HPM6750 SPI接口最高支持的工作频率;
  4. 热点(PC或路由器)的WiFi最高传输速率;
  5. 各种环境因素,例如开发板和PC直接的距离、环境是否有其他热点干扰等等;

 

原理简介

以上操作,我们没有任何底层驱动相关代码,就实现了通过HPM6750EVKMINI开发板的RW007 WiFi模组实现联网功能。这是因为我们基于RT-Thread的项目中,从底到上已经有了:

  • HPM6750EVKIMNI BSP中包含了SPI驱动(libraries/drivers/drv_spi.c文件);
    • 默认打开了spi1的编译配置;
  • HPM6750EVKIMNI BSP中包含了网卡初始化代码(board/rw007_port.c文件);
    • 向系统注册了启动时自动执行的wifi_spi_device_init函数;
    • wifi_spi_device_init函数内部会调用rw007软件包中的rt_hw_wifi_init函数;
  • RW007软件包,包含RW007模组的驱动代码;
    • 底层使用SPI驱动实现主控和RW007模组之间的通讯;
    • 上层向RT-Thread系统注册WLAN设备(rt_hw_wifi_init函数内部会调用rt_wlan_dev_register函数);
  • RT-Thread的WiFi(也叫WLAN)驱动框架;
    • 对下连接具体的 WIFI 驱动,控制 WIFI 的连接断开,扫描等操作。
    • 对上承载不同的应用,为应用提供 WIFI 控制,事件,数据导流等操作,为上层提供统一的 WIFI 控制接口。
  • RT-Thread的Socket抽象层(SAL),统一集中不同的socket实现;
  • RT-Thread的TCP/IP协议栈(lwip+一些专有修改),具体的TCP/IP协议实现;

本篇就到这里了,感谢你的阅读,下次再见。

本文来自论坛,点击查看完整帖子内容。

评论 (0 个评论)

facelist doodle 涂鸦板

您需要登录后才可以评论 登录 | 注册