Урок 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 2.0
Ethernet LAN Сетевой Модуль можно купить здесь (модуль SD SPI в подарок) ENC28J60 Ethernet LAN Сетевой Модуль.
Смотреть ВИДЕОУРОК (нажмите на картинку)
Не весьма понятно условие
//пропускаем ложный байт
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