TN1305 Technical note
IEEE802.3-2018
STM32F4xx中文参考手册
基于HAL库以及stm32f407芯片使用GPIO模拟SMI接口时序实现SMI的底层函数。
为了让软件SMI移植性更强,设计了SMI句柄,可以指定MDC和MDIO的端口。
typedef struct
{
uint32_t MDC_PIN;
uint32_t MDIO_PIN;
GPIO_TypeDef *MDC_PORT;
GPIO_TypeDef *MDIO_PORT;
} smi_portcfg_t;
SMI端口初始化工作可以分为以下2步:
(1)根据端口使能对应GPIO时钟
(2)配置MDC为推挽输出、配置MDIO为上拉输入
(3)设置MDC为低电平
相关函数如下:
/**
* @brief 使能SMI端口时钟
*
* @param port 端口地址
*/
void smi_enable_clk(GPIO_TypeDef *port)
{
switch ((uint32_t )port)
{
case GPIOA_BASE:
__HAL_RCC_GPIOA_CLK_ENABLE();
break;
case GPIOB_BASE:
__HAL_RCC_GPIOB_CLK_ENABLE();
break;
case GPIOC_BASE:
__HAL_RCC_GPIOC_CLK_ENABLE();
break;
case GPIOD_BASE:
__HAL_RCC_GPIOD_CLK_ENABLE();
break;
case GPIOE_BASE:
__HAL_RCC_GPIOE_CLK_ENABLE();
break;
case GPIOF_BASE:
__HAL_RCC_GPIOF_CLK_ENABLE();
break;
case GPIOG_BASE:
__HAL_RCC_GPIOG_CLK_ENABLE();
break;
case GPIOH_BASE:
__HAL_RCC_GPIOH_CLK_ENABLE();
break;
default:
break;
}
}
/**
* @brief 初始化SMI的端口时钟、模式
*
* @param smi_portcfg SMI配置句柄地址
*/
void smi_init_port(smi_portcfg_t *smi_portcfg)
{
GPIO_InitTypeDef GPIO_Initure;
g_smi_portcfg = *smi_portcfg;
smi_enable_clk(g_smi_portcfg.MDC_PORT);
smi_enable_clk(g_smi_portcfg.MDIO_PORT);
/* 配置MDC为推挽输出 */
GPIO_Initure.Pin = g_smi_portcfg.MDC_PIN;
GPIO_Initure.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_Initure.Pull = GPIO_PULLUP;
GPIO_Initure.Speed = GPIO_SPEED_HIGH;
HAL_GPIO_Init(g_smi_portcfg.MDC_PORT, &GPIO_Initure);
/* 配置MDIO为上拉输入 */
GPIO_Initure.Pin = g_smi_portcfg.MDIO_PIN;
GPIO_Initure.Mode = GPIO_MODE_INPUT;
GPIO_Initure.Pull = GPIO_PULLUP;
GPIO_Initure.Speed = GPIO_SPEED_HIGH;
HAL_GPIO_Init(g_smi_portcfg.MDIO_PORT, &GPIO_Initure);
smi_set_mdc_low();
}
为了提高软件SMI的兼容性,我们没有将MDIO端口设置为开漏输出(避免MDIO没有硬件上拉MDIO端口电平异常问题),而是在主机需要通过MDIO发送数据时配置MDIO为推挽输出,在主机需要通过MDIO接收数据时配置为上拉输入。相关函数如下:
/**
* @brief 设置SMI的MDIO为推挽输出模式
*
*/
void smi_set_mdio_ouput(void)
{
GPIO_InitTypeDef GPIO_Initure;
/* 配置MDIO为推挽输出 */
GPIO_Initure.Pin = g_smi_portcfg.MDIO_PIN;
GPIO_Initure.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_Initure.Pull = GPIO_PULLUP;
GPIO_Initure.Speed = GPIO_SPEED_HIGH;
smi_set_mdio_high();
HAL_GPIO_Init(g_smi_portcfg.MDIO_PORT, &GPIO_Initure);
}
/**
* @brief 设置SMI的MDIO为上拉输入模式
*
*/
void smi_set_mdio_input(void)
{
GPIO_InitTypeDef GPIO_Initure;
/* 配置MDIO为上拉输入 */
GPIO_Initure.Pin = g_smi_portcfg.MDIO_PIN;
GPIO_Initure.Mode = GPIO_MODE_INPUT;
GPIO_Initure.Pull = GPIO_PULLUP;
GPIO_Initure.Speed = GPIO_SPEED_HIGH;
HAL_GPIO_Init(g_smi_portcfg.MDIO_PORT, &GPIO_Initure);
}
SMI的MDC端口只有输出功能,只需要设计MDC端口输出高、低电平函数即可。MDIO端口除了需要设计输出高、低电平函数外,还需要读取MDIO状态,需要设计MDIO读取函数。相关函数如下:
/**
* @brief 设置MDC输出低电平
*
*/
void smi_set_mdc_low(void)
{
HAL_GPIO_WritePin(g_smi_portcfg.MDC_PORT, g_smi_portcfg.MDC_PIN, GPIO_PIN_RESET);
}
/**
* @brief 设置MDC输出高电平
*
*/
void smi_set_mdc_high(void)
{
HAL_GPIO_WritePin(g_smi_portcfg.MDC_PORT, g_smi_portcfg.MDC_PIN, GPIO_PIN_SET);
}
/**
* @brief 设置MDIO输出低电平
*
*/
void smi_set_mdio_low(void)
{
HAL_GPIO_WritePin(g_smi_portcfg.MDIO_PORT, g_smi_portcfg.MDIO_PIN, GPIO_PIN_RESET);
}
/**
* @brief 设置MDIO输出高电平
*
*/
void smi_set_mdio_high(void)
{
HAL_GPIO_WritePin(g_smi_portcfg.MDIO_PORT, g_smi_portcfg.MDIO_PIN, GPIO_PIN_SET);
}
/**
* @brief 获取MDIO输入电平
*
* @return uint8_t 0-低电平 1-高电平
*/
uint8_t smi_read_mdio(void)
{
return HAL_GPIO_ReadPin(g_smi_portcfg.MDIO_PORT, g_smi_portcfg.MDIO_PIN);
}
SMI协议规定了不同位长度的字段,为了灵活实现数据发送,设计了一个可以写入n位数据的函数:
/**
* @brief 通过SMI写入n位数据
*
* @param data 数据地址
* @param count 数据长度(bit)
*/
void smi_write_n_bit(uint8_t *data, uint8_t count)
{
int i;
uint8_t writeData;
for (i = 0; i < count; i++)
{
writeData = data[i / 8];
smi_set_mdc_low();
if ((writeData >> (7 - i % 8) & 0x01) == 0x01)
{
smi_set_mdio_high();
}
else
{
smi_set_mdio_low();
}
smi_delay;
smi_set_mdc_high();
smi_delay;
}
smi_set_mdc_low();
}
这里要注意:写入数据时,主机在下降沿切换数据,PHY设备在上升沿采样数据
SMI协议规定了不同位长度的字段,为了灵活实现数据读取,设计了一个可以读取n位数据的函数:
/**
* @brief 通过SMI读取n位数据
*
* @param data 数据地址
* @param count 数据长度(bit)
*/
void smi_read_n_bit(uint8_t *data, uint8_t count)
{
int i;
for (i = 0; i < count; i++)
{
smi_set_mdc_low();
smi_delay;
if (smi_read_mdio() == 1)
{
data[i / 8] |= (1 << (7 - i % 8));
}
smi_set_mdc_high();
smi_delay;
}
smi_set_mdc_low();
}
这里要注意:主机在读操作时(从TA字段开始),MDIO数据在上升沿被切换,在下降沿被主机采样(在发送完寄存器地址后,第1个下降沿就是TA的首个bit)
SMI协议层的函数和底层函数解耦,这里设计了2个函数分别按照SMI协议读、写PHY设备的寄存器。
/**
* @brief 读取PHY设备的寄存器值
*
* @param phyAddr PHY设备地址
* @param regAddr 寄存器地址
* @return uint16_t 寄存器值
*/
uint16_t smi_read_reg(uint8_t phyAddr, uint8_t regAddr)
{
// 切换MDIO为推挽输出模式
smi_set_mdio_ouput();
// 前导码:32bit 1b
uint32_t preambleData = SMI_FRAME_PREAMBLE_VALUE;
smi_write_n_bit((uint8_t *)&preambleData, SMI_FRAME_PREAMBLE_BIT_LEN);
// 帧起始:2bit 01b
uint8_t stData = SMI_FRAME_ST_VALUE << 6;
smi_write_n_bit(&stData, SMI_FRAME_ST_BIT_LEN);
// 操作:2bit 10b(读取)
uint8_t opData = SMI_FRAME_OP_READ_VALUE << 6;
smi_write_n_bit(&opData, SMI_FRAME_OP_BIT_LEN);
// PHY地址(5bit)+ 寄存器地址(5bit)
uint8_t praddr[2] = {0};
praddr[0] = (phyAddr << 5);
praddr[0] |= ((regAddr & 0x1C) >> 2);
praddr[1] = ((regAddr & 0x3) << 6);
smi_write_n_bit(praddr, SMI_FRAME_PRADDR_BIT_LEN);
// 切换MDIO为上拉输入模式
smi_set_mdio_input();
// 获取MDIO的TA状态,为0直接读取16bit数据
uint8_t taData = 0;
smi_read_n_bit(&taData, SMI_FRAME_READ_TA_BIT_LEN);
if ((taData & 0x40) != 0x00)
{
printf("%s", SMI_DEBUG_PRINTF == 1 ? "Error : TA bit2 value is 1\r\n" : "");
}
// 读取数据,16bit
uint8_t regValue[2] = {0};
smi_read_n_bit(®Value[0], 16);
// 切换MDIO为上拉输入模式,驱动MDIO为高阻态
smi_set_mdio_input();
return (regValue[0] << 8) | (regValue[1]);
}
该函数按照SMI读寄存器协议,完成对PHY设备寄存器的读取。
/**
* @brief 设置PHY设备的寄存器值
*
* @param phyAddr PHY设备地址
* @param regAddr 寄存器地址
* @param regVal 寄存器值
*/
void smi_set_reg(uint8_t phyAddr, uint8_t regAddr, uint16_t regVal)
{
// 切换MDIO为推挽输出模式
smi_set_mdio_ouput();
// 前导码:32bit 1b
uint32_t preambleData = SMI_FRAME_PREAMBLE_VALUE;
smi_write_n_bit((uint8_t *)&preambleData, SMI_FRAME_PREAMBLE_BIT_LEN);
// 帧起始:2bit 01b
uint8_t stData = SMI_FRAME_ST_VALUE << 6;
smi_write_n_bit(&stData, SMI_FRAME_ST_BIT_LEN);
// 操作:2bit 10b(读取)
uint8_t opData = SMI_FRAME_OP_WRITE_VALUE << 6;
smi_write_n_bit(&opData, SMI_FRAME_OP_BIT_LEN);
// PHY地址(5bit)+ 寄存器地址(5bit)
uint8_t praddr[2] = {0};
praddr[0] = (phyAddr << 5);
praddr[0] |= ((regAddr & 0x1C) >> 2);
praddr[1] = ((regAddr & 0x3) << 6);
smi_write_n_bit(praddr, SMI_FRAME_PRADDR_BIT_LEN);
// 设置MDIO的TA状态,2bit,10b
uint8_t taData = SMI_FRAME_WRITE_TA;
smi_write_n_bit(&taData, SMI_FRAME_WRITE_TA_BIT_LEN);
// 设置,16bit
uint8_t regValue[2] = {0};
regValue[0] = regVal >> 8;
regValue[1] = regVal & 0xFF;
smi_write_n_bit(®Value[0], 16);
// 切换MDIO为上拉输入模式,驱动MDIO为高阻态
smi_set_mdio_input();
}
该函数按照SMI写寄存器协议,完成对PHY设备寄存器的设置。
工程文件已经上传至CSDN,有需要的可以直接下载。
https://download.csdn.net/download/kevin1499/88768490