打印
[信息]

LwIP在stm32上的无操作系统移植

[复制链接]
7186|25
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
LwIP是一个轻型IP协议,有无操作系统的支持都可以运行。这里的移植是无操作系统移植。
LwIP虽然是一个轻型的IP协议,但是TCP/IP基本功能都有。而且占用的资源不多,非常适合用于嵌入式系统。
移植的平台:STM32F103VE+MDK 4.7+ LWIP-1.4.1


下载LwIP-1.4.1源码以及contrib-1.4.1_官网地址
下载ST官方LWIP参考实例下载地址
我的移植实例+LwIP-1.4.1+contrib-1.4.1_+ST官方lwIP移植实例+串口网络调试助手


1:移植工程描述
     将Lwip-1.4.1移植到野火stm32开发板上,无操作系统,轮询网络接口,使用TCP接口对数据进行收发。将stm32与PC通过路由器连接在一个局域网内进行通信测试。


2:在进行移植之前必须知道LwIP源码的框架结构
LwIP-1.4.1包含了协议栈的核心源码。
其目录结构如下:


在 src 目录下的 api 文件夹保存的文件中,包含了适用于具有操作系统平台调用的应用
层接口函数。core文件夹下的文件内容为 LwIP 协议栈对于各种协议的实现。netif下的文

件则保存了与硬件底层关系比较紧密的函数。这三个文件夹下的都是c 源文件,它们的头
文件都被保存到 include 文件夹中。在实际应用中,可根据需要进行裁剪(前期移植不管了,就将这些源码全部添加到工程中)。


3:工程文件准备
A:将LwIP-1.4.1的src文件夹的所有文件和文件夹复制到自己的工程中。
B:将contrib-1.4.1_ 目录\contrib-1.4.1_\contrib\ports old\6502\include\arch的 cc.h、perf.h 和 sys_arch.h 这三个文件复制到Lwip文件夹中的自己创建的arch文件夹中。其中 cc.h 包含了 LwIP 对于基本数据类型的定义。sys_arch.h 定义了与系统
有关的信号量、邮箱及线程。
C:ST库文件。将版本3.5版本固件库复制到stmlib工程文件下。
D:串口和systicks的bsp文件复制到bsp文件夹。
E:将以太网模块驱动enc28j60文件夹复制到工程app文件夹下(这里用的是野火的模块以及程序)
F:将st工程模板中的project文件夹中src文件夹中的netconf.c和 inc文件夹的netconf.h复制到工程user文件夹中
G:将stm32f10x_conf.h,stm32f10x_it.h,stm32f10x_it.c复制到工程user文件夹下。

4:硬件链接
以太网模块enc28j60与开发板SIP2链接。

5:建立工程,设置相关头文件路径,将源文件添加进工程。在创建main.c main.h以及iptest.c iptest.h(测试功能模块)。如下图所示:





沙发
幸福小强|  楼主 | 2018-5-13 19:52 | 只看该作者
6:移植
LwIP协议栈给底层的驱动提供了接口,如初始化网卡、发送数据包、接收数据包等最底层的操作。这些接口定义位于\lwip-
1.4.1\src\netif 目录下的 ethernetif.c 文件,该文件已经被添加进了工程中,接口的具体实现因使用的网络接口不同而有所区别。
移植的主要工作就是将ethernetif.c中的功能实现。在官方源码中ethernetif.c提供了模板,我们只需要参照st实例根据我们的硬件去实现这些函数功能即可。
需要我们进行修改的函数为low_level_init,low_level_output,low_level_input这三个函数(橙色标记为修改部分),以及定义相关的数据结构和宏即可。
low_level_init主要功能是进行底层硬件的初始化,具体看注释
static void low_level_init(struct netif *netif)
{
  struct ethernetif *ethernetif = netif->state;


  /* set MAC hardware address length */
  netif->hwaddr_len = ETHARP_HWADDR_LEN;


  /* set MAC hardware address */
  netif->hwaddr[0] = MacAddr[0]; //根据硬件mac地址,初始化netif结构体的硬件地址信息。该模块mac地址可以由用户定义。
  netif->hwaddr[1] = MacAddr[1]; //本实例的mac地址定义在 全局变量数组uint8_t MacAddr[6] = {0x54,0x55,0x58,0x10,0x00,0x24};
  netif->hwaddr[2] = MacAddr[2];
  netif->hwaddr[3] = MacAddr[3];
  netif->hwaddr[4] = MacAddr[4];
  netif->hwaddr[5] = MacAddr[5];


  /* maximum transfer unit */
  netif->mtu = 1500;


  /* device capabilities */
  /* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */
  netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP;


  /* Do whatever else is needed to initialize interface. */
  //下面调用的两个函数都是根据特定硬件进行初始化的,具体根据硬件的不同而实现不同。
   enc28j60Init(MacAddr); //初始化以太网模块,并设置mac值(根据不同的芯片不同)
   enc28j60clkout(2);//设置模块时钟。注意这里必须初始化,在复位之后为0,此值禁止clk。(根据不同的芯片可能不同)
}


定义接收发送缓冲区uint8_t SendDataBuf[1500 + 20];uint8_t RecvDataBuf[1500 + 20];
缓冲区的长度为mtu(1500)+ 地址 +类型 +CRC等(小于20)


low_level_output函数的主要功能将数据包发送出去


static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
  struct ethernetif *ethernetif = netif->state;
  struct pbuf *q ;
  uint32_t i = 0;


// initiate transfer();


#if ETH_PAD_SIZE
  pbuf_header(p, -ETH_PAD_SIZE); /* drop the padding word */
#endif


  for(q = p; q != NULL; q = q->next) {
    /* Send the data from the pbuf to the interface, one pbuf at a
       time. The size of the data in each pbuf is kept in the ->len
       variable. */
   MEMCPY((uint8_t *)&SendDataBuf,(uint8_t *)q->payload,q->len);//将pbuf所指向链表的数据复制到数据缓冲区中
     i = i + q->len;
//   send data from(q->payload, q->len);
  }
  enc28j60PacketSend(i,SendDataBuf);//发送数据包
// signal that packet should be sent();


#if ETH_PAD_SIZE
  pbuf_header(p, ETH_PAD_SIZE); /* reclaim the padding word */
#endif


  LINK_STATS_INC(link.xmit);


  return ERR_OK;
}


low_level_input该函数的主要功能是将接收到的数据并且分配到一个pbuf链表中
因为该工程是轮询的,在周期调用ethernetif_input的时候,ethernetif_input会调用该函数看是否有接收到的数据。
len = enc28j60PacketReceive(1600, RecvDataBuf); //接收到的数据大小
  if(len == 0){ //若是没有,则返回,若是非零,就执行再去
    return 0;
  }
p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);//申请与接收到数据大小相匹配的数据pbuf链表
for (q = p; q != NULL; q = q->next)
        {     
             memcpy((u8_t*)q->payload, (u8_t*)&Data_Buf, q->len);//将接收到的数据分配到p指向的pbuf链表中


             i = i + q->len;
        }
        if( i != p->tot_len ){ return 0;}  //相等的时候,表明到了数据尾
    }




轮询和计时
在配置底层函数之后,将Lwip接口与驱动想关联之后,还没有完成,因为我们需要轮询输入函数和计时。
轮询函数将在main函数的最后while(1)循环中调用,例如本工程模板中的 LwIP_Periodic_Handle(LocalTime) (在后面再讲)
和st模板中main函数System_Periodic_Handle()。
关于计时,在工程模板中,我们使用的是stm32 systick时钟产生。
systick计时每10ms产生中断,每次中断跟新在main函数中定义的全局变量LocalTime,每次加10。
systick配置函数在BSP_SysTick.c中定义。
systick中断函数SysTick_Handler 定义在stm32f10x_it.c
void SysTick_Handler(void)
{
  Time_Update();
}
void Time_Update(void) //在netconf.c中定义
{
  LocalTime += SYSTEMTICK_PERIOD_MS;
};


在core文件夹中有opt.h文件,该文件主要用于Lwip的配置,在前期的移植,我们只需要使用默认的即可。


7:LWIP初始化函数 LwIP_Init以及轮询函数LwIP_Periodic_Handle的实现
该函数定义在netconf.c源文件中,在移植的时候将st参考实例中的文件复制过来了,做了稍微的修改。
LwIP_Init 该函数的主要功能是配置协议栈的网络接口结构体链表,内存池,pbuf结构体等。
在这里将IP地址、掩码、网关根据我们连接的路由器进行初始化。
我这里的IP地址设置为192.168.1.254
同时这里不要设置MAC地址,这个是根据st不一样的,因为我们在初始化底层的时候,就对以太网络模块进行mac地址设定。
LwIP_Periodic_Handle根据我们的计时数值LocalTime 在不同的时候执行不同的函数,
如是否有数据接收,在某些特定值时候更新TCP时钟等


8:TCP网络应用测试应用的编写
在进行上面移植操作之后,我们接下来需要验证我们的移植是否成功。
我们编写的测试应用在iptest.c中定义。主要参考的是st模板中的helloworld.c。
主要功能是建立一个TCP服务器端,客户端与服务器建立TCP连接之后,通过串口打印INTRODUCT(宏),
当服务器收到客户端的数据的时候,将接收到的信息通过串口打印出来,同时将接收的数据返回给客户端。
TcpTest_init实现TCP测试服务器整个过程更Linux的方式基本上一样,调用的函数名称都基本上相似
void TcpTest_init(void)
{
  struct tcp_pcb *pcb;
  pcb = tcp_new();      //建立通信的TCP控制块
  tcp_bind(pcb, IP_ADDR_ANY,23); //绑定端口号为23,绑定本地IP。因为只有一个网络接口,不需要指定IP地址。
  pcb = tcp_listen(pcb); //进入监听状态
  tcp_accept(pcb,TcpTest_accept); //设置有请求连接时候的回调函数,当有连接的时候,LWIP就会调用TcpTest_accept函数。
}
TcpTest_accept函数主要是回调函数
static err_t TcpTest_accept(void *arg,struct tcp_pcb *pcb,err_t err)
{
  tcp_arg(pcb,mem_calloc(sizeof(struct name),1));
  tcp_recv(pcb,TcpTest_recv); //设置接收到数据后的回调函数;在建立连接后,当接收到数据之后,就会调用TcpTest_recv函数。
  tcp_write(pcb, INTRODUCT, strlen(INTRODUCT),1); //在建立连接的时候,向客户端发送INTRDUCT(宏)的字符串
  return  ERR_OK;
};
TcpTest_recv在接收到数据之后,将所接收到的数据回写给客户端同时通过串口打印出来。




9:main函数的编写(参考st工程实例)
int main(void)
{
/****************************************************/
    USART1_Init(); //串口初始化

  ENC_SPI_Init(); //SPI2接口初始化用于后面以太网模块的初始化

  SysTick_Init(); //系统时钟的初始化

  LwIP_Init(); //LWIP的初始化

  TcpTest_init(); //建立TCP服务器
  while(1)
  {
    LwIP_Periodic_Handle(LocalTime); //轮询
  }
}

使用特权

评论回复
板凳
幸福小强|  楼主 | 2018-5-13 19:53 | 只看该作者
10:实验结果如图


使用特权

评论回复
地板
734774645| | 2018-5-13 21:01 | 只看该作者
不知道是有操作系统的好实现,还是无操作系统的更容易

使用特权

评论回复
5
suzhanhua| | 2018-5-14 16:39 | 只看该作者
能够做远程的服务器吗

使用特权

评论回复
6
mituzu| | 2018-5-14 16:39 | 只看该作者
网卡驱动呢?

使用特权

评论回复
7
hellosdc| | 2018-5-14 16:40 | 只看该作者
野火stm32开发板上有的

使用特权

评论回复
8
uiint| | 2018-5-14 16:40 | 只看该作者
有与实际项目相关的内容

使用特权

评论回复
9
51xlf| | 2018-5-14 16:41 | 只看该作者
个协议太大了

使用特权

评论回复
10
i1mcu| | 2018-5-14 16:41 | 只看该作者
自带 MAC控制器吗?

使用特权

评论回复
11
pmp| | 2018-5-14 16:42 | 只看该作者
源代码简单说明一下。

使用特权

评论回复
12
mmbs| | 2018-5-14 16:42 | 只看该作者
准备进行  UDP  数据传输

使用特权

评论回复
13
1988020566| | 2018-5-14 16:43 | 只看该作者
LWIP是一款开源的嵌入式网络协议栈

使用特权

评论回复
14
lzbf| | 2018-5-14 16:43 | 只看该作者
在STM32上“裸奔”成功

使用特权

评论回复
15
51xlf| | 2018-5-14 16:43 | 只看该作者
简化版的TCP/IP协议还有其他的吗?

使用特权

评论回复
16
suzhanhua| | 2018-5-14 16:43 | 只看该作者
TCP/IP协议栈资源占用多大呢

使用特权

评论回复
17
uiint| | 2018-5-14 16:43 | 只看该作者
来一个具体的应用实例分析。

使用特权

评论回复
18
mituzu| | 2018-5-14 16:43 | 只看该作者
是不是兼容所有的网卡驱动呢?

使用特权

评论回复
19
hellosdc| | 2018-5-14 16:43 | 只看该作者
楼主也没有完整的工程代码。

使用特权

评论回复
20
pmp| | 2018-5-14 16:43 | 只看该作者
幸福小强 发表于 2018-5-13 19:52
6:移植
LwIP协议栈给底层的驱动提供了接口,如初始化网卡、发送数据包、接收数据包等最底层的操作。这些接 ...

感觉代码这么分散呢。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

123

主题

1480

帖子

2

粉丝