mirror of
https://github.com/halleysfifthinc/AVCLAN-Mockingboard.git
synced 2026-08-07 01:13:18 +00:00
Substantially refactor ~everything to separate concerns and improve flow clarity
Highlights: - Remove all use of malloc and friends - Refactor message handling functions to work with references - Move core message read => triage => respond logic loop to main function - Improve message triage and support follow-up messages - Refactor hex byte parsing - Disable periodic interrupt when not playing
This commit is contained in:
+251
-101
@@ -24,12 +24,15 @@
|
||||
#include <avr/io.h>
|
||||
#include <avr/sfr_defs.h>
|
||||
#include <avr/xmega.h>
|
||||
#include <ctype.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "avclandrv.h"
|
||||
#include "com232.h"
|
||||
#include "queue.h"
|
||||
|
||||
uint8_t echoCharacters;
|
||||
uint8_t readBinary;
|
||||
@@ -38,23 +41,61 @@ uint8_t readkey;
|
||||
|
||||
const char *const offon[] = {"OFF", "ON"};
|
||||
|
||||
#define CACHE_SIZE 16
|
||||
|
||||
AVCLAN_frame_t frames[CACHE_SIZE];
|
||||
RFrame_t responses[CACHE_SIZE];
|
||||
uint8_t framesdata[CACHE_SIZE][MAXMSGLEN];
|
||||
|
||||
Queue_t cache, rcache, incoming, outgoing;
|
||||
|
||||
volatile uint8_t enqueueStatus = 0;
|
||||
|
||||
void Setup();
|
||||
void general_GPIO_init();
|
||||
void print_help();
|
||||
|
||||
static uint8_t return_resp(RFrame_t *resp) {
|
||||
uint8_t err;
|
||||
AVCLAN_frame_t *out = resp->frame;
|
||||
if ((out >= frames) && (out < &frames[CACHE_SIZE]))
|
||||
// only return cache-owned frames (e.g. not status, etc)
|
||||
err = pushQueue(&cache, out);
|
||||
err = pushQueue(&rcache, resp);
|
||||
return err;
|
||||
}
|
||||
|
||||
static uint8_t push_or_return_resp(RFrame_t *resp) {
|
||||
uint8_t err = pushQueue(&outgoing, resp) && return_resp(resp);
|
||||
return err;
|
||||
}
|
||||
|
||||
int main() {
|
||||
uint8_t readSeq = 0;
|
||||
uint8_t s_len = 0;
|
||||
uint8_t s_dig = 0;
|
||||
uint8_t s_c[2];
|
||||
uint8_t i;
|
||||
uint8_t hexChars[2];
|
||||
uint8_t hexDigit = 0; // current digit being written to hexChars
|
||||
|
||||
MSG_TYPE_t seqBroadcast = BROADCAST;
|
||||
uint8_t lastPrintAllFrames = 1;
|
||||
|
||||
uint8_t data_tmp[MAXMSGLEN];
|
||||
AVCLAN_frame_t msg = {
|
||||
.broadcast = UNICAST,
|
||||
.controller_addr = DEVICE_ADDR,
|
||||
.control = 0xF,
|
||||
.data = data_tmp,
|
||||
};
|
||||
uint8_t seqLen = 0; // current length written to data_tmp
|
||||
|
||||
uint8_t err = 0;
|
||||
|
||||
AVCLAN_frame_t *msg, *out;
|
||||
RFrame_t *resp;
|
||||
AVCLAN_frame_t *status = AVCLAN_getStatusFrame();
|
||||
|
||||
for (uint8_t i = 0; i < CACHE_SIZE; ++i) {
|
||||
frames[i].data = framesdata[i];
|
||||
frames[i].control = 0x0f;
|
||||
}
|
||||
|
||||
constructQueue(&cache, frames, sizeof(AVCLAN_frame_t), CACHE_SIZE, 1);
|
||||
constructEmptyQueue(&incoming, sizeof(AVCLAN_frame_t), CACHE_SIZE);
|
||||
constructQueue(&rcache, responses, sizeof(RFrame_t), CACHE_SIZE, 1);
|
||||
constructEmptyQueue(&outgoing, sizeof(RFrame_t), CACHE_SIZE);
|
||||
|
||||
Setup();
|
||||
print_help();
|
||||
@@ -62,16 +103,89 @@ int main() {
|
||||
while (1) {
|
||||
|
||||
if (!BUS_IS_IDLE) {
|
||||
AVCLAN_readframe();
|
||||
} else if (AVCLAN_responseNeeded()) {
|
||||
AVCLAN_respond();
|
||||
msg = (AVCLAN_frame_t *)(&cache);
|
||||
if (!msg) {
|
||||
RS232_Print("!! Dropping an incoming message; cache is empty !!");
|
||||
continue;
|
||||
}
|
||||
|
||||
err = AVCLAN_readframe(msg);
|
||||
if (!err)
|
||||
err = pushQueue(&incoming, msg) && pushQueue(&cache, msg);
|
||||
} else if (!isEmpty(&incoming)) {
|
||||
out = (AVCLAN_frame_t *)popQueue(&cache);
|
||||
if (!out) {
|
||||
RS232_Print("!! Unable to respond; cache is empty !!");
|
||||
continue;
|
||||
}
|
||||
|
||||
msg = (AVCLAN_frame_t *)popQueue(
|
||||
&incoming); // prior !isempty(incoming) guarantees success
|
||||
response_t respond = AVCLAN_handleframe(msg, out);
|
||||
|
||||
if (respond) {
|
||||
resp = (RFrame_t *)popQueue(&rcache);
|
||||
if (resp) {
|
||||
*resp = (RFrame_t){.r = respond, .frame = out};
|
||||
push_or_return_resp(resp);
|
||||
} else
|
||||
pushQueue(&cache, out);
|
||||
} else // no response needed; return to circulation
|
||||
pushQueue(&cache, out);
|
||||
|
||||
pushQueue(&cache, msg);
|
||||
} else if (!isEmpty(&outgoing)) {
|
||||
resp = (RFrame_t *)popQueue(
|
||||
&outgoing); // prior !isempty(outgoing) guarantees success
|
||||
out = resp->frame;
|
||||
err = AVCLAN_sendframe(out);
|
||||
if (err) {
|
||||
RS232_Print("!! Failed to send frame; error code ");
|
||||
RS232_PrintHex(err);
|
||||
RS232_Print(" !!\n");
|
||||
return_resp(resp);
|
||||
} else {
|
||||
// Re-use successful resp for sequence
|
||||
switch (resp->r) {
|
||||
case r_TrackChange: AVCLAN_setTime(0x00, 0x00); // fallthrough
|
||||
case r_NormalizeState:
|
||||
AVCLAN_normalizeState(out);
|
||||
resp->r = r_Handled;
|
||||
push_or_return_resp(resp);
|
||||
break;
|
||||
case r_StartPlaying:
|
||||
AVCLAN_generateStatus(out);
|
||||
resp->r = r_NormalizeState;
|
||||
push_or_return_resp(resp);
|
||||
break;
|
||||
case r_StatusReport:
|
||||
AVCLAN_generateStatus(out);
|
||||
resp->r = r_Handled;
|
||||
push_or_return_resp(resp);
|
||||
break;
|
||||
case r_Nothing: __builtin_unreachable();
|
||||
case r_Handled: return_resp(resp); break;
|
||||
}
|
||||
}
|
||||
} else if (enqueueStatus) {
|
||||
AVCLAN_generateStatus(status);
|
||||
resp = (RFrame_t *)popQueue(&rcache);
|
||||
if (resp) {
|
||||
*resp = (RFrame_t){.r = r_Handled, .frame = status};
|
||||
err = pushQueue(&outgoing, resp);
|
||||
if (err) {
|
||||
RS232_Print("Outgoing queue full; unable to send status update");
|
||||
pushQueue(&rcache, resp);
|
||||
} else
|
||||
enqueueStatus = 0; // Only clear if successful
|
||||
}
|
||||
// no further error handling needed; status isn't part of the cache
|
||||
}
|
||||
|
||||
// Key handler
|
||||
if (RS232_RxCharEnd) {
|
||||
cli();
|
||||
readkey = RS232_RxCharBuffer[RS232_RxCharBegin];
|
||||
RS232_RxCharBegin++;
|
||||
readkey = RS232_RxCharBuffer[RS232_RxCharBegin++];
|
||||
if (RS232_RxCharBegin == RS232_RxCharEnd) // if buffer is consumed
|
||||
RS232_RxCharBegin = RS232_RxCharEnd = 0; // reset buffer
|
||||
sei();
|
||||
@@ -84,6 +198,9 @@ int main() {
|
||||
RS232_Print("\n");
|
||||
break;
|
||||
case 'X':
|
||||
// X/x isn't a single toggle interface because this is used
|
||||
// programmatically and is simpler than reading the toggle
|
||||
// state
|
||||
printBinary = 1;
|
||||
RS232_Print("Binary: ");
|
||||
RS232_Print(offon[1]);
|
||||
@@ -95,30 +212,6 @@ int main() {
|
||||
RS232_Print(offon[0]);
|
||||
RS232_Print("\n");
|
||||
break;
|
||||
case 'S': // Read sequence
|
||||
printAllFrames = 0;
|
||||
RS232_Print("READ SEQUENCE > \n");
|
||||
readSeq = 1;
|
||||
s_len = 0;
|
||||
s_dig = 0;
|
||||
s_c[0] = s_c[1] = 0;
|
||||
break;
|
||||
case 'W': // Send command
|
||||
printAllFrames = 1;
|
||||
readSeq = 0;
|
||||
msg.broadcast = UNICAST;
|
||||
msg.length = s_len;
|
||||
AVCLAN_sendframe(&msg);
|
||||
break;
|
||||
case 'Q': // Send broadcast
|
||||
printAllFrames = 1;
|
||||
readSeq = 0;
|
||||
msg.broadcast = BROADCAST;
|
||||
msg.peripheral_addr = 0x1FF;
|
||||
msg.length = s_len;
|
||||
AVCLAN_sendframe(&msg);
|
||||
msg.peripheral_addr = HU_ADDR;
|
||||
break;
|
||||
case 'l': // Print received messages
|
||||
printAllFrames ^= 1;
|
||||
RS232_Print("Logging: ");
|
||||
@@ -138,31 +231,43 @@ int main() {
|
||||
RS232_Print(offon[muteBus]);
|
||||
RS232_Print("\n");
|
||||
break;
|
||||
case 'b':
|
||||
case 'B': // Beep
|
||||
{
|
||||
const uint8_t beep[] = {0x00, dev_CD_CHANGER, dev_BEEP_SPEAKERS, 0x60,
|
||||
0x01};
|
||||
memcpy(data_tmp, beep, sizeof(beep));
|
||||
msg.length = sizeof(beep);
|
||||
}
|
||||
msg.broadcast = UNICAST;
|
||||
msg.controller_addr = DEVICE_ADDR;
|
||||
msg.peripheral_addr = HU_ADDR;
|
||||
AVCLAN_sendframe(&msg);
|
||||
break;
|
||||
case 'p':
|
||||
CD_Mode = stPlay;
|
||||
{
|
||||
const uint8_t play[] = {0x00, dev_COMM_CTRL, dev_COMM_v1,
|
||||
Insertion, dev_CD_CHANGER, 0x01};
|
||||
memcpy(data_tmp, play, sizeof(play));
|
||||
msg.length = sizeof(play);
|
||||
case 'E': // Beep
|
||||
out = (AVCLAN_frame_t *)popQueue(&cache);
|
||||
if (out) {
|
||||
resp = (RFrame_t *)popQueue(&rcache);
|
||||
if (resp) {
|
||||
out->broadcast = UNICAST;
|
||||
out->controller_addr = DEVICE_ADDR;
|
||||
out->peripheral_addr = HU_ADDR;
|
||||
{
|
||||
const uint8_t beep[] = {0x00, dev_CD_CHANGER, dev_BEEP_SPEAKERS,
|
||||
0x60, 0x01};
|
||||
out->length = sizeof(beep);
|
||||
memcpy(out->data, beep, sizeof(beep));
|
||||
}
|
||||
*resp = (RFrame_t){.r = r_Handled, .frame = out};
|
||||
push_or_return_resp(resp);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'P':
|
||||
out = (AVCLAN_frame_t *)popQueue(&cache);
|
||||
if (out) {
|
||||
resp = (RFrame_t *)popQueue(&rcache);
|
||||
if (resp) {
|
||||
out->broadcast = UNICAST;
|
||||
out->controller_addr = DEVICE_ADDR;
|
||||
out->peripheral_addr = HU_ADDR;
|
||||
{
|
||||
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));
|
||||
}
|
||||
*resp = (RFrame_t){.r = r_Handled, .frame = out};
|
||||
push_or_return_resp(resp);
|
||||
}
|
||||
}
|
||||
msg.broadcast = UNICAST;
|
||||
msg.controller_addr = DEVICE_ADDR;
|
||||
msg.peripheral_addr = HU_ADDR;
|
||||
AVCLAN_sendframe(&msg);
|
||||
break;
|
||||
|
||||
#ifdef SOFTWARE_DEBUG
|
||||
@@ -171,52 +276,90 @@ int main() {
|
||||
|
||||
case 0x10: // Signals binary sequence incoming
|
||||
if (!readSeq && !readBinary) {
|
||||
readSeq = 1;
|
||||
readBinary = 1;
|
||||
s_len = 0;
|
||||
readSeq = readBinary = 1;
|
||||
seqLen = 0;
|
||||
break;
|
||||
} // else (readSeq || readBinary); fall through to default
|
||||
} // otherwise we're reading binary and that's a data byte
|
||||
case 'U': // Send command
|
||||
RS232_Print("READ SEQUENCE (U)> \n");
|
||||
lastPrintAllFrames = printAllFrames;
|
||||
printAllFrames = 0;
|
||||
readSeq = 1;
|
||||
seqLen = hexDigit = 0;
|
||||
hexChars[0] = hexChars[1] = 0;
|
||||
seqBroadcast = UNICAST;
|
||||
break;
|
||||
case 'B': // Send broadcast
|
||||
RS232_Print("READ SEQUENCE (B)> \n");
|
||||
lastPrintAllFrames = printAllFrames;
|
||||
printAllFrames = 0;
|
||||
readSeq = 1;
|
||||
seqLen = hexDigit = 0;
|
||||
hexChars[0] = hexChars[1] = 0;
|
||||
seqBroadcast = BROADCAST;
|
||||
break;
|
||||
case '\n':
|
||||
if (readSeq && readBinary && data_tmp[s_len] == 0x17) {
|
||||
{
|
||||
uint8_t tmp[MAXMSGLEN];
|
||||
AVCLAN_frame_t frame = {.data = tmp};
|
||||
err = AVCLAN_parseframe(data_tmp, --s_len, &frame);
|
||||
if (!err) {
|
||||
AVCLAN_sendframe(frame);
|
||||
readSeq = 0;
|
||||
readBinary = 0;
|
||||
if (readSeq) {
|
||||
if (readBinary) {
|
||||
if (data_tmp[seqLen] == 0x17) {
|
||||
out = (AVCLAN_frame_t *)popQueue(&cache);
|
||||
if (out) {
|
||||
err = AVCLAN_parseframe(data_tmp, --seqLen, out);
|
||||
if (!err) {
|
||||
resp = (RFrame_t *)popQueue(&rcache);
|
||||
if (resp) {
|
||||
*resp = (RFrame_t){.r = r_Handled, .frame = out};
|
||||
push_or_return_resp(resp);
|
||||
}
|
||||
}
|
||||
}
|
||||
readSeq = readBinary = 0;
|
||||
} else
|
||||
goto DEFAULT; // reading binary and this is a real data byte;
|
||||
// fall through to default
|
||||
} else {
|
||||
out = (AVCLAN_frame_t *)popQueue(&cache);
|
||||
if (out) {
|
||||
resp = (RFrame_t *)popQueue(&rcache);
|
||||
if (resp) {
|
||||
out->broadcast = seqBroadcast;
|
||||
out->controller_addr = DEVICE_ADDR;
|
||||
switch (seqBroadcast) {
|
||||
case UNICAST: out->peripheral_addr = HU_ADDR; break;
|
||||
case BROADCAST: out->peripheral_addr = 0x1FF; break;
|
||||
}
|
||||
out->length = seqLen;
|
||||
memcpy(out->data, data_tmp, seqLen);
|
||||
*resp = (RFrame_t){.r = r_Handled, .frame = out};
|
||||
push_or_return_resp(resp);
|
||||
}
|
||||
}
|
||||
printAllFrames = lastPrintAllFrames;
|
||||
}
|
||||
break;
|
||||
} // else (readSeq || readBinary || most recent char != 0x17);
|
||||
// fall through to default
|
||||
}
|
||||
DEFAULT:
|
||||
default:
|
||||
if (readSeq) {
|
||||
if (readBinary) {
|
||||
data_tmp[s_len++] = readkey;
|
||||
data_tmp[seqLen++] = readkey;
|
||||
} else {
|
||||
s_c[s_dig] = readkey;
|
||||
hexChars[hexDigit++] = readkey;
|
||||
|
||||
s_dig++;
|
||||
if (s_dig == 2) {
|
||||
if (s_c[0] < ':')
|
||||
s_c[0] -= 48;
|
||||
else
|
||||
s_c[0] -= 55;
|
||||
data_tmp[s_len] = 16 * s_c[0];
|
||||
if (s_c[1] < ':')
|
||||
s_c[1] -= 48;
|
||||
else
|
||||
s_c[1] -= 55;
|
||||
data_tmp[s_len] += s_c[1];
|
||||
s_len++;
|
||||
s_dig = 0;
|
||||
s_c[0] = s_c[1] = 0;
|
||||
if (hexDigit == 2) {
|
||||
char h, l;
|
||||
h = toupper(hexChars[0]);
|
||||
h += (h < ':') ? 0xd0 : 0xc9; // digit or letter
|
||||
|
||||
l = toupper(hexChars[1]);
|
||||
l += (l < ':') ? 0xd0 : 0xc9;
|
||||
|
||||
data_tmp[seqLen++] = (h << 4) | l;
|
||||
hexDigit = hexChars[0] = hexChars[1] = 0;
|
||||
}
|
||||
if (echoCharacters) {
|
||||
RS232_Print("CURRENT SEQUENCE > ");
|
||||
for (i = 0; i < s_len; i++) {
|
||||
for (uint8_t i = 0; i < seqLen; i++) {
|
||||
RS232_PrintHex8(data_tmp[i]);
|
||||
RS232_SendByte(' ');
|
||||
}
|
||||
@@ -279,14 +422,14 @@ void general_GPIO_init() {
|
||||
|
||||
void print_help() {
|
||||
RS232_Print("AVCLAN Mockingboard v1\n");
|
||||
RS232_Print("S - read sequence\n"
|
||||
"W - send command\n"
|
||||
"Q - send broadcast\n"
|
||||
RS232_Print("W - begin reading for unicast message\n"
|
||||
"Q - begin reading for broadcast message\n"
|
||||
"m - Toggle mute for mockingboard bus activity\n"
|
||||
"l - Toggle message logging\n"
|
||||
"k - Toggle character echo\n"
|
||||
"X/x - Turn binary ON or OFF, respectively\n"
|
||||
"B - Beep\n"
|
||||
"X/x - Turn binary printing ON or OFF, respectively\n"
|
||||
"E - Beep\n"
|
||||
"P - Play\n"
|
||||
"v - Toggle verbose logging\n"
|
||||
#ifdef SOFTWARE_DEBUG
|
||||
"M - Measure bit-timing (pulse-widths and periods)\n"
|
||||
@@ -297,3 +440,10 @@ void print_help() {
|
||||
#endif
|
||||
"? - Print this message\n");
|
||||
}
|
||||
|
||||
// Periodic interrupt with a 1 sec period; only enabled when playing
|
||||
ISR(RTC_PIT_vect) {
|
||||
AVCLAN_incrementTime();
|
||||
enqueueStatus = 1;
|
||||
RTC.PITINTFLAGS = RTC_PI_bm;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user