MyCollege1999 2025-08-29 15:58 采纳率: 59.2%
浏览 6

Attiny84A单片机mdio读phy不对

环境是Attiny84A对接phy 88q2112,Attiny mdio访问phy时,读device1, register2的值老是不对,不知道怎么调试。
代码如下:

#if CODE_DESC("Clause45")
// 定义ATtiny84A的引脚 - 使用PORTB
#define MDIO_DIR_REG DDRB
#define MDIO_OUT_REG PORTB
#define MDIO_IN_REG PINB
#define MDIO_PIN PB1

#define MDC_DIR_REG DDRB
#define MDC_OUT_REG PORTB
#define MDC_PIN PB0

// MDIO线方向控制宏
#define MDIO_AS_OUTPUT() (MDIO_DIR_REG |= (1 << MDIO_PIN))
#define MDIO_AS_INPUT() (MDIO_DIR_REG &= ~(1 << MDIO_PIN))
#define MDIO_HIGH() (MDIO_OUT_REG |= (1 << MDIO_PIN))
#define MDIO_LOW() (MDIO_OUT_REG &= ~(1 << MDIO_PIN))
#define MDIO_READ() (MDIO_IN_REG & (1 << MDIO_PIN))

#define MDC_HIGH() (MDC_OUT_REG |= (1 << MDC_PIN))
#define MDC_LOW() (MDC_OUT_REG &= ~(1 << MDC_PIN))

// Clause 45 操作码定义
#define C45_ADDR_OPCODE  0x00  // 地址操作
#define C45_WRITE_OPCODE 0x01  // 写操作
#define C45_READ_OPCODE  0x03  // 读操作后递增地址
#define C45_READ_INC_OPCODE 0x02 // 读操作

// 函数声明
void mdio_send_preamble(void);
void mdio_send_bits(uint32_t data, uint8_t num_bits);
uint16_t mdio_read_bits(uint8_t num_bits);
void mdio_idle(void);
void mdio_c45_send_header(uint8_t phy_addr, uint8_t dev_addr, uint16_t reg_addr, uint8_t opcode);
//uint16_t mdio_c45_read(uint8_t phy_addr, uint8_t dev_addr, uint16_t reg_addr);
void mdio_c45_set_dev_addr(uint8_t phy_addr, uint8_t dev_addr);

// 发送32位前导码
void mdio_send_preamble(void) {
    MDIO_AS_OUTPUT();
    for (uint8_t i = 0; i < 32; i++) {
        MDIO_HIGH();
        MDC_HIGH();
        _delay_us(0.4);
        MDC_LOW();
        _delay_us(0.4);
    }
}

// 发送指定数量的比特
void mdio_send_bits(uint32_t data, uint8_t num_bits) {
    MDIO_AS_OUTPUT();

    for (int8_t i = num_bits - 1; i >= 0; i--) {
        if (data & (1UL << i)) {
            MDIO_HIGH();
        } else {
            MDIO_LOW();
        }

        MDC_LOW();
        _delay_us(0.2);
        MDC_HIGH();
        _delay_us(0.4);
        MDC_LOW();
        _delay_us(0.2);
    }
}

// 读取指定数量的比特
uint16_t mdio_read_bits(uint8_t num_bits) {
    uint16_t result = 0;
    MDIO_AS_INPUT();

    for (uint8_t i = 0; i < num_bits; i++) {
        result <<= 1;

        MDC_LOW();
        _delay_us(0.2);
        MDC_HIGH();
        #if 1
        _delay_us(0.3);
        #else
        _delay_us(1);
        #endif

        if (MDIO_READ()) {
            result |= 1;
        }

        #if 1
        _delay_us(0.3);
        #else
        _delay_us(1);
        #endif

        MDC_LOW();
        _delay_us(0.2);
    }
    return result;
}

// 将总线置于空闲状态
void mdio_idle(void) {
    MDC_LOW();
    MDIO_AS_INPUT();
}

// 发送Clause 45帧头
void mdio_c45_send_header(uint8_t phy_addr, uint8_t dev_addr, uint16_t reg_addr, uint8_t opcode) {
    // Clause 45帧格式: 0b00 + PHY_ADDR(5) + OPCODE(2) + DEV_ADDR(5) + REG_ADDR(16)

    // 发送起始符(0b00) + PHY地址(5位) + 操作码(2位)
    uint8_t header_part1 = (0b00 << 6) | ((phy_addr & 0x1F) << 1) | (opcode & 0x03);
    mdio_send_bits(header_part1, 8);

    // 发送设备地址(5位) + 寄存器地址高3位
    uint8_t header_part2 = ((dev_addr & 0x1F) << 3) | ((reg_addr >> 13) & 0x07);
    mdio_send_bits(header_part2, 8);

    // 发送寄存器地址剩余13位
    uint16_t header_part3 = reg_addr & 0x1FFF;
    mdio_send_bits(header_part3, 13);
}

// Clause 45 读取操作
uint16_t mdio_c45_read(uint8_t phy_addr, uint8_t dev_addr, uint16_t reg_addr) {
    // 1. 发送前导码
    mdio_send_preamble();

    // 2. 发送地址帧头 (设置要访问的寄存器地址)
    mdio_c45_send_header(phy_addr, dev_addr, reg_addr, C45_ADDR_OPCODE);

    // 3. Turnaround阶段
    MDIO_AS_INPUT();
    MDC_HIGH();
    _delay_us(1);
    MDC_LOW();
    _delay_us(1);

    // 4. 发送读操作帧头
    mdio_send_preamble(); // 需要新的前导码
    mdio_c45_send_header(phy_addr, dev_addr, reg_addr, C45_READ_OPCODE);

    // 5. Turnaround阶段
    MDIO_AS_INPUT();
    MDC_HIGH();
    _delay_us(1);
    MDC_LOW();
    _delay_us(1);
    MDC_HIGH();
    _delay_us(1);
    MDC_LOW();
    _delay_us(1);

    // 6. 读取16位数据
    uint16_t data = mdio_read_bits(16);

    // 7. 回到空闲状态
    mdio_idle();

//g_walk_times to prove code run here
    g_walk_times = 0x99;
    return data;
}

// 设置设备地址(如果需要)
void mdio_c45_set_dev_addr(uint8_t phy_addr, uint8_t dev_addr) {
    // 通过Clause 22写入MMD Access Control Register
    // 这里使用Clause 22方式设置设备地址
    // 13/14寄存器用于MMD访问控制
    uint16_t mmd_ctrl = (dev_addr << 5) | 0x0000; // 设置设备地址
    // 需要使用Clause 22写入13和14寄存器
}

// 初始化函数
void mdio_init(void) {
    MDC_DIR_REG |= (1 << MDC_PIN);
    MDC_LOW();
    MDIO_AS_INPUT();

    // 等待PHY上电稳定
    _delay_ms(100);
}
#endif
int main(void)
{
    mdio_init();
    uint16_t dev_id = mdio_c45_read(0, 0x1, 0x02);   //dev_id 读出来不对,现在是0xff01,是错的
}

  • 写回答

5条回答 默认 最新

  • 檀越@新空间 2025-08-29 15:59
    关注

    下午好🌅🌅🌅
    本答案参考通义千问

    在使用 Attiny84A 单片机与 PHY 88Q2112 进行 MDIO(Management Data Input/Output)通信 时,如果发现读取 Device 1, Register 2 的值不正确,这可能涉及多个方面的问题。以下是详细的调试步骤和解决方案


    一、问题分析

    1. MDIO通信协议理解

    • MDIO 是 IEEE 802.3 标准中定义的物理层管理接口,用于与 PHY 芯片进行通信。
    • Clause 45 是一种更现代的协议,支持多设备地址(Dev Addr),适用于较新的 PHY 芯片(如 88Q2112)。

    2. 可能的原因

    • MDIO 引脚配置错误:引脚方向未正确设置为输入/输出。
    • MDC 时序控制不准确:MDC(Management Data Clock)是同步信号,必须严格遵循时序要求。
    • 前导码(Preamble)发送不正确:MDIO 通信需要发送 32 位前导码。
    • 帧头格式错误:Clause 45 帧头格式不正确,导致 PHY 无法识别请求。
    • Turnaround 阶段缺失或错误:在读操作中,MDIO 必须切换到输入模式,并等待 MDC 上升沿。
    • 延时不足或过长_delay_us() 函数的延时可能不准确,导致通信失败。

    二、解决方案

    1. 检查并修正 MDIO 引脚配置

    确保 MDIO_PINMDC_PIN 正确映射到 Attiny84A 的实际引脚,并且方向设置正确。

    // 确保 MDIO 和 MDC 引脚已正确配置
    #define MDIO_DIR_REG DDRB
    #define MDIO_OUT_REG PORTB
    #define MDIO_IN_REG PINB
    #define MDIO_PIN PB1
    
    #define MDC_DIR_REG DDRB
    #define MDC_OUT_REG PORTB
    #define MDC_PIN PB0
    

    重点:确保 MDIO_PINMDC_PIN 对应的是你实际使用的引脚。


    2. 确保 MDC 时序正确

    MDC 是同步时钟,必须严格按照标准进行操作。以下是一个推荐的 MDC 时序:

    • 每个 MDC 周期约为 1.25 μs(即 800 kHz)。
    • 在发送数据时,先拉低 MDC,然后在上升沿后发送数据。

    重点:不要使用 _delay_us(0.2) 或其他不精确的延时,建议使用定时器或精确延时函数。


    3. 检查 Clause 45 帧头格式

    根据 Clause 45 协议,帧头格式如下:

    | 位 | 内容 | |----|------| | 0-1 | 起始符 (0b00) | | 2-6 | PHY 地址 (5 bits) | | 7-8 | 操作码 (2 bits) | | 9-13 | 设备地址 (5 bits) | | 14-15 | 寄存器地址高 2 位 | | 16-28 | 寄存器地址其余部分 (13 bits) |

    确保 mdio_c45_send_header() 函数正确构建了帧头。


    4. 修正 mdio_c45_read() 函数中的错误

    目前你的代码中存在一些逻辑错误,比如重复发送前导码和帧头。下面是修正后的版本:

    uint16_t mdio_c45_read(uint8_t phy_addr, uint8_t dev_addr, uint16_t reg_addr) {
        // 1. 发送前导码
        mdio_send_preamble();
    
        // 2. 发送地址帧头
        mdio_c45_send_header(phy_addr, dev_addr, reg_addr, C45_ADDR_OPCODE);
    
        // 3. Turnaround 阶段
        MDIO_AS_INPUT();
        MDC_HIGH();
        _delay_us(1);   // 等待 MDC 上升沿
        MDC_LOW();
        _delay_us(1);
    
        // 4. 发送读操作帧头
        mdio_c45_send_header(phy_addr, dev_addr, reg_addr, C45_READ_OPCODE);
    
        // 5. Turnaround 阶段
        MDIO_AS_INPUT();
        MDC_HIGH();
        _delay_us(1);
        MDC_LOW();
        _delay_us(1);
    
        // 6. 读取16位数据
        uint16_t data = mdio_read_bits(16);
    
        // 7. 回到空闲状态
        mdio_idle();
    
        return data;
    }
    

    重点:不要重复发送前导码,只发送一次即可。


    5. 添加调试信息

    在关键位置添加调试信息(如打印寄存器值、延时时间等),便于定位问题。

    g_walk_times = 0x99;  // 调试标志
    printf("Read register 0x%04X: 0x%04X\n", reg_addr, data);
    

    6. 使用示波器验证信号

    使用示波器检查以下信号:

    • MDIO 数据线:是否在读取时切换为输入。
    • MDC 时钟线:是否符合 800 kHz 左右的频率。
    • 帧头是否正确发送:是否有 32 位前导码 + 帧头。

    三、推荐修改后的完整代码片段

    // 修改后的 mdio_c45_read 函数
    uint16_t mdio_c45_read(uint8_t phy_addr, uint8_t dev_addr, uint16_t reg_addr) {
        // 1. 发送前导码
        mdio_send_preamble();
    
        // 2. 发送地址帧头
        mdio_c45_send_header(phy_addr, dev_addr, reg_addr, C45_ADDR_OPCODE);
    
        // 3. Turnaround 阶段
        MDIO_AS_INPUT();
        MDC_HIGH();
        _delay_us(1);
        MDC_LOW();
        _delay_us(1);
    
        // 4. 发送读操作帧头
        mdio_c45_send_header(phy_addr, dev_addr, reg_addr, C45_READ_OPCODE);
    
        // 5. Turnaround 阶段
        MDIO_AS_INPUT();
        MDC_HIGH();
        _delay_us(1);
        MDC_LOW();
        _delay_us(1);
    
        // 6. 读取16位数据
        uint16_t data = mdio_read_bits(16);
    
        // 7. 回到空闲状态
        mdio_idle();
    
        g_walk_times = 0x99;  // 调试标志
        printf("Read register 0x%04X: 0x%04X\n", reg_addr, data);
    
        return data;
    }
    

    四、总结

    1. 检查引脚配置和方向:确保 MDIO 和 MDC 引脚正确配置。
    2. 确保 MDC 时序正确:使用精确的延时函数。
    3. 修正帧头发送逻辑:避免重复发送前导码。
    4. 添加调试信息:便于跟踪程序执行流程。
    5. 使用示波器验证信号:确认硬件通信正常。

    如果你仍然无法解决问题,请提供具体的 register 2 的预期值实际读取值,我可以进一步帮助你分析问题所在。

    评论

报告相同问题?

问题事件

  • 创建了问题 8月29日