mirror of
https://github.com/halleysfifthinc/AVCLAN-Mockingboard.git
synced 2026-09-25 13:22:06 +00:00
Migrate to FreeRTOS
This commit is contained in:
+2
-1
@@ -1,5 +1,7 @@
|
|||||||
cmake_minimum_required(VERSION 3.30)
|
cmake_minimum_required(VERSION 3.30)
|
||||||
|
|
||||||
|
include(FetchContent)
|
||||||
|
|
||||||
if(AVCLAN_TARGET_SDK_FILE)
|
if(AVCLAN_TARGET_SDK_FILE)
|
||||||
include(${AVCLAN_TARGET_SDK_FILE})
|
include(${AVCLAN_TARGET_SDK_FILE})
|
||||||
endif()
|
endif()
|
||||||
@@ -119,7 +121,6 @@ add_subdirectory(src/avclan/target/${AVCLAN_TARGET})
|
|||||||
# subdirectory to provide a port-supplied freestanding stdlib via
|
# subdirectory to provide a port-supplied freestanding stdlib via
|
||||||
# CMAKE_REQUIRED_INCLUDES, if necessary.
|
# CMAKE_REQUIRED_INCLUDES, if necessary.
|
||||||
include(CheckCXXSourceCompiles)
|
include(CheckCXXSourceCompiles)
|
||||||
include(FetchContent)
|
|
||||||
find_program(GIT_EXECUTABLE git REQUIRED)
|
find_program(GIT_EXECUTABLE git REQUIRED)
|
||||||
|
|
||||||
if(TARGET libstdcpp)
|
if(TARGET libstdcpp)
|
||||||
|
|||||||
+12
-14
@@ -30,11 +30,11 @@
|
|||||||
(zero) is not expected.
|
(zero) is not expected.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <concepts>
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <new>
|
|
||||||
|
#include "FreeRTOS.h" // IWYU pragma: export
|
||||||
|
|
||||||
#include "avclan.h"
|
#include "avclan.h"
|
||||||
#include "bus.hpp"
|
#include "bus.hpp"
|
||||||
@@ -113,20 +113,15 @@ void Bus::init(uint16_t address) {
|
|||||||
return;
|
return;
|
||||||
phy_init(address);
|
phy_init(address);
|
||||||
muted_ = false; // phy_init leaves the bus TX unmuted
|
muted_ = false; // phy_init leaves the bus TX unmuted
|
||||||
deafened_ = false; // Default to listening
|
|
||||||
inited_ = true;
|
inited_ = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
|
||||||
bool Bus::is_active() const { return !deafened_ && phy_frame_pending(); };
|
|
||||||
void Bus::mute(bool mute) {
|
void Bus::mute(bool mute) {
|
||||||
phy_mute(mute);
|
phy_mute(mute);
|
||||||
muted_ = mute; // Only update muted_ *AFTER* hardware has finished muting
|
muted_ = mute; // Only update muted_ *AFTER* hardware has finished muting
|
||||||
};
|
};
|
||||||
void Bus::deafen(bool deaf) {
|
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
||||||
phy_deafen(deaf);
|
void Bus::deafen(bool deaf) { phy_deafen(deaf); }
|
||||||
deafened_ = deaf; // Only update deafened_ *AFTER* the phy has stopped acking
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Bus::read(Frame::Print print)
|
auto Bus::read(Frame::Print print)
|
||||||
-> expected<std::unique_ptr<Frame>, Error::Read> {
|
-> expected<std::unique_ptr<Frame>, Error::Read> {
|
||||||
@@ -137,12 +132,14 @@ auto Bus::read(Frame::Print print)
|
|||||||
|
|
||||||
using enum Read;
|
using enum Read;
|
||||||
|
|
||||||
std::unique_ptr<Frame> in(new (std::nothrow) Frame);
|
std::unique_ptr<Frame> in = Frame::acquire();
|
||||||
if (!in) {
|
if (!in) {
|
||||||
err.type = POOL_EMPTY;
|
err.type = POOL_EMPTY;
|
||||||
goto handle_err;
|
goto handle_err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
phy_wait_frame(portMAX_DELAY);
|
||||||
|
|
||||||
{ // bound handle lifetime
|
{ // bound handle lifetime
|
||||||
auto handle = get();
|
auto handle = get();
|
||||||
|
|
||||||
@@ -198,6 +195,8 @@ auto Bus::read(Frame::Print print)
|
|||||||
|
|
||||||
if (false) { // NOLINT(readability-simplify-boolean-expr)
|
if (false) { // NOLINT(readability-simplify-boolean-expr)
|
||||||
handle_err:;
|
handle_err:;
|
||||||
|
if (err.type == NO_FRAME)
|
||||||
|
return unexpected{NO_FRAME};
|
||||||
fputs("ERR(read): ", stdout);
|
fputs("ERR(read): ", stdout);
|
||||||
switch (err.type) {
|
switch (err.type) {
|
||||||
case POOL_EMPTY: puts("failed Frame alloc"); break;
|
case POOL_EMPTY: puts("failed Frame alloc"); break;
|
||||||
@@ -281,8 +280,7 @@ auto Bus::send(const Frame &out, Frame::Print print) -> Send {
|
|||||||
if (err.type != Send{0})
|
if (err.type != Send{0})
|
||||||
goto handle_err;
|
goto handle_err;
|
||||||
|
|
||||||
err.type =
|
err.type = handle.send_data(out.data, out.length, out.is_unicast, &err.val);
|
||||||
handle.send_data(out.data, out.length, out.is_unicast, &err.val);
|
|
||||||
if (err.type != Send{0})
|
if (err.type != Send{0})
|
||||||
goto handle_err;
|
goto handle_err;
|
||||||
|
|
||||||
@@ -360,8 +358,8 @@ Send Bus::sendbyte(uint8_t byte, bool ack) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef MEASURE_BUS
|
#ifdef MEASURE_BUS
|
||||||
// Debug bit-timing measurement on the one physical bus; instance-scoped for
|
// Debug bit-timing measurement on the one physical bus; instance-scoped like
|
||||||
// the same reason as is_active().
|
// deafen().
|
||||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
||||||
void Bus::measure() { phy_measure(); }
|
void Bus::measure() { phy_measure(); }
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -73,10 +73,6 @@ public:
|
|||||||
// frames addressed to it without further instruction. One address per bus.
|
// frames addressed to it without further instruction. One address per bus.
|
||||||
void init(uint16_t address);
|
void init(uint16_t address);
|
||||||
|
|
||||||
// True when there is a frame to read and we aren't deafened. Depending on the
|
|
||||||
// target that means the bus has gone dominant or a frame is already buffered.
|
|
||||||
bool is_active() const;
|
|
||||||
|
|
||||||
// Prevent the device from being active on the bus
|
// Prevent the device from being active on the bus
|
||||||
void mute(bool mute);
|
void mute(bool mute);
|
||||||
bool is_muted() const { return muted_; };
|
bool is_muted() const { return muted_; };
|
||||||
@@ -101,7 +97,6 @@ private:
|
|||||||
|
|
||||||
// Assume mute after default ctor; only viable after init call
|
// Assume mute after default ctor; only viable after init call
|
||||||
bool muted_ = true;
|
bool muted_ = true;
|
||||||
bool deafened_ = false;
|
|
||||||
bool inited_ = false;
|
bool inited_ = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+108
-106
@@ -3,19 +3,22 @@
|
|||||||
// Copyright (C) 2015 Allen Hill <allenofthehills@gmail.com>
|
// Copyright (C) 2015 Allen Hill <allenofthehills@gmail.com>
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
#include "avclan.h"
|
#include "avclan.h"
|
||||||
#include "cdchanger.hpp"
|
#include "cdchanger.hpp"
|
||||||
#include "device.hpp"
|
#include "device.hpp"
|
||||||
#include "frame.hpp"
|
#include "frame.hpp"
|
||||||
#include "hal/cd_timer.h"
|
|
||||||
#include "hal/media.h"
|
#include "hal/media.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
using namespace avclan;
|
using namespace avclan;
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
constexpr uint8_t cdloading_resp[] = {to_underlying(Device::CD_CHANGER),
|
constexpr uint8_t cdloading_resp[] = {to_underlying(Device::CD_CHANGER),
|
||||||
to_underlying(Device::STATUS),
|
to_underlying(Device::STATUS),
|
||||||
@@ -29,38 +32,44 @@ constexpr uint8_t cdloading_resp[] = {to_underlying(Device::CD_CHANGER),
|
|||||||
0x02};
|
0x02};
|
||||||
|
|
||||||
constexpr int WIRE_SIZE = 8; // cd state report size in bytes
|
constexpr int WIRE_SIZE = 8; // cd state report size in bytes
|
||||||
constexpr int TIME_SKIP = 15; // seconds
|
constexpr auto TIME_SKIP = 15s;
|
||||||
constexpr int TWODIGIT_MAX = 99;
|
constexpr int TWODIGIT_MAX = 99;
|
||||||
|
constexpr auto MAX_TIME = std::chrono::minutes{TWODIGIT_MAX} + 59s;
|
||||||
|
|
||||||
/* Pack a 0–TWODIGIT_MAX count into 2-digit BCD. Values >TWODIGIT_MAX (sentinels
|
// Pack a 0–TWODIGIT_MAX count into 2-digit BCD
|
||||||
such as 0xFF / 0x7F meaning "no time") pass through unchanged so they survive
|
|
||||||
the wire round-trip. */
|
|
||||||
constexpr uint8_t toBCD(uint8_t val) {
|
constexpr uint8_t toBCD(uint8_t val) {
|
||||||
if (val > TWODIGIT_MAX)
|
|
||||||
return val;
|
|
||||||
return (uint8_t)(((val / 10) << 4) | (val % 10));
|
return (uint8_t)(((val / 10) << 4) | (val % 10));
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void incrementTime_callback(void *self) {
|
// Whole seconds, saturated to the displayable range
|
||||||
static_cast<avclan::CDChanger *>(self)->incrementTime();
|
constexpr std::chrono::seconds displaySeconds(std::chrono::milliseconds t) {
|
||||||
}
|
return std::clamp(std::chrono::floor<std::chrono::seconds>(t), -MAX_TIME,
|
||||||
|
MAX_TIME);
|
||||||
extern "C" bool isPlaying_callback(void *self) {
|
|
||||||
return static_cast<avclan::CDChanger *>(self)->isPlaying();
|
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace avclan {
|
namespace avclan {
|
||||||
|
|
||||||
void CDChanger::init() {
|
void CDChanger::init(Notifier notif) {
|
||||||
media_init();
|
media_init();
|
||||||
cdtimer_init(this, &incrementTime_callback, &isPlaying_callback);
|
notifier = notif;
|
||||||
|
statusTimer = xTimerCreate(
|
||||||
|
"cd status", pdMS_TO_TICKS(1000), true, this, [](TimerHandle_t timer) {
|
||||||
|
auto &self = *static_cast<CDChanger *>(pvTimerGetTimerID(timer));
|
||||||
|
// Timer callbacks must not block; a refused request is re-made next
|
||||||
|
// tick
|
||||||
|
if (!self.statusQueued.exchange(true) && !self.notifier.notify(0, 0))
|
||||||
|
self.statusQueued = false;
|
||||||
|
});
|
||||||
|
configASSERT(statusTimer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CDChanger::handle(const Frame &in, Frame &out) {
|
void CDChanger::handle(const Frame &in, Frame &out) {
|
||||||
if (in.length < 4)
|
if (in.length < 4)
|
||||||
return; // [Currently known] valid CDChanger frames have at least 4 bytes
|
return; // [Currently known] valid CDChanger frames have at least 4 bytes
|
||||||
|
|
||||||
|
std::optional<MediaAction> media;
|
||||||
|
|
||||||
const uint8_t *data = &in.data[1];
|
const uint8_t *data = &in.data[1];
|
||||||
const auto from = static_cast<Device>(*data++);
|
const auto from = static_cast<Device>(*data++);
|
||||||
/* const auto to = */ data++;
|
/* const auto to = */ data++;
|
||||||
@@ -73,7 +82,7 @@ void CDChanger::handle(const Frame &in, Frame &out) {
|
|||||||
#pragma GCC diagnostic push
|
#pragma GCC diagnostic push
|
||||||
#pragma GCC diagnostic ignored "-Wswitch-enum"
|
#pragma GCC diagnostic ignored "-Wswitch-enum"
|
||||||
// Unicast to CD changer: bytes are (0x00, from, to, action, [extra...]).
|
// Unicast to CD changer: bytes are (0x00, from, to, action, [extra...]).
|
||||||
switch (action) {
|
switch (const std::lock_guard lock(mutex); action) {
|
||||||
case Enable_Function_Req:
|
case Enable_Function_Req:
|
||||||
out.is_unicast = true;
|
out.is_unicast = true;
|
||||||
out.length = sizeof(function_change_resp);
|
out.length = sizeof(function_change_resp);
|
||||||
@@ -93,7 +102,7 @@ void CDChanger::handle(const Frame &in, Frame &out) {
|
|||||||
out.data[2] = to_underlying(from);
|
out.data[2] = to_underlying(from);
|
||||||
out.data[3] = to_underlying(Disable_Function_Resp);
|
out.data[3] = to_underlying(Disable_Function_Resp);
|
||||||
if (isPlaying()) {
|
if (isPlaying()) {
|
||||||
stopPlaying();
|
media = stopPlaying();
|
||||||
state = 0;
|
state = 0;
|
||||||
flags2 = 0x80;
|
flags2 = 0x80;
|
||||||
out.reaction = r_StatusReport;
|
out.reaction = r_StatusReport;
|
||||||
@@ -165,47 +174,39 @@ void CDChanger::handle(const Frame &in, Frame &out) {
|
|||||||
++track;
|
++track;
|
||||||
else
|
else
|
||||||
track = 1;
|
track = 1;
|
||||||
mins = 0xff;
|
time.reset();
|
||||||
secs = 0x7f;
|
|
||||||
flags2 &= ~NEGATIVE;
|
|
||||||
generateStatus(out, true, Device::CMD_SW);
|
generateStatus(out, true, Device::CMD_SW);
|
||||||
media_action(MediaAction::Track_Next);
|
media = MediaAction::Track_Next;
|
||||||
out.reaction = r_TrackChange;
|
out.reaction = r_TrackChange;
|
||||||
break;
|
break;
|
||||||
case Track_Seek_Down:
|
case Track_Seek_Down:
|
||||||
state = SEEKING_TRACK;
|
state = SEEKING_TRACK;
|
||||||
// Track down returns to track beginning if in ~middle of song
|
// Track down returns to track beginning if in ~middle of song
|
||||||
if ((flags2 & NEGATIVE) != 0 || (mins == 0 && secs < 5)) {
|
if (const auto t = trackTime(); t && *t < 5s) {
|
||||||
if (track > 1)
|
if (track > 1)
|
||||||
--track;
|
--track;
|
||||||
else
|
else
|
||||||
track = TWODIGIT_MAX;
|
track = TWODIGIT_MAX;
|
||||||
|
|
||||||
media_action(MediaAction::Track_Prev);
|
media = MediaAction::Track_Prev;
|
||||||
}
|
}
|
||||||
mins = 0xff;
|
time.reset();
|
||||||
secs = 0x7f;
|
|
||||||
flags2 &= ~NEGATIVE;
|
|
||||||
generateStatus(out, true, Device::CMD_SW);
|
generateStatus(out, true, Device::CMD_SW);
|
||||||
out.reaction = r_TrackChange;
|
out.reaction = r_TrackChange;
|
||||||
break;
|
break;
|
||||||
case Track_Fast_Forward: {
|
case Track_Fast_Forward: {
|
||||||
state |= SEEKING;
|
state |= SEEKING;
|
||||||
incrementTime(TIME_SKIP);
|
seek(TIME_SKIP);
|
||||||
generateStatus(out, true, Device::CMD_SW);
|
generateStatus(out, true, Device::CMD_SW);
|
||||||
media_action(MediaAction::Skip_Forward);
|
media = MediaAction::Skip_Forward;
|
||||||
cdtimer_reset(); // Skipped to a whole/round sec; ensure next tick
|
|
||||||
// is ~1 sec from now
|
|
||||||
out.reaction = r_SendOnly;
|
out.reaction = r_SendOnly;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Track_Rewind: {
|
case Track_Rewind: {
|
||||||
state |= SEEKING;
|
state |= SEEKING;
|
||||||
incrementTime(-TIME_SKIP);
|
seek(-TIME_SKIP);
|
||||||
generateStatus(out, true, Device::CMD_SW);
|
generateStatus(out, true, Device::CMD_SW);
|
||||||
media_action(MediaAction::Skip_Backward);
|
media = MediaAction::Skip_Backward;
|
||||||
cdtimer_reset(); // Skipped to a whole/round sec; ensure next tick
|
|
||||||
// is ~1 sec from now
|
|
||||||
out.reaction = r_SendOnly;
|
out.reaction = r_SendOnly;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -272,17 +273,23 @@ void CDChanger::handle(const Frame &in, Frame &out) {
|
|||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
#pragma GCC diagnostic pop
|
#pragma GCC diagnostic pop
|
||||||
|
|
||||||
|
// Media action handled outside of switch to minimize lock duration
|
||||||
|
if (media)
|
||||||
|
media_action(*media);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Frame>
|
std::unique_ptr<Frame>
|
||||||
CDChanger::react(expected<std::unique_ptr<Frame>, detail::SendError> exp) {
|
CDChanger::react(expected<std::unique_ptr<Frame>, detail::SendError> exp) {
|
||||||
|
std::optional<MediaAction> media;
|
||||||
|
std::unique_ptr<Frame> next;
|
||||||
|
|
||||||
if (!exp) {
|
if (const std::lock_guard lock(mutex); !exp) {
|
||||||
if (exp.error().reaction == to_underlying(r_StateReport) &&
|
if (exp.error().reaction == to_underlying(r_StateReport) &&
|
||||||
exp.error().err == detail::Error::Send::NAK_ADDRESS &&
|
exp.error().err == detail::Error::Send::NAK_ADDRESS &&
|
||||||
++failedStatusReports > 1) {
|
++failedStatusReports > 1) {
|
||||||
failedStatusReports = 0;
|
failedStatusReports = 0;
|
||||||
stopPlaying(); // Disable periodic updates if e.g. no-one's
|
media = stopPlaying(); // Disable periodic updates if e.g. no-one's
|
||||||
// listening (car was turned off?)
|
// listening (car was turned off?)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -312,9 +319,9 @@ CDChanger::react(expected<std::unique_ptr<Frame>, detail::SendError> exp) {
|
|||||||
out->reaction = r_SendOnly;
|
out->reaction = r_SendOnly;
|
||||||
break;
|
break;
|
||||||
case r_TrackChange:
|
case r_TrackChange:
|
||||||
setTime(0, 0);
|
setTime(0ms);
|
||||||
cdtimer_reset(); // Skipped to a whole/round sec; ensure next tick is
|
// Track starts at a whole sec; next status is ~1 sec out
|
||||||
// ~1 sec from now
|
xTimerReset(statusTimer, 0);
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
case r_NormalizeState:
|
case r_NormalizeState:
|
||||||
normalizeState();
|
normalizeState();
|
||||||
@@ -327,7 +334,7 @@ CDChanger::react(expected<std::unique_ptr<Frame>, detail::SendError> exp) {
|
|||||||
out->reaction = r_BeganPlaying;
|
out->reaction = r_BeganPlaying;
|
||||||
break;
|
break;
|
||||||
case r_BeganPlaying:
|
case r_BeganPlaying:
|
||||||
startPlaying(); // only start PIT after normalizing state
|
media = startPlaying(); // only start status timer after normalizing
|
||||||
out->reaction = r_Nothing;
|
out->reaction = r_Nothing;
|
||||||
break;
|
break;
|
||||||
case r_StatusReport:
|
case r_StatusReport:
|
||||||
@@ -341,18 +348,19 @@ CDChanger::react(expected<std::unique_ptr<Frame>, detail::SendError> exp) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (out->reaction > r_Nothing)
|
if (out->reaction > r_Nothing)
|
||||||
return out;
|
next = std::move(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
if (media)
|
||||||
|
media_action(*media);
|
||||||
|
return next;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CDChanger::enable(Frame &out) {
|
void CDChanger::enable(Frame &out) {
|
||||||
if (!isPlaying()) {
|
if (!isPlaying()) {
|
||||||
if (mins > TWODIGIT_MAX)
|
const std::lock_guard lock(mutex);
|
||||||
mins = 0;
|
if (!time)
|
||||||
if (secs > TWODIGIT_MAX)
|
setTime(0ms);
|
||||||
secs = 0;
|
|
||||||
state = SEEKING | SEEKING_TRACK;
|
state = SEEKING | SEEKING_TRACK;
|
||||||
flags2 = 0x80;
|
flags2 = 0x80;
|
||||||
generateStatus(out, false, Device::STATUS);
|
generateStatus(out, false, Device::STATUS);
|
||||||
@@ -360,85 +368,81 @@ void CDChanger::enable(Frame &out) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CDChanger::pending() { return cdtimer_pending(); }
|
void CDChanger::emit(Frame &out, uint32_t /*payload*/) {
|
||||||
|
statusQueued = false; // Clear first so a tick during emit() queues anew
|
||||||
void CDChanger::emit(Frame &out) {
|
const std::lock_guard lock(mutex);
|
||||||
generateStatus(out, false, Device::STATUS);
|
generateStatus(out, false, Device::STATUS);
|
||||||
out.reaction = r_StateReport;
|
out.reaction = r_StateReport;
|
||||||
cdtimer_clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CDChanger::isPlaying() const { return playing; }
|
bool CDChanger::isPlaying() const { return playing; }
|
||||||
|
|
||||||
// Sets CD_mode to play and resets timer count (so that the next interrupt is in
|
// Starts track time and the ~1 Hz status report (the first ~1 sec from now)
|
||||||
// 1 sec)
|
std::optional<MediaAction> CDChanger::startPlaying() {
|
||||||
void CDChanger::startPlaying() {
|
|
||||||
static bool havePlayed = false;
|
static bool havePlayed = false;
|
||||||
|
std::optional<MediaAction> media;
|
||||||
if (havePlayed)
|
if (havePlayed)
|
||||||
media_action(MediaAction::Play);
|
media = MediaAction::Play;
|
||||||
havePlayed |= true;
|
havePlayed |= true;
|
||||||
|
if (!playing)
|
||||||
|
refTick = xTaskGetTickCount();
|
||||||
playing = true;
|
playing = true;
|
||||||
cdtimer_reset();
|
xTimerReset(statusTimer, 0);
|
||||||
|
return media;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CDChanger::stopPlaying() {
|
MediaAction CDChanger::stopPlaying() {
|
||||||
cdtimer_disable();
|
xTimerStop(statusTimer, 0);
|
||||||
|
time = trackTime();
|
||||||
playing = false;
|
playing = false;
|
||||||
media_action(MediaAction::Pause);
|
return MediaAction::Pause;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::chrono::milliseconds> CDChanger::trackTime() const {
|
||||||
|
if (!time || !playing)
|
||||||
|
return time;
|
||||||
|
return *time + std::chrono::milliseconds{
|
||||||
|
pdTICKS_TO_MS(xTaskGetTickCount() - refTick)};
|
||||||
|
}
|
||||||
|
|
||||||
|
void CDChanger::setTime(std::chrono::milliseconds t) {
|
||||||
|
time = t;
|
||||||
|
refTick = xTaskGetTickCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seek `by` from the current whole second; no-op while no time is shown
|
||||||
|
void CDChanger::seek(std::chrono::seconds by) {
|
||||||
|
const auto t = trackTime();
|
||||||
|
if (!t)
|
||||||
|
return;
|
||||||
|
setTime(displaySeconds(*t + by));
|
||||||
|
if (playing) // Seeked to a whole sec; next status is ~1 sec out
|
||||||
|
xTimerReset(statusTimer, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serialize cd_status into the wire format.
|
// Serialize cd_status into the wire format.
|
||||||
// track/mins/secs, need converted from decimal to BCD
|
// track/mins/secs, need converted from decimal to BCD
|
||||||
void CDChanger::serialize(uint8_t *dst) const {
|
void CDChanger::serialize(uint8_t *dst) const {
|
||||||
|
uint8_t mins = 0xFF; // "no time" sentinels
|
||||||
|
uint8_t secs = 0x7F;
|
||||||
|
bool negative = false;
|
||||||
|
if (const auto t = trackTime()) {
|
||||||
|
const auto sec = displaySeconds(*t);
|
||||||
|
const auto magnitude = std::chrono::abs(sec);
|
||||||
|
const auto wholeMins = std::chrono::floor<std::chrono::minutes>(magnitude);
|
||||||
|
mins = toBCD((uint8_t)wholeMins.count());
|
||||||
|
secs = toBCD((uint8_t)(magnitude - wholeMins).count());
|
||||||
|
negative = sec < 0s;
|
||||||
|
}
|
||||||
|
|
||||||
*dst++ = cds;
|
*dst++ = cds;
|
||||||
*dst++ = state;
|
*dst++ = state;
|
||||||
*dst++ = disc;
|
*dst++ = disc;
|
||||||
*dst++ = toBCD(track);
|
*dst++ = toBCD(track);
|
||||||
*dst++ = toBCD(mins);
|
*dst++ = mins;
|
||||||
*dst++ = toBCD(secs);
|
*dst++ = secs;
|
||||||
*dst++ = flags;
|
*dst++ = flags;
|
||||||
*dst++ = flags2;
|
*dst++ = negative ? (flags2 | NEGATIVE) : (flags2 & ~NEGATIVE);
|
||||||
}
|
|
||||||
|
|
||||||
void CDChanger::setTime(uint8_t min, uint8_t sec) {
|
|
||||||
mins = min;
|
|
||||||
secs = sec;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Increment the time by inc_sec (REQUIRES |inc_sec| <= 59).
|
|
||||||
void CDChanger::incrementTime(int8_t inc_sec) {
|
|
||||||
// Sentinel values (>TWODIGIT_MAX) mean "no time"; leave them alone until
|
|
||||||
// setTime() replaces them with a real count.
|
|
||||||
if (mins > TWODIGIT_MAX)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if ((flags2 & NEGATIVE) != 0)
|
|
||||||
inc_sec = -inc_sec; // time forward shrinks a negative magnitude
|
|
||||||
int8_t sum = secs + inc_sec;
|
|
||||||
|
|
||||||
if (sum < 0 && mins == 0) {
|
|
||||||
// Stepped through zero: the display flips sign and counts away from it.
|
|
||||||
secs = (uint8_t)-sum;
|
|
||||||
flags2 ^= NEGATIVE;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sum > 59) {
|
|
||||||
if (mins == TWODIGIT_MAX) { // saturate at 99:59 rather than wrap the hour
|
|
||||||
secs = 59;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sum -= 60;
|
|
||||||
++mins;
|
|
||||||
} else if (sum < 0) {
|
|
||||||
sum += 60;
|
|
||||||
--mins; // mins > 0: the mins == 0 borrow was handled above
|
|
||||||
}
|
|
||||||
secs = (uint8_t)sum;
|
|
||||||
|
|
||||||
// Zero is neither sign, so it must never display as -00:00.
|
|
||||||
if ((mins | secs) == 0)
|
|
||||||
flags2 &= ~NEGATIVE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used for changed status messages
|
// Used for changed status messages
|
||||||
@@ -460,10 +464,8 @@ void CDChanger::generateStatus(Frame &status, bool is_unicast,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CDChanger::normalizeState() {
|
void CDChanger::normalizeState() {
|
||||||
if (mins > TWODIGIT_MAX)
|
if (!time)
|
||||||
mins = 0;
|
setTime(0ms);
|
||||||
if (secs > TWODIGIT_MAX)
|
|
||||||
secs = 0;
|
|
||||||
state = PLAYBACK;
|
state = PLAYBACK;
|
||||||
flags &= (uint8_t)~(DISK_SCAN | SCAN);
|
flags &= (uint8_t)~(DISK_SCAN | SCAN);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,15 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include "FreeRTOS.h" // IWYU pragma: export
|
||||||
|
#include "semphr.h"
|
||||||
|
#include "timers.h"
|
||||||
|
|
||||||
#include "avclan.h"
|
#include "avclan.h"
|
||||||
#include "device.hpp"
|
#include "device.hpp"
|
||||||
@@ -60,16 +67,17 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
static constexpr Device id = Device::CD_CHANGER;
|
static constexpr Device id = Device::CD_CHANGER;
|
||||||
void init();
|
CDChanger() : mutex(xSemaphoreCreateMutex()) {}
|
||||||
|
CDChanger(const CDChanger &) = delete;
|
||||||
|
|
||||||
|
void init(Notifier notifier);
|
||||||
|
|
||||||
void handle(const Frame &in, Frame &out);
|
void handle(const Frame &in, Frame &out);
|
||||||
std::unique_ptr<Frame>
|
std::unique_ptr<Frame>
|
||||||
react(expected<std::unique_ptr<Frame>, detail::SendError> exp);
|
react(expected<std::unique_ptr<Frame>, detail::SendError> exp);
|
||||||
void enable(Frame &out);
|
void enable(Frame &out);
|
||||||
void disable(Frame &out);
|
void disable(Frame &out);
|
||||||
static bool pending();
|
void emit(Frame &out, uint32_t payload);
|
||||||
void emit(Frame &out);
|
|
||||||
void incrementTime(int8_t inc_sec = 1);
|
|
||||||
bool isPlaying() const;
|
bool isPlaying() const;
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
void media_action(MediaAction action);
|
void media_action(MediaAction action);
|
||||||
@@ -78,21 +86,43 @@ public:
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void startPlaying();
|
// Implements the BasicLockable named requirements for an xSemaphore
|
||||||
void stopPlaying();
|
class xMutex {
|
||||||
|
public:
|
||||||
|
explicit xMutex(SemaphoreHandle_t mutex) : mutex_{mutex} {}
|
||||||
|
xMutex(const xMutex &) = delete;
|
||||||
|
|
||||||
|
void lock() noexcept { xSemaphoreTake(mutex_, portMAX_DELAY); }
|
||||||
|
void unlock() noexcept { xSemaphoreGive(mutex_); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
SemaphoreHandle_t mutex_ = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::optional<MediaAction> startPlaying();
|
||||||
|
MediaAction stopPlaying();
|
||||||
|
std::optional<std::chrono::milliseconds> trackTime() const;
|
||||||
|
void setTime(std::chrono::milliseconds t);
|
||||||
|
void seek(std::chrono::seconds by);
|
||||||
void serialize(uint8_t *dst) const;
|
void serialize(uint8_t *dst) const;
|
||||||
void setTime(uint8_t mins, uint8_t secs);
|
|
||||||
void generateStatus(Frame &status, bool is_unicast, Device to) const;
|
void generateStatus(Frame &status, bool is_unicast, Device to) const;
|
||||||
void normalizeState();
|
void normalizeState();
|
||||||
|
|
||||||
|
Notifier notifier{};
|
||||||
|
TimerHandle_t statusTimer = nullptr;
|
||||||
|
std::atomic<bool> statusQueued = false; // coalesces status emit requests
|
||||||
|
xMutex mutex;
|
||||||
|
|
||||||
|
// Track time is a stopwatch: `time` is relative to `refTick`, and
|
||||||
|
// advances while `playing`. nullopt means no time is shown.
|
||||||
|
std::optional<std::chrono::milliseconds> time;
|
||||||
|
TickType_t refTick = 0;
|
||||||
bool playing = false;
|
bool playing = false;
|
||||||
int failedStatusReports = 0;
|
int failedStatusReports = 0;
|
||||||
uint8_t cds = CD1;
|
uint8_t cds = CD1;
|
||||||
uint8_t state = SEEKING | SEEKING_TRACK;
|
uint8_t state = SEEKING | SEEKING_TRACK;
|
||||||
uint8_t disc = 1;
|
uint8_t disc = 1;
|
||||||
uint8_t track = 1; // Decimal storage; serialize to BCD
|
uint8_t track = 1; // Decimal storage; serialize to BCD
|
||||||
uint8_t mins = 0xFF; // Decimal storage; serialize to BCD
|
|
||||||
uint8_t secs = 0x7F; // Decimal storage; serialize to BCD
|
|
||||||
uint8_t flags = 0;
|
uint8_t flags = 0;
|
||||||
uint8_t flags2 = 0x80;
|
uint8_t flags2 = 0x80;
|
||||||
};
|
};
|
||||||
|
|||||||
+27
-10
@@ -7,6 +7,9 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
#include "FreeRTOS.h" // IWYU pragma: export
|
||||||
|
#include "queue.h"
|
||||||
|
|
||||||
#include "avclan.h"
|
#include "avclan.h"
|
||||||
#include "frame.hpp"
|
#include "frame.hpp"
|
||||||
#include "stdshim.hpp"
|
#include "stdshim.hpp"
|
||||||
@@ -47,20 +50,34 @@ enum class Device : uint8_t {
|
|||||||
TRIP_INFO = 0xE5,
|
TRIP_INFO = 0xE5,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Notifies the Peripheral to emit for a device at `index`. Requests are served
|
||||||
|
// in order, one `emit()` each; coalescing is up to the device. `val` (24 bits)
|
||||||
|
// is device-defined. Returns whether the request was accepted within `wait`.
|
||||||
|
class Notifier {
|
||||||
|
public:
|
||||||
|
Notifier() = default;
|
||||||
|
Notifier(QueueHandle_t queue, uint8_t index) : queue{queue}, index{index} {}
|
||||||
|
|
||||||
|
bool notify(uint32_t val = 0, TickType_t wait = portMAX_DELAY) const {
|
||||||
|
const uint32_t item = (val << 8) | index;
|
||||||
|
return xQueueSend(queue, &item, wait) == pdPASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
QueueHandle_t queue = nullptr;
|
||||||
|
uint8_t index = 0;
|
||||||
|
};
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
concept DeviceInterface =
|
concept DeviceInterface =
|
||||||
requires { std::integral_constant<Device, T::id>{}; } &&
|
requires { std::integral_constant<Device, T::id>{}; } &&
|
||||||
requires(T dev, const Frame &in, Frame &out,
|
requires(T dev, Notifier notifier, const Frame &in, Frame &out,
|
||||||
expected<std::unique_ptr<Frame>, detail::SendError> exp) {
|
expected<std::unique_ptr<Frame>, detail::SendError> exp,
|
||||||
dev.init();
|
uint32_t payload) {
|
||||||
|
dev.init(notifier);
|
||||||
dev.handle(in, out);
|
dev.handle(in, out);
|
||||||
dev.enable(out);
|
dev.enable(out);
|
||||||
{
|
{ dev.react(std::move(exp)) } -> std::same_as<std::unique_ptr<Frame>>;
|
||||||
dev.react(std::move(exp))
|
dev.emit(out, payload);
|
||||||
} -> std::same_as<std::unique_ptr<Frame>>;
|
|
||||||
|
|
||||||
{ dev.pending() } -> std::convertible_to<bool>;
|
|
||||||
// Devices must clear `pending()` after `emit()` is called
|
|
||||||
dev.emit(out);
|
|
||||||
};
|
};
|
||||||
} // namespace avclan
|
} // namespace avclan
|
||||||
|
|||||||
+20
-2
@@ -6,8 +6,13 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <memory>
|
||||||
|
#include <new>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
|
||||||
|
#include "FreeRTOS.h" // IWYU pragma: export
|
||||||
|
#include "task.h"
|
||||||
|
|
||||||
#include "frame.hpp"
|
#include "frame.hpp"
|
||||||
#include "hal/stdio.h"
|
#include "hal/stdio.h"
|
||||||
#include "stdshim.hpp"
|
#include "stdshim.hpp"
|
||||||
@@ -16,7 +21,6 @@
|
|||||||
#include <array>
|
#include <array>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <new>
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
template <class T, std::uint8_t N>
|
template <class T, std::uint8_t N>
|
||||||
@@ -60,14 +64,28 @@ namespace avclan {
|
|||||||
#if defined(AVCLAN_FRAME_POOL_N)
|
#if defined(AVCLAN_FRAME_POOL_N)
|
||||||
void *Frame::operator new(std::size_t /*count*/,
|
void *Frame::operator new(std::size_t /*count*/,
|
||||||
const std::nothrow_t & /*tag*/) noexcept {
|
const std::nothrow_t & /*tag*/) noexcept {
|
||||||
return pool.acquire();
|
taskENTER_CRITICAL();
|
||||||
|
Frame *frame = pool.acquire();
|
||||||
|
taskEXIT_CRITICAL();
|
||||||
|
return frame;
|
||||||
}
|
}
|
||||||
// NOLINTNEXTLINE(misc-new-delete-overloads) false-positive
|
// NOLINTNEXTLINE(misc-new-delete-overloads) false-positive
|
||||||
void Frame::operator delete(void *ptr) noexcept {
|
void Frame::operator delete(void *ptr) noexcept {
|
||||||
|
taskENTER_CRITICAL();
|
||||||
pool.release(static_cast<Frame *>(ptr));
|
pool.release(static_cast<Frame *>(ptr));
|
||||||
|
taskEXIT_CRITICAL();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
std::unique_ptr<Frame> Frame::acquire() {
|
||||||
|
std::unique_ptr<Frame> frame(new (std::nothrow) Frame);
|
||||||
|
for (uint8_t retries = 0; !frame && retries < 3; retries++) {
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(2));
|
||||||
|
frame.reset(new (std::nothrow) Frame);
|
||||||
|
}
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
// Emit `value` as at least `width` (lowercase, as to_chars emits) hex digits,
|
// Emit `value` as at least `width` (lowercase, as to_chars emits) hex digits,
|
||||||
// zero-padded. `width` must be <= 3 (see the padding below); every call site
|
// zero-padded. `width` must be <= 3 (see the padding below); every call site
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "avclan.h"
|
#include "avclan.h"
|
||||||
|
|
||||||
@@ -30,6 +31,9 @@ struct Frame {
|
|||||||
Error::Parse parse(const uint8_t *bytes, uint8_t len);
|
Error::Parse parse(const uint8_t *bytes, uint8_t len);
|
||||||
void print(Print print) const;
|
void print(Print print) const;
|
||||||
|
|
||||||
|
// Retries every 2ms up to 3 times (max 6ms wait); null if still exhausted
|
||||||
|
static std::unique_ptr<Frame> acquire();
|
||||||
|
|
||||||
#if defined(AVCLAN_FRAME_POOL_N)
|
#if defined(AVCLAN_FRAME_POOL_N)
|
||||||
// O(1) heapless pooled allocation. Only `new (std::nothrow) Frame` is
|
// O(1) heapless pooled allocation. Only `new (std::nothrow) Frame` is
|
||||||
// supported.
|
// supported.
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
// 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
|
|
||||||
+8
-10
@@ -7,6 +7,8 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "FreeRTOS.h" // IWYU pragma: export
|
||||||
|
|
||||||
#include "avclan.h"
|
#include "avclan.h"
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
@@ -30,21 +32,16 @@ void phy_mute(bool mute);
|
|||||||
// Non-mutating (e.g. theoretically const qualified/-able)
|
// Non-mutating (e.g. theoretically const qualified/-able)
|
||||||
bool phy_is_muted(void);
|
bool phy_is_muted(void);
|
||||||
|
|
||||||
// Withhold acknowledgement. Unlike mute this leaves TX alone: a deaf device
|
// Do not respond to or log incoming frames. Can still send frames.
|
||||||
// can still manipulate the bus/send frames, it just never responds (e.g. ACK,
|
|
||||||
// etc). An empty implementation is sufficient for synchronous ports.
|
|
||||||
void phy_deafen(bool deaf);
|
void phy_deafen(bool deaf);
|
||||||
|
|
||||||
// True when there is a frame to read. This may reflect current bus state (e.g.
|
// Blocks until a frame is ready
|
||||||
// a frame can be synchronously read from the bus) or indicate that a buffered
|
void phy_wait_frame(TickType_t xTicksToWait);
|
||||||
// frame is available to "read".
|
|
||||||
bool phy_frame_pending(void);
|
|
||||||
|
|
||||||
// Bus-transaction guard: quiesce the other async sources (e.g. interrupts)
|
// Bus-transaction guard: quiesce the other async sources (e.g. interrupts)
|
||||||
// so that bus read/send timing isn't disturbed. Re-enable relevant async
|
// so that bus read/send timing isn't disturbed. Re-enable relevant async
|
||||||
// sources with `phy_guard_leave`.
|
// sources with `phy_guard_leave`.
|
||||||
// - May be a no-op on a target where contention isn't a concern.
|
// - May be a no-op on a target where concurrency/preemption isn't a concern.
|
||||||
// - May acquire a hardware lock to prevent concurrent use
|
|
||||||
void phy_guard_enter(void);
|
void phy_guard_enter(void);
|
||||||
void phy_guard_leave(void);
|
void phy_guard_leave(void);
|
||||||
|
|
||||||
@@ -67,8 +64,9 @@ void phy_guard_leave(void);
|
|||||||
*
|
*
|
||||||
* `expect_ack` indicates whether the recipient should be ACK'ing; false for
|
* `expect_ack` indicates whether the recipient should be ACK'ing; false for
|
||||||
* broadcast frames which don't have a single recipient.
|
* broadcast frames which don't have a single recipient.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Returns NO_FRAME when there is no frame to read.
|
||||||
Read phy_read_header(bool *is_unicast);
|
Read phy_read_header(bool *is_unicast);
|
||||||
Read phy_read_controller_addr(uint16_t *addr);
|
Read phy_read_controller_addr(uint16_t *addr);
|
||||||
Read phy_read_peripheral_addr(uint16_t *addr);
|
Read phy_read_peripheral_addr(uint16_t *addr);
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ extern "C" {
|
|||||||
|
|
||||||
// Generic stdio interface initialization. All user I/O goes through <stdio.h>
|
// Generic stdio interface initialization. All user I/O goes through <stdio.h>
|
||||||
// functions. Assumptions/invariants:
|
// functions. Assumptions/invariants:
|
||||||
// - stdin MUST be non-blocking (ie. a libc read yields EOF immediately when no
|
// - A libc read of stdin blocks the calling task (not the CPU) until at least
|
||||||
// input is buffered). Necessary to avoid stalling the REPL poll loop.
|
// one byte is available.
|
||||||
// - stdout is *raw*. There is no '\n' -> "\r\n" translation. The port does not
|
// - stdout is *raw*. There is no '\n' -> "\r\n" translation. The port does not
|
||||||
// change a bare LF or a binary frame payload. Writes through
|
// change a bare LF or a binary frame payload. Writes through
|
||||||
// <stdio.h> and writes through stdio_write_nonblock() must reach the same
|
// <stdio.h> and writes through stdio_write_nonblock() must reach the same
|
||||||
|
|||||||
+37
-45
@@ -4,15 +4,20 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <new>
|
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
#include "FreeRTOS.h" // IWYU pragma: export
|
||||||
|
#include "queue.h"
|
||||||
|
|
||||||
#include "avclan.h"
|
#include "avclan.h"
|
||||||
#include "bus.hpp"
|
#include "bus.hpp"
|
||||||
#include "device.hpp"
|
#include "device.hpp"
|
||||||
@@ -38,18 +43,26 @@ public:
|
|||||||
static_assert(((Devs::id != NoDevice) && ...),
|
static_assert(((Devs::id != NoDevice) && ...),
|
||||||
"a registered Device id collides with the NoDevice sentinel; "
|
"a registered Device id collides with the NoDevice sentinel; "
|
||||||
"update the sentinel value in avclan.h");
|
"update the sentinel value in avclan.h");
|
||||||
|
static_assert(sizeof...(Devs) <= 0x100,
|
||||||
|
"too many devices: Notifier packs the device index into 8 bits");
|
||||||
|
|
||||||
Peripheral(Bus &bus, uint16_t address) : bus{bus}, address_{address} {
|
static constexpr UBaseType_t EMIT_QUEUE_LEN = 2 * sizeof...(Devs);
|
||||||
|
|
||||||
|
Peripheral(Bus &bus, uint16_t address)
|
||||||
|
: bus{bus}, address_{address},
|
||||||
|
emit_requests_{xQueueCreate(EMIT_QUEUE_LEN, sizeof(uint32_t))} {
|
||||||
|
configASSERT(emit_requests_);
|
||||||
bus.init(address);
|
bus.init(address);
|
||||||
(std::get<Devs>(devices_).init(), ...);
|
(std::get<Devs>(devices_).init(Notifier{emit_requests_, index_of<Devs>()}),
|
||||||
|
...);
|
||||||
}
|
}
|
||||||
|
Peripheral(const Peripheral &) = delete;
|
||||||
|
|
||||||
uint16_t controller() const { return controller_; };
|
uint16_t controller() const { return controller_; };
|
||||||
template <DeviceInterface Dev> Dev &device() {
|
template <DeviceInterface Dev> Dev &device() {
|
||||||
return std::get<Dev>(devices_);
|
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(); };
|
||||||
|
|
||||||
@@ -59,9 +72,6 @@ public:
|
|||||||
|
|
||||||
expected<std::unique_ptr<Frame>, Error::Read>
|
expected<std::unique_ptr<Frame>, Error::Read>
|
||||||
read(Frame::Print print = Frame::Print{}) {
|
read(Frame::Print print = Frame::Print{}) {
|
||||||
if (!bus.is_active())
|
|
||||||
return unexpected{detail::Error::Read::NO_FRAME};
|
|
||||||
|
|
||||||
return bus.read(print);
|
return bus.read(print);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -72,8 +82,9 @@ public:
|
|||||||
out->control = 0xF;
|
out->control = 0xF;
|
||||||
auto err = bus.send(*out, print);
|
auto err = bus.send(*out, print);
|
||||||
if (err != Error::Send{0})
|
if (err != Error::Send{0})
|
||||||
return unexpected{
|
return unexpected{detail::SendError{.owning_device = out->owning_device,
|
||||||
detail::SendError{out->owning_device, out->reaction, err}};
|
.reaction = out->reaction,
|
||||||
|
.err = err}};
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
};
|
};
|
||||||
@@ -88,7 +99,7 @@ public:
|
|||||||
if (is_muted() || in.length < 3)
|
if (is_muted() || in.length < 3)
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
std::unique_ptr<Frame> out(new (std::nothrow) Frame);
|
std::unique_ptr<Frame> out = Frame::acquire();
|
||||||
if (!out) {
|
if (!out) {
|
||||||
puts("!! failed Frame alloc in route !!");
|
puts("!! failed Frame alloc in route !!");
|
||||||
return unexpected{Error::Read::POOL_EMPTY};
|
return unexpected{Error::Read::POOL_EMPTY};
|
||||||
@@ -207,53 +218,33 @@ public:
|
|||||||
return next;
|
return next;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Service ready devices in round-robin order
|
// Blocks until a device requests an emit; requests are served in order.
|
||||||
std::unique_ptr<Frame> poll() {
|
std::unique_ptr<Frame> poll() {
|
||||||
using U = std::unique_ptr<Frame>;
|
std::unique_ptr<Frame> out = Frame::acquire();
|
||||||
auto does_emit = [&](DeviceInterface auto &dev) -> U {
|
|
||||||
if (!dev.pending())
|
|
||||||
return {};
|
|
||||||
|
|
||||||
U out(new (std::nothrow) Frame);
|
|
||||||
if (!out) {
|
if (!out) {
|
||||||
puts("!! failed Frame alloc in poll !!");
|
puts("!! failed Frame alloc in poll !!");
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
originate(dev, *out, [](auto &d, auto &out) { d.emit(out); });
|
uint32_t val;
|
||||||
return out;
|
xQueueReceive(emit_requests_, &val, portMAX_DELAY);
|
||||||
};
|
const uint8_t index = val & 0xFF;
|
||||||
|
|
||||||
// Runtime tuple index helper
|
auto emit_d = [val](auto &d, auto &out) { d.emit(out, val >> 8); };
|
||||||
auto does_index_emit = [&](std::size_t t) -> U {
|
[&]<std::size_t... Is>(std::index_sequence<Is...>) {
|
||||||
return [&]<std::size_t... Is>(std::index_sequence<Is...>) -> U {
|
((Is == index ? originate(std::get<Is>(devices_), *out, emit_d) : void()),
|
||||||
U out;
|
...);
|
||||||
((Is == t && (out = does_emit(std::get<Is>(devices_)))) || ...);
|
|
||||||
return out;
|
|
||||||
}(std::index_sequence_for<Devs...>{});
|
}(std::index_sequence_for<Devs...>{});
|
||||||
};
|
|
||||||
|
|
||||||
constexpr std::size_t N = sizeof...(Devs);
|
|
||||||
if constexpr (N == 1) { // round-robin not needed
|
|
||||||
return does_emit(std::get<0>(devices_));
|
|
||||||
} else {
|
|
||||||
static uint8_t rr_ = 0; // round-robin cursor
|
|
||||||
const std::size_t start = rr_;
|
|
||||||
for (std::size_t t = start; t < N; ++t) // [start, N)
|
|
||||||
if (auto out = does_index_emit(t)) {
|
|
||||||
rr_ = (t + 1 == N) ? 0 : t + 1;
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
for (std::size_t t = 0; t < start; ++t) // [0, start); t+1 <= start < N
|
|
||||||
if (auto out = does_index_emit(t)) {
|
|
||||||
rr_ = t + 1;
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// Dev's position in Devs
|
||||||
|
template <class Dev> static constexpr uint8_t index_of() {
|
||||||
|
constexpr std::array is_dev{std::is_same_v<Dev, Devs>...};
|
||||||
|
return std::ranges::find(is_dev, true) - is_dev.begin();
|
||||||
|
}
|
||||||
|
|
||||||
template <Party P> void stamp(Frame &out) const {
|
template <Party P> void stamp(Frame &out) const {
|
||||||
if constexpr (P == Sender)
|
if constexpr (P == Sender)
|
||||||
out.controller_addr = address_;
|
out.controller_addr = address_;
|
||||||
@@ -271,5 +262,6 @@ private:
|
|||||||
uint16_t controller_ = 0;
|
uint16_t controller_ = 0;
|
||||||
const uint16_t address_;
|
const uint16_t address_;
|
||||||
std::tuple<Devs...> devices_;
|
std::tuple<Devs...> devices_;
|
||||||
|
QueueHandle_t emit_requests_;
|
||||||
};
|
};
|
||||||
} // namespace avclan
|
} // namespace avclan
|
||||||
|
|||||||
@@ -1,16 +1,50 @@
|
|||||||
pico_sdk_init()
|
pico_sdk_init()
|
||||||
|
|
||||||
|
include_directories(
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}
|
||||||
|
include
|
||||||
|
)
|
||||||
|
FetchContent_Declare( freertos_kernel
|
||||||
|
EXCLUDE_FROM_ALL
|
||||||
|
GIT_REPOSITORY https://github.com/FreeRTOS/FreeRTOS-Kernel.git
|
||||||
|
GIT_TAG 3a22924e0a9ddbbc8b0758881c33b3422a5cc20d # v11.3.1
|
||||||
|
)
|
||||||
|
add_library(freertos_config INTERFACE)
|
||||||
|
target_include_directories(freertos_config SYSTEM
|
||||||
|
INTERFACE
|
||||||
|
include
|
||||||
|
)
|
||||||
|
target_compile_definitions(freertos_config
|
||||||
|
INTERFACE
|
||||||
|
projCOVERAGE_TEST=0
|
||||||
|
)
|
||||||
|
target_compile_options(freertos_config
|
||||||
|
INTERFACE
|
||||||
|
$<$<COMPILE_LANGUAGE:CXX>:-Wno-volatile>
|
||||||
|
)
|
||||||
|
|
||||||
|
set( FREERTOS_PORT "GCC_RP2040" CACHE STRING "" FORCE)
|
||||||
|
FetchContent_MakeAvailable(freertos_kernel)
|
||||||
|
set(FREERTOS_KERNEL_PATH "${freertos_kernel_SOURCE_DIR}" CACHE STRING "" FORCE)
|
||||||
|
include(${freertos_kernel_SOURCE_DIR}/portable/ThirdParty/GCC/RP2040/FreeRTOS_Kernel_import.cmake)
|
||||||
|
|
||||||
target_sources(avclan PRIVATE
|
target_sources(avclan PRIVATE
|
||||||
phy.cc
|
phy.cc
|
||||||
media.cc
|
media.cc
|
||||||
cd_timer.cc
|
|
||||||
board.cc
|
board.cc
|
||||||
stdio.cc
|
stdio.cc
|
||||||
)
|
)
|
||||||
pico_generate_pio_header(avclan ${CMAKE_CURRENT_LIST_DIR}/iebus.pio)
|
pico_generate_pio_header(avclan ${CMAKE_CURRENT_LIST_DIR}/iebus.pio)
|
||||||
|
|
||||||
target_link_libraries(avclan PUBLIC
|
target_link_libraries(avclan PUBLIC
|
||||||
pico_stdlib pico_stdio_usb hardware_pio hardware_dma pico_multicore
|
hardware_pio
|
||||||
|
hardware_dma
|
||||||
|
pico_stdlib
|
||||||
|
pico_stdio_usb
|
||||||
|
pico_multicore
|
||||||
|
pico_async_context_freertos
|
||||||
|
freertos_config
|
||||||
|
FreeRTOS-Kernel-Heap4
|
||||||
)
|
)
|
||||||
pico_enable_stdio_usb(avclan 1)
|
pico_enable_stdio_usb(avclan 1)
|
||||||
|
|
||||||
@@ -36,8 +70,6 @@ set_source_files_properties(${_tinyusb_sources}
|
|||||||
unset(_tinyusb_sources)
|
unset(_tinyusb_sources)
|
||||||
|
|
||||||
pico_add_extra_outputs(mockingboard_pico)
|
pico_add_extra_outputs(mockingboard_pico)
|
||||||
pico_set_float_implementation(mockingboard_pico none)
|
|
||||||
pico_set_double_implementation(mockingboard_pico none)
|
|
||||||
|
|
||||||
pico_set_program_name(mockingboard_pico mockingboard)
|
pico_set_program_name(mockingboard_pico mockingboard)
|
||||||
pico_set_program_description(mockingboard_pico
|
pico_set_program_description(mockingboard_pico
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
#include "hal/cd_timer.h"
|
|
||||||
|
|
||||||
extern "C" void cdtimer_init(void *ptr, void(clbk)(void *),
|
|
||||||
bool(isplay)(void *)) {}
|
|
||||||
|
|
||||||
extern "C" void cdtimer_reset() {}
|
|
||||||
|
|
||||||
extern "C" void cdtimer_restore() {}
|
|
||||||
|
|
||||||
extern "C" void cdtimer_disable() {}
|
|
||||||
|
|
||||||
volatile bool cdtimer_pending_flag;
|
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
/*
|
||||||
|
* FreeRTOS V202111.00
|
||||||
|
* Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*
|
||||||
|
* http://www.FreeRTOS.org
|
||||||
|
* http://aws.amazon.com/freertos
|
||||||
|
*
|
||||||
|
* 1 tab == 4 spaces!
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef FREERTOS_CONFIG_EXAMPLES_COMMON_H
|
||||||
|
#define FREERTOS_CONFIG_EXAMPLES_COMMON_H
|
||||||
|
|
||||||
|
/*-----------------------------------------------------------
|
||||||
|
* Application specific definitions.
|
||||||
|
*
|
||||||
|
* These definitions should be adjusted for your particular hardware and
|
||||||
|
* application requirements.
|
||||||
|
*
|
||||||
|
* THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE
|
||||||
|
* FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE.
|
||||||
|
*
|
||||||
|
* See http://www.freertos.org/a00110.html
|
||||||
|
*----------------------------------------------------------*/
|
||||||
|
|
||||||
|
/* Scheduler Related */
|
||||||
|
#define configUSE_PREEMPTION 1
|
||||||
|
#define configUSE_TICKLESS_IDLE 0
|
||||||
|
#define configCPU_CLOCK_HZ (150000000UL)
|
||||||
|
#define configTICK_RATE_HZ ((TickType_t)1000)
|
||||||
|
#define configMAX_PRIORITIES 32
|
||||||
|
#define configMINIMAL_STACK_SIZE (configSTACK_DEPTH_TYPE)512
|
||||||
|
#define configIDLE_SHOULD_YIELD 1
|
||||||
|
#define configUSE_16_BIT_TICKS 0
|
||||||
|
#define configUSE_IDLE_HOOK 0
|
||||||
|
#define configUSE_TICK_HOOK 0
|
||||||
|
|
||||||
|
/* Synchronization Related */
|
||||||
|
#define configUSE_MUTEXES 1
|
||||||
|
#define configUSE_RECURSIVE_MUTEXES 1
|
||||||
|
#define configUSE_APPLICATION_TASK_TAG 0
|
||||||
|
#define configUSE_COUNTING_SEMAPHORES 1
|
||||||
|
#define configQUEUE_REGISTRY_SIZE 8
|
||||||
|
#define configUSE_QUEUE_SETS 1
|
||||||
|
#define configUSE_TIME_SLICING 1
|
||||||
|
#define configUSE_NEWLIB_REENTRANT 0
|
||||||
|
#define configENABLE_BACKWARD_COMPATIBILITY 0
|
||||||
|
#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 5
|
||||||
|
|
||||||
|
/* System */
|
||||||
|
#define configSTACK_DEPTH_TYPE uint32_t
|
||||||
|
#define configMESSAGE_BUFFER_LENGTH_TYPE size_t
|
||||||
|
|
||||||
|
/* Memory allocation related definitions. */
|
||||||
|
#ifndef configSUPPORT_STATIC_ALLOCATION
|
||||||
|
#define configSUPPORT_STATIC_ALLOCATION 0
|
||||||
|
#endif
|
||||||
|
#ifndef configSUPPORT_DYNAMIC_ALLOCATION
|
||||||
|
#define configSUPPORT_DYNAMIC_ALLOCATION 1
|
||||||
|
#endif
|
||||||
|
#define configTOTAL_HEAP_SIZE (128 * 1024)
|
||||||
|
#define configAPPLICATION_ALLOCATED_HEAP 0
|
||||||
|
|
||||||
|
/* Hook function related definitions. */
|
||||||
|
#define configCHECK_FOR_STACK_OVERFLOW 0
|
||||||
|
#define configUSE_MALLOC_FAILED_HOOK 0
|
||||||
|
#define configUSE_DAEMON_TASK_STARTUP_HOOK 0
|
||||||
|
|
||||||
|
/* Run time and task stats gathering related definitions. */
|
||||||
|
#define configGENERATE_RUN_TIME_STATS 0
|
||||||
|
#define configUSE_TRACE_FACILITY 1
|
||||||
|
#define configUSE_STATS_FORMATTING_FUNCTIONS 0
|
||||||
|
|
||||||
|
/* Co-routine related definitions. */
|
||||||
|
#define configUSE_CO_ROUTINES 0
|
||||||
|
#define configMAX_CO_ROUTINE_PRIORITIES 1
|
||||||
|
|
||||||
|
/* Software timer related definitions. */
|
||||||
|
#define configUSE_TIMERS 1
|
||||||
|
#define configTIMER_TASK_PRIORITY (configMAX_PRIORITIES - 1)
|
||||||
|
#define configTIMER_QUEUE_LENGTH 10
|
||||||
|
#define configTIMER_TASK_STACK_DEPTH 1024
|
||||||
|
|
||||||
|
/* Interrupt nesting behaviour configuration. */
|
||||||
|
/*
|
||||||
|
#define configKERNEL_INTERRUPT_PRIORITY [dependent of processor]
|
||||||
|
#define configMAX_SYSCALL_INTERRUPT_PRIORITY [dependent on processor and
|
||||||
|
application] #define configMAX_API_CALL_INTERRUPT_PRIORITY [dependent on
|
||||||
|
processor and application]
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if FREE_RTOS_KERNEL_SMP // set by the RP2xxx SMP port of FreeRTOS
|
||||||
|
/* SMP port only */
|
||||||
|
#ifndef configNUMBER_OF_CORES
|
||||||
|
#define configNUMBER_OF_CORES 2
|
||||||
|
#endif
|
||||||
|
#define configNUM_CORES configNUMBER_OF_CORES
|
||||||
|
#define configTICK_CORE 0
|
||||||
|
#define configRUN_MULTIPLE_PRIORITIES 1
|
||||||
|
#if configNUMBER_OF_CORES > 1
|
||||||
|
#define configUSE_CORE_AFFINITY 1
|
||||||
|
#endif
|
||||||
|
#define configUSE_PASSIVE_IDLE_HOOK 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* RP2040 specific */
|
||||||
|
#define configSUPPORT_PICO_SYNC_INTEROP 1
|
||||||
|
#define configSUPPORT_PICO_TIME_INTEROP 1
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
/* Define to trap errors during development. */
|
||||||
|
#define configASSERT(x) assert(x)
|
||||||
|
|
||||||
|
/* Set the following definitions to 1 to include the API function, or zero
|
||||||
|
to exclude the API function. */
|
||||||
|
#define INCLUDE_vTaskPrioritySet 1
|
||||||
|
#define INCLUDE_uxTaskPriorityGet 1
|
||||||
|
#define INCLUDE_vTaskDelete 1
|
||||||
|
#define INCLUDE_vTaskSuspend 1
|
||||||
|
#define INCLUDE_vTaskDelay 1
|
||||||
|
#define INCLUDE_xTaskDelayUntil 1
|
||||||
|
#define INCLUDE_xTaskGetSchedulerState 1
|
||||||
|
#define INCLUDE_xTaskGetCurrentTaskHandle 1
|
||||||
|
#define INCLUDE_uxTaskGetStackHighWaterMark 1
|
||||||
|
#define INCLUDE_xTaskGetIdleTaskHandle 1
|
||||||
|
#define INCLUDE_eTaskGetState 1
|
||||||
|
#define INCLUDE_xTimerPendFunctionCall 1
|
||||||
|
#define INCLUDE_xTaskAbortDelay 1
|
||||||
|
#define INCLUDE_xTaskGetHandle 1
|
||||||
|
#define INCLUDE_xTaskResumeFromISR 1
|
||||||
|
#define INCLUDE_xQueueGetMutexHolder 1
|
||||||
|
|
||||||
|
#if PICO_RP2350
|
||||||
|
#define configENABLE_MPU 0
|
||||||
|
#define configENABLE_TRUSTZONE 0
|
||||||
|
#define configRUN_FREERTOS_SECURE_ONLY 1
|
||||||
|
#define configENABLE_FPU 1
|
||||||
|
#define configMAX_SYSCALL_INTERRUPT_PRIORITY 16
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* A header file that defines trace macro can be included here. */
|
||||||
|
|
||||||
|
#endif /* FREERTOS_CONFIG_H */
|
||||||
@@ -6,5 +6,5 @@ extern "C" void media_action(MediaAction fn) {}
|
|||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
extern "C" bool media_mic_toggle(void) { return false; }
|
extern "C" bool media_mic_toggle(void) { return false; }
|
||||||
extern "C" bool media_busy(void) { return true; }
|
extern "C" bool media_busy(void) { return false; }
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -5,6 +5,9 @@
|
|||||||
#include <hardware/clocks.h>
|
#include <hardware/clocks.h>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
|
||||||
|
#include "FreeRTOS.h" // IWYU pragma: export
|
||||||
|
#include "semphr.h"
|
||||||
|
|
||||||
#include "avclan.h"
|
#include "avclan.h"
|
||||||
#include "hal/phy.h"
|
#include "hal/phy.h"
|
||||||
#include "hardware/gpio.h"
|
#include "hardware/gpio.h"
|
||||||
@@ -136,11 +139,14 @@ public:
|
|||||||
prepare_ack();
|
prepare_ack();
|
||||||
begin_frame();
|
begin_frame();
|
||||||
|
|
||||||
|
xNotice = xSemaphoreCreateCounting(RXQ_N, 0); // one give per buffered frame
|
||||||
|
configASSERT(xNotice);
|
||||||
|
|
||||||
pio_set_irq0_source_enabled(
|
pio_set_irq0_source_enabled(
|
||||||
pio_, pio_get_rx_fifo_not_empty_interrupt_source(sm_), true);
|
pio_, pio_get_rx_fifo_not_empty_interrupt_source(sm_), true);
|
||||||
irq_ = (uint)pio_get_irq_num(pio_, 0);
|
irq_ = (uint)pio_get_irq_num(pio_, 0);
|
||||||
irq_set_exclusive_handler(irq_, irq_handler);
|
irq_set_exclusive_handler(irq_, irq_handler);
|
||||||
irq_set_priority(irq_, PICO_HIGHEST_IRQ_PRIORITY);
|
irq_set_priority(irq_, configMAX_SYSCALL_INTERRUPT_PRIORITY);
|
||||||
irq_set_enabled(irq_, true);
|
irq_set_enabled(irq_, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,6 +184,10 @@ public:
|
|||||||
// Only valid while frame_pending().
|
// Only valid while frame_pending().
|
||||||
const RxFrame &frame() const { return rxq_[rxq_tail_]; }
|
const RxFrame &frame() const { return rxq_[rxq_tail_]; }
|
||||||
|
|
||||||
|
void wait_frame(TickType_t xTicksToWait = portMAX_DELAY) {
|
||||||
|
xSemaphoreTake(xNotice, xTicksToWait);
|
||||||
|
}
|
||||||
|
|
||||||
void release() { rxq_tail_ = (rxq_tail_ + 1) & (RXQ_N - 1); }
|
void release() { rxq_tail_ = (rxq_tail_ + 1) & (RXQ_N - 1); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -189,6 +199,7 @@ private:
|
|||||||
static void __time_critical_func(irq_handler)() { instance_->isr(); }
|
static void __time_critical_func(irq_handler)() { instance_->isr(); }
|
||||||
|
|
||||||
void __time_critical_func(isr)() {
|
void __time_critical_func(isr)() {
|
||||||
|
xHigherPriorityTaskWoken = false;
|
||||||
// Every field after the controller address is followed by an ack slot,
|
// Every field after the controller address is followed by an ack slot,
|
||||||
// which the SM consumes whether or not it drives it.
|
// which the SM consumes whether or not it drives it.
|
||||||
const auto next_after_ack = [this](RxField next, uint8_t bits) {
|
const auto next_after_ack = [this](RxField next, uint8_t bits) {
|
||||||
@@ -219,15 +230,20 @@ private:
|
|||||||
// its own acks, and a copy here would only cost a queue slot. A frame
|
// its own acks, and a copy here would only cost a queue slot. A frame
|
||||||
// we owe an ack for is refused when there is no room for it -- the
|
// we owe an ack for is refused when there is no room for it -- the
|
||||||
// NAK asks the sender to send it again, rather than losing it behind
|
// NAK asks the sender to send it again, rather than losing it behind
|
||||||
// an ack we can't honour. Both come before the field checks: a frame
|
// an ack we can't honour. One we will not ack at all goes the same
|
||||||
// being given up needs no parity verdict, and reporting one would
|
// way: deaf we keep nothing, and a lost arbitration leaves the
|
||||||
// name the wrong cause for the same NAK.
|
// winner's frame unacked until we rearm. Not mute -- a muted device
|
||||||
|
// still listens, and its log takes a duplicate over a gap. They come
|
||||||
|
// before the field checks: a frame being given up needs no parity
|
||||||
|
// verdict, and reporting one would name the wrong cause for the same
|
||||||
|
// NAK.
|
||||||
const bool ours =
|
const bool ours =
|
||||||
building_.controller_addr == (uint16_t)(self_addrp_ >> 1);
|
building_.controller_addr == (uint16_t)(self_addrp_ >> 1);
|
||||||
|
const bool not_acking = deafened_ || transmitting_;
|
||||||
const bool refuse =
|
const bool refuse =
|
||||||
!ours && queue_full() && pio_interrupt_get(pio_, ack_latch);
|
!ours && queue_full() && pio_interrupt_get(pio_, ack_latch);
|
||||||
|
|
||||||
if (ours || refuse) {
|
if (ours || not_acking || refuse) {
|
||||||
if (refuse)
|
if (refuse)
|
||||||
rxq_refused_ = rxq_refused_ + 1;
|
rxq_refused_ = rxq_refused_ + 1;
|
||||||
// Withdrawing the ack is part of giving the frame up; its slot is
|
// Withdrawing the ack is part of giving the frame up; its slot is
|
||||||
@@ -288,6 +304,7 @@ private:
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start (or restart) a frame. The start-bit block is dispatched like any
|
// Start (or restart) a frame. The start-bit block is dispatched like any
|
||||||
@@ -360,6 +377,7 @@ private:
|
|||||||
} else {
|
} else {
|
||||||
rxq_[rxq_head_] = building_;
|
rxq_[rxq_head_] = building_;
|
||||||
rxq_head_ = (rxq_head_ + 1) & (RXQ_N - 1);
|
rxq_head_ = (rxq_head_ + 1) & (RXQ_N - 1);
|
||||||
|
xSemaphoreGiveFromISR(xNotice, &xHigherPriorityTaskWoken);
|
||||||
}
|
}
|
||||||
begin_frame();
|
begin_frame();
|
||||||
}
|
}
|
||||||
@@ -393,6 +411,9 @@ private:
|
|||||||
// NAK'd for want of room; the sender still owns the frame and sends it again.
|
// NAK'd for want of room; the sender still owns the frame and sends it again.
|
||||||
volatile uint32_t rxq_refused_ = 0;
|
volatile uint32_t rxq_refused_ = 0;
|
||||||
|
|
||||||
|
SemaphoreHandle_t xNotice = nullptr;
|
||||||
|
BaseType_t xHigherPriorityTaskWoken = false;
|
||||||
|
|
||||||
RxFrame building_ = {};
|
RxFrame building_ = {};
|
||||||
RxField state_ = RxField::Broadcast;
|
RxField state_ = RxField::Broadcast;
|
||||||
uint8_t data_i_ = 0;
|
uint8_t data_i_ = 0;
|
||||||
@@ -795,7 +816,9 @@ extern "C" bool phy_is_muted() { return phy.is_muted(); }
|
|||||||
|
|
||||||
extern "C" void phy_deafen(bool deaf) { phy.rx().deafen(deaf); }
|
extern "C" void phy_deafen(bool deaf) { phy.rx().deafen(deaf); }
|
||||||
|
|
||||||
extern "C" bool phy_frame_pending() { return phy.rx().frame_pending(); }
|
extern "C" void phy_wait_frame(TickType_t xTicksToWait) {
|
||||||
|
phy.rx().wait_frame(xTicksToWait);
|
||||||
|
}
|
||||||
|
|
||||||
extern "C" void phy_guard_enter() {}
|
extern "C" void phy_guard_enter() {}
|
||||||
extern "C" void phy_guard_leave() {}
|
extern "C" void phy_guard_leave() {}
|
||||||
@@ -807,6 +830,8 @@ extern "C" void phy_guard_leave() {}
|
|||||||
// call of the frame; the caller abandons the frame on it, which releases it.
|
// call of the frame; the caller abandons the frame on it, which releases it.
|
||||||
|
|
||||||
extern "C" Read phy_read_header(bool *is_unicast) {
|
extern "C" Read phy_read_header(bool *is_unicast) {
|
||||||
|
if (!phy.rx().frame_pending())
|
||||||
|
return Read::NO_FRAME;
|
||||||
const IEBusRx::RxFrame &frame = phy.rx().frame();
|
const IEBusRx::RxFrame &frame = phy.rx().frame();
|
||||||
if (frame.err != Read{0}) {
|
if (frame.err != Read{0}) {
|
||||||
phy.rx().release();
|
phy.rx().release();
|
||||||
|
|||||||
@@ -3,11 +3,14 @@
|
|||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "FreeRTOS.h" // IWYU pragma: export
|
||||||
|
#include "semphr.h"
|
||||||
|
|
||||||
#include "hal/stdio.h"
|
#include "hal/stdio.h"
|
||||||
#include "pico/stdio.h"
|
#include "pico/stdio.h"
|
||||||
#include "pico/stdio_usb.h"
|
#include "pico/stdio_usb.h"
|
||||||
#include "pico/time.h"
|
#include "pico/time.h"
|
||||||
#include "tusb.h" // IWYU pragma: keep
|
#include "tusb.h" // IWYU pragma: export
|
||||||
|
|
||||||
// stdio_write_nonblock() is all-or-nothing. The CDC TX FIFO must hold the
|
// stdio_write_nonblock() is all-or-nothing. The CDC TX FIFO must hold the
|
||||||
// largest buffer the app writes; that is a text frame log line (Frame::print),
|
// largest buffer the app writes; that is a text frame log line (Frame::print),
|
||||||
@@ -26,10 +29,22 @@ bool drop_indicator_pending = false;
|
|||||||
void queue(const char *str, int len) {
|
void queue(const char *str, int len) {
|
||||||
stdio_put_string(str, len, false, false);
|
stdio_put_string(str, len, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SemaphoreHandle_t rx_ready = nullptr;
|
||||||
|
|
||||||
|
// Called from the stdio_usb background IRQ after each tud_task() with RX data
|
||||||
|
// pending.
|
||||||
|
void on_rx([[maybe_unused]] void *param) {
|
||||||
|
BaseType_t woken = false;
|
||||||
|
xSemaphoreGiveFromISR(rx_ready, &woken);
|
||||||
|
portYIELD_FROM_ISR(woken);
|
||||||
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
extern "C" void stdio_init() {
|
extern "C" void stdio_init() {
|
||||||
|
rx_ready = xSemaphoreCreateBinary();
|
||||||
stdio_usb_init();
|
stdio_usb_init();
|
||||||
|
stdio_set_chars_available_callback(on_rx, nullptr);
|
||||||
stdio_set_translate_crlf(&stdio_usb, false);
|
stdio_set_translate_crlf(&stdio_usb, false);
|
||||||
// pico_stdio wraps printf/puts/putchar straight onto the CDC, but not
|
// pico_stdio wraps printf/puts/putchar straight onto the CDC, but not
|
||||||
// fputs/fwrite. Unbuffered stdout keeps the newlib path in step with them
|
// fputs/fwrite. Unbuffered stdout keeps the newlib path in step with them
|
||||||
@@ -37,21 +52,19 @@ extern "C" void stdio_init() {
|
|||||||
setvbuf(stdout, nullptr, _IONBF, 0);
|
setvbuf(stdout, nullptr, _IONBF, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Overrides the SDK's weak newlib hook, which waits forever. EAGAIN rather than
|
// Overrides the SDK's weak newlib hook, which busy-waits.
|
||||||
// a 0-length read: newlib's refill skips a stream that has ever seen EOF.
|
|
||||||
extern "C" int _read(int handle, char *buffer, int length) {
|
extern "C" int _read(int handle, char *buffer, int length) {
|
||||||
if (handle != STDIN_FILENO) {
|
if (handle != STDIN_FILENO) {
|
||||||
errno = EBADF;
|
errno = EBADF;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
const int count = stdio_get_until(buffer, length, make_timeout_time_us(0));
|
const int count = stdio_get_until(buffer, length, make_timeout_time_us(0));
|
||||||
if (count < 0) {
|
if (count > 0)
|
||||||
errno = EAGAIN;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
|
xSemaphoreTake(rx_ready, portMAX_DELAY);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" bool stdio_write_nonblock(const void *buf, uint8_t len) {
|
extern "C" bool stdio_write_nonblock(const void *buf, uint8_t len) {
|
||||||
|
|||||||
+22
-107
@@ -3,124 +3,39 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <concepts>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <limits>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <type_traits>
|
|
||||||
|
|
||||||
namespace detail {
|
#include "FreeRTOS.h" // IWYU pragma: export
|
||||||
template <class T, auto N> struct Deleter;
|
#include "queue.h"
|
||||||
}
|
|
||||||
|
|
||||||
template <class T, std::integral auto N, bool Owning = false,
|
|
||||||
class Deleter = std::default_delete<T>>
|
|
||||||
requires((N & (N - 1)) == 0 && N <= std::numeric_limits<uint8_t>::max() &&
|
|
||||||
// push() drops the passed-in deleter and pop() fabricates a fresh
|
|
||||||
// one, so a deleter must either derive its state from the queue
|
|
||||||
// itself (the pool Deleter) or carry no state at all
|
|
||||||
(std::is_same_v<Deleter, detail::Deleter<T, N>> ||
|
|
||||||
(std::is_empty_v<Deleter> &&
|
|
||||||
std::is_nothrow_default_constructible_v<Deleter>)))
|
|
||||||
class Queue {
|
|
||||||
friend detail::Deleter<T, N>;
|
|
||||||
|
|
||||||
|
// Owning FIFO of `T`s, safe to share between tasks
|
||||||
|
template <class T> class Queue {
|
||||||
public:
|
public:
|
||||||
// Only empty construction is allowed for non-Owning, non-pool deleters
|
explicit Queue(UBaseType_t length)
|
||||||
constexpr Queue()
|
: handle{xQueueCreate(length, sizeof(T *))} {
|
||||||
requires(!Owning && !std::is_same_v<Deleter, detail::Deleter<T, N>>)
|
configASSERT(handle);
|
||||||
: owner{nullptr} {}
|
}
|
||||||
Queue(const Queue &) = delete;
|
Queue(const Queue &) = delete;
|
||||||
|
|
||||||
// Moving is unsupported due to being self-referential
|
// Blocks until accepted (INCLUDE_vTaskSuspend: portMAX_DELAY never times out)
|
||||||
Queue(Queue &&) = delete;
|
void push(std::unique_ptr<T> x) {
|
||||||
Queue &operator=(Queue &&) = delete;
|
T *ptr = x.release();
|
||||||
|
xQueueSend(handle, &ptr, portMAX_DELAY);
|
||||||
// Construct from pre-defined storage
|
|
||||||
constexpr Queue(T (&items)[N])
|
|
||||||
requires(Owning) && std::same_as<Deleter, detail::Deleter<T, N>>
|
|
||||||
: owner{this}, write{N} {
|
|
||||||
for (uint8_t i = 0; i < N; ++i)
|
|
||||||
buf[i] = &items[i];
|
|
||||||
}
|
|
||||||
// Construct non-Owning empty from Owning queue
|
|
||||||
constexpr Queue(Queue<T, N, true, Deleter> &queue)
|
|
||||||
requires(!Owning)
|
|
||||||
: owner{&queue} {}
|
|
||||||
|
|
||||||
bool isEmpty() const { return write == read; }
|
|
||||||
uint8_t size() const { return write - read; }
|
|
||||||
uint8_t capacity() const { return N; }
|
|
||||||
bool isFull() const { return size() == capacity(); }
|
|
||||||
|
|
||||||
uint8_t push(std::unique_ptr<T, Deleter> x)
|
|
||||||
requires(!Owning)
|
|
||||||
{
|
|
||||||
if constexpr (!std::is_same_v<Deleter, detail::Deleter<T, N>>) {
|
|
||||||
// structurally unnecessary for detail::Deleter, where empty construction
|
|
||||||
// is only from same size parent
|
|
||||||
if (isFull())
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!x)
|
std::unique_ptr<T> pop(TickType_t wait = portMAX_DELAY) {
|
||||||
return 1;
|
T *ptr = nullptr;
|
||||||
|
xQueueReceive(handle, &ptr, wait);
|
||||||
if constexpr (std::is_same_v<Deleter, detail::Deleter<T, N>>) {
|
return std::unique_ptr<T>(ptr);
|
||||||
if (!x.get_deleter().is_owned_by(owner))
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
claim(x.release());
|
// The front item stays owned by the queue
|
||||||
|
const T *peek(TickType_t wait = portMAX_DELAY) const {
|
||||||
return 0;
|
T *ptr = nullptr;
|
||||||
}
|
xQueuePeek(handle, &ptr, wait);
|
||||||
|
return ptr;
|
||||||
const T *peek() const {
|
|
||||||
if (isEmpty())
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
return buf[mask(read)];
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<T, Deleter> pop() {
|
|
||||||
if (isEmpty())
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
if constexpr (!std::is_same_v<Deleter, detail::Deleter<T, N>>)
|
|
||||||
return {buf[mask(read++)], Deleter{}};
|
|
||||||
else if constexpr (Owning)
|
|
||||||
return {buf[mask(read++)], Deleter{this}};
|
|
||||||
else
|
|
||||||
return {buf[mask(read++)], Deleter{owner}};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint8_t mask(uint8_t pos) const { return pos & (N - 1); }
|
QueueHandle_t handle;
|
||||||
void claim(T *x) { buf[mask(write++)] = x; }
|
|
||||||
|
|
||||||
std::array<T *, N> buf = {};
|
|
||||||
Queue<T, N, true, Deleter> *const owner;
|
|
||||||
uint8_t read = 0;
|
|
||||||
uint8_t write = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <class T, auto N, class D>
|
|
||||||
Queue(Queue<T, N, true, D> &) -> Queue<T, N, false, D>;
|
|
||||||
template <class T, auto N>
|
|
||||||
Queue(T (&items)[N]) -> Queue<T, N, true, detail::Deleter<T, N>>;
|
|
||||||
|
|
||||||
namespace detail {
|
|
||||||
template <class T, auto N> struct Deleter {
|
|
||||||
Deleter() = default;
|
|
||||||
constexpr Deleter(Queue<T, N, true, Deleter> *owner) : owner{owner} {}
|
|
||||||
void operator()(T *x) const { owner->claim(x); }
|
|
||||||
constexpr bool is_owned_by(const Queue<T, N, true, Deleter> *parent) const {
|
|
||||||
return owner == parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
Queue<T, N, true, Deleter> *const owner = nullptr;
|
|
||||||
};
|
|
||||||
} // namespace detail
|
|
||||||
|
|||||||
+90
-38
@@ -7,9 +7,11 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <new>
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
#include "FreeRTOS.h" // IWYU pragma: export
|
||||||
|
#include "task.h"
|
||||||
|
|
||||||
#include "cdchanger.hpp"
|
#include "cdchanger.hpp"
|
||||||
#include "frame.hpp"
|
#include "frame.hpp"
|
||||||
#include "hal/board.h"
|
#include "hal/board.h"
|
||||||
@@ -18,17 +20,30 @@
|
|||||||
#include "queue.hpp"
|
#include "queue.hpp"
|
||||||
#include "stdshim.hpp"
|
#include "stdshim.hpp"
|
||||||
|
|
||||||
const char *const offon[] = {"OFF", "ON"};
|
|
||||||
|
|
||||||
constexpr uint8_t CACHE_SIZE = AVCLAN_MSG_QUEUE_SIZE;
|
|
||||||
static_assert(CACHE_SIZE >= AVCLAN_FRAME_POOL_N,
|
|
||||||
"CACHE_SIZE must be >= avclan::Frame allocator pool capacity");
|
|
||||||
|
|
||||||
using namespace avclan;
|
using namespace avclan;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constinit Queue<Frame, CACHE_SIZE> incoming;
|
using Periph = Peripheral<CDChanger>;
|
||||||
constinit Queue<Frame, CACHE_SIZE> outgoing;
|
|
||||||
|
const char *const offon[] = {"OFF", "ON"};
|
||||||
|
|
||||||
|
constexpr uint8_t CACHE_SIZE = AVCLAN_MSG_QUEUE_SIZE;
|
||||||
|
// A queue that can hold the entire Frame pool is never full, so the sender's
|
||||||
|
// blocking push of a follow-up frame onto its own queue can't deadlock
|
||||||
|
static_assert(CACHE_SIZE >= AVCLAN_FRAME_POOL_N,
|
||||||
|
"CACHE_SIZE must be >= avclan::Frame allocator pool capacity");
|
||||||
|
|
||||||
|
// Must be ranked such that queues deterministically tend to empty
|
||||||
|
constexpr UBaseType_t SendPriority = tskIDLE_PRIORITY + 3;
|
||||||
|
constexpr UBaseType_t RoutePriority = tskIDLE_PRIORITY + 2;
|
||||||
|
constexpr UBaseType_t PollPriority = tskIDLE_PRIORITY + 2;
|
||||||
|
constexpr UBaseType_t ReceivePriority = tskIDLE_PRIORITY + 1;
|
||||||
|
|
||||||
|
struct Context {
|
||||||
|
Periph &peripheral;
|
||||||
|
Queue<Frame> &incoming;
|
||||||
|
Queue<Frame> &outgoing;
|
||||||
|
};
|
||||||
|
|
||||||
uint8_t hexChars[2];
|
uint8_t hexChars[2];
|
||||||
uint8_t hexDigit = 0; // current digit being written to hexChars
|
uint8_t hexDigit = 0; // current digit being written to hexChars
|
||||||
@@ -61,48 +76,59 @@ void set_flag(bool *flag, bool val, const char *msg) {
|
|||||||
|
|
||||||
void Setup();
|
void Setup();
|
||||||
void print_help();
|
void print_help();
|
||||||
} // namespace
|
|
||||||
|
|
||||||
int main() {
|
[[noreturn]] void vReceiverTask(void *pvParameters) {
|
||||||
Bus phy;
|
|
||||||
using enum Action;
|
|
||||||
using enum Device;
|
|
||||||
Peripheral<CDChanger> peripheral(phy, 0x360);
|
|
||||||
using Print = Frame::Print;
|
using Print = Frame::Print;
|
||||||
|
auto &[peripheral, incoming, outgoing] =
|
||||||
Setup();
|
*static_cast<Context *>(pvParameters);
|
||||||
print_help();
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (auto msg = peripheral.read(Print{.print = printAllFrames,
|
if (auto msg = peripheral.read(Print{.print = printAllFrames,
|
||||||
.binary = printBinary,
|
.binary = printBinary,
|
||||||
.verbose = verbose}))
|
.verbose = verbose}))
|
||||||
incoming.push(std::move(*msg));
|
incoming.push(std::move(*msg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (const auto *in = incoming.peek()) {
|
[[noreturn]] void vRoutingTask(void *pvParameters) {
|
||||||
|
auto &[peripheral, incoming, outgoing] =
|
||||||
|
*static_cast<Context *>(pvParameters);
|
||||||
|
while (true) {
|
||||||
|
const Frame *in = incoming.peek();
|
||||||
if (auto resp = peripheral.route(*in)) {
|
if (auto resp = peripheral.route(*in)) {
|
||||||
incoming.pop();
|
incoming.pop();
|
||||||
if (*resp) {
|
if (*resp)
|
||||||
outgoing.push(std::move(*resp));
|
outgoing.push(std::move(*resp));
|
||||||
continue; // route can be long; re-check the bus before poll/send
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[noreturn]] void vPollTask(void *pvParameters) {
|
||||||
|
auto &[peripheral, incoming, outgoing] =
|
||||||
|
*static_cast<Context *>(pvParameters);
|
||||||
|
while (true) {
|
||||||
if (auto msg = peripheral.poll())
|
if (auto msg = peripheral.poll())
|
||||||
outgoing.push(std::move(msg));
|
outgoing.push(std::move(msg));
|
||||||
|
}
|
||||||
if (auto out = outgoing.pop()) {
|
|
||||||
auto result =
|
|
||||||
peripheral.send(std::move(out), Print{.print = printAllFrames,
|
|
||||||
.binary = printBinary});
|
|
||||||
if (auto next = peripheral.react(std::move(result)))
|
|
||||||
outgoing.push(std::move(next));
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// stdin must be non-blocking: yielding EOF when idle/empty
|
[[noreturn]] void vSenderTask(void *pvParameters) {
|
||||||
|
using Print = Frame::Print;
|
||||||
|
auto &[peripheral, incoming, outgoing] =
|
||||||
|
*static_cast<Context *>(pvParameters);
|
||||||
|
while (true) {
|
||||||
|
auto result = peripheral.send(
|
||||||
|
outgoing.pop(), Print{.print = printAllFrames, .binary = printBinary});
|
||||||
|
if (auto next = peripheral.react(std::move(result)))
|
||||||
|
outgoing.push(std::move(next));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[noreturn]] void vREPLTask(void *pvParameters) {
|
||||||
|
using enum Action;
|
||||||
|
using enum Device;
|
||||||
|
auto &[peripheral, incoming, outgoing] =
|
||||||
|
*static_cast<Context *>(pvParameters);
|
||||||
|
while (true) {
|
||||||
if (int readkey = fgetc(stdin); readkey != EOF) {
|
if (int readkey = fgetc(stdin); readkey != EOF) {
|
||||||
switch (readkey) {
|
switch (readkey) {
|
||||||
case '?': print_help(); break;
|
case '?': print_help(); break;
|
||||||
@@ -121,7 +147,7 @@ int main() {
|
|||||||
case 'x': set_flag(&printBinary, false, "Binary:"); break;
|
case 'x': set_flag(&printBinary, false, "Binary:"); break;
|
||||||
|
|
||||||
case 'E': // Beep
|
case 'E': // Beep
|
||||||
if (auto out = std::unique_ptr<Frame>(new (std::nothrow) Frame)) {
|
if (auto out = Frame::acquire()) {
|
||||||
out->is_unicast = true;
|
out->is_unicast = true;
|
||||||
out->peripheral_addr = peripheral.controller();
|
out->peripheral_addr = peripheral.controller();
|
||||||
{
|
{
|
||||||
@@ -136,7 +162,7 @@ int main() {
|
|||||||
puts("!! failed Frame alloc for Beep !! ");
|
puts("!! failed Frame alloc for Beep !! ");
|
||||||
break;
|
break;
|
||||||
case 'P':
|
case 'P':
|
||||||
if (auto out = std::unique_ptr<Frame>(new (std::nothrow) Frame)) {
|
if (auto out = Frame::acquire()) {
|
||||||
out->is_unicast = true;
|
out->is_unicast = true;
|
||||||
out->peripheral_addr = peripheral.controller();
|
out->peripheral_addr = peripheral.controller();
|
||||||
{
|
{
|
||||||
@@ -235,8 +261,7 @@ int main() {
|
|||||||
if (readSeq && seqIdx > 0) {
|
if (readSeq && seqIdx > 0) {
|
||||||
if (readBinary) {
|
if (readBinary) {
|
||||||
if (data_tmp[seqIdx - 1] == 0x17) {
|
if (data_tmp[seqIdx - 1] == 0x17) {
|
||||||
if (auto out =
|
if (auto out = Frame::acquire()) {
|
||||||
std::unique_ptr<Frame>(new (std::nothrow) Frame)) {
|
|
||||||
if (out->parse(data_tmp, --seqIdx) ==
|
if (out->parse(data_tmp, --seqIdx) ==
|
||||||
Frame::Error::Parse{0}) {
|
Frame::Error::Parse{0}) {
|
||||||
out->reaction = 1;
|
out->reaction = 1;
|
||||||
@@ -249,7 +274,7 @@ int main() {
|
|||||||
goto DEFAULT; // reading binary and this is a real data byte;
|
goto DEFAULT; // reading binary and this is a real data byte;
|
||||||
// fall through to default
|
// fall through to default
|
||||||
} else { // ASCII message
|
} else { // ASCII message
|
||||||
if (auto out = std::unique_ptr<Frame>(new (std::nothrow) Frame)) {
|
if (auto out = Frame::acquire()) {
|
||||||
const uint8_t sendLen =
|
const uint8_t sendLen =
|
||||||
seqIdx <= Frame::MAXLENGTH ? seqIdx : Frame::MAXLENGTH;
|
seqIdx <= Frame::MAXLENGTH ? seqIdx : Frame::MAXLENGTH;
|
||||||
out->is_unicast = seqIsUnicast;
|
out->is_unicast = seqIsUnicast;
|
||||||
@@ -322,7 +347,34 @@ int main() {
|
|||||||
clearerr(stdin);
|
clearerr(stdin);
|
||||||
} // if (readkey != EOF)
|
} // if (readkey != EOF)
|
||||||
}
|
}
|
||||||
return 0;
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
Setup();
|
||||||
|
print_help();
|
||||||
|
|
||||||
|
// Static: the scheduler reclaims main's stack for ISRs
|
||||||
|
static Bus phy;
|
||||||
|
static Periph peripheral(phy, 0x360);
|
||||||
|
static Queue<Frame> incoming(CACHE_SIZE);
|
||||||
|
static Queue<Frame> outgoing(CACHE_SIZE);
|
||||||
|
static Context ctx{
|
||||||
|
.peripheral = peripheral, .incoming = incoming, .outgoing = outgoing};
|
||||||
|
|
||||||
|
xTaskCreateAffinitySet(vSenderTask, "send/react", configMINIMAL_STACK_SIZE,
|
||||||
|
&ctx, SendPriority, 0b01, nullptr);
|
||||||
|
xTaskCreateAffinitySet(vRoutingTask, "router", configMINIMAL_STACK_SIZE, &ctx,
|
||||||
|
RoutePriority, 0b01, nullptr);
|
||||||
|
xTaskCreateAffinitySet(vPollTask, "poll", configMINIMAL_STACK_SIZE, &ctx,
|
||||||
|
PollPriority, 0b01, nullptr);
|
||||||
|
xTaskCreateAffinitySet(vReceiverTask, "receiver", configMINIMAL_STACK_SIZE,
|
||||||
|
&ctx, ReceivePriority, 0b01, nullptr);
|
||||||
|
xTaskCreateAffinitySet(vREPLTask, "repl", configMINIMAL_STACK_SIZE, &ctx,
|
||||||
|
tskIDLE_PRIORITY, 0b01, nullptr);
|
||||||
|
|
||||||
|
vTaskStartScheduler();
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|||||||
Reference in New Issue
Block a user