wip: enc28j60 driver.
This commit is contained in:
parent
0fd9d7400b
commit
2c1402843a
13 changed files with 1552 additions and 0 deletions
23
embassy-net-enc28j60/Cargo.toml
Normal file
23
embassy-net-enc28j60/Cargo.toml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
[package]
|
||||||
|
name = "embassy-net-enc28j60"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "embassy-net driver for the ENC28J60 ethernet chip"
|
||||||
|
keywords = ["embedded", "enc28j60", "embassy-net", "embedded-hal-async", "ethernet", "async"]
|
||||||
|
categories = ["embedded", "hardware-support", "no-std", "network-programming", "async"]
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
embedded-hal = { version = "1.0.0-alpha.11" }
|
||||||
|
embedded-hal-async = { version = "=0.2.0-alpha.2" }
|
||||||
|
embassy-net-driver = { version = "0.1.0", path = "../embassy-net-driver" }
|
||||||
|
embassy-time = { version = "0.1.2", path = "../embassy-time" }
|
||||||
|
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
|
||||||
|
|
||||||
|
defmt = { version = "0.3", optional = true }
|
||||||
|
log = { version = "0.4.14", optional = true }
|
||||||
|
|
||||||
|
[package.metadata.embassy_docs]
|
||||||
|
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-enc28j60-v$VERSION/embassy-net-enc28j60/src/"
|
||||||
|
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-enc28j60/src/"
|
||||||
|
target = "thumbv7em-none-eabi"
|
1
embassy-net-enc28j60/README.md
Normal file
1
embassy-net-enc28j60/README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
# `embassy-net-enc28j60`
|
69
embassy-net-enc28j60/src/bank0.rs
Normal file
69
embassy-net-enc28j60/src/bank0.rs
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum Register {
|
||||||
|
ERDPTL = 0x00,
|
||||||
|
ERDPTH = 0x01,
|
||||||
|
EWRPTL = 0x02,
|
||||||
|
EWRPTH = 0x03,
|
||||||
|
ETXSTL = 0x04,
|
||||||
|
ETXSTH = 0x05,
|
||||||
|
ETXNDL = 0x06,
|
||||||
|
ETXNDH = 0x07,
|
||||||
|
ERXSTL = 0x08,
|
||||||
|
ERXSTH = 0x09,
|
||||||
|
ERXNDL = 0x0a,
|
||||||
|
ERXNDH = 0x0b,
|
||||||
|
ERXRDPTL = 0x0c,
|
||||||
|
ERXRDPTH = 0x0d,
|
||||||
|
ERXWRPTL = 0x0e,
|
||||||
|
ERXWRPTH = 0x0f,
|
||||||
|
EDMASTL = 0x10,
|
||||||
|
EDMASTH = 0x11,
|
||||||
|
EDMANDL = 0x12,
|
||||||
|
EDMANDH = 0x13,
|
||||||
|
EDMADSTL = 0x14,
|
||||||
|
EDMADSTH = 0x15,
|
||||||
|
EDMACSL = 0x16,
|
||||||
|
EDMACSH = 0x17,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Register {
|
||||||
|
pub(crate) fn addr(&self) -> u8 {
|
||||||
|
*self as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_eth_register(&self) -> bool {
|
||||||
|
match *self {
|
||||||
|
Register::ERDPTL => true,
|
||||||
|
Register::ERDPTH => true,
|
||||||
|
Register::EWRPTL => true,
|
||||||
|
Register::EWRPTH => true,
|
||||||
|
Register::ETXSTL => true,
|
||||||
|
Register::ETXSTH => true,
|
||||||
|
Register::ETXNDL => true,
|
||||||
|
Register::ETXNDH => true,
|
||||||
|
Register::ERXSTL => true,
|
||||||
|
Register::ERXSTH => true,
|
||||||
|
Register::ERXNDL => true,
|
||||||
|
Register::ERXNDH => true,
|
||||||
|
Register::ERXRDPTL => true,
|
||||||
|
Register::ERXRDPTH => true,
|
||||||
|
Register::ERXWRPTL => true,
|
||||||
|
Register::ERXWRPTH => true,
|
||||||
|
Register::EDMASTL => true,
|
||||||
|
Register::EDMASTH => true,
|
||||||
|
Register::EDMANDL => true,
|
||||||
|
Register::EDMANDH => true,
|
||||||
|
Register::EDMADSTL => true,
|
||||||
|
Register::EDMADSTH => true,
|
||||||
|
Register::EDMACSL => true,
|
||||||
|
Register::EDMACSH => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<super::Register> for Register {
|
||||||
|
fn into(self) -> super::Register {
|
||||||
|
super::Register::Bank0(self)
|
||||||
|
}
|
||||||
|
}
|
84
embassy-net-enc28j60/src/bank1.rs
Normal file
84
embassy-net-enc28j60/src/bank1.rs
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum Register {
|
||||||
|
EHT0 = 0x00,
|
||||||
|
EHT1 = 0x01,
|
||||||
|
EHT2 = 0x02,
|
||||||
|
EHT3 = 0x03,
|
||||||
|
EHT4 = 0x04,
|
||||||
|
EHT5 = 0x05,
|
||||||
|
EHT6 = 0x06,
|
||||||
|
EHT7 = 0x07,
|
||||||
|
EPMM0 = 0x08,
|
||||||
|
EPMM1 = 0x09,
|
||||||
|
EPMM2 = 0x0a,
|
||||||
|
EPMM3 = 0x0b,
|
||||||
|
EPMM4 = 0x0c,
|
||||||
|
EPMM5 = 0x0d,
|
||||||
|
EPMM6 = 0x0e,
|
||||||
|
EPMM7 = 0x0f,
|
||||||
|
EPMCSL = 0x10,
|
||||||
|
EPMCSH = 0x11,
|
||||||
|
EPMOL = 0x14,
|
||||||
|
EPMOH = 0x15,
|
||||||
|
ERXFCON = 0x18,
|
||||||
|
EPKTCNT = 0x19,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Register {
|
||||||
|
pub(crate) fn addr(&self) -> u8 {
|
||||||
|
*self as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_eth_register(&self) -> bool {
|
||||||
|
match *self {
|
||||||
|
Register::EHT0 => true,
|
||||||
|
Register::EHT1 => true,
|
||||||
|
Register::EHT2 => true,
|
||||||
|
Register::EHT3 => true,
|
||||||
|
Register::EHT4 => true,
|
||||||
|
Register::EHT5 => true,
|
||||||
|
Register::EHT6 => true,
|
||||||
|
Register::EHT7 => true,
|
||||||
|
Register::EPMM0 => true,
|
||||||
|
Register::EPMM1 => true,
|
||||||
|
Register::EPMM2 => true,
|
||||||
|
Register::EPMM3 => true,
|
||||||
|
Register::EPMM4 => true,
|
||||||
|
Register::EPMM5 => true,
|
||||||
|
Register::EPMM6 => true,
|
||||||
|
Register::EPMM7 => true,
|
||||||
|
Register::EPMCSL => true,
|
||||||
|
Register::EPMCSH => true,
|
||||||
|
Register::EPMOL => true,
|
||||||
|
Register::EPMOH => true,
|
||||||
|
Register::ERXFCON => true,
|
||||||
|
Register::EPKTCNT => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<super::Register> for Register {
|
||||||
|
fn into(self) -> super::Register {
|
||||||
|
super::Register::Bank1(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
register!(ERXFCON, 0b1010_0001, u8, {
|
||||||
|
#[doc = "Broadcast Filter Enable bit"]
|
||||||
|
bcen @ 0,
|
||||||
|
#[doc = "Multicast Filter Enable bit"]
|
||||||
|
mcen @ 1,
|
||||||
|
#[doc = "Hash Table Filter Enable bit"]
|
||||||
|
hten @ 2,
|
||||||
|
#[doc = "Magic Packet™ Filter Enable bit"]
|
||||||
|
mpen @ 3,
|
||||||
|
#[doc = "Pattern Match Filter Enable bit"]
|
||||||
|
pmen @ 4,
|
||||||
|
#[doc = "Post-Filter CRC Check Enable bit"]
|
||||||
|
crcen @ 5,
|
||||||
|
#[doc = "AND/OR Filter Select bit"]
|
||||||
|
andor @ 6,
|
||||||
|
#[doc = "Unicast Filter Enable bit"]
|
||||||
|
ucen @ 7,
|
||||||
|
});
|
86
embassy-net-enc28j60/src/bank2.rs
Normal file
86
embassy-net-enc28j60/src/bank2.rs
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum Register {
|
||||||
|
MACON1 = 0x00,
|
||||||
|
MACON3 = 0x02,
|
||||||
|
MACON4 = 0x03,
|
||||||
|
MABBIPG = 0x04,
|
||||||
|
MAIPGL = 0x06,
|
||||||
|
MAIPGH = 0x07,
|
||||||
|
MACLCON1 = 0x08,
|
||||||
|
MACLCON2 = 0x09,
|
||||||
|
MAMXFLL = 0x0a,
|
||||||
|
MAMXFLH = 0x0b,
|
||||||
|
MICMD = 0x12,
|
||||||
|
MIREGADR = 0x14,
|
||||||
|
MIWRL = 0x16,
|
||||||
|
MIWRH = 0x17,
|
||||||
|
MIRDL = 0x18,
|
||||||
|
MIRDH = 0x19,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Register {
|
||||||
|
pub(crate) fn addr(&self) -> u8 {
|
||||||
|
*self as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_eth_register(&self) -> bool {
|
||||||
|
match *self {
|
||||||
|
Register::MACON1 => false,
|
||||||
|
Register::MACON3 => false,
|
||||||
|
Register::MACON4 => false,
|
||||||
|
Register::MABBIPG => false,
|
||||||
|
Register::MAIPGL => false,
|
||||||
|
Register::MAIPGH => false,
|
||||||
|
Register::MACLCON1 => false,
|
||||||
|
Register::MACLCON2 => false,
|
||||||
|
Register::MAMXFLL => false,
|
||||||
|
Register::MAMXFLH => false,
|
||||||
|
Register::MICMD => false,
|
||||||
|
Register::MIREGADR => false,
|
||||||
|
Register::MIWRL => false,
|
||||||
|
Register::MIWRH => false,
|
||||||
|
Register::MIRDL => false,
|
||||||
|
Register::MIRDH => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<super::Register> for Register {
|
||||||
|
fn into(self) -> super::Register {
|
||||||
|
super::Register::Bank2(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
register!(MACON1, 0, u8, {
|
||||||
|
#[doc = "Enable packets to be received by the MAC"]
|
||||||
|
marxen @ 0,
|
||||||
|
#[doc = "Control frames will be discarded after being processed by the MAC"]
|
||||||
|
passall @ 1,
|
||||||
|
#[doc = "Inhibit transmissions when pause control frames are received"]
|
||||||
|
rxpaus @ 2,
|
||||||
|
#[doc = "Allow the MAC to transmit pause control frames"]
|
||||||
|
txpaus @ 3,
|
||||||
|
});
|
||||||
|
|
||||||
|
register!(MACON3, 0, u8, {
|
||||||
|
#[doc = "MAC will operate in Full-Duplex mode"]
|
||||||
|
fuldpx @ 0,
|
||||||
|
#[doc = "The type/length field of transmitted and received frames will be checked"]
|
||||||
|
frmlnen @ 1,
|
||||||
|
#[doc = "Frames bigger than MAMXFL will be aborted when transmitted or received"]
|
||||||
|
hfrmen @ 2,
|
||||||
|
#[doc = "No proprietary header is present"]
|
||||||
|
phdren @ 3,
|
||||||
|
#[doc = "MAC will append a valid CRC to all frames transmitted regardless of PADCFG bit"]
|
||||||
|
txcrcen @ 4,
|
||||||
|
#[doc = "All short frames will be zero-padded to 64 bytes and a valid CRC will then be appended"]
|
||||||
|
padcfg @ 5..7,
|
||||||
|
});
|
||||||
|
|
||||||
|
register!(MICMD, 0, u8, {
|
||||||
|
#[doc = "MII Read Enable bit"]
|
||||||
|
miird @ 0,
|
||||||
|
#[doc = "MII Scan Enable bit"]
|
||||||
|
miiscan @ 1,
|
||||||
|
});
|
53
embassy-net-enc28j60/src/bank3.rs
Normal file
53
embassy-net-enc28j60/src/bank3.rs
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum Register {
|
||||||
|
MAADR5 = 0x00,
|
||||||
|
MAADR6 = 0x01,
|
||||||
|
MAADR3 = 0x02,
|
||||||
|
MAADR4 = 0x03,
|
||||||
|
MAADR1 = 0x04,
|
||||||
|
MAADR2 = 0x05,
|
||||||
|
EBSTSD = 0x06,
|
||||||
|
EBSTCON = 0x07,
|
||||||
|
EBSTCSL = 0x08,
|
||||||
|
EBSTCSH = 0x09,
|
||||||
|
MISTAT = 0x0a,
|
||||||
|
EREVID = 0x12,
|
||||||
|
ECOCON = 0x15,
|
||||||
|
EFLOCON = 0x17,
|
||||||
|
EPAUSL = 0x18,
|
||||||
|
EPAUSH = 0x19,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Register {
|
||||||
|
pub(crate) fn addr(&self) -> u8 {
|
||||||
|
*self as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_eth_register(&self) -> bool {
|
||||||
|
match *self {
|
||||||
|
Register::MAADR5 => false,
|
||||||
|
Register::MAADR6 => false,
|
||||||
|
Register::MAADR3 => false,
|
||||||
|
Register::MAADR4 => false,
|
||||||
|
Register::MAADR1 => false,
|
||||||
|
Register::MAADR2 => false,
|
||||||
|
Register::EBSTSD => true,
|
||||||
|
Register::EBSTCON => true,
|
||||||
|
Register::EBSTCSL => true,
|
||||||
|
Register::EBSTCSH => true,
|
||||||
|
Register::MISTAT => false,
|
||||||
|
Register::EREVID => true,
|
||||||
|
Register::ECOCON => true,
|
||||||
|
Register::EFLOCON => true,
|
||||||
|
Register::EPAUSL => true,
|
||||||
|
Register::EPAUSH => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<super::Register> for Register {
|
||||||
|
fn into(self) -> super::Register {
|
||||||
|
super::Register::Bank3(self)
|
||||||
|
}
|
||||||
|
}
|
106
embassy-net-enc28j60/src/common.rs
Normal file
106
embassy-net-enc28j60/src/common.rs
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum Register {
|
||||||
|
ECON1 = 0x1f,
|
||||||
|
ECON2 = 0x1e,
|
||||||
|
EIE = 0x1b,
|
||||||
|
EIR = 0x1c,
|
||||||
|
ESTAT = 0x1d,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Register {
|
||||||
|
pub(crate) fn addr(&self) -> u8 {
|
||||||
|
*self as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_eth_register(&self) -> bool {
|
||||||
|
match *self {
|
||||||
|
Register::ECON1 => true,
|
||||||
|
Register::ECON2 => true,
|
||||||
|
Register::EIE => true,
|
||||||
|
Register::EIR => true,
|
||||||
|
Register::ESTAT => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<super::Register> for Register {
|
||||||
|
fn into(self) -> super::Register {
|
||||||
|
super::Register::Common(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
register!(EIE, 0, u8, {
|
||||||
|
#[doc = "Receive Error Interrupt Enable bit"]
|
||||||
|
rxerie @ 0,
|
||||||
|
#[doc = "Transmit Error Interrupt Enable bit"]
|
||||||
|
txerie @ 1,
|
||||||
|
#[doc = "Transmit Enable bit"]
|
||||||
|
txie @ 3,
|
||||||
|
#[doc = "Link Status Change Interrupt Enable bit"]
|
||||||
|
linkie @ 4,
|
||||||
|
#[doc = "DMA Interrupt Enable bit"]
|
||||||
|
dmaie @ 5,
|
||||||
|
#[doc = "Receive Packet Pending Interrupt Enable bit"]
|
||||||
|
pktie @ 6,
|
||||||
|
#[doc = "Global INT Interrupt Enable bit"]
|
||||||
|
intie @ 7,
|
||||||
|
});
|
||||||
|
|
||||||
|
register!(EIR, 0, u8, {
|
||||||
|
#[doc = "Receive Error Interrupt Flag bit"]
|
||||||
|
rxerif @ 0,
|
||||||
|
#[doc = "Transmit Error Interrupt Flag bit"]
|
||||||
|
txerif @ 1,
|
||||||
|
#[doc = "Transmit Interrupt Flag bit"]
|
||||||
|
txif @ 3,
|
||||||
|
#[doc = "Link Change Interrupt Flag bit"]
|
||||||
|
linkif @ 4,
|
||||||
|
#[doc = "DMA Interrupt Flag bit"]
|
||||||
|
dmaif @ 5,
|
||||||
|
#[doc = "Receive Packet Pending Interrupt Flag bit"]
|
||||||
|
pktif @ 6,
|
||||||
|
});
|
||||||
|
|
||||||
|
register!(ESTAT, 0, u8, {
|
||||||
|
#[doc = "Clock Ready bit"]
|
||||||
|
clkrdy @ 0,
|
||||||
|
#[doc = "Transmit Abort Error bit"]
|
||||||
|
txabrt @ 1,
|
||||||
|
#[doc = "Receive Busy bit"]
|
||||||
|
rxbusy @ 2,
|
||||||
|
#[doc = "Late Collision Error bit"]
|
||||||
|
latecol @ 4,
|
||||||
|
#[doc = "Ethernet Buffer Error Status bit"]
|
||||||
|
bufer @ 6,
|
||||||
|
#[doc = "INT Interrupt Flag bit"]
|
||||||
|
int @ 7,
|
||||||
|
});
|
||||||
|
|
||||||
|
register!(ECON2, 0b1000_0000, u8, {
|
||||||
|
#[doc = "Voltage Regulator Power Save Enable bit"]
|
||||||
|
vrps @ 3,
|
||||||
|
#[doc = "Power Save Enable bit"]
|
||||||
|
pwrsv @ 5,
|
||||||
|
#[doc = "Packet Decrement bit"]
|
||||||
|
pktdec @ 6,
|
||||||
|
#[doc = "Automatic Buffer Pointer Increment Enable bit"]
|
||||||
|
autoinc @ 7,
|
||||||
|
});
|
||||||
|
|
||||||
|
register!(ECON1, 0, u8, {
|
||||||
|
#[doc = "Bank Select bits"]
|
||||||
|
bsel @ 0..1,
|
||||||
|
#[doc = "Receive Enable bi"]
|
||||||
|
rxen @ 2,
|
||||||
|
#[doc = "Transmit Request to Send bit"]
|
||||||
|
txrts @ 3,
|
||||||
|
#[doc = "DMA Checksum Enable bit"]
|
||||||
|
csumen @ 4,
|
||||||
|
#[doc = "DMA Start and Busy Status bit"]
|
||||||
|
dmast @ 5,
|
||||||
|
#[doc = "Receive Logic Reset bit"]
|
||||||
|
rxrst @ 6,
|
||||||
|
#[doc = "Transmit Logic Reset bit"]
|
||||||
|
txrst @ 7,
|
||||||
|
});
|
225
embassy-net-enc28j60/src/fmt.rs
Normal file
225
embassy-net-enc28j60/src/fmt.rs
Normal file
|
@ -0,0 +1,225 @@
|
||||||
|
#![macro_use]
|
||||||
|
#![allow(unused_macros)]
|
||||||
|
|
||||||
|
#[cfg(all(feature = "defmt", feature = "log"))]
|
||||||
|
compile_error!("You may not enable both `defmt` and `log` features.");
|
||||||
|
|
||||||
|
macro_rules! assert {
|
||||||
|
($($x:tt)*) => {
|
||||||
|
{
|
||||||
|
#[cfg(not(feature = "defmt"))]
|
||||||
|
::core::assert!($($x)*);
|
||||||
|
#[cfg(feature = "defmt")]
|
||||||
|
::defmt::assert!($($x)*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! assert_eq {
|
||||||
|
($($x:tt)*) => {
|
||||||
|
{
|
||||||
|
#[cfg(not(feature = "defmt"))]
|
||||||
|
::core::assert_eq!($($x)*);
|
||||||
|
#[cfg(feature = "defmt")]
|
||||||
|
::defmt::assert_eq!($($x)*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! assert_ne {
|
||||||
|
($($x:tt)*) => {
|
||||||
|
{
|
||||||
|
#[cfg(not(feature = "defmt"))]
|
||||||
|
::core::assert_ne!($($x)*);
|
||||||
|
#[cfg(feature = "defmt")]
|
||||||
|
::defmt::assert_ne!($($x)*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! debug_assert {
|
||||||
|
($($x:tt)*) => {
|
||||||
|
{
|
||||||
|
#[cfg(not(feature = "defmt"))]
|
||||||
|
::core::debug_assert!($($x)*);
|
||||||
|
#[cfg(feature = "defmt")]
|
||||||
|
::defmt::debug_assert!($($x)*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! debug_assert_eq {
|
||||||
|
($($x:tt)*) => {
|
||||||
|
{
|
||||||
|
#[cfg(not(feature = "defmt"))]
|
||||||
|
::core::debug_assert_eq!($($x)*);
|
||||||
|
#[cfg(feature = "defmt")]
|
||||||
|
::defmt::debug_assert_eq!($($x)*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! debug_assert_ne {
|
||||||
|
($($x:tt)*) => {
|
||||||
|
{
|
||||||
|
#[cfg(not(feature = "defmt"))]
|
||||||
|
::core::debug_assert_ne!($($x)*);
|
||||||
|
#[cfg(feature = "defmt")]
|
||||||
|
::defmt::debug_assert_ne!($($x)*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! todo {
|
||||||
|
($($x:tt)*) => {
|
||||||
|
{
|
||||||
|
#[cfg(not(feature = "defmt"))]
|
||||||
|
::core::todo!($($x)*);
|
||||||
|
#[cfg(feature = "defmt")]
|
||||||
|
::defmt::todo!($($x)*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! unreachable {
|
||||||
|
($($x:tt)*) => {
|
||||||
|
{
|
||||||
|
#[cfg(not(feature = "defmt"))]
|
||||||
|
::core::unreachable!($($x)*);
|
||||||
|
#[cfg(feature = "defmt")]
|
||||||
|
::defmt::unreachable!($($x)*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! panic {
|
||||||
|
($($x:tt)*) => {
|
||||||
|
{
|
||||||
|
#[cfg(not(feature = "defmt"))]
|
||||||
|
::core::panic!($($x)*);
|
||||||
|
#[cfg(feature = "defmt")]
|
||||||
|
::defmt::panic!($($x)*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! trace {
|
||||||
|
($s:literal $(, $x:expr)* $(,)?) => {
|
||||||
|
{
|
||||||
|
#[cfg(feature = "log")]
|
||||||
|
::log::trace!($s $(, $x)*);
|
||||||
|
#[cfg(feature = "defmt")]
|
||||||
|
::defmt::trace!($s $(, $x)*);
|
||||||
|
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||||
|
let _ = ($( & $x ),*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! debug {
|
||||||
|
($s:literal $(, $x:expr)* $(,)?) => {
|
||||||
|
{
|
||||||
|
#[cfg(feature = "log")]
|
||||||
|
::log::debug!($s $(, $x)*);
|
||||||
|
#[cfg(feature = "defmt")]
|
||||||
|
::defmt::debug!($s $(, $x)*);
|
||||||
|
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||||
|
let _ = ($( & $x ),*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! info {
|
||||||
|
($s:literal $(, $x:expr)* $(,)?) => {
|
||||||
|
{
|
||||||
|
#[cfg(feature = "log")]
|
||||||
|
::log::info!($s $(, $x)*);
|
||||||
|
#[cfg(feature = "defmt")]
|
||||||
|
::defmt::info!($s $(, $x)*);
|
||||||
|
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||||
|
let _ = ($( & $x ),*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! warn {
|
||||||
|
($s:literal $(, $x:expr)* $(,)?) => {
|
||||||
|
{
|
||||||
|
#[cfg(feature = "log")]
|
||||||
|
::log::warn!($s $(, $x)*);
|
||||||
|
#[cfg(feature = "defmt")]
|
||||||
|
::defmt::warn!($s $(, $x)*);
|
||||||
|
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||||
|
let _ = ($( & $x ),*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! error {
|
||||||
|
($s:literal $(, $x:expr)* $(,)?) => {
|
||||||
|
{
|
||||||
|
#[cfg(feature = "log")]
|
||||||
|
::log::error!($s $(, $x)*);
|
||||||
|
#[cfg(feature = "defmt")]
|
||||||
|
::defmt::error!($s $(, $x)*);
|
||||||
|
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||||
|
let _ = ($( & $x ),*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "defmt")]
|
||||||
|
macro_rules! unwrap {
|
||||||
|
($($x:tt)*) => {
|
||||||
|
::defmt::unwrap!($($x)*)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "defmt"))]
|
||||||
|
macro_rules! unwrap {
|
||||||
|
($arg:expr) => {
|
||||||
|
match $crate::fmt::Try::into_result($arg) {
|
||||||
|
::core::result::Result::Ok(t) => t,
|
||||||
|
::core::result::Result::Err(e) => {
|
||||||
|
::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($arg:expr, $($msg:expr),+ $(,)? ) => {
|
||||||
|
match $crate::fmt::Try::into_result($arg) {
|
||||||
|
::core::result::Result::Ok(t) => t,
|
||||||
|
::core::result::Result::Err(e) => {
|
||||||
|
::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
pub struct NoneError;
|
||||||
|
|
||||||
|
pub trait Try {
|
||||||
|
type Ok;
|
||||||
|
type Error;
|
||||||
|
fn into_result(self) -> Result<Self::Ok, Self::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Try for Option<T> {
|
||||||
|
type Ok = T;
|
||||||
|
type Error = NoneError;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn into_result(self) -> Result<T, NoneError> {
|
||||||
|
self.ok_or(NoneError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, E> Try for Result<T, E> {
|
||||||
|
type Ok = T;
|
||||||
|
type Error = E;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn into_result(self) -> Self {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
30
embassy-net-enc28j60/src/header.rs
Normal file
30
embassy-net-enc28j60/src/header.rs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
register!(RxStatus, 0, u32, {
|
||||||
|
#[doc = "Indicates length of the received frame"]
|
||||||
|
byte_count @ 0..15,
|
||||||
|
#[doc = "Indicates a packet over 50,000 bit times occurred or that a packet was dropped since the last receive"]
|
||||||
|
long_event @ 16,
|
||||||
|
#[doc = "Indicates that at some time since the last receive, a carrier event was detected"]
|
||||||
|
carrier_event @ 18,
|
||||||
|
#[doc = "Indicates that frame CRC field value does not match the CRC calculated by the MAC"]
|
||||||
|
crc_error @ 20,
|
||||||
|
#[doc = "Indicates that frame length field value in the packet does not match the actual data byte length and specifies a valid length"]
|
||||||
|
length_check_error @ 21,
|
||||||
|
#[doc = "Indicates that frame type/length field was larger than 1500 bytes (type field)"]
|
||||||
|
length_out_of_range @ 22,
|
||||||
|
#[doc = "Indicates that at the packet had a valid CRC and no symbol errors"]
|
||||||
|
received_ok @ 23,
|
||||||
|
#[doc = "Indicates packet received had a valid Multicast address"]
|
||||||
|
multicast @ 24,
|
||||||
|
#[doc = "Indicates packet received had a valid Broadcast address."]
|
||||||
|
broadcast @ 25,
|
||||||
|
#[doc = "Indicates that after the end of this packet, an additional 1 to 7 bits were received"]
|
||||||
|
dribble_nibble @ 26,
|
||||||
|
#[doc = "Current frame was recognized as a control frame for having a valid type/length designating it as a control frame"]
|
||||||
|
receive_control_frame @ 27,
|
||||||
|
#[doc = "Current frame was recognized as a control frame containing a valid pause frame opcode and a valid destination address"]
|
||||||
|
receive_pause_control_frame @ 28,
|
||||||
|
#[doc = "Current frame was recognized as a control frame but it contained an unknown opcode"]
|
||||||
|
receive_unknown_opcode @ 29,
|
||||||
|
#[doc = "Current frame was recognized as a VLAN tagged frame"]
|
||||||
|
receive_vlan_type_detected @ 30,
|
||||||
|
});
|
693
embassy-net-enc28j60/src/lib.rs
Normal file
693
embassy-net-enc28j60/src/lib.rs
Normal file
|
@ -0,0 +1,693 @@
|
||||||
|
#![no_std]
|
||||||
|
#![doc = include_str!("../README.md")]
|
||||||
|
|
||||||
|
// must go first.
|
||||||
|
mod fmt;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod macros;
|
||||||
|
mod bank0;
|
||||||
|
mod bank1;
|
||||||
|
mod bank2;
|
||||||
|
mod bank3;
|
||||||
|
mod common;
|
||||||
|
mod header;
|
||||||
|
mod phy;
|
||||||
|
mod traits;
|
||||||
|
|
||||||
|
use core::cmp;
|
||||||
|
use core::convert::TryInto;
|
||||||
|
|
||||||
|
use embassy_net_driver::{Capabilities, HardwareAddress, LinkState, Medium};
|
||||||
|
use embassy_time::Duration;
|
||||||
|
use embedded_hal::digital::OutputPin;
|
||||||
|
use embedded_hal::spi::{Operation, SpiDevice};
|
||||||
|
use traits::U16Ext;
|
||||||
|
|
||||||
|
// Total buffer size (see section 3.2)
|
||||||
|
const BUF_SZ: u16 = 8 * 1024;
|
||||||
|
|
||||||
|
// Maximum frame length
|
||||||
|
const MAX_FRAME_LENGTH: u16 = 1518; // value recommended in the data sheet
|
||||||
|
|
||||||
|
// Size of the Frame check sequence (32-bit CRC)
|
||||||
|
const CRC_SZ: u16 = 4;
|
||||||
|
|
||||||
|
// define the boundaries of the TX and RX buffers
|
||||||
|
// to workaround errata #5 we do the opposite of what section 6.1 of the data sheet
|
||||||
|
// says: we place the RX buffer at address 0 and the TX buffer after it
|
||||||
|
const RXST: u16 = 0x0000;
|
||||||
|
const RXND: u16 = 0x19ff;
|
||||||
|
const TXST: u16 = 0x1a00;
|
||||||
|
const _TXND: u16 = 0x1fff;
|
||||||
|
|
||||||
|
const MTU: usize = 1514; // 1500 IP + 14 ethernet header
|
||||||
|
|
||||||
|
pub struct Enc28j60<S, O> {
|
||||||
|
mac_addr: [u8; 6],
|
||||||
|
|
||||||
|
spi: S,
|
||||||
|
rst: Option<O>,
|
||||||
|
|
||||||
|
bank: Bank,
|
||||||
|
|
||||||
|
// address of the next packet in buffer memory
|
||||||
|
next_packet: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, O> Enc28j60<S, O>
|
||||||
|
where
|
||||||
|
S: SpiDevice,
|
||||||
|
O: OutputPin,
|
||||||
|
{
|
||||||
|
pub fn new(spi: S, rst: Option<O>, mac_addr: [u8; 6]) -> Self {
|
||||||
|
let mut res = Self {
|
||||||
|
mac_addr,
|
||||||
|
spi,
|
||||||
|
rst,
|
||||||
|
|
||||||
|
bank: Bank::Bank0,
|
||||||
|
next_packet: RXST,
|
||||||
|
};
|
||||||
|
res.init();
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(&mut self) {
|
||||||
|
if let Some(rst) = &mut self.rst {
|
||||||
|
rst.set_low().unwrap();
|
||||||
|
embassy_time::block_for(Duration::from_millis(5));
|
||||||
|
rst.set_high().unwrap();
|
||||||
|
embassy_time::block_for(Duration::from_millis(5));
|
||||||
|
} else {
|
||||||
|
embassy_time::block_for(Duration::from_millis(5));
|
||||||
|
self.soft_reset();
|
||||||
|
embassy_time::block_for(Duration::from_millis(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
"enc28j60: erevid {=u8:x}",
|
||||||
|
self.read_control_register(bank3::Register::EREVID)
|
||||||
|
);
|
||||||
|
debug!("enc28j60: waiting for clk");
|
||||||
|
while common::ESTAT(self.read_control_register(common::Register::ESTAT)).clkrdy() == 0 {}
|
||||||
|
debug!("enc28j60: clk ok");
|
||||||
|
|
||||||
|
if self.read_control_register(bank3::Register::EREVID) == 0 {
|
||||||
|
panic!("ErevidIsZero");
|
||||||
|
}
|
||||||
|
|
||||||
|
// disable CLKOUT output
|
||||||
|
self.write_control_register(bank3::Register::ECOCON, 0);
|
||||||
|
|
||||||
|
// RX start
|
||||||
|
// "It is recommended that the ERXST Pointer be programmed with an even address"
|
||||||
|
self.write_control_register(bank0::Register::ERXSTL, RXST.low());
|
||||||
|
self.write_control_register(bank0::Register::ERXSTH, RXST.high());
|
||||||
|
|
||||||
|
// RX read pointer
|
||||||
|
// NOTE Errata #14 so we are using an *odd* address here instead of ERXST
|
||||||
|
self.write_control_register(bank0::Register::ERXRDPTL, RXND.low());
|
||||||
|
self.write_control_register(bank0::Register::ERXRDPTH, RXND.high());
|
||||||
|
|
||||||
|
// RX end
|
||||||
|
self.write_control_register(bank0::Register::ERXNDL, RXND.low());
|
||||||
|
self.write_control_register(bank0::Register::ERXNDH, RXND.high());
|
||||||
|
|
||||||
|
// TX start
|
||||||
|
// "It is recommended that an even address be used for ETXST"
|
||||||
|
debug_assert_eq!(TXST % 2, 0);
|
||||||
|
self.write_control_register(bank0::Register::ETXSTL, TXST.low());
|
||||||
|
self.write_control_register(bank0::Register::ETXSTH, TXST.high());
|
||||||
|
|
||||||
|
// TX end is set in `transmit`
|
||||||
|
|
||||||
|
// MAC initialization (see section 6.5)
|
||||||
|
// 1. Set the MARXEN bit in MACON1 to enable the MAC to receive frames.
|
||||||
|
self.write_control_register(
|
||||||
|
bank2::Register::MACON1,
|
||||||
|
bank2::MACON1::default().marxen(1).passall(0).rxpaus(1).txpaus(1).bits(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 2. Configure the PADCFG, TXCRCEN and FULDPX bits of MACON3.
|
||||||
|
self.write_control_register(
|
||||||
|
bank2::Register::MACON3,
|
||||||
|
bank2::MACON3::default().frmlnen(1).txcrcen(1).padcfg(0b001).bits(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 4. Program the MAMXFL registers with the maximum frame length to be permitted to be
|
||||||
|
// received or transmitted
|
||||||
|
self.write_control_register(bank2::Register::MAMXFLL, MAX_FRAME_LENGTH.low());
|
||||||
|
self.write_control_register(bank2::Register::MAMXFLH, MAX_FRAME_LENGTH.high());
|
||||||
|
|
||||||
|
// 5. Configure the Back-to-Back Inter-Packet Gap register, MABBIPG.
|
||||||
|
// Use recommended value of 0x12
|
||||||
|
self.write_control_register(bank2::Register::MABBIPG, 0x12);
|
||||||
|
|
||||||
|
// 6. Configure the Non-Back-to-Back Inter-Packet Gap register low byte, MAIPGL.
|
||||||
|
// Use recommended value of 0x12
|
||||||
|
self.write_control_register(bank2::Register::MAIPGL, 0x12);
|
||||||
|
self.write_control_register(bank2::Register::MAIPGH, 0x0c);
|
||||||
|
|
||||||
|
// 9. Program the local MAC address into the MAADR1:MAADR6 registers
|
||||||
|
self.write_control_register(bank3::Register::MAADR1, self.mac_addr[0]);
|
||||||
|
self.write_control_register(bank3::Register::MAADR2, self.mac_addr[1]);
|
||||||
|
self.write_control_register(bank3::Register::MAADR3, self.mac_addr[2]);
|
||||||
|
self.write_control_register(bank3::Register::MAADR4, self.mac_addr[3]);
|
||||||
|
self.write_control_register(bank3::Register::MAADR5, self.mac_addr[4]);
|
||||||
|
self.write_control_register(bank3::Register::MAADR6, self.mac_addr[5]);
|
||||||
|
|
||||||
|
// Set the PHCON2.HDLDIS bit to prevent automatic loopback of the data which is transmitted
|
||||||
|
self.write_phy_register(phy::Register::PHCON2, phy::PHCON2::default().hdldis(1).bits());
|
||||||
|
|
||||||
|
// Globally enable interrupts
|
||||||
|
//self.bit_field_set(common::Register::EIE, common::EIE::mask().intie());
|
||||||
|
|
||||||
|
// Set the per packet control byte; we'll always use the value 0
|
||||||
|
self.write_buffer_memory(Some(TXST), &mut [0]);
|
||||||
|
|
||||||
|
// decrease the packet count to 0
|
||||||
|
while self.read_control_register(bank1::Register::EPKTCNT) != 0 {
|
||||||
|
self.bit_field_set(common::Register::ECON2, common::ECON2::mask().pktdec());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable reception
|
||||||
|
self.bit_field_set(common::Register::ECON1, common::ECON1::mask().rxen());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Flushes the transmit buffer, ensuring all pending transmissions have completed
|
||||||
|
/// NOTE: The returned packet *must* be `read` or `ignore`-d, otherwise this method will always
|
||||||
|
/// return `None` on subsequent invocations
|
||||||
|
pub fn receive<'a>(&mut self, buf: &'a mut [u8]) -> Option<&'a mut [u8]> {
|
||||||
|
if self.pending_packets() == 0 {
|
||||||
|
// Errata #6: we can't rely on PKTIF so we check PKTCNT
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let curr_packet = self.next_packet;
|
||||||
|
|
||||||
|
// read out the first 6 bytes
|
||||||
|
let mut temp_buf = [0; 6];
|
||||||
|
self.read_buffer_memory(Some(curr_packet), &mut temp_buf);
|
||||||
|
|
||||||
|
// next packet pointer
|
||||||
|
let next_packet = u16::from_parts(temp_buf[0], temp_buf[1]);
|
||||||
|
if next_packet > RXND {
|
||||||
|
panic!("CorruptRxBuffer");
|
||||||
|
}
|
||||||
|
|
||||||
|
// status vector
|
||||||
|
let status = header::RxStatus(u32::from_le_bytes(temp_buf[2..].try_into().unwrap()));
|
||||||
|
let len = status.byte_count() as u16 - CRC_SZ;
|
||||||
|
|
||||||
|
if len > RXND {
|
||||||
|
panic!("CorruptRxBuffer 2");
|
||||||
|
}
|
||||||
|
|
||||||
|
self.read_buffer_memory(None, &mut buf[..len as usize]);
|
||||||
|
|
||||||
|
// update ERXRDPT
|
||||||
|
// due to Errata #14 we must write an odd address to ERXRDPT
|
||||||
|
// we know that ERXST = 0, that ERXND is odd and that next_packet is even
|
||||||
|
let rxrdpt = if self.next_packet < 1 || self.next_packet > RXND + 1 {
|
||||||
|
RXND
|
||||||
|
} else {
|
||||||
|
self.next_packet - 1
|
||||||
|
};
|
||||||
|
// "To move ERXRDPT, the host controller must write to ERXRDPTL first."
|
||||||
|
self.write_control_register(bank0::Register::ERXRDPTL, rxrdpt.low());
|
||||||
|
self.write_control_register(bank0::Register::ERXRDPTH, rxrdpt.high());
|
||||||
|
|
||||||
|
// decrease the packet count
|
||||||
|
self.bit_field_set(common::Register::ECON2, common::ECON2::mask().pktdec());
|
||||||
|
|
||||||
|
self.next_packet = next_packet;
|
||||||
|
|
||||||
|
Some(&mut buf[..len as usize])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_tx_ready(&mut self) {
|
||||||
|
for _ in 0u32..10000 {
|
||||||
|
if common::ECON1(self.read_control_register(common::Register::ECON1)).txrts() == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// work around errata #12 by resetting the transmit logic before every new
|
||||||
|
// transmission
|
||||||
|
self.bit_field_set(common::Register::ECON1, common::ECON1::mask().txrst());
|
||||||
|
self.bit_field_clear(common::Register::ECON1, common::ECON1::mask().txrst());
|
||||||
|
//self.bit_field_clear(common::Register::EIR, {
|
||||||
|
// let mask = common::EIR::mask();
|
||||||
|
// mask.txerif() | mask.txif()
|
||||||
|
//});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Starts the transmission of `bytes`
|
||||||
|
///
|
||||||
|
/// It's up to the caller to ensure that `bytes` is a valid Ethernet frame. The interface will
|
||||||
|
/// take care of appending a (4 byte) CRC to the frame and of padding the frame to the minimum
|
||||||
|
/// size allowed by the Ethernet specification (64 bytes, or 46 bytes of payload).
|
||||||
|
///
|
||||||
|
/// NOTE This method will flush any previous transmission that's in progress
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// If `bytes` length is greater than 1514, the maximum frame length allowed by the interface,
|
||||||
|
/// or greater than the transmit buffer
|
||||||
|
pub fn transmit(&mut self, bytes: &[u8]) {
|
||||||
|
assert!(bytes.len() <= self.mtu() as usize);
|
||||||
|
|
||||||
|
self.wait_tx_ready();
|
||||||
|
|
||||||
|
// NOTE the plus one is to not overwrite the per packet control byte
|
||||||
|
let wrpt = TXST + 1;
|
||||||
|
|
||||||
|
// 1. ETXST was set during initialization
|
||||||
|
|
||||||
|
// 2. write the frame to the IC memory
|
||||||
|
self.write_buffer_memory(Some(wrpt), bytes);
|
||||||
|
|
||||||
|
let txnd = wrpt + bytes.len() as u16 - 1;
|
||||||
|
|
||||||
|
// 3. Set the end address of the transmit buffer
|
||||||
|
self.write_control_register(bank0::Register::ETXNDL, txnd.low());
|
||||||
|
self.write_control_register(bank0::Register::ETXNDH, txnd.high());
|
||||||
|
|
||||||
|
// 4. reset interrupt flag
|
||||||
|
//self.bit_field_clear(common::Register::EIR, common::EIR::mask().txif());
|
||||||
|
|
||||||
|
// 5. start transmission
|
||||||
|
self.bit_field_set(common::Register::ECON1, common::ECON1::mask().txrts());
|
||||||
|
|
||||||
|
// Wait until transmission finishes
|
||||||
|
//while common::ECON1(self.read_control_register(common::Register::ECON1)).txrts() == 1 {}
|
||||||
|
|
||||||
|
/*
|
||||||
|
// read the transmit status vector
|
||||||
|
let mut tx_stat = [0; 7];
|
||||||
|
self.read_buffer_memory(None, &mut tx_stat);
|
||||||
|
|
||||||
|
let stat = common::ESTAT(self.read_control_register(common::Register::ESTAT));
|
||||||
|
|
||||||
|
if stat.txabrt() == 1 {
|
||||||
|
// work around errata #12 by reading the transmit status vector
|
||||||
|
if stat.latecol() == 1 || (tx_stat[2] & (1 << 5)) != 0 {
|
||||||
|
panic!("LateCollision")
|
||||||
|
} else {
|
||||||
|
panic!("TransmitAbort")
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_link_up(&mut self) -> bool {
|
||||||
|
let bits = self.read_phy_register(phy::Register::PHSTAT2);
|
||||||
|
phy::PHSTAT2(bits).lstat() == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the interface Maximum Transmission Unit (MTU)
|
||||||
|
///
|
||||||
|
/// The value returned by this function will never exceed 1514 bytes. The actual value depends
|
||||||
|
/// on the memory assigned to the transmission buffer when initializing the device
|
||||||
|
pub fn mtu(&self) -> u16 {
|
||||||
|
cmp::min(BUF_SZ - RXND - 1, MAX_FRAME_LENGTH - CRC_SZ)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Miscellaneous */
|
||||||
|
/// Returns the number of packets that have been received but have not been processed yet
|
||||||
|
pub fn pending_packets(&mut self) -> u8 {
|
||||||
|
self.read_control_register(bank1::Register::EPKTCNT)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adjusts the receive filter to *accept* these packet types
|
||||||
|
pub fn accept(&mut self, packets: &[Packet]) {
|
||||||
|
let mask = bank1::ERXFCON::mask();
|
||||||
|
let mut val = 0;
|
||||||
|
for packet in packets {
|
||||||
|
match packet {
|
||||||
|
Packet::Broadcast => val |= mask.bcen(),
|
||||||
|
Packet::Multicast => val |= mask.mcen(),
|
||||||
|
Packet::Unicast => val |= mask.ucen(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.bit_field_set(bank1::Register::ERXFCON, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adjusts the receive filter to *ignore* these packet types
|
||||||
|
pub fn ignore(&mut self, packets: &[Packet]) {
|
||||||
|
let mask = bank1::ERXFCON::mask();
|
||||||
|
let mut val = 0;
|
||||||
|
for packet in packets {
|
||||||
|
match packet {
|
||||||
|
Packet::Broadcast => val |= mask.bcen(),
|
||||||
|
Packet::Multicast => val |= mask.mcen(),
|
||||||
|
Packet::Unicast => val |= mask.ucen(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.bit_field_clear(bank1::Register::ERXFCON, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
/* Read */
|
||||||
|
fn read_control_register<R>(&mut self, register: R) -> u8
|
||||||
|
where
|
||||||
|
R: Into<Register>,
|
||||||
|
{
|
||||||
|
self._read_control_register(register.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _read_control_register(&mut self, register: Register) -> u8 {
|
||||||
|
self.change_bank(register);
|
||||||
|
|
||||||
|
let mut buffer = [Instruction::RCR.opcode() | register.addr(), 0];
|
||||||
|
self.spi.transfer_in_place(&mut buffer).unwrap();
|
||||||
|
|
||||||
|
buffer[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_phy_register(&mut self, register: phy::Register) -> u16 {
|
||||||
|
embassy_time::block_for(Duration::from_millis(1));
|
||||||
|
|
||||||
|
// set PHY register address
|
||||||
|
self.write_control_register(bank2::Register::MIREGADR, register.addr());
|
||||||
|
|
||||||
|
// start read operation
|
||||||
|
self.write_control_register(bank2::Register::MICMD, bank2::MICMD::default().miird(1).bits());
|
||||||
|
|
||||||
|
// wait until the read operation finishes
|
||||||
|
while self.read_control_register(bank3::Register::MISTAT) & 0b1 != 0 {}
|
||||||
|
|
||||||
|
let h = self.read_control_register(bank2::Register::MIRDH);
|
||||||
|
let l = self.read_control_register(bank2::Register::MIRDL);
|
||||||
|
|
||||||
|
self.write_control_register(bank2::Register::MICMD, bank2::MICMD::default().miird(0).bits());
|
||||||
|
|
||||||
|
(l as u16) | (h as u16) << 8
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Write */
|
||||||
|
fn _write_control_register(&mut self, register: Register, value: u8) {
|
||||||
|
self.change_bank(register);
|
||||||
|
|
||||||
|
let buffer = [Instruction::WCR.opcode() | register.addr(), value];
|
||||||
|
self.spi.write(&buffer).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_control_register<R>(&mut self, register: R, value: u8)
|
||||||
|
where
|
||||||
|
R: Into<Register>,
|
||||||
|
{
|
||||||
|
self._write_control_register(register.into(), value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_phy_register(&mut self, register: phy::Register, value: u16) {
|
||||||
|
// set PHY register address
|
||||||
|
self.write_control_register(bank2::Register::MIREGADR, register.addr());
|
||||||
|
|
||||||
|
self.write_control_register(bank2::Register::MIWRL, (value & 0xff) as u8);
|
||||||
|
// this starts the write operation
|
||||||
|
self.write_control_register(bank2::Register::MIWRH, (value >> 8) as u8);
|
||||||
|
|
||||||
|
// wait until the write operation finishes
|
||||||
|
while self.read_control_register(bank3::Register::MISTAT) & 0b1 != 0 {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RMW */
|
||||||
|
fn modify_control_register<R, F>(&mut self, register: R, f: F)
|
||||||
|
where
|
||||||
|
F: FnOnce(u8) -> u8,
|
||||||
|
R: Into<Register>,
|
||||||
|
{
|
||||||
|
self._modify_control_register(register.into(), f)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _modify_control_register<F>(&mut self, register: Register, f: F)
|
||||||
|
where
|
||||||
|
F: FnOnce(u8) -> u8,
|
||||||
|
{
|
||||||
|
let r = self._read_control_register(register);
|
||||||
|
self._write_control_register(register, f(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Auxiliary */
|
||||||
|
fn change_bank(&mut self, register: Register) {
|
||||||
|
let bank = register.bank();
|
||||||
|
|
||||||
|
if let Some(bank) = bank {
|
||||||
|
if self.bank == bank {
|
||||||
|
// already on the register bank
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// change bank
|
||||||
|
self.bank = bank;
|
||||||
|
match bank {
|
||||||
|
Bank::Bank0 => self.bit_field_clear(common::Register::ECON1, 0b11),
|
||||||
|
Bank::Bank1 => self.modify_control_register(common::Register::ECON1, |r| (r & !0b11) | 0b01),
|
||||||
|
Bank::Bank2 => self.modify_control_register(common::Register::ECON1, |r| (r & !0b11) | 0b10),
|
||||||
|
Bank::Bank3 => self.bit_field_set(common::Register::ECON1, 0b11),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// common register
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Primitive operations */
|
||||||
|
fn bit_field_clear<R>(&mut self, register: R, mask: u8)
|
||||||
|
where
|
||||||
|
R: Into<Register>,
|
||||||
|
{
|
||||||
|
self._bit_field_clear(register.into(), mask)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _bit_field_clear(&mut self, register: Register, mask: u8) {
|
||||||
|
debug_assert!(register.is_eth_register());
|
||||||
|
|
||||||
|
self.change_bank(register);
|
||||||
|
|
||||||
|
self.spi
|
||||||
|
.write(&[Instruction::BFC.opcode() | register.addr(), mask])
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bit_field_set<R>(&mut self, register: R, mask: u8)
|
||||||
|
where
|
||||||
|
R: Into<Register>,
|
||||||
|
{
|
||||||
|
self._bit_field_set(register.into(), mask)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _bit_field_set(&mut self, register: Register, mask: u8) {
|
||||||
|
debug_assert!(register.is_eth_register());
|
||||||
|
|
||||||
|
self.change_bank(register);
|
||||||
|
|
||||||
|
self.spi
|
||||||
|
.write(&[Instruction::BFS.opcode() | register.addr(), mask])
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_buffer_memory(&mut self, addr: Option<u16>, buf: &mut [u8]) {
|
||||||
|
if let Some(addr) = addr {
|
||||||
|
self.write_control_register(bank0::Register::ERDPTL, addr.low());
|
||||||
|
self.write_control_register(bank0::Register::ERDPTH, addr.high());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.spi
|
||||||
|
.transaction(&mut [Operation::Write(&[Instruction::RBM.opcode()]), Operation::Read(buf)])
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn soft_reset(&mut self) {
|
||||||
|
self.spi.write(&[Instruction::SRC.opcode()]).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_buffer_memory(&mut self, addr: Option<u16>, buffer: &[u8]) {
|
||||||
|
if let Some(addr) = addr {
|
||||||
|
self.write_control_register(bank0::Register::EWRPTL, addr.low());
|
||||||
|
self.write_control_register(bank0::Register::EWRPTH, addr.high());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.spi
|
||||||
|
.transaction(&mut [Operation::Write(&[Instruction::WBM.opcode()]), Operation::Write(buffer)])
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
|
enum Bank {
|
||||||
|
Bank0,
|
||||||
|
Bank1,
|
||||||
|
Bank2,
|
||||||
|
Bank3,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
enum Instruction {
|
||||||
|
/// Read Control Register
|
||||||
|
RCR = 0b000_00000,
|
||||||
|
/// Read Buffer Memory
|
||||||
|
RBM = 0b001_11010,
|
||||||
|
/// Write Control Register
|
||||||
|
WCR = 0b010_00000,
|
||||||
|
/// Write Buffer Memory
|
||||||
|
WBM = 0b011_11010,
|
||||||
|
/// Bit Field Set
|
||||||
|
BFS = 0b100_00000,
|
||||||
|
/// Bit Field Clear
|
||||||
|
BFC = 0b101_00000,
|
||||||
|
/// System Reset Command
|
||||||
|
SRC = 0b111_11111,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Instruction {
|
||||||
|
fn opcode(&self) -> u8 {
|
||||||
|
*self as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
enum Register {
|
||||||
|
Bank0(bank0::Register),
|
||||||
|
Bank1(bank1::Register),
|
||||||
|
Bank2(bank2::Register),
|
||||||
|
Bank3(bank3::Register),
|
||||||
|
Common(common::Register),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Register {
|
||||||
|
fn addr(&self) -> u8 {
|
||||||
|
match *self {
|
||||||
|
Register::Bank0(r) => r.addr(),
|
||||||
|
Register::Bank1(r) => r.addr(),
|
||||||
|
Register::Bank2(r) => r.addr(),
|
||||||
|
Register::Bank3(r) => r.addr(),
|
||||||
|
Register::Common(r) => r.addr(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bank(&self) -> Option<Bank> {
|
||||||
|
Some(match *self {
|
||||||
|
Register::Bank0(_) => Bank::Bank0,
|
||||||
|
Register::Bank1(_) => Bank::Bank1,
|
||||||
|
Register::Bank2(_) => Bank::Bank2,
|
||||||
|
Register::Bank3(_) => Bank::Bank3,
|
||||||
|
Register::Common(_) => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_eth_register(&self) -> bool {
|
||||||
|
match *self {
|
||||||
|
Register::Bank0(r) => r.is_eth_register(),
|
||||||
|
Register::Bank1(r) => r.is_eth_register(),
|
||||||
|
Register::Bank2(r) => r.is_eth_register(),
|
||||||
|
Register::Bank3(r) => r.is_eth_register(),
|
||||||
|
Register::Common(r) => r.is_eth_register(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Packet type, used to configure receive filters
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||||
|
pub enum Packet {
|
||||||
|
/// Broadcast packets
|
||||||
|
Broadcast,
|
||||||
|
/// Multicast packets
|
||||||
|
Multicast,
|
||||||
|
/// Unicast packets
|
||||||
|
Unicast,
|
||||||
|
}
|
||||||
|
|
||||||
|
static mut TX_BUF: [u8; MTU] = [0; MTU];
|
||||||
|
static mut RX_BUF: [u8; MTU] = [0; MTU];
|
||||||
|
|
||||||
|
impl<S, O> embassy_net_driver::Driver for Enc28j60<S, O>
|
||||||
|
where
|
||||||
|
S: SpiDevice,
|
||||||
|
O: OutputPin,
|
||||||
|
{
|
||||||
|
type RxToken<'a> = RxToken<'a>
|
||||||
|
where
|
||||||
|
Self: 'a;
|
||||||
|
|
||||||
|
type TxToken<'a> = TxToken<'a, S, O>
|
||||||
|
where
|
||||||
|
Self: 'a;
|
||||||
|
|
||||||
|
fn receive(&mut self, cx: &mut core::task::Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
|
||||||
|
let rx_buf = unsafe { &mut RX_BUF };
|
||||||
|
let tx_buf = unsafe { &mut TX_BUF };
|
||||||
|
if let Some(pkt) = self.receive(rx_buf) {
|
||||||
|
let n = pkt.len();
|
||||||
|
Some((RxToken { buf: &mut pkt[..n] }, TxToken { buf: tx_buf, eth: self }))
|
||||||
|
} else {
|
||||||
|
cx.waker().wake_by_ref();
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transmit(&mut self, _cx: &mut core::task::Context) -> Option<Self::TxToken<'_>> {
|
||||||
|
let tx_buf = unsafe { &mut TX_BUF };
|
||||||
|
Some(TxToken { buf: tx_buf, eth: self })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn link_state(&mut self, cx: &mut core::task::Context) -> LinkState {
|
||||||
|
cx.waker().wake_by_ref();
|
||||||
|
match self.is_link_up() {
|
||||||
|
true => LinkState::Up,
|
||||||
|
false => LinkState::Down,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn capabilities(&self) -> Capabilities {
|
||||||
|
let mut caps = Capabilities::default();
|
||||||
|
caps.max_transmission_unit = MTU;
|
||||||
|
caps.medium = Medium::Ethernet;
|
||||||
|
caps
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hardware_address(&self) -> HardwareAddress {
|
||||||
|
HardwareAddress::Ethernet(self.mac_addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RxToken<'a> {
|
||||||
|
buf: &'a mut [u8],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> embassy_net_driver::RxToken for RxToken<'a> {
|
||||||
|
fn consume<R, F>(self, f: F) -> R
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut [u8]) -> R,
|
||||||
|
{
|
||||||
|
f(self.buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TxToken<'a, S, O>
|
||||||
|
where
|
||||||
|
S: SpiDevice,
|
||||||
|
O: OutputPin,
|
||||||
|
{
|
||||||
|
eth: &'a mut Enc28j60<S, O>,
|
||||||
|
buf: &'a mut [u8],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, S, O> embassy_net_driver::TxToken for TxToken<'a, S, O>
|
||||||
|
where
|
||||||
|
S: SpiDevice,
|
||||||
|
O: OutputPin,
|
||||||
|
{
|
||||||
|
fn consume<R, F>(self, len: usize, f: F) -> R
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut [u8]) -> R,
|
||||||
|
{
|
||||||
|
assert!(len <= self.buf.len());
|
||||||
|
let r = f(&mut self.buf[..len]);
|
||||||
|
self.eth.transmit(&self.buf[..len]);
|
||||||
|
r
|
||||||
|
}
|
||||||
|
}
|
89
embassy-net-enc28j60/src/macros.rs
Normal file
89
embassy-net-enc28j60/src/macros.rs
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
macro_rules! register {
|
||||||
|
($REGISTER:ident, $reset_value:expr, $uxx:ty, {
|
||||||
|
$(#[$($attr:tt)*] $bitfield:ident @ $range:expr,)+
|
||||||
|
}) => {
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub(crate) struct $REGISTER<MODE> {
|
||||||
|
bits: $uxx,
|
||||||
|
_mode: ::core::marker::PhantomData<MODE>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl $REGISTER<super::traits::Mask> {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn mask() -> $REGISTER<super::traits::Mask> {
|
||||||
|
$REGISTER { bits: 0, _mode: ::core::marker::PhantomData }
|
||||||
|
}
|
||||||
|
|
||||||
|
$(
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn $bitfield(&self) -> $uxx {
|
||||||
|
use super::traits::OffsetSize;
|
||||||
|
|
||||||
|
let size = $range.size();
|
||||||
|
let offset = $range.offset();
|
||||||
|
((1 << size) - 1) << offset
|
||||||
|
}
|
||||||
|
)+
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ::core::default::Default for $REGISTER<super::traits::W> {
|
||||||
|
fn default() -> Self {
|
||||||
|
$REGISTER { bits: $reset_value, _mode: ::core::marker::PhantomData }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn $REGISTER(bits: $uxx) -> $REGISTER<super::traits::R> {
|
||||||
|
$REGISTER { bits, _mode: ::core::marker::PhantomData }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl $REGISTER<super::traits::R> {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn modify(self) -> $REGISTER<super::traits::W> {
|
||||||
|
$REGISTER { bits: self.bits, _mode: ::core::marker::PhantomData }
|
||||||
|
}
|
||||||
|
|
||||||
|
$(
|
||||||
|
#[$($attr)*]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn $bitfield(&self) -> $uxx {
|
||||||
|
use super::traits::OffsetSize;
|
||||||
|
|
||||||
|
let offset = $range.offset();
|
||||||
|
let size = $range.size();
|
||||||
|
let mask = (1 << size) - 1;
|
||||||
|
|
||||||
|
(self.bits >> offset) & mask
|
||||||
|
}
|
||||||
|
)+
|
||||||
|
}
|
||||||
|
|
||||||
|
impl $REGISTER<super::traits::W> {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn bits(self) -> $uxx {
|
||||||
|
self.bits
|
||||||
|
}
|
||||||
|
|
||||||
|
$(
|
||||||
|
#[$($attr)*]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn $bitfield(&mut self, mut bits: $uxx) -> &mut Self {
|
||||||
|
use super::traits::OffsetSize;
|
||||||
|
|
||||||
|
let offset = $range.offset();
|
||||||
|
let size = $range.size();
|
||||||
|
let mask = (1 << size) - 1;
|
||||||
|
|
||||||
|
debug_assert!(bits <= mask);
|
||||||
|
bits &= mask;
|
||||||
|
|
||||||
|
self.bits &= !(mask << offset);
|
||||||
|
self.bits |= bits << offset;
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
)+
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
36
embassy-net-enc28j60/src/phy.rs
Normal file
36
embassy-net-enc28j60/src/phy.rs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum Register {
|
||||||
|
PHCON1 = 0x00,
|
||||||
|
PHSTAT1 = 0x01,
|
||||||
|
PHID1 = 0x02,
|
||||||
|
PHID2 = 0x03,
|
||||||
|
PHCON2 = 0x10,
|
||||||
|
PHSTAT2 = 0x11,
|
||||||
|
PHIE = 0x12,
|
||||||
|
PHIR = 0x13,
|
||||||
|
PHLCON = 0x14,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Register {
|
||||||
|
pub(crate) fn addr(&self) -> u8 {
|
||||||
|
*self as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
register!(PHCON2, 0, u16, {
|
||||||
|
#[doc = "PHY Half-Duplex Loopback Disable bit"]
|
||||||
|
hdldis @ 8,
|
||||||
|
#[doc = "Jabber Correction Disable bit"]
|
||||||
|
jabber @ 10,
|
||||||
|
#[doc = "Twisted-Pair Transmitter Disable bit"]
|
||||||
|
txdis @ 13,
|
||||||
|
#[doc = "PHY Force Linkup bit"]
|
||||||
|
frclnk @ 14,
|
||||||
|
});
|
||||||
|
|
||||||
|
register!(PHSTAT2, 0, u16, {
|
||||||
|
// Datasheet says it's bit 10, but it's actually bit 2 ?!?!
|
||||||
|
#[doc = "Link Status bit"]
|
||||||
|
lstat @ 2,
|
||||||
|
});
|
57
embassy-net-enc28j60/src/traits.rs
Normal file
57
embassy-net-enc28j60/src/traits.rs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
use core::ops::Range;
|
||||||
|
|
||||||
|
pub(crate) trait OffsetSize {
|
||||||
|
fn offset(self) -> u8;
|
||||||
|
fn size(self) -> u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OffsetSize for u8 {
|
||||||
|
fn offset(self) -> u8 {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(self) -> u8 {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OffsetSize for Range<u8> {
|
||||||
|
fn offset(self) -> u8 {
|
||||||
|
self.start
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(self) -> u8 {
|
||||||
|
self.end - self.start
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait U16Ext {
|
||||||
|
fn from_parts(low: u8, high: u8) -> Self;
|
||||||
|
|
||||||
|
fn low(self) -> u8;
|
||||||
|
|
||||||
|
fn high(self) -> u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl U16Ext for u16 {
|
||||||
|
fn from_parts(low: u8, high: u8) -> u16 {
|
||||||
|
((high as u16) << 8) + low as u16
|
||||||
|
}
|
||||||
|
|
||||||
|
fn low(self) -> u8 {
|
||||||
|
(self & 0xff) as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
fn high(self) -> u8 {
|
||||||
|
(self >> 8) as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct Mask;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct R;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct W;
|
Loading…
Reference in a new issue