/* | |
LPCUSB, an USB device driver for LPC microcontrollers | |
Copyright (C) 2006 Bertrik Sikken (bertrik@sikken.nl) | |
Redistribution and use in source and binary forms, with or without | |
modification, are permitted provided that the following conditions are met: | |
1. Redistributions of source code must retain the above copyright | |
notice, this list of conditions and the following disclaimer. | |
2. Redistributions in binary form must reproduce the above copyright | |
notice, this list of conditions and the following disclaimer in the | |
documentation and/or other materials provided with the distribution. | |
3. The name of the author may not be used to endorse or promote products | |
derived from this software without specific prior written permission. | |
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR | |
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, | |
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | |
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | |
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
*/ | |
/* | |
Minimal implementation of a USB serial port, using the CDC class. | |
This example application simply echoes everything it receives right back | |
to the host. | |
Windows: | |
Extract the usbser.sys file from .cab file in C:\WINDOWS\Driver Cache\i386 | |
and store it somewhere (C:\temp is a good place) along with the usbser.inf | |
file. Then plug in the LPC176x and direct windows to the usbser driver. | |
Windows then creates an extra COMx port that you can open in a terminal | |
program, like hyperterminal. [Note for FreeRTOS users - the required .inf | |
file is included in the project directory.] | |
Linux: | |
The device should be recognised automatically by the cdc_acm driver, | |
which creates a /dev/ttyACMx device file that acts just like a regular | |
serial port. | |
*/ | |
#include "FreeRTOS.h" | |
#include "task.h" | |
#include "queue.h" | |
#include <stdio.h> | |
#include <string.h> | |
#include "usbapi.h" | |
#include "usbdebug.h" | |
#include "usbstruct.h" | |
#include "LPC17xx.h" | |
#define usbMAX_SEND_BLOCK ( 20 / portTICK_PERIOD_MS ) | |
#define usbBUFFER_LEN ( 20 ) | |
#define INCREMENT_ECHO_BY 1 | |
#define BAUD_RATE 115200 | |
#define INT_IN_EP 0x81 | |
#define BULK_OUT_EP 0x05 | |
#define BULK_IN_EP 0x82 | |
#define MAX_PACKET_SIZE 64 | |
#define LE_WORD(x) ((x)&0xFF),((x)>>8) | |
// CDC definitions | |
#define CS_INTERFACE 0x24 | |
#define CS_ENDPOINT 0x25 | |
#define SET_LINE_CODING 0x20 | |
#define GET_LINE_CODING 0x21 | |
#define SET_CONTROL_LINE_STATE 0x22 | |
// data structure for GET_LINE_CODING / SET_LINE_CODING class requests | |
typedef struct { | |
unsigned long dwDTERate; | |
unsigned char bCharFormat; | |
unsigned char bParityType; | |
unsigned char bDataBits; | |
} TLineCoding; | |
static TLineCoding LineCoding = {115200, 0, 0, 8}; | |
static unsigned char abBulkBuf[64]; | |
static unsigned char abClassReqData[8]; | |
static QueueHandle_t xRxedChars = NULL, xCharsForTx = NULL; | |
// forward declaration of interrupt handler | |
void USBIntHandler(void); | |
static const unsigned char abDescriptors[] = { | |
// device descriptor | |
0x12, | |
DESC_DEVICE, | |
LE_WORD(0x0101), // bcdUSB | |
0x02, // bDeviceClass | |
0x00, // bDeviceSubClass | |
0x00, // bDeviceProtocol | |
MAX_PACKET_SIZE0, // bMaxPacketSize | |
LE_WORD(0xFFFF), // idVendor | |
LE_WORD(0x0005), // idProduct | |
LE_WORD(0x0100), // bcdDevice | |
0x01, // iManufacturer | |
0x02, // iProduct | |
0x03, // iSerialNumber | |
0x01, // bNumConfigurations | |
// configuration descriptor | |
0x09, | |
DESC_CONFIGURATION, | |
LE_WORD(67), // wTotalLength | |
0x02, // bNumInterfaces | |
0x01, // bConfigurationValue | |
0x00, // iConfiguration | |
0xC0, // bmAttributes | |
0x32, // bMaxPower | |
// control class interface | |
0x09, | |
DESC_INTERFACE, | |
0x00, // bInterfaceNumber | |
0x00, // bAlternateSetting | |
0x01, // bNumEndPoints | |
0x02, // bInterfaceClass | |
0x02, // bInterfaceSubClass | |
0x01, // bInterfaceProtocol, linux requires value of 1 for the cdc_acm module | |
0x00, // iInterface | |
// header functional descriptor | |
0x05, | |
CS_INTERFACE, | |
0x00, | |
LE_WORD(0x0110), | |
// call management functional descriptor | |
0x05, | |
CS_INTERFACE, | |
0x01, | |
0x01, // bmCapabilities = device handles call management | |
0x01, // bDataInterface | |
// ACM functional descriptor | |
0x04, | |
CS_INTERFACE, | |
0x02, | |
0x02, // bmCapabilities | |
// union functional descriptor | |
0x05, | |
CS_INTERFACE, | |
0x06, | |
0x00, // bMasterInterface | |
0x01, // bSlaveInterface0 | |
// notification EP | |
0x07, | |
DESC_ENDPOINT, | |
INT_IN_EP, // bEndpointAddress | |
0x03, // bmAttributes = intr | |
LE_WORD(8), // wMaxPacketSize | |
0x0A, // bInterval | |
// data class interface descriptor | |
0x09, | |
DESC_INTERFACE, | |
0x01, // bInterfaceNumber | |
0x00, // bAlternateSetting | |
0x02, // bNumEndPoints | |
0x0A, // bInterfaceClass = data | |
0x00, // bInterfaceSubClass | |
0x00, // bInterfaceProtocol | |
0x00, // iInterface | |
// data EP OUT | |
0x07, | |
DESC_ENDPOINT, | |
BULK_OUT_EP, // bEndpointAddress | |
0x02, // bmAttributes = bulk | |
LE_WORD(MAX_PACKET_SIZE), // wMaxPacketSize | |
0x00, // bInterval | |
// data EP in | |
0x07, | |
DESC_ENDPOINT, | |
BULK_IN_EP, // bEndpointAddress | |
0x02, // bmAttributes = bulk | |
LE_WORD(MAX_PACKET_SIZE), // wMaxPacketSize | |
0x00, // bInterval | |
// string descriptors | |
0x04, | |
DESC_STRING, | |
LE_WORD(0x0409), | |
0x0E, | |
DESC_STRING, | |
'L', 0, 'P', 0, 'C', 0, 'U', 0, 'S', 0, 'B', 0, | |
0x14, | |
DESC_STRING, | |
'U', 0, 'S', 0, 'B', 0, 'S', 0, 'e', 0, 'r', 0, 'i', 0, 'a', 0, 'l', 0, | |
0x12, | |
DESC_STRING, | |
'D', 0, 'E', 0, 'A', 0, 'D', 0, 'C', 0, '0', 0, 'D', 0, 'E', 0, | |
// terminating zero | |
0 | |
}; | |
/** | |
Local function to handle incoming bulk data | |
@param [in] bEP | |
@param [in] bEPStatus | |
*/ | |
static void BulkOut(unsigned char bEP, unsigned char bEPStatus) | |
{ | |
int i, iLen; | |
long lHigherPriorityTaskWoken = pdFALSE; | |
( void ) bEPStatus; | |
// get data from USB into intermediate buffer | |
iLen = USBHwEPRead(bEP, abBulkBuf, sizeof(abBulkBuf)); | |
for (i = 0; i < iLen; i++) { | |
// put into queue | |
xQueueSendFromISR( xRxedChars, &( abBulkBuf[ i ] ), &lHigherPriorityTaskWoken ); | |
} | |
portEND_SWITCHING_ISR( lHigherPriorityTaskWoken ); | |
} | |
/** | |
Local function to handle outgoing bulk data | |
@param [in] bEP | |
@param [in] bEPStatus | |
*/ | |
static void BulkIn(unsigned char bEP, unsigned char bEPStatus) | |
{ | |
int i, iLen; | |
long lHigherPriorityTaskWoken = pdFALSE; | |
( void ) bEPStatus; | |
if (uxQueueMessagesWaitingFromISR( xCharsForTx ) == 0) { | |
// no more data, disable further NAK interrupts until next USB frame | |
USBHwNakIntEnable(0); | |
return; | |
} | |
// get bytes from transmit FIFO into intermediate buffer | |
for (i = 0; i < MAX_PACKET_SIZE; i++) { | |
if( xQueueReceiveFromISR( xCharsForTx, ( &abBulkBuf[i] ), &lHigherPriorityTaskWoken ) != pdPASS ) | |
{ | |
break; | |
} | |
} | |
iLen = i; | |
// send over USB | |
if (iLen > 0) { | |
USBHwEPWrite(bEP, abBulkBuf, iLen); | |
} | |
portEND_SWITCHING_ISR( lHigherPriorityTaskWoken ); | |
} | |
/** | |
Local function to handle the USB-CDC class requests | |
@param [in] pSetup | |
@param [out] piLen | |
@param [out] ppbData | |
*/ | |
static BOOL HandleClassRequest(TSetupPacket *pSetup, int *piLen, unsigned char **ppbData) | |
{ | |
switch (pSetup->bRequest) { | |
// set line coding | |
case SET_LINE_CODING: | |
DBG("SET_LINE_CODING\n"); | |
memcpy((unsigned char *)&LineCoding, *ppbData, 7); | |
*piLen = 7; | |
DBG("dwDTERate=%u, bCharFormat=%u, bParityType=%u, bDataBits=%u\n", | |
LineCoding.dwDTERate, | |
LineCoding.bCharFormat, | |
LineCoding.bParityType, | |
LineCoding.bDataBits); | |
break; | |
// get line coding | |
case GET_LINE_CODING: | |
DBG("GET_LINE_CODING\n"); | |
*ppbData = (unsigned char *)&LineCoding; | |
*piLen = 7; | |
break; | |
// set control line state | |
case SET_CONTROL_LINE_STATE: | |
// bit0 = DTR, bit = RTS | |
DBG("SET_CONTROL_LINE_STATE %X\n", pSetup->wValue); | |
break; | |
default: | |
return FALSE; | |
} | |
return TRUE; | |
} | |
/** | |
Writes one character to VCOM port | |
@param [in] c character to write | |
@returns character written, or EOF if character could not be written | |
*/ | |
int VCOM_putchar(int c) | |
{ | |
char cc = ( char ) c; | |
if( xQueueSend( xCharsForTx, &cc, usbMAX_SEND_BLOCK ) == pdPASS ) | |
{ | |
return c; | |
} | |
else | |
{ | |
return EOF; | |
} | |
} | |
/** | |
Reads one character from VCOM port | |
@returns character read, or EOF if character could not be read | |
*/ | |
int VCOM_getchar(void) | |
{ | |
unsigned char c; | |
/* Block the task until a character is available. */ | |
xQueueReceive( xRxedChars, &c, portMAX_DELAY ); | |
return c; | |
} | |
/** | |
Interrupt handler | |
Simply calls the USB ISR | |
*/ | |
//void USBIntHandler(void) | |
void USB_IRQHandler(void) | |
{ | |
USBHwISR(); | |
} | |
static void USBFrameHandler(unsigned short wFrame) | |
{ | |
( void ) wFrame; | |
if( uxQueueMessagesWaitingFromISR( xCharsForTx ) > 0 ) | |
{ | |
// data available, enable NAK interrupt on bulk in | |
USBHwNakIntEnable(INACK_BI); | |
} | |
} | |
void vUSBTask( void *pvParameters ) | |
{ | |
int c; | |
/* Just to prevent compiler warnings about the unused parameter. */ | |
( void ) pvParameters; | |
DBG("Initialising USB stack\n"); | |
xRxedChars = xQueueCreate( usbBUFFER_LEN, sizeof( char ) ); | |
xCharsForTx = xQueueCreate( usbBUFFER_LEN, sizeof( char ) ); | |
if( ( xRxedChars == NULL ) || ( xCharsForTx == NULL ) ) | |
{ | |
/* Not enough heap available to create the buffer queues, can't do | |
anything so just delete ourselves. */ | |
vTaskDelete( NULL ); | |
} | |
// initialise stack | |
USBInit(); | |
// register descriptors | |
USBRegisterDescriptors(abDescriptors); | |
// register class request handler | |
USBRegisterRequestHandler(REQTYPE_TYPE_CLASS, HandleClassRequest, abClassReqData); | |
// register endpoint handlers | |
USBHwRegisterEPIntHandler(INT_IN_EP, NULL); | |
USBHwRegisterEPIntHandler(BULK_IN_EP, BulkIn); | |
USBHwRegisterEPIntHandler(BULK_OUT_EP, BulkOut); | |
// register frame handler | |
USBHwRegisterFrameHandler(USBFrameHandler); | |
// enable bulk-in interrupts on NAKs | |
USBHwNakIntEnable(INACK_BI); | |
DBG("Starting USB communication\n"); | |
NVIC_SetPriority( USB_IRQn, configUSB_INTERRUPT_PRIORITY ); | |
NVIC_EnableIRQ( USB_IRQn ); | |
// connect to bus | |
DBG("Connecting to USB bus\n"); | |
USBHwConnect(TRUE); | |
// echo any character received (do USB stuff in interrupt) | |
for( ;; ) | |
{ | |
c = VCOM_getchar(); | |
if (c != EOF) | |
{ | |
// Echo character back with INCREMENT_ECHO_BY offset, so for example if | |
// INCREMENT_ECHO_BY is 1 and 'A' is received, 'B' will be echoed back. | |
VCOM_putchar(c + INCREMENT_ECHO_BY ); | |
} | |
} | |
} | |