最新教程下载:http://www.armbbs.cn/forum.php?mod=viewthread&tid=95243
第6章 RL-TCPnet底层驱动说明
本章节为大家讲解RL-TCPnet的底层驱动,主要是STM32自带MAC的驱动实现和PHY的驱动实现。
6.1初学者重要提示
6.2 MAC PHY驱动实现方案
6.3 CMSIS-Driver简介和驱动工作流程
6.4 CMSIS-Driver的PHY底层驱动实现
6.5 CMSIS-Driver的MAC底层驱动实现
6.6 总结
6.1 初学者重要提示
- 学习本章节前,务必学习STM32参考手册中MAC章节的基础知识讲解,非常重要。
- DM9161和DM9162的手册可以在官网地址下载,本章节需要用到部分寄存器:http://www.davicom.com.tw/production-item.php。
- 早期STM32F407开发板使用的PHY芯片是DM9161,不过现在基本已经停产了,当前F407,F429和H7开发板统一使用DM9162。底层代码对这两个芯片都可以正确驱动。
- MAC全称Media Access Control,媒介访问控制。
- PHY全称Physical Interface Transceiver,物理层接口收发器。
6.2 MAC PHY驱动实现方案
STM32F4自带MAC,所以只需外置PHY芯片即可使用以太网,示意图如下:
当前V5开发板使用的PHY芯片是DM9162。反映到硬件设计上,原图如下:
通过这个原理图,我们要注意以下两点:
- 1.8V的电压是PHY芯片DM916x自己产生的。
- PHY芯片的地址由PHYAD[0:3]引脚决定,当前是将PHYAD[0]引脚接了一个上拉电阻,也就是说DM916x的地址是0x01。
教程配套的开发板采用的RMII接口,即下面这种硬件接口方式:
RMII接口降低了 10/100Mbps下微控制器以太网外设与外部PHY间的引脚数。根据IEEE 802.3u标准, MII包括16个数据和控制信号的引脚。RMII规范将引脚数减少为7个(引脚数减少62.5%)。RMII具有以下特性:
- 支持10Mbps和100Mbps的运行速率。
- 参考时钟必须是 50 MHz。
- 相同的参考时钟必须从外部提供给 MAC 和外部以太网 PHY。
- 它提供了独立的2位宽(双位)的发送和接收数据路径,即发生和接收都是占用了两个引脚。
6.3 CMSIS-Driver简介和驱动工作流程
这个是ARM做好的驱动框架,支持的外设如下:
针对不同厂商,ARM会出一个完整的驱动包,比如STM32F4系列,在MDK安装目录的此路径下(前提是大家安装了STM32H7软件包):ARMPACKKeilSTM32F4xx_DFP2.14.0CMSISDriver。
ARM做的这个驱动跟HAL库有什么区别呢?ARM做的这个库要调用到HAL的一些API(H7版的有调用到,F4版的很少调用到,基本是独立的),然后封装了一些比较好用的API,方便用户调用。
关于这些不同外设的驱动文件,它们都有统一的API函数,调用流程如下:
关于这个驱动的流程,大家有个认识即可,网络协议栈会直接调用这些API进行操作,无需用户去调用。
6.4 CMSIS-Driver的PHY底层驱动实现
PHY驱动由CMSIS-Driver软件包提供,当前支持的PHY如下(位于MDK安装路径ARMCMSIS-Driver2.4.0ETH,数字2.4.0表示当前的CMSIS-Driver版本):
这些驱动文件主要分为两类:
- 以ETH开头的,这些芯片是MAC PHY二合一。
- 以PHY开头的,这些芯片仅是个PHY。
CMSIS-Driver现有的驱动里面是没有DM9162,所以需要用户自己实现,这里将DM9162的实现函数逐一为大家做个说明。CMSIS-Driver已经定义好了API,用户实现每个API的具体功能即可。
6.4.1 DM9161和DM9162的区别
早期我们发布的STM32F407开发板的PHY芯片使用的是DM9161,现在这个芯片基本已经停产,所以已经统一改成使用DM9162,这两个型号主要在以下两个地方有区别,其它基本都一样。
- 两个PHY芯片的的ID不一样,DM9161的ID是0x0181B8B1,DM9162的ID是0x0181B8A0。
- 系统刚上电时,DM9161的ID寄存器支持立即读取,但是DM9162不支持,这一点用户在使用的时候要特别注意。但是DM9161和DM9162都支持立即写寄存器BMCR,所以当前的操作就是直接对寄存器BMCR发复位命令,然后再进行相关设置。
对于这两个芯片,了解这两点区别就可以了。另外,这两个芯片的手册和其它的相关知识在这个帖子里面进行了简单的汇总:http://www.armbbs.cn/forum.php?mod=viewthread&tid=19577
6.4.2 函数GetVersion
函数原型:
代码语言:javascript复制static ARM_DRIVER_VERSION GetVersion (void) {
return DriverVersion;
}
函数描述:
用于获取当前的PHY驱动版本。
6.4.3 函数Initialize
函数原型:
代码语言:javascript复制static int32_t Initialize (ARM_ETH_PHY_Read_t fn_read, ARM_ETH_PHY_Write_t fn_write) {
if ((fn_read == NULL) || (fn_write == NULL)) { return ARM_DRIVER_ERROR_PARAMETER; }
if ((PHY.flags & PHY_INIT) == 0U) {
/* Register PHY read/write functions. */
PHY.reg_rd = fn_read;
PHY.reg_wr = fn_write;
PHY.bmcr = 0U;
PHY.flags = PHY_INIT;
}
return ARM_DRIVER_OK;
}
函数描述:
初始化读写PHY芯片所需要的API
函数参数:
- 第1个参数是读PHY芯片API地址。
- 第2个参数是写PHY芯片API地址。
- 返回值,无参数错误返回ARM_DRIVER_OK。有参数错误返回ARM_DRIVER_ERROR_PARAMETER。
6.4.4 函数Uninitialize
函数原型:
代码语言:javascript复制static int32_t Uninitialize (void) {
PHY.reg_rd = NULL;
PHY.reg_wr = NULL;
PHY.bmcr = 0U;
PHY.flags = 0U;
return ARM_DRIVER_OK;
}
函数描述:
复位读写PHY芯片所需要的API。
函数参数:
- 返回值,返回ARM_DRIVER_OK
6.4.5 函数PowerControl
函数原型:
代码语言:javascript复制static int32_t PowerControl (ARM_POWER_STATE state) {
uint16_t val;
switch ((int32_t)state) {
/* 将PHY断电 */
case ARM_POWER_OFF:
/* 初始化状态才可以配置POWER OFF */
if ((PHY.flags & PHY_INIT) == 0U) {
return ARM_DRIVER_ERROR;
}
PHY.flags &= ~PHY_POWER;
PHY.bmcr = BMCR_POWER_DOWN;
/* 设置BMCR寄存器,断电 */
return (PHY.reg_wr(ETH_PHY_ADDR, REG_BMCR, PHY.bmcr));
/* PHY上电,并清除BMCR寄存器 */
case ARM_POWER_FULL:
/* 初始化状态才可以配置POWER FULL */
if ((PHY.flags & PHY_INIT) == 0U) {
return ARM_DRIVER_ERROR;
}
/* 已经处于POWER ON状态,直接返回OK */
if (PHY.flags & PHY_POWER) {
return ARM_DRIVER_OK;
}
/* 读取设备 */
PHY.reg_rd(ETH_PHY_ADDR, REG_PHYIDR1, &val);
/* 读取ID1 */
if (val != PHY_ID1) {
return ARM_DRIVER_ERROR_UNSUPPORTED;
}
PHY.reg_rd(ETH_PHY_ADDR, REG_PHYIDR2, &val);
/* 读取ID2, 此处做了一个特别处理,屏蔽后面8个bit,方便DM9162和DM9161都可以识别,因为这两个
PHY后面的ID不同。
*/
if ((val & 0xFF00) != PHY_ID2) {
return ARM_DRIVER_ERROR_UNSUPPORTED;
}
/* DM916X用不到这个 */
#if (ETH_PHY_REF_CLK_50M != 0)
PHY.reg_rd(ETH_PHY_ADDR, REG_PHYCR2, &val);
val |= PHYCR2_REF_CLK_SELECT;
PHY.reg_wr(ETH_PHY_ADDR, REG_PHYCR2, val);
#endif
PHY.bmcr = 0U;
/* BMCR寄存器清零 */
if (PHY.reg_wr(ETH_PHY_ADDR, REG_BMCR, PHY.bmcr) != ARM_DRIVER_OK) {
return ARM_DRIVER_ERROR;
}
PHY.flags |= PHY_POWER;
return ARM_DRIVER_OK;
/* 不支持低功耗操作 */
case ARM_POWER_LOW:
default:
return ARM_DRIVER_ERROR_UNSUPPORTED;
}
}
函数描述:
用于控制PHY的上电和断电。
函数参数:
- 第1个参数是PHY配置
- ARM_POWER_OFF 表示断电,程序此处做了BMCR寄存器断电操作。
- ARM_POWER_FULL 表示上电,程序此处读取PHY的ID,并清除BMCR寄存器。
- ARM_POWER_LOW 表示低功耗,程序此处不支持。
- 第2个参数是写PHY芯片API地址。
- 返回值,设置正确返回ARM_DRIVER_OK,设置错误返回ARM_DRIVER_ERROR,而ARM_DRIVER_ERROR_UNSUPPORTED表示不支持。
6.4.6 函数SetInterface
函数原型:
代码语言:javascript复制static int32_t SetInterface (uint32_t interface) {
int32_t status;
if ((PHY.flags & PHY_POWER) == 0U) { return ARM_DRIVER_ERROR; }
/* 仅作了RMII接口支持 */
switch (interface) {
case ARM_ETH_INTERFACE_RMII: status = ARM_DRIVER_OK; break;
default:
status = ARM_DRIVER_ERROR_UNSUPPORTED; break;
}
return (status);
}
函数描述:
用于配置使用SMII,RMII还是MII接口外接的PHY芯片。
函数参数:
- 第1个参数设置使用的PHY接口类型。
- ARM_ETH_INTERFACE_MII,Media Independent Interface (MII)
- ARM_ETH_INTERFACE_RMII,Reduced Media Independent Interface (RMII)
- ARM_ETH_INTERFACE_SMII,Serial Media Independent Interface (SMII)
- 返回值,设置正确返回ARM_DRIVER_OK,设置错误返回ARM_DRIVER_ERROR,而ARM_DRIVER_ERROR_UNSUPPORTED表示不支持。
6.4.7 函数SetMode
函数原型:
代码语言:javascript复制static int32_t SetMode (uint32_t mode) {
uint16_t val;
/* 上电状态才可以配置 */
if ((PHY.flags & PHY_POWER) == 0U) { return ARM_DRIVER_ERROR; }
val = PHY.bmcr & BMCR_POWER_DOWN;
/* 速度配置10M或者100M */
switch (mode & ARM_ETH_PHY_SPEED_Msk) {
case ARM_ETH_PHY_SPEED_10M:
break;
case ARM_ETH_PHY_SPEED_100M:
val |= BMCR_SPEED_SELECT;
break;
default:
return ARM_DRIVER_ERROR_UNSUPPORTED;
}
/* 全双工或者半双工配置 */
switch (mode & ARM_ETH_PHY_DUPLEX_Msk) {
case ARM_ETH_PHY_DUPLEX_HALF:
break;
case ARM_ETH_PHY_DUPLEX_FULL:
val |= BMCR_DUPLEX_MODE;
break;
default:
return ARM_DRIVER_ERROR_UNSUPPORTED;
}
/* 自动协商配置使能 */
if (mode & ARM_ETH_PHY_AUTO_NEGOTIATE) {
val |= BMCR_ANEG_EN;
}
/* 回环配置使能,方便回环测试 */
if (mode & ARM_ETH_PHY_LOOPBACK) {
val |= BMCR_LOOPBACK;
}
/* 设置隔离,电气隔离RMII/MII/SMII接口 */
if (mode & ARM_ETH_PHY_ISOLATE) {
val |= BMCR_ISOLATE;
}
PHY.bmcr = val;
return (PHY.reg_wr(ETH_PHY_ADDR, REG_BMCR, PHY.bmcr));
}
函数描述:
用于设置PHY芯片的工作模式。
函数参数:
- 第1个参数设置工作模式,这几项支持或操作。
- 返回值,设置正确返回ARM_DRIVER_OK,设置错误返回ARM_DRIVER_ERROR,而ARM_DRIVER_ERROR_UNSUPPORTED表示不支持。
6.4.8 函数GetLinkState
函数原型:
代码语言:javascript复制static ARM_ETH_LINK_STATE GetLinkState (void) {
ARM_ETH_LINK_STATE state;
uint16_t val = 0U;
if (PHY.flags & PHY_POWER) {
PHY.reg_rd(ETH_PHY_ADDR, REG_BMSR, &val);
}
state = (val & BMSR_LINK_STAT) ? ARM_ETH_LINK_UP : ARM_ETH_LINK_DOWN;
return (state);
}
函数描述:
用于获取网线插拔状态。
函数参数:
- 返回值,返回ARM_ETH_LINK_UP表示网线在连接状态,返回ARM_ETH_LINK_DOWN表示网线处于断开状态。
6.4.9 函数GetLinkInfo
函数原型:
代码语言:javascript复制static ARM_ETH_LINK_INFO GetLinkInfo (void) {
ARM_ETH_LINK_INFO info;
uint16_t val = 0U;
if (PHY.flags & PHY_POWER) {
/* 读取PHY DSCSR 寄存器 */
PHY.reg_rd(ETH_PHY_ADDR, REG_DSCSR, &val);
}
/* 获取速度和双工模式 */
info.speed = ((val & DSCSR_100M_FD)|(val & DSCSR_100M_HD)) ? ARM_ETH_SPEED_100M : ARM_ETH_SPEED_10M;
info.duplex = ((val & DSCSR_100M_FD)|(val & DSCSR_10M_FD)) ? ARM_ETH_DUPLEX_FULL : ARM_ETH_DUPLEX_HALF;
return (info);
}
函数描述:
用于获取速度和双工模式。
函数参数:
- 返回值记录速度(10Mbps或者100Mbps)和双工模式(半双工或者全双工)。
6.5 CMSIS-Driver的MAC底层驱动实现
KEIL已经为STM32F4制作好MAC驱动文件EMAC_STM32F4xx.c。我们这里将相关实现函数为大家做个说明。
6.5.1 函数GetVersion
函数原型:
代码语言:javascript复制static ARM_DRIVER_VERSION GetVersion (void) {
return DriverVersion;
}
函数描述:
用于获取当前的MAC驱动版本。
6.5.2 函数GetCapabilities
函数原型:
代码语言:javascript复制#ifndef EMAC_CHECKSUM_OFFLOAD
#define EMAC_CHECKSUM_OFFLOAD 1
#endif
static const ARM_ETH_MAC_CAPABILITIES DriverCapabilities = {
(EMAC_CHECKSUM_OFFLOAD != 0) ? 1U : 0U, /* checksum_offload_rx_ip4 */
(EMAC_CHECKSUM_OFFLOAD != 0) ? 1U : 0U, /* checksum_offload_rx_ip6 */
(EMAC_CHECKSUM_OFFLOAD != 0) ? 1U : 0U, /* checksum_offload_rx_udp */
(EMAC_CHECKSUM_OFFLOAD != 0) ? 1U : 0U, /* checksum_offload_rx_tcp */
(EMAC_CHECKSUM_OFFLOAD != 0) ? 1U : 0U, /* checksum_offload_rx_icmp */
(EMAC_CHECKSUM_OFFLOAD != 0) ? 1U : 0U, /* checksum_offload_tx_ip4 */
(EMAC_CHECKSUM_OFFLOAD != 0) ? 1U : 0U, /* checksum_offload_tx_ip6 */
(EMAC_CHECKSUM_OFFLOAD != 0) ? 1U : 0U, /* checksum_offload_tx_udp */
(EMAC_CHECKSUM_OFFLOAD != 0) ? 1U : 0U, /* checksum_offload_tx_tcp */
(EMAC_CHECKSUM_OFFLOAD != 0) ? 1U : 0U, /* checksum_offload_tx_icmp */
(ETH_MII != 0) ?
ARM_ETH_INTERFACE_MII :
ARM_ETH_INTERFACE_RMII, /* media_interface */
0U, /* mac_address */
1U, /* event_rx_frame */
1U, /* event_tx_frame */
1U, /* event_wakeup */
(EMAC_TIME_STAMP != 0) ? 1U : 0U /* precision_timer */
#if (defined(ARM_ETH_MAC_API_VERSION) && (ARM_ETH_MAC_API_VERSION >= 0x201U))
, 0U /* reserved bits */
#endif
};
static ARM_ETH_MAC_CAPABILITIES GetCapabilities (void) {
return DriverCapabilities;
}
函数描述:
用于获取MAC的硬件功能。
从当前的宏定义来看,支持发送和接收的IP4,IP6,UDP,TCP和ICMP的硬件校验和计算。
6.5.3 函数Initialize
函数原型:
代码语言:javascript复制static int32_t Initialize (ARM_ETH_MAC_SignalEvent_t cb_event) {
#if defined(RTE_DEVICE_FRAMEWORK_CLASSIC)
GPIO_InitTypeDef GPIO_InitStruct;
const ETH_PIN *io;
#endif
/* 使能SYSCFG时钟 */
RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;
#if (ETH_MII == 0)
SYSCFG->PMC |= SYSCFG_PMC_MII_RMII_SEL;
#else
SYSCFG->PMC &= ~SYSCFG_PMC_MII_RMII_SEL;
#endif
#if defined(RTE_DEVICE_FRAMEWORK_CLASSIC)
/* 配置以太网引脚 */
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF11_ETH;
for (io = eth_pins; io != ð_pins[sizeof(eth_pins)/sizeof(ETH_PIN)]; io ) {
Enable_GPIO_Clock (io->port);
GPIO_InitStruct.Pin = io->pin;
HAL_GPIO_Init (io->port, &GPIO_InitStruct);
}
#else
heth.Instance = ETH;
#endif
/* 清空控制结构体 */
memset ((void *)&Emac, 0, sizeof (EMAC_CTRL));
Emac.cb_event = cb_event;
Emac.flags = EMAC_FLAG_INIT;
return ARM_DRIVER_OK;
}
函数描述:
用于初始化MAC,配置以太网引脚,注册回调函数。
函数参数:
- 第1个参数用于注册回调函数。
- 返回值,固定返回ARM_DRIVER_OK。
6.5.4 函数Uninitialize
函数原型:
代码语言:javascript复制static int32_t Uninitialize (void) {
#if defined(RTE_DEVICE_FRAMEWORK_CLASSIC)
const ETH_PIN *io;
/* 复位以太网引脚配置 */
for (io = eth_pins; io != ð_pins[sizeof(eth_pins)/sizeof(ETH_PIN)]; io ) {
HAL_GPIO_DeInit(io->port, io->pin);
}
#else
heth.Instance = NULL;
#endif
Emac.flags &= ~EMAC_FLAG_INIT;
return ARM_DRIVER_OK;
}
函数描述:
复位初始化。
函数参数:
- 返回值,固定返回ARM_DRIVER_OK。
6.5.5 函数PowerControl
函数原型:
代码语言:javascript复制static int32_t PowerControl (ARM_POWER_STATE state) {
uint32_t hclk, clkdiv;
if ((state != ARM_POWER_OFF) &&
(state != ARM_POWER_FULL) &&
(state != ARM_POWER_LOW)) {
return ARM_DRIVER_ERROR_UNSUPPORTED;
}
switch (state) {
/* 关闭电源 */
case ARM_POWER_OFF:
内容较多,省略未写
break;
/* 低功耗 */
case ARM_POWER_LOW:
return ARM_DRIVER_ERROR_UNSUPPORTED;
/* 上电 */
case ARM_POWER_FULL:
内容较多,省略未写
break;
}
return ARM_DRIVER_OK;
}
函数描述:
用于控制MAC的上电和断电。
函数参数:
- 第1个参数是MAC配置
- ARM_POWER_OFF 表示断电,程序此处做了MAC复位。
- ARM_POWER_FULL 表示上电,程序此处初始化MAC。
- ARM_POWER_LOW 表示低功耗,程序此处不支持。
- 返回值,设置正确返回ARM_DRIVER_OK,设置错误返回ARM_DRIVER_ERROR,而ARM_DRIVER_ERROR_UNSUPPORTED表示不支持。
6.5.6 函数GetMacAddress
函数原型:
代码语言:javascript复制static int32_t GetMacAddress (ARM_ETH_MAC_ADDR *ptr_addr) {
uint32_t val;
if (ptr_addr == NULL) {
return ARM_DRIVER_ERROR_PARAMETER;
}
if ((Emac.flags & EMAC_FLAG_POWER) == 0U) {
return ARM_DRIVER_ERROR;
}
val = ETH->MACA0HR;
ptr_addr->b[5] = (uint8_t)(val >> 8);
ptr_addr->b[4] = (uint8_t)(val);
val = ETH->MACA0LR;
ptr_addr->b[3] = (uint8_t)(val >> 24);
ptr_addr->b[2] = (uint8_t)(val >> 16);
ptr_addr->b[1] = (uint8_t)(val >> 8);
ptr_addr->b[0] = (uint8_t)(val);
return ARM_DRIVER_OK;
}
函数描述:
用于获取MAC地址。
函数参数:
- 第1个参数用于存储获取的MAC地址。
- 返回值,设置正确返回ARM_DRIVER_OK,设置错误返回ARM_DRIVER_ERROR,而ARM_DRIVER_ERROR_PARAMETER表示参数错误。
6.5.7 函数SetMacAddress
函数原型:
代码语言:javascript复制static int32_t SetMacAddress (const ARM_ETH_MAC_ADDR *ptr_addr) {
if (ptr_addr == NULL) {
return ARM_DRIVER_ERROR_PARAMETER;
}
if ((Emac.flags & EMAC_FLAG_POWER) == 0U) {
return ARM_DRIVER_ERROR;
}
/* Set Ethernet MAC Address registers */
ETH->MACA0HR = ((uint32_t)ptr_addr->b[5] << 8) | (uint32_t)ptr_addr->b[4];
ETH->MACA0LR = ((uint32_t)ptr_addr->b[3] << 24) | ((uint32_t)ptr_addr->b[2] << 16) |
((uint32_t)ptr_addr->b[1] << 8) | (uint32_t)ptr_addr->b[0];
return ARM_DRIVER_OK;
}
函数描述:
用于设置本身的MAC地址。包含这个MAC地址的以太网帧才会被这个芯片所接受。也可以通过函数ARM_ETH_MAC_SetAddressFilter设置接收其它的MAC地址。除此之外,还可以通过函数ARM_ETH_MAC_Control 的参数ARM_ETH_MAC_CONFIGURE设置广播和组播。
函数参数:
- 第1个参数是MAC地址。
- 返回值,设置正确返回ARM_DRIVER_OK,设置错误返回ARM_DRIVER_ERROR,而ARM_DRIVER_ERROR_PARAMETER表示参数错误。
6.5.8 函数SetAddressFilter
函数原型:
代码语言:javascript复制static int32_t SetAddressFilter (const ARM_ETH_MAC_ADDR *ptr_addr, uint32_t num_addr) {
uint32_t crc;
if ((ptr_addr == NULL) && (num_addr != 0)) {
return ARM_DRIVER_ERROR_PARAMETER;
}
if ((Emac.flags & EMAC_FLAG_POWER) == 0U) {
return ARM_DRIVER_ERROR;
}
/* 使用单播过滤前三个MAC */
ETH->MACFFR &= ~(ETH_MACFFR_HPF | ETH_MACFFR_HM);
ETH->MACHTHR = 0U; ETH->MACHTLR = 0U;
if (num_addr == 0U) {
ETH->MACA1HR = 0U; ETH->MACA1LR = 0U;
ETH->MACA2HR = 0U; ETH->MACA2LR = 0U;
ETH->MACA3HR = 0U; ETH->MACA3LR = 0U;
return ARM_DRIVER_OK;
}
ETH->MACA1HR = ((uint32_t)ptr_addr->b[5] << 8) | (uint32_t)ptr_addr->b[4] | ETH_MACA1HR_AE;
ETH->MACA1LR = ((uint32_t)ptr_addr->b[3] << 24) | ((uint32_t)ptr_addr->b[2] << 16) |
((uint32_t)ptr_addr->b[1] << 8) | (uint32_t)ptr_addr->b[0];
num_addr--;
if (num_addr == 0U) {
ETH->MACA2HR = 0U; ETH->MACA2LR = 0U;
ETH->MACA3HR = 0U; ETH->MACA3LR = 0U;
return ARM_DRIVER_OK;
}
ptr_addr ;
ETH->MACA2HR = ((uint32_t)ptr_addr->b[5] << 8) | (uint32_t)ptr_addr->b[4] | ETH_MACA2HR_AE;
ETH->MACA2LR = ((uint32_t)ptr_addr->b[3] << 24) | ((uint32_t)ptr_addr->b[2] << 16) |
((uint32_t)ptr_addr->b[1] << 8) | (uint32_t)ptr_addr->b[0];
num_addr--;
if (num_addr == 0U) {
ETH->MACA3HR = 0U; ETH->MACA3LR = 0U;
return ARM_DRIVER_OK;
}
ptr_addr ;
ETH->MACA3HR = ((uint32_t)ptr_addr->b[5] << 8) | (uint32_t)ptr_addr->b[4] | ETH_MACA3HR_AE;
ETH->MACA3LR = ((uint32_t)ptr_addr->b[3] << 24) | ((uint32_t)ptr_addr->b[2] << 16) |
((uint32_t)ptr_addr->b[1] << 8) | (uint32_t)ptr_addr->b[0];
num_addr--;
if (num_addr == 0U) {
return ARM_DRIVER_OK;
}
ptr_addr ;
/* 计算剩余MAC地址的64bit Hash表 */
for ( ; num_addr; ptr_addr , num_addr--) {
crc = crc32_data (&ptr_addr->b[0], 6U) >> 26;
if (crc & 0x20U) {
ETH->MACHTHR |= (1U << (crc & 0x1FU));
}
else {
ETH->MACHTLR |= (1U << crc);
}
}
/* 使能单播和Hash地址过滤 */
ETH->MACFFR |= ETH_MACFFR_HPF | ETH_MACFFR_HM;
return ARM_DRIVER_OK;
}
函数描述:
用于以太网MAC接收地址过滤,通过这个函数可以设置此设备可以接收到的MAC地址(设备本身MAC以外的地址)。MAC还可以通过函数ARM_ETH_MAC_Control 的参数ARM_ETH_MAC_CONFIGURE设置广播和组播。
函数参数:
- 第1个参数是MAC地址列表。
- 第2个参数是MAC地址个数。
- 返回值,设置正确返回ARM_DRIVER_OK,设置错误返回ARM_DRIVER_ERROR,而ARM_DRIVER_ERROR_PARAMETER表示参数错误。
6.5.9 函数SendFrame
函数原型:
代码语言:javascript复制static int32_t SendFrame (const uint8_t *frame, uint32_t len, uint32_t flags) {
uint8_t *dst = Emac.frame_end;
uint32_t ctrl;
if ((frame == NULL) || (len == 0U)) {
return ARM_DRIVER_ERROR_PARAMETER;
}
if ((Emac.flags & EMAC_FLAG_POWER) == 0U) {
return ARM_DRIVER_ERROR;
}
if (dst == NULL) {
/* 启动新的传输帧 */
if (tx_desc[Emac.tx_index].CtrlStat & DMA_TX_OWN) {
/* 传输忙 */
return ARM_DRIVER_ERROR_BUSY;
}
dst = tx_desc[Emac.tx_index].Addr;
tx_desc[Emac.tx_index].Size = len;
}
else {
/* 分步传输 */
tx_desc[Emac.tx_index].Size = len;
}
/* 快速复制数据到ETH-DMA */
for ( ; len > 7U; dst = 8, frame = 8, len -= 8U) {
__UNALIGNED_UINT32_WRITE(&dst[0], __UNALIGNED_UINT32_READ(&frame[0]));
__UNALIGNED_UINT32_WRITE(&dst[4], __UNALIGNED_UINT32_READ(&frame[4]));
}
/* 复制剩余字节 */
for ( ; len > 1U; dst = 2, frame = 2, len -= 2U) {
__UNALIGNED_UINT16_WRITE(&dst[0], __UNALIGNED_UINT16_READ(&frame[0]));
}
if (len > 0U) { dst [0] = frame [0]; }
if (flags & ARM_ETH_MAC_TX_FRAME_FRAGMENT) {
/* 还有数据,记录当前写入位置 */
Emac.frame_end = dst;
return ARM_DRIVER_OK;
}
/* 帧就绪,发送给DMA */
ctrl = tx_desc[Emac.tx_index].CtrlStat & ~DMA_TX_CIC;
#if (EMAC_CHECKSUM_OFFLOAD != 0)
if (Emac.tx_cks_offload) {
/* The following is a workaround for EMAC silicon problem: */
/* "Incorrect layer 3 (L3) checksum is inserted in the sent */
/* IPv4 fragmented packets." */
/* Description: */
/* When automatic checksum insertion is enabled and the packet */
/* is IPv4 frame fragment, then the MAC may incorrectly insert */
/* checksum into the packet. This corrupts the payload data */
/* and generates checksum errors at the receiver. */
uint16_t prot = __UNALIGNED_UINT16_READ(&tx_desc[Emac.tx_index].Addr[12]);
uint16_t frag = __UNALIGNED_UINT16_READ(&tx_desc[Emac.tx_index].Addr[20]);
if ((prot == 0x0008) && (frag & 0xFF3F)) {
/* Insert only IP header checksum in fragmented frame */
ctrl |= DMA_TX_CIC_IP;
}
else {
/* Insert IP header and payload checksums (TCP,UDP,ICMP) */
ctrl |= DMA_TX_CIC;
}
}
#endif
ctrl &= ~(DMA_TX_IC | DMA_TX_TTSE);
if (flags & ARM_ETH_MAC_TX_FRAME_EVENT) { ctrl |= DMA_TX_IC; }
#if (EMAC_TIME_STAMP != 0)
if (flags & ARM_ETH_MAC_TX_FRAME_TIMESTAMP) { ctrl |= DMA_TX_TTSE; }
Emac.tx_ts_index = Emac.tx_index;
#endif
tx_desc[Emac.tx_index].CtrlStat = ctrl | DMA_TX_OWN;
Emac.tx_index ;
if (Emac.tx_index == NUM_TX_BUF) { Emac.tx_index = 0U; }
Emac.frame_end = NULL;
/* 启动帧传输 */
ETH->DMASR = ETH_DMASR_TPSS;
ETH->DMATPDR = 0U;
return ARM_DRIVER_OK;
}
函数描述:
用于控制以太网帧数据的发送。此函数会将用户要发送的数据存入到以太网DMA缓冲里面,而不必等待发送完成,只要有缓冲,就可以继续往里面存数据。
函数参数:
- 第1个参数是要发送的数据地址。
- 第2个参数是发送的字节数。
- 第3个参数支持的配置如下:
6.5.10 函数ReadFrame
函数原型:
代码语言:javascript复制static int32_t ReadFrame (uint8_t *frame, uint32_t len) {
uint8_t const *src = rx_desc[Emac.rx_index].Addr;
int32_t cnt = (int32_t)len;
if ((frame == NULL) && (len != 0U)) {
return ARM_DRIVER_ERROR_PARAMETER;
}
if ((Emac.flags & EMAC_FLAG_POWER) == 0U) {
return ARM_DRIVER_ERROR;
}
/* 快速复制数据到帧缓冲 */
for ( ; len > 7U; frame = 8, src = 8, len -= 8U) {
__UNALIGNED_UINT32_WRITE(&frame[0], __UNALIGNED_UINT32_READ(&src[0]));
__UNALIGNED_UINT32_WRITE(&frame[4], __UNALIGNED_UINT32_READ(&src[4]));
}
/* 复制剩余7字节 */
for ( ; len > 1U; frame = 2, src = 2, len -= 2U) {
__UNALIGNED_UINT16_WRITE(&frame[0], __UNALIGNED_UINT16_READ(&src[0]));
}
if (len > 0U) { frame[0] = src[0]; }
/* 设置此块到ETH-DMA */
rx_desc[Emac.rx_index].Stat = DMA_RX_OWN;
Emac.rx_index ;
if (Emac.rx_index == NUM_RX_BUF) { Emac.rx_index = 0; }
if (ETH->DMASR & ETH_DMASR_RBUS) {
/* 没有缓冲,释放 */
ETH->DMASR = ETH_DMASR_RBUS;
ETH->DMARPDR = 0;
}
return (cnt);
}
函数描述:
用于读取以太网帧数据。
函数参数:
- 第1个参数是读取数据的存储地址。
- 第2个参数存储数据的缓冲大小。
- 返回值,返回数值大于0,表示读取的字节数,返回数值小于0表示出错。
注意事项:
调用此函数前,需要先调用函数ARM_ETH_MAC_Control (ARM_ETH_MAC_CONTROL_RX , 1)使能接收。
6.5.11 函数GetRxFrameSize
函数原型:
代码语言:javascript复制static uint32_t GetRxFrameSize (void) {
uint32_t stat = rx_desc[Emac.rx_index].Stat;
if ((Emac.flags & EMAC_FLAG_POWER) == 0U) {
return (0U);
}
if (stat & DMA_RX_OWN) {
/* DMA使用中 */
return (0U);
}
if (((stat & DMA_RX_ES) != 0) ||
((stat & DMA_RX_FS) == 0) ||
((stat & DMA_RX_LS) == 0)) {
/* 错误,块无效 */
return (0xFFFFFFFFU);
}
return (((stat & DMA_RX_FL) >> 16) - 4U);
}
函数描述:
用于获取接收到的帧大小,此函数会在ARM_ETH_MAC_ReadFrame之前被调用。
函数参数:
- 返回值,返回接收到的数据大小。
注意事项:
帧大小包括MAC地址和接收到数据。此函数返回数值0表示接收缓冲区里面没有数据,如果接收到的数数据大于最大的帧大小或者小于最小的帧大小,都将被函数ARM_ETH_MAC_ReadFrame放弃。
6.5.12 函数GetRxFrameTime
函数原型:
代码语言:javascript复制static int32_t GetRxFrameTime (ARM_ETH_MAC_TIME *time) {
#if (EMAC_TIME_STAMP)
RX_Desc *rxd = &rx_desc[Emac.rx_index];
if ((Emac.flags & EMAC_FLAG_POWER) == 0U) {
return ARM_DRIVER_ERROR;
}
if (rxd->Stat & DMA_RX_OWN) {
/* DMA使用中 */
return ARM_DRIVER_ERROR_BUSY;
}
time->ns = rxd->TimeLo;
time->sec = rxd->TimeHi;
return ARM_DRIVER_OK;
#else
(void)time;
return ARM_DRIVER_ERROR;
#endif
}
函数描述:
用于获取以太网接收帧时间戳。
函数参数:
- 第1个参数用于存储获取的以太网发送帧时间戳。
- 回值,设置正确返回ARM_DRIVER_OK,设置错误返回ARM_DRIVER_ERROR,而ARM_DRIVER_ERROR_UNSUPPORTED表示不支持。
注意事项:
必须在调用函数ARM_ETH_MAC_ReadFrame前,调用此函数。
6.5.13 函数GetTxFrameSize
函数原型:
代码语言:javascript复制static int32_t GetTxFrameTime (ARM_ETH_MAC_TIME *time) {
#if (EMAC_TIME_STAMP)
TX_Desc *txd = &tx_desc[Emac.tx_ts_index];
if ((Emac.flags & EMAC_FLAG_POWER) == 0U) {
return ARM_DRIVER_ERROR;
}
if (txd->CtrlStat & DMA_RX_OWN) {
/* DMA忙 */
return ARM_DRIVER_ERROR_BUSY;
}
if ((txd->CtrlStat & DMA_TX_TTSS) == 0) {
/* 驱动错误,发送时间戳不可用 */
return ARM_DRIVER_ERROR;
}
time->ns = txd->TimeLo;
time->sec = txd->TimeHi;
return ARM_DRIVER_OK;
#else
(void)time;
return ARM_DRIVER_ERROR;
#endif
}
函数描述:
用于获取以太网发送帧时间戳。
函数参数:
- 第1个参数用于存储返回的时间戳。
- 返回值,设置正确返回ARM_DRIVER_OK,设置错误返回ARM_DRIVER_ERROR,而ARM_DRIVER_ERROR_UNSUPPORTED表示不支持。
6.5.14 函数ControlTimer
函数原型:
代码语言:javascript复制static int32_t ControlTimer (uint32_t control, ARM_ETH_MAC_TIME *time) {
#if (EMAC_TIME_STAMP != 0)
if ((Emac.flags & EMAC_FLAG_POWER) == 0U) {
return ARM_DRIVER_ERROR;
}
if ((control != ARM_ETH_MAC_TIMER_GET_TIME) &&
(control != ARM_ETH_MAC_TIMER_SET_TIME) &&
(control != ARM_ETH_MAC_TIMER_INC_TIME) &&
(control != ARM_ETH_MAC_TIMER_DEC_TIME) &&
(control != ARM_ETH_MAC_TIMER_SET_ALARM) &&
(control != ARM_ETH_MAC_TIMER_ADJUST_CLOCK)) {
return ARM_DRIVER_ERROR_PARAMETER;
}
switch (control) {
case ARM_ETH_MAC_TIMER_GET_TIME:
/* 获取当前时间 */
time->sec = ETH->PTPTSHR;
time->ns = ETH->PTPTSLR;
break;
case ARM_ETH_MAC_TIMER_SET_TIME:
/* 设置新时间*/
ETH->PTPTSHUR = time->sec;
ETH->PTPTSLUR = time->ns;
/* 初始TS */
ETH->PTPTSCR |= ETH_PTPTSCR_TSSTI;
break;
case ARM_ETH_MAC_TIMER_INC_TIME:
/* 增加当前时间 */
ETH->PTPTSHUR = time->sec;
ETH->PTPTSLUR = time->ns;
/* 更新 */
ETH->PTPTSCR |= ETH_PTPTSCR_TSSTU;
break;
case ARM_ETH_MAC_TIMER_DEC_TIME:
/* 减少当前时间 */
ETH->PTPTSHUR = time->sec;
ETH->PTPTSLUR = time->ns | 0x80000000U;
/* 更新 */
ETH->PTPTSCR |= ETH_PTPTSCR_TSSTU;
break;
case ARM_ETH_MAC_TIMER_SET_ALARM:
/* 设置闹钟时间 */
ETH->PTPTTHR = time->sec;
ETH->PTPTTLR = time->ns;
/* 使能PTP控制中的时间戳中断 */
ETH->PTPTSCR |= ETH_PTPTSCR_TSITE;
if (time->sec || time->ns) {
/* 使能时间戳触发中断 */
ETH->MACIMR &= ~ETH_MACIMR_TSTIM;
} else {
/* 禁能时间戳触发中断 Disable */
ETH->MACIMR |= ETH_MACIMR_TSTIM;
}
break;
case ARM_ETH_MAC_TIMER_ADJUST_CLOCK:
/* 调整当前时间,精确校准 */
/* 校准因子Q31 (0x80000000 = 1.000000000) */
ETH->PTPTSAR = (uint32_t)(((uint64_t)time->ns * ETH->PTPTSAR) >> 31);
/* 精确的TS时钟校准 */
ETH->PTPTSCR |= ETH_PTPTSCR_TSARU;
break;
}
return ARM_DRIVER_OK;
#else
(void)control;
(void)time;
return ARM_DRIVER_ERROR;
#endif
}
函数描述:
高精度定时器控制。
函数参数:
- 第1个参数是高精度定时器配置选项,支持的配置如下:
- 第2个参数设置时间。
- 返回值,设置正确返回ARM_DRIVER_OK,设置错误返回ARM_DRIVER_ERROR,而ARM_DRIVER_ERROR_UNSUPPORTED表示不支持。
6.5.15 函数Control
函数原型:
代码语言:javascript复制static int32_t Control (uint32_t control, uint32_t arg) {
uint32_t maccr;
uint32_t dmaomr;
uint32_t macffr;
if ((Emac.flags & EMAC_FLAG_POWER) == 0U) {
return ARM_DRIVER_ERROR;
}
if ((control != ARM_ETH_MAC_CONFIGURE) &&
(control != ARM_ETH_MAC_CONTROL_TX) &&
(control != ARM_ETH_MAC_CONTROL_RX) &&
(control != ARM_ETH_MAC_FLUSH) &&
(control != ARM_ETH_MAC_SLEEP) &&
(control != ARM_ETH_MAC_VLAN_FILTER)) {
return ARM_DRIVER_ERROR_PARAMETER;
}
switch (control) {
case ARM_ETH_MAC_CONFIGURE:
maccr = ETH->MACCR & ~(ETH_MACCR_FES | ETH_MACCR_DM |
ETH_MACCR_LM | ETH_MACCR_IPCO);
/* 配置100Mbps或者10Mbps模式 */
switch (arg & ARM_ETH_MAC_SPEED_Msk) {
case ARM_ETH_MAC_SPEED_10M:
#if (ETH_MII == 0)
/* RMII Half Duplex Colision detection does not work */
maccr |= ETH_MACCR_DM;
#endif
break;
case ARM_ETH_SPEED_100M:
maccr |= ETH_MACCR_FES;
break;
default:
return ARM_DRIVER_ERROR_UNSUPPORTED;
}
/* 配置全双工或者半双工模式 */
switch (arg & ARM_ETH_MAC_DUPLEX_Msk) {
case ARM_ETH_MAC_DUPLEX_FULL:
maccr |= ETH_MACCR_DM;
break;
case ARM_ETH_MAC_DUPLEX_HALF:
break;
default:
return ARM_DRIVER_ERROR;
}
/* 配置回环模式 */
if (arg & ARM_ETH_MAC_LOOPBACK) {
maccr |= ETH_MACCR_LM;
}
dmaomr = ETH->DMAOMR & ~(ETH_DMAOMR_RSF| ETH_DMAOMR_TSF);
#if (EMAC_CHECKSUM_OFFLOAD != 0)
/* 使能接收校验和验证 */
if (arg & ARM_ETH_MAC_CHECKSUM_OFFLOAD_RX) {
maccr |= ETH_MACCR_IPCO;
dmaomr |= ETH_DMAOMR_RSF;
}
/* 使能发送校验和产生 */
if (arg & ARM_ETH_MAC_CHECKSUM_OFFLOAD_TX) {
dmaomr |= ETH_DMAOMR_TSF;
Emac.tx_cks_offload = true;
}
else {
Emac.tx_cks_offload = false;
}
#else
if ((arg & ARM_ETH_MAC_CHECKSUM_OFFLOAD_RX) ||
(arg & ARM_ETH_MAC_CHECKSUM_OFFLOAD_TX)) {
/* 驱动程序禁止了硬件校验和 */
return ARM_DRIVER_ERROR;
}
#endif
ETH->DMAOMR = dmaomr;
ETH->MACCR = maccr;
macffr = ETH->MACFFR & ~(ETH_MACFFR_PM | ETH_MACFFR_PAM | ETH_MACFFR_BFD);
/* 使能广播帧接收 */
if ((arg & ARM_ETH_MAC_ADDRESS_BROADCAST) == 0) {
macffr |= ETH_MACFFR_BFD;
}
/* 使能组播帧接收 */
if (arg & ARM_ETH_MAC_ADDRESS_MULTICAST) {
macffr |= ETH_MACFFR_PAM;
}
/* 设置无过滤,所有帧都可以接收 */
if (arg & ARM_ETH_MAC_ADDRESS_ALL) {
macffr |= ETH_MACFFR_PM;
}
ETH->MACFFR = macffr;
break;
case ARM_ETH_MAC_CONTROL_TX:
/* 使能或者禁止MAC发送 */
maccr = ETH->MACCR & ~ETH_MACCR_TE;
dmaomr = ETH->DMAOMR & ~ETH_DMAOMR_ST;
if (arg != 0) {
init_dma ();
maccr |= ETH_MACCR_TE;
dmaomr |= ETH_DMAOMR_ST;
}
ETH->MACCR = maccr;
ETH->DMAOMR = dmaomr;
break;
case ARM_ETH_MAC_CONTROL_RX:
/* 使能或者禁止MAC接收 */
maccr = ETH->MACCR & ~ETH_MACCR_RE;
dmaomr = ETH->DMAOMR & ~ETH_DMAOMR_SR;
if (arg != 0) {
init_dma ();
maccr |= ETH_MACCR_RE;
dmaomr |= ETH_DMAOMR_SR;
}
ETH->MACCR = maccr;
ETH->DMAOMR = dmaomr;
break;
case ARM_ETH_MAC_FLUSH:
/* 清空发送或者接收缓冲 */
if (arg & ARM_ETH_MAC_FLUSH_RX) {
}
if (arg & ARM_ETH_MAC_FLUSH_TX) {
ETH->DMAOMR |= ETH_DMAOMR_FTF;
}
break;
case ARM_ETH_MAC_VLAN_FILTER:
/* 配置VLAN过滤 */
ETH->MACVLANTR = arg;
break;
}
return ARM_DRIVER_OK;
}
函数描述:
用于MAC的配置
函数参数:
- 第1个参数支持的配置如下
- 第2个参数针对第1个参数做的具体配置。
- ARM_ETH_MAC_CONFIGURE 支持的配置:
-
- ARM_ETH_MAC_CONTROL_TX
0表示禁止发送,1表示使能发送。
-
- ARM_ETH_MAC_CONTROL_RX
0表示禁止接收,1表示使能接收。
-
- ARM_ETH_MAC_FLUSH支持的配置:
ARM_ETH_MAC_FLUSH_RX 表示接收清空。
ARM_ETH_MAC_FLUSH_TX 表示发送清空。
-
- VLAN滤波器支持的配置:
6.5.16 函数PHY_Read
函数原型:
代码语言:javascript复制static int32_t PHY_Read (uint8_t phy_addr, uint8_t reg_addr, uint16_t *data) {
uint32_t val, tick;
if ((Emac.flags & EMAC_FLAG_POWER) == 0U) {
return ARM_DRIVER_ERROR;
}
val = ETH->MACMIIAR & ETH_MACMIIAR_CR;
ETH->MACMIIAR = val | ETH_MACMIIAR_MB | ((uint32_t)phy_addr << 11) |
((uint32_t)reg_addr << 6) ;
/* 等待操作完成 */
tick = HAL_GetTick();
do {
if ((ETH->MACMIIAR & ETH_MACMIIAR_MB) == 0U) { break; }
} while ((HAL_GetTick() - tick) < PHY_TIMEOUT);
if ((ETH->MACMIIAR & ETH_MACMIIAR_MB) == 0U) {
*data = ETH->MACMIIDR & ETH_MACMIIDR_MD;
return ARM_DRIVER_OK;
}
return ARM_DRIVER_ERROR_TIMEOUT;
}
函数描述:
用于以太网PHY芯片的读操作。
函数参数:
- 第1个参数是PHY地址。
- 第2个参数是寄存器地址。
- 第3个参数是寄存器写入的数据。
- 返回值,操作正确返回ARM_DRIVER_OK,操作错误返回ARM_DRIVER_ERROR。
6.5.17 函数PHY_Write
函数原型:
代码语言:javascript复制static int32_t PHY_Write (uint8_t phy_addr, uint8_t reg_addr, uint16_t data) {
uint32_t val, tick;
if ((Emac.flags & EMAC_FLAG_POWER) == 0U) {
return ARM_DRIVER_ERROR;
}
ETH->MACMIIDR = data;
val = ETH->MACMIIAR & ETH_MACMIIAR_CR;
ETH->MACMIIAR = val | ETH_MACMIIAR_MB | ETH_MACMIIAR_MW | ((uint32_t)phy_addr << 11) |
((uint32_t)reg_addr << 6) ;
/* 等待操作完成 */
tick = HAL_GetTick();
do {
if ((ETH->MACMIIAR & ETH_MACMIIAR_MB) == 0U) { break; }
} while ((HAL_GetTick() - tick) < PHY_TIMEOUT);
if ((ETH->MACMIIAR & ETH_MACMIIAR_MB) == 0U) {
return ARM_DRIVER_OK;
}
return ARM_DRIVER_ERROR_TIMEOUT;
}
函数描述:
用于以太网PHY芯片的写操作。
函数参数:
- 第1个参数是PHY地址。
- 第2个参数是寄存器地址。
- 第3个参数是寄存器写入的数据。
- 返回值,操作正确返回ARM_DRIVER_OK,操作错误返回ARM_DRIVER_ERROR。
6.6 总结
本章节就为大家讲解这么多,主要是为学习下个章节RL-TCPnet的移植做准备。学完本章后,务必将STM32参考手册中MAC章节读一遍。