Files
micropendous/Micropendous/Firmware/USBtoSerial/USBtoSerial.c

568 lines
19 KiB
C

/*
LUFA Library
Copyright (C) Dean Camera, 2011.
dean [at] fourwalledcubicle [dot] com
www.lufa-lib.org
*/
/*
Copyright 2011 Dean Camera (dean [at] fourwalledcubicle [dot] com)
Modified for flow control capability by Opendous Inc. 2011-11-14
www.Micropendous.org/USBtoSerial
http://www.beyondlogic.org/serial/serial.htm is a useful introduction to serial/RS-232
Careful consideration was taken to make sure that all signals
would work with both the Micropendous and Arduino Leonardo boards.
Micropendous:
Arduino Pin# - AT90USB1287
0 - PD2 - RXD - RX : IN : Data Input
1 - PD3 - TXD - TX : OUT : Data Output
2 - PD4 - ICP1
3 - PD5 -
4 - PD1 - Int/SDA
5 - PD0 - Int/SCL
6 - PD6 -
7 - PD7 -
Arduino Leonardo:
Arduino Pin# - ATmega32U4
0 - PD2 - RXD - RX : IN : Data Input
1 - PD3 - TXD - TX : OUT : Data Output
2 - PD1 - Int/SDA
3 - PD0 - Int/SCL
4 - PD4 - ICP1
5 - PC6 -
6 - PD7 -
7 - PE6 - Int
Arduino Pin# - v.24 Signal - Micropendous Pin - Leonardo Pin
0 - RXD (IN) - PD2 - PD2
1 - TXD (OUT) - PD3 - PD3
2 - nCTS (IN) - PD4(ICP1) - PD1
3 - nDSR (IN) - PD5 - PD0
4 - nRTS (OUT) - PD1 - PD4
5 - nDTR (OUT) - PD0 - PC6
6 - nDCD (IN) - PD6 - PD7
7 - nRI (IN) - PD7 - PE6
Notes:
nRTS - OUT - Request to Send
nDTR - OUT - Data Terminal Ready
nCTS - IN - Clear To Send Control
nDSR - IN - Data Set Ready Control
nDCD - IN - Data Carrier Detect Control
nRI - IN - Ring Indicator
For LoopBack or DTE to DTE testing with flow control you will
need to create a Null Modem cable:
GND1=GND2
RXD1=TXD2
RXD1=TXD2
RTS1=CTS1 , RTS2=CTS2
DTR1=DSR1=DCD1, DTR2=DSR2=DCD2
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 disclaim 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.
*/
// for RS232, a control lone is 'Set' if it is Low='0' and 'Clear' if it is High='1'
#define IS_LINE_SET(PORT_IN, PIN_NUM) (~PORT_IN & (1 << PIN_NUM))
#define SET_LINE(PORT, PIN_NUM) PINLOW(PORT, PIN_NUM);
#define CLEAR_LINE(PORT, PIN_NUM) PINHIGH(PORT, PIN_NUM);
#if (BOARD == BOARD_ARDUINO_LEONARDO)
#define PIN_CTS PIND
#define PORT_CTS PORTD
#define DDR_CTS DDRD
#define Pn_CTS PD1
#define PIN_DSR PIND
#define PORT_DSR PORTD
#define DDR_DSR DDRD
#define Pn_DSR PD0
#define PIN_RTS PIND
#define PORT_RTS PORTD
#define DDR_RTS DDRD
#define Pn_RTS PD4
#define PIN_DTR PINC
#define PORT_DTR PORTC
#define DDR_DTR DDRC
#define Pn_DTR PC6
#define PIN_DCD PIND
#define PORT_DCD PORTD
#define DDR_DCD DDRD
#define Pn_DCD PD7
#define PIN_RI PINE
#define PORT_RI PORTE
#define DDR_RI DDRE
#define Pn_RI PE6
#else
#define PIN_CTS PIND
#define PORT_CTS PORTD
#define DDR_CTS DDRD
#define Pn_CTS PD4
#define PIN_DSR PIND
#define PORT_DSR PORTD
#define DDR_DSR DDRD
#define Pn_DSR PD5
#define PIN_RTS PIND
#define PORT_RTS PORTD
#define DDR_RTS DDRD
#define Pn_RTS PD1
#define PIN_DTR PIND
#define PORT_DTR PORTD
#define DDR_DTR DDRD
#define Pn_DTR PD0
#define PIN_DCD PIND
#define PORT_DCD PORTD
#define DDR_DCD DDRD
#define Pn_DCD PD6
#define PIN_RI PIND
#define PORT_RI PORTD
#define DDR_RI DDRD
#define Pn_RI PD7
#endif
/** \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 "USBtoSerial.h"
/** Circular buffer to hold data from the host before it is sent to the device via the serial port. */
static RingBuffer_t USBtoUSART_Buffer;
/** Underlying data buffer for \ref USBtoUSART_Buffer, where the stored bytes are located. */
static uint8_t USBtoUSART_Buffer_Data[128];
/** Circular buffer to hold data from the serial port before it is sent to the host. */
static RingBuffer_t USARTtoUSB_Buffer;
/** Underlying data buffer for \ref USARTtoUSB_Buffer, where the stored bytes are located. */
static uint8_t USARTtoUSB_Buffer_Data[128];
/** UART error flags */
volatile uint8_t __UARTLineStatesAndFlags = 0;
volatile uint8_t __UARTUseRTSCTSFlowControl = 0;
/** 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 = 0,
.DataINEndpointNumber = CDC_TX_EPNUM,
.DataINEndpointSize = CDC_TXRX_EPSIZE,
.DataINEndpointDoubleBank = false,
.DataOUTEndpointNumber = CDC_RX_EPNUM,
.DataOUTEndpointSize = CDC_TXRX_EPSIZE,
.DataOUTEndpointDoubleBank = false,
.NotificationEndpointNumber = CDC_NOTIFICATION_EPNUM,
.NotificationEndpointSize = CDC_NOTIFICATION_EPSIZE,
.NotificationEndpointDoubleBank = 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(&USBtoUSART_Buffer, USBtoUSART_Buffer_Data, sizeof(USBtoUSART_Buffer_Data));
RingBuffer_InitBuffer(&USARTtoUSB_Buffer, USARTtoUSB_Buffer_Data, sizeof(USARTtoUSB_Buffer_Data));
LEDs_SetAllLEDs(LEDMASK_USB_NOTREADY);
sei();
for (;;)
{
// Only try to read in bytes from the CDC interface if the transmit buffer is not full
if (!(RingBuffer_IsFull(&USBtoUSART_Buffer)))
{
int16_t ReceivedByte = CDC_Device_ReceiveByte(&VirtualSerial_CDC_Interface);
// Read bytes from the USB OUT endpoint into the USART transmit buffer
if (!(ReceivedByte < 0)) {
RingBuffer_Insert(&USBtoUSART_Buffer, ReceivedByte);
}
}
// Check if the UART receive buffer flush timer has expired or the buffer is nearly full
uint16_t BufferCount = RingBuffer_GetCount(&USARTtoUSB_Buffer);
if ((TIFR0 & (1 << TOV0)) || (BufferCount > ((uint8_t)((sizeof(USARTtoUSB_Buffer_Data)) *.75))))
{
// Clear flush timer expiry flag
TIFR0 |= (1 << TOV0);
// Read bytes from the USART receive buffer into the USB IN endpoint
while (BufferCount--)
{
// 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(&USARTtoUSB_Buffer)) != ENDPOINT_READYWAIT_NoError)
{
break;
}
// Dequeue the already sent byte from the buffer now we have confirmed that no transmission error occurred
RingBuffer_Remove(&USARTtoUSB_Buffer);
}
}
// Load the next byte from the USART transmit buffer into the USART
if (!(RingBuffer_IsEmpty(&USBtoUSART_Buffer))) {
// if RTS/CTS flow control is enabled then only copy to USART if nCTS is 'Set'
if (__UARTUseRTSCTSFlowControl && IS_LINE_SET(PIN_CTS, Pn_CTS)) {
Serial_SendByte(RingBuffer_Remove(&USBtoUSART_Buffer));
} else if (!__UARTUseRTSCTSFlowControl) {
Serial_SendByte(RingBuffer_Remove(&USBtoUSART_Buffer));
} else {
// sending over USART is not valid
}
}
// send any Line state changes or error flags
LineStates_ErrorFlags();
CDC_Device_USBTask(&VirtualSerial_CDC_Interface);
USB_USBTask();
}
}
/** 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);
/* Hardware Initialization */
JTAG_DISABLE();
/* enable Ports based on which IC is being used */
/* For more information look over the corresponding AVR's datasheet in the
'I/O Ports' Chapter under subheading 'Ports as General Digital I/O' */
#if (defined(__AVR_AT90USB162__) || defined(__AVR_AT90USB82__) || \
defined(__AVR_ATmega16U2__) || defined(__AVR_ATmega32U2__))
DDRD = 0;
PORTD = 0;
DDRB = 0;
PORTB = 0;
DDRC |= ((0 << PC2) | (0 << PC4) | (0 << PC5) | (0 << PC6) | (0 << PC7)); //only PC2,4,5,6,7 are pins
PORTC |= ((0 << PC2) | (0 << PC4) | (0 << PC5) | (0 << PC6) | (0 << PC7)); //only PC2,4,5,6,7 are pins
// be careful using PortC as PC0 is used for the Crystal and PC1 is nRESET
#endif
#if (defined(__AVR_ATmega16U4__) || defined(__AVR_ATmega32U4__))
// set up Ports for v.24 UART while maintaining compatibility with Arduino Leonardo pinout
DDRD = 0;
PORTD = 0;
DDRB = 0;
PORTB = 0;
DDRC = ((0 << PC6) | (0 << PC7)); //only PC6,7 are pins
PORTC = ((0 << PC6) | (0 << PC7)); //only PC6,7 are pins
DDRE = ((0 << PE2) | (0 << PE6)); //only PE2,6 are pins
PORTE = ((0 << PE2) | (0 << PE6)); //only PE2,6 are pins
DDRF = ((0 << PF0) | (0 << PF1) | (0 << PF4) | (0 << PF5) | (0 << PF6) | (0 << PF7)); // only PF0,1,4,5,6,7 are pins
PORTF = ((0 << PF0) | (0 << PF1) | (0 << PF4) | (0 << PF5) | (0 << PF6) | (0 << PF7)); // only PF0,1,4,5,6,7 are pins
#endif
#if (defined(__AVR_AT90USB1287__) || defined(__AVR_AT90USB647__) || \
defined(__AVR_AT90USB1286__) || defined(__AVR_AT90USB646__) || \
defined(__AVR_ATmega32U6__))
DDRA = 0;
PORTA = 0;
DDRB = 0;
PORTB = 0;
DDRC = 0;
PORTC = 0;
DDRD = 0;
PORTD = 0;
DDRE = 0;
PORTE = 0;
DDRF = 0;
PORTF = 0;
#endif
// Input pins have DDR=0 and PORT=0 while output pins are DDR=1
// nCTS, nDSR, nDCD, nRI are inputs so don't need to change anything
// nRTS, nDTR are outputs with High Logic Levels to start:
DDR_RTS |= (1 << Pn_RTS); SET_LINE(PORT_RTS, Pn_RTS);
DDR_DTR |= (1 << Pn_DTR); SET_LINE(PORT_DTR, Pn_DTR);
Board_Init(); // initialize LEDs and Buttons and any other peripherals
DISABLE_VOLTAGE_TXRX;
DISABLE_EXT_SRAM;
SELECT_USB_B;
USB_Init();
/* Start the flush timer so that overflows occur rapidly to push received bytes to the USB interface */
TCCR0B = (1 << CS02); // prescale core clock by /256 for timer clock
}
/** Event handler for the library USB Connection event. */
void EVENT_USB_Device_Connect(void)
{
LEDs_SetAllLEDs(LEDMASK_USB_ENUMERATING);
}
/** Event handler for the library USB Disconnection event. */
void EVENT_USB_Device_Disconnect(void)
{
LEDs_SetAllLEDs(LEDMASK_USB_NOTREADY);
}
/** Event handler for the library USB Configuration Changed event. */
void EVENT_USB_Device_ConfigurationChanged(void)
{
bool ConfigSuccess = true;
ConfigSuccess &= CDC_Device_ConfigureEndpoints(&VirtualSerial_CDC_Interface);
LEDs_SetAllLEDs(ConfigSuccess ? LEDMASK_USB_READY : LEDMASK_USB_ERROR);
}
/** Event handler for the library USB Control Request reception event. */
void EVENT_USB_Device_ControlRequest(void)
{
CDC_Device_ProcessControlRequest(&VirtualSerial_CDC_Interface);
}
/** ISR to manage the reception of data from the serial port, placing received bytes into a circular buffer
* for later transmission to the host.
*/
ISR(USART1_RX_vect, ISR_BLOCK)
{
__UARTLineStatesAndFlags = UCSR1A; // must be read before UDRn
// for 9-bit bata, the RXB81 bit should be read from UCSR1B BEFORE reading UDR1
uint8_t ReceivedByte = UDR1;
if (USB_DeviceState == DEVICE_STATE_Configured)
RingBuffer_Insert(&USARTtoUSB_Buffer, ReceivedByte);
}
/** CDC class driver event for a control line state change on a CDC interface. This event fires each time the host requests a
* control line state change (containing the virtual serial control line states, such as DTR). The new control line states
* are available in the ControlLineStates.HostToDevice value inside the CDC interface structure passed as a parameter, set as
* a mask of CDC_CONTROL_LINE_OUT_* masks. 1 is for 'Set'(Low) and 0 is for 'Clear'(High) as these are active low signals.
* \param[in,out] CDCInterfaceInfo Pointer to a structure containing a CDC Class configuration and state
*/
void EVENT_CDC_Device_ControLineStateChanged(USB_ClassInfo_CDC_Device_t* const CDCInterfaceInfo)
{
if ((CDCInterfaceInfo->State.ControlLineStates.HostToDevice) & CDC_CONTROL_LINE_OUT_RTS) {
// Host has set the RTS line
SET_LINE(PORT_RTS, Pn_RTS);
__UARTUseRTSCTSFlowControl = 1; // RTS-CTS flow control must be enabled if this flag is set
} else {
// Host has cleared the RTS line
CLEAR_LINE(PORT_RTS, Pn_RTS);
}
if ((CDCInterfaceInfo->State.ControlLineStates.HostToDevice) & CDC_CONTROL_LINE_OUT_DTR) {
// Host has set the DTR line
SET_LINE(PORT_DTR, Pn_DTR);
__UARTUseRTSCTSFlowControl = 1; // RTS-CTS flow control must be enabled if this flag is set
} else {
CLEAR_LINE(PORT_DTR, Pn_DTR);
}
}
/** Event handler for the CDC Class driver Line Encoding Changed event.
*
* \param[in] CDCInterfaceInfo Pointer to the CDC class interface configuration structure being referenced
*/
void EVENT_CDC_Device_LineEncodingChanged(USB_ClassInfo_CDC_Device_t* const CDCInterfaceInfo)
{
uint8_t ConfigMask = 0;
// the Line Encoding will change each time a new connection is intitiated with
// the device so this is a good place to intialize standard settings
SET_LINE(PORT_RTS, Pn_RTS);
SET_LINE(PORT_DTR, Pn_DTR);
switch (CDCInterfaceInfo->State.LineEncoding.ParityType)
{
case CDC_PARITY_Odd:
ConfigMask = ((1 << UPM11) | (1 << UPM10));
break;
case CDC_PARITY_Even:
ConfigMask = (1 << UPM11);
break;
}
if (CDCInterfaceInfo->State.LineEncoding.CharFormat == CDC_LINEENCODING_TwoStopBits) {
ConfigMask |= (1 << USBS1);
} // else USBS1=0 and 1 stop-bit is used
switch (CDCInterfaceInfo->State.LineEncoding.DataBits)
{
case 6:
ConfigMask |= (1 << UCSZ10);
break;
case 7:
ConfigMask |= (1 << UCSZ11);
break;
case 8:
ConfigMask |= ((1 << UCSZ11) | (1 << UCSZ10));
break;
}
/* Must turn off USART before reconfiguring it, otherwise incorrect operation may occur */
UCSR1B = 0;
UCSR1A = 0;
UCSR1C = 0;
/* Set the new baud rate before configuring the USART */
UBRR1 = SERIAL_2X_UBBRVAL(CDCInterfaceInfo->State.LineEncoding.BaudRateBPS);
/* Reconfigure the USART in double speed mode for a wider baud rate range at the expense of accuracy */
UCSR1C = ConfigMask;
UCSR1A = (1 << U2X1);
UCSR1B = ((1 << RXCIE1) | (1 << TXEN1) | (1 << RXEN1));
} // void EVENT_CDC_Device_LineEncodingChanged
/** CDC class driver event for a send break request sent to the device from the host. This is generally used to separate
* data or to indicate a special condition to the receiving device.
*
* \param[in,out] CDCInterfaceInfo Pointer to a structure containing a CDC Class configuration and state.
* \param[in] Duration Duration of the break that has been sent by the host, in milliseconds.
*/
void EVENT_CDC_Device_BreakSent(USB_ClassInfo_CDC_Device_t* const CDCInterfaceInfo, const uint8_t Duration)
{
uint8_t PrevUCSR1B = UCSR1B;
uint8_t PrevUCSR1A = UCSR1A;
uint8_t PrevUCSR1C = UCSR1C;
uint8_t PrevUBRR1 = UBRR1;
// the 'idle' state is Mark=High='1'. A break is a period of Space=Low='0' and is equivalent
// to sending 0x00 with no stop bits for the intended period of time. TXD=PD3
// disable USART to allow manual control of TXD
cli();
UCSR1B = 0;
UCSR1A = 0;
UCSR1C = 0;
PINLOW(PORTD, PD3);
sei();
_delay_ms(Duration);
// re-enable the USART as before
cli();
PINHIGH(PORTD, PD3);
UCSR1C = PrevUCSR1C;
UCSR1A = PrevUCSR1A;
UCSR1B = PrevUCSR1B;
UBRR1 = PrevUBRR1;
sei();
}
void LineStates_ErrorFlags(void)
{
uint16_t __PreviousControlLineState = (VirtualSerial_CDC_Interface.State.ControlLineStates.DeviceToHost);
// The nDCD line state has been set
if (IS_LINE_SET(PIN_DCD, Pn_DCD)) {
(VirtualSerial_CDC_Interface.State.ControlLineStates.DeviceToHost) |= CDC_CONTROL_LINE_IN_DCD;
} else {
(VirtualSerial_CDC_Interface.State.ControlLineStates.DeviceToHost) &= ~CDC_CONTROL_LINE_IN_DCD;
}
// The nDSR line state has been set
if (IS_LINE_SET(PIN_DSR, Pn_DSR)) {
(VirtualSerial_CDC_Interface.State.ControlLineStates.DeviceToHost) |= CDC_CONTROL_LINE_IN_DSR;
} else {
(VirtualSerial_CDC_Interface.State.ControlLineStates.DeviceToHost) &= ~CDC_CONTROL_LINE_IN_DSR;
}
// The nRI line state has been set
if (IS_LINE_SET(PIN_RI, Pn_RI)) {
(VirtualSerial_CDC_Interface.State.ControlLineStates.DeviceToHost) |= CDC_CONTROL_LINE_IN_RING;
} else {
(VirtualSerial_CDC_Interface.State.ControlLineStates.DeviceToHost) &= ~CDC_CONTROL_LINE_IN_RING;
}
// A framing error has occurred which also means that a BREAK has occured
if (__UARTLineStatesAndFlags & (1 << FE1)) {
(VirtualSerial_CDC_Interface.State.ControlLineStates.DeviceToHost) |= CDC_CONTROL_LINE_IN_FRAMEERROR;
(VirtualSerial_CDC_Interface.State.ControlLineStates.DeviceToHost) |= CDC_CONTROL_LINE_IN_BREAK;
} else {
(VirtualSerial_CDC_Interface.State.ControlLineStates.DeviceToHost) &= ~CDC_CONTROL_LINE_IN_FRAMEERROR;
(VirtualSerial_CDC_Interface.State.ControlLineStates.DeviceToHost) &= ~CDC_CONTROL_LINE_IN_BREAK;
}
// A parity error has occurred
if (__UARTLineStatesAndFlags & (1 << UPE1)) {
(VirtualSerial_CDC_Interface.State.ControlLineStates.DeviceToHost) |= CDC_CONTROL_LINE_IN_PARITYERROR;
} else {
(VirtualSerial_CDC_Interface.State.ControlLineStates.DeviceToHost) &= ~CDC_CONTROL_LINE_IN_PARITYERROR;
}
// A data overrun error has occurred
if (__UARTLineStatesAndFlags & (1 << DOR1)) {
(VirtualSerial_CDC_Interface.State.ControlLineStates.DeviceToHost) |= CDC_CONTROL_LINE_IN_OVERRUNERROR;
} else {
(VirtualSerial_CDC_Interface.State.ControlLineStates.DeviceToHost) &= ~CDC_CONTROL_LINE_IN_OVERRUNERROR;
}
// If something has changed, tell the host
if ((VirtualSerial_CDC_Interface.State.ControlLineStates.DeviceToHost) != __PreviousControlLineState) {
CDC_Device_SendControlLineStateChange(&VirtualSerial_CDC_Interface);
__UARTLineStatesAndFlags = 0;
}
} // LineStates_ErrorFlags(void)