mbed OS5
Fork of UIPEthernet by
Diff: utility/Enc28J60Network.cpp
- Revision:
- 3:5b17e4656dd0
- Child:
- 4:d774541a34da
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/utility/Enc28J60Network.cpp Sat Dec 20 11:10:40 2014 +0000 @@ -0,0 +1,695 @@ +/* + Enc28J60Network.h + UIPEthernet network driver for Microchip ENC28J60 Ethernet Interface. + + Copyright (c) 2013 Norbert Truchsess <norbert.truchsess@t-online.de> + All rights reserved. + + based on enc28j60.c file from the AVRlib library by Pascal Stang. + For AVRlib See http://www.procyonengineering.com/ + + Modified (ported to mbed) by Zoltan Hudak <hudakz@inbox.com> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +#include "Enc28J60Network.h" +#include "mbed.h" + +extern "C" +{ +#include "enc28j60.h" +#include "uip.h" +} +#define DMARUNNING 1 +#define DMANEWPACKET 2 + +#define waitspi() while(!(SPSR & (1 << SPIF))) + Enc28J60Network::Enc28J60Network(PinName mosi, PinName miso, PinName sclk, PinName cs) : + MemoryPool(TXSTART_INIT + 1, TXSTOP_INIT - TXSTART_INIT), + + // 1 byte in between RX_STOP_INIT and pool to allow prepending of controlbyte + bank(0xff), + _spi(mosi, miso, sclk), + _cs(cs) { } + +/** + * @brief + * @note + * @param + * @retval + */ +void Enc28J60Network::init(uint8_t* macaddr) { + + // initialize SPI interface + + _spi.format(8, 0); // 8bit, mode 0 + _spi.frequency(7000000); // 7MHz + wait(1); // 1 second for stable state + + // initialize I/O + _cs = 1; // ss=0 + + // initialize SPI interface + // master mode and Fosc/2 clock: + //SPCR = (1<<SPE)|(1<<MSTR); + //SPSR |= (1<<SPI2X); + // perform system reset + writeOp(ENC28J60_SOFT_RESET, 0, ENC28J60_SOFT_RESET); + wait(0.020); // 20ms + + // check CLKRDY bit to see if reset is complete + // The CLKRDY does not work. See Rev. B4 Silicon Errata point. Just wait. + //while(!(readReg(ESTAT) & ESTAT_CLKRDY)); + // do bank 0 stuff + // initialize receive buffer + // 16-bit transfers, must write low byte first + // set receive buffer start address + nextPacketPtr = RXSTART_INIT; + + // Rx start + writeRegPair(ERXSTL, RXSTART_INIT); + + // set receive pointer address + writeRegPair(ERXRDPTL, RXSTART_INIT); + + // RX end + writeRegPair(ERXNDL, RXSTOP_INIT); + + // TX start + //writeRegPair(ETXSTL, TXSTART_INIT); + // TX end + //writeRegPair(ETXNDL, TXSTOP_INIT); + // 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 on 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); + + // + // + // do bank 2 stuff + // 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); + + // do bank 3 stuff + // write MAC address + // NOTE: MAC address in ENC28J60 is byte-backward + writeReg(MAADR5, macaddr[0]); + writeReg(MAADR4, macaddr[1]); + writeReg(MAADR3, macaddr[2]); + writeReg(MAADR2, macaddr[3]); + writeReg(MAADR1, macaddr[4]); + writeReg(MAADR0, macaddr[5]); + + // 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); + clkout(2); // change clkout from 6.25MHz to 12.5MHz + wait(0.000060); // 60us + + //Configure leds + phyWrite(PHLCON, 0x476); +} + +/** + * @brief + * @note + * @param + * @retval + */ +memhandle Enc28J60Network::receivePacket(void) { + uint16_t rxstat; + uint16_t len; + // check if a packet has been received and buffered + + //if( !(readReg(EIR) & EIR_PKTIF) ){ + // The above does not work. See Rev. B4 Silicon Errata point 6. + if(readReg(EPKTCNT) != 0) { + uint16_t readPtr = nextPacketPtr + 6; + // Set the read pointer to the start of the received packet + + writeRegPair(ERDPTL, nextPacketPtr); + + // read the next packet pointer + nextPacketPtr = readOp(ENC28J60_READ_BUF_MEM, 0); + nextPacketPtr |= readOp(ENC28J60_READ_BUF_MEM, 0) << 8; + + // read the packet length (see datasheet page 43) + len = readOp(ENC28J60_READ_BUF_MEM, 0); + len |= readOp(ENC28J60_READ_BUF_MEM, 0) << 8; + len -= 4; //remove the CRC count + + // read the receive status (see datasheet page 43) + rxstat = readOp(ENC28J60_READ_BUF_MEM, 0); + rxstat |= readOp(ENC28J60_READ_BUF_MEM, 0) << 8; + + // decrement the packet counter indicate we are done with this packet + writeOp(ENC28J60_BIT_FIELD_SET, ECON2, ECON2_PKTDEC); + + // 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. + if((rxstat & 0x80) != 0) { + receivePkt.begin = readPtr; + receivePkt.size = len; + return UIP_RECEIVEBUFFERHANDLE; + } + + // Move the RX read pointer to the start of the next received packet + // This frees the memory we just read out + setERXRDPT(); + } + + return(NOBLOCK); +} + +/** + * @brief + * @note + * @param + * @retval + */ +void Enc28J60Network::setERXRDPT(void) { + writeRegPair(ERXRDPTL, nextPacketPtr == RXSTART_INIT ? RXSTOP_INIT : nextPacketPtr - 1); +} + +/** + * @brief + * @note + * @param + * @retval + */ +memaddress Enc28J60Network::blockSize(memhandle handle) { + return handle == UIP_RECEIVEBUFFERHANDLE ? receivePkt.size : blocks[handle].size; +} + +/** + * @brief + * @note + * @param + * @retval + */ +void Enc28J60Network::sendPacket(memhandle handle) { + memblock* packet = &blocks[handle]; + uint16_t start = packet->begin - 1; + uint16_t end = start + packet->size; + + // backup data at control-byte position + uint8_t data = readByte(start); + // write control-byte (if not 0 anyway) + + if(data) + writeByte(start, 0); + +#ifdef ENC28J60DEBUG + pc.print("sendPacket("); + pc.print(handle); + pc.print(") ["); + pc.print(start); + pc.print("-"); + pc.print(end); + pc.println("]:"); + for(uint16_t i = start; i <= end; i++) { + pc.print(readByte(i), HEX); + pc.print(" "); + } + + pc.println(); +#endif + // TX start + + writeRegPair(ETXSTL, start); + + // Set the TXND pointer to correspond to the packet size given + writeRegPair(ETXNDL, end); + + // send the contents of the transmit buffer onto the network + writeOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRTS); + + // Reset the transmit logic problem. See Rev. B4 Silicon Errata point 12. + if((readReg(EIR) & EIR_TXERIF)) { + writeOp(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_TXRTS); + } + + //restore data on control-byte position + if(data) + writeByte(start, data); +} + +/** + * @brief + * @note + * @param + * @retval + */ +uint16_t Enc28J60Network::setReadPtr(memhandle handle, memaddress position, uint16_t len) { + memblock* packet = handle == UIP_RECEIVEBUFFERHANDLE ? &receivePkt : &blocks[handle]; + memaddress start = packet->begin + position; + + writeRegPair(ERDPTL, start); + + if(len > packet->size - position) + len = packet->size - position; + return len; +} + +/** + * @brief + * @note + * @param + * @retval + */ +uint16_t Enc28J60Network::readPacket(memhandle handle, memaddress position, uint8_t* buffer, uint16_t len) { + len = setReadPtr(handle, position, len); + readBuffer(len, buffer); + return len; +} + +/** + * @brief + * @note + * @param + * @retval + */ +uint16_t Enc28J60Network::writePacket(memhandle handle, memaddress position, uint8_t* buffer, uint16_t len) { + memblock* packet = &blocks[handle]; + uint16_t start = packet->begin + position; + + writeRegPair(EWRPTL, start); + + if(len > packet->size - position) + len = packet->size - position; + writeBuffer(len, buffer); + return len; +} + +/** + * @brief + * @note + * @param + * @retval + */ +uint8_t Enc28J60Network::readByte(uint16_t addr) { + uint8_t result; + + writeRegPair(ERDPTL, addr); + + _cs = 0; + + // issue read command + _spi.write(ENC28J60_READ_BUF_MEM); + + // read data + result = _spi.write(0x00); + _cs = 1; + return(result); +} + +/** + * @brief + * @note + * @param + * @retval + */ +void Enc28J60Network::writeByte(uint16_t addr, uint8_t data) { + writeRegPair(EWRPTL, addr); + + _cs = 0; + + // issue write command + _spi.write(ENC28J60_WRITE_BUF_MEM); + + // write data + _spi.write(data); + _cs = 1; +} + +/** + * @brief + * @note + * @param + * @retval + */ +void Enc28J60Network::copyPacket +( + memhandle dest_pkt, + memaddress dest_pos, + memhandle src_pkt, + memaddress src_pos, + uint16_t len +) { + memblock* dest = &blocks[dest_pkt]; + memblock* src = src_pkt == UIP_RECEIVEBUFFERHANDLE ? &receivePkt : &blocks[src_pkt]; + memblock_mv_cb(dest->begin + dest_pos, src->begin + src_pos, len); + + // Move the RX read pointer to the start of the next received packet + // This frees the memory we just read out + setERXRDPT(); +} + +/** + * @brief + * @note + * @param + * @retval + */ +void Enc28J60Network::memblock_mv_cb(uint16_t dest, uint16_t src, uint16_t len) { + + //as ENC28J60 DMA is unable to copy single bytes: + + if(len == 1) { + writeByte(dest, readByte(src)); + } + else { + + // calculate address of last byte + len += src - 1; + + /* 1. Appropriately program the EDMAST, EDMAND + and EDMADST register pairs. The EDMAST + registers should point to the first byte to copy + from, the EDMAND registers should point to the + last byte to copy and the EDMADST registers + should point to the first byte in the destination + range. The destination range will always be + linear, never wrapping at any values except from + 8191 to 0 (the 8-Kbyte memory boundary). + Extreme care should be taken when + programming the start and end pointers to + prevent a never ending DMA operation which + would overwrite the entire 8-Kbyte buffer. + */ + writeRegPair(EDMASTL, src); + writeRegPair(EDMADSTL, dest); + + if((src <= RXSTOP_INIT) && (len > RXSTOP_INIT)) + len -= (RXSTOP_INIT - RXSTART_INIT); + writeRegPair(EDMANDL, len); + + /* + 2. If an interrupt at the end of the copy process is + desired, set EIE.DMAIE and EIE.INTIE and + clear EIR.DMAIF. + + 3. Verify that ECON1.CSUMEN is clear. */ + writeOp(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_CSUMEN); + + /* 4. Start the DMA copy by setting ECON1.DMAST. */ + writeOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_DMAST); + + // wait until runnig DMA is completed + while(readOp(ENC28J60_READ_CTRL_REG, ECON1) & ECON1_DMAST); + } +} + +/** + * @brief + * @note + * @param + * @retval + */ +void Enc28J60Network::freePacket(void) { + setERXRDPT(); +} + +/** + * @brief + * @note + * @param + * @retval + */ +uint8_t Enc28J60Network::readOp(uint8_t op, uint8_t address) { + uint8_t result; + + _cs = 0; + + // issue read command + _spi.write(op | (address & ADDR_MASK)); + + // read data + result = _spi.write(0x00); + + // do dummy read if needed (for mac and mii, see datasheet page 29) + if(address & 0x80) + result = _spi.write(0x00); + + // release CS + _cs = 1; + return(result); +} + +/** + * @brief + * @note + * @param + * @retval + */ +void Enc28J60Network::writeOp(uint8_t op, uint8_t address, uint8_t data) { + _cs = 0; + + // issue write command + _spi.write(op | (address & ADDR_MASK)); + + // write data + _spi.write(data); + _cs = 1; +} + +/** + * @brief + * @note + * @param + * @retval + */ +void Enc28J60Network::readBuffer(uint16_t len, uint8_t* data) { + _cs = 0; + + // issue read command + _spi.write(ENC28J60_READ_BUF_MEM); + while(len) { + len--; + + // read data + *data = _spi.write(0x00); + data++; + } + + *data = '\0'; + _cs = 1; +} + +/** + * @brief + * @note + * @param + * @retval + */ +void Enc28J60Network::writeBuffer(uint16_t len, uint8_t* data) { + _cs = 0; + + // issue write command + _spi.write(ENC28J60_WRITE_BUF_MEM); + while(len) { + len--; + + // write data + _spi.write(*data); + data++; + } + + _cs = 1; +} + +/** + * @brief + * @note + * @param + * @retval + */ +void Enc28J60Network::setBank(uint8_t address) { + + // set the bank (if needed) + + if((address & BANK_MASK) != bank) { + + // set the 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 + */ +uint8_t Enc28J60Network::readReg(uint8_t address) { + + // set the bank + + setBank(address); + + // do the read + return readOp(ENC28J60_READ_CTRL_REG, address); +} + +/** + * @brief + * @note + * @param + * @retval + */ +void Enc28J60Network::writeReg(uint8_t address, uint8_t data) { + + // set the bank + + setBank(address); + + // do the write + writeOp(ENC28J60_WRITE_CTRL_REG, address, data); +} + +/** + * @brief + * @note + * @param + * @retval + */ +void Enc28J60Network::writeRegPair(uint8_t address, uint16_t data) { + + // set the bank + + 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 + */ +void Enc28J60Network::phyWrite(uint8_t address, uint16_t data) { + + // 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(0.000015); + } +} + +/** + * @brief + * @note + * @param + * @retval + */ +void Enc28J60Network::clkout(uint8_t clk) { + + //setup clkout: 2 is 12.5MHz: + + writeReg(ECOCON, clk & 0x7); +} + +// read the revision of the chip: +uint8_t Enc28J60Network::getRev(void) { + return(readReg(EREVID)); +} + +/** + * @brief + * @note + * @param + * @retval + */ +uint16_t Enc28J60Network::chksum(uint16_t sum, memhandle handle, memaddress pos, uint16_t len) { + uint8_t spdr; + uint16_t t; + uint16_t i; + + len = setReadPtr(handle, pos, len) - 1; + _cs = 0; + + // issue read command + spdr = _spi.write(ENC28J60_READ_BUF_MEM); + for(i = 0; i < len; i += 2) { + + // read data + spdr = _spi.write(0x00); + t = spdr << 8; + spdr = _spi.write(0x00); + t += spdr; + sum += t; + if(sum < t) { + sum++; /* carry */ + } + } + + if(i == len) { + spdr = _spi.write(0x00); + t = (spdr << 8) + 0; + sum += t; + if(sum < t) { + sum++; /* carry */ + } + } + + _cs = 1; + + /* Return sum in host byte order. */ + return sum; +} +