Decouple hardware specific code from generic, agnostic code

This commit is contained in:
Allen Hill
2026-06-23 17:20:59 -07:00
parent fb130559e2
commit 0b0c9335f9
24 changed files with 768 additions and 910 deletions
+33 -1
View File
@@ -27,7 +27,6 @@
#ifndef AVCLAN_DEFS_H
#define AVCLAN_DEFS_H
#include <stdbool.h>
#include <stdint.h>
#define MAXMSGLEN 32
@@ -143,4 +142,37 @@ typedef struct AVCLAN_frame_struct {
uint8_t *data;
} AVCLAN_frame_t;
// A single bus symbol. bit_zero/bit_one carry data (and double as parity
// values); bit_start marks a frame start bit.
typedef enum avclan_bit : uint8_t {
bit_zero = 0x00,
bit_one = 0x01,
bit_start = 0x10
} avclan_bit_t;
// Error enums are ordered such that a lower numeric value corresponds to more
// progress/success before an error occured, with 0 being no errors
typedef enum : uint8_t {
rNO_ERROR = 0x00,
rBAD_DATA_PARITY,
rBAD_LENGTH_RANGE,
rBAD_LENGTH_PARITY,
rBAD_PERIPHERAL_PARITY,
rBAD_CONTROLLER_PARITY,
rBAD_CONTROL_PARITY,
rSTARTBIT_TOO_SHORT,
rSTARTBIT_TOO_LONG,
rLATCHED_COMPARATOR,
} avclan_readerr_t;
typedef enum : uint8_t {
sNO_ERROR = 0x00,
sNAK_DATA,
sNAK_MESSAGE_LENGTH,
sNAK_CONTROL,
sNAK_ADDRESS,
sBUSY,
sMUTED,
} avclan_senderr_t;
#endif // AVCLAN_DEFS_H
+50 -202
View File
@@ -20,58 +20,16 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/sfr_defs.h>
#include <stdint.h>
#include <string.h>
#include <util/atomic.h>
#include "avclan_frame.h"
#include "avclan_phy.h"
#include "cdchanger.h"
#include "com232.h"
#include "mediacontrol.h"
#include "statustimer.h"
#include "avclan_phy.h" // bus symbol I/O + transaction guard (target-provided)
#include "com232.h" // error logging
// F_CPU defined in timing.h and potentially needed by avr-libc (e.g. delay.h)
#include "timing.h"
/* Disable non-read related interrupts (USART RX, PIT, TCA) during AVCLAN reads.
*/
void AVCLAN_stopEvent() {
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
statustimer_disable();
USART0.CTRLA &= ~USART_RXCIE_bm;
mediacontrol_syncDuringMask();
}
}
// Re-enable serial and periodic interrupts.
void AVCLAN_startEvent() {
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
if (AVCLAN_isPlaying()) // Reenable status interrupt if currently playing
statustimer_enable();
USART0.CTRLA |= USART_RXCIE_bm;
}
}
uint8_t AVCLAN_readframe(AVCLAN_frame_t *frame, log_t print) {
avclan_readerr_t AVCLAN_readframe(AVCLAN_frame_t *frame, log_t print) {
struct errtype {
// Error enum is ordered such that a lower numeric value corresponds to more
// successful read
enum : uint8_t {
NO_ERROR = 0x00,
BAD_DATA_PARITY = 0x01,
BAD_LENGTH_RANGE,
BAD_LENGTH_PARITY,
BAD_PERIPHERAL_PARITY,
BAD_CONTROLLER_PARITY,
BAD_CONTROL_PARITY,
STARTBIT_TOO_SHORT,
STARTBIT_TOO_LONG,
LATCHED_COMPARATOR,
} errno;
avclan_readerr_t errno;
union {
uint8_t val; // BAD_LENGTH_RANGE: the out-of-range length value
struct {
@@ -81,48 +39,13 @@ uint8_t AVCLAN_readframe(AVCLAN_frame_t *frame, log_t print) {
};
} err = {0};
AVCLAN_stopEvent(); // disable timer1 interrupt
AVCLAN_stopEvent(); // quiesce contending sources during the read
uint8_t tmp = 0;
uint16_t startbitlen = TCB1.CNT = 0;
while (!BUS_IS_IDLE) {
startbitlen = TCB1.CNT;
if (startbitlen > (uint16_t)AVCLAN_STARTBIT_LOGIC_0 * 1.2) {
err.errno = STARTBIT_TOO_LONG;
while (!BUS_IS_IDLE) {
// If bus is "driven" too long, assume the AC2 is latched (e.g.
// because the bus is actually floating). Kick it if so.
// This should prevent/resolve a flood of "STARTBIT_TOO_LONG" errors
if (TCB1.CNT > (uint16_t)(AVCLAN_STARTBIT_LOGIC_0 * 3)) {
err.errno = LATCHED_COMPARATOR;
PORTA.OUTSET = PIN7_bm; // preset high before enabling the driver
PORTA.DIRSET = PIN7_bm; // drive (-) hard high
TCB1.CNT = 0;
while (!BUS_IS_IDLE && TCB1.CNT < (uint16_t)AVCLAN_BIT0_LOGIC_1) {
// Wait a max of ~6μs until bus is idle
}
PORTA.DIRCLR = PIN7_bm; // back to high-Z comparator input
PORTA.OUTCLR = PIN7_bm;
}
}
goto handle_err;
}
}
if (startbitlen < (uint16_t)(AVCLAN_STARTBIT_LOGIC_0 * 0.8)) {
err.errno = STARTBIT_TOO_SHORT;
// We missed the beginning of this message; wait for it to finish (bus
// continuously idle for >1 bit length) before returning, so we don't have
// multiple false-starts while the in-progress message keeps sending more
// bits.
TCB1.CNT = 0;
while (TCB1.CNT < (uint16_t)(AVCLAN_BIT_LENGTH_MAX * 1.2)) {
if (!BUS_IS_IDLE)
TCB1.CNT = 0;
}
err.errno = AVCLAN_readstartbit();
if (err.errno)
goto handle_err;
}
// Otherwise that was a start bit
AVCLAN_readbits(&tmp, 1);
frame->is_unicast = tmp;
@@ -130,7 +53,7 @@ uint8_t AVCLAN_readframe(AVCLAN_frame_t *frame, log_t print) {
uint8_t parity = AVCLAN_readbits(&frame->controller_addr, 12);
AVCLAN_readbits(&tmp, 1);
if (parity != (tmp &= 1)) {
err.errno = BAD_CONTROLLER_PARITY;
err.errno = rBAD_CONTROLLER_PARITY;
if (print.verbose) {
err.read_val = frame->controller_addr;
err.parity = tmp;
@@ -141,7 +64,7 @@ uint8_t AVCLAN_readframe(AVCLAN_frame_t *frame, log_t print) {
parity = AVCLAN_readbits(&frame->peripheral_addr, 12);
AVCLAN_readbits(&tmp, 1);
if (parity != (tmp &= 1)) {
err.errno = BAD_PERIPHERAL_PARITY;
err.errno = rBAD_PERIPHERAL_PARITY;
if (print.verbose) {
err.read_val = frame->peripheral_addr;
err.parity = tmp;
@@ -159,7 +82,7 @@ uint8_t AVCLAN_readframe(AVCLAN_frame_t *frame, log_t print) {
parity = AVCLAN_readbits(&frame->control, 4);
AVCLAN_readbits(&tmp, 1);
if (parity != (tmp &= 1)) {
err.errno = BAD_CONTROL_PARITY;
err.errno = rBAD_CONTROL_PARITY;
if (print.verbose) {
err.read_val = frame->control;
err.parity = tmp;
@@ -174,7 +97,7 @@ uint8_t AVCLAN_readframe(AVCLAN_frame_t *frame, log_t print) {
parity = AVCLAN_readbyte(&frame->length);
AVCLAN_readbits(&tmp, 1);
if (parity != (tmp &= 1)) {
err.errno = BAD_LENGTH_PARITY;
err.errno = rBAD_LENGTH_PARITY;
if (print.verbose) {
err.read_val = frame->length;
err.parity = tmp;
@@ -187,7 +110,7 @@ uint8_t AVCLAN_readframe(AVCLAN_frame_t *frame, log_t print) {
}
if (frame->length == 0 || frame->length > MAXMSGLEN) {
err.errno = BAD_LENGTH_RANGE;
err.errno = rBAD_LENGTH_RANGE;
err.val = frame->length;
goto handle_err;
}
@@ -196,7 +119,7 @@ uint8_t AVCLAN_readframe(AVCLAN_frame_t *frame, log_t print) {
parity = AVCLAN_readbyte(&frame->data[i]);
AVCLAN_readbits(&tmp, 1);
if (parity != (tmp &= 1)) {
err.errno = BAD_DATA_PARITY;
err.errno = rBAD_DATA_PARITY;
if (print.verbose) {
err.read_val = frame->data[i];
err.parity = tmp;
@@ -214,23 +137,23 @@ uint8_t AVCLAN_readframe(AVCLAN_frame_t *frame, log_t print) {
AVCLAN_startEvent();
RS232_Print("ERR(read): ");
switch (err.errno) {
case LATCHED_COMPARATOR: RS232_Print("latched comparator"); break;
case STARTBIT_TOO_SHORT: RS232_Print("start bit too short"); break;
case STARTBIT_TOO_LONG: RS232_Print("start bit too long"); break;
case BAD_CONTROLLER_PARITY:
case rLATCHED_COMPARATOR: RS232_Print("latched comparator"); break;
case rSTARTBIT_TOO_SHORT: RS232_Print("start bit too short"); break;
case rSTARTBIT_TOO_LONG: RS232_Print("start bit too long"); break;
case rBAD_CONTROLLER_PARITY:
RS232_Print("reading controller addr.");
goto VERBOSE;
case BAD_PERIPHERAL_PARITY:
case rBAD_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:
case rBAD_CONTROL_PARITY: RS232_Print("reading control"); goto VERBOSE;
case rBAD_LENGTH_PARITY: RS232_Print("reading length"); goto VERBOSE;
case rBAD_LENGTH_RANGE:
RS232_Print("bad length 0x");
RS232_PrintHex4(err.val);
break;
case BAD_DATA_PARITY: RS232_Print("reading data"); goto VERBOSE;
case NO_ERROR:
case rBAD_DATA_PARITY: RS232_Print("reading data"); goto VERBOSE;
case rNO_ERROR:
__builtin_unreachable();
VERBOSE:
if (print.verbose) {
@@ -246,8 +169,8 @@ uint8_t AVCLAN_readframe(AVCLAN_frame_t *frame, log_t print) {
}
// Only print if some data has been correctly received
if (print.print && (err.errno < STARTBIT_TOO_SHORT)) {
if (err.errno > BAD_DATA_PARITY)
if (print.print && (err.errno < rSTARTBIT_TOO_SHORT)) {
if (err.errno > rBAD_DATA_PARITY)
frame->length = 0;
AVCLAN_printframe(frame, print.binary);
}
@@ -255,59 +178,27 @@ uint8_t AVCLAN_readframe(AVCLAN_frame_t *frame, log_t print) {
return err.errno;
}
uint8_t AVCLAN_sendframe(const AVCLAN_frame_t *frame, log_t print) {
avclan_senderr_t AVCLAN_sendframe(const AVCLAN_frame_t *frame, log_t print) {
struct errtype {
// Error enum is ordered such that a lower numeric value corresponds to more
// success
enum : uint8_t {
NO_ERROR = 0x00,
NAK_DATA = 0x01,
NAK_MESSAGE_LENGTH,
NAK_CONTROL,
NAK_ADDRESS,
BUSY,
MUTED,
} errno;
avclan_senderr_t errno;
uint8_t val;
} err = {0};
if (AVCLAN_ismuted()) {
err.errno = MUTED;
err.errno = sMUTED;
goto handle_err;
}
AVCLAN_stopEvent();
// wait for free line
TCB1.CNT = 0;
while (BUS_IS_IDLE) {
// Wait for 120% of a bit length
if (TCB1.CNT >= (uint16_t)(AVCLAN_BIT_LENGTH_MAX * 2))
break;
}
// End of first loop could be due to bus being driven
TCB1.CNT = 0;
if (!BUS_IS_IDLE) {
// Some other device started sending
// Can't yet simultaneously send and recieve to do proper CSMA/CD
err.errno = BUSY;
if (!AVCLAN_sendstartbit()) {
// Some other device is already driving the bus
err.errno = sBUSY;
goto handle_err;
// Beginnings of CSMA/CD
// do {
// if (TCB1.CNT >= (uint16_t)(AVCLAN_STARTBIT_LOGIC_0 * 1.2))
// return 1; // Something's hinky; nothing is longer than the start bit
// } while (!BUS_IS_IDLE);
// if (TCB1.CNT <= (uint16_t)(AVCLAN_STARTBIT_LOGIC_0 * 0.8))
// return 1; // Shouldn't be possible (waiting 2 bit lengths with idle
// bus,
// // then next bit should be a long one ie start)
// set_AVC_logic_for(1, AVCLAN_STARTBIT_LOGIC_1); // wait for end of start
// bit
} else {
AVCLAN_sendbit(bit_start);
}
AVCLAN_sendbits(&(uint8_t){frame->is_unicast}, 1);
avclan_bit_t parity = AVCLAN_sendbits(&frame->controller_addr, 12);
@@ -317,7 +208,7 @@ uint8_t AVCLAN_sendframe(const AVCLAN_frame_t *frame, log_t print) {
AVCLAN_sendbit(parity);
if (frame->is_unicast && !AVCLAN_readbit_ACK()) {
err.errno = NAK_ADDRESS;
err.errno = sNAK_ADDRESS;
goto handle_err;
}
@@ -325,7 +216,7 @@ uint8_t AVCLAN_sendframe(const AVCLAN_frame_t *frame, log_t print) {
AVCLAN_sendbit(parity);
if (frame->is_unicast && !AVCLAN_readbit_ACK()) {
err.errno = NAK_CONTROL;
err.errno = sNAK_CONTROL;
goto handle_err;
}
@@ -333,7 +224,7 @@ uint8_t AVCLAN_sendframe(const AVCLAN_frame_t *frame, log_t print) {
AVCLAN_sendbit(parity);
if (frame->is_unicast && !AVCLAN_readbit_ACK()) {
err.errno = NAK_MESSAGE_LENGTH;
err.errno = sNAK_MESSAGE_LENGTH;
goto handle_err;
}
@@ -344,7 +235,7 @@ uint8_t AVCLAN_sendframe(const AVCLAN_frame_t *frame, log_t print) {
// necessary (i.e. This deviates from the previous broadcast specific
// function that sent an extra `1` bit after each byte/parity)
if (frame->is_unicast && !AVCLAN_readbit_ACK()) {
err.errno = NAK_DATA;
err.errno = sNAK_DATA;
err.val = i;
goto handle_err;
}
@@ -358,28 +249,28 @@ uint8_t AVCLAN_sendframe(const AVCLAN_frame_t *frame, log_t print) {
AVCLAN_startEvent();
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 sMUTED: RS232_Print(": Device muted"); break;
case sBUSY: RS232_Print(": Busy bus"); break;
case sNAK_ADDRESS:
case sNAK_CONTROL:
case sNAK_MESSAGE_LENGTH:
case sNAK_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:
case sNAK_ADDRESS: RS232_Print("address"); break;
case sNAK_CONTROL: RS232_Print("Control"); break;
case sNAK_MESSAGE_LENGTH: RS232_Print("Message length"); break;
case sNAK_DATA:
RS232_Print(" data[");
RS232_PrintDec(err.val);
RS232_Print("]");
break;
case NO_ERROR:
case MUTED:
case BUSY: __builtin_unreachable();
case sNO_ERROR:
case sMUTED:
case sBUSY: __builtin_unreachable();
}
break;
case NO_ERROR: __builtin_unreachable();
case sNO_ERROR: __builtin_unreachable();
}
RS232_Print("\n");
} else {
@@ -494,46 +385,3 @@ uint8_t AVCLAN_parseframe(const uint8_t *bytes, uint8_t len,
return err.errno;
}
#ifndef NDEBUG
// Only used immediately below
#define XSTR(x) #x
#define STR(x) XSTR(x)
uint16_t pulses[100];
uint16_t periods[100];
void AVCLan_Measure() {
AVCLAN_stopEvent();
uint8_t tmp = 0;
RS232_Print(
"Timing config: F_CPU=" STR(F_CPU) ", TCB_CLKSEL=" STR(TCB_CLKSEL) "\n");
RS232_Print("Sampling bit (pulse-width and period) timing...\n");
for (uint8_t n = 0; n < 100; n++) {
while (pulse_count == tmp) {}
pulses[n] = pulsewidth;
periods[n] = period;
tmp = pulse_count;
}
RS232_Print("Pulses:\n");
for (uint8_t i = 0; i < 100; i++) {
RS232_PrintHex8((uint8_t)(pulses[i] >> 8));
RS232_PrintHex8((uint8_t)pulses[i]);
RS232_Print("\n");
}
RS232_Print("Periods:\n");
for (uint8_t i = 0; i < 100; i++) {
RS232_PrintHex8((uint8_t)(periods[i] >> 8));
RS232_PrintHex8((uint8_t)periods[i]);
RS232_Print("\n");
}
RS232_Print("\nDone.\n");
AVCLAN_startEvent();
}
#endif
+2 -14
View File
@@ -51,22 +51,10 @@
#include "avclan_defs.h"
uint8_t AVCLAN_readframe(AVCLAN_frame_t *frame, log_t print);
uint8_t AVCLAN_sendframe(const AVCLAN_frame_t *frame, log_t print);
avclan_readerr_t AVCLAN_readframe(AVCLAN_frame_t *frame, log_t print);
avclan_senderr_t AVCLAN_sendframe(const AVCLAN_frame_t *frame, log_t print);
void AVCLAN_printframe(const AVCLAN_frame_t *frame, bool binary);
uint8_t AVCLAN_parseframe(const uint8_t *bytes, uint8_t len,
AVCLAN_frame_t *frame);
// Bus-transaction guard: quiesce the other async sources (USART RX, the RTC
// status tick, the mic timer) around a bus read/send so framing isn't disturbed.
// NOTE (temporary): these couple the frame layer to the statustimer /
// mediacontrol / cdchanger modules; this intermingling is accepted pending the
// RP2350 port rework. TCB0 must remain enabled.
void AVCLAN_stopEvent();
void AVCLAN_startEvent();
#ifndef NDEBUG
void AVCLan_Measure();
#endif
#endif // AVCLAN_FRAME_H
+29 -32
View File
@@ -20,45 +20,44 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// AVC-LAN PHY: bus bit-banging via TCB timers + analog comparator AC2.
// This is the lowest layer and has no dependencies on the higher layers
// (frame / protocol / cdchanger) — keep it that way.
#ifndef AVCLAN_PHY_H
#define AVCLAN_PHY_H
#include <avr/io.h>
#include <avr/sfr_defs.h>
#include <stdint.h>
#include "avclan_defs.h"
// AVC LAN bus on AC2 (PA6/7)
// PA6 AINP0 +
// PA7 AINN1 -
#define BUS_IS_IDLE (bit_is_clear(AC2_STATUS, AC_STATE_bp))
// One-time bring-up of the bus hardware. Leaves the bus idle and TX unmuted.
void AVCLAN_busInit(void);
typedef enum avclan_bit : uint8_t {
bit_zero = 0x00,
bit_one = 0x01,
bit_start = 0x10
} avclan_bit_t;
// One-time hardware bring-up for the PHY: AC2, EVSYS, TCB0/TCB1, the AC inputs
// (PA6/7) and AC2-OUT LED (PB2). Leaves the bus idle and TX unmuted.
void AVCLAN_phyInit();
// Returns true if device TX is muted on AVCLAN bus
static inline bool AVCLAN_ismuted() {
return (((VPORTA_DIR & PIN4_bm) | (VPORTA_DIR & PIN0_bm)) == 0);
}
// Mute device TX on AVCLAN bus
// Mute/unmute device TX. "Muted" means we still listen, we just don't ACK or
// transmit.
void AVCLAN_muteDevice(bool mute);
bool AVCLAN_ismuted(void);
// True when there is activity on the bus (something is driving it).
bool AVCLAN_busActive(void);
// Bus-transaction guard: quiesce the target's other async sources around a bus
// read/send so framing isn't disturbed, then restore them. May be a no-op on a
// target without such contention.
void AVCLAN_stopEvent(void);
void AVCLAN_startEvent(void);
// Start-bit handling, factored out of read/sendframe so the framing layer holds
// no bus-timing or hardware-recovery logic.
// - AVCLAN_readstartbit waits for and validates an incoming start bit, doing
// any target-specific bus recovery; see avclan_readerr_t.
// - AVCLAN_sendstartbit acquires the bus and emits a start bit; returns false
// if the bus was busy.
avclan_readerr_t AVCLAN_readstartbit(void);
bool AVCLAN_sendstartbit(void);
// Per-symbol I/O. The send* helpers return the even parity of the bits sent;
// the read* helpers return the even parity of the bits read.
void AVCLAN_sendbit(avclan_bit_t bit);
void AVCLAN_sendbit_ACK();
uint8_t AVCLAN_readbit_ACK();
void AVCLAN_sendbit_ACK(void);
uint8_t AVCLAN_readbit_ACK(void);
avclan_bit_t AVCLAN_sendbitsi(const uint8_t *bits, int8_t len);
avclan_bit_t AVCLAN_sendbitsl(const uint16_t *bits, int8_t len);
@@ -83,10 +82,8 @@ uint8_t AVCLAN_readbyte(uint8_t *byte);
uint8_t *: AVCLAN_readbitsi)(bits, len)
#ifndef NDEBUG
// Bit-timing capture, populated by the TCB0 capture ISR; read by AVCLan_Measure.
extern volatile uint16_t pulsewidth;
extern volatile uint8_t pulse_count;
extern volatile uint16_t period;
// Sample and dump bus bit timing over the serial link (REPL `M`).
void AVCLan_Measure(void);
#endif
#endif // AVCLAN_PHY_H
+4 -4
View File
@@ -235,7 +235,7 @@ response_t AVCLAN_handleframe(const AVCLAN_frame_t *in, AVCLAN_frame_t *out) {
cd_status.secs = 0x7f;
cd_status.flags2 = 0xc0;
AVCLAN_generateStatus(out, true, dev_CMD_SW);
AVCLAN_micSkipForward();
AVCLAN_mediaFunction(MEDIA_SKIP_FORWARD);
respond = r_TrackChange;
break;
case PACK3(dev_CMD_SW, dev_CD_CHANGER, Track_Seek_Down):
@@ -251,7 +251,7 @@ response_t AVCLAN_handleframe(const AVCLAN_frame_t *in, AVCLAN_frame_t *out) {
cd_status.secs = 0x7f;
cd_status.flags2 = 0xc0;
AVCLAN_generateStatus(out, true, dev_CMD_SW);
AVCLAN_micSkipBackward();
AVCLAN_mediaFunction(MEDIA_SKIP_BACKWARD);
respond = r_TrackChange;
break;
case PACK3(dev_CMD_SW, dev_CD_CHANGER, Track_Fast_Forward): {
@@ -262,7 +262,7 @@ response_t AVCLAN_handleframe(const AVCLAN_frame_t *in, AVCLAN_frame_t *out) {
++cd_status.mins;
}
AVCLAN_generateStatus(out, true, dev_CMD_SW);
AVCLAN_micSkipForward();
AVCLAN_mediaFunction(MEDIA_SKIP_FORWARD);
statustimer_reset(); // Skipped to a whole/round sec; ensure next tick is
// ~1 sec from now
respond = r_Handled;
@@ -282,7 +282,7 @@ response_t AVCLAN_handleframe(const AVCLAN_frame_t *in, AVCLAN_frame_t *out) {
} else
cd_status.secs -= 15;
AVCLAN_generateStatus(out, true, dev_CMD_SW);
AVCLAN_micSkipBackward();
AVCLAN_mediaFunction(MEDIA_SKIP_BACKWARD);
statustimer_reset(); // Skipped to a whole/round sec; ensure next tick is
// ~1 sec from now
respond = r_Handled;
+3 -3
View File
@@ -37,7 +37,7 @@ static cd_modes CD_Mode;
void AVCLAN_startPlaying() {
static bool havePlayed = false;
if (havePlayed)
AVCLAN_micPlayPause();
AVCLAN_mediaFunction(MEDIA_PLAY_PAUSE);
havePlayed |= true;
CD_Mode = stPlay;
statustimer_reset();
@@ -48,7 +48,7 @@ void AVCLAN_startPlaying() {
void AVCLAN_stopPlaying() {
statustimer_disable();
CD_Mode = stStop;
AVCLAN_micPlayPause();
AVCLAN_mediaFunction(MEDIA_PLAY_PAUSE);
}
/* Pack a 099 count into 2-digit BCD. Values >99 (sentinels such as 0xFF /
@@ -139,7 +139,7 @@ void AVCLAN_normalizeState() {
}
void AVCLAN_init() {
AVCLAN_phyInit();
AVCLAN_busInit();
mediacontrol_init();
statustimer_init();
+12 -14
View File
@@ -16,27 +16,25 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// Media-control actuator: emulates play/pause and skip button presses on the
// audio source by toggling MIC_CONTROL (PB1/WO1) with TCA0 in FRQ mode.
// Media-control: route/handle head-unit button presses to the audio source.
#ifndef MEDIACONTROL_H
#define MEDIACONTROL_H
#include <stdbool.h>
#include <stdint.h>
// One-time hardware bring-up for the mic/button-press driver (TCA0 + PB1).
// Actions list
typedef enum : uint8_t {
MEDIA_PLAY_PAUSE = 0,
MEDIA_SKIP_FORWARD,
MEDIA_SKIP_BACKWARD,
} AVCLAN_media_fn_t;
// One-time hardware bring-up for the media driver.
void mediacontrol_init();
// Emulate a single play/pause button press on the source device.
void AVCLAN_micPlayPause();
// Emulate skip-forward/backward button presses
void AVCLAN_micSkipForward();
void AVCLAN_micSkipBackward();
// Keep the press waveform roughly in sync while a bus transaction has masked
// interrupts. MUST be called with interrupts disabled (e.g. from within the
// frame layer's AVCLAN_stopEvent ATOMIC_BLOCK).
void mediacontrol_syncDuringMask();
// Emulate a button press on the source device.
void AVCLAN_mediaFunction(AVCLAN_media_fn_t fn);
#ifndef NDEBUG
bool AVCLAN_micToggle();
+15 -12
View File
@@ -16,23 +16,26 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// ~1 Hz status-update tick, driven by the RTC overflow. The overflow handler
// (ISR(RTC_CNT_vect)) lives in the app (sniffer.c); this module owns only the
// RTC hardware configuration and enable/disable/reset of the tick.
// ~1 Hz status-update tick interface. The app
// polls statustimer_tickPending() and clears the tick with
// statustimer_clearTick()
#ifndef STATUSTIMER_H
#define STATUSTIMER_H
// One-time RTC hardware bring-up (clock source + period). Leaves the overflow
// interrupt disabled.
void statustimer_init();
// One-time hardware bring-up. Leaves the tick disabled.
void statustimer_init(void);
// Reset the count so the next tick is ~1 s out, and enable the overflow tick.
void statustimer_reset();
// Reset the count so the next tick is ~1 s out, and enable the tick.
void statustimer_reset(void);
// Enable / disable the ~1 Hz overflow interrupt (bare register RMW; wrap in a
// critical section if called with interrupts enabled and concurrency matters).
void statustimer_enable();
void statustimer_disable();
// Enable / disable the ~1 Hz tick.
void statustimer_enable(void);
void statustimer_disable(void);
extern volatile bool tick_pending;
static inline bool statustimer_tickPending() { return tick_pending; }
static inline void statustimer_clearTick() { tick_pending = false; }
#endif // STATUSTIMER_H
@@ -0,0 +1,135 @@
# AVR / ATtiny3216 hardware target (port).
include(CMakeDependentOption)
# --- Port implementation sources -------------------------------------------
target_sources(avclan PRIVATE
phy_avr.c
media_avr.c
statustick_avr.c
board_avr.c)
target_include_directories(avclan PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
find_program(AVRDUDE avrdude)
set(AVR_PROGRAMMER serialupdi CACHE STRING "avrdude programmer hardware")
set(AVRDUDE_PORT /dev/ttyUSB0 CACHE STRING "avrdude serial port")
set(AVRDUDE_BAUDRATE 230400 CACHE STRING "avrdude baud rate")
set(AVRDUDE_BASE_OPTIONS
-p ${AVR_MCU}
-c ${AVR_PROGRAMMER}
-b ${AVRDUDE_BAUDRATE})
# --- Hardware configuration options ----------------------------------------
set(FREQSEL 16MHz CACHE STRING "Select the operating frequency")
set_property(CACHE FREQSEL PROPERTY STRINGS "20MHz" "16MHz")
if(FREQSEL MATCHES "20MHz")
set(FREQSEL 20000000L)
set(AVRDUDE_BASE_OPTIONS ${AVRDUDE_BASE_OPTIONS} -U osccfg:w:0x2:m)
else()
set(FREQSEL 16000000L)
set(AVRDUDE_BASE_OPTIONS ${AVRDUDE_BASE_OPTIONS} -U osccfg:w:0x1:m)
endif()
# Set startup time to 8 ms (0x4)
set(AVRDUDE_BASE_OPTIONS ${AVRDUDE_BASE_OPTIONS} -U syscfg1:w:0x4:m)
option(CLK_PRESCALE "Enable the main clock prescaler")
cmake_dependent_option(CLK_PRESCALE_DIV "Prescaler divisor" CLKCTRL_PDIV_2X_gc STRING "CLK_PRESCALE")
if(DEFINED CACHE{CLK_PRESCALE_DIV})
set_property(CACHE CLK_PRESCALE_DIV PROPERTY STRINGS
CLKCTRL_PDIV_2X_gc
CLKCTRL_PDIV_4X_gc
CLKCTRL_PDIV_8X_gc
CLKCTRL_PDIV_16X_gc
CLKCTRL_PDIV_32X_gc
CLKCTRL_PDIV_64X_gc
CLKCTRL_PDIV_6X_gc
CLKCTRL_PDIV_10X_gc
CLKCTRL_PDIV_12X_gc
CLKCTRL_PDIV_24X_gc
CLKCTRL_PDIV_48X_gc
)
else()
set(CLK_PRESCALE_DIV CLKCTRL_PDIV_2X_gc)
endif()
set(TCB_CLKSEL "TCB_CLKSEL_CLKDIV2_gc" CACHE STRING "Choose the clock for TCB")
set_property(CACHE TCB_CLKSEL PROPERTY STRINGS
TCB_CLKSEL_CLKDIV1_gc
TCB_CLKSEL_CLKDIV2_gc
TCB_CLKSEL_CLKTCA_gc
)
set(USART_RXMODE "USART_RXMODE_CLK2X_gc" CACHE STRING "USART at normal or double speed operation")
set_property(CACHE USART_RXMODE PROPERTY STRINGS
USART_RXMODE_CLK2X_gc
USART_RXMODE_NORMAL_gc
)
# Measured wall-clock duration (ms) of one nominal 32768-tick RTC period, used
# to calibrate out the internal OSCULP32K's tolerance for the status-update
# tick. 1000 = no correction; set per-board in CMakeUserPresets.json.
set(RTC_STATUS_PERIOD_MS 1000 CACHE STRING "Measured ms per nominal RTC status period (1000 = no correction)")
try_compile(LIBC_VERSION_TEST
SOURCES "${CMAKE_SOURCE_DIR}/cmake/libc-version-test.cpp"
COMPILE_DEFINITIONS -mmcu=${AVR_MCU}
)
if(NOT LIBC_VERSION_TEST)
include(FetchContent)
FetchContent_Declare(
attiny_atpack
URL http://packs.download.atmel.com/Atmel.ATtiny_DFP.2.0.368.atpack
URL_HASH SHA512=ee16a8ebecb57bd998a9cd4373368e3d45982cbbc3825e18d1dcac58215db6b9d907ad1ba2020cba9187fed7ba8c6f255a4fa1214e40c7a17ab2d18474f4d079
DOWNLOAD_NAME Atmel.ATtiny_DFP.2.0.368.atpack.zip
)
FetchContent_MakeAvailable(attiny_atpack)
try_compile(LIBC_VERSION_TEST
SOURCES "${CMAKE_SOURCE_DIR}/cmake/libc-version-test.cpp"
COMPILE_DEFINITIONS
-B "${attiny_atpack_SOURCE_DIR}/gcc/dev/${AVR_MCU}"
-isystem "${attiny_atpack_SOURCE_DIR}/include"
-mmcu=${AVR_MCU}
)
if(NOT LIBC_VERSION_TEST)
message(FATAL_ERROR "Insufficient AVR-LIBC/Microchip pack for chosen MCU '${AVR_MCU}'")
else()
# PUBLIC on the library so its compile inherits the device headers and
# the requirement propagates to mockingboard via linking.
target_include_directories(avclan SYSTEM
PUBLIC "${attiny_atpack_SOURCE_DIR}/include")
target_link_options(mockingboard PUBLIC
-B "${attiny_atpack_SOURCE_DIR}/gcc/dev/${AVR_MCU}"
)
endif()
endif()
# --- Compile definitions / options -----------------------------------------
target_compile_definitions(avclan PUBLIC
FREQSEL=${FREQSEL}
CLK_PRESCALE=$<IF:$<BOOL:${CLK_PRESCALE}>,0x01,0x00>
CLK_PRESCALE_DIV=${CLK_PRESCALE_DIV}
__CLK_PRESCALE_DIV=__${CLK_PRESCALE_DIV}
TCB_CLKSEL=${TCB_CLKSEL}
USART_RXMODE=${USART_RXMODE}
RTC_STATUS_PERIOD_MS=${RTC_STATUS_PERIOD_MS}
)
target_compile_options(avclan PUBLIC
--param=min-pagesize=0
-ffunction-sections
-fdata-sections
)
# --- Flashing --------------------------------------------------------------
add_custom_target(flash
${AVRDUDE} ${AVRDUDE_BASE_OPTIONS} ${AVRDUDE_OPTIONS}
-U flash:w:$<TARGET_FILE:mockingboard>:e
-P ${AVRDUDE_PORT}
DEPENDS mockingboard
COMMENT "Flashing mockingboard to ${AVR_MCU} using ${AVR_PROGRAMMER}"
VERBATIM USES_TERMINAL
)
@@ -0,0 +1,62 @@
/*
AVCLAN-Mockingboard
Copyright (C) 2015 Allen Hill <allenofthehills@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// ATtiny3216 board bring-up: main-clock prescaler + GPIO config for pins not
// owned by a peripheral's own init.
#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/xmega.h> // _PROTECTED_WRITE
#include "board.h"
void board_init(void) {
// Main clock prescale (CLK_PRESCALE / CLK_PRESCALE_DIV come from the build).
_PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, (CLK_PRESCALE | CLK_PRESCALE_DIV));
// Set pins PC2-3, PB0,3-5 as inputs
PORTC.DIRCLR = (PIN2_bm | // Unconnected
PIN3_bm); // CTS
PORTB.DIRCLR = (PIN0_bm | // Unconnected
PIN3_bm | // IGN_SENSE
PIN4_bm | // Unused, but connected to WOC (PC0)
PIN5_bm); // Unused, but connected to WOD (PC1)
// Enable pull-up resistor and disable input buffer (reduces any EM caused
// pin toggling and saves power) for unused and unconnected pins
PORTC.PIN2CTRL = PORT_PULLUPEN_bm | PORT_ISC_INPUT_DISABLE_gc;
PORTB.PIN0CTRL = PORT_PULLUPEN_bm | PORT_ISC_INPUT_DISABLE_gc;
// TODO: Remove once IGN_SENSE hardware is fixed
PORTB.DIRSET = PIN3_bm;
PORTB.OUTSET = PIN3_bm;
// Output only pins: PA3-5, PB1-2,4-5; PC0-1
// TODO: TxD (PA1), RTS (PA3) is output only, test if RxD needs the input
// buffer or if the UART peripheral bypasses it
PORTA.PIN3CTRL = PORT_ISC_INPUT_DISABLE_gc; // RTS
PORTA.PIN4CTRL = PORT_ISC_INPUT_DISABLE_gc; // WOA
PORTA.PIN5CTRL = PORT_ISC_INPUT_DISABLE_gc; // WOB
PORTB.PIN1CTRL = PORT_ISC_INPUT_DISABLE_gc; // MIC_CONTROL
PORTB.PIN4CTRL = PORT_ISC_INPUT_DISABLE_gc; // non-driving WOC
PORTB.PIN5CTRL = PORT_ISC_INPUT_DISABLE_gc; // non-driving WOD
PORTC.PIN0CTRL = PORT_ISC_INPUT_DISABLE_gc; // WOC
PORTC.PIN1CTRL = PORT_ISC_INPUT_DISABLE_gc; // WOD
}
void board_interruptsEnable(void) { sei(); }
@@ -21,11 +21,12 @@
#include <stdint.h>
#include <util/atomic.h>
#include "media_avr.h" // mediacontrol_syncDuringMask (used by the bus guard)
#include "mediacontrol.h"
// F_CPU defined in timing.h; the mic tick constants below are derived from it.
// TODO(HAL): re-derive these from a target-agnostic TICK_NS instead of F_CPU.
#include "timing.h"
// F_CPU defined in timing_avr.h; the mic tick constants below are derived from
// it (this hardware generation's TCA0/PB1 button-press implementation).
#include "timing_avr.h"
// pending WO1 toggles (even); signed to avoid underflows from a stray OVF
static volatile int8_t mic_ntoggles = 0;
@@ -111,12 +112,15 @@ static inline void mic_timer_isr_body(bool is_early) {
ISR(TCA0_OVF_vect) { mic_timer_isr_body(false); }
// Emulate a single play/pause button press on the source device.
void AVCLAN_micPlayPause() { mic_pulse(1); }
// Emulate skip-forward/backward button presses
void AVCLAN_micSkipForward() { mic_pulse(3); } // double-press
void AVCLAN_micSkipBackward() { mic_pulse(5); } // triple-press
// Emulate a transport-control button press on the source device. Each action
// maps to a press-train of a given length on MIC_CONTROL.
void AVCLAN_mediaFunction(AVCLAN_media_fn_t fn) {
switch (fn) {
case MEDIA_PLAY_PAUSE: mic_pulse(1); break; // single press
case MEDIA_SKIP_FORWARD: mic_pulse(3); break; // double-press
case MEDIA_SKIP_BACKWARD: mic_pulse(5); break; // triple-press
}
}
// Pre-emptively "overflow" and run the OVF ISR body early if a press is in
// progress and likely to overflow within the masked window. This maintains:
@@ -0,0 +1,32 @@
/*
AVCLAN-Mockingboard
Copyright (C) 2015 Allen Hill <allenofthehills@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// AVR-internal media-driver hooks, shared between media_avr.c and the bus
// transaction guard (phy_avr.c's AVCLAN_stopEvent). Not part of the public
// mediacontrol.h interface.
#ifndef MEDIA_AVR_H
#define MEDIA_AVR_H
// Keep the TCA0 press waveform roughly in sync while a bus transaction has
// masked interrupts (runs the OVF ISR body early if an overflow is imminent).
// MUST be called with interrupts disabled (from within AVCLAN_stopEvent's
// ATOMIC_BLOCK).
void mediacontrol_syncDuringMask();
#endif // MEDIA_AVR_H
@@ -68,28 +68,37 @@
#include <avr/io.h>
#include <avr/sfr_defs.h>
#include <stdint.h>
#include <util/atomic.h>
#include "avclan_phy.h"
#include "cdchanger.h" // AVCLAN_isPlaying (startEvent)
#include "com232.h" // RS232_setRxInterrupt (guard); RS232_Print (Measure)
#include "media_avr.h" // mediacontrol_syncDuringMask (guard)
#include "statustimer.h" // statustimer_enable/disable (guard)
// F_CPU defined in timing.h and potentially needed by avr-libc (e.g. delay.h)
#include "timing.h"
// F_CPU + TICK_US (timing.h) defined here; F_CPU potentially needed by
// avr-libc.
#include "timing_avr.h"
// Name difference between avr-libc and Microchip pack
#if defined(EVSYS_ASYNCCH00_bm)
#define EVSYS_ASYNCCH0_0_bm EVSYS_ASYNCCH00_bm
#endif
// AVC LAN bus on AC2 (PA6/7): PA6 AINP0 (+), PA7 AINN1 (-)
#define BUS_IS_IDLE (bit_is_clear(AC2_STATUS, AC_STATE_bp))
#define READING_BYTE GPIOR1
#define READING_NBITS GPIOR2
#define READING_PARITY GPIOR3
#define TCB_CNTMODE TCB_CNTMODE_PW_gc
volatile uint16_t pulsewidth;
static volatile uint16_t pulsewidth;
#ifndef NDEBUG
volatile uint8_t pulse_count = 0;
volatile uint16_t period = 0;
static volatile uint8_t pulse_count = 0;
static volatile uint16_t period = 0;
#endif
// clang-format off
@@ -109,6 +118,15 @@ static inline void AVCLAN_setBusDriven() {
}
// clang-format on
// Returns true if device TX is muted on the AVCLAN bus (both drive pins are
// configured as inputs).
bool AVCLAN_ismuted() {
return (((VPORTA_DIR & PIN4_bm) | (VPORTA_DIR & PIN0_bm)) == 0);
}
// True when the bus is being driven (i.e. not idle/floating).
bool AVCLAN_busActive() { return !BUS_IS_IDLE; }
// Mute device TX on AVCLAN bus
void AVCLAN_muteDevice(bool mute) {
if (mute) {
@@ -333,7 +351,7 @@ uint8_t AVCLAN_readbyte(uint8_t *byte) {
return (parity & 1);
}
void AVCLAN_phyInit() {
void AVCLAN_busInit() {
// Set pin 6 and 7 as input
PORTA.DIRCLR = (PIN6_bm | PIN7_bm);
// Disable input buffer; recommended when using AC
@@ -367,3 +385,141 @@ void AVCLAN_phyInit() {
AVCLAN_muteDevice(false); // unmute AVCLAN bus TX
}
// Wait for and validate an incoming start bit. On an over-long "driven" bus
// (AC2 latched high because the bus is actually floating) this kicks PA7 hard
// high to unlatch the comparator. The framing layer maps the result to its own
// error reporting; no printing happens here.
avclan_readerr_t AVCLAN_readstartbit() {
uint16_t startbitlen = TCB1.CNT = 0;
while (!BUS_IS_IDLE) {
startbitlen = TCB1.CNT;
if (startbitlen > (uint16_t)AVCLAN_STARTBIT_LOGIC_0 * 1.2) {
avclan_readerr_t result = rSTARTBIT_TOO_LONG;
while (!BUS_IS_IDLE) {
// If bus is "driven" too long, assume the AC2 is latched (e.g.
// because the bus is actually floating). Kick it if so.
// This should prevent/resolve a flood of "STARTBIT_TOO_LONG" errors
if (TCB1.CNT > (uint16_t)(AVCLAN_STARTBIT_LOGIC_0 * 3)) {
result = rLATCHED_COMPARATOR;
PORTA.OUTSET = PIN7_bm; // preset high before enabling the driver
PORTA.DIRSET = PIN7_bm; // drive (-) hard high
TCB1.CNT = 0;
while (!BUS_IS_IDLE && TCB1.CNT < (uint16_t)AVCLAN_BIT0_LOGIC_1) {
// Wait a max of ~6μs until bus is idle
}
PORTA.DIRCLR = PIN7_bm; // back to high-Z comparator input
PORTA.OUTCLR = PIN7_bm;
}
}
return result;
}
}
if (startbitlen < (uint16_t)(AVCLAN_STARTBIT_LOGIC_0 * 0.8)) {
// We missed the beginning of this message; wait for it to finish (bus
// continuously idle for >1 bit length) before returning, so we don't have
// multiple false-starts while the in-progress message keeps sending more
// bits.
TCB1.CNT = 0;
while (TCB1.CNT < (uint16_t)(AVCLAN_BIT_LENGTH_MAX * 1.2)) {
if (!BUS_IS_IDLE)
TCB1.CNT = 0;
}
return rSTARTBIT_TOO_SHORT;
}
return rNO_ERROR; // that was a start bit
}
// Acquire the bus and emit a start bit. Returns false if another device is
// already driving the bus (we can't yet do proper CSMA/CD).
bool AVCLAN_sendstartbit() {
// wait for free line
TCB1.CNT = 0;
while (BUS_IS_IDLE) {
// Wait for 120% of a bit length
if (TCB1.CNT >= (uint16_t)(AVCLAN_BIT_LENGTH_MAX * 2))
break;
}
// End of first loop could be due to bus being driven
TCB1.CNT = 0;
if (!BUS_IS_IDLE) {
// Some other device started sending
// Can't yet simultaneously send and receive to do proper CSMA/CD
// Beginnings of CSMA/CD
// do {
// if (TCB1.CNT >= (uint16_t)(AVCLAN_STARTBIT_LOGIC_0 * 1.2))
// return false; // Something's hinky; nothing is longer than start bit
// } while (!BUS_IS_IDLE);
// if (TCB1.CNT <= (uint16_t)(AVCLAN_STARTBIT_LOGIC_0 * 0.8))
// return false; // Shouldn't be possible
// set_AVC_logic_for(1, AVCLAN_STARTBIT_LOGIC_1); // wait for end of start
return false;
}
AVCLAN_sendbit(bit_start);
return true;
}
/* Disable non-read related interrupts (USART RX, RTC status tick, mic timer)
during AVCLAN bus transactions so framing isn't disturbed. TCB0 must remain
enabled. */
void AVCLAN_stopEvent() {
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
statustimer_disable();
RS232_setRxInterrupt(false);
mediacontrol_syncDuringMask();
}
}
// Re-enable serial and periodic interrupts after a bus transaction.
void AVCLAN_startEvent() {
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
if (AVCLAN_isPlaying()) // Reenable status interrupt if currently playing
statustimer_enable();
RS232_setRxInterrupt(true);
}
}
#ifndef NDEBUG
// Only used immediately below
#define XSTR(x) #x
#define STR(x) XSTR(x)
static uint16_t pulses[100];
static uint16_t periods[100];
void AVCLan_Measure() {
AVCLAN_stopEvent();
uint8_t tmp = 0;
RS232_Print(
"Timing config: F_CPU=" STR(F_CPU) ", TCB_CLKSEL=" STR(TCB_CLKSEL) "\n");
RS232_Print("Sampling bit (pulse-width and period) timing...\n");
for (uint8_t n = 0; n < 100; n++) {
while (pulse_count == tmp) {}
pulses[n] = pulsewidth;
periods[n] = period;
tmp = pulse_count;
}
RS232_Print("Pulses:\n");
for (uint8_t i = 0; i < 100; i++) {
RS232_PrintHex8((uint8_t)(pulses[i] >> 8));
RS232_PrintHex8((uint8_t)pulses[i]);
RS232_Print("\n");
}
RS232_Print("Periods:\n");
for (uint8_t i = 0; i < 100; i++) {
RS232_PrintHex8((uint8_t)(periods[i] >> 8));
RS232_PrintHex8((uint8_t)periods[i]);
RS232_Print("\n");
}
RS232_Print("\nDone.\n");
AVCLAN_startEvent();
}
#endif
@@ -16,10 +16,12 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <avr/interrupt.h>
#include <avr/io.h>
#include <stdint.h>
#include <util/atomic.h>
#include "cdchanger.h"
#include "statustimer.h"
// Measured wall-clock duration (in ms) of one nominal 32768-tick RTC period,
@@ -62,3 +64,13 @@ void statustimer_reset() {
void statustimer_enable() { RTC.INTCTRL |= RTC_OVF_bm; }
void statustimer_disable() { RTC.INTCTRL &= ~RTC_OVF_bm; }
// Set once per overflow; consumed by the app via statustimer_tickPending().
volatile bool tick_pending = false;
// Periodic interrupt with a ~1 sec period; only enabled while playing.
ISR(RTC_CNT_vect) {
AVCLAN_incrementTime();
tick_pending = true;
RTC.INTFLAGS = RTC_OVF_bm;
}
@@ -0,0 +1,65 @@
#ifndef TIMING_AVR_H
#define TIMING_AVR_H
// AVR ATtiny3216 timing parameters. Derives F_CPU (needed by avr-libc, e.g.
// util/delay.h) and the bus-timer (TCB) tick period from the CMake-provided
// FREQSEL / CLK_PRESCALE / TCB_CLKSEL, then hands the generic timing.h a TICK_US
// (microseconds per TCB tick) so the physical bit-phase durations resolve to
// TCB-tick counts. TICK_US == TCB_TICK / 1000, so every derived constant is
// numerically identical to the previous F_CPU/TCB_CLKSEL formulation.
#define __CLKCTRL_PDIV_2X_gc 2
#define __CLKCTRL_PDIV_4X_gc 4
#define __CLKCTRL_PDIV_8X_gc 8
#define __CLKCTRL_PDIV_16X_gc 16
#define __CLKCTRL_PDIV_32X_gc 32
#define __CLKCTRL_PDIV_64X_gc 64
#define __CLKCTRL_PDIV_6X_gc 6
#define __CLKCTRL_PDIV_10X_gc 10
#define __CLKCTRL_PDIV_12X_gc 12
#define __CLKCTRL_PDIV_24X_gc 24
#define __CLKCTRL_PDIV_48X_gc 48
#if CLK_PRESCALE == 0x01
#define F_CPU (FREQSEL / __CLK_PRESCALE_DIV)
#define CYCLE_MUL __CLK_PRESCALE_DIV
#else
#define F_CPU (FREQSEL)
#define CYCLE_MUL 1
#endif
// CPU_CYCLE / TCB_TICK are in nanoseconds.
#if FREQSEL == 20000000L
#define CPU_CYCLE (50 * CYCLE_MUL)
#elif FREQSEL == 16000000L
#define CPU_CYCLE (62.5 * CYCLE_MUL)
#else
#error "Not implemented"
#endif
#ifndef TCB_CLKSEL_CLKDIV1_gc
#define TCB_CLKSEL_CLKDIV1_gc (0x00 << 1)
#endif
#ifndef TCB_CLKSEL_CLKDIV2_gc
#define TCB_CLKSEL_CLKDIV2_gc (0x01 << 1)
#endif
#ifndef TCB_CLKSEL_CLKTCA_gc
#define TCB_CLKSEL_CLKTCA_gc (0x02 << 1)
#endif
#if TCB_CLKSEL == TCB_CLKSEL_CLKDIV1_gc
#define TCB_TICK (CPU_CYCLE)
#elif TCB_CLKSEL == TCB_CLKSEL_CLKDIV2_gc
#define TCB_TICK (CPU_CYCLE * 2)
#elif TCB_CLKSEL == TCB_CLKSEL_CLKTCA_gc
#error "Not implemented"
#endif
// TCB_TICK is nanoseconds/tick; the generic timing.h wants microseconds/tick.
#define TICK_US (TCB_TICK / 1000.0)
#include "timing.h"
#endif // TIMING_AVR_H
+32
View File
@@ -0,0 +1,32 @@
#ifndef _TIMING_HPP_
#define _TIMING_HPP_
// Physical AVC-LAN bit-phase durations, in microseconds. These are protocol
// facts (the bus spec), independent of any particular hardware. The active
// target provides TICK_US — the wall-clock duration, in microseconds, of one
// tick of whatever free-running timer it uses to measure/generate bus bits — so
// each constant below resolves to a count of that target's ticks.
//
// Kept as #defines (not constexpr): no target is guaranteed to want these as a
// specific integer width, so leave the type to the use site / target.
#ifndef TICK_US
#error \
"target must define TICK_US (microseconds per bus-timer tick) before including timing.h"
#endif
// Measured at ±0.02 μs @ F_CPU=20MHz, TCB_CLKSEL=TCB_CLKSEL_CLKDIV1_gc
#define AVCLAN_STARTBIT_LOGIC_0 (169.0 / TICK_US)
#define AVCLAN_STARTBIT_LOGIC_1 (20.6 / TICK_US)
#define AVCLAN_BIT1_LOGIC_0 (19.7 / TICK_US)
#define AVCLAN_BIT1_LOGIC_1 (18.1 / TICK_US)
#define AVCLAN_BIT0_LOGIC_0 (32.85 / TICK_US)
#define AVCLAN_BIT0_LOGIC_1 (6.2 / TICK_US)
#define AVCLAN_READBIT_THRESHOLD (26.0 / TICK_US)
#define AVCLAN_BIT_LENGTH_MAX (39.1 / TICK_US)
#endif