相比于网络芯片dp83848,现在可能有其它类似于W5500这类内置了硬件协议栈的芯片使用起来更加简易,但是dp83848还是有一些优势,使用更加灵活,适用范围也更广。
LwIP协议栈STM32官方有提供例程,修改后可以使用,首先是DP83848硬件初始化,使用HAL库。
//DP83848初始化
//返回值:0,成功;
// 其他,失败
u8 DP83848_Init(void)
{
u8 macaddress[6];
GPIO_InitTypeDef GPIO_InitStructure;
__HAL_RCC_GPIOE_CLK_ENABLE(); //开启GPIOB时钟
INTX_DISABLE(); //关闭所有中断,复位过程不能被打断!
// PCF8574_WriteBit(ETH_RESET_IO,1); //硬件复位
// delay_ms(100);
// PCF8574_WriteBit(ETH_RESET_IO,0); //复位结束
// delay_ms(100);
GPIO_InitStructure.Pin = GPIO_PIN_3;
GPIO_InitStructure.Mode = GPIO_MODE_IT_FALLING;
GPIO_InitStructure.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOE, &GPIO_InitStructure);
HAL_NVIC_SetPriority(EXTI3_IRQn,0,0);
HAL_NVIC_EnableIRQ(EXTI3_IRQn);
INTX_ENABLE(); //开启所有中断
macaddress[0]=lwipdev.mac[0];
macaddress[1]=lwipdev.mac[1];
macaddress[2]=lwipdev.mac[2];
macaddress[3]=lwipdev.mac[3];
macaddress[4]=lwipdev.mac[4];
macaddress[5]=lwipdev.mac[5];
ETH_Handler.Instance=ETH;
ETH_Handler.Init.AutoNegotiation=ETH_AUTONEGOTIATION_ENABLE;//使能自协商模式
ETH_Handler.Init.Speed=ETH_SPEED_100M;//速度100M,如果开启了自协商模式,此配置就无效
ETH_Handler.Init.DuplexMode=ETH_MODE_FULLDUPLEX;//全双工模式,如果开启了自协商模式,此配置就无效
ETH_Handler.Init.PhyAddress=DP83848_PHY_ADDRESS;//DP83848地址
ETH_Handler.Init.MACAddr=macaddress; //MAC地址
ETH_Handler.Init.RxMode=ETH_RXINTERRUPT_MODE; //轮训接收模式
ETH_Handler.Init.ChecksumMode=ETH_CHECKSUM_BY_HARDWARE;//硬件帧校验
ETH_Handler.Init.MediaInterface=ETH_MEDIA_INTERFACE_RMII;//RMII接口
if(HAL_ETH_Init(Ð_Handler)==HAL_OK)
{
Eth_Link_PHYITConfig();
return 0; //成功
}
else
{
Eth_Link_PHYITConfig();
return 1; //失败
}
}
IO口都是固定那几个引脚,不用多说,dp83848的PHY地址根据实际情况确定
#define DP83848_PHY_ADDRESS 0x01
//ETH底层驱动,时钟使能,引脚配置
//此函数会被HAL_ETH_Init()调用
//heth:以太网句柄
void HAL_ETH_MspInit(ETH_HandleTypeDef *heth)
{
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_ETH_CLK_ENABLE(); //开启ETH时钟
__HAL_RCC_GPIOA_CLK_ENABLE(); //开启GPIOA时钟
__HAL_RCC_GPIOB_CLK_ENABLE(); //开启GPIOB时钟
__HAL_RCC_GPIOC_CLK_ENABLE(); //开启GPIOC时钟
__HAL_RCC_GPIOG_CLK_ENABLE(); //开启GPIOG时钟
/*网络引脚设置 RMII接口
ETH_MDIO -------------------------> PA2
ETH_MDC --------------------------> PC1
ETH_RMII_REF_CLK------------------> PA1
ETH_RMII_CRS_DV ------------------> PA7
ETH_RMII_RXD0 --------------------> PC4
ETH_RMII_RXD1 --------------------> PC5
ETH_RMII_TX_EN -------------------> PB11
ETH_RMII_TXD0 --------------------> PG13
ETH_RMII_TXD1 --------------------> PG14
ETH_RESET-------------------------> PCF8574扩展IO*/
//PA1,2,7
GPIO_Initure.Pin=GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_7;
GPIO_Initure.Mode=GPIO_MODE_AF_PP; //推挽复用
GPIO_Initure.Pull=GPIO_NOPULL; //不带上下拉
GPIO_Initure.Speed=GPIO_SPEED_HIGH; //高速
GPIO_Initure.Alternate=GPIO_AF11_ETH; //复用为ETH功能
HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化
//PB11
GPIO_Initure.Pin=GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13; //PB11
HAL_GPIO_Init(GPIOB,&GPIO_Initure); //始化
//PC1,4,5
GPIO_Initure.Pin=GPIO_PIN_1|GPIO_PIN_4|GPIO_PIN_5; //PC1,4,5
HAL_GPIO_Init(GPIOC,&GPIO_Initure); //初始化
// //PG13,14
// GPIO_Initure.Pin=GPIO_PIN_13|GPIO_PIN_14; //PG13,14
// HAL_GPIO_Init(GPIOG,&GPIO_Initure); //初始化
HAL_NVIC_SetPriority(ETH_IRQn,0,0); //网络中断优先级应该高一点
HAL_NVIC_EnableIRQ(ETH_IRQn);
}
PHY初始化完成之后是LwIP协议初始化
//LWIP初始化(LWIP启动的时候使用)
//返回值:0,成功
// 1,内存错误
// 2,dp83848初始化失败
// 3,网卡添加失败.
u8 lwip_comm_init(void)
{
u8 retry=0;
struct netif *Netif_Init_Flag; //调用netif_add()函数时的返回值,用于判断网络初始化是否成功
struct ip_addr ipaddr; //ip地址
struct ip_addr netmask; //子网掩码
struct ip_addr gw; //默认网关
if(Mem_init_flag==0)
{
if(ETH_Mem_Malloc())return 0; //内存申请失败
if(lwip_comm_mem_malloc())return 0; //内存申请失败
lwip_comm_default_ip_set(&lwipdev); //设置默认IP等信息
Mem_init_flag=1;
}
while(DP83848_Init()) //初始化DP83848,如果失败的话就重试5次
{
retry++;
if(retry>5) {retry=0;return 0;} //DP83848初始化失败 //5s
}
tcpip_init(NULL,NULL); //初始化tcp ip内核,该函数里面会创建tcpip_thread内核任务
#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.gateway[1],lwipdev.gateway[2],lwipdev.gateway[3]);
printf("网卡en的MAC地址为:................%d.%d.%d.%d.%d.%d\r\n",lwipdev.mac[0],lwipdev.mac[1],lwipdev.mac[2],lwipdev.mac[3],lwipdev.mac[4],lwipdev.mac[5]);
printf("静态IP地址........................%d.%d.%d.%d\r\n",lwipdev.ip[0],lwipdev.ip[1],lwipdev.ip[2],lwipdev.ip[3]);
printf("子网掩码..........................%d.%d.%d.%d\r\n",lwipdev.netmask[0],lwipdev.netmask[1],lwipdev.netmask[2],lwipdev.netmask[3]);
printf("默认网关..........................%d.%d.%d.%d\r\n",lwipdev.gateway[0],lwipdev.gateway[1],lwipdev.gateway[2],lwipdev.gateway[3]);
#endif
Netif_Init_Flag=netif_add(&lwip_netif,&ipaddr,&netmask,&gw,NULL,ðernetif_init,&tcpip_input);//向网卡列表中添加一个网口
if(Netif_Init_Flag==NULL)return 4;//网卡添加失败
else//网口添加成功后,设置netif为默认值,并且打开netif网口
{
netif_set_default(&lwip_netif); //设置netif为默认网口
netif_set_up(&lwip_netif); //打开netif网口
}
return 1;//操作OK.
}
如果需要使用DHCP,则创建DHCP任务来获取IP地址,在一段时间不能获取到IP地址则使用默认的静态IP地址。任务完成后删除DHCP任务本身。
接下来需要使用TCP/IP客户端模式进行通信,则创建TCP客户端线程,任务函数如下
//tcp客户端任务函数
static void tcp_client_thread(void *arg)
{
CPU_SR_ALLOC();
OS_ERR oserr;
u32 data_len = 0;
struct pbuf *q;
err_t err,recv_err;
static ip_addr_t server_ipaddr,loca_ipaddr;
static u16_t server_port,loca_port;
LWIP_UNUSED_ARG(arg);
server_port = eth_remote_port;
IP4_ADDR(&server_ipaddr, lwipdev.remoteip[0],lwipdev.remoteip[1], lwipdev.remoteip[2],lwipdev.remoteip[3]);
while (1)
{
tcp_clientconn = netconn_new(NETCONN_TCP); //创建一个TCP链接
err = netconn_connect(tcp_clientconn,&server_ipaddr,server_port);//连接服务器
if(err != ERR_OK) netconn_delete(tcp_clientconn); //返回值不等于ERR_OK,删除tcp_clientconn连接
else if (err == ERR_OK) //处理新连接的数据
{
Tcp_Work_State=STATE_NET_READY;
led2_state=LED_TWINKLE;
struct netbuf *recvbuf;
tcp_clientconn->recv_timeout = 10;
netconn_getaddr(tcp_clientconn,&loca_ipaddr,&loca_port,1); //获取本地IP主机IP地址和端口号
printf("连接上服务器%d.%d.%d.%d,本机端口号为:%d\r\n",lwipdev.remoteip[0],lwipdev.remoteip[1], lwipdev.remoteip[2],lwipdev.remoteip[3],loca_port);
while(1)
{
if((tcp_client_flag & LWIP_SEND_DATA) == LWIP_SEND_DATA) //有数据要发送
{
// err = netconn_write(tcp_clientconn ,tcp_client_sendbuf,strlen((char*)tcp_client_sendbuf),NETCONN_COPY); //发送tcp_server_sentbuf中的数据
err = netconn_write(tcp_clientconn ,tcp_client_sendbuf,tcp_client_sendbuf_len,NETCONN_COPY); //发送tcp_server_sentbuf中的数据
if(err != ERR_OK)
{
printf("发送失败\r\n");
}
tcp_client_flag &= ~LWIP_SEND_DATA;
}
if((recv_err = netconn_recv(tcp_clientconn,&recvbuf)) == ERR_OK) //接收到数据
{
OS_CRITICAL_ENTER(); //关中断
memset(tcp_client_recvbuf,0,TCP_CLIENT_RX_BUFSIZE); //数据接收缓冲区清零
for(q=recvbuf->p;q!=NULL;q=q->next) //遍历完整个pbuf链表
{
//判断要拷贝到TCP_CLIENT_RX_BUFSIZE中的数据是否大于TCP_CLIENT_RX_BUFSIZE的剩余空间,如果大于
//的话就只拷贝TCP_CLIENT_RX_BUFSIZE中剩余长度的数据,否则的话就拷贝所有的数据
if(q->len > (TCP_CLIENT_RX_BUFSIZE-data_len)) memcpy(tcp_client_recvbuf+data_len,q->payload,(TCP_CLIENT_RX_BUFSIZE-data_len));//拷贝数据
else memcpy(tcp_client_recvbuf+data_len,q->payload,q->len);
data_len += q->len;
if(data_len > TCP_CLIENT_RX_BUFSIZE) break; //超出TCP客户端接收数组,跳出
}
OS_CRITICAL_EXIT(); //开中断
// printf_HEX(tcp_client_recvbuf,data_len);
data_len=0; //复制完成后data_len要清零。
// printf("%s\r\n",tcp_client_recvbuf);
Tcp_Rxdone_flag=1;
netbuf_delete(recvbuf);
}else if(recv_err == ERR_CLSD||(User_Cfg.Main_Channel==ETH_PATH&&Net_Error_Cnt>=2)) //关闭连接
{
Net_Error_Cnt=0;
Tcp_Work_State=STATE_CONNECT_CLSD;
led2_state=LED_FREE;
netconn_close(tcp_clientconn);
netconn_delete(tcp_clientconn);
printf("服务器%d.%d.%d.%d断开连接\r\n",lwipdev.remoteip[0],lwipdev.remoteip[1], lwipdev.remoteip[2],lwipdev.remoteip[3]);
break;
}
}
OSTimeDly ( 2, OS_OPT_TIME_DLY, & oserr );
}
OSTimeDly ( 5, OS_OPT_TIME_DLY, & oserr );
}
}
需要注意的是移植的LwIP协议栈是不支持网线热插拔的,需要自己写处理函数来检测网线的连接状态,方法是通过芯片提供的中断来实现,在DP83848硬件初始化的时候有进行外部中断IO口的初始化,
如果移植的文件中没有的话,需要添加两个寄存器的地址
#define PHY_MICR ((uint16_t)0x11)
#define PHY_MISR ((uint16_t)0x12)
中断回调函数处理,置一个插入或拔出的标志
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
uint32_t status;
if(GPIO_Pin==GPIO_PIN_3)
{
HAL_ETH_ReadPHYRegister(Ð_Handler, PHY_MISR,&status);
// if((status & PHY_LINK_STATUS) != 0)
// {
Plug_flag=1;
// }
}
}
需要创建一个任务检测网线连接,网线连接的时候主要有两种情况,一种是之前未成功初始化,那么就从PHY开始初始化一遍;另一种是之前已经成功初始化,那么只需要调用netif_set_link_up(&lwip_netif);重新建立连接。
网线断开的时候调用netif_set_link_down(&lwip_netif); 断开连接。测试能正常进行网线热插拔,没有问题。
void Eth_Link_ITHandler(void)
{
uint32_t status;
uint8_t retry;
/* Check whether the link interrupt has occurred or not */
if(Plug_flag)
{
Plug_flag=0;
HAL_ETH_ReadPHYRegister(Ð_Handler, PHY_BSR, &status);
if(status & ( PHY_AUTONEGO_COMPLETE |PHY_LINKED_STATUS))/*检测到网线连接*/
{
if(Tcp_Work_State==STATE_NET_READY)
{
led2_state=LED_TWINKLE;
}
if(EthInitStatus == 0)/*之前未成功初始化过*/
{
/*Reinit PHY*/
retry=5;
while(retry) //lwip初始化
{
retry--;
delay_ms(500);
delay_ms(500);
if(lwip_comm_init()==1)
{
EthInitStatus=1;
#if LWIP_DHCP
lwip_comm_dhcp_creat(); //创建DHCP任务
#endif
break;
}
}
// #if LWIP_DHCP
// lwip_comm_dhcp_creat(); //创建DHCP任务
// #endif
}
else/*之前已经成功初始化*/
{
/*set link up for re link callbalk function*/
netif_set_link_up(&lwip_netif);
}
}
else/*网线断开*/
{
/*set link down for re link callbalk function*/
netif_set_link_down(&lwip_netif);
led2_state=LED_FREE;
}
}
}