DM9000在STM32F407上的网络驱动移植及调试经验分享
本文介绍了基于STM32 MCU通过DM9000扩展双网口的实现方案。首先阐述了项目背景,由于MCU原生仅支持单MAC,需通过外接网络芯片实现双网口功能。接着详细说明了移植过程:1) 配置FSMC总线参数,注意关闭写保护并优化时序;2) DM9000初始化流程,包括硬件复位、PHY配置等关键步骤;3) 数据读写验证方法。文章还总结了驱动开发经验,包括完整的驱动流程和常见问题排查方法,如PHY连接异
目录
一、需求由来
双Ecat主站需求(独立主站、冗余主站)需要两个网口,一般的MCU仅有一个原生的MAC,而自带双MAC的cortex-M芯片STM没有;GD也是去年刚推出,资料较少。
于是调研了一圈(对于市面上的网络拓展芯片的调研及摸底测试大家有兴趣的话单独出一个文档分享),最终选用DM9000作为网络扩展芯片。
硬件上的方案大体如下:

二、资料获取
从网络上获取到了
DM9000规格书:
DM900编程指导:
DM9000在Linux上的驱动示例:
lnx_dm9000_KT4.14.200.zip - 飞书云文档
三、移植调试
3.1、MCU的FSMC配置
由于DM9000是通过外挂SRAM的方式与MCU通讯的,因此选用FSMC外设,CubeMX配置如下:

生成的FSMC配置代码如下:
/* FSMC initialization function */
void MX_FSMC_Init(void)
{
/* USER CODE BEGIN FSMC_Init 0 */
/* USER CODE END FSMC_Init 0 */
FSMC_NORSRAM_TimingTypeDef Timing = {0};
/* USER CODE BEGIN FSMC_Init 1 */
/* USER CODE END FSMC_Init 1 */
/** Perform the SRAM4 memory initialization sequence
*/
hsram4.Instance = FSMC_NORSRAM_DEVICE;
hsram4.Extended = FSMC_NORSRAM_EXTENDED_DEVICE;
/* hsram4.Init */
hsram4.Init.NSBank = FSMC_NORSRAM_BANK3;
hsram4.Init.DataAddressMux = FSMC_DATA_ADDRESS_MUX_DISABLE;
hsram4.Init.MemoryType = FSMC_MEMORY_TYPE_SRAM;
hsram4.Init.MemoryDataWidth = FSMC_NORSRAM_MEM_BUS_WIDTH_16;
hsram4.Init.BurstAccessMode = FSMC_BURST_ACCESS_MODE_DISABLE;
hsram4.Init.WaitSignalPolarity = FSMC_WAIT_SIGNAL_POLARITY_LOW;
hsram4.Init.WrapMode = FSMC_WRAP_MODE_DISABLE;
hsram4.Init.WaitSignalActive = FSMC_WAIT_TIMING_BEFORE_WS;
hsram4.Init.WriteOperation = FSMC_WRITE_OPERATION_ENABLE;
hsram4.Init.WaitSignal = FSMC_WAIT_SIGNAL_DISABLE;
hsram4.Init.ExtendedMode = FSMC_EXTENDED_MODE_DISABLE;
hsram4.Init.AsynchronousWait = FSMC_ASYNCHRONOUS_WAIT_DISABLE;
hsram4.Init.WriteBurst = FSMC_WRITE_BURST_DISABLE;
hsram4.Init.PageSize = FSMC_PAGE_SIZE_NONE;
/* Timing */
Timing.AddressSetupTime = 8;
Timing.AddressHoldTime = 0;
Timing.DataSetupTime = 16;
Timing.BusTurnAroundDuration = 0;
Timing.CLKDivision = 1;
Timing.DataLatency = 1;
Timing.AccessMode = FSMC_ACCESS_MODE_A;
/* ExtTiming */
if (HAL_SRAM_Init(&hsram4, &Timing, NULL) != HAL_OK)
{
Error_Handler( );
}
/* USER CODE BEGIN FSMC_Init 2 */
/* USER CODE END FSMC_Init 2 */
}
static void HAL_FSMC_MspInit(void){
/* USER CODE BEGIN FSMC_MspInit 0 */
/* USER CODE END FSMC_MspInit 0 */
GPIO_InitTypeDef GPIO_InitStruct = {0};
if (FSMC_Initialized) {
return;
}
FSMC_Initialized = 1;
/* Peripheral clock enable */
__HAL_RCC_FSMC_CLK_ENABLE();
/** FSMC GPIO Configuration
PF0 ------> FSMC_A0
PE7 ------> FSMC_D4
PE8 ------> FSMC_D5
PE9 ------> FSMC_D6
PE10 ------> FSMC_D7
PE11 ------> FSMC_D8
PE12 ------> FSMC_D9
PE13 ------> FSMC_D10
PE14 ------> FSMC_D11
PE15 ------> FSMC_D12
PD8 ------> FSMC_D13
PD9 ------> FSMC_D14
PD10 ------> FSMC_D15
PD14 ------> FSMC_D0
PD15 ------> FSMC_D1
PD0 ------> FSMC_D2
PD1 ------> FSMC_D3
PD4 ------> FSMC_NOE
PD5 ------> FSMC_NWE
PG10 ------> FSMC_NE3
*/
/* GPIO_InitStruct */
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF12_FSMC;
HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);
/* GPIO_InitStruct */
GPIO_InitStruct.Pin = GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10
|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14
|GPIO_PIN_15;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF12_FSMC;
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
/* GPIO_InitStruct */
GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_14
|GPIO_PIN_15|GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_4
|GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF12_FSMC;
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
/* GPIO_InitStruct */
GPIO_InitStruct.Pin = GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF12_FSMC;
HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);
/* USER CODE BEGIN FSMC_MspInit 1 */
/* USER CODE END FSMC_MspInit 1 */
}
需要注意几点:
1、默认的生成代码对SRAM是写保护的,也就是说写操作会导致HardFault。需要手动将写保护关掉,操作后如下:
hsram4.Init.WriteOperation = FSMC_WRITE_OPERATION_ENABLE;
2、FSMC的数据建立时间、地址建立时间、地址保持时间等参数配置需要综合考虑读写效率、数据稳定性等因素自行调试,本项目调试出来的参数如下:
Timing.AddressSetupTime = 8;
Timing.AddressHoldTime = 0;
Timing.DataSetupTime = 16;
Timing.BusTurnAroundDuration = 0;
Timing.CLKDivision = 1;
Timing.DataLatency = 1;
3.2、DM9000初始化
由于本方案采用A0地址线,NE3片选,因此是挂在SRAM3的地址位置的,对应的读写地址配置应该是:
这里需要注意:A0地址线并非当地址使用,而是作为DM9000的CMD信号使用,具体见《DM9000A中文资料.pdf》

#define NET_BASE_ADDR 0x68000000
#define NET_REG_ADDR (*((volatile uint16_t *) NET_BASE_ADDR))
#define NET_REG_DATA (*((volatile uint16_t *) (NET_BASE_ADDR + 0x00000002)))
读写函数封装示例:
uint8_t dm9k_ReadReg(uint8_t reg)
{
NET_REG_ADDR = reg;
return (NET_REG_DATA);
}
void dm9k_WriteReg(uint8_t reg, uint8_t writedata)
{
NET_REG_ADDR = reg;
NET_REG_DATA = writedata;
}
按照《DM9000A编程指导.pdf》初始化,伪代码如下:
void dm9k_init(void)
{
//复位DM9000,复位步骤参考<DM9000 Application Notes V1.22>手册29页
//DM9000硬件复位
//第一步:设置GPCR寄存器(0X1E)的bit0为1
//第二步:设置GPR寄存器(0X1F)的bit1为0,//
//第三步:软件复位DM9000
//等待DM9000软复位完成
//第四步:软件复位完成,进入正常工作模式
//第五步:第二次软件复位DM9000
//第六步:软件复位完成,进入正常工作模式
//第七步:设置PHY
dm9k_WriteReg(DM9000_REG_GPR, DM9000_PHY_OFF); /* 关闭 PHY ,进行 PHY 设置*/
dm9k_phy_write(0x00, 0x8000); /* 重置 PHY 的寄存器 */
dm9k_phy_write(0x04, 0x01e1); /* 设置 自适应模式相容表 */
dm9k_phy_write(0x00, 0x1000); /* 设置 基本连接模式 */
dm9k_WriteReg(DM9000_REG_GPR, DM9000_PHY_ON); /* 结束 PHY 设置, 开启 PHY */
HAL_Delay(2000);
}
3.3、读取验证
在初始化后,通过读取DM9000寄存器参数,用于确认通讯、数据收发、PHY配置是否正常,示例代码如下:
printf("DM9000_PID_VID is:%x\n",dm9k_ReadID());
printf(" DM9000_REG_NSR is:%x\n",dm9k_ReadReg(DM9000_REG_NSR));
printf(" DM9000_PHY_RES1 is:%x\n",dm9k_phy_read(0X01));
打印的log示例如下:
DM9000_PID_VID is:a469000
DM9000_REG_NSR is:40
DM9000_PHY_RES1 is:7869
3.4、数据收发封装
验证MCU与DM9000通讯正常后,便可进行收发封装,给到以太网协议使用,如Lwip、Ecat、Eip等。封装伪代码如下:
uint16_t dm9k_receive_packet(void)
{
uint16_t ReceiveData[1600];
uint8_t rx_int_count = 0;
uint32_t rx_checkbyte;
uint16_t rx_status, rx_length;
uint8_t jump_packet;
uint16_t i;
uint16_t calc_len;
uint16_t calc_MRR;
do
{
jump_packet = 0; /* 清除跳包动作 */
dm9k_ReadReg(DM9000_REG_MRCMDX); /* 读取内存数据,地址不增加 */
/* 计算内存数据位置 */
calc_MRR = (dm9k_ReadReg(DM9000_REG_MRRH) << 8) + dm9k_ReadReg(DM9000_REG_MRRL);
rx_checkbyte = dm9k_ReadReg(DM9000_REG_MRCMDX); /* */
if(rx_checkbyte == DM9000_PKT_RDY) /* 取 */
{
/* 读取封包相关资讯 及 长度 */
NET_REG_ADDR = DM9000_REG_MRCMD;
rx_status = NET_REG_DATA;
rx_length = NET_REG_DATA;
/* 若收到超过系统可承受的封包,此包跳过 */
if(rx_length > Max_Ethernet_Lenth)
jump_packet = 1;
/* 若收到的广播或多播包超过特定长度,此包跳过 */
if(rx_status & 0x4000)
{
if(rx_length > Max_Broadcast_Lenth)
jump_packet = 1;
}
/* 计算下一个包的指针位 , 若接收长度为奇数,需加一对齐偶字节。*/
/* 若是超过 0x3fff ,则需回归绕到 0x0c00 起始位置 */
calc_MRR += (rx_length + 4);
if(rx_length & 0x01) calc_MRR++;
if(calc_MRR > 0x3fff) calc_MRR -= 0x3400;
if(jump_packet == 0x01)
{
/* 将指针移到下一个包的包头位置 */
dm9k_WriteReg (DM9000_REG_MRRH, (calc_MRR >> 8) & 0xff);
dm9k_WriteReg (DM9000_REG_MRRL, calc_MRR & 0xff );
continue;
}
/* 开始将内存的资料搬到到系统中,每次移动一个 word */
calc_len = (rx_length + 1) >> 1;
for(i = 0 ; i < calc_len ; i++)
ReceiveData[i] = NET_REG_DATA;
/* 将包长回报给 TCP/IP 上层,并减去最後 4 BYTE 的 CRC-32 检核码 */
receiveLen_DM9000 = rx_length - 4;
memcpy((unsigned char*)receiveBuffer_DM9000,(uint8_t *)ReceiveData,receiveLen_DM9000);
rx_int_count++; /* 累计收包次数 */
if(calc_MRR != ((dm9k_ReadReg(DM9000_REG_MRRH) << 8) + dm9k_ReadReg(DM9000_REG_MRRL)))
{
/*若是指针出错,将指针移到下一个包的包头位置 */
dm9k_WriteReg(DM9000_REG_MRRH, (calc_MRR >> 8) & 0xff);
dm9k_WriteReg(DM9000_REG_MRRL, calc_MRR & 0xff);
}
dm9k_WriteReg(DM9000_REG_MRRH, (calc_MRR >> 8) & 0xff);
dm9k_WriteReg(DM9000_REG_MRRL, calc_MRR & 0xff);
return receiveLen_DM9000;
}
else
{
if(rx_checkbyte == DM9000_PKT_NORDY) /* 未收到包 */
{
dm9k_WriteReg(DM9000_REG_ISR, 0x3f); /* */
}
else
{
dm9k_err_reset(); /* 接收指针出错,重置 */
}
return (0);
}
}while(rx_int_count < Max_Int_Count); /* 是否超过最多接收封包计数 */
return 0;
}
int dm9k_send_packet(uint8_t *p_char, uint16_t length)
{
uint16_t SendLength = length;
uint16_t *SendData = (uint16_t *) p_char;
uint16_t i;
uint16_t calc_len;
__IO uint16_t calc_MWR;
uint32_t timer2;
/* 设置传送计数 */
NET_REG_ADDR = DM9000_REG_MWCMD;
/* 开始将系统的资料搬到到内存中,每次移动一个 word */
calc_len = (SendLength + 1) >> 1;
for(i = 0; i < calc_len; i++)
NET_REG_DATA = SendData[i];
dm9k_WriteReg(DM9000_REG_TXPLH, (SendLength >> 8) & 0xff); /* 设置传送封包的长度 */
dm9k_WriteReg(DM9000_REG_TXPLL, SendLength & 0xff);
dm9k_WriteReg(DM9000_REG_TCR, DM9000_TCR_SET); /* 进行传送 */
timer2=HAL_GetTick();
while(dm9k_ReadReg(DM9000_REG_TCR)&0x01)
{
if(HAL_GetTick()-timer2>=100)
break;
}
dm9k_WriteReg(DM9000_REG_NSR, 0x2c);
dm9k_WriteReg(DM9000_REG_IMR , DM9000_IMR_OFF);
return 0;
}
四、经验总结
4.1、DM9000的驱动大体流程
1、配置FSMC:可通过cubemx生成解决
2、DM9000初始化:重启,设置PHY参数,写MAC及IP地址等
3、读DM9000版本号确认通讯、数据正常
4、读取DM9000的PHY的连接状态,用于确认第2步的配置是否有效
5、进入正常的以太网收发流程
4.2、可能出现的问题
1、读取PHY的连接状态显示无连接,主要是两个原因
(1)网线没插好,通过看指示灯可判断
(2)在初始化阶段给PHY上电后,间隔时间太短导致NSR寄存器未刷新

2、收到的数据有错误,可能由两个原因
(1)FSMC配置的读写时间参数不合适,导致数据未正常读取,调整配置参数解决
(2)通讯干扰,检查板子是否对高速信号有处理,如等长,包地等错误,必要的话上示波器检查

文内示例见咸鱼ID:tb764914262
「智能机器人开发者大赛」官方平台,致力于为开发者和参赛选手提供赛事技术指导、行业标准解读及团队实战案例解析;聚焦智能机器人开发全栈技术闭环,助力开发者攻克技术瓶颈,促进软硬件集成、场景应用及商业化落地的深度研讨。 加入智能机器人开发者社区iRobot Developer,与全球极客并肩突破技术边界,定义机器人开发的未来范式!
更多推荐
所有评论(0)