Finalize C <=> C++ interface

This commit is contained in:
Allen Hill
2026-07-06 19:39:49 -07:00
parent 7ceed80128
commit 9fd8c5636e
20 changed files with 396 additions and 346 deletions
+2 -1
View File
@@ -13,7 +13,8 @@ add_compile_options(
$<$<COMPILE_LANGUAGE:CXX>:-fno-threadsafe-statics> $<$<COMPILE_LANGUAGE:CXX>:-fno-threadsafe-statics>
$<$<COMPILE_LANGUAGE:CXX>:-fno-exceptions> $<$<COMPILE_LANGUAGE:CXX>:-fno-exceptions>
$<$<COMPILE_LANGUAGE:CXX>:-fno-rtti> $<$<COMPILE_LANGUAGE:CXX>:-fno-rtti>
$<$<CONFIG:Debug>:-fanalyzer>) $<$<CONFIG:Debug>:-fanalyzer>
$<$<CONFIG:Debug>:-Wno-analyzer-use-of-uninitialized-value>)
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "13") if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "13")
+17
View File
@@ -104,6 +104,23 @@ enum AVCLAN_ENUM_CLASS Action : uint8_t {
Report_TOC = 0xf9, Report_TOC = 0xf9,
}; };
// Media functions a source device can perform (ultimately bounded by the Action
// set above).
enum AVCLAN_ENUM_CLASS MediaAction : uint8_t {
Play = 0x01,
Pause = 0x02,
Play_Pause = Play | Pause,
Skip_Forward,
Skip_Backward,
Track_Next,
Track_Prev,
Repeat,
Repeat_Single,
Shuffle,
Volume_Up,
Volume_Down,
};
#ifdef __cplusplus #ifdef __cplusplus
namespace detail { namespace detail {
struct Error { struct Error {
+134 -22
View File
@@ -31,24 +31,149 @@
#include "bus.hpp" #include "bus.hpp"
#include "avclan.h" #include "avclan.h"
#include "avclan_phy.h" // bridge until phy has been ported
#include "com232.h" #include "com232.h"
#include "frame.hpp" #include "frame.hpp"
#include "hal/phy.h" // bridge until phy has been ported
namespace { namespace {
constexpr int ADDR_WIDTH = 12; constexpr int ADDR_WIDTH = 12;
constexpr int CONTROL_WIDTH = 4; constexpr int CONTROL_WIDTH = 4;
constexpr int BYTE_WIDTH = 8; constexpr int BYTE_WIDTH = 8;
struct trailer_bits_t {};
struct no_parity_t : trailer_bits_t {}; // raw bits (the broadcast bit)
struct with_parity_t : trailer_bits_t {}; // bits + parity (controller address)
struct with_ack_t : trailer_bits_t {
}; // bits + parity + ACK slot (all other fields)
inline constexpr no_parity_t no_parity{};
inline constexpr with_parity_t with_parity{};
inline constexpr with_ack_t with_ack{};
} // namespace } // namespace
namespace avclan { namespace avclan {
Bus::Handle::Handle() { AVCLAN_stopEvent(); } class Bus::Handle {
Bus::Handle::~Handle() { AVCLAN_startEvent(); } Handle() { phy_guard_enter(); };
friend Bus;
void Bus::init() { AVCLAN_busInit(); }; public:
void Bus::mute(bool mute) { AVCLAN_muteDevice(mute); }; ~Handle() { phy_guard_leave(); };
bool Bus::is_muted() const { return AVCLAN_ismuted(); }; Handle(const Handle &) = delete;
Handle(Handle &&) = delete;
using Error = detail::Error;
bool sendstartbit() { return phy_send_startbit(); };
auto readstartbit() -> Read { return phy_read_startbit(); };
template <auto N, std::unsigned_integral T,
std::derived_from<trailer_bits_t> Trailer>
requires(sizeof(T) < 3 && N < 16 && !std::same_as<Trailer, with_ack_t>)
Error::Send send(T bits, Trailer /*tag*/) {
const auto parity = sendbits<N>(bits);
if constexpr (std::is_same_v<Trailer, with_parity_t>)
sendbits<1>(static_cast<uint8_t>(parity));
return Send{0};
};
template <auto N, std::unsigned_integral T>
requires(sizeof(T) < 3 && N < 16)
Error::Send send(T bits, with_ack_t /*tag*/, bool expect_ack) {
send<N>(bits, with_parity);
if (expect_ack && !read_ACK())
return Send::NAK;
return Send{0};
};
template <auto N, std::unsigned_integral T,
std::derived_from<trailer_bits_t> Trailer>
requires(sizeof(T) < 3 && N < 16 && !std::same_as<Trailer, with_ack_t>)
Error::Read read(T *bits, Trailer /*tag*/) {
const auto calc_parity = readbits<N>(bits);
if constexpr (std::is_same_v<Trailer, with_parity_t>) {
uint8_t read_parity;
readbits<1>(&read_parity);
if (static_cast<uint8_t>(calc_parity) != read_parity)
return Read::BAD_PARITY;
}
return Read{0};
};
template <auto N, std::unsigned_integral T, class F>
requires(sizeof(T) < 3 && N < 16)
Error::Read read(T *bits, with_ack_t /*tag*/, F &&ack) {
if (read<N>(bits, with_parity) == Read::BAD_PARITY)
return Read::BAD_PARITY;
if (ack()) {
send_ACK();
} else {
uint8_t slot;
readbits<1>(&slot);
}
return Read{0};
};
template <auto N, std::unsigned_integral T>
requires(sizeof(T) < 3 && N < 16)
Error::Read read(T *bits, with_ack_t /*tag*/, bool ack) {
return read<N>(bits, with_ack, [=]() { return ack; });
}
private:
using Read = Error::Read;
using Send = Error::Send;
using Bit = detail::Bit;
static void send_ACK() { phy_send_ack(); };
static uint8_t read_ACK() { return phy_read_ack(); };
template <auto N, class T> Bit sendbits(T bits);
template <auto N, class T> Bit 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)
Bit sendbits(uint8_t bits) {
return phy_send_bits_u8(&bits, N);
};
template <auto N>
requires(N <= 16)
Bit sendbits(uint16_t bits) {
return phy_send_bits_u16(&bits, N);
};
template <auto N>
requires(N < 8)
Bit readbits(uint8_t *bits) {
return static_cast<Bit>(phy_read_bits_u8(bits, N));
};
template <auto N>
requires(N <= 16)
Bit readbits(uint16_t *bits) {
return static_cast<Bit>(phy_read_bits_u16(bits, N));
};
};
template <> inline Bit Bus::Handle::sendbits<8>(uint8_t bits) {
return phy_send_byte(&bits);
};
template <> inline Bit Bus::Handle::sendbits<1>(uint8_t bits) {
const Bit bit{static_cast<Bit>(bits & 1U)};
phy_send_bit(bit);
return bit;
};
template <> inline Bit Bus::Handle::readbits<8>(uint8_t *bits) {
return static_cast<Bit>(phy_read_byte(bits));
};
void Bus::init() { phy_init(); };
bool Bus::is_active() const { return phy_active(); };
void Bus::mute(bool mute) { phy_mute(mute); };
bool Bus::is_muted() const { return phy_is_muted(); };
auto Bus::read(uint16_t address, Frame *in, Frame::Print print) -> Error::Read { auto Bus::read(uint16_t address, Frame *in, Frame::Print print) -> Error::Read {
struct errtype { struct errtype {
@@ -278,21 +403,8 @@ auto Bus::send(const Frame *out, Frame::Print print) -> Error::Send {
Bus::Handle Bus::get() { return {}; }; Bus::Handle Bus::get() { return {}; };
bool Bus::Handle::sendstartbit() { return AVCLAN_sendstartbit(); }; #ifndef NDEBUG
auto Bus::Handle::readstartbit() -> Read { return AVCLAN_readstartbit(); }; void Bus::measure() { phy_measure(); }
void Bus::Handle::send_ACK() { AVCLAN_sendbit_ACK(); }; #endif
uint8_t Bus::Handle::read_ACK() { return AVCLAN_readbit_ACK(); };
template <> inline Bit Bus::Handle::sendbits<8>(uint8_t bits) {
return AVCLAN_sendbyte(&bits);
};
template <> inline Bit Bus::Handle::sendbits<1>(uint8_t bits) {
const Bit bit{static_cast<Bit>(bits & 1U)};
AVCLAN_sendbit(bit);
return bit;
};
template <> inline Bit Bus::Handle::readbits<8>(uint8_t *bits) {
return static_cast<Bit>(AVCLAN_readbyte(bits));
};
} // namespace avclan } // namespace avclan
+8 -117
View File
@@ -52,139 +52,30 @@
#include <type_traits> #include <type_traits>
#include "avclan.h" #include "avclan.h"
#include "avclan_phy.h" // bridge until phy has been ported
#include "frame.hpp" #include "frame.hpp"
namespace avclan { namespace avclan {
struct trailer_bits_t {};
struct no_parity_t : trailer_bits_t {}; // raw bits (the broadcast bit)
struct with_parity_t : trailer_bits_t {}; // bits + parity (controller address)
struct with_ack_t : trailer_bits_t {
}; // bits + parity + ACK slot (all other fields)
inline constexpr no_parity_t no_parity{};
inline constexpr with_parity_t with_parity{};
inline constexpr with_ack_t with_ack{};
class Bus { class Bus {
public: public:
class Handle;
using Error = detail::Error; using Error = detail::Error;
void init(); void init();
bool is_active() const;
void mute(bool mute); void mute(bool mute);
bool is_muted() const; bool is_muted() const;
#ifndef NDEBUG
void measure();
#endif
Error::Read read(uint16_t address, Frame *in, Frame::Print print); Error::Read read(uint16_t address, Frame *in, Frame::Print print);
Error::Send send(const Frame *out, Frame::Print print); Error::Send send(const Frame *out, Frame::Print print);
private:
class Handle;
static Handle get(); static Handle get();
}; };
class Bus::Handle {
Handle();
friend Bus;
public:
~Handle();
Handle(const Handle &) = delete;
Handle(Handle &&) = delete;
using Error = detail::Error;
bool sendstartbit();
Error::Read readstartbit();
template <auto N, std::unsigned_integral T,
std::derived_from<trailer_bits_t> Trailer>
requires(sizeof(T) < 3 && N < 16 && !std::same_as<Trailer, with_ack_t>)
Error::Send send(T bits, Trailer /*tag*/) {
const auto parity = sendbits<N>(bits);
if constexpr (std::is_same_v<Trailer, with_parity_t>)
sendbits<1>(static_cast<uint8_t>(parity));
return Send{0};
};
template <auto N, std::unsigned_integral T>
requires(sizeof(T) < 3 && N < 16)
Error::Send send(T bits, with_ack_t /*tag*/, bool expect_ack) {
send<N>(bits, with_parity);
if (expect_ack && !read_ACK())
return Send::NAK;
return Send{0};
};
template <auto N, std::unsigned_integral T,
std::derived_from<trailer_bits_t> Trailer>
requires(sizeof(T) < 3 && N < 16 && !std::same_as<Trailer, with_ack_t>)
Error::Read read(T *bits, Trailer /*tag*/) {
const auto calc_parity = readbits<N>(bits);
if constexpr (std::is_same_v<Trailer, with_parity_t>) {
uint8_t read_parity;
readbits<1>(&read_parity);
if (static_cast<uint8_t>(calc_parity) != read_parity)
return Read::BAD_PARITY;
}
return Read{0};
};
template <auto N, std::unsigned_integral T, class F>
requires(sizeof(T) < 3 && N < 16)
Error::Read read(T *bits, with_ack_t /*tag*/, F &&ack) {
if (read<N>(bits, with_parity) == Read::BAD_PARITY)
return Read::BAD_PARITY;
if (ack()) {
send_ACK();
} else {
uint8_t slot;
readbits<1>(&slot);
}
return Read{0};
};
template <auto N, std::unsigned_integral T>
requires(sizeof(T) < 3 && N < 16)
Error::Read read(T *bits, with_ack_t /*tag*/, bool ack) {
return read<N>(bits, with_ack, [=]() { return ack; });
}
private:
using Read = Error::Read;
using Send = Error::Send;
using Bit = detail::Bit;
static void send_ACK();
static uint8_t read_ACK();
template <auto N, class T> Bit sendbits(T bits);
template <auto N, class T> Bit 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)
Bit sendbits(uint8_t bits) {
return AVCLAN_sendbitsi(&bits, N);
};
template <auto N>
requires(N <= 16)
Bit sendbits(uint16_t bits) {
return AVCLAN_sendbitsl(&bits, N);
};
template <auto N>
requires(N < 8)
Bit readbits(uint8_t *bits) {
return static_cast<Bit>(AVCLAN_readbitsi(bits, N));
};
template <auto N>
requires(N <= 16)
Bit readbits(uint16_t *bits) {
return static_cast<Bit>(AVCLAN_readbitsl(bits, N));
};
};
} // namespace avclan } // namespace avclan
+26 -21
View File
@@ -10,14 +10,13 @@
#include "cdchanger.hpp" #include "cdchanger.hpp"
#include "device.hpp" #include "device.hpp"
#include "frame.hpp" #include "frame.hpp"
#include "mediacontrol.h" #include "hal/cd_timer.h"
#include "statustimer.h" #include "hal/media.h"
namespace { namespace {
using namespace avclan; using namespace avclan;
constexpr uint8_t cdloading_resp[] = { constexpr uint8_t cdloading_resp[] = {to_underlying(Device::CD_CHANGER),
to_underlying(Device::CD_CHANGER),
to_underlying(Device::STATUS), to_underlying(Device::STATUS),
to_underlying(Action::Loading_Status), to_underlying(Action::Loading_Status),
0x00, 0x00,
@@ -53,8 +52,8 @@ extern "C" bool isPlaying_callback(void *self) {
namespace avclan { namespace avclan {
void CDChanger::init() { void CDChanger::init() {
mediacontrol_init(); media_init();
statustimer_init(this, &incrementTime_callback, &isPlaying_callback); cdtimer_init(this, &incrementTime_callback, &isPlaying_callback);
} }
void CDChanger::handle(const Frame *in, Frame *out) { void CDChanger::handle(const Frame *in, Frame *out) {
@@ -119,8 +118,7 @@ void CDChanger::handle(const Frame *in, Frame *out) {
case Initial_Report_Req: { case Initial_Report_Req: {
out->is_unicast = true; out->is_unicast = true;
// No knowledge/understanding of field meaning/interpretation // No knowledge/understanding of field meaning/interpretation
const uint8_t cdinitreport_resp[] = { const uint8_t cdinitreport_resp[] = {0x00,
0x00,
to_underlying(Device::CD_CHANGER), to_underlying(Device::CD_CHANGER),
to_underlying(from), to_underlying(from),
to_underlying(Initial_Report_Resp), to_underlying(Initial_Report_Resp),
@@ -163,7 +161,7 @@ void CDChanger::handle(const Frame *in, Frame *out) {
secs = 0x7f; secs = 0x7f;
flags2 = 0xc0; flags2 = 0xc0;
generateStatus(out, true, Device::CMD_SW); generateStatus(out, true, Device::CMD_SW);
AVCLAN_mediaFunction(MEDIA_SKIP_FORWARD); media_action(MediaAction::Track_Next);
out->reaction = r_TrackChange; out->reaction = r_TrackChange;
break; break;
case Track_Seek_Down: case Track_Seek_Down:
@@ -174,12 +172,13 @@ void CDChanger::handle(const Frame *in, Frame *out) {
--track; --track;
else else
track = TWODIGIT_MAX; track = TWODIGIT_MAX;
media_action(MediaAction::Track_Prev);
} }
mins = 0xff; mins = 0xff;
secs = 0x7f; secs = 0x7f;
flags2 = 0xc0; flags2 = 0xc0;
generateStatus(out, true, Device::CMD_SW); generateStatus(out, true, Device::CMD_SW);
AVCLAN_mediaFunction(MEDIA_SKIP_BACKWARD);
out->reaction = r_TrackChange; out->reaction = r_TrackChange;
break; break;
case Track_Fast_Forward: { case Track_Fast_Forward: {
@@ -190,8 +189,8 @@ void CDChanger::handle(const Frame *in, Frame *out) {
++mins; ++mins;
} }
generateStatus(out, true, Device::CMD_SW); generateStatus(out, true, Device::CMD_SW);
AVCLAN_mediaFunction(MEDIA_SKIP_FORWARD); media_action(MediaAction::Skip_Forward);
statustimer_reset(); // Skipped to a whole/round sec; ensure next tick cdtimer_reset(); // Skipped to a whole/round sec; ensure next tick
// is ~1 sec from now // is ~1 sec from now
out->reaction = r_SendOnly; out->reaction = r_SendOnly;
break; break;
@@ -210,8 +209,8 @@ void CDChanger::handle(const Frame *in, Frame *out) {
} else } else
secs -= TIME_SKIP; secs -= TIME_SKIP;
generateStatus(out, true, Device::CMD_SW); generateStatus(out, true, Device::CMD_SW);
AVCLAN_mediaFunction(MEDIA_SKIP_BACKWARD); media_action(MediaAction::Skip_Backward);
statustimer_reset(); // Skipped to a whole/round sec; ensure next tick cdtimer_reset(); // Skipped to a whole/round sec; ensure next tick
// is ~1 sec from now // is ~1 sec from now
out->reaction = r_SendOnly; out->reaction = r_SendOnly;
break; break;
@@ -296,7 +295,7 @@ void CDChanger::react(Frame *out, detail::Error::Send err) {
break; break;
case r_TrackChange: case r_TrackChange:
setTime(0, 0); setTime(0, 0);
statustimer_reset(); // Skipped to a whole/round sec; ensure next tick is cdtimer_reset(); // Skipped to a whole/round sec; ensure next tick is
// ~1 sec from now // ~1 sec from now
[[fallthrough]]; [[fallthrough]];
case r_NormalizeState: case r_NormalizeState:
@@ -336,8 +335,8 @@ void CDChanger::enable(Frame *out) {
} }
} }
bool CDChanger::pending() { return statustimer_tickPending(); } bool CDChanger::pending() { return cdtimer_pending(); }
void CDChanger::resolvepending() { statustimer_clearTick(); } void CDChanger::resolvepending() { cdtimer_clear(); }
void CDChanger::emit(Frame *out, uint16_t peripheral) { void CDChanger::emit(Frame *out, uint16_t peripheral) {
out->peripheral_addr = peripheral; out->peripheral_addr = peripheral;
@@ -352,16 +351,16 @@ bool CDChanger::isPlaying() const { return playing; }
void CDChanger::startPlaying() { void CDChanger::startPlaying() {
static bool havePlayed = false; static bool havePlayed = false;
if (havePlayed) if (havePlayed)
AVCLAN_mediaFunction(MEDIA_PLAY_PAUSE); media_action(MediaAction::Play);
havePlayed |= true; havePlayed |= true;
playing = true; playing = true;
statustimer_reset(); cdtimer_reset();
} }
void CDChanger::stopPlaying() { void CDChanger::stopPlaying() {
statustimer_disable(); cdtimer_disable();
playing = false; playing = false;
AVCLAN_mediaFunction(MEDIA_PLAY_PAUSE); media_action(MediaAction::Pause);
} }
// Serialize cd_status into the wire format. // Serialize cd_status into the wire format.
@@ -425,4 +424,10 @@ void CDChanger::normalizeState() {
flags2 = 0x80; flags2 = 0x80;
} }
#ifndef NDEBUG
void CDChanger::media_action(MediaAction action) { ::media_action(action); }
bool CDChanger::media_busy() const { return ::media_busy(); };
void CDChanger::mic_toggle() { media_mic_toggle(); };
#endif
} // namespace avclan } // namespace avclan
+5
View File
@@ -69,6 +69,11 @@ public:
void emit(Frame *out, uint16_t peripheral); void emit(Frame *out, uint16_t peripheral);
void incrementTime(); void incrementTime();
bool isPlaying() const; bool isPlaying() const;
#ifndef NDEBUG
void media_action(MediaAction action);
bool media_busy() const;
void mic_toggle();
#endif
private: private:
void startPlaying(); void startPlaying();
+1 -1
View File
@@ -12,7 +12,7 @@ extern "C" {
void board_init(void); void board_init(void);
// Globally enable interrupts. Call after all peripherals are initialized. // Globally enable interrupts. Call after all peripherals are initialized.
void board_interruptsEnable(void); void board_enable_interrupts(void);
#ifdef __cplusplus #ifdef __cplusplus
} }
+32
View File
@@ -0,0 +1,32 @@
// Copyright (C) 2026 Allen Hill <allenofthehills@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
// CDChanger periodic-timer HAL: the ~1 Hz tick that drives the CD changer's
// status updates. The timer hardware is target-specific; the CD changer owns
// what a tick means (see cdchanger.cc). The device polls cdtimer_pending() and
// clears the tick with cdtimer_clear().
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
// One-time hardware bring-up. Leaves the tick disabled.
void cdtimer_init(void *ptr, void(clbk)(void *), bool(isplay)(void *));
// Reset the count so the next tick is ~1 s out, and enable the tick.
void cdtimer_reset(void);
// Restore / disable the ~1 Hz tick.
void cdtimer_restore(void);
void cdtimer_disable(void);
extern volatile bool cdtimer_pending_flag;
static inline bool cdtimer_pending() { return cdtimer_pending_flag; }
static inline void cdtimer_clear() { cdtimer_pending_flag = false; }
#ifdef __cplusplus
}
#endif
+32
View File
@@ -0,0 +1,32 @@
// Copyright (C) 2026 Allen Hill <allenofthehills@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
// Media-control HAL: emulate head-unit button presses to the audio source. This
// is device functionality (see CDChanger), so this contract is consumed only by
// the device implementation, not the app.
#pragma once
#include "avclan.h"
#ifdef __cplusplus
using MediaAction = avclan::MediaAction;
extern "C" {
#else
typedef enum MediaAction MediaAction;
#endif
// One-time hardware bring-up for the media driver.
void media_init(void);
// Emulate a media function (button press) on the source device.
void media_action(MediaAction fn);
#ifndef NDEBUG
bool media_mic_toggle(void);
bool media_busy(void);
#endif
#ifdef __cplusplus
}
#endif
@@ -19,48 +19,49 @@ typedef enum Bit Bit;
#endif #endif
// One-time bring-up of the bus hardware. Leaves the bus idle and TX unmuted. // One-time bring-up of the bus hardware. Leaves the bus idle and TX unmuted.
void AVCLAN_busInit(void); void phy_init(void);
// Mute/unmute device TX. "Muted" means we still listen, we just don't ACK or // Mute/unmute device TX. "Muted" means we still listen, we just don't ACK or
// transmit. // transmit.
void AVCLAN_muteDevice(bool mute); void phy_mute(bool mute);
bool AVCLAN_ismuted(void); bool phy_is_muted(void);
// True when there is activity on the bus (something is driving it). // True when there is activity on the bus (something is driving it).
bool AVCLAN_busActive(void); bool phy_active(void);
// Bus-transaction guard: quiesce the target's other async sources around a bus // Bus-transaction guard: quiesce the target's other async sources around a bus
// read/send so framing isn't disturbed, then restore them. May be a no-op on a // read/send so framing isn't disturbed, then restore them. May be a no-op on a
// target without such contention. // target without such contention.
void AVCLAN_stopEvent(void); void phy_guard_enter(void);
void AVCLAN_startEvent(void); void phy_guard_leave(void);
// Start-bit handling, factored out of read/sendframe so the framing layer holds // Start-bit handling, factored out of read/sendframe so the framing layer holds
// no bus-timing or hardware-recovery logic. // no bus-timing or hardware-recovery logic.
// - AVCLAN_readstartbit waits for and validates an incoming start bit, doing // - phy_read_startbit waits for and validates an incoming start bit, doing
// any target-specific bus recovery; see avclan::detail::Error::Read. // any target-specific bus recovery; see avclan::detail::Error::Read.
// - AVCLAN_sendstartbit acquires the bus and emits a start bit; returns false // - phy_send_startbit acquires the bus and emits a start bit; returns false
// if the bus was busy. // if the bus was busy.
Read AVCLAN_readstartbit(void); Read phy_read_startbit(void);
bool AVCLAN_sendstartbit(void); bool phy_send_startbit(void);
// Per-symbol I/O. The send* helpers return the even parity of the bits sent; // Per-symbol I/O. The send* helpers return the even parity of the bits sent;
// the read* helpers return the even parity of the bits read. // the read* helpers return the even parity of the bits read. The _u8/_u16
void AVCLAN_sendbit(Bit bit); // suffixes name the source-operand width; `len` is how many bits (<= width).
void AVCLAN_sendbit_ACK(void); void phy_send_bit(Bit bit);
uint8_t AVCLAN_readbit_ACK(void); void phy_send_ack(void);
uint8_t phy_read_ack(void);
Bit AVCLAN_sendbitsi(const uint8_t *bits, int8_t len); Bit phy_send_bits_u8(const uint8_t *bits, int8_t len);
Bit AVCLAN_sendbitsl(const uint16_t *bits, int8_t len); Bit phy_send_bits_u16(const uint16_t *bits, int8_t len);
Bit AVCLAN_sendbyte(const uint8_t *byte); Bit phy_send_byte(const uint8_t *byte);
uint8_t AVCLAN_readbitsi(uint8_t *bits, uint8_t len); uint8_t phy_read_bits_u8(uint8_t *bits, uint8_t len);
uint8_t AVCLAN_readbitsl(uint16_t *bits, int8_t len); uint8_t phy_read_bits_u16(uint16_t *bits, int8_t len);
uint8_t AVCLAN_readbyte(uint8_t *byte); uint8_t phy_read_byte(uint8_t *byte);
#ifndef NDEBUG #ifndef NDEBUG
// Sample and dump bus bit timing over the serial link (REPL `M`). // Sample and dump bus bit timing over the serial link (REPL `M`).
void AVCLan_Measure(void); void phy_measure(void);
#endif #endif
#ifdef __cplusplus #ifdef __cplusplus
-34
View File
@@ -1,34 +0,0 @@
// Copyright (C) 2026 Allen Hill <allenofthehills@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
// Media-control: route/handle head-unit button presses to the audio source.
#pragma once
#include <stdint.h>
// Actions list
typedef enum : uint8_t {
MEDIA_PLAY_PAUSE = 0,
MEDIA_SKIP_FORWARD,
MEDIA_SKIP_BACKWARD,
} AVCLAN_media_fn_t;
#ifdef __cplusplus
extern "C" {
#endif
// One-time hardware bring-up for the media driver.
void mediacontrol_init();
// Emulate a button press on the source device.
void AVCLAN_mediaFunction(AVCLAN_media_fn_t fn);
#ifndef NDEBUG
bool AVCLAN_micToggle();
bool AVCLAN_isMediaFunctioning();
#endif
#ifdef __cplusplus
}
#endif
+12 -3
View File
@@ -25,9 +25,18 @@ public:
uint16_t address() const { return address_; }; uint16_t address() const { return address_; };
uint16_t controller() const { return controller_; }; uint16_t controller() const { return controller_; };
template <DeviceInterface Dev> Dev &device() {
return std::get<Dev>(devices_);
}
bool bus_is_active() const { return bus.is_active(); };
void mute(bool mute) { bus.mute(mute); }; void mute(bool mute) { bus.mute(mute); };
bool is_muted() const { return bus.is_muted(); }; bool is_muted() const { return bus.is_muted(); };
#ifndef NDEBUG
Bus &get_bus() { return bus; }
#endif
Error::Read read(Frame *in, Frame::Print print) { Error::Read read(Frame *in, Frame::Print print) {
return bus.read(address_, in, print); return bus.read(address_, in, print);
}; };
@@ -44,7 +53,7 @@ public:
using enum Action; using enum Action;
out->reaction = 0; out->reaction = 0;
if (AVCLAN_ismuted() || in->length < 3) if (is_muted() || in->length < 3)
return; return;
// 0xFF placeholders are variant bytes filled by writing directly to // 0xFF placeholders are variant bytes filled by writing directly to
@@ -137,6 +146,8 @@ public:
} }
} }
#undef PACK3
void react(Frame *out, Error::Send err) { void react(Frame *out, Error::Send err) {
if (((Devs::id == out->owning_device) || ...)) if (((Devs::id == out->owning_device) || ...))
((Devs::id == out->owning_device ((Devs::id == out->owning_device
@@ -147,8 +158,6 @@ public:
out->reaction = 0; out->reaction = 0;
} }
#undef PACK3
template <class F> void poll_devices(F &&fun) { template <class F> void poll_devices(F &&fun) {
(poller(std::get<Devs>(devices_), fun), ...); (poller(std::get<Devs>(devices_), fun), ...);
} }
-31
View File
@@ -1,31 +0,0 @@
// Copyright (C) 2026 Allen Hill <allenofthehills@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
// ~1 Hz status-update tick interface. The app
// polls statustimer_tickPending() and clears the tick with
// statustimer_clearTick()
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
// One-time hardware bring-up. Leaves the tick disabled.
void statustimer_init(void *ptr, void(clbk)(void *), bool(isplay)(void *));
// Reset the count so the next tick is ~1 s out, and enable the tick.
void statustimer_reset(void);
// Enable / disable the ~1 Hz tick.
void statustimer_restore(void);
void statustimer_disable(void);
extern volatile bool tick_pending;
static inline bool statustimer_tickPending() { return tick_pending; }
static inline void statustimer_clearTick() { tick_pending = false; }
#ifdef __cplusplus
}
#endif
@@ -6,7 +6,7 @@ include(FetchContent)
target_sources(avclan PRIVATE target_sources(avclan PRIVATE
phy_avr.c phy_avr.c
media_avr.c media_avr.c
statustick_avr.c cd_timer_avr.c
board_avr.c) board_avr.c)
target_include_directories(avclan PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(avclan PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
+2 -2
View File
@@ -10,7 +10,7 @@
#include <avr/io.h> #include <avr/io.h>
#include <avr/xmega.h> // _PROTECTED_WRITE #include <avr/xmega.h> // _PROTECTED_WRITE
#include "board.h" #include "hal/board.h"
void board_init(void) { void board_init(void) {
// Main clock prescale (CLK_PRESCALE / CLK_PRESCALE_DIV come from the build). // Main clock prescale (CLK_PRESCALE / CLK_PRESCALE_DIV come from the build).
@@ -46,4 +46,4 @@ void board_init(void) {
PORTC.PIN1CTRL = PORT_ISC_INPUT_DISABLE_gc; // WOD PORTC.PIN1CTRL = PORT_ISC_INPUT_DISABLE_gc; // WOD
} }
void board_interruptsEnable(void) { sei(); } void board_enable_interrupts(void) { sei(); }
@@ -8,7 +8,7 @@
#include <stdint.h> #include <stdint.h>
#include <util/atomic.h> #include <util/atomic.h>
#include "statustimer.h" #include "hal/cd_timer.h"
// Measured wall-clock duration (in ms) of one nominal 32768-tick RTC period, // Measured wall-clock duration (in ms) of one nominal 32768-tick RTC period,
// used to calibrate out the internal OSCULP32K's error. The RTC runs from // used to calibrate out the internal OSCULP32K's error. The RTC runs from
@@ -29,7 +29,7 @@ static void* changer = nullptr;
static void (*increment)(void *) = nullptr; static void (*increment)(void *) = nullptr;
static bool (*isplaying)(void *) = nullptr; static bool (*isplaying)(void *) = nullptr;
void statustimer_init(void *ptr, void (inc)(void *), bool (isplay)(void *)) { void cdtimer_init(void *ptr, void (inc)(void *), bool (isplay)(void *)) {
// Setup RTC as a ~1 sec periodic timer via the normal counter's overflow. // Setup RTC as a ~1 sec periodic timer via the normal counter's overflow.
// Use the RTC directly (not PIT) to tune the status report interval closer to // Use the RTC directly (not PIT) to tune the status report interval closer to
// 1 sec (internal osc may be slightly off) // 1 sec (internal osc may be slightly off)
@@ -46,7 +46,7 @@ void statustimer_init(void *ptr, void (inc)(void *), bool (isplay)(void *)) {
isplaying = isplay; isplaying = isplay;
} }
void statustimer_reset() { void cdtimer_reset() {
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
loop_until_bit_is_clear(RTC_STATUS, RTC_CNTBUSY_bp); loop_until_bit_is_clear(RTC_STATUS, RTC_CNTBUSY_bp);
RTC.CNT = 0; RTC.CNT = 0;
@@ -55,18 +55,18 @@ void statustimer_reset() {
} }
} }
void statustimer_restore() { void cdtimer_restore() {
if (isplaying(changer)) if (isplaying(changer))
RTC.INTCTRL |= RTC_OVF_bm; RTC.INTCTRL |= RTC_OVF_bm;
} }
void statustimer_disable() { RTC.INTCTRL &= ~RTC_OVF_bm; } void cdtimer_disable() { RTC.INTCTRL &= ~RTC_OVF_bm; }
// Set once per overflow; consumed by the app via statustimer_tickPending(). // Set once per overflow; consumed by the app via cdtimer_pending().
volatile bool tick_pending = false; volatile bool cdtimer_pending_flag = false;
// Periodic interrupt with a ~1 sec period; only enabled while playing. // Periodic interrupt with a ~1 sec period; only enabled while playing.
ISR(RTC_CNT_vect) { ISR(RTC_CNT_vect) {
increment(changer); increment(changer);
tick_pending = true; cdtimer_pending_flag = true;
RTC.INTFLAGS = RTC_OVF_bm; RTC.INTFLAGS = RTC_OVF_bm;
} }
+22 -12
View File
@@ -6,8 +6,8 @@
#include <stdint.h> #include <stdint.h>
#include <util/atomic.h> #include <util/atomic.h>
#include "media_avr.h" // mediacontrol_syncDuringMask (used by the bus guard) #include "media_avr.h" // media_sync_during_mask (used by the bus guard)
#include "mediacontrol.h" #include "hal/media.h"
// F_CPU defined in timing_avr.h; the mic tick constants below are derived from // F_CPU defined in timing_avr.h; the mic tick constants below are derived from
// it (this hardware generation's TCA0/PB1 button-press implementation). // it (this hardware generation's TCA0/PB1 button-press implementation).
@@ -31,14 +31,14 @@ static constexpr uint16_t close_thresh =
#ifndef NDEBUG #ifndef NDEBUG
// Toggle PB1 and return its new level. // Toggle PB1 and return its new level.
bool AVCLAN_micToggle() { bool media_mic_toggle() {
// Take manual control of PB1 (CMP1EN gives TCA0 control of WO1/PB1 level) // Take manual control of PB1 (CMP1EN gives TCA0 control of WO1/PB1 level)
TCA0.SINGLE.CTRLB &= ~TCA_SINGLE_CMP1EN_bm; TCA0.SINGLE.CTRLB &= ~TCA_SINGLE_CMP1EN_bm;
VPORTB.OUT ^= PIN1_bm; VPORTB.OUT ^= PIN1_bm;
return (VPORTB.OUT & PIN1_bm) != 0; return (VPORTB.OUT & PIN1_bm) != 0;
} }
bool AVCLAN_isMediaFunctioning() { return mic_ntoggles != 0; } bool media_busy() { return mic_ntoggles != 0; }
#endif #endif
// Begin a press waveform of `nphases` × 100 ms level segments. // Begin a press waveform of `nphases` × 100 ms level segments.
@@ -99,11 +99,21 @@ ISR(TCA0_OVF_vect) { mic_timer_isr_body(false); }
// Emulate a transport-control button press on the source device. Each action // Emulate a transport-control button press on the source device. Each action
// maps to a press-train of a given length on MIC_CONTROL. // maps to a press-train of a given length on MIC_CONTROL.
void AVCLAN_mediaFunction(AVCLAN_media_fn_t fn) { void media_action(enum MediaAction action) {
switch (fn) { switch (action) {
case MEDIA_PLAY_PAUSE: mic_pulse(1); break; // single press case Play:
case MEDIA_SKIP_FORWARD: mic_pulse(3); break; // double-press case Pause:
case MEDIA_SKIP_BACKWARD: mic_pulse(5); break; // triple-press case Play_Pause: mic_pulse(1); break; // single press
case Skip_Forward:
case Track_Next: mic_pulse(3); break; // double-press
case Skip_Backward:
case Track_Prev: mic_pulse(5); break; // triple-press
case Repeat:
case Repeat_Single:
case Shuffle:
case Volume_Up:
case Volume_Down:
default: break;
} }
} }
@@ -113,13 +123,13 @@ void AVCLAN_mediaFunction(AVCLAN_media_fn_t fn) {
// - peripheral WO1 toggles and mic_ntoggles kept in sync // - peripheral WO1 toggles and mic_ntoggles kept in sync
// - maximum frame duration is ~15ms, "early" OVF remains within acceptable // - maximum frame duration is ~15ms, "early" OVF remains within acceptable
// ranges for either high/low pulses // ranges for either high/low pulses
// Caller (AVCLAN_stopEvent) guarantees interrupts are disabled. // Caller (phy_guard_enter) guarantees interrupts are disabled.
void mediacontrol_syncDuringMask() { void media_sync_during_guard() {
if (mic_ntoggles && TCA0.SINGLE.CNT >= (TCA0.SINGLE.CMP0 - close_thresh)) if (mic_ntoggles && TCA0.SINGLE.CNT >= (TCA0.SINGLE.CMP0 - close_thresh))
mic_timer_isr_body(true); mic_timer_isr_body(true);
} }
void mediacontrol_init() { void media_init() {
// PB1 needs to be set as an output for TCA0 to set the level // PB1 needs to be set as an output for TCA0 to set the level
PORTB.DIRSET = PIN1_bm; PORTB.DIRSET = PIN1_bm;
+3 -3
View File
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// AVR-internal media-driver hooks, shared between media_avr.c and the bus // AVR-internal media-driver hooks, shared between media_avr.c and the bus
// transaction guard (phy_avr.c's AVCLAN_stopEvent). Not part of the public // transaction guard (phy_avr.c's phy_guard_enter). Not part of the public
// mediacontrol.h interface. // mediacontrol.h interface.
#pragma once #pragma once
@@ -13,9 +13,9 @@ extern "C" {
// Keep the TCA0 press waveform roughly in sync while a bus transaction has // Keep the TCA0 press waveform roughly in sync while a bus transaction has
// masked interrupts (runs the OVF ISR body early if an overflow is imminent). // masked interrupts (runs the OVF ISR body early if an overflow is imminent).
// MUST be called with interrupts disabled (from within AVCLAN_stopEvent's // MUST be called with interrupts disabled (from within phy_guard_enter's
// ATOMIC_BLOCK). // ATOMIC_BLOCK).
void mediacontrol_syncDuringMask(); void media_sync_during_guard();
#ifdef __cplusplus #ifdef __cplusplus
} }
+34 -34
View File
@@ -9,10 +9,10 @@
#include <stdint.h> #include <stdint.h>
#include <util/atomic.h> #include <util/atomic.h>
#include "avclan_phy.h" #include "hal/phy.h"
#include "com232.h" // RS232_setRxInterrupt (guard); RS232_Print (Measure) #include "com232.h" // RS232_setRxInterrupt (guard); RS232_Print (Measure)
#include "media_avr.h" // mediacontrol_syncDuringMask (guard) #include "media_avr.h" // media_sync_during_mask (guard)
#include "statustimer.h" // statustimer_enable/disable (guard) #include "hal/cd_timer.h" // statustimer_enable/disable (guard)
// F_CPU + TICK_US (timing.h) defined here; F_CPU potentially needed by // F_CPU + TICK_US (timing.h) defined here; F_CPU potentially needed by
// avr-libc. // avr-libc.
@@ -58,15 +58,15 @@ static inline void AVCLAN_setBusDriven() {
// Returns true if device TX is muted on the AVCLAN bus (both drive pins are // Returns true if device TX is muted on the AVCLAN bus (both drive pins are
// configured as inputs). // configured as inputs).
bool AVCLAN_ismuted() { bool phy_is_muted() {
return (((VPORTA_DIR & PIN4_bm) | (VPORTA_DIR & PIN0_bm)) == 0); return (((VPORTA_DIR & PIN4_bm) | (VPORTA_DIR & PIN0_bm)) == 0);
} }
// True when the bus is being driven (i.e. not idle/floating). // True when the bus is being driven (i.e. not idle/floating).
bool AVCLAN_busActive() { return !BUS_IS_IDLE; } bool phy_active() { return !BUS_IS_IDLE; }
// Mute device TX on AVCLAN bus // Mute device TX on AVCLAN bus
void AVCLAN_muteDevice(bool mute) { void phy_mute(bool mute) {
if (mute) { if (mute) {
// clang-format off // clang-format off
__asm__ __volatile__("cbi %[vporta_dir], 4; \n\t" // set as INPUT (output values ignored) __asm__ __volatile__("cbi %[vporta_dir], 4; \n\t" // set as INPUT (output values ignored)
@@ -99,7 +99,7 @@ static void set_AVC_logic_for(uint8_t val, uint16_t period) {
return; return;
} }
void AVCLAN_sendbit(Bit bit) { void phy_send_bit(Bit bit) {
uint16_t zero_length, one_length; uint16_t zero_length, one_length;
switch (bit) { switch (bit) {
case bit_zero: case bit_zero:
@@ -120,7 +120,7 @@ void AVCLAN_sendbit(Bit bit) {
set_AVC_logic_for(1, one_length); set_AVC_logic_for(1, one_length);
} }
void AVCLAN_sendbit_ACK() { void phy_send_ack() {
TCB1.CNT = 0; TCB1.CNT = 0;
// Wait for controller to begin ACK bit // Wait for controller to begin ACK bit
@@ -131,7 +131,7 @@ void AVCLAN_sendbit_ACK() {
return; return;
} }
AVCLAN_sendbit(bit_zero); phy_send_bit(bit_zero);
} }
/* Returns true if the peripheral sent an ACK bit. /* Returns true if the peripheral sent an ACK bit.
@@ -139,7 +139,7 @@ void AVCLAN_sendbit_ACK() {
sync period, and allows the receiver to drive the bus (or not) to finish a "1" sync period, and allows the receiver to drive the bus (or not) to finish a "1"
bit. bit.
*/ */
uint8_t AVCLAN_readbit_ACK() { uint8_t phy_read_ack() {
TCB1.CNT = 0; // Double reset of TCB1.CNT: here TCB1.CNT = 0; // Double reset of TCB1.CNT: here
set_AVC_logic_for(0, AVCLAN_BIT1_LOGIC_0); // And here (within) set_AVC_logic_for(0, AVCLAN_BIT1_LOGIC_0); // And here (within)
AVCLAN_setBusIdle(); // Stop driving bus AVCLAN_setBusIdle(); // Stop driving bus
@@ -160,7 +160,7 @@ uint8_t AVCLAN_readbit_ACK() {
} }
// Send `len` bits on the AVCLAN bus; returns the even parity // Send `len` bits on the AVCLAN bus; returns the even parity
Bit AVCLAN_sendbitsi(const uint8_t *bits, int8_t len) { Bit phy_send_bits_u8(const uint8_t *bits, int8_t len) {
uint8_t b = *bits; uint8_t b = *bits;
uint8_t parity = 0; uint8_t parity = 0;
int8_t len_mod8 = 8; int8_t len_mod8 = 8;
@@ -175,7 +175,7 @@ Bit AVCLAN_sendbitsi(const uint8_t *bits, int8_t len) {
for (; len_mod8 > 0; len_mod8--) { for (; len_mod8 > 0; len_mod8--) {
Bit bit = (b & 0x80) != 0; Bit bit = (b & 0x80) != 0;
parity += (uint8_t)bit; parity += (uint8_t)bit;
AVCLAN_sendbit(bit); phy_send_bit(bit);
b <<= 1; b <<= 1;
} }
len_mod8 = 8; len_mod8 = 8;
@@ -185,18 +185,18 @@ Bit AVCLAN_sendbitsi(const uint8_t *bits, int8_t len) {
} }
// Send `len` bits on the AVCLAN bus; returns the even parity // Send `len` bits on the AVCLAN bus; returns the even parity
Bit AVCLAN_sendbitsl(const uint16_t *bits, int8_t len) { Bit phy_send_bits_u16(const uint16_t *bits, int8_t len) {
return AVCLAN_sendbitsi((const uint8_t *)bits + 1, len); return phy_send_bits_u8((const uint8_t *)bits + 1, len);
} }
Bit AVCLAN_sendbyte(const uint8_t *byte) { Bit phy_send_byte(const uint8_t *byte) {
uint8_t b = *byte; uint8_t b = *byte;
uint8_t parity = 0; uint8_t parity = 0;
for (uint8_t nbits = 8; nbits > 0; nbits--) { for (uint8_t nbits = 8; nbits > 0; nbits--) {
Bit bit = (b & 0x80) != 0; Bit bit = (b & 0x80) != 0;
parity += (uint8_t)bit; parity += (uint8_t)bit;
AVCLAN_sendbit(bit); phy_send_bit(bit);
b <<= 1; b <<= 1;
} }
return (parity & 1); return (parity & 1);
@@ -225,7 +225,7 @@ ISR(TCB0_INT_vect) {
} }
// Read `len` bits on the AVCLAN bus; returns the even parity // Read `len` bits on the AVCLAN bus; returns the even parity
uint8_t AVCLAN_readbitsi(uint8_t *bits, uint8_t len) { uint8_t phy_read_bits_u8(uint8_t *bits, uint8_t len) {
cli(); cli();
READING_BYTE = 0; READING_BYTE = 0;
READING_PARITY = 0; READING_PARITY = 0;
@@ -251,20 +251,20 @@ uint8_t AVCLAN_readbitsi(uint8_t *bits, uint8_t len) {
} }
// Read `len` bits on the AVCLAN bus; returns the even parity // Read `len` bits on the AVCLAN bus; returns the even parity
uint8_t AVCLAN_readbitsl(uint16_t *bits, int8_t len) { uint8_t phy_read_bits_u16(uint16_t *bits, int8_t len) {
uint8_t parity = 0; uint8_t parity = 0;
if (len > 8) { if (len > 8) {
uint8_t over = len - 8; uint8_t over = len - 8;
parity = AVCLAN_readbitsi((uint8_t *)bits + 1, over); parity = phy_read_bits_u8((uint8_t *)bits + 1, over);
len -= over; len -= over;
} }
parity += AVCLAN_readbitsi((uint8_t *)bits + 0, len); parity += phy_read_bits_u8((uint8_t *)bits + 0, len);
return (parity & 1); return (parity & 1);
} }
// Read a byte on the AVCLAN bus // Read a byte on the AVCLAN bus
uint8_t AVCLAN_readbyte(uint8_t *byte) { uint8_t phy_read_byte(uint8_t *byte) {
cli(); cli();
READING_BYTE = 0; READING_BYTE = 0;
READING_PARITY = 0; READING_PARITY = 0;
@@ -289,7 +289,7 @@ uint8_t AVCLAN_readbyte(uint8_t *byte) {
return (parity & 1); return (parity & 1);
} }
void AVCLAN_busInit() { void phy_init() {
// Set pin 6 and 7 as input // Set pin 6 and 7 as input
PORTA.DIRCLR = (PIN6_bm | PIN7_bm); PORTA.DIRCLR = (PIN6_bm | PIN7_bm);
// Disable input buffer; recommended when using AC // Disable input buffer; recommended when using AC
@@ -321,14 +321,14 @@ void AVCLAN_busInit() {
AVCLAN_setBusIdle(); AVCLAN_setBusIdle();
AVCLAN_muteDevice(false); // unmute AVCLAN bus TX phy_mute(false); // unmute AVCLAN bus TX
} }
// Wait for and validate an incoming start bit. On an over-long "driven" bus // Wait for and validate an incoming start bit. On an over-long "driven" bus
// (AC2 latched high because the bus is actually floating) this kicks PA7 hard // (AC2 latched high because the bus is actually floating) this kicks PA7 hard
// high to unlatch the comparator. The framing layer maps the result to its own // high to unlatch the comparator. The framing layer maps the result to its own
// error reporting; no printing happens here. // error reporting; no printing happens here.
Read AVCLAN_readstartbit() { Read phy_read_startbit() {
uint16_t startbitlen = TCB1.CNT = 0; uint16_t startbitlen = TCB1.CNT = 0;
while (!BUS_IS_IDLE) { while (!BUS_IS_IDLE) {
startbitlen = TCB1.CNT; startbitlen = TCB1.CNT;
@@ -370,7 +370,7 @@ Read AVCLAN_readstartbit() {
// Acquire the bus and emit a start bit. Returns false if another device is // Acquire the bus and emit a start bit. Returns false if another device is
// already driving the bus (we can't yet do proper CSMA/CD). // already driving the bus (we can't yet do proper CSMA/CD).
bool AVCLAN_sendstartbit() { bool phy_send_startbit() {
// wait for free line // wait for free line
TCB1.CNT = 0; TCB1.CNT = 0;
while (BUS_IS_IDLE) { while (BUS_IS_IDLE) {
@@ -395,25 +395,25 @@ bool AVCLAN_sendstartbit() {
// set_AVC_logic_for(1, AVCLAN_STARTBIT_LOGIC_1); // wait for end of start // set_AVC_logic_for(1, AVCLAN_STARTBIT_LOGIC_1); // wait for end of start
return false; return false;
} }
AVCLAN_sendbit(bit_start); phy_send_bit(bit_start);
return true; return true;
} }
/* Disable non-read related interrupts (USART RX, RTC status tick, mic timer) /* Disable non-read related interrupts (USART RX, RTC status tick, mic timer)
during AVCLAN bus transactions so framing isn't disturbed. TCB0 must remain during AVCLAN bus transactions so framing isn't disturbed. TCB0 must remain
enabled. */ enabled. */
void AVCLAN_stopEvent() { void phy_guard_enter() {
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
statustimer_disable(); cdtimer_disable();
RS232_setRxInterrupt(false); RS232_setRxInterrupt(false);
mediacontrol_syncDuringMask(); media_sync_during_guard();
} }
} }
// Re-enable serial and periodic interrupts after a bus transaction. // Re-enable serial and periodic interrupts after a bus transaction.
void AVCLAN_startEvent() { void phy_guard_leave() {
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
statustimer_restore(); // Reenable status interrupt if currently playing cdtimer_restore(); // Reenable status interrupt if currently playing
RS232_setRxInterrupt(true); RS232_setRxInterrupt(true);
} }
} }
@@ -426,8 +426,8 @@ void AVCLAN_startEvent() {
static uint16_t pulses[100]; static uint16_t pulses[100];
static uint16_t periods[100]; static uint16_t periods[100];
void AVCLan_Measure() { void phy_measure() {
AVCLAN_stopEvent(); phy_guard_enter();
uint8_t tmp = 0; uint8_t tmp = 0;
@@ -457,6 +457,6 @@ void AVCLan_Measure() {
} }
RS232_Print("\nDone.\n"); RS232_Print("\nDone.\n");
AVCLAN_startEvent(); phy_guard_leave();
} }
#endif #endif
+13 -13
View File
@@ -7,10 +7,10 @@
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
#include "board.h"
#include "cdchanger.hpp" #include "cdchanger.hpp"
#include "com232.h" #include "com232.h"
#include "frame.hpp" #include "frame.hpp"
#include "hal/board.h"
#include "peripheral.hpp" #include "peripheral.hpp"
#include "queue.hpp" #include "queue.hpp"
@@ -76,7 +76,7 @@ int main() {
print_help(); print_help();
while (true) { while (true) {
if (AVCLAN_busActive()) { if (peripheral.bus_is_active()) {
if (auto msg = cache.pop()) { if (auto msg = cache.pop()) {
auto err = peripheral.read(msg.get(), Print{.print = printAllFrames, auto err = peripheral.read(msg.get(), Print{.print = printAllFrames,
.binary = printBinary, .binary = printBinary,
@@ -171,29 +171,29 @@ int main() {
break; break;
#ifndef NDEBUG #ifndef NDEBUG
case 'g': AVCLAN_micToggle(); break; case 'g': peripheral.device<CDChanger>().mic_toggle(); break;
case 'p': case 'p':
RS232_Print("First play/pause begin ... "); RS232_Print("First play/pause begin ... ");
AVCLAN_mediaFunction(MEDIA_PLAY_PAUSE); peripheral.device<CDChanger>().media_action(MediaAction::Play_Pause);
while (AVCLAN_isMediaFunctioning()) {} while (peripheral.device<CDChanger>().media_busy()) {}
RS232_Print("end\nSecond play/pause begin ... "); RS232_Print("end\nSecond play/pause begin ... ");
AVCLAN_mediaFunction(MEDIA_PLAY_PAUSE); peripheral.device<CDChanger>().media_action(MediaAction::Play_Pause);
while (AVCLAN_isMediaFunctioning()) {} while (peripheral.device<CDChanger>().media_busy()) {}
RS232_Print("end\n"); RS232_Print("end\n");
break; break;
case 's': case 's':
RS232_Print("Skip begin ... "); RS232_Print("Skip begin ... ");
AVCLAN_mediaFunction(MEDIA_SKIP_FORWARD); peripheral.device<CDChanger>().media_action(MediaAction::Track_Next);
while (AVCLAN_isMediaFunctioning()) {} while (peripheral.device<CDChanger>().media_busy()) {}
RS232_Print("end\n"); RS232_Print("end\n");
break; break;
case 'b': case 'b':
RS232_Print("Skip back begin ... "); RS232_Print("Skip back begin ... ");
AVCLAN_mediaFunction(MEDIA_SKIP_BACKWARD); peripheral.device<CDChanger>().media_action(MediaAction::Track_Prev);
while (AVCLAN_isMediaFunctioning()) {} while (peripheral.device<CDChanger>().media_busy()) {}
RS232_Print("end\n"); RS232_Print("end\n");
break; break;
case 'M': AVCLan_Measure(); break; case 'M': peripheral.get_bus().measure(); break;
#endif #endif
case 0x10: // Signals binary sequence incoming case 0x10: // Signals binary sequence incoming
@@ -290,7 +290,7 @@ namespace {
void Setup() { void Setup() {
board_init(); // clock + GPIO bring-up (target-specific) board_init(); // clock + GPIO bring-up (target-specific)
RS232_Init(); RS232_Init();
board_interruptsEnable(); board_enable_interrupts();
} }
void print_help() { void print_help() {