GPIO模拟SMI[MDC/MDIO]协议

文章介绍了如何使用GPIO模拟SMI协议,特别是MDC/MDIO数据传输,用于在没有专用接口的芯片上操作PHY层。SMI是MAC和PHY之间的管理接口,包括MDIO(双向数据线)和MDC(时钟线)。代码示例展示了STM32F4平台上初始化GPIO并执行读写寄存器操作的方法。
摘要由CSDN通过智能技术生成


背景

在某些芯片上可能没有提供MDC/MDIO接口,可以通过GPIO(General Purpose Input/Output)来模拟,GPIO可实现串行输入输出,且一般CPU上会提供很多GPIO接口供用户自定义使用。

每组SMI需要两个GPIO口分别来模拟MDC和MDIO,首先需要保证这两个GPIO口不作其他用途,且相应的复用模式设置为GPIO模式。


一、SMI概念

SMI:串行管理接口(Serial Management Interface),通常直接被称为MDIO接口(Management Data Input/Output Interface)。MDIO最早在IEEE 802.3的第22卷定义,后来在第45卷又定义了增强版本的MDIO,其主要被应用于以太网的MAC和PHY层之间,用于MAC层器件通过读写寄存器来实现对PHY层器件的操作与管理。

MDIO主机(即产生MDC时钟的设备)通常被称为STA(Station Management Entity),而MDIO从机通常被称为MMD(MDIO Management Device)。通常STA都是MAC层器件的一部分,而MMD则是PHY层器件的一部分。

MDIO接口包括两条线,MDIO和MDC,其中MDIO是双向数据线,而MDC是由STA驱动的时钟线。MDC时钟的最高速率一般为2.5MHz,MDC也可以是非固定频率,甚至可以是非周期的。MDIO接口只是会在MDC时钟的上升沿进行采样,而并不在意MDC时钟的频率(类似于I2C接口)。MDIO是一个PHY的管理接口,用来读/写PHY的寄存器,以控制PHY的行为或获取PHY的状态,MDC为MDIO提供时钟。

MDIO原本是为MII总线接口定义的,MII用于连接MAC和PHY,包含两种信号接口:

  1. 一个数据接口用于MAC和PHY之间接收和发送以太网帧数据。

  2. 一个PHY管理接口,即MDIO,用于读写每个PHY的控制寄存器和状态寄存器,以达到控制PHY行为和监控PHY状态的目的。

MDIO是双向的,只支持一个MAC连接最多32个PHY的连接方式,且MAC作为master,PHY作为slave。在写PHY寄存器的时候,由MAC驱动MDIO向PHY写入数据;在读PHY寄存器时,前半段由MAC驱动发送寄存器地址,后半段由PHY驱动回复寄存器的值。

MDC要求由MAC输出,是非周期性的,即不要求提供固定频率的时钟,对于PHY芯片则作为输入,以在上升沿触发MDIO的读写。MDC的时钟频率可以是DC-2.5MHz,即最小的时钟周期为400ns。

MDIO接口有两个版本,通常被称为卷22版本和卷45版本。卷22版本的MDIO接口最多支持连接32个MMD(PHY层设备),每个设备最多支持32个寄存器。卷45版本的MDIO接口最多支持连接32个MMD,32个设备类型,每个设备最多支持64K个寄存器

二、MDIO数据传输协议

MDIO数据格式定义在IEEE 802.3以太网标准中,如下图所示(数据传输顺序为从左至右):

上图中*表示高阻态,这时MDIO的状态由一个外部的1.5KΩ电阻决定。
Preamble+Start:32bits的前导码以及2bit的开始位。
OP Code:2bits的操作码,10表示读,01表示写。
PHYAD:5bits的PHY地址,一般PHY地址从0开始顺序编号,例如6口switch中PHY地址为0-5。
REGAD:5bits的寄存器地址,即要读或写的寄存器。
Turn Around:2bits的TA,在读命令中,MDIO在此时由MAC驱动改为PHY驱动,并等待一个时钟周期准备发送数据。在写命令中,不需要MDIO方向发生变化,则只是等待两个时钟周期准备写入数据。
Data:16bits数据,在读命令中,PHY芯片将读到的对应PHYAD的REGAD寄存器的数据写到Data中,在写命令中,MAC将要写入对应PHYAD的REGAD寄存器的值写入Data中。
Idle:空闲状态,此时MDIO无源驱动,处高阻状态,但一般用上拉电阻使其处在高电平。

三、实现代码示例

代码如下(示例)通过chat-gpt搜索,这个是好东西:

#include "stm32f4xx.h"

#define SMI_CLK_Pin GPIO_Pin_8 // SMI CLK 引脚
#define SMI_DATA_Pin GPIO_Pin_9 // SMI DATA 引脚

void SMI_Init_GPIO(void) {
    
    GPIO_InitTypeDef GPIO_InitStruct;
    
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); // Enable GPIOA clock
    GPIO_InitStruct.GPIO_Pin = SMI_CLK_Pin | SMI_DATA_Pin;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; // 配置为推挽输出模式
    GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
}

uint16_t SMI_ReadRegister(uint32_t phy_addr, uint16_t reg_addr) {

    uint16_t data = 0;
    uint16_t command = 0;
    uint8_t i;
    
    // 发送PRE数据
    for(i = 0; i < 32; i++) {
        GPIO_SetBits(GPIOA, SMI_CLK_Pin);
        GPIO_ResetBits(GPIOA, SMI_CLK_Pin);
    }
    
    // 发送ST数据
    GPIO_ResetBits(GPIOA, SMI_DATA_Pin);
    GPIO_SetBits(GPIOA, SMI_CLK_Pin);
    GPIO_ResetBits(GPIOA, SMI_CLK_Pin);
    GPIO_SetBits(GPIOA, SMI_DATA_Pin);
    
    // 发送OP数据
    command = ((phy_addr & 0x1F) << 5) | ((reg_addr & 0x1F) << 0);
    for(i = 0; i < 14; i++) {
        GPIO_WriteBit(GPIOA, SMI_DATA_Pin, (command >> (13-i)) & 0x01);
        GPIO_SetBits(GPIOA, SMI_CLK_Pin);
        GPIO_ResetBits(GPIOA, SMI_CLK_Pin);
    }
    
    // 接收数据
    GPIO_SetBits(GPIOA, SMI_DATA_Pin); // 配置DATA引脚为输入模式
    for(i = 0; i < 16; i++) {
        GPIO_SetBits(GPIOA, SMI_CLK_Pin);
        data |= GPIO_ReadInputDataBit(GPIOA, SMI_DATA_Pin) << (15 - i);
        GPIO_ResetBits(GPIOA, SMI_CLK_Pin);
    }
    
    return data;
}

void SMI_WriteRegister(uint32_t phy_addr, uint16_t reg_addr, uint16_t data) {

    uint16_t command = 0;
    uint8_t i;
    
    // 发送PRE数据
    for(i = 0; i < 32; i++) {
        GPIO_SetBits(GPIOA, SMI_CLK_Pin);
        GPIO_ResetBits(GPIOA, SMI_CLK_Pin);
    }
    
    // 发送ST数据
    GPIO_ResetBits(GPIOA, SMI_DATA_Pin);
    GPIO_SetBits(GPIOA, SMI_CLK_Pin);
    GPIO_ResetBits(GPIOA, SMI_CLK_Pin);
    GPIO_SetBits(GPIOA, SMI_DATA_Pin);
    
    // 发送OP数据
    command = ((phy_addr & 0x1F) << 5) | ((reg_addr & 0x1F) << 0) | 0x4000;
    for(i = 0; i < 14; i++) {
        GPIO_WriteBit(GPIOA, SMI_DATA_Pin, (command >> (13-i)) & 0x01);
        GPIO_SetBits(GPIOA, SMI_CLK_Pin);
        GPIO_ResetBits(GPIOA, SMI_CLK_Pin);
    }
    
    // 发送DATA数据
    for(i = 0; i < 16; i++) {
        GPIO_WriteBit(GPIOA, SMI_DATA_Pin, (data >> (15-i)) & 0x01);
        GPIO_SetBits(GPIOA, SMI_CLK_Pin);
        GPIO_ResetBits(GPIOA, SMI_CLK_Pin);
    }
}

  • 4
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值