|
@@ -1,3 +1,11 @@
|
|
|
+/*
|
|
|
+ * usbcas.c
|
|
|
+ *
|
|
|
+ * Emulator for the ABC80/800 cassette port
|
|
|
+ * Based in part on USBtoSerial.c from the LUFA library
|
|
|
+ *
|
|
|
+ * Copyright (C) 2021 H. Peter Anvin
|
|
|
+ */
|
|
|
/*
|
|
|
LUFA Library
|
|
|
Copyright (C) Dean Camera, 2021.
|
|
@@ -28,69 +36,53 @@
|
|
|
this software.
|
|
|
*/
|
|
|
|
|
|
-/** \file
|
|
|
- *
|
|
|
- * Main source file for the USBtoSerial project. This file contains the main tasks of
|
|
|
- * the project and is responsible for the initial application hardware configuration.
|
|
|
- */
|
|
|
-
|
|
|
#include "usbcas.h"
|
|
|
|
|
|
-/** Circular buffer to hold data from the host before it is sent to the device via the serial port. */
|
|
|
-static RingBuffer_t USBtoCAS_Buffer;
|
|
|
-
|
|
|
-/** Underlying data buffer for \ref USBtoCAS_Buffer, where the stored bytes are located. */
|
|
|
-static uint8_t USBtoCAS_Buffer_Data[128];
|
|
|
-
|
|
|
-/** Circular buffer to hold data from the serial port before it is sent to the host. */
|
|
|
-static RingBuffer_t CAStoUSB_Buffer;
|
|
|
-
|
|
|
-/** Underlying data buffer for \ref CAStoUSB_Buffer, where the stored bytes are located. */
|
|
|
-static uint8_t CAStoUSB_Buffer_Data[128];
|
|
|
-
|
|
|
-/** LUFA CDC Class driver interface configuration and state information. This structure is
|
|
|
- * passed to all CDC Class driver functions, so that multiple instances of the same class
|
|
|
- * within a device can be differentiated from one another.
|
|
|
- */
|
|
|
-USB_ClassInfo_CDC_Device_t VirtualSerial_CDC_Interface =
|
|
|
- {
|
|
|
- .Config =
|
|
|
- {
|
|
|
- .ControlInterfaceNumber = INTERFACE_ID_CDC_CCI,
|
|
|
- .DataINEndpoint =
|
|
|
- {
|
|
|
- .Address = CDC_TX_EPADDR,
|
|
|
- .Size = CDC_TXRX_EPSIZE,
|
|
|
- .Banks = 1,
|
|
|
- },
|
|
|
- .DataOUTEndpoint =
|
|
|
- {
|
|
|
- .Address = CDC_RX_EPADDR,
|
|
|
- .Size = CDC_TXRX_EPSIZE,
|
|
|
- .Banks = 1,
|
|
|
- },
|
|
|
- .NotificationEndpoint =
|
|
|
- {
|
|
|
- .Address = CDC_NOTIFICATION_EPADDR,
|
|
|
- .Size = CDC_NOTIFICATION_EPSIZE,
|
|
|
- .Banks = 1,
|
|
|
- },
|
|
|
- },
|
|
|
- };
|
|
|
-
|
|
|
-
|
|
|
/*
|
|
|
- * Transmit an FM-modulated signal using the USART in SPI master mode.
|
|
|
- * As per ABC standard, this signal is transmitted LSB first.
|
|
|
+ * Input and output ring buffers
|
|
|
*/
|
|
|
-
|
|
|
-#include "usbcas.h"
|
|
|
+static RingBuffer_t u2c_ringbuf; /* USB -> CDC */
|
|
|
+static uint8_t u2c_buffer[128];
|
|
|
+static RingBuffer_t c2u_ringbuf; /* CDC -> USB */
|
|
|
+static uint8_t c2u_buffer[128];
|
|
|
+
|
|
|
+/**
|
|
|
+ * LUFA CDC Class driver interface configuration and state
|
|
|
+ * information. This structure is passed to all CDC Class driver
|
|
|
+ * functions, so that multiple instances of the same class within a
|
|
|
+ * device can be differentiated from one another.
|
|
|
+ *
|
|
|
+ * "vcpif" stands for "Virtual COM Port Interface".
|
|
|
+ */
|
|
|
+USB_ClassInfo_CDC_Device_t vcpif = {
|
|
|
+ .Config = {
|
|
|
+ .ControlInterfaceNumber = INTERFACE_ID_CDC_CCI,
|
|
|
+ .DataINEndpoint = {
|
|
|
+ .Address = CDC_TX_EPADDR,
|
|
|
+ .Size = CDC_TXRX_EPSIZE,
|
|
|
+ .Banks = 1,
|
|
|
+ },
|
|
|
+ .DataOUTEndpoint = {
|
|
|
+ .Address = CDC_RX_EPADDR,
|
|
|
+ .Size = CDC_TXRX_EPSIZE,
|
|
|
+ .Banks = 1,
|
|
|
+ },
|
|
|
+ .NotificationEndpoint =
|
|
|
+ {
|
|
|
+ .Address = CDC_NOTIFICATION_EPADDR,
|
|
|
+ .Size = CDC_NOTIFICATION_EPSIZE,
|
|
|
+ .Banks = 1,
|
|
|
+ },
|
|
|
+ },
|
|
|
+};
|
|
|
|
|
|
/*
|
|
|
* Probe for modem control status bits and send them if changed,
|
|
|
- * or if forced...
|
|
|
+ * or if forced. DSR is always set, DCD indicates if the cassette
|
|
|
+ * motor relay is active.
|
|
|
*/
|
|
|
-static void update_modem_status(USB_ClassInfo_CDC_Device_t* const CDCInterfaceInfo, bool forced)
|
|
|
+static void update_modem_status(USB_ClassInfo_CDC_Device_t * const cii,
|
|
|
+ bool forced)
|
|
|
{
|
|
|
uint16_t lines = CDC_CONTROL_LINE_IN_DSR;
|
|
|
|
|
@@ -104,74 +96,109 @@ static void update_modem_status(USB_ClassInfo_CDC_Device_t* const CDCInterfaceIn
|
|
|
PORTB |= (1U << 0);
|
|
|
#endif
|
|
|
|
|
|
- if (forced || CDCInterfaceInfo->State.ControlLineStates.DeviceToHost != lines) {
|
|
|
- CDCInterfaceInfo->State.ControlLineStates.DeviceToHost = lines;
|
|
|
- CDC_Device_SendControlLineStateChange(CDCInterfaceInfo);
|
|
|
+ if (forced || cii->State.ControlLineStates.DeviceToHost != lines) {
|
|
|
+ cii->State.ControlLineStates.DeviceToHost = lines;
|
|
|
+ CDC_Device_SendControlLineStateChange(cii);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-/* This is called in the main loop, but also any time we are waiting on something... */
|
|
|
+/*
|
|
|
+ * This is called in the main loop, but also any time we are busy-waiting
|
|
|
+ * on something.
|
|
|
+ */
|
|
|
void do_usb_task(void)
|
|
|
{
|
|
|
/* Update modem flags if needed */
|
|
|
- update_modem_status(&VirtualSerial_CDC_Interface, false);
|
|
|
+ update_modem_status(&vcpif, false);
|
|
|
|
|
|
- CDC_Device_USBTask(&VirtualSerial_CDC_Interface);
|
|
|
+ CDC_Device_USBTask(&vcpif);
|
|
|
USB_USBTask();
|
|
|
}
|
|
|
|
|
|
-/** Main program entry point. This routine contains the overall program flow, including initial
|
|
|
- * setup of all components and the main program loop.
|
|
|
+/*
|
|
|
+ * Try to send the next byte of data to the host, abort without
|
|
|
+ * dequeuing if there is an error. Returns true on error.
|
|
|
+ */
|
|
|
+static bool usb_send_next_byte(void)
|
|
|
+{
|
|
|
+ uint8_t byte = RingBuffer_Peek(&c2u_ringbuf);
|
|
|
+
|
|
|
+ if (CDC_Device_SendByte(&vcpif, byte) != ENDPOINT_READYWAIT_NoError)
|
|
|
+ return true;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Dequeue the already sent byte from the buffer now we have
|
|
|
+ * confirmed that no transmission error occurred
|
|
|
+ */
|
|
|
+ RingBuffer_Remove(&c2u_ringbuf);
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Main program entry point. This routine contains the overall program
|
|
|
+ * flow, including initial setup of
|
|
|
+ * all components and the main program
|
|
|
+ * loop.
|
|
|
*/
|
|
|
int main(void)
|
|
|
{
|
|
|
SetupHardware();
|
|
|
|
|
|
- RingBuffer_InitBuffer(&USBtoCAS_Buffer, USBtoCAS_Buffer_Data, sizeof(USBtoCAS_Buffer_Data));
|
|
|
- RingBuffer_InitBuffer(&CAStoUSB_Buffer, CAStoUSB_Buffer_Data, sizeof(CAStoUSB_Buffer_Data));
|
|
|
+ RingBuffer_InitBuffer(&u2c_ringbuf, u2c_buffer, sizeof(u2c_buffer));
|
|
|
+ RingBuffer_InitBuffer(&c2u_ringbuf, c2u_buffer, sizeof(c2u_buffer));
|
|
|
|
|
|
GlobalInterruptEnable();
|
|
|
|
|
|
for (;;)
|
|
|
{
|
|
|
- /* Only try to read in bytes from the CDC interface if the transmit buffer is not full */
|
|
|
- if (!(RingBuffer_IsFull(&USBtoCAS_Buffer)))
|
|
|
- {
|
|
|
- int16_t ReceivedByte = CDC_Device_ReceiveByte(&VirtualSerial_CDC_Interface);
|
|
|
-
|
|
|
- /* Store received byte into the USART transmit buffer */
|
|
|
- if (!(ReceivedByte < 0)) {
|
|
|
- RingBuffer_Insert(&USBtoCAS_Buffer, ReceivedByte);
|
|
|
- fmtx_enable(); /* Enable TX interrupt routine if disabled */
|
|
|
+ /*
|
|
|
+ * Only try to read in bytes from the CDC interface if
|
|
|
+ * the transmit buffer is not full
|
|
|
+ */
|
|
|
+ if (!(RingBuffer_IsFull(&u2c_ringbuf))) {
|
|
|
+ int byte;
|
|
|
+
|
|
|
+ byte = CDC_Device_ReceiveByte(&vcpif);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Store received byte into the transmit buffer, then
|
|
|
+ * enable the transmit ISR if disabled
|
|
|
+ */
|
|
|
+ if (byte >= 0) {
|
|
|
+ RingBuffer_Insert(&u2c_ringbuf, byte);
|
|
|
+ fmtx_enable();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- uint16_t BufferCount = RingBuffer_GetCount(&CAStoUSB_Buffer);
|
|
|
- if (BufferCount)
|
|
|
- {
|
|
|
- Endpoint_SelectEndpoint(VirtualSerial_CDC_Interface.Config.DataINEndpoint.Address);
|
|
|
-
|
|
|
- /* Check if a packet is already enqueued to the host - if so, we shouldn't try to send more data
|
|
|
- * until it completes as there is a chance nothing is listening and a lengthy timeout could occur */
|
|
|
- if (Endpoint_IsINReady())
|
|
|
- {
|
|
|
- /* Never send more than one bank size less one byte to the host at a time, so that we don't block
|
|
|
- * while a Zero Length Packet (ZLP) to terminate the transfer is sent if the host isn't listening */
|
|
|
- uint8_t BytesToSend = MIN(BufferCount, (CDC_TXRX_EPSIZE - 1));
|
|
|
-
|
|
|
- /* Read bytes from the USART receive buffer into the USB IN endpoint */
|
|
|
- while (BytesToSend--)
|
|
|
- {
|
|
|
- /* Try to send the next byte of data to the host, abort if there is an error without dequeuing */
|
|
|
- if (CDC_Device_SendByte(&VirtualSerial_CDC_Interface,
|
|
|
- RingBuffer_Peek(&CAStoUSB_Buffer)) != ENDPOINT_READYWAIT_NoError)
|
|
|
- {
|
|
|
+ uint16_t outbytes = RingBuffer_GetCount(&c2u_ringbuf);
|
|
|
+ if (outbytes) {
|
|
|
+ Endpoint_SelectEndpoint(vcpif.Config.DataINEndpoint.Address);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Check if a packet is already enqueued to
|
|
|
+ * the host - if so, we shouldn't try to send
|
|
|
+ * more data until it completes as there is a
|
|
|
+ * chance nothing is listening and a lengthy
|
|
|
+ * timeout could occur
|
|
|
+ */
|
|
|
+ if (Endpoint_IsINReady()) {
|
|
|
+ /*
|
|
|
+ * Never send more than one bank size
|
|
|
+ * less one byte to the host at a
|
|
|
+ * time, so that we don't block while
|
|
|
+ * a Zero Length Packet (ZLP) to
|
|
|
+ * terminate the transfer is sent if
|
|
|
+ * the host isn't listening */
|
|
|
+ outbytes = MIN(outbytes, (CDC_TXRX_EPSIZE - 1));
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Transfer output bytes to the USB
|
|
|
+ * endpoint. Abort without dequeuing if
|
|
|
+ * there is an error.
|
|
|
+ */
|
|
|
+ while (outbytes--)
|
|
|
+ if (usb_send_next_byte())
|
|
|
break;
|
|
|
- }
|
|
|
-
|
|
|
- /* Dequeue the already sent byte from the buffer now we have confirmed that no transmission error occurred */
|
|
|
- RingBuffer_Remove(&CAStoUSB_Buffer);
|
|
|
- }
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -222,15 +249,15 @@ void EVENT_USB_Device_ConfigurationChanged(void)
|
|
|
{
|
|
|
bool success = true;
|
|
|
|
|
|
- success &= CDC_Device_ConfigureEndpoints(&VirtualSerial_CDC_Interface);
|
|
|
+ success &= CDC_Device_ConfigureEndpoints(&vcpif);
|
|
|
if (success)
|
|
|
- update_modem_status(&VirtualSerial_CDC_Interface, true);
|
|
|
+ update_modem_status(&vcpif, true);
|
|
|
}
|
|
|
|
|
|
/** Event handler for the library USB Control Request reception event. */
|
|
|
void EVENT_USB_Device_ControlRequest(void)
|
|
|
{
|
|
|
- CDC_Device_ProcessControlRequest(&VirtualSerial_CDC_Interface);
|
|
|
+ CDC_Device_ProcessControlRequest(&vcpif);
|
|
|
}
|
|
|
|
|
|
/*
|
|
@@ -239,8 +266,8 @@ void EVENT_USB_Device_ControlRequest(void)
|
|
|
void fmrx_recv_byte(uint8_t byte)
|
|
|
{
|
|
|
if (USB_DeviceState == DEVICE_STATE_Configured &&
|
|
|
- !RingBuffer_IsFull(&CAStoUSB_Buffer))
|
|
|
- RingBuffer_Insert(&CAStoUSB_Buffer, byte);
|
|
|
+ !RingBuffer_IsFull(&c2u_ringbuf))
|
|
|
+ RingBuffer_Insert(&c2u_ringbuf, byte);
|
|
|
}
|
|
|
|
|
|
/*
|
|
@@ -250,20 +277,20 @@ void fmrx_recv_byte(uint8_t byte)
|
|
|
int fmtx_next_byte(void)
|
|
|
{
|
|
|
if (USB_DeviceState != DEVICE_STATE_Configured ||
|
|
|
- RingBuffer_IsEmpty(&USBtoCAS_Buffer))
|
|
|
+ RingBuffer_IsEmpty(&u2c_ringbuf))
|
|
|
return -1;
|
|
|
|
|
|
- return RingBuffer_Remove(&USBtoCAS_Buffer);
|
|
|
+ return RingBuffer_Remove(&u2c_ringbuf);
|
|
|
}
|
|
|
|
|
|
-/** Event handler for the CDC Class driver Line Encoding Changed event.
|
|
|
- *
|
|
|
- * \param[in] CDCInterfaceInfo Pointer to the CDC class interface configuration structure being referenced
|
|
|
+/*
|
|
|
+ * Event handler for the CDC Class driver Line Encoding Changed event.
|
|
|
*/
|
|
|
-void EVENT_CDC_Device_LineEncodingChanged(USB_ClassInfo_CDC_Device_t* const CDCInterfaceInfo)
|
|
|
+void EVENT_CDC_Device_LineEncodingChanged(USB_ClassInfo_CDC_Device_t* const cii)
|
|
|
{
|
|
|
- uint32_t baudrate = CDCInterfaceInfo->State.LineEncoding.BaudRateBPS;
|
|
|
+ uint32_t baudrate = cii->State.LineEncoding.BaudRateBPS;
|
|
|
|
|
|
+ /* This is a hack to give a sensible default */
|
|
|
if (baudrate == 9600)
|
|
|
baudrate = CAS_BAUDRATE_ABC80;
|
|
|
|
|
@@ -278,15 +305,8 @@ void EVENT_CDC_Device_LineEncodingChanged(USB_ClassInfo_CDC_Device_t* const CDCI
|
|
|
}
|
|
|
|
|
|
/*
|
|
|
- * Control lines changed
|
|
|
+ * Event handler for control line changes
|
|
|
*/
|
|
|
-void EVENT_CDC_Device_ControLineStateChanged(USB_ClassInfo_CDC_Device_t* const CDCInterfaceInfo)
|
|
|
+void EVENT_CDC_Device_ControLineStateChanged(USB_ClassInfo_CDC_Device_t* const cii)
|
|
|
{
|
|
|
- /* Indicate the DTR line via RXLED for now */
|
|
|
-#if 0
|
|
|
- if (CDCInterfaceInfo->State.ControlLineStates.HostToDevice & CDC_CONTROL_LINE_OUT_DTR)
|
|
|
- PORTB &= ~(1U << 0);
|
|
|
- else
|
|
|
- PORTB |= (1U << 0);
|
|
|
-#endif
|
|
|
}
|