Browse Source

fmtx: add a command-based interface, allowing for additional features

Use a command-based interface on the downlink, so that it is possible
to pass parameters and issue non-data commands. Furthermore, don't
start transmitting until a full block is available to transmit.
H. Peter Anvin 3 years ago
parent
commit
8cebb5c242
5 changed files with 220 additions and 113 deletions
  1. 5 0
      USBCAS/fmrx.c
  2. 168 64
      USBCAS/fmtx.c
  3. 11 3
      USBCAS/reboot.c
  4. 6 39
      USBCAS/usbcas.c
  5. 30 7
      USBCAS/usbcas.h

+ 5 - 0
USBCAS/fmrx.c

@@ -45,6 +45,11 @@ void fmrx_init(void)
 	if (!(PIND & (1 << 4)))
 		tccr1b |= 1 << ICES1;
 	TCCR1B = tccr1b;
+
+	/*
+	 * Default speed
+	 */
+	fmrx_set_speed(CAS_BASE_CLOCK/CAS_DIVISOR_ABC80);
 }
 
 /* Timing constants: start and end of data bit interval, and timeout */

+ 168 - 64
USBCAS/fmtx.c

@@ -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;
+	}
+}

+ 11 - 3
USBCAS/reboot.c

@@ -27,9 +27,9 @@ static void bootloader_jump_check(void)
 }
 
 /*
- * Host sent BREAK; reboot into bootloader
+ * Host sent BREAK or &U command, reboot into bootloader
  */
-void EVENT_CDC_Device_BreakSent(USB_ClassInfo_CDC_Device_t* const CDCInterfaceInfo, const uint8_t duration)
+void go_to_bootloader(void)
 {
 	/* Set the bootloader invocation key */
 	boot_key_magic = MAGIC_BOOT_KEY;
@@ -37,5 +37,13 @@ void EVENT_CDC_Device_BreakSent(USB_ClassInfo_CDC_Device_t* const CDCInterfaceIn
 	/* Arm the watchdog to force a hardware reset */
 	wdt_enable(WDTO_250MS);
 
-	/* Return to the main loop so we ack the send break correctly */
+	/* Return so we can ack the USB transaction before we die */
+	GlobalInterruptEnable();
+}
+
+/*
+ * Host sent BREAK; reboot into bootloader
+ */
+void EVENT_CDC_Device_BreakSent(USB_ClassInfo_CDC_Device_t* const CDCInterfaceInfo, const uint8_t duration)
+{
 }

+ 6 - 39
USBCAS/usbcas.c

@@ -41,8 +41,6 @@
 /*
  * Input and output ring buffers
  */
-static RingBuffer_t u2c_ringbuf;	/* USB -> CAS */
-static uint8_t      u2c_buffer[512];
 static RingBuffer_t c2u_ringbuf;	/* CAS -> USB */
 static uint8_t      c2u_buffer[128];
 
@@ -207,19 +205,15 @@ static void usb_recv_data(void)
 		 * Only try to read in bytes from the CDC interface if
 		 * the transmit buffer is not full
 		 */
-		if (RingBuffer_IsFull(&u2c_ringbuf))
+		if (fmtx_full())
 			return;
 
 		byte = CDC_Device_ReceiveByte(&vcpif);
 		if (byte < 0)
 			return;		/* No data */
 
-		/*
-		 * Store received byte into the transmit buffer, then
-		 * enable the transmit ISR if disabled
-		 */
-		RingBuffer_Insert(&u2c_ringbuf, byte);
-		fmtx_enable();
+		/* Pass to the cas transmit layer */
+		fmtx_recv_byte(byte);
 	}
 }
 
@@ -278,7 +272,6 @@ int main(void)
 {
 	SetupHardware();
 
-	RingBuffer_InitBuffer(&u2c_ringbuf, u2c_buffer, sizeof(u2c_buffer));
 	RingBuffer_InitBuffer(&c2u_ringbuf, c2u_buffer, sizeof(c2u_buffer));
 
 	GlobalInterruptEnable();
@@ -370,9 +363,7 @@ void SetupHardware(void)
 
 	/* Initialize receiver and transmitter */
 	fmrx_init();
-	current_baudrate = fmtx_real_baudrate(CAS_BAUDRATE_ABC80);
-	fmrx_set_speed(current_baudrate);
-	fmtx_init_speed(current_baudrate);
+	fmtx_init();
 }
 
 /** Event handler for the library USB Connection event. */
@@ -411,37 +402,13 @@ void fmrx_recv_byte(uint8_t byte)
 		RingBuffer_Insert(&c2u_ringbuf, byte);
 }
 
-/*
- * Called from the transmit register empty ISR to fetch a new byte;
- * returns -1 if the ring buffer is empty.
- */
-int fmtx_next_byte(void)
-{
-	if (USB_DeviceState != DEVICE_STATE_Configured ||
-	    RingBuffer_IsEmpty(&u2c_ringbuf))
-		return -1;
-
-	return RingBuffer_Remove(&u2c_ringbuf);
-}
-
 /*
  * Event handler for the CDC Class driver Line Encoding Changed event.
  */
 void EVENT_CDC_Device_LineEncodingChanged(USB_ClassInfo_CDC_Device_t* const cii)
 {
-	uint32_t baudrate = cii->State.LineEncoding.BaudRateBPS;
-
-	/* This is a hack to give a sensible default */
-	baudrate = CAS_BAUDRATE_ABC80;
-
-	baudrate = fmtx_real_baudrate(baudrate);
-
-	if (1 || baudrate != current_baudrate) {
-		current_baudrate = baudrate;
-		fmtx_drain();
-		fmrx_set_speed(current_baudrate);
-		fmtx_init_speed(current_baudrate);
-	}
+	/* Currently not used */
+	/* XXX: handle fmrx speed setting */
 }
 
 /*

+ 30 - 7
USBCAS/usbcas.h

@@ -49,6 +49,25 @@
 #include <LUFA/Drivers/USB/USB.h>
 #include <LUFA/Platform/Platform.h>
 
+/* Cassette block data format */
+struct cas_block_data {
+	uint8_t leadin[32];         /* 0x00 */
+	uint8_t sync[3];            /* 0x16 */
+	uint8_t stx;                /* 0x02 */
+	uint8_t blktype;            /* 0x00 for data, 0xff for filename */
+	uint16_t blkno;		    /* Block number (littleendian) */
+	uint8_t data[253];          /* Actual data */
+	uint8_t etx;                /* 0x03 */
+	uint16_t csum;		    /* Checksum */
+	uint8_t leadout[1];	    /* 0x00 */
+} __attribute__((packed));
+
+struct cas_block {
+	volatile bool ready;	/* Filled with data */
+	unsigned int pause;	/* Post-block delay */
+	struct cas_block *next;	/* Next block pointer */
+	struct cas_block_data data;
+};
 
 /* Function Prototypes: */
 void SetupHardware(void);
@@ -62,16 +81,17 @@ void EVENT_USB_Device_ControlRequest(void);
 void EVENT_CDC_Device_LineEncodingChanged(USB_ClassInfo_CDC_Device_t* const CDCInterfaceInfo);
 void EVENT_CDC_Device_ControLineStateChanged(USB_ClassInfo_CDC_Device_t* const CDCInterfaceInfo);
 
-int fmtx_next_byte(void);
-void fmtx_drain(void);
-uint32_t fmtx_real_baudrate(uint32_t baudrate);
-void fmtx_init_speed(uint32_t baudrate);
 /* Enable the TX ISR which enables sending data */
 static inline ATTR_ALWAYS_INLINE void fmtx_enable(void)
 {
 	UCSR1B |= (1 << UDRIE1);
-	PORTB &= ~(1 << 0);
 }
+
+void fmtx_drain(void);
+void fmtx_init(void);
+bool fmtx_full(void);
+void fmtx_recv_byte(uint8_t byte);
+
 extern bool _motor_relay;
 
 static inline ATTR_ALWAYS_INLINE bool motor_relay(void)
@@ -83,8 +103,11 @@ void fmrx_init(void);
 void fmrx_set_speed(uint32_t baudrate);
 void fmrx_recv_byte(uint8_t byte);
 
+void go_to_bootloader(void);
+
 /* Parameters */
-#define CAS_BAUDRATE_ABC80	725
-#define CAS_BAUDRATE_ABC800	2680
+#define CAS_BASE_CLOCK		(1500000/16) /* What ABC800 uses */
+#define CAS_DIVISOR_ABC80	129
+#define CAS_BAUDRATE_ABC800	35
 
 #endif