单片机联网,UIP实现tcp/udp协议
生活随笔
收集整理的這篇文章主要介紹了
单片机联网,UIP实现tcp/udp协议
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
原文地址:https://www.cnblogs.com/dengxiaojun/p/4375047.html
UIP是單片機界聯網的一個很好地選擇,移植這個庫有點復雜,首先是第一步,網卡驅動要寫好,使用的網卡芯片為ENC28J60,驅動可以再工程包里面找到
//配置網卡硬件,并設置MAC地址 //返回值:0,正常;1,失敗; u8 tapdev_init(u8* macaddr) { u8 i,res=0; res=ENC28J60_Init((u8*)macaddr); //初始化ENC28J60 //把IP地址和MAC地址寫入緩存區for (i = 0; i < 6; i++)uip_ethaddr.addr[i]=macaddr[i]; //指示燈狀態:0x476 is PHLCON LEDA(綠)=links status, LEDB(紅)=receive/transmit//PHLCON:PHY 模塊LED 控制寄存器 ENC28J60_PHY_Write(PHLCON,0x0476);return res; }//讀取一包數據 uint16_t tapdev_read(void) { return ENC28J60_Packet_Receive(MAX_FRAMELEN,uip_buf); }//發送一包數據 void tapdev_send(void) {ENC28J60_Packet_Send(uip_len,uip_buf); }分別是初始化,讀,寫
這些驅動會在一個叫做uip_call的函數中用到,其次,要設置uip的時鐘,這個時鐘適用于arp表的更新的
#include "clock-arch.h" #include "sys.h" //時鐘驅動文件,//uip時鐘 extern u32 uip_timer;//uip 計時器,每10ms增加1. /*---------------------------------------------------------------------------*/ clock_time_t clock_time(void) {return uip_timer; /* 10ms 單位 */ } u32 uip_timer=0;//uip 計時器,每10ms增加1.//定時器6中斷服務程序 void TIM6_IRQHandler(void) { if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET) //檢查指定的TIM中斷發生與否:TIM 中斷源 {uip_timer++;//uip計時器增加1 } TIM_ClearITPendingBit(TIM6, TIM_IT_Update ); //清除TIMx的中斷待處理位:TIM 中斷源 }//基本定時器6中斷初始化 //這里時鐘選擇為APB1的2倍,而APB1為36M //arr:自動重裝值。 //psc:時鐘預分頻數 //這里使用的是定時器3! void TIM6_Int_Init(u16 arr,u16 psc) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE); //時鐘使能TIM_TimeBaseStructure.TIM_Period = arr; //設置在下一個更新事件裝入活動的自動重裝載寄存器周期的值 計數到5000為500msTIM_TimeBaseStructure.TIM_Prescaler =psc; //設置用來作為TIMx時鐘頻率除數的預分頻值 10Khz的計數頻率 TIM_TimeBaseStructure.TIM_ClockDivision = 0; //設置時鐘分割:TDTS = Tck_timTIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上計數模式TIM_TimeBaseInit(TIM6, &TIM_TimeBaseStructure); //根據TIM_TimeBaseInitStruct中指定的參數初始化TIMx的時間基數單位TIM_ITConfig( TIM6,TIM_IT_Update|TIM_IT_Trigger,ENABLE);//使能定時器6更新觸發中斷TIM_Cmd(TIM6, ENABLE); //使能TIMx外設NVIC_InitStructure.NVIC_IRQChannel = TIM6_IRQn; //TIM3中斷NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //先占優先級0級NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //從優先級3級NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能NVIC_Init(&NVIC_InitStructure); //根據NVIC_InitStruct中指定的參數初始化外設NVIC寄存器 }定時器的定時長度取決于這個宏定義?
#ifndef __CLOCK_ARCH_H__ #define __CLOCK_ARCH_H__typedef int clock_time_t; #define CLOCK_CONF_SECOND 100#endif /* __CLOCK_ARCH_H__ */上面是100,也就是說定時器的長度應該是10MS
接下來是配置回調函數
//uip事件處理函數 //必須將該函數插入用戶主循環,循環調用. void uip_polling(void) {u8 i;static struct timer periodic_timer, arp_timer;static u8 timer_ok=0; if(timer_ok==0)//僅初始化一次{timer_ok = 1;timer_set(&periodic_timer,CLOCK_SECOND/2); //創建1個0.5秒的定時器 timer_set(&arp_timer,CLOCK_SECOND*10); //創建1個10秒的定時器 }uip_len=tapdev_read(); //從網絡設備讀取一個IP包,得到數據長度.uip_len在uip.c中定義if(uip_len>0) //有數據{ //處理IP數據包(只有校驗通過的IP包才會被接收) if(BUF->type == htons(UIP_ETHTYPE_IP))//是否是IP包? {uip_arp_ipin(); //去除以太網頭結構,更新ARP表uip_input(); //IP包處理//當上面的函數執行后,如果需要發送數據,則全局變量 uip_len > 0//需要發送的數據在uip_buf, 長度是uip_len (這是2個全局變量) if(uip_len>0)//需要回應數據{uip_arp_out();//加以太網頭結構,在主動連接時可能要構造ARP請求tapdev_send();//發送數據到以太網}}else if (BUF->type==htons(UIP_ETHTYPE_ARP))//處理arp報文,是否是ARP請求包?{uip_arp_arpin();//當上面的函數執行后,如果需要發送數據,則全局變量uip_len>0//需要發送的數據在uip_buf, 長度是uip_len(這是2個全局變量)if(uip_len>0)tapdev_send();//需要發送數據,則通過tapdev_send發送 }}else if(timer_expired(&periodic_timer)) //0.5秒定時器超時{timer_reset(&periodic_timer); //復位0.5秒定時器 //輪流處理每個TCP連接, UIP_CONNS缺省是40個 for(i=0;i<UIP_CONNS;i++){uip_periodic(i); //處理TCP通信事件 //當上面的函數執行后,如果需要發送數據,則全局變量uip_len>0//需要發送的數據在uip_buf, 長度是uip_len (這是2個全局變量)if(uip_len>0){uip_arp_out();//加以太網頭結構,在主動連接時可能要構造ARP請求tapdev_send();//發送數據到以太網}} #if UIP_UDP //UIP_UDP //輪流處理每個UDP連接, UIP_UDP_CONNS缺省是10個for(i=0;i<UIP_UDP_CONNS;i++){uip_udp_periodic(i); //處理UDP通信事件//當上面的函數執行后,如果需要發送數據,則全局變量uip_len>0//需要發送的數據在uip_buf, 長度是uip_len (這是2個全局變量)if(uip_len > 0){uip_arp_out();//加以太網頭結構,在主動連接時可能要構造ARP請求tapdev_send();//發送數據到以太網}} #endif //每隔10秒調用1次ARP定時器函數 用于定期ARP處理,ARP表10秒更新一次,舊的條目會被拋棄if(timer_expired(&arp_timer)){timer_reset(&arp_timer);uip_arp_timer();}} }這個函數是uip的靈魂,可以說全部的功能都是在這個函數里面實現的,然后定義網卡數據回調函數
//通信程序狀態字(用戶可以自己定義) enum {STATE_CMD = 0, //命令接收狀態 STATE_TX_TEST = 1, //連續發送數據包狀態(速度測試) STATE_RX_TEST = 2 //連續接收數據包狀態(速度測試) }; //定義 uip_tcp_appstate_t 數據類型,用戶可以添加應用程序需要用到 //成員變量。不要更改結構體類型的名字,因為這個類型名會被uip引用。 //uip.h 中定義的 struct uip_conn 結構體中引用了 uip_tcp_appstate_t struct tcp_appstate {u8_t state;u8_t *textptr;int textlen; }; struct uip_appstate {u8_t state;u8_t *textptr;int textlen; }; typedef struct tcp_appstate uip_tcp_appstate_t; typedef struct uip_appstate uip_udp_appstate_t;//TCP的回調 void tcp_appcall(void); void tcp_client_appcall(void); //tcp客戶端的回調,PC是服務器 void tcp_server_appcall(void); //tcp服務器的回調,pc是客戶端//UDP的回調 void udp_appcall(void); void udp_send_appcall(void); void udp_recv_appcall(void);//定義應用程序回調函數 #ifndef UIP_APPCALL #define UIP_APPCALL tcp_appcall //定義回調函數為 tcp_demo_appcall #endif#ifndef UIP_UDP_APPCALL #define UIP_UDP_APPCALL udp_appcall //定義回調函數為 udp_demo_appcall #endifUIP_UDP_APPCALL和UIP_APPCALL分別是TCP通訊和udp通訊的回調函數,實現的架構如下
//TCP應用接口函數(UIP_APPCALL) //完成TCP服務(包括server和client)和HTTP服務 void tcp_appcall(void) { switch(uip_conn->lport)//本地監聽端口對應的事件處理程序 {case HTONS(80): // httpd_appcall(); break;case HTONS(1200):tcp_server_appcall(); break;default: break;} switch(uip_conn->rport) //遠程連接1400端口{case HTONS(1400): //遠程連接端口號tcp_client_appcall();break;default: break;} }void udp_appcall(void) {switch(uip_udp_conn->lport)//本地監聽端口1600 {case HTONS(1600):udp_recv_appcall(); break;default: break;} switch(uip_udp_conn->rport) //遠程連接1500端口,也就是數據發送端{case HTONS(1500):udp_send_appcall();break;default: break;} }可以看到,處理過程是分端口處理的,分別是四個,TCP客戶端,服務器,UDP客戶端,UDP服務器,分別說明
tcp_client_connect(); //嘗試連接到TCP Server端,用于TCP Client u8 tcp_client_databuf[200]; //發送數據緩存u8 tcp_client_sta; //客戶端狀態 //[7]:0,無連接;1,已經連接; //[6]:0,無數據;1,收到客戶端數據 //[5]:0,無數據;1,有數據需要發送//這是一個TCP 客戶端應用回調函數。 //該函數通過UIP_APPCALL(tcp_demo_appcall)調用,實現Web Client的功能. //當uip事件發生時,UIP_APPCALL函數會被調用,根據所屬端口(1400),確定是否執行該函數。 //例如 : 當一個TCP連接被創建時、有新的數據到達、數據已經被應答、數據需要重發等事件 void tcp_client_appcall(void) { struct tcp_appstate *s = (struct tcp_appstate *)&uip_conn->appstate;if(uip_aborted())tcp_client_aborted(); //連接終止 if(uip_timedout())tcp_client_timedout(); //連接超時 if(uip_closed())tcp_client_closed(); //連接關閉 if(uip_connected())tcp_client_connected(); //連接成功 if(uip_acked())tcp_client_acked(); //發送的數據成功送達 //接收到一個新的TCP數據包 if (uip_newdata()){if((tcp_client_sta&(1<<6))==0)//還未收到數據{if(uip_len>199){ ((u8*)uip_appdata)[199]=0;} strcpy((char*)tcp_client_databuf,uip_appdata); tcp_client_sta|=1<<6;//表示收到客戶端數據} }else if(tcp_client_sta&(1<<5))//有數據需要發送{s->textptr=tcp_client_databuf;s->textlen=strlen((const char*)tcp_client_databuf);tcp_client_sta&=~(1<<5);//清除標記} //當需要重發、新數據到達、數據包送達、連接建立時,通知uip發送數據 if(uip_rexmit()||uip_newdata()||uip_acked()||uip_connected()||uip_poll()){tcp_client_senddata();} }//這里我們假定Server端的IP地址為:192.168.1.101 //這個IP必須根據Server端的IP修改. //嘗試重新連接 void tcp_client_connect() {uip_ipaddr_t ipaddr;uip_ipaddr(&ipaddr,192,168,1,100); //設置IP為192.168.1.103uip_connect(&ipaddr,htons(1400)); //端口為1400 }//終止連接,回調函數 void tcp_client_aborted(void) {tcp_client_sta&=~(1<<7); //標志沒有連接tcp_client_connect(); //嘗試重新連接uip_log("tcp_client aborted!\r\n");//打印log }//連接超時,回調函數 void tcp_client_timedout(void) {tcp_client_sta&=~(1<<7); //標志沒有連接 uip_log("tcp_client timeout!\r\n");//打印log }//連接關閉,回調函數 void tcp_client_closed(void) {tcp_client_sta&=~(1<<7); //標志沒有連接tcp_client_connect(); //嘗試重新連接uip_log("tcp_client closed!\r\n");//打印log } //連接建立,回調函數 void tcp_client_connected(void) { tcp_client_sta|=1<<7; //標志連接成功uip_log("tcp_client connected!\r\n");//打印log }//發送的數據成功送達 void tcp_client_acked(void) { struct tcp_appstate *s=(struct tcp_appstate *)&uip_conn->appstate;s->textlen=0;//發送清零uip_log("tcp_client acked!\r\n");//表示成功發送 }//發送數據給服務端 void tcp_client_senddata(void) {struct tcp_appstate *s = (struct tcp_appstate *)&uip_conn->appstate;//s->textptr:發送的數據包緩沖區指針//s->textlen:數據包的大小(單位字節) if(s->textlen>0)uip_send(s->textptr, s->textlen);//發送TCP數據包 }TCP客戶端的使用如上,服務器的使用如下
uip_listen(HTONS(1200)); //監聽1200端口,用于TCP Server監聽端口,自然就是服務器了,回調如下
u8 tcp_server_databuf[200]; //發送數據緩存 u8 tcp_server_sta; //服務端狀態 //[7]:0,無連接;1,已經連接; //[6]:0,無數據;1,收到客戶端數據 //[5]:0,無數據;1,有數據需要發送//這是一個TCP 服務器應用回調函數。 //該函數通過UIP_APPCALL(tcp_demo_appcall)調用,實現Web Server的功能. //當uip事件發生時,UIP_APPCALL函數會被調用,根據所屬端口(1200),確定是否執行該函數。 //例如 : 當一個TCP連接被創建時、有新的數據到達、數據已經被應答、數據需要重發等事件 void tcp_server_appcall(void) {struct tcp_appstate *s = (struct tcp_appstate *)&uip_conn->appstate;if(uip_aborted())tcp_server_aborted(); //連接終止if(uip_timedout())tcp_server_timedout(); //連接超時 if(uip_closed())tcp_server_closed(); //連接關閉 if(uip_connected())tcp_server_connected(); //連接成功 if(uip_acked())tcp_server_acked(); //發送的數據成功送達 //接收到一個新的TCP數據包 if (uip_newdata())//收到客戶端發過來的數據{if((tcp_server_sta&(1<<6))==0)//還未收到數據{if(uip_len>199){ ((u8*)uip_appdata)[199]=0;} strcpy((char*)tcp_server_databuf,uip_appdata); tcp_server_sta|=1<<6;//表示收到客戶端數據}}else if(tcp_server_sta&(1<<5))//有數據需要發送{s->textptr=tcp_server_databuf;s->textlen=strlen((const char*)tcp_server_databuf);tcp_server_sta&=~(1<<5);//清除標記} //當需要重發、新數據到達、數據包送達、連接建立時,通知uip發送數據 if(uip_rexmit()||uip_newdata()||uip_acked()||uip_connected()||uip_poll()){tcp_server_senddata();} } //終止連接 void tcp_server_aborted(void) {tcp_server_sta&=~(1<<7); //標志沒有連接uip_log("tcp_server aborted!\r\n");//打印log }//連接超時 void tcp_server_timedout(void) {tcp_server_sta&=~(1<<7); //標志沒有連接uip_log("tcp_server timeout!\r\n");//打印log }//連接關閉 void tcp_server_closed(void) {tcp_server_sta&=~(1<<7); //標志沒有連接uip_log("tcp_server closed!\r\n");//打印log }//連接建立 void tcp_server_connected(void) { // struct tcp_appstate *s = (struct tcp_appstate *)&uip_conn->appstate;//uip_conn結構體有一個"appstate"字段指向應用程序自定義的結構體。//聲明一個s指針,是為了便于使用。//不需要再單獨為每個uip_conn分配內存,這個已經在uip中分配好了。//在uip.c 中 的相關代碼如下:// struct uip_conn *uip_conn;// struct uip_conn uip_conns[UIP_CONNS]; //UIP_CONNS缺省=10//定義了1個連接的數組,支持同時創建幾個連接。//uip_conn是一個全局的指針,指向當前的tcp或udp連接。tcp_server_sta|=1<<7; //標志連接成功uip_log("tcp_server connected!\r\n");//打印log } //發送的數據成功送達 void tcp_server_acked(void) { struct tcp_appstate *s=(struct tcp_appstate *)&uip_conn->appstate;s->textlen=0;//發送清零uip_log("tcp_server acked!\r\n");//表示成功發送 }//發送數據給客戶端 void tcp_server_senddata(void) {struct tcp_appstate *s = (struct tcp_appstate *)&uip_conn->appstate;//s->textptr : 發送的數據包緩沖區指針//s->textlen :數據包的大小(單位字節) if(s->textlen>0)uip_send(s->textptr, s->textlen);//發送TCP數據包 }到此,TCP結束,另外,在UIP的初始化的時候要指明IP地址網關子網掩碼子類的,如下
//配置IP地址uip_ipaddr(ipaddr, 192,168,1,103); //設置本地設置IP地址uip_sethostaddr(ipaddr); uip_ipaddr(ipaddr, 192,168,1,1); //設置網關IP地址(其實就是你路由器的IP地址)uip_setdraddr(ipaddr); uip_ipaddr(ipaddr, 255,255,255,0); //設置網絡掩碼uip_setnetmask(ipaddr);而UDP的通訊是無連接的,不分客戶端和服務器,只分為接收端和發送端,接收端如下
u8 udp_recv_databuf[200]; //發送數據緩存 u8 udp_recv_sta; //客戶端狀態void udp_recv_appcall(void) { // struct uip_appstate *s = (struct uip_appstate *)&uip_udp_conn->appstate;//接收到一個新的udp數據包 if (uip_newdata())//收到客戶端發過來的數據{if((udp_recv_sta&(1<<6))==0)//還未收到數據{if(uip_len>199){ ((u8*)uip_appdata)[199]=0;} strcpy((char*)udp_recv_databuf,uip_appdata); udp_recv_sta|=1<<6;//表示收到客戶端數據}}if(uip_poll())//udp空轉{uip_log("udp_server uip_poll!\r\n");//打印log } }//建立UDP接收鏈接 //建立UDP服務器需要將目標IP設置為全1 并對應端口為0,綁定相應的數據端口 void udp_recv_connect(void) {uip_ipaddr_t ipaddr;static struct uip_udp_conn *c=0; uip_ipaddr(&ipaddr,0xff,0xff,0xff,0xff); //將遠程IP設置為 255.255.255.255 具體原理見uip.c的源碼if(c!=0) //已經建立連接則刪除連接{ uip_udp_remove(c);}c = uip_udp_new(&ipaddr,0); //遠程端口為0if(c){uip_udp_bind(c, HTONS(1600));} }其回調函數不發送數據,只接收數據,發送端如下
u8 udp_send_databuf[200]; //發送數據緩存 u8 udp_send_sta; //發送端狀態//這是一個udp 發送端應用回調函數。 //該函數通過UIP_APPCALL(udp_demo_appcall)調用,實現Web Client的功能. //當uip事件發生時,UIP_APPCALL函數會被調用,根據所屬端口(1400),確定是否執行該函數。 //例如 : 當一個udp連接被創建時、有新的數據到達、數據已經被應答、數據需要重發等事件 void udp_send_appcall(void) { struct uip_appstate *s = (struct uip_appstate *)&uip_udp_conn->appstate;if(uip_poll())//當前連接空閑輪訓{ uip_log("udp_send uip_poll!\r\n");//打印logif(udp_send_sta&(1<<5))//需要發送數據{ s->textptr=udp_send_databuf;s->textlen=strlen((const char*)udp_send_databuf);udp_send_sta&=~(1<<5);//清除標記uip_send(s->textptr, s->textlen);//發送udp數據包 uip_udp_send(s->textlen);}}}//建立一個udp_client的連接 void udp_send_connect() {uip_ipaddr_t ipaddr;static struct uip_udp_conn *c=0; uip_ipaddr(&ipaddr,192,168,1,101); //設置IP為192.168.1.101if(c!=0){ //已經建立連接則刪除連接uip_udp_remove(c);}c = uip_udp_new(&ipaddr,htons(1500)); //端口為1500//發送端發送的數據端口為1500 }只發送數據不接收數據
基本上到這里整個程序的框架就做好了,測試用的一段代碼也貼上來
uip_polling(); //處理uip事件,必須插入到用戶程序的循環體中if(tcp_client_tsta!=tcp_client_sta)//TCP Client狀態改變{ if(tcp_client_sta&(1<<7))LCD_ShowString(0,12,240,320,(u8*)"TCP Client Connected ",LCD_BLACK);elseLCD_ShowString(0,12,240,320,(u8*)"TCP Client Disconnected ",LCD_BLACK);if(tcp_client_sta&(1<<6)) //收到新數據{LCD_ShowString(18,24,240,320,(u8*)" ",LCD_BLACK);LCD_ShowString(18,24,240,320,(u8*)tcp_client_databuf,LCD_BLACK);printf("TCP Client RX:%s\r\n",tcp_client_databuf);//打印數據tcp_client_sta&=~(1<<6); //標記數據已經被處理 }tcp_client_tsta=tcp_client_sta;}if(tcp_server_tsta!=tcp_server_sta)//TCP Server狀態改變{ if(tcp_server_sta&(1<<7))LCD_ShowString(0,48,240,320,(u8*)"TCP Server Connected ",LCD_BLACK);else LCD_ShowString(0,48,240,320,(u8*)"TCP Server Disconnected ",LCD_BLACK);if(tcp_server_sta&(1<<6)) //收到新數據{LCD_ShowString(18,60,240,320,(u8*)" ",LCD_BLACK);LCD_ShowString(18,60,240,320,(u8*)tcp_server_databuf,LCD_BLACK);printf("TCP Server RX:%s\r\n",tcp_server_databuf);//打印數據tcp_server_sta&=~(1<<6); //標記數據已經被處理 }tcp_server_tsta=tcp_server_sta;}if(udp_recv_sta & (1<<6)){LCD_ShowString(18,120,240,320,(u8*)" ",LCD_BLACK);LCD_ShowString(18,120,240,320,(u8*)udp_recv_databuf,LCD_BLACK);udp_recv_sta =~(1<<6);//標記數據已經被處理}if(keyValue == KEY_LEFT){if(tcp_client_sta&(1<<7)) //連接還存在{sprintf((char*)tcp_client_databuf,"TCP Client OK %d\r\n",tclientcnt); LCD_ShowString(24,36,240,320,(u8*)" ",LCD_BLACK);LCD_ShowString(24,36,240,320,(u8*)tcp_client_databuf,LCD_BLACK);tcp_client_sta|=1<<5;//標記有數據需要發送tclientcnt++;keyValue = 0;}}if(keyValue == KEY_RIGHT){if(tcp_server_sta&(1<<7)) //連接還存在{sprintf((char*)tcp_server_databuf,"TCP Server OK %d\r\n",tserivcecnt); LCD_ShowString(24,72,240,320,(u8*)" ",LCD_BLACK);LCD_ShowString(24,72,240,320,(u8*)tcp_server_databuf,LCD_BLACK);tcp_server_sta|=1<<5;//標記有數據需要發送tserivcecnt++;keyValue = 0;}}if(keyValue == KEY_DOWN){sprintf((char*)udp_send_databuf,"UDP SEND OK %d\r\n",usendcnt);LCD_ShowString(18,96,240,320,(u8*)" ",LCD_BLACK);LCD_ShowString(18,96,240,320,(u8*)udp_send_databuf,LCD_BLACK);udp_send_sta |= 1<<5;//標記有數據需要發送keyValue = 0;usendcnt++;}}工程下載地址
http://download.csdn.net/detail/dengrengong/8542905總結
以上是生活随笔為你收集整理的单片机联网,UIP实现tcp/udp协议的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C++用顶层函数重载操作符
- 下一篇: 第1章 Qt概述和下载安装及创建工程