一、創建RT-Thread項目
開始本篇實驗前,需要搭建RT-Thread開發環境,具體可以參考:“快來看!先楫芯與RT-Thread碰出火花了”
使用RT-Thread Studio創建名為hpm_net_test的項目:
二、為項目添加RW007支持
2.1 打開RT-Thread Settings
項目創建成功后,打開項目的RT-Thread Settings界面:
可以看到,默認情況下SPI驅動框架已經打開了。
BSP中的SPI1驅動也已經打開了:
2.2 添加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子目錄下:
2.3 配置RW007驅動
在RT-Thread Settings界面,將鼠標移動到RW007組件上,會彈出懸浮菜單:
點擊懸浮菜單中的“配置項”,即可進入RW007軟件包的配置界面:
可以看到,默認有一個RW007 for stm32的配置,就是說RW007默認包含了STM32的驅動。
這里我們需要修改的就是這個example driver port配置項,點擊下拉菜單改為不使用示例驅動:
選中后,記得Ctrl+S保存配置。
2.4 編譯、燒錄、運行項目
在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的msh交互環境中使用help查看當前已有哪些命令:
可以看到有一個wifi命令。
接下來我們查看wifi命令的使用方式:
3.1 掃描測試
嘗試掃描周圍的WiFi熱點:
可以看到,成功掃描到了周圍的WiFi熱點。
3.2 連接測試
嘗試連接其中的一個熱點:
然而,不幸的是,發生異常了。
不過,從這里的幾個warning打印信息可以看到,應該是因為tcpip線程棧溢出導致的。
3.3 調大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的代碼。
3.4 重新測試
配置修改完成后,Ctrl+S保存,重新編譯項目、燒錄、運行,這次能夠成功連接WiFi熱點了:
可以看到,已經成功通過DHCP從熱點獲取到IP地址了。
四、網絡測試
4.1 RT-Thread網絡組件
前面提到,添加了RW007軟件包后,會開啟RT-Thread的WiFi驅動框架;同時,也會開啟系統中網絡協議相關的組件,主要包括套接字抽象層(SAL)、網絡接口層、輕量級TCP/IP堆棧(LwIP),如下圖所示。
其中,LwIP的默認版本用的是v2.0.3,也可以切換為其他版本(RT-Thread系統中同時提供了LwIP的好幾個版本可供選擇)。
4.2 RT-Thread網絡組件相關的命令
RT-Thread系統網絡相關組件打開后,將會向msh中注冊幾個命令用于測試,具體包括:ifconfig、ping、netstat、dns等,可以在help的輸出中找到:
4.3 ping測試
有IP地址了,我們可以用ping命令測試一下能不能訪問baidu.com:
可以看到,能夠成功ping通baidu.com了。
使用baidu.com的域名能夠訪問,說明DNS整個流程都是OK的,同時網路協議也是沒問題的。
五、網絡帶寬測試
5.1 添加netutils軟件包
RT-Thread的netutils軟件包中提供了iperf命令,可以用于測試網絡帶寬;
和前面類似的方法,為項目添加netutils組件:
打開“配置項”后,打開iperf的配置項:
修改配置后,Ctrl+S保存。
重新編譯、燒錄、運行項目,help的輸出可以看到多了iperf命令。
5.2 iperf命令參數
在RT-Thread的msh中運行iperf,默認輸出幫助信息:
可以看到iperf的命令參數使用方法。
需要注意:
1.RT-Thread的iperf命令實現中,對參數的順序有要求,如果使用過程中發現參數報錯,需要查看源碼定位原因;
2.RT-Thread的iperf不支持持續時間選項,一般是先啟動,后通過stop選項停止的方式控制測試時長;
5.3 PC端的iperf
PC端的iperf可以到iperf項目官網下載:https://iperf.fr/iperf-download.php
我使用的mobaxterm,里面自帶了iperf命令,所以就不單獨下載了:
5.4 進行iperf測試
進行iperf測試之前,需要注意:
1.最好用PC創建熱點,用無線路由器也行,但是需要確保信號強度足夠;
2.確保開發板和PC直線的距離不要太遠,否則WiFi信號較弱,測試的結果可能會偏小;
3.最好在WiFi熱點較少的環境下進行測試,否則測出的結果數據也會偏小;
下面進行測試,測試步驟如下:
在PC上,創建熱點,例如名為rtt,密碼為12345678
在PC上,啟動iperf服務端:iperf -s -p 5678
在PC上,使用ipconfig/ifconfig命令查看熱點的IP地址,例如我在Win10上創建的熱點,IP地址是:192.168.137.1
在開發板上,連接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
5.5 iperf測試小結
實際上,影響WiFi帶寬測試結果數據的因素很多。我們這里,其中,起決定性的的主要由以下幾個方面:
RW007模組本身支持的最高WiFi傳輸速率;
RW007模組的SPI接口支持的最高工作頻率;
HPM6750 SPI接口最高支持的工作頻率;
熱點(PC或路由器)的WiFi最高傳輸速率;
各種環境因素,例如開發板和PC直接的距離、環境是否有其他熱點干擾等等;
六、業務代碼——socket測試
前面的ping測試、iperf測試使用的是系統已有組件或軟件包。除此之外,也可以通過socket連接網絡。這里以一個簡單的使用socket獲取baidu首頁為例(直接使用web_client軟件包也可以實現該功能):
#include #include #include #include #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[i]); } rt_kprintf("==== Response Content ====:\\n"); for (int i = content_start; i < recved; i++) { rt_kprintf("%c", response[i]); } 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客戶端,依次進行了請求發送、回復接收和回復解析的過程,測試結果:
七、原理簡介
以上操作,我們沒有任何底層驅動相關代碼,就實現了通過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協議實現;
(文章摘選自RTT @xusiwei1236)
-
WIFI
+關注
關注
81文章
5309瀏覽量
204810 -
RTThread
+關注
關注
8文章
132瀏覽量
41002
發布評論請先 登錄
相關推薦
評論