• ENC28J60基于AVRNET修改ENC28J60驱动过程(STM32+ CubeMx + ENC28J60)


    背景(一些没用的话,建议跳过)

    想给自己的MCU接入网络,在某宝上入手了一块网口模块(ENC28J60),第一次接触SPI接口,信心满满的以为和以往的TTL、RS485、RS232没什么区别,链接到电脑也是一个COM接口,可以通过串口调试工具发送指令、接收指令。所以在买网口的同时还买了SPI转USB模块,实时证明这个模块白买了,SPI根本不能通过串口工具调试!SPI链接电脑后也不是串口!

    MCU: stm32f103c6t6

    网络模块: ENC28J60

    因为尽量照顾对一些概念性东西不熟悉的同学,本次写的非常啰嗦,请见谅。

    如果第一次接触ENC28J60,请准备好,它可能没想象中那么容易,但当调通后,一定也会有也没有想象中那么难的感觉!

    主要介绍内容

    本章不会讨论原理,只希望开始接触enc28j60的同学更快入门,写出第一个hello world!。

    网上介绍ENC28J60设备的文章主要是CSDN 的xukai871105: https://blog.csdn.net/xukai871105/article/details/13931833  

    写的很好,非常适合想搞嵌入式的朋友学习,理解。但对于一些初学者, 想快速入手的同学来说,确实有些头大,不知道该从哪里入手的感觉。

    主要介绍以下内容

    1. 简单介绍SPI

    2. ENC28J60驱动获取、修改SPI读写操作位置、HAL库的SPI读写、GPIO读写、MCU接入

    3. 测试ENC28J60通讯是否正常,写一个最简单的测试驱动通讯是否正常小程序,类似软件的Hello World,通过Wireshark监控,能监控到对应消息表示驱动通讯正常!

    4. 遇见的一些简单问题,以及问题原因

    接入ENC28J60前准备

    SPI相关介绍

    如果有兴趣,建议简单了解一下SPI接口,至少知道SPI接口的基本通讯四根线(MISO、MOSI、SCK、CS),ENC28J60要和MCU通讯,也需要这四根线。

    个人感觉SPI比TTL要底层,通讯效率要比TTL要高,速度当然也比TTL快。

    为方便理解,MISO和MOSI,防止搞混,全名是:

    MISO: Master(主机) Input(接收) Slave(从机) Output(发送)

    MOSI:Master(主机) Output(发送) Slave(从机) Input(接收)

    SCK: 控制主、从设备通讯频率,由主机控制

    CS: 片选信号,由主机选择那个从设备进行通讯

    关于CS(片选信号)的一些自己的理解

    可以把它理解成一个单纯的GPIO 输出,一般情况下是低电平有效,默认高电平状态, 在和从机(ENC28J60)通讯前,第一步就是拉低电平,在SPI通讯(发送指令、发送数据.....) 最后拉高点平.

    SPI设计的就是一对多的情况,一个主机,可以连接多个从机,但同时只能跟一个从机进行通讯。

    最后,SPI在数据通讯时, 是有读有写的。要想读入数据,需要先向从机发送数据!

    在操作ENC28J60前,需要掌握SPI的读写函数、控制GPIO 高低电平(控制CS针脚高低电平)

    HAL库SPI读写函数

    此处只介绍HAL库的SPI读写函数,其他的读写方式请自行百度,因为我不会O-O。

    非常重要,这是操作enc28j60设备的第一步,也是后续所有操作的基础,如果你也是使用STM32+HAL库(CubeMx)+Keil5,可以跳过所有,下载最下边提到的keil5 helloworld项目,尝试跑通"第一个程序"。

    HAL库对SPI做了封装,只需要调用HAL_SPI_TransmitReceive函数即可:

    HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size,
                                              uint32_t Timeout)

    hspi: SPI实例指针, CubeMX已声明SPI示例,实例中包括SPIMISO、MOSI、SCK指针位置、和一些其他参数

    pTxData: 要发送的数据

    pRxData: 要接收的数据

    Size: 发送/接收 数据长度,因为SPI读写是同时进行的,如果不理解请自行百度具体介绍

    Timeout: 超时时间

     比如我想读取1字节数据,要想读1字节,需要先写1字节:

    //接收的数据
    uint8_t Rxdata = 0x00;
    //发送的数据
    uint8_t Txdata;
    //hspi1 cubemx生成的SPI实例
    //1000为超时时间,毫秒
    HAL_SPI_TransmitReceive(&hspi1, &TxData, &Rxdata,sizeof(TxData), 1000)

    HAL库GPIO Output 高低点平控制

     相对上边介绍的SPI,高低电平控制就简单的多:

    HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)

    GPIOx: 针脚所在区

    GPIO_Pin: 针脚所在位置

    PinState: 状态枚举, 高电平: GPIO_PIN_SET,   低电平: GPIO_PIN_RESET

    详细GPIO 高低点平控制介绍,可以看我之前记录的随笔: https://www.cnblogs.com/GengMingYan/p/15614068.html

    通过CubeMx创建项目

    具体详细暂不介绍,这里只介绍SPI配置信息,SPI参数全部默认即可:

     

    PDF电子书文档:

    最后,通过CubeMx生成Keil5项目,生成前记得勾选Generate peripheral initialzation as a pair of '.c/.h' fiels per peripheral:

    开始修改ENC28J60驱动

    如果不想看修改过程,可以直接跳过,直接看SPI实现和GPIO实现部分,通过AVR修改好的驱动文件(enc28j60.h和enc28j60.c)已放在文章最后。

    如果是STM32 + HAL库,那可以不看SPI实现和GPIO实现部分,直接下载我修改好的,只关注代码中用到的MCU针脚就可以。

    首先,ENC28J60驱动是从国外的一个项目(AVRNET)中获取的,因为操作ENC28J60过程非常复杂(至少对于我来说是这样),如果从0开始实现需要了解很多概念性东西,并且也没有必要重复造轮子。

    从gitee或github上拉取或打包下载AVRNET项目,AVRNET项目地址已写在本章最后。

    项目目录如下,我们只需要用项目中的enc28j60.c和enc28j60.h:

     由于AVRNET项目使用的是AVR类型单片机,有很多数据类型、SPI、GPIO 操作方式和STM32不一样,但最终效果都一样,SPI读写,GPIO高低点平控制。

    把enc28j60.h和enc28j60.c文件放入开发工具中,这里我使用的Keil5,具体怎么添加头文件和源文件请自行百度。

    需要自己修改/实现的地方:

    1. SPI读写

      需要实现通过SPI读写一字节数据,所有ENC28J60操作都需要基于实现的读写函数

    2. GPIO 高低点平控制

      实现高低电平控制,控制CS片选信号,ENC28J60操作数据前置低电平,操作完后置高点平

    3. 类型定义

      AVR有自己的类型BYTE(1字节)、WORD_BYTES(2字节)等一些其他类型,在STM32中并没有,需要用unsigned char(1字节)和unsigned short(2字节)代替

    4. 睡眠函数实现(ms毫秒)

       HAL提供了线程的睡眠函数:HAL_Delay函数,实现睡眠指定毫秒,有一些操作,是需要等待几十毫秒到几百毫秒的等待响应时间

    5. 删除一些AVR初始化GPIO、SPI操作

    GPIO高低点平控制CS

    主要实现操作:  CSACTIVE(置低电平,使能ENC28J60)和CSPASSIVE(置高电平,释放ENC28J60使能,低电平有效)

    如下,CS针脚我在MCU中用的是A4,CS针脚不一定是A4,只要是可以GPIO 高低电平输出的针脚,都可以用来当SPI 的CS片选:

    enc28j60.h

    //新增头
    //gpio.h 主要用在操作CS高低点平
    #include "gpio.h"
    //置A4针脚低电平,激活从机,APIOA A区   GPIO_PIN_4  4针脚
    #define CSACTIVE HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET)
    //置A4针脚高电平,释放从机
    #define CSPASSIVE HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET)

    SPI读写在驱动中实现

    在AVRNET提供的ENC28J60驱动中,有一个全局变量(1字节),临时存储要写入的数据和读出的数据:

    变量名为: SPDR

    enc28j60.h声明SPDR类型:

    用unsigned char代替1字节的全局变量

    //要读写的数据,设置后立即调用waitspi(),这是AVR的读写方式,在waitspi中实现SPI读写
    #define SPDR_TYPE unsigned char

    enc28j60.c中定义SPDR变量:

    ...
    //SPDR变量
    SPDR_TYPE SPDR;
    ...

    所以读写一字节的操作流程是(伪代码):

    SPDR = 0x01//要写入的内容置到SPDR中
    waitspi()//调用waitspi向enc28j60(寄存器)发送(0x01)并读取1字节数据(读取内容到SPDR中),读到的内容存在SPDR变量中
    print(SPDR)//打印读取的1字节数据

     SPI读写在驱动中实现

    调用HAL_SPI_TransmitReceive函数,需要引入"spi.h"头文件

    enc28j60.h中声明:

    void waitspi()

    enc28j60.c:

    void waitspi() {
        //hspi1 为cubemx生成好的SPI实例,存储MISO、MOSI、SCK针脚位置信息和一些SPI的其他参数
        if(HAL_SPI_TransmitReceive(&hspi1, &SPDR, &SPDR, sizeof(SPDR), 1000) != HAL_OK) {
            //读写错误
            print("error!");//可去掉,仅打印调试信息到串口,方便排查问题
            return ;
        }
        //读写正确
    }

    LOW和HIGH函数实现

    在项目中一些地方用了LOW和HIGH函数,并没有明白具体用途:

    //不明白...
    BYTE LOW(int d) {
        return d & 0xFF;
    }
    //不明白...
    BYTE HIGH(int d) {
        return d >> 8;
    }

    类型定义

    AVR项目驱动中用到的STM32中没有的一些类型代替声明:

    //新增的定义
    #define BYTE unsigned char //1字节
    #define WORD unsigned short  //2字节
    #define WORD_BYTES unsigned short  //2字节

    睡眠函数实现(ms毫秒)

    这里使用HAL库带的HAL_Delay

    //睡指定毫秒
    void _delay_ms(int sleep_ms) {
        HAL_Delay(sleep_ms);
    }

     删除、修改处,比较复杂的地方

     1. enc28j60.h删除AVR GPIO初始化

    enc28j60.h

    ....
    #define ENC28J60_WRITE_CTRL_REG      0x40
    #define ENC28J60_WRITE_BUF_MEM       0x7A
    #define ENC28J60_BIT_FIELD_SET       0x80
    #define ENC28J60_BIT_FIELD_CLR       0xA0
    #define ENC28J60_SOFT_RESET          0xFF
    
    //删除的地方 AVR高低点平、SPI读写
    // set CS to 0 = active
    //#define CSACTIVE PORTB &= ~_BV(PB4)
    // set CS to 1 = passive
    //#define CSPASSIVE PORTB |= _BV(PB4)
    //#define waitspi() while(!(SPSR&(1<<SPIF)))
    //删除的地方 AVR高低点平、SPI读写 END
    
    // The RXSTART_INIT should be zero. See Rev. B4 Silicon Errata
    // buffer boundaries applied to internal 8K ram
    // the entire available packet buffer space is allocated
    //
    #define MAX_TX_BUFFER    1500
    #define MAX_RX_BUFFER    1500
    ....

    2. 修改每包接收数据最大为常量1500

    .....
    #define
    TXSTART_INIT (8192-1500) #define TXSTOP_INIT 8192 // // max frame length which the conroller will accept: //修改为常量 1500,每包可接受数据最大长度 //#define MAX_FRAMELEN (1500+sizeof(ETH_HEADER)+4) // maximum ethernet frame length #define MAX_FRAMELEN 1500 #define ENC28J60_RESET_PIN_DDR DDD3 #define ENC28J60_INT_PIN_DDR DDD2
    ....

    3. 修改操作enc28j60PhyWritePHY位置

    enc28j60.c中:

    void enc28j60PhyWrite(BYTE address, WORD_BYTES data)
    {
        // set the PHY register address
        enc28j60Write(MIREGADR, address);
        // write the PHY data
        //修改地方...
        //enc28j60Write(MIWRL, data.byte.low);
        //enc28j60Write(MIWRH, data.byte.high);
        //修改为
        enc28j60Write(MIWRL, data);
        enc28j60Write(MIWRH, data>>8);
        // wait until the PHY write completes
        while(enc28j60Read(MISTAT) & MISTAT_BUSY)
        {
            //睡15微秒,是否添加在自己,不睡也没太大的问题个人感觉
            //目前并不知道怎么睡15微秒...所以此处注释
            //_delay_us(15);
        }
    }

    4. 初始化enc28j60_init一些操作修改

    enc28j60.c中:

    void enc28j60_init( BYTE *avr_mac)
    {
        // initialize I/O
        //DDRB |= _BV( DDB4 );
        //打开注释,CS置高电平
        CSPASSIVE;
        //AVR初始化SPI的一些操作,注释
        /*
        // enable PB0, reset as output 
        ENC28J60_DDR |= _BV(ENC28J60_RESET_PIN_DDR);
    
        // enable PD2/INT0, as input
        ENC28J60_DDR &= ~_BV(ENC28J60_INT_PIN_DDR);
        ENC28J60_PORT |= _BV(ENC28J60_INT_PIN);
    
        // set output to gnd, reset the ethernet chip
        ENC28J60_PORT &= ~_BV(ENC28J60_RESET_PIN);
        _delay_ms(10);
    
        // set output to Vcc, reset inactive
        ENC28J60_PORT |= _BV(ENC28J60_RESET_PIN);
        _delay_ms(200);
    
        //    
        DDRB  |= _BV( DDB4 ) | _BV( DDB5 ) | _BV( DDB7 ); // mosi, sck, ss output
        //DDRB &= ~_BV( DDB6 ); // MISO is input
    
        CSPASSIVE;
        PORTB &= ~(_BV( PB5 ) | _BV( PB7 ) );
        //
        // initialize SPI interface
        // master mode and Fosc/2 clock:
        SPCR = _BV( SPE ) | _BV( MSTR );
        SPSR |= _BV( SPI2X );
        
        */
        //AVR初始化SPI的一些操作,注释 END
        // perform system reset
        enc28j60WriteOp(ENC28J60_SOFT_RESET, 0, ENC28J60_SOFT_RESET);
        _delay_ms(50);
    
        // check CLKRDY bit to see if reset is complete
        // The CLKRDY does not work. See Rev. B4 Silicon Errata point. Just wait.
        //打开注释,如果初始化不成功将进入死循环,直到初始化成功
        while(!(enc28j60Read(ESTAT) & ESTAT_CLKRDY));
        // do bank 0 stuff
        // initialize receive buffer
        // 16-bit transfers, must write low byte first
        // set receive buffer start address
        
        //去掉.word,下一包数据开始指针
        //next_packet_ptr.word = RXSTART_INIT;
        next_packet_ptr = RXSTART_INIT;
        // Rx start
        enc28j60Write(ERXSTL, RXSTART_INIT&0xFF);
        enc28j60Write(ERXSTH, RXSTART_INIT>>8);
            ....
    }

    5. 修改包接收函数一些操作,相对其他来说最复杂的地方

    enc28j60.c中:

    WORD enc28j60_packet_receive ( BYTE *rxtx_buffer, WORD max_length )
    {
        WORD_BYTES rx_status, data_length;
        
        // check if a packet has been received and buffered
        // if( !(enc28j60Read(EIR) & EIR_PKTIF) ){
        // The above does not work. See Rev. B4 Silicon Errata point 6.
        if( enc28j60Read(EPKTCNT) == 0 )
        {
            return 0;
        }
        //修改最多的地方,不懂最多的地方, 把所有  <WORD_BYTES类型变量>.word 都改为 <WORD_BYTES类型变量>
        //中文注释下的代码,是需要修改的地方
        
        // Set the read pointer to the start of the received packet
        //设置读取指针为接收数据包开头?? ↑
        //enc28j60Write(ERDPTL, next_packet_ptr.bytes[0]);
        //enc28j60Write(ERDPTH, next_packet_ptr.bytes[1]);
        enc28j60Write(ERDPTL, (next_packet_ptr));
        enc28j60Write(ERDPTH, (next_packet_ptr)>>8);
        
        // read the next packet pointer
        //读取下一包指针??? ↑
        //next_packet_ptr.bytes[0] = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0);
        //next_packet_ptr.bytes[1] = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0);
        next_packet_ptr = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0);
        next_packet_ptr |= enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0) << 8;
        
    
        // read the packet length (see datasheet page 43)
        // 读取数据包长度
        //data_length.bytes[0] = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0);
        //data_length.bytes[1] = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0);
        data_length = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0);
        data_length |= enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0) << 8;
        
        //删除CRC 计数..还是不懂
        //data_length.word -=4; //remove the CRC count
        data_length -= 4;
        
        // read the receive status (see datasheet page 43)
        //读取接收状态
        //rx_status.bytes[0] = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0);
        //rx_status.bytes[1] = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0);
        rx_status = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0);
        rx_status |= enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0)<<8;
        
        //应该是防止数据越界,做的一些判断
        /*if ( data_length.word > (max_length-1) )
        {
            data_length.word = max_length-1;
        }
        */
        if ( data_length > (max_length-1) )
        {
            data_length = max_length-1;
        }
        
        
        // check CRC and symbol errors (see datasheet page 44, table 7-3):
        // The ERXFCON.CRCEN is set by default. Normally we should not
        // need to check this.
        //判断接收状态,如果接收到数据,读出来
        /*
        if ( (rx_status.word & 0x80)==0 )
        {
            // invalid
            data_length.word = 0;
        }
        else
        {
            // read data from rx buffer and save to rxtx_buffer
            rx_status.word = data_length.word;
            CSACTIVE;
            // issue read command
            SPDR = ENC28J60_READ_BUF_MEM;
            waitspi();
            while(rx_status.word)
            {
                rx_status.word--;
                SPDR = 0x00;
                waitspi();
                *rxtx_buffer++ = SPDR;
            }
            CSPASSIVE;
        }
        */
        if ( (rx_status & 0x80)==0 )
        {
            //未读到数据
            // invalid
            data_length = 0;
        }
        else
        {
            //成功读到数据
            //临时变量,要操作包索引
            WORD_BYTES data_length_index = data_length;
            //开始读
            CSACTIVE;
            //发送读指令
            SPDR = ENC28J60_READ_BUF_MEM;
            waitspi();
            while(data_length_index) {
                data_length_index--;
                SPDR = 0x00;
                waitspi();
                *rxtx_buffer++ = SPDR;
            }
            CSPASSIVE;
            //开始读 END
        }
        // Move the RX read pointer to the start of the next received packet
        // This frees the memory we just read out
        //还是不懂...
        //enc28j60Write(ERXRDPTL, next_packet_ptr.bytes[0]);
        //enc28j60Write(ERXRDPTH, next_packet_ptr.bytes[1]);
        enc28j60Write(ERXRDPTL, (next_packet_ptr));
        enc28j60Write(ERXRDPTH, (next_packet_ptr)>>8);
        // decrement the packet counter indicate we are done with this packet
        enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON2, ECON2_PKTDEC);
        //这懂,发送接收到的数据包长度
        //return( data_length.word );
        return( data_length);
    }

    到此,驱动应该可以在STM32中编译了。

    MCU和ENC28J60接线

    MCU和ENC28J60链接,需要(最少)4根线:

    ENC28J60      MCU(STM32F103C6T6)

    CS   <--->    GPIO OUTPUT CS针脚,本篇介绍为PA4

    SCK      <--->    SCK 时钟信号,由频率主机控制, 本篇介绍为 PA5

    MISO  <--->    主机接收,从机发送,本篇介绍为 PA6

    MOSI  <--->    主机发送,从机接收,本篇介绍为 PA7

    调试ENC28J60需要循序渐进,慢慢来。

    可以先调通初始化函数,enc28j60_init

    在调通enc28j60_packet_send函数,电脑端成功收到发送的数据

    在接入UIP 、LWIP框架,实现ICMP(ping)、TCP、UDP协议通讯

    开始调试ENC28J60

    驱动文件可以成功通过编译后,在main.c中引入enc28j60.h,开始写第一个Hello World:

    /* Includes ------------------------------------------------------------------*/
    #include "main.h"
    #include "spi.h"
    #include "usart.h"
    #include "gpio.h"
    
    /* Private includes ----------------------------------------------------------*/
    /* USER CODE BEGIN Includes */
    #include "string.h"
    #include "enc28j60.h"
    /* USER CODE END Includes */
    
    
    //打印调试信息
    void print(char *data) {
        
        HAL_UART_Transmit(&huart1, (uint8_t*)data, strlen(data), 1000);
    }
    /* USER CODE END 0 */
    
    /**
      * @brief  The application entry point.
      * @retval int
      */
    int main(void)
    {
      /* USER CODE BEGIN 1 *//* USER CODE END 1 */
      
    
      /* MCU Configuration--------------------------------------------------------*/
    
      /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
      HAL_Init();
    
      /* USER CODE BEGIN Init */
    
      /* USER CODE END Init */
    
      /* Configure the system clock */
      SystemClock_Config();
    
      /* USER CODE BEGIN SysInit */
    
      /* USER CODE END SysInit */
    
      /* Initialize all configured peripherals */
      MX_GPIO_Init();//GPIO初始化cubemx自动生成
      MX_SPI1_Init();//SPI初始化自动生成
      MX_USART1_UART_Init();//UART初始化自动生成
      /* USER CODE BEGIN 2 */
        
      MX_USART1_UART_Init();//自动生成
        //等待一些时间,准备初始化ENC28J60
      /* USER CODE BEGIN 2 */
        for(int i = 0;i < 20;i++) {
            //enc28j60PhyWrite(PHLCON,0x7a4);    
            HAL_Delay(500);
            print("begin init enc28j60...");
        }
        //ENC28J60网卡地址
        unsigned char my_mac[6] = {0x29, 0x7C, 0x07, 0x37, 0x24, 0x63};
        //开始初始化,如果初始化不成功会阻塞
        enc28j60_init(my_mac);
        //表示初始化成功,说明接线正常
        print("init enc28j60 success!!!");
        
      /* USER CODE END 2 */
    
      /* Infinite loop */
      /* USER CODE BEGIN WHILE */
        
      while (1)
      {
        /* USER CODE END WHILE */
            //向网口发送物理网卡地址,直连电脑,通过Wireshark看是否能收到物理网卡地址,并且对比是否发送正确
    enc28j60_packet_send(my_mac, 6); HAL_Delay(1000); /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ }

    通过网线直连电脑,电脑不用配置网关、IP地址等一些信息。

    发送内容不一定必须是物理网卡地址,可以发送0xAA, 0xBB, 0xCC等,看电脑端Wireshark能否正常接收消息,接收消息是否和发送消息一致

    通过Wireshark监控对应网口,如果发送成功,会在列表中发现如下灰色项目:

    双击打开:

     如图,0x29  0x7c  0x07  0x37  0x24  0x63 为成功接收到的消息,并且消息接收正确。

     到此,MCU和ENC28J60成功通讯! 

    ENC28J60相关资料

    已修改好的STM32驱动(根据AVRNET,cubemx+keil5   HAL库):https://wwb.lanzouw.com/i9bnmy0cuja

      代码中用到的针脚是: CS: A4  SCK: A5  MISO: A6   MOSI: A7,不是所有针脚都能当MISO、MOSI、SCK!!!大部分针脚能当CS (GPIO 输出)

    cubemx+keil5 已测试成功helloworld项目,每秒发送一次网卡地址: https://wwb.lanzouw.com/i0Euey0czef

      代码中用到的针脚是: CS: A4  SCK: A5  MISO: A6   MOSI: A7,不是所有针脚都能当MISO、MOSI、SCK!!!大部分针脚能当CS (GPIO 输出)

    PDF中文电子书: https://wwb.lanzouw.com/iUihexzikri 密码:bvqf

    gitee AVRNET项目地址: https://gitee.com/liming2019/AVRNET

    github AVRNET 项目地址: https://github.com/JonTian/AVRNET

    某宝中ENC28J60 驱动: https://wwb.lanzouw.com/i981lxzz2kf

      从某宝上找到的资料中的ENC28J60驱动,也是从AVRNET项目中改过来的,这个驱动文件给我改AVRNET提供了很好的参考(手抄)信息。

    结尾

    从了解SPI,到找ENC28J60驱动,到成功通讯,到使用UIP ping通,前前后后花了15天左右时间(不是15天每天都在搞,也是要上班赚钱的o-o),在这里特别感谢CSDN的qllaoda帮助,遇到问题在CSDN提问后,给我解答,和私聊指导。

    CSDN 提问: enc28j60 + UIP + STM32F103C6T6 电脑端接收数据错误,导致不能通讯问题

    CSDN 提问: enc28j60 可以正常正常接收ARP消息,但是ping不通?

  • 相关阅读:
    LeetCode 123. Best Time to Buy and Sell Stock III (stock problem)
    精帖转载(关于stock problem)
    LeetCode 122. Best Time to Buy and Sell Stock II (stock problem)
    LeetCode 121. Best Time to Buy and Sell Stock (stock problem)
    LeetCode 120. Triangle
    基于docker 搭建Elasticsearch5.6.4 分布式集群
    从零开始构建一个centos+jdk7+tomcat7的docker镜像文件
    Harbor实现容器镜像仓库的管理和运维
    docker中制作自己的JDK+tomcat镜像
    docker镜像制作---jdk7+tomcat7基础镜像
  • 原文地址:https://www.cnblogs.com/GengMingYan/p/15732090.html
Copyright © 2020-2023  润新知