在介绍Web播放器开发时,我们简单讲解了RVB2601开发板的Wi-Fi联网操作。为了给予大家更详细的操作指导,本期内容将讲解RVB2601开发板的软硬件接口原理和以太网通讯测试的步骤,并在最后为大家总结测试过程中遇到的问题及解决办法。
01 概述
RVB2601中集成的CH2601通过W800提供了AT透传的wifi 功能,可以完成和外界进行数据交换的需要。
02 驱动描述
2.1 硬件接口原理
CH2601采用SPI接口和W800进行数据交换,如图所示。
序号 |
W800 |
GPIO |
1 |
SPI CS |
PA15(SPI0_CS) |
2 |
SPI MOSI |
PA17(SPI0_MOSI) |
3 |
SPI MISO |
PA18(SPI0_MISO) |
4 |
SPI CLK |
PA16(SPI0_CLK) |
5 |
RST_N |
PA21 |
6 |
WAKEUP |
PA25 |
2.2 软件驱动设计
2.2.1 网络管理
网络管理支持有线网络、无线网络、GPRS网络、NB-IOT网络。我们只涉及到无线网络,所以这里就研究和网线网络相关的接口。网络管理接口如下所示:
函数 |
说明 |
netmgr_dev_wifi_init |
无线设备初始化 |
netmgr_service_init |
服务初始化 |
netmgr_config_wifi |
无线配置 |
netmgr_start |
使能网络设备 |
netmgr_reset |
重置网络设备 |
netmgr_stop |
停止网络链接 |
netmgr_is_gotip |
网络设备是否获取到ip |
1)网络管理接口详细说明
网络设备初始化
netmgr_hdl_t netmgr_dev_wifi_init()
函数说明:
该函数会注册netmgr_dev_t结构中的provision配网等回调函数。同时打开已注册的wifi设备节点,调用该设备实现的hal层初始化接口。同时将该设备加入到网络设备列表中统一管理。
返回值:
调用失败时返回NULL
服务初始化
void netmgr_service_init(utask_t *task)
初始化网络管理微服务。若外部微任务task为空,则内部创建微任务。同时将微服务加入到微任务中。
无线网络设备配置
int netmgr_config_wifi(netmgr_hdl_t hdl, char *ssid, uint8_t ssid_length, char *psk, uint8_t psk_length)
配置无线设备的ssid名称和对应的秘钥psk。
若定义了CONFIG_KV_SMART配置,如在某solution下的package.yaml中配置了CONFIG_KV_SMART: 1,则ssid和psk同时会被存储到kv文件系统中。对应的key定义如下:
#define KV_WIFI_SSID "wifi_ssid" #define KV_WIFI_PSK "wifi_psk"
返回值:
调用成功时返回0,否则返回-1。
使能网络设备
int netmgr_start(netmgr_hdl_t hdl)
当网络参数配置后,就可以调用该接口使能指定网络设备开始正常工作。该函数最终会调用到对应网络设备初始化配置的provision配网回调。
该接口是非阻塞的,网络连接成功后,网络管理器会上报EVENT_NETMGR_GOT_IP事件,否则上报EVENT_NETMGR_NET_DISCON事件。应用开发者可通过event_subscribe接口订阅这两个消息来判断网络是否连接成功。用户也可通过调用netmgr_is_gotip判断是否正常获取到ip。
返回值:
调用成功时返回0,否则返回-1。
重置网络设备
int netmgr_reset(netmgr_hdl_t hdl, uint32_t sec)
复位网络设备连接,并在指定sec秒后自动重连。当sec为0时,复位后立即重连。该函数最终会调用到对应网络设备初始化配置的reset配网回调。该接口是非阻塞的。
返回值:
调用成功时返回0,否则返回-1。
停止网络设备
int netmgr_stop(netmgr_hdl_t hdl)
停止指定网络设备运行。该函数最终会调用到对应网络设备初始化配置的unprovision配网回调。该接口是阻塞的。
返回值:
调用成功时返回0,否则返回-1。
网络设备是否获取到ip
int netmgr_is_gotip(netmgr_hdl_t hdl)
指定hdl的网络设备是否获取到ip。
返回值:
当前网络设备已经成功获取到ip时返回1,否则返回0
2) SAL套接字适配层
SAL组件完成对不同网络实现接口的抽象并对上层提供一组标准的 BSD Socket API,开发者只需关心和使用标准网络接口,而无需关心底层具体实现,极大的提高了系统的兼容性,方便开发者完成协议栈的适配和网络通信相关的开发。
如下图所示,本次试用的RVB2601评估板采用的AT通道透传与W800芯片通讯完成以太网通讯功能。
系统中提供了W800模块驱动中已经完成了SAL层接口的移植工作,因此在完成W800设备注册后,软件打开该设备的时候,驱动自动注册进SAL接口。后续的使用过程中,上层软件就感受不到ATPaser的存在了。
如下代码:
static int w800_dev_open(aos_dev_t *dev) { // power on device sal_module_register(&w800_sal_driver); sal_init(); return 0; }
03 程序测试
本测试程序通过RVB2601建立一个TCPclient测试程序,与TCPServer通讯,完成TCPClient向TCPServer定时数据传递的功能。
3.1 初始化
注册的以太网事件回调接口函数
static void network_event(uint32_t event_id, const void *param, void *context) { switch(event_id) { case EVENT_NETMGR_GOT_IP: { LOGD(TAG, "EVENT_NETMGR_GOT_IP"); } break; case EVENT_NETMGR_NET_DISCON: LOGD(TAG, "EVENT_NETMGR_NET_DISCON"); break; } /*do exception process */ // app_exception_event(event_id); }
以太网设备初始化函数
static void network_init() { w800_wifi_param_t w800_param; /* init wifi driver and network */ w800_param.reset_pin = PA21; w800_param.baud = 1*1000000; w800_param.cs_pin = PA15; w800_param.wakeup_pin = PA25; w800_param.int_pin = PA22; w800_param.channel_id = 0; w800_param.buffer_size = 4*1024; wifi_w800_register(NULL, &w800_param); app_netmgr_hdl = netmgr_dev_wifi_init(); if (app_netmgr_hdl) { utask_t *task = utask_new("netmgr", 2 * 1024, QUEUE_MSG_COUNT, AOS_DEFAULT_APP_PRI); netmgr_service_init(task); netmgr_config_wifi(app_netmgr_hdl, "Baidu-jy", 10, "12345678", 10); netmgr_start(app_netmgr_hdl); event_subscribe(EVENT_NETMGR_GOT_IP, network_event, NULL); event_subscribe(EVENT_NETMGR_NET_DISCON, network_event, NULL); } }
3.2 TCPClient程序
static char lan_buf[1600]; int tcpclient(void) { int iCounter; struct sockaddr_in sAddr; int iAddrSize; int iSockFD; int iStatus; long lLoopCount = 0; char *cBsdBuf = NULL; int time_ms = aos_now_ms(); int time_ms_step = aos_now_ms(); int send_bytes = 0; running = 1; cBsdBuf = lan_buf; //filling the TCP server socket address FD_ZERO(&sAddr); sAddr.sin_family = AF_INET; sAddr.sin_port = htons(26666); sAddr.sin_addr.s_addr = inet_addr("192.168.95.5"); iAddrSize = sizeof(struct sockaddr_in); // creating a TCP socket iSockFD = socket(AF_INET, SOCK_STREAM, 0); if (iSockFD < 0) { LOGE(TAG, "TCP ERROR: create tcp client socket fd error!"); goto Exit1; } LOGD(TAG, "ServerIP=%s port=%d.", "192.168.95.5", 26666); LOGD(TAG, "Create socket %d.", iSockFD); // connecting to TCP server iStatus = connect(iSockFD, (struct sockaddr *)&sAddr, iAddrSize); if (iStatus < 0) { LOGE(TAG, "TCP ERROR: tcp client connect server error! "); goto Exit; } LOGD(TAG, "TCP: Connect server successfully."); // sending multiple packets to the TCP server printf("[ ID] Interval Transfer Bandwidth\n"); while (running) { sprintf(cBsdBuf,"%02d",lLoopCount); // sending packet iStatus = send(iSockFD, cBsdBuf, strlen(cBsdBuf)+1, 0); if (iStatus <= 0) { printf("TCP ERROR: tcp client send data error! iStatus:%d", iStatus); goto Exit; } lLoopCount++; aos_msleep(100); if ((aos_now_ms() - time_ms) / 1000 > 2) { break; } } LOGD(TAG, "TCP: Sent packets successfully."); Exit: //closing the socket after sending 1000 packets close(iSockFD); return 0; }
3.3 通过console中断调用
int tcpclient(void); static void cmd_tcp_client_handler(char *wbuf, int wbuf_len, int argc, char **argv) { tcpclient(); } int cli_reg_cmd_ft(void) { static const struct cli_command tcp_cmd_info = { "tcptest", "tcp client test", cmd_tcp_client_handler, }; aos_cli_register_command(&tcp_cmd_info); return 0; }
3.4 Linux系统端的TCPServer测试程序
#include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <unistd.h> #include <netinet/in.h> #define SERVPORT 26666 #define BACKLOG 10 #define MAXDATASIZE 1024 int main() { struct sockaddr_in server_sockaddr;//声明服务器socket存储结构 int sin_size,recvbytes; int sockfd,client_fd;//socket描述符 char buf[MAXDATASIZE];//传输的数据 //1.建立socket //AF_INET 表示IPV4 //SOCK_STREAM 表示TCP if((sockfd = socket(AF_INET,SOCK_STREAM,0)) < 0) { perror("Socket"); exit(1); } printf("Socket successful!,sockfd=%d\n",sockfd); //以sockaddt_in结构体填充socket信息 server_sockaddr.sin_family = AF_INET;//IPv4 server_sockaddr.sin_port = htons(SERVPORT);//端口 server_sockaddr.sin_addr.s_addr = INADDR_ANY;//本主机的任意IP都可以使用 bzero(&(server_sockaddr.sin_zero),8);//保留的8字节置零 //2.绑定 fd与 端口和地址 if((bind(sockfd,(struct sockaddr *)&server_sockaddr,sizeof(struct sockaddr))) < 0) { perror("bind"); exit(-1); } printf("bind successful !\n"); //3.监听 if(listen(sockfd,BACKLOG) < 0) { perror("listen"); exit(1); } printf("listening ... \n"); //4.接收请求,函数在有客户端连接时返回一个客户端socket fd,否则则阻塞 //优化:这里同样可以使用select,以及poll来实现异步通信 if((client_fd = accept(sockfd,NULL,&sin_size)) == -1) { perror("accept"); exit(1); } printf("accept success! client_fd:%d\n",client_fd); while(1){ //5.接收数据 //注意:这里传入的fd,不是建立的socket fd,而是accept返回的连接客户端 socket fd if((recvbytes = read(client_fd,buf,MAXDATASIZE)) == -1) { perror("recv"); exit(1); } if(recvbytes == 0) { printf("client quit\n"); break; } printf("received data %d: %s\n",recvbytes,buf); } //6.关闭 close(sockfd); }
04 实测效果演示
RVB2601链接wifi并获取IP地址,IP地址为192.168.95.2
服务器的IP地址为:192.168.95.5
为了更好的展示通讯过程,在linux机器上运行tcpdump工具来抓取对应端口的网络数据包,通过wireshark分析这些数据包。
我们在linux系统上打开两个终端。
一个终端运行tcpdump:
sudo tcpdump tcp port 26666 and host 192.168.95.5 -i wlan0 -w ./1.cap
另一个终端运行tcpserver软件。
在RVB2601评估板的console中执行tcptest命令
通过wireshark分析tcpdump抓取的以太网TCP数据帧分析
05 特殊情况总结
RVB2061这种协议栈运行在W800的情况,无法实现TCPServer中accept和listen这些函数,因此,也就无法实现TCPServer这种功能了,只能采用TCPClient方式通讯。
06 下期预告
有关RVB2601开发板的以太网测试就先讲到这里,下期内容将为大家推荐RVB2601的麦克风录音测试。欢迎大家持续关注应用实战精解系列内容。