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); //轮询
}
}
|