天天看點

STM32移植uIP

檔案說明:

STM32移植uIP
  • 這是uIP1.0源碼檔案,apps是作者寫好的應用程式demo,doc是一些文檔,lib裡面隻有一個檔案是記憶體申請與釋放函數的接口,uip是tcp/ip的協定棧了,unix是與外部的接口,我們移植也主要去改這些檔案。
STM32移植uIP
  • 說一下一眼看不出作用的檔案。lc.h、lc-addrlabels.h、lc-switch.h、psock.c、psock.h和pt.h這些是一個模拟線程的輕量級庫protothreads。uip_arp.c位址解析協定的接口。uip-fw.c是多重網絡轉發資料包的借口(沒用到)。uiplib.c裡隻有一個函數uiplib_ipaddrconv:把ip位址轉換成字元格式(沒用到)。uip-neighbor.c可能是存儲網絡上一下ip與mac(網絡鄰居?沒用到)。uip-split.c拆包發送的借口,tcp/ip協定規定包的長度不能超過1500個位元組,否則需要拆包發送(沒用到)。
STM32移植uIP
  • clock-arch.c裡面隻有一個函數clock_time。main函數自己重寫吧。tapdev.c是與硬體網絡子產品的接口。uip-conf.h是一些配置。
  • 為什麼我覺得uip比RL_TCPnet移植更麻煩?就是因為uip的檔案關系有點亂,而且一些資料類型需要重定義,甚至還需要改動庫檔案裡的,可能RL_TCPnet是因為Keil公司專門寫了用于M3核心的庫才沒有這麼些問題吧。下面是簡單的頭檔案包含關系,在移植的時候需要注意。
STM32移植uIP

移植:

  • 因為隻做udp、tcp(用戶端與伺服器)和http,是以添加這些檔案就夠了。工程結構如下圖。uip_user.c是自己寫的應用函數。
STM32移植uIP
  • 我用的網絡子產品是enc28j60。網絡上有很多該子產品的驅動,隻要改一下spi管腳配置就行。
  • tapdev.c就是uip與硬體的接口函數了。具體見下。
void
tapdev_init(unsigned char *my_mac)
{
     int i;
     /*初始化 enc28j60*/
     enc28j60Init(my_mac);
     for (i = 0; i < 6; i++)
     {
          uip_ethaddr.addr[i] = my_mac[i];
     }                          

}
/*---------------------------------------------------------------------------*/
/****************************************************************************
* 名    稱:uint16_t tapdev_read(void)
* 功    能:                                                                                         
* 入口參數:讀取一包資料
* 出口參數: 如果一個資料包收到傳回資料包長度,以位元組為機關,否則為零。
* 說    明:
* 調用方法:
****************************************************************************/
unsigned int tapdev_read(void)
{    
  return  enc28j60PacketReceive(1500,uip_buf);
}
/****************************************************************************
* 名    稱:void tapdev_send(void)
* 功    能:                                                                                         
* 入口參數:發送一包資料
* 出口參數:
* 說    明:
* 調用方法:
****************************************************************************/
void tapdev_send(void)
{
  enc28j60PacketSend(uip_len,uip_buf);
}
           
  • uip-conf.h是配置檔案,關閉日志輸出:#define UIP_CONF_LOGGING         0。打開udp連接配接:#define UIP_CONF_UDP             1。隻留下#include webserver.h(裡面有個http回調函數,不過最好在之前自己寫回調函數,因為還要做tcp的其他連接配接)。ps.實際的配置檔案是uipopth.h,不過那裡面的一般不用改。
/* 定義應用程式回調函數 */
#ifndef UIP_APPCALL
#define UIP_APPCALL tcp_appcall
#endif
#define UIP_UDP_APPCALL udp_appcall
//uip-tcp的總回調函數
void tcp_appcall(void)
{
    if(uip_conn->lport == HTONS(80))
    {
        httpd_appcall();//指向了http.c的httpd_appcall()回調函數。
    }
    if(uip_conn->lport == HTONS(LocalPort))
    {
        tcp_server_appcall();
    }

    if(uip_conn->rport == HTONS(RemotePort))
    {
        tcp_client_appcall();
    }
}
           
  • clock-arch.h中隻有一個函數clock_time()。uip這個協定棧需要時間輪詢函數,在clock-arch.h中定義#define CLOCK_CONF_SECOND 100,也就是100毫秒。由于我是用的ucos-ii,是以就寫在系統時間鈎子函數(在os_cpu_c.c裡),如果沒有用的話,也隻需要另開一個定時器就可以了。
volatile INT32U uIP_time = 0;
#if (OS_CPU_HOOKS_EN > 0) && (OS_TIME_TICK_HOOK_EN > 0)
void  OSTimeTickHook (void)
{
#if OS_APP_HOOKS_EN > 0
    App_TimeTickHook();
#endif
     static INT8U s_count = 0;
     if (++s_count >= 10)
     {
          s_count = 0;
          uIP_time++;     /* 全局運作時間每10ms增1 */    
          if (uIP_time == 0x80000000)
          {
               uIP_time = 0;
          }
     }
#if OS_TMR_EN > 0
    OSTmrCtr++;
    if (OSTmrCtr >= (OS_TICKS_PER_SEC / OS_TMR_CFG_TICKS_PER_SEC)) {
        OSTmrCtr = 0;
        OSTmrSignal();
    }
#endif
}
#endif
           
  • uip_user.c是自己寫的一些應用函數
extern u16 LocalPort;               //本地端口
extern u16 RemotePort;               //遠端端口
extern u8 RemoteIP[4];               //遠端ip
extern u8 enc_send_buf[256];     //發送資料最大位元組256
extern u8 enc_send_size;       //需要發送的位元組數
void Net_Init(void);             //網絡初始化
void Udp_Init(void);             //udp初始化
void Tcp_Init(void);             //tcp初始化
void tcp_appcall(void);             //tcp總回調函數
void udp_appcall(void);             //udp總回調函數
void Uip_Proc(void);             //uip的處理函數(需要循環調用)
void tcp_server_appcall(void); //tcp作為伺服器時的回調函數
void tcp_client_appcall(void); //tcp作為用戶端時的回調函數
void tcp_newdata(void);             //tcp接收函數
void tcp_senddata(void);        //tcp發送函數
void udp_newdata(void);             //udp接收函數
void udp_senddata(void);        //udp發送函數
           
  • 到此,uip的移植基本上差不多了。隻需要再修改一些小錯誤:比如,資料類型的重定義,删除一些列印函數,tcp_udp_appstate_t(使用者自定義類型)的重定義在http.c裡面有了,如果不需要用到作者的一些應用函數可以直接定義成int,同樣uip_udp_appstate_t(使用者自定義類型)也可以直接定義為int,當然也可以定義成數組,把資料的指針和長度放裡面。
  • pt.h中115行中把PT_YIELD_FLAG定義為volatile可變的,可以減少好的警告資訊。
  • psock.c中164行,添加一行uip_flags &= ~UIP_ACKDATA。還有一個udp連接配接bug在uip.c裡,具體可見奮鬥闆。
static char
data_acked(register struct psock *s)
{
  if(s->state == STATE_DATA_SENT && uip_acked()) {
    uip_flags &= ~UIP_ACKDATA;
    if(s->sendlen > uip_mss()) {
      s->sendlen -= uip_mss();
      s->sendptr += uip_mss();
    } else {
      s->sendptr += s->sendlen;
      s->sendlen = 0;
    }
    s->state = STATE_ACKED;
    return 1;
  }
  return 0;
}
           

應用:

  • uip初始化要設定網絡基本資訊和輪詢時間。
void Net_Init(void)
{
    uip_ipaddr_t ipaddr;
    unsigned char mymac[6] = {0x04, 0x02, 0x35, 0x00, 0x00, 0x01}; //MAC位址
    tapdev_init(mymac);                               //ENC28J60初始化
    uip_init();                                        //UIP協定棧初始化
    uip_ipaddr(ipaddr, 172, 72, 100, 212);          //設定IP位址
    uip_sethostaddr(ipaddr);
    uip_ipaddr(ipaddr, 172, 72, 101, 1);          //設定預設路由器IP位址
    uip_setdraddr(ipaddr);
    uip_ipaddr(ipaddr, 255, 255, 252, 0);          //設定網絡掩碼
    uip_setnetmask(ipaddr);
    timer_set(&periodic_timer, CLOCK_SECOND / 2);
    timer_set(&arp_timer, CLOCK_SECOND * 10);     //設定輪詢時間

}
           
  • udp的初始化連接配接注意一下綁定本地ip。tcp伺服器端隻需要監聽某個端口(80是http服務端口)。tcp用戶端需要建立連接配接。
//建立UDP連接配接初始化*****************************
void Udp_Init(void)
{

    uip_ipaddr_t ipaddr;//定義IP類型變量
    uip_ipaddr(ipaddr, RemoteIP[0], RemoteIP[1], RemoteIP[2], RemoteIP[3]); //遠端IP
    if(my_udp_conn != NULL)
    {
        uip_udp_remove(my_udp_conn);//如果連接配接已經建立,則删除之
    }

    my_udp_conn = uip_udp_new(&ipaddr, HTONS(RemotePort));//建立到遠端端口
    if(my_udp_conn != NULL)
    {
        uip_udp_bind(my_udp_conn, HTONS(LocalPort));//綁定本地端口
    }

}
//作為tcp用戶端初始化
void Tcp_Init(void)
{
    uip_ipaddr_t ipaddr;//定義IP類型變量
    uip_ipaddr(ipaddr, RemoteIP[0], RemoteIP[1], RemoteIP[2], RemoteIP[3]); //遠端IP
    my_tcp_conn = uip_connect(&ipaddr, HTONS(RemotePort));//建立到遠端端口
}
           
  • 還有一個問題是:作為tcp用戶端的時候,pc機使用網絡調試助手,如果在連接配接後再點斷開的話就重連不上。 PC機斷開連接配接後,那個端口先是timeout然後應該就删除了,斷開後會進入2次uip_closed(),是以判斷一下重新連接配接(會配置設定新的端口)。如果在程式運作開始但是PC機并沒有打開連接配接的話,會一直進入uip_aborted(),在裡面設定重新連接配接,這樣會直到PC機打開。
if(uip_closed())
    {
        tcp_server_flag++;
        if(tcp_server_flag == 2)
        {
            uip_ipaddr_t ipaddr;//定義IP類型變量
            uip_ipaddr(ipaddr, RemoteIP[0], RemoteIP[1], RemoteIP[2], RemoteIP[3]); //遠端IP
            my_tcp_conn = uip_connect(&ipaddr, HTONS(RemotePort));//建立到遠端端口
            tcp_server_flag = 0;
        }
    }
    if( uip_aborted())
    {
        uip_ipaddr_t ipaddr;//定義IP類型變量
        uip_ipaddr(ipaddr, RemoteIP[0], RemoteIP[1], RemoteIP[2], RemoteIP[3]); //遠端IP
        my_tcp_conn = uip_connect(&ipaddr, HTONS(RemotePort));     //建立到遠端端口
     }
           
  • 有關http的應用請見下一篇。ps.uip1.0的smtp好像不完整,請參照0.9版。

下載下傳:

  • uip1.0英文使用指南下載下傳,請點選。
  • stm32移植uip1.0工程源碼下載下傳,請點選。如果使用HG 隻要clone https://[email protected]/zouw96/uip_http

繼續閱讀