EMAC driver for the ENC28J60 Ethernet controller. This is a simplified fork of https://github.com/tobiasjaster/ENC28J60-EMAC-Driver published by Tobias Jaster.
Dependents: MQTT_Hello MQTT_HelloENC28J60
EMAC driver for the ENC28J60 Ethernet controller
This is a fork (the INT and RST pins are not used) of the ENC28J60-EMAC driver published by Tobias Jaster at
https://github.com/tobiasjaster/ENC28J60-EMAC-Driver
Usage:
- Connect the ENC28J60 module to the Mbed board as follows:
- Import (add) this ENC28J60-EMAC library to your program.
- Add a "mbed_app.json" file with the following content to the root directory of your program:
{ "target_overrides": { "*": { "platform.callback-nontrivial": true, "enc28j60-emac.mosi": "D11", "enc28j60-emac.miso": "D12", "enc28j60-emac.sck" : "D13", "enc28j60-emac.cs" : "D10" } } }
- Replace "D11", ..., "D10" with the actual pin names you selected on the Mbed board to be used for the SPI communication.
- To set the MAC address define an array with the desired address bytes and call the "set_hwaddr(mac)" function before calling the network interface "connect" function.
For example:
const uint8_t MAC[6] = { 0, 1, 2, 3, 4, 5 }; EthernetInterface net; int main() { net.get_emac().set_hwaddr(MAC); // set MAC address if (net.connect() != 0) { printf("Error: Unable to connect to the network.\n"); return -1; } ...
enc28j60.cpp
- Committer:
- hudakz
- Date:
- 2021-03-29
- Revision:
- 3:aa88808326b9
- Parent:
- 1:bce04bfc41fe
File content as of revision 3:aa88808326b9:
/* * Copyright (c) 2019 Tobias Jaster * * Modified by Zoltan Hudak * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "cmsis.h" #include "enc28j60.h" #ifndef NULL #define NULL ((void*)0) #endif #define ENC28J60_DEBUG 0 #if ENC28J60_DEBUG #define ENC28J60_DEBUG_PRINTF(...) printf(__VA_ARGS__) #else #define ENC28J60_DEBUG_PRINTF(...) #endif /** Millisec timeout macros */ #define RESET_TIME_OUT_MS 10ms #define REG_WRITE_TIME_OUT_MS 50ms #define PHY_RESET_TIME_OUT_MS 100ms #define INIT_FINISH_DELAY 2000ms /** * \brief TX FIFO Size definitions * */ #define TX_STATUS_FIFO_SIZE_BYTES 512U /*< fixed allocation in bytes */ #define TX_DATA_FIFO_SIZE_KBYTES_POS 8U #define TX_DATA_FIFO_SIZE_KBYTES_MASK 0x0FU #define KBYTES_TO_BYTES_MULTIPLIER 1024U /** * \brief FIFO Info definitions * */ #define FIFO_USED_SPACE_MASK 0xFFFFU #define DATA_FIFO_USED_SPACE_POS 0U #define STATUS_FIFO_USED_SPACE_POS 16U #define HW_CFG_REG_RX_FIFO_POS 10U #define HW_CFG_REG_RX_FIFO_SIZE_ALL 8U #define HW_CFG_REG_RX_FIFO_SIZE_MIN 2U /*< Min Rx fifo size in KB */ #define HW_CFG_REG_RX_FIFO_SIZE_MAX 6U /*< Max Rx fifo size in KB */ #define HW_CFG_REG_RX_FIFO_SIZE 5U /*< Rx fifo size in KB */ /** * @brief * @note * @param * @retval */ ENC28J60::ENC28J60(PinName mosi, PinName miso, PinName sclk, PinName cs) : _spi(new SPI(mosi, miso, sclk)), _cs(cs), _bank(0), _ready(true), _next(ERXST_INI) { init(); } /** * @brief * @note * @param * @retval */ ENC28J60::ENC28J60(mbed::SPI* spi, PinName cs) : _spi(spi), _cs(cs), _bank(0), _ready(true), _next(ERXST_INI) { init(); } /** * @brief * @note * @param * @retval */ void ENC28J60::init() { // Initialize SPI interface _cs = 1; _spi->format(8, 0); // 8bit, mode 0 _spi->frequency(20000000); // 20MHz // Wait SPI to become stable ThisThread::sleep_for(RESET_TIME_OUT_MS); // Perform system reset writeOp(ENC28J60_SOFT_RESET, 0, ENC28J60_SOFT_RESET); // Check CLKRDY bit to see if reset is complete // while(!(readReg(ESTAT) & ESTAT_CLKRDY)); // The CLKRDY does not work. See Rev. B4 Silicon Errata point. // Workaround: Just wait. ThisThread::sleep_for(RESET_TIME_OUT_MS); // Set pointers to receive buffer boundaries writeRegPair(ERXSTL, ERXST_INI); writeRegPair(ERXNDL, ERXND_INI); // Set receive pointer. Receive hardware will write data up to, // but not including the memory pointed to by ERXRDPT writeReg(ERXRDPTL, ERXST_INI); // All memory which is not used by the receive buffer is considered the transmission buffer. // No explicit action is required to initialize the transmission buffer. // TX buffer start. writeRegPair(ETXSTL, ETXST_INI); // TX buffer end at end of ethernet buffer memory. writeRegPair(ETXNDL, ETXND_INI); // However, he host controller should leave at least seven bytes between each // packet and the beginning of the receive buffer. // do bank 1 stuff, packet filter: // For broadcast packets we allow only ARP packtets // All other packets should be unicast only for our mac (MAADR) // // The pattern to match is therefore // Type ETH.DST // ARP BROADCAST // 06 08 -- ff ff ff ff ff ff -> ip checksum for theses bytes=f7f9 // in binary these poitions are:11 0000 0011 1111 // This is hex 303F->EPMM0=0x3f,EPMM1=0x30 //TODO define specific pattern to receive dhcp-broadcast packages instead of setting ERFCON_BCEN! writeReg(ERXFCON, ERXFCON_UCEN | ERXFCON_CRCEN | ERXFCON_PMEN | ERXFCON_BCEN); writeRegPair(EPMM0, 0x303f); writeRegPair(EPMCSL, 0xf7f9); // Enable MAC receive and bring MAC out of reset (writes 0x00 to MACON2) writeRegPair(MACON1, MACON1_MARXEN | MACON1_TXPAUS | MACON1_RXPAUS); // Enable automatic padding to 60bytes and CRC operations writeOp(ENC28J60_BIT_FIELD_SET, MACON3, MACON3_PADCFG0 | MACON3_TXCRCEN | MACON3_FRMLNEN); // Set inter-frame gap (non-back-to-back) writeRegPair(MAIPGL, 0x0C12); // Set inter-frame gap (back-to-back) writeReg(MABBIPG, 0x12); // Set the maximum packet size which the controller will accept // Do not send packets longer than MAX_FRAMELEN: writeRegPair(MAMXFLL, MAX_FRAMELEN); // No loopback of transmitted frames phyWrite(PHCON2, PHCON2_HDLDIS); // Switch to bank 0 _setBank(ECON1); // Enable interrutps writeOp(ENC28J60_BIT_FIELD_SET, EIE, EIE_INTIE | EIE_PKTIE); // Enable packet reception writeOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_RXEN); // Configure leds phyWrite(PHLCON, 0x476); } /** * @brief * @note * @param * @retval */ enc28j60_error_t ENC28J60::getPacketInfo(packet_t* packet) { enc28j60_error_t ret; uint8_t nextPacketAddrL; uint8_t nextPacketAddrH; uint8_t status[RX_STAT_LEN]; if (!_ready) return ENC28J60_ERROR_LASTPACKET; // Check if a new packet has been received and buffered. // The Receive Packet Pending Interrupt Flag (EIR.PKTIF) does not reliably // report the status of pending packets. See Rev. B4 Silicon Errata point 6. // Workaround: Check EPKTCNT if (readReg(EPKTCNT) == 0) return ENC28J60_ERROR_NOPACKET; _ready = false; // Packet pointer in receive buffer is wrapped by hardware. packet->addr = _next; // Program the receive buffer read pointer to point to this packet. writeRegPair(ERDPTL, packet->addr); // Read the next packet address nextPacketAddrL = readOp(ENC28J60_READ_BUF_MEM, 0); nextPacketAddrH = readOp(ENC28J60_READ_BUF_MEM, 0); _next = (nextPacketAddrH << 8) | nextPacketAddrL; // Read the packet status vector bytes (see datasheet page 43) for (uint8_t i = 0; i < RX_STAT_LEN; i++) { status[i] = readOp(ENC28J60_READ_BUF_MEM, 0); } // Get payload length (see datasheet page 43) packet->payload.len = (status[1] << 8) | status[0]; // Remove CRC bytes packet->payload.len -= RX_CRC_LEN; // Check CRC and symbol errors (see datasheet page 44, table 7-3): // The ERXFCON.CRCEN is set by default. Normally we should not // need to check this. // Bit 7 in byte 3 of receive status vectors indicates that the packet // had a valid CRC and no symbol errors. if ((status[2] & (1 << 7)) != 0) { ret = ENC28J60_ERROR_OK; } else { // Drop faulty packet: // Move the receive read pointer to the begin of the next packet. // This frees the memory we just read out. freeRxBuffer(); ret = ENC28J60_ERROR_RECEIVE; } // Decrement the packet counter to indicate we are done with this packet. writeOp(ENC28J60_BIT_FIELD_SET, ECON2, ECON2_PKTDEC); return ret; } /** * @brief * @note * @param * @retval */ void ENC28J60::readPacket(packet_t* packet) { // Read operation in receive buffer wraps the read pointer automatically. // To utilize this feature we program the receive buffer read pointer (ERDPTL) // to point to the begin of this packet (see datasheet page 43). // Then we read the next packet pointer bytes + packet status vector bytes // rather than calculating the payload position and advancing ERDPTL by software. writeRegPair(ERDPTL, packet->addr); // Advance the read pointer to the payload by reading bytes out of interest. for (uint8_t i = 0; i < (RX_NEXT_LEN + RX_STAT_LEN); i++) readOp(ENC28J60_READ_BUF_MEM, 0); // The receive buffer read pointer is now pointing to the correct place. // We can read packet payload bytes. readBuf(packet->payload.buf, packet->payload.len); } /** * @brief Frees the memory occupied by last packet. * @note Programs the Receive Pointer (ERXRDPT)to point to the next * packet address. Receive hardware will write data up to, * but not including the memory pointed to by ERXRDPT. * @param * @retval */ void ENC28J60::freeRxBuffer(void) { // Compensate for the errata rev B7, point 11: // The receive hardware may corrupt the circular // receive buffer (including the Next Packet Pointer // and receive status vector fields) when an even value // is programmed into the ERXRDPTH:ERXRDPTL registers. // Workaround: Never write an even address! if ((_next - 1 < ERXST_INI) || (_next - 1 > ERXND_INI)) writeRegPair(ERXRDPTL, ERXND_INI); else writeRegPair(ERXRDPTL, _next - 1); _ready = true; // ready for next packet } /** * @brief * @note * @param * @retval */ enc28j60_error_t ENC28J60::loadPacketInTxBuffer(packet_t* packet) { uint8_t controlByte = 0; enc28j60_error_t error = ENC28J60_ERROR_OK; uint16_t packetLen = TX_CTRL_LEN + packet->payload.len + TX_STAT_LEN; if (packetLen > ETXND_INI - ETXST_INI) { error = ENC28J60_ERROR_FIFOFULL; return error; } setWritePrt(ETXST_INI, 0); //_tx_packet.payload.len = data_len; writeBuf(&controlByte, sizeof(controlByte)); error = setWritePrt(ETXST_INI, sizeof(controlByte)); writeBuf(packet->payload.buf, packet->payload.len); return error; } /** * @brief * @note * @param * @retval */ enc28j60_error_t ENC28J60::softReset(void) { writeOp(ENC28J60_SOFT_RESET, 0, ENC28J60_SOFT_RESET); ThisThread::sleep_for(1ms); return ENC28J60_ERROR_OK; } /** * @brief * @note * @param * @retval */ void ENC28J60::setRxBufSize(uint32_t size_kb) { if (size_kb >= HW_CFG_REG_RX_FIFO_SIZE_MIN && size_kb <= HW_CFG_REG_RX_FIFO_SIZE_MAX) { writeRegPair(ERXSTL, ERXST_INI); // set receive pointer address writeRegPair(ERXRDPTL, ERXST_INI); // RX end writeRegPair(ERXNDL, 0x1FFF - (size_kb << HW_CFG_REG_RX_FIFO_POS)); } } /** * @brief * @note * @param * @retval */ enc28j60_error_t ENC28J60::resetPhy(void) { enc28j60_error_t error = ENC28J60_ERROR_OK; uint16_t phcon1 = 0; error = phyRead(PHCON1, &phcon1); if (error) return ENC28J60_ERROR_TIMEOUT; error = phyWrite(PHCON1, (phcon1 | PHCON1_PRST)); if (error) return ENC28J60_ERROR_TIMEOUT; ThisThread::sleep_for(PHY_RESET_TIME_OUT_MS); error = phyRead(PHCON1, &phcon1); if (error || (phcon1 & PHCON1_PRST) != 0) { return ENC28J60_ERROR_TIMEOUT; } return ENC28J60_ERROR_OK; } /** * @brief * @note * @param * @retval */ void ENC28J60::enableMacRecv(void) { writeOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_RXEN); } /** * @brief * @note * @param * @retval */ void ENC28J60::disableMacRecv(void) { writeOp(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_RXEN); } /** * @brief * @note * @param * @retval */ enc28j60_error_t ENC28J60::readMacAddr(char* mac) { if (!mac) { return ENC28J60_ERROR_PARAM; } _setBank(MAADR0); mac[0] = readReg(MAADR5); mac[1] = readReg(MAADR4); mac[2] = readReg(MAADR3); mac[3] = readReg(MAADR2); mac[4] = readReg(MAADR1); mac[5] = readReg(MAADR0); return ENC28J60_ERROR_OK; } /** * @brief * @note * @param * @retval */ enc28j60_error_t ENC28J60::writeMacAddr(char* mac) { if (!mac) { return ENC28J60_ERROR_PARAM; } _setBank(MAADR0); writeReg(MAADR5, mac[0]); writeReg(MAADR4, mac[1]); writeReg(MAADR3, mac[2]); writeReg(MAADR2, mac[3]); writeReg(MAADR1, mac[4]); writeReg(MAADR0, mac[5]); return ENC28J60_ERROR_OK; } /** * @brief * @note * @param * @retval */ enc28j60_error_t ENC28J60::setWritePrt(uint16_t position, uint16_t offset) { uint32_t start = position + offset > ETXND_INI ? position + offset - ETXND_INI + ETXST_INI : position + offset; writeRegPair(EWRPTL, start); return ENC28J60_ERROR_OK; } /** * @brief * @note * @param * @retval */ enc28j60_error_t ENC28J60::transmitPacket(packet_t* packet) { // Set Transmit Buffer Start pointer writeRegPair(ETXSTL, ETXST_INI); // Set Transmit Buffer End pointer writeRegPair(ETXNDL, ETXST_INI + TX_CTRL_LEN + packet->payload.len); // Enable transmittion writeOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRTS); // Wait until transmission is completed while ((readReg(ECON1) & ECON1_TXRTS) != 0) { } // Chek whether the transmission was successfull if ((readReg(ESTAT) & ESTAT_TXABRT) == 0) return ENC28J60_ERROR_OK; else return ENC28J60_ERROR_NEXTPACKET; } /** * @brief * @note * @param * @retval */ uint32_t ENC28J60::getRxBufFreeSpace(void) { uint16_t readPointer = getRecvPointer(); uint16_t writePointer = getWritePointer(); uint32_t freeSpace = 0; if (writePointer > readPointer) { freeSpace = (uint32_t) (ERXND_INI - ERXST_INI) - (writePointer - readPointer); } else if (writePointer == readPointer) { freeSpace = (ERXND_INI - ERXST_INI); } else { freeSpace = readPointer - writePointer - 1; } return freeSpace; } /** * @brief Sets Ethernet buffer read pointer * @note Points to a location in receive buffer to read from. * @param * @retval */ enc28j60_error_t ENC28J60::setRxBufReadPtr(uint16_t position) { // // Wrap the start pointer of received data when greater than end of receive buffer if (position > ERXND_INI) position = ERXST_INI + (position - ERXND_INI - 1); writeRegPair(ERDPTL, position); return ENC28J60_ERROR_OK; } /** * @brief Sets receive pointer. * @note Receive hardware will write received data up to, but not including * the memory pointed to by the receive pointer. * @param * @retval */ uint16_t ENC28J60::getRecvPointer(void) { return readRegPair(ERXRDPTL); } /** * @brief Gets receive buffer's write pointer. * @note Location within the receive buffer where the hardware will write bytes that it receives. * The pointer is read-only and is automatically updated by the hardware whenever * a new packet is successfully received. * @param * @retval */ uint16_t ENC28J60::getWritePointer(void) { uint16_t count_pre = readReg(EPKTCNT); uint16_t writePointer = readRegPair(ERXWRPTL); uint16_t count_post = readReg(EPKTCNT); while (count_pre != count_post) { count_pre = count_post; writePointer = readRegPair(ERXWRPTL); count_post = readReg(EPKTCNT); } ENC28J60_DEBUG_PRINTF("[ENC28J60] rx_getWritePointer: %d", writePointer); return writePointer; } /** * @brief * @note * @param * @retval */ void ENC28J60::readBuf(uint8_t* data, uint16_t len) { _read(ENC28J60_READ_BUF_MEM, data, len, false); data[len] = '\0'; } /** * @brief * @note * @param * @retval */ void ENC28J60::writeBuf(uint8_t* data, uint16_t len) { _write(ENC28J60_WRITE_BUF_MEM, data, len, false); } /** * @brief * @note * @param * @retval */ uint8_t ENC28J60::readReg(uint8_t address) { _setBank(address); // do the read return readOp(ENC28J60_READ_CTRL_REG, address); } /** * @brief * @note * @param * @retval */ uint16_t ENC28J60::readRegPair(uint8_t address) { uint16_t temp; _setBank(address); // do the read temp = (uint16_t) (readOp(ENC28J60_READ_CTRL_REG, address + 1)) << 8; temp |= readOp(ENC28J60_READ_CTRL_REG, address); return temp; } /** * @brief * @note * @param * @retval */ void ENC28J60::writeReg(uint8_t address, uint8_t data) { _setBank(address); // do the write writeOp(ENC28J60_WRITE_CTRL_REG, address, data); } /** * @brief * @note * @param * @retval */ void ENC28J60::writeRegPair(uint8_t address, uint16_t data) { _setBank(address); // do the write writeOp(ENC28J60_WRITE_CTRL_REG, address, (data & 0xFF)); writeOp(ENC28J60_WRITE_CTRL_REG, address + 1, (data) >> 8); } /** * @brief * @note * @param * @retval */ enc28j60_error_t ENC28J60::phyRead(uint8_t address, uint16_t* data) { uint8_t timeout = 0; writeReg(MIREGADR, address); writeReg(MICMD, MICMD_MIIRD); // wait until the PHY read completes while (readReg(MISTAT) & MISTAT_BUSY) { wait_us(15); timeout++; if (timeout > 10) return ENC28J60_ERROR_TIMEOUT; } //and MIRDH writeReg(MICMD, 0); *data = (readReg(MIRDL) | readReg(MIRDH) << 8); return ENC28J60_ERROR_OK; } /** * @brief * @note * @param * @retval */ enc28j60_error_t ENC28J60::phyWrite(uint8_t address, uint16_t data) { uint8_t timeout = 0; // set the PHY register address writeReg(MIREGADR, address); // write the PHY data writeRegPair(MIWRL, data); // wait until the PHY write completes while (readReg(MISTAT) & MISTAT_BUSY) { wait_us(15); timeout++; if (timeout > 10) return ENC28J60_ERROR_TIMEOUT; } return ENC28J60_ERROR_OK; } /** * @brief * @note * @param * @retval */ bool ENC28J60::linkStatus(void) { uint16_t data; phyRead(PHSTAT2, &data); return(data & 0x0400) > 0; } /** * @brief * @note * @param * @retval */ uint8_t ENC28J60::readOp(uint8_t op, uint8_t address) { uint8_t result; uint8_t data[2]; // issue read command if (address & 0x80) { _read((op | (address & ADDR_MASK)), &data[0], 2, false); result = data[1]; return result; } else { _read((op | (address & ADDR_MASK)), &data[0], 1, false); } result = data[0]; return result; } /** * @brief * @note * @param * @retval */ void ENC28J60::writeOp(uint8_t op, uint8_t address, uint8_t data) { // issue write command _write(op | (address & ADDR_MASK), &data, 1, false); } /** * @brief * @note * @param * @retval */ void ENC28J60::_setBank(uint8_t address) { if ((address & BANK_MASK) != _bank) { writeOp(ENC28J60_BIT_FIELD_CLR, ECON1, (ECON1_BSEL1 | ECON1_BSEL0)); writeOp(ENC28J60_BIT_FIELD_SET, ECON1, (address & BANK_MASK) >> 5); _bank = (address & BANK_MASK); } } /** * @brief * @note * @param * @retval */ void ENC28J60::_read(uint8_t cmd, uint8_t* buf, uint16_t len, bool blocking) { #ifndef ENC28J60_READWRITE _SPIMutex.lock(); _cs = 0; // issue read command _spi->write((int)cmd); while (len) { len--; // read data *buf = _spi->write(0x00); buf++; } _cs = 1; _SPIMutex.unlock(); #else uint8_t* dummy = NULL; _readwrite(cmd, buf, dummy, len, blocking); #endif } /** * @brief * @note * @param * @retval */ void ENC28J60::_write(uint8_t cmd, uint8_t* buf, uint16_t len, bool blocking) { #ifndef ENC28J60_READWRITE _SPIMutex.lock(); _cs = 0; // issue read command _spi->write((int)cmd); while (len) { len--; // read data _spi->write((int) *buf); buf++; } _cs = 1; _SPIMutex.unlock(); #else uint8_t* dummy = NULL; _readwrite(cmd, dummy, buf, len, blocking); #endif } #ifdef ENC28J60_READWRITE /** * @brief * @note * @param * @retval */ void ENC28J60::_readwrite(uint8_t cmd, uint8_t* readbuf, uint8_t* writebuf, uint16_t len, bool blocking) { _SPIMutex.lock(); _cs = 0; // issue read command _spi->write((int)cmd); while (len) { len--; if (readbuf == NULL) { _spi->write((int) *writebuf); writebuf++; } else if (writebuf == NULL) { *readbuf = _spi->write(0x00); readbuf++; } else { break; } } _cs = 1; _SPIMutex.unlock(); } #endif