diff --git a/CMakeLists.txt b/CMakeLists.txt index ad20784..01646bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,6 +35,7 @@ add_library(avclan STATIC src/avclan/avclan_protocol.c src/avclan/cdchanger.c src/avclan/peripheral.cc + src/avclan/bus.cc ) # avclan exports its public generic headers (src/avclan) to consumers and diff --git a/src/avclan/bus.cc b/src/avclan/bus.cc new file mode 100644 index 0000000..0b825d4 --- /dev/null +++ b/src/avclan/bus.cc @@ -0,0 +1,32 @@ +// copyright (C) 2006 Marcin Slonicki +// copyright (C) 2007 Louis Frigon +// Copyright (C) 2015 Allen Hill +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "bus.hpp" +#include "avclan_defs.h" +#include "avclan_phy.h" // bridge until phy has been ported + +namespace avclan { +void Bus::init() { AVCLAN_busInit(); }; +void Bus::mute(bool mute) { AVCLAN_muteDevice(mute); }; +bool Bus::is_muted() const { return AVCLAN_ismuted(); }; +Bus::Handle Bus::get() { return {}; }; + +bool Bus::Handle::sendstartbit() { return AVCLAN_sendstartbit(); }; +Bus::Error::Read Bus::Handle::readstartbit() { + using enum Error::Read; + auto err = AVCLAN_readstartbit(); + if (err == rSTARTBIT_TOO_LONG) + return STARTBIT_TOO_LONG; + else if (err == rLATCHED_COMPARATOR) + return BAD_STARTBIT; + else if (err == rSTARTBIT_TOO_SHORT) + return STARTBIT_TOO_SHORT; + else + return Read{0}; +}; +void Bus::Handle::send_ACK() { AVCLAN_sendbit_ACK(); }; +uint8_t Bus::Handle::read_ACK() { return AVCLAN_readbit_ACK(); }; + +} // namespace avclan diff --git a/src/avclan/bus.hpp b/src/avclan/bus.hpp new file mode 100644 index 0000000..9835c94 --- /dev/null +++ b/src/avclan/bus.hpp @@ -0,0 +1,178 @@ +// copyright (C) 2006 Marcin Slonicki +// copyright (C) 2007 Louis Frigon +// Copyright (C) 2015 Allen Hill +// SPDX-License-Identifier: GPL-3.0-or-later + +/* + AVC LAN Theory + + The AVC LAN bus is an implementation of the IEBus (mode 1) which is a + differential signal; IEBus is electrically (but not logically) compatible with + CAN bus. + + - Logical `1`: Potential difference between bus lines (BUS+ pin and BUS– pin) + is 20 mV or lower (floating). + - Logical `0`: Potential difference between bus lines (BUS+ pin and BUS– pin) + is 120 mV or higher (driving). + + A nominal bit length is 39 us, composed of 3 periods: preparation, + synchronization, data. + + Figure 1. AVCLAN Bus bit format + + │ Prep │<─ Sync ─>│<─ Data ─>│ ... + Driving (logical `0`) ╭──────────╮──────────╮ + │ │ │ + Floating (logical `1`) ─────────╯ ╰──────────╰───────── + │ 6 μs │── 19 μs ─│─ 13 μs ──│ + + The logical value during the data period signifies the bit value, e.g. a bit + `0` continues the logical `0` (high potential difference between bus lines) of + the sync period thru the data period, and a bit `1` has a logical `1` + (low/floating potential between bus lines) during the data period. Using the + TCB pulse-width and frequency measure mode, the total bit length differs for + bit `1` and `0`; detailed bit timing can be found in "timing.h". The bus + idles at low potential (floating). + + A start bit is nominally 169 us high followed by 20 us low. + + A bit `0` is dominant on the bus, which is a design choice that affects + bit/interpretation: + - Low addresses have priority upon transmission conflicts + - The broadcast bit is `1` (floating, no effort) for normal communication + - For acknowledge bits, the receiver extends the logical '0' of the sync + period to the length of a normal bit `0`. Hence, a NAK (bit `1`) is + literally the absence of an ACK. +*/ + +#pragma once + +#include + +#include "avclan_defs.h" +#include "avclan_phy.h" // bridge until phy has been ported + +namespace avclan { +class Bus { +public: + struct Error { + enum class Read : uint8_t { + BAD_PARITY = 0x01, + STARTBIT_TOO_SHORT = 0x80, // Start *well* above Peripher::Error::Read + // (which ~inherits these values) + STARTBIT_TOO_LONG, + BAD_STARTBIT, + }; + + enum class Send : uint8_t { + NAK = 0x01, + }; + }; + class Handle; + + void init(); + void mute(bool mute); + bool is_muted() const; + Handle get(); +}; + +class Bus::Handle { + Handle() { AVCLAN_stopEvent(); } + friend Bus; + +public: + ~Handle() { AVCLAN_startEvent(); } + Handle(const Handle &) = delete; + Handle(Handle &&) = delete; + + bool sendstartbit(); + Error::Read readstartbit(); + + template + requires(sizeof(T) < 3 && N < 16) + Error::Send send(T bits, bool ack) { + const auto parity = sendbits(bits); + sendbits<1>(static_cast(parity)); + + if (ack && !read_ACK()) + return Send::NAK; + + return Send{0}; + }; + + template + requires(sizeof(T) < 3 && N < 16) + Error::Read read(T *bits, F &&ack) { + const auto calc_parity = readbits(bits); + uint8_t read_parity; + readbits<1>(&read_parity); + if (calc_parity != read_parity) { + return Read::BAD_PARITY; + } else if (ack()) { + send_ACK(); + } else + readbits<1>(&read_parity); + + return Read{0}; + }; + template + requires(sizeof(T) < 3 && N < 16) + Error::Read read(T *bits, bool ack) { + return read(bits, [=]() { return ack; }); + } + +private: + using Read = Error::Read; + using Send = Error::Send; + + // A single bus symbol. bit_zero/bit_one carry data (and double as parity + // values); bit_start marks a frame start bit. + enum class avclan_bit : uint8_t { + bit_zero = 0x00, + bit_one = 0x01, + bit_start = 0x10 + }; + + void send_ACK(); + uint8_t read_ACK(); + + template avclan_bit_t sendbits(T bits); + template avclan_bit_t readbits(T *bits); + + // Temporary specializations bridging to legacy C API + // Replace with proper (single?) template when phy has been ported + template + requires(N > 1 && N < 8) + avclan_bit_t sendbits(uint8_t bits) { + return AVCLAN_sendbitsi(&bits, N); + }; + template + requires(N <= 16) + avclan_bit_t sendbits(uint16_t bits) { + return AVCLAN_sendbitsl(&bits, N); + }; + template + requires(N < 8) + avclan_bit_t readbits(uint8_t *bits) { + return static_cast(AVCLAN_readbitsi(bits, N)); + }; + template + requires(N <= 16) + avclan_bit_t readbits(uint16_t *bits) { + return static_cast(AVCLAN_readbitsl(bits, N)); + }; +}; + +template <> inline avclan_bit_t Bus::Handle::sendbits<8>(uint8_t byte) { + return AVCLAN_sendbyte(&byte); +}; +template <> inline avclan_bit_t Bus::Handle::sendbits<1>(uint8_t byte) { + const avclan_bit_t b{static_cast(byte & 1u)}; + AVCLAN_sendbit(b); + return b; +}; +template <> inline avclan_bit_t Bus::Handle::readbits<8>(uint8_t *byte) { + return static_cast(AVCLAN_readbyte(byte)); +}; + +} // namespace avclan diff --git a/src/avclan/peripheral.cc b/src/avclan/peripheral.cc index 8e3b66d..cc9b63e 100644 --- a/src/avclan/peripheral.cc +++ b/src/avclan/peripheral.cc @@ -6,8 +6,8 @@ #include "peripheral.hpp" #include "avclan_defs.h" #include "avclan_frame.h" -#include "avclan_phy.h" // bus symbol I/O + transaction guard (target-provided) -#include "com232.h" // error logging +#include "bus.hpp" +#include "com232.h" // error logging #include @@ -26,104 +26,83 @@ Peripheral::Error::Read Peripheral::read(AVCLAN_frame_t *in, log_t print) { } err = {}; using enum Error::Read; + auto BAD_PARITY = Bus::Error::Read::BAD_PARITY; - AVCLAN_stopEvent(); // quiesce contending sources during the read + { // bound handle lifetime + auto handle = bus.get(); - bool shouldACK = false; - uint8_t tmp = 0, parity = 0; + bool shouldACK = false; + uint8_t tmp = 0; - err.errno = Error::Read{AVCLAN_readstartbit()}; - if (static_cast(err.errno)) - goto handle_err; + err.errno = Error::Read{static_cast(handle.readstartbit())}; + if (static_cast(err.errno)) + goto handle_err; - AVCLAN_readbits<1>(&tmp); - in->is_unicast = tmp; + handle.read<1>(&tmp, false); + in->is_unicast = tmp; - parity = AVCLAN_readbits<12>(&in->controller_addr); - AVCLAN_readbits<1>(&tmp); - if (parity != (tmp &= 1)) { - err.errno = BAD_CONTROLLER_PARITY; - if (print.verbose) { - err.read_val = in->controller_addr; - err.parity = tmp; - } - goto handle_err; - } - - parity = AVCLAN_readbits<12>(&in->peripheral_addr); - AVCLAN_readbits<1>(&tmp); - if (parity != (tmp &= 1)) { - err.errno = BAD_PERIPHERAL_PARITY; - if (print.verbose) { - err.read_val = in->peripheral_addr; - err.parity = tmp; - } - goto handle_err; - } - - shouldACK = !AVCLAN_ismuted() && (in->peripheral_addr == address); - - if (shouldACK) - AVCLAN_sendbit_ACK(); - else - AVCLAN_readbits<1>(&tmp); - - parity = AVCLAN_readbits<4>(&in->control); - AVCLAN_readbits<1>(&tmp); - if (parity != (tmp &= 1)) { - err.errno = BAD_CONTROL_PARITY; - if (print.verbose) { - err.read_val = in->control; - err.parity = tmp; - } - goto handle_err; - } else if (shouldACK) { - AVCLAN_sendbit_ACK(); - } else { - AVCLAN_readbits<1>(&tmp); - } - - parity = AVCLAN_readbyte(&in->length); - AVCLAN_readbits<1>(&tmp); - if (parity != (tmp &= 1)) { - err.errno = BAD_LENGTH_PARITY; - if (print.verbose) { - err.read_val = in->length; - err.parity = tmp; - } - goto handle_err; - } else if (shouldACK) { - AVCLAN_sendbit_ACK(); - } else { - AVCLAN_readbits<1>(&tmp); - } - - if (in->length == 0 || in->length > MAXMSGLEN) { - err.errno = BAD_LENGTH_RANGE; - err.val = in->length; - goto handle_err; - } - - for (uint8_t i = 0; i < in->length; i++) { - parity = AVCLAN_readbits<8>(&in->data[i]); - AVCLAN_readbits<1>(&tmp); - if (parity != (tmp &= 1)) { - err.errno = BAD_DATA_PARITY; + if (auto rerr = handle.read<12>(&in->controller_addr, false); + rerr == BAD_PARITY) { + err.errno = BAD_CONTROLLER_PARITY; if (print.verbose) { - err.read_val = in->data[i]; - err.parity = tmp; + err.read_val = in->controller_addr; } goto handle_err; - } else if (shouldACK) { - AVCLAN_sendbit_ACK(); - } else { - AVCLAN_readbits<1>(&tmp); } - } + + if (auto rerr = handle.read<12>(&in->peripheral_addr, + [&]() { + return !bus.is_muted() && + (in->peripheral_addr == address); + }); + rerr == BAD_PARITY) { + err.errno = BAD_PERIPHERAL_PARITY; + if (print.verbose) { + err.read_val = in->peripheral_addr; + } + goto handle_err; + } + + shouldACK = !bus.is_muted() && (in->peripheral_addr == address); + + if (auto rerr = handle.read<4>(&in->control, shouldACK); + rerr == BAD_PARITY) { + err.errno = BAD_CONTROL_PARITY; + if (print.verbose) { + err.read_val = in->control; + } + goto handle_err; + } + + if (auto rerr = handle.read<8>(&in->length, shouldACK); + rerr == BAD_PARITY) { + err.errno = BAD_LENGTH_PARITY; + if (print.verbose) { + err.read_val = in->length; + } + goto handle_err; + } + + if (in->length == 0 || in->length > MAXMSGLEN) { + err.errno = BAD_LENGTH_RANGE; + err.val = in->length; + goto handle_err; + } + + for (uint8_t i = 0; i < in->length; i++) { + if (auto rerr = handle.read<8>(&in->data[i], shouldACK); + rerr == BAD_PARITY) { + err.errno = BAD_DATA_PARITY; + if (print.verbose) { + err.read_val = in->data[i]; + } + goto handle_err; + } + } + } // destroy handle if (false) { handle_err:; - AVCLAN_startEvent(); RS232_Print("ERR(read): "); switch (err.errno) { case BAD_STARTBIT: RS232_Print("bad start bit (other)"); break; @@ -153,8 +132,6 @@ Peripheral::Error::Read Peripheral::read(AVCLAN_frame_t *in, log_t print) { } } RS232_Print("\n"); - } else { - AVCLAN_startEvent(); } // Only print if some data has been correctly received @@ -177,70 +154,60 @@ Peripheral::Error::Send Peripheral::send(const AVCLAN_frame_t *out, } err = {}; using enum Error::Send; - - avclan_bit_t parity; + auto NAK = Bus::Error::Send::NAK; if (AVCLAN_ismuted()) { err.errno = MUTED; goto handle_err; } - AVCLAN_stopEvent(); + { // bound handle lifetime + auto handle = bus.get(); - if (!AVCLAN_sendstartbit()) { - // Some other device is already driving the bus - err.errno = BUSY; - goto handle_err; - } - - AVCLAN_sendbits<1>(static_cast(out->is_unicast)); - - parity = AVCLAN_sendbits<12>(out->controller_addr); - AVCLAN_sendbit(parity); - - parity = AVCLAN_sendbits<12>(address); - AVCLAN_sendbit(parity); - - if (out->is_unicast && !AVCLAN_readbit_ACK()) { - err.errno = NAK_ADDRESS; - goto handle_err; - } - - parity = AVCLAN_sendbits<4>(out->control); - AVCLAN_sendbit(parity); - - if (out->is_unicast && !AVCLAN_readbit_ACK()) { - err.errno = NAK_CONTROL; - goto handle_err; - } - - parity = AVCLAN_sendbits<8>(out->length); // data length - AVCLAN_sendbit(parity); - - if (out->is_unicast && !AVCLAN_readbit_ACK()) { - err.errno = NAK_MESSAGE_LENGTH; - goto handle_err; - } - - for (uint8_t i = 0; i < out->length; i++) { - parity = AVCLAN_sendbits<8>(out->data[i]); - AVCLAN_sendbit(parity); - // Based on the µPD6708 datasheet, ACK bit for broadcast doesn't seem - // necessary (i.e. This deviates from the previous broadcast specific - // function that sent an extra `1` bit after each byte/parity) - if (out->is_unicast && !AVCLAN_readbit_ACK()) { - err.errno = NAK_DATA; - err.val = i; + if (!handle.sendstartbit()) { + // Some other device is already driving the bus + err.errno = BUSY; goto handle_err; } - // else - // AVCLAN_sendbit_1(); - } + + handle.send<1>(static_cast(out->is_unicast), false); + + handle.send<12>(out->controller_addr, false); + + if (auto serr = handle.send<12>(out->controller_addr, out->is_unicast); + serr == NAK) { + err.errno = NAK_ADDRESS; + goto handle_err; + } + + if (auto serr = handle.send<4>(out->control, out->is_unicast); + serr == NAK) { + err.errno = NAK_CONTROL; + goto handle_err; + } + + if (auto serr = handle.send<8>(out->length, out->is_unicast); serr == NAK) { + err.errno = NAK_MESSAGE_LENGTH; + goto handle_err; + } + + for (uint8_t i = 0; i < out->length; i++) { + // Based on the µPD6708 datasheet, ACK bit for broadcast doesn't seem + // necessary (i.e. This deviates from the previous broadcast specific + // function that sent an extra `1` bit after each byte/parity) + // Explanation for why audio-group broadcast state report isn't working? + if (auto serr = handle.send<8>(out->data[i], out->is_unicast); + serr == NAK) { + err.errno = NAK_DATA; + err.val = i; + goto handle_err; + } + } + } // destroy handle // back to read mode if (false) { handle_err:; - AVCLAN_startEvent(); RS232_Print("Error"); switch (err.errno) { case MUTED: RS232_Print(": Device muted"); break; @@ -265,8 +232,6 @@ Peripheral::Error::Send Peripheral::send(const AVCLAN_frame_t *out, break; } RS232_Print("\n"); - } else { - AVCLAN_startEvent(); } if (print.print) diff --git a/src/avclan/peripheral.hpp b/src/avclan/peripheral.hpp index 10da2fb..7321604 100644 --- a/src/avclan/peripheral.hpp +++ b/src/avclan/peripheral.hpp @@ -5,6 +5,8 @@ #pragma once #include "avclan_defs.h" +#include "bus.hpp" + #include namespace avclan { @@ -14,33 +16,38 @@ public: // Error enums are ordered such that a lower numeric value corresponds to // more progress/success before an error occured, with 0 being no errors enum class Read : uint8_t { - BAD_DATA_PARITY = rBAD_DATA_PARITY, // = 0x01 - BAD_LENGTH_RANGE = rBAD_LENGTH_RANGE, - BAD_LENGTH_PARITY = rBAD_LENGTH_PARITY, - BAD_PERIPHERAL_PARITY = rBAD_PERIPHERAL_PARITY, - BAD_CONTROLLER_PARITY = rBAD_CONTROLLER_PARITY, - BAD_CONTROL_PARITY = rBAD_CONTROL_PARITY, - STARTBIT_TOO_SHORT = rSTARTBIT_TOO_SHORT, - STARTBIT_TOO_LONG = rSTARTBIT_TOO_LONG, - BAD_STARTBIT = rLATCHED_COMPARATOR, + BAD_DATA_PARITY = 0x01, + BAD_LENGTH_RANGE, + BAD_LENGTH_PARITY, + BAD_PERIPHERAL_PARITY, + BAD_CONTROLLER_PARITY, + BAD_CONTROL_PARITY, + STARTBIT_TOO_SHORT = + static_cast(Bus::Error::Read::STARTBIT_TOO_SHORT), + STARTBIT_TOO_LONG = + static_cast(Bus::Error::Read::STARTBIT_TOO_LONG), + BAD_STARTBIT = static_cast(Bus::Error::Read::BAD_STARTBIT), }; enum class Send : uint8_t { - NAK_DATA = sNAK_DATA, // = 0x01 - NAK_MESSAGE_LENGTH = sNAK_MESSAGE_LENGTH, - NAK_CONTROL = sNAK_CONTROL, - NAK_ADDRESS = sNAK_ADDRESS, - BUSY = sBUSY, - MUTED = sMUTED, + NAK_DATA = 0x01, + NAK_MESSAGE_LENGTH, + NAK_CONTROL, + NAK_ADDRESS, + BUSY, + MUTED, }; }; - Peripheral(uint16_t address) : address{address} {} + Peripheral(Bus bus, uint16_t address) : bus{bus}, address{address} { + bus.init(); + } Error::Read read(AVCLAN_frame_t *in, log_t print); Error::Send send(const AVCLAN_frame_t *out, log_t print); private: + Bus bus; const uint16_t address; }; } // namespace avclan diff --git a/src/avclan/target/avr-attiny3216/phy_avr.c b/src/avclan/target/avr-attiny3216/phy_avr.c index b8fe394..1f40781 100644 --- a/src/avclan/target/avr-attiny3216/phy_avr.c +++ b/src/avclan/target/avr-attiny3216/phy_avr.c @@ -3,48 +3,6 @@ // Copyright (C) 2015 Allen Hill // SPDX-License-Identifier: GPL-3.0-or-later -/* - AVC LAN Theory - - The AVC LAN bus is an implementation of the IEBus (mode 1) which is a - differential signal; IEBus is electrically (but not logically) compatible with - CAN bus. - - - Logical `1`: Potential difference between bus lines (BUS+ pin and BUS– pin) - is 20 mV or lower (floating). - - Logical `0`: Potential difference between bus lines (BUS+ pin and BUS– pin) - is 120 mV or higher (driving). - - A nominal bit length is 39 us, composed of 3 periods: preparation, - synchronization, data. - - Figure 1. AVCLAN Bus bit format - - │ Prep │<─ Sync ─>│<─ Data ─>│ ... - Driving (logical `0`) ╭──────────╮──────────╮ - │ │ │ - Floating (logical `1`) ─────────╯ ╰──────────╰───────── - │ 6 μs │── 19 μs ─│─ 13 μs ──│ - - The logical value during the data period signifies the bit value, e.g. a bit - `0` continues the logical `0` (high potential difference between bus lines) of - the sync period thru the data period, and a bit `1` has a logical `1` - (low/floating potential between bus lines) during the data period. Using the - TCB pulse-width and frequency measure mode, the total bit length differs for - bit `1` and `0`; detailed bit timing can be found in "timing.h". The bus - idles at low potential (floating). - - A start bit is nominally 169 us high followed by 20 us low. - - A bit `0` is dominant on the bus, which is a design choice that affects - bit/interpretation: - - Low addresses have priority upon transmission conflicts - - The broadcast bit is `1` (floating, no effort) for normal communication - - For acknowledge bits, the receiver extends the logical '0' of the sync - period to the length of a normal bit `0`. Hence, a NAK (bit `1`) is - literally the absence of an ACK. -*/ - #include #include #include diff --git a/src/sniffer.cc b/src/sniffer.cc index 66c848e..47bd927 100644 --- a/src/sniffer.cc +++ b/src/sniffer.cc @@ -71,7 +71,8 @@ int main() { const AVCLAN_frame_t *lastStatus = nullptr; - avclan::Peripheral cd_changer(0x360); + avclan::Bus phy; + avclan::Peripheral cd_changer(phy, 0x360); using Error = avclan::Peripheral::Error; Setup();