Урок 40
Часть 2
LAN. ENC28J60
В предыдущей части нашего урока мы познакомились с микросхемой ENC28J60, создали и настроили проект и добавили каркасы некоторых функций.
Так как к интерфейсу SPI мы будем обращаться только в файле enc28j60.c, то в файле enc28j60.h добавим слеующие макросы
#include «main.h»
//—————————————————
#define MOSI 3
#define MISO 4
#define SCK 5
#define SS 2
#define SS_SELECT() PORTB&=~(1<<SS) //CS = L
#define SS_DESELECT() PORTB|=(1<<SS) //CS = H
//————————————————
В файле enc28j60.c напишем функцию инициализации SPI
#include «enc28j60.h»
//———————————————-
void SPI_ini(void)
{
DDRB|=(1<<SS)|(1<<MOSI)|(1<<SCK); //ножки SPI на выход
DDRB&=~(1<<MISO); //MISO на вход
SS_DESELECT();
SPCR = (1<<SPE)|(1<<MSTR); //Enable SPI, Master
SPSR |= (1<<SPI2X);//Double SPI Speed (fosc/2 = 8 MHz)
}
//—————————————-
Ниже в этом же файле напишем функцию обмена байтами по интерфейсу SPI
//—————————————-
uint8_t SPI_ChangeByte(uint8_t bt)
{
SPDR = bt; //отправим байт в регистр данных
while(!(SPSR & (1<<SPIF))) //подождем пока данные передадутся
;
return SPDR; //вернём регистр данных
}
//—————————————-
Также, пользуясь вышенаписанной функцией, напишем функции отправки и приёма байта по шине SPI
//—————————————-
void SPI_SendByte(uint8_t bt)
{
SPI_ChangeByte(bt);
}
//—————————————-
uint8_t SPI_ReceiveByte(void)
{
uint8_t bt = SPI_ChangeByte(0xFF);
return bt; //вернём регистр данных
}
//—————————————-
В функции инициализации вызовем инициализацию шины SPI
void enc28j60_ini(void)
{
SPI_ini();
Теперь необходимо изучить и понять, каким образом отправляются команды и данные в микросхему, а также читаются данные.
Во-первых, любая команда состоит из опкода и аргумента
Опкод — это своего рода указатель типа команды (чтение управляющего регистра, запись управляющего регистра, чтение буфера, запись буфера, установка бита в регистре, очистка бита в регистре, программная (мягкая) перезагрузка. Состоит он из трёх старших битов байта команды, а в пяти младших байтах (в аргументе) уже остальная информация команды (адрес регистра, битовая маска и т.д.).
Дальше передаётся при необходимости байт данных, либо при опкодах чтения принимается байт данных с шины MISO.
Теперь давайте посмотрим, как именно передаются данные в микросхему
И также посмотрим, как принимаются данные из управляющих регистров
В случае если используются регистры MAC или MII, то чтение происходит с пропуском ложного байта
В заголовочный файл enc28j60.h напишем серию макросов, которые нам потребуются впоследствии, в основном там будут адреса необходимых регистров, а также некоторые управляющие биты. Также здесь мы добавим желающий физический адрес (MAC-адрес) нашего устройства. Адрес можно задавать любой, так как он не присвоен микросхеме жестко, то есть это очень даже задаваемая величина. Главное чтобы адрес не совпал с каким-то другим в нашей сети, а также по некоторым сложившимся заключениям нежелательно во избежание проблем в самый первый байт что-то вообще писать, лучше оставить все биты там пустыми, ну, по крайней мере в адресе не должен быть выставлен 40-й бит.
#define SS_DESELECT() PORTB|=(1<<SS) //CS = H
//————————————————
#define ENC28J60_MAXFRAME 512
#define MAC_ADDR {0x00,0x13,0x37,0x01,0x23,0x45}
//————————————————
#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 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_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 и напишем функцию операции записи байта в регистр
//—————————————-
static void enc28j60_writeOp (uint8_t op, uint8_t address, uint8_t data)
{
SS_SELECT();
SPI_SendByte(op | (address & ADDR_MASK));
SPI_SendByte(data);
SS_DESELECT();
}
//—————————————-
Во входящих аргументах функции у нас будет отдельно опкод, сдвинутый соответственно заранее на своё место в три старшие биты, адрес регистра и собственно байт данных.
ADDR_MASK — макрос для маски адреса регистра, который будет обнулять первые три бита, освобождая пространство для опкода
Ну, а теперь, операция чтения
//—————————————-
static uint8_t enc28j60_readOp (uint8_t op, uint8_t address)
{
SS_SELECT();
SPI_SendByte(op | (address & ADDR_MASK));
SPI_SendByte(0x00);
//пропускаем ложный байт
if (address & 0x80)
SPI_SendByte(0x00);
uint8_t result = SPDR;
SS_DESELECT();
return result;
}
//—————————————-
Произведём «мягкий» сброс в функции инициализации и проверим, что всё перезагрузилось
SPI_ini();
enc28j60_writeOp(ENC28J60_SOFT_RESET, 0, ENC28J60_SOFT_RESET);
_delay_ms(2);
//проверим, что всё перезагрузилось
while (!enc28j60_readOp(ENC28J60_READ_CTRL_REG, ESTAT) & ESTAT_CLKRDY)
;
В следующей части нашего урока мы напишем ещё несколько служебных функций, настроим буферы микросхемы, а также её канальный уровень и напишем функцию записи данных в регистры PHY.
Предыдущая часть Программирование МК AVR Следующая часть
Техническая документация:
Документация на микросхему ENC28J60
Перечень ошибок ENC28J60 (Errata)
Приобрести плату Atmega 328p Pro Mini можно здесь.
Приобрести программатор USBASP USBISP с адаптером можно здесь USBASP USBISP 3.3 с адаптером
Ethernet LAN Сетевой Модуль можно купить здесь ENC28J60 Ethernet LAN Сетевой Модуль.
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Не весьма понятно условие
//пропускаем ложный байт
if (address & 0x80)
что конкретно так проверяется
Ничего не проверяется. Есть лишний байт, его не обрабатываем.
Тоже не до конца понял как обрабатывается это условие if (address & 0x80)
Пробовал это условие в тестовом режиме, начинает выполняться если оно сравнивается с чем-то ( в частности с 0x80). Может быть все зависит от компилятора? Использую Atmel studio 7.
Хотя нет. Разобрался. В данном случае, if (address & 0x80), проверяется именно старший (7) бит. И если он (7 бит) равен 1, тогда условие истинно. Во всех других (кроме как 7 бит = 1) условие лож.
Здравствуйте, а можно поподробнее почему: "в адресе не должен быть выставлен 40-й бит"
Здравствуйте. Я думаю, что это уже уходит далеко за рамки нашего занятия.
Тем более я сам не знаю, почему.
Точно так же прочитал в технической документации, уже не помню в какой, вот и решил поделиться (лучше бы не делился не полчил бы провокационный вопрос).
Ок, понятно. просто я такой информации не нашел. Наверное, да лучше сразу отсылать к первоисточникам. Спасибо вам за уроки.
Доброго дня !
можете прокоментировать обьявление
#define MAADR1 (0x00|0x60|0x80)
то что находиться в скобках
1- адрес МААDR в банке 3
2- ?
3- ?
почему у вас опкод в дифайнах не совпадаетс документацией?
насчет дифайнов — всё верно, не досмотрел.
а для чего нам нужна функция SPI_ChangeByte?
если только для двух последующих функций, то их блоки можно переписать без функции SPI_ChangeByte.
Или другой смысл несёт эта функция?
Подключил библиотеку usart. но у меня ошибка с 328 мегой. как будто он не знает что у него есть USART на борту. UBRRH, RXCIE, UDR и т.д. он не знает, Библиотеки подключил, не пойму в чем подвох, с тем же usart проектом но только на 8 меги все работает
Значит решил я залезть в стандартные библиотеки и нашел там значения UBRR0H, RXCIE0, UDR0 я так понимаю в нем 2 usart планировалось или для удобства сделали, в общем не нашел только URSEL. и еще из уроков по usart помню там про вектора что то говорилось 11 и 12 а тут 18, короче у меня не подходит старая библиотека под мегу 328