[LWIP的RAW API UDP通信详解(stm32f103---enc28j60)](https://blog.csdn.net/weixin_42149196/article/details/90246815)
[项目整体托管到github上](https://github.com/WyxLOVES/LWIP-RAW-API-UDP-stm32-f103----enc28j60-)
# 实现任务
通过接收到上位机端发送来的数据来实现控制开发板做相应的操作。
# 代码实现
### 首先看看几个主要的结构体
//发送数据包
~~~c
struct sardata
{
u8 head[4]; //标志头
u16 length; //长度
u8 humi[2]; //温湿度传感器状态
u8 data[4]; //温度湿度数据
u8 elengine[2];//电机及状态
u8 led[3]; //led灯
};
//初始化上述结构体函数,用来发送数据包到上位机
void initsardata(void)
{
u8 i;
sendrecv.head[0]=0x5A;//Z
sendrecv.head[1]=0x4E;//N
sendrecv.head[2]=0x4A;//J
sendrecv.head[3]=0x4A;//J 相当于前缀
sendrecv.length=15;
for(i=0;i<8;i++)
{
sendrecv.data[i]=0x00;
}
sendrecv.humi[0]=0x00;//传感器0状态
sendrecv.humi[1]=0x00;//传感器1状态
sendrecv.elengine[0]=0x00;//电机0状态
sendrecv.elengine[1]=0x00;//电机1状态
sendrecv.led[0]=0x00;//led0状态
sendrecv.led[1]=0x00;//led1状态
sendrecv.led[2]=0x00;//led2状态
}
~~~
数据包是根据我们和服务器端所定的协议来构建的。如下图

udp_pcb udp协议控制块(用来装一些双方连接信息)
~~~ c
struct udp_pcb {
/* Common members of all PCB types */
IP_PCB;
/* 指向下一个PCB */
struct udp_pcb *next;
u8_t flags;
/** 端口 本地和远端定义的端口号*/
u16_t local_port, remote_port;
#if LWIP_IGMP
/** outgoing network interface for multicast packets */
ip_addr_t multicast_ip;
#endif /* LWIP_IGMP */
#if LWIP_UDPLITE
/** used for UDP_LITE only */
u16_t chksum_len_rx, chksum_len_tx;
#endif /* LWIP_UDPLITE */
/** 接受回调函数*/
udp_recv_fn recv;
/** user-supplied argument for the recv callback 用户提供的回调函数 */
void *recv_arg;
};
~~~
- ip_addr 32位的IP地址
- 设置远端IP地址
~~~c
/* This is the aligned version of ip_addr_t,
used as local variable, on the stack, etc. */
struct ip_addr {
u32_t addr;
};
/*此函数用来设置远端IP地址
*/
void udp_demo_set_remoteip(void)
{
u8 *tbuf;
u16 xoff;
u8 key;
POINT_COLOR=RED;
tbuf=mymalloc(SRAMIN,100); //申请内存
if(tbuf==NULL)return;
//前三个IP保持和DHCP得到的IP一致 同一网段
lwipdev.remoteip[0]=lwipdev.ip[0];//192
lwipdev.remoteip[1]=lwipdev.ip[1];//168
lwipdev.remoteip[2]=lwipdev.ip[2];//1
lwipdev.remoteip[3]=(uint8_t)(0x01);//自己定义服务器端IP
sprintf((char*)tbuf,"Remote IP:%d.%d.%d.",lwipdev.remoteip[0],lwipdev.remoteip[1],lwipdev.remoteip[2]);//远端IP
POINT_COLOR=BLUE;
xoff=strlen((char*)tbuf)*8+30;
printf("KEY1:+ KEY0:-\r\n");
printf("KEY_UP:OK\r\n");
//此处使用可调节式的远端IP设置
while(1)
{
key=KEY_Scan(0);
if(key==WKUP_PRES)break;
else if(key)
{
if(key==KEY1_PRES)lwipdev.remoteip[3]++;//IP增加
if(key==KEY0_PRES)lwipdev.remoteip[3]--;//IP减少
printf("%d.%d.%d.%d\r\n",lwipdev.remoteip[0],lwipdev.remoteip[1],lwipdev.remoteip[2],lwipdev.remoteip[3]);//显示新IP
}
}
myfree(SRAMIN,tbuf);
}
~~~
1. 本地IP可从上述代码看出,本地IP采用DHCP动态分配的IP地址,如果分配不成功则使用默认设置IP,如下代码所示,还初始化了MAC、默认远端IP、默认子网掩码、默认网关。lwip控制结构体主要对上述结构体进行封装。
```c
//lwip控制结构体
typedef struct
{
u8 mac[6]; //MAC地址
u8 remoteip[4]; //远端主机IP地址
u8 ip[4]; //本机IP地址
u8 netmask[4]; //子网掩码
u8 gateway[4]; //默认网关的IP地址
vu8 dhcpstatus; //dhcp状态
//0,未获取DHCP地址;
//1,进入DHCP获取状态
//2,成功获取DHCP地址
//0XFF,获取失败.
}__lwip_dev;
//lwip 默认IP设置
//lwipx:lwip控制结构体指针
void lwip_comm_default_ip_set(__lwip_dev *lwipx)
{
//默认远端IP为:192.168.1.100
lwipx->remoteip[0]=192;
lwipx->remoteip[1]=168;
lwipx->remoteip[2]=1;
lwipx->remoteip[3]=100;
//MAC地址设置(高三字节固定为:2.0.0,低三字节用STM32唯一ID)
lwipx->mac[0]=enc28j60_dev.macaddr[0];
lwipx->mac[1]=enc28j60_dev.macaddr[1];
lwipx->mac[2]=enc28j60_dev.macaddr[2];
lwipx->mac[3]=enc28j60_dev.macaddr[3];
lwipx->mac[4]=enc28j60_dev.macaddr[4];
lwipx->mac[5]=enc28j60_dev.macaddr[5];
//默认本地IP为:192.168.1.30
lwipx->ip[0]=192;
lwipx->ip[1]=168;
lwipx->ip[2]=1;
lwipx->ip[3]=30;
//默认子网掩码:255.255.255.0
lwipx->netmask[0]=255;
lwipx->netmask[1]=255;
lwipx->netmask[2]=255;
lwipx->netmask[3]=0;
//默认网关:192.168.1.1
lwipx->gateway[0]=192;
lwipx->gateway[1]=168;
lwipx->gateway[2]=1;
lwipx->gateway[3]=1;
lwipx->dhcpstatus=0;//没有DHCP
}
```
## int main(void)
主函数我们主要看下lwip_comm_init();与udp_demo_test();这两个函数。
```c
extern u8 udp_demo_flag; //UDP 测试全局状态标记变量
int main(void)
{
u8 key;
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
uart_init(115200); //串口初始化为115200
KEY_Init(); //初始化按键
LED_Init(); //LED端口初始化
TIM3_Int_Init(1000,719); //定时器3频率为100hz //系统时钟 用来保持LWIP精准计时
MOTOR_PWM_Init(7199,0); //=====初始化PWM 10KHZ,用于驱动电机 如需初始化电调接口 改为MiniBalance_PWM_Init(9999,35) 200HZ
FSMC_SRAM_Init(); //初始化外部SRAM
my_mem_init(SRAMIN); //初始化内部内存池
my_mem_init(SRAMEX); //初始化外部内存池
while(lwip_comm_init()) //lwip初始化 TCP/IP协议栈
{
delay_ms(1200);
}
printf("LWIP Init Success!\r\n");
#if LWIP_DHCP //使用DHCP
while((lwipdev.dhcpstatus!=2)&&(lwipdev.dhcpstatus!=0XFF))//等待DHCP获取成功/超时溢出
{
lwip_periodic_handle(); //LWIP内核需要定时处理的函数
}
#endif
delay_ms(500); //延时1s
delay_ms(500);
udp_demo_test(); //UDP 模式 连接 连接成功就在此函数
//连接失败才会执行 下面while(1)用来再次连接上位机
while(1)
{
key = KEY_Scan(0);
if(key == KEY1_PRES) //按KEY1键建立连接
{
if((udp_demo_flag & 1<<5)) printf("UDP连接已经建立,不能重复连接\r\n"); //如果连接成功,不做任何处理
else udp_demo_test(); //当断开连接后,调用udp_demo_test()函数
}
delay_ms(10);
}
}
```
### u8 lwip_comm_init(void)
*此函数的执行流程*
1. 初始化enc28j60模块
2. 初始化lwip内核
3. 设置默认IP等信息
4. 如果使用DHCP,则开启DHCP服务并分配IP等信息
5. 添加网口
6. 设置网口为默认网口
7. 打开网口
```c
//LWIP初始化(LWIP启动的时候使用)
//返回值:0,成功
// 1,内存错误
// 2,DM9000初始化失败
// 3,网卡添加失败.
u8 lwip_comm_init(void)
{
struct netif *Netif_Init_Flag; //调用netif_add()函数时的返回值,用于判断网络初始化是否成功
struct ip_addr ipaddr; //ip地址
struct ip_addr netmask; //子网掩码
struct ip_addr gw; //默认网关
if(lwip_comm_mem_malloc())return 1; //内存申请失败
if(ENC28J60_Init())return 2; //初始化ENC28J60
lwip_init(); //初始化LWIP内核
lwip_comm_default_ip_set(&lwipdev); //设置默认IP等信息
#if LWIP_DHCP //使用动态IP
ipaddr.addr = 0;
netmask.addr = 0;
gw.addr = 0;
#else //使用静态IP
IP4_ADDR(&ipaddr,lwipdev.ip[0],lwipdev.ip[1],lwipdev.ip[2],lwipdev.ip[3]);
IP4_ADDR(&netmask,lwipdev.netmask[0],lwipdev.netmask[1] ,lwipdev.netmask[2],lwipdev.netmask[3]);
IP4_ADDR(&gw,lwipdev.gateway[0],lwipdev.g