📄 lib_emac.c
字号:
//*------------------------------------------------------------------------------------------------
//* 文件名 : lib_emac.c
//* 功能描述 : EMAC外设的函数库
//* 作者 : 焦海波
//* 版本 : 0.1
//* 建立日期、时间 : 2006/06/08 10:17
//* 最近修改日期、时间 :
//* 修改原因 :
//*------------------------------------------------------------------------------------------------
//*------------------------------------------ 头文件 -----------------------------------------------
#include "/uCOS_II/includes.h"
#include "/at91sam7x256/include/AT91SAM7X256.h"
#include "/at91sam7x256/include/lib_AT91SAM7X256.h"
#include "/LwIP/include/lwip/opt.h"
#include "lib_emac.h"
//*------------------------------------- 常量、变量、宏 --------------------------------------------
//* 因为接收缓冲区描述符字0的2到31位保存缓冲区地址,最低两位有其它用处,所以指定4字节对齐,以保证缓冲区地址
//* 的最低两位为0
__align(4) static volatile BYTE baRxBufs[NB_RX_BUFS][ETH_RX_BUF_SIZE]; //* 接收缓冲区
static BYTE baTxBufs[NB_TX_BUFS][ETH_TX_BUF_SIZE]; //* 发送缓冲区
/* 因为缓冲区描述符长度为双字,正好8个字节,所以指定双字对齐 */
__align(8) static volatile AT91S_RxBDescriptor __staRxBDescriptors[NB_RX_BUFS]; //* 接收缓冲区描述符数组
__align(8) static volatile AT91S_TxBDescriptor __staTxBDescriptors[NB_TX_BUFS]; //* 发送缓冲区描述符数组
//* 保存要读取的接收缓冲区的索引位置
static UWORD __uwCurRxBIdx = 0;
static BYTE *__pbFrom;
//*-------------------------------------- 函数原型声明 ---------------------------------------------
static void __HandlePHY(UBYTE ubRegAddr, UWORD *puwData, BOOLEAN blIsRead);
static void __ResetPHY(void);
static void __CheckPHYID(void);
static void __SetupLinkSpeedAndDuplex(void);
static void __InitDescriptorsForRxBAndTxB(void);
__inline void __ilResetTxBDescriptors(void);
//*================================================================================================
//* 函 数 区
//*================================================================================================
//*------------------------------------------------------------------------------------------------
//* 函数名称 : irqEMACISR
//* 功能描述 : EMAC发送和接收结束中断处理函数
//* 入口参数 : 无
//* 出口参数 : 无
//*------------------------------------------------------------------------------------------------
__irq void irqEMACISR(void)
{
extern HANDLER hEthernetInput;
ULONG __ulIntStatus, __ulReceiveStatus;
OSIntEnter();
{
//* 在读取时中断状态寄存器位会被清除
__ulIntStatus = AT91C_BASE_EMAC->EMAC_ISR;
//* 这个是容易忽略的地方,只有读取RSR寄存器,EMAC中断处理才能在写EOICR寄存器之后真正结束
__ulReceiveStatus = AT91C_BASE_EMAC->EMAC_RSR;
if((__ulIntStatus & AT91C_EMAC_RCOMP) || (__ulReceiveStatus & AT91C_EMAC_REC))
{
//* 向EMAC接收任务发送信号
OSAPISemSend(hEthernetInput);
//* 清除REC(Frame Received)位
AT91C_BASE_EMAC->EMAC_RSR = AT91C_EMAC_REC;
}
if(__ulIntStatus & AT91C_EMAC_TCOMP)
{
//* 复位发送缓冲区描述符的Used位使其为程序所有
__ilResetTxBDescriptors();
AT91C_BASE_EMAC->EMAC_TSR = AT91C_EMAC_COMP;
}
//* 清除中断标志,结束中断处理
AT91C_BASE_AIC->AIC_EOICR = 0;
}
OSIntExit();
}
//*------------------------------------------------------------------------------------------------
//* 函数名称 : EMACSendPacket
//* 功能描述 : 由__low_level_output()函数调用。其实现过程如下:首先我们知道,要发送的这一帧数据保存在pbuf
//* : 链中,每一个pbuf节点保存一部分数据,它们通过pbuf->next指针顺序链接在一起。其上层调用函数
//* : __low_level_output()使用一个for循环将这些pbuf通过pbFrom参数传递过来。EMACSendPacket()函数
//* : 将每一个pbuf节点的数据长度与当前使用的发送缓冲区长度进行比较,如果其数据长度超出发送缓冲区长
//* : 度则将其分割保存在多个发送缓冲区中。然后判断当前处理的pbuf是否已经到了链表的末尾,也就是
//* : pbuf->next为空(通过blIsEndOfFrame参数传递)。如果到了末尾并且数据复制完毕,则置位当前缓冲
//* : 区描述符的Last Buffer位,没有,则只需将当前缓冲区中保存的实际数据长度填充到缓冲区描述符中即
//* : 可。如果我们在多次调用EMACSendPacket()函数之后到达了缓冲区队列的末尾,也就是函数中使用的私有
//* : 静态变量__uwTxBIndex其索引计数等于发送缓冲区个数减1(即,__uwTxBIndex == (NB_TX_BUFS-1))
//* : ,则索引计数立即复位为0,从缓冲区队列的开始位置继续保存剩余数据。同时,置位当前缓冲区描述符的
//* : Wrap位,以使发送缓冲区队列指针寄存器能够在数据发送时回到队列开始位置以顺利找到剩余数据。注意,
//* : 置位缓冲区描述符的Wrap位并不会使EMAC认为已经到达帧尾,从而停止发送,只有Last Buffer位置位才行。
//* : 上述过程处理完毕,最后,如果标记了Last Buffer则立即启动传输(置位网络控制寄存器NCR的位9)。在
//* : 这里需要进一步说明的是:假设发送缓冲区队列存在10个缓冲区,在发送第一帧数据时(实际上就是调用了
//* : 一次__low_level_output()函数)我只使用了7个缓冲区,这时__uwTxBIndex索引计数就是8,其指向了下
//* : 一个可用缓冲区,同样在数据发送完毕后,发送缓冲区队列指针寄存器(EMAC_TBQP)也指向了该位置。我
//* : 需要再发送一帧数据时,同样是再次调用__low_level_output()函数。现在要发送的这帧数据需要占用10
//* : 个缓冲区队列。EMACSendPacket()函数在复制数据到缓冲区时,__uwTxBIndex索引增加到了10,也就是到
//* : 缓冲区末尾的时候,前7个缓冲区并没有被使用,所以函数将__uwTxBIndex缩影宠幸复位为0,剩余数据被保
//* : 到了前面。同样,按照EMAC的数据手册,置位Wrap位的目的就是使EMAC_TBQP寄存器重新指向发送队列开始
//* : 位置。从上面的描述我们还看出,发送队列一定要大于或等于帧的最大长度,否则就会出现尾部数据覆盖头
//* : 部数据的问题。
//* 入口参数 : <pbFrom>[in] 指向pbuf中数据的指针
//* : <ulLength>[in] pbuf中的数据长度
//* : <blIsEndOfFrame>[in] 是否是pbuf链中的最后一个,也就是帧尾
//* 出口参数 : 如果无法申请下内存则返回ERR_MEM,成功则返回ERR_OK
//*------------------------------------------------------------------------------------------------
BOOLEAN EMACSendPacket(BYTE *pbFrom, ULONG ulLength, BOOLEAN blIsEndOfFrame)
{
ULONG __ulTotalLenToWrite = 0, __ulCurLenToWrite, __ulLenRemainToWrite, __ulIsLastBuf;
//* 注意这是一个私有的静态变量
static UWORD __uwTxBIndex = 0;
LONG i;
BYTE *__pbBuf;
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr = 0;
#endif
//* 如果要发送的数据长度大于一个发送缓冲区,则需要将这些数据分割进多个缓冲区进行发送
while(__ulTotalLenToWrite < ulLength)
{
//* 等待缓冲区可用,最长等待3秒钟
i = 0;
while(!__staTxBDescriptors[__uwTxBIndex].uStatus.bstStatus.bitIsUsed)
{
//* 如果已经到达等待时间仍然没有可用缓冲区,则立即返回
if(i > 300)
return FALSE;
OSTimeDly(1);
i++;
}
OS_ENTER_CRITICAL()
{
//* 从描述符中获得缓冲区地址,然后把数据复制到缓冲区
__pbBuf = (BYTE*)__staTxBDescriptors[__uwTxBIndex].ulTxBAddr;
//* 计算向缓冲区写入的数据长度
__ulLenRemainToWrite = ulLength - __ulTotalLenToWrite;
if(__ulLenRemainToWrite > ETH_TX_BUF_SIZE)
__ulCurLenToWrite = ETH_TX_BUF_SIZE;
else
__ulCurLenToWrite = __ulLenRemainToWrite;
//* 将pbuf中的数据复制到发送缓冲区
memcpy(__pbBuf, &(pbFrom[__ulTotalLenToWrite]), __ulCurLenToWrite);
__ulTotalLenToWrite += __ulCurLenToWrite;
//* 看看是否是已经到达pbuf链的末尾,如果是则标记当前使用的缓冲区为最后一个缓冲区
if(blIsEndOfFrame && (__ulTotalLenToWrite >= ulLength))
{
__ulIsLastBuf = TxDESC_STATUS_LAST_BUF;
}
else
__ulIsLastBuf = 0;
//* 填充当前的描述符:缓冲区中的数据实际长度、最后一个缓冲区标记、WRAP位(如果确实是最后一个描述符)
if(__uwTxBIndex >= (NB_TX_BUFS-1))
{
__staTxBDescriptors[__uwTxBIndex].uStatus.ulStatus = (__ulCurLenToWrite & TxDESC_STATUS_BUF_SIZE)
| __ulIsLastBuf
| TxDESC_STATUS_WRAP;
__uwTxBIndex = 0;
}
else
{
__staTxBDescriptors[__uwTxBIndex].uStatus.ulStatus = (__ulCurLenToWrite & TxDESC_STATUS_BUF_SIZE)
| __ulIsLastBuf;
__uwTxBIndex++;
}
//* 如果已经到达pbuf链的末尾则立即发送
if(__ulIsLastBuf)
AT91C_BASE_EMAC->EMAC_NCR |= AT91C_EMAC_TSTART;
}
OS_EXIT_CRITICAL()
}
return TRUE;
}
//*------------------------------------------------------------------------------------------------
//* 函数名称 : EMACReadPacket
//* 功能描述 : 从EMAC读取信息包到申请的pbuf链,该函数要求PBUF_POOL_BUFSIZE大于或等于接收缓冲区,最好是整
//* : 数倍,这样函数处理最简单。注意,在调用该函数之前必须先调用GetInputPacketLen()函数,这样才
//* : 能获取正确的读取位置
//* 入口参数 : <pbTo>[in] 指向pbuf的指针
//* : <uwSegmentLen>[in] pbuf中需要存储的实际数据长度,因为在申请pbuf时,pbuf_alloc()已经根据
//* : 实际的帧长将其分割进了多个pbuf组成的pbuf链中,而pstPbuf->len则保存了
//* : 每个pbuf需要存储的数据。换句话说pbuf链中的最后一个pbuf的len字段长度应
//* : 该小于或等于PBUF_POOL_BUFSIZE,而前面的pbuf则等于PBUF_POOL_BUFSIZE
//* : <blIsLastPbuf>[in] 是否是最后一个Pbuf
//* 出口参数 : 无
//*------------------------------------------------------------------------------------------------
void EMACReadPacket(BYTE *pbTo, UWORD uwSegmentLen, BOOLEAN blIsLastPbuf)
{
UWORD __uwTotalLenToRead = 0, //* 已经读取到pbuf的总字节数
__uwRemainLenToRead, //* 还剩多少字节没有读取到pbuf中
__uwRemainLenInRxBToRead; //* EMAC接收缓冲区中还剩下多少字节没有读取
static UWORD __uwTotalLenInRxBToRead = 0; //* EMAC接收缓冲区中已经读了多少字节的数据
BOOLEAN __blIsNotRelease; //* 是否已经主动释放给EMAC
/* 注意,必须保证PBUF_POOL_BUFSIZE大于或等于接收缓冲区 */
while(__uwTotalLenToRead < uwSegmentLen)
{
__uwRemainLenInRxBToRead = ETH_RX_BUF_SIZE - __uwTotalLenInRxBToRead;
__uwRemainLenToRead = uwSegmentLen - __uwTotalLenToRead;
if(__uwRemainLenToRead >= __uwRemainLenInRxBToRead)
{
memcpy(pbTo + __uwTotalLenToRead, __pbFrom, __uwRemainLenInRxBToRead);
__uwTotalLenToRead += __uwRemainLenInRxBToRead;
__uwTotalLenInRxBToRead = 0;
//* 将接收缓冲区归还给EMAC
__staRxBDescriptors[__uwCurRxBIdx].ulRxBAddrAndFlag &= (~RxDESC_FLAG_OWNSHIP);
__blIsNotRelease = FALSE;
//* 调整描述符索引与读取指针
__uwCurRxBIdx++;
if(__uwCurRxBIdx >= NB_RX_BUFS)
__uwCurRxBIdx = 0;
__pbFrom = (BYTE*)(__staRxBDescriptors[__uwCurRxBIdx].ulRxBAddrAndFlag & EMAC_RxB_ADDR_MASK);
}
else
{
memcpy(pbTo + __uwTotalLenToRead, __pbFrom, __uwRemainLenToRead);
__uwTotalLenToRead += __uwRemainLenToRead;
__uwTotalLenInRxBToRead += __uwRemainLenToRead;
__pbFrom = __pbFrom + __uwTotalLenInRxBToRead;
__blIsNotRelease = TRUE;
}
}
if(blIsLastPbuf)
{
//* 将接收缓冲区归还给EMAC,如果存在恰好是PBUF_POOL_BUFSIZE的整数倍的数据包,则没有必要再一次释放,因为它已经在
//* 上面被释放
if(__blIsNotRelease)
{
__staRxBDescriptors[__uwCurRxBIdx].ulRxBAddrAndFlag &= (~RxDESC_FLAG_OWNSHIP);
__uwCurRxBIdx++;
if(__uwCurRxBIdx >= NB_RX_BUFS)
__uwCurRxBIdx = 0;
}
__uwTotalLenInRxBToRead = 0;
}
}
//*------------------------------------------------------------------------------------------------
//* 函数名称 : EMACInit
//* 功能描述 : 初始化EMAC:对PHY、MII口线、EMAC操作模式进行配置,设置接收和发送缓冲区描述符。设置EMAC接收
//* : 和发送中断
//* 入口参数 : 无
//* 出口参数 : 无
//*------------------------------------------------------------------------------------------------
void EMACInit(void)
{
extern HANDLER hEthernetInput;
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr = 0;
#endif
//* 复位PHY芯片,使其进入UTP模式
__ResetPHY();
//* 等待一段指定的时间,使PHY就绪
OSTimeDlyHMSM(0, 0, 3, 0);
//* 设置PIOB引脚为外设A引脚(即EMAC引脚),禁止PIOB控制,改为外设控制
AT91C_BASE_PIOB->PIO_ASR = EMAC_MII_PINS;
AT91C_BASE_PIOB->PIO_PDR = EMAC_MII_PINS;
//* 因为没有使用ETXER,所以这里将其配置为由PIO控制
AT91C_BASE_PIOB->PIO_PER = AT91C_PB12_ETXER;
AT91C_BASE_PIOB->PIO_ODR = AT91C_PB12_ETXER;
//* 设置MDC时钟分频数
AT91C_BASE_EMAC->EMAC_NCFGR |= AT91C_EMAC_CLK_HCLK_32;
//* 检查PHY ID是否为0x82010000,如果不是则表明PHY还没有就绪或者出现故障,函数将一直查询直至正确
__CheckPHYID();
//* 从PHY获取自动协商的结果,设置EMAC自身的链路速度和单双工方式。注意,该函数会阻塞所在任务的正常执行
//* 直至设置成功
__SetupLinkSpeedAndDuplex();
//* 建立接收任务使用的信号量,对uCOS的配置保证信号量在软件逻辑上能够百分百建立成功
hEthernetInput = OSAPISemNew(0);
//* 初始化接收和发送缓冲区描述符,使每个描述符指向正确的缓冲区地址
__InitDescriptorsForRxBAndTxB();
//* 设置EMAC为MII模式,使能EMAC时钟
AT91C_BASE_EMAC->EMAC_USRIO = AT91C_EMAC_CLKEN;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -