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不通?

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM