物联网技术
直播中

tr4578

8年用户 928经验值
擅长:模拟技术
私信 关注
[经验]

UIP移植到CC2530的方法

UIP移植到CC2530的方法


最近老板有个项目,其中要做一个Zigbee的无线接入点,即将ZigBee无线传感网络中的数据通过TCP/IP协议传输到以太网上。传统的这种无线接入点即网关都是上位机加下位机模式做成的,即主控芯片(如ARM)加无线模块(如cc2530),ARM与cc2530通过UARST通信,cc2530建立WSN网络,ARM与PC机通过TCP/IP通信,考虑到此系统的成本以及其中的数据传输量不大,就不用ARM,直接将cc2530做成网关,这就需要将TCP/IP协议栈移植到cc2530上,同时与ZigBee协议栈能很好的协同运行。

       因为cc2530的FLASH有256K,Z-stack占用了大部分,所以要用TCP/IP,只能移植一个轻量型网络协议栈,现在比较流行的就是Adam Dunkels写的lwIP和uIP,还有Micrium的uc/IP,lwip和uc/ip所占空间较大,移植较为麻烦,所以就用uip。uip是一种免费可的极小的TCP/IP协议栈,主要实现了ARP,ICMP,TCP,UDP协议,在8位或16位单片机上用的较多,对rom和ram要求很少。

       在网上看了一些uip移植到51或STM32的文章,同时也花了两天时间看了uip的实现源码,如果不熟悉TCP/IP协议的话读起来还是很吃力,所以先看看TCP/IP,建议看TCP/IP协议详解——卷一。看完之后大概知道移植过程了。

       移植之前先要写网络芯片驱动程序,我用的是enc28j60,独立控制的SPI接口,因为cc2530的spi接口用来下载调试了,另一个spi被串口复用了,所以只用用GPIO模拟SPI。

      写驱动程序之前认真读了enc28j60的datasheet,在网上也找到了相关的驱动程序,可以稍加修改拿来用。下面贴出spi程序和enc28j60的程序。


#include "spi.h"  
  
void WriteByte(u8_t dat)  
{  
  u8_t i;  
  for(i=0;i<8;i++)  
  {  
    SCKN = 0;  
    asm("nop");  
    if(dat&0x80)  
    {  
       SIN = 1;  
    }  
    else   
       SIN = 0;  
    dat <<= 1;  
    asm("nop");  
    SCKN = 1;  
    asm("nop");  
  }  
  SCKN=0; //空闲状态为低电平  
}  
  
u8_t ReadByte(void)  
{  
  u8_t i,dat;  
  SCKN=0;  
  dat1=0;  
  for(i=0;i<8;i++)  
  {   
    SCKN=1;  
    dat1 <<=1;  
    dat1 |= SON;   
    SCKN=0;   
  }  
  return dat;  
}  

     spi.h文件定义了与enc28j60spi接口的GPIO,


#ifndef SPI_H  
#define SPI_H  
#include   
  
#define SON   P0_5    // MISO  
#define SIN   P0_6    // MOSI  
#define SCKN  P0_7    // SCK  
#define CSN   P1_3    // 28J60-- CS  
#define RESET P1_2    // Reset   
  
void WriteByte(u8_t dat);  
u8_t ReadByte(void);  
  
#endif  

        enc28j60.c文件:


#include "enc28j60.h"  
#include "spi.h"  
  
#define MIN(a,b) (a) < (b) ? (a) : (b)  
XDATA u8_t Enc28j60Bank;  
XDATA u16_t NextPacketPtr;  
  
void delay_100ns()  
{  
     asm("nop");  
     asm("nop");  
     asm("nop");  
}  
  
void delay_ms(int t1)  
{   
     int i;   
     while(t1--)   
     {  
    for(i=10;i;--i)  
        {  
           delay_100ns();  
        }  
     }   
}  
  
//*******************************************************************************************  
//  
// Function : enc28j60ReadOp  
// Description :   
//  
//*******************************************************************************************  
u8_t enc28j60ReadOp(u8_t op, u8_t address)  
{  
    u8_t dat1;  
    // activate CS   
    CSN =0;  
    // issue read command  
    delay_100ns();  
    WriteByte(op | (address & ADDR_MASK));   
    dat1 = ReadByte();  
    // do dummy read if needed (for mac and mii, see datasheet page 29)  
    if(address & 0x80)  dat1 = ReadByte();  
    // release CS  
    CSN=1;  
    return(dat1);  
}  
//*******************************************************************************************  
//  
// Function : enc28j60WriteOp  
// Description :   
//  
//*******************************************************************************************  
void enc28j60WriteOp(u8_t op, u8_t address, u8_t mydat)  
{  
    CSN=0;  
    // issue write command  
    delay_100ns();  
    WriteByte( op | (address & ADDR_MASK));  
    // write data  
    WriteByte(mydat);  
    CSN=1;  
    delay_100ns();  
}  
//*******************************************************************************************  
//  
// Function : icmp_send_request  
// Description : Send ARP request packet to destination.  
//  
//*******************************************************************************************  
void enc28j60SetBank(u8_t address)  
{  
    // set the bank (if needed)  
    if((address & BANK_MASK) != Enc28j60Bank)  
    {  
        // set the bank  
        enc28j60WriteOp(ENC28J60_BIT_FIELD_CLR, ECON1, (ECON1_BSEL1|ECON1_BSEL0));  
        enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON1, (address & BANK_MASK)>>5);  
        Enc28j60Bank = (address & BANK_MASK);  
    }  
}  
//*******************************************************************************************  
//  
// Function : icmp_send_request  
// Description : Send ARP request packet to destination.  
//  
//*******************************************************************************************  
u8_t enc28j60Read(u8_t address)  
{  
    // select bank to read  
    enc28j60SetBank(address);     
    // do the read  
    return enc28j60ReadOp(ENC28J60_READ_CTRL_REG, address);  
}  
//*******************************************************************************************  
//  
// Function : icmp_send_request  
// Description : Send ARP request packet to destination.  
//  
//*******************************************************************************************  
void enc28j60Write(u8_t address, u8_t mydat)  
{  
    // select bank to write  
    enc28j60SetBank(address);   
    // do the write  
    enc28j60WriteOp(ENC28J60_WRITE_CTRL_REG, address, mydat);  
}  
//*******************************************************************************************  
//  
// Function : icmp_send_request  
// Description : Send ARP request packet to destination.  
//  
//*******************************************************************************************  
u16_t enc28j60_read_phyreg(u8_t address)  
{  
    u16_t mydat;  
    // set the PHY register address  
    enc28j60Write(MIREGADR, address);  
    enc28j60Write(MICMD, MICMD_MIIRD);  
    // Loop to wait until the PHY register has been read through the MII  
    // This requires 10.24us  
    while( (enc28j60Read(MISTAT) & MISTAT_BUSY) );  
      
    // Stop reading 这里应该清零  
    //enc28j60Write(MICMD, MICMD_MIIRD);  
    enc28j60Write(MICMD, 0x0);  
    // Obtain results and return  
    mydat = enc28j60Read ( MIRDL );  
    //PrintHex(mydat);  
    //mydat |= enc28j60Read ( MIRDH ); //此地方源代码有误 改成  
    mydat |= (enc28j60Read ( MIRDH )<<8);  
   // PrintHex(mydat);  
    return mydat;  
}   
//*******************************************************************************************  
//  
// Function : icmp_send_request  
// Description : Send ARP request packet to destination.  
//  
//*******************************************************************************************  
void enc28j60PhyWrite(u8_t address, u16_t mydat)  
{  
    // set the PHY register address  
    enc28j60Write(MIREGADR, address);  
    // write the PHY data  
    enc28j60Write(MIWRL, mydat & 0x00ff);  
    enc28j60Write(MIWRH, mydat >> 8);  
    // wait until the PHY write completes  
    while(enc28j60Read(MISTAT) & MISTAT_BUSY)  
    {  
            delay_100ns();  
    }  
}  
  
void enc28j60ReadBuffer(u16_t len, u8_t* dat)  
{  
    // assert CS  
    CSN = 0;  
    // issue read command  
    delay_100ns();  
    WriteByte(ENC28J60_READ_BUF_MEM);           
    while(len--)  
    {  
       *dat++ = ReadByte();  
    }     
    // release CS  
    CSN = 1;  
}  
  
void enc28j60WriteBuffer(u16_t len, u8_t* dat)  
{  
    // assert CS  
    CSN = 0;   
    // issue write command   
    WriteByte(ENC28J60_WRITE_BUF_MEM);  
    // while(!(SPSR & (1<
    while(len--)  
    {        
       WriteByte(*dat++);  
    }     
    // release CS  
    CSN = 1;  
}  
  
#define ETHERNET_MIN_PACKET_LENGTH  0x3C  
#define ETHERNET_HEADER_LENGTH      0x0E  
#define IP_TCP_HEADER_LENGTH 40  
#define TOTAL_HEADER_LENGTH (IP_TCP_HEADER_LENGTH+ETHERNET_HEADER_LENGTH)  
  
void enc28j60PacketSend(u16_t len, u8_t* packet)  
{  
    // Set the write pointer to start of transmit buffer area  
    enc28j60Write(EWRPTL, TXSTART_INIT);  
    enc28j60Write(EWRPTH, TXSTART_INIT>>8);  
    // Set the TXND pointer to correspond to the packet size given  
    enc28j60Write(ETXNDL, (TXSTART_INIT+len));  
    enc28j60Write(ETXNDH, (TXSTART_INIT+len)>>8);  
    // write per-packet control byte  
    enc28j60WriteOp(ENC28J60_WRITE_BUF_MEM, 0, 0x00);  
    // TODO, fix this up  
    if( uip_len <= TOTAL_HEADER_LENGTH )  
    {  
        // copy the packet into the transmit buffer  
        enc28j60WriteBuffer(len, packet);  
    }  
    else  
    {  
        len -= TOTAL_HEADER_LENGTH;  
        enc28j60WriteBuffer(TOTAL_HEADER_LENGTH, packet);  
        enc28j60WriteBuffer(len, (unsigned char *)uip_appdata);  
    }  
    // send the contents of the transmit buffer onto the network  
    enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRTS);  
}  
  
u16_t enc28j60PacketReceive(u16_t maxlen, u8_t* packet)  
{  
    u16_t rxstat,len;  
    if (enc28j60Read(EPKTCNT) == 0)  
    {   
      return 0;  
    }  
// Set the read pointer to the start of the received packet  
    enc28j60Write(ERDPTL, (NextPacketPtr));  
    enc28j60Write(ERDPTH, (NextPacketPtr)>>8);  
// read the next packet pointer  
    NextPacketPtr  = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0);  
    NextPacketPtr |= (enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0)<<8);      
// read the packet length  
    len  = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0);  
    len |= (enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0)<<8);  
    len -= 4; //以太帧最小46字节 减去4字节的FCS校验和 加上帧头14字节 共64字节  
//PrintHex(len);  
// read the receive status  
    rxstat  = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0);  
    rxstat |= enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0)<<8;  
// limit retrieve length  
    // (we reduce the MAC-reported length by 4 to remove the CRC)  
    len = MIN(len, maxlen);  
// copy the packet from the receive buffer  
    enc28j60ReadBuffer(len, packet);  
// Errata workaround #13. Make sure ERXRDPT is odd  
    u16_t rs,re;  
    rs = enc28j60Read(ERXSTH);  
    rs <<= 8;  
    rs |= enc28j60Read(ERXSTL);  
    re = enc28j60Read(ERXNDH);  
    re <<= 8;  
    re |= enc28j60Read(ERXNDL);  
    if (NextPacketPtr - 1 < rs || NextPacketPtr - 1 > re)  
    {  
        enc28j60Write(ERXRDPTL, (re));  
        enc28j60Write(ERXRDPTH, (re)>>8);  
    }  
    else  
    {  
        enc28j60Write(ERXRDPTL, (NextPacketPtr-1));  
        enc28j60Write(ERXRDPTH, (NextPacketPtr-1)>>8);  
    }  
// decrement the packet counter indicate we are done with this packet  
    enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON2, ECON2_PKTDEC);         
    return len;  
}  
  
void dev_init(void)  
{  
    enc28j60_init();  
}  
  
void dev_send(void)  
{  
    enc28j60PacketSend(uip_len, uip_buf);  
}  
  
u16_t dev_poll(void)  
{  
    return enc28j60PacketReceive(UIP_BUFSIZE, uip_buf);  
}  
  
void enc28j60_init(void)  
{  
//SPI INIT  
    SpiInit();      
// perform system reset  
    enc28j60WriteOp(ENC28J60_SOFT_RESET, 0, ENC28J60_SOFT_RESET);  
// check CLKRDY bit to see if reset is complete  
    //while(!(enc28j60Read(ESTAT) & ESTAT_CLKRDY));  
    // Errata workaround #2, CLKRDY check is unreliable, delay 1 mS instead  
    delay_ms(5);  
// lamp test  
// enc28j60PhyWrite(PHLCON, 0x0AA2);  
      
// do bank 0 stuff  
    // initialize receive buffer  
    // 16-bit transfers, must write low byte first  
// set receive buffer start address  
    NextPacketPtr = RXSTART_INIT;  
    enc28j60Write(ERXSTL, RXSTART_INIT&0xFF);  
    enc28j60Write(ERXSTH, RXSTART_INIT>>8);  
// set receive pointer address  
    enc28j60Write(ERXRDPTL, RXSTART_INIT&0xFF);  
    enc28j60Write(ERXRDPTH, RXSTART_INIT>>8);  
// set receive buffer end  
    // ERXND defaults to 0x1FFF (end of ram)  
    enc28j60Write(ERXNDL, RXSTOP_INIT&0xFF);  
    enc28j60Write(ERXNDH, RXSTOP_INIT>>8);  
// set transmit buffer start  
    // ETXST defaults to 0x0000 (beginnging of ram)  
    enc28j60Write(ETXSTL, TXSTART_INIT&0xFF);  
    enc28j60Write(ETXSTH, TXSTART_INIT>>8);      
// do bank 2 stuff  
    // enable MAC receive  
    enc28j60Write(MACON1, MACON1_MARXEN|MACON1_TXPAUS|MACON1_RXPAUS);  
// bring MAC out of reset  
    enc28j60Write(MACON2, 0x00);  
// enable automatic padding and CRC operations  
    enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, MACON3, MACON3_PADCFG0|MACON3_TXCRCEN|MACON3_FRMLNEN);  
// enc28j60Write(MACON3, MACON3_PADCFG0|MACON3_TXCRCEN|MACON3_FRMLNEN);  
    // set inter-frame gap (non-back-to-back)  
    enc28j60Write(MAIPGL, 0x12);  
    enc28j60Write(MAIPGH, 0x0C);  
// set inter-frame gap (back-to-back)  
    enc28j60Write(MABBIPG, 0x12);  
// Set the maximum packet size which the controller will accept  
    enc28j60Write(MAMXFLL, MAX_FRAMELEN&0xFF);   
    enc28j60Write(MAMXFLH, MAX_FRAMELEN>>8);  
// do bank 3 stuff  
    // write MAC address  
    // NOTE: MAC address in ENC28J60 is byte-backward  
    enc28j60Write(MAADR5, UIP_ETHADDR0);  
    enc28j60Write(MAADR4, UIP_ETHADDR1);  
    enc28j60Write(MAADR3, UIP_ETHADDR2);  
    enc28j60Write(MAADR2, UIP_ETHADDR3);  
    enc28j60Write(MAADR1, UIP_ETHADDR4);  
    enc28j60Write(MAADR0, UIP_ETHADDR5);   
// no loopback of transmitted frames  
    enc28j60PhyWrite(PHCON2, PHCON2_HDLDIS);  
// switch to bank 0  
    enc28j60SetBank(ECON1);  
// enable interrutps  
    enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, EIE, EIE_INTIE|EIE_PKTIE);  
// enable packet reception   
    enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_RXEN);        
}  

  


          在上面文件中有一个delay_100ns函数,为什么要写这个函数呢,看了enc28j60的输入输出的时序之后发现在cs置低之后只要要保持100ns,所以每次在写入之前都要延迟100ns左右。cc2530的时钟是32MHz,所以差不多3个机器周期就是100ns了。这个是要注意的地方,我在这之前,调试了半天,一直都没发现有网络数据包出来,然后再仔细读了datasheet才发现的。
       这个驱动根据datasheet写,或者参考网上的,哈哈,只要能实现就行了。         
       有了以上驱动程序就很好移植了! 废话不多说,直接给移植步骤。
       在网上下载源码,我用的是uip0.9版本。编译环境是IAR,因为cc2530用的是IAR开发环境。

       接下来建立工程,如下图所示:

          将spi.c enc28j60.c即相关头文件放在driver目录下,将uip.c uip_arp.c uip_arch.c还有相关头文件放在uip目录下,
linker目录下放的是IAR8051的连接文件(可以不用),将应用程序main.c app.c app.h文件放在user目录下。
      将uip源代码中unix文件夹中的main函数改一下,其中tapdev_read函数替换成我们的dev_poll,将tapdev_send替换成dev_send,然后将http相关的东西删去,我们先实现简单的功能。
      根据源码文件夹中的doc文档,写个简单的历程。

       app.c的代码如下:

#include "app.h"  
#include "uip.h"  
void example1_init(void)   
{  
   uip_listen(HTONS(1234));  
}  
void example1_appcall(void)  
{  
   struct example1_state *s;  
   s = (struct example1_state *)uip_conn->appstate;   
   if(uip_connected()) {  
      s->state = WELCOME_SENT;  
      uip_send("Welcome!n", 9);  
      return;  
   }   
  
   if(uip_acked() && s->state == WELCOME_SENT) {  
      s->state = WELCOME_ACKED;  
   }  
   if(uip_newdata()) {  
      uip_send("okn", 3);  
   }  
   if(uip_rexmit()) {  
      switch(s->state) {  
      case WELCOME_SENT:  
         uip_send("Welcome!n", 9);  
         break;  
      case WELCOME_ACKED:  
         uip_send("okn", 3);  
         break;  
      }  
   }  

}  


      对了,还有个重要的问题,就是大小端的问题,这个也是我调试好久未果的一个经验。cc2530是8051内核的,是小端,所以在uipopt.h文件中做如下改动
#ifndef BYTE_ORDER
#define BYTE_ORDER     LITTLE_ENDIAN
#endif /* BYTE_ORDER */  
      还有个编译设置的问题,具体按照如下来:


     好了,然后编译连接,过程中可能有一些警告和错误,一个一个耐心排除,很容易。
     用网线连上电脑,然后打开命令终端,输入ping 219.223.173.242  我主机ip是219.223.173.243
    如下结果:

            


        然后打开网络调试助手



     如程序需要的结果相同,哈哈!过几天实现一个web服务器!
    接下来的工作就是将uip移植到z-stack上,在cc2530上实现网关的功能!

更多回帖

×
20
完善资料,
赚取积分