Added Teensy usb core sources.
This commit is contained in:
parent
561d4bb015
commit
522dcdf095
|
|
@ -0,0 +1,4 @@
|
|||
#define CORE_TEENSY_HID
|
||||
#define CORE_TEENSY_MIDI
|
||||
#define CORE_TEENSY_KEYBOARD
|
||||
//#define CORE_TEENSY_JOYSTICK
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -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();
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
@ -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_
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue