├── .gitignore ├── bors.toml ├── ci ├── install.sh └── script.sh ├── .travis.yml ├── src ├── phy.rs ├── bank3.rs ├── traits.rs ├── bank0.rs ├── bank1.rs ├── bank2.rs ├── common.rs ├── macros.rs └── lib.rs ├── Cargo.toml ├── CHANGELOG.md ├── README.md ├── LICENSE-MIT └── LICENSE-APACHE /.gitignore: -------------------------------------------------------------------------------- 1 | **/*.rs.bk 2 | .#* 3 | /target/ 4 | Cargo.lock 5 | -------------------------------------------------------------------------------- /bors.toml: -------------------------------------------------------------------------------- 1 | status = [ 2 | "continuous-integration/travis-ci/push", 3 | ] -------------------------------------------------------------------------------- /ci/install.sh: -------------------------------------------------------------------------------- 1 | set -euxo pipefail 2 | 3 | main() { 4 | if [ $TARGET != rustfmt ]; then 5 | rustup target add $TARGET 6 | else 7 | rustup component add rustfmt 8 | fi 9 | } 10 | 11 | main 12 | -------------------------------------------------------------------------------- /ci/script.sh: -------------------------------------------------------------------------------- 1 | set -euxo pipefail 2 | 3 | main() { 4 | if [ $TARGET = rustfmt ]; then 5 | cargo fmt -- --check 6 | return 7 | fi 8 | 9 | cargo check --target $TARGET 10 | } 11 | 12 | main 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | 3 | matrix: 4 | include: 5 | - env: TARGET=x86_64-unknown-linux-gnu 6 | 7 | - env: TARGET=thumbv7m-none-eabi 8 | 9 | - env: TARGET=rustfmt 10 | 11 | before_install: set -e 12 | 13 | install: 14 | - bash ci/install.sh 15 | 16 | script: 17 | - bash ci/script.sh 18 | 19 | after_script: set +e 20 | 21 | cache: cargo 22 | 23 | before_cache: 24 | # Travis can't cache files that are not readable by "others" 25 | - chmod -R a+r $HOME/.cargo 26 | 27 | branches: 28 | only: 29 | - staging 30 | - trying 31 | 32 | notifications: 33 | email: 34 | on_success: never 35 | -------------------------------------------------------------------------------- /src/phy.rs: -------------------------------------------------------------------------------- 1 | #[allow(dead_code)] 2 | #[derive(Clone, Copy)] 3 | pub enum Register { 4 | PHCON1 = 0x00, 5 | PHSTAT1 = 0x01, 6 | PHID1 = 0x02, 7 | PHID2 = 0x03, 8 | PHCON2 = 0x10, 9 | PHSTAT2 = 0x11, 10 | PHIE = 0x12, 11 | PHIR = 0x13, 12 | PHLCON = 0x14, 13 | } 14 | 15 | impl Register { 16 | pub(crate) fn addr(&self) -> u8 { 17 | *self as u8 18 | } 19 | } 20 | 21 | register!(PHCON2, 0, u16, { 22 | #[doc = "PHY Half-Duplex Loopback Disable bit"] 23 | hdldis @ 8, 24 | #[doc = "Jabber Correction Disable bit"] 25 | jabber @ 10, 26 | #[doc = "Twisted-Pair Transmitter Disable bit"] 27 | txdis @ 13, 28 | #[doc = "PHY Force Linkup bit"] 29 | frclnk @ 14, 30 | }); 31 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | categories = ["embedded", "hardware-support", "no-std"] 4 | description = "A platform agnostic driver to interface the ENC28J60 (Ethernet controller)" 5 | documentation = "https://docs.rs/enc28j60" 6 | keywords = ["embedded-hal-driver", "Ethernet", "SPI"] 7 | license = "MIT OR Apache-2.0" 8 | name = "enc28j60" 9 | readme = "README.md" 10 | repository = "https://github.com/japaric/enc28j60" 11 | version = "0.2.1" 12 | 13 | [dependencies.cast] 14 | default-features = false 15 | version = "0.2.2" 16 | 17 | [dependencies.embedded-hal] 18 | version = "0.2.0" 19 | features = ["unproven"] 20 | 21 | [dependencies.byteorder] 22 | default-features = false 23 | version = "1.2.1" 24 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/). 7 | 8 | ## [v0.2.1] - 2019-01-30 9 | 10 | ### Fixed 11 | 12 | - Properly work around silicon Erratum #1. Some devices would get stuck while 13 | executing `Enc28j60::new` due to a silicon bug because the documented 14 | workaround was not properly applied; this has been fixed. 15 | 16 | ## [v0.2.0] - 2018-05-16 17 | 18 | ### Changed 19 | 20 | - [breaking-change] moved the `embedded-hal` v0.2.x 21 | 22 | ## v0.1.0 - 2018-03-13 23 | 24 | Initial release 25 | 26 | [Unreleased]: https://github.com/japaric/enc28j60/compare/v0.2.0...HEAD 27 | [v0.2.0]: https://github.com/japaric/enc28j60/compare/v0.1.0...v0.2.0 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `enc28j60` 2 | 3 | > A platform agnostic driver to interface with the [ENC28J60][] (Ethernet controller) 4 | 5 | [ENC28J60]: http://www.microchip.com/wwwproducts/en/en022889 6 | 7 | ## [API documentation](https://docs.rs/enc28j60) 8 | 9 | ## Examples 10 | 11 | You should find some examples in the [`stm32f103xx-hal`] crate. 12 | 13 | [`stm32f103xx-hal`]: https://github.com/japaric/stm32f103xx-hal/tree/master/examples 14 | 15 | ## License 16 | 17 | Licensed under either of 18 | 19 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or 20 | http://www.apache.org/licenses/LICENSE-2.0) 21 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 22 | 23 | at your option. 24 | 25 | ### Contribution 26 | 27 | Unless you explicitly state otherwise, any contribution intentionally submitted 28 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 29 | dual licensed as above, without any additional terms or conditions. 30 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Jorge Aparicio 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /src/bank3.rs: -------------------------------------------------------------------------------- 1 | #[allow(dead_code)] 2 | #[derive(Clone, Copy)] 3 | pub enum Register { 4 | MAADR5 = 0x00, 5 | MAADR6 = 0x01, 6 | MAADR3 = 0x02, 7 | MAADR4 = 0x03, 8 | MAADR1 = 0x04, 9 | MAADR2 = 0x05, 10 | EBSTSD = 0x06, 11 | EBSTCON = 0x07, 12 | EBSTCSL = 0x08, 13 | EBSTCSH = 0x09, 14 | MISTAT = 0x0a, 15 | EREVID = 0x12, 16 | ECOCON = 0x15, 17 | EFLOCON = 0x17, 18 | EPAUSL = 0x18, 19 | EPAUSH = 0x19, 20 | } 21 | 22 | impl Register { 23 | pub(crate) fn addr(&self) -> u8 { 24 | *self as u8 25 | } 26 | 27 | pub(crate) fn is_eth_register(&self) -> bool { 28 | match *self { 29 | Register::MAADR5 => false, 30 | Register::MAADR6 => false, 31 | Register::MAADR3 => false, 32 | Register::MAADR4 => false, 33 | Register::MAADR1 => false, 34 | Register::MAADR2 => false, 35 | Register::EBSTSD => true, 36 | Register::EBSTCON => true, 37 | Register::EBSTCSL => true, 38 | Register::EBSTCSH => true, 39 | Register::MISTAT => false, 40 | Register::EREVID => true, 41 | Register::ECOCON => true, 42 | Register::EFLOCON => true, 43 | Register::EPAUSL => true, 44 | Register::EPAUSH => true, 45 | } 46 | } 47 | } 48 | 49 | impl Into<::Register> for Register { 50 | fn into(self) -> ::Register { 51 | ::Register::Bank3(self) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/traits.rs: -------------------------------------------------------------------------------- 1 | use core::mem; 2 | use core::ops::Range; 3 | 4 | use byteorder::{ByteOrder, BE, LE}; 5 | 6 | pub(crate) trait OffsetSize { 7 | fn offset(self) -> u8; 8 | fn size(self) -> u8; 9 | } 10 | 11 | impl OffsetSize for u8 { 12 | fn offset(self) -> u8 { 13 | self 14 | } 15 | 16 | fn size(self) -> u8 { 17 | 1 18 | } 19 | } 20 | 21 | impl OffsetSize for Range { 22 | fn offset(self) -> u8 { 23 | self.start 24 | } 25 | 26 | fn size(self) -> u8 { 27 | self.end - self.start 28 | } 29 | } 30 | 31 | pub(crate) trait U16Ext { 32 | fn from_parts(low: u8, high: u8) -> Self; 33 | 34 | fn low(self) -> u8; 35 | 36 | fn high(self) -> u8; 37 | 38 | // Big Endian byte representation 39 | fn be_repr(self) -> [u8; 2]; 40 | 41 | // Little Endian byte representation 42 | fn le_repr(self) -> [u8; 2]; 43 | } 44 | 45 | impl U16Ext for u16 { 46 | fn from_parts(low: u8, high: u8) -> u16 { 47 | ((high as u16) << 8) + low as u16 48 | } 49 | 50 | fn low(self) -> u8 { 51 | (self & 0xff) as u8 52 | } 53 | 54 | fn high(self) -> u8 { 55 | (self >> 8) as u8 56 | } 57 | 58 | fn be_repr(self) -> [u8; 2] { 59 | let mut bytes: [u8; 2] = unsafe { mem::uninitialized() }; 60 | BE::write_u16(&mut bytes, self); 61 | bytes 62 | } 63 | 64 | fn le_repr(self) -> [u8; 2] { 65 | let mut bytes: [u8; 2] = unsafe { mem::uninitialized() }; 66 | LE::write_u16(&mut bytes, self); 67 | bytes 68 | } 69 | } 70 | 71 | #[derive(Clone, Copy)] 72 | pub struct Mask; 73 | 74 | #[derive(Clone, Copy)] 75 | pub struct R; 76 | 77 | #[derive(Clone, Copy)] 78 | pub struct W; 79 | -------------------------------------------------------------------------------- /src/bank0.rs: -------------------------------------------------------------------------------- 1 | #[allow(dead_code)] 2 | #[derive(Clone, Copy)] 3 | pub enum Register { 4 | ERDPTL = 0x00, 5 | ERDPTH = 0x01, 6 | EWRPTL = 0x02, 7 | EWRPTH = 0x03, 8 | ETXSTL = 0x04, 9 | ETXSTH = 0x05, 10 | ETXNDL = 0x06, 11 | ETXNDH = 0x07, 12 | ERXSTL = 0x08, 13 | ERXSTH = 0x09, 14 | ERXNDL = 0x0a, 15 | ERXNDH = 0x0b, 16 | ERXRDPTL = 0x0c, 17 | ERXRDPTH = 0x0d, 18 | ERXWRPTL = 0x0e, 19 | ERXWRPTH = 0x0f, 20 | EDMASTL = 0x10, 21 | EDMASTH = 0x11, 22 | EDMANDL = 0x12, 23 | EDMANDH = 0x13, 24 | EDMADSTL = 0x14, 25 | EDMADSTH = 0x15, 26 | EDMACSL = 0x16, 27 | EDMACSH = 0x17, 28 | } 29 | 30 | impl Register { 31 | pub(crate) fn addr(&self) -> u8 { 32 | *self as u8 33 | } 34 | 35 | pub(crate) fn is_eth_register(&self) -> bool { 36 | match *self { 37 | Register::ERDPTL => true, 38 | Register::ERDPTH => true, 39 | Register::EWRPTL => true, 40 | Register::EWRPTH => true, 41 | Register::ETXSTL => true, 42 | Register::ETXSTH => true, 43 | Register::ETXNDL => true, 44 | Register::ETXNDH => true, 45 | Register::ERXSTL => true, 46 | Register::ERXSTH => true, 47 | Register::ERXNDL => true, 48 | Register::ERXNDH => true, 49 | Register::ERXRDPTL => true, 50 | Register::ERXRDPTH => true, 51 | Register::ERXWRPTL => true, 52 | Register::ERXWRPTH => true, 53 | Register::EDMASTL => true, 54 | Register::EDMASTH => true, 55 | Register::EDMANDL => true, 56 | Register::EDMANDH => true, 57 | Register::EDMADSTL => true, 58 | Register::EDMADSTH => true, 59 | Register::EDMACSL => true, 60 | Register::EDMACSH => true, 61 | } 62 | } 63 | } 64 | 65 | impl Into<::Register> for Register { 66 | fn into(self) -> ::Register { 67 | ::Register::Bank0(self) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/bank1.rs: -------------------------------------------------------------------------------- 1 | #[allow(dead_code)] 2 | #[derive(Clone, Copy)] 3 | pub enum Register { 4 | EHT0 = 0x00, 5 | EHT1 = 0x01, 6 | EHT2 = 0x02, 7 | EHT3 = 0x03, 8 | EHT4 = 0x04, 9 | EHT5 = 0x05, 10 | EHT6 = 0x06, 11 | EHT7 = 0x07, 12 | EPMM0 = 0x08, 13 | EPMM1 = 0x09, 14 | EPMM2 = 0x0a, 15 | EPMM3 = 0x0b, 16 | EPMM4 = 0x0c, 17 | EPMM5 = 0x0d, 18 | EPMM6 = 0x0e, 19 | EPMM7 = 0x0f, 20 | EPMCSL = 0x10, 21 | EPMCSH = 0x11, 22 | EPMOL = 0x14, 23 | EPMOH = 0x15, 24 | ERXFCON = 0x18, 25 | EPKTCNT = 0x19, 26 | } 27 | 28 | impl Register { 29 | pub(crate) fn addr(&self) -> u8 { 30 | *self as u8 31 | } 32 | 33 | pub(crate) fn is_eth_register(&self) -> bool { 34 | match *self { 35 | Register::EHT0 => true, 36 | Register::EHT1 => true, 37 | Register::EHT2 => true, 38 | Register::EHT3 => true, 39 | Register::EHT4 => true, 40 | Register::EHT5 => true, 41 | Register::EHT6 => true, 42 | Register::EHT7 => true, 43 | Register::EPMM0 => true, 44 | Register::EPMM1 => true, 45 | Register::EPMM2 => true, 46 | Register::EPMM3 => true, 47 | Register::EPMM4 => true, 48 | Register::EPMM5 => true, 49 | Register::EPMM6 => true, 50 | Register::EPMM7 => true, 51 | Register::EPMCSL => true, 52 | Register::EPMCSH => true, 53 | Register::EPMOL => true, 54 | Register::EPMOH => true, 55 | Register::ERXFCON => true, 56 | Register::EPKTCNT => true, 57 | } 58 | } 59 | } 60 | 61 | impl Into<::Register> for Register { 62 | fn into(self) -> ::Register { 63 | ::Register::Bank1(self) 64 | } 65 | } 66 | 67 | register!(ERXFCON, 0b1010_0001, u8, { 68 | #[doc = "Broadcast Filter Enable bit"] 69 | bcen @ 0, 70 | #[doc = "Multicast Filter Enable bit"] 71 | mcen @ 1, 72 | #[doc = "Hash Table Filter Enable bit"] 73 | hten @ 2, 74 | #[doc = "Magic Packet™ Filter Enable bit"] 75 | mpen @ 3, 76 | #[doc = "Pattern Match Filter Enable bit"] 77 | pmen @ 4, 78 | #[doc = "Post-Filter CRC Check Enable bit"] 79 | crcen @ 5, 80 | #[doc = "AND/OR Filter Select bit"] 81 | andor @ 6, 82 | #[doc = "Unicast Filter Enable bit"] 83 | ucen @ 7, 84 | }); 85 | -------------------------------------------------------------------------------- /src/bank2.rs: -------------------------------------------------------------------------------- 1 | #[allow(dead_code)] 2 | #[derive(Clone, Copy)] 3 | pub enum Register { 4 | MACON1 = 0x00, 5 | MACON3 = 0x02, 6 | MACON4 = 0x03, 7 | MABBIPG = 0x04, 8 | MAIPGL = 0x06, 9 | MAIPGH = 0x07, 10 | MACLCON1 = 0x08, 11 | MACLCON2 = 0x09, 12 | MAMXFLL = 0x0a, 13 | MAMXFLH = 0x0b, 14 | MICMD = 0x12, 15 | MIREGADR = 0x14, 16 | MIWRL = 0x16, 17 | MIWRH = 0x17, 18 | MIRDL = 0x18, 19 | MIRDH = 0x19, 20 | } 21 | 22 | impl Register { 23 | pub(crate) fn addr(&self) -> u8 { 24 | *self as u8 25 | } 26 | 27 | pub(crate) fn is_eth_register(&self) -> bool { 28 | match *self { 29 | Register::MACON1 => false, 30 | Register::MACON3 => false, 31 | Register::MACON4 => false, 32 | Register::MABBIPG => false, 33 | Register::MAIPGL => false, 34 | Register::MAIPGH => false, 35 | Register::MACLCON1 => false, 36 | Register::MACLCON2 => false, 37 | Register::MAMXFLL => false, 38 | Register::MAMXFLH => false, 39 | Register::MICMD => false, 40 | Register::MIREGADR => false, 41 | Register::MIWRL => false, 42 | Register::MIWRH => false, 43 | Register::MIRDL => false, 44 | Register::MIRDH => false, 45 | } 46 | } 47 | } 48 | 49 | impl Into<::Register> for Register { 50 | fn into(self) -> ::Register { 51 | ::Register::Bank2(self) 52 | } 53 | } 54 | 55 | register!(MACON1, 0, u8, { 56 | #[doc = "Enable packets to be received by the MAC"] 57 | marxen @ 0, 58 | #[doc = "Control frames will be discarded after being processed by the MAC"] 59 | passall @ 1, 60 | #[doc = "Inhibit transmissions when pause control frames are received"] 61 | rxpaus @ 2, 62 | #[doc = "Allow the MAC to transmit pause control frames"] 63 | txpaus @ 3, 64 | }); 65 | 66 | register!(MACON3, 0, u8, { 67 | #[doc = "MAC will operate in Full-Duplex mode"] 68 | fuldpx @ 0, 69 | #[doc = "The type/length field of transmitted and received frames will be checked"] 70 | frmlnen @ 1, 71 | #[doc = "Frames bigger than MAMXFL will be aborted when transmitted or received"] 72 | hfrmen @ 2, 73 | #[doc = "No proprietary header is present"] 74 | phdren @ 3, 75 | #[doc = "MAC will append a valid CRC to all frames transmitted regardless of PADCFG bit"] 76 | txcrcen @ 4, 77 | #[doc = "All short frames will be zero-padded to 64 bytes and a valid CRC will then be appended"] 78 | padcfg @ 5..7, 79 | }); 80 | 81 | register!(MICMD, 0, u8, { 82 | #[doc = "MII Read Enable bit"] 83 | miird @ 0, 84 | #[doc = "MII Scan Enable bit"] 85 | miiscan @ 1, 86 | }); 87 | -------------------------------------------------------------------------------- /src/common.rs: -------------------------------------------------------------------------------- 1 | #[allow(dead_code)] 2 | #[derive(Clone, Copy)] 3 | pub enum Register { 4 | ECON1 = 0x1f, 5 | ECON2 = 0x1e, 6 | EIE = 0x1b, 7 | EIR = 0x1c, 8 | ESTAT = 0x1d, 9 | } 10 | 11 | impl Register { 12 | pub(crate) fn addr(&self) -> u8 { 13 | *self as u8 14 | } 15 | 16 | pub(crate) fn is_eth_register(&self) -> bool { 17 | match *self { 18 | Register::ECON1 => true, 19 | Register::ECON2 => true, 20 | Register::EIE => true, 21 | Register::EIR => true, 22 | Register::ESTAT => true, 23 | } 24 | } 25 | } 26 | 27 | impl Into<::Register> for Register { 28 | fn into(self) -> ::Register { 29 | ::Register::Common(self) 30 | } 31 | } 32 | 33 | register!(EIE, 0, u8, { 34 | #[doc = "Receive Error Interrupt Enable bit"] 35 | rxerie @ 0, 36 | #[doc = "Transmit Error Interrupt Enable bit"] 37 | txerie @ 1, 38 | #[doc = "Transmit Enable bit"] 39 | txie @ 3, 40 | #[doc = "Link Status Change Interrupt Enable bit"] 41 | linkie @ 4, 42 | #[doc = "DMA Interrupt Enable bit"] 43 | dmaie @ 5, 44 | #[doc = "Receive Packet Pending Interrupt Enable bit"] 45 | pktie @ 6, 46 | #[doc = "Global INT Interrupt Enable bit"] 47 | intie @ 7, 48 | }); 49 | 50 | register!(EIR, 0, u8, { 51 | #[doc = "Receive Error Interrupt Flag bit"] 52 | rxerif @ 0, 53 | #[doc = "Transmit Error Interrupt Flag bit"] 54 | txerif @ 1, 55 | #[doc = "Transmit Interrupt Flag bit"] 56 | txif @ 3, 57 | #[doc = "Link Change Interrupt Flag bit"] 58 | linkif @ 4, 59 | #[doc = "DMA Interrupt Flag bit"] 60 | dmaif @ 5, 61 | #[doc = "Receive Packet Pending Interrupt Flag bit"] 62 | pktif @ 6, 63 | }); 64 | 65 | register!(ESTAT, 0, u8, { 66 | #[doc = "Clock Ready bit"] 67 | clkrdy @ 0, 68 | #[doc = "Transmit Abort Error bit"] 69 | txabrt @ 1, 70 | #[doc = "Receive Busy bit"] 71 | rxbusy @ 2, 72 | #[doc = "Late Collision Error bit"] 73 | latecol @ 4, 74 | #[doc = "Ethernet Buffer Error Status bit"] 75 | bufer @ 6, 76 | #[doc = "INT Interrupt Flag bit"] 77 | int @ 7, 78 | }); 79 | 80 | register!(ECON2, 0b1000_0000, u8, { 81 | #[doc = "Voltage Regulator Power Save Enable bit"] 82 | vrps @ 3, 83 | #[doc = "Power Save Enable bit"] 84 | pwrsv @ 5, 85 | #[doc = "Packet Decrement bit"] 86 | pktdec @ 6, 87 | #[doc = "Automatic Buffer Pointer Increment Enable bit"] 88 | autoinc @ 7, 89 | }); 90 | 91 | register!(ECON1, 0, u8, { 92 | #[doc = "Bank Select bits"] 93 | bsel @ 0..1, 94 | #[doc = "Receive Enable bi"] 95 | rxen @ 2, 96 | #[doc = "Transmit Request to Send bit"] 97 | txrts @ 3, 98 | #[doc = "DMA Checksum Enable bit"] 99 | csumen @ 4, 100 | #[doc = "DMA Start and Busy Status bit"] 101 | dmast @ 5, 102 | #[doc = "Receive Logic Reset bit"] 103 | rxrst @ 6, 104 | #[doc = "Transmit Logic Reset bit"] 105 | txrst @ 7, 106 | }); 107 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | macro_rules! register { 2 | ($REGISTER:ident, $reset_value:expr, $uxx:ty, { 3 | $(#[$($attr:tt)*] $bitfield:ident @ $range:expr,)+ 4 | }) => { 5 | #[derive(Clone, Copy)] 6 | pub(crate) struct $REGISTER { 7 | bits: $uxx, 8 | _mode: ::core::marker::PhantomData, 9 | } 10 | 11 | impl $REGISTER<::traits::Mask> { 12 | #[allow(dead_code)] 13 | pub(crate) fn mask() -> $REGISTER<::traits::Mask> { 14 | $REGISTER { bits: 0, _mode: ::core::marker::PhantomData } 15 | } 16 | 17 | $( 18 | #[allow(dead_code)] 19 | pub(crate) fn $bitfield(&self) -> $uxx { 20 | use ::traits::OffsetSize; 21 | 22 | let size = $range.size(); 23 | let offset = $range.offset(); 24 | ((1 << size) - 1) << offset 25 | } 26 | )+ 27 | } 28 | 29 | impl ::core::default::Default for $REGISTER<::traits::W> { 30 | fn default() -> Self { 31 | $REGISTER { bits: $reset_value, _mode: ::core::marker::PhantomData } 32 | } 33 | } 34 | 35 | #[allow(non_snake_case)] 36 | #[allow(dead_code)] 37 | pub(crate) fn $REGISTER(bits: $uxx) -> $REGISTER<::traits::R> { 38 | $REGISTER { bits, _mode: ::core::marker::PhantomData } 39 | } 40 | 41 | impl $REGISTER<::traits::R> { 42 | #[allow(dead_code)] 43 | pub(crate) fn modify(self) -> $REGISTER<::traits::W> { 44 | $REGISTER { bits: self.bits, _mode: ::core::marker::PhantomData } 45 | } 46 | 47 | $( 48 | #[$($attr)*] 49 | #[allow(dead_code)] 50 | pub(crate) fn $bitfield(&self) -> $uxx { 51 | use ::traits::OffsetSize; 52 | 53 | let offset = $range.offset(); 54 | let size = $range.size(); 55 | let mask = (1 << size) - 1; 56 | 57 | (self.bits >> offset) & mask 58 | } 59 | )+ 60 | } 61 | 62 | impl $REGISTER<::traits::W> { 63 | #[allow(dead_code)] 64 | pub(crate) fn bits(self) -> $uxx { 65 | self.bits 66 | } 67 | 68 | $( 69 | #[$($attr)*] 70 | #[allow(dead_code)] 71 | pub(crate) fn $bitfield(&mut self, mut bits: $uxx) -> &mut Self { 72 | use ::traits::OffsetSize; 73 | 74 | let offset = $range.offset(); 75 | let size = $range.size(); 76 | let mask = (1 << size) - 1; 77 | 78 | debug_assert!(bits <= mask); 79 | bits &= mask; 80 | 81 | self.bits &= !(mask << offset); 82 | self.bits |= bits << offset; 83 | 84 | self 85 | } 86 | )+ 87 | } 88 | } 89 | } 90 | 91 | /// Poor man's specialization 92 | macro_rules! typeid { 93 | ($type_parameter:ident == $concrete_type:ident) => { 94 | ::core::any::TypeId::of::<$type_parameter>() == ::core::any::TypeId::of::<$concrete_type>() 95 | }; 96 | ($type_parameter:ident != $concrete_type:ident) => { 97 | !typeid!($type_parameter == $concrete_type) 98 | }; 99 | } 100 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A platform agnostic driver to interface with the ENC28J60 (Ethernet controller) 2 | //! 3 | //! This driver is built on top of the [`embedded-hal`] traits. 4 | //! 5 | //! [`embedded-hal`]: https://docs.rs/embedded-hal/~0.2 6 | //! 7 | //! # Examples 8 | //! 9 | //! You should find some examples in the [`stm32f103xx-hal`] crate. 10 | //! 11 | //! [`stm32f103xx-hal`]: https://github.com/japaric/stm32f103xx-hal/tree/master/examples 12 | //! 13 | //! # References 14 | //! 15 | //! - [ENC28J60 Data Sheet](http://ww1.microchip.com/downloads/en/DeviceDoc/39662e.pdf) 16 | //! - [ENC28J60 Rev. B7 Silicon Errata](http://ww1.microchip.com/downloads/en/DeviceDoc/80349b.pdf) 17 | 18 | #![deny(missing_docs)] 19 | #![deny(warnings)] 20 | #![no_std] 21 | 22 | extern crate byteorder; 23 | extern crate cast; 24 | extern crate embedded_hal as hal; 25 | 26 | use core::mem; 27 | use core::ptr; 28 | use core::u16; 29 | 30 | use byteorder::{ByteOrder, LE}; 31 | use cast::{u16, usize}; 32 | use hal::blocking; 33 | use hal::blocking::delay::DelayMs; 34 | use hal::digital::{InputPin, OutputPin}; 35 | use hal::spi::{Mode, Phase, Polarity}; 36 | 37 | use traits::U16Ext; 38 | 39 | #[macro_use] 40 | mod macros; 41 | mod bank0; 42 | mod bank1; 43 | mod bank2; 44 | mod bank3; 45 | mod common; 46 | mod phy; 47 | mod traits; 48 | 49 | /// SPI mode 50 | pub const MODE: Mode = Mode { 51 | phase: Phase::CaptureOnFirstTransition, 52 | polarity: Polarity::IdleLow, 53 | }; 54 | 55 | /// Error 56 | #[derive(Debug)] 57 | pub enum Error { 58 | /// Late collision 59 | LateCollision, 60 | /// SPI error 61 | Spi(E), 62 | } 63 | 64 | /// Events that the ENC28J60 can notify about via the INT pin 65 | pub enum Event { 66 | /// There are packets pending to be processed in the RX buffer 67 | Pkt, 68 | } 69 | 70 | impl From for Error { 71 | fn from(e: E) -> Self { 72 | Error::Spi(e) 73 | } 74 | } 75 | 76 | /// ENC28J60 driver 77 | pub struct Enc28j60 { 78 | int: INT, 79 | ncs: NCS, 80 | reset: RESET, 81 | spi: SPI, 82 | 83 | bank: Bank, 84 | /// address of the next packet in buffer memory 85 | // NOTE this should be an `Option` but we know this is a 13-bit address so we'll use `u16::MAX` 86 | // as the `None` variant, to avoid an extra byte (enum tag) 87 | next_packet: u16, 88 | /// End of the RX buffer / Start of the TX buffer 89 | rxnd: u16, 90 | /// End address of the *previous* transmission 91 | // NOTE as above this should be an option but we are going to use `u16::MAX` for the `None` 92 | // variant 93 | txnd: u16, 94 | } 95 | 96 | const NONE: u16 = u16::MAX; 97 | const RXST: u16 = 0; 98 | 99 | impl Enc28j60 100 | where 101 | SPI: blocking::spi::Transfer + blocking::spi::Write, 102 | NCS: OutputPin, 103 | INT: IntPin, 104 | RESET: ResetPin, 105 | { 106 | // Maximum frame length 107 | const MAX_FRAME_LENGTH: u16 = 1518; // value recommended in the data sheet 108 | // Size of the Frame check sequence (32-bit CRC) 109 | const CRC_SZ: u16 = 4; // 110 | 111 | /* Constructors */ 112 | /// Creates a new driver from a SPI peripheral, a NCS pin, a RESET pin and an INT 113 | /// (interrupt) pin 114 | /// 115 | /// If you haven't physically connected the RESET and / or the INT pin(s) pass the `Unconnected` 116 | /// value as the `reset` and / or `int` argument(s), respectively. 117 | /// 118 | /// `rx_buf_sz` is the size of the ENC28J60 RX buffer in bytes. Note that if `rx_buf_sz` is odd 119 | /// it will be rounded to an even number. 120 | /// 121 | /// `src` is the MAC address to associate to this interface. Note that this MAC address is 122 | /// only used to decide which frames will be ignored; frames that don't have their destination 123 | /// address set to broadcast (`ff:ff:ff:ff:ff:ff`) or to `src` will be ignored by the 124 | /// interface. 125 | /// 126 | /// # Panics 127 | /// 128 | /// If `rx_buf_sz` is greater than `8192` (8 Kibibytes); that's the size of the ENC28J60 129 | /// internal memory. 130 | pub fn new( 131 | spi: SPI, 132 | ncs: NCS, 133 | int: INT, 134 | reset: RESET, 135 | delay: &mut D, 136 | mut rx_buf_sz: u16, 137 | src: [u8; 6], 138 | ) -> Result 139 | where 140 | D: DelayMs, 141 | RESET: ResetPin, 142 | INT: IntPin, 143 | { 144 | // Total buffer size (cf. section 3.2) 145 | const BUF_SZ: u16 = 8 * 1024; 146 | 147 | // round up `rx_buf_sz` to an even number 148 | if rx_buf_sz % 2 == 1 { 149 | rx_buf_sz += 1; 150 | } 151 | 152 | assert!(rx_buf_sz <= BUF_SZ); 153 | 154 | let mut enc28j60 = Enc28j60 { 155 | bank: Bank::Bank0, 156 | int, 157 | ncs, 158 | next_packet: NONE, 159 | reset, 160 | rxnd: NONE, 161 | spi, 162 | txnd: NONE, 163 | }; 164 | 165 | // (software) reset to return to a clean slate state 166 | if typeid!(RESET == Unconnected) { 167 | enc28j60.soft_reset()?; 168 | } else { 169 | enc28j60.reset.reset(); 170 | } 171 | 172 | // This doesn't work because of a silicon bug; see workaround below 173 | // wait for the clock to settle 174 | // while common::ESTAT(enc28j60.read_control_register(common::Register::ESTAT)?).clkrdy() == 0 175 | // { 176 | // } 177 | 178 | // Workaround Errata issue 1 179 | delay.delay_ms(1); 180 | 181 | // disable CLKOUT output 182 | enc28j60.write_control_register(bank3::Register::ECOCON, 0)?; 183 | 184 | // define the boundaries of the TX and RX buffers (cf. section 6.1) 185 | // to workaround Errata issue 3 we do the opposite of what section 6.1 of the data sheet 186 | // says: we place the RX buffer at address 0 and the TX buffer after it 187 | let rxnd = rx_buf_sz - 1; 188 | enc28j60.rxnd = rxnd; 189 | 190 | // RX start 191 | // "It is recommended that the ERXST Pointer be programmed with an even address" 192 | enc28j60.write_control_register(bank0::Register::ERXSTL, RXST.low())?; 193 | enc28j60.write_control_register(bank0::Register::ERXSTH, RXST.high())?; 194 | 195 | // RX read pointer 196 | // NOTE Errata issue 11 so we are using an *odd* address here instead of ERXST 197 | enc28j60.write_control_register(bank0::Register::ERXRDPTL, rxnd.low())?; 198 | enc28j60.write_control_register(bank0::Register::ERXRDPTH, rxnd.high())?; 199 | 200 | // RX end 201 | enc28j60.write_control_register(bank0::Register::ERXNDL, rxnd.low())?; 202 | enc28j60.write_control_register(bank0::Register::ERXNDH, rxnd.high())?; 203 | 204 | // TX start 205 | // "It is recommended that an even address be used for ETXST" 206 | let txst = enc28j60.txst(); 207 | debug_assert_eq!(txst % 2, 0); 208 | enc28j60.write_control_register(bank0::Register::ETXSTL, txst.low())?; 209 | enc28j60.write_control_register(bank0::Register::ETXSTH, txst.high())?; 210 | 211 | // TX end is set in `transmit` 212 | 213 | // MAC initialization (see section 6.5) 214 | // 1. Set the MARXEN bit in MACON1 to enable the MAC to receive frames. 215 | enc28j60.write_control_register( 216 | bank2::Register::MACON1, 217 | bank2::MACON1::default() 218 | .marxen(1) 219 | .passall(0) 220 | .rxpaus(1) 221 | .txpaus(1) 222 | .bits(), 223 | )?; 224 | 225 | // 2. Configure the PADCFG, TXCRCEN and FULDPX bits of MACON3. 226 | enc28j60.write_control_register( 227 | bank2::Register::MACON3, 228 | bank2::MACON3::default() 229 | .frmlnen(1) 230 | .txcrcen(1) 231 | .padcfg(0b001) 232 | .bits(), 233 | )?; 234 | 235 | // 4. Program the MAMXFL registers with the maximum frame length to be permitted to be 236 | // received or transmitted 237 | enc28j60.write_control_register(bank2::Register::MAMXFLL, Self::MAX_FRAME_LENGTH.low())?; 238 | enc28j60.write_control_register(bank2::Register::MAMXFLH, Self::MAX_FRAME_LENGTH.high())?; 239 | 240 | // 5. Configure the Back-to-Back Inter-Packet Gap register, MABBIPG. 241 | // Use recommended value of 0x12 242 | enc28j60.write_control_register(bank2::Register::MABBIPG, 0x12)?; 243 | 244 | // 6. Configure the Non-Back-to-Back Inter-Packet Gap register low byte, MAIPGL. 245 | // Use recommended value of 0x12 246 | enc28j60.write_control_register(bank2::Register::MAIPGL, 0x12)?; 247 | 248 | // 9. Program the local MAC address into the MAADR1:MAADR6 registers 249 | enc28j60.write_control_register(bank3::Register::MAADR1, src[0])?; 250 | enc28j60.write_control_register(bank3::Register::MAADR2, src[1])?; 251 | enc28j60.write_control_register(bank3::Register::MAADR3, src[2])?; 252 | enc28j60.write_control_register(bank3::Register::MAADR4, src[3])?; 253 | enc28j60.write_control_register(bank3::Register::MAADR5, src[4])?; 254 | enc28j60.write_control_register(bank3::Register::MAADR6, src[5])?; 255 | 256 | // Set the PHCON2.HDLDIS bit to prevent automatic loopback of the data which is transmitted 257 | enc28j60.write_phy_register( 258 | phy::Register::PHCON2, 259 | phy::PHCON2::default().hdldis(1).bits(), 260 | )?; 261 | 262 | // Globally enable interrupts 263 | if typeid!(INT != Unconnected) { 264 | enc28j60.bit_field_set(common::Register::EIE, common::EIE::mask().intie())?; 265 | } 266 | 267 | // Set the per packet control byte; we'll always use the value 0 268 | enc28j60.write_buffer_memory(Some(txst), &[0])?; 269 | 270 | // Enable reception 271 | enc28j60.bit_field_set(common::Register::ECON1, common::ECON1::mask().rxen())?; 272 | 273 | Ok(enc28j60) 274 | } 275 | 276 | /* I/O */ 277 | /// Flushes the transmit buffer, ensuring all pending transmissions have completed 278 | pub fn flush(&mut self) -> Result<(), Error> { 279 | if self.txnd != NONE { 280 | // Wait until transmission finishes 281 | while common::ECON1(self.read_control_register(common::Register::ECON1)?).txrts() == 1 { 282 | } 283 | 284 | // NOTE(volatile) to avoid this value being set *before* the transmission is over 285 | let txnd = self.txnd; 286 | unsafe { ptr::write_volatile(&mut self.txnd, NONE) } 287 | 288 | // read the transmit status vector 289 | let mut tx_stat = [0; 7]; 290 | self.read_buffer_memory(Some(txnd + 1), &mut tx_stat)?; 291 | 292 | let stat = common::ESTAT(self.read_control_register(common::Register::ESTAT)?); 293 | 294 | if stat.txabrt() == 1 { 295 | // work around errata issue 12 by reading the transmit status vector 296 | if stat.latecol() == 1 || (tx_stat[2] & (1 << 5)) != 0 { 297 | Err(Error::LateCollision) 298 | } else { 299 | // TODO check for other error conditions 300 | unimplemented!() 301 | } 302 | } else { 303 | Ok(()) 304 | } 305 | } else { 306 | Ok(()) 307 | } 308 | } 309 | 310 | /// Copies a received frame into the specified `buffer` 311 | /// 312 | /// Returns the size of the frame 313 | /// 314 | /// **NOTE** If there's no pending packet this method will *block* until a new packet arrives 315 | pub fn receive(&mut self, buffer: &mut [u8]) -> Result { 316 | // Busy wait for a packet 317 | loop { 318 | let eir = common::EIR(self.read_control_register(common::Register::EIR)?); 319 | 320 | // TODO check for error conditions 321 | debug_assert!(eir.rxerif() == 0); 322 | 323 | if eir.pktif() == 1 { 324 | break; 325 | } 326 | } 327 | 328 | // prepare to read buffer memory 329 | let curr_packet = if self.next_packet == NONE { 330 | RXST 331 | } else { 332 | self.next_packet 333 | }; 334 | 335 | // read out the first 6 bytes 336 | let mut temp_buf: [u8; 6] = unsafe { mem::uninitialized() }; 337 | self.read_buffer_memory(Some(curr_packet), &mut temp_buf)?; 338 | 339 | // next packet pointer 340 | let next_packet = u16::from_parts(temp_buf[0], temp_buf[1]); 341 | self.next_packet = next_packet; 342 | 343 | // status vector 344 | let status = RxStatus(LE::read_u32(&temp_buf[2..])); 345 | 346 | let n = status.byte_count() as u16; 347 | // NOTE exclude the CRC (4 bytes) 348 | let end = n - Self::CRC_SZ; 349 | self.read_buffer_memory(None, &mut buffer[..usize(end)])?; 350 | 351 | // update ERXRDPT 352 | // due to Errata issue 11 we must write an odd address to ERXRDPT 353 | // we know that ERXST = 0, that ERXND is odd and that next_packet is even 354 | let rxrdpt = next_packet.checked_sub(1).unwrap_or(self.rxnd); 355 | self.write_control_register(bank0::Register::ERXRDPTL, rxrdpt.low())?; 356 | self.write_control_register(bank0::Register::ERXRDPTH, rxrdpt.high())?; 357 | 358 | // decrease the packet count 359 | self.write_control_register( 360 | common::Register::ECON2, 361 | common::ECON2::default().pktdec(1).bits(), 362 | )?; 363 | 364 | Ok(end) 365 | } 366 | 367 | /// Starts the transmission of `bytes` 368 | /// 369 | /// It's up to the caller to ensure that `bytes` is a valid Ethernet frame. The interface will 370 | /// take care of appending a (4 byte) CRC to the frame and of padding the frame to the minimum 371 | /// size allowed by the Ethernet specification (64 bytes, or 46 bytes of payload). 372 | /// 373 | /// NOTE This method will flush any previous transmission that's in progress 374 | /// 375 | /// # Panics 376 | /// 377 | /// If `bytes` length is greater than 1514, the maximum frame length allowed by the interface. 378 | pub fn transmit(&mut self, bytes: &[u8]) -> Result<(), Error> { 379 | assert!(bytes.len() <= usize(Self::MAX_FRAME_LENGTH - Self::CRC_SZ)); 380 | 381 | self.flush()?; 382 | 383 | let txst = self.txst(); 384 | 385 | // work around errata issue 10 by resetting the transmit logic before any every new 386 | // transmission 387 | self.bit_field_set(common::Register::ECON1, common::ECON1::mask().txrst())?; 388 | self.bit_field_clear(common::Register::ECON1, common::ECON1::mask().txrst())?; 389 | self.bit_field_clear(common::Register::EIR, { 390 | let mask = common::EIR::mask(); 391 | mask.txerif() | mask.txif() 392 | })?; 393 | 394 | // NOTE the plus one is to not override the per packet control byte 395 | let wrpt = txst + 1; 396 | 397 | // 1. ETXST was set during initialization 398 | 399 | // 2. write the frame to the IC memory 400 | self.write_buffer_memory(Some(wrpt), bytes)?; 401 | 402 | let txnd = wrpt + u16(bytes.len()).unwrap() - 1; 403 | 404 | // 3. Set the end address of the transmit buffer 405 | self.write_control_register(bank0::Register::ETXNDL, txnd.low())?; 406 | self.write_control_register(bank0::Register::ETXNDH, txnd.high())?; 407 | 408 | // 4. reset interrupt flag 409 | self.bit_field_clear(common::Register::EIR, { common::EIR::mask().txif() })?; 410 | 411 | // 5. start transmission 412 | self.bit_field_set(common::Register::ECON1, common::ECON1::mask().txrts())?; 413 | 414 | // NOTE(volatile) to avoid this value being set *before* the transmission is started 415 | unsafe { ptr::write_volatile(&mut self.txnd, txnd) } 416 | 417 | Ok(()) 418 | } 419 | 420 | /* Miscellaneous */ 421 | /// Destroys the driver and returns all the hardware resources that were owned by it 422 | pub fn free(self) -> (SPI, NCS, INT, RESET) { 423 | (self.spi, self.ncs, self.int, self.reset) 424 | } 425 | 426 | /// Returns the number of packets that have been received but have not been processed yet 427 | pub fn pending_packets(&mut self) -> Result { 428 | self.read_control_register(bank1::Register::EPKTCNT) 429 | } 430 | 431 | /* Private */ 432 | fn bit_field_clear(&mut self, register: R, mask: u8) -> Result<(), E> 433 | where 434 | R: Into, 435 | { 436 | self._bit_field_clear(register.into(), mask) 437 | } 438 | 439 | fn _bit_field_clear(&mut self, register: Register, mask: u8) -> Result<(), E> { 440 | assert!(register.is_eth_register()); 441 | 442 | self.change_bank(register)?; 443 | 444 | self.ncs.set_low(); 445 | self.spi 446 | .write(&[Instruction::BFC.opcode() | register.addr(), mask])?; 447 | self.ncs.set_high(); 448 | 449 | Ok(()) 450 | } 451 | 452 | fn bit_field_set(&mut self, register: R, mask: u8) -> Result<(), E> 453 | where 454 | R: Into, 455 | { 456 | self._bit_field_set(register.into(), mask) 457 | } 458 | 459 | fn _bit_field_set(&mut self, register: Register, mask: u8) -> Result<(), E> { 460 | assert!(register.is_eth_register()); 461 | 462 | self.change_bank(register)?; 463 | 464 | self.ncs.set_low(); 465 | self.spi 466 | .write(&[Instruction::BFS.opcode() | register.addr(), mask])?; 467 | self.ncs.set_high(); 468 | 469 | Ok(()) 470 | } 471 | 472 | fn modify_control_register(&mut self, register: R, f: F) -> Result<(), E> 473 | where 474 | F: FnOnce(u8) -> u8, 475 | R: Into, 476 | { 477 | self._modify_control_register(register.into(), f) 478 | } 479 | 480 | fn _modify_control_register(&mut self, register: Register, f: F) -> Result<(), E> 481 | where 482 | F: FnOnce(u8) -> u8, 483 | { 484 | let r = self._read_control_register(register)?; 485 | self._write_control_register(register, f(r)) 486 | } 487 | 488 | fn read_control_register(&mut self, register: R) -> Result 489 | where 490 | R: Into, 491 | { 492 | self._read_control_register(register.into()) 493 | } 494 | 495 | fn _read_control_register(&mut self, register: Register) -> Result { 496 | self.change_bank(register)?; 497 | 498 | self.ncs.set_low(); 499 | let mut buffer = [Instruction::RCR.opcode() | register.addr(), 0]; 500 | self.spi.transfer(&mut buffer)?; 501 | self.ncs.set_high(); 502 | 503 | Ok(buffer[1]) 504 | } 505 | 506 | #[allow(dead_code)] 507 | fn read_phy_register(&mut self, register: phy::Register) -> Result { 508 | // set PHY register address 509 | self.write_control_register(bank2::Register::MIREGADR, register.addr())?; 510 | 511 | // start read operation 512 | self.bit_field_set(bank2::Register::MICMD, bank2::MICMD::mask().miird())?; 513 | 514 | // wait until the read operation finishes 515 | while self.read_control_register(bank3::Register::MISTAT)? & 0b1 != 0 {} 516 | 517 | self.bit_field_clear(bank2::Register::MICMD, bank2::MICMD::mask().miird())?; 518 | 519 | Ok( 520 | ((self.read_control_register(bank2::Register::MIRDH)? as u16) << 8) 521 | | (self.read_control_register(bank2::Register::MIRDL)? as u16), 522 | ) 523 | } 524 | 525 | fn read_buffer_memory(&mut self, addr: Option, buf: &mut [u8]) -> Result<(), E> { 526 | if let Some(addr) = addr { 527 | self.write_control_register(bank0::Register::ERDPTL, addr.low())?; 528 | self.write_control_register(bank0::Register::ERDPTH, addr.high())?; 529 | } 530 | 531 | self.ncs.set_low(); 532 | self.spi.write(&[Instruction::RBM.opcode()])?; 533 | self.spi.transfer(buf)?; 534 | self.ncs.set_high(); 535 | 536 | Ok(()) 537 | } 538 | 539 | fn write_buffer_memory(&mut self, addr: Option, buffer: &[u8]) -> Result<(), E> { 540 | if let Some(addr) = addr { 541 | self.write_control_register(bank0::Register::EWRPTL, addr.low())?; 542 | self.write_control_register(bank0::Register::EWRPTH, addr.high())?; 543 | } 544 | 545 | self.ncs.set_low(); 546 | self.spi.write(&[Instruction::WBM.opcode()])?; 547 | self.spi.write(buffer)?; 548 | self.ncs.set_high(); 549 | Ok(()) 550 | } 551 | 552 | fn write_control_register(&mut self, register: R, value: u8) -> Result<(), E> 553 | where 554 | R: Into, 555 | { 556 | self._write_control_register(register.into(), value) 557 | } 558 | 559 | fn _write_control_register(&mut self, register: Register, value: u8) -> Result<(), E> { 560 | self.change_bank(register)?; 561 | 562 | self.ncs.set_low(); 563 | let buffer = [Instruction::WCR.opcode() | register.addr(), value]; 564 | self.spi.write(&buffer)?; 565 | self.ncs.set_high(); 566 | 567 | Ok(()) 568 | } 569 | 570 | fn write_phy_register(&mut self, register: phy::Register, value: u16) -> Result<(), E> { 571 | // set PHY register address 572 | self.write_control_register(bank2::Register::MIREGADR, register.addr())?; 573 | 574 | self.write_control_register(bank2::Register::MIWRL, (value & 0xff) as u8)?; 575 | // this starts the write operation 576 | self.write_control_register(bank2::Register::MIWRH, (value >> 8) as u8)?; 577 | 578 | // XXX should we not block until the write operation finishes 579 | // wait until the write operation finishes 580 | while self.read_control_register(bank3::Register::MISTAT)? & 0b1 != 0 {} 581 | 582 | Ok(()) 583 | } 584 | 585 | fn change_bank(&mut self, register: Register) -> Result<(), E> { 586 | let bank = register.bank(); 587 | 588 | if let Some(bank) = bank { 589 | if self.bank == bank { 590 | // already on the register bank 591 | return Ok(()); 592 | } 593 | 594 | // change bank 595 | self.bank = bank; 596 | match bank { 597 | Bank::Bank0 => self.bit_field_clear(common::Register::ECON1, 0b11), 598 | Bank::Bank1 => { 599 | self.modify_control_register(common::Register::ECON1, |r| (r & !0b11) | 0b01) 600 | } 601 | Bank::Bank2 => { 602 | self.modify_control_register(common::Register::ECON1, |r| (r & !0b11) | 0b10) 603 | } 604 | Bank::Bank3 => self.bit_field_set(common::Register::ECON1, 0b11), 605 | } 606 | } else { 607 | // common register 608 | Ok(()) 609 | } 610 | } 611 | 612 | fn soft_reset(&mut self) -> Result<(), E> { 613 | self.ncs.set_low(); 614 | self.spi.transfer(&mut [Instruction::SRC.opcode()])?; 615 | self.ncs.set_high(); 616 | 617 | Ok(()) 618 | } 619 | 620 | fn txst(&self) -> u16 { 621 | self.rxnd + 1 622 | } 623 | } 624 | 625 | impl Enc28j60 626 | where 627 | SPI: blocking::spi::Transfer + blocking::spi::Write, 628 | NCS: OutputPin, 629 | INT: IntPin + InputPin, 630 | RESET: ResetPin, 631 | { 632 | /// Starts listening for the specified event 633 | pub fn listen(&mut self, event: Event) -> Result<(), E> { 634 | match event { 635 | Event::Pkt => self.bit_field_set(common::Register::EIE, common::EIE::mask().pktie()), 636 | } 637 | } 638 | 639 | /// Checks if there's any interrupt pending to be processed by polling the INT pin 640 | pub fn interrupt_pending(&mut self) -> bool { 641 | self.int.is_low() 642 | } 643 | 644 | /// Stops listening for the specified event 645 | pub fn unlisten(&mut self, event: Event) -> Result<(), E> { 646 | match event { 647 | Event::Pkt => self.bit_field_clear(common::Register::EIE, common::EIE::mask().pktie()), 648 | } 649 | } 650 | } 651 | 652 | /// Reset pin or interrupt pin left unconnected 653 | pub struct Unconnected; 654 | 655 | // FIXME this should be a closed set trait 656 | /// [Implementation detail] Reset pin 657 | pub unsafe trait ResetPin: 'static { 658 | #[doc(hidden)] 659 | fn reset(&mut self); 660 | } 661 | 662 | unsafe impl ResetPin for Unconnected { 663 | fn reset(&mut self) {} 664 | } 665 | 666 | unsafe impl ResetPin for OP 667 | where 668 | OP: OutputPin + 'static, 669 | { 670 | fn reset(&mut self) { 671 | self.set_low(); 672 | self.set_high(); 673 | } 674 | } 675 | 676 | // FIXME this should be a closed set trait 677 | /// [Implementation detail] Interrupt pin 678 | pub unsafe trait IntPin: 'static {} 679 | 680 | unsafe impl IntPin for Unconnected {} 681 | 682 | unsafe impl IntPin for IP where IP: InputPin + 'static {} 683 | 684 | #[derive(Clone, Copy, PartialEq)] 685 | enum Bank { 686 | Bank0, 687 | Bank1, 688 | Bank2, 689 | Bank3, 690 | } 691 | 692 | #[derive(Clone, Copy)] 693 | enum Instruction { 694 | /// Read Control Register 695 | RCR = 0b000_00000, 696 | /// Read Buffer Memory 697 | RBM = 0b001_11010, 698 | /// Write Control Register 699 | WCR = 0b010_00000, 700 | /// Write Buffer Memory 701 | WBM = 0b011_11010, 702 | /// Bit Field Set 703 | BFS = 0b100_00000, 704 | /// Bit Field Clear 705 | BFC = 0b101_00000, 706 | /// System Reset Command 707 | SRC = 0b111_11111, 708 | } 709 | 710 | impl Instruction { 711 | fn opcode(&self) -> u8 { 712 | *self as u8 713 | } 714 | } 715 | 716 | #[derive(Clone, Copy)] 717 | enum Register { 718 | Bank0(bank0::Register), 719 | Bank1(bank1::Register), 720 | Bank2(bank2::Register), 721 | Bank3(bank3::Register), 722 | Common(common::Register), 723 | } 724 | 725 | impl Register { 726 | fn addr(&self) -> u8 { 727 | match *self { 728 | Register::Bank0(r) => r.addr(), 729 | Register::Bank1(r) => r.addr(), 730 | Register::Bank2(r) => r.addr(), 731 | Register::Bank3(r) => r.addr(), 732 | Register::Common(r) => r.addr(), 733 | } 734 | } 735 | 736 | fn bank(&self) -> Option { 737 | Some(match *self { 738 | Register::Bank0(_) => Bank::Bank0, 739 | Register::Bank1(_) => Bank::Bank1, 740 | Register::Bank2(_) => Bank::Bank2, 741 | Register::Bank3(_) => Bank::Bank3, 742 | Register::Common(_) => return None, 743 | }) 744 | } 745 | 746 | fn is_eth_register(&self) -> bool { 747 | match *self { 748 | Register::Bank0(r) => r.is_eth_register(), 749 | Register::Bank1(r) => r.is_eth_register(), 750 | Register::Bank2(r) => r.is_eth_register(), 751 | Register::Bank3(r) => r.is_eth_register(), 752 | Register::Common(r) => r.is_eth_register(), 753 | } 754 | } 755 | } 756 | 757 | register!(RxStatus, 0, u32, { 758 | #[doc = "Indicates length of the received frame"] 759 | byte_count @ 0..15, 760 | #[doc = "Indicates a packet over 50,000 bit times occurred or that a packet was dropped since the last receive"] 761 | long_event @ 16, 762 | #[doc = "Indicates that at some time since the last receive, a carrier event was detected"] 763 | carrier_event @ 18, 764 | #[doc = "Indicates that frame CRC field value does not match the CRC calculated by the MAC"] 765 | crc_error @ 20, 766 | #[doc = "Indicates that frame length field value in the packet does not match the actual data byte length and specifies a valid length"] 767 | length_check_error @ 21, 768 | #[doc = "Indicates that frame type/length field was larger than 1500 bytes (type field)"] 769 | length_out_of_range @ 22, 770 | #[doc = "Indicates that at the packet had a valid CRC and no symbol errors"] 771 | received_ok @ 23, 772 | #[doc = "Indicates packet received had a valid Multicast address"] 773 | multicast @ 24, 774 | #[doc = "Indicates packet received had a valid Broadcast address."] 775 | broadcast @ 25, 776 | #[doc = "Indicates that after the end of this packet, an additional 1 to 7 bits were received"] 777 | dribble_nibble @ 26, 778 | #[doc = "Current frame was recognized as a control frame for having a valid type/length designating it as a control frame"] 779 | receive_control_frame @ 27, 780 | #[doc = "Current frame was recognized as a control frame containing a valid pause frame opcode and a valid destination address"] 781 | receive_pause_control_frame @ 28, 782 | #[doc = "Current frame was recognized as a control frame but it contained an unknown opcode"] 783 | receive_unknown_opcode @ 29, 784 | #[doc = "Current frame was recognized as a VLAN tagged frame"] 785 | receive_vlan_type_detected @ 30, 786 | }); 787 | --------------------------------------------------------------------------------