Added Teensy usb core sources.

This commit is contained in:
Francois Best 2012-05-22 21:51:34 +02:00
parent 561d4bb015
commit 522dcdf095
7 changed files with 2192 additions and 0 deletions

View File

@ -0,0 +1,4 @@
#define CORE_TEENSY_HID
#define CORE_TEENSY_MIDI
#define CORE_TEENSY_KEYBOARD
//#define CORE_TEENSY_JOYSTICK

597
teensy_core/usb_midi/usb.c Normal file
View File

@ -0,0 +1,597 @@
/* USB Serial Example for Teensy USB Development Board
* http://www.pjrc.com/teensy/usb_serial.html
* Copyright (c) 2008 PJRC.COM, LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "usb_common.h"
#include "usb_private.h"
/**************************************************************************
*
* Endpoint Buffer Configuration
*
**************************************************************************/
static const uint8_t PROGMEM endpoint_config_table[] = {
1, EP_TYPE_INTERRUPT_IN, EP_SIZE(DEBUG_TX_SIZE) | DEBUG_TX_BUFFER,
1, EP_TYPE_INTERRUPT_OUT, EP_SIZE(DEBUG_RX_SIZE) | DEBUG_RX_BUFFER,
1, EP_TYPE_BULK_IN, EP_SIZE(MIDI_TX_SIZE) | MIDI_TX_BUFFER,
1, EP_TYPE_BULK_OUT, EP_SIZE(MIDI_RX_SIZE) | MIDI_RX_BUFFER
};
/**************************************************************************
*
* Descriptor Data
*
**************************************************************************/
// Descriptors are the data that your computer reads when it auto-detects
// this USB device (called "enumeration" in USB lingo). The most commonly
// changed items are editable at the top of this file. Changing things
// in here should only be done by those who've read chapter 9 of the USB
// spec and relevant portions of any USB class specifications!
static uint8_t PROGMEM device_descriptor[] = {
18, // bLength
1, // bDescriptorType
0x00, 0x02, // bcdUSB
0, // bDeviceClass
0, // bDeviceSubClass
0, // bDeviceProtocol
ENDPOINT0_SIZE, // bMaxPacketSize0
LSB(VENDOR_ID), MSB(VENDOR_ID), // idVendor
LSB(PRODUCT_ID), MSB(PRODUCT_ID), // idProduct
0x00, 0x01, // bcdDevice
0, // iManufacturer
1, // iProduct
0, // iSerialNumber
1 // bNumConfigurations
};
static uint8_t PROGMEM debug_hid_report_desc[] = {
0x06, 0xC9, 0xFF, // Usage Page 0xFFC9 (vendor defined)
0x09, 0x04, // Usage 0x04
0xA1, 0x5C, // Collection 0x5C
0x75, 0x08, // report size = 8 bits (global)
0x15, 0x00, // logical minimum = 0 (global)
0x26, 0xFF, 0x00, // logical maximum = 255 (global)
0x95, DEBUG_TX_SIZE, // report count (global)
0x09, 0x75, // usage (local)
0x81, 0x02, // Input
0x95, DEBUG_RX_SIZE, // report count (global)
0x09, 0x76, // usage (local)
0x91, 0x02, // Output
0x95, 0x04, // report count (global)
0x09, 0x76, // usage (local)
0xB1, 0x02, // Feature
0xC0 // end collection
};
#define CONFIG1_DESC_SIZE ( 9 + 74 + 32 )
#define DEBUG_HID_DESC_OFFSET ( 9 + 74 + 9 )
static uint8_t PROGMEM config1_descriptor[CONFIG1_DESC_SIZE] = {
// configuration descriptor, USB spec 9.6.3, page 264-266, Table 9-10
9, // bLength;
2, // bDescriptorType;
LSB(CONFIG1_DESC_SIZE), // wTotalLength
MSB(CONFIG1_DESC_SIZE),
NUM_INTERFACE, // bNumInterfaces
1, // bConfigurationValue
0, // iConfiguration
0xC0, // bmAttributes
50, // bMaxPower
// This MIDI stuff is a copy of the example from the Audio Class
// MIDI spec 1.0 (Nov 1, 1999), Appendix B, pages 37 to 43.
// Standard MS Interface Descriptor,
9, // bLength
4, // bDescriptorType
MIDI_INTERFACE, // bInterfaceNumber
0, // bAlternateSetting
2, // bNumEndpoints
0x01, // bInterfaceClass (0x01 = Audio)
0x03, // bInterfaceSubClass (0x03 = MIDI)
0x00, // bInterfaceProtocol (unused for MIDI)
0, // iInterface
// MIDI MS Interface Header, USB MIDI 6.1.2.1, page 21, Table 6-2
7, // bLength
0x24, // bDescriptorType = CS_INTERFACE
0x01, // bDescriptorSubtype = MS_HEADER
0x00, 0x01, // bcdMSC = revision 01.00
0x41, 0x00, // wTotalLength
// MIDI IN Jack Descriptor, B.4.3, Table B-7 (embedded), page 40
6, // bLength
0x24, // bDescriptorType = CS_INTERFACE
0x02, // bDescriptorSubtype = MIDI_IN_JACK
0x01, // bJackType = EMBEDDED
1, // bJackID, ID = 1
0, // iJack
// MIDI IN Jack Descriptor, B.4.3, Table B-8 (external), page 40
6, // bLength
0x24, // bDescriptorType = CS_INTERFACE
0x02, // bDescriptorSubtype = MIDI_IN_JACK
0x02, // bJackType = EXTERNAL
2, // bJackID, ID = 2
0, // iJack
// MIDI OUT Jack Descriptor, B.4.4, Table B-9, page 41
9,
0x24, // bDescriptorType = CS_INTERFACE
0x03, // bDescriptorSubtype = MIDI_OUT_JACK
0x01, // bJackType = EMBEDDED
3, // bJackID, ID = 3
1, // bNrInputPins = 1 pin
2, // BaSourceID(1) = 2
1, // BaSourcePin(1) = first pin
0, // iJack
// MIDI OUT Jack Descriptor, B.4.4, Table B-10, page 41
9,
0x24, // bDescriptorType = CS_INTERFACE
0x03, // bDescriptorSubtype = MIDI_OUT_JACK
0x02, // bJackType = EXTERNAL
4, // bJackID, ID = 4
1, // bNrInputPins = 1 pin
1, // BaSourceID(1) = 1
1, // BaSourcePin(1) = first pin
0, // iJack
// Standard Bulk OUT Endpoint Descriptor, B.5.1, Table B-11, pae 42
9, // bLength
5, // bDescriptorType = ENDPOINT
MIDI_RX_ENDPOINT, // bEndpointAddress
0x02, // bmAttributes (0x02=bulk)
MIDI_RX_SIZE, 0, // wMaxPacketSize
0, // bInterval
0, // bRefresh
0, // bSynchAddress
// Class-specific MS Bulk OUT Endpoint Descriptor, B.5.2, Table B-12, page 42
5, // bLength
0x25, // bDescriptorSubtype = CS_ENDPOINT
0x01, // bJackType = MS_GENERAL
1, // bNumEmbMIDIJack = 1 jack
1, // BaAssocJackID(1) = jack ID #1
// Standard Bulk IN Endpoint Descriptor, B.5.1, Table B-11, pae 42
9, // bLength
5, // bDescriptorType = ENDPOINT
MIDI_TX_ENDPOINT | 0x80, // bEndpointAddress
0x02, // bmAttributes (0x02=bulk)
MIDI_TX_SIZE, 0, // wMaxPacketSize
0, // bInterval
0, // bRefresh
0, // bSynchAddress
// Class-specific MS Bulk IN Endpoint Descriptor, B.5.2, Table B-12, page 42
5, // bLength
0x25, // bDescriptorSubtype = CS_ENDPOINT
0x01, // bJackType = MS_GENERAL
1, // bNumEmbMIDIJack = 1 jack
3, // BaAssocJackID(1) = jack ID #3
// interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12
9, // bLength
4, // bDescriptorType
DEBUG_INTERFACE, // bInterfaceNumber
0, // bAlternateSetting
2, // bNumEndpoints
0x03, // bInterfaceClass (0x03 = HID)
0x00, // bInterfaceSubClass
0x00, // bInterfaceProtocol
0, // iInterface
// HID interface descriptor, HID 1.11 spec, section 6.2.1
9, // bLength
0x21, // bDescriptorType
0x11, 0x01, // bcdHID
0, // bCountryCode
1, // bNumDescriptors
0x22, // bDescriptorType
sizeof(debug_hid_report_desc), // wDescriptorLength
0,
// endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13
7, // bLength
5, // bDescriptorType
DEBUG_TX_ENDPOINT | 0x80, // bEndpointAddress
0x03, // bmAttributes (0x03=intr)
DEBUG_TX_SIZE, 0, // wMaxPacketSize
DEBUG_TX_INTERVAL, // bInterval
// endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13
7, // bLength
5, // bDescriptorType
DEBUG_RX_ENDPOINT, // bEndpointAddress
0x03, // bmAttributes (0x03=intr)
DEBUG_RX_SIZE, 0, // wMaxPacketSize
DEBUG_RX_INTERVAL, // bInterval
};
// If you're desperate for a little extra code memory, these strings
// can be completely removed if iManufacturer, iProduct, iSerialNumber
// in the device desciptor are changed to zeros.
struct usb_string_descriptor_struct {
uint8_t bLength;
uint8_t bDescriptorType;
int16_t wString[];
};
static struct usb_string_descriptor_struct PROGMEM string0 = {
4,
3,
{0x0409}
};
static struct usb_string_descriptor_struct PROGMEM string1 = {
sizeof(STR_PRODUCT),
3,
STR_PRODUCT
};
// This table defines which descriptor data is sent for each specific
// request from the host (in wValue and wIndex).
static struct descriptor_list_struct {
uint16_t wValue;
uint16_t wIndex;
const uint8_t *addr;
uint8_t length;
} PROGMEM descriptor_list[] = {
{0x0100, 0x0000, device_descriptor, sizeof(device_descriptor)},
{0x0200, 0x0000, config1_descriptor, sizeof(config1_descriptor)},
{0x2200, DEBUG_INTERFACE, debug_hid_report_desc, sizeof(debug_hid_report_desc)},
{0x2100, DEBUG_INTERFACE, config1_descriptor+DEBUG_HID_DESC_OFFSET, 9},
{0x0300, 0x0000, (const uint8_t *)&string0, 4},
{0x0301, 0x0409, (const uint8_t *)&string1, sizeof(STR_PRODUCT)},
};
#define NUM_DESC_LIST (sizeof(descriptor_list)/sizeof(struct descriptor_list_struct))
/**************************************************************************
*
* Variables - these are the only non-stack RAM usage
*
**************************************************************************/
// zero when we are not configured, non-zero when enumerated
volatile uint8_t usb_configuration USBSTATE;
volatile uint8_t usb_suspended USBSTATE;
// the time remaining before we transmit any partially full
// packet, or send a zero length packet.
volatile uint8_t debug_flush_timer USBSTATE;
/**************************************************************************
*
* Public Functions - these are the API intended for the user
*
**************************************************************************/
// initialize USB serial
void usb_init(void)
{
uint8_t u;
u = USBCON;
if ((u & (1<<USBE)) && !(u & (1<<FRZCLK))) return;
HW_CONFIG();
USB_FREEZE(); // enable USB
PLL_CONFIG(); // config PLL
while (!(PLLCSR & (1<<PLOCK))) ; // wait for PLL lock
USB_CONFIG(); // start USB clock
UDCON = 0; // enable attach resistor
usb_configuration = 0;
usb_suspended = 0;
debug_flush_timer = 0;
UDINT = 0;
UDIEN = (1<<EORSTE)|(1<<SOFE);
//sei(); // init() in wiring.c does this
}
void usb_shutdown(void)
{
UDIEN = 0; // disable interrupts
UDCON = 1; // disconnect attach resistor
USBCON = 0; // shut off USB periperal
PLLCSR = 0; // shut off PLL
usb_configuration = 0;
usb_suspended = 1;
}
// Public API functions moved to usb_api.cpp
/**************************************************************************
*
* Private Functions - not intended for general user consumption....
*
**************************************************************************/
// USB Device Interrupt - handle all device-level events
// the transmit buffer flushing is triggered by the start of frame
//
ISR(USB_GEN_vect)
{
uint8_t intbits, t, i;
static uint8_t div4=0;
intbits = UDINT;
UDINT = 0;
if (intbits & (1<<EORSTI)) {
UENUM = 0;
UECONX = 1;
UECFG0X = EP_TYPE_CONTROL;
UECFG1X = EP_SIZE(ENDPOINT0_SIZE) | EP_SINGLE_BUFFER;
UEIENX = (1<<RXSTPE);
usb_configuration = 0;
}
if ((intbits & (1<<SOFI)) && usb_configuration) {
t = debug_flush_timer;
if (t) {
debug_flush_timer = --t;
if (!t) {
UENUM = DEBUG_TX_ENDPOINT;
while ((UEINTX & (1<<RWAL))) {
UEDATX = 0;
}
UEINTX = 0x3A;
}
}
UENUM = MIDI_TX_ENDPOINT;
if (UEBCLX) UEINTX = 0x3A;
}
if (intbits & (1<<SUSPI)) {
// USB Suspend (inactivity for 3ms)
UDIEN = (1<<WAKEUPE);
usb_configuration = 0;
usb_suspended = 1;
#if (F_CPU >= 8000000L)
// WAKEUPI does not work with USB clock freeze
// when CPU is running less than 8 MHz.
// Is this a hardware bug?
USB_FREEZE(); // shut off USB
PLLCSR = 0; // shut off PLL
#endif
// to properly meet the USB spec, current must
// reduce to less than 2.5 mA, which means using
// powerdown mode, but that breaks the Arduino
// user's paradigm....
}
if (usb_suspended && (intbits & (1<<WAKEUPI))) {
// USB Resume (pretty much any activity)
#if (F_CPU >= 8000000L)
PLL_CONFIG();
while (!(PLLCSR & (1<<PLOCK))) ;
USB_CONFIG();
#endif
UDIEN = (1<<EORSTE)|(1<<SOFE)|(1<<SUSPE);
usb_suspended = 0;
return;
}
}
// Misc functions to wait for ready and send/receive packets
static inline void usb_wait_in_ready(void)
{
while (!(UEINTX & (1<<TXINI))) ;
}
static inline void usb_send_in(void)
{
UEINTX = ~(1<<TXINI);
}
static inline void usb_wait_receive_out(void)
{
while (!(UEINTX & (1<<RXOUTI))) ;
}
static inline void usb_ack_out(void)
{
UEINTX = ~(1<<RXOUTI);
}
// USB Endpoint Interrupt - endpoint 0 is handled here. The
// other endpoints are manipulated by the user-callable
// functions, and the start-of-frame interrupt.
//
ISR(USB_COM_vect)
{
uint8_t intbits;
const uint8_t *list;
const uint8_t *cfg;
uint8_t i, n, len, en;
uint8_t *p;
uint8_t bmRequestType;
uint8_t bRequest;
uint16_t wValue;
uint16_t wIndex;
uint16_t wLength;
uint16_t desc_val;
const uint8_t *desc_addr;
uint8_t desc_length;
UENUM = 0;
intbits = UEINTX;
if (intbits & (1<<RXSTPI)) {
bmRequestType = UEDATX;
bRequest = UEDATX;
read_word_lsbfirst(wValue, UEDATX);
read_word_lsbfirst(wIndex, UEDATX);
read_word_lsbfirst(wLength, UEDATX);
UEINTX = ~((1<<RXSTPI) | (1<<RXOUTI) | (1<<TXINI));
if (bRequest == GET_DESCRIPTOR) {
list = (const uint8_t *)descriptor_list;
for (i=0; ; i++) {
if (i >= NUM_DESC_LIST) {
UECONX = (1<<STALLRQ)|(1<<EPEN); //stall
return;
}
pgm_read_word_postinc(desc_val, list);
if (desc_val != wValue) {
list += sizeof(struct descriptor_list_struct)-2;
continue;
}
pgm_read_word_postinc(desc_val, list);
if (desc_val != wIndex) {
list += sizeof(struct descriptor_list_struct)-4;
continue;
}
pgm_read_word_postinc(desc_addr, list);
desc_length = pgm_read_byte(list);
break;
}
len = (wLength < 256) ? wLength : 255;
if (len > desc_length) len = desc_length;
list = desc_addr;
do {
// wait for host ready for IN packet
do {
i = UEINTX;
} while (!(i & ((1<<TXINI)|(1<<RXOUTI))));
if (i & (1<<RXOUTI)) return; // abort
// send IN packet
n = len < ENDPOINT0_SIZE ? len : ENDPOINT0_SIZE;
for (i = n; i; i--) {
pgm_read_byte_postinc(UEDATX, list);
}
len -= n;
usb_send_in();
} while (len || n == ENDPOINT0_SIZE);
return;
}
if (bRequest == SET_ADDRESS) {
usb_send_in();
usb_wait_in_ready();
UDADDR = wValue | (1<<ADDEN);
return;
}
if (bRequest == SET_CONFIGURATION && bmRequestType == 0) {
usb_configuration = wValue;
debug_flush_timer = 0;
usb_send_in();
cfg = endpoint_config_table;
for (i=1; i<NUM_ENDPOINTS; i++) {
UENUM = i;
pgm_read_byte_postinc(en, cfg);
UECONX = en;
if (en) {
pgm_read_byte_postinc(UECFG0X, cfg);
pgm_read_byte_postinc(UECFG1X, cfg);
}
}
UERST = 0x1E;
UERST = 0;
return;
}
if (bRequest == GET_CONFIGURATION && bmRequestType == 0x80) {
usb_wait_in_ready();
UEDATX = usb_configuration;
usb_send_in();
return;
}
if (bRequest == GET_STATUS) {
usb_wait_in_ready();
i = 0;
if (bmRequestType == 0x82) {
UENUM = wIndex;
if (UECONX & (1<<STALLRQ)) i = 1;
UENUM = 0;
}
UEDATX = i;
UEDATX = 0;
usb_send_in();
return;
}
if ((bRequest == CLEAR_FEATURE || bRequest == SET_FEATURE)
&& bmRequestType == 0x02 && wValue == 0) {
i = wIndex & 0x7F;
if (i >= 1 && i <= MAX_ENDPOINT) {
usb_send_in();
UENUM = i;
if (bRequest == SET_FEATURE) {
UECONX = (1<<STALLRQ)|(1<<EPEN);
} else {
UECONX = (1<<STALLRQC)|(1<<RSTDT)|(1<<EPEN);
UERST = (1 << i);
UERST = 0;
}
return;
}
}
if (wIndex == DEBUG_INTERFACE) {
if (bRequest == HID_GET_REPORT && bmRequestType == 0xA1) {
len = wLength;
do {
// wait for host ready for IN packet
do {
i = UEINTX;
} while (!(i & ((1<<TXINI)|(1<<RXOUTI))));
if (i & (1<<RXOUTI)) return; // abort
// send IN packet
n = len < ENDPOINT0_SIZE ? len : ENDPOINT0_SIZE;
for (i = n; i; i--) {
UEDATX = 0;
}
len -= n;
usb_send_in();
} while (len || n == ENDPOINT0_SIZE);
return;
}
if (bRequest == HID_SET_REPORT && bmRequestType == 0x21) {
if (wValue == 0x0300 && wLength == 0x0004) {
uint8_t b1, b2, b3, b4;
usb_wait_receive_out();
b1 = UEDATX;
b2 = UEDATX;
b3 = UEDATX;
b4 = UEDATX;
usb_ack_out();
usb_send_in();
if (b1 == 0xA9 && b2 == 0x45 && b3 == 0xC2 && b4 == 0x6B)
_reboot_Teensyduino_();
if (b1 == 0x8B && b2 == 0xC5 && b3 == 0x1D && b4 == 0x70)
_restart_Teensyduino_();
}
}
}
}
UECONX = (1<<STALLRQ) | (1<<EPEN); // stall
}

View File

@ -0,0 +1,270 @@
/* USB API for Teensy USB Development Board
* http://www.pjrc.com/teensy/teensyduino.html
* Copyright (c) 2008 PJRC.COM, LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <stdint.h>
#include "usb_common.h"
#include "usb_private.h"
#include "usb_api.h"
#include "wiring.h"
#include "usb_midi.h"
void usb_serial_class::begin(long speed)
{
// make sure USB is initialized
usb_init();
uint16_t begin_wait = (uint16_t)millis();
while (1) {
if (usb_configuration) {
delay(200); // a little time for host to load a driver
return;
}
if (usb_suspended) {
uint16_t begin_suspend = (uint16_t)millis();
while (usb_suspended) {
// must remain suspended for a while, because
// normal USB enumeration causes brief suspend
// states, typically under 0.1 second
if ((uint16_t)millis() - begin_suspend > 250) {
return;
}
}
}
// ... or a timout (powered by a USB power adaptor that
// wiggles the data lines to keep a USB device charging)
if ((uint16_t)millis() - begin_wait > 2500) return;
}
}
void usb_serial_class::end(void)
{
usb_shutdown();
delay(25);
}
static volatile uint8_t prev_byte=0;
// number of bytes available in the receive buffer
uint8_t usb_serial_class::available(void)
{
uint8_t c;
c = prev_byte; // assume 1 byte static volatile access is atomic
if (c) return 1;
c = readnext();
if (c) {
prev_byte = c;
return 1;
}
return 0;
}
// get the next character, or -1 if nothing received
int usb_serial_class::read(void)
{
uint8_t c;
c = prev_byte;
if (c) {
prev_byte = 0;
return c;
}
c = readnext();
if (c) return c;
return -1;
}
// get the next character, or 0 if nothing
uint8_t usb_serial_class::readnext(void)
{
uint8_t c, c2, intr_state;
// interrupts are disabled so these functions can be
// used from the main program or interrupt context,
// even both in the same program!
intr_state = SREG;
cli();
if (!usb_configuration) {
SREG = intr_state;
return 0;
}
UENUM = DEBUG_RX_ENDPOINT;
try_again:
if (!(UEINTX & (1<<RWAL))) {
// no packet in buffer
SREG = intr_state;
return 0;
}
// take one byte out of the buffer
c = UEDATX;
if (c == 0) {
// if we see a zero, discard it and
// discard the rest of this packet
UEINTX = 0x6B;
goto try_again;
}
// if this drained the buffer, release it
if (!(UEINTX & (1<<RWAL))) UEINTX = 0x6B;
SREG = intr_state;
return c;
}
// discard any buffered input
void usb_serial_class::flush()
{
uint8_t intr_state;
if (usb_configuration) {
intr_state = SREG;
cli();
//UENUM = CDC_RX_ENDPOINT;
while ((UEINTX & (1<<RWAL))) {
UEINTX = 0x6B;
}
SREG = intr_state;
}
}
// transmit a character.
void usb_serial_class::write(uint8_t c)
{
//static uint8_t previous_timeout=0;
uint8_t timeout, intr_state;
// if we're not online (enumerated and configured), error
if (!usb_configuration) return;
// interrupts are disabled so these functions can be
// used from the main program or interrupt context,
// even both in the same program!
intr_state = SREG;
cli();
UENUM = DEBUG_TX_ENDPOINT;
// if we gave up due to timeout before, don't wait again
#if 0
// this seems to be causig a lockup... why????
if (previous_timeout) {
if (!(UEINTX & (1<<RWAL))) {
SREG = intr_state;
return;
}
previous_timeout = 0;
}
#endif
// wait for the FIFO to be ready to accept data
timeout = UDFNUML + TRANSMIT_TIMEOUT;
while (1) {
// are we ready to transmit?
if (UEINTX & (1<<RWAL)) break;
SREG = intr_state;
// have we waited too long? This happens if the user
// is not running an application that is listening
if (UDFNUML == timeout) {
//previous_timeout = 1;
return;
}
// has the USB gone offline?
if (!usb_configuration) return;
// get ready to try checking again
intr_state = SREG;
cli();
UENUM = DEBUG_TX_ENDPOINT;
}
// actually write the byte into the FIFO
UEDATX = c;
// if this completed a packet, transmit it now!
if (!(UEINTX & (1<<RWAL))) {
UEINTX = 0x3A;
debug_flush_timer = 0;
} else {
debug_flush_timer = TRANSMIT_FLUSH_TIMEOUT;
}
SREG = intr_state;
}
// These are Teensy-specific extensions to the Serial object
// immediately transmit any buffered output.
// This doesn't actually transmit the data - that is impossible!
// USB devices only transmit when the host allows, so the best
// we can do is release the FIFO buffer for when the host wants it
void usb_serial_class::send_now(void)
{
uint8_t intr_state;
intr_state = SREG;
cli();
if (debug_flush_timer) {
UENUM = DEBUG_TX_ENDPOINT;
while ((UEINTX & (1<<RWAL))) {
UEDATX = 0;
}
UEINTX = 0x3A;
debug_flush_timer = 0;
}
SREG = intr_state;
}
uint32_t usb_serial_class::baud(void)
{
return (DEBUG_TX_SIZE * 1000 / DEBUG_TX_INTERVAL);
}
uint8_t usb_serial_class::stopbits(void)
{
return 0;
}
uint8_t usb_serial_class::paritytype(void)
{
return 0;
}
uint8_t usb_serial_class::numbits(void)
{
return 8;
}
uint8_t usb_serial_class::dtr(void)
{
return 0;
}
uint8_t usb_serial_class::rts(void)
{
return 0;
}
// Preinstantiate Objects //////////////////////////////////////////////////////
usb_serial_class usbSerial = usb_serial_class();
MIDI_Class usbMIDI = MIDI_Class();
//usb_midi_class usbMIDI = usb_midi_class();

View File

@ -0,0 +1,36 @@
#ifndef USBserial_h_
#define USBserial_h_
#include <inttypes.h>
#include "Print.h"
class MIDI_Class;
extern MIDI_Class usbMIDI;
class usb_serial_class : public Print
{
public:
// standard Arduino functions
void begin(long);
void end(void);
uint8_t available(void);
int read(void);
void flush(void);
virtual void write(uint8_t);
// Teensy extensions
void send_now(void);
uint32_t baud(void);
uint8_t stopbits(void);
uint8_t paritytype(void);
uint8_t numbits(void);
uint8_t dtr(void);
uint8_t rts(void);
private:
uint8_t readnext(void);
};
extern usb_serial_class usbSerial;
#endif

View File

@ -0,0 +1,899 @@
/*!
* @file usb_midi.cpp
* Project Teensy MIDI Core
* @brief MIDI Library for Teensy - USB side
* Version 3.1
* @author Francois Best
* @date 28/04/11
* License GPL Forty Seven Effects - 2011
*/
#include "usb_midi.h"
#include <stdlib.h>
#include "WConstants.h"
#include "usb_api.h"
#define USE_SERIAL_PORT usbSerial
/*! Main instance (the class comes pre-instantiated). */
MIDI_Class usbMIDI;
/*! Default constructor for MIDI_Class. */
MIDI_Class::MIDI_Class() {
#if USE_CALLBACKS
// Initialise callbacks to NULL pointer
mNoteOffCallback = NULL;
mNoteOnCallback = NULL;
mAfterTouchPolyCallback = NULL;
mControlChangeCallback = NULL;
mProgramChangeCallback = NULL;
mAfterTouchChannelCallback = NULL;
mPitchBendCallback = NULL;
mSystemExclusiveCallback = NULL;
mTimeCodeQuarterFrameCallback = NULL;
mSongPositionCallback = NULL;
mSongSelectCallback = NULL;
mTuneRequestCallback = NULL;
mClockCallback = NULL;
mStartCallback = NULL;
mContinueCallback = NULL;
mStopCallback = NULL;
mActiveSensingCallback = NULL;
mSystemResetCallback = NULL;
#endif
}
/*! Default destructor for MIDI_Class.\n
This is not really useful for the Arduino, as it is never called...
*/
MIDI_Class::~MIDI_Class() { }
/*! Call the begin method in the setup() function of the Arduino.
All parameters are set to their default values:
- Input channel set to 1 if no value is specified
- Full thru mirroring
*/
void MIDI_Class::begin(const byte inChannel) {
// Initialise the Serial port
USE_SERIAL_PORT.begin(MIDI_BAUDRATE);
#if COMPILE_MIDI_OUT
#if USE_RUNNING_STATUS
mRunningStatus_TX = InvalidType;
#endif // USE_RUNNING_STATUS
#endif // COMPILE_MIDI_OUT
#if COMPILE_MIDI_IN
mInputChannel = inChannel;
mRunningStatus_RX = InvalidType;
mPendingMessageIndex = 0;
mPendingMessageExpectedLenght = 0;
mMessage.valid = false;
mMessage.type = InvalidType;
mMessage.channel = 0;
mMessage.data1 = 0;
mMessage.data2 = 0;
#endif // COMPILE_MIDI_IN
#if (COMPILE_MIDI_IN && COMPILE_MIDI_OUT && COMPILE_MIDI_THRU) // Thru
mThruFilterMode = Full;
mThruActivated = true;
#endif // Thru
}
#if COMPILE_MIDI_OUT
// Private method for generating a status byte from channel and type
const byte MIDI_Class::genstatus(const kMIDIType inType,const byte inChannel) {
return ((byte)inType | ((inChannel-1) & 0x0F));
}
/* Generate and send a custom MIDI mMessage.
\param type The message type (see type defines for reference)
\param data1 The first data byte.
\param data2 The second data byte (if the message contains only 1 data byte, set this one to 0).
\param channel The output channel on which the message will be sent (values from 1 to 16). Note: you cannot send to OMNI.
*/
void MIDI_Class::send(kMIDIType type, byte data1, byte data2, byte channel) {
// Then test if channel is valid
if (channel >= MIDI_CHANNEL_OFF || channel == MIDI_CHANNEL_OMNI || type < NoteOff) {
#if USE_RUNNING_STATUS
mRunningStatus_TX = InvalidType;
#endif
return; // Don't send anything
}
if (type <= PitchBend) {
// Channel messages
// Protection: remove MSBs on data
data1 &= 0x7F;
data2 &= 0x7F;
byte statusbyte = genstatus(type,channel);
#if USE_RUNNING_STATUS
// Check Running Status
if (mRunningStatus_TX != statusbyte) {
// New message, memorise and send header
mRunningStatus_TX = statusbyte;
USE_SERIAL_PORT.write(mRunningStatus_TX);
}
#else
// Don't care about running status, send the Control byte.
USE_SERIAL_PORT.write(statusbyte);
#endif
// Then send data
USE_SERIAL_PORT.write(data1);
if (type != ProgramChange && type != AfterTouchChannel) {
USE_SERIAL_PORT.write(data2);
}
return;
}
if (type >= TuneRequest && type <= SystemReset) {
// System Real-time and 1 byte.
sendRealTime(type);
}
}
/*! Send a Note On message
\param NoteNumber Pitch value in the MIDI format (0 to 127). Take a look at the values, names and frequencies of notes here: http://www.phys.unsw.edu.au/jw/notes.html\n
\param Velocity Note attack velocity (0 to 127). A NoteOn with 0 velocity is considered as a NoteOff.
\param Channel The channel on which the message will be sent (1 to 16).
*/
void MIDI_Class::sendNoteOn(byte NoteNumber,byte Velocity,byte Channel) { send(NoteOn,NoteNumber,Velocity,Channel); }
/*! Send a Note Off message (a real Note Off, not a Note On with null velocity)
\param NoteNumber Pitch value in the MIDI format (0 to 127). Take a look at the values, names and frequencies of notes here: http://www.phys.unsw.edu.au/jw/notes.html\n
\param Velocity Release velocity (0 to 127).
\param Channel The channel on which the message will be sent (1 to 16).
*/
void MIDI_Class::sendNoteOff(byte NoteNumber,byte Velocity,byte Channel) { send(NoteOff,NoteNumber,Velocity,Channel); }
/*! Send a Program Change message
\param ProgramNumber The Program to select (0 to 127).
\param Channel The channel on which the message will be sent (1 to 16).
*/
void MIDI_Class::sendProgramChange(byte ProgramNumber,byte Channel) { send(ProgramChange,ProgramNumber,0,Channel); }
/*! Send a Control Change message
\param ControlNumber The controller number (0 to 127). See the detailed description here: http://www.somascape.org/midi/tech/spec.html#ctrlnums
\param ControlValue The value for the specified controller (0 to 127).
\param Channel The channel on which the message will be sent (1 to 16).
*/
void MIDI_Class::sendControlChange(byte ControlNumber, byte ControlValue,byte Channel) { send(ControlChange,ControlNumber,ControlValue,Channel); }
/*! Send a Polyphonic AfterTouch message (applies to only one specified note)
\param NoteNumber The note to apply AfterTouch to (0 to 127).
\param Pressure The amount of AfterTouch to apply (0 to 127).
\param Channel The channel on which the message will be sent (1 to 16).
*/
void MIDI_Class::sendPolyPressure(byte NoteNumber,byte Pressure,byte Channel) { send(AfterTouchPoly,NoteNumber,Pressure,Channel); }
/*! Send a MonoPhonic AfterTouch message (applies to all notes)
\param Pressure The amount of AfterTouch to apply to all notes.
\param Channel The channel on which the message will be sent (1 to 16).
*/
void MIDI_Class::sendAfterTouch(byte Pressure,byte Channel) { send(AfterTouchChannel,Pressure,0,Channel); }
/*! Send a Pitch Bend message using an integer value.
\param PitchValue The amount of bend to send (in an integer format), between 0 (maximum downwards bend) and 16383 (max upwards bend), center value is 8192.
\param Channel The channel on which the message will be sent (1 to 16).
*/
void MIDI_Class::sendPitchBend(unsigned int PitchValue,byte Channel) {
send(PitchBend,(PitchValue & 0x7F),(PitchValue >> 7) & 0x7F,Channel);
}
/*! Send a Pitch Bend message using a floating point value.
\param PitchValue The amount of bend to send (in a floating point format), between -1 (maximum downwards bend) and +1 (max upwards bend), center value is 0.
\param Channel The channel on which the message will be sent (1 to 16).
*/
void MIDI_Class::sendPitchBend(double PitchValue,byte Channel) {
unsigned int pitchval = (PitchValue+1.f)*8192;
if (pitchval > 16383) pitchval = 16383; // overflow protection
sendPitchBend(pitchval,Channel);
}
/*! Generate and send a System Exclusive frame.
\param length The size of the array to send
\param array The byte array containing the data to send
\param ArrayContainsBoundaries When set to 'true', 0xF0 & 0xF7 bytes (start & stop SysEx) will NOT be sent (and therefore must be included in the array).\
default value is set to 'false' for compatibility with previous versions of the library.
*/
void MIDI_Class::sendSysEx(byte length, byte * array, bool ArrayContainsBoundaries) {
if (!ArrayContainsBoundaries) USE_SERIAL_PORT.write(0xF0);
for (byte i=0;i<length;i++) USE_SERIAL_PORT.write(array[i]);
if (!ArrayContainsBoundaries) USE_SERIAL_PORT.write(0xF7);
#if USE_RUNNING_STATUS
mRunningStatus_TX = InvalidType;
#endif
}
/*! Send a Tune Request message. When a MIDI unit receives this message, it should tune its oscillators (if equipped with any) */
void MIDI_Class::sendTuneRequest() { sendRealTime(TuneRequest); }
/*! Send a MIDI Time Code Quarter Frame. See MIDI Specification for more information.
\param TypeNibble MTC type
\param ValuesNibble MTC data
*/
void MIDI_Class::sendTimeCodeQuarterFrame(byte TypeNibble, byte ValuesNibble) {
byte data = ( ((TypeNibble & 0x07) << 4) | (ValuesNibble & 0x0F) );
sendTimeCodeQuarterFrame(data);
}
/*! Send a MIDI Time Code Quarter Frame. See MIDI Specification for more information.
\param data if you want to encode directly the nibbles in your program, you can send the byte here.
*/
void MIDI_Class::sendTimeCodeQuarterFrame(byte data) {
USE_SERIAL_PORT.write((byte)TimeCodeQuarterFrame);
USE_SERIAL_PORT.write(data);
#if USE_RUNNING_STATUS
mRunningStatus_TX = InvalidType;
#endif
}
/*! Send a Song Position Pointer message.
\param Beats The number of beats since the start of the song.
*/
void MIDI_Class::sendSongPosition(unsigned int Beats) {
USE_SERIAL_PORT.write((byte)SongPosition);
USE_SERIAL_PORT.write(Beats & 0x7F);
USE_SERIAL_PORT.write((Beats >> 7) & 0x7F);
#if USE_RUNNING_STATUS
mRunningStatus_TX = InvalidType;
#endif
}
/*! Send a Song Select message */
void MIDI_Class::sendSongSelect(byte SongNumber) {
USE_SERIAL_PORT.write((byte)SongSelect);
USE_SERIAL_PORT.write(SongNumber & 0x7F);
#if USE_RUNNING_STATUS
mRunningStatus_TX = InvalidType;
#endif
}
/*! Send a Real Time (one byte) message. \n You can also send a Tune Request with this method.
\param Type The available Real Time types are: Start, Stop, Continue, Clock, ActiveSensing and SystemReset.
*/
void MIDI_Class::sendRealTime(kMIDIType Type) {
switch (Type) {
case TuneRequest: // Not really real-time, but one byte anyway.
case Clock:
case Start:
case Stop:
case Continue:
case ActiveSensing:
case SystemReset:
USE_SERIAL_PORT.write((byte)Type);
break;
default:
// Invalid Real Time marker
break;
}
#if USE_RUNNING_STATUS
mRunningStatus_TX = InvalidType;
#endif
}
#endif // COMPILE_MIDI_OUT
#if COMPILE_MIDI_IN
/*! Read a MIDI message from the serial port using the main input channel (see setInputChannel() for reference). \n
Returned value: true if any valid message has been stored in the structure, false if not.
A valid message is a message that matches the input channel. \n\n
If the Thru is enabled and the messages matches the filter, it is sent back on the MIDI output.
*/
bool MIDI_Class::read() {
return read(mInputChannel);
}
/*! Reading/thru-ing method, the same as read() with a given input channel to read on. */
bool MIDI_Class::read(const byte inChannel) {
if (inChannel >= MIDI_CHANNEL_OFF) return false; // MIDI Input disabled.
if (parse(inChannel)) {
if (input_filter(inChannel)) {
#if (COMPILE_MIDI_OUT && COMPILE_MIDI_THRU)
thru_filter(inChannel);
#endif
#if USE_CALLBACKS
launchCallback();
#endif
return true;
}
}
return false;
}
// Private method: MIDI parser
bool MIDI_Class::parse(byte inChannel) {
// If the buffer is full -> Don't Panic! Call the Vogons to destroy it.
if (USE_SERIAL_PORT.available() == 128) {
USE_SERIAL_PORT.flush();
}
if (USE_SERIAL_PORT.available() <= 0) {
// No data available.
return false;
}
else {
/* Parsing algorithm:
Get a byte from the serial buffer.
* If there is no pending message to be recomposed, start a new one.
- Find type and channel (if pertinent)
- Look for other bytes in buffer, call parser recursively, until the message is assembled or the buffer is empty.
* Else, add the extracted byte to the pending message, and check validity. When the message is done, store it.
*/
byte extracted = USE_SERIAL_PORT.read();
if (mPendingMessageIndex == 0) { // Start a new pending message
mPendingMessage[0] = extracted;
// Check for running status first
switch (getTypeFromStatusByte(mRunningStatus_RX)) {
// Only these types allow Running Status:
case NoteOff:
case NoteOn:
case AfterTouchPoly:
case ControlChange:
case ProgramChange:
case AfterTouchChannel:
case PitchBend:
// If the status byte is not received, prepend it to the pending message
if (extracted < 0x80) {
mPendingMessage[0] = mRunningStatus_RX;
mPendingMessage[1] = extracted;
mPendingMessageIndex = 1;
}
// Else: well, we received another status byte, so the running status does not apply here.
// It will be updated upon completion of this message.
break;
default:
// No running status
break;
}
switch (getTypeFromStatusByte(mPendingMessage[0])) {
// 1 byte messages
case Start:
case Continue:
case Stop:
case Clock:
case ActiveSensing:
case SystemReset:
case TuneRequest:
// Handle the message type directly here.
mMessage.type = getTypeFromStatusByte(mPendingMessage[0]);
mMessage.channel = 0;
mMessage.data1 = 0;
mMessage.data2 = 0;
mMessage.valid = true;
reset_input_attributes();
return true;
break;
// 2 bytes messages
case ProgramChange:
case AfterTouchChannel:
case TimeCodeQuarterFrame:
case SongSelect:
mPendingMessageExpectedLenght = 2;
break;
// 3 bytes messages
case NoteOn:
case NoteOff:
case ControlChange:
case PitchBend:
case AfterTouchPoly:
case SongPosition:
mPendingMessageExpectedLenght = 3;
break;
case SystemExclusive:
mPendingMessageExpectedLenght = MIDI_SYSEX_ARRAY_SIZE; // As the message can be any lenght between 3 and MIDI_SYSEX_ARRAY_SIZE bytes
mRunningStatus_RX = InvalidType;
break;
case InvalidType:
default:
// This is obviously wrong. Let's get the hell out'a here.
reset_input_attributes();
return false;
break;
}
// Then update the index of the pending message.
mPendingMessageIndex++;
// And call the parser, again.
return parse(inChannel);
}
else {
// First, test if this is a status byte
if (extracted >= 0x80) {
// Reception of status bytes in the middle of an uncompleted message
// are allowed only for interleaved Real Time message or EOX
switch (extracted) {
case Clock:
case Start:
case Continue:
case Stop:
case ActiveSensing:
case SystemReset:
/*
This is tricky. Here we will have to extract the one-byte message,
pass it to the structure for being read outside the MIDI class,
and recompose the message it was interleaved into.
Oh, and without killing the running status..
This is done by leaving the pending message as is, it will be completed on next calls.
*/
mMessage.type = (kMIDIType)extracted;
mMessage.data1 = 0;
mMessage.data2 = 0;
mMessage.channel = 0;
mMessage.valid = true;
return true;
break;
// End of Exclusive
case 0xF7:
if (getTypeFromStatusByte(mPendingMessage[0]) == SystemExclusive) {
// Store System Exclusive array in midimsg structure
for (byte i=0;i<MIDI_SYSEX_ARRAY_SIZE;i++) {
mMessage.sysex_array[i] = mPendingMessage[i];
}
mMessage.type = SystemExclusive;
mMessage.data1 = mPendingMessageIndex+1; // Get length
mMessage.data2 = 0;
mMessage.channel = 0;
mMessage.valid = true;
reset_input_attributes();
return true;
}
else {
// Well well well.. error.
reset_input_attributes();
return false;
}
break;
default:
break;
}
}
// Add extracted data byte to pending message
mPendingMessage[mPendingMessageIndex] = extracted;
// Now we are going to check if we have reached the end of the message
if (mPendingMessageIndex >= (mPendingMessageExpectedLenght-1)) {
// "FML" case: fall down here with an overflown SysEx..
// This means we received the last possible data byte that can fit the buffer.
// If this happens, try increasing MIDI_SYSEX_ARRAY_SIZE.
if (getTypeFromStatusByte(mPendingMessage[0]) == SystemExclusive) {
reset_input_attributes();
return false;
}
mMessage.type = getTypeFromStatusByte(mPendingMessage[0]);
mMessage.channel = (mPendingMessage[0] & 0x0F)+1; // Don't check if it is a Channel Message
mMessage.data1 = mPendingMessage[1];
// Save data2 only if applicable
if (mPendingMessageExpectedLenght == 3) mMessage.data2 = mPendingMessage[2];
else mMessage.data2 = 0;
// Reset local variables
mPendingMessageIndex = 0;
mPendingMessageExpectedLenght = 0;
mMessage.valid = true;
// Activate running status (if enabled for the received type)
switch (mMessage.type) {
case NoteOff:
case NoteOn:
case AfterTouchPoly:
case ControlChange:
case ProgramChange:
case AfterTouchChannel:
case PitchBend:
// Running status enabled: store it from received message
mRunningStatus_RX = mPendingMessage[0];
break;
default:
// No running status
mRunningStatus_RX = InvalidType;
break;
}
return true;
}
else {
// Then update the index of the pending message.
mPendingMessageIndex++;
// And call the parser, again.
return parse(inChannel);
}
}
}
// What are our chances to fall here?
return false;
}
// Private method: check if the received message is on the listened channel
bool MIDI_Class::input_filter(byte inChannel) {
// This method handles recognition of channel (to know if the message is destinated to the Arduino)
if (mMessage.type == InvalidType) return false;
// First, check if the received message is Channel
if (mMessage.type >= NoteOff && mMessage.type <= PitchBend) {
// Then we need to know if we listen to it
if ((mMessage.channel == mInputChannel) || (mInputChannel == MIDI_CHANNEL_OMNI)) {
return true;
}
else {
// We don't listen to this channel
return false;
}
}
else {
// System messages are always received
return true;
}
}
// Private method: reset input attributes
void MIDI_Class::reset_input_attributes() {
mPendingMessageIndex = 0;
mPendingMessageExpectedLenght = 0;
mRunningStatus_RX = InvalidType;
}
// Getters
/*! Getter method: access to the message type stored in the structure. \n Returns an enumerated type. */
kMIDIType MIDI_Class::getType() { return mMessage.type; }
/*! Getter method: access to the channel of the message stored in the structure. */
byte MIDI_Class::getChannel() { return mMessage.channel; }
/*! Getter method: access to the first data byte of the message stored in the structure. \n If the message is SysEx, the length of the array is stocked there. */
byte MIDI_Class::getData1() { return mMessage.data1; }
/*! Getter method: access to the second data byte of the message stored in the structure. */
byte MIDI_Class::getData2() { return mMessage.data2; }
/*! Getter method: access to the System Exclusive byte array. Array length is stocked in Data1. */
byte * MIDI_Class::getSysExArray() { return mMessage.sysex_array; }
/*! Check if a valid message is stored in the structure. */
bool MIDI_Class::check() { return mMessage.valid; }
// Setters
/*! Set the value for the input MIDI channel
\param Channel the channel value. Valid values are 1 to 16,
MIDI_CHANNEL_OMNI if you want to listen to all channels, and MIDI_CHANNEL_OFF to disable MIDI input.
*/
void MIDI_Class::setInputChannel(const byte Channel) { mInputChannel = Channel; }
#if USE_CALLBACKS
void MIDI_Class::setHandleNoteOff(void (*fptr)(byte ch, byte note, byte vel)) { mNoteOffCallback = fptr; }
void MIDI_Class::setHandleNoteOn(void (*fptr)(byte ch, byte note, byte vel)) { mNoteOnCallback = fptr; }
void MIDI_Class::setHandleAfterTouchPoly(void (*fptr)(byte ch, byte note, byte vel)) { mAfterTouchPolyCallback = fptr; }
void MIDI_Class::setHandleControlChange(void (*fptr)(byte ch, byte, byte)) { mControlChangeCallback = fptr; }
void MIDI_Class::setHandleProgramChange(void (*fptr)(byte ch, byte)) { mProgramChangeCallback = fptr; }
void MIDI_Class::setHandleAfterTouchChannel(void (*fptr)(byte ch, byte)) { mAfterTouchChannelCallback = fptr; }
void MIDI_Class::setHandlePitchBend(void (*fptr)(byte ch, word)) { mPitchBendCallback = fptr; }
void MIDI_Class::setHandleSystemExclusive(void (*fptr)(byte * array, byte size)) { mSystemExclusiveCallback = fptr; }
void MIDI_Class::setHandleTimeCodeQuarterFrame(void (*fptr)(byte data)) { mTimeCodeQuarterFrameCallback = fptr; }
void MIDI_Class::setHandleSongPosition(void (*fptr)(word beats)) { mSongPositionCallback = fptr; }
void MIDI_Class::setHandleSongSelect(void (*fptr)(byte song_number)) { mSongSelectCallback = fptr; }
void MIDI_Class::setHandleTuneRequest(void (*fptr)(void)) { mTuneRequestCallback = fptr; }
void MIDI_Class::setHandleClock(void (*fptr)(void)) { mClockCallback = fptr; }
void MIDI_Class::setHandleStart(void (*fptr)(void)) { mStartCallback = fptr; }
void MIDI_Class::setHandleContinue(void (*fptr)(void)) { mContinueCallback = fptr; }
void MIDI_Class::setHandleStop(void (*fptr)(void)) { mStopCallback = fptr; }
void MIDI_Class::setHandleActiveSensing(void (*fptr)(void)) { mActiveSensingCallback = fptr; }
void MIDI_Class::setHandleSystemReset(void (*fptr)(void)) { mSystemResetCallback = fptr; }
/*! Detach an external function from the given type.\n
Use this method to cancel the effects of connectCallback.
\param Type The type of message to unbind. When a message of this type is received, no function will be called.
*/
void MIDI_Class::disconnectCallbackFromType(kMIDIType Type) {
switch (Type) {
case NoteOff: mNoteOffCallback = NULL; break;
case NoteOn: mNoteOnCallback = NULL; break;
case AfterTouchPoly: mAfterTouchPolyCallback = NULL; break;
case ControlChange: mControlChangeCallback = NULL; break;
case ProgramChange: mProgramChangeCallback = NULL; break;
case AfterTouchChannel: mAfterTouchChannelCallback = NULL; break;
case PitchBend: mPitchBendCallback = NULL; break;
case SystemExclusive: mSystemExclusiveCallback = NULL; break;
case TimeCodeQuarterFrame: mTimeCodeQuarterFrameCallback = NULL; break;
case SongPosition: mSongPositionCallback = NULL; break;
case SongSelect: mSongSelectCallback = NULL; break;
case TuneRequest: mTuneRequestCallback = NULL; break;
case Clock: mClockCallback = NULL; break;
case Start: mStartCallback = NULL; break;
case Continue: mContinueCallback = NULL; break;
case Stop: mStopCallback = NULL; break;
case ActiveSensing: mActiveSensingCallback = NULL; break;
case SystemReset: mSystemResetCallback = NULL; break;
default:
break;
}
}
// Private - launch callback function based on received type.
void MIDI_Class::launchCallback() {
// The order is mixed to allow frequent messages to trigger their callback faster.
switch (mMessage.type) {
// Notes
case NoteOff: if (mNoteOffCallback != NULL) mNoteOffCallback(mMessage.channel,mMessage.data1,mMessage.data2); break;
case NoteOn: if (mNoteOnCallback != NULL) mNoteOnCallback(mMessage.channel,mMessage.data1,mMessage.data2); break;
// Real-time messages
case Clock: if (mClockCallback != NULL) mClockCallback(); break;
case Start: if (mStartCallback != NULL) mStartCallback(); break;
case Continue: if (mContinueCallback != NULL) mContinueCallback(); break;
case Stop: if (mStopCallback != NULL) mStopCallback(); break;
case ActiveSensing: if (mActiveSensingCallback != NULL) mActiveSensingCallback(); break;
// Continuous controllers
case ControlChange: if (mControlChangeCallback != NULL) mControlChangeCallback(mMessage.channel,mMessage.data1,mMessage.data2); break;
case PitchBend: if (mPitchBendCallback != NULL) mPitchBendCallback(mMessage.channel,(mMessage.data1 & 0x7F) | ((mMessage.data2 & 0x7F)<< 7)); break;
case AfterTouchPoly: if (mAfterTouchPolyCallback != NULL) mAfterTouchPolyCallback(mMessage.channel,mMessage.data1,mMessage.data2); break;
case AfterTouchChannel: if (mAfterTouchChannelCallback != NULL) mAfterTouchChannelCallback(mMessage.channel,mMessage.data1); break;
case ProgramChange: if (mProgramChangeCallback != NULL) mProgramChangeCallback(mMessage.channel,mMessage.data1); break;
case SystemExclusive: if (mSystemExclusiveCallback != NULL) mSystemExclusiveCallback(mMessage.sysex_array,mMessage.data1); break;
// Occasional messages
case TimeCodeQuarterFrame: if (mTimeCodeQuarterFrameCallback != NULL) mTimeCodeQuarterFrameCallback(mMessage.data1); break;
case SongPosition: if (mSongPositionCallback != NULL) mSongPositionCallback((mMessage.data1 & 0x7F) | ((mMessage.data2 & 0x7F)<< 7)); break;
case SongSelect: if (mSongSelectCallback != NULL) mSongSelectCallback(mMessage.data1); break;
case TuneRequest: if (mTuneRequestCallback != NULL) mTuneRequestCallback(); break;
case SystemReset: if (mSystemResetCallback != NULL) mSystemResetCallback(); break;
case InvalidType:
default:
break;
}
}
#endif // USE_CALLBACKS
#endif // COMPILE_MIDI_IN
#if (COMPILE_MIDI_IN && COMPILE_MIDI_OUT && COMPILE_MIDI_THRU) // Thru
/*! Set the filter for thru mirroring
\param inThruFilterMode a filter mode
See kThruFilterMode for detailed description.
*/
void MIDI_Class::setThruFilterMode(kThruFilterMode inThruFilterMode) {
mThruFilterMode = inThruFilterMode;
if (mThruFilterMode != Off) mThruActivated = true;
else mThruActivated = false;
}
/*! Set the filter for thru mirroring
\param inThruFilterMode a filter mode
See kThruFilterMode for detailed description. \n
This method uses a byte parameter and is for compatibility only, please use kThruFilterMode for future programs.
*/
void MIDI_Class::setThruFilterMode(byte inThruFilterMode) {
mThruFilterMode = (kThruFilterMode)inThruFilterMode;
if (mThruFilterMode != Off) mThruActivated = true;
else mThruActivated = false;
}
/*! Setter method: turn message mirroring on. */
void MIDI_Class::turnThruOn(kThruFilterMode inThruFilterMode) {
mThruActivated = true;
mThruFilterMode = inThruFilterMode;
}
/*! Setter method: turn message mirroring off. */
void MIDI_Class::turnThruOff() {
mThruActivated = false;
mThruFilterMode = Off;
}
// This method is called upon reception of a message and takes care of Thru filtering and sending.
void MIDI_Class::thru_filter(byte inChannel) {
/*
This method handles Soft-Thru filtering.
Soft-Thru filtering:
- All system messages (System Exclusive, Common and Real Time) are passed to output unless filter is set to Off
- Channel messages are passed to the output whether their channel is matching the input channel and the filter setting
*/
#if TEENSY_SUPPORT && TEENSY_MIDI_TO_USB
// Pass the message to the USB side if enabled
#endif
// If the feature is disabled, don't do anything.
if (!mThruActivated || (mThruFilterMode == Off)) return;
// First, check if the received message is Channel
if (mMessage.type >= NoteOff && mMessage.type <= PitchBend) {
bool filter_condition = ((mMessage.channel == mInputChannel) || (mInputChannel == MIDI_CHANNEL_OMNI));
// Now let's pass it to the output
switch (mThruFilterMode) {
case Full:
send(mMessage.type,mMessage.data1,mMessage.data2,mMessage.channel);
return;
break;
case SameChannel:
if (filter_condition) {
send(mMessage.type,mMessage.data1,mMessage.data2,mMessage.channel);
return;
}
break;
case DifferentChannel:
if (!filter_condition) {
send(mMessage.type,mMessage.data1,mMessage.data2,mMessage.channel);
return;
}
case Off:
// Do nothing
break;
default:
break;
}
}
else {
// Send the message to the output
if (mThruFilterMode != Off) {
switch (mMessage.type) {
// Real Time and 1 byte
case Clock:
case Start:
case Stop:
case Continue:
case ActiveSensing:
case SystemReset:
case TuneRequest:
sendRealTime(mMessage.type);
return;
break;
case SystemExclusive:
// Send SysEx (0xF0 and 0xF7 are included in the buffer)
sendSysEx(mMessage.data1,mMessage.sysex_array,true);
return;
break;
case SongSelect:
sendSongSelect(mMessage.data1); // TODO: check this
return;
break;
case SongPosition:
sendSongPosition(mMessage.data1 | ((unsigned)mMessage.data2<<7)); // TODO: check this
return;
break;
case TimeCodeQuarterFrame:
sendTimeCodeQuarterFrame(mMessage.data1,mMessage.data2); // TODO: check this
return;
break;
default:
break;
}
}
}
}
#endif // Thru

View File

@ -0,0 +1,302 @@
/*!
* @file usb_midi.h
* Project Teensy MIDI Core
* @brief MIDI Library for Teensy - USB side
* Version 3.1
* @author Francois Best
* @date 28/04/11
* License GPL Forty Seven Effects - 2011
*/
#ifndef _TEENSY_LIB_MIDI_USB_FSE_H_
#define _TEENSY_LIB_MIDI_USB_FSE_H_
#include <inttypes.h>
/*
###############################################################
# #
# CONFIGURATION AREA #
# #
# Here are a few settings you can change to customize #
# the library for your own project. You can for example #
# choose to compile only parts of it so you gain flash #
# space and optimise the speed of your sketch. #
# #
###############################################################
*/
#define CONVERT_USB_TO_MIDI 1 // Set this to 1 to forward incoming messages on the USB MIDI to the UART.
#define CONVERT_MIDI_TO_USB 1 // Set this to 1 to forward incoming messages on the UART to the USB MIDI.
#define COMPILE_MIDI_IN 1 // Set this setting to 1 to use the MIDI input.
#define COMPILE_MIDI_OUT 1 // Set this setting to 1 to use the MIDI output.
#define COMPILE_MIDI_THRU 1 // Set this setting to 1 to use the MIDI Soft Thru feature
// Please note that the Thru will work only when both COMPILE_MIDI_IN and COMPILE_MIDI_OUT set to 1.
#define USE_RUNNING_STATUS 1 // Running status enables short messages when sending multiple values
// of the same type and channel.
// Set to 0 if you have troubles with controlling you hardware.
#define USE_CALLBACKS 1 // Set this to 1 if you want to use callback handlers (to bind your functions to the library).
// To use the callbacks, you need to have COMPILE_MIDI_IN set to 1
// END OF CONFIGURATION AREA
// (do not modify anything under this line unless you know what you are doing)
#define MIDI_BAUDRATE 31250
#define MIDI_CHANNEL_OMNI 0
#define MIDI_CHANNEL_OFF 17 // and over
#define MIDI_SYSEX_ARRAY_SIZE 60
/*! Type definition for practical use (because "unsigned char" is a bit long to write.. )*/
typedef uint8_t byte;
typedef uint16_t word;
/*! Enumeration of MIDI types */
enum kMIDIType {
NoteOff = 0x80, // Note Off
NoteOn = 0x90, // Note On
AfterTouchPoly = 0xA0, // Polyphonic AfterTouch
ControlChange = 0xB0, // Control Change / Channel Mode
ProgramChange = 0xC0, // Program Change
AfterTouchChannel = 0xD0, // Channel (monophonic) AfterTouch
PitchBend = 0xE0, // Pitch Bend
SystemExclusive = 0xF0, // System Exclusive
TimeCodeQuarterFrame = 0xF1, // System Common - MIDI Time Code Quarter Frame
SongPosition = 0xF2, // System Common - Song Position Pointer
SongSelect = 0xF3, // System Common - Song Select
TuneRequest = 0xF6, // System Common - Tune Request
Clock = 0xF8, // System Real Time - Timing Clock
Start = 0xFA, // System Real Time - Start
Continue = 0xFB, // System Real Time - Continue
Stop = 0xFC, // System Real Time - Stop
ActiveSensing = 0xFE, // System Real Time - Active Sensing
SystemReset = 0xFF, // System Real Time - System Reset
InvalidType = 0x00 // For notifying errors
};
/*! Enumeration of Thru filter modes */
enum kThruFilterMode {
Off = 0, // Thru disabled (nothing passes through).
Full = 1, // Fully enabled Thru (every incoming message is sent back).
SameChannel = 2, // Only the messages on the Input Channel will be sent back.
DifferentChannel = 3 // All the messages but the ones on the Input Channel will be sent back.
};
/*! The midimsg structure contains decoded data of a MIDI message read from the serial port with read() or thru(). \n */
struct midimsg {
/*! The MIDI channel on which the message was recieved. \n Value goes from 1 to 16. */
byte channel;
/*! The type of the message (see the define section for types reference) */
kMIDIType type;
/*! The first data byte.\n Value goes from 0 to 127.\n If the message is SysEx, this byte contains the array length. */
byte data1;
/*! The second data byte. If the message is only 2 bytes long, this one is null.\n Value goes from 0 to 127. */
byte data2;
/*! System Exclusive dedicated byte array. \n Array length is stocked in data1. */
byte sysex_array[MIDI_SYSEX_ARRAY_SIZE];
/*! This boolean indicates if the message is valid or not. There is no channel consideration here, validity means the message respects the MIDI norm. */
bool valid;
};
/*! The main class for MIDI handling.
See member descriptions to know how to use it,
or check out the examples supplied with the library.
*/
class MIDI_Class {
public:
// Constructor and Destructor
MIDI_Class();
~MIDI_Class();
void begin(const byte inChannel = 1);
/* ####### OUTPUT COMPILATION BLOCK ####### */
#if COMPILE_MIDI_OUT
public:
void sendNoteOn(byte NoteNumber,byte Velocity,byte Channel);
void sendNoteOff(byte NoteNumber,byte Velocity,byte Channel);
void sendProgramChange(byte ProgramNumber,byte Channel);
void sendControlChange(byte ControlNumber, byte ControlValue,byte Channel);
void sendPitchBend(unsigned int PitchValue,byte Channel);
void sendPitchBend(double PitchValue,byte Channel);
void sendPolyPressure(byte NoteNumber,byte Pressure,byte Channel);
void sendAfterTouch(byte Pressure,byte Channel);
void sendSysEx(byte length, byte * array,bool ArrayContainsBoundaries = false);
void sendTimeCodeQuarterFrame(byte TypeNibble, byte ValuesNibble);
void sendTimeCodeQuarterFrame(byte data);
void sendSongPosition(unsigned int Beats);
void sendSongSelect(byte SongNumber);
void sendTuneRequest();
void sendRealTime(kMIDIType Type);
private:
const byte genstatus(const kMIDIType inType,const byte inChannel);
void send(kMIDIType type, byte param1, byte param2, byte channel);
// Attributes
#if USE_RUNNING_STATUS
byte mRunningStatus_TX;
#endif // USE_RUNNING_STATUS
#endif // COMPILE_MIDI_OUT
/* ####### INPUT COMPILATION BLOCK ####### */
#if COMPILE_MIDI_IN
public:
bool read();
bool read(const byte Channel);
// Getters
kMIDIType getType();
byte getChannel();
byte getData1();
byte getData2();
byte * getSysExArray();
bool check();
byte getInputChannel() { return mInputChannel; }
// Setters
void setInputChannel(const byte Channel);
#if USE_CALLBACKS
void setHandleNoteOff(void (*fptr)(byte ch, byte note, byte vel));
void setHandleNoteOn(void (*fptr)(byte ch, byte note, byte vel));
void setHandleAfterTouchPoly(void (*fptr)(byte ch, byte note, byte vel));
void setHandleControlChange(void (*fptr)(byte ch, byte, byte));
void setHandleProgramChange(void (*fptr)(byte ch, byte));
void setHandleAfterTouchChannel(void (*fptr)(byte ch, byte));
void setHandlePitchBend(void (*fptr)(byte ch, word));
void setHandleSystemExclusive(void (*fptr)(byte * array, byte size));
void setHandleTimeCodeQuarterFrame(void (*fptr)(byte data));
void setHandleSongPosition(void (*fptr)(word beats));
void setHandleSongSelect(void (*fptr)(byte song_number));
void setHandleTuneRequest(void (*fptr)(void));
void setHandleClock(void (*fptr)(void));
void setHandleStart(void (*fptr)(void));
void setHandleContinue(void (*fptr)(void));
void setHandleStop(void (*fptr)(void));
void setHandleActiveSensing(void (*fptr)(void));
void setHandleSystemReset(void (*fptr)(void));
void disconnectCallbackFromType(kMIDIType Type);
#endif // USE_CALLBACKS
private:
inline const kMIDIType getTypeFromStatusByte(const byte inStatus) {
if ((inStatus < 0x80)
|| (inStatus == 0xF4)
|| (inStatus == 0xF5)
|| (inStatus == 0xF9)
|| (inStatus == 0xFD)) return InvalidType; // data bytes and undefined.
if (inStatus < 0xF0) return (kMIDIType)(inStatus & 0xF0); // Channel message, remove channel nibble.
else return (kMIDIType)inStatus;
}
bool input_filter(byte inChannel);
bool parse(byte inChannel);
void reset_input_attributes();
// Attributes
byte mRunningStatus_RX;
byte mInputChannel;
byte mPendingMessage[MIDI_SYSEX_ARRAY_SIZE];
byte mPendingMessageExpectedLenght;
byte mPendingMessageIndex;
midimsg mMessage;
#if USE_CALLBACKS
void launchCallback();
void (*mNoteOffCallback)(byte ch, byte note, byte vel);
void (*mNoteOnCallback)(byte ch, byte note, byte vel);
void (*mAfterTouchPolyCallback)(byte ch, byte note, byte vel);
void (*mControlChangeCallback)(byte ch, byte, byte);
void (*mProgramChangeCallback)(byte ch, byte);
void (*mAfterTouchChannelCallback)(byte ch, byte);
void (*mPitchBendCallback)(byte ch, word);
void (*mSystemExclusiveCallback)(byte * array, byte size);
void (*mTimeCodeQuarterFrameCallback)(byte data);
void (*mSongPositionCallback)(word beats);
void (*mSongSelectCallback)(byte song_number);
void (*mTuneRequestCallback)(void);
void (*mClockCallback)(void);
void (*mStartCallback)(void);
void (*mContinueCallback)(void);
void (*mStopCallback)(void);
void (*mActiveSensingCallback)(void);
void (*mSystemResetCallback)(void);
#endif // USE_CALLBACKS
#endif // COMPILE_MIDI_IN
/* ####### THRU COMPILATION BLOCK ####### */
#if (COMPILE_MIDI_IN && COMPILE_MIDI_OUT && COMPILE_MIDI_THRU) // Thru
public:
// Getters
kThruFilterMode getFilterMode() { return mThruFilterMode; }
bool getThruState() { return mThruActivated; }
// Setters
void turnThruOn(kThruFilterMode inThruFilterMode = Full);
void turnThruOff();
void setThruFilterMode(const byte inThruFilterMode); // For compatibility only, avoid in future programs.
void setThruFilterMode(const kThruFilterMode inThruFilterMode);
private:
void thru_filter(byte inChannel);
bool mThruActivated;
kThruFilterMode mThruFilterMode;
#endif // Thru
};
extern MIDI_Class usbMIDI;
#endif // _TEENSY_LIB_MIDI_USB_H_

View File

@ -0,0 +1,84 @@
#ifndef usb_serial_h__
#define usb_serial_h__
#include <stdint.h>
#ifdef __cplusplus
extern "C"{
#endif
/**************************************************************************
*
* Configurable Options
*
**************************************************************************/
#define VENDOR_ID 0x16C0
#define PRODUCT_ID 0x0485
#define TRANSMIT_FLUSH_TIMEOUT 4 /* in milliseconds */
#define TRANSMIT_TIMEOUT 25 /* in milliseconds */
/**************************************************************************
*
* Endpoint Buffer Configuration
*
**************************************************************************/
// These buffer sizes are best for most applications, but perhaps if you
// want more buffering on some endpoint at the expense of others, this
// is where you can make such changes. The AT90USB162 has only 176 bytes
// of DPRAM (USB buffers) and only endpoints 3 & 4 can double buffer.
// 0: control
// 1: debug IN
// 2: debug OUT
// 3: midi IN
// 4: midi OUT
#if defined(__AVR_ATmega32U4__) || defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB1286__)
#define STR_PRODUCT L"Teensy MIDI"
#define ENDPOINT0_SIZE 64
#define DEBUG_INTERFACE 1
#define DEBUG_TX_ENDPOINT 1
#define DEBUG_TX_SIZE 64
#define DEBUG_TX_BUFFER EP_DOUBLE_BUFFER
#define DEBUG_TX_INTERVAL 1
#define DEBUG_RX_ENDPOINT 2
#define DEBUG_RX_SIZE 32
#define DEBUG_RX_BUFFER EP_DOUBLE_BUFFER
#define DEBUG_RX_INTERVAL 2
#define MIDI_INTERFACE 0
#define MIDI_TX_ENDPOINT 3
#define MIDI_TX_SIZE 64
#define MIDI_TX_BUFFER EP_DOUBLE_BUFFER
#define MIDI_RX_ENDPOINT 4
#define MIDI_RX_SIZE 64
#define MIDI_RX_BUFFER EP_DOUBLE_BUFFER
#define NUM_ENDPOINTS 5
#define NUM_INTERFACE 2
#endif
// setup
void usb_init(void); // initialize everything
void usb_shutdown(void); // shut off USB
// variables
extern volatile uint8_t usb_configuration;
extern volatile uint8_t usb_suspended;
extern volatile uint8_t debug_flush_timer;
#ifdef __cplusplus
} // extern "C"
#endif
#endif