Урок 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
Ethernet LAN Сетевой Модуль можно купить здесь ENC28J60 Ethernet LAN
Переходник USB to TTL можно приобрести здесь USB to TTL ftdi ft232rl
Смотреть ВИДЕОУРОК (нажмите на картинку)
Можно обяснить зачем надо проверить условии 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;
}