开篇介绍
由于项目中需要使用到STM32H7系列的芯片,且该系列无法移植ST的标准库,只能使用ST的HAL库,通过STM32Cube生成HAL库的基本代码。在项目开发中需要使用到STM32板载的ETH口,在简化的四层模型中充当着数据链路层的角色,我们只需添加PHY并配置好IP,即可实现基本的TCP主从机通信、UDP通信等等。
一、激光雷达3i-T1
由于使用了一款只具有网口通信的单线激光雷达,其具有TCP和UDP方式的通信方式,为了避免进行TCP中的socket报文解析和繁琐的通信握手过程,我们首先将雷达配置成UDP通信的模式,也就是网线直连通信,从比特层面进行数据交互。3i-T1激光雷达的OOB协议如下图所示:
3i-T1激光雷达的数据帧解析如下:
接着,在电脑上使用LVS2D上位机对激光雷达进行数据格式的配置,3i-T1激光雷达出厂默认IP是192.168.10.160,端口号为2105,必须配置同一网段(即192.168.10.XXX)后,才能与雷达通信;于是先将PC配置成静态IP——192.168.10.4,子网掩码为255.255.255.0:
然后使用LVS2D上位机将激光雷达设置为UDP通信模式(也即Out of-band模式):
从上位机中查看激光雷达的点云图大致如下:
至此,激光雷达的配置结束,接下来要调试STM32H7的ETH外设与LWIP了。
二、STM32CubeMX之ETH、LWIP配置
跳过STM32CubeMX的设备选型,进入可视化配置界面,首先,打开CORTEX-M7中的CPU ICache和DCache,若不打开此项,中间件Middleware中的LWIP中将无法打开!:
随后,配置系统时钟,PRVS设置为0,先将系统时钟调到最大值480MHz:
然后,使能ETH的RMII功能,将各个IO配置成Speed为High的模式,要注意其中还有三个管脚的重映射要更改,一切遵循着原理图进行!其他按照默认设置,同时使能UART3口作为板载串口调试,波特率115200bps,其他一切默认:
接着,配置Middleware中间件,由于我们使用的是静态IP,无需使用LWIP_DHCP模式,使其失能后,自己手动设置需要的静态IP、掩码和网关,其他一切按照默认配置进行:
最后,选取自己的IDE,点击Generate Code,生成HAL库代码,编译成功:
三、问题出现
在生成代码后,发现HAL库里成功将PHY芯片LAN8720A初始化成功了,但尝试与PC机进行Ping,提示无法访问目标主机。
因此,目前为了逐步查错,先把STM32H7使用静态IP的情况下与PC机Ping成功,再与激光雷达模块进行通信。
(2020-04-12)
重新调整思路以及学习LWIP
一、回顾TCP/IP分层
在计算机网络里,TCP/IP是一个协议族,包含着众多协议,包括ARP、IP、ICMP、UDP、TCP、DNS、HTTP、FTP、MQTT等。这些协议按照功能,可以划分为几个不同的层次:
TCP/IP协议栈各层的报文封装与拆封如下:
在本项目中,
①STM32H7为我们提供了以太口接口,充当于五层模型中的数据链路层,通过STM32Cube配置其物理地址(在Cube上是6个字节),以太口模式 (在嵌入式平台中,一般使用RMII以减少GPIO占用数量)以及收发的缓存区大小和中断相关开启等操作。
注:在一个网络里面,各种设备物理地址都是不同的。
②然后,我们采用PHY芯片LAN8720、网络接口HR911105A(里含变压器电路)以及网线,这些部分共同组成我们的物理层,负责比特字节层面的传输,其中PHY芯片LAN8720可配置10Mbp/s或100Mbp/s的半双工、全双工通信模式。在STM32Cube的HAL库中,由于版本的迭代和更新,最新版本下已采用了LAN8742作为PHY芯片的底层驱动,LAN8742与LAN8720比较看,两者管脚兼容,LAN8742是LAN8720 的升级版,多了一个MCO管脚,其他的芯片特性一样,寄存器配置完全一样。
注:LAN8720物理芯片的配置在STM32Cube中的中间层LWIP中。
③在STM32等中端嵌入式平台中,一般采用的是轻量化的TCP/IP协议——LwIP(Light weight IP),这是瑞典计算机科学院(SICS)的Adam Dunkels开发的一个小型开源的TCP/IP协议栈。其目的是以最低的系统开销实现“完整”的TCP/IP协议栈,减少RAM资源的占用。此外LwIP即可在裸机上移植,亦可在操作系统上移植。
注:截至博客发布时,目前HAL库上采用LwIP的迭代2.12版本。
二、LwIP的网络接口管理
1.网卡抽象层
为了于底层硬件网络接口衔接,LwIP作为轻量级的TCP/IP,为了兼容各种物理层芯片和底层硬件,其对网卡进行了抽象,网卡的抽象层都在netif.c和netif.h中,其中涵盖了许多关于网卡的操作,如网卡的注册和删除、启用与禁用、网卡IP设置等等。
在网卡初始化过程中,通过调用网卡抽象层的函数主要有:
1.netif_add(&gnetif, &ipaddr, &netmask, &gw, NULL, ðernetif_init, ðernet_input);
其中,gnetif是LwIP中定义的网卡抽象的结构体,HAL库在网卡初始化之前先往gnetif结构体填充好数据。ipaddr、netmask和gw分别设置为我们所需的stm32的静态IP、子网掩码和网关,这里我们设置成与雷达IP的同一网段,前三个地址字节是一样的,即可。
2.netif_set_default(&gnetif);
在经过步骤1的网卡抽象结构体填充后,通过步骤2来进行网卡注册,将网卡注册到网卡链表当中。
3.if (netif_is_link_up(&gnetif)){/* When the netif is fully configured this function must be called */netif_set_up(&gnetif);}else{/* When the netif link is down this function must be called */netif_set_down(&gnetif);}
接着调用netif_is_link_up(&gnetif)检查netif是否配置好,当配置好之后,一切就绪,调用netif_set_up(&gnetif);启动网卡,可以开始数据传输了。
2.网卡驱动层
网卡驱动层作为底层接口,为网卡抽象层提供注册操作,给每个netif接口提供访问硬件的支持。在实际开发移植中,我们往往需要根据自己实际网卡特性去完善修改底层驱动的函数即可。
与网卡驱动密切相关的函数有3个,分别是low_level_init()、low_level_output()和low_level_input()。
1.low_level_init()为网卡初始化函数,主要完成网卡的复位和参数初始化,根据实际的网卡属性配置netif中与网卡相关的字段,如网卡的MAC地址、长度、最大发送单元等等
2.low_level_output()为网卡的发送函数,主要将内核的数据包发送出去,数据包的封装采用pbuf数据结构进行描述,而pbuf数据结构专门用了pbuf.c和pbuf.h进行定义。
3.low_level_input()是网卡的数据接收函数,同样将接收到的数据采取pbuf的形式进行各层之间的递交,保持收发的一致性且方便LwIP内核处理。
三、亲手实践过程
由于采用硬石提供的整套Demo,我们是可以跑通整个过程的,远端设备能够Ping通。因此我把硬石官方代码中的ethernetif.c和ethernetif.h移植到了我自己的CubeMX工程中,把底层网卡驱动部分直接替换掉。在调用新版LwIP的网卡抽象层进行注册和初始化网卡。发现还是存在问题-_-!!(绝望)
底层硬件驱动部分:
结果发现Ping不通,还是显示主机无法访问。直接调用硬石模板能Ping通。
四、最终解决
在开发板Demo移植很多次后,几乎丧失了信心,在同样的代码上,只换了底层驱动,仍然不行。于是在五一期间歇了一下,重新调整,最后尝试着直接用Cube生成的HAL库进行网口开发,因为细想到,STM32是开源的大社区,全球的开发者都在共同维护和做贡献,我能做的也就相信Cube的官方库,具体细节再去微调。
重新配置STM32CubeMX情况如下:
1.配置CPU-Dcache和MPU,这里配置多了一个MPU Region,大小为16KB:
2.采用以太口中断接收数据,通过ethernet_input()接收数据并交给lwip内核处理:
3.其余配置按照之前进行,基本保持不变,生成代码后进行代码修改:
(1)在LAN8742初始化之前,先对其进行硬件复位:
(2)对以太口底层输入,去掉宏定义,令以太口接收缓存Cache失效:
(3)在定时检测ETH是否断线的函数中,在检测到断线时,取消掉HAL_ETH_Stop_IT():
(4)开启SRAM3的时钟,涉及了MPU那边的使用:
(5)在主函数中添加MX_LWIP_Process(),把ethernet_input()去掉,改在ETH_IRQHandler中添加,在中断里将以太数据传给lwip内核处理,同时清楚相关的中断;
其中sys_check_timeouts是lwip内部的超时处理函数,在主函数里面都是必须的;Ethernet_Link_Periodic_Handle是每100ms检测以太口是否断开或者重新连上,使网线热插拔后,网口都能重新自动连接和自动失效:
4.一切就绪后,在Keil5上编译并烧录到板子上,一切运作正常,在使用静态IP的情况下设备两端能正常ping通,说明物理层、数据链路层、网络层一切正常且就绪,接下来可以进行tcp或udp通信。
五、总结
lwip的网卡部分,其实HAL方面移植得已经非常不错,总体过程大致如下:
1.lwip内核初始化,lwip_init();
2.设置静态本地IP、子网掩码和网关或者采用DHCP方法分配本地一个动态IP、子网掩码和网关;
3.分配好IP、子网掩码和网关等本地设置后,调用lwip接口进行网口注册,若注册成功且网线已经接好,启动网卡,若注册失败或网线没接上,关闭网卡。
4.注册断线检测和网线重连函数,在回调函数中进行网线启停处理。