Skip to content

概念先知道

  • 以太网 MAC/PHY:MAC 是芯片里的“收发信机”(负责组帧/解帧),PHY 是外部芯片(负责把数字信号变成网线信号),两者配合才能上网。
  • RMII:一种简化的 MAC-PHY 接口(50MHz 时钟),比标准 MII 少一半引脚。
  • ARP:局域网里“问谁有这个 IP”的协议,例程发送一个 ARP 应答帧来验证链路。

例程功能简介

本页对应博流官方 SDK 的 emac_basic 例程(examples/peripherals/emac/emac_basic),演示 RMII 以太网 MAC 的帧收发:

  • 初始化 EMAC 与外部 PHY(LAN8720 等),完成链路协商;
  • 例程构造并发送一个 ARP 应答帧,同时统计 TX/RX 成功与错误帧数;
  • 通过串口日志与对端抓包可验证以太网链路是否打通。
  • 同族例程examples/peripherals/emac/):lwip_emac(集成 lwIP 协议栈,可跑 TCP/IP 应用)。

操作步骤

1
硬件准备

需要外接以太网 PHY(例程支持 LAN8720 等 RMII PHY)。按 board_emac_rmii_gpio_init 的引脚接 RMII 数据线(TXD/RXD/CLK),并用 board_emac_mdio_gpio_init 接 MDIO/MDC 管理线,再通过网线连接交换机/路由器。

2
进入例程目录

在终端进入 SDK 的 EMAC 例程目录(前提:已按快速开始(Linux)Windows搭建好环境):

cd examples/peripherals/emac/emac_basic
3
编译工程

执行编译命令。Ai-M62(BL616)与 Ai-M61(BL618)同属一个系列,统一填写引脚最少的 bl616 即可:

make CHIP=bl616 BOARD=bl616dk
4
烧录固件

用 USB 线连接开发板,按住 BOOT 键(Ai-M61-32S-Kit 为 IO2)不放、短按 EN/RST 进入下载模式,然后执行烧录(把串口号换成实际值):

make flash CHIP=bl616 COMX=/dev/ttyUSB0
5
运行验证

打开串口助手(波特率 2000000)。例程初始化 PHY 后发送 ARP 应答报文,并统计收发帧数/字节数(tx_success_cntrx_success_cnt 等日志),最后打印 EMAC ARP Packet test end!

代码执行流程

例程从启动到运行的完整流程如下(图中的循环箭头表示反复执行):

例程调用的 API 介绍

board_emac_rmii_gpio_init / board_emac_mdio_gpio_init(...)

初始化 RMII 数据引脚与 MDIO/MDC 管理引脚(按板级端口配置)。

参数:板级端口宏

返回值:无

bflb_emac_init(emac, config)

初始化 EMAC 控制器,配置 MAC 地址、速率/双工等参数。

参数

  • emacx:EMAC 设备句柄(用 bsp_emac_get_device(BSP_EMAC_RMII_DEFAULT_PORT) 获取)
  • config:配置结构体指针

返回值:成功返回 0;失败返回负值错误码

eth_phy_init / eth_phy_autoneg(...)

初始化外部 PHY 并启动自动协商(来自 eth_phy 组件),完成后链路建立。

参数:PHY 控制结构体、MDIO 读写回调等

返回值:成功返回 0;失败返回负值错误码

bflb_emac_queue_tx_push / bflb_emac_feature_control(...)

发送以太网帧:把帧描述符推入发送队列(bflb_emac_queue_tx_push),并先用 bflb_emac_feature_control(EMAC_CMD_SET_TX_EN / SET_RX_EN, true) 使能收发、用 EMAC_CMD_GET_TX_DB_AVAILABLE 查询队列余量;收发完成事件经 emac_irq_cb 回调统计 EMAC_IRQ_EVENT_TX_FRAME / RX_FRAME

参数struct bflb_emac_trans_desc_s 帧描述符(buff_addr 缓冲区地址、data_len 长度)

返回值:成功返回 0;失败返回负值错误码

完整代码

以下为 emac_basic/main.c 完整源码,与官方示例(examples/peripherals/emac/emac_basic)一致;点灯引脚已适配 Ai-M61/62-32S-Kit 板载 RGB 灯,默认折叠,点击展开:

📜 点击展开 emac_basic/main.c 完整代码
c
#include "bflb_mtimer.h"
#include "bflb_name.h"
#include "bflb_emac.h"

#include "eth_phy.h"
#include "ephy_general.h"
#include "ephy_lan8720.h"

#include "board.h"

#define DBG_TAG "MAIN"
#include "log.h"

struct bflb_device_s *emacx;
eth_phy_ctrl_t phy_ctrl;

volatile uint32_t tx_success_cnt = 0;
volatile uint32_t tx_error_cnt = 0;
volatile uint64_t tx_total_size = 0;

volatile uint32_t rx_success_cnt = 0;
volatile uint32_t rx_error_cnt = 0;
volatile uint32_t rx_busy_cnt = 0;
volatile uint64_t rx_total_size = 0;

static const uint8_t arp_data[42] = {
    /* ARP reply to 192.168.123.178(e4:54:e8:ca:31:16): 192.168.123.100 is at 18:b9:05:12:34:56 */
    0xb0, 0x7b, 0x25, 0x00, 0x89, 0x53,                         // dst mac b0:7b:25:00:89:53
    0x18, 0xB9, 0x05, 0x12, 0x34, 0x56,                         // src mac
    0x08, 0x06, 0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, 0x02, // arp reply
    0x18, 0xB9, 0x05, 0x12, 0x34, 0x56,                         // src mac
    0xc0, 0xa8, 0x7b, 0x64,                                     // src ip 192.168.123.100
    0xb0, 0x7b, 0x25, 0x00, 0x89, 0x53,                         // dst mac b0:7b:25:00:89:53
    0xc0, 0xa8, 0x7b, 0xb2                                      // dst ip 192.168.123.178
};

ATTR_NOCACHE_NOINIT_RAM_SECTION __ALIGNED(32) uint8_t eth_tx_buff[2 * 1024];
ATTR_NOCACHE_NOINIT_RAM_SECTION __ALIGNED(32) uint8_t eth_rx_buff[2 * 1024];

void emac_irq_cb(void *arg, uint32_t irq_event, struct bflb_emac_trans_desc_s *trans_desc)
{
    switch (irq_event) {
        case EMAC_IRQ_EVENT_RX_BUSY:
            rx_busy_cnt++;
            break;
        case EMAC_IRQ_EVENT_RX_FRAME:
            rx_success_cnt++;
            rx_total_size += trans_desc->data_len;
            break;

        case EMAC_IRQ_EVENT_RX_CTRL_FRAME:
            rx_success_cnt++;
            rx_total_size += trans_desc->data_len;
            break;

        case EMAC_IRQ_EVENT_RX_ERR_FRAME:
            LOG_W("rx err sta:%d\r\n", trans_desc->err_status);
            rx_error_cnt++;
            break;

        case EMAC_IRQ_EVENT_TX_FRAME:
            tx_success_cnt++;
            tx_total_size += trans_desc->data_len;
            break;

        case EMAC_IRQ_EVENT_TX_ERR_FRAME:
            if (trans_desc->err_status & (~EMAC_TX_STA_ERR_CS)) {
                LOG_W("tx err sta:%d\r\n", trans_desc->err_status);
                tx_error_cnt++;
            } else {
                tx_success_cnt++;
                tx_total_size += trans_desc->data_len;
            }

            break;

        default:
            break;
    }
}

int emac_test_init(void)
{
    int ret;

    /* phy cfg */
    eth_phy_init_cfg_t phy_cfg = {
        .speed_mode = EPHY_SPEED_MODE_AUTO_NEGOTIATION,
        .local_auto_negotiation_ability = EPHY_ABILITY_100M_TX | EPHY_ABILITY_100M_FULL_DUPLEX,
    };

    /* emac cfg */
    struct bflb_emac_config_s emac_cfg = {
        .mac_addr = { 0x18, 0xB9, 0x05, 0x12, 0x34, 0x56 },
        .clk_internal_mode = false,
#if defined(BL616CL) || defined(BL618DG)
        .md_clk_div = 79,
#else
        .md_clk_div = 39,
#endif
        .min_frame_len = 14 + 46 + 4,
        .max_frame_len = 14 + 1500 + 4,
    };

    /* emac init */
    emacx = bsp_emac_get_device(BSP_EMAC_RMII_DEFAULT_PORT);
    if (emacx == NULL) {
        LOG_E("device_get error\r\n");
        return -1;
    }
    bflb_emac_init(emacx, &emac_cfg);
    bflb_emac_irq_attach(emacx, emac_irq_cb, NULL);

    /* scan eth_phy */
    phy_ctrl.mac_mdio_dev = bsp_emac_get_device(BSP_EMAC_MDIO_DEFAULT_PORT);
    if (phy_ctrl.mac_mdio_dev == NULL) {
        LOG_E("mdio device_get error\r\n");
        return -1;
    }

    ret = eth_phy_scan(&phy_ctrl, BSP_EMAC_PHY_DEFAULT_SCAN_START, BSP_EMAC_PHY_DEFAULT_SCAN_END);
    if (ret < 0) {
        return -1;
    }

    /* eth_phy init */
    ret = eth_phy_init(&phy_ctrl, &phy_cfg);
    if (ret < 0) {
        return -1;
    }

    /* LAN8720 Timing Adjustment: When in ref_clk input mode, invert the rx_clk. */
    if( (emac_cfg.clk_internal_mode == false) &&
        (phy_ctrl.phy_drv->phy_id == EPHY_LAN8720_ID)) {
        LOG_W("Invert rx_clk for LAN8720 Timing Adjustment.\r\n");
        bflb_emac_feature_control(emacx, EMAC_CMD_SET_MAC_RX_CLK_INVERT, true);
    }

    /* wait link up */
    LOG_I("waiting link_up...\r\n");
    while (eth_phy_ctrl(&phy_ctrl, EPHY_CMD_GET_LINK_STA, 0) != EPHY_LINK_STA_UP) {
        bflb_mtimer_delay_ms(10);
    }
    LOG_W("EPHY LINK UP\r\n");

    int speed_mode = eth_phy_ctrl(&phy_ctrl, EPHY_CMD_GET_SPEED_MODE, 0);
    if (speed_mode == EPHY_SPEED_MODE_10M_HALF_DUPLEX) {
        LOG_I("eth_phy speed: 10M_HALF_DUPLEX\r\n");
    } else if (speed_mode == EPHY_SPEED_MODE_10M_FULL_DUPLEX) {
        LOG_I("eth_phy speed: 10M_FULL_DUPLEX\r\n");
    } else if (speed_mode == EPHY_SPEED_MODE_100M_HALF_DUPLEX) {
        LOG_I("eth_phy speed: 100M_HALF_DUPLEX\r\n");
    } else if (speed_mode == EPHY_SPEED_MODE_100M_FULL_DUPLEX) {
        LOG_I("eth_phy speed: 100M_FULL_DUPLEX\r\n");
    }

    if (speed_mode == EPHY_SPEED_MODE_10M_FULL_DUPLEX || speed_mode == EPHY_SPEED_MODE_100M_FULL_DUPLEX) {
        bflb_emac_feature_control(emacx, EMAC_CMD_SET_FULL_DUPLEX, true);
    } else {
        bflb_emac_feature_control(emacx, EMAC_CMD_SET_FULL_DUPLEX, false);
    }
    if (speed_mode == EPHY_SPEED_MODE_10M_HALF_DUPLEX || speed_mode == EPHY_SPEED_MODE_10M_FULL_DUPLEX) {
#ifdef BL618DG
        bflb_emac_feature_control(emacx, EMAC_CMD_SET_SPEED_10M, true);
#else
        LOG_E("10M speed not supported!!!!\r\n");
        while(1);
#endif
    } else {
        bflb_emac_feature_control(emacx, EMAC_CMD_SET_SPEED_100M, true);
    }

#if 0
    /* loop back mode */
    eth_phy_ctrl(&phy_ctrl, EPHY_CMD_SET_LOOPBACK_MODE, true);
    LOG_I("eth_phy loopback mode\r\n");

    bflb_emac_feature_control(emacx, EMAC_CMD_SET_FULL_DUPLEX, true);
#endif

    LOG_I("eth_phy init done\r\n\r\n");
    return 0;
}

void emac_test(void)
{
    uint32_t time_node;

    uint32_t tx_cnt_old = 0;
    uint32_t rx_cnt_old = 0;

    uint64_t tx_total_size_old = 0;
    uint64_t rx_total_size_old = 0;

    uint32_t tx_push_cnt = 0;
    uint32_t rx_push_cnt = 0;

    if (emac_test_init() < 0) {
        LOG_E("emac test init falied\r\n");
        return;
    }

    /* tx arp data */
    memcpy(eth_tx_buff, arp_data, sizeof(arp_data));

    struct bflb_emac_trans_desc_s tx_test_desc = {
        .buff_addr = eth_tx_buff,
        .data_len = sizeof(arp_data),
    };

    struct bflb_emac_trans_desc_s rx_test_desc = {
        .buff_addr = eth_rx_buff,
    };

    /* enable tx and rx */
    bflb_emac_feature_control(emacx, EMAC_CMD_SET_TX_EN, true);
    bflb_emac_feature_control(emacx, EMAC_CMD_SET_RX_EN, true);

    time_node = bflb_mtimer_get_time_ms();

    while (1) {
        /* try to push tx */
        if (bflb_emac_feature_control(emacx, EMAC_CMD_GET_TX_DB_AVAILABLE, 0) > 0) {
            if (bflb_emac_queue_tx_push(emacx, &tx_test_desc) == 0) {
                tx_push_cnt += 1;
            }
        }

        /* try to push rx */
        if (bflb_emac_feature_control(emacx, EMAC_CMD_GET_RX_DB_AVAILABLE, 0) > 0) {
            if (bflb_emac_queue_rx_push(emacx, &rx_test_desc) == 0) {
                rx_push_cnt += 1;
            }
        }

        /* get info */
        if (bflb_mtimer_get_time_ms() - time_node > 2 * 1000) {
            time_node = bflb_mtimer_get_time_ms();

            uint32_t tx_db_avail = bflb_emac_feature_control(emacx, EMAC_CMD_GET_TX_DB_AVAILABLE, 0);
            uint32_t rx_db_avail = bflb_emac_feature_control(emacx, EMAC_CMD_GET_RX_DB_AVAILABLE, 0);

            uint64_t tx_size = tx_total_size - tx_total_size_old;
            tx_total_size_old = tx_total_size;
            uint32_t tx_cnt = tx_success_cnt - tx_cnt_old;
            tx_cnt_old = tx_success_cnt;
            LOG_I("TX: Speed: %dMbps, valid_data_speed: %dMbps\r\n", (uint32_t)(tx_cnt * 64 * 8 / 2 / 1000 / 1000), (uint32_t)(tx_size * 8 / 2 / 1000 / 1000));
            LOG_I("    success cnt:%d, error cnt:%d, total size:%lldByte\r\n", tx_success_cnt, tx_error_cnt, tx_total_size);
            LOG_I("    push_cnt:%d, tx_db available:%d\r\n", tx_push_cnt, tx_db_avail);

            uint64_t rx_size = rx_total_size - rx_total_size_old;
            rx_total_size_old = rx_total_size;
            uint32_t rx_cnt = rx_success_cnt - rx_cnt_old;
            rx_cnt_old = rx_success_cnt;
            LOG_I("RX: Speed: %dMbps, valid_data_speed: %dMbps\r\n", (uint32_t)(rx_cnt * 64 * 8 / 2 / 1000 / 1000), (uint32_t)(rx_size * 8 / 2 / 1000 / 1000));
            LOG_I("    success cnt:%d, error cnt:%d, total size:%lldByte\r\n", rx_success_cnt, rx_error_cnt, rx_total_size);
            LOG_I("    push_cnt:%d, rx_db available:%d, busy cnt:%d\r\n", rx_push_cnt, rx_db_avail, rx_busy_cnt);
            LOG_RI("\r\n");

            /* check link sta */
            if (eth_phy_ctrl(&phy_ctrl, EPHY_CMD_GET_LINK_STA, 0) != EPHY_LINK_STA_UP) {
                LOG_W("EPHY LINK DOWN\r\n");
                /* disable tx and rx, and clean tx/rx bd */
                bflb_emac_feature_control(emacx, EMAC_CMD_SET_TX_EN, false);
                bflb_emac_feature_control(emacx, EMAC_CMD_SET_RX_EN, false);
                bflb_emac_bd_ctrl_clean(emacx);

                LOG_I("waiting link_up...\r\n");
                while (eth_phy_ctrl(&phy_ctrl, EPHY_CMD_GET_LINK_STA, 0) != EPHY_LINK_STA_UP) {
                    bflb_mtimer_delay_ms(10);
                }
                LOG_W("EPHY LINK UP\r\n");

                int speed_mode = eth_phy_ctrl(&phy_ctrl, EPHY_CMD_GET_SPEED_MODE, 0);
                if (speed_mode == EPHY_SPEED_MODE_10M_HALF_DUPLEX) {
                    LOG_I("eth_phy speed: 10M_HALF_DUPLEX\r\n");
                } else if (speed_mode == EPHY_SPEED_MODE_10M_FULL_DUPLEX) {
                    LOG_I("eth_phy speed: 10M_FULL_DUPLEX\r\n");
                } else if (speed_mode == EPHY_SPEED_MODE_100M_HALF_DUPLEX) {
                    LOG_I("eth_phy speed: 100M_HALF_DUPLEX\r\n");
                } else if (speed_mode == EPHY_SPEED_MODE_100M_FULL_DUPLEX) {
                    LOG_I("eth_phy speed: 100M_FULL_DUPLEX\r\n");
                }

                if (speed_mode == EPHY_SPEED_MODE_10M_FULL_DUPLEX || speed_mode == EPHY_SPEED_MODE_100M_FULL_DUPLEX) {
                    bflb_emac_feature_control(emacx, EMAC_CMD_SET_FULL_DUPLEX, true);
                } else {
                    bflb_emac_feature_control(emacx, EMAC_CMD_SET_FULL_DUPLEX, false);
                }
                /* enable tx and rx */
                bflb_emac_feature_control(emacx, EMAC_CMD_SET_TX_EN, true);
                bflb_emac_feature_control(emacx, EMAC_CMD_SET_RX_EN, true);

                time_node = bflb_mtimer_get_time_ms();
            }
        }
    }
}

int main(void)
{
    board_init();
    /* emac gpio init */
    board_emac_rmii_gpio_init(BSP_EMAC_RMII_DEFAULT_PORT);
    board_emac_mdio_gpio_init(BSP_EMAC_MDIO_DEFAULT_PORT);

    bflb_mtimer_delay_ms(100);

    LOG_I("EMAC ARP Packet test!\r\n");

    emac_test();

    LOG_I("EMAC ARP Packet test end!\r\n");

    while (1) {
        bflb_mtimer_delay_ms(1000);
    }
}

FAQ

链路起不来(TX/RX 全为 0)

检查 PHY 型号与 eth_phy 驱动是否匹配、RMII 时钟(50MHz)是否正常、网线与对端交换机连接是否可靠;确认 MDIO 能读到 PHY 寄存器。

需要完整 TCP/IP 协议栈吗

本页是裸 MAC 测试。需要联网协议栈时使用同目录下的 lwip_emac 例程(集成 lwIP)。

RX 一直报 RX_BUSY

接收缓冲区被占用或描述符不足,检查 eth_rx_buff 大小与接收中断处理;确认数据缓存一致性(NOCACHE 区或 cache 维护)。

遇到问题?

如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

Released under the MIT License. Build Time 2026-09-11 14:52:23