STM Урок 68. LAN. ENC28J60. Часть 2



 

Урок 68

 

Часть 2

 

LAN. ENC28J60

 

В предыдущей части нашего урока мы кратко познакомились с микросхемой ENC28J60, также создали проекты и произвели их настройки.

 

Также создадим ещё четыре файла: net.c, net.h, enc28j60.c и enc28j60.h и разместим их по папкам Inc и Src.

Сразу заполним данные фалы некоторыми стандартными макросами и директивами

 

Файл net.h

 

#ifndef __NET_H
#define __NET_H
//--------------------------------------------------
#include "stm32f1xx_hal.h"

#include <string.h>
#include <stdlib.h>
#include <stdint.h>

#include "enc28j60.h"
//--------------------------------------------------
#endif /* __NET_H */

Файл net.c

 

#include "net.h"
//--------------------------------------------------

 

Файл enc28j60.h

 

#ifndef ENC28J60_H_
#define ENC28J60_H_
//--------------------------------------------------
#include "stm32f1xx_hal.h"
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
//--------------------------------------------------
#endif /* ENC28J60_H_ */

 

Файл enc28j60.c

 

#include "enc28j60.h"
//--------------------------------------------------

 

Подключим наши библиотеки в файле main.c

 

/* USER CODE BEGIN Includes */
#include "net.h"
#include "enc28j60.h"
/* USER CODE END Includes */

 

Ну, и как всегда, первым делом инициализация.

Перейдём в файл enc28j60.c и создадим функцию

 

#include "enc28j60.h"
//--------------------------------------------------
void enc28j60_ini(void)
{
}
//--------------------------------------------------

 

Сделаем для неё прототип в хедер-файле, перейдём в файл net.c и напишем там также функцию инициализации, в которой вызовем вышенаписанную функцию

 

 

#include "net.h"
//--------------------------------------------------
void net_ini(void)
{
 enc28j60_ini();
}
//--------------------------------------------------

 

 

Сделаем для данной функции также прототип в хедер-файле net.h и вызовем её в main()

 

  /* USER CODE BEGIN 2 */
 net_ini();
  /* USER CODE END 2 */

 

Так как к интерфейсу SPI мы будем обращаться только в файле enc28j60.c, то в файле enc28j60.h добавим слеуюющие макросы (заодно напишем макросы для удобства управления красным светодиодом)

 

#include <stdint.h>
//--------------------------------------------------
#define CS_GPIO_PORT GPIOA                                      
#define CS_PIN GPIO_PIN_4
#define SS_SELECT() HAL_GPIO_WritePin(CS_GPIO_PORT, CS_PIN, GPIO_PIN_RESET)
#define SS_DESELECT() HAL_GPIO_WritePin(CS_GPIO_PORT, CS_PIN, GPIO_PIN_SET)
#define LD_ON HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); //RED
#define LD_OFF HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); //RED
//--------------------------------------------------

 

Вернёмся в файл enc28j60.c и напишем там функцию для обработки ошибки выше функции инициализации

 

//--------------------------------------------------
static void Error (void)
{
 LD_ON;
}
//--------------------------------------------------

 

Подключим шины SPI и USART в файле enc28j60.c

 

#include "enc28j60.h"
//--------------------------------------------------
extern UART_HandleTypeDef huart1;
extern SPI_HandleTypeDef hspi1;

 

После функции обработки ошибок создадим функции для работы с шиной SPI (подобными функциями мы пользовались при работе с акселерометром на плате Discovery 4 здесь)

 

//--------------------------------------------------
static uint8_t SPIx_WriteRead(uint8_t Byte)
{
  uint8_t receivedbyte = 0;
  if(HAL_SPI_TransmitReceive(&hspi1, (uint8_t*) &Byte, (uint8_t*) &receivedbyte, 1, 0x1000) != HAL_OK)
  {
    Error();
  }
  return receivedbyte;
}
//--------------------------------------------------
void SPI_SendByte(uint8_t bt)
{
 SPIx_WriteRead(bt);
}
//--------------------------------------------------
uint8_t SPI_ReceiveByte(void)
{
 uint8_t bt = SPIx_WriteRead(0xFF);
 return bt; //вернём регистр данных
}
//--------------------------------------------------

 

В функции инициализации потушим светодиод

 

void enc28j60_ini(void)
{
 LD_OFF;
}

 

 

Теперь необходимо изучить и понять, каким образом отправляются команды и данные в микросхему, а также читаются данные.

Во-первых, любая команда состоит из опкода и аргумента

 

image18

 

Опкод — это своего рода указатель типа команды (чтение управляющего регистра, запись управляющего регистра, чтение буфера, запись буфера, установка бита в регистре, очистка бита в регистре, программная (мягкая) перезагрузка. Состоит он из трёх старших битов байта команды, а в пяти младших байтах (в аргументе) уже остальная информация команды (адрес регистра, битовая маска и т.д.).

Дальше передаётся при необходимости байт данных, либо при опкодах чтения принимается байт данных с шины MISO.

Теперь давайте посмотрим, как именно передаются данные в микросхему

 

image19

 

И также посмотрим, как принимаются данные из управляющих регистров

 

image20

 

В случае если используются регистры MAC или MII, то чтение происходит с пропуском ложного байта

 

image21

 

В заголовочный файл enc28j60.h напишем серию макросов, которые нам потребуются впоследствии, в основном там будут адреса необходимых регистров, а также некоторые управляющие биты. Также здесь мы добавим желающий физический адрес (MAC-адрес) нашего устройства. Адрес можно задавать любой, так как он не присвоен микросхеме жестко, то есть это очень даже задаваемая величина. Главное чтобы адрес не совпал с каким-то другим в нашей сети, а также по некоторым сложившимся заключениям нежелательно во избежание проблем в самый первый байт что-то вообще писать, лучше оставить все биты там пустыми, ну, по крайней мере в адресе не должен быть выставлен 40-й бит.

 

enc28j60.h

#define LD_OFF HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); //RED
//--------------------------------------------------
#define ENC28J60_MAXFRAME 512
#define MAC_ADDR   {0x00,0x15,0x42,0xBF,0xF0,0x51}
//--------------------------------------------------
#define ADDR_MASK        0x1F
#define BANK_MASK        0x60
//--------------------------------------------------
//All-bank registers
#define EIE              0x1B
#define EIR              0x1C
#define ESTAT            0x1D
#define ECON2            0x1E
#define ECON1            0x1F
//--------------------------------------------------
// Bank 0 registers
#define ERDPT           (0x00|0x00)
#define EWRPT           (0x02|0x00)
#define ETXST           (0x04|0x00)
#define ETXND           (0x06|0x00)
#define ERXST           (0x08|0x00)
#define ERXND           (0x0A|0x00)
#define ERXRDPT         (0x0C|0x00)
//--------------------------------------------------
// Bank 1 registers
#define EPMM0           (0x08|0x20)
#define EPMM1           (0x09|0x20)
#define EPMCS   (0x10|0x20)
#define ERXFCON         (0x18|0x20)
#define EPKTCNT   (0x19|0x20)
//--------------------------------------------------
// Bank 2 registers
#define MACON1          (0x00|0x40|0x80)
#define MACON2          (0x01|0x40|0x80)
#define MACON3          (0x02|0x40|0x80)
#define MACON4          (0x03|0x40|0x80)
#define MABBIPG         (0x04|0x40|0x80)
#define MAIPG   (0x06|0x40|0x80)
#define MAMXFL          (0x0A|0x40|0x80)
#define MIREGADR  (0x14|0x40|0x80)
#define MIWR            (0x16|0x40|0x80)
//--------------------------------------------------
// Bank 3 registers
#define MAADR1           (0x00|0x60|0x80)
#define MAADR0           (0x01|0x60|0x80)
#define MAADR3           (0x02|0x60|0x80)
#define MAADR2           (0x03|0x60|0x80)
#define MAADR5           (0x04|0x60|0x80)
#define MAADR4           (0x05|0x60|0x80)
#define MISTAT           (0x0A|0x60|0x80)
//--------------------------------------------------
#define ERXFCON_UCEN     0x80
#define ERXFCON_ANDOR    0x40
#define ERXFCON_CRCEN    0x20
#define ERXFCON_PMEN     0x10
#define ERXFCON_MPEN     0x08
#define ERXFCON_HTEN     0x04
#define ERXFCON_MCEN     0x02
#define ERXFCON_BCEN     0x01
//--------------------------------------------------
// ENC28J60 EIE Register Bit Definitions
#define EIE_INTIE        0x80
#define EIE_PKTIE        0x40
#define EIE_DMAIE        0x20
#define EIE_LINKIE       0x10
#define EIE_TXIE         0x08
#define EIE_WOLIE        0x04
#define EIE_TXERIE       0x02
#define EIE_RXERIE       0x01
//--------------------------------------------------
// ENC28J60 EIR Register Bit Definitions
#define EIR_PKTIF        0x40
#define EIR_DMAIF        0x20
#define EIR_LINKIF       0x10
#define EIR_TXIF         0x08
#define EIR_WOLIF        0x04
#define EIR_TXERIF       0x02
#define EIR_RXERIF       0x01
//------------------------------------------------
// ENC28J60 ESTAT Register Bit Definitions
#define ESTAT_INT        0x80
#define ESTAT_LATECOL    0x10
#define ESTAT_RXBUSY     0x04
#define ESTAT_TXABRT     0x02
#define ESTAT_CLKRDY     0x01
//????????
// ENC28J60 ECON1 Register Bit Definitions
#define ECON1_TXRST      0x80
#define ECON1_RXRST      0x40
#define ECON1_DMAST      0x20
#define ECON1_CSUMEN     0x10
#define ECON1_TXRTS      0x08
#define ECON1_RXEN       0x04
#define ECON1_BSEL1      0x02
#define ECON1_BSEL0      0x01
//--------------------------------------------------
// ENC28J60 ECON1 Register Bit Definitions
#define ECON2_PKTDEC     0x40
//--------------------------------------------------
#define MACON1_LOOPBK    0x10
#define MACON1_TXPAUS    0x08
#define MACON1_RXPAUS    0x04
#define MACON1_PASSALL   0x02
#define MACON1_MARXEN    0x01
//--------------------------------------------------
#define MACON3_PADCFG2   0x80
#define MACON3_PADCFG1   0x40
#define MACON3_PADCFG0   0x20
#define MACON3_TXCRCEN   0x10
#define MACON3_PHDRLEN   0x08
#define MACON3_HFRMLEN   0x04
#define MACON3_FRMLNEN   0x02
#define MACON3_FULDPX    0x01
//--------------------------------------------------
#define MISTAT_BUSY      0x01
//--------------------------------------------------
// PHY registers
#define PHCON1           0x00
#define PHSTAT1          0x01
#define PHHID1           0x02
#define PHHID2           0x03
#define PHCON2           0x10
#define PHSTAT2          0x11
#define PHIE             0x12
#define PHIR             0x13
#define PHLCON           0x14
//--------------------------------------------------
#define PHCON2_HDLDIS    0x0100
//--------------------------------------------------
// PHLCON
#define PHLCON_LACFG3  0x0800
#define PHLCON_LACFG2  0x0400
#define PHLCON_LACFG1  0x0200
#define PHLCON_LACFG0  0x0100
#define PHLCON_LBCFG3  0x0080
#define PHLCON_LBCFG2  0x0040
#define PHLCON_LBCFG1  0x0020
#define PHLCON_LBCFG0  0x0010
#define PHLCON_LFRQ1  0x0008
#define PHLCON_LFRQ0  0x0004
#define PHLCON_STRCH  0x0002
//--------------------------------------------------
#define ENC28J60_READ_CTRL_REG       0x00
#define ENC28J60_READ_BUF_MEM        0x3A
#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
//--------------------------------------------------
#define RXSTART_INIT        0x0000  // start of RX buffer, room for 2 packets
#define RXSTOP_INIT         0x0BFF  // end of RX buffer
//--------------------------------------------------
#define TXSTART_INIT        0x0C00  // start of TX buffer, room for 1 packet
#define TXSTOP_INIT         0x11FF  // end of TX buffer
//--------------------------------------------------
#define MAX_FRAMELEN      1500
//--------------------------------------------------

 

Теперь вернёмся в файл реализации enc28j60.c и напишем функцию операции записи байта в регистр

 

//--------------------------------------------------
void enc28j60_writeOp(uint8_t op,uint8_t addres, uint8_t data)
{
 SS_SELECT();
 SPI_SendByte(op|(addres&ADDR_MASK));
 SPI_SendByte(data);
 SS_DESELECT();
}
//--------------------------------------------------

 

Во входящих аргументах функции у нас будет отдельно опкод, сдвинутый соответственно заранее на своё место в три старшие биты, адрес регистра и собственно байт данных.

ADDR_MASK — макрос для маски адреса регистра, который будет обнулять первые три бита, освобождая пространство для опкода

Ну, а теперь, операция чтения

 

//--------------------------------------------------
static uint8_t enc28j60_readOp(uint8_t op,uint8_t addres)
{
 uint8_t result;
 SS_SELECT();
 SPI_SendByte(op|(addres&ADDR_MASK));
 SPI_SendByte(0x00);
 //пропускаем ложный байт
  if(addres & 0x80) SPI_ReceiveByte();
 result=SPI_ReceiveByte();
  SS_DESELECT();
  return result;
}
//--------------------------------------------------

 

В следующей части нашего урока мы начнем писать код инициализации микросхемы, а также напишем ещё ряд нужных функций для дальнейшей работы с модулем.

 

 

Предыдущая часть Программирование МК STM32 Следующая часть

 

 

Техническая документация:

Документация на микросхему ENC28J60

Перечень ошибок ENC28J60 (Errata)

 

 

Отладочную плату STM32F103C8T6 можно приобрести здесь STM32F103C8T6

Программатор недорогой можно купить здесь ST-Link V2

Ethernet LAN Сетевой Модуль можно купить здесь ENC28J60 Ethernet LAN Сетевой Модуль.

Переходник USB to TTL можно приобрести здесь ftdi ft232rl

 

 

Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)

STM LAN. ENC28J60

 

 

Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)

STM LAN. ENC28J60

 

8 комментариев на “STM Урок 68. LAN. ENC28J60. Часть 2
  1. Можно обяснить зачем надо проверить условии addres&0x80?что такое значение 0х80.
    SPI_SendByte(op|(addres&ADDR_MASK)) я тоже непоню зачем addres&ADDR_MASK
    БОЛЬЩОЕ СБАСИБО

  2. Роман:

    #define LD_ON HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); //RED
    #define LD_OFF HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); //RED
    По-моему должно быть так:
    #define LD_ON HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); //RED
    #define LD_OFF HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); //RED

  3. dinh:

    thanks you for your great tutorials. I still don't understand your readop function. Why do you send 0x00 after send opcode and address. while i read in the datasheet, in figure 4-3, it show that the data will be received affter send opcade?
    Best regard

    • Nguyễn Tuấn Anh:

      i think it should be:
      static uint8_t enc28j60_readOp(uint8_t op,uint8_t addres)
      {
      uint8_t result;
      SS_SELECT();
      SPI_SendByte(op|(addres&ADDR_MASK));
      if(addres & 0x80) SPI_SendByte(0x00);
      result=SPI_ReceiveByte();
      SS_DESELECT();
      return result;
      }

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*