mirror of
https://github.com/halleysfifthinc/AVCLAN-Mockingboard.git
synced 2026-08-06 17:03:17 +00:00
Finalize C <=> C++ interface
This commit is contained in:
+2
-1
@@ -13,7 +13,8 @@ add_compile_options(
|
||||
$<$<COMPILE_LANGUAGE:CXX>:-fno-threadsafe-statics>
|
||||
$<$<COMPILE_LANGUAGE:CXX>:-fno-exceptions>
|
||||
$<$<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_VERSION VERSION_LESS "13")
|
||||
|
||||
+19
-2
@@ -99,11 +99,28 @@ enum AVCLAN_ENUM_CLASS Action : uint8_t {
|
||||
Track_Name_Resp = 0xfd,
|
||||
|
||||
// Reports
|
||||
Playback_Status = 0xf1, // Typically unprompted, sent to Device::STATUS
|
||||
Loading_Status = 0xf3, // Typically unprompted, sent to Device::STATUS
|
||||
Playback_Status = 0xf1, // Typically unprompted, sent to Device::STATUS
|
||||
Loading_Status = 0xf3, // Typically unprompted, sent to Device::STATUS
|
||||
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
|
||||
namespace detail {
|
||||
struct Error {
|
||||
|
||||
+134
-22
@@ -31,24 +31,149 @@
|
||||
|
||||
#include "bus.hpp"
|
||||
#include "avclan.h"
|
||||
#include "avclan_phy.h" // bridge until phy has been ported
|
||||
#include "com232.h"
|
||||
#include "frame.hpp"
|
||||
#include "hal/phy.h" // bridge until phy has been ported
|
||||
|
||||
namespace {
|
||||
constexpr int ADDR_WIDTH = 12;
|
||||
constexpr int CONTROL_WIDTH = 4;
|
||||
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 avclan {
|
||||
|
||||
Bus::Handle::Handle() { AVCLAN_stopEvent(); }
|
||||
Bus::Handle::~Handle() { AVCLAN_startEvent(); }
|
||||
class Bus::Handle {
|
||||
Handle() { phy_guard_enter(); };
|
||||
friend Bus;
|
||||
|
||||
void Bus::init() { AVCLAN_busInit(); };
|
||||
void Bus::mute(bool mute) { AVCLAN_muteDevice(mute); };
|
||||
bool Bus::is_muted() const { return AVCLAN_ismuted(); };
|
||||
public:
|
||||
~Handle() { phy_guard_leave(); };
|
||||
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 {
|
||||
struct errtype {
|
||||
@@ -278,21 +403,8 @@ auto Bus::send(const Frame *out, Frame::Print print) -> Error::Send {
|
||||
|
||||
Bus::Handle Bus::get() { return {}; };
|
||||
|
||||
bool Bus::Handle::sendstartbit() { return AVCLAN_sendstartbit(); };
|
||||
auto Bus::Handle::readstartbit() -> Read { return AVCLAN_readstartbit(); };
|
||||
void Bus::Handle::send_ACK() { AVCLAN_sendbit_ACK(); };
|
||||
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));
|
||||
};
|
||||
#ifndef NDEBUG
|
||||
void Bus::measure() { phy_measure(); }
|
||||
#endif
|
||||
|
||||
} // namespace avclan
|
||||
|
||||
+8
-117
@@ -52,139 +52,30 @@
|
||||
#include <type_traits>
|
||||
|
||||
#include "avclan.h"
|
||||
#include "avclan_phy.h" // bridge until phy has been ported
|
||||
#include "frame.hpp"
|
||||
|
||||
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 {
|
||||
public:
|
||||
class Handle;
|
||||
using Error = detail::Error;
|
||||
|
||||
void init();
|
||||
|
||||
bool is_active() const;
|
||||
void mute(bool mute);
|
||||
bool is_muted() const;
|
||||
|
||||
#ifndef NDEBUG
|
||||
void measure();
|
||||
#endif
|
||||
|
||||
Error::Read read(uint16_t address, Frame *in, Frame::Print print);
|
||||
Error::Send send(const Frame *out, Frame::Print print);
|
||||
|
||||
private:
|
||||
class Handle;
|
||||
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
|
||||
|
||||
+46
-41
@@ -10,23 +10,22 @@
|
||||
#include "cdchanger.hpp"
|
||||
#include "device.hpp"
|
||||
#include "frame.hpp"
|
||||
#include "mediacontrol.h"
|
||||
#include "statustimer.h"
|
||||
#include "hal/cd_timer.h"
|
||||
#include "hal/media.h"
|
||||
|
||||
namespace {
|
||||
using namespace avclan;
|
||||
|
||||
constexpr uint8_t cdloading_resp[] = {
|
||||
to_underlying(Device::CD_CHANGER),
|
||||
to_underlying(Device::STATUS),
|
||||
to_underlying(Action::Loading_Status),
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x01,
|
||||
0x02};
|
||||
constexpr uint8_t cdloading_resp[] = {to_underlying(Device::CD_CHANGER),
|
||||
to_underlying(Device::STATUS),
|
||||
to_underlying(Action::Loading_Status),
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x01,
|
||||
0x02};
|
||||
|
||||
constexpr int WIRE_SIZE = 8; // cd state report size in bytes
|
||||
constexpr int TIME_SKIP = 15; // seconds
|
||||
@@ -53,8 +52,8 @@ extern "C" bool isPlaying_callback(void *self) {
|
||||
namespace avclan {
|
||||
|
||||
void CDChanger::init() {
|
||||
mediacontrol_init();
|
||||
statustimer_init(this, &incrementTime_callback, &isPlaying_callback);
|
||||
media_init();
|
||||
cdtimer_init(this, &incrementTime_callback, &isPlaying_callback);
|
||||
}
|
||||
|
||||
void CDChanger::handle(const Frame *in, Frame *out) {
|
||||
@@ -119,16 +118,15 @@ void CDChanger::handle(const Frame *in, Frame *out) {
|
||||
case Initial_Report_Req: {
|
||||
out->is_unicast = true;
|
||||
// No knowledge/understanding of field meaning/interpretation
|
||||
const uint8_t cdinitreport_resp[] = {
|
||||
0x00,
|
||||
to_underlying(Device::CD_CHANGER),
|
||||
to_underlying(from),
|
||||
to_underlying(Initial_Report_Resp),
|
||||
0x01,
|
||||
0x31,
|
||||
0x10,
|
||||
0x01,
|
||||
0x01};
|
||||
const uint8_t cdinitreport_resp[] = {0x00,
|
||||
to_underlying(Device::CD_CHANGER),
|
||||
to_underlying(from),
|
||||
to_underlying(Initial_Report_Resp),
|
||||
0x01,
|
||||
0x31,
|
||||
0x10,
|
||||
0x01,
|
||||
0x01};
|
||||
out->length = sizeof(cdinitreport_resp);
|
||||
memcpy(out->data, cdinitreport_resp, sizeof(cdinitreport_resp));
|
||||
out->reaction = r_SendOnly;
|
||||
@@ -163,7 +161,7 @@ void CDChanger::handle(const Frame *in, Frame *out) {
|
||||
secs = 0x7f;
|
||||
flags2 = 0xc0;
|
||||
generateStatus(out, true, Device::CMD_SW);
|
||||
AVCLAN_mediaFunction(MEDIA_SKIP_FORWARD);
|
||||
media_action(MediaAction::Track_Next);
|
||||
out->reaction = r_TrackChange;
|
||||
break;
|
||||
case Track_Seek_Down:
|
||||
@@ -174,12 +172,13 @@ void CDChanger::handle(const Frame *in, Frame *out) {
|
||||
--track;
|
||||
else
|
||||
track = TWODIGIT_MAX;
|
||||
|
||||
media_action(MediaAction::Track_Prev);
|
||||
}
|
||||
mins = 0xff;
|
||||
secs = 0x7f;
|
||||
flags2 = 0xc0;
|
||||
generateStatus(out, true, Device::CMD_SW);
|
||||
AVCLAN_mediaFunction(MEDIA_SKIP_BACKWARD);
|
||||
out->reaction = r_TrackChange;
|
||||
break;
|
||||
case Track_Fast_Forward: {
|
||||
@@ -190,9 +189,9 @@ void CDChanger::handle(const Frame *in, Frame *out) {
|
||||
++mins;
|
||||
}
|
||||
generateStatus(out, true, Device::CMD_SW);
|
||||
AVCLAN_mediaFunction(MEDIA_SKIP_FORWARD);
|
||||
statustimer_reset(); // Skipped to a whole/round sec; ensure next tick
|
||||
// is ~1 sec from now
|
||||
media_action(MediaAction::Skip_Forward);
|
||||
cdtimer_reset(); // Skipped to a whole/round sec; ensure next tick
|
||||
// is ~1 sec from now
|
||||
out->reaction = r_SendOnly;
|
||||
break;
|
||||
}
|
||||
@@ -210,9 +209,9 @@ void CDChanger::handle(const Frame *in, Frame *out) {
|
||||
} else
|
||||
secs -= TIME_SKIP;
|
||||
generateStatus(out, true, Device::CMD_SW);
|
||||
AVCLAN_mediaFunction(MEDIA_SKIP_BACKWARD);
|
||||
statustimer_reset(); // Skipped to a whole/round sec; ensure next tick
|
||||
// is ~1 sec from now
|
||||
media_action(MediaAction::Skip_Backward);
|
||||
cdtimer_reset(); // Skipped to a whole/round sec; ensure next tick
|
||||
// is ~1 sec from now
|
||||
out->reaction = r_SendOnly;
|
||||
break;
|
||||
}
|
||||
@@ -296,8 +295,8 @@ void CDChanger::react(Frame *out, detail::Error::Send err) {
|
||||
break;
|
||||
case r_TrackChange:
|
||||
setTime(0, 0);
|
||||
statustimer_reset(); // Skipped to a whole/round sec; ensure next tick is
|
||||
// ~1 sec from now
|
||||
cdtimer_reset(); // Skipped to a whole/round sec; ensure next tick is
|
||||
// ~1 sec from now
|
||||
[[fallthrough]];
|
||||
case r_NormalizeState:
|
||||
normalizeState();
|
||||
@@ -336,8 +335,8 @@ void CDChanger::enable(Frame *out) {
|
||||
}
|
||||
}
|
||||
|
||||
bool CDChanger::pending() { return statustimer_tickPending(); }
|
||||
void CDChanger::resolvepending() { statustimer_clearTick(); }
|
||||
bool CDChanger::pending() { return cdtimer_pending(); }
|
||||
void CDChanger::resolvepending() { cdtimer_clear(); }
|
||||
|
||||
void CDChanger::emit(Frame *out, uint16_t peripheral) {
|
||||
out->peripheral_addr = peripheral;
|
||||
@@ -352,16 +351,16 @@ bool CDChanger::isPlaying() const { return playing; }
|
||||
void CDChanger::startPlaying() {
|
||||
static bool havePlayed = false;
|
||||
if (havePlayed)
|
||||
AVCLAN_mediaFunction(MEDIA_PLAY_PAUSE);
|
||||
media_action(MediaAction::Play);
|
||||
havePlayed |= true;
|
||||
playing = true;
|
||||
statustimer_reset();
|
||||
cdtimer_reset();
|
||||
}
|
||||
|
||||
void CDChanger::stopPlaying() {
|
||||
statustimer_disable();
|
||||
cdtimer_disable();
|
||||
playing = false;
|
||||
AVCLAN_mediaFunction(MEDIA_PLAY_PAUSE);
|
||||
media_action(MediaAction::Pause);
|
||||
}
|
||||
|
||||
// Serialize cd_status into the wire format.
|
||||
@@ -425,4 +424,10 @@ void CDChanger::normalizeState() {
|
||||
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
|
||||
|
||||
@@ -69,6 +69,11 @@ public:
|
||||
void emit(Frame *out, uint16_t peripheral);
|
||||
void incrementTime();
|
||||
bool isPlaying() const;
|
||||
#ifndef NDEBUG
|
||||
void media_action(MediaAction action);
|
||||
bool media_busy() const;
|
||||
void mic_toggle();
|
||||
#endif
|
||||
|
||||
private:
|
||||
void startPlaying();
|
||||
|
||||
@@ -12,7 +12,7 @@ extern "C" {
|
||||
void board_init(void);
|
||||
|
||||
// Globally enable interrupts. Call after all peripherals are initialized.
|
||||
void board_interruptsEnable(void);
|
||||
void board_enable_interrupts(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
// 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
|
||||
// transmit.
|
||||
void AVCLAN_muteDevice(bool mute);
|
||||
bool AVCLAN_ismuted(void);
|
||||
void phy_mute(bool mute);
|
||||
bool phy_is_muted(void);
|
||||
|
||||
// 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
|
||||
// read/send so framing isn't disturbed, then restore them. May be a no-op on a
|
||||
// target without such contention.
|
||||
void AVCLAN_stopEvent(void);
|
||||
void AVCLAN_startEvent(void);
|
||||
void phy_guard_enter(void);
|
||||
void phy_guard_leave(void);
|
||||
|
||||
// Start-bit handling, factored out of read/sendframe so the framing layer holds
|
||||
// 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.
|
||||
// - 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.
|
||||
Read AVCLAN_readstartbit(void);
|
||||
bool AVCLAN_sendstartbit(void);
|
||||
Read phy_read_startbit(void);
|
||||
bool phy_send_startbit(void);
|
||||
|
||||
// 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.
|
||||
void AVCLAN_sendbit(Bit bit);
|
||||
void AVCLAN_sendbit_ACK(void);
|
||||
uint8_t AVCLAN_readbit_ACK(void);
|
||||
// the read* helpers return the even parity of the bits read. The _u8/_u16
|
||||
// suffixes name the source-operand width; `len` is how many bits (<= width).
|
||||
void phy_send_bit(Bit bit);
|
||||
void phy_send_ack(void);
|
||||
uint8_t phy_read_ack(void);
|
||||
|
||||
Bit AVCLAN_sendbitsi(const uint8_t *bits, int8_t len);
|
||||
Bit AVCLAN_sendbitsl(const uint16_t *bits, int8_t len);
|
||||
Bit AVCLAN_sendbyte(const uint8_t *byte);
|
||||
Bit phy_send_bits_u8(const uint8_t *bits, int8_t len);
|
||||
Bit phy_send_bits_u16(const uint16_t *bits, int8_t len);
|
||||
Bit phy_send_byte(const uint8_t *byte);
|
||||
|
||||
uint8_t AVCLAN_readbitsi(uint8_t *bits, uint8_t len);
|
||||
uint8_t AVCLAN_readbitsl(uint16_t *bits, int8_t len);
|
||||
uint8_t AVCLAN_readbyte(uint8_t *byte);
|
||||
uint8_t phy_read_bits_u8(uint8_t *bits, uint8_t len);
|
||||
uint8_t phy_read_bits_u16(uint16_t *bits, int8_t len);
|
||||
uint8_t phy_read_byte(uint8_t *byte);
|
||||
|
||||
#ifndef NDEBUG
|
||||
// Sample and dump bus bit timing over the serial link (REPL `M`).
|
||||
void AVCLan_Measure(void);
|
||||
void phy_measure(void);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
@@ -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
|
||||
@@ -25,9 +25,18 @@ public:
|
||||
|
||||
uint16_t address() const { return address_; };
|
||||
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); };
|
||||
bool is_muted() const { return bus.is_muted(); };
|
||||
|
||||
#ifndef NDEBUG
|
||||
Bus &get_bus() { return bus; }
|
||||
#endif
|
||||
|
||||
Error::Read read(Frame *in, Frame::Print print) {
|
||||
return bus.read(address_, in, print);
|
||||
};
|
||||
@@ -44,7 +53,7 @@ public:
|
||||
using enum Action;
|
||||
out->reaction = 0;
|
||||
|
||||
if (AVCLAN_ismuted() || in->length < 3)
|
||||
if (is_muted() || in->length < 3)
|
||||
return;
|
||||
|
||||
// 0xFF placeholders are variant bytes filled by writing directly to
|
||||
@@ -137,6 +146,8 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
#undef PACK3
|
||||
|
||||
void react(Frame *out, Error::Send err) {
|
||||
if (((Devs::id == out->owning_device) || ...))
|
||||
((Devs::id == out->owning_device
|
||||
@@ -147,8 +158,6 @@ public:
|
||||
out->reaction = 0;
|
||||
}
|
||||
|
||||
#undef PACK3
|
||||
|
||||
template <class F> void poll_devices(F &&fun) {
|
||||
(poller(std::get<Devs>(devices_), fun), ...);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
phy_avr.c
|
||||
media_avr.c
|
||||
statustick_avr.c
|
||||
cd_timer_avr.c
|
||||
board_avr.c)
|
||||
|
||||
target_include_directories(avclan PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include <avr/io.h>
|
||||
#include <avr/xmega.h> // _PROTECTED_WRITE
|
||||
|
||||
#include "board.h"
|
||||
#include "hal/board.h"
|
||||
|
||||
void board_init(void) {
|
||||
// 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
|
||||
}
|
||||
|
||||
void board_interruptsEnable(void) { sei(); }
|
||||
void board_enable_interrupts(void) { sei(); }
|
||||
|
||||
+8
-8
@@ -8,7 +8,7 @@
|
||||
#include <stdint.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,
|
||||
// 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 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.
|
||||
// Use the RTC directly (not PIT) to tune the status report interval closer to
|
||||
// 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;
|
||||
}
|
||||
|
||||
void statustimer_reset() {
|
||||
void cdtimer_reset() {
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
|
||||
loop_until_bit_is_clear(RTC_STATUS, RTC_CNTBUSY_bp);
|
||||
RTC.CNT = 0;
|
||||
@@ -55,18 +55,18 @@ void statustimer_reset() {
|
||||
}
|
||||
}
|
||||
|
||||
void statustimer_restore() {
|
||||
void cdtimer_restore() {
|
||||
if (isplaying(changer))
|
||||
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().
|
||||
volatile bool tick_pending = false;
|
||||
// Set once per overflow; consumed by the app via cdtimer_pending().
|
||||
volatile bool cdtimer_pending_flag = false;
|
||||
|
||||
// Periodic interrupt with a ~1 sec period; only enabled while playing.
|
||||
ISR(RTC_CNT_vect) {
|
||||
increment(changer);
|
||||
tick_pending = true;
|
||||
cdtimer_pending_flag = true;
|
||||
RTC.INTFLAGS = RTC_OVF_bm;
|
||||
}
|
||||
@@ -6,8 +6,8 @@
|
||||
#include <stdint.h>
|
||||
#include <util/atomic.h>
|
||||
|
||||
#include "media_avr.h" // mediacontrol_syncDuringMask (used by the bus guard)
|
||||
#include "mediacontrol.h"
|
||||
#include "media_avr.h" // media_sync_during_mask (used by the bus guard)
|
||||
#include "hal/media.h"
|
||||
|
||||
// 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).
|
||||
@@ -31,14 +31,14 @@ static constexpr uint16_t close_thresh =
|
||||
|
||||
#ifndef NDEBUG
|
||||
// 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)
|
||||
TCA0.SINGLE.CTRLB &= ~TCA_SINGLE_CMP1EN_bm;
|
||||
VPORTB.OUT ^= PIN1_bm;
|
||||
return (VPORTB.OUT & PIN1_bm) != 0;
|
||||
}
|
||||
|
||||
bool AVCLAN_isMediaFunctioning() { return mic_ntoggles != 0; }
|
||||
bool media_busy() { return mic_ntoggles != 0; }
|
||||
#endif
|
||||
|
||||
// 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
|
||||
// maps to a press-train of a given length on MIC_CONTROL.
|
||||
void AVCLAN_mediaFunction(AVCLAN_media_fn_t fn) {
|
||||
switch (fn) {
|
||||
case MEDIA_PLAY_PAUSE: mic_pulse(1); break; // single press
|
||||
case MEDIA_SKIP_FORWARD: mic_pulse(3); break; // double-press
|
||||
case MEDIA_SKIP_BACKWARD: mic_pulse(5); break; // triple-press
|
||||
void media_action(enum MediaAction action) {
|
||||
switch (action) {
|
||||
case Play:
|
||||
case Pause:
|
||||
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
|
||||
// - maximum frame duration is ~15ms, "early" OVF remains within acceptable
|
||||
// ranges for either high/low pulses
|
||||
// Caller (AVCLAN_stopEvent) guarantees interrupts are disabled.
|
||||
void mediacontrol_syncDuringMask() {
|
||||
// Caller (phy_guard_enter) guarantees interrupts are disabled.
|
||||
void media_sync_during_guard() {
|
||||
if (mic_ntoggles && TCA0.SINGLE.CNT >= (TCA0.SINGLE.CMP0 - close_thresh))
|
||||
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
|
||||
PORTB.DIRSET = PIN1_bm;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
@@ -13,9 +13,9 @@ extern "C" {
|
||||
|
||||
// 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).
|
||||
// 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).
|
||||
void mediacontrol_syncDuringMask();
|
||||
void media_sync_during_guard();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
#include <stdint.h>
|
||||
#include <util/atomic.h>
|
||||
|
||||
#include "avclan_phy.h"
|
||||
#include "hal/phy.h"
|
||||
#include "com232.h" // RS232_setRxInterrupt (guard); RS232_Print (Measure)
|
||||
#include "media_avr.h" // mediacontrol_syncDuringMask (guard)
|
||||
#include "statustimer.h" // statustimer_enable/disable (guard)
|
||||
#include "media_avr.h" // media_sync_during_mask (guard)
|
||||
#include "hal/cd_timer.h" // statustimer_enable/disable (guard)
|
||||
|
||||
// F_CPU + TICK_US (timing.h) defined here; F_CPU potentially needed by
|
||||
// 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
|
||||
// configured as inputs).
|
||||
bool AVCLAN_ismuted() {
|
||||
bool phy_is_muted() {
|
||||
return (((VPORTA_DIR & PIN4_bm) | (VPORTA_DIR & PIN0_bm)) == 0);
|
||||
}
|
||||
|
||||
// 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
|
||||
void AVCLAN_muteDevice(bool mute) {
|
||||
void phy_mute(bool mute) {
|
||||
if (mute) {
|
||||
// clang-format off
|
||||
__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;
|
||||
}
|
||||
|
||||
void AVCLAN_sendbit(Bit bit) {
|
||||
void phy_send_bit(Bit bit) {
|
||||
uint16_t zero_length, one_length;
|
||||
switch (bit) {
|
||||
case bit_zero:
|
||||
@@ -120,7 +120,7 @@ void AVCLAN_sendbit(Bit bit) {
|
||||
set_AVC_logic_for(1, one_length);
|
||||
}
|
||||
|
||||
void AVCLAN_sendbit_ACK() {
|
||||
void phy_send_ack() {
|
||||
TCB1.CNT = 0;
|
||||
|
||||
// Wait for controller to begin ACK bit
|
||||
@@ -131,7 +131,7 @@ void AVCLAN_sendbit_ACK() {
|
||||
return;
|
||||
}
|
||||
|
||||
AVCLAN_sendbit(bit_zero);
|
||||
phy_send_bit(bit_zero);
|
||||
}
|
||||
|
||||
/* 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"
|
||||
bit.
|
||||
*/
|
||||
uint8_t AVCLAN_readbit_ACK() {
|
||||
uint8_t phy_read_ack() {
|
||||
TCB1.CNT = 0; // Double reset of TCB1.CNT: here
|
||||
set_AVC_logic_for(0, AVCLAN_BIT1_LOGIC_0); // And here (within)
|
||||
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
|
||||
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 parity = 0;
|
||||
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--) {
|
||||
Bit bit = (b & 0x80) != 0;
|
||||
parity += (uint8_t)bit;
|
||||
AVCLAN_sendbit(bit);
|
||||
phy_send_bit(bit);
|
||||
b <<= 1;
|
||||
}
|
||||
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
|
||||
Bit AVCLAN_sendbitsl(const uint16_t *bits, int8_t len) {
|
||||
return AVCLAN_sendbitsi((const uint8_t *)bits + 1, len);
|
||||
Bit phy_send_bits_u16(const uint16_t *bits, int8_t 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 parity = 0;
|
||||
|
||||
for (uint8_t nbits = 8; nbits > 0; nbits--) {
|
||||
Bit bit = (b & 0x80) != 0;
|
||||
parity += (uint8_t)bit;
|
||||
AVCLAN_sendbit(bit);
|
||||
phy_send_bit(bit);
|
||||
b <<= 1;
|
||||
}
|
||||
return (parity & 1);
|
||||
@@ -225,7 +225,7 @@ ISR(TCB0_INT_vect) {
|
||||
}
|
||||
|
||||
// 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();
|
||||
READING_BYTE = 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
|
||||
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;
|
||||
if (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;
|
||||
}
|
||||
parity += AVCLAN_readbitsi((uint8_t *)bits + 0, len);
|
||||
parity += phy_read_bits_u8((uint8_t *)bits + 0, len);
|
||||
|
||||
return (parity & 1);
|
||||
}
|
||||
|
||||
// Read a byte on the AVCLAN bus
|
||||
uint8_t AVCLAN_readbyte(uint8_t *byte) {
|
||||
uint8_t phy_read_byte(uint8_t *byte) {
|
||||
cli();
|
||||
READING_BYTE = 0;
|
||||
READING_PARITY = 0;
|
||||
@@ -289,7 +289,7 @@ uint8_t AVCLAN_readbyte(uint8_t *byte) {
|
||||
return (parity & 1);
|
||||
}
|
||||
|
||||
void AVCLAN_busInit() {
|
||||
void phy_init() {
|
||||
// Set pin 6 and 7 as input
|
||||
PORTA.DIRCLR = (PIN6_bm | PIN7_bm);
|
||||
// Disable input buffer; recommended when using AC
|
||||
@@ -321,14 +321,14 @@ void AVCLAN_busInit() {
|
||||
|
||||
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
|
||||
// (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
|
||||
// error reporting; no printing happens here.
|
||||
Read AVCLAN_readstartbit() {
|
||||
Read phy_read_startbit() {
|
||||
uint16_t startbitlen = TCB1.CNT = 0;
|
||||
while (!BUS_IS_IDLE) {
|
||||
startbitlen = TCB1.CNT;
|
||||
@@ -370,7 +370,7 @@ Read AVCLAN_readstartbit() {
|
||||
|
||||
// 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).
|
||||
bool AVCLAN_sendstartbit() {
|
||||
bool phy_send_startbit() {
|
||||
// wait for free line
|
||||
TCB1.CNT = 0;
|
||||
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
|
||||
return false;
|
||||
}
|
||||
AVCLAN_sendbit(bit_start);
|
||||
phy_send_bit(bit_start);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Disable non-read related interrupts (USART RX, RTC status tick, mic timer)
|
||||
during AVCLAN bus transactions so framing isn't disturbed. TCB0 must remain
|
||||
enabled. */
|
||||
void AVCLAN_stopEvent() {
|
||||
void phy_guard_enter() {
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
|
||||
statustimer_disable();
|
||||
cdtimer_disable();
|
||||
RS232_setRxInterrupt(false);
|
||||
mediacontrol_syncDuringMask();
|
||||
media_sync_during_guard();
|
||||
}
|
||||
}
|
||||
|
||||
// Re-enable serial and periodic interrupts after a bus transaction.
|
||||
void AVCLAN_startEvent() {
|
||||
void phy_guard_leave() {
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
|
||||
statustimer_restore(); // Reenable status interrupt if currently playing
|
||||
cdtimer_restore(); // Reenable status interrupt if currently playing
|
||||
RS232_setRxInterrupt(true);
|
||||
}
|
||||
}
|
||||
@@ -426,8 +426,8 @@ void AVCLAN_startEvent() {
|
||||
static uint16_t pulses[100];
|
||||
static uint16_t periods[100];
|
||||
|
||||
void AVCLan_Measure() {
|
||||
AVCLAN_stopEvent();
|
||||
void phy_measure() {
|
||||
phy_guard_enter();
|
||||
|
||||
uint8_t tmp = 0;
|
||||
|
||||
@@ -457,6 +457,6 @@ void AVCLan_Measure() {
|
||||
}
|
||||
RS232_Print("\nDone.\n");
|
||||
|
||||
AVCLAN_startEvent();
|
||||
phy_guard_leave();
|
||||
}
|
||||
#endif
|
||||
|
||||
+13
-13
@@ -7,10 +7,10 @@
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#include "board.h"
|
||||
#include "cdchanger.hpp"
|
||||
#include "com232.h"
|
||||
#include "frame.hpp"
|
||||
#include "hal/board.h"
|
||||
#include "peripheral.hpp"
|
||||
#include "queue.hpp"
|
||||
|
||||
@@ -76,7 +76,7 @@ int main() {
|
||||
print_help();
|
||||
|
||||
while (true) {
|
||||
if (AVCLAN_busActive()) {
|
||||
if (peripheral.bus_is_active()) {
|
||||
if (auto msg = cache.pop()) {
|
||||
auto err = peripheral.read(msg.get(), Print{.print = printAllFrames,
|
||||
.binary = printBinary,
|
||||
@@ -171,29 +171,29 @@ int main() {
|
||||
break;
|
||||
|
||||
#ifndef NDEBUG
|
||||
case 'g': AVCLAN_micToggle(); break;
|
||||
case 'g': peripheral.device<CDChanger>().mic_toggle(); break;
|
||||
case 'p':
|
||||
RS232_Print("First play/pause begin ... ");
|
||||
AVCLAN_mediaFunction(MEDIA_PLAY_PAUSE);
|
||||
while (AVCLAN_isMediaFunctioning()) {}
|
||||
peripheral.device<CDChanger>().media_action(MediaAction::Play_Pause);
|
||||
while (peripheral.device<CDChanger>().media_busy()) {}
|
||||
RS232_Print("end\nSecond play/pause begin ... ");
|
||||
AVCLAN_mediaFunction(MEDIA_PLAY_PAUSE);
|
||||
while (AVCLAN_isMediaFunctioning()) {}
|
||||
peripheral.device<CDChanger>().media_action(MediaAction::Play_Pause);
|
||||
while (peripheral.device<CDChanger>().media_busy()) {}
|
||||
RS232_Print("end\n");
|
||||
break;
|
||||
case 's':
|
||||
RS232_Print("Skip begin ... ");
|
||||
AVCLAN_mediaFunction(MEDIA_SKIP_FORWARD);
|
||||
while (AVCLAN_isMediaFunctioning()) {}
|
||||
peripheral.device<CDChanger>().media_action(MediaAction::Track_Next);
|
||||
while (peripheral.device<CDChanger>().media_busy()) {}
|
||||
RS232_Print("end\n");
|
||||
break;
|
||||
case 'b':
|
||||
RS232_Print("Skip back begin ... ");
|
||||
AVCLAN_mediaFunction(MEDIA_SKIP_BACKWARD);
|
||||
while (AVCLAN_isMediaFunctioning()) {}
|
||||
peripheral.device<CDChanger>().media_action(MediaAction::Track_Prev);
|
||||
while (peripheral.device<CDChanger>().media_busy()) {}
|
||||
RS232_Print("end\n");
|
||||
break;
|
||||
case 'M': AVCLan_Measure(); break;
|
||||
case 'M': peripheral.get_bus().measure(); break;
|
||||
#endif
|
||||
|
||||
case 0x10: // Signals binary sequence incoming
|
||||
@@ -290,7 +290,7 @@ namespace {
|
||||
void Setup() {
|
||||
board_init(); // clock + GPIO bring-up (target-specific)
|
||||
RS232_Init();
|
||||
board_interruptsEnable();
|
||||
board_enable_interrupts();
|
||||
}
|
||||
|
||||
void print_help() {
|
||||
|
||||
Reference in New Issue
Block a user