uIP学习笔记
1.前言
? ? 最近半年的時(shí)間一直在學(xué)習(xí)應(yīng)用嵌入式以太網(wǎng)。雖然學(xué)習(xí)的動機(jī)僅僅是玩玩,但是以太網(wǎng)真的深深吸引了我。這里我和各位分享一下uIP的使用經(jīng)驗(yàn)。uIP是一個(gè)簡單好用的嵌入式網(wǎng)絡(luò)協(xié)議棧,易于移植且消耗的內(nèi)存空間較少,非常適合學(xué)習(xí)和使用??梢钥隙ǖ恼fuIP是嵌入式以太網(wǎng)學(xué)習(xí)的好起點(diǎn),但不一定是終點(diǎn)。uIP的功能遠(yuǎn)不如LwIP強(qiáng)大,但兩者并沒有孰優(yōu)孰劣之分,uIP和LwIP的作者同為Adam Dunkels,LwIP開發(fā)較早uIP開發(fā)較晚,uIP經(jīng)過這幾年的發(fā)展從IPV4遷移到IPV6,最終可以適用于無線傳感網(wǎng)絡(luò)。總的來說,uIP是一個(gè)很好的起點(diǎn),學(xué)好uIP可以遷移到LwIP,也可以遷移到uIPV6。
【uIP官方代碼】
?
1.1 工程代碼
【1】CSDN資源?下載該資源需要1個(gè)積分,請可憐可憐我讓我也有機(jī)會下載CSDN上某些優(yōu)質(zhì)資源
【2】CSDN代碼倉庫
1.2 進(jìn)階博文
【freemodbus modbus TCP 學(xué)習(xí)筆記】——使用uIP協(xié)議棧實(shí)現(xiàn)modbus TCP。
【uip的yeelink實(shí)現(xiàn)】——作者為我的(前)同事,使用uIP協(xié)議棧與yeelink平臺交互數(shù)據(jù),很有意思。
2.搭建實(shí)驗(yàn)環(huán)境
? ? 先講一下如何搭建實(shí)驗(yàn)環(huán)境。建議于把開發(fā)板接到路由器上,而調(diào)試使用的PC機(jī)通過有線或者無線接到路由器上,保證開發(fā)板和PC機(jī)接入同一個(gè)路由器。由于uIP不支持DHCP(不直接支持),所以需要保證開發(fā)板和PC位于相同的子網(wǎng),開發(fā)板的IP地址、路由器地址和子網(wǎng)掩碼都需要手動設(shè)定。設(shè)定之前最好看看調(diào)試PC機(jī)IP地址和路由器(網(wǎng)關(guān))地址。例如調(diào)試PC機(jī)的IP地址如下圖所示。
圖1 PC機(jī)IP地址
? ? 路由器的IP地址為192.168.1.1。那么開發(fā)板的IP地址可以設(shè)定為 192.168.1.2到192.168.1.255。為保證你的調(diào)試萬無一失,還是建議訪問路由器,確認(rèn)此時(shí)有哪些設(shè)備接入路由器,該步驟的主要功能是避免IP地址重復(fù)。
圖2 和路由器相連的以太網(wǎng)設(shè)備
3.硬件和軟件說明
3.1 硬件環(huán)境
? ? 【奮斗開發(fā)板】
????奮斗開發(fā)板上有一片ENC28J60,ENC28J60通過SPI接口控制內(nèi)部寄存器,并有中斷輸出接口。STM32通過SPI1和ENC28J60相連。具體接口如下:
? ? SPI1_MISO@PA6
? ? SPI1_MOSI@PA7
? ? SPI1_SCK@PA5
? ? ENC28J60_CS@PA4
? ? ENC28J60_INT@PA1
? ? 由于SPI上同時(shí)掛載其他SPI從設(shè)備,所有初始化的過程中需要通過操作CS端口禁止其他SPI從設(shè)備。(別小看這步,調(diào)試的時(shí)候在這步花費(fèi)了非常多的時(shí)間)其他SPI從設(shè)備包括SST25VF016,CS端位于PC5;VS1003,CS端口位于PB12。
? ? 【其他說明】
? ? 串口調(diào)試位于UART1。有三個(gè)LED燈分別位于PB.5,PD.6和PD.3。如果您的開發(fā)板和有存在差別,請按照順序修改相關(guān)IO口并打開相應(yīng)RCC時(shí)鐘。
3.2 軟件說明
? ? 工具鏈為EWARM 6.5。
4.網(wǎng)卡驅(qū)動
? ? 網(wǎng)卡驅(qū)動采用ENC28J60。具體可參考論壇中的另一篇博文【ENC28J60學(xué)習(xí)筆記】
? ? 博文詳細(xì)分析了如何使用ENC28J60,雖然ENC28J60使用復(fù)雜但是深入理解兩點(diǎn)即可,第一點(diǎn)如何通過SPI發(fā)送命令和數(shù)據(jù);第二點(diǎn)理解ENC28J60的緩沖區(qū),在發(fā)送以太網(wǎng)和接受以太網(wǎng)數(shù)據(jù)包的過程中,ENC28J60會幫助用戶做些額外的工作,例如發(fā)送時(shí)自動填充SFD,在讀取接收緩沖區(qū)數(shù)據(jù)時(shí)會包含若干狀態(tài)信息,包括數(shù)據(jù)包長度和CRC校驗(yàn)結(jié)果等。如果你比較“速食”可以跳過該部分內(nèi)容,如果你比較“耐心”可以花點(diǎn)時(shí)間看看。其他的以太網(wǎng)驅(qū)動芯片或RF芯片也遵循相同的規(guī)律,可以做到觸類旁通。
5.一個(gè)簡單有效的定時(shí)器
? ? uIP協(xié)議棧處理過程需要一個(gè)定時(shí)配合,該定時(shí)器實(shí)際為一個(gè)軟件定時(shí)器,定時(shí)器幫助uIP處理若干周期性任務(wù),例如處理TCP連接重傳,定時(shí)更新ARP緩沖表等。設(shè)計(jì)定時(shí)器的方法很多,在這里推薦uIP原作者的timer模塊。timer模塊的原理類似于MCU硬件中的比較匹配原理,timer模塊中有一個(gè)全部變量counter,每次MCU發(fā)生某個(gè)定時(shí)器中斷時(shí)累加1,如果某個(gè)任務(wù)需要使用定時(shí)器服務(wù),在該任務(wù)中聲明一個(gè)timer(在該任務(wù)中為全局變量),并記錄此時(shí)的counter值。判斷溢出可查詢當(dāng)前的counter和被記錄的counter的差值,如果差值超過間隔值那么軟件定時(shí)器timer溢出(類似于發(fā)生比較匹配中斷)。軟件定時(shí)器的主要作用有兩個(gè)。第一,更新TCP或UDP連接,第二,更新ARP緩沖區(qū)(ARP表)。雖然uIP在功能上比LwIP簡單的多,但是LwIP也有類似的部分(或者說完全一樣)。
? ? 詳細(xì)代碼如下:
#include "timer.h" #include "stm32f10x_it.h"uint16_t current_clock = 0;void timer_config(void) {/* Systick時(shí)鐘每秒觸發(fā)CLOCK_SECOND次 */if (SysTick_Config(SystemCoreClock / CLOCK_SECOND)){while (1);} }void SysTick_Handler(void) {/* 時(shí)間標(biāo)志累加 */current_clock++; }uint16_t clock_time(void) {return current_clock; }void timer_set(timer_typedef* ptimer,uint16_t interval) {/* 設(shè)置時(shí)間間隔 */ptimer->interval = interval;/* 設(shè)置啟動時(shí)間 */ptimer->start = clock_time(); }void timer_reset(timer_typedef * ptimer) {ptimer->start =ptimer->start + ptimer->interval; }int8_t timer_expired(timer_typedef* ptimer) {/* 一定要裝換為有符號數(shù),進(jìn)行數(shù)學(xué)比較時(shí),多使用有符號數(shù) */if((int16_t)(clock_time() - ptimer->start) >= (int16_t)ptimer->interval)return 1;elsereturn 0; }6.uIP基本結(jié)構(gòu)與配置
6.1 uIP基本結(jié)構(gòu)
? ? uIP的代碼編寫需要遵守一定的結(jié)構(gòu),而且這種結(jié)構(gòu)最好保持穩(wěn)定(保持不變)。這個(gè)結(jié)構(gòu)主要做以下幾個(gè)部分任務(wù)。
? ? 【1】獲得以太網(wǎng)數(shù)據(jù)包
? ? 【2】處理ARP報(bào)文
? ? 【3】處理IP報(bào)文
? ? 【4】定期處理TCP和UDP連接
? ? 【5】定期更新ARP緩沖區(qū)
// BUF指向uIP緩沖區(qū) uip_eth_hdr為以太網(wǎng)首部結(jié)構(gòu)體 // 6字節(jié)目標(biāo)MAC地址 6字節(jié)源MAC地址 2字節(jié)類型 #define BUF ((struct uip_eth_hdr *)&uip_buf[0]) void GPIO_Config(void);int main(void) {timer_typedef periodic_timer, arp_timer;uip_ipaddr_t ipaddr;/* 設(shè)定查詢定時(shí)器 ARP定時(shí)器 */timer_set(&periodic_timer, CLOCK_SECOND / 2);timer_set(&arp_timer, CLOCK_SECOND * 10);GPIO_Config(); /* 禁止SPI其他設(shè)備,防止竄擾 */timer_config(); /* 配置systic作為1ms中斷 */BSP_ConfigSPI1();/* 網(wǎng)卡初始化,ENC28J60,包括MAC地址初始化 */tapdev_init();/* UIP協(xié)議棧初始化 */uip_init();/* 設(shè)置本機(jī)IP地址 */uip_ipaddr(ipaddr, 192,168,1,15);uip_sethostaddr(ipaddr);/* 設(shè)置默認(rèn)路由器IP地址 */uip_ipaddr(ipaddr, 192,168,1,1);uip_setdraddr(ipaddr);/* 設(shè)置網(wǎng)絡(luò)掩碼 */uip_ipaddr(ipaddr, 255,255,255,0);uip_setnetmask(ipaddr);/* 用戶任務(wù)初始化 為TCP echo任務(wù)*/example1_init();/* 初始化串口 重定義putchar */BSP_ConfigUSART1();/* 打印本機(jī)IP地址 */printf("\r\nuip start!\r\n");printf("ipaddr:192.168.1.15\r\n");/* 打印個(gè)人信息,呵呵*/printf("eID:xukai871105\r\r");printf("Email:xukai19871105@126.com");while (1){/* 讀取以太網(wǎng)數(shù)據(jù)包,返回?cái)?shù)據(jù)長度 */uip_len = tapdev_read();if(uip_len > 0){/* 收到IP數(shù)據(jù)包 */if(BUF->type == htons(UIP_ETHTYPE_IP)){uip_arp_ipin();uip_input();if (uip_len > 0){uip_arp_out();tapdev_send();}}/* 收到ARP數(shù)據(jù)包 */else if (BUF->type == htons(UIP_ETHTYPE_ARP)){uip_arp_arpin();if (uip_len > 0){tapdev_send();}}}/* 查詢定時(shí)器是否超時(shí) */if(timer_expired(&periodic_timer)){timer_reset(&periodic_timer);/* 測試使用,表現(xiàn)為LED燈閃爍 */GPIOB->ODR ^= GPIO_Pin_5;/* 查詢并處理所有TCP連接*/for(uint8_t i = 0; i < UIP_CONNS; i++){uip_periodic(i);if(uip_len > 0){uip_arp_out();tapdev_send();}}#if UIP_UDP/* 查詢并處理所有UDP連接*/for(uint8_t i = 0; i < UIP_UDP_CONNS; i++){uip_udp_periodic(i);if(uip_len > 0){uip_arp_out();tapdev_send();}} #endif /* UIP_UDP *//* ARP定時(shí)是否溢出 */if (timer_expired(&arp_timer)){timer_reset(&arp_timer);uip_arp_timer();}}} }? ?【簡單說明】
? ? 1.#define BUF ((struct uip_eth_hdr *)&uip_buf[0])
? ? 指向uIP緩沖區(qū),強(qiáng)制類型轉(zhuǎn)化為uip_eth_hdr結(jié)構(gòu)體,uip_eth_hdr即為以太網(wǎng)首部結(jié)構(gòu),6字節(jié)目標(biāo)MAC地址 6字節(jié)源MAC地址 2字節(jié)類型。
? ? 2. tapdev_init();tapdev_read();tapdev_send();
? ? 三個(gè)函數(shù)為以太網(wǎng)操作函數(shù),只有tapdev_read有返回值,其他函數(shù)即無輸入?yún)?shù)也無返回參數(shù)。這三個(gè)函數(shù)便是ENC28J60操作的三個(gè)封裝,ENC28J60發(fā)送或接收直接操作uIP的兩個(gè)全局變量uip_buf和uip_len。
? ? 具體代碼如下:
#include "tapdev.h" #include "uip.h" #include "uip_arp.h" #include "enc28j60.h" // MAC地址 struct uip_eth_addr uip_mac; static unsigned char ethernet_mac[6] = {0x00,0x14,0x0B,0x3F,0x04,0xB1};void tapdev_init(void) {enc28j60_init(ethernet_mac); /*初始化enc28j60 賦值MAC地址*/for (uint8_t i = 0; i < 6; i++){uip_mac.addr[i] = ethernet_mac[i];}uip_setethaddr(uip_mac); /* 設(shè)定uip mac地址*/ }uint16_t tapdev_read(void) {return enc28j60_packet_receive(uip_buf,1500); }void tapdev_send(void) {enc28j60_packet_send(uip_buf,uip_len); }6.2 uIP配置部分
? ? 【IP地址配置】
????? ? IP地址設(shè)置包括,本地IP地址,網(wǎng)關(guān)地址和子網(wǎng)掩碼。具體代碼如下:
? ? 【MAC地址配置】
????? ? MAC的地址較為特殊,由于ENC28J60本身沒有唯一的EUI-48(俗稱MAC地址)地址,所以EUI-48地址需要手動配置。該地址不但應(yīng)用于ENC28J60也應(yīng)用于uIP。相關(guān)代碼在上一小節(jié)已說明。? ?
6.3 uip-conf.h部分
????? ? uip-conf部分說明三點(diǎn)
? ? ? ? 【1】如果不熟悉請保留默認(rèn)參數(shù),例如UIP_CONF_MAX_CONNECTIONS等
? ? ? ? 【2】如果設(shè)置UIP_CONF_LOGGING為1,請?zhí)砑觱oid uip_log(char *m){}
? ? ? ? 【3】必須包含用戶任務(wù)頭文件,且放在該頭文件的最后。例如添加#include "example1.h"。這樣做的主要目的是定義uip_tcp_appstate_t和UIP_APPCALL兩個(gè)關(guān)鍵參數(shù)。
????? ? 具體代碼如下:
#ifndef __UIP_CONF_H #define __UIP_CONF_H #include <inttypes.h> typedef uint8_t u8_t; typedef uint16_t u16_t; typedef unsigned short uip_stats_t; /* 最大TCP連接數(shù) */ #define UIP_CONF_MAX_CONNECTIONS 10 /* 最大端口監(jiān)聽數(shù) */ #define UIP_CONF_MAX_LISTENPORTS 10 /* uIP 緩存大小*/ #define UIP_CONF_BUFFER_SIZE 1500 /* CPU字節(jié)順序 */ #define UIP_CONF_BYTE_ORDER UIP_LITTLE_ENDIAN /* 日志開關(guān) */ #define UIP_CONF_LOGGING 1 /* UDP支持開關(guān)*/ #define UIP_CONF_UDP 0 /* UDP校驗(yàn)和開關(guān) */ #define UIP_CONF_UDP_CHECKSUMS 1 /* uIP統(tǒng)計(jì)開關(guān) */ #define UIP_CONF_STATISTICS 1 // 加入用戶任務(wù)頭文件,請修改 #include "example1.h" #endif7.案例——最簡單的TCP echo程序
? ? 先來一個(gè)最簡單的TCP程序。uIP作為server,IP地址為192.168.1.15。PC機(jī)做client,IP地址為192.168.1.10X。
? ? 【1】在網(wǎng)絡(luò)調(diào)試助手中,選擇以太網(wǎng)通信種類為client(表示PC機(jī)為Client),IP地址輸入192.168.1.15,端口號輸入1234。最后點(diǎn)擊連接。
? ? 【2】在發(fā)送區(qū)域輸入任意內(nèi)容,點(diǎn)擊發(fā)送數(shù)據(jù)。
? ? 【3】觀察返回結(jié)果,是否和發(fā)送數(shù)據(jù)相同。
? ? 為了實(shí)現(xiàn)該功能新建example1.c和example1.兩個(gè)文件。代碼如下:
#include "example1.h" #include "uip.h" #include <string.h> #include <stdio.h> #include <stdint.h> void example1_init(void) {uip_listen(HTONS(1234)); } void example1_appcall(void) {if( uip_newdata() ){// 輸出遠(yuǎn)程IP和端口號printf("remote ip addr:%d.%d.%d.%d\r\n",(uip_conn->ripaddr[0]) & 0X00ff,(uip_conn->ripaddr[0]) >> 8,(uip_conn->ripaddr[1]) & 0X00ff,(uip_conn->ripaddr[1]) >> 8);printf("remote ip port:%d\r\n",HTONS(uip_conn->rport));// TCP ECHOuip_send(uip_appdata,uip_len);} }圖3 TCP Echo實(shí)驗(yàn)結(jié)果
?????代碼做如下分析
? ? 【1】uip_listen(HTONS(1234));偵聽1234端口,
? ? 【2】uip_newdata()即查詢uip_buf中是否有新數(shù)據(jù),如果返回1的話,表示接收到新數(shù)據(jù)。
? ? 【3】uip_send(uip_appdata,uip_len);uip_send為發(fā)送數(shù)據(jù)包函數(shù)
? ? 【4】uip_appdata指向用戶數(shù)據(jù),所謂用戶數(shù)據(jù)即TCP負(fù)載數(shù)據(jù),例如網(wǎng)絡(luò)調(diào)試助手發(fā)送xukai871105,那么uip_appdata指向xukai871105.
? ? 【5】uip_len為用戶數(shù)據(jù)長度,若串口調(diào)試助手發(fā)送xukai871105,那么uip_len為11。
8.wireshark網(wǎng)絡(luò)包分析
? ? 程序雖然簡單,但是TCP通過過程還是可以好好分析一下的。通過wireshark軟件抓取整個(gè)通信過程。
? ? 其中192.168.1.102為調(diào)試PC機(jī)(下文簡稱PC機(jī)),192.168.1.15為uIP嵌入式開發(fā)板(下文簡稱uIP)。
圖4 網(wǎng)絡(luò)數(shù)據(jù)包分析
===================================================
?1.建立連接階段
【36】PC機(jī)向uIP發(fā)送SYN,表示請求連接(點(diǎn)擊網(wǎng)絡(luò)調(diào)試助手的連接按鈕)
【37】uIP向PC機(jī)返回ACK,同時(shí)發(fā)送SYN(注意若接收到SYN標(biāo)志,必須返回ACK)
【38】PC機(jī)向uIP發(fā)送應(yīng)答ACK,表示該次TCP連接成功。
===================================================
2.數(shù)據(jù)交換階段
(負(fù)載數(shù)據(jù)包假定為1234)
【51】PC機(jī)向uIP發(fā)送1234,標(biāo)志位PSH+ACK,表示該數(shù)據(jù)包需要立即處理,并需要應(yīng)答
【52】uIP向PC機(jī)返回1234,標(biāo)志位PSH+ACK,表示該數(shù)據(jù)包需要立即處理,并需要應(yīng)答
【53】PC機(jī)返回應(yīng)答,表示PC機(jī)接收到echo數(shù)據(jù)包。
此時(shí)數(shù)據(jù)交換完成,若在網(wǎng)絡(luò)調(diào)試助手再次點(diǎn)擊發(fā)送,便重復(fù)51到53部分。
===================================================
3.關(guān)閉連接部分
【65】PC機(jī)要求停止連接,發(fā)送FIN標(biāo)志。(點(diǎn)擊網(wǎng)絡(luò)調(diào)試助手的關(guān)閉按鈕)
【66】uIP返回FIN+ACK,表示同意結(jié)束本次TCP連接。
【67】PC機(jī)發(fā)送ACK,表示收到了uIP的FIN。(至此,TCP連接完全結(jié)束)
===================================================
10.總結(jié)
? ? 【1】掌握嵌入式以太網(wǎng)需要較多的背景知識,只能在實(shí)踐的過程中一點(diǎn)一滴積累?;剡^頭來想想自己的學(xué)習(xí)嵌入式以太網(wǎng)的經(jīng)歷,多數(shù)時(shí)間多是在急躁和失望中度過。唯有耐心與細(xì)致并不斷學(xué)習(xí)基礎(chǔ)知識才可以把問題解決,最終把想法變成現(xiàn)實(shí)。
? ? 【2】uIP功能簡單,但是易于使用。如果覺得uIP在實(shí)際中難以發(fā)揮作用的話,還有LwIP作為補(bǔ)充。雖然兩者存在功能上的差異,但是TCP連接還是那幾個(gè)——SYN、ACK、PSH、FIN標(biāo)志位。LwIP提供套接字通信,這使得嵌入式以太網(wǎng)應(yīng)用和PC機(jī)上的以太網(wǎng)應(yīng)用變得極為相似。
? ? 【3】由于TCP協(xié)議屬于運(yùn)輸層協(xié)議,TCP傳輸?shù)膬?nèi)容本身并沒有含義,這些被傳輸?shù)臄?shù)據(jù)需要被賦予含義才可以使用。從工業(yè)控制來說,MODBUS協(xié)議可以應(yīng)用與TCP協(xié)議,并可以實(shí)現(xiàn)完善的檢測與控制功能。從其他應(yīng)用來說,嵌入式系統(tǒng)可以提供HTTP通信、提供web service應(yīng)用,通過解析JSON格式等手段實(shí)現(xiàn)更廣泛的應(yīng)用。
? ? 最后感謝大家的關(guān)注,我一定繼續(xù)努力。若有描述錯誤的地方請指出,定當(dāng)更正。
11.推薦圖書資料
? ? 《基于IP的物聯(lián)網(wǎng)架構(gòu)、技術(shù)與應(yīng)用》。圖書作者之一adam dunkels為uIP和LwIP的作者,雖然uIP在書中只占非常小的一部分,但本書信息量較大,技術(shù)非常新穎。書中提到的PACHUBE即是在論壇打廣告樂為物聯(lián)的原型。
? ? 《嵌入式Internet TCP IP基礎(chǔ)、實(shí)現(xiàn)及應(yīng)用》。本書的TCP IP部分介紹的非常詳細(xì),書中有實(shí)現(xiàn)嵌入式以太網(wǎng)的代碼分析。本書的作者也設(shè)計(jì)了一套功能完善的TCP IP協(xié)議棧。結(jié)合書中前半部分的基礎(chǔ)和中部的實(shí)現(xiàn),會有非常大的收獲。
12.其他網(wǎng)絡(luò)資料
? ? 第一次有學(xué)習(xí)嵌入式以太網(wǎng)的沖動便從淘寶上購入ENC28J60模塊,賣家提供的源碼為國外AVRNET項(xiàng)目的源碼。如果耐心一點(diǎn)認(rèn)真分析AVRNET項(xiàng)目的源代碼,并不斷修改實(shí)踐,收獲頗豐。順著ARP、IP、ICMP、UDP、TCP寫了幾個(gè)帖子,算是自己對嵌入式以太網(wǎng)的第一個(gè)總結(jié)。在這里再次貼一下鏈接。
【ARP部分】【IP和ICMP部分】【UDP部分】【TCP部分】
? ? 在這一系類帖子中,還欠了一個(gè)HTTP的帖子。通過大家的關(guān)注度我發(fā)現(xiàn),ARP部分關(guān)注的人最少,因?yàn)檫@個(gè)離HTTP最遠(yuǎn)。包括我在內(nèi)得到網(wǎng)絡(luò)模塊ENC28J60的第一個(gè)反應(yīng)就是如果實(shí)現(xiàn)網(wǎng)頁(HTTP)控制LED燈,讀取溫度濕度數(shù)據(jù)?,F(xiàn)在回過頭來看看基礎(chǔ)還是非常重要的。
?
總結(jié)
- 上一篇: QQ出现大规模盗号,为什么会这样?就没有
- 下一篇: 小志志和小峰峰的日常(SG函数)