目录
基于UCOS移植Lwip主要包含两个方面的工作:
1. 根据Lwip提供的操作系统模拟层接口编写基于UCOS的实现代码,以实现Lwip和UCOS的完美融合。
2. 根据Lwip提供的底层网卡驱动接口,结合RTL8019AS网卡datasheet编制网卡驱动程序。
sys_arch需要为LwIP提供创建新线程功能,提供信号量 (semaphores) 和邮箱 (mailboxes) 两种进程间通讯方式 (IPC) 。
1. 模拟层需要添加的头文件 cc.h 说明
Lwip使用的数据类型定义:
typedef unsigned char u8_t;
typedef signed char s8_t;
typedef unsigned short u16_t;
typedef signed short s16_t;
typedef unsigned int u32_t;
typedef signed int s32_t;
typedef unsigned int sys_prot_t;
typedef unsigned int mem_ptr_t;
lwip使用的结构体对齐方式声明相关的宏定义:
#define PACK_STRUCT_FIELD(x) x
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_BEGIN __packed
#define PACK_STRUCT_END
为方便操作协议帧数据,lwip协议栈中结构体使用单字节对齐方式。
处理器模式:
#define BYTE_ORDER LITTLE_ENDIAN
我们使用的LPC2220为小端模式处理器,故定义为小端模式。
其他内容主要和调试输出功能有关,这里不进行一一说明。
2. 需要实现的操作系统模拟层函数
- void sys_init(void)
初始化lwip操作系统模拟层。
- sys_sem_t sys_sem_new(u8_t count)
创建一个信号量,count表示初始化后的信号量状态。
- void sys_sem_free(sys_sem_t sem)
删除指定的信号量。
- void sys_sem_signal(sys_sem_t sem)
发送一个信号量。
- u32_t sys_arch_sem_wait(sys_sem_t sem, u32_t timeout)
等待指定的信号并阻塞线程。timeout 参数为 0,线程会一直被阻塞至收到指定的信号;非 0,则线程仅被阻塞至指定的 timeout时间(单位为毫秒)。在timeout 参数值非 0 的情况下,返回值为等待指定的信号所消耗的毫秒数。如果在指定的时间内并没有收到信号,返回值为SYS_ARCH_TIMEOUT。如果线程不必再等待这个信号(也就是说,已经收到信号) ,返回值也可以为 0。注意,LwIP实现了一个名称与之相似的函数来调用这个函数,sys_sem_wait(),注意区别。
- sys_mbox_t sys_mbox_new(void)
创建一个空消息邮箱。
- void sys_mbox_free(sys_mbox_t mbox)
释放一个邮箱。
- void sys_mbox_post(sys_mbox_t mbox, void *msg)
投递消息“msg”到指定的邮箱“mbox” 。
- u32_t sys_arch_mbox_fetch(sys_mbox_t mbox, void **msg, u32_t timeout)
阻塞线程直至邮箱收到至少一条消息。最长阻塞时间由 timeout 参数指定(与
sys_arch_sem_wait()函数类似) 。msg 是一个结果参数,用来保存邮箱中的消息指针 (即*msg = ptr) ,它的值由这个函数设置。 “msg”参数有可能为空,这表明当前这条消息应该被丢弃。 返回值与 sys_arch_sem_wait()函数相同:等待的毫秒数或者 SYS_ARCH_TIMEOUT――如果时间溢出的话。LwIP实现的函数中,有一个名称与之相似的――sys_mbox_fetch(),注意区分。
- struct sys_timeouts *sys_arch_timeouts(void)
返回一个指向当前线程使用的 sys_timeouts 结构的指针。LwIP 中,每一个线程都有一个timeouts 链表,这个链表由 sys_timeout 结构组成,sys_timeouts 结构则保存了指向这个链表的指针。这个函数由 LwIP 的超时调度程序调用,并且不能返回一个空(NULL)值。 单线程 sys_arch 实现中,这个函数只需简单返回一个指针即可。这个指针指向保存在 sys_arch 模块中的 sys_timeouts 全局变量。
- sys_thread_t sys_thread_new(void (* thread)(void *arg), void *arg, int prio)
创建一个新的线程。
实现sys_sem_t sys_sem_new(u8_t count)函数:
sys_sem_t sys_sem_new(u8_t count)
{
return OSSemCreate((u16_t)count);
}
这个函数实现比较简单,UCOS提供了信号量的操作函数,直接调用即可。
实现void sys_sem_free(sys_sem_t sem)函数:
void sys_sem_free(sys_sem_t sem)
{
u8_t Err;
OSSemDel(sem, OS_DEL_ALWAYS, &Err);
}
实现void sys_sem_signal(sys_sem_t sem)函数:
void sys_sem_signal(sys_sem_t sem)
{
OSSemPost(sem);
}
实现u32_t sys_arch_sem_wait(sys_sem_t sem, u32_t timeout)函数:
u32_t sys_arch_sem_wait(sys_sem_t sem, u32_t timeout)
{
u8_t Err;
u32_t wait_ticks;
if (OSSemAccept(sem))/* 如果已经收到, 则返回0 */
{
return 0;
}
wait_ticks = 0;
if(timeout!=0){
wait_ticks = (timeout * OS_TICKS_PER_SEC)/1000;
if(wait_ticks < 1)
wait_ticks = 1;
else if(wait_ticks > 65535)
wait_ticks = 65535;
}
OSSemPend(sem, (u16_t)wait_ticks, &Err);
if (Err == OS_NO_ERR)
return timeout/2; //将等待时间设置为timeout/2
else
return SYS_ARCH_TIMEOUT;
}
阻塞进程,等待一个信号量的到来。如果timeout不为0,则进程阻塞的时间最多为相关的毫秒数,否则进程一直阻塞,直到收到信号量。
返回值:如果timeout不为0,则返回值为等待该信号量的毫秒数,如果函数在规定的时间内没有等到信号量,则返回值为SYS_ARCH_TIMEOUT,如果信号量在调用函数时已经可用,则函数不会发生任何阻塞操作,返回值这时可以是0。
实现sys_mbox_t sys_mbox_new(int size)函数功能:
sys_mbox_t sys_mbox_new(int size)
{
u8_t Err;
sys_mbox_t pQDesc;
pQDesc = OSMemGet( MboxMem, &Err );
if( Err == OS_NO_ERR ) {
pQDesc->ucos_queue = OSQCreate( &(pQDesc->mbox_msg_entris[0]), MAX_MSG_IN_MBOX );
if( pQDesc->ucos_queue != NULL ) {
return pQDesc;
}
else{
OSMemPut(MboxMem,pQDesc);
}
}
return SYS_MBOX_NULL;
}
邮箱用于消息传递,用户即可以将其实现为一个队列,允许多条消息投递到这个邮箱,也可以每次只允许投递一个消息,这两种方式 LwIP都可以正常运作。不过,前者更加有效。这里我们使用消息队列的方式,允许投递多条消息。
实现void sys_mbox_free(sys_mbox_t mbox)函数:
void sys_mbox_free(sys_mbox_t mbox)
{
u8_t Err;
OSQFlush(mbox->ucos_queue);
OSQDel(mbox->ucos_queue, OS_DEL_ALWAYS, &Err);
OSMemPut( MboxMem, mbox );
}
实现void sys_mbox_post(sys_mbox_t mbox, void *msg)函数功能:
void sys_mbox_post(sys_mbox_t mbox, void *msg)
{
if (msg == NULL)
msg = (void*)&NullMessage;//解决空指针投递的问题
while (OSQPost(mbox->ucos_queue, msg) == OS_Q_FULL)
OSTimeDly(10);
}
实现u32_t
sys_arch_mbox_fetch(sys_mbox_t mbox, void **msg, u32_t timeout)函数:
u32_t
sys_arch_mbox_fetch(sys_mbox_t mbox, void **msg, u32_t timeout)
{
u8_t Err;
u32_t wait_ticks;
void *Data;
Data = OSQAccept(mbox->ucos_queue);
if (Data != NULL)
{
if (Data == (void*)&NullMessage)
{
*msg = NULL;
}
else
{
*msg = Data;
}
return 0;
}
wait_ticks = 0;
if(timeout!=0){
wait_ticks = (timeout * OS_TICKS_PER_SEC)/1000;
if(wait_ticks < 1)
wait_ticks = 1;
else if(wait_ticks > 65535)
wait_ticks = 65535;
}
Data = OSQPend(mbox->ucos_queue, (u16_t)wait_ticks, &Err);
if (Data != NULL)
{
if (Data == (void*)&NullMessage)
{
*msg = NULL;
}
else
{
*msg = Data;
}
}
if (Err == OS_NO_ERR)
return timeout/2; //将等待时间设置为timeout/2
else
return SYS_ARCH_TIMEOUT;
}
实现struct sys_timeouts * sys_arch_timeouts(void)函数功能:
struct sys_timeouts * sys_arch_timeouts(void)
{
return &global_timeouts;
}
实现sys_thread_t sys_thread_new(char *name, void (* thread)(void *arg), void *arg, int stacksize, int prio)函数功能:
sys_thread_t sys_thread_new(char *name, void (* thread)(void *arg), void *arg, int stacksize, int prio)
{
static u32_t TaskCreateFlag=0;
u8_t i=0;
name=name;
stacksize=stacksize;
while((TaskCreateFlag>>i)&0x01){
if(i<LWIP_MAX_TASKS&&i<32)
i++;
else return 0;
}
if(OSTaskCreate(thread,(void*)arg, &LWIP_STK_AREA[i][LWIP_STK_SIZE-1],prio)==OS_NO_ERR){
TaskCreateFlag |=(0x01<<i);
};
return prio;
}
新建一个进程,在整个系统中只会被调用一次。
移植操作系统模拟层完成。
RTL8019AS与主控芯片间通讯的输入/输出地址共有32个,地址偏移量为00H-1FH。其中00-0F共16个地址,为内部寄存器地址,RTL8019AS的内部寄存器每个都是8位的,所有寄存器一共分为4页,每一页都共享这16个偏移量,当前那一页有效是由CR寄存器的值决定的。
要接收和发送数据包都必须读写网卡的内部的16k的ram,必须通过DMA进行读和写.网卡的内部ram是一块双端口的16k字节的ram.所谓双端口就是说有两套总线连结到该ram,一套总线A是网卡控制器读/写网卡上的ram,另一套总线B是主控制器读/写网卡上的ram.总线A又叫Local DMA,总线B又叫Remote DMA.
远程DMA地址包括10H~17H,都可以用来做远程DMA端口,只要用其中的一个就可以了。我们使用10H。
复位端口包括18H~1FH共8个地址,功能一样,用于RTL8019AS复位。我们使用18H。
Lwip提供了网卡驱动框架形式,我们只要根据实际使用的网卡特性完善这些函数就可以了。
具体说我们应该实现以5个函数的实现。
static void low_level_init(struct netif *netif)。
static err_t low_level_output(struct netif *netif, struct pbuf *p)
static struct pbuf *low_level_input(struct netif *netif)
err_t ethernetif_init(struct netif *netif)
static void ethernetif_input(struct netif *netif)
前3个函数与网卡驱动函数密切相关。low_level_init为网卡初始化函数,主要用来完成网卡的复位及参数初始化。low_level_output为网卡数据包发送函数。low_level_input为网卡数据包接收函数。
ethernetif_input函数主要作用是调用网卡数据包接收函数low_level_input从网卡SRAM中读取一个数据包,然后解析数据包类型,然后交付给上层应用程序。实际上,ethernetif_input已经是一个可以直接使用的函数,调用一次可以完成数据包的接收和递交。我们在应用层建立一个任务周期性调用该函数实现接收数据包的功能。
ethernetif_init是上层应用在管理网络接口结构netif时调用的函数。该函数主要完成netif结构中的某些字段初始化,并最终调用low_level_init函数完成网卡的初始化。
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] = MyMacID[0];
netif->hwaddr[1] = MyMacID[1];
netif->hwaddr[2] = MyMacID[2];
netif->hwaddr[3] = MyMacID[3];
netif->hwaddr[4] = MyMacID[4];
netif->hwaddr[5] = MyMacID[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. */
board_eth_init();
}
netif结构体是协议栈内核对系统网络接口设备进行管理的重要数据结构,内核会为每个网络接口分配一个netif结构,用于描述接口属性。上面函数初始化了hwaddr、mtu、flag等关键属性域,并最后调用board_eth_init函数。
board_eth_init函数源代码如下:
void board_eth_init(void)
{
unsigned char i;
unsigned char j;
IODIR=IODIR|RSTDRV;
IOCLR=RSTDRV;
for(i=0;i<200;i++)
{
for(j=0;j<200;j++);
}
IOSET=RSTDRV;
for(i=0;i<200;i++)
{
for(j=0;j<200;j++);
}
IOCLR=RSTDRV;
for(i=0;i<200;i++)
{
for(j=0;j<200;j++);
}
NE_RESET = 0x12;
Delay(500);
NE_CR = ENCR_PAGE0 + ENCR_NODMA;
NE_DCR = NE_DCRVAL;
NE_RBCR0 = 0x00; /* MSB remote byte count reg */
NE_RBCR1 = 0x00; /* LSB remote byte count reg */
NE_TPSR = TX_START_PG;
NE_PSTART = RX_START_PG ; /* DMA START PAGE 46h */
NE_PSTOP = RX_STOP_PG; /* Ending page +1 of ring buffer */
NE_BNRY = RX_START_PG;/* Boundary page of ring buffer */
NE_RCR = ENRCR_RXCONFIG;
NE_TCR = ENTCR_TXCONFIG; /* xmit on. */
NE_ISR = 0xff; /* Individual bits are cleared by writing a "1" into it. */
NE_IMR = ENIMR_RX; // by Forrest..
NE_CR = ENCR_PAGE1 + ENCR_NODMA;
NE_PAR0 = MyMacID[0];
NE_PAR1 = MyMacID[1];
NE_PAR2 = MyMacID[2];
NE_PAR3 = MyMacID[3];
NE_PAR4 = MyMacID[4];
NE_PAR5 = MyMacID[5];
NE_MAR0 = 0xff;
NE_MAR1 = 0xff;
NE_MAR2 = 0xff;
NE_MAR3 = 0xff;
NE_MAR4 = 0xff;
NE_MAR5 = 0xff;
NE_MAR6 = 0xff;
NE_MAR7 = 0xff;
NE_CURR = RX_START_PG; /* RX_CURR_PG; Current memory page = RX_CURR_PG ? */
NE_CR = ENCR_PAGE0 + ENCR_NODMA + ENCR_START;
}
board_eth_init函数是保证网卡RTL8019AS正常工作的前提,它首先完成网卡的硬件复位,然后进行网卡的软件复位(往0X18端口写入任意值使其软复位),接着初始化网卡配置中的发送、接收缓冲区的页地址、配置了网卡发送配置寄存器、接收寄存器,最后设置网卡自身的物理地址和多播过滤地址。
low_level_output函数,上层应用层数据需要封装成协议栈要求的pbuf数据格式,然后再操作网卡发送数据。其源代码如下:
static err_t
low_level_output(struct netif *netif, struct pbuf *p)
{
struct pbuf *q;
u8_t isr;
u8_t chain=0;
u8_t * tr_ptr;
u16_t tr_len, temp_dw;
u16_t padLength,packetLength;
/* Set up to transfer the packet contents to the NIC RAM. */
padLength = 0;
packetLength = p->tot_len;
/* packetLength muse >=64 (see 802.3) */
if ((p->tot_len) < 64)
{
padLength = 64 - (p->tot_len);
packetLength = 64;
}
/* don't close nic,just close receive interrupt */
NE_CR = ENCR_PAGE2 | ENCR_NODMA | ENCR_START;
isr = NE_IMR;
isr &= ~ENISR_RX;
NE_CR = ENCR_PAGE0 | ENCR_NODMA | ENCR_START;
NE_IMR = isr;
NE_ISR = ENISR_RDC;
/* Amount to send */
NE_RBCR0 = packetLength & 0xff;
NE_RBCR1 = packetLength >> 8;
/* Address on NIC to store */
NE_RSAR0 = 0x00;
NE_RSAR1 = NE_START_PG;
/* Write command to start */
NE_CR = ENCR_PAGE0 | ENCR_RWRITE | ENCR_START;
/* write packet to ring buffers. */
for(q = p, chain = 0; q != NULL; q = q->next)
{
if(chain == 1)
{
if(((q->len-1) & 0x01) && (q->next != NULL))
{
tr_len = q->len - 2;
tr_ptr = ((u8_t*)q->payload) + 1;
temp_dw = *(((u8_t *)q->payload) + q->len - 1);
temp_dw += *(u8_t *)(q->next->payload) << 8;
chain = 1;
}
else
{
tr_len = q->len - 1;
tr_ptr = ((u8_t*)q->payload) + 1;
chain = 0;
}
}
else
{
if((q->len & 0x01) && (q->next != NULL))
{
tr_len = q->len - 1;
tr_ptr = (u8_t*)q->payload;
temp_dw = *(((u8_t *)q->payload) + q->len - 1);
temp_dw += *(u8_t *)(q->next->payload) << 8;
chain = 1;
}
else
{
tr_len = q->len;
tr_ptr = (u8_t*)q->payload;
chain = 0;
}
}
ne2k_copyout(tr_len, tr_ptr);
if (chain == 1) NE_DATAW = temp_dw;
}
if(padLength>0)
ne2k_outpad(padLength);
/* Wait for remote dma to complete - ISR Bit 6 clear if busy */
while((u8_t)(NE_ISR & ENISR_RDC) == 0 );
/* clear RDC */
NE_ISR = ENISR_RDC;
/* Issue the transmit command.(start local dma) */
NE_TPSR = NE_START_PG;
NE_TBCR0 = packetLength & 0xff;
NE_TBCR1 = packetLength >> 8;
/* Start transmission (and shut off remote dma) */
NE_CR = ENCR_PAGE0 | ENCR_NODMA | ENCR_TRANS | ENCR_START;
/* reopen receive interrupt */
NE_CR = ENCR_PAGE2 | ENCR_NODMA | ENCR_START;
isr = NE_IMR;
isr |= ENISR_RX;
NE_CR = ENCR_PAGE0 | ENCR_NODMA | ENCR_START;
NE_IMR = isr;
#ifdef LINK_STATS
lwip_stats.link.xmit++;
#endif /* LINK_STATS */
return ERR_OK;
}
low_level_input函数从网卡读取数据,封装成pbuf形式后传递给上层应用层。其源代码如下:
static struct pbuf *
low_level_input(struct netif *netif)
{
struct pbuf *p, *q;
u16_t packetLength, len;
u8_t PDHeader[18]; /* Temp storage for ethernet headers */
u8_t * payload;
NE_ISR = ENISR_RDC;
// NE_RBCR1 = 0x0f; /* See controller manual , use send packet command */
NE_CR = ENCR_PAGE0 | ENCR_RREAD | ENCR_RWRITE | ENCR_START;
// NE_CR = ENCR_PAGE0 | ENCR_RREAD | ENCR_START;
/* get the first 18 bytes from nic */
ne2k_copyin(18,PDHeader);
/* Store real length, set len to packet length - header */
packetLength = ((unsigned) PDHeader[2] | (PDHeader[3] << 8 ));
/* verify if the packet is an IP packet or ARP packet */
if((PDHeader[3]>0x06)||(PDHeader[16] != 8)||(PDHeader[17] != 0 && PDHeader[17] != 6))
{
ne2k_discard(packetLength-14);
return NULL;
}
/* We allocate a pbuf chain of pbufs from the pool. */
p = pbuf_alloc(PBUF_RAW, packetLength, PBUF_POOL);
if (p != NULL) {
/* We iterate over the pbuf chain until we have read the entire
packet into the pbuf. */
/* This assumes a minimum pbuf size of 14 ... a good assumption */
memcpy(p->payload, PDHeader + 4, 14);
for(q = p; q != NULL; q = q->next) {
/* Read enough bytes to fill this pbuf in the chain. The
available data in the pbuf is given by the q->len
variable. */
payload = q->payload;
len = q->len;
if (q == p) {
payload += 14;
len -=14;
}
ne2k_copyin(len,payload);
}
#ifdef LINK_STATS
lwip_stats.link.recv++;
#endif /* LINK_STATS */
} else {
/* no more PBUF resource, Discard packet in buffer. */
ne2k_discard(packetLength-14);
#ifdef LINK_STATS
lwip_stats.link.memerr++;
lwip_stats.link.drop++;
#endif /* LINK_STATS */
}
return p;
}
Lwip要求的协议栈底层操作网卡的函数编写完毕。
void GetPacket(void)
{
u8_t isr,curr,bnry;
NE_CR = ENCR_PAGE0 | ENCR_NODMA;
isr = NE_ISR;
/* got packet with no errors */
if (isr & ENISR_RX) {
NE_ISR = ENISR_RX;
NE_CR = ENCR_PAGE1 | ENCR_NODMA;
curr = NE_CURR;
NE_CR = ENCR_PAGE0 | ENCR_NODMA;
bnry = NE_BNRY;
/* get more than one packet until receive buffer is empty */
while(curr != bnry) {
ethernetif_input(&rtl8019_netif);
NE_CR = ENCR_PAGE1 | ENCR_NODMA;
curr = NE_CURR;
NE_CR = ENCR_PAGE0 | ENCR_NODMA;
bnry = NE_BNRY;
}
// rBNRY = NE_BNRY;
}
else {
NE_ISR = 0xFF;
};
}
在测试lwip协议栈前,我们需要初始化。初始化代码:
struct netif rtl8019_netif;
struct netif loop_netif;
extern err_t ethernetif_init(struct netif *netif);
void lwip_init_task(void)
{
struct ip_addr ipaddr, netmask, gw;
tcpip_init(NULL,NULL);
IP4_ADDR(&gw, 192,168,0,1);
IP4_ADDR(&ipaddr, 192,168,0,174);
IP4_ADDR(&netmask, 255,255,255,0);
netif_add(&rtl8019_netif,&ipaddr,&netmask,&gw,NULL,ethernetif_init,tcpip_input);
netif_set_default(&rtl8019_netif);
netif_set_up(&rtl8019_netif);
}
系统ping测试成功如图4.5-1 ping测试:
通过浏览器访问一个WEB服务器时,其实就是利用HTTP 协议向服务器发送web页面请求,WEB服务器接收到该请求后,返回应答信息和浏览器请求的网页内容。
我们以一个最简单的例子说明一下HTTP协议:
浏览器发送的标准请求是这样的:
1. GET /index.html HTTP/1.1
2. Accept: text/html
3. Accept-Language: zh-cn
4. User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)
5. Connection: Keep-Alive
上面的请求含义:
1. 说明我需要index.html这个网页内容,使用的HTTP协议版本是1.1
2. 我可以接收的文件类型是text/html
3. 我可以接收的语言是中文
4. 浏览器的型号和版本号
5. 需要保持长连接。
服务器的回复信息是这样的:
1. HTTP/1.1 200 OK
2. Date: Sat, 4 Apr 2015 18:54:17 GMT
3. Server: microHttp/1.0 Zlgmcu Corporation
4. Accept-Ranges: bytes
5. Connection: Keep-Close
6. Content-Type: text/html; charset=gb2312
服务器回复的信息含义:
1. 服务器返回浏览器访问的页面存在。
2. 该响应头表明服务器支持Range请求,以及服务器所支持的单位是字节(这也是唯一可用的单位)。
3. 关闭连接
4. 服务器返回的文件类型为text/html,文件编码为gb2312。
基于上述HTTP协议原理,我们可以设计一个简单的WEB服务器,有浏览器访问时WEB服务器返回固定的页面。
在浏览器中输入开发板的IP地址:192.168.0.174
页面显示如图4.6-1 简单WEB服务器:
浏览器默认访问端口是80,我们开发板使用lwip提供的socket编程接口编程实现监听80端口,有浏览器访问开发板的80端口,开发板向浏览器返回指定WEB页面。
实现代码如下:
void lwip_demo(void *pdata)
{
struct netconn *conn,*newconn;
lwip_init_task();
conn=netconn_new(NETCONN_TCP);
netconn_bind(conn,NULL,80);
netconn_listen(conn);
while(1)
{
newconn=netconn_accept(conn);
if(newconn!=NULL)
{
struct netbuf *inbuf;
char *dataptr;
u16_t size;
inbuf = netconn_recv(newconn);
if(inbuf!=NULL)
{
//测试案例
netbuf_data(inbuf,(void **)&dataptr,&size);
netconn_write(newconn,htmldata,sizeof(htmldata), NETCONN_NOCOPY);
netbuf_delete(inbuf);
}
netconn_close(newconn);
netconn_delete(newconn);
}
}
}
网页内容:
const unsigned char htmldata[]={
"HTTP/1.1 200 OK\r\n"
"Date: Sat, 4 Apr 2015 18:54:17 GMT\r\n"
"Server: microHttp/1.0 Zlgmcu Corporation\r\n"
"Accept-Ranges: bytes\r\n"
"Connection: Keep-Close\r\n"
"Content-Type: text/html; charset=gb2312\r\n"
"\r\n"
"<HTML>\r\n"
"<HEAD>\r\n"
"<TITLE>this is Lwip test</TITLE>\r\n"
"<BODY>\r\n"
"<H1>HELLO WELCOME TO LWIP WEB sever</H1>\r\n"
"<P>硬件平台:ARM</P>\r\n"
"<P>软件平台:UCOS Lwip</P>\r\n"
"<P>Design by ***</P>\r\n"
"</BODY>\r\n"
"</HTML>\r\n"
};
1.硬件平台
1.1硬件平台简介
1.2硬件设计及电路原理图
2.Keil 开发工具及Keil工程简介
2.1Keil开发工具
Keil MDK提供了针对ARM系列芯片的汇编器、C/C++的编译器和一个能进行工程管理的IDE。同时,该软件还支持JLink在线调试,是进行嵌入式软件开发非常优秀的软件。
2.2Keil工程简介
Keil MDK可以建立针对具体芯片的工程,根据选定的ARM芯片自动生成启动代码,负责硬件的基本初始化和C语言运行环境以及C语言库的初始化。提供工程文件管理,整体编译、链接、调试。Keil MDK工程还可以编制链接文件,链接器会根据编制的链接文件进行链接二进制文件,用来满足嵌入式开发的不同硬件平台需求。
2.3链接文件、启动文件分析
ARM芯片运行模式和堆栈相关知识都对理解UCOS的任务切换都有很大的帮助,因此我们首先应该理解芯片运行模式和堆栈的概念。理解这些概念最好的方式是分析一下系统启动代码。
在分析启动代码之前,先理解一下Keil MDK 工程中Scf链接文件的相关知识。我们知道源代码程序经过编译、链接后生成一个二进制文件,这个二进制文件是用来控制ARM芯片的。
这个二进制文件是直接下载到ARM处理器芯片的,这个二进制文件的格式如图2.4-1 ARM Image映像文件结构。
在分析启动代码之前,先理解一下Keil MDK 工程中Scf链接文件的相关知识。我们知道源代码程序经过编译、链接后生成一个二进制文件,这个二进制文件是用来控制ARM芯片的。
这个二进制文件是直接下载到ARM处理器芯片的,这个二进制文件的格式如图2.4-1 ARM Image映像文件结构。
- 调用__user_setup_stackheap()设置用户模式下的栈空间和堆空间。空间可以通过程序定义,也可以通过分散加载文件制定绝对地址空间。
- 调用__rt_lib_init()初始化库函数,在必要时为用户main函数设置argc、argv参数。调用__cpp_initialize__aeabi_初始化C++特性。
- Calls main(), the user-level root of the application.
- Calls exit() with the value returned by main().
- 当main函数返回时,调用exit函数清理资源。
3.UCOS移植
3.1 ucos简介
3.2 ucos移植总述
3.3 和移植UCOS有关的ARM芯片知识
3.4 系统堆栈和UCOS的任务堆栈
3.5 系统时钟
3.6 任务级任务切换
3.7 中断级任务切换
4.Lwip移植
4.1 lwip简介
lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。LwIP是Light Weight (轻型)IP协议,有无操作系统的支持都可以运行。LwIP实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用,它只需十几KB的RAM和40K左右的ROM就可以运行,这使LwIP协议栈适合在低端的嵌入式系统中使用。4.2 lwip移植总述
Lwip有无操作系统的支持都可以运行,我们移植是基于UCOS的。基于UCOS移植Lwip主要包含两个方面的工作:
1. 根据Lwip提供的操作系统模拟层接口编写基于UCOS的实现代码,以实现Lwip和UCOS的完美融合。
2. 根据Lwip提供的底层网卡驱动接口,结合RTL8019AS网卡datasheet编制网卡驱动程序。
4.3移植lwip操作系统模拟层
操作系统模拟层(sys_arch)存在的目的主要是为了方便 LwIP 的移植,它在底层操作系统和LwIP 之间提供了一个接口。这样,我们在移植 LwIP 到一个新的目标系统时,只需修改这个接口即可。不过,不依赖底层操作系统的支持也可以实现这个接口。sys_arch需要为LwIP提供创建新线程功能,提供信号量 (semaphores) 和邮箱 (mailboxes) 两种进程间通讯方式 (IPC) 。
1. 模拟层需要添加的头文件 cc.h 说明
Lwip使用的数据类型定义:
typedef unsigned char u8_t;
typedef signed char s8_t;
typedef unsigned short u16_t;
typedef signed short s16_t;
typedef unsigned int u32_t;
typedef signed int s32_t;
typedef unsigned int sys_prot_t;
typedef unsigned int mem_ptr_t;
lwip使用的结构体对齐方式声明相关的宏定义:
#define PACK_STRUCT_FIELD(x) x
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_BEGIN __packed
#define PACK_STRUCT_END
为方便操作协议帧数据,lwip协议栈中结构体使用单字节对齐方式。
处理器模式:
#define BYTE_ORDER LITTLE_ENDIAN
我们使用的LPC2220为小端模式处理器,故定义为小端模式。
其他内容主要和调试输出功能有关,这里不进行一一说明。
2. 需要实现的操作系统模拟层函数
- void sys_init(void)
初始化lwip操作系统模拟层。
- sys_sem_t sys_sem_new(u8_t count)
创建一个信号量,count表示初始化后的信号量状态。
- void sys_sem_free(sys_sem_t sem)
删除指定的信号量。
- void sys_sem_signal(sys_sem_t sem)
发送一个信号量。
- u32_t sys_arch_sem_wait(sys_sem_t sem, u32_t timeout)
等待指定的信号并阻塞线程。timeout 参数为 0,线程会一直被阻塞至收到指定的信号;非 0,则线程仅被阻塞至指定的 timeout时间(单位为毫秒)。在timeout 参数值非 0 的情况下,返回值为等待指定的信号所消耗的毫秒数。如果在指定的时间内并没有收到信号,返回值为SYS_ARCH_TIMEOUT。如果线程不必再等待这个信号(也就是说,已经收到信号) ,返回值也可以为 0。注意,LwIP实现了一个名称与之相似的函数来调用这个函数,sys_sem_wait(),注意区别。
- sys_mbox_t sys_mbox_new(void)
创建一个空消息邮箱。
- void sys_mbox_free(sys_mbox_t mbox)
释放一个邮箱。
- void sys_mbox_post(sys_mbox_t mbox, void *msg)
投递消息“msg”到指定的邮箱“mbox” 。
- u32_t sys_arch_mbox_fetch(sys_mbox_t mbox, void **msg, u32_t timeout)
阻塞线程直至邮箱收到至少一条消息。最长阻塞时间由 timeout 参数指定(与
sys_arch_sem_wait()函数类似) 。msg 是一个结果参数,用来保存邮箱中的消息指针 (即*msg = ptr) ,它的值由这个函数设置。 “msg”参数有可能为空,这表明当前这条消息应该被丢弃。 返回值与 sys_arch_sem_wait()函数相同:等待的毫秒数或者 SYS_ARCH_TIMEOUT――如果时间溢出的话。LwIP实现的函数中,有一个名称与之相似的――sys_mbox_fetch(),注意区分。
- struct sys_timeouts *sys_arch_timeouts(void)
返回一个指向当前线程使用的 sys_timeouts 结构的指针。LwIP 中,每一个线程都有一个timeouts 链表,这个链表由 sys_timeout 结构组成,sys_timeouts 结构则保存了指向这个链表的指针。这个函数由 LwIP 的超时调度程序调用,并且不能返回一个空(NULL)值。 单线程 sys_arch 实现中,这个函数只需简单返回一个指针即可。这个指针指向保存在 sys_arch 模块中的 sys_timeouts 全局变量。
- sys_thread_t sys_thread_new(void (* thread)(void *arg), void *arg, int prio)
创建一个新的线程。
实现sys_sem_t sys_sem_new(u8_t count)函数:
sys_sem_t sys_sem_new(u8_t count)
{
return OSSemCreate((u16_t)count);
}
这个函数实现比较简单,UCOS提供了信号量的操作函数,直接调用即可。
实现void sys_sem_free(sys_sem_t sem)函数:
void sys_sem_free(sys_sem_t sem)
{
u8_t Err;
OSSemDel(sem, OS_DEL_ALWAYS, &Err);
}
实现void sys_sem_signal(sys_sem_t sem)函数:
void sys_sem_signal(sys_sem_t sem)
{
OSSemPost(sem);
}
实现u32_t sys_arch_sem_wait(sys_sem_t sem, u32_t timeout)函数:
u32_t sys_arch_sem_wait(sys_sem_t sem, u32_t timeout)
{
u8_t Err;
u32_t wait_ticks;
if (OSSemAccept(sem))/* 如果已经收到, 则返回0 */
{
return 0;
}
wait_ticks = 0;
if(timeout!=0){
wait_ticks = (timeout * OS_TICKS_PER_SEC)/1000;
if(wait_ticks < 1)
wait_ticks = 1;
else if(wait_ticks > 65535)
wait_ticks = 65535;
}
OSSemPend(sem, (u16_t)wait_ticks, &Err);
if (Err == OS_NO_ERR)
return timeout/2; //将等待时间设置为timeout/2
else
return SYS_ARCH_TIMEOUT;
}
阻塞进程,等待一个信号量的到来。如果timeout不为0,则进程阻塞的时间最多为相关的毫秒数,否则进程一直阻塞,直到收到信号量。
返回值:如果timeout不为0,则返回值为等待该信号量的毫秒数,如果函数在规定的时间内没有等到信号量,则返回值为SYS_ARCH_TIMEOUT,如果信号量在调用函数时已经可用,则函数不会发生任何阻塞操作,返回值这时可以是0。
实现sys_mbox_t sys_mbox_new(int size)函数功能:
sys_mbox_t sys_mbox_new(int size)
{
u8_t Err;
sys_mbox_t pQDesc;
pQDesc = OSMemGet( MboxMem, &Err );
if( Err == OS_NO_ERR ) {
pQDesc->ucos_queue = OSQCreate( &(pQDesc->mbox_msg_entris[0]), MAX_MSG_IN_MBOX );
if( pQDesc->ucos_queue != NULL ) {
return pQDesc;
}
else{
OSMemPut(MboxMem,pQDesc);
}
}
return SYS_MBOX_NULL;
}
邮箱用于消息传递,用户即可以将其实现为一个队列,允许多条消息投递到这个邮箱,也可以每次只允许投递一个消息,这两种方式 LwIP都可以正常运作。不过,前者更加有效。这里我们使用消息队列的方式,允许投递多条消息。
实现void sys_mbox_free(sys_mbox_t mbox)函数:
void sys_mbox_free(sys_mbox_t mbox)
{
u8_t Err;
OSQFlush(mbox->ucos_queue);
OSQDel(mbox->ucos_queue, OS_DEL_ALWAYS, &Err);
OSMemPut( MboxMem, mbox );
}
实现void sys_mbox_post(sys_mbox_t mbox, void *msg)函数功能:
void sys_mbox_post(sys_mbox_t mbox, void *msg)
{
if (msg == NULL)
msg = (void*)&NullMessage;//解决空指针投递的问题
while (OSQPost(mbox->ucos_queue, msg) == OS_Q_FULL)
OSTimeDly(10);
}
实现u32_t
sys_arch_mbox_fetch(sys_mbox_t mbox, void **msg, u32_t timeout)函数:
u32_t
sys_arch_mbox_fetch(sys_mbox_t mbox, void **msg, u32_t timeout)
{
u8_t Err;
u32_t wait_ticks;
void *Data;
Data = OSQAccept(mbox->ucos_queue);
if (Data != NULL)
{
if (Data == (void*)&NullMessage)
{
*msg = NULL;
}
else
{
*msg = Data;
}
return 0;
}
wait_ticks = 0;
if(timeout!=0){
wait_ticks = (timeout * OS_TICKS_PER_SEC)/1000;
if(wait_ticks < 1)
wait_ticks = 1;
else if(wait_ticks > 65535)
wait_ticks = 65535;
}
Data = OSQPend(mbox->ucos_queue, (u16_t)wait_ticks, &Err);
if (Data != NULL)
{
if (Data == (void*)&NullMessage)
{
*msg = NULL;
}
else
{
*msg = Data;
}
}
if (Err == OS_NO_ERR)
return timeout/2; //将等待时间设置为timeout/2
else
return SYS_ARCH_TIMEOUT;
}
实现struct sys_timeouts * sys_arch_timeouts(void)函数功能:
struct sys_timeouts * sys_arch_timeouts(void)
{
return &global_timeouts;
}
实现sys_thread_t sys_thread_new(char *name, void (* thread)(void *arg), void *arg, int stacksize, int prio)函数功能:
sys_thread_t sys_thread_new(char *name, void (* thread)(void *arg), void *arg, int stacksize, int prio)
{
static u32_t TaskCreateFlag=0;
u8_t i=0;
name=name;
stacksize=stacksize;
while((TaskCreateFlag>>i)&0x01){
if(i<LWIP_MAX_TASKS&&i<32)
i++;
else return 0;
}
if(OSTaskCreate(thread,(void*)arg, &LWIP_STK_AREA[i][LWIP_STK_SIZE-1],prio)==OS_NO_ERR){
TaskCreateFlag |=(0x01<<i);
};
return prio;
}
新建一个进程,在整个系统中只会被调用一次。
移植操作系统模拟层完成。
4.4根据lwip提供的软件架构编写相应的网卡芯片驱动
从用户编程角度看,针对RTL8019AS的操作实际上就是对RTL8019AS内部寄存器的操作,以实现网卡的初始化、数据发送、数据接收等操作。发送数据时,主控制器将数据写入网卡的SRAM中,然后发送一个发送数据指令,网卡就会将数据封装成标准以太网物理层数据帧发送出去。同理,网卡接收到以太网数据时,网卡会自动解析成高层使用的有效格式,放在内部SRAM中供主控芯片读取,我们采用周期查询方式实现接收数据的处理。RTL8019AS与主控芯片间通讯的输入/输出地址共有32个,地址偏移量为00H-1FH。其中00-0F共16个地址,为内部寄存器地址,RTL8019AS的内部寄存器每个都是8位的,所有寄存器一共分为4页,每一页都共享这16个偏移量,当前那一页有效是由CR寄存器的值决定的。
要接收和发送数据包都必须读写网卡的内部的16k的ram,必须通过DMA进行读和写.网卡的内部ram是一块双端口的16k字节的ram.所谓双端口就是说有两套总线连结到该ram,一套总线A是网卡控制器读/写网卡上的ram,另一套总线B是主控制器读/写网卡上的ram.总线A又叫Local DMA,总线B又叫Remote DMA.
远程DMA地址包括10H~17H,都可以用来做远程DMA端口,只要用其中的一个就可以了。我们使用10H。
复位端口包括18H~1FH共8个地址,功能一样,用于RTL8019AS复位。我们使用18H。
Lwip提供了网卡驱动框架形式,我们只要根据实际使用的网卡特性完善这些函数就可以了。
具体说我们应该实现以5个函数的实现。
static void low_level_init(struct netif *netif)。
static err_t low_level_output(struct netif *netif, struct pbuf *p)
static struct pbuf *low_level_input(struct netif *netif)
err_t ethernetif_init(struct netif *netif)
static void ethernetif_input(struct netif *netif)
前3个函数与网卡驱动函数密切相关。low_level_init为网卡初始化函数,主要用来完成网卡的复位及参数初始化。low_level_output为网卡数据包发送函数。low_level_input为网卡数据包接收函数。
ethernetif_input函数主要作用是调用网卡数据包接收函数low_level_input从网卡SRAM中读取一个数据包,然后解析数据包类型,然后交付给上层应用程序。实际上,ethernetif_input已经是一个可以直接使用的函数,调用一次可以完成数据包的接收和递交。我们在应用层建立一个任务周期性调用该函数实现接收数据包的功能。
ethernetif_init是上层应用在管理网络接口结构netif时调用的函数。该函数主要完成netif结构中的某些字段初始化,并最终调用low_level_init函数完成网卡的初始化。
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] = MyMacID[0];
netif->hwaddr[1] = MyMacID[1];
netif->hwaddr[2] = MyMacID[2];
netif->hwaddr[3] = MyMacID[3];
netif->hwaddr[4] = MyMacID[4];
netif->hwaddr[5] = MyMacID[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. */
board_eth_init();
}
netif结构体是协议栈内核对系统网络接口设备进行管理的重要数据结构,内核会为每个网络接口分配一个netif结构,用于描述接口属性。上面函数初始化了hwaddr、mtu、flag等关键属性域,并最后调用board_eth_init函数。
board_eth_init函数源代码如下:
void board_eth_init(void)
{
unsigned char i;
unsigned char j;
IODIR=IODIR|RSTDRV;
IOCLR=RSTDRV;
for(i=0;i<200;i++)
{
for(j=0;j<200;j++);
}
IOSET=RSTDRV;
for(i=0;i<200;i++)
{
for(j=0;j<200;j++);
}
IOCLR=RSTDRV;
for(i=0;i<200;i++)
{
for(j=0;j<200;j++);
}
NE_RESET = 0x12;
Delay(500);
NE_CR = ENCR_PAGE0 + ENCR_NODMA;
NE_DCR = NE_DCRVAL;
NE_RBCR0 = 0x00; /* MSB remote byte count reg */
NE_RBCR1 = 0x00; /* LSB remote byte count reg */
NE_TPSR = TX_START_PG;
NE_PSTART = RX_START_PG ; /* DMA START PAGE 46h */
NE_PSTOP = RX_STOP_PG; /* Ending page +1 of ring buffer */
NE_BNRY = RX_START_PG;/* Boundary page of ring buffer */
NE_RCR = ENRCR_RXCONFIG;
NE_TCR = ENTCR_TXCONFIG; /* xmit on. */
NE_ISR = 0xff; /* Individual bits are cleared by writing a "1" into it. */
NE_IMR = ENIMR_RX; // by Forrest..
NE_CR = ENCR_PAGE1 + ENCR_NODMA;
NE_PAR0 = MyMacID[0];
NE_PAR1 = MyMacID[1];
NE_PAR2 = MyMacID[2];
NE_PAR3 = MyMacID[3];
NE_PAR4 = MyMacID[4];
NE_PAR5 = MyMacID[5];
NE_MAR0 = 0xff;
NE_MAR1 = 0xff;
NE_MAR2 = 0xff;
NE_MAR3 = 0xff;
NE_MAR4 = 0xff;
NE_MAR5 = 0xff;
NE_MAR6 = 0xff;
NE_MAR7 = 0xff;
NE_CURR = RX_START_PG; /* RX_CURR_PG; Current memory page = RX_CURR_PG ? */
NE_CR = ENCR_PAGE0 + ENCR_NODMA + ENCR_START;
}
board_eth_init函数是保证网卡RTL8019AS正常工作的前提,它首先完成网卡的硬件复位,然后进行网卡的软件复位(往0X18端口写入任意值使其软复位),接着初始化网卡配置中的发送、接收缓冲区的页地址、配置了网卡发送配置寄存器、接收寄存器,最后设置网卡自身的物理地址和多播过滤地址。
low_level_output函数,上层应用层数据需要封装成协议栈要求的pbuf数据格式,然后再操作网卡发送数据。其源代码如下:
static err_t
low_level_output(struct netif *netif, struct pbuf *p)
{
struct pbuf *q;
u8_t isr;
u8_t chain=0;
u8_t * tr_ptr;
u16_t tr_len, temp_dw;
u16_t padLength,packetLength;
/* Set up to transfer the packet contents to the NIC RAM. */
padLength = 0;
packetLength = p->tot_len;
/* packetLength muse >=64 (see 802.3) */
if ((p->tot_len) < 64)
{
padLength = 64 - (p->tot_len);
packetLength = 64;
}
/* don't close nic,just close receive interrupt */
NE_CR = ENCR_PAGE2 | ENCR_NODMA | ENCR_START;
isr = NE_IMR;
isr &= ~ENISR_RX;
NE_CR = ENCR_PAGE0 | ENCR_NODMA | ENCR_START;
NE_IMR = isr;
NE_ISR = ENISR_RDC;
/* Amount to send */
NE_RBCR0 = packetLength & 0xff;
NE_RBCR1 = packetLength >> 8;
/* Address on NIC to store */
NE_RSAR0 = 0x00;
NE_RSAR1 = NE_START_PG;
/* Write command to start */
NE_CR = ENCR_PAGE0 | ENCR_RWRITE | ENCR_START;
/* write packet to ring buffers. */
for(q = p, chain = 0; q != NULL; q = q->next)
{
if(chain == 1)
{
if(((q->len-1) & 0x01) && (q->next != NULL))
{
tr_len = q->len - 2;
tr_ptr = ((u8_t*)q->payload) + 1;
temp_dw = *(((u8_t *)q->payload) + q->len - 1);
temp_dw += *(u8_t *)(q->next->payload) << 8;
chain = 1;
}
else
{
tr_len = q->len - 1;
tr_ptr = ((u8_t*)q->payload) + 1;
chain = 0;
}
}
else
{
if((q->len & 0x01) && (q->next != NULL))
{
tr_len = q->len - 1;
tr_ptr = (u8_t*)q->payload;
temp_dw = *(((u8_t *)q->payload) + q->len - 1);
temp_dw += *(u8_t *)(q->next->payload) << 8;
chain = 1;
}
else
{
tr_len = q->len;
tr_ptr = (u8_t*)q->payload;
chain = 0;
}
}
ne2k_copyout(tr_len, tr_ptr);
if (chain == 1) NE_DATAW = temp_dw;
}
if(padLength>0)
ne2k_outpad(padLength);
/* Wait for remote dma to complete - ISR Bit 6 clear if busy */
while((u8_t)(NE_ISR & ENISR_RDC) == 0 );
/* clear RDC */
NE_ISR = ENISR_RDC;
/* Issue the transmit command.(start local dma) */
NE_TPSR = NE_START_PG;
NE_TBCR0 = packetLength & 0xff;
NE_TBCR1 = packetLength >> 8;
/* Start transmission (and shut off remote dma) */
NE_CR = ENCR_PAGE0 | ENCR_NODMA | ENCR_TRANS | ENCR_START;
/* reopen receive interrupt */
NE_CR = ENCR_PAGE2 | ENCR_NODMA | ENCR_START;
isr = NE_IMR;
isr |= ENISR_RX;
NE_CR = ENCR_PAGE0 | ENCR_NODMA | ENCR_START;
NE_IMR = isr;
#ifdef LINK_STATS
lwip_stats.link.xmit++;
#endif /* LINK_STATS */
return ERR_OK;
}
low_level_input函数从网卡读取数据,封装成pbuf形式后传递给上层应用层。其源代码如下:
static struct pbuf *
low_level_input(struct netif *netif)
{
struct pbuf *p, *q;
u16_t packetLength, len;
u8_t PDHeader[18]; /* Temp storage for ethernet headers */
u8_t * payload;
NE_ISR = ENISR_RDC;
// NE_RBCR1 = 0x0f; /* See controller manual , use send packet command */
NE_CR = ENCR_PAGE0 | ENCR_RREAD | ENCR_RWRITE | ENCR_START;
// NE_CR = ENCR_PAGE0 | ENCR_RREAD | ENCR_START;
/* get the first 18 bytes from nic */
ne2k_copyin(18,PDHeader);
/* Store real length, set len to packet length - header */
packetLength = ((unsigned) PDHeader[2] | (PDHeader[3] << 8 ));
/* verify if the packet is an IP packet or ARP packet */
if((PDHeader[3]>0x06)||(PDHeader[16] != 8)||(PDHeader[17] != 0 && PDHeader[17] != 6))
{
ne2k_discard(packetLength-14);
return NULL;
}
/* We allocate a pbuf chain of pbufs from the pool. */
p = pbuf_alloc(PBUF_RAW, packetLength, PBUF_POOL);
if (p != NULL) {
/* We iterate over the pbuf chain until we have read the entire
packet into the pbuf. */
/* This assumes a minimum pbuf size of 14 ... a good assumption */
memcpy(p->payload, PDHeader + 4, 14);
for(q = p; q != NULL; q = q->next) {
/* Read enough bytes to fill this pbuf in the chain. The
available data in the pbuf is given by the q->len
variable. */
payload = q->payload;
len = q->len;
if (q == p) {
payload += 14;
len -=14;
}
ne2k_copyin(len,payload);
}
#ifdef LINK_STATS
lwip_stats.link.recv++;
#endif /* LINK_STATS */
} else {
/* no more PBUF resource, Discard packet in buffer. */
ne2k_discard(packetLength-14);
#ifdef LINK_STATS
lwip_stats.link.memerr++;
lwip_stats.link.drop++;
#endif /* LINK_STATS */
}
return p;
}
Lwip要求的协议栈底层操作网卡的函数编写完毕。
4.5移植完成后测试TCP/IP协议栈
我们使用查询方式读取网卡数据包,具体方案是建一个查询任务,周期性调用GetPacket()函数,函数源代码:void GetPacket(void)
{
u8_t isr,curr,bnry;
NE_CR = ENCR_PAGE0 | ENCR_NODMA;
isr = NE_ISR;
/* got packet with no errors */
if (isr & ENISR_RX) {
NE_ISR = ENISR_RX;
NE_CR = ENCR_PAGE1 | ENCR_NODMA;
curr = NE_CURR;
NE_CR = ENCR_PAGE0 | ENCR_NODMA;
bnry = NE_BNRY;
/* get more than one packet until receive buffer is empty */
while(curr != bnry) {
ethernetif_input(&rtl8019_netif);
NE_CR = ENCR_PAGE1 | ENCR_NODMA;
curr = NE_CURR;
NE_CR = ENCR_PAGE0 | ENCR_NODMA;
bnry = NE_BNRY;
}
// rBNRY = NE_BNRY;
}
else {
NE_ISR = 0xFF;
};
}
在测试lwip协议栈前,我们需要初始化。初始化代码:
struct netif rtl8019_netif;
struct netif loop_netif;
extern err_t ethernetif_init(struct netif *netif);
void lwip_init_task(void)
{
struct ip_addr ipaddr, netmask, gw;
tcpip_init(NULL,NULL);
IP4_ADDR(&gw, 192,168,0,1);
IP4_ADDR(&ipaddr, 192,168,0,174);
IP4_ADDR(&netmask, 255,255,255,0);
netif_add(&rtl8019_netif,&ipaddr,&netmask,&gw,NULL,ethernetif_init,tcpip_input);
netif_set_default(&rtl8019_netif);
netif_set_up(&rtl8019_netif);
}
系统ping测试成功如图4.5-1 ping测试:
4.6 设计并实现简单的WEB服务器
HTTP是一个基于TCP/IP,属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。通过浏览器访问一个WEB服务器时,其实就是利用HTTP 协议向服务器发送web页面请求,WEB服务器接收到该请求后,返回应答信息和浏览器请求的网页内容。
我们以一个最简单的例子说明一下HTTP协议:
浏览器发送的标准请求是这样的:
1. GET /index.html HTTP/1.1
2. Accept: text/html
3. Accept-Language: zh-cn
4. User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)
5. Connection: Keep-Alive
上面的请求含义:
1. 说明我需要index.html这个网页内容,使用的HTTP协议版本是1.1
2. 我可以接收的文件类型是text/html
3. 我可以接收的语言是中文
4. 浏览器的型号和版本号
5. 需要保持长连接。
服务器的回复信息是这样的:
1. HTTP/1.1 200 OK
2. Date: Sat, 4 Apr 2015 18:54:17 GMT
3. Server: microHttp/1.0 Zlgmcu Corporation
4. Accept-Ranges: bytes
5. Connection: Keep-Close
6. Content-Type: text/html; charset=gb2312
服务器回复的信息含义:
1. 服务器返回浏览器访问的页面存在。
2. 该响应头表明服务器支持Range请求,以及服务器所支持的单位是字节(这也是唯一可用的单位)。
3. 关闭连接
4. 服务器返回的文件类型为text/html,文件编码为gb2312。
基于上述HTTP协议原理,我们可以设计一个简单的WEB服务器,有浏览器访问时WEB服务器返回固定的页面。
在浏览器中输入开发板的IP地址:192.168.0.174
页面显示如图4.6-1 简单WEB服务器:
浏览器默认访问端口是80,我们开发板使用lwip提供的socket编程接口编程实现监听80端口,有浏览器访问开发板的80端口,开发板向浏览器返回指定WEB页面。
实现代码如下:
void lwip_demo(void *pdata)
{
struct netconn *conn,*newconn;
lwip_init_task();
conn=netconn_new(NETCONN_TCP);
netconn_bind(conn,NULL,80);
netconn_listen(conn);
while(1)
{
newconn=netconn_accept(conn);
if(newconn!=NULL)
{
struct netbuf *inbuf;
char *dataptr;
u16_t size;
inbuf = netconn_recv(newconn);
if(inbuf!=NULL)
{
//测试案例
netbuf_data(inbuf,(void **)&dataptr,&size);
netconn_write(newconn,htmldata,sizeof(htmldata), NETCONN_NOCOPY);
netbuf_delete(inbuf);
}
netconn_close(newconn);
netconn_delete(newconn);
}
}
}
网页内容:
const unsigned char htmldata[]={
"HTTP/1.1 200 OK\r\n"
"Date: Sat, 4 Apr 2015 18:54:17 GMT\r\n"
"Server: microHttp/1.0 Zlgmcu Corporation\r\n"
"Accept-Ranges: bytes\r\n"
"Connection: Keep-Close\r\n"
"Content-Type: text/html; charset=gb2312\r\n"
"\r\n"
"<HTML>\r\n"
"<HEAD>\r\n"
"<TITLE>this is Lwip test</TITLE>\r\n"
"<BODY>\r\n"
"<H1>HELLO WELCOME TO LWIP WEB sever</H1>\r\n"
"<P>硬件平台:ARM</P>\r\n"
"<P>软件平台:UCOS Lwip</P>\r\n"
"<P>Design by ***</P>\r\n"
"</BODY>\r\n"
"</HTML>\r\n"
};