mirror of
https://github.com/FreeRTOS/FreeRTOS-Kernel.git
synced 2025-04-23 23:11:58 -04:00
438 lines
11 KiB
C
438 lines
11 KiB
C
/*
|
|
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_RATE_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 xQueueHandle 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 );
|
|
}
|
|
}
|
|
}
|
|
|