|
@@ -5,18 +5,22 @@
|
|
|
|
|
|
#include "usbcas.h"
|
|
|
|
|
|
-#define BITS_PER_PATTERN_BYTE 2
|
|
|
+#define BIT_PATTERN_LEN_LG2 2
|
|
|
+#define BITS_PER_PATTERN_BYTE (8 >> BIT_PATTERN_LEN_LG2)
|
|
|
#define PATTERN_BIT_MASK ((1 << BITS_PER_PATTERN_BYTE) - 1)
|
|
|
|
|
|
#include "fmpat.h"
|
|
|
|
|
|
+/* Triple buffer: one being transmitted, one being received, and one ready. */
|
|
|
+static struct cas_block block[3];
|
|
|
+
|
|
|
#define TX_BUSY 0
|
|
|
#define TX_IDLE (1 << TXC1)
|
|
|
|
|
|
+/* Disable the TX ISR */
|
|
|
static inline ATTR_ALWAYS_INLINE void fmtx_disable(void)
|
|
|
{
|
|
|
UCSR1B &= ~(1 << UDRIE1);
|
|
|
- PORTB |= (1 << 0);
|
|
|
}
|
|
|
|
|
|
static uint8_t parity;
|
|
@@ -26,55 +30,37 @@ static uint8_t parity;
|
|
|
*/
|
|
|
ISR(USART1_UDRE_vect)
|
|
|
{
|
|
|
- static uint8_t data, left, gap;
|
|
|
- static uint16_t block_bytes;
|
|
|
+ static uint8_t tx_data, tx_bits;
|
|
|
+ static unsigned int tx_offset; /* Offset into current buffer */
|
|
|
+ static struct cas_block *blk = &block[0];
|
|
|
uint8_t pattern;
|
|
|
|
|
|
- if (!left) {
|
|
|
- /* Fetch a new byte if there is one, unless motor is off */
|
|
|
- int nd;
|
|
|
- if (!motor_relay() || (nd = fmtx_next_byte()) < 0) {
|
|
|
- /*
|
|
|
- * Nothing to send. Hold the line for some
|
|
|
- * number of byte-times in case we suffered an
|
|
|
- * underrun. Afterwards, the hardware will
|
|
|
- * drive the line high.
|
|
|
- */
|
|
|
+ if (!tx_bits) {
|
|
|
+ if (!blk->ready || !motor_relay()) {
|
|
|
+ /* Hold the line for one pattern time, then idle */
|
|
|
UCSR1A = TX_IDLE;
|
|
|
- UCSR1A = parity;
|
|
|
- if (!++gap)
|
|
|
- fmtx_disable();
|
|
|
- return;
|
|
|
- }
|
|
|
- data = nd;
|
|
|
- if (!block_bytes) {
|
|
|
- if (data == 0x02) {
|
|
|
- /*
|
|
|
- * Beginning of a data block, suppress
|
|
|
- * FF = silence until the end of the block
|
|
|
- *
|
|
|
- * STX+header+data+ETX+csum
|
|
|
- */
|
|
|
- block_bytes = 1+3+253+1+2;
|
|
|
+ UDR1 = parity;
|
|
|
+ fmtx_disable(); /* Nothing left to do */
|
|
|
+ } else if (tx_offset < sizeof blk->data) {
|
|
|
+ tx_data = ((const uint8_t *)&blk->data)[tx_offset++];
|
|
|
+ tx_bits = 8;
|
|
|
+ } else if (blk->pause) {
|
|
|
+ /* Interblock silence; hold the line */
|
|
|
+ UCSR1A = TX_IDLE;
|
|
|
+ UDR1 = parity;
|
|
|
+ if (!--blk->pause) {
|
|
|
+ blk->ready = false; /* Free to use */
|
|
|
+ blk = blk->next;
|
|
|
+ tx_offset = 0;
|
|
|
}
|
|
|
- } else {
|
|
|
- block_bytes--;
|
|
|
}
|
|
|
-
|
|
|
- data = nd;
|
|
|
- left = 8;
|
|
|
- gap = 0;
|
|
|
- }
|
|
|
-
|
|
|
- if (!block_bytes && data == 0xff) {
|
|
|
- pattern = parity;
|
|
|
- } else {
|
|
|
- pattern = fm_pat[data & PATTERN_BIT_MASK] ^ parity;
|
|
|
- data >>= BITS_PER_PATTERN_BYTE;
|
|
|
- parity = -((int8_t)pattern < 0);
|
|
|
}
|
|
|
- left -= BITS_PER_PATTERN_BYTE;
|
|
|
|
|
|
+ /* Actual data bits available */
|
|
|
+ pattern = fm_pat[tx_data & PATTERN_BIT_MASK] ^ parity;
|
|
|
+ tx_data >>= BITS_PER_PATTERN_BYTE;
|
|
|
+ parity = -((int8_t)pattern < 0);
|
|
|
+ tx_bits -= BITS_PER_PATTERN_BYTE;
|
|
|
UCSR1A = TX_IDLE;
|
|
|
UDR1 = pattern;
|
|
|
}
|
|
@@ -89,33 +75,33 @@ void fmtx_drain(void)
|
|
|
do_usb_tasks();
|
|
|
}
|
|
|
|
|
|
-/*
|
|
|
- * Initialize the USART hardware and set transmission baudrate.
|
|
|
- * This is mostly the same as SerialSPI_Init().
|
|
|
- * Returns the actual baud rate adjusted for underflow.
|
|
|
- */
|
|
|
-#define FMTX_UBRRVAL(b) (SERIAL_SPI_UBBRVAL((b) * (8/BITS_PER_PATTERN_BYTE)))
|
|
|
-#define UBRR_MAX 4095
|
|
|
-#define FMTX_MIN_BAUD (F_CPU / (2 * (8/BITS_PER_PATTERN_BYTE) * (UBRR_MAX+1UL)))
|
|
|
-#define FMTX_MAX_BAUD (F_CPU / (2 * (8/BITS_PER_PATTERN_BYTE)))
|
|
|
+static inline uint16_t divisor_to_ubrr(uint8_t divisor)
|
|
|
+{
|
|
|
+ uint16_t ubrr;
|
|
|
+
|
|
|
+ if (!divisor)
|
|
|
+ divisor = CAS_DIVISOR_ABC80;
|
|
|
|
|
|
-#if FMTX_MIN_BAUD > CAS_BAUDRATE_ABC80
|
|
|
-# error "UBRR overflows for standard ABC80 baud rate"
|
|
|
+#if F_CPU != 16000000
|
|
|
+#error "Assuming F_CPU is 16 MHz"
|
|
|
#endif
|
|
|
+ ubrr = ((uint16_t)divisor << (8-BIT_PATTERN_LEN_LG2))/3 - 1;
|
|
|
|
|
|
-uint32_t fmtx_real_baudrate(uint32_t baudrate)
|
|
|
-{
|
|
|
- if (baudrate < FMTX_MIN_BAUD || baudrate > FMTX_MAX_BAUD)
|
|
|
- baudrate = CAS_BAUDRATE_ABC80;
|
|
|
- return baudrate;
|
|
|
+ if (ubrr > 4095)
|
|
|
+ ubrr = 4095;
|
|
|
+
|
|
|
+ return ubrr;
|
|
|
}
|
|
|
|
|
|
-void fmtx_init_speed(uint32_t baudrate)
|
|
|
+static void fmtx_set_speed(uint8_t divisor)
|
|
|
{
|
|
|
+ if (!divisor)
|
|
|
+ divisor = CAS_DIVISOR_ABC80;
|
|
|
+
|
|
|
parity = 0xff; /* We start out with a high idle */
|
|
|
|
|
|
- DDRD |= 0x28; /* PD3 = outdata, PD5 = XCK/TXLED */
|
|
|
- PORTD |= 0x28; /* Drive data and clock high while idle */
|
|
|
+ DDRD |= 0x28; /* PD3 = outdata, PD5 = XCK/TXLED */
|
|
|
+ PORTD |= 0x28; /* Drive data and clock high while idle */
|
|
|
|
|
|
UCSR1B = 0; /* Disable transmitter and ISR */
|
|
|
|
|
@@ -133,7 +119,7 @@ void fmtx_init_speed(uint32_t baudrate)
|
|
|
UCSR1B = (1 << TXEN1);
|
|
|
|
|
|
/* Configure baud rate */
|
|
|
- UBRR1 = FMTX_UBRRVAL(baudrate);
|
|
|
+ UBRR1 = divisor_to_ubrr(divisor);
|
|
|
|
|
|
/*
|
|
|
* Enable transmitter ISR (which probably will immediately
|
|
@@ -141,3 +127,121 @@ void fmtx_init_speed(uint32_t baudrate)
|
|
|
*/
|
|
|
fmtx_enable();
|
|
|
}
|
|
|
+
|
|
|
+static enum rx_state {
|
|
|
+ rx_idle, /* Waiting for & */
|
|
|
+ rx_amp, /* & received */
|
|
|
+ rx_cmd, /* command byte received, getting args */
|
|
|
+ rx_data /* receiving data */
|
|
|
+} rx_state;
|
|
|
+
|
|
|
+static struct cas_block *rx_blk;
|
|
|
+void fmtx_init(void)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+
|
|
|
+ /* Fill in the invariant part of the block buffers */
|
|
|
+
|
|
|
+ memset(block, 0, sizeof block);
|
|
|
+
|
|
|
+ block[0].next = &block[1];
|
|
|
+ block[1].next = &block[2];
|
|
|
+ block[2].next = &block[0];
|
|
|
+ rx_blk = &block[0];
|
|
|
+ rx_state = rx_idle;
|
|
|
+
|
|
|
+ for (i = 0; i <= 2; i++) {
|
|
|
+ memset(block[i].data.sync, 0x16, sizeof block[i].data.sync);
|
|
|
+ block[i].data.stx = 0x02;
|
|
|
+ block[i].data.etx = 0x03;
|
|
|
+ }
|
|
|
+
|
|
|
+ fmtx_set_speed(CAS_DIVISOR_ABC80);
|
|
|
+}
|
|
|
+
|
|
|
+bool fmtx_full(void)
|
|
|
+{
|
|
|
+ return rx_blk->ready;
|
|
|
+}
|
|
|
+
|
|
|
+#define MAX_ARGS 4
|
|
|
+
|
|
|
+static uint8_t do_cmd(uint8_t cmd, const uint16_t *arg)
|
|
|
+{
|
|
|
+ switch (cmd) {
|
|
|
+ case 'U':
|
|
|
+ /* Firmware update, kick us to boot loader */
|
|
|
+ /* &U */
|
|
|
+ go_to_bootloader();
|
|
|
+ return rx_idle;
|
|
|
+ case 'H':
|
|
|
+ /* Begin header block */
|
|
|
+ /* &H [divisor] */
|
|
|
+ rx_blk->pause = (3*362) << BIT_PATTERN_LEN_LG2;
|
|
|
+ rx_blk->data.blktype = rx_blk->data.blkno = -1;
|
|
|
+ goto blk_common;
|
|
|
+
|
|
|
+ case 'D':
|
|
|
+ /* Begin data block */
|
|
|
+ /* &D divisor,blocknr */
|
|
|
+ rx_blk->pause = 362 << BIT_PATTERN_LEN_LG2;
|
|
|
+ rx_blk->data.blktype = 0;
|
|
|
+ rx_blk->data.blkno = arg[1];
|
|
|
+ blk_common:
|
|
|
+ fmtx_drain();
|
|
|
+ fmtx_set_speed(arg[0]);
|
|
|
+ rx_blk->data.csum = 0x03 /* ETX is part of the checksum */
|
|
|
+ + rx_blk->data.blktype
|
|
|
+ + (uint8_t)rx_blk->data.blkno
|
|
|
+ + (rx_blk->data.blkno >> 8);
|
|
|
+ return rx_data;
|
|
|
+ default:
|
|
|
+ return rx_idle; /* Unknown/unimplemented command */
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void fmtx_recv_byte(uint8_t byte)
|
|
|
+{
|
|
|
+ static uint8_t cmd; /* Command byte */
|
|
|
+ static uint8_t ctr; /* State-specific counter */
|
|
|
+ static uint16_t arg[4];
|
|
|
+
|
|
|
+ switch (rx_state) {
|
|
|
+ case rx_idle:
|
|
|
+ if (byte == '&')
|
|
|
+ rx_state = rx_amp;
|
|
|
+ break;
|
|
|
+ case rx_amp:
|
|
|
+ ctr = 0;
|
|
|
+ memset(arg, 0, sizeof arg);
|
|
|
+ cmd = byte;
|
|
|
+ if (byte <= ' ' || byte > '~')
|
|
|
+ rx_state = rx_idle;
|
|
|
+ else
|
|
|
+ rx_state = rx_cmd;
|
|
|
+ break;
|
|
|
+ case rx_cmd:
|
|
|
+ if (byte == '\n') {
|
|
|
+ ctr = 0;
|
|
|
+ rx_state = do_cmd(cmd, arg);
|
|
|
+ break;
|
|
|
+ } else if (byte == ',') {
|
|
|
+ ctr++;
|
|
|
+ } else if (ctr < MAX_ARGS) {
|
|
|
+ byte -= '0';
|
|
|
+ if (byte < 10)
|
|
|
+ arg[ctr] = arg[ctr]*10 + byte;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case rx_data:
|
|
|
+ rx_blk->data.data[ctr++] = byte;
|
|
|
+ rx_blk->data.csum += byte;
|
|
|
+ if (ctr == 253) {
|
|
|
+ rx_blk->ready = true;
|
|
|
+ rx_blk = rx_blk->next;
|
|
|
+ rx_state = rx_idle;
|
|
|
+ fmtx_enable();
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+}
|