Files
2026-04-23 10:50:18 +08:00

426 lines
14 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "spi.h"
//#include "delay.h"
//#include "timerx.h"
#include <stdio.h>
#include "enc28j60.h"
//////////////////////////////////////////////////////////////////////////////////
//ALIENTEK战舰STM32开发板
//ENC28J60驱动 代码
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//修改日期:2012/9/28
//版本V1.0
//////////////////////////////////////////////////////////////////////////////////
static u8 ENC28J60BANK;
static u32 NextPacketPtr;
//复位ENC28J60
//包括SPI初始化/IO初始化等
static void ENC28J60_SPI2_Init(void)
{
// SPI_InitTypeDef SPI_InitStructure;
// GPIO_InitTypeDef GPIO_InitStructure;
// RCC_APB1PeriphClockCmd( RCC_APB1Periph_SPI2, ENABLE );//SPI2时钟使能
// RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOG, ENABLE );//PORTB,D,G时钟使能
//
// GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; // 端口配置
// GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
// GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
// GPIO_Init(GPIOD, &GPIO_InitStructure); //根据设定参数初始化GPIOD.2
// GPIO_SetBits(GPIOD,GPIO_Pin_2); //PD.2上拉
//
// //这里PG7和PB12拉高,是为了防止NRF24L01和SPI FLASH影响.
// //因为他们共用一个SPI口.
// GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; // PB12 推挽 上拉
// GPIO_Init(GPIOB, &GPIO_InitStructure); //根据设定参数初始化GPIOB.12
// GPIO_SetBits(GPIOB,GPIO_Pin_12); //PB.12上拉
// GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_8;//PG6/7/8 推挽 上拉
// GPIO_Init(GPIOG, &GPIO_InitStructure); //根据设定参数初始化//PG6/7/8
// GPIO_SetBits(GPIOG,GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_8);//PG6/7/8上拉
//
// GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
// GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //PB13/14/15复用推挽输出
// GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
// GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOB
// GPIO_SetBits(GPIOB,GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15); //PB13/14/15上拉
// SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
// SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置SPI工作模式:设置为主SPI
// SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //设置SPI的数据大小:SPI发送接收8位帧结构
// SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //串行同步时钟的空闲状态为低电平
// SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; //串行同步时钟的第一个跳变沿(上升或下降)数据被采样
// SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS信号由硬件NSS管脚还是软件使用SSI位管理:内部NSS信号有SSI位控制
// SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //定义波特率预分频的值:波特率预分频值为256
// SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
// SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值计算的多项式
// SPI_Init(SPI2, &SPI_InitStructure); //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
//
// SPI_Cmd(SPI2, ENABLE); //使能SPI外设
//
// SPI2_ReadWriteByte(0xff);//启动传输
}
void ENC28J60_Reset(void)
{
// ENC28J60_SPI2_Init();//SPI2初始化
// SPI2_SetSpeed(SPI_BaudRatePrescaler_4); //SPI2 SCK频率为36M/4=9Mhz
// TIM6_Int_Init(1000,719);//100Khz计数频率计数到1000为10ms
// ENC28J60_RST=0; //复位ENC28J60
// delay_ms(10);
// ENC28J60_RST=1; //复位结束
// delay_ms(10);
}
//读取ENC28J60寄存器(带操作码)
//op操作码
//addr:寄存器地址/参数
//返回值:读到的数据
u8 ENC28J60_Read_Op(u8 op,u8 addr)
{
u8 dat=0;
ENC28J60_CSL ;
dat=op|(addr&ADDR_MASK);
SPI1_ReadWrite(dat);
dat=SPI1_ReadWrite(0xFF);
//如果是读取MAC/MII寄存器,则第二次读到的数据才是正确的,见手册29页
if(addr&0x80)dat=SPI1_ReadWrite(0xFF);
ENC28J60_CSH;
return dat;
}
//读取ENC28J60寄存器(带操作码)
//op操作码
//addr:寄存器地址
//data:参数
void ENC28J60_Write_Op(u8 op,u8 addr,u8 data)
{
u8 dat = 0;
ENC28J60_CSL;
dat=op|(addr&ADDR_MASK);
SPI1_ReadWrite(dat);
SPI1_ReadWrite(data);
ENC28J60_CSH;
}
//读取ENC28J60接收缓存数据
//len:要读取的数据长度
//data:输出数据缓存区(末尾自动添加结束符)
void ENC28J60_Read_Buf(u32 len,u8* data)
{
ENC28J60_CSL;
SPI1_ReadWrite(ENC28J60_READ_BUF_MEM);
while(len)
{
len--;
*data=(u8)SPI1_ReadWrite(0);
data++;
}
*data='\0';
ENC28J60_CSL;
}
//向ENC28J60写发送缓存数据
//len:要写入的数据长度
//data:数据缓存区
void ENC28J60_Write_Buf(u32 len,u8* data)
{
ENC28J60_CSL;
SPI1_ReadWrite(ENC28J60_WRITE_BUF_MEM);
while(len)
{
len--;
SPI1_ReadWrite(*data);
data++;
}
ENC28J60_CSH;
}
//设置ENC28J60寄存器Bank
//ban:要设置的bank
void ENC28J60_Set_Bank(u8 bank)
{
if((bank&BANK_MASK)!=ENC28J60BANK)//和当前bank不一致的时候,才设置
{
ENC28J60_Write_Op(ENC28J60_BIT_FIELD_CLR,ECON1,(ECON1_BSEL1|ECON1_BSEL0));
ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,ECON1,(bank&BANK_MASK)>>5);
ENC28J60BANK=(bank&BANK_MASK);
}
}
//读取ENC28J60指定寄存器
//addr:寄存器地址
//返回值:读到的数据
u8 ENC28J60_Read(u8 addr)
{
ENC28J60_Set_Bank(addr);//设置BANK
return ENC28J60_Read_Op(ENC28J60_READ_CTRL_REG,addr);
}
//向ENC28J60指定寄存器写数据
//addr:寄存器地址
//data:要写入的数据
void ENC28J60_Write(u8 addr,u8 data)
{
ENC28J60_Set_Bank(addr);
ENC28J60_Write_Op(ENC28J60_WRITE_CTRL_REG,addr,data);
}
//向ENC28J60的PHY寄存器写入数据
//addr:寄存器地址
//data:要写入的数据
void ENC28J60_PHY_Write(u8 addr,u32 data)
{
u16 retry=0;
ENC28J60_Write(MIREGADR,addr); //设置PHY寄存器地址
ENC28J60_Write(MIWRL,data); //写入数据
ENC28J60_Write(MIWRH,data>>8);
while((ENC28J60_Read(MISTAT)&MISTAT_BUSY)&&retry<0XFFF)retry++;//等待写入PHY结束
}
//初始化ENC28J60
//macaddr:MAC地址
//返回值:0,初始化成功;
// 1,初始化失败;
u8 ENC28J60_Init(u8* macaddr)
{
u16 retry=0;
ENC28J60_Reset();
ENC28J60_Write_Op(ENC28J60_SOFT_RESET,0,ENC28J60_SOFT_RESET);//软件复位
while(!(ENC28J60_Read(ESTAT)&ESTAT_CLKRDY)&&retry<500)//等待时钟稳定
{
retry++;
// delay_ms(1);
};
if(retry>=500)return 1;//ENC28J60初始化失败
// do bank 0 stuff
// initialize receive buffer
// 16-bit transfers,must write low byte first
// set receive buffer start address 设置接收缓冲区地址 8K字节容量
NextPacketPtr=RXSTART_INIT;
// Rx start
//接收缓冲器由一个硬件管理的循环FIFO 缓冲器构成。
//寄存器对ERXSTH:ERXSTL 和ERXNDH:ERXNDL 作
//为指针,定义缓冲器的容量和其在存储器中的位置。
//ERXST和ERXND指向的字节均包含在FIFO缓冲器内。
//当从以太网接口接收数据字节时,这些字节被顺序写入
//接收缓冲器。 但是当写入由ERXND 指向的存储单元
//后硬件会自动将接收的下一字节写入由ERXST 指向
//的存储单元。 因此接收硬件将不会写入FIFO 以外的单
//元。
//设置接收起始字节
ENC28J60_Write(ERXSTL,RXSTART_INIT&0xFF);
ENC28J60_Write(ERXSTH,RXSTART_INIT>>8);
//ERXWRPTH:ERXWRPTL 寄存器定义硬件向FIFO 中
//的哪个位置写入其接收到的字节。 指针是只读的,在成
//功接收到一个数据包后,硬件会自动更新指针。 指针可
//用于判断FIFO 内剩余空间的大小 8K-1500。
//设置接收读指针字节
ENC28J60_Write(ERXRDPTL,RXSTART_INIT&0xFF);
ENC28J60_Write(ERXRDPTH,RXSTART_INIT>>8);
//设置接收结束字节
ENC28J60_Write(ERXNDL,RXSTOP_INIT&0xFF);
ENC28J60_Write(ERXNDH,RXSTOP_INIT>>8);
//设置发送起始字节
ENC28J60_Write(ETXSTL,TXSTART_INIT&0xFF);
ENC28J60_Write(ETXSTH,TXSTART_INIT>>8);
//设置发送结束字节
ENC28J60_Write(ETXNDL,TXSTOP_INIT&0xFF);
ENC28J60_Write(ETXNDH,TXSTOP_INIT>>8);
// do bank 1 stuff,packet filter:
// For broadcast packets we allow only ARP packtets
// All other packets should be unicast only for our mac (MAADR)
//
// The pattern to match on is therefore
// Type ETH.DST
// ARP BROADCAST
// 06 08 -- ff ff ff ff ff ff -> ip checksum for theses bytes=f7f9
// in binary these poitions are:11 0000 0011 1111
// This is hex 303F->EPMM0=0x3f,EPMM1=0x30
//接收过滤器
//UCEN单播过滤器使能位
//当ANDOR = 1 时:
//1 = 目标地址与本地MAC 地址不匹配的数据包将被丢弃
//0 = 禁止过滤器
//当ANDOR = 0 时:
//1 = 目标地址与本地MAC 地址匹配的数据包会被接受
//0 = 禁止过滤器
//CRCEN后过滤器CRC 校验使能位
//1 = 所有CRC 无效的数据包都将被丢弃
//0 = 不考虑CRC 是否有效
//PMEN格式匹配过滤器使能位
//当ANDOR = 1 时:
//1 = 数据包必须符合格式匹配条件,否则将被丢弃
//0 = 禁止过滤器
//当ANDOR = 0 时:
//1 = 符合格式匹配条件的数据包将被接受
//0 = 禁止过滤器
ENC28J60_Write(ERXFCON,ERXFCON_UCEN|ERXFCON_CRCEN|ERXFCON_PMEN);
ENC28J60_Write(EPMM0,0x3f);
ENC28J60_Write(EPMM1,0x30);
ENC28J60_Write(EPMCSL,0xf9);
ENC28J60_Write(EPMCSH,0xf7);
// do bank 2 stuff
// enable MAC receive
//bit 0 MARXENMAC 接收使能位
//1 = 允许MAC 接收数据包
//0 = 禁止数据包接收
//bit 3 TXPAUS暂停控制帧发送使能位
//1 = 允许MAC 发送暂停控制帧(用于全双工模式下的流量控制)
//0 = 禁止暂停帧发送
//bit 2 RXPAUS暂停控制帧接收使能位
//1 = 当接收到暂停控制帧时,禁止发送(正常操作)
//0 = 忽略接收到的暂停控制帧
ENC28J60_Write(MACON1,MACON1_MARXEN|MACON1_TXPAUS|MACON1_RXPAUS);
// bring MAC out of reset
//将MACON2 中的MARST 位清零使MAC 退出复位状态。
ENC28J60_Write(MACON2,0x00);
// enable automatic padding to 60bytes and CRC operations
//bit 7-5 PADCFG2:PACDFG0自动填充和CRC 配置位
//111 = 用0 填充所有短帧至64 字节长并追加一个有效的CRC
//110 = 不自动填充短帧
//101 = MAC 自动检测具有8100h 类型字段的VLAN 协议帧并自动填充到64 字节长。如果不
//是VLAN 帧则填充至60 字节长。填充后还要追加一个有效的CRC
//100 = 不自动填充短帧
//011 = 用0 填充所有短帧至64 字节长并追加一个有效的CRC
//010 = 不自动填充短帧
//001 = 用0 填充所有短帧至60 字节长并追加一个有效的CRC
//000 = 不自动填充短帧
//bit 4 TXCRCEN发送CRC 使能位
//1 = 不管PADCFG如何MAC都会在发送帧的末尾追加一个有效的CRC。 如果PADCFG规定要
//追加有效的CRC则必须将TXCRCEN 置1。
//0 = MAC不会追加CRC。 检查最后4 个字节如果不是有效的CRC 则报告给发送状态向量。
//bit 0 FULDPXMAC 全双工使能位
//1 = MAC工作在全双工模式下。 PHCON1.PDPXMD 位必须置1。
//0 = MAC工作在半双工模式下。 PHCON1.PDPXMD 位必须清零。
ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,MACON3,MACON3_PADCFG0|MACON3_TXCRCEN|MACON3_FRMLNEN|MACON3_FULDPX);
// set inter-frame gap (non-back-to-back)
//配置非背对背包间间隔寄存器的低字节
//MAIPGL。 大多数应用使用12h 编程该寄存器。
//如果使用半双工模式,应编程非背对背包间间隔
//寄存器的高字节MAIPGH。 大多数应用使用0Ch
//编程该寄存器。
ENC28J60_Write(MAIPGL,0x12);
ENC28J60_Write(MAIPGH,0x0C);
// set inter-frame gap (back-to-back)
//配置背对背包间间隔寄存器MABBIPG。当使用
//全双工模式时大多数应用使用15h 编程该寄存
//器而使用半双工模式时则使用12h 进行编程。
ENC28J60_Write(MABBIPG,0x15);
// Set the maximum packet size which the controller will accept
// Do not send packets longer than MAX_FRAMELEN:
// 最大帧长度 1500
ENC28J60_Write(MAMXFLL,MAX_FRAMELEN&0xFF);
ENC28J60_Write(MAMXFLH,MAX_FRAMELEN>>8);
// do bank 3 stuff
// write MAC address
// NOTE: MAC address in ENC28J60 is byte-backward
//设置MAC地址
ENC28J60_Write(MAADR5,macaddr[0]);
ENC28J60_Write(MAADR4,macaddr[1]);
ENC28J60_Write(MAADR3,macaddr[2]);
ENC28J60_Write(MAADR2,macaddr[3]);
ENC28J60_Write(MAADR1,macaddr[4]);
ENC28J60_Write(MAADR0,macaddr[5]);
//配置PHY为全双工 LEDB为拉电流
ENC28J60_PHY_Write(PHCON1,PHCON1_PDPXMD);
// no loopback of transmitted frames 禁止环回
//HDLDISPHY 半双工环回禁止位
//当PHCON1.PDPXMD = 1 或PHCON1.PLOOPBK = 1 时:
//此位可被忽略。
//当PHCON1.PDPXMD = 0 且PHCON1.PLOOPBK = 0 时:
//1 = 要发送的数据仅通过双绞线接口发出
//0 = 要发送的数据会环回到MAC 并通过双绞线接口发出
ENC28J60_PHY_Write(PHCON2,PHCON2_HDLDIS);
// switch to bank 0
//ECON1 寄存器
//寄存器3-1 所示为ECON1 寄存器,它用于控制
//ENC28J60 的主要功能。 ECON1 中包含接收使能、发
//送请求、DMA 控制和存储区选择位。
ENC28J60_Set_Bank(ECON1);
// enable interrutps
//EIE 以太网中断允许寄存器
//bit 7 INTIE 全局INT 中断允许位
//1 = 允许中断事件驱动INT 引脚
//0 = 禁止所有INT 引脚的活动(引脚始终被驱动为高电平)
//bit 6 PKTIE 接收数据包待处理中断允许位
//1 = 允许接收数据包待处理中断
//0 = 禁止接收数据包待处理中断
ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,EIE,EIE_INTIE|EIE_PKTIE);
// enable packet reception
//bit 2 RXEN接收使能位
//1 = 通过当前过滤器的数据包将被写入接收缓冲器
//0 = 忽略所有接收的数据包
ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,ECON1,ECON1_RXEN);
if(ENC28J60_Read(MAADR5)== macaddr[0])return 0;//初始化成功
else return 1;
}
//读取EREVID
u8 ENC28J60_Get_EREVID(void)
{
//在EREVID 内也存储了版本信息。 EREVID 是一个只读控
//制寄存器包含一个5 位标识符,用来标识器件特定硅片
//的版本号
return ENC28J60_Read(EREVID);
}
//#include "uip.h"
//通过ENC28J60发送数据包到网络
//len:数据包大小
//packet:数据包
void ENC28J60_Packet_Send(u32 len,u8* packet)
{
//设置发送缓冲区地址写指针入口
ENC28J60_Write(EWRPTL,TXSTART_INIT&0xFF);
ENC28J60_Write(EWRPTH,TXSTART_INIT>>8);
//设置TXND指针以对应给定的数据包大小
ENC28J60_Write(ETXNDL,(TXSTART_INIT+len)&0xFF);
ENC28J60_Write(ETXNDH,(TXSTART_INIT+len)>>8);
//写每包控制字节0x00表示使用macon3的设置
ENC28J60_Write_Op(ENC28J60_WRITE_BUF_MEM,0,0x00);
//复制数据包到发送缓冲区
//printf("len:%d\r\n",len); //监视发送数据长度
ENC28J60_Write_Buf(len,packet);
//发送数据到网络
ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,ECON1,ECON1_TXRTS);
//复位发送逻辑的问题。参见Rev. B4 Silicon Errata point 12.
if((ENC28J60_Read(EIR)&EIR_TXERIF))ENC28J60_Write_Op(ENC28J60_BIT_FIELD_CLR,ECON1,ECON1_TXRTS);
}
//从网络获取一个数据包内容
//maxlen:数据包最大允许接收长度
//packet:数据包缓存区
//返回值:收到的数据包长度(字节)
u32 ENC28J60_Packet_Receive(u32 maxlen,u8* packet)
{
u32 rxstat;
u32 len;
if(ENC28J60_Read(EPKTCNT)==0)return 0; //是否收到数据包?
//设置接收缓冲器读指针
ENC28J60_Write(ERDPTL,(NextPacketPtr));
ENC28J60_Write(ERDPTH,(NextPacketPtr)>>8);
// 读下一个包的指针
NextPacketPtr=ENC28J60_Read_Op(ENC28J60_READ_BUF_MEM,0);
NextPacketPtr|=ENC28J60_Read_Op(ENC28J60_READ_BUF_MEM,0)<<8;
//读包的长度
len=ENC28J60_Read_Op(ENC28J60_READ_BUF_MEM,0);
len|=ENC28J60_Read_Op(ENC28J60_READ_BUF_MEM,0)<<8;
len-=4; //去掉CRC计数
//读取接收状态
rxstat=ENC28J60_Read_Op(ENC28J60_READ_BUF_MEM,0);
rxstat|=ENC28J60_Read_Op(ENC28J60_READ_BUF_MEM,0)<<8;
//限制接收长度
if (len>maxlen-1)len=maxlen-1;
//检查CRC和符号错误
// ERXFCON.CRCEN为默认设置,一般我们不需要检查.
if((rxstat&0x80)==0)len=0;//无效
else ENC28J60_Read_Buf(len,packet);//从接收缓冲器中复制数据包
//RX读指针移动到下一个接收到的数据包的开始位置
//并释放我们刚才读出过的内存
ENC28J60_Write(ERXRDPTL,(NextPacketPtr));
ENC28J60_Write(ERXRDPTH,(NextPacketPtr)>>8);
//递减数据包计数器标志我们已经得到了这个包
ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,ECON2,ECON2_PKTDEC);
return(len);
}