本专栏由非官方人员 王小小海 所著,其内容主要记录了在开发NCS的过程中遇到的一些问题和解决方法,还有一些应用的例程。作者本人也是在实践应用中遇到的问题,想着把这些问题分享给可能遇到的朋友。仅仅做个人技术交流分享,不做任何商业用途。如有不对之处,请留言,本人及时更改。
本专栏不涉及基础的安装和环境搭建问题,本例程开发使用NCS 2.8.0开发,还请注意!。
所有分享内容
笔记分享
- 【笔记分享】NCS下radio_test添加FEM
- 【笔记分享】5340基于 BLE LBS 自定义网络核固件点亮LED并合并固件
- 【笔记分享】5340基于LBS自定义网络核双核DFU实现
- 【笔记分享】5340 设置public address 和 random address
- 【笔记分享】NCS nRF52/53 添加LVGL组件驱动屏幕
- 【笔记分享】VirtualBox Ubuntu22.04 不能使用nrfjprog问题记录
- 【笔记分享】5340使用内部负载电容调频偏
- 【笔记分享】基于 LE Audio 例程移植到nRF52840上运行思路
- 【nRF52/53】【笔记分享】基于 BLE LBS DFU使用内部外部Flash 升级
- 【nRF54H20】基础介绍与使用介绍
应用分享
暂无
前言
最近在使用 nRF54L15 开发蓝牙网关的时候,SPIM挂了一个W5500,UART挂了一个 ESP8266。
.
两个网卡都能正常在Zephyr的网络协议中挂载网卡,并且也能通过 Zephyr的网络函数收发数据。
.
使用条件是两个网卡不会同时使用。
.
文章末尾有详细 挂载方法和配置方法。
问题描述
- 双网卡IP为DHCP分配的IP时候 TCP连接同一个服务器,不管怎么去切换网卡,都能正常收发数据。
- 当设置为静态IP时,假设当前IP地址为:192.168.1.100,设置W5500 为192.168.1.100 首次使用TCP连接服务器,能够正常首发数据,然后切换到WiFi 网卡 设置IP地址为:192.168.1.100 使用TCP连接服务器,也能正常收发数据。
- 先把WiFi网卡断开路由器,在切换到W5500网卡时。结果发现TCP无法正常连接服务器。一直提示 TCP Port number reused。
- 只要按照 W5500 —> ESP8266 —> W5500 的使用方式,必现问题。
- 每次复位又能正常连接服务器了。
问题分析
- 已经在切换网卡之前,断开了WiFi,那么网卡应该是W5500,通过打印查看确实是。
- 可以通过上面的图片确认,本地端口确实没有被用到,每次分配的都是新的端口。
以上分析,基本确认确实是代码里面的网卡处理的逻辑有问题。但是一时半会儿发现不了问题。
最后打开Wireshark 将与服务器的连接过程的抓包数据,一句一句的点开分析,最后找到了问题。
问题解析
第一次通过 W5500 进行通信,可以看到TCP 的SYN 的MAC地址确实是W5500的MAC地址。
切换到 ESP8266 进行通信,TCP 的SYN确实是 ESP8266的MAC地址。
切换到W5500 通信,SYN发送的MAC地址是 W5500的MAC地址,但是服务器回复 SYN,ACK的MAC地址竟然是之前 ESP8266的MAC地址。
通过分析问题可以总结为网卡切换时,即使ESP8266 断开了路由,也无法让服务器 底层的ARP池忘记 之前IP 的MAC地址。
.
那么双网卡设置为同样的IP时,需要将 活动的网卡 去使能,然后在 使能 需要活动的网卡才能避免此问题。
.
其核心原理还是因为没有arp去更新,新的IP和Mac之间的关系。重新使能网卡会产生一次ARP。
.
具体见下面代码:
int net_mgmt_restart(void)
{
int ret;
struct net_if *iface = net_if_get_default();
if (!net_if_is_admin_up(iface))
{
ret = net_if_up(iface);
if (ret)
{
LOG_ERR("Cannot bring up iface (%d)", ret);
return ret;
}
LOG_INF("Interface up");
}
return 0;
}
int net_mgmt_stop(void)
{
int ret;
struct net_if *iface = net_if_get_default();
if (net_if_is_admin_up(iface))
{
ret = net_if_down(iface);
if (ret)
{
LOG_ERR("Cannot bring up iface (%d)", ret);
return ret;
}
LOG_INF("Interface Down");
}
return 0;
}
static int net_mgmt_ip_addr_remove(struct net_if *iface, struct in_addr *ip)
{
if (net_ipv4_is_addr_mcast(ip))
{
int ret = net_ipv4_igmp_leave(iface, ip);
if (ret < 0)
{
LOG_ERR("Cannot %s multicast group %s (%d)\n",
"leave", net_utils_sprint_addr(ip), ret);
return ret;
}
}
else
{
if (!net_if_ipv4_addr_rm(iface, ip))
{
LOG_ERR("Failed to delete %s\n", net_utils_sprint_addr(ip));
return -ENOEXEC;
}
}
return 0;
}
static int net_mgmt_clear_info(struct net_if *iface)
{
int ret = 0;
for (uint8_t i = 0; i < NET_IF_MAX_IPV4_ADDR; i++)
{
if (iface->config.ip.ipv4 != NULL)
{
ret = net_mgmt_ip_addr_remove(iface,
&iface->config.ip.ipv4->unicast[i].ipv4.address.in_addr);
if (ret != 0)
{
// 重启网卡.
net_mgmt_stop();
LOG_INF("net restart!");
k_sleep(K_MSEC(1000));
net_mgmt_restart();
LOG_INF("net restart done!");
}
}
}
return 0;
}
int netif_init(net_if_t interface)
{
struct net_if *iface = NULL;
// 停止DHCPv4.
net_dhcpv4_stop(net_if_get_default());
// 清除网络信息, 如果ETH和WLAN都设置为相同的IP会默认选择ETH接口.
// 导致HARDFAULT.
net_mgmt_clear_info(net_if_get_default());
// 如果当前设置为静态IP时, 以太网和WiFi使用同一个IP, 即使WiFi断开了连接,
// 依然也会导致无法正常连接, 解决这个问题需要将不使用的网卡停止.
// 停止当前网卡.
net_mgmt_stop();
// 选择当前网卡.
switch (interface)
{
case NET_IF_ETH:
// 断开WiFi连接.
net_mgmt_link_stop();
iface = net_if_get_by_index(net_if_get_by_name(ETH_MGMT_IFACE_NAME));
break;
case NET_IF_WLAN:
iface = net_if_get_by_index(net_if_get_by_name(WIFI_MGMT_IFACE_NAME));
break;
default:
return -EINVAL;
}
// 重置当前网络接口.
if (iface != NULL)
{
// 设置当前网络接口为默认接口.
net_if_set_default(iface);
// 重启网卡.
net_mgmt_restart();
return 0;
}
return -EINVAL;
}
附加
看了上面的文章 大家也知道了具体问题,避免了踩坑的风险。有朋友也想通过这种方式去挂载 双网卡,那么下面我们开始介绍。
不管是使用的什么芯片,在Zephyr下方法都是通过设备树使能。我使用的nRF54Lxx,不过不限于nRF54L,可以是nRF52/nRF53,或者是其他MCU。
&pinctrl {
spi3_default: spi3_default {
group1 {
psels = <NRF_PSEL(SPIM_MISO, 0, 8)>,
<NRF_PSEL(SPIM_MOSI, 0, 14)>,
<NRF_PSEL(SPIM_SCK, 0, 6)>;
nordic,drive-mode = <NRF_DRIVE_H0H1>;
};
};
spi3_sleep: spi3_sleep {
group1 {
psels = <NRF_PSEL(SPIM_MISO, 0, 8)>,
<NRF_PSEL(SPIM_MOSI, 0, 14)>,
<NRF_PSEL(SPIM_SCK, 0, 6)>;
low-power-enable;
};
};
uart1_default: uart1_default {
group1 {
psels = <NRF_PSEL(UART_TX, 0, 20)>;
nordic,drive-mode = <NRF_DRIVE_H0H1>;
};
group2 {
psels = <NRF_PSEL(UART_RX, 0, 17)>;
nordic,drive-mode = <NRF_DRIVE_H0H1>;
};
};
uart1_sleep: uart1_sleep {
group1 {
psels = <NRF_PSEL(UART_TX, 0, 20)>,
<NRF_PSEL(UART_RX, 0, 17)>;
low-power-enable;
};
};
};
&spi3 {
compatible = "nordic,nrf-spim";
status = "okay";
pinctrl-0 = <&spi3_default>;
pinctrl-1 = <&spi3_sleep>;
pinctrl-names = "default", "sleep";
max-frequency = <DT_FREQ_M(32)>;
cs-gpios = <&gpio0 4 GPIO_ACTIVE_LOW>;
ethernet: w5500@0 {
compatible = "wiznet,w5500";
reg = <0x0>;
spi-max-frequency = <DT_FREQ_M(32)>;
int-gpios = <&gpio0 16 GPIO_ACTIVE_LOW>;
reset-gpios = <&gpio0 13 GPIO_ACTIVE_LOW>;
};
};
&uart1 {
status = "okay";
current-speed = <115200>;
pinctrl-0 = <&uart1_default>;
pinctrl-1 = <&uart1_sleep>;
pinctrl-names = "default", "sleep";
esp_at: esp_at {
compatible = "espressif,esp-at";
status = "okay";
target-speed = <1000000>;
reset-gpios = <&gpio0 15 GPIO_ACTIVE_LOW>;
};
};
将以上的内容添加到对应板子设备树文件即可,如果用的 Nordic 芯片建议 串口开启硬件异步功能。
CONFIG_UART_0_ASYNC=n
CONFIG_UART_1_NRF_HW_ASYNC=y
CONFIG_UART_1_NRF_HW_ASYNC_TIMER=2
prj.conf 文件配置
# 自定义ETH网络接口.
CONFIG_ETH_DRIVER=y
CONFIG_MODEM=y
CONFIG_WIFI=y
CONFIG_WIFI_ESP_AT_DNS_USE=y
CONFIG_WIFI_ESP_AT_PASSIVE_MODE=y
CONFIG_WIFI_ESP_AT_MDM_RING_BUF_SIZE=4096
CONFIG_WIFI_ESP_AT_RX_STACK_SIZE=2048
CONFIG_MODEM_IFACE_UART_ASYNC=y
CONFIG_MODEM_IFACE_UART_ASYNC_RX_BUFFER_SIZE=1400
CONFIG_MODEM_IFACE_UART_ASYNC_RX_NUM_BUFFERS=3
其他的就按照正常网络配置添加就好了。参考如下:
####################### NETWORK ###################
CONFIG_NETWORKING=y
CONFIG_NET_SOCKETS=y
CONFIG_NET_SOCKETS_POSIX_NAMES=y
CONFIG_NET_IPV4=y
CONFIG_NET_IPV4_IGMP=y
CONFIG_NET_IPV6=n
CONFIG_NET_DHCPV4=y
CONFIG_NET_TCP=y
CONFIG_NET_TCP_KEEPALIVE=y
CONFIG_NET_TCP_KEEPINTVL_DEFAULT=120
CONFIG_NET_UDP=y
CONFIG_DNS_RESOLVER=y
CONFIG_NET_CONTEXT_RCVTIMEO=y
CONFIG_NET_CONTEXT_SNDTIMEO=y
CONFIG_NET_CONFIG_SETTINGS=y
CONFIG_NET_CONFIG_AUTO_INIT=n
CONFIG_NET_CONFIG_INIT_TIMEOUT=0
CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.168.1.2"
CONFIG_NET_CONFIG_MY_IPV4_NETMASK="255.255.255.0"
CONFIG_NET_CONFIG_MY_IPV4_GW="192.168.1.1"
CONFIG_NET_IF_MAX_IPV4_COUNT=2
CONFIG_NET_HOSTNAME_ENABLE=y
CONFIG_NET_HOSTNAME_DYNAMIC=y
CONFIG_NET_PKT_RX_COUNT=16
CONFIG_NET_PKT_TX_COUNT=16
CONFIG_NET_BUF_RX_COUNT=32
CONFIG_NET_BUF_TX_COUNT=32
CONFIG_NET_BUF_DATA_SIZE=512
CONFIG_NET_TC_RX_COUNT=0
CONFIG_NET_L2_ETHERNET=y
CONFIG_NET_L2_ETHERNET_MGMT=y
CONFIG_NET_MGMT_EVENT_STACK_SIZE=1024
CONFIG_ZVFS_OPEN_MAX=5
CONFIG_NET_MAX_CONN=5
CONFIG_NET_MAX_CONTEXTS=5
CONFIG_NET_SOCKETS_POLL_MAX=5
结束语
好的,本次分享基本上就是这些。
有不明白的地方欢迎提问,也厚脸皮要个赞或者关注,谢谢各位啦。
如果有哪位朋友需要定制方案,也可以联系私信我。感谢大家的浏览。
本系列文章大多数是本人遇到和解决过的问题,难有疏忽之处,有什么问题或者不明白的地方,欢迎留言询问!