RX62N入门套件的以太网驱动开发
第一节:前言
本文及后续文章均以瑞萨RX62N Starter Kit+(简称RSK+,下同)为目标环境,所有代码均在此环境中测试通过。本系列文章系私人的自学成果,供交流学习使用,本人以及所属公司不对代码的质量和性能提供任何担保。RX62N是瑞萨开发的复杂指令集CPU产品,在通用CPU中加强了通信能力的同时支持串口通信和USB通信,比较适合类似网络摄像头,车载娱乐系统之类的运算量不大,但是对信息收发能力要求较高的系统。RSK+则是瑞萨公司为了普及此类产品,同时提供驱动等平台相关代码的运行环境而开发的套件。整个套件除了开发板外还有E1在线仿真器,HEW统合开发环境等。
图0 RSK+开发套件
本文的目的是以瑞萨提供的实例代码为基础,分析并记录开发RX62N网络驱动模块的过程,为之后的TCP/IP协议栈移植提供知识预备。
第二节:硬件概要
现代网络通信系统都遵守TCP/IP的分层概念,RSK+环境也一样,它集成了LAN8700i芯片来完成物理层的通信任务(简称:PHY-LSI,下同),而链路层则是由RX62N芯片自己负责,这两层模块之间通过MII标准接口相连。MII是IEEE的通用接口标准,是以太网物理层和链路层通信的通用方式。MII除了标准协议外还有简化版RMII等协议,RSK+使用标准MII连接。具体的端口连接请参见图1。
图1 RSK+中PHY-LSI和RX62N的连接方法
链路层的本质就是MAC管理,所谓的全球唯一的标签就是链路层来实现的。RX62N中提供了ETHERC模块来实现链路层的功能,在图1中,以ET开头的都是ETHERC模块的端口。由此可见,RX62N的网络驱动中最多的代码应该就在ETHERC的控制上了。
第三节:MII接口
MII全称Media Independent Interface:媒体独立接口。在IEEE中,它的一头是MAC链路层芯片,另一头是物理层芯片。
标准的MII接口共有16根线:
Tx_CLK/Rx_CLK: 分别是Tx和Rx的时钟信号。他们都是由PHY-LSI驱动的。
TxD[0...3]/RxD[0...3]: 分别是Tx和Rx的数据传输线。TxD是控制器驱动,RxD是PHY-LSI驱动。
Tx_EN/RX_DV: 分别是Tx和Rx的开关。这两根线都是PHY-LSI驱动。
Tx_ER/Rx_ER: 分别是Tx和Rx的报错线。
CRS/COL: 监听介质是否空闲/监听是否有冲突发生。这两根线都是由PHY驱动,并且只在半双工模式下有效。
除了上述的接口外,MII提供了一个管理用的接口,通过这个接口可以对PHY-LSI进行初期化,设置PHY-LSI的通信方式,以及获取PHY-LSI状态等基本的控制。为了减少端口的数量,MII管理接口以串口通信的方式工作,所以通常叫Serial Management Interface(简称:SMI,下同)。SMI有两个端口:MDC和MDIO。MDC是一个时钟或者说是一个驱动波形,简单的来说就是一个发令枪。无论MDIO上准备了什么数据,准备了多长时间,只有MDC出现对应的波形时,MII才会将MDIO上的数据读入/写出。MDC是单向驱动的,仅仅有ETHERC送给PHY-LSI,相反方向为高阻。而MDIO是双向的,MDC和MDIO的关系类似CPU的地址总线和数据总线的关系。
SMI仅仅只有一个数据端口,通过它的访问都是靠串行的方式进行,这需要严格的时序格式。其读写格式参见图2。
图2 SMI的时序格式
SMI包首先要发送32个1启动通信,之后根据读/写的需求不同填写ST和OP位的数据。PHYAD用来确定PHY-LSI的对象,在某些系统中一个CPU对应多个PHY-LSI,需要通过这个位来选片。在RSK+中仅仅只有一个PHY-LSI,在硬件上就决定了PHYAD的地址,这个地址在以太网基本电路图里有描述。RSK+的物理地址描述可参见下图。

图3 PHY-LSI的物理地址
REGAD是用户需要读写的寄存器地址,不同的PHY-LSI对应的寄存器应该是不同的,RSK+所使用的PHY-LSI拥有编号从0到6的7个寄存器,具体的寄存器编号和作用在之后的章节描述。
SIM不仅仅只规定了时序数据的格式,因为这仅仅是MDIO的特征,按照串行通信理论,SIM还要对MDC和MDIO的时序关系进行规定。我们用四幅图描述RX62N的时序关系。
图4 写入时的时序图
上图中的PIR为ETHERC的PIR寄存器,它负责管理MDC和MDIO,其中的MDC对应了MII的MDC,MDO对应了MII的MDIO中的输出部分。上图显示,MDIO每写入一个数据,就需要三步操作。第一步是让MDC为低电平。第二步是让MDC为高电平。第三步再下降到低电平。电平操作的同时保持MDIO的数据值不变。完成全部写入操作后需要将总线挂起,以免影响其他端口的正常使用。挂起总线的操作请见图5。
图5 写入完成后的挂起总线时序图
图6和图7是读取时序图和读取完成后IDLE操作时序图。
图6 读取时序图
图7 读取操作完成后的IDLE时序图
关于时序操作,为了加深印象先贴出部分驱动样本的代码,看看它的具体实现方法以增加理解深度。首先来看读取PHY-LSI中指定寄存器的内容的代码。
uint16_t _phy_read( uint16_t reg_addr ) { uint16_t data; _phy_preamble(); // 1. 写入32个1表示开始。 _phy_reg_set( reg_addr, PHY_READ ); // 2. 设置ST位为01,OP为10,并同时指定目标寄存器的编号。 _phy_ta_z0(); // 3. TA上做一个Z0放开总线。 _phy_reg_read( &data ); // 4. 读取16位数据。 _phy_ta_z0(); // 5. TA上做一个Z0放开总线。 return( data ); }
这个函数是完全按照图2的时序格式编写的,完全符合SMI标准格式。下面再看看_phy_preamble()函数,按照SMI标准它应该写入32个1来启动通信。
void _phy_preamble( void ) { int16_t i; i = 32; while( i > 0 ){ _phy_mii_write_1(); i—; } }
_phy_preamble的主体就是一个32位的循环,里面用到了向MII写入1的函数:_phy_mii_write_1()。它应该就要涉及到MDC和MDIO的时序关系了。但是在分析这个函数之前,先要描述RX62N的ETHERC模块的MII管理寄存器PIR。PIR有4个位用来管理SMI,它的MDC对应着MII的MDC;MDI和MDO的组合对应MII的MDIO;MMD是唯一一个不和MII直接对应的位,它表征MII的输出和输入方向。按照顺序,四个为的关系是:[MDI][MDO][MMD][MDC]。
void _phy_mii_write_1( void ) { int32_t j; for(j = MDC_WAIT; j > 0; j—){ ETHERC.PIR.LONG = 0x00000006; // MDO=1(出力为1),MMD=1(方向为写入),MDC=0(低电平) } for(j = MDC_WAIT; j > 0; j—){ ETHERC.PIR.LONG = 0x00000007; // MDO=1(出力为1),MMD=1(方向为写入),MDC=1(高电平) } for(j = MDC_WAIT; j > 0; j—){ ETHERC.PIR.LONG = 0x00000007; // MDO=1(出力为1),MMD=1(方向为写入),MDC=1(高电平) } for(j = MDC_WAIT; j > 0; j—){ ETHERC.PIR.LONG = 0x00000006; // MDO=1(出力为1),MMD=1(方向为写入),MDC=0(低电平) } }
_phy_mii_write_1函数其实就是实现了MDC和MDIO的时序波形,先让MDC为0,持续2个时钟后再转为1,持续4个时钟后再降为0,这个过程中MDIO始终保持1,这样就写入1到MII中。其他的几个函数也都是以这个函数为基础实现的。写入0的函数也是类似就不贴代码了。但是_phy_mii_write_1函数中还有一个奇特的数据,那就是MDC_WAIT。在本例中这个数字被定义为2,也就是说每个电平信号要坚持两个for循环时钟。这个数字的来源是什么呢?参见LAN8700/LAN8700i Datasheet中的SMI时序图。

上图中的MDC对应的时钟T1.1最小为400纳秒,它包含一个高电平和一个低电平。根据代码我们发现MDC_WAIT在4个for循环中完成一个T1.1,也就说每个MDC_WAIT应该保证电平维持时间至少为100纳秒。这个数据很微妙,在RX62N的不同运行模式下这个数字应该是不同的,本例中使用2的取值目前还没有数学上的计算结果来支持,按一般理论,应该根据芯片的MIPS已经for循环的汇编步数进行计算,本例中不做详细解读。
第四节:PHY-LSI的操作
第三节描述了通过SMI向PHY-LSI传递数据的方法,然而PHY-LSI到底需要哪些数据呢?图2的MII数据包里DATA到底应该写入什么数据?通过查看PHY-LSI的芯片手册可以找到答案。

图8 LAN8700i的SMI寄存器列表
图8列出了LAN8700i的寄存器分类列表,编号从0到6的寄存器都是MII标准下的寄存器,剩下的大多都是Vendor-specific(供应商定制)。本文的目标是实现一个基础的以太网驱动,只需要操作前7个寄存器就够了。此表中最关键的信息就是寄存器的编号,这在MII通信包里需要明确指定。
以太网协议复杂,相应的控制寄存器比较多,本文的目标仅仅是实现一个基础的以太网驱动,所以不逐一描述7个寄存器的工作特性,而是根据瑞萨公司的实例代码来说明需要设置的寄存器的意义。在以太网驱动中,通过MII对PHY-LSI的操作基本上仅仅局限在初始化,读取状态等功能,首先通过初始化来说明PHY-LSI的用法。
int16_t phy_init( void ) { uint16_t reg; uint32_t count; /* Reset PHY */ _phy_write(BASIC_MODE_CONTROL_REG, 0x8000); // 向基础模式控制寄存器中写入0x8000 count = 0; do { reg = _phy_read(BASIC_MODE_CONTROL_REG); // 读取基础模式控制寄存器,并循环确认有没有重新置位。 count++; } while (reg & 0x8000 && count < PHY_RESET_WAIT); if (count >= PHY_RESET_WAIT) { return R_PHY_ERROR; } else { return R_PHY_OK; } }
BASIC_MODE_CONTROL_REG寄存器的代码编号为0,各个位的含义参见下图。

图9 基础控制寄存器的各个位及其含义
基础控制寄存器的第15位为reset位,将其置1后或引发PHY-LSI的重启,重启成功后会重新置位。这就是代码中置位后再利用一个循环进行读取确认的原因。初始化代码并未对通信速度和方式进行设置,仅仅是简单的重启而已,而设置通信速度和方式的代码被安排到了另两个函数中。
void phy_set_100full( void ) { _phy_write(BASIC_MODE_CONTROL_REG, 0x2100); } void phy_set_10half( void ) { _phy_write(BASIC_MODE_CONTROL_REG, 0x0000); }
这两个函数同样是针对基础控制寄存器进行的控制,对照图9可以理解相应的含义,不再赘述。在PHY-LSI基本设置中,自动协议比较复杂一些,亲参见下面的代码。
int16_t phy_set_autonegotiate( void ) { uint16_t reg; uint32_t count; _phy_write(AN_ADVERTISEMENT_REG, 0x01E1); _phy_write(BASIC_MODE_CONTROL_REG, 0x1200); count = 0; do { reg = _phy_read(BASIC_MODE_STATUS_REG); count++; } while (!(reg & 0x0020) && count < PHY_AUTO_NEGOTIATON_WAIT); if (count >= PHY_AUTO_NEGOTIATON_WAIT) { return R_PHY_ERROR; } else { return ((int16_t)_phy_read(AN_LINK_PARTNER_ABILITY_REG)); } }
phy_set_autonegotiate函数首先设置了专门用于自动协议的寄存器AN_ADVERTISEMENT_REG,再整体设置基础空基寄存器BASIC_MODE_CONTROL_REG,之后等待通信双方的自动协议的交接。
第五章 ETHERC控制器的初始化
在前面的章节中详细介绍了通过SMI接口访问和控制PHY-LSI的各种方法及实例代码的实现理由,然而对担当链路层的ETHERC并未有涉及。链路层的控制涉及较多的TCP/IP协议栈的内容,所以这一层的寄存器数量也较多。RX62N为了简化开发还专门为ETHERC配置了一个EDMAC用与ETHERC和RAM之间的桥梁。这个专门的EDMAC不占用MPU的资源,在不需要程序专门关注的条件下实现数据接口道RAM的传输,更加便捷的同时通信速度也更快。EDMAC和ETHERC的关系见下图。

图10 ETHERC和EDMAC的关系
根据上图可以发现,EDMAC是链路层控制器进行通信的必要手段,所以在ETHERC初始化时也需要同时对EDMAC进行初始化。不同的芯片有不同的初始化顺序和内容,同时,因为编写驱动的要求也不尽相同,对初期化的设置内容也不能一概而论,本节以实例代码为基础描述RX62N的链路层驱动相关的设置。
第一:重新启动EDMAC。
EDMAC的EDMR寄存器中的SWR位被设置为1时,EDMA开始重新启动。启动总共需要64个EDMA周期。在实例代码里,程序等待了0x0100个for循环,这个数字和64个EDMA周期的理论上的关系目前尚不得而知,作为悬念点保留。
第二:初始化ETHERC。
按照基本驱动的设计需求,进行如下的设置。
1,清空ETHERC模块。
2,屏蔽ETHERC的异常响应中断,在本例中,我们不需要处理异常情况。
3,设置链路层的数据包长度。本例中设置为1500+CRC校验码长度,也就是1518个byte。
4,设置包间隔时间为96个bit。
5,设置MAC地址。
6,设置双工方式。注意:当物理层芯片启用自动协商模式时,双工的方式是协商硬件自己决定的,不是通过上位软件设置的结果。
7,使能ETHERC模块。
上述设置中的大多数都是链路层协议相关的设置,具体的设置用户完全可以按照自己的需求重新设置。除了上述的设置外,RX62N还提供了其他的设置寄存器,请参见datasheet完成其他功能的设置。
第三:初始化EDMAC
EDMAC相比ETHERC是个新东西,在功能较为单一的芯片里是没有这个模块的。它负责从ETHERC的SMI接口接收存入RAM,或者把RAM的数据通过SMI交给ETHERC。这些操作对ETHERC方面来说是简单的,EDMAC和ETHERC的交互都是通过固定的寄存器完成,然而EMDAC和RAM的交互则显得苍白许多。好在EDMAC专门设计了一套固定的数据格式,复合这个数据格式的RAM区域被设置到EDMAC后就可以自动进行收发。数据格式见下图。

图11 送信用数据包格式

图12 收信用数据包格式
参见实例代码中关于数据包的定义。
struct Descriptor { __evenaccess uint32_t status; #if __LIT /* Little endian */ __evenaccess uint16_t size; __evenaccess uint16_t bufsize; #else /* Big endian */ __evenaccess uint16_t bufsize; __evenaccess uint16_t size; #endif int8_t *buf_p; struct Descriptor *next; };
收信包和送信包不仅具有数据buffer指针,同时也具有收发过程中体现的错误flag,基于这些flag可以进行通信过程中的错误判断。在初始化EDMAC前需要首先做好这样一个数据格式的buffer,有了这个前提后按照如下顺序初始化EDMAC。
1,清空EDMAC的状态寄存器。
2,设置接收buffer和发送buffer的头指针到EDMAC寄存器。
3,设置FIFO的长度。根据RX62N的datasheet,初始化时这个数字应该被设置为0x0707。参见图1发现,在ETHERC和EDMAC之间有一个发送FIFO和一个接收FIFO,它们用来缓存发送和接收数据。这个FIFO缓存和RAM上的buffer不是同一个东西。
4,设置FIFO发送触发条件阀值。EDMAC并不会在任何时刻都向ETHERC传送发送用的数据,当FIFO中的数据量超过这个阀值时才会想ETHERC发送传输命令。这个阀值不能大于第4步中设置的FIFO长度。
5,设置EDMAC的收信方式。EDMAC的EDRRR寄存器中有一个RR位,当这个位为1时EDMAC开始从ETHERC接收数据。这个接收动作有两个模式,第一个是自动模式:当接收完一个包后RR自动复位,之后将不再接受数据包。第二个是高位软件控制模式。一旦被置位为1后将一直接收数据包,直到上位软件将其置为0。
6,使能EDMAC。
第四:初始化PHY-LSI
除了对链路层芯片控制器ETHERC和EDMAC进行初始化外,还需要对前一章中的描述过的物理层芯片进行初期化,他们主要包含两个API:芯片初始化和自动协商。在第二步的初始化中,对ETHERC的双工模式设置需要底层物理芯片的反馈,在实例驱动中就是在这个API返回值里获取。
int32_t R_Ether_Open(uint32_t ch, uint8_t mac_addr[]) { int32_t i; uint32_t mac_h,mac_l; int16_t phydata; ch = ch; /* Keep compiler happy */ /* Initialize driver */ le0.open = 1; _eth_fifoInit(rxdesc, (uint32_t)ACT); _eth_fifoInit(txdesc, (uint32_t)0); le0.rxcurrent = &rxdesc[0]; le0.txcurrent = &txdesc[0]; le0.mac_addr[0] = mac_addr[0]; le0.mac_addr[1] = mac_addr[1]; le0.mac_addr[2] = mac_addr[2]; le0.mac_addr[3] = mac_addr[3]; le0.mac_addr[4] = mac_addr[4]; le0.mac_addr[5] = mac_addr[5]; /* Initialize EDMAC and ETHERC */ EDMAC.EDMR.BIT.SWR = 1; for( i = 0 ; i < 0x00000100 ; i++ ); /* Reset EDMAC */ /* ETHERC */ /* TODO: Check bit 5 */ ETHERC.ECSR.LONG = 0x00000037; /* clear all ETHERC status BFR, PSRTO, LCHNG, MPD, ICD */ /* TODO: Check bit 5 */ ETHERC.ECSIPR.LONG = 0x00000020; /* disable ETHERC status change interrupt */ ETHERC.RFLR.LONG = 1518; /* ether payload is 1500+ CRC */ ETHERC.IPGR.LONG = 0x00000014; /* Intergap is 96-bit time */ mac_h = ((uint32_t)mac_addr[0] << 24) | \ ((uint32_t)mac_addr[1] << 16) | \ ((uint32_t)mac_addr[2] << 8 ) | \ (uint32_t)mac_addr[3]; mac_l = ((uint32_t)mac_addr[4] << 8 ) | \ (uint32_t)mac_addr[5]; if(mac_h == 0 && mac_l == 0) { /* * If 2nd parameter is 0, the MAC address should be acquired from the system, * depending on user implementation. (e.g.: EEPROM) */ } else { ETHERC.MAHR = mac_h; ETHERC.MALR.LONG = mac_l; } /* EDMAC */ EDMAC.EESR.LONG = 0x47FF0F9F; /* clear all ETHERC and EDMAC status bits */ EDMAC.RDLAR = le0.rxcurrent; /* initialize Rx Descriptor List Address */ EDMAC.TDLAR = le0.txcurrent; /* initialize Tx Descriptor List Address */ EDMAC.TRSCER.LONG = 0x00000000; /* copy-back status is RFE & TFE only */ EDMAC.TFTR.LONG = 0x00000000; /* threshold of Tx_FIFO */ EDMAC.FDR.LONG = 0x00000707; /* transmit fifo & receive fifo is 2048 bytes */ EDMAC.RMCR.LONG = 0x00000001; /* RR in EDRRR is under driver control */ #if __LIT EDMAC.EDMR.BIT.DE = 1; #endif /* Initialize PHY */ phydata = phy_init(); if (phydata == R_PHY_ERROR) { return R_ETHER_ERROR; } /* Start PHY auto negotiation */ phydata = phy_set_autonegotiate(); if (phydata == R_PHY_ERROR) { return R_ETHER_ERROR; } if (phydata & PHY_AN_LINK_PARTNER_FULL) { /* Full duplex */ ETHERC.ECMR.BIT.DM = 1; } else { /* Half duplex */ ETHERC.ECMR.BIT.DM = 0; } #if ETH_MODE_SEL == ETH_RMII_MODE if (phydata & PHY_AN_LINK_PARTNER_100BASE) { /* 100Mbps */ ETHERC.ECMR.BIT.RTM = 1; } else { /* 10Mbps */ ETHERC.ECMR.BIT.RTM = 0; } #endif /* ETH_MODE_SEL */ /* Enable interrupt */ /* Sets up interrupt when you use interrupt */ //EDMAC.EESIPR.LONG = 0x00040000; //ICU.IER04.BYTE |= 0x01; //ICU.IPR08.BYTE = 4; // Set priority level /* Enable receive and transmit */ ETHERC.ECMR.BIT.RE = 1; ETHERC.ECMR.BIT.TE = 1; /* Enable EDMAC receive */ EDMAC.EDRRR.LONG = 0x00000001; for( i = 0 ; i < 0x0000100 ; i++ ); return R_ETHER_OK; }







浙公网安备 33010602011771号