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 <ioCC2530.h>

#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<<SPIF)));
    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("ok\n", 3);
   }
   if(uip_rexmit()) {
      switch(s->state) {
      case WELCOME_SENT:
         uip_send("Welcome!\n", 9);
         break;
      case WELCOME_ACKED:
         uip_send("ok\n", 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上實現網關的功能!
    有需要源碼的可以聯繫我!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章