Add C++ Bus class for the physical layer interface

This commit is contained in:
Allen Hill
2026-06-25 20:40:10 -07:00
parent 67ba8e774c
commit d98cc8acad
7 changed files with 344 additions and 202 deletions
+1
View File
@@ -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
+32
View File
@@ -0,0 +1,32 @@
// copyright (C) 2006 Marcin Slonicki <marcin@softservice.com.pl>
// copyright (C) 2007 Louis Frigon
// Copyright (C) 2015 Allen Hill <allenofthehills@gmail.com>
// 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
+178
View File
@@ -0,0 +1,178 @@
// copyright (C) 2006 Marcin Slonicki <marcin@softservice.com.pl>
// copyright (C) 2007 Louis Frigon
// Copyright (C) 2015 Allen Hill <allenofthehills@gmail.com>
// 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 <concepts>
#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 <auto N, std::unsigned_integral T>
requires(sizeof(T) < 3 && N < 16)
Error::Send send(T bits, bool ack) {
const auto parity = sendbits<N>(bits);
sendbits<1>(static_cast<uint8_t>(parity));
if (ack && !read_ACK())
return Send::NAK;
return Send{0};
};
template <auto N, std::unsigned_integral T, class F>
requires(sizeof(T) < 3 && N < 16)
Error::Read read(T *bits, F &&ack) {
const auto calc_parity = readbits<N>(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 <auto N, std::unsigned_integral T>
requires(sizeof(T) < 3 && N < 16)
Error::Read read(T *bits, bool ack) {
return read<N>(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 <auto N, class T> avclan_bit_t sendbits(T bits);
template <auto N, class T> avclan_bit_t readbits(T *bits);
// Temporary specializations bridging to legacy C API
// Replace with proper (single?) template when phy has been ported
template <auto N>
requires(N > 1 && N < 8)
avclan_bit_t sendbits(uint8_t bits) {
return AVCLAN_sendbitsi(&bits, N);
};
template <auto N>
requires(N <= 16)
avclan_bit_t sendbits(uint16_t bits) {
return AVCLAN_sendbitsl(&bits, N);
};
template <auto N>
requires(N < 8)
avclan_bit_t readbits(uint8_t *bits) {
return static_cast<avclan_bit_t>(AVCLAN_readbitsi(bits, N));
};
template <auto N>
requires(N <= 16)
avclan_bit_t readbits(uint16_t *bits) {
return static_cast<avclan_bit_t>(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<avclan_bit_t>(byte & 1u)};
AVCLAN_sendbit(b);
return b;
};
template <> inline avclan_bit_t Bus::Handle::readbits<8>(uint8_t *byte) {
return static_cast<avclan_bit_t>(AVCLAN_readbyte(byte));
};
} // namespace avclan
+108 -143
View File
@@ -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 <cstdint>
@@ -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<uint8_t>(err.errno))
goto handle_err;
err.errno = Error::Read{static_cast<uint8_t>(handle.readstartbit())};
if (static_cast<uint8_t>(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<uint8_t>(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<uint8_t>(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)
+23 -16
View File
@@ -5,6 +5,8 @@
#pragma once
#include "avclan_defs.h"
#include "bus.hpp"
#include <cstdint>
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<uint8_t>(Bus::Error::Read::STARTBIT_TOO_SHORT),
STARTBIT_TOO_LONG =
static_cast<uint8_t>(Bus::Error::Read::STARTBIT_TOO_LONG),
BAD_STARTBIT = static_cast<uint8_t>(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
@@ -3,48 +3,6 @@
// Copyright (C) 2015 Allen Hill <allenofthehills@gmail.com>
// 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 <avr/interrupt.h>
#include <avr/io.h>
#include <avr/sfr_defs.h>
+2 -1
View File
@@ -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();