Урок 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;
}
Теперь необходимо изучить и понять, каким образом отправляются команды и данные в микросхему, а также читаются данные.
Во-первых, любая команда состоит из опкода и аргумента
Опкод — это своего рода указатель типа команды (чтение управляющего регистра, запись управляющего регистра, чтение буфера, запись буфера, установка бита в регистре, очистка бита в регистре, программная (мягкая) перезагрузка. Состоит он из трёх старших битов байта команды, а в пяти младших байтах (в аргументе) уже остальная информация команды (адрес регистра, битовая маска и т.д.).
Дальше передаётся при необходимости байт данных, либо при опкодах чтения принимается байт данных с шины MISO.
Теперь давайте посмотрим, как именно передаются данные в микросхему
И также посмотрим, как принимаются данные из управляющих регистров
В случае если используются регистры MAC или MII, то чтение происходит с пропуском ложного байта
В заголовочный файл enc28j60.h напишем серию макросов, которые нам потребуются впоследствии, в основном там будут адреса необходимых регистров, а также некоторые управляющие биты. Также здесь мы добавим желающий физический адрес (MAC-адрес) нашего устройства. Адрес можно задавать любой, так как он не присвоен микросхеме жестко, то есть это очень даже задаваемая величина. Главное чтобы адрес не совпал с каким-то другим в нашей сети, а также по некоторым сложившимся заключениям нежелательно во избежание проблем в самый первый байт что-то вообще писать, лучше оставить все биты там пустыми, ну, по крайней мере в адресе не должен быть выставлен 40-й бит.
#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 (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Можно обяснить зачем надо проверить условии addres&0x80?что такое значение 0х80.
SPI_SendByte(op|(addres&ADDR_MASK)) я тоже непоню зачем addres&ADDR_MASK
БОЛЬЩОЕ СБАСИБО
Даже что-то и не помню зачем, давно уже это было, но хорошо помню, что я объяснял зачем)
уже понимал, но вот ворос:
uint8_t SPI_ReceiveByte(void)
{
uint8_t bt = SPIx_WriteRead(0xFF);
return bt; //вернём регистр данных
}
После этого bt==0xFF???
SPIx_WriteRead(0xFF)???НЕ ПОНИМАЮ ПОЧЕМУ В СКОБКИ ЗНАЧЕНИЕ 0xFF.
Можно любое, но принято так. Надо что-то послать, чтобы был обмен.
#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
У нас светодиод подключен катодом, поэтому, когда мы дадим на него нулевой уровень, то он загорится. У Вас, видимо, другая плата.
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
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;
}