開發環境:
硬體闆卡: STM32F427ZITx
網卡:DP83848
LWIP版本:V3.2
最近要使用LWIP協定實作多點傳播,由于此前并不知道這個闆卡,對嵌入式也不算熟悉,平時還有其他的任務,前前後後忙了近兩個月,後來發現實作過程非常簡單,下面介紹我的實作過程。
首先,使用STM32F4時不用完全自己搭建環境的,到官網下載下傳類似于stsw-stm32070.zip的檔案包,裡面包含了多個典型的Keil uVision工程,配置一下就能用。這個過程改天再寫個文檔。
在Kei工程中配置多點傳播的過程可以分為三步:配置LWIP,啟動多點傳播;編寫多點傳播實作代碼;調用。
Step1.首先配置LWIP,啟動多點傳播功能
修改lwipopts.h 文
#define LWIP_IGMP 1
在lwip初始化後調用
2.配置多點傳播計時器
在 netconf.c檔案靠前的位置添加變量,大約在該檔案的第70行左右,代碼如下所示。
#ifdef LWIP_IGMP
uint32_t IGMPTimer=0;
#endif
然後在該檔案的#ifdef USE_DHCP前面,大約在第180行的位置添加igmp計時器調用代碼:
#if LWIP_IGMP
if(localtime-IGMPTimer>=IGMP_TMR_INTERVAL)
{
IGMPTimer=localtime;
igmp_tmr();
}
#endif
編譯,如果能正确編譯,并且在編譯過程中看到編譯igmp.c檔案,說明配置基本成功了。
Step2:編寫多點傳播實作代碼
添加兩個檔案,multicast.h和multicast.c檔案。廢話少說,先上代碼:
multicast.h檔案的代碼為:
#ifndef __MULTICAST__H
#define __MULTICAST__H
#include "stm324xg_eval_sdio_sd.h"
#include "lwip/udp.h"
#include "lwip/pbuf.h"
#include "lwip/igmp.h"
/*port */
#define UDP_MULTICASE_RECV_PORT 1178 // multicast port for recive
#define UDP_MULTICASE_SEND_PORT 1180 // multicast port for send
#define LWIP_DEMO_BUF 2048
#endif
其中 UDP_MULTICASE_RECV_PORT 1178 是闆子接收多點傳播的端口, UDP_MULTICASE_SEND_PORT 1180 是 闆子對外發送多點傳播的端口
multicast.c的代碼為:
#include "multicast.h"
struct udp_pcb* udp_server_multi_pcb;//多點傳播PCB控制塊
struct ip_addr ipgroup_rev,ipgroup_send;
u16 lwip_demo_buf_len = 0;
u8_t lwip_demo_buf[LWIP_DEMO_BUF];
//UDP發送
void multicast_send_data(unsigned char * data,unsigned short len)
{
struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT,len, PBUF_RAM);
memcpy(p->payload, data, len);
udp_sendto(udp_server_multi_pcb, p,(struct ip_addr *) (&ipgroup_send),UDP_MULTICASE_SEND_PORT);//1180
pbuf_free(p);
}
//多點傳播接收,回調函數
void udp_server_rev(void* arg,struct udp_pcb* upcb,struct pbuf* p,struct ip_addr*addr ,u16_t port){
int i,j;
if(p!=NULL){
if((p->tot_len)>=LWIP_DEMO_BUF){ //如果長度過長則額外處理
memcpy(lwip_demo_buf,p->payload,LWIP_DEMO_BUF);
lwip_demo_buf_len = LWIP_DEMO_BUF;
}else{
memcpy(lwip_demo_buf,p->payload,p->tot_len);
lwip_demo_buf_len = p->tot_len;
}
for(i=0;i<p->tot_len;i++)//測試多點傳播時,有時候即使沒發出去也可能顯示收到,是以,我這裡将收到的資料加2,以作區分
{
printf("%02x ",lwip_demo_buf[i]);
lwip_demo_buf[i]=lwip_demo_buf[i]+2;
}
printf("\n");
}
}
void Multicast_Config()
{
int i;
IP4_ADDR(&ipgroup_rev, 230,1,1,11);//用于接收多點傳播的位址
IP4_ADDR(&ipgroup_send, 230,12,2,22);//用于發送多點傳播的位址
igmp_joingroup(IP_ADDR_ANY,(struct ip_addr *)(&ipgroup_rev));//隻需要将接收位址放入igmp組,發送的不需要
udp_server_multi_pcb = udp_new();
if(udp_server_multi_pcb!=NULL){
udp_bind(udp_server_multi_pcb,IP_ADDR_ANY,UDP_MULTICASE_RECV_PORT);//多點傳播接收位址1178
udp_recv(udp_server_multi_pcb,udp_server_rev,NULL);//
}
}
//測試發送的方法
void UDP_Send()
{
multicast_send_data(lwip_demo_buf,lwip_demo_buf_len);
}
Step3:調用
在主函數main.c下添加#include "multicast.h"引用
并在main()函數中的while循環的前面添加多點傳播配置方法
Multicast_Config();
添加成功後闆子就能接收多點傳播了。
如果想測試發送,則直接調用UDP_Send()方法即可。我為了進行長時間測試,直接在stm32f4xx_it.c檔案的TIM2_IRQHandler中添加了調用,這樣可以使得闆子每隔一秒就發送一次。該函數的代碼為:
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
if(flash==1)
{
STM_EVAL_LEDOff(LED1);
flash=0;
}
else
{
STM_EVAL_LEDOn(LED1);
flash=1;
}
UDP_Send();
}
}
解釋
接收的持續監聽是如何進行
上述代碼有兩點讓人不太明确,也是起初一直讓我迷糊的地方:1多點傳播到底是如何接收的?2如何實作收發同步。 在《LwIP協定深度剖析與實戰演練》一書的16.3給了一個多點傳播的例子。代碼如下。如果了解進階程式設計語言,這個例子非常易懂,但是其前提是必須配置好一個作業系統,目前常用的是FreeOST和uosII,但是在闆子上配置這個系統要做很多工作,我曾花了近半個月來配置,後來發現根本不需要這麼做。
void lwip_demo(void *pdata)
{
struct netconn *conn;
struct ip_addr local_addr,group_addr,remote_addr;
lwip_init_task();
EnableMacInt();
IP4_ADDR(&local_addr,192,168,1,37);
IP4_ADDR(&group_addr,233,0,0,6);
IP4_ADDR(&remote_addr,192,168,1,78);
conn=netconn_new(NETCONN_UDP);
netconn_bind(conn,NULL,9090);
netconn_join_leave_group(conn,&group_addr,&local_addr,NETCONN_JOIN);
while(1)
{
struct netbuf *inbuf;
inbuf = netconn_recv(conn);
if(inbuf!=NULL)
{
netconn_sendto(conn,inbuf,&remote_addr,8080);
netbuf_delete(inbuf);
}
}
netconn_delete(conn);
}
在無作業系統的環境下使用多點傳播的關鍵在于前面我們提到的 添加 igmp_tmr()的調用,它能保證闆子可以在一定的周期内監聽網絡上發來的多點傳播流。如果你對嵌入式程式設計很熟悉,這一點不難了解,如果像我一樣習慣了進階程式設計語言的風格,會一直想不明白。
如何實作收發同步進行
上面解決了接收的問題,那如何發送呢?并且如何保證收發能同步進行呢?其實發送更為簡單,隻要你配置好UDP環境,将單點傳播位址換成多點傳播位址就能發送,這裡的關鍵在于Multicast_Config()中的配置。
開始我一直在想是否需要寫兩個配置函數,一個同僚堅持認為應該建立兩個pcb,一個用于收,一個用于發,但是測試發現根本不需要,隻要為pcb添加兩個多點傳播位址就行了。 IP4_ADDR(&ipgroup_rev, 230,1,1,11);用來配置接收多點傳播的位址,為了能收到資料,還需要将其添加到igmp_joingroup組中。然後将其與udp_server_multi_pcb綁定,最後在回調函數中執行。這幾個函數包涵了大量的底層操作,感興趣的話可以看看LWIP的源代碼。 IP4_ADDR(&ipgroup_send, 230,12,2,22)用來配置發送多點傳播的位址,multicast_send_data()用來發送,具體請參考代碼。
測試
我的測試如下圖所示:左側是我發送資料,右側為接收,我的設定是接收一次後每隔一秒發送一次,是以可以看到大量的接收資訊。
完善
上述代碼還有一些不足的,一個是一次不能收發比較大的資料,測試發現,一次收發1KB的位元組都不行,這個我調試完成後再補充。另一個是缺少業務控制邏輯,其實這個已經很簡單了,隻要添加一個bool變量,接收到新資料是置為1,發送了之後置為0即可靈活地控制資料收發,進而封裝到你的業務邏輯中。
感謝
雖然STM32用的比較多,LWIP也用的比較多,但是如何實作多點傳播,網絡的材料比較雜亂,含金量實在很低,梳理出上面的内容花了我很長的時間。不過,在此特别感謝lutao614http://bbs.csdn.net/topics/391894501,我曾兩次和他交流,他為了明确方向給了很大的幫助,并且無私地将自己的代碼給我看,特此表示感謝。