开发环境:
硬件板卡: 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"
-
-
-
#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;
-
struct ip_addr ipgroup_rev,ipgroup_send;
-
-
u16 lwip_demo_buf_len = 0;
-
u8_t lwip_demo_buf[LWIP_DEMO_BUF];
-
-
-
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);
-
-
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;itot_len;i++)
-
{
-
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));
-
-
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);
-
-
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也用的比较多,但是如何实现组播,网络的材料比较杂乱,含金量实在很低,梳理出上面的内容花了我很长的时间。不过,在此特别感谢,我曾两次和他交流,他为了明确方向给了很大的帮助,并且无私地将自己的代码给我看,特此表示感谢。
//代码实现
-
#include "includes.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);
-
-
}
-
unsigned char DateRlease[15]="20170224121212";
-
int ReadSysInfo(void)
-
{
-
//Get
-
int i;
-
-
//1.Get UUID
-
for(i=0;i<4;i++)
-
lwip_demo_buf[2+i] = g_ucUUID[i];
-
//Return cmd
-
lwip_demo_buf[6]= 0x32;
-
-
//IndexSum =
-
lwip_demo_buf[7]= 8;
-
-
//IP地址
-
lwip_demo_buf[8]= 1; //IP
-
InFlashReadBytes(FLASH_ADDR_IP,4,&lwip_demo_buf[9]);
-
-
//Mask
-
lwip_demo_buf[13]= 2; //IP
-
InFlashReadBytes(FLASH_ADDR_IP+8,4,&lwip_demo_buf[14]);
-
-
//Host
-
lwip_demo_buf[18] = 3;
-
InFlashReadBytes(FLASH_ADDR_IP+4,4,&lwip_demo_buf[19]);
-
-
-
//TCP PORT
-
lwip_demo_buf[23] = 3;
-
InFlashReadBytes(FLASH_ADDR_PORT,2,&lwip_demo_buf[24]);
-
-
//DevType
-
lwip_demo_buf[28] = 14;
-
lwip_demo_buf[29] = 1;
-
-
//DevStype
-
lwip_demo_buf[30] = 15;
-
lwip_demo_buf[31] = 0;
-
-
//DevVer
-
lwip_demo_buf[30] = 16;
-
lwip_demo_buf[31] = 0x01;
-
lwip_demo_buf[32] = 0x02;
-
-
//Date.
-
lwip_demo_buf[30] = 17;
-
for(i=0;i<14;i++)
-
lwip_demo_buf[31+i] = DateRlease[i];
-
lwip_demo_buf[45] = 0x55;
-
lwip_demo_buf[46] = 0x55;
-
-
lwip_demo_buf_len = 47;
-
return 47;
-
}
-
-
int SetSysInfo(void)
-
{
-
int i=0,j=0;
-
int Cnt=0;
-
if(lwip_demo_buf[7]>0)
-
{
-
Cnt = 0;
-
for(i=0;i<lwip_demo_buf[7];i++)
-
{
-
Cnt+=1; //索引值大小
-
switch(lwip_demo_buf[7+Cnt])
-
{
-
case 1:
-
InFlashWriteByte(FLASH_ADDR_IP,4,&lwip_demo_buf[8+Cnt]);
-
Cnt+=4;
-
break;
-
case 2:
-
InFlashWriteByte(FLASH_ADDR_IP+8,4,&lwip_demo_buf[8+Cnt]);
-
Cnt+=4;
-
break;
-
case 3:
-
InFlashWriteByte(FLASH_ADDR_IP+4,4,&lwip_demo_buf[8+Cnt]);
-
Cnt+=4;
-
break;
-
case 4:
-
InFlashWriteByte(FLASH_ADDR_PORT,2,&lwip_demo_buf[8+Cnt]);
-
Cnt+=2;
-
break;
-
}
-
}
-
}
-
//1.Get UUID
-
//for(i=0;i<4;i++)
-
// lwip_demo_buf[2+i] = g_ucUUID[i];
-
//Return cmd
-
lwip_demo_buf[6]= 0x34;
-
lwip_demo_buf[7]= 0x00;
-
lwip_demo_buf[8] = 0x55;
-
lwip_demo_buf[9] = 0x55;
-
lwip_demo_buf_len = 10;
-
return 10;
-
}
-
-
//组播接收,回调函数
-
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;
-
}
-
-
if(lwip_demo_buf_len>8)
-
{
-
if(lwip_demo_buf[0]==0xAA && lwip_demo_buf[1]==0xAA)
-
{
-
if(lwip_demo_buf[6]==0x31) //GetCmd
-
{
-
ReadSysInfo();
-
multicast_send_data(lwip_demo_buf,lwip_demo_buf_len);
-
}
-
else if(lwip_demo_buf[6]==0x33) //Set Cmd
-
{
-
for(i=0;i<4;i++)
-
if(lwip_demo_buf[2+i]!=g_ucUUID[i])
-
break;
-
if(i==4)
-
{
-
SetSysInfo();
-
multicast_send_data(lwip_demo_buf,lwip_demo_buf_len);
-
}
-
}
-
else if(lwip_demo_buf[6]==0x35) //reboot device
-
{
-
for(i=0;i<4;i++)
-
if(lwip_demo_buf[2+i]!=g_ucUUID[i])
-
break;
-
if(i==4)
-
{
-
NVIC_SystemReset();
-
while(1);
-
}
-
}
-
}
-
}
-
-
-
-
// printf("\n");
-
-
}
-
}
-
-
void Multicast_Config(void)
-
{
-
int i;
-
IP4_ADDR(&ipgroup_rev, 230,1,11,111);//用于接收组播的地址
-
IP4_ADDR(&ipgroup_send, 230,2,22,222);//用于发送组播的地址
-
-
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);
-
//udp_bind(udp_server_multi_pcb,(struct ip_addr *)&ipgroup_rev,UDP_MULTICASE_RECV_PORT);//组播接收地址1178
-
// udp_connect(udp_server_multi_pcb,&ipgroup_send , UDP_MULTICASE_SEND_PORT);
-
udp_recv(udp_server_multi_pcb,udp_server_rev,NULL);//
-
}
-
}
-
-
-
//测试发送的方法
-
void UDP_Send(void)
-
{
-
int i;
-
for(i=0;i<20;i++)
-
lwip_demo_buf[i] = 0x30 +i;
-
lwip_demo_buf_len = 20;
-
multicast_send_data(lwip_demo_buf,lwip_demo_buf_len);
-
}
阅读(6407) | 评论(0) | 转发(0) |