概念先知道
- 以太网 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 应用)。
操作步骤
需要外接以太网 PHY(例程支持 LAN8720 等 RMII PHY)。按 board_emac_rmii_gpio_init 的引脚接 RMII 数据线(TXD/RXD/CLK),并用 board_emac_mdio_gpio_init 接 MDIO/MDC 管理线,再通过网线连接交换机/路由器。
在终端进入 SDK 的 EMAC 例程目录(前提:已按快速开始(Linux)或Windows搭建好环境):
cd examples/peripherals/emac/emac_basic执行编译命令。Ai-M62(BL616)与 Ai-M61(BL618)同属一个系列,统一填写引脚最少的 bl616 即可:
make CHIP=bl616 BOARD=bl616dk用 USB 线连接开发板,按住 BOOT 键(Ai-M61-32S-Kit 为 IO2)不放、短按 EN/RST 进入下载模式,然后执行烧录(把串口号换成实际值):
make flash CHIP=bl616 COMX=/dev/ttyUSB0打开串口助手(波特率 2000000)。例程初始化 PHY 后发送 ARP 应答报文,并统计收发帧数/字节数(tx_success_cnt、rx_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 完整代码
#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

