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

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

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

 

 

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

 

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 не будет опубликован. Обязательные поля помечены *

*