前言
从同事工程上改了一个版本出来, 发现如果不插入网线, 不能正常跑. 卡在网卡初始化那里了. 如果不能实现网线热插拔, 我改出的版本不能实际用在客户现场的. 如果不插入网线, 就不能正常用, 这谁受的了啊.
前面做了一个预研使用STM32F4标准外设库实现网线热插拔- 分析STM3240G-EVAL官方工程, 知道ST官方怎么玩网线热插拔了.
今天, 用ST官方工程的思路, 将网线热插拔实现移植到自己工程中, 基本可以用. 但是有一些小问题(还能接受, 也能跟同事和客户解释的通), 先这样.
官方的网线热插拔效果: 不管啥时候插拔网线, 只要再插入网线, 网络操作就好使(e.g. ping), 即使固件开机时, 没有插入网线.
我移植完的效果: 如果固件开机时, 插入了网线,正常情况网卡就能初始化成功, 这种场景下, 再插拔网线, 只要插上网线, 网络操作就好使(e.g. ping). 但是, 如果固件开机时, 没插入网线, 等程序跑起来后, 再插拔网线, 网络操作是不好使的, 很明显, 是少做了一些网卡初始化操作, 但是我暂时找不出来, 同事的工程也是乱乱的. 猜测是网卡没插网线(网卡初始化失败), 后面起的LWIP操作(起了一些LWIP相关线程), 操作的好多数据结构都是无效的, 所以简单设置网卡上线/下线是不好使的. 想了一个折中的方法, 如果检测到网线插入, 但是此时是网线没插入开机启动的情况, 因为我知道此种情况, 网络操作是不好使的, 我就重启固件. 然后就走开机时, 插入网线的操作流程, 剩下就OK了. 就是比官方工程多了一次重启固件的动作. 在我们这个工程中, 重启固件没啥大事. 能接受.
总之吧, 这次只是将问题解决了. 不完美. 以后我自己从头写的固件工程, 如果还有机会使用STM标准外设库(不过以后新工程该使用HAL库了), 我好好弄弄. 整的明明白白的. 这次就先这样.
实验记录
这次记录, 从我开始改之前的git版本和改完做的git版本比对, 看看哪里变了, 就记录下来作为笔记.
IDE
MDK5
用图形化git工具比较任意2个git版本的方法
先在git log列表中, 按住CTRL键, 选中2个版本, 再选择 git比较版本.
打开网络状态回调宏
opt.h
#ifndef LWIP_NETIF_LINK_CALLBACK
#define LWIP_NETIF_LINK_CALLBACK 1
#endif
如果要在LWIP函数中使用bool 类型, 需要加 stdbool.h
#include <stdbool.h>
增加网线热插拔回调函数调用
extern void ETH_link_callback(struct netif* netif);
void Ethernet_Sys_Init(void)
{
struct ip_addr ipaddr;
struct ip_addr netmask;
struct ip_addr gw;
tcpip_init(NULL, NULL);
Create_MAC(CONFIG_Table.MAC);
#if LWIP_DHCP
ipaddr.addr = 0;
netmask.addr = 0;
gw.addr = 0;
#else
IP4_ADDR(&ipaddr, LWIP_Netcfg.ipaddr[0], LWIP_Netcfg.ipaddr[1], LWIP_Netcfg.ipaddr[2], LWIP_Netcfg.ipaddr[3]);
IP4_ADDR(&netmask, LWIP_Netcfg.netmask[0], LWIP_Netcfg.netmask[1], LWIP_Netcfg.netmask[2], LWIP_Netcfg.netmask[3]);
IP4_ADDR(&gw, LWIP_Netcfg.gateway[0], LWIP_Netcfg.gateway[1], LWIP_Netcfg.gateway[2], LWIP_Netcfg.gateway[3]);
#endif
netif_add(&stm32_netif, &ipaddr, &netmask, &gw, NULL, ðernetif_init, &tcpip_input);
netif_set_default(&stm32_netif);
#if LWIP_DHCP
dhcp_start(&stm32_netif);
#endif
netif_set_up(&stm32_netif);
netif_set_link_callback(&stm32_netif, ETH_link_callback);
}
extern void ETH_Delay(__IO uint32_t nCount);
extern struct netif stm32_netif;
void ETH_link_callback(struct netif* netif)
{
__IO uint32_t timeout = 0;
uint32_t tmpreg, RegValue;
struct ip_addr ipaddr;
struct ip_addr netmask;
struct ip_addr gw;
if (NULL != netif) {
if (netif_is_link_up(netif)) {
if (ETH_InitStructure.ETH_AutoNegotiation != ETH_AutoNegotiation_Disable) {
timeout = 0;
ETH_WritePHYRegister(LAN8720_PHY_ADDRESS, PHY_BCR, PHY_AutoNegotiation);
do {
timeout++;
} while (!(ETH_ReadPHYRegister(LAN8720_PHY_ADDRESS, PHY_BSR) & PHY_AutoNego_Complete)
&& (timeout < (uint32_t)PHY_READ_TO));
timeout = 0;
RegValue = ETH_ReadPHYRegister(LAN8720_PHY_ADDRESS, PHY_SR);
if ((RegValue & PHY_DUPLEX_STATUS) != (uint32_t)RESET) {
ETH_InitStructure.ETH_Mode = ETH_Mode_FullDuplex;
} else {
ETH_InitStructure.ETH_Mode = ETH_Mode_HalfDuplex;
}
if (RegValue & PHY_SPEED_STATUS) {
ETH_InitStructure.ETH_Speed = ETH_Speed_10M;
} else {
ETH_InitStructure.ETH_Speed = ETH_Speed_100M;
}
tmpreg = ETH->MACCR;
tmpreg |= (uint32_t)(ETH_InitStructure.ETH_Speed | ETH_InitStructure.ETH_Mode);
ETH->MACCR = (uint32_t)tmpreg;
_eth_delay_(ETH_REG_WRITE_DELAY);
tmpreg = ETH->MACCR;
ETH->MACCR = tmpreg;
}
ETH_Start();
IP4_ADDR(&ipaddr, LWIP_Netcfg.ipaddr[0], LWIP_Netcfg.ipaddr[1], LWIP_Netcfg.ipaddr[2], LWIP_Netcfg.ipaddr[3]);
IP4_ADDR(&netmask, LWIP_Netcfg.netmask[0], LWIP_Netcfg.netmask[1], LWIP_Netcfg.netmask[2], LWIP_Netcfg.netmask[3]);
IP4_ADDR(&gw, LWIP_Netcfg.gateway[0], LWIP_Netcfg.gateway[1], LWIP_Netcfg.gateway[2], LWIP_Netcfg.gateway[3]);
netif_set_addr(&stm32_netif, &ipaddr, &netmask, &gw);
netif_set_up(&stm32_netif);
} else {
ETH_Stop();
netif_set_down(&stm32_netif);
}
}
}
加入热插拔状态检测函数调用
我用的LAN8720, 没有网络状态改变的中断通知, 只能是随便找个线程, 来主动查询网线插拔状态
PHY_Linked_Status 的定义如下, 就是检测uint16_t的bit2是否为1
#define PHY_AutoNego_Complete ((uint16_t)0x0020)
#define PHY_Linked_Status ((uint16_t)0x0004)
#define PHY_Jabber_detection ((uint16_t)0x0002)
extern void check_net_RJ45_hot_plug_inout(void);
void check_net_RJ45_hot_plug_inout(void)
{
uint16_t RegValue_PHY_BSR = 0;
OSTimeDlyHMSM(0, 0, 1, 0);
RegValue_PHY_BSR = ETH_ReadPHYRegister(LAN8720_PHY_ADDRESS, PHY_BSR);
if (RegValue_PHY_BSR & PHY_Linked_Status) {
if (gb_ETH_Init_ok) {
if ((RegValue_PHY_BSR & PHY_Linked_Status) != (g_RegValue_PHY_BSR & PHY_Linked_Status)) {
g_RegValue_PHY_BSR = RegValue_PHY_BSR;
netif_set_link_up(&stm32_netif);
}
} else {
set_flag_system_restart(true);
}
} else {
if ((RegValue_PHY_BSR & PHY_Linked_Status) != (g_RegValue_PHY_BSR & PHY_Linked_Status)) {
g_RegValue_PHY_BSR = RegValue_PHY_BSR;
netif_set_link_down(&stm32_netif);
}
}
}
在网卡初始化之后, 调用LWIP初始化
void exec_lan8720_init()
{
LAN8720_Init();
Ethernet_Sys_Init();
}
void start_task(void* p_arg)
{
#ifndef DEBUG_FLAG_NOT_USE_HTTP_SERVER_INSIDE
exec_lan8720_init();
#endif
do {
check_net_RJ45_hot_plug_inout();
} while (true);
}
打开 USE_Delay 宏, 使用自己的LWIP延时函数
#define USE_Delay
#ifdef USE_Delay
#define _eth_delay_ my_ETH_Delay
#else
#define _eth_delay_ ETH_Delay
#endif
重新实现LWIP操作延时函数
以前维护工程时, 发现LAN8720初始化不是每次都成功(但是最多初始化10次, 就能初始化成功, 感觉好奇怪, 最后改为只有网卡初始化成功了, 才往下执行), 今天发现是LWIP自用的延时函数时间短了, 重新包装了一个ms延时的函数, 每次LAN8720都能初始化成功(只要先用网线将设备和HUB连在一起)
extern void my_ETH_Delay(__IO uint32_t nCount);
void my_ETH_Delay(__IO uint32_t nCount)
{
__IO uint32_t index = 0;
for (index = nCount; index != 0; index--) {
OSTimeDlyHMSM(0, 0, 0, 1);
}
}
增加 ETH_Stop() 实现
检测到网线拔出时用的. 这个函数DISABLE的顺序, 最好和ETH_Start()是反的.
void ETH_Stop(void)
{
ETH_DMAReceptionCmd(DISABLE);
ETH_DMATransmissionCmd(DISABLE);
ETH_MACReceptionCmd(DISABLE);
ETH_FlushTransmitFIFO();
ETH_MACTransmissionCmd(DISABLE);
}
void ETH_Start(void)
{
ETH_MACTransmissionCmd(ENABLE);
ETH_FlushTransmitFIFO();
ETH_MACReceptionCmd(ENABLE);
ETH_DMATransmissionCmd(ENABLE);
ETH_DMAReceptionCmd(ENABLE);
}
LAN8720用到的几个寄存器
ST官方板子用的DP83848, 我的板子用的LAN8720. 从官方工程移植时, 注意自己板子的网卡地址(硬件原理图相关, 不是固定的地址),网卡寄存器号码, 网卡寄存器含义(datasheet有描述).
LAN8720主要是以下3个寄存器, 网卡插拔状态在PHY_BSR的bit2, PHY_BSR.bit2 = 1 为网线插入, 0 为网线拔出. BSR means : Basic Status Register
#define PHY_BCR 0
#define PHY_BSR 1
#define PHY_SR ((uint16_t)31)
这些寄存器和寄存器定义, 可以看LAN8720 datasheet <<C17146_LAN8720AI-CP-TR_2017-12-12.PDF>> 42页
LAN8720_Init()实现
这函数以前是返回是否初始化成功的, 现在不管是否初始化成功了, 管了也没用. 如果插入有效网线(网线一端连接设备,一端连接HUB), 一定能初始化成功 如果没有插入有效网线(包括网线一端连接设备,一端悬空), 一定初始化失败 不管网卡是否初始化成功, 都要往下走, 不能卡在这.
void LAN8720_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOB | RCC_AHB1Periph_GPIOC | RCC_AHB1Periph_GPIOD,
ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
SYSCFG_ETH_MediaInterfaceConfig(SYSCFG_ETH_MediaInterface_RMII);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource1, GPIO_AF_ETH);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_ETH);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_ETH);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_4 | GPIO_Pin_5;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOC, GPIO_PinSource1, GPIO_AF_ETH);
GPIO_PinAFConfig(GPIOC, GPIO_PinSource4, GPIO_AF_ETH);
GPIO_PinAFConfig(GPIOC, GPIO_PinSource5, GPIO_AF_ETH);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11 | GPIO_Pin_12 | GPIO_Pin_13;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource11, GPIO_AF_ETH);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource12, GPIO_AF_ETH);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource13, GPIO_AF_ETH);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
GPIO_Init(GPIOD, &GPIO_InitStructure);
LAN8720_RST = 0;
delay_ms(50);
LAN8720_RST = 1;
delay_ms(50);
ETH_MACDMA_Config();
}
对网卡初始化成功做个标记
gb_ETH_Init_ok 为标记, 如果固件启动后, 网线不插, ETH_Init就会在中途返回失败 将 ETH_Init() 结果赋值给 gb_ETH_Init_ok , 这样就知道固件启动时, 是否插入了网线启动的. 如果(gb_ETH_Init_ok == true), 说明固件启动时, 是插入网线(网线一端接设备, 一端接HUB)启动的. 如果(gb_ETH_Init_ok == false), 说明固件启动时, 是没插入网线启动的, 如果再检测到网线插入, 就要重启固件, 走插入网线启动固件的正常流程.
在(gb_ETH_Init_ok == true), 用 g_RegValue_PHY_BSR保存一下当前网线插入状态, 这样在网线热插拔时, 可以保证只在网线插拔状态改变时, 执行一次有效的网卡上线或下线操作.
void ETH_MACDMA_Config(void)
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_ETH_MAC | RCC_AHB1Periph_ETH_MAC_Tx | RCC_AHB1Periph_ETH_MAC_Rx, ENABLE);
ETH_DeInit();
ETH_SoftwareReset();
while (ETH_GetSoftwareResetStatus() == SET);
ETH_StructInit(Ð_InitStructure);
ETH_InitStructure.ETH_AutoNegotiation = ETH_AutoNegotiation_Enable;
ETH_InitStructure.ETH_LoopbackMode = ETH_LoopbackMode_Disable;
ETH_InitStructure.ETH_RetryTransmission = ETH_RetryTransmission_Disable;
ETH_InitStructure.ETH_AutomaticPadCRCStrip = ETH_AutomaticPadCRCStrip_Disable;
ETH_InitStructure.ETH_ReceiveAll = ETH_ReceiveAll_Disable;
ETH_InitStructure.ETH_BroadcastFramesReception = ETH_BroadcastFramesReception_Enable;
ETH_InitStructure.ETH_PromiscuousMode = ETH_PromiscuousMode_Disable;
ETH_InitStructure.ETH_MulticastFramesFilter =
ETH_MulticastFramesFilter_Perfect;
ETH_InitStructure.ETH_UnicastFramesFilter = ETH_UnicastFramesFilter_Perfect;
#ifdef CHECKSUM_BY_HARDWARE
ETH_InitStructure.ETH_ChecksumOffload =
ETH_ChecksumOffload_Enable;
#endif
ETH_InitStructure.ETH_DropTCPIPChecksumErrorFrame =
ETH_DropTCPIPChecksumErrorFrame_Enable;
ETH_InitStructure.ETH_ReceiveStoreForward =
ETH_ReceiveStoreForward_Enable;
ETH_InitStructure.ETH_TransmitStoreForward =
ETH_TransmitStoreForward_Enable;
ETH_InitStructure.ETH_ForwardErrorFrames = ETH_ForwardErrorFrames_Disable;
ETH_InitStructure.ETH_ForwardUndersizedGoodFrames = ETH_ForwardUndersizedGoodFrames_Disable;
ETH_InitStructure.ETH_SecondFrameOperate = ETH_SecondFrameOperate_Enable;
ETH_InitStructure.ETH_AddressAlignedBeats = ETH_AddressAlignedBeats_Enable;
ETH_InitStructure.ETH_FixedBurst = ETH_FixedBurst_Enable;
ETH_InitStructure.ETH_RxDMABurstLength =
ETH_RxDMABurstLength_32Beat;
ETH_InitStructure.ETH_TxDMABurstLength = ETH_TxDMABurstLength_32Beat;
ETH_InitStructure.ETH_DMAArbitration = ETH_DMAArbitration_RoundRobin_RxTx_2_1;
if (ETH_SUCCESS == ETH_Init(Ð_InitStructure, LAN8720_PHY_ADDRESS)) {
gb_ETH_Init_ok =
true;
g_RegValue_PHY_BSR = ETH_ReadPHYRegister(LAN8720_PHY_ADDRESS, PHY_BSR);
}
ETH_DMAITConfig(ETH_DMA_IT_NIS | ETH_DMA_IT_R, ENABLE);
}
本次实验结束
就这些改动了, 研究了4,5天.
|