1.LWIP介绍
lwip是瑞典计算机科学院网络嵌入式系统小组(SICS)的Adam Dunkels(亚当·邓克尔) 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持 TCP 协议主要功能的基础上减少对RAM的占用。
LwIP是Light Weight(轻型)IP 协议,有无操作系统的支持都可以运行。LwIP 实现的重点是在保持TCP协议 主要功能的基础上减少对RAM的占用,它只需十几KB的RAM和 40K左右的ROM就可以运行,这使LwIP协议栈适合在低端的嵌入式系统中使用。
STM32移植LWIP协议栈示例:https://blog.51cto.com/u_15688123/6156658
2.物联网平台介绍
物联网(Internet of Things,简称IoT)是指通过各种信息传感器、射频识别技术、全球定位系统、红外感应器、激光扫描器等各种装置与技术,实时采集任何需要监控、 连接、互动的物体或过程,采集其声、光、热、电、力学、化学、生物、位置等各种需要的信息,通过各类可能的网络接入,实现物与物、物与人的泛在连接,实现对物品和过程的智能化感知、识别和管理。物联网是一个基于互联网、传统电信网等的信息承载体,它让所有能够被独立寻址的普通物理对象形成互联互通的网络。
物联网平台是一个集成了设备管理、数据安全通信和消息订阅等能力的一体化平台。向下支持连接海量设备,采集设备数据上云;向上提供云端API,服务端可通过调用云端API将指令下发至设备端,实现远程控制。
3.硬件平台
硬件开发平台:STM32F103ZET6
网口驱动:DM9000
开发环境:KEIL5
3.1 DM9000简介
DM9000 是一款完全集成的、 性价比高、 引 脚数少、 带有通用处理器接口的 单芯片快速以太网控制器。 自 带一个 10/100M PHY 和 4K 双字的 SRAM , DM9000A 为适应各种处理器提供了 8 位、 16 位数据接口访问 内部存储器, DM9000 拥有自 动协商功能, DM9000 特性如下:
- 集成自 适应 10/100M 收发器。
- 内置 16k 字节的 SRAM。
- 支持硬件帧校验。
- 兼容 3.3V 和 5.0V 输入输出电压。
DM9000 有多种型号, 有 100 引 脚和 48 引 脚的, 开发板选择的是 48 引 脚的 DM9000,型号为 DM9000CEP。
引脚 |
说明 |
PWRST |
DM9000 复位信号 |
WR(IOW) |
处理器写命令 |
RD(IOR) |
处理器读命令 |
CMD |
命令/数据标志, 0,读写命令; 1,读写数据 |
CS |
DM9000 的片选信号 |
SD0~SD15 |
16 位双向数据线 |
3.2 DM9000硬件初始化
/******************DM9000初始化**************
**硬件接口:
** FSMC_D0~D1 -- PD14~PD15
** FSMC_D2~D3 -- PD0~PD1
** FSMC_D4~D12 -- PE7~PE15
** FSMC_D13~D14-- PD8~PD9
** FSMC_D15 -- PD10
** CS(FSMC_NE2) -- PG9(片选)
** WR(FSMC_NWE) -- PD5(写使能)
** RD(FSMC_NOE) -- PD4(读使能)
** DM9000_RST -- PD7(复位脚)
** CMD(FSMC_A7) -- PF13(数据命令选择脚)
** DM9000_INT --PG6(中断脚)
**
*********************************************/
void DM9000_GPIO_Init(void)
{
/*开时钟*/
RCC->APB2ENR|=1<<3;//PB
RCC->APB2ENR|=1<<5;//PD
RCC->APB2ENR|=1<<6;//PE
RCC->APB2ENR|=1<<7;//PF
RCC->APB2ENR|=1<<8;//PG
GPIOD->CRL&=0x0F00FF00;//PD5写使能,PD4读使能,PD7复位脚
GPIOD->CRL|=0x30BB00BB;
GPIOD->CRH&=0x00FFF000;
GPIOD->CRH|=0xBB000BBB;
GPIOE->CRL&=0x0FFFFFFF;
GPIOE->CRL|=0xB0000000;
GPIOE->CRH&=0x00000000;
GPIOE->CRH|=0xBBBBBBBB;
GPIOG->CRH&=0xFFFFFF0F;
GPIOG->CRH|=0x000000B0;//片选NE2
GPIOF->CRH&=0xFF0FFFFF;
GPIOF->CRH|=0x00B00000;//数据命令选择脚
GPIOG->CRL&=0xF0FFFFFF;
GPIOG->CRL|=0x08000000;//DM9000中断脚
GPIOG->ODR|=1<<6;//上拉
EXTI->IMR|=1<<6; //开放中断线6的中断请求
EXTI->FTSR|=1<<6; //允许中断线6下降沿触发请求
RCC->APB2ENR|=1<<0;//AFIO时钟使能
AFIO->EXTICR[1]&=~(0xF<<(2*4));//选择输入源为PG6
AFIO->EXTICR[1]|=0x6<<(2*4);//选择输入源为PG6
STM32_NVIC_SetPriority(EXTI9_5_IRQn,1,1);//设置优先级
/*FSMC核心寄存器配置*/
RCC->AHBENR|=1<<8;//FSMC
FSMC_Bank1->BTCR[2]=0;//BCR2
FSMC_Bank1->BTCR[3]=0;//BTR2
FSMC_Bank1->BTCR[2]&=~(0x1<<19);//异步模式
FSMC_Bank1->BTCR[2]&=~(0x1<<14);//读写使用相同时序
FSMC_Bank1->BTCR[2]|=1<<12;//允许写操作
FSMC_Bank1->BTCR[2]|=0x1<<4;//16位宽度
FSMC_Bank1->BTCR[3]|=0x1<<8;//2个时钟周期
FSMC_Bank1->BTCR[3]&=~(0xF<<4);//地址保持时间1个时钟周期
FSMC_Bank1->BTCR[3]&=~(0xF<<0);//地址保持时间1个时钟周周期
FSMC_Bank1->BTCR[2]|=1<<0;//启动存储器块
}
3.3 LWIP协议栈动态获取ip
IP协议是为计算机网络相互连接进行通信而设计的协议。在因特网中,它是能使连接到网上的所有计算机网络实现相互通信的一套规则,规定了计算机在因特网上进行通信时应当遵守的规则。任何厂家生产的计算机系统,只要遵守IP协议就可以与因特网互连互通。各个厂家生产的网络系统和设备,如以太网、分组交换网等,它们相互之间不能互通,不能互通的主要原因是因为它们所传送数据的基本单元(技术上称之为“帧”)的格式不同。IP协议实际上是一套由软件程序组成的协议软件,它把各种不同“帧”统一转换成“IP数据报”格式,这种转换是因特网的一个最重要的特点,使所有各种计算机都能在因特网上实现互通,即具有“开放性”的特点。正是因为有了IP协议,因特网才得以迅速发展成为世界上最大的、开放的计算机通信网络。因此,IP协议也可以叫做“因特网协议”。
IP协议中还有一个非常重要的内容,那就是给因特网上的每台计算机和其它设备都规定了一个唯一的地址,叫做“IP地址”。由于有这种唯一的地址,才保证了用户在连网的计算机上操作时,能够高效而且方便地从千千万万台计算机中选出自己所需的对象来。
IP地址就像是我们的家庭住址一样,如果你要写信给一个人,你就要知道他(她)的地址,这样邮递员才能把信送到。计算机发送信息就好比是邮递员,它必须知道唯一的“家庭地址”才能不至于把信送错人家。只不过我们的地址是用文字来表示的,计算机的地址用二进制数字表示。
IP地址被用来给Internet上的电脑一个编号。大家日常见到的情况是每台联网的PC上都需要有IP地址,才能正常通信。我们可以把“个人计算机”比作“一台电话”,那么“IP地址”就相当于“电话号码”,而Internet中的路由器,就相当于电信局的“程控式交换机”。
IP地址是一个32位的二进制数,通常被分割为4个“8位二进制数”(也就是4个字节)。IP地址通常用“点分十进制”表示成(a.b.c.d)的形式,其中,a,b,c,d都是0~255之间的十进制整数。例:点分十进IP地址(100.4.5.6),实际上是32位二进制数(01100100.00000100.00000101.00000110)。
#include "lwip_config.h"
#include "lwip/ip_addr.h"
#include "lwip/init.h"
#include "lwip/netif.h"
#include "netif/etharp.h"
#include "lwip/dhcp.h"
#include "lwip/tcp_impl.h"
extern err_t ethernetif_init(struct netif *netif);//网卡初始化函数,原型在ethernetif.c中
extern void ethernetif_input(struct netif *netif);
/**************LWIP协议栈初始化***********/
struct netif lwip_netif; //结构体原型在lwip/netif.h中
void LWIP_Config_Init(void)
{
ip_addr_t ip_addr={0};//IP地址
ip_addr_t netmask={0};//子网掩码
ip_addr_t gw={0};//网关
/*静态分配IP*/
// IP4_ADDR(&ip_addr,192,168,12,23);//设置静态IP
// IP4_ADDR(&netmask,255,255,255,0);//子网掩码
// IP4_ADDR(&netmask,192,168,12,1);//网关
/*1.LWIP协议栈初始化*/
lwip_init();
/*2.添加新的网卡设备到LWIP协议栈中*/
/*err_t ethernet_input(struct pbuf *p, struct netif *netif)处理已经读取从硬件接口发来的一个数据包*/
netif_add(&lwip_netif,&ip_addr,&netmask,&gw,NULL,ethernetif_init,ethernet_input);
/*3.设置网卡为LWIP协议栈默认设备*/
netif_set_default(&lwip_netif);
/*4.注册网卡设备为默认接口*/
netif_set_up(&lwip_netif);
/*5.动态分配IP*/
dhcp_start(&lwip_netif);
}
/*****DHCP定期数据处理函数(轮询方式实现数据更新)****/
u32 LWIP_TCP_TIME_CNT=0;
u32 LWIP_ARP_TIME_CNT=0;
u32 LWIP_DHCP_TIME_CNT=0;
u32 LWIP_UPDATE_DHCP_TIME_CNT=0;
u8 lwip_dhcp_stat=0;
void LWIP_DataUpdata(void)
{
/*250MS 更新一次*/
if(LWIP_TCP_TIME_CNT>=TCP_TMR_INTERVAL)
{
LWIP_TCP_TIME_CNT=0;
tcp_tmr(); //对 TCP 数据进行解析
}
/*5000ms 更新一次*/
if(LWIP_ARP_TIME_CNT>=ARP_TMR_INTERVAL)
{
LWIP_ARP_TIME_CNT=0;
etharp_tmr();//清理 ARP 缓存表
}
//500ms获取一次
if(LWIP_DHCP_TIME_CNT>=DHCP_FINE_TIMER_MSECS )
{
LWIP_DHCP_TIME_CNT=0;
dhcp_fine_tmr(); //解析 DHCP 请求,判断 IP 地址是否获取成功
}
/*60S 更新一次*/
if(LWIP_UPDATE_DHCP_TIME_CNT>=DHCP_COARSE_TIMER_MSECS)
{
LWIP_UPDATE_DHCP_TIME_CNT=0;
dhcp_coarse_tmr(); //更新检查 DHCP 的租约时间
}
ethernetif_input(&lwip_netif); //读取网卡的数据进行上报
if(lwip_dhcp_stat==0)
{
LWIP_GetDHCP_Addr();
}
}
/******获取动态分配的IP地址*************/
void LWIP_GetDHCP_Addr(void)
{
char buff[200];
u32 ip_addr;//IP地址
u32 netmask;//子网掩码
u32 gw;//网关
ip_addr=lwip_netif.ip_addr.addr;//IP地址
netmask=lwip_netif.netmask.addr;//子网掩码
gw=lwip_netif.gw.addr;
if(ip_addr!=0)
{
lwip_dhcp_stat=1;//成功获取到IP地址
printf("IP地址:%d.%d.%d.%d\r\n",(ip_addr>>0)&0xff,(ip_addr>>8)&0xff,(ip_addr>>16)&0xff,(ip_addr>>24)&0xff);
snprintf(buff,sizeof(buff),"IP地址:%d.%d.%d.%d",(ip_addr>>0)&0xff,(ip_addr>>8)&0xff,
(ip_addr>>16)&0xff,(ip_addr>>24)&0xff);
LCD_ShowStr(30,30+20*4,16,(u8 *)buff);//IP地址
printf("子网掩码:%d.%d.%d.%d\r\n",(netmask>>0)&0xff,(netmask>>8)&0xff,
(netmask>>16)&0xff,(netmask>>24)&0xff);
snprintf(buff,sizeof(buff),"子网掩码:%d.%d.%d.%d\r\n",(netmask>>0)&0xff,(netmask>>8)&0xff,
(netmask>>16)&0xff,(netmask>>24)&0xff);
LCD_ShowStr(30,30+20*5,16,(u8 *)buff);//子网掩码
printf("网关:%d.%d.%d.%d\r\n",(gw>>0)&0xff,(gw>>8)&0xff,
(gw>>16)&0xff,(gw>>24)&0xff);
snprintf(buff,sizeof(buff),"网关:%d.%d.%d.%d\r\n",(gw>>0)&0xff,(gw>>8)&0xff,
(gw>>16)&0xff,(gw>>24)&0xff);
LCD_ShowStr(30,30+20*6,16,(u8 *)buff);//子网掩码
}
}
3.4 LWIP域名解析
域名(英语:Domain Name),又称网域,是由一串用点分隔的名字组成的Internet上某一台计算机或计算机组的名称,用于在数据传输时对计算机的定位标识(有时也指地理位置)。
由于IP地址具有不方便记忆并且不能显示地址组织的名称和性质等缺点,人们设计出了域名,并通过网域名称系统(DNS,Domain Name System)来将域名和IP地址相互映射,使人更方便地访问互联网,而不用去记住能够被机器直接读取的IP地址数串。
err_t dns_gethostbyname(const char *hostname, ip_addr_t *addr, dns_found_callback found,void *callback_arg) 函数功能:DNS域名解析 形 参:hostname--- 主机名(网址) addr---解析到的IP found---函数指针,域名解析成功回调函数。 callback_arg---传给回调函数形参 回调函数原型: typedef void (*dns_found_callback)(const char *name, ip_addr_t *ipaddr, void *callback_arg); 返回值:成功返回ERR_OK,失败其它值 |
4.物联网平台连接
通过RAW lWIP协议建立TCP客户端,连接物联网平台。
#include "lwip_config.h"
#include "lwip/tcp.h"
/*接收成功回调函数*/
u8 dm9000_rx_buff[1024];
u16 dm9000_rx_len=0;
err_t tcp_recv_func(void *arg, struct tcp_pcb *tpcb,struct pbuf *p, err_t err)
{
memset(dm9000_rx_buff,0,sizeof(dm9000_rx_buff));
dm9000_rx_len=0;
u16 len=0;
if(p==NULL)
{
printf("服务器断开连接\r\n");
client_connect_stat=0;
}
else
{
if(p->tot_len==p->len)//p->tot_len整包数据长度,p->len当前节点数据长度
{
memcpy(dm9000_rx_buff,p->payload,p->len);
len=p->len;
}
else
{
struct pbuf *temp=p;
len=0;
while(temp!=NULL)
{
memcpy(dm9000_rx_buff+len,temp->payload,temp->len);
len+=temp->len;
temp=temp->next;
}
}
pbuf_free(p);//该释放函数只需填链表头进来即可
dm9000_rx_len=len;
}
return ERR_OK;
}
struct tcp_pcb *new_tcp;//tcp网络信息(套接字)
u8 client_addr[4];
u8 client_connect_stat=0;
/*连接服务器成功回调函数*/
err_t tcp_connect_func(void *arg, struct tcp_pcb *tpcb, err_t err)
{
client_addr[0]=tpcb->local_ip.addr>>0;
client_addr[1]=tpcb->local_ip.addr>>8;
client_addr[2]=tpcb->local_ip.addr>>16;
client_addr[3]=tpcb->local_ip.addr>>24;
new_tcp=tpcb;
client_connect_stat=1;
printf("%d.%d.%d.%d连接服务器成功\r\n",client_addr[0],client_addr[1],client_addr[2],client_addr[3]);
tcp_recv(tpcb,tcp_recv_func);
return 0;
}
/**********TCP客户端创建*****************
**
**形参: addr IP地址
** u16_t port -- 服务器端口号
**返回值:0 --服务器创建成功,其他值--失败
**
*****************************************/
u8 LWIP_CreateTcpClient(ip_addr_t *addr,u16 port)
{
/*1.创建新的网卡设备*/
new_tcp=tcp_new();
if(new_tcp==NULL)return 1;
/*连接服务器*/
tcp_connect(new_tcp,addr,port,tcp_connect_func);
return 0;
}