阐述下此实验目的,就是通过移植lwip协议栈在MCU上完成一个服务器,从浏览器访问板子的网页。
先来张B图瞅瞅
需求:一块板子 ram有10几K就行,一个串口(我们使用串口去替代网卡(slip协议),方便大家学习与理解)。
1.我这手里是刚好有块STM32L476VGT6的板子,我就移植到此板子测试。
2.快速操作STM32Cube直接生成MDK V5的工程,开启串口1,波特率9600,开启串口接收中断如下图所示,一分钟启动。
3.去 http://download.savannah.nongnu.org/releases/lwip/ 我这直接往下拉然后下载了个最新的lwip-2.1.2.zip。然后解压没毛病。
4.将整个解压的协议栈拷贝到工程目录下,然后在MDK上新建三个目录 LWIP-NETIF, LWIP-CORE,LWIP-API。然后将协议栈里src目录下的netif的slipif.c加入到目录LWIP-NETIF中(因为我们要用串口代替网卡完成骚操作),将src中core目录下所有.c以及ipv4里所以.c加入到LWIP-CORE中(ipv6不用),最后将src的api所有文件加到LWIP-API中完整如下图所示。将src\include 加入到头文件搜索目录。
5.编译一下,会发现没有 lwipopts.h这头文件,没事我们自己定义这个头文件(主要是用来配置自己所需要的功能)
#ifndef LWIP_HDR_LWIPOPTS_H
#define LWIP_HDR_LWIPOPTS_H
//#define LWIP_DEBUG 1
#define MEM_ALIGNMENT 4
#define NO_SYS 1
#define SYS_LIGHTWEIGHT_PROT 0
#define LWIP_NETCONN 0
#define LWIP_SOCKET 0
#define LWIP_ETHERNET 0
#define LWIP_ARP 0
#define MEM_SIZE 4096
#define TCP_SND_QUEUELEN 6
#define TCP_SNDLOWAT 1071
#define PBUF_POOL_SIZE 5
#endif
我这里从简配置如上,小巧玲珑贼香。
再编译还是报错没有#include "arch/cc.h" 看注解,我们明显有memset可用,所以这里直接不加入这个头文件,注释后面加
#define PACK_STRUCT_BEGIN __packed 因为lwip协议栈用这个宏对齐,所以定义 __packed这个对齐操作。
继续编译结果如下
这几个没有实现的函数就很重要的了进入下一步详解。
6.
第一个 sio_open 就是打开串口的一个函数用户需要自己实现这个函数并返回一个sio_fd_t
第二个sio_send函数这个就是完成字符发送的一个函数实现
第三个sio_tryread函数系统读取接收到的数据的一个接口
第四个sys_now 就是给一个ms的变化值类似于提供时钟节拍。
我这里定义了一个lwip_app.c完成了上述4个函数并增加了lwip_app_init函数。因为不能上传.c后缀这里把后缀改为了.gz下载后重命名即可。
lwip_app.gz
如下图在main函数中加入下面三个函数即可
编译完成没毛病,然后在串口中断函数中加入接收缓存
下面为串口1中断接收函数,将数据缓存给slipif使用。
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
uint8_t Res;
if((__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE) != RESET))
{
HAL_UART_Receive(&huart1, &Res, 1, 1000);
extern void sio_setData(uint8_t data); //此函数在lwip_app中接收串口数据缓存
sio_setData(Res);
}
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
至此已经完成了lwip的移植,开启了tcp功能。
下面进行骚操作,整个虚拟机装个Linux。然后将ttl转USB的串口接入到Linux中
1.打开一个终端运行 sudo slattach /dev/ttyUSB0 -p slip -s 9600 -m -d 这里的ttyUSB0就是接入的ch430的ttl转USB接口。如果没有slattach命令可以根据提示安装即可。
2.sudo ifconfig sl0 2.23.29.180 pointtopoint 2.23.29.160 up。第一个IP在板子里设置为网关,第二个IP就是板子自己的IP用于通讯。
3.设置完可以使用ifconfig 查看就多了新增的网卡了结果如下图所示:
然后板子上电启动。在linux终端下ping 2.23.29.160然后就会神奇的通过了,而且是通过串口连通的。
有兴趣的可以看看slipif.c文件没多少代码 研究研究就可以理解了。这里无非是把网卡做的事变成了串口来进行数据传输。从这里可以看出如果我们需要用网卡进行数据传输需要完成哪部分功能,需要给协议栈什么东西就很容易理解了。
离线
协议栈已经跑起来,然后我们来说说tcp和http问题。
我们经常会遇到Http协议或者TCP协议,这里做一个简单的说明。
TCP协议是在传输层,HTTP协议在应用层,Http协议是建立在TCP协议基础之上的,当我们的浏览器从服务器获取网页数据时,会发出一次Http请求。Http会通过TCP建立起一个到服务器的连接通道,当本次请求需要的数据完毕后,Http会立即将TCP连接断开,这个过程是很短的。所以Http连接是一种短连接,是一种无状态的连接。所谓的无状态,是指浏览器每次向服务器发起请求的时候,不是通过一个连接,而是每次都建立一个新的连接。如果是一个连接的话,服务器进程中就能保持住这个连接并且在内存中记住一些信息状态。而每次请求结束后,连接就关闭,相关的内容就释放了,所以记不住任何状态,成为无状态连接。
可能很多人都知道这个,但是又不知道怎么去理解它。那么针对这个问题我们上面已经移植了协议栈,这个时候我们就可以通过协议栈将板子开启tcp服务器了。既然都说http是建立在TCP协议之上的,那么我们首先要实现TCP的一个传输通讯,接着上章的工程这里我从样例里随便改了下实现tcpserver的一个例子代码如下同上下载后重新命名.c即可。
tcp_echoserver.gz
在主函数中调用tcp_echoserver_init();初始化即可一个开启tcp服务器了。
然后我在dispose_recv_data(struct tcp_pcb *tpcb, struct tcp_echoserver_struct *es)(tcp_echoserver.c里面)这是tcp接收到数据后的一个处理函数
在这里我将接收到的数据返回给客户端。
启动程序,然后我在linux下用python写了个脚本tcp.py去连接我们的板子,看是否可以完成tcp的通讯。
import socket
if __name__ == '__main__':
user_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print('connect...')
user_sock.connect(('2.23.29.160',80))
print('send message')
pStr =''.join (str(i%10) for i in range(200))
user_sock.send(pStr)
ret = user_sock.recv(4096)
print('recv:%s' %ret.decode())
user_sock.close()
运行 python tcp.py 发送收据然后又接收到板子返回的数据。至此我们基于lwip协议栈的tcp数据传输就已经可以了。
离线
在TCP通讯完成的情况下实现一个网页的传输,让浏览器可以访问并显示。
我们在工程目录下创建LWIP-HTTP目录,然后将协议栈下 src/apps/http目录下的fs.c加入进来把原来注释的那部分代码启用。
这个时候我们可以清楚的看到http请求的数据是通过TCP传输过来的,我们在TCP接收数据的函数里去响应客户端的http请求,依旧是将数据通过tcp发送给客户端。
这里面的 fs_open 就是去读取一个网页的数据,就是客户端请求哪个网页或者是哪张图片。网页的数据都通过软件转为了数据存储起来,网页文件数据在fsdata.c里面。
在lwip协议栈的src\apps\http\makefsdata下有把网页和图片生成fsdata.c的程序。我们可以把上一目录的fs文件夹拷贝到此目录下然后如下图运行makefsdata程序如果你无法运行,也可以编译那个.c来运行程序会把fs下的所有网页(可以修改里面的网页或者自己新增都可以)或者图片打包成一个fsdata.c这个文件拷贝到上一目录就是新的了。
到此整个过程就完毕了,如果自己动手亲自去移植并且理解下代码,可以非常深的加深自己对于http,tcp,网卡他们之间的关系的理解了。网卡(串口)完成的就是物理层的传输协议。tcp是传输层的协议。http又是架在tcp通讯上的一层应用协议。至此如果以后遇到抓什么http协议之类的。妥妥的可以直接通过TCP把http的数据整块的偷出来了。再底层一点甚至可以直接从网卡上将数据抓出来,然后将数据从自己的协议栈解析并且把数据全部暴露出来。
离线
无法形容,只能说lz牛逼。脑洞大开一下,既然串口可以用作物理网卡完成物理层协议,那么其他通讯口应该也可以,i2c,spi,can甚至自己用io模拟都可以用作网卡了.
从理论上来说当然是可以的,问题是你得有对应接口的解析程序。这里只是为了阐述他们直接的关系使用=。=
离线
还是没明白是怎么把 串口当成网口 用的。
是否是这样:
板子端 是 slipif.c 来实现,电脑端 是 sudo ifconfig sl0 2.23.29.180 pointtopoint 2.23.29.160 up 来实现 ?
LWIP可以将需要传输的数据打包成TCP的数据包(传输层),正常情况下需要使用网卡(物理层)将数据传送到另外的机器上同时另外机器上也有网卡去接收这里的数据,然后再解析TCP数据包。这里等LWIP将TCP数据包打包之后直接通过串口发送出去(这里也不是直接裸发,因为要定义串口自己的一种收发协议SLIP),然后Linux上运行slattach 完成串口数据的解析还原TCP数据包。
离线
vip888888 说:从理论上来说当然是可以的,问题是你得有对应接口的解析程序。这里只是为了阐述他们直接的关系使用=。=
楼主这里的串口并没有完成物理层协议,只是这里的通信是没有经过物理层的
物理层就是传输数据的,网卡通过网线到另外一个网卡,就是物理传输的过程了。我这里就是串口1加几根线到另外一台机器的串口2。就像你可以使用WIFI芯片通过无线发送接收,这也算是物理层吧。不过物理层协议确实是没有的。只能说是物理传输介质换了吧。
最近编辑记录 vip888888 (2020-09-11 12:14:42)
离线
楼主,能否出个教程,移植LWIP2.1.2最新版的,网上普遍1.4的,结构不大一样,以前只用过uIP1.0,LWIP还是初次接触,芯片用的ENC424J600,UIP的已经移植没问题的,想试试LWIP,我是使用PIC24FJ512GB606
你说的这个ENC424J600我没有我上网查了下这个芯片还是内置PHY和MAC层,此芯片的驱动我就不说了自行完成接收和发送帧,封装成一个发送函数一个接收函数这个没问题吧?,我们就说LWIP协议移植的关键点在哪。你看我给的整个例子LWIP是直接下载的。然后你看我初始化的时候提供的东西就知道了。因为你使用的是以太网卡所以配置那里开启#define LWIP_ETHERNET 1 这个置1就行,我没用所以我置0了。然后你将协议栈里面netif文件夹里ethemet.c加进工程,然后初始化的时候加入你自己的初始化。
然后基本上每张网卡的初始化就是一个struct netif结构。你去看下此结构就明白需要给什么了。实际上需要物理层提供的东西1.一个网卡包发送函数初始化到netif结构体上,2一个接收到网卡数据包函数当然这里接收到的数据需要用数据结构 struct pbuf链表连起来。然后像我给的例子一样调用下面处理函数就行了。
if (netif->input(p, netif) != ERR_OK) {
pbuf_free(p);
}
离线
我又发现了另外一个开源的TCP/IP 协议栈,竟然直接支持ENC424J600,自带Driver,楼主有兴趣没,整个移植教程:)
https://www.oryx-embedded.com/products/CycloneTCP
https://whycan.cn/files/members/4881/2020-09-12_114303.png
https://whycan.cn/files/members/4881/072941fc66oyk4chlgowkc.jpg
=。=没事移植辣么多协议栈干哈,下次做玩具玩了。
离线
楼主,下边这个问题怎么解决呢?
这是个16位的单片机,XC16编译器,是编译器不支持吗?
#define PACK_STRUCT_BEGIN __packed[main.c@0092]:Project Name:PIC24FJ512GB606
[main.c@0097]:RN4870 Init:Success
[main.c@0103]:BM64 Init:Success
[main.c@0104]:DSP Init:Success
Assertion "Struct packing not implemented correctly. Check your lwIP port." failed at line 340 in src/lwip-2.1.2/src/core/init.c
ABRT
你把 lwipopts.h的那个 MEM_ALIGNMENT 4 四字节对齐 改成MEM_ALIGNMENT 2 两字节试试。
离线
这是LWIP 路由器里能看到获取IP,但ping不通
[main.c@0088]:Build DateTime:Sep 15 2020 11:43:50
[main.c@0089]:Project Name:PIC24FJ512GB606
[main.c@0094]:RN4870 Init:Success
[main.c@0100]:Set BLE Name:Success
netif: netmask of interface set to 255.255.255.0
netif: GW address of interface set to 192.168.6.1
netif_set_ipaddr: netif address being changed
netif: added interface en IP addr 192.168.6.123 netmask 255.255.255.0 gw 192.168.6.1
netif: setting default interface en
etharp_request: sending ARP request.
etharp_raw: sending raw ARP packet.
ethernet_output: sending packet 528e
dhcp_start(netif=60a2) en0
dhcp_start(): mallocing new DHCP client
dhcp_start(): allocated dhcpdhcp_start(): starting DHCP configuration
dhcp_discover()
transaction id xid(0)
dhcp_discover: making request
dhcp_discover: sendto(DISCOVER, IP_ADDR_BROADCAST, LWIP_IANA_PORT_DHCP_SERVER)
ip4_output_if: en0
IP header:
+-------------------------------+
| 4 | 5 | 0x00 | 336 | (v, hl, tos, len)
+-------------------------------+
| 0 |000| 0 | (id, flags, offset)
+-------------------------------+
| 255 | 17 | 0xba9d | (ttl, proto, chksum)
+-------------------------------+
| 0 | 0 | 0 | 0 | (src)
+-------------------------------+
| 255 | 255 | 255 | 255 | (dest)
+-------------------------------+
ip4_output_if: call netif->output()
ethernet_output: sending packet 52c6
dhcp_discover: deleting()ing
dhcp_discover: SELECTING
dhcp_discover(): set request timeout 2000 msecs
[main.c@0122]:netif_is_link_up.
etharp_timer
etharp_timer
dhcp_fine_tmr(): request timeout
dhcp_timeout()
dhcp_timeout(): restarting discovery
dhcp_discover()
transaction id xid(0)
dhcp_discover: making request
dhcp_discover: sendto(DISCOVER, IP_ADDR_BROADCAST, LWIP_IANA_PORT_DHCP_SERVER)
ip4_output_if: en0
IP header:
+-------------------------------+
| 4 | 5 | 0x00 | 336 | (v, hl, tos, len)
+-------------------------------+
| 1 |000| 0 | (id, flags, offset)
+-------------------------------+
| 255 | 17 | 0xba9c | (ttl, proto, chksum)
+-------------------------------+
| 0 | 0 | 0 | 0 | (src)
+-------------------------------+
| 255 | 255 | 255 | 255 | (dest)
+-------------------------------+
ip4_output_if: call netif->output()
ethernet_output: sending packet 52c6
dhcp_discover: deleting()ing
dhcp_discover: SELECTING
dhcp_discover(): set request timeout 4000 msecs
etharp_timer
etharp_timer
etharp_timer
etharp_timer
dhcp_fine_tmr(): request timeout
dhcp_timeout()
dhcp_timeout(): restarting discovery
dhcp_discover()
transaction id xid(0)
dhcp_discover: making request
dhcp_discover: sendto(DISCOVER, IP_ADDR_BROADCAST, LWIP_IANA_PORT_DHCP_SERVER)
ip4_output_if: en0
IP header:
+-------------------------------+
| 4 | 5 | 0x00 | 336 | (v, hl, tos, len)
+-------------------------------+
| 2 |000| 0 | (id, flags, offset)
+-------------------------------+
| 255 | 17 | 0xba9b | (ttl, proto, chksum)
+-------------------------------+
| 0 | 0 | 0 | 0 | (src)
+-------------------------------+
| 255 | 255 | 255 | 255 | (dest)
+-------------------------------+
ip4_output_if: call netif->output()
ethernet_output: sending packet 52c6
dhcp_discover: deleting()ing
dhcp_discover: SELECTING
dhcp_discover(): set request timeout 8000 msecs
etharp_timer
etharp_timer
etharp_timer
etharp_timer
etharp_timer
ethernet_input: dest:22:33:44:55:08:00, src:45:00:01:50:00:02, type:0
etharp_timer
ethernet_input: dest:11:22:33:44:55:08, src:00:45:00:01:50:00, type:200
etharp_timer
etharp_timer
dhcp_fine_tmr(): request timeout
dhcp_timeout()
dhcp_timeout(): restarting discovery
dhcp_discover()
transaction id xid(0)
dhcp_discover: making request
dhcp_discover: sendto(DISCOVER, IP_ADDR_BROADCAST, LWIP_IANA_PORT_DHCP_SERVER)
ip4_output_if: en0
IP header:
+-------------------------------+
| 4 | 5 | 0x00 | 336 | (v, hl, tos, len)
+-------------------------------+
| 3 |000| 0 | (id, flags, offset)
+-------------------------------+
| 255 | 17 | 0xba9a | (ttl, proto, chksum)
+-------------------------------+
| 0 | 0 | 0 | 0 | (src)
+-------------------------------+
| 255 | 255 | 255 | 255 | (dest)
+-------------------------------+
ip4_output_if: call netif->output()
ethernet_output: sending packet 52c6
dhcp_discover: deleting()ing
dhcp_discover: SELECTING
dhcp_discover(): set request timeout 16000 msecs
etharp_timer
etharp_timer
etharp_timer
etharp_timer
etharp_timer
ethernet_input: dest:22:33:44:55:08:00, src:45:00:01:50:00:03, type:0
etharp_timer
ethernet_input: dest:11:22:33:44:55:08, src:00:45:00:01:50:00, type:300
etharp_timer
etharp_timer
etharp_timer
etharp_timer
etharp_timer
etharp_timer
etharp_timer
看你这里 输出的log来说好像是发现了DHCP服务器,然后发送什么请求(是请求IP地址?)。前面的好几个都是失败的,最后这个是log是请求成功了吗?type一个是0一个是300的你看下表示的是啥东西?我没遇到过这情况不是很清楚,看两个input数据看起来好像丢了数据一样的,第二条比第一条dest左移了还是丢失了一个字节, src就想又移了一个字节一样的。是哪里啥问题咩?
离线