mirror of
https://github.com/halleysfifthinc/AVCLAN-Mockingboard.git
synced 2026-08-07 01:13:18 +00:00
Parameterise Peripheral by its Devices and add CDChanger as one
This commit is contained in:
+26
-18
@@ -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: ""
|
WarningsAsErrors: ""
|
||||||
HeaderFilterRegex: ""
|
ExcludeHeaderFilterRegex: 'out/build'
|
||||||
AnalyzeTemporaryDtors: false
|
HeaderFilterRegex: '^src/.*'
|
||||||
|
SystemHeaders: false
|
||||||
|
# AnalyzeTemporaryDtors: false
|
||||||
FormatStyle: none
|
FormatStyle: none
|
||||||
CheckOptions:
|
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
|
- key: bugprone-argument-comment.CommentBoolLiterals
|
||||||
value: "0"
|
value: "0"
|
||||||
- key: bugprone-argument-comment.CommentCharacterLiterals
|
- key: bugprone-argument-comment.CommentCharacterLiterals
|
||||||
@@ -31,8 +53,6 @@ CheckOptions:
|
|||||||
value: __builtin_expect
|
value: __builtin_expect
|
||||||
- key: bugprone-dangling-handle.HandleClasses
|
- key: bugprone-dangling-handle.HandleClasses
|
||||||
value: "std::basic_string_view;std::experimental::basic_string_view"
|
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
|
- key: bugprone-easily-swappable-parameters.IgnoredParameterNames
|
||||||
value: '"";iterator;Iterator;begin;Begin;end;End;first;First;last;Last;lhs;LHS;rhs;RHS'
|
value: '"";iterator;Iterator;begin;Begin;end;End;first;First;last;Last;lhs;LHS;rhs;RHS'
|
||||||
- key: bugprone-easily-swappable-parameters.IgnoredParameterTypeSuffixes
|
- key: bugprone-easily-swappable-parameters.IgnoredParameterTypeSuffixes
|
||||||
@@ -105,10 +125,6 @@ CheckOptions:
|
|||||||
value: llvm
|
value: llvm
|
||||||
- key: bugprone-suspicious-enum-usage.StrictMode
|
- key: bugprone-suspicious-enum-usage.StrictMode
|
||||||
value: "false"
|
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
|
- key: bugprone-suspicious-missing-comma.MaxConcatenatedTokens
|
||||||
value: "5"
|
value: "5"
|
||||||
- key: bugprone-suspicious-missing-comma.RatioThreshold
|
- key: bugprone-suspicious-missing-comma.RatioThreshold
|
||||||
@@ -151,19 +167,13 @@ CheckOptions:
|
|||||||
value: "false"
|
value: "false"
|
||||||
- key: llvm-qualified-auto.AddConstToQualified
|
- key: llvm-qualified-auto.AddConstToQualified
|
||||||
value: "false"
|
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
|
- key: misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic
|
||||||
value: "false"
|
value: "false"
|
||||||
- key: misc-non-private-member-variables-in-classes.IgnorePublicMemberVariables
|
- key: misc-non-private-member-variables-in-classes.IgnorePublicMemberVariables
|
||||||
value: "false"
|
value: "false"
|
||||||
- key: misc-throw-by-value-catch-by-reference.CheckThrowTemporaries
|
- key: misc-throw-by-value-catch-by-reference.CheckThrowTemporaries
|
||||||
value: "true"
|
value: "true"
|
||||||
- key: misc-throw-by-value-catch-by-reference.MaxSize
|
- key: misc-throw-by-value-catch-by-reference.WarnOnLargeObject
|
||||||
value: "-1"
|
|
||||||
- key: misc-throw-by-value-catch-by-reference.WarnOnLargeObjects
|
|
||||||
value: "false"
|
value: "false"
|
||||||
- key: misc-uniqueptr-reset-release.IncludeStyle
|
- key: misc-uniqueptr-reset-release.IncludeStyle
|
||||||
value: llvm
|
value: llvm
|
||||||
@@ -243,8 +253,6 @@ CheckOptions:
|
|||||||
value: "true"
|
value: "true"
|
||||||
- key: modernize-use-equals-delete.IgnoreMacros
|
- key: modernize-use-equals-delete.IgnoreMacros
|
||||||
value: "true"
|
value: "true"
|
||||||
- key: modernize-use-nodiscard.ReplacementString
|
|
||||||
value: "[[nodiscard]]"
|
|
||||||
- key: modernize-use-noexcept.ReplacementString
|
- key: modernize-use-noexcept.ReplacementString
|
||||||
value: ""
|
value: ""
|
||||||
- key: modernize-use-noexcept.UseNoexceptFalse
|
- key: modernize-use-noexcept.UseNoexceptFalse
|
||||||
|
|||||||
+2
-3
@@ -8,6 +8,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
|||||||
|
|
||||||
add_compile_options(
|
add_compile_options(
|
||||||
-Wall -Wswitch-enum -Werror
|
-Wall -Wswitch-enum -Werror
|
||||||
|
$<$<COMPILE_LANGUAGE:CXX>:-fno-threadsafe-statics>
|
||||||
$<$<COMPILE_LANGUAGE:CXX>:-fno-exceptions>
|
$<$<COMPILE_LANGUAGE:CXX>:-fno-exceptions>
|
||||||
$<$<COMPILE_LANGUAGE:CXX>:-fno-rtti>
|
$<$<COMPILE_LANGUAGE:CXX>:-fno-rtti>
|
||||||
$<$<CONFIG:Debug>:-fanalyzer>)
|
$<$<CONFIG:Debug>:-fanalyzer>)
|
||||||
@@ -32,9 +33,7 @@ set(AVCLAN_TARGET avr-attiny3216 CACHE STRING "Hardware target (port) to build")
|
|||||||
# are contributed by its subdirectory.
|
# are contributed by its subdirectory.
|
||||||
add_library(avclan STATIC
|
add_library(avclan STATIC
|
||||||
src/avclan/avclan_frame.c
|
src/avclan/avclan_frame.c
|
||||||
src/avclan/avclan_protocol.c
|
src/avclan/cdchanger.cc
|
||||||
src/avclan/cdchanger.c
|
|
||||||
src/avclan/peripheral.cc
|
|
||||||
src/avclan/bus.cc
|
src/avclan/bus.cc
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -117,6 +117,7 @@ typedef struct print_struct {
|
|||||||
|
|
||||||
typedef struct AVCLAN_frame_struct {
|
typedef struct AVCLAN_frame_struct {
|
||||||
uint8_t reaction;
|
uint8_t reaction;
|
||||||
|
uint8_t owning_device;
|
||||||
bool is_unicast;
|
bool is_unicast;
|
||||||
uint16_t controller_addr; // formerly "master"
|
uint16_t controller_addr; // formerly "master"
|
||||||
uint16_t peripheral_addr; // formerly "slave"
|
uint16_t peripheral_addr; // formerly "slave"
|
||||||
|
|||||||
@@ -61,38 +61,3 @@ void AVCLan_Measure(void);
|
|||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#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
|
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -11,7 +11,5 @@
|
|||||||
#include "avclan_defs.h"
|
#include "avclan_defs.h"
|
||||||
#include "avclan_frame.h"
|
#include "avclan_frame.h"
|
||||||
#include "avclan_phy.h"
|
#include "avclan_phy.h"
|
||||||
#include "avclan_protocol.h"
|
|
||||||
#include "cdchanger.h"
|
|
||||||
#include "mediacontrol.h"
|
#include "mediacontrol.h"
|
||||||
#include "statustimer.h"
|
#include "statustimer.h"
|
||||||
|
|||||||
+267
-4
@@ -3,27 +3,290 @@
|
|||||||
// 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
|
||||||
|
|
||||||
|
/*
|
||||||
|
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 "bus.hpp"
|
||||||
|
#include "avclan.hpp"
|
||||||
#include "avclan_defs.h"
|
#include "avclan_defs.h"
|
||||||
|
#include "avclan_frame.h"
|
||||||
#include "avclan_phy.h" // bridge until phy has been ported
|
#include "avclan_phy.h" // bridge until phy has been ported
|
||||||
|
#include "com232.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr int ADDR_WIDTH = 12;
|
||||||
|
}
|
||||||
|
|
||||||
namespace avclan {
|
namespace avclan {
|
||||||
void Bus::init() { AVCLAN_busInit(); };
|
void Bus::init() { AVCLAN_busInit(); };
|
||||||
void Bus::mute(bool mute) { AVCLAN_muteDevice(mute); };
|
void Bus::mute(bool mute) { AVCLAN_muteDevice(mute); };
|
||||||
bool Bus::is_muted() const { return AVCLAN_ismuted(); };
|
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 {}; };
|
Bus::Handle Bus::get() { return {}; };
|
||||||
|
|
||||||
bool Bus::Handle::sendstartbit() { return AVCLAN_sendstartbit(); };
|
bool Bus::Handle::sendstartbit() { return AVCLAN_sendstartbit(); };
|
||||||
Bus::Error::Read Bus::Handle::readstartbit() {
|
auto Bus::Handle::readstartbit() -> Read {
|
||||||
using enum Error::Read;
|
using enum Error::Read;
|
||||||
auto err = AVCLAN_readstartbit();
|
auto err = AVCLAN_readstartbit();
|
||||||
if (err == rSTARTBIT_TOO_LONG)
|
if (err == rSTARTBIT_TOO_LONG)
|
||||||
return STARTBIT_TOO_LONG;
|
return STARTBIT_TOO_LONG;
|
||||||
else if (err == rLATCHED_COMPARATOR)
|
|
||||||
|
if (err == rLATCHED_COMPARATOR)
|
||||||
return BAD_STARTBIT;
|
return BAD_STARTBIT;
|
||||||
else if (err == rSTARTBIT_TOO_SHORT)
|
|
||||||
|
if (err == rSTARTBIT_TOO_SHORT)
|
||||||
return STARTBIT_TOO_SHORT;
|
return STARTBIT_TOO_SHORT;
|
||||||
else
|
|
||||||
return Read{0};
|
return Read{0};
|
||||||
};
|
};
|
||||||
void Bus::Handle::send_ACK() { AVCLAN_sendbit_ACK(); };
|
void Bus::Handle::send_ACK() { AVCLAN_sendbit_ACK(); };
|
||||||
|
|||||||
+20
-25
@@ -49,31 +49,24 @@
|
|||||||
|
|
||||||
#include <concepts>
|
#include <concepts>
|
||||||
|
|
||||||
|
#include "avclan.hpp"
|
||||||
#include "avclan_defs.h"
|
#include "avclan_defs.h"
|
||||||
#include "avclan_phy.h" // bridge until phy has been ported
|
#include "avclan_phy.h" // bridge until phy has been ported
|
||||||
|
|
||||||
namespace avclan {
|
namespace avclan {
|
||||||
class Bus {
|
class Bus {
|
||||||
public:
|
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;
|
class Handle;
|
||||||
|
using Error = detail::Error;
|
||||||
|
|
||||||
void init();
|
void init();
|
||||||
void mute(bool mute);
|
void mute(bool mute);
|
||||||
bool is_muted() const;
|
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 {
|
class Bus::Handle {
|
||||||
@@ -84,6 +77,7 @@ public:
|
|||||||
~Handle() { AVCLAN_startEvent(); }
|
~Handle() { AVCLAN_startEvent(); }
|
||||||
Handle(const Handle &) = delete;
|
Handle(const Handle &) = delete;
|
||||||
Handle(Handle &&) = delete;
|
Handle(Handle &&) = delete;
|
||||||
|
using Error = detail::Error;
|
||||||
|
|
||||||
bool sendstartbit();
|
bool sendstartbit();
|
||||||
Error::Read readstartbit();
|
Error::Read readstartbit();
|
||||||
@@ -108,7 +102,8 @@ public:
|
|||||||
readbits<1>(&read_parity);
|
readbits<1>(&read_parity);
|
||||||
if (calc_parity != read_parity) {
|
if (calc_parity != read_parity) {
|
||||||
return Read::BAD_PARITY;
|
return Read::BAD_PARITY;
|
||||||
} else if (ack()) {
|
}
|
||||||
|
if (ack()) {
|
||||||
send_ACK();
|
send_ACK();
|
||||||
} else
|
} else
|
||||||
readbits<1>(&read_parity);
|
readbits<1>(&read_parity);
|
||||||
@@ -133,8 +128,8 @@ private:
|
|||||||
bit_start = 0x10
|
bit_start = 0x10
|
||||||
};
|
};
|
||||||
|
|
||||||
void send_ACK();
|
static void send_ACK();
|
||||||
uint8_t read_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 sendbits(T bits);
|
||||||
template <auto N, class T> avclan_bit_t readbits(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) {
|
template <> inline avclan_bit_t Bus::Handle::sendbits<8>(uint8_t bits) {
|
||||||
return AVCLAN_sendbyte(&byte);
|
return AVCLAN_sendbyte(&bits);
|
||||||
};
|
};
|
||||||
template <> inline avclan_bit_t Bus::Handle::sendbits<1>(uint8_t byte) {
|
template <> inline avclan_bit_t Bus::Handle::sendbits<1>(uint8_t bits) {
|
||||||
const avclan_bit_t b{static_cast<avclan_bit_t>(byte & 1u)};
|
const avclan_bit_t bit{static_cast<avclan_bit_t>(bits & 1U)};
|
||||||
AVCLAN_sendbit(b);
|
AVCLAN_sendbit(bit);
|
||||||
return b;
|
return bit;
|
||||||
};
|
};
|
||||||
template <> inline avclan_bit_t Bus::Handle::readbits<8>(uint8_t *byte) {
|
template <> inline avclan_bit_t Bus::Handle::readbits<8>(uint8_t *bits) {
|
||||||
return static_cast<avclan_bit_t>(AVCLAN_readbyte(byte));
|
return static_cast<avclan_bit_t>(AVCLAN_readbyte(bits));
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace avclan
|
} // namespace avclan
|
||||||
|
|||||||
@@ -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 0–99 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;
|
|
||||||
}
|
|
||||||
@@ -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 0–TWODIGIT_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
|
||||||
@@ -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
|
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
@@ -6,48 +6,149 @@
|
|||||||
|
|
||||||
#include "avclan_defs.h"
|
#include "avclan_defs.h"
|
||||||
#include "bus.hpp"
|
#include "bus.hpp"
|
||||||
|
#include "device.hpp"
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
namespace avclan {
|
namespace avclan {
|
||||||
class Peripheral {
|
template <Device... Devs> class Peripheral {
|
||||||
public:
|
public:
|
||||||
struct Error {
|
using Error = detail::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),
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class Send : uint8_t {
|
Peripheral(Bus bus, uint16_t address) : bus{bus}, address_{address} {
|
||||||
NAK_DATA = 0x01,
|
|
||||||
NAK_MESSAGE_LENGTH,
|
|
||||||
NAK_CONTROL,
|
|
||||||
NAK_ADDRESS,
|
|
||||||
BUSY,
|
|
||||||
MUTED,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
Peripheral(Bus bus, uint16_t address) : bus{bus}, address{address} {
|
|
||||||
bus.init();
|
bus.init();
|
||||||
|
(std::get<Devs>(devices_).init(), ...);
|
||||||
}
|
}
|
||||||
|
|
||||||
Error::Read read(AVCLAN_frame_t *in, log_t print);
|
uint16_t address() const { return address_; };
|
||||||
Error::Send send(const AVCLAN_frame_t *out, log_t print);
|
|
||||||
|
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:
|
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;
|
Bus bus;
|
||||||
const uint16_t address;
|
uint16_t controller = 0;
|
||||||
|
const uint16_t address_;
|
||||||
|
std::tuple<Devs...> devices_;
|
||||||
};
|
};
|
||||||
} // namespace avclan
|
} // namespace avclan
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ extern "C" {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// One-time hardware bring-up. Leaves the tick disabled.
|
// 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.
|
// Reset the count so the next tick is ~1 s out, and enable the tick.
|
||||||
void statustimer_reset(void);
|
void statustimer_reset(void);
|
||||||
|
|
||||||
// Enable / disable the ~1 Hz tick.
|
// Enable / disable the ~1 Hz tick.
|
||||||
void statustimer_enable(void);
|
void statustimer_restore(void);
|
||||||
void statustimer_disable(void);
|
void statustimer_disable(void);
|
||||||
|
|
||||||
extern volatile bool tick_pending;
|
extern volatile bool tick_pending;
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
#include <util/atomic.h>
|
#include <util/atomic.h>
|
||||||
|
|
||||||
#include "avclan_phy.h"
|
#include "avclan_phy.h"
|
||||||
#include "cdchanger.h" // AVCLAN_isPlaying (startEvent)
|
|
||||||
#include "com232.h" // RS232_setRxInterrupt (guard); RS232_Print (Measure)
|
#include "com232.h" // RS232_setRxInterrupt (guard); RS232_Print (Measure)
|
||||||
#include "media_avr.h" // mediacontrol_syncDuringMask (guard)
|
#include "media_avr.h" // mediacontrol_syncDuringMask (guard)
|
||||||
#include "statustimer.h" // statustimer_enable/disable (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.
|
// Re-enable serial and periodic interrupts after a bus transaction.
|
||||||
void AVCLAN_startEvent() {
|
void AVCLAN_startEvent() {
|
||||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
|
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
|
||||||
if (AVCLAN_isPlaying()) // Reenable status interrupt if currently playing
|
statustimer_restore(); // Reenable status interrupt if currently playing
|
||||||
statustimer_enable();
|
|
||||||
RS232_setRxInterrupt(true);
|
RS232_setRxInterrupt(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <util/atomic.h>
|
#include <util/atomic.h>
|
||||||
|
|
||||||
#include "cdchanger.h"
|
|
||||||
#include "statustimer.h"
|
#include "statustimer.h"
|
||||||
|
|
||||||
// Measured wall-clock duration (in ms) of one nominal 32768-tick RTC period,
|
// Measured wall-clock duration (in ms) of one nominal 32768-tick RTC period,
|
||||||
@@ -26,7 +25,11 @@
|
|||||||
static constexpr uint16_t rtc_status_per =
|
static constexpr uint16_t rtc_status_per =
|
||||||
(uint16_t)(32768UL * 1000UL / RTC_STATUS_PERIOD_MS) - 1U;
|
(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.
|
// Setup RTC as a ~1 sec periodic timer via the normal counter's overflow.
|
||||||
// Use the RTC directly (not PIT) to tune the status report interval closer to
|
// Use the RTC directly (not PIT) to tune the status report interval closer to
|
||||||
// 1 sec (internal osc may be slightly off)
|
// 1 sec (internal osc may be slightly off)
|
||||||
@@ -37,6 +40,10 @@ void statustimer_init() {
|
|||||||
RTC.INTCTRL = 0;
|
RTC.INTCTRL = 0;
|
||||||
loop_until_bit_is_clear(RTC_STATUS, RTC_CTRLABUSY_bp);
|
loop_until_bit_is_clear(RTC_STATUS, RTC_CTRLABUSY_bp);
|
||||||
RTC.CTRLA = RTC_PRESCALER_DIV1_gc | RTC_RTCEN_bm;
|
RTC.CTRLA = RTC_PRESCALER_DIV1_gc | RTC_RTCEN_bm;
|
||||||
|
|
||||||
|
changer = ptr;
|
||||||
|
increment = inc;
|
||||||
|
isplaying = isplay;
|
||||||
}
|
}
|
||||||
|
|
||||||
void statustimer_reset() {
|
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; }
|
void statustimer_disable() { RTC.INTCTRL &= ~RTC_OVF_bm; }
|
||||||
|
|
||||||
// Set once per overflow; consumed by the app via statustimer_tickPending().
|
// Set once per overflow; consumed by the app via statustimer_tickPending().
|
||||||
@@ -57,7 +66,7 @@ volatile bool tick_pending = false;
|
|||||||
|
|
||||||
// Periodic interrupt with a ~1 sec period; only enabled while playing.
|
// Periodic interrupt with a ~1 sec period; only enabled while playing.
|
||||||
ISR(RTC_CNT_vect) {
|
ISR(RTC_CNT_vect) {
|
||||||
AVCLAN_incrementTime();
|
increment(changer);
|
||||||
tick_pending = true;
|
tick_pending = true;
|
||||||
RTC.INTFLAGS = RTC_OVF_bm;
|
RTC.INTFLAGS = RTC_OVF_bm;
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-33
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
#include "avclandrv.h"
|
#include "avclandrv.h"
|
||||||
#include "board.h"
|
#include "board.h"
|
||||||
|
#include "cdchanger.hpp"
|
||||||
#include "com232.h"
|
#include "com232.h"
|
||||||
#include "peripheral.hpp"
|
#include "peripheral.hpp"
|
||||||
#include "queue.hpp"
|
#include "queue.hpp"
|
||||||
@@ -62,18 +63,14 @@ int main() {
|
|||||||
uint8_t data_tmp[MAXMSGLEN + sizeof(AVCLAN_frame_t)];
|
uint8_t data_tmp[MAXMSGLEN + sizeof(AVCLAN_frame_t)];
|
||||||
uint8_t seqIdx = 0; // current index in data_tmp
|
uint8_t seqIdx = 0; // current index in data_tmp
|
||||||
|
|
||||||
uint8_t failedStatusReports = 0;
|
|
||||||
|
|
||||||
// Temporary, direct access is questionable since cache has ownership
|
// Temporary, direct access is questionable since cache has ownership
|
||||||
for (auto &frame : frames) {
|
for (auto &frame : frames) {
|
||||||
frame.control = 0x0f;
|
frame.control = 0x0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AVCLAN_frame_t *lastStatus = nullptr;
|
|
||||||
|
|
||||||
avclan::Bus phy;
|
avclan::Bus phy;
|
||||||
avclan::Peripheral cd_changer(phy, 0x360);
|
avclan::Peripheral<avclan::CDChanger> cd_changer(phy, 0x360);
|
||||||
using Error = avclan::Peripheral::Error;
|
using Error = decltype(cd_changer)::Error;
|
||||||
|
|
||||||
Setup();
|
Setup();
|
||||||
print_help();
|
print_help();
|
||||||
@@ -91,42 +88,33 @@ int main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto in = incoming.peek()) {
|
if (const auto *in = incoming.peek()) {
|
||||||
if (auto out = cache.pop()) {
|
if (auto out = cache.pop()) {
|
||||||
AVCLAN_handleframe(in, out.get());
|
cd_changer.route(in, out.get());
|
||||||
incoming.pop();
|
incoming.pop();
|
||||||
|
|
||||||
if (out->reaction)
|
if (out->reaction > 0)
|
||||||
outgoing.push(std::move(out));
|
outgoing.push(std::move(out));
|
||||||
} else {
|
} else {
|
||||||
RS232_Print("!! Unable to respond; cache is empty !!\n");
|
RS232_Print("!! Unable to respond; cache is empty !!\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (statustimer_tickPending()) {
|
cd_changer.poll_devices([&](auto dev) {
|
||||||
if (auto status = cache.pop()) {
|
if (auto status = cache.pop()) {
|
||||||
lastStatus = status.get();
|
dev.emit(status.get());
|
||||||
AVCLAN_generateStatus(status.get(), true, dev_STATUS);
|
|
||||||
status->reaction = r_SendOnly;
|
|
||||||
outgoing.push(std::move(status));
|
outgoing.push(std::move(status));
|
||||||
statustimer_clearTick();
|
return true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
if (auto out = outgoing.pop()) {
|
if (auto out = outgoing.pop()) {
|
||||||
auto err = cd_changer.send(
|
auto err = cd_changer.send(
|
||||||
out.get(), (log_t){.print = printAllFrames, .binary = printBinary});
|
out.get(), (log_t){.print = printAllFrames, .binary = printBinary});
|
||||||
if (err == Error::Send{0x00} && (reaction_t)out->reaction > r_SendOnly) {
|
cd_changer.react(out.get(), err);
|
||||||
AVCLAN_statemachine(out.get());
|
if (out->reaction > 0)
|
||||||
if (out->reaction)
|
|
||||||
outgoing.push(std::move(out));
|
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?)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Key handler
|
// Key handler
|
||||||
@@ -151,7 +139,7 @@ int main() {
|
|||||||
case 'E': // Beep
|
case 'E': // Beep
|
||||||
if (auto out = cache.pop()) {
|
if (auto out = cache.pop()) {
|
||||||
out->is_unicast = true;
|
out->is_unicast = true;
|
||||||
out->controller_addr = DEVICE_ADDR;
|
out->controller_addr = cd_changer.address();
|
||||||
out->peripheral_addr = HU_ADDR;
|
out->peripheral_addr = HU_ADDR;
|
||||||
{
|
{
|
||||||
const uint8_t beep[] = {0x00, dev_CD_CHANGER, dev_BEEP_SPEAKERS,
|
const uint8_t beep[] = {0x00, dev_CD_CHANGER, dev_BEEP_SPEAKERS,
|
||||||
@@ -159,14 +147,14 @@ int main() {
|
|||||||
out->length = sizeof(beep);
|
out->length = sizeof(beep);
|
||||||
memcpy(out->data, beep, sizeof(beep));
|
memcpy(out->data, beep, sizeof(beep));
|
||||||
}
|
}
|
||||||
out->reaction = r_SendOnly;
|
out->reaction = 1;
|
||||||
outgoing.push(std::move(out));
|
outgoing.push(std::move(out));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'P':
|
case 'P':
|
||||||
if (auto out = cache.pop()) {
|
if (auto out = cache.pop()) {
|
||||||
out->is_unicast = true;
|
out->is_unicast = true;
|
||||||
out->controller_addr = DEVICE_ADDR;
|
out->controller_addr = cd_changer.address();
|
||||||
out->peripheral_addr = HU_ADDR;
|
out->peripheral_addr = HU_ADDR;
|
||||||
{
|
{
|
||||||
const uint8_t play[] = {0x00, dev_COMM_CTRL, dev_COMM_v1,
|
const uint8_t play[] = {0x00, dev_COMM_CTRL, dev_COMM_v1,
|
||||||
@@ -174,7 +162,7 @@ int main() {
|
|||||||
out->length = sizeof(play);
|
out->length = sizeof(play);
|
||||||
memcpy(out->data, play, 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));
|
outgoing.push(std::move(out));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -237,7 +225,7 @@ int main() {
|
|||||||
if (data_tmp[seqIdx] == 0x17) {
|
if (data_tmp[seqIdx] == 0x17) {
|
||||||
if (auto out = cache.pop()) {
|
if (auto out = cache.pop()) {
|
||||||
if (!AVCLAN_parseframe(data_tmp, --seqIdx, out.get())) {
|
if (!AVCLAN_parseframe(data_tmp, --seqIdx, out.get())) {
|
||||||
out->reaction = r_SendOnly;
|
out->reaction = 1;
|
||||||
outgoing.push(std::move(out));
|
outgoing.push(std::move(out));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -248,11 +236,11 @@ int main() {
|
|||||||
} else {
|
} else {
|
||||||
if (auto out = cache.pop()) {
|
if (auto out = cache.pop()) {
|
||||||
out->is_unicast = seqIsUnicast;
|
out->is_unicast = seqIsUnicast;
|
||||||
out->controller_addr = DEVICE_ADDR;
|
out->controller_addr = cd_changer.address();
|
||||||
out->peripheral_addr = seqIsUnicast ? HU_ADDR : 0x1FF;
|
out->peripheral_addr = seqIsUnicast ? HU_ADDR : 0x1FF;
|
||||||
out->length = seqIdx;
|
out->length = seqIdx;
|
||||||
memcpy(out->data, data_tmp, seqIdx);
|
memcpy(out->data, data_tmp, seqIdx);
|
||||||
out->reaction = r_SendOnly;
|
out->reaction = 1;
|
||||||
outgoing.push(std::move(out));
|
outgoing.push(std::move(out));
|
||||||
}
|
}
|
||||||
printAllFrames = lastPrintAllFrames;
|
printAllFrames = lastPrintAllFrames;
|
||||||
@@ -298,7 +286,6 @@ namespace {
|
|||||||
void Setup() {
|
void Setup() {
|
||||||
board_init(); // clock + GPIO bring-up (target-specific)
|
board_init(); // clock + GPIO bring-up (target-specific)
|
||||||
RS232_Init();
|
RS232_Init();
|
||||||
AVCLAN_init();
|
|
||||||
board_interruptsEnable();
|
board_interruptsEnable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user