123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453 |
- /*
- * 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.
- dean [at] fourwalledcubicle [dot] com
- www.lufa-lib.org
- */
- /*
- Copyright 2021 Dean Camera (dean [at] fourwalledcubicle [dot] com)
- Permission to use, copy, modify, distribute, and sell this
- software and its documentation for any purpose is hereby granted
- without fee, provided that the above copyright notice appear in
- all copies and that both that the copyright notice and this
- permission notice and warranty disclaimer appear in supporting
- documentation, and that the name of the author not be used in
- advertising or publicity pertaining to distribution of the
- software without specific, written prior permission.
- The author disclaims all warranties with regard to this
- software, including all implied warranties of merchantability
- and fitness. In no event shall the author be liable for any
- special, indirect or consequential damages or any damages
- whatsoever resulting from loss of use, data or profits, whether
- in an action of contract, negligence or other tortious action,
- arising out of or in connection with the use or performance of
- this software.
- */
- #include "usbcas.h"
- /*
- * 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];
- /**
- * 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,
- },
- },
- };
- /*
- * Motor relay sense. The motor relay is connected to PD1/INT1#, but
- * the interrupt isn't being used for that purpose. Rather, it is used
- * to latch an upward flank as on ABC800 this will have the output data
- * fed back to it. Thus, we can check if *either* it is grounded *or*
- * there has been a flank.
- *
- * Set up timer 3 to wrap around at 100 Hz, use this to poll and
- * debounce/deglitch the relay, and to delay the sensing of the
- * motor relay switch with ~160 ms.
- */
- static void motor_sense_init(void)
- {
- /*
- * Set up INT1# to latch a rising edge, but mask the resulting
- * interrupt request.
- */
- EIMSK = 0; /* all external interrupts masked */
- EICRA = 3 << ISC10; /* rising edge */
- EIFR = -1; /* clear all external interrupt flags */
- /*
- * Set up timer 3 to count at fosc/8 in CTC mode,
- * with wraparound at 20000 = 100 Hz.
- */
- TCCR3B = 0; /* Disable timer */
- TCCR3A = 0; /* No capture, no waveforms */
- ICR3 = (F_CPU/(8*100)) - 1; /* Top of counter */
- TCNT3 = 0;
- TIMSK3 = TIFR3 = (1 << ICF3); /* Interrupt on ICR3 match */
- TCCR3B = (3 << WGM32)|(2 << CS30); /* fosc/8, CTC mode, TOP=ICR3 */
- }
- bool _motor_relay;
- static inline ATTR_ALWAYS_INLINE void motor_led_on(void)
- {
- //PORTB &= ~(1 << 0);
- }
- static inline ATTR_ALWAYS_INLINE void motor_led_off(void)
- {
- //PORTB |= (1 << 0);
- }
- ISR(TIMER3_CAPT_vect)
- {
- static uint8_t relay_ctr;
- if (!(PIND & 1) || (EIFR & 2)) {
- /* Relay active */
- EIFR = 2; /* Clear edge detect */
- if (relay_ctr < 16) {
- relay_ctr++;
- } else if (!_motor_relay) {
- _motor_relay = true;
- motor_led_on();
- fmtx_enable();
- }
- } else {
- /* Relay not active */
- if (relay_ctr) {
- relay_ctr--;
- } else {
- _motor_relay = false;
- motor_led_off();
- }
- }
- }
- /*
- * Probe for modem control status bits and send them if changed,
- * 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 cii,
- bool forced)
- {
- uint16_t lines = 0;
- if (motor_relay()) /* Cassette relay active */
- lines |= CDC_CONTROL_LINE_IN_DCD | CDC_CONTROL_LINE_IN_DSR;
- 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 busy-waiting
- * on something.
- */
- static void usb_run_stack(void)
- {
- /* Update modem flags if needed */
- update_modem_status(&vcpif, false);
- CDC_Device_USBTask(&vcpif);
- USB_USBTask();
- }
- /*
- * 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;
- }
- static void usb_recv_data(void)
- {
- int byte;
- uint8_t nbytes = CDC_TXRX_EPSIZE - 1; /* Max bytes to receive */
- while (nbytes--) {
- /*
- * Only try to read in bytes from the CDC interface if
- * the transmit buffer is not full
- */
- if (RingBuffer_IsFull(&u2c_ringbuf))
- 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();
- }
- }
- static void usb_send_data(void)
- {
- uint16_t outbytes = RingBuffer_GetCount(&c2u_ringbuf);
- if (!outbytes)
- return;
- 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())
- return;
- /*
- * 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;
- }
- void do_usb_tasks(void)
- {
- usb_recv_data();
- usb_send_data();
- usb_run_stack();
- }
- /*
- * 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(&u2c_ringbuf, u2c_buffer, sizeof(u2c_buffer));
- RingBuffer_InitBuffer(&c2u_ringbuf, c2u_buffer, sizeof(c2u_buffer));
- GlobalInterruptEnable();
- while (1) {
- do_usb_tasks();
- }
- }
- static uint32_t current_baudrate;
- /** Configures the board hardware and chip peripherals for the demo's functionality. */
- void SetupHardware(void)
- {
- /* Disable watchdog if enabled by bootloader/fuses */
- MCUSR &= ~(1 << WDRF);
- wdt_disable();
- /* Disable clock division */
- clock_prescale_set(clock_div_1);
- /*
- * Configure ports.
- * NC: not connected, but have pads on board. Input/pullup.
- * NT: not terminated on board. Output/GND.
- */
- /*
- * PORT B:
- * PB0 - RXLED#
- * PB1 - NC
- * PB2 - NC
- * PB3 - NC
- * PB4 - NC
- * PB5 - NC
- * PB6 - NC
- * PB7 - NT
- */
- DDRB = 0x81;
- PORTB = 0x7f;
- /*
- * PORT C:
- * PC6 - DIN pin 3 (ABC800 data out)
- * PC7 - NP
- */
- DDRC = 0x80;
- PORTC = 0x40;
- /*
- * PORT D:
- * PD0 - motor sense unused (GND)
- * PD1 - motor sense#
- * PD2 - NC
- * PD3 - data out
- * PD4 - data in (main)
- * PD5 - TXLED#
- * PD6 - NT
- * PD7 - NC
- */
- DDRD = 0x68;
- PORTD = 0x93;
- /*
- * PORT E:
- * PE6 - NC
- * PE2 - hardware bootloader, tied to GND on board
- */
- DDRE = 0x00;
- PORTE = 0x40;
- /*
- * PORT F:
- * PF0 - NT
- * PF1 - NT
- * PF4 - NC
- * PF5 - NC
- * PF6 - NC
- * PF7 - DIN pin 4 (ABC800 motor sense#)
- */
- DDRF = 0x03;
- PORTF = 0xf0;
- /* Hardware Initialization */
- USB_Init();
- /* Initialize motor sense setup */
- motor_sense_init();
- /* Initialize receiver and transmitter */
- fmrx_init();
- current_baudrate = fmtx_real_baudrate(CAS_BAUDRATE_ABC80);
- fmrx_set_speed(current_baudrate);
- fmtx_init_speed(current_baudrate);
- }
- /** Event handler for the library USB Connection event. */
- void EVENT_USB_Device_Connect(void)
- {
- }
- /** Event handler for the library USB Disconnection event. */
- void EVENT_USB_Device_Disconnect(void)
- {
- }
- /** Event handler for the library USB Configuration Changed event. */
- void EVENT_USB_Device_ConfigurationChanged(void)
- {
- bool success = true;
- success &= CDC_Device_ConfigureEndpoints(&vcpif);
- if (success)
- update_modem_status(&vcpif, true);
- }
- /** Event handler for the library USB Control Request reception event. */
- void EVENT_USB_Device_ControlRequest(void)
- {
- CDC_Device_ProcessControlRequest(&vcpif);
- }
- /*
- * Called from the demodulation ISR to push a new byte into the buffer
- */
- void fmrx_recv_byte(uint8_t byte)
- {
- if (USB_DeviceState == DEVICE_STATE_Configured &&
- !RingBuffer_IsFull(&c2u_ringbuf))
- 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);
- }
- }
- /*
- * Event handler for control line changes
- */
- void EVENT_CDC_Device_ControLineStateChanged(USB_ClassInfo_CDC_Device_t* const cii)
- {
- /* Currently not used */
- }
|