Parameterise Peripheral by its Devices and add CDChanger as one

This commit is contained in:
Allen Hill
2026-06-26 20:10:35 -07:00
parent d98cc8acad
commit 66bbf8639e
21 changed files with 1047 additions and 1022 deletions
+26 -18
View File
@@ -1,10 +1,32 @@
---
Checks: "-*,clang-diagnostic-*,clang-analyzer-*,-clang-analyzer-core.NullDereference,bugprone-*,misc-*,modernize-*,performance-*"
Checks: |
-*,
clang-diagnostic-*,
clang-analyzer-*,
-clang-analyzer-core.NullDereference,
bugprone-*,
misc-*,
modernize-*,
performance-*,
readability-*,
-modernize-use-nodiscard,
-modernize-use-trailing-return-type,
-readability-braces-around-statements,
WarningsAsErrors: ""
HeaderFilterRegex: ""
AnalyzeTemporaryDtors: false
ExcludeHeaderFilterRegex: 'out/build'
HeaderFilterRegex: '^src/.*'
SystemHeaders: false
# AnalyzeTemporaryDtors: false
FormatStyle: none
CheckOptions:
- key: readability-magic-numbers.IgnorePowersOf2IntegerValues
value: "true"
- key: readability-magic-numbers.IgnoredIntegerValues
value: "3;10;0x80;0xc0;0xFF;0x7f;0xf"
- key: readability-identifier-length.IgnoredVariableNames
value: "(in|to|id)"
- key: readability-identifier-length.IgnoredParameterNames
value: "(in|to)"
- key: bugprone-argument-comment.CommentBoolLiterals
value: "0"
- key: bugprone-argument-comment.CommentCharacterLiterals
@@ -31,8 +53,6 @@ CheckOptions:
value: __builtin_expect
- key: bugprone-dangling-handle.HandleClasses
value: "std::basic_string_view;std::experimental::basic_string_view"
- key: bugprone-dynamic-static-initializers.HeaderFileExtensions
value: ";h;hh;hpp;hxx"
- key: bugprone-easily-swappable-parameters.IgnoredParameterNames
value: '"";iterator;Iterator;begin;Begin;end;End;first;First;last;Last;lhs;LHS;rhs;RHS'
- key: bugprone-easily-swappable-parameters.IgnoredParameterTypeSuffixes
@@ -105,10 +125,6 @@ CheckOptions:
value: llvm
- key: bugprone-suspicious-enum-usage.StrictMode
value: "false"
- key: bugprone-suspicious-include.HeaderFileExtensions
value: ";h;hh;hpp;hxx"
- key: bugprone-suspicious-include.ImplementationFileExtensions
value: "c;cc;cpp;cxx"
- key: bugprone-suspicious-missing-comma.MaxConcatenatedTokens
value: "5"
- key: bugprone-suspicious-missing-comma.RatioThreshold
@@ -151,19 +167,13 @@ CheckOptions:
value: "false"
- key: llvm-qualified-auto.AddConstToQualified
value: "false"
- key: misc-definitions-in-headers.HeaderFileExtensions
value: ";h;hh;hpp;hxx"
- key: misc-definitions-in-headers.UseHeaderFileExtension
value: "true"
- key: misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic
value: "false"
- key: misc-non-private-member-variables-in-classes.IgnorePublicMemberVariables
value: "false"
- key: misc-throw-by-value-catch-by-reference.CheckThrowTemporaries
value: "true"
- key: misc-throw-by-value-catch-by-reference.MaxSize
value: "-1"
- key: misc-throw-by-value-catch-by-reference.WarnOnLargeObjects
- key: misc-throw-by-value-catch-by-reference.WarnOnLargeObject
value: "false"
- key: misc-uniqueptr-reset-release.IncludeStyle
value: llvm
@@ -243,8 +253,6 @@ CheckOptions:
value: "true"
- key: modernize-use-equals-delete.IgnoreMacros
value: "true"
- key: modernize-use-nodiscard.ReplacementString
value: "[[nodiscard]]"
- key: modernize-use-noexcept.ReplacementString
value: ""
- key: modernize-use-noexcept.UseNoexceptFalse
+2 -3
View File
@@ -8,6 +8,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
add_compile_options(
-Wall -Wswitch-enum -Werror
$<$<COMPILE_LANGUAGE:CXX>:-fno-threadsafe-statics>
$<$<COMPILE_LANGUAGE:CXX>:-fno-exceptions>
$<$<COMPILE_LANGUAGE:CXX>:-fno-rtti>
$<$<CONFIG:Debug>:-fanalyzer>)
@@ -32,9 +33,7 @@ set(AVCLAN_TARGET avr-attiny3216 CACHE STRING "Hardware target (port) to build")
# are contributed by its subdirectory.
add_library(avclan STATIC
src/avclan/avclan_frame.c
src/avclan/avclan_protocol.c
src/avclan/cdchanger.c
src/avclan/peripheral.cc
src/avclan/cdchanger.cc
src/avclan/bus.cc
)
+35
View File
@@ -0,0 +1,35 @@
// Copyright (C) 2026 Allen Hill <allenofthehills@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <cstdint>
namespace avclan::detail {
struct Error {
// Error enums are ordered such that a lower numeric value corresponds to
// more progress/success before an error occured, with 0 being no errors
enum class Read : uint8_t {
BAD_DATA_PARITY = 0x01,
BAD_LENGTH_RANGE,
BAD_LENGTH_PARITY,
BAD_PERIPHERAL_PARITY,
BAD_CONTROLLER_PARITY,
BAD_CONTROL_PARITY,
BAD_PARITY, // non-specific bad parity
STARTBIT_TOO_SHORT,
STARTBIT_TOO_LONG,
BAD_STARTBIT,
};
enum class Send : uint8_t {
NAK_DATA = 0x01,
NAK_MESSAGE_LENGTH,
NAK_CONTROL,
NAK_ADDRESS,
NAK, // non-specific NAK
BUSY,
MUTED,
};
};
} // namespace avclan::detail
+1
View File
@@ -117,6 +117,7 @@ typedef struct print_struct {
typedef struct AVCLAN_frame_struct {
uint8_t reaction;
uint8_t owning_device;
bool is_unicast;
uint16_t controller_addr; // formerly "master"
uint16_t peripheral_addr; // formerly "slave"
-35
View File
@@ -61,38 +61,3 @@ void AVCLan_Measure(void);
#ifdef __cplusplus
}
#endif
#ifdef __cplusplus
template <class T, auto N> avclan_bit_t AVCLAN_sendbits(T bits);
template <class T, auto N> avclan_bit_t AVCLAN_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 <= 8)
avclan_bit_t AVCLAN_sendbits(uint8_t bits) {
return AVCLAN_sendbitsi(&bits, N);
}
template <auto N>
requires(N <= 16)
avclan_bit_t AVCLAN_sendbits(uint16_t bits) {
return AVCLAN_sendbitsl(&bits, N);
}
template <> inline avclan_bit_t AVCLAN_sendbits<8>(uint8_t byte) {
return AVCLAN_sendbyte(&byte);
}
template <auto N>
requires(N <= 8)
avclan_bit_t AVCLAN_readbits(uint8_t *bits) {
return static_cast<avclan_bit_t>(AVCLAN_readbitsi(bits, N));
}
template <auto N>
requires(N <= 16)
avclan_bit_t AVCLAN_readbits(uint16_t *bits) {
return static_cast<avclan_bit_t>(AVCLAN_readbitsl(bits, N));
}
template <> inline avclan_bit_t AVCLAN_readbits<8>(uint8_t *byte) {
return static_cast<avclan_bit_t>(AVCLAN_readbyte(byte));
}
#endif
-368
View File
@@ -1,368 +0,0 @@
// copyright (C) 2006 Marcin Slonicki <marcin@softservice.com.pl>
// copyright (C) 2007 Louis Frigon
// Copyright (C) 2015 Allen Hill <allenofthehills@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#include <stdint.h>
#include <string.h>
#include "avclan_phy.h" // AVCLAN_ismuted
#include "avclan_protocol.h"
#include "cdchanger.h"
#include "mediacontrol.h"
#include "statustimer.h"
#define PACK3(a, b, c) (((uint32_t)(a) << 16) | ((uint32_t)(b) << 8) | (c))
static const uint8_t cdloading_resp[] = {dev_CD_CHANGER,
dev_STATUS,
Loading_Status_Report,
0x00,
0x01,
0x00,
0x01,
0x00,
0x01,
0x02};
void AVCLAN_handleframe(const AVCLAN_frame_t *in, AVCLAN_frame_t *out) {
out->reaction = r_Nothing;
if (AVCLAN_ismuted() || in->length < 3)
return;
// 0xFF placeholders are variant bytes filled by writing directly to
// out->data[N] after memcpy.
static const uint8_t lancheck_resp[] = {0x00, dev_COMM_CTRL, dev_LAN, 0xFF,
0xFF};
static const uint8_t function_change_resp[] = {0x00, dev_CD_CHANGER,
dev_COMM_v1, 0xFF, 0x01};
out->controller_addr = DEVICE_ADDR;
out->control = 0xF;
const uint8_t *data = in->data;
const uint8_t b0 = *data++;
const uint8_t b1 = *data++;
const uint8_t b2 = *data++;
uint8_t b3 = 0;
if (in->length > 3) // the shortest known/valid messages are 3 bytes long
b3 = *data++;
if (!in->is_unicast) {
// Broadcast: bytes are (from, to, action, [extra...]).
// peripheral_addr unchecked — always 0xFFF or 0x1FF in known traffic.
switch (PACK3(b0, b1, b2)) {
case PACK3(dev_LAN, dev_COMM_CTRL, Lancheck_Scan_Req):
out->length = sizeof(lancheck_resp);
out->is_unicast = true;
out->peripheral_addr = HU_ADDR;
memcpy(out->data, lancheck_resp, sizeof(lancheck_resp));
out->data[3] = Lancheck_Scan_Resp;
out->data[4] = 0x01;
out->reaction = r_SendOnly;
break;
case PACK3(dev_LAN, dev_COMM_CTRL, Lancheck_Req):
out->length = sizeof(lancheck_resp);
out->is_unicast = true;
out->peripheral_addr = HU_ADDR;
memcpy(out->data, lancheck_resp, sizeof(lancheck_resp));
out->data[3] = Lancheck_Resp;
out->data[4] = 0x00;
out->reaction = r_SendOnly;
break;
case PACK3(dev_LAN, dev_COMM_CTRL, Lancheck_End_Req):
out->is_unicast = true;
out->peripheral_addr = HU_ADDR;
out->length = sizeof(lancheck_resp) - 1;
memcpy(out->data, lancheck_resp, out->length);
out->data[3] = Lancheck_End_Resp;
out->reaction = r_SendOnly;
break;
case PACK3(dev_COMM_v1, dev_COMM_CTRL, Current_Function):
case PACK3(dev_COMM_v2, dev_COMM_CTRL, Current_Function):
if ((b3 == dev_CD_CHANGER) && !AVCLAN_isPlaying()) {
if (cd_status.mins > 99)
cd_status.mins = 0;
if (cd_status.secs > 99)
cd_status.secs = 0;
cd_status.state = cd_SEEKING | cd_SEEKING_TRACK;
cd_status.flags2 = 0xc0;
AVCLAN_generateStatus(out, true, dev_STATUS);
out->reaction = r_StartPlaying;
}
break;
case PACK3(dev_COMM_v1, dev_COMM_CTRL, Ping_Req):
case PACK3(dev_COMM_v2, dev_COMM_CTRL, Ping_Req): {
out->is_unicast = true;
out->peripheral_addr = HU_ADDR;
const uint8_t ping_resp[] = {0x00, dev_COMM_CTRL, dev_COMM_v1,
Ping_Resp, 0xFF, b3};
out->length = sizeof(ping_resp);
memcpy(out->data, ping_resp, sizeof(ping_resp));
out->reaction = r_SendOnly;
break;
}
case PACK3(dev_COMM_v1, dev_COMM_CTRL, List_Functions_Req):
case PACK3(dev_COMM_v2, dev_COMM_CTRL, List_Functions_Req): {
out->is_unicast = true;
out->peripheral_addr = HU_ADDR;
const uint8_t list_functions_resp[] = {0x00, dev_COMM_CTRL, dev_COMM_v1,
List_Functions_Resp,
dev_CD_CHANGER};
out->length = sizeof(list_functions_resp);
memcpy(out->data, list_functions_resp, sizeof(list_functions_resp));
out->reaction = r_SendOnly;
break;
}
// case Restart_Lan: not handled
}
} else if (in->peripheral_addr == DEVICE_ADDR && b0 == 0x00) {
// Unicast to CD changer: bytes are (0x00, from, to, action, [extra...]).
switch (PACK3(b1, b2, b3)) {
case PACK3(dev_COMM_v1, dev_CD_CHANGER, Enable_Function_Req):
[[fallthrough]];
case PACK3(dev_COMM_v2, dev_CD_CHANGER, Enable_Function_Req):
out->is_unicast = true;
out->peripheral_addr = HU_ADDR;
out->length = sizeof(function_change_resp);
memcpy(out->data, function_change_resp, sizeof(function_change_resp));
out->data[3] = Enable_Function_Resp;
cd_status.state = 0;
cd_status.flags2 = 0x80;
out->reaction = r_StatusReport;
break;
case PACK3(dev_COMM_v1, dev_CD_CHANGER, Disable_Function_Req):
[[fallthrough]];
case PACK3(dev_COMM_v2, dev_CD_CHANGER, Disable_Function_Req):
// No change/response needed if we're already not playing
if (AVCLAN_isPlaying()) {
AVCLAN_stopPlaying();
out->length = sizeof(function_change_resp);
memcpy(out->data, function_change_resp, sizeof(function_change_resp));
out->data[3] = Disable_Function_Resp;
cd_status.state = 0;
cd_status.flags2 = 0x80;
out->is_unicast = true;
out->peripheral_addr = HU_ADDR;
out->reaction = r_StatusReport;
}
break;
case PACK3(dev_CMD_SW, dev_CD_CHANGER, Eject): {
// "Eject" label is multiply wrong; proper meaning unclear:
// - First observed on initial multiple presses of "CD" button,
// triggering (after {0x00, dev_CD_CHANGER, dev_COMM_v1, Insertion,
// 0x01} response) proper activation of mockingboard/cd-changer.
// - Subsequently observed when pressing (technically
// releasing?) the fast-forward button and rewind
if (cd_status.state | cd_SEEKING) { // FF/RW button released
cd_status.state &= ~cd_SEEKING;
} else {
out->is_unicast = true;
out->peripheral_addr = HU_ADDR;
{
const uint8_t msg[] = {0x00, dev_CD_CHANGER, dev_CMD_SW, Insertion,
0x01};
out->length = sizeof(msg);
memcpy(out->data, msg, sizeof(msg));
}
out->reaction = r_SendOnly;
}
break;
}
case PACK3(dev_CMD_SW, dev_CD_CHANGER, Initial_Report_Request):
[[fallthrough]];
case PACK3(dev_STATUS, dev_CD_CHANGER, Initial_Report_Request): {
out->is_unicast = true;
out->peripheral_addr = HU_ADDR;
// No knowledge/understanding of field meaning/interpretation
const uint8_t cdinitreport_resp[] = {
0x00, dev_CD_CHANGER, b1, Initial_Report_Response, 0x01, 0x31,
0x10, 0x01, 0x01};
out->length = sizeof(cdinitreport_resp);
memcpy(&out->data[1], cdinitreport_resp, sizeof(cdinitreport_resp));
out->reaction = r_SendOnly;
break;
}
case PACK3(dev_CMD_SW, dev_CD_CHANGER, Playback_Request): [[fallthrough]];
case PACK3(dev_STATUS, dev_CD_CHANGER, Playback_Request):
out->data[0] = 0x00;
out->data[1] = dev_CD_CHANGER;
out->data[2] = b1;
out->data[3] = Playback_Report;
out->length = sizeof(AVCLAN_CD_Status_t) + 4;
serializeCDStatus(&out->data[4]);
out->is_unicast = true;
out->peripheral_addr = HU_ADDR;
out->reaction = r_SendOnly;
break;
case PACK3(dev_CMD_SW, dev_CD_CHANGER, Loading_Request2): [[fallthrough]];
case PACK3(dev_STATUS, dev_CD_CHANGER, Loading_Request2):
out->data[0] = 0x00;
out->length = sizeof(cdloading_resp) + 1;
memcpy(&out->data[1], cdloading_resp, sizeof(cdloading_resp));
out->data[2] = b1;
out->data[3] = Loading_Response2;
out->is_unicast = true;
out->peripheral_addr = HU_ADDR;
out->reaction = r_SendOnly;
break;
case PACK3(dev_CMD_SW, dev_CD_CHANGER, Track_Seek_Up):
cd_status.state = cd_SEEKING_TRACK;
if (cd_status.track < 98)
++cd_status.track;
else
cd_status.track = 1;
cd_status.mins = 0xff;
cd_status.secs = 0x7f;
cd_status.flags2 = 0xc0;
AVCLAN_generateStatus(out, true, dev_CMD_SW);
AVCLAN_mediaFunction(MEDIA_SKIP_FORWARD);
out->reaction = r_TrackChange;
break;
case PACK3(dev_CMD_SW, dev_CD_CHANGER, Track_Seek_Down):
cd_status.state = cd_SEEKING_TRACK;
// Track down returns to track beginning if in ~middle of song
if (cd_status.mins == 0 && cd_status.secs < 0x05) {
if (cd_status.track > 1)
--cd_status.track;
else
cd_status.track = 99;
}
cd_status.mins = 0xff;
cd_status.secs = 0x7f;
cd_status.flags2 = 0xc0;
AVCLAN_generateStatus(out, true, dev_CMD_SW);
AVCLAN_mediaFunction(MEDIA_SKIP_BACKWARD);
out->reaction = r_TrackChange;
break;
case PACK3(dev_CMD_SW, dev_CD_CHANGER, Track_Fast_Forward): {
cd_status.state |= cd_SEEKING;
cd_status.secs += 15;
if (cd_status.secs > 60) {
cd_status.secs -= 60;
++cd_status.mins;
}
AVCLAN_generateStatus(out, true, dev_CMD_SW);
AVCLAN_mediaFunction(MEDIA_SKIP_FORWARD);
statustimer_reset(); // Skipped to a whole/round sec; ensure next tick
// is ~1 sec from now
out->reaction = r_SendOnly;
break;
}
case PACK3(dev_CMD_SW, dev_CD_CHANGER, Track_Rewind): {
cd_status.state |= cd_SEEKING;
if (cd_status.secs < 15) {
if (cd_status.mins > 0) {
uint8_t d = 15 - cd_status.secs;
cd_status.secs = 60 - d;
--cd_status.mins;
} else {
cd_status.mins = 0;
cd_status.secs = 0;
}
} else
cd_status.secs -= 15;
AVCLAN_generateStatus(out, true, dev_CMD_SW);
AVCLAN_mediaFunction(MEDIA_SKIP_BACKWARD);
statustimer_reset(); // Skipped to a whole/round sec; ensure next tick
// is ~1 sec from now
out->reaction = r_SendOnly;
break;
}
case PACK3(dev_CMD_SW, dev_CD_CHANGER, CD_Enable_Random):
cd_status.flags |= cd_RANDOM;
AVCLAN_generateStatus(out, true, dev_CMD_SW);
out->reaction = r_StatusReport;
break;
case PACK3(dev_CMD_SW, dev_CD_CHANGER, CD_Disable_Random):
cd_status.flags &= ~cd_RANDOM;
AVCLAN_generateStatus(out, true, dev_CMD_SW);
out->reaction = r_StatusReport;
break;
case PACK3(dev_CMD_SW, dev_CD_CHANGER, CD_Enable_Repeat):
cd_status.flags |= cd_REPEAT;
AVCLAN_generateStatus(out, true, dev_CMD_SW);
out->reaction = r_StatusReport;
break;
case PACK3(dev_CMD_SW, dev_CD_CHANGER, CD_Disable_Repeat):
cd_status.flags &= ~cd_REPEAT;
AVCLAN_generateStatus(out, true, dev_CMD_SW);
out->reaction = r_StatusReport;
break;
case PACK3(dev_CMD_SW, dev_CD_CHANGER, CD_Enable_Disk_Random):
cd_status.flags |= cd_DISK_RANDOM;
AVCLAN_generateStatus(out, true, dev_CMD_SW);
out->reaction = r_StatusReport;
break;
case PACK3(dev_CMD_SW, dev_CD_CHANGER, CD_Disable_Disk_Random):
cd_status.flags &= ~cd_DISK_RANDOM;
AVCLAN_generateStatus(out, true, dev_CMD_SW);
out->reaction = r_StatusReport;
break;
case PACK3(dev_CMD_SW, dev_CD_CHANGER, CD_Enable_Disk_Repeat):
cd_status.flags |= cd_DISK_REPEAT;
AVCLAN_generateStatus(out, true, dev_CMD_SW);
out->reaction = r_StatusReport;
break;
case PACK3(dev_CMD_SW, dev_CD_CHANGER, CD_Disable_Disk_Repeat):
cd_status.flags &= ~cd_DISK_REPEAT;
AVCLAN_generateStatus(out, true, dev_CMD_SW);
out->reaction = r_StatusReport;
break;
}
}
}
#undef PACK3
void AVCLAN_statemachine(AVCLAN_frame_t *out) {
reaction_t r = out->reaction;
out->reaction = r_Nothing;
switch (r) {
case r_Ejection: {
const uint8_t play[] = {0x00, dev_COMM_CTRL, dev_COMM_v1,
Insertion, dev_CD_CHANGER, 0x01};
out->length = sizeof(play);
memcpy(out->data, play, sizeof(play));
}
out->reaction = r_Report_Load;
break;
case r_Report_Load:
out->is_unicast = false;
out->peripheral_addr = 0x1FF;
out->length = sizeof(cdloading_resp) + 1;
memcpy(out->data, cdloading_resp, sizeof(cdloading_resp));
out->data[1] = dev_STATUS;
out->data[2] = Loading_Status_Report;
out->reaction = r_SendOnly;
break;
case r_TrackChange:
AVCLAN_setTime(0x00, 0x00);
statustimer_reset(); // Skipped to a whole/round sec; ensure next tick is
// ~1 sec from now
[[fallthrough]];
case r_NormalizeState:
AVCLAN_normalizeState();
AVCLAN_generateStatus(out, true, dev_STATUS);
out->reaction = r_SendOnly;
break;
case r_StartPlaying:
AVCLAN_normalizeState();
AVCLAN_generateStatus(out, true, dev_STATUS);
out->reaction = r_BeganPlaying;
break;
case r_BeganPlaying:
AVCLAN_startPlaying(); // only start PIT after normalizing state
out->reaction = r_Nothing;
break;
case r_StatusReport:
AVCLAN_generateStatus(out, true, dev_STATUS);
out->reaction = r_SendOnly;
break;
case r_SendOnly: [[fallthrough]];
case r_Nothing: [[fallthrough]];
default: out->reaction = r_Nothing;
}
}
-39
View File
@@ -1,39 +0,0 @@
// Copyright (C) 2026 Allen Hill <allenofthehills@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
// AVC-LAN message dispatcher and response state machine. Currently mixes
// generic peripheral-level handling with CD-changer device-specific handling;
// the device-specific cases will later migrate into cdchanger.
#pragma once
#include <stdint.h>
#include "avclan_defs.h"
#ifdef __cplusplus
extern "C" {
#endif
/// Message state machine
// - r_Nothing (0x00) means don't send current message
// - All other instances mean send current message and imply the presence of
// follow-up messages within state machine
typedef enum : uint8_t {
r_Nothing = 0x00,
r_SendOnly, // No further follow-up needed (beyond sending current)
r_StatusReport = 0x02, // Needs follow-up status report
r_NormalizeState, // cd_status needs normalized and resent
r_StartPlaying, // ~equivalent to normalizeState, but cycles to BeganPlaying
r_BeganPlaying,
r_TrackChange, // Time needs reset
r_Ejection,
r_Report_Load,
} reaction_t;
void AVCLAN_handleframe(const AVCLAN_frame_t *in, AVCLAN_frame_t *out);
void AVCLAN_statemachine(AVCLAN_frame_t *out);
#ifdef __cplusplus
}
#endif
-2
View File
@@ -11,7 +11,5 @@
#include "avclan_defs.h"
#include "avclan_frame.h"
#include "avclan_phy.h"
#include "avclan_protocol.h"
#include "cdchanger.h"
#include "mediacontrol.h"
#include "statustimer.h"
+268 -5
View File
@@ -3,28 +3,291 @@
// Copyright (C) 2015 Allen Hill <allenofthehills@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
/*
AVC LAN Frame Format
│ Bits │ Description
────────────────────────────────────────
| 1 │ Start bit
| 1 │ Direct/broadcast
| 12 │ Controller address
| 1 │ Parity
| 12 │ Peripheral address
| 1 │ Parity
| 1 │ *Acknowledge* (read below)
| 4 │ Control
| 1 │ Parity
| 1 │ *Acknowledge*
| 8 │ Message length (n)
| 1 │ Parity
| 1 │ *Acknowledge*
────────
| 8 │ Data
| 1 │ Parity
| 1 │ *Acknowledge*
*repeat `n` times*
No acknowledge bits are sent for broadcast frames.
*/
#include "bus.hpp"
#include "avclan.hpp"
#include "avclan_defs.h"
#include "avclan_frame.h"
#include "avclan_phy.h" // bridge until phy has been ported
#include "com232.h"
namespace {
constexpr int ADDR_WIDTH = 12;
}
namespace avclan {
void Bus::init() { AVCLAN_busInit(); };
void Bus::mute(bool mute) { AVCLAN_muteDevice(mute); };
bool Bus::is_muted() const { return AVCLAN_ismuted(); };
auto Bus::read(uint16_t address, AVCLAN_frame_t *in, log_t print)
-> Error::Read {
struct errtype {
Error::Read errno;
union {
uint8_t val; // BAD_LENGTH_RANGE: the out-of-range length value
struct {
uint8_t parity; // received (bad) parity bit
uint16_t read_val;
};
};
} err = {};
using enum detail::Error::Read;
{ // bound handle lifetime
auto handle = get();
bool shouldACK = false;
uint8_t tmp = 0;
err.errno = Error::Read{static_cast<uint8_t>(handle.readstartbit())};
if (static_cast<bool>(err.errno))
goto handle_err;
handle.read<1>(&tmp, false);
in->is_unicast = tmp;
if (auto rerr = handle.read<ADDR_WIDTH>(&in->controller_addr, false);
rerr == BAD_PARITY) {
err.errno = BAD_CONTROLLER_PARITY;
if (print.verbose) {
err.read_val = in->controller_addr;
}
goto handle_err;
}
if (auto rerr = handle.read<ADDR_WIDTH>(
&in->peripheral_addr,
[&]() { return !is_muted() && (in->peripheral_addr == address); });
rerr == BAD_PARITY) {
err.errno = BAD_PERIPHERAL_PARITY;
if (print.verbose) {
err.read_val = in->peripheral_addr;
}
goto handle_err;
}
shouldACK = !is_muted() && (in->peripheral_addr == address);
if (auto rerr = handle.read<4>(&in->control, shouldACK);
rerr == BAD_PARITY) {
err.errno = BAD_CONTROL_PARITY;
if (print.verbose) {
err.read_val = in->control;
}
goto handle_err;
}
if (auto rerr = handle.read<8>(&in->length, shouldACK);
rerr == BAD_PARITY) {
err.errno = BAD_LENGTH_PARITY;
if (print.verbose) {
err.read_val = in->length;
}
goto handle_err;
}
if (in->length == 0 || in->length > MAXMSGLEN) {
err.errno = BAD_LENGTH_RANGE;
err.val = in->length;
goto handle_err;
}
for (uint8_t i = 0; i < in->length; i++) {
if (auto rerr = handle.read<8>(&in->data[i], shouldACK);
rerr == BAD_PARITY) {
err.errno = BAD_DATA_PARITY;
if (print.verbose) {
err.read_val = in->data[i];
}
goto handle_err;
}
}
} // destroy handle
if (false) { // NOLINT(readability-simplify-boolean-expr)
handle_err:;
RS232_Print("ERR(read): ");
switch (err.errno) {
case BAD_STARTBIT: RS232_Print("bad start bit (other)"); break;
case STARTBIT_TOO_SHORT: RS232_Print("bad start bit (short)"); break;
case STARTBIT_TOO_LONG: RS232_Print("bad start bit (long)"); break;
case BAD_CONTROLLER_PARITY:
RS232_Print("reading controller addr.");
goto VERBOSE;
case BAD_PERIPHERAL_PARITY:
RS232_Print("reading peripheral addr.");
goto VERBOSE;
case BAD_CONTROL_PARITY: RS232_Print("reading control"); goto VERBOSE;
case BAD_LENGTH_PARITY: RS232_Print("reading length"); goto VERBOSE;
case BAD_LENGTH_RANGE:
RS232_Print("bad length 0x");
RS232_PrintHex4(err.val);
break;
case BAD_DATA_PARITY:
RS232_Print("reading data");
goto VERBOSE;
VERBOSE:
if (print.verbose) {
RS232_Print("; read 0x");
RS232_PrintHex(err.read_val);
RS232_Print(" and got bad parity ");
RS232_PrintHex4(err.parity);
}
case BAD_PARITY: __builtin_unreachable();
}
RS232_Print("\n");
}
// Only print if some data has been correctly received
if (print.print && (err.errno < STARTBIT_TOO_SHORT)) {
if (err.errno > BAD_DATA_PARITY)
in->length = 0;
AVCLAN_printframe(in, print.binary);
}
return err.errno;
}
auto Bus::send(const AVCLAN_frame_t *out, log_t print) -> Error::Send {
struct errtype {
// Error enum is ordered such that a lower numeric value corresponds to
// more success
Error::Send errno;
uint8_t val;
} err = {};
using enum detail::Error::Send;
if (is_muted()) {
err.errno = MUTED;
goto handle_err;
}
{ // bound handle lifetime
auto handle = get();
if (!handle.sendstartbit()) {
// Some other device is already driving the bus
err.errno = BUSY;
goto handle_err;
}
handle.send<1>(static_cast<uint8_t>(out->is_unicast), false);
handle.send<ADDR_WIDTH>(out->controller_addr, false);
if (auto serr =
handle.send<ADDR_WIDTH>(out->controller_addr, out->is_unicast);
serr == NAK) {
err.errno = NAK_ADDRESS;
goto handle_err;
}
if (auto serr = handle.send<4>(out->control, out->is_unicast);
serr == NAK) {
err.errno = NAK_CONTROL;
goto handle_err;
}
if (auto serr = handle.send<8>(out->length, out->is_unicast); serr == NAK) {
err.errno = NAK_MESSAGE_LENGTH;
goto handle_err;
}
for (uint8_t i = 0; i < out->length; i++) {
// Based on the µPD6708 datasheet, ACK bit for broadcast doesn't seem
// necessary (i.e. This deviates from the previous broadcast specific
// function that sent an extra `1` bit after each byte/parity)
// Explanation for why audio-group broadcast state report isn't working?
if (auto serr = handle.send<8>(out->data[i], out->is_unicast);
serr == NAK) {
err.errno = NAK_DATA;
err.val = i;
goto handle_err;
}
}
} // destroy handle
// back to read mode
if (false) { // NOLINT(readability-simplify-boolean-expr)
handle_err:;
RS232_Print("Error");
switch (err.errno) {
case MUTED: RS232_Print(": Device muted"); break;
case BUSY: RS232_Print(": Busy bus"); break;
case NAK_ADDRESS:
case NAK_CONTROL:
case NAK_MESSAGE_LENGTH:
case NAK_DATA:
case NAK:
RS232_Print(" NAK: ");
switch (err.errno) {
case NAK_ADDRESS: RS232_Print("address"); break;
case NAK_CONTROL: RS232_Print("Control"); break;
case NAK_MESSAGE_LENGTH: RS232_Print("Message length"); break;
case NAK_DATA:
RS232_Print(" data[");
RS232_PrintDec(err.val);
RS232_Print("]");
break;
case NAK:
case MUTED:
case BUSY: __builtin_unreachable();
}
break;
}
RS232_Print("\n");
}
if (print.print)
AVCLAN_printframe(out, print.binary);
return err.errno;
}
Bus::Handle Bus::get() { return {}; };
bool Bus::Handle::sendstartbit() { return AVCLAN_sendstartbit(); };
Bus::Error::Read Bus::Handle::readstartbit() {
auto Bus::Handle::readstartbit() -> Read {
using enum Error::Read;
auto err = AVCLAN_readstartbit();
if (err == rSTARTBIT_TOO_LONG)
return STARTBIT_TOO_LONG;
else if (err == rLATCHED_COMPARATOR)
if (err == rLATCHED_COMPARATOR)
return BAD_STARTBIT;
else if (err == rSTARTBIT_TOO_SHORT)
if (err == rSTARTBIT_TOO_SHORT)
return STARTBIT_TOO_SHORT;
else
return Read{0};
return Read{0};
};
void Bus::Handle::send_ACK() { AVCLAN_sendbit_ACK(); };
uint8_t Bus::Handle::read_ACK() { return AVCLAN_readbit_ACK(); };
+20 -25
View File
@@ -49,31 +49,24 @@
#include <concepts>
#include "avclan.hpp"
#include "avclan_defs.h"
#include "avclan_phy.h" // bridge until phy has been ported
namespace avclan {
class Bus {
public:
struct Error {
enum class Read : uint8_t {
BAD_PARITY = 0x01,
STARTBIT_TOO_SHORT = 0x80, // Start *well* above Peripher::Error::Read
// (which ~inherits these values)
STARTBIT_TOO_LONG,
BAD_STARTBIT,
};
enum class Send : uint8_t {
NAK = 0x01,
};
};
class Handle;
using Error = detail::Error;
void init();
void mute(bool mute);
bool is_muted() const;
Handle get();
Error::Read read(uint16_t address, AVCLAN_frame_t *in, log_t print);
Error::Send send(const AVCLAN_frame_t *out, log_t print);
static Handle get();
};
class Bus::Handle {
@@ -84,6 +77,7 @@ public:
~Handle() { AVCLAN_startEvent(); }
Handle(const Handle &) = delete;
Handle(Handle &&) = delete;
using Error = detail::Error;
bool sendstartbit();
Error::Read readstartbit();
@@ -108,7 +102,8 @@ public:
readbits<1>(&read_parity);
if (calc_parity != read_parity) {
return Read::BAD_PARITY;
} else if (ack()) {
}
if (ack()) {
send_ACK();
} else
readbits<1>(&read_parity);
@@ -133,8 +128,8 @@ private:
bit_start = 0x10
};
void send_ACK();
uint8_t read_ACK();
static void send_ACK();
static uint8_t read_ACK();
template <auto N, class T> avclan_bit_t sendbits(T bits);
template <auto N, class T> avclan_bit_t readbits(T *bits);
@@ -163,16 +158,16 @@ private:
};
};
template <> inline avclan_bit_t Bus::Handle::sendbits<8>(uint8_t byte) {
return AVCLAN_sendbyte(&byte);
template <> inline avclan_bit_t Bus::Handle::sendbits<8>(uint8_t bits) {
return AVCLAN_sendbyte(&bits);
};
template <> inline avclan_bit_t Bus::Handle::sendbits<1>(uint8_t byte) {
const avclan_bit_t b{static_cast<avclan_bit_t>(byte & 1u)};
AVCLAN_sendbit(b);
return b;
template <> inline avclan_bit_t Bus::Handle::sendbits<1>(uint8_t bits) {
const avclan_bit_t bit{static_cast<avclan_bit_t>(bits & 1U)};
AVCLAN_sendbit(bit);
return bit;
};
template <> inline avclan_bit_t Bus::Handle::readbits<8>(uint8_t *byte) {
return static_cast<avclan_bit_t>(AVCLAN_readbyte(byte));
template <> inline avclan_bit_t Bus::Handle::readbits<8>(uint8_t *bits) {
return static_cast<avclan_bit_t>(AVCLAN_readbyte(bits));
};
} // namespace avclan
-124
View File
@@ -1,124 +0,0 @@
// copyright (C) 2006 Marcin Slonicki <marcin@softservice.com.pl>
// copyright (C) 2007 Louis Frigon
// Copyright (C) 2015 Allen Hill <allenofthehills@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#include <stdint.h>
#include <string.h>
#include "avclan_phy.h"
#include "cdchanger.h"
#include "mediacontrol.h"
#include "statustimer.h"
AVCLAN_CD_Status_t cd_status;
static cd_modes CD_Mode;
// Sets CD_mode to play and resets timer count (so that the next interrupt is in
// 1 sec)
void AVCLAN_startPlaying() {
static bool havePlayed = false;
if (havePlayed)
AVCLAN_mediaFunction(MEDIA_PLAY_PAUSE);
havePlayed |= true;
CD_Mode = stPlay;
statustimer_reset();
}
// Sets CD_mode to play and resets timer count (so that the next interrupt is in
// 1 sec)
void AVCLAN_stopPlaying() {
statustimer_disable();
CD_Mode = stStop;
AVCLAN_mediaFunction(MEDIA_PLAY_PAUSE);
}
/* Pack a 099 count into 2-digit BCD. Values >99 (sentinels such as 0xFF /
0x7F meaning "no time") pass through unchanged so they survive the wire
round-trip. */
static uint8_t toBCD(uint8_t x) {
if (x > 99)
return x;
return (uint8_t)(((x / 10) << 4) | (x % 10));
}
// Serialize cd_status into the wire format. The struct layout mirrors the wire
// format byte-for-byte, except for track/mins/secs, which need converted from
// decimal to BCD
void serializeCDStatus(uint8_t *dst) {
memcpy(dst, &cd_status, sizeof(cd_status));
dst[3] = toBCD(cd_status.track);
dst[4] = toBCD(cd_status.mins);
dst[5] = toBCD(cd_status.secs);
}
bool AVCLAN_isPlaying() { return (CD_Mode == stPlay); }
void AVCLAN_incrementTime() {
// Sentinel values (>99) mean "no time"; leave them alone until setTime()
// replaces them with a real count.
if (cd_status.secs > 99)
return;
if (cd_status.secs == 59) {
cd_status.secs = 0;
if (cd_status.mins == 99)
cd_status.mins = 0;
else
cd_status.mins++;
} else
cd_status.secs++;
}
void AVCLAN_setTime(uint8_t mins, uint8_t secs) {
cd_status.mins = mins;
cd_status.secs = secs;
}
// Used for changed status messages
void AVCLAN_generateStatus(AVCLAN_frame_t *status, bool is_unicast,
devices to) {
status->is_unicast = is_unicast;
status->controller_addr = DEVICE_ADDR;
status->peripheral_addr = (is_unicast) ? HU_ADDR : 0x1FF;
status->control = 0xF;
status->length = sizeof(AVCLAN_CD_Status_t) + ((is_unicast) ? 4 : 3);
uint8_t *data = status->data;
if (is_unicast)
*data++ = 0x00;
*data++ = dev_CD_CHANGER;
*data++ = to;
*data++ = Status_Report;
serializeCDStatus(data);
}
void AVCLAN_normalizeState() {
// if (cd_status.state != cd_PLAYBACK) {
if (cd_status.mins > 99)
cd_status.mins = 0;
if (cd_status.secs > 99)
cd_status.secs = 0;
cd_status.state = cd_PLAYBACK;
cd_status.flags &= (uint8_t)~(cd_DISK_SCAN | cd_SCAN);
cd_status.flags2 = 0x80;
// }
}
void AVCLAN_init() {
AVCLAN_busInit();
mediacontrol_init();
statustimer_init();
cd_status.cds = cd_CD1;
cd_status.disc = 1;
cd_status.state = cd_SEEKING | cd_SEEKING_TRACK;
cd_status.flags = 0;
cd_status.flags2 = 0xC0;
cd_status.track = 1;
cd_status.mins = 0xFF;
cd_status.secs = 0x7F;
CD_Mode = stStop;
}
+407
View File
@@ -0,0 +1,407 @@
// copyright (C) 2006 Marcin Slonicki <marcin@softservice.com.pl>
// copyright (C) 2007 Louis Frigon
// Copyright (C) 2015 Allen Hill <allenofthehills@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#include <cstdint>
#include <cstring>
#include "avclan_defs.h"
#include "cdchanger.hpp"
#include "mediacontrol.h"
#include "statustimer.h"
namespace {
constexpr uint8_t cdloading_resp[] = {dev_CD_CHANGER,
dev_STATUS,
Loading_Status_Report,
0x00,
0x01,
0x00,
0x01,
0x00,
0x01,
0x02};
constexpr int WIRE_SIZE = 8;
constexpr int TIME_SKIP = 15;
constexpr int TWODIGIT_MAX = 99;
/* Pack a 0TWODIGIT_MAX count into 2-digit BCD. Values >TWODIGIT_MAX (sentinels
such as 0xFF / 0x7F meaning "no time") pass through unchanged so they survive
the wire round-trip. */
constexpr uint8_t toBCD(uint8_t val) {
if (val > TWODIGIT_MAX)
return val;
return (uint8_t)(((val / 10) << 4) | (val % 10));
}
extern "C" void incrementTime_callback(void *self) {
static_cast<avclan::CDChanger *>(self)->incrementTime();
}
extern "C" bool isPlaying_callback(void *self) {
return static_cast<avclan::CDChanger *>(self)->isPlaying();
}
} // namespace
namespace avclan {
void CDChanger::init() {
mediacontrol_init();
statustimer_init(this, &incrementTime_callback, &isPlaying_callback);
}
void CDChanger::handle(const AVCLAN_frame_t *in, AVCLAN_frame_t *out) {
const uint8_t *data = &in->data[1];
const auto from = static_cast<devices>(*data++);
/* const auto to = */ data++;
const auto action = *data++;
static const uint8_t function_change_resp[] = {0x00, dev_CD_CHANGER, from,
0xFF, 0x01};
// Unicast to CD changer: bytes are (0x00, from, to, action, [extra...]).
switch (action) {
case Enable_Function_Req:
out->is_unicast = true;
out->length = sizeof(function_change_resp);
memcpy(out->data, function_change_resp, sizeof(function_change_resp));
out->data[3] = Enable_Function_Resp;
state = 0;
flags2 = 0x80;
out->reaction = r_StatusReport;
break;
case Disable_Function_Req:
// No change/response needed if we're already not playing
if (isPlaying()) {
stopPlaying();
out->length = sizeof(function_change_resp);
memcpy(out->data, function_change_resp, sizeof(function_change_resp));
out->data[3] = Disable_Function_Resp;
state = 0;
flags2 = 0x80;
out->is_unicast = true;
out->reaction = r_StatusReport;
}
break;
case Eject: {
// "Eject" label is multiply wrong; proper meaning unclear:
// - First observed on initial multiple presses of "CD" button,
// triggering (after {0x00, dev_CD_CHANGER, dev_COMM_v1, Insertion,
// 0x01} response) proper activation of mockingboard/cd-changer.
// - Subsequently observed when pressing (technically
// releasing?) the fast-forward button and rewind
if (static_cast<bool>(state & SEEKING)) { // FF/RW button released
state &= ~SEEKING;
} else {
out->is_unicast = true;
{
const uint8_t msg[] = {0x00, dev_CD_CHANGER, dev_CMD_SW, Insertion,
0x01};
out->length = sizeof(msg);
memcpy(out->data, msg, sizeof(msg));
}
out->reaction = r_SendOnly;
}
break;
}
case Initial_Report_Request: {
out->is_unicast = true;
// No knowledge/understanding of field meaning/interpretation
const uint8_t cdinitreport_resp[] = {
0x00, dev_CD_CHANGER, from, Initial_Report_Response, 0x01, 0x31,
0x10, 0x01, 0x01};
out->length = sizeof(cdinitreport_resp);
memcpy(&out->data[1], cdinitreport_resp, sizeof(cdinitreport_resp));
out->reaction = r_SendOnly;
break;
}
case Playback_Request:
out->data[0] = 0x00;
out->data[1] = dev_CD_CHANGER;
out->data[2] = from;
out->data[3] = Playback_Report;
out->length = WIRE_SIZE + 4;
serialize(&out->data[4]);
out->is_unicast = true;
out->reaction = r_SendOnly;
break;
case Loading_Request2:
out->data[0] = 0x00;
out->length = sizeof(cdloading_resp) + 1;
memcpy(&out->data[1], cdloading_resp, sizeof(cdloading_resp));
out->data[2] = from;
out->data[3] = Loading_Response2;
out->is_unicast = true;
out->reaction = r_SendOnly;
break;
case Track_Seek_Up:
state = SEEKING_TRACK;
if (track < 98)
++track;
else
track = 1;
mins = 0xff;
secs = 0x7f;
flags2 = 0xc0;
generateStatus(out, true, dev_CMD_SW);
AVCLAN_mediaFunction(MEDIA_SKIP_FORWARD);
out->reaction = r_TrackChange;
break;
case Track_Seek_Down:
state = SEEKING_TRACK;
// Track down returns to track beginning if in ~middle of song
if (mins == 0 && secs < 5) {
if (track > 1)
--track;
else
track = TWODIGIT_MAX;
}
mins = 0xff;
secs = 0x7f;
flags2 = 0xc0;
generateStatus(out, true, dev_CMD_SW);
AVCLAN_mediaFunction(MEDIA_SKIP_BACKWARD);
out->reaction = r_TrackChange;
break;
case Track_Fast_Forward: {
state |= SEEKING;
secs += TIME_SKIP;
if (secs > 60) {
secs -= 60;
++mins;
}
generateStatus(out, true, dev_CMD_SW);
AVCLAN_mediaFunction(MEDIA_SKIP_FORWARD);
statustimer_reset(); // Skipped to a whole/round sec; ensure next tick
// is ~1 sec from now
out->reaction = r_SendOnly;
break;
}
case Track_Rewind: {
state |= SEEKING;
if (secs < TIME_SKIP) {
if (mins > 0) {
const uint8_t dif = TIME_SKIP - secs;
secs = 60 - dif;
--mins;
} else {
mins = 0;
secs = 0;
}
} else
secs -= TIME_SKIP;
generateStatus(out, true, dev_CMD_SW);
AVCLAN_mediaFunction(MEDIA_SKIP_BACKWARD);
statustimer_reset(); // Skipped to a whole/round sec; ensure next tick
// is ~1 sec from now
out->reaction = r_SendOnly;
break;
}
case CD_Enable_Random:
flags |= RANDOM;
generateStatus(out, true, dev_CMD_SW);
out->reaction = r_StatusReport;
break;
case CD_Disable_Random:
flags &= ~RANDOM;
generateStatus(out, true, dev_CMD_SW);
out->reaction = r_StatusReport;
break;
case CD_Enable_Repeat:
flags |= REPEAT;
generateStatus(out, true, dev_CMD_SW);
out->reaction = r_StatusReport;
break;
case CD_Disable_Repeat:
flags &= ~REPEAT;
generateStatus(out, true, dev_CMD_SW);
out->reaction = r_StatusReport;
break;
case CD_Enable_Disk_Random:
flags |= DISK_RANDOM;
generateStatus(out, true, dev_CMD_SW);
out->reaction = r_StatusReport;
break;
case CD_Disable_Disk_Random:
flags &= ~DISK_RANDOM;
generateStatus(out, true, dev_CMD_SW);
out->reaction = r_StatusReport;
break;
case CD_Enable_Disk_Repeat:
flags |= DISK_REPEAT;
generateStatus(out, true, dev_CMD_SW);
out->reaction = r_StatusReport;
break;
case CD_Disable_Disk_Repeat:
flags &= ~DISK_REPEAT;
generateStatus(out, true, dev_CMD_SW);
out->reaction = r_StatusReport;
break;
default: break;
}
}
void CDChanger::react(AVCLAN_frame_t *out, detail::Error::Send err) {
auto resp = static_cast<reaction_t>(out->reaction);
out->reaction = r_Nothing;
switch (resp) {
case r_StateReport:
if (err == detail::Error::Send::NAK_ADDRESS &&
++failedStatusReports > 1) {
failedStatusReports = 0;
stopPlaying(); // Disable periodic updates if e.g. no-one's
// listening (car was turned off?)
}
break;
case r_Ejection: {
const uint8_t play[] = {0x00, dev_COMM_CTRL, dev_COMM_v1,
Insertion, dev_CD_CHANGER, 0x01};
out->length = sizeof(play);
memcpy(out->data, play, sizeof(play));
}
out->reaction = r_Report_Load;
break;
case r_Report_Load:
out->is_unicast = false;
out->peripheral_addr = 0x1FF;
out->length = sizeof(cdloading_resp) + 1;
memcpy(out->data, cdloading_resp, sizeof(cdloading_resp));
out->data[1] = dev_STATUS;
out->data[2] = Loading_Status_Report;
out->reaction = r_SendOnly;
break;
case r_TrackChange:
setTime(0x00, 0x00);
statustimer_reset(); // Skipped to a whole/round sec; ensure next tick is
// ~1 sec from now
[[fallthrough]];
case r_NormalizeState:
normalizeState();
generateStatus(out, true, dev_STATUS);
out->reaction = r_SendOnly;
break;
case r_StartPlaying:
normalizeState();
generateStatus(out, true, dev_STATUS);
out->reaction = r_BeganPlaying;
break;
case r_BeganPlaying:
startPlaying(); // only start PIT after normalizing state
out->reaction = r_Nothing;
break;
case r_StatusReport:
generateStatus(out, true, dev_STATUS);
out->reaction = r_SendOnly;
break;
case r_SendOnly: [[fallthrough]];
case r_Nothing: [[fallthrough]];
default: out->reaction = r_Nothing;
}
}
void CDChanger::enable(AVCLAN_frame_t *out) {
if (!isPlaying()) {
if (mins > TWODIGIT_MAX)
mins = 0;
if (secs > TWODIGIT_MAX)
secs = 0;
state = SEEKING | SEEKING_TRACK;
flags2 = 0xc0;
generateStatus(out, true, dev_STATUS);
out->reaction = r_StartPlaying;
}
}
bool CDChanger::pending() { return statustimer_tickPending(); }
void CDChanger::resolvepending() { statustimer_clearTick(); }
void CDChanger::emit(AVCLAN_frame_t *out) {
generateStatus(out, true, dev_STATUS);
out->reaction = r_StateReport;
}
bool CDChanger::isPlaying() const { return playing; }
// Sets CD_mode to play and resets timer count (so that the next interrupt is in
// 1 sec)
void CDChanger::startPlaying() {
static bool havePlayed = false;
if (havePlayed)
AVCLAN_mediaFunction(MEDIA_PLAY_PAUSE);
havePlayed |= true;
playing = true;
statustimer_reset();
}
void CDChanger::stopPlaying() {
statustimer_disable();
playing = false;
AVCLAN_mediaFunction(MEDIA_PLAY_PAUSE);
}
// Serialize cd_status into the wire format. The struct layout mirrors the wire
// format byte-for-byte, except for track/mins/secs, which need converted from
// decimal to BCD
void CDChanger::serialize(uint8_t *dst) const {
*dst++ = cds;
*dst++ = state;
*dst++ = disc;
*dst++ = toBCD(track);
*dst++ = toBCD(mins);
*dst++ = toBCD(secs);
*dst++ = flags;
*dst++ = flags2;
}
void CDChanger::setTime(uint8_t min, uint8_t sec) {
mins = min;
secs = sec;
}
void CDChanger::incrementTime() {
// Sentinel values (>TWODIGIT_MAX) mean "no time"; leave them alone until
// setTime() replaces them with a real count.
if (secs > TWODIGIT_MAX)
return;
if (secs == 59) {
secs = 0;
if (mins == TWODIGIT_MAX)
mins = 0;
else
mins++;
} else
secs++;
}
// Used for changed status messages
void CDChanger::generateStatus(AVCLAN_frame_t *status, bool is_unicast,
devices to) const {
status->is_unicast = is_unicast;
if (!is_unicast)
status->peripheral_addr = 0x1FF;
status->control = 0xF;
status->length = WIRE_SIZE + ((is_unicast) ? 4 : 3);
uint8_t *data = status->data;
if (is_unicast)
*data++ = 0x00;
*data++ = dev_CD_CHANGER;
*data++ = to;
*data++ = Status_Report;
serialize(data);
}
void CDChanger::normalizeState() {
if (mins > TWODIGIT_MAX)
mins = 0;
if (secs > TWODIGIT_MAX)
secs = 0;
state = PLAYBACK;
flags &= (uint8_t)~(DISK_SCAN | SCAN);
flags2 = 0x80;
}
} // namespace avclan
-85
View File
@@ -1,85 +0,0 @@
// copyright (C) 2006 Marcin Slonicki <marcin@softservice.com.pl>
// copyright (C) 2007 Louis Frigon
// Copyright (C) 2015 Allen Hill <allenofthehills@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
// Emulated CD-changer device (AVC-LAN device 0x63): playback state, BCD time,
// status-frame generation, and the play/stop mode FSM. This is pure-ish device
// logic; it commands the mediacontrol and statustimer drivers rather than
// touching their hardware directly.
#pragma once
#include <stdint.h>
#include "avclan_defs.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef enum : uint8_t {
cd_OPEN = 0x01,
cd_ERR1 = 0x02,
cd_SEEKING = 0x08,
cd_PLAYBACK = 0x10,
cd_SEEKING_TRACK = 0x20,
cd_LOADING = 0x80,
} cd_state;
typedef enum : uint8_t {
cd_CD1 = 1 << 0,
cd_CD2 = 1 << 1,
cd_CD3 = 1 << 2,
cd_CD4 = 1 << 3,
cd_CD5 = 1 << 4,
cd_CD6 = 1 << 5,
} cd_present_t;
typedef enum : uint8_t {
cd_DISK_RANDOM = 1 << 1,
cd_RANDOM = 1 << 2,
cd_DISK_REPEAT = 1 << 3,
cd_REPEAT = 1 << 4,
cd_DISK_SCAN = 1 << 5,
cd_SCAN = 1 << 6,
} cd_flag_t;
typedef struct AVCLAN_CD_Status {
uint8_t cds;
uint8_t state;
uint8_t disc;
uint8_t track; // Decimal storage; serialize to BCD
uint8_t mins; // Decimal storage; serialize to BCD
uint8_t secs; // Decimal storage; serialize to BCD
uint8_t flags;
uint8_t flags2;
} AVCLAN_CD_Status_t;
typedef enum : uint8_t { stStop = 0, stPlay = 1 } cd_modes;
// Mutable device state. Exposed for the protocol dispatcher, which currently
// mutates these fields directly. (Will be re-encapsulated when the device-
// specific message handling migrates here from avclan_protocol.)
extern AVCLAN_CD_Status_t cd_status;
// Full device-stack bring-up (calls AVCLAN_phyInit / mediacontrol_init /
// statustimer_init, then initialises cd_status).
void AVCLAN_init();
bool AVCLAN_isPlaying();
void AVCLAN_startPlaying();
void AVCLAN_stopPlaying();
void AVCLAN_incrementTime();
void AVCLAN_setTime(uint8_t mins, uint8_t secs);
void AVCLAN_normalizeState();
// Serialize cd_status into the wire format (track/mins/secs as BCD).
void serializeCDStatus(uint8_t *dst);
AVCLAN_frame_t *AVCLAN_getStatusFrame();
void AVCLAN_generateStatus(AVCLAN_frame_t *status, bool is_unicast, devices to);
#ifdef __cplusplus
}
#endif
+93
View File
@@ -0,0 +1,93 @@
// copyright (C) 2006 Marcin Slonicki <marcin@softservice.com.pl>
// copyright (C) 2007 Louis Frigon
// Copyright (C) 2015 Allen Hill <allenofthehills@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <cstdint>
#include "avclan.hpp"
#include "avclan_defs.h"
namespace avclan {
class CDChanger {
public:
enum State : uint8_t {
OPEN = 0x01,
ERR1 = 0x02,
SEEKING = 0x08,
PLAYBACK = 0x10,
SEEKING_TRACK = 0x20,
LOADING = 0x80,
};
enum CD : uint8_t {
CD1 = 1 << 0,
CD2 = 1 << 1,
CD3 = 1 << 2,
CD4 = 1 << 3,
CD5 = 1 << 4,
CD6 = 1 << 5,
};
enum Flags : uint8_t {
DISK_RANDOM = 1 << 1,
RANDOM = 1 << 2,
DISK_REPEAT = 1 << 3,
REPEAT = 1 << 4,
DISK_SCAN = 1 << 5,
SCAN = 1 << 6,
};
/// Message state machine
// - r_Nothing (0x00) means don't send current message
// - All other instances mean send current message and imply the presence of
// follow-up messages within state machine
enum reaction_t : uint8_t {
r_Nothing = 0x00,
r_SendOnly, // No further follow-up needed (beyond sending current)
r_StatusReport, // Needs follow-up status report
r_NormalizeState, // cd_status needs normalized and resent
r_StartPlaying, // ~equivalent to normalizeState, but cycles to BeganPlaying
r_BeganPlaying,
r_TrackChange, // Time needs reset
r_Ejection,
r_Report_Load,
r_StateReport, // *IS* a status report (follow-up or unprompted)
};
static constexpr uint8_t id = dev_CD_CHANGER;
void init();
void handle(const AVCLAN_frame_t *in, AVCLAN_frame_t *out);
void react(AVCLAN_frame_t *out, detail::Error::Send err);
void enable(AVCLAN_frame_t *out);
void disable(AVCLAN_frame_t *out);
static bool pending();
static void resolvepending();
void emit(AVCLAN_frame_t *out);
void incrementTime();
bool isPlaying() const;
private:
void startPlaying();
void stopPlaying();
void serialize(uint8_t *dst) const;
void setTime(uint8_t mins, uint8_t secs);
void generateStatus(AVCLAN_frame_t *status, bool is_unicast,
devices to) const;
void normalizeState();
bool playing = false;
int failedStatusReports = 0;
uint8_t cds = CD1;
uint8_t state = SEEKING | SEEKING_TRACK;
uint8_t disc = 1;
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 flags2 = 0xC0;
};
} // namespace avclan
+25
View File
@@ -0,0 +1,25 @@
// copyright (C) 2026 Allen Hill <allenofthehills@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include "avclan.hpp"
#include "avclan_defs.h"
#include <concepts>
#include <cstdint>
namespace avclan {
template <class T>
concept Device = requires { std::integral_constant<uint8_t, T::id>{}; } &&
requires(T dev, const AVCLAN_frame_t *in, AVCLAN_frame_t *out,
detail::Error::Send err) {
dev.init();
dev.handle(in, out);
dev.enable(out);
dev.react(out, err);
{ dev.pending() } -> std::convertible_to<bool>;
dev.resolvepending();
dev.emit(out);
};
} // namespace avclan
-243
View File
@@ -1,243 +0,0 @@
// copyright (C) 2006 Marcin Slonicki <marcin@softservice.com.pl>
// copyright (C) 2007 Louis Frigon
// Copyright (C) 2015 Allen Hill <allenofthehills@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#include "peripheral.hpp"
#include "avclan_defs.h"
#include "avclan_frame.h"
#include "bus.hpp"
#include "com232.h" // error logging
#include <cstdint>
namespace avclan {
Peripheral::Error::Read Peripheral::read(AVCLAN_frame_t *in, log_t print) {
struct errtype {
Error::Read errno;
union {
uint8_t val; // BAD_LENGTH_RANGE: the out-of-range length value
struct {
uint8_t parity; // received (bad) parity bit
uint16_t read_val;
};
};
} err = {};
using enum Error::Read;
auto BAD_PARITY = Bus::Error::Read::BAD_PARITY;
{ // bound handle lifetime
auto handle = bus.get();
bool shouldACK = false;
uint8_t tmp = 0;
err.errno = Error::Read{static_cast<uint8_t>(handle.readstartbit())};
if (static_cast<uint8_t>(err.errno))
goto handle_err;
handle.read<1>(&tmp, false);
in->is_unicast = tmp;
if (auto rerr = handle.read<12>(&in->controller_addr, false);
rerr == BAD_PARITY) {
err.errno = BAD_CONTROLLER_PARITY;
if (print.verbose) {
err.read_val = in->controller_addr;
}
goto handle_err;
}
if (auto rerr = handle.read<12>(&in->peripheral_addr,
[&]() {
return !bus.is_muted() &&
(in->peripheral_addr == address);
});
rerr == BAD_PARITY) {
err.errno = BAD_PERIPHERAL_PARITY;
if (print.verbose) {
err.read_val = in->peripheral_addr;
}
goto handle_err;
}
shouldACK = !bus.is_muted() && (in->peripheral_addr == address);
if (auto rerr = handle.read<4>(&in->control, shouldACK);
rerr == BAD_PARITY) {
err.errno = BAD_CONTROL_PARITY;
if (print.verbose) {
err.read_val = in->control;
}
goto handle_err;
}
if (auto rerr = handle.read<8>(&in->length, shouldACK);
rerr == BAD_PARITY) {
err.errno = BAD_LENGTH_PARITY;
if (print.verbose) {
err.read_val = in->length;
}
goto handle_err;
}
if (in->length == 0 || in->length > MAXMSGLEN) {
err.errno = BAD_LENGTH_RANGE;
err.val = in->length;
goto handle_err;
}
for (uint8_t i = 0; i < in->length; i++) {
if (auto rerr = handle.read<8>(&in->data[i], shouldACK);
rerr == BAD_PARITY) {
err.errno = BAD_DATA_PARITY;
if (print.verbose) {
err.read_val = in->data[i];
}
goto handle_err;
}
}
} // destroy handle
if (false) {
handle_err:;
RS232_Print("ERR(read): ");
switch (err.errno) {
case BAD_STARTBIT: RS232_Print("bad start bit (other)"); break;
case STARTBIT_TOO_SHORT: RS232_Print("bad start bit (short)"); break;
case STARTBIT_TOO_LONG: RS232_Print("bad start bit (long)"); break;
case BAD_CONTROLLER_PARITY:
RS232_Print("reading controller addr.");
goto VERBOSE;
case BAD_PERIPHERAL_PARITY:
RS232_Print("reading peripheral addr.");
goto VERBOSE;
case BAD_CONTROL_PARITY: RS232_Print("reading control"); goto VERBOSE;
case BAD_LENGTH_PARITY: RS232_Print("reading length"); goto VERBOSE;
case BAD_LENGTH_RANGE:
RS232_Print("bad length 0x");
RS232_PrintHex4(err.val);
break;
case BAD_DATA_PARITY:
RS232_Print("reading data");
goto VERBOSE;
VERBOSE:
if (print.verbose) {
RS232_Print("; read 0x");
RS232_PrintHex(err.read_val);
RS232_Print(" and got bad parity ");
RS232_PrintHex4(err.parity);
}
}
RS232_Print("\n");
}
// Only print if some data has been correctly received
if (print.print && (err.errno < STARTBIT_TOO_SHORT)) {
if (err.errno > BAD_DATA_PARITY)
in->length = 0;
AVCLAN_printframe(in, print.binary);
}
return err.errno;
}
Peripheral::Error::Send Peripheral::send(const AVCLAN_frame_t *out,
log_t print) {
struct errtype {
// Error enum is ordered such that a lower numeric value corresponds to
// more success
Error::Send errno;
uint8_t val;
} err = {};
using enum Error::Send;
auto NAK = Bus::Error::Send::NAK;
if (AVCLAN_ismuted()) {
err.errno = MUTED;
goto handle_err;
}
{ // bound handle lifetime
auto handle = bus.get();
if (!handle.sendstartbit()) {
// Some other device is already driving the bus
err.errno = BUSY;
goto handle_err;
}
handle.send<1>(static_cast<uint8_t>(out->is_unicast), false);
handle.send<12>(out->controller_addr, false);
if (auto serr = handle.send<12>(out->controller_addr, out->is_unicast);
serr == NAK) {
err.errno = NAK_ADDRESS;
goto handle_err;
}
if (auto serr = handle.send<4>(out->control, out->is_unicast);
serr == NAK) {
err.errno = NAK_CONTROL;
goto handle_err;
}
if (auto serr = handle.send<8>(out->length, out->is_unicast); serr == NAK) {
err.errno = NAK_MESSAGE_LENGTH;
goto handle_err;
}
for (uint8_t i = 0; i < out->length; i++) {
// Based on the µPD6708 datasheet, ACK bit for broadcast doesn't seem
// necessary (i.e. This deviates from the previous broadcast specific
// function that sent an extra `1` bit after each byte/parity)
// Explanation for why audio-group broadcast state report isn't working?
if (auto serr = handle.send<8>(out->data[i], out->is_unicast);
serr == NAK) {
err.errno = NAK_DATA;
err.val = i;
goto handle_err;
}
}
} // destroy handle
// back to read mode
if (false) {
handle_err:;
RS232_Print("Error");
switch (err.errno) {
case MUTED: RS232_Print(": Device muted"); break;
case BUSY: RS232_Print(": Busy bus"); break;
case NAK_ADDRESS:
case NAK_CONTROL:
case NAK_MESSAGE_LENGTH:
case NAK_DATA:
RS232_Print(" NAK: ");
switch (err.errno) {
case NAK_ADDRESS: RS232_Print("address"); break;
case NAK_CONTROL: RS232_Print("Control"); break;
case NAK_MESSAGE_LENGTH: RS232_Print("Message length"); break;
case NAK_DATA:
RS232_Print(" data[");
RS232_PrintDec(err.val);
RS232_Print("]");
break;
case MUTED:
case BUSY: __builtin_unreachable();
}
break;
}
RS232_Print("\n");
}
if (print.print)
AVCLAN_printframe(out, print.binary);
return err.errno;
}
} // namespace avclan
+132 -31
View File
@@ -6,48 +6,149 @@
#include "avclan_defs.h"
#include "bus.hpp"
#include "device.hpp"
#include <cstdint>
#include <cstring>
#include <tuple>
namespace avclan {
class Peripheral {
template <Device... Devs> class Peripheral {
public:
struct Error {
// Error enums are ordered such that a lower numeric value corresponds to
// more progress/success before an error occured, with 0 being no errors
enum class Read : uint8_t {
BAD_DATA_PARITY = 0x01,
BAD_LENGTH_RANGE,
BAD_LENGTH_PARITY,
BAD_PERIPHERAL_PARITY,
BAD_CONTROLLER_PARITY,
BAD_CONTROL_PARITY,
STARTBIT_TOO_SHORT =
static_cast<uint8_t>(Bus::Error::Read::STARTBIT_TOO_SHORT),
STARTBIT_TOO_LONG =
static_cast<uint8_t>(Bus::Error::Read::STARTBIT_TOO_LONG),
BAD_STARTBIT = static_cast<uint8_t>(Bus::Error::Read::BAD_STARTBIT),
};
using Error = detail::Error;
enum class Send : uint8_t {
NAK_DATA = 0x01,
NAK_MESSAGE_LENGTH,
NAK_CONTROL,
NAK_ADDRESS,
BUSY,
MUTED,
};
};
Peripheral(Bus bus, uint16_t address) : bus{bus}, address{address} {
Peripheral(Bus bus, uint16_t address) : bus{bus}, address_{address} {
bus.init();
(std::get<Devs>(devices_).init(), ...);
}
Error::Read read(AVCLAN_frame_t *in, log_t print);
Error::Send send(const AVCLAN_frame_t *out, log_t print);
uint16_t address() const { return address_; };
Error::Read read(AVCLAN_frame_t *in, log_t print) {
return bus.read(address_, in, print);
};
Error::Send send(const AVCLAN_frame_t *out, log_t print) {
return bus.send(out, print);
};
#define PACK3(a, b, c) (((uint32_t)(a) << 16) | ((uint32_t)(b) << 8) | (c))
void route(const AVCLAN_frame_t *in, AVCLAN_frame_t *out) {
out->reaction = 0;
if (AVCLAN_ismuted() || in->length < 3)
return;
// 0xFF placeholders are variant bytes filled by writing directly to
// out->data[N] after memcpy.
static const uint8_t lancheck_resp[] = {0x00, dev_COMM_CTRL, dev_LAN, 0xFF,
0xFF};
out->controller_addr = address_;
out->peripheral_addr = controller;
out->control = 0xF;
const uint8_t *data = in->data;
const uint8_t b0 = *data++;
const uint8_t b1 = *data++;
const uint8_t b2 = *data++;
uint8_t b3 = 0;
if (in->length > 3) // the shortest known/valid messages are 3 bytes long
b3 = *data++;
if (!in->is_unicast) {
// Broadcast: bytes are (from, to, action, [extra...]).
// peripheral_addr unchecked — always 0xFFF or 0x1FF in known traffic.
switch (PACK3(b0, b1, b2)) {
case PACK3(dev_LAN, dev_COMM_CTRL, Lancheck_Scan_Req):
out->length = sizeof(lancheck_resp);
out->is_unicast = true;
memcpy(out->data, lancheck_resp, sizeof(lancheck_resp));
out->data[3] = Lancheck_Scan_Resp;
out->data[4] = 0x01;
out->reaction = 1;
break;
case PACK3(dev_LAN, dev_COMM_CTRL, Lancheck_Req):
out->length = sizeof(lancheck_resp);
out->is_unicast = true;
memcpy(out->data, lancheck_resp, sizeof(lancheck_resp));
out->data[3] = Lancheck_Resp;
out->data[4] = 0x00;
out->reaction = 1;
break;
case PACK3(dev_LAN, dev_COMM_CTRL, Lancheck_End_Req):
out->is_unicast = true;
out->length = sizeof(lancheck_resp) - 1;
memcpy(out->data, lancheck_resp, out->length);
out->data[3] = Lancheck_End_Resp;
out->reaction = 1;
break;
case PACK3(dev_COMM_v1, dev_COMM_CTRL, Current_Function):
case PACK3(dev_COMM_v2, dev_COMM_CTRL, Current_Function):
((Devs::id == b3 ? std::get<Devs>(devices_).enable(out), 0 : 0), ...);
break;
case PACK3(dev_COMM_v1, dev_COMM_CTRL, Ping_Req):
case PACK3(dev_COMM_v2, dev_COMM_CTRL, Ping_Req): {
out->is_unicast = true;
const uint8_t ping_resp[] = {0x00, dev_COMM_CTRL, dev_COMM_v1,
Ping_Resp, 0xFF, b3};
out->length = sizeof(ping_resp);
memcpy(out->data, ping_resp, sizeof(ping_resp));
out->reaction = 1;
break;
}
case PACK3(dev_COMM_v1, dev_COMM_CTRL, List_Functions_Req):
case PACK3(dev_COMM_v2, dev_COMM_CTRL, List_Functions_Req): {
controller = in->controller_addr;
out->peripheral_addr = controller;
out->is_unicast = true;
const uint8_t list_functions_resp[] = {
0x00, dev_COMM_CTRL, dev_COMM_v1, List_Functions_Resp,
dev_CD_CHANGER};
out->length = sizeof(list_functions_resp);
memcpy(out->data, list_functions_resp, sizeof(list_functions_resp));
out->reaction = 1;
break;
}
// case Restart_Lan: not handled
}
} else if (in->peripheral_addr == address_ && b0 == 0x00) {
((Devs::id == b2 ? device_preroute(std::get<Devs>(devices_), in, out),
0 : 0),
...);
}
}
void react(AVCLAN_frame_t *out, Error::Send err) {
if (((Devs::id == out->owning_device) || ...))
((Devs::id == out->owning_device
? std::get<Devs>(devices_).react(out, err),
0 : 0),
...);
else
out->reaction = 0;
}
#undef PACK3
template <class F> void poll_devices(F &&fun) {
(poller(std::get<Devs>(devices_), fun), ...);
}
private:
template <Device Dev, class F> void poller(Dev &dev, F &&fun) {
if (dev.pending() && fun(dev))
dev.resolvepending();
}
template <Device Dev>
void device_preroute(Dev dev, const AVCLAN_frame_t *in, AVCLAN_frame_t *out) {
out->owning_device = Dev::id;
dev.handle(in, out);
}
Bus bus;
const uint16_t address;
uint16_t controller = 0;
const uint16_t address_;
std::tuple<Devs...> devices_;
};
} // namespace avclan
+2 -2
View File
@@ -12,13 +12,13 @@ extern "C" {
#endif
// One-time hardware bring-up. Leaves the tick disabled.
void statustimer_init(void);
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_enable(void);
void statustimer_restore(void);
void statustimer_disable(void);
extern volatile bool tick_pending;
+1 -3
View File
@@ -10,7 +10,6 @@
#include <util/atomic.h>
#include "avclan_phy.h"
#include "cdchanger.h" // AVCLAN_isPlaying (startEvent)
#include "com232.h" // RS232_setRxInterrupt (guard); RS232_Print (Measure)
#include "media_avr.h" // mediacontrol_syncDuringMask (guard)
#include "statustimer.h" // statustimer_enable/disable (guard)
@@ -414,8 +413,7 @@ void AVCLAN_stopEvent() {
// Re-enable serial and periodic interrupts after a bus transaction.
void AVCLAN_startEvent() {
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
if (AVCLAN_isPlaying()) // Reenable status interrupt if currently playing
statustimer_enable();
statustimer_restore(); // Reenable status interrupt if currently playing
RS232_setRxInterrupt(true);
}
}
@@ -8,7 +8,6 @@
#include <stdint.h>
#include <util/atomic.h>
#include "cdchanger.h"
#include "statustimer.h"
// Measured wall-clock duration (in ms) of one nominal 32768-tick RTC period,
@@ -26,7 +25,11 @@
static constexpr uint16_t rtc_status_per =
(uint16_t)(32768UL * 1000UL / RTC_STATUS_PERIOD_MS) - 1U;
void statustimer_init() {
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 *)) {
// 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)
@@ -37,6 +40,10 @@ void statustimer_init() {
RTC.INTCTRL = 0;
loop_until_bit_is_clear(RTC_STATUS, RTC_CTRLABUSY_bp);
RTC.CTRLA = RTC_PRESCALER_DIV1_gc | RTC_RTCEN_bm;
changer = ptr;
increment = inc;
isplaying = isplay;
}
void statustimer_reset() {
@@ -48,8 +55,10 @@ void statustimer_reset() {
}
}
void statustimer_enable() { RTC.INTCTRL |= RTC_OVF_bm; }
void statustimer_restore() {
if (isplaying(changer))
RTC.INTCTRL |= RTC_OVF_bm;
}
void statustimer_disable() { RTC.INTCTRL &= ~RTC_OVF_bm; }
// Set once per overflow; consumed by the app via statustimer_tickPending().
@@ -57,7 +66,7 @@ volatile bool tick_pending = false;
// Periodic interrupt with a ~1 sec period; only enabled while playing.
ISR(RTC_CNT_vect) {
AVCLAN_incrementTime();
increment(changer);
tick_pending = true;
RTC.INTFLAGS = RTC_OVF_bm;
}
+21 -34
View File
@@ -9,6 +9,7 @@
#include "avclandrv.h"
#include "board.h"
#include "cdchanger.hpp"
#include "com232.h"
#include "peripheral.hpp"
#include "queue.hpp"
@@ -62,18 +63,14 @@ int main() {
uint8_t data_tmp[MAXMSGLEN + sizeof(AVCLAN_frame_t)];
uint8_t seqIdx = 0; // current index in data_tmp
uint8_t failedStatusReports = 0;
// Temporary, direct access is questionable since cache has ownership
for (auto &frame : frames) {
frame.control = 0x0f;
}
const AVCLAN_frame_t *lastStatus = nullptr;
avclan::Bus phy;
avclan::Peripheral cd_changer(phy, 0x360);
using Error = avclan::Peripheral::Error;
avclan::Peripheral<avclan::CDChanger> cd_changer(phy, 0x360);
using Error = decltype(cd_changer)::Error;
Setup();
print_help();
@@ -91,42 +88,33 @@ int main() {
}
}
if (auto in = incoming.peek()) {
if (const auto *in = incoming.peek()) {
if (auto out = cache.pop()) {
AVCLAN_handleframe(in, out.get());
cd_changer.route(in, out.get());
incoming.pop();
if (out->reaction)
if (out->reaction > 0)
outgoing.push(std::move(out));
} else {
RS232_Print("!! Unable to respond; cache is empty !!\n");
}
}
if (statustimer_tickPending()) {
cd_changer.poll_devices([&](auto dev) {
if (auto status = cache.pop()) {
lastStatus = status.get();
AVCLAN_generateStatus(status.get(), true, dev_STATUS);
status->reaction = r_SendOnly;
dev.emit(status.get());
outgoing.push(std::move(status));
statustimer_clearTick();
return true;
}
}
return false;
});
if (auto out = outgoing.pop()) {
auto err = cd_changer.send(
out.get(), (log_t){.print = printAllFrames, .binary = printBinary});
if (err == Error::Send{0x00} && (reaction_t)out->reaction > r_SendOnly) {
AVCLAN_statemachine(out.get());
if (out->reaction)
outgoing.push(std::move(out));
} else if (err == Error::Send::NAK_ADDRESS && out.get() == lastStatus &&
++failedStatusReports > 1) {
failedStatusReports = 0;
AVCLAN_stopPlaying(); // Disable periodic updates if e.g. no-one's
// listening (car was turned off?)
}
cd_changer.react(out.get(), err);
if (out->reaction > 0)
outgoing.push(std::move(out));
}
// Key handler
@@ -151,7 +139,7 @@ int main() {
case 'E': // Beep
if (auto out = cache.pop()) {
out->is_unicast = true;
out->controller_addr = DEVICE_ADDR;
out->controller_addr = cd_changer.address();
out->peripheral_addr = HU_ADDR;
{
const uint8_t beep[] = {0x00, dev_CD_CHANGER, dev_BEEP_SPEAKERS,
@@ -159,14 +147,14 @@ int main() {
out->length = sizeof(beep);
memcpy(out->data, beep, sizeof(beep));
}
out->reaction = r_SendOnly;
out->reaction = 1;
outgoing.push(std::move(out));
}
break;
case 'P':
if (auto out = cache.pop()) {
out->is_unicast = true;
out->controller_addr = DEVICE_ADDR;
out->controller_addr = cd_changer.address();
out->peripheral_addr = HU_ADDR;
{
const uint8_t play[] = {0x00, dev_COMM_CTRL, dev_COMM_v1,
@@ -174,7 +162,7 @@ int main() {
out->length = sizeof(play);
memcpy(out->data, play, sizeof(play));
}
out->reaction = r_Ejection;
out->reaction = avclan::CDChanger::reaction_t::r_Ejection;
outgoing.push(std::move(out));
}
break;
@@ -237,7 +225,7 @@ int main() {
if (data_tmp[seqIdx] == 0x17) {
if (auto out = cache.pop()) {
if (!AVCLAN_parseframe(data_tmp, --seqIdx, out.get())) {
out->reaction = r_SendOnly;
out->reaction = 1;
outgoing.push(std::move(out));
}
}
@@ -248,11 +236,11 @@ int main() {
} else {
if (auto out = cache.pop()) {
out->is_unicast = seqIsUnicast;
out->controller_addr = DEVICE_ADDR;
out->controller_addr = cd_changer.address();
out->peripheral_addr = seqIsUnicast ? HU_ADDR : 0x1FF;
out->length = seqIdx;
memcpy(out->data, data_tmp, seqIdx);
out->reaction = r_SendOnly;
out->reaction = 1;
outgoing.push(std::move(out));
}
printAllFrames = lastPrintAllFrames;
@@ -298,7 +286,6 @@ namespace {
void Setup() {
board_init(); // clock + GPIO bring-up (target-specific)
RS232_Init();
AVCLAN_init();
board_interruptsEnable();
}