Modified firmware v1
This commit is contained in:
864
USBCore.cpp
Normal file
864
USBCore.cpp
Normal file
@@ -0,0 +1,864 @@
|
|||||||
|
|
||||||
|
|
||||||
|
/* Copyright (c) 2010, Peter Barrett
|
||||||
|
** Sleep/Wakeup support added by Michael Dreher
|
||||||
|
**
|
||||||
|
** Permission to use, copy, modify, and/or distribute this software for
|
||||||
|
** any purpose with or without fee is hereby granted, provided that the
|
||||||
|
** above copyright notice and this permission notice appear in all copies.
|
||||||
|
**
|
||||||
|
** THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||||
|
** WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||||
|
** WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
|
||||||
|
** BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
|
||||||
|
** OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
|
||||||
|
** WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||||
|
** ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
|
||||||
|
** SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//We include our own version of this core USB file with WARBL because it has been modified to remove the CDC serial class to make the device fully class-compliant.
|
||||||
|
//The line below will remove serial communication to make the device fully class-commpliant and not require drivers on any OS.
|
||||||
|
//It can be commented out to use Serial.print and to make it unnecessary to double-click the reset button for programming.
|
||||||
|
|
||||||
|
|
||||||
|
#define CDCCON_DISABLE
|
||||||
|
|
||||||
|
|
||||||
|
#include "USBAPI.h"
|
||||||
|
#include "PluggableUSB.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#if defined(USBCON)
|
||||||
|
|
||||||
|
/** Pulse generation counters to keep track of the number of milliseconds remaining for each pulse type */
|
||||||
|
#define TX_RX_LED_PULSE_MS 100
|
||||||
|
volatile u8 TxLEDPulse; /**< Milliseconds remaining for data Tx LED pulse */
|
||||||
|
volatile u8 RxLEDPulse; /**< Milliseconds remaining for data Rx LED pulse */
|
||||||
|
|
||||||
|
//==================================================================
|
||||||
|
//==================================================================
|
||||||
|
|
||||||
|
extern const u16 STRING_LANGUAGE[] PROGMEM;
|
||||||
|
extern const u8 STRING_PRODUCT[] PROGMEM;
|
||||||
|
extern const u8 STRING_MANUFACTURER[] PROGMEM;
|
||||||
|
extern const DeviceDescriptor USB_DeviceDescriptorIAD PROGMEM;
|
||||||
|
|
||||||
|
|
||||||
|
const u16 STRING_LANGUAGE[2] = {
|
||||||
|
(3<<8) | (2+2),
|
||||||
|
0x0409 // English
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifndef USB_PRODUCT
|
||||||
|
// If no product is provided, use USB IO Board
|
||||||
|
#define USB_PRODUCT "USB IO Board"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const u8 STRING_PRODUCT[] PROGMEM = USB_PRODUCT;
|
||||||
|
|
||||||
|
|
||||||
|
# define USB_MANUFACTURER "Mowry Stringed Instruments"
|
||||||
|
|
||||||
|
|
||||||
|
const u8 STRING_MANUFACTURER[] PROGMEM = USB_MANUFACTURER;
|
||||||
|
|
||||||
|
|
||||||
|
#define DEVICE_CLASS 0x02
|
||||||
|
|
||||||
|
|
||||||
|
const DeviceDescriptor USB_DeviceDescriptorIAD =
|
||||||
|
D_DEVICE(0xEF,0x02,0x01,64,USB_VID,USB_PID,0x100,IMANUFACTURER,IPRODUCT,ISERIAL,1);
|
||||||
|
|
||||||
|
|
||||||
|
//==================================================================
|
||||||
|
//==================================================================
|
||||||
|
|
||||||
|
volatile u8 _usbConfiguration = 0;
|
||||||
|
volatile u8 _usbCurrentStatus = 0; // meaning of bits see usb_20.pdf, Figure 9-4. Information Returned by a GetStatus() Request to a Device
|
||||||
|
volatile u8 _usbSuspendState = 0; // copy of UDINT to check SUSPI and WAKEUPI bits
|
||||||
|
|
||||||
|
static inline void WaitIN(void)
|
||||||
|
{
|
||||||
|
while (!(UEINTX & (1<<TXINI)))
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void ClearIN(void)
|
||||||
|
{
|
||||||
|
UEINTX = ~(1<<TXINI);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void WaitOUT(void)
|
||||||
|
{
|
||||||
|
while (!(UEINTX & (1<<RXOUTI)))
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline u8 WaitForINOrOUT()
|
||||||
|
{
|
||||||
|
while (!(UEINTX & ((1<<TXINI)|(1<<RXOUTI))))
|
||||||
|
;
|
||||||
|
return (UEINTX & (1<<RXOUTI)) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void ClearOUT(void)
|
||||||
|
{
|
||||||
|
UEINTX = ~(1<<RXOUTI);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void Recv(volatile u8* data, u8 count)
|
||||||
|
{
|
||||||
|
while (count--)
|
||||||
|
*data++ = UEDATX;
|
||||||
|
|
||||||
|
//RXLED1; // light the RX LED //not used for WARBL
|
||||||
|
//RxLEDPulse = TX_RX_LED_PULSE_MS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline u8 Recv8()
|
||||||
|
{
|
||||||
|
//RXLED1; // light the RX LED //not used for WARBL
|
||||||
|
//RxLEDPulse = TX_RX_LED_PULSE_MS;
|
||||||
|
|
||||||
|
return UEDATX;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void Send8(u8 d)
|
||||||
|
{
|
||||||
|
UEDATX = d;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void SetEP(u8 ep)
|
||||||
|
{
|
||||||
|
UENUM = ep;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline u8 FifoByteCount()
|
||||||
|
{
|
||||||
|
return UEBCLX;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline u8 ReceivedSetupInt()
|
||||||
|
{
|
||||||
|
return UEINTX & (1<<RXSTPI);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void ClearSetupInt()
|
||||||
|
{
|
||||||
|
UEINTX = ~((1<<RXSTPI) | (1<<RXOUTI) | (1<<TXINI));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void Stall()
|
||||||
|
{
|
||||||
|
UECONX = (1<<STALLRQ) | (1<<EPEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline u8 ReadWriteAllowed()
|
||||||
|
{
|
||||||
|
return UEINTX & (1<<RWAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline u8 Stalled()
|
||||||
|
{
|
||||||
|
return UEINTX & (1<<STALLEDI);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline u8 FifoFree()
|
||||||
|
{
|
||||||
|
return UEINTX & (1<<FIFOCON);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void ReleaseRX()
|
||||||
|
{
|
||||||
|
UEINTX = 0x6B; // FIFOCON=0 NAKINI=1 RWAL=1 NAKOUTI=0 RXSTPI=1 RXOUTI=0 STALLEDI=1 TXINI=1
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void ReleaseTX()
|
||||||
|
{
|
||||||
|
UEINTX = 0x3A; // FIFOCON=0 NAKINI=0 RWAL=1 NAKOUTI=1 RXSTPI=1 RXOUTI=0 STALLEDI=1 TXINI=0
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline u8 FrameNumber()
|
||||||
|
{
|
||||||
|
return UDFNUML;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==================================================================
|
||||||
|
//==================================================================
|
||||||
|
|
||||||
|
u8 USBGetConfiguration(void)
|
||||||
|
{
|
||||||
|
return _usbConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define USB_RECV_TIMEOUT
|
||||||
|
class LockEP
|
||||||
|
{
|
||||||
|
u8 _sreg;
|
||||||
|
public:
|
||||||
|
LockEP(u8 ep) : _sreg(SREG)
|
||||||
|
{
|
||||||
|
cli();
|
||||||
|
SetEP(ep & 7);
|
||||||
|
}
|
||||||
|
~LockEP()
|
||||||
|
{
|
||||||
|
SREG = _sreg;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Number of bytes, assumes a rx endpoint
|
||||||
|
u8 USB_Available(u8 ep)
|
||||||
|
{
|
||||||
|
LockEP lock(ep);
|
||||||
|
return FifoByteCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non Blocking receive
|
||||||
|
// Return number of bytes read
|
||||||
|
int USB_Recv(u8 ep, void* d, int len)
|
||||||
|
{
|
||||||
|
if (!_usbConfiguration || len < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
LockEP lock(ep);
|
||||||
|
u8 n = FifoByteCount();
|
||||||
|
len = min(n,len);
|
||||||
|
n = len;
|
||||||
|
u8* dst = (u8*)d;
|
||||||
|
while (n--)
|
||||||
|
*dst++ = Recv8();
|
||||||
|
if (len && !FifoByteCount()) // release empty buffer
|
||||||
|
ReleaseRX();
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recv 1 byte if ready
|
||||||
|
int USB_Recv(u8 ep)
|
||||||
|
{
|
||||||
|
u8 c;
|
||||||
|
if (USB_Recv(ep,&c,1) != 1)
|
||||||
|
return -1;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Space in send EP
|
||||||
|
u8 USB_SendSpace(u8 ep)
|
||||||
|
{
|
||||||
|
LockEP lock(ep);
|
||||||
|
if (!ReadWriteAllowed())
|
||||||
|
return 0;
|
||||||
|
return USB_EP_SIZE - FifoByteCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blocking Send of data to an endpoint
|
||||||
|
int USB_Send(u8 ep, const void* d, int len)
|
||||||
|
{
|
||||||
|
if (!_usbConfiguration)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (_usbSuspendState & (1<<SUSPI)) {
|
||||||
|
//send a remote wakeup
|
||||||
|
UDCON |= (1 << RMWKUP);
|
||||||
|
}
|
||||||
|
|
||||||
|
int r = len;
|
||||||
|
const u8* data = (const u8*)d;
|
||||||
|
u8 timeout = 250; // 250ms timeout on send? TODO
|
||||||
|
bool sendZlp = false;
|
||||||
|
|
||||||
|
while (len || sendZlp)
|
||||||
|
{
|
||||||
|
u8 n = USB_SendSpace(ep);
|
||||||
|
if (n == 0)
|
||||||
|
{
|
||||||
|
if (!(--timeout))
|
||||||
|
return -1;
|
||||||
|
delay(1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n > len) {
|
||||||
|
n = len;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
LockEP lock(ep);
|
||||||
|
// Frame may have been released by the SOF interrupt handler
|
||||||
|
if (!ReadWriteAllowed())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
len -= n;
|
||||||
|
if (ep & TRANSFER_ZERO)
|
||||||
|
{
|
||||||
|
while (n--)
|
||||||
|
Send8(0);
|
||||||
|
}
|
||||||
|
else if (ep & TRANSFER_PGM)
|
||||||
|
{
|
||||||
|
while (n--)
|
||||||
|
Send8(pgm_read_byte(data++));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
while (n--)
|
||||||
|
Send8(*data++);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sendZlp) {
|
||||||
|
ReleaseTX();
|
||||||
|
sendZlp = false;
|
||||||
|
} else if (!ReadWriteAllowed()) { // ...release if buffer is full...
|
||||||
|
ReleaseTX();
|
||||||
|
if (len == 0) sendZlp = true;
|
||||||
|
} else if ((len == 0) && (ep & TRANSFER_RELEASE)) { // ...or if forced with TRANSFER_RELEASE
|
||||||
|
// XXX: TRANSFER_RELEASE is never used can be removed?
|
||||||
|
ReleaseTX();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//TXLED1; // light the TX LED
|
||||||
|
//TxLEDPulse = TX_RX_LED_PULSE_MS; //not used for WARBL
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
u8 _initEndpoints[USB_ENDPOINTS] =
|
||||||
|
{
|
||||||
|
0, // Control Endpoint
|
||||||
|
|
||||||
|
//#if !defined(CDCCON_DISABLE)
|
||||||
|
EP_TYPE_INTERRUPT_IN, // CDC_ENDPOINT_ACM
|
||||||
|
EP_TYPE_BULK_OUT, // CDC_ENDPOINT_OUT
|
||||||
|
EP_TYPE_BULK_IN, // CDC_ENDPOINT_IN
|
||||||
|
//#endif
|
||||||
|
// Following endpoints are automatically initialized to 0
|
||||||
|
};
|
||||||
|
|
||||||
|
#define EP_SINGLE_64 0x32 // EP0
|
||||||
|
#define EP_DOUBLE_64 0x36 // Other endpoints
|
||||||
|
#define EP_SINGLE_16 0x12
|
||||||
|
|
||||||
|
static
|
||||||
|
void InitEP(u8 index, u8 type, u8 size)
|
||||||
|
{
|
||||||
|
UENUM = index;
|
||||||
|
UECONX = (1<<EPEN);
|
||||||
|
UECFG0X = type;
|
||||||
|
UECFG1X = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
void InitEndpoints()
|
||||||
|
{
|
||||||
|
for (u8 i = 1; i < sizeof(_initEndpoints) && _initEndpoints[i] != 0; i++)
|
||||||
|
{
|
||||||
|
UENUM = i;
|
||||||
|
UECONX = (1<<EPEN);
|
||||||
|
UECFG0X = _initEndpoints[i];
|
||||||
|
#if USB_EP_SIZE == 16
|
||||||
|
UECFG1X = EP_SINGLE_16;
|
||||||
|
#elif USB_EP_SIZE == 64
|
||||||
|
UECFG1X = EP_DOUBLE_64;
|
||||||
|
#else
|
||||||
|
#error Unsupported value for USB_EP_SIZE
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
UERST = 0x7E; // And reset them
|
||||||
|
UERST = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle CLASS_INTERFACE requests
|
||||||
|
static
|
||||||
|
bool ClassInterfaceRequest(USBSetup& setup)
|
||||||
|
{
|
||||||
|
u8 i = setup.wIndex;
|
||||||
|
|
||||||
|
#if !defined(CDCCON_DISABLE)
|
||||||
|
if (CDC_ACM_INTERFACE == i)
|
||||||
|
return CDC_Setup(setup);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef PLUGGABLE_USB_ENABLED
|
||||||
|
return PluggableUSB().setup(setup);
|
||||||
|
#endif
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _cmark;
|
||||||
|
static int _cend;
|
||||||
|
void InitControl(int end)
|
||||||
|
{
|
||||||
|
SetEP(0);
|
||||||
|
_cmark = 0;
|
||||||
|
_cend = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
bool SendControl(u8 d)
|
||||||
|
{
|
||||||
|
if (_cmark < _cend)
|
||||||
|
{
|
||||||
|
if (!WaitForINOrOUT())
|
||||||
|
return false;
|
||||||
|
Send8(d);
|
||||||
|
if (!((_cmark + 1) & 0x3F))
|
||||||
|
ClearIN(); // Fifo is full, release this packet
|
||||||
|
}
|
||||||
|
_cmark++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clipped by _cmark/_cend
|
||||||
|
int USB_SendControl(u8 flags, const void* d, int len)
|
||||||
|
{
|
||||||
|
int sent = len;
|
||||||
|
const u8* data = (const u8*)d;
|
||||||
|
bool pgm = flags & TRANSFER_PGM;
|
||||||
|
while (len--)
|
||||||
|
{
|
||||||
|
u8 c = pgm ? pgm_read_byte(data++) : *data++;
|
||||||
|
if (!SendControl(c))
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return sent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a USB descriptor string. The string is stored in PROGMEM as a
|
||||||
|
// plain ASCII string but is sent out as UTF-16 with the correct 2-byte
|
||||||
|
// prefix
|
||||||
|
static bool USB_SendStringDescriptor(const u8*string_P, u8 string_len, uint8_t flags) {
|
||||||
|
SendControl(2 + string_len * 2);
|
||||||
|
SendControl(3);
|
||||||
|
bool pgm = flags & TRANSFER_PGM;
|
||||||
|
for(u8 i = 0; i < string_len; i++) {
|
||||||
|
bool r = SendControl(pgm ? pgm_read_byte(&string_P[i]) : string_P[i]);
|
||||||
|
r &= SendControl(0); // high byte
|
||||||
|
if(!r) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Does not timeout or cross fifo boundaries
|
||||||
|
int USB_RecvControl(void* d, int len)
|
||||||
|
{
|
||||||
|
auto length = len;
|
||||||
|
while(length)
|
||||||
|
{
|
||||||
|
// Dont receive more than the USB Control EP has to offer
|
||||||
|
// Use fixed 64 because control EP always have 64 bytes even on 16u2.
|
||||||
|
auto recvLength = length;
|
||||||
|
if(recvLength > 64){
|
||||||
|
recvLength = 64;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write data to fit to the end (not the beginning) of the array
|
||||||
|
WaitOUT();
|
||||||
|
Recv((u8*)d + len - length, recvLength);
|
||||||
|
ClearOUT();
|
||||||
|
length -= recvLength;
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u8 SendInterfaces()
|
||||||
|
{
|
||||||
|
u8 interfaces = 0;
|
||||||
|
|
||||||
|
#if !defined(CDCCON_DISABLE)
|
||||||
|
CDC_GetInterface(&interfaces);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef PLUGGABLE_USB_ENABLED
|
||||||
|
PluggableUSB().getInterface(&interfaces);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return interfaces;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct a dynamic configuration descriptor
|
||||||
|
// This really needs dynamic endpoint allocation etc
|
||||||
|
// TODO
|
||||||
|
static
|
||||||
|
bool SendConfiguration(int maxlen)
|
||||||
|
{
|
||||||
|
// Count and measure interfaces
|
||||||
|
InitControl(0);
|
||||||
|
u8 interfaces = SendInterfaces();
|
||||||
|
ConfigDescriptor config = D_CONFIG(_cmark + sizeof(ConfigDescriptor),interfaces);
|
||||||
|
|
||||||
|
// Now send them
|
||||||
|
InitControl(maxlen);
|
||||||
|
USB_SendControl(0,&config,sizeof(ConfigDescriptor));
|
||||||
|
SendInterfaces();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
bool SendDescriptor(USBSetup& setup)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
u8 t = setup.wValueH;
|
||||||
|
if (USB_CONFIGURATION_DESCRIPTOR_TYPE == t)
|
||||||
|
return SendConfiguration(setup.wLength);
|
||||||
|
|
||||||
|
InitControl(setup.wLength);
|
||||||
|
#ifdef PLUGGABLE_USB_ENABLED
|
||||||
|
ret = PluggableUSB().getDescriptor(setup);
|
||||||
|
if (ret != 0) {
|
||||||
|
return (ret > 0 ? true : false);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const u8* desc_addr = 0;
|
||||||
|
if (USB_DEVICE_DESCRIPTOR_TYPE == t)
|
||||||
|
{
|
||||||
|
desc_addr = (const u8*)&USB_DeviceDescriptorIAD;
|
||||||
|
}
|
||||||
|
else if (USB_STRING_DESCRIPTOR_TYPE == t)
|
||||||
|
{
|
||||||
|
if (setup.wValueL == 0) {
|
||||||
|
desc_addr = (const u8*)&STRING_LANGUAGE;
|
||||||
|
}
|
||||||
|
else if (setup.wValueL == IPRODUCT) {
|
||||||
|
return USB_SendStringDescriptor(STRING_PRODUCT, strlen(USB_PRODUCT), TRANSFER_PGM);
|
||||||
|
}
|
||||||
|
else if (setup.wValueL == IMANUFACTURER) {
|
||||||
|
return USB_SendStringDescriptor(STRING_MANUFACTURER, strlen(USB_MANUFACTURER), TRANSFER_PGM);
|
||||||
|
}
|
||||||
|
else if (setup.wValueL == ISERIAL) {
|
||||||
|
#ifdef PLUGGABLE_USB_ENABLED
|
||||||
|
char name[ISERIAL_MAX_LEN];
|
||||||
|
PluggableUSB().getShortName(name);
|
||||||
|
return USB_SendStringDescriptor((uint8_t*)name, strlen(name), 0);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (desc_addr == 0)
|
||||||
|
return false;
|
||||||
|
u8 desc_length = pgm_read_byte(desc_addr);
|
||||||
|
|
||||||
|
USB_SendControl(TRANSFER_PGM,desc_addr,desc_length);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Endpoint 0 interrupt
|
||||||
|
ISR(USB_COM_vect)
|
||||||
|
{
|
||||||
|
SetEP(0);
|
||||||
|
if (!ReceivedSetupInt())
|
||||||
|
return;
|
||||||
|
|
||||||
|
USBSetup setup;
|
||||||
|
Recv((u8*)&setup,8);
|
||||||
|
ClearSetupInt();
|
||||||
|
|
||||||
|
u8 requestType = setup.bmRequestType;
|
||||||
|
if (requestType & REQUEST_DEVICETOHOST)
|
||||||
|
WaitIN();
|
||||||
|
else
|
||||||
|
ClearIN();
|
||||||
|
|
||||||
|
bool ok = true;
|
||||||
|
if (REQUEST_STANDARD == (requestType & REQUEST_TYPE))
|
||||||
|
{
|
||||||
|
// Standard Requests
|
||||||
|
u8 r = setup.bRequest;
|
||||||
|
u16 wValue = setup.wValueL | (setup.wValueH << 8);
|
||||||
|
if (GET_STATUS == r)
|
||||||
|
{
|
||||||
|
if (requestType == (REQUEST_DEVICETOHOST | REQUEST_STANDARD | REQUEST_DEVICE))
|
||||||
|
{
|
||||||
|
Send8(_usbCurrentStatus);
|
||||||
|
Send8(0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO: handle the HALT state of an endpoint here
|
||||||
|
// see "Figure 9-6. Information Returned by a GetStatus() Request to an Endpoint" in usb_20.pdf for more information
|
||||||
|
Send8(0);
|
||||||
|
Send8(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (CLEAR_FEATURE == r)
|
||||||
|
{
|
||||||
|
if((requestType == (REQUEST_HOSTTODEVICE | REQUEST_STANDARD | REQUEST_DEVICE))
|
||||||
|
&& (wValue == DEVICE_REMOTE_WAKEUP))
|
||||||
|
{
|
||||||
|
_usbCurrentStatus &= ~FEATURE_REMOTE_WAKEUP_ENABLED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (SET_FEATURE == r)
|
||||||
|
{
|
||||||
|
if((requestType == (REQUEST_HOSTTODEVICE | REQUEST_STANDARD | REQUEST_DEVICE))
|
||||||
|
&& (wValue == DEVICE_REMOTE_WAKEUP))
|
||||||
|
{
|
||||||
|
_usbCurrentStatus |= FEATURE_REMOTE_WAKEUP_ENABLED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (SET_ADDRESS == r)
|
||||||
|
{
|
||||||
|
WaitIN();
|
||||||
|
UDADDR = setup.wValueL | (1<<ADDEN);
|
||||||
|
}
|
||||||
|
else if (GET_DESCRIPTOR == r)
|
||||||
|
{
|
||||||
|
ok = SendDescriptor(setup);
|
||||||
|
}
|
||||||
|
else if (SET_DESCRIPTOR == r)
|
||||||
|
{
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
else if (GET_CONFIGURATION == r)
|
||||||
|
{
|
||||||
|
Send8(1);
|
||||||
|
}
|
||||||
|
else if (SET_CONFIGURATION == r)
|
||||||
|
{
|
||||||
|
if (REQUEST_DEVICE == (requestType & REQUEST_RECIPIENT))
|
||||||
|
{
|
||||||
|
InitEndpoints();
|
||||||
|
_usbConfiguration = setup.wValueL;
|
||||||
|
} else
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
else if (GET_INTERFACE == r)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
else if (SET_INTERFACE == r)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
InitControl(setup.wLength); // Max length of transfer
|
||||||
|
ok = ClassInterfaceRequest(setup);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ok)
|
||||||
|
ClearIN();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Stall();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void USB_Flush(u8 ep)
|
||||||
|
{
|
||||||
|
SetEP(ep);
|
||||||
|
if (FifoByteCount())
|
||||||
|
ReleaseTX();
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void USB_ClockDisable()
|
||||||
|
{
|
||||||
|
#if defined(OTGPADE)
|
||||||
|
USBCON = (USBCON & ~(1<<OTGPADE)) | (1<<FRZCLK); // freeze clock and disable VBUS Pad
|
||||||
|
#else // u2 Series
|
||||||
|
USBCON = (1 << FRZCLK); // freeze clock
|
||||||
|
#endif
|
||||||
|
PLLCSR &= ~(1<<PLLE); // stop PLL
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void USB_ClockEnable()
|
||||||
|
{
|
||||||
|
#if defined(UHWCON)
|
||||||
|
UHWCON |= (1<<UVREGE); // power internal reg
|
||||||
|
#endif
|
||||||
|
USBCON = (1<<USBE) | (1<<FRZCLK); // clock frozen, usb enabled
|
||||||
|
|
||||||
|
// ATmega32U4
|
||||||
|
#if defined(PINDIV)
|
||||||
|
#if F_CPU == 16000000UL
|
||||||
|
PLLCSR |= (1<<PINDIV); // Need 16 MHz xtal
|
||||||
|
#elif F_CPU == 8000000UL
|
||||||
|
PLLCSR &= ~(1<<PINDIV); // Need 8 MHz xtal
|
||||||
|
#else
|
||||||
|
#error "Clock rate of F_CPU not supported"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#elif defined(__AVR_AT90USB82__) || defined(__AVR_AT90USB162__) || defined(__AVR_ATmega32U2__) || defined(__AVR_ATmega16U2__) || defined(__AVR_ATmega8U2__)
|
||||||
|
// for the u2 Series the datasheet is confusing. On page 40 its called PINDIV and on page 290 its called PLLP0
|
||||||
|
#if F_CPU == 16000000UL
|
||||||
|
// Need 16 MHz xtal
|
||||||
|
PLLCSR |= (1 << PLLP0);
|
||||||
|
#elif F_CPU == 8000000UL
|
||||||
|
// Need 8 MHz xtal
|
||||||
|
PLLCSR &= ~(1 << PLLP0);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// AT90USB646, AT90USB647, AT90USB1286, AT90USB1287
|
||||||
|
#elif defined(PLLP2)
|
||||||
|
#if F_CPU == 16000000UL
|
||||||
|
#if defined(__AVR_AT90USB1286__) || defined(__AVR_AT90USB1287__)
|
||||||
|
// For Atmel AT90USB128x only. Do not use with Atmel AT90USB64x.
|
||||||
|
PLLCSR = (PLLCSR & ~(1<<PLLP1)) | ((1<<PLLP2) | (1<<PLLP0)); // Need 16 MHz xtal
|
||||||
|
#elif defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB647__)
|
||||||
|
// For AT90USB64x only. Do not use with AT90USB128x.
|
||||||
|
PLLCSR = (PLLCSR & ~(1<<PLLP0)) | ((1<<PLLP2) | (1<<PLLP1)); // Need 16 MHz xtal
|
||||||
|
#else
|
||||||
|
#error "USB Chip not supported, please defined method of USB PLL initialization"
|
||||||
|
#endif
|
||||||
|
#elif F_CPU == 8000000UL
|
||||||
|
// for Atmel AT90USB128x and AT90USB64x
|
||||||
|
PLLCSR = (PLLCSR & ~(1<<PLLP2)) | ((1<<PLLP1) | (1<<PLLP0)); // Need 8 MHz xtal
|
||||||
|
#else
|
||||||
|
#error "Clock rate of F_CPU not supported"
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#error "USB Chip not supported, please defined method of USB PLL initialization"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
PLLCSR |= (1<<PLLE);
|
||||||
|
while (!(PLLCSR & (1<<PLOCK))) // wait for lock pll
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some tests on specific versions of macosx (10.7.3), reported some
|
||||||
|
// strange behaviors when the board is reset using the serial
|
||||||
|
// port touch at 1200 bps. This delay fixes this behavior.
|
||||||
|
delay(1);
|
||||||
|
#if defined(OTGPADE)
|
||||||
|
USBCON = (USBCON & ~(1<<FRZCLK)) | (1<<OTGPADE); // start USB clock, enable VBUS Pad
|
||||||
|
#else
|
||||||
|
USBCON &= ~(1 << FRZCLK); // start USB clock
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(RSTCPU)
|
||||||
|
#if defined(LSM)
|
||||||
|
UDCON &= ~((1<<RSTCPU) | (1<<LSM) | (1<<RMWKUP) | (1<<DETACH)); // enable attach resistor, set full speed mode
|
||||||
|
#else // u2 Series
|
||||||
|
UDCON &= ~((1 << RSTCPU) | (1 << RMWKUP) | (1 << DETACH)); // enable attach resistor, set full speed mode
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
// AT90USB64x and AT90USB128x don't have RSTCPU
|
||||||
|
UDCON &= ~((1<<LSM) | (1<<RMWKUP) | (1<<DETACH)); // enable attach resistor, set full speed mode
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// General interrupt
|
||||||
|
ISR(USB_GEN_vect)
|
||||||
|
{
|
||||||
|
u8 udint = UDINT;
|
||||||
|
UDINT &= ~((1<<EORSTI) | (1<<SOFI)); // clear the IRQ flags for the IRQs which are handled here, except WAKEUPI and SUSPI (see below)
|
||||||
|
|
||||||
|
// End of Reset
|
||||||
|
if (udint & (1<<EORSTI))
|
||||||
|
{
|
||||||
|
InitEP(0,EP_TYPE_CONTROL,EP_SINGLE_64); // init ep0
|
||||||
|
_usbConfiguration = 0; // not configured yet
|
||||||
|
UEIENX = 1 << RXSTPE; // Enable interrupts for ep0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start of Frame - happens every millisecond so we use it for TX and RX LED one-shot timing, too
|
||||||
|
if (udint & (1<<SOFI))
|
||||||
|
{
|
||||||
|
USB_Flush(CDC_TX); // Send a tx frame if found
|
||||||
|
|
||||||
|
// check whether the one-shot period has elapsed. if so, turn off the LED //not used for WARBL
|
||||||
|
// if (TxLEDPulse && !(--TxLEDPulse))
|
||||||
|
// TXLED0;
|
||||||
|
// if (RxLEDPulse && !(--RxLEDPulse))
|
||||||
|
// RXLED0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the WAKEUPI interrupt is triggered as soon as there are non-idle patterns on the data
|
||||||
|
// lines. Thus, the WAKEUPI interrupt can occur even if the controller is not in the "suspend" mode.
|
||||||
|
// Therefore the we enable it only when USB is suspended
|
||||||
|
if (udint & (1<<WAKEUPI))
|
||||||
|
{
|
||||||
|
UDIEN = (UDIEN & ~(1<<WAKEUPE)) | (1<<SUSPE); // Disable interrupts for WAKEUP and enable interrupts for SUSPEND
|
||||||
|
|
||||||
|
//TODO
|
||||||
|
// WAKEUPI shall be cleared by software (USB clock inputs must be enabled before).
|
||||||
|
//USB_ClockEnable();
|
||||||
|
UDINT &= ~(1<<WAKEUPI);
|
||||||
|
_usbSuspendState = (_usbSuspendState & ~(1<<SUSPI)) | (1<<WAKEUPI);
|
||||||
|
}
|
||||||
|
else if (udint & (1<<SUSPI)) // only one of the WAKEUPI / SUSPI bits can be active at time
|
||||||
|
{
|
||||||
|
UDIEN = (UDIEN & ~(1<<SUSPE)) | (1<<WAKEUPE); // Disable interrupts for SUSPEND and enable interrupts for WAKEUP
|
||||||
|
|
||||||
|
//TODO
|
||||||
|
//USB_ClockDisable();
|
||||||
|
|
||||||
|
UDINT &= ~((1<<WAKEUPI) | (1<<SUSPI)); // clear any already pending WAKEUP IRQs and the SUSPI request
|
||||||
|
_usbSuspendState = (_usbSuspendState & ~(1<<WAKEUPI)) | (1<<SUSPI);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// VBUS or counting frames
|
||||||
|
// Any frame counting?
|
||||||
|
u8 USBConnected()
|
||||||
|
{
|
||||||
|
u8 f = UDFNUML;
|
||||||
|
delay(3);
|
||||||
|
return f != UDFNUML;
|
||||||
|
}
|
||||||
|
|
||||||
|
//=======================================================================
|
||||||
|
//=======================================================================
|
||||||
|
|
||||||
|
USBDevice_ USBDevice;
|
||||||
|
|
||||||
|
USBDevice_::USBDevice_()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void USBDevice_::attach()
|
||||||
|
{
|
||||||
|
_usbConfiguration = 0;
|
||||||
|
_usbCurrentStatus = 0;
|
||||||
|
_usbSuspendState = 0;
|
||||||
|
USB_ClockEnable();
|
||||||
|
|
||||||
|
UDINT &= ~((1<<WAKEUPI) | (1<<SUSPI)); // clear already pending WAKEUP / SUSPEND requests
|
||||||
|
UDIEN = (1<<EORSTE) | (1<<SOFE) | (1<<SUSPE); // Enable interrupts for EOR (End of Reset), SOF (start of frame) and SUSPEND
|
||||||
|
|
||||||
|
//TX_RX_LED_INIT; //not used for WARBL
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void USBDevice_::detach()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for interrupts
|
||||||
|
// TODO: VBUS detection
|
||||||
|
bool USBDevice_::configured()
|
||||||
|
{
|
||||||
|
return _usbConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
void USBDevice_::poll()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool USBDevice_::wakeupHost()
|
||||||
|
{
|
||||||
|
// clear any previous wakeup request which might have been set but could be processed at that time
|
||||||
|
// e.g. because the host was not suspended at that time
|
||||||
|
UDCON &= ~(1 << RMWKUP);
|
||||||
|
|
||||||
|
if(!(UDCON & (1 << RMWKUP))
|
||||||
|
&& (_usbSuspendState & (1<<SUSPI))
|
||||||
|
&& (_usbCurrentStatus & FEATURE_REMOTE_WAKEUP_ENABLED))
|
||||||
|
{
|
||||||
|
// This short version will only work, when the device has not been suspended. Currently the
|
||||||
|
// Arduino core doesn't handle SUSPEND at all, so this is ok.
|
||||||
|
USB_ClockEnable();
|
||||||
|
UDCON |= (1 << RMWKUP); // send the wakeup request
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* if defined(USBCON) */
|
||||||
2163
fingering_charts.ino
Normal file
2163
fingering_charts.ino
Normal file
File diff suppressed because it is too large
Load Diff
2231
functions.ino
Normal file
2231
functions.ino
Normal file
File diff suppressed because it is too large
Load Diff
677
warbl_firmware.ino
Normal file
677
warbl_firmware.ino
Normal file
@@ -0,0 +1,677 @@
|
|||||||
|
|
||||||
|
/*
|
||||||
|
Copyright (C) 2018-2021 Andrew Mowry warbl.xyz
|
||||||
|
|
||||||
|
Many thanks to Michael Eskin and Jesse Chappell for their additions and debugging help.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see http://www.gnu.org/licenses/
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//#include <MemoryUsage.h> //can be used to show free RAM
|
||||||
|
#include <avr/wdt.h> //for resetting with watchdog
|
||||||
|
#include <TimerOne.h> //for timer interrupt for reading sensors at a regular interval
|
||||||
|
#include <DIO2.h> //fast digitalWrite library used for toggling IR LEDs
|
||||||
|
#include <MIDIUSB.h>
|
||||||
|
#include <EEPROM.h>
|
||||||
|
#include <avr/pgmspace.h> // for using PROGMEM for fingering chart storage
|
||||||
|
|
||||||
|
#define GPIO2_PREFER_SPEED 1 //digitalread speed, see: https://github.com/Locoduino/DIO2/blob/master/examples/standard_outputs/standard_outputs.ino
|
||||||
|
|
||||||
|
#define DEBOUNCE_TIME 0.02 //debounce time, in seconds---Integrating debouncing algorithm is taken from debounce.c, written by Kenneth A. Kuhn:http://www.kennethkuhn.com/electronics/debounce.c
|
||||||
|
#define SAMPLE_FREQUENCY 200 //button sample frequency, in Hz
|
||||||
|
|
||||||
|
#define MAXIMUM (DEBOUNCE_TIME * SAMPLE_FREQUENCY) //the integrator value required to register a button press
|
||||||
|
|
||||||
|
#define VERSION 20 //software version number (without decimal point)
|
||||||
|
|
||||||
|
//MIDI commands
|
||||||
|
#define NOTE_OFF 0x80 //127
|
||||||
|
#define NOTE_ON 0x90 // 144
|
||||||
|
#define KEY_PRESSURE 0xA0 // 160
|
||||||
|
#define CC 0xB0 // 176
|
||||||
|
#define PROGRAM_CHANGE 0xC0 // 192
|
||||||
|
#define CHANNEL_PRESSURE 0xD0 // 208
|
||||||
|
#define PITCH_BEND 0xE0 // 224
|
||||||
|
|
||||||
|
// Fingering Patterns
|
||||||
|
#define kModeWhistle 0
|
||||||
|
#define kModeUilleann 1
|
||||||
|
#define kModeGHB 2
|
||||||
|
#define kModeNorthumbrian 3
|
||||||
|
#define kModeChromatic 4
|
||||||
|
#define kModeGaita 5
|
||||||
|
#define kModeNAF 6
|
||||||
|
#define kModeKaval 7
|
||||||
|
#define kModeRecorder 8
|
||||||
|
#define kModeRegulators 9 //only used for a custom regulators implementation, not the "official" software
|
||||||
|
#define kModeUilleannStandard 10 //contains no accidentals
|
||||||
|
#define kModeXiao 11
|
||||||
|
#define kModeSax 12
|
||||||
|
#define kModeGaitaExtended 13
|
||||||
|
#define kModeSaxBasic 14
|
||||||
|
#define kModeEVI 15
|
||||||
|
#define kModeShakuhachi 16
|
||||||
|
#define kModeSackpipaMajor 17
|
||||||
|
#define kModeSackpipaMinor 18
|
||||||
|
#define kModeCustom 19
|
||||||
|
#define kModeNModes 20
|
||||||
|
#define kModeBoha 21
|
||||||
|
|
||||||
|
// Pitch bend modes
|
||||||
|
#define kPitchBendSlideVibrato 0
|
||||||
|
#define kPitchBendVibrato 1
|
||||||
|
#define kPitchBendNone 2
|
||||||
|
#define kPitchBendLegatoSlideVibrato 3
|
||||||
|
#define kPitchBendNModes 4
|
||||||
|
|
||||||
|
// Register control modes
|
||||||
|
#define kPressureSingle 0
|
||||||
|
#define kPressureBreath 1
|
||||||
|
#define kPressureThumb 2
|
||||||
|
#define kPressureBell 3
|
||||||
|
#define kPressureNModes 4
|
||||||
|
|
||||||
|
// Secret function drone control MIDI parameters
|
||||||
|
#define kDroneVelocity 36
|
||||||
|
#define kLynchDroneMIDINote 50
|
||||||
|
#define kCrowleyDroneMIDINote 51
|
||||||
|
|
||||||
|
// Drones control mode
|
||||||
|
#define kNoDroneControl 0
|
||||||
|
#define kSecretDroneControl 1
|
||||||
|
#define kBaglessDroneControl 2
|
||||||
|
#define kPressureDroneControl 3
|
||||||
|
#define kDroneNModes 4
|
||||||
|
|
||||||
|
//Variables in the switches array (settings for the swiches in the slide/vibrato and register control panels)
|
||||||
|
#define VENTED 0
|
||||||
|
#define BAGLESS 1
|
||||||
|
#define SECRET 2
|
||||||
|
#define INVERT 3
|
||||||
|
#define CUSTOM 4
|
||||||
|
#define SEND_VELOCITY 5
|
||||||
|
#define SEND_AFTERTOUCH 6 //second bit of this one is used for poly
|
||||||
|
#define FORCE_MAX_VELOCITY 7
|
||||||
|
#define IMMEDIATE_PB 8
|
||||||
|
#define LEGATO 9
|
||||||
|
#define OVERRIDE 10
|
||||||
|
#define THUMB_AND_OVERBLOW 11
|
||||||
|
#define R4_FLATTEN 12
|
||||||
|
#define kSWITCHESnVariables 13
|
||||||
|
|
||||||
|
//Variables in the ED array (all the settings for the Expression and Drones panels)
|
||||||
|
#define EXPRESSION_ON 0
|
||||||
|
#define EXPRESSION_DEPTH 1
|
||||||
|
#define SEND_PRESSURE 2
|
||||||
|
#define CURVE 3 // (0 is linear, 1 and 2 are power curves)
|
||||||
|
#define PRESSURE_CHANNEL 4
|
||||||
|
#define PRESSURE_CC 5
|
||||||
|
#define INPUT_PRESSURE_MIN 6
|
||||||
|
#define INPUT_PRESSURE_MAX 7
|
||||||
|
#define OUTPUT_PRESSURE_MIN 8
|
||||||
|
#define OUTPUT_PRESSURE_MAX 9
|
||||||
|
#define DRONES_ON_COMMAND 10
|
||||||
|
#define DRONES_ON_CHANNEL 11
|
||||||
|
#define DRONES_ON_BYTE2 12
|
||||||
|
#define DRONES_ON_BYTE3 13
|
||||||
|
#define DRONES_OFF_COMMAND 14
|
||||||
|
#define DRONES_OFF_CHANNEL 15
|
||||||
|
#define DRONES_OFF_BYTE2 16
|
||||||
|
#define DRONES_OFF_BYTE3 17
|
||||||
|
#define DRONES_CONTROL_MODE 18
|
||||||
|
#define DRONES_PRESSURE_LOW_BYTE 19
|
||||||
|
#define DRONES_PRESSURE_HIGH_BYTE 20
|
||||||
|
#define VELOCITY_INPUT_PRESSURE_MIN 21
|
||||||
|
#define VELOCITY_INPUT_PRESSURE_MAX 22
|
||||||
|
#define VELOCITY_OUTPUT_PRESSURE_MIN 23
|
||||||
|
#define VELOCITY_OUTPUT_PRESSURE_MAX 24
|
||||||
|
#define AFTERTOUCH_INPUT_PRESSURE_MIN 25
|
||||||
|
#define AFTERTOUCH_INPUT_PRESSURE_MAX 26
|
||||||
|
#define AFTERTOUCH_OUTPUT_PRESSURE_MIN 27
|
||||||
|
#define AFTERTOUCH_OUTPUT_PRESSURE_MAX 28
|
||||||
|
#define POLY_INPUT_PRESSURE_MIN 29
|
||||||
|
#define POLY_INPUT_PRESSURE_MAX 30
|
||||||
|
#define POLY_OUTPUT_PRESSURE_MIN 31
|
||||||
|
#define POLY_OUTPUT_PRESSURE_MAX 32
|
||||||
|
#define VELOCITY_CURVE 33
|
||||||
|
#define AFTERTOUCH_CURVE 34
|
||||||
|
#define POLY_CURVE 35
|
||||||
|
#define EXPRESSION_MIN 36
|
||||||
|
#define EXPRESSION_MAX 37
|
||||||
|
#define CUSTOM_FINGERING_1 38
|
||||||
|
#define CUSTOM_FINGERING_2 39
|
||||||
|
#define CUSTOM_FINGERING_3 40
|
||||||
|
#define CUSTOM_FINGERING_4 41
|
||||||
|
#define CUSTOM_FINGERING_5 42
|
||||||
|
#define CUSTOM_FINGERING_6 43
|
||||||
|
#define CUSTOM_FINGERING_7 44
|
||||||
|
#define CUSTOM_FINGERING_8 45
|
||||||
|
#define CUSTOM_FINGERING_9 46
|
||||||
|
#define CUSTOM_FINGERING_10 47
|
||||||
|
#define CUSTOM_FINGERING_11 48
|
||||||
|
#define kEXPRESSIONnVariables 49
|
||||||
|
|
||||||
|
//GPIO constants
|
||||||
|
const uint8_t ledPin = 13;
|
||||||
|
const uint8_t holeTrans[] = {5, 9, 10, 0, 1, 2, 3, 11, 6}; //the analog pins used for the tone hole phototransistors, in the following order: Bell,R4,R3,R2,R1,L3,L2,L1,Lthumb
|
||||||
|
const GPIO_pin_t pins[] = {DP11, DP6, DP8, DP5, DP7, DP1, DP0, DP3, DP2}; //the digital pins used for the tone hole leds, in the following order: Bell,R4,R3,R2,R1,L3,L2,L1,Lthumb. Uses a special declaration format for the GPIO library.
|
||||||
|
const GPIO_pin_t buttons[] = {DP15, DP14, DP16}; //the pins used for the buttons
|
||||||
|
|
||||||
|
//instrument
|
||||||
|
byte mode = 0; // The current mode (instrument), from 0-2.
|
||||||
|
byte defaultMode = 0; // The default mode, from 0-2.
|
||||||
|
|
||||||
|
//variables that can change according to instrument.
|
||||||
|
int8_t octaveShift = 0; //octave transposition
|
||||||
|
int8_t noteShift = 0; //note transposition, for changing keys. All fingering patterns are initially based on the key of D, and transposed with this variable to the desired key.
|
||||||
|
byte pitchBendMode = kPitchBendSlideVibrato; //0 means slide and vibrato are on. 1 means only vibrato is on. 2 is all pitchbend off, 3 is legato slide/vibrato.
|
||||||
|
byte senseDistance = 10; //the sensor value above which the finger is sensed for bending notes. Needs to be higher than the baseline sensor readings, otherwise vibrato will be turned on erroneously.
|
||||||
|
byte breathMode = kPressureBreath; //the desired presure sensor behavior: single register, overblow, thumb register control, bell register control.
|
||||||
|
unsigned int vibratoDepth = 1024; //vibrato depth from 0 (no vibrato) to 8191 (one semitone)
|
||||||
|
bool useLearnedPressure = 0; //whether we use learned pressure for note on threshold, or we use calibration pressure from startup
|
||||||
|
byte midiBendRange = 2; // +/- semitones that the midi bend range represents
|
||||||
|
byte mainMidiChannel = 1; // current MIDI channel to send notes on
|
||||||
|
|
||||||
|
//These are containers for the above variables, storing the value used by the three different instruments. First variable in array is for instrument 0, etc.
|
||||||
|
byte modeSelector[] = {kModeBoha, kModeUilleann, kModeGHB}; //the fingering patterns chosen in the configuration tool, for the three instruments.
|
||||||
|
int8_t octaveShiftSelector[] = {0, 0, 0};
|
||||||
|
int8_t noteShiftSelector[] = {0, 0, 8};
|
||||||
|
byte pitchBendModeSelector[] = {1, 1, 1};
|
||||||
|
byte senseDistanceSelector[] = {10, 10, 10};
|
||||||
|
byte breathModeSelector[] = {1, 1, 0};
|
||||||
|
byte useLearnedPressureSelector[] = {0, 0, 0};
|
||||||
|
int learnedPressureSelector[] = {0, 0, 0};
|
||||||
|
byte LSBlearnedPressure; //used to reconstruct learned pressure from two MIDI bytes.
|
||||||
|
unsigned int vibratoHolesSelector[] = {0b011111111, 0b011111111, 0b011111111};
|
||||||
|
unsigned int vibratoDepthSelector[] = {1024, 1024, 1024};
|
||||||
|
byte midiBendRangeSelector[] = {2, 2, 2};
|
||||||
|
byte midiChannelSelector[] = {1, 1, 1};
|
||||||
|
|
||||||
|
bool momentary[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}} ; //whether momentary click behavior is desired for MIDI on/off message sent with a button. Dimension 0 is mode (instrument), dimension 1 is button 0,1,2.
|
||||||
|
|
||||||
|
byte switches[3][13] = //the settings for the five switches in the vibrato/slide and register control panels
|
||||||
|
//instrument 0
|
||||||
|
{
|
||||||
|
{
|
||||||
|
1, // vented mouthpiece on or off (there are different pressure settings for the vented mouthpiece)
|
||||||
|
0, // bagless mode off or on
|
||||||
|
0, // secret button command mode off or on
|
||||||
|
0, // whether the functionality for using the right thumb or the bell sensor for increasing the register is inverted.
|
||||||
|
0, // off/on for Michael Eskin's custom vibrato approach
|
||||||
|
0, // send pressure as NoteOn velocity off or on
|
||||||
|
0, // send pressure as aftertouch (channel pressure) off or on, and/or poly aftertouch (2nd bit)
|
||||||
|
1, // force maximum velocity (127)
|
||||||
|
0, // send pitchbend immediately before Note On (recommnded for MPE)
|
||||||
|
1, // send legato (Note On message before Note Off for previous note)
|
||||||
|
0, //override pitch expression pressure range
|
||||||
|
0, //use both thumb and overblowing for register control with custom fingering chart
|
||||||
|
0 //use R4 finger to flatten any note one semitone with custom fingering chart
|
||||||
|
},
|
||||||
|
|
||||||
|
//same for instrument 1
|
||||||
|
{0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0},
|
||||||
|
|
||||||
|
//same for instrument 2
|
||||||
|
{0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0}
|
||||||
|
};
|
||||||
|
|
||||||
|
byte ED[3][49] = //an array that holds all the settings for the Expression and Drones Control panels in the Configuration Tool.
|
||||||
|
//instrument 0
|
||||||
|
{
|
||||||
|
{
|
||||||
|
0, //EXPRESSION_ON
|
||||||
|
3, //EXPRESSION_DEPTH (can have a value of 1-8)
|
||||||
|
0, //SEND_PRESSURE
|
||||||
|
0, //CURVE (0 is linear)
|
||||||
|
1, //PRESSURE_CHANNEL
|
||||||
|
7, //PRESSURE_CC
|
||||||
|
0, //INPUT_PRESSURE_MIN range is from 0-100, to be scaled later up to maximum input values
|
||||||
|
100, //INPUT_PRESSURE_MAX range is from 0-100, to be scaled later
|
||||||
|
0, //OUTPUT_PRESSURE_MIN range is from 0-127, to be scaled later
|
||||||
|
127, //OUTPUT_PRESSURE_MAX range is from 0-127, to be scaled later
|
||||||
|
0, //DRONES_ON_COMMAND
|
||||||
|
1, //DRONES_ON_CHANNEL
|
||||||
|
51, //DRONES_ON_BYTE2
|
||||||
|
36, //DRONES_ON_BYTE3
|
||||||
|
0, //DRONES_OFF_COMMAND
|
||||||
|
1, //DRONES_OFF_CHANNEL
|
||||||
|
51, //DRONES_OFF_BYTE2
|
||||||
|
36, //DRONES_OFF_BYTE3
|
||||||
|
0, //DRONES_CONTROL_MODE (0 is no drone control, 1 is use secret button, 2 is use bagless button, 3 is use pressure.
|
||||||
|
0, //DRONES_PRESSURE_LOW_BYTE
|
||||||
|
0, //DRONES_PRESSURE_HIGH_BYTE
|
||||||
|
0, //VELOCITY_INPUT_PRESSURE_MIN
|
||||||
|
100, //VELOCITY_INPUT_PRESSURE_MAX
|
||||||
|
0, //VELOCITY_OUTPUT_PRESSURE_MIN
|
||||||
|
127, //VELOCITY_OUTPUT_PRESSURE_MAX
|
||||||
|
0, //AFTERTOUCH_INPUT_PRESSURE_MIN
|
||||||
|
100, //AFTERTOUCH_INPUT_PRESSURE_MAX
|
||||||
|
0, //AFTERTOUCH_OUTPUT_PRESSURE_MIN
|
||||||
|
127, //AFTERTOUCH_OUTPUT_PRESSURE_MAX
|
||||||
|
0, //POLY_INPUT_PRESSURE_MIN
|
||||||
|
100, //POLY_INPUT_PRESSURE_MAX
|
||||||
|
0, //POLY_OUTPUT_PRESSURE_MIN
|
||||||
|
127, //POLY_OUTPUT_PRESSURE_MAX
|
||||||
|
0, //VELOCITY_CURVE
|
||||||
|
0, //AFTERTOUCH_CURVE
|
||||||
|
0, //POLY_CURVE
|
||||||
|
0, //EXPRESSION_MIN
|
||||||
|
100, //EXPRESSION_MAX
|
||||||
|
0, //CUSTOM_FINGERING_1
|
||||||
|
74, //CUSTOM_FINGERING_2
|
||||||
|
73, //CUSTOM_FINGERING_3
|
||||||
|
72, //CUSTOM_FINGERING_4
|
||||||
|
71, //CUSTOM_FINGERING_5
|
||||||
|
69, //CUSTOM_FINGERING_6
|
||||||
|
67, //CUSTOM_FINGERING_7
|
||||||
|
66, //CUSTOM_FINGERING_8
|
||||||
|
64, //CUSTOM_FINGERING_9
|
||||||
|
62, //CUSTOM_FINGERING_10
|
||||||
|
61 //CUSTOM_FINGERING_11
|
||||||
|
},
|
||||||
|
|
||||||
|
//same for instrument 1
|
||||||
|
{0, 3, 0, 0, 1, 7, 0, 100, 0, 127, 0, 1, 51, 36, 0, 1, 51, 36, 0, 0, 0, 0, 127, 0, 127, 0, 127, 0, 127, 0, 127, 0, 127, 0, 0, 0, 0, 100, 0, 74, 73, 72, 71, 69, 67, 66, 64, 62, 61},
|
||||||
|
|
||||||
|
//same for instrument 2
|
||||||
|
{0, 3, 0, 0, 1, 7, 0, 100, 0, 127, 0, 1, 51, 36, 0, 1, 51, 36, 0, 0, 0, 0, 127, 0, 127, 0, 127, 0, 127, 0, 127, 0, 127, 0, 0, 0, 0, 100, 0, 74, 73, 72, 71, 69, 67, 66, 64, 62, 61}
|
||||||
|
};
|
||||||
|
|
||||||
|
byte pressureSelector[3][12] = //a selector array for all the register control variables that can be changed in the Configuration Tool
|
||||||
|
//instrument 0
|
||||||
|
{
|
||||||
|
{
|
||||||
|
15, 15, 15, 15, 30, 30, //closed mouthpiece: offset, multiplier, jump, drop, jump time, drop time
|
||||||
|
3, 7, 100, 7, 9, 9
|
||||||
|
}, //vented mouthpiece: offset, multiplier, jump, drop, jump time, drop time
|
||||||
|
//instrument 1
|
||||||
|
{
|
||||||
|
15, 15, 15, 15, 30, 30,
|
||||||
|
3, 7, 10, 7, 9, 9
|
||||||
|
},
|
||||||
|
//instrument 2
|
||||||
|
{
|
||||||
|
15, 15, 15, 15, 30, 30,
|
||||||
|
3, 7, 10, 7, 9, 9
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t buttonPrefs[3][8][5] = //The button configuration settings. Dimension 1 is the three instruments. Dimension 2 is the button combination: click 1, click 2, click3, hold 2 click 1, hold 2 click 3, longpress 1, longpress2, longpress3
|
||||||
|
//Dimension 3 is the desired action: Action, MIDI command type (noteon/off, CC, PC), MIDI channel, MIDI byte 2, MIDI byte 3.
|
||||||
|
//instrument 0 //the actions are: 0 none, 1 send MIDI message, 2 change pitchbend mode, 3 instrument, 4 play/stop (bagless mode), 5 octave shift up, 6 octave shift down, 7 MIDI panic, 8 change register control mode, 9 drones on/off, 10 semitone shift up, 11 semitone shift down
|
||||||
|
{ { {2, 0, 0, 0, 0}, //for example, this means that clicking button 0 will send a CC message, channel 1, byte 2 = 0, byte 3 = 0.
|
||||||
|
{8, 0, 0, 0, 0}, //for example, this means that clicking button 1 will change pitchbend mode.
|
||||||
|
{0, 0, 0, 0, 0},
|
||||||
|
{5, 0, 0, 0, 0},
|
||||||
|
{6, 0, 0, 0, 0},
|
||||||
|
{0, 0, 0, 0, 0},
|
||||||
|
{0, 0, 0, 0, 0},
|
||||||
|
{0, 0, 0, 0, 0}
|
||||||
|
},
|
||||||
|
|
||||||
|
//same for instrument 1
|
||||||
|
{{2, 0, 0, 0, 0}, {8, 0, 0, 0, 0}, {9, 0, 0, 0, 0}, {5, 0, 0, 0, 0}, {6, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}},
|
||||||
|
|
||||||
|
//same for instrument 2
|
||||||
|
{{2, 0, 0, 0, 0}, {8, 0, 0, 0, 0}, {9, 0, 0, 0, 0}, {5, 0, 0, 0, 0}, {6, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}}
|
||||||
|
};
|
||||||
|
|
||||||
|
//other misc. variables
|
||||||
|
byte hardwareRevision = 30;
|
||||||
|
unsigned long ledTimer = 0; //for blinking LED
|
||||||
|
byte blinkNumber = 1; //the number of remaining blinks when blinking LED to indicate control changes
|
||||||
|
bool LEDon = 0; //whether the LED is currently on
|
||||||
|
bool play = 0; //turns sound off and on (with the use of a button action) when in bagless mode
|
||||||
|
bool bellSensor = 0; //whether the bell sensor is plugged in
|
||||||
|
bool prevBellSensor = 0; //the previous reading of the bell sensor detection pin
|
||||||
|
unsigned long initialTime = 0; //for testing
|
||||||
|
unsigned long finalTime = 0; //for testing
|
||||||
|
unsigned long cycles = 0; //for testing
|
||||||
|
byte program = 0; //current MIDI program change value. This always starts at 0 but can be increased/decreased with assigned buttons.
|
||||||
|
bool dronesState = 0; //keeps track of whether we're above or below the pressure threshold for turning drones on.
|
||||||
|
|
||||||
|
//variables for reading pressure sensor
|
||||||
|
volatile unsigned int tempSensorValue = 0; //for holding the pressure sensor value inside the ISR
|
||||||
|
int sensorValue = 0; // first value read from the pressure sensor
|
||||||
|
int sensorValue2 = 0; // second value read from the pressure sensor, for measuring rate of change in pressure
|
||||||
|
int prevSensorValue = 0; // previous sensor reading, used to tell if the pressure has changed and should be sent.
|
||||||
|
int pressureChangeRate = 0; //the difference between current and previous sensor readings
|
||||||
|
int sensorCalibration = 0; //the sensor reading at startup, used as a base value
|
||||||
|
byte offset = 15;
|
||||||
|
byte customScalePosition; //used to indicate the position of the current note on the custom chart scale (needed for state calculation)
|
||||||
|
int sensorThreshold[] = {260, 0}; //the pressure sensor thresholds for initial note on and shift from register 1 to register 2, before some transformations.
|
||||||
|
int upperBound = 255; //this represents the pressure transition between the first and second registers. It is calculated on the fly as: (sensorThreshold[1] + ((newNote - 60) * multiplier))
|
||||||
|
byte newState; //the note/octave state based on the sensor readings (1=not enough force to sound note, 2=enough force to sound first octave, 3 = enough force to sound second octave)
|
||||||
|
byte prevState = 1; //the previous state, used to monitor change necessary for adding a small delay when a note is turned on from silence and we're sending not on velocity based on pressure.
|
||||||
|
boolean sensorDataReady = 0; //tells us that pressure data is available
|
||||||
|
boolean velocityDelay = 0; //whether we are currently waiting for the pressure to rise after crossing the threshold from having no note playing to have a note playing. This is only used if we're sending velocity based on pressure.
|
||||||
|
unsigned long velocityDelayTimer = 0; //a timer for the above delay.
|
||||||
|
bool jump = 0; //whether we jumped directly to second octave from note off because of rapidly increasing pressure.
|
||||||
|
unsigned long jumpTimer = 0; //records time when we dropped to note off.
|
||||||
|
int jumpTime = 15; //the amount of time to wait before dropping back down from an octave jump to first octave because of insufficient pressure
|
||||||
|
bool drop = 0; //whether we dropped directly from second octave to note off
|
||||||
|
unsigned long dropTimer = 0; //time when we jumped to second octave.
|
||||||
|
int dropTime = 15 ; //the amount of time to wait (ms) before turning a note back on after dropping directly from second octave to note off
|
||||||
|
byte jumpValue = 15;
|
||||||
|
byte dropValue = 15;
|
||||||
|
byte multiplier = 15; //controls how much more difficult it is to jump to second octave from higher first-octave notes than from lower first-octave notes. Increasing this makes playing with a bag more forgiving but requires more force to reach highest second-octave notes. Can be set according to fingering mode and breath mode (i.e. a higher jump factor may be used for playing with a bag). Array indices 1-3 are for breath mode jump factor, indices 4-6 are for bag mode jump factor.
|
||||||
|
byte soundTriggerOffset = 3; //the number of sensor values above the calibration setting at which a note on will be triggered (first octave)
|
||||||
|
int learnedPressure = 0; //the learned pressure reading, used as a base value
|
||||||
|
|
||||||
|
unsigned int inputPressureBounds[4][4] = { //for mapping pressure input range to output range. Dimension 1 is CC, velocity, aftertouch, poly. Dimension 2 is minIn, maxIn, scaledMinIn, mappedPressure
|
||||||
|
{100, 800, 0, 0},
|
||||||
|
{100, 800, 0, 0},
|
||||||
|
{100, 800, 0, 0},
|
||||||
|
{100, 800, 0, 0},
|
||||||
|
};
|
||||||
|
|
||||||
|
unsigned long pressureInputScale[4] = // precalculated scale factor for mapping the input pressure range, for CC, velocity, aftertouch, and poly.
|
||||||
|
{0, 0, 0, 0};
|
||||||
|
|
||||||
|
byte outputBounds[4][2] = { // container for ED output pressure range variables (CC, velocity, aftertouch, poly)-- the ED variables will be copied here so they're in a more logical order. This is a fix for variables that were added later.
|
||||||
|
{0, 127},
|
||||||
|
{0, 127},
|
||||||
|
{0, 127},
|
||||||
|
{0, 127}
|
||||||
|
};
|
||||||
|
|
||||||
|
byte curve[4] = {0, 0, 0, 0}; //similar to above-- more logical odering for the pressure curve variable
|
||||||
|
|
||||||
|
|
||||||
|
//variables for reading tonehole sensors
|
||||||
|
volatile byte lastRead = 0; //the transistor that was last read, so we know which to read the next time around the loop.
|
||||||
|
unsigned int toneholeCovered[] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; //covered hole tonehole sensor readings for calibration
|
||||||
|
int toneholeBaseline[] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; //baseline (uncovered) hole tonehole sensor readings
|
||||||
|
volatile int tempToneholeRead[] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; //temporary storage for tonehole sensor readings with IR LED on, written during the timer ISR
|
||||||
|
int toneholeRead[] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; //storage for tonehole sensor readings, transferred from the above volatile variable
|
||||||
|
volatile int tempToneholeReadA[] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; //temporary storage for ambient light tonehole sensor readings, written during the timer ISR
|
||||||
|
unsigned int holeCovered = 0; //whether each hole is covered-- each bit corresponds to a tonehole.
|
||||||
|
uint8_t tempCovered = 0; //used when masking holeCovered to ignore certain holes depending on the fingering pattern.
|
||||||
|
bool fingersChanged = 1; //keeps track of when the fingering pattern has changed.
|
||||||
|
unsigned int prevHoleCovered = 1; //so we can track changes.
|
||||||
|
volatile int tempNewNote = 0;
|
||||||
|
byte prevNote;
|
||||||
|
byte newNote = -1; //the next note to be played, based on the fingering chart (does not include transposition).
|
||||||
|
byte notePlaying; //the actual MIDI note being played, which we remember so we can turn it off again.
|
||||||
|
volatile bool firstTime = 1; // we have the LEDs off ~50% of the time. This bool keeps track of whether each LED is off or on at the end of each timer cycle
|
||||||
|
volatile byte timerCycle = 0; //the number of times we've entered the timer ISR with the new sensors.
|
||||||
|
byte newDroneNote;
|
||||||
|
//byte prevDroneNote; // Has no use for now
|
||||||
|
byte droneNotePlaying;
|
||||||
|
|
||||||
|
//pitchbend variables
|
||||||
|
unsigned long pitchBendTimer = 0; //to keep track of the last time we sent a pitchbend message
|
||||||
|
byte pitchBendOn[] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; //whether pitchbend is currently turned for for a specific hole
|
||||||
|
int pitchBend = 8192; //total current pitchbend value
|
||||||
|
int prevPitchBend = 8192; //a record of the previous pitchBend value, so we don't send the same one twice
|
||||||
|
int iPitchBend[] = {8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192}; //current pitchbend value for each tonehole
|
||||||
|
int pitchBendPerSemi = 4096;
|
||||||
|
int prevChanPressure = 0;
|
||||||
|
int prevCCPressure = 0;
|
||||||
|
int prevPolyPressure = 0;
|
||||||
|
unsigned long pressureTimer = 0; //to keep track of the last time we sent a pressure message
|
||||||
|
unsigned long noteOnTimestamp = 0; // ms timestamp the note was activated
|
||||||
|
byte slideHole; //the hole above the current highest uncovered hole. Used for detecting slides between notes.
|
||||||
|
byte stepsDown = 1; //the number of half steps down from the slideHole to the next lowest note on the scale, used for calculating pitchbend values.
|
||||||
|
byte vibratoEnable = 0; // if non-zero, send vibrato pitch bend
|
||||||
|
unsigned int holeLatched = 0b000000000; //holes that are disabled for vibrato because they were covered when the note was triggered. They become unlatched (0) when the finger is removed all the way.
|
||||||
|
unsigned int vibratoHoles = 0b111111111; //holes to be used for vibrato, left thumb on left, bell sensor far right.
|
||||||
|
unsigned int toneholeScale[] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; //a scale for normalizing the range of each sensor, for sliding
|
||||||
|
unsigned int vibratoScale[] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; //same as above but for vibrato
|
||||||
|
int expression = 0; //pitchbend up or down from current note based on pressure
|
||||||
|
bool customEnabled = 0; //Whether the custom vibrato above is currently enabled based on fingering pattern and pitchbend mode.
|
||||||
|
int adjvibdepth; //vibrato depth scaled to MIDI bend range.
|
||||||
|
|
||||||
|
//variables for managing MIDI note output
|
||||||
|
bool noteon = 0; //whether a note is currently turned on
|
||||||
|
bool shiftState = 0; //whether the octave is shifted (could be combined with octaveShift)
|
||||||
|
int8_t shift = 0; //the total amount of shift up or down from the base note 62 (D). This takes into account octave shift and note shift.
|
||||||
|
byte velocity = 127;//default MIDI note velocity
|
||||||
|
|
||||||
|
//tonehole calibration variables
|
||||||
|
byte calibration = 0; //whether we're currently calibrating. 1 is for calibrating all sensors, 2 is for calibrating bell sensor only, 3 is for calibrating all sensors plus baseline calibration (normally only done once, in the "factory").
|
||||||
|
unsigned long calibrationTimer = 0;
|
||||||
|
|
||||||
|
//variables for reading buttons
|
||||||
|
unsigned long buttonReadTimer = 0; //for telling when it's time to read the buttons
|
||||||
|
byte integrator[] = {0, 0, 0}; //stores integration of button readings. When this reaches MAXIMUM, a button press is registered. When it reaches 0, a release is registered.
|
||||||
|
bool pressed[] = {0, 0, 0}; //whether a button is currently presed (this it the output from the integrator)
|
||||||
|
bool released[] = {0, 0, 0}; //if a button has just been released
|
||||||
|
bool justPressed[] = {0, 0, 0}; //if a button has just been pressed
|
||||||
|
bool prevOutput[] = {0, 0, 0}; //previous state of button, to track state through time
|
||||||
|
bool longPress[] = {0, 0, 0}; //long button press
|
||||||
|
unsigned int longPressCounter[] = {0, 0, 0}; //for counting how many readings each button has been held, to indicate a long button press
|
||||||
|
bool noteOnOffToggle[] = {0, 0, 0}; //if using a button to toggle a noteOn/noteOff command, keep track of state.
|
||||||
|
bool longPressUsed[] = {0, 0, 0}; //if we used a long button press, we set a flag so we don't use it again unless the button has been released first.
|
||||||
|
bool buttonUsed = 0; //flags any button activity, so we know to handle it.
|
||||||
|
bool specialPressUsed[] = {0, 0, 0};
|
||||||
|
bool dronesOn = 0; //used to monitor drones on/off.
|
||||||
|
|
||||||
|
//variables for communication with the WARBL Configuration Tool
|
||||||
|
bool communicationMode = 0; //whether we are currently communicating with the tool.
|
||||||
|
byte buttonReceiveMode = 100; //which row in the button configuration matrix for which we're currently receiving data.
|
||||||
|
byte pressureReceiveMode = 100; //which pressure variable we're currently receiving date for. From 1-12: Closed: offset, multiplier, jump, drop, jump time, drop time, Vented: offset, multiplier, jump, drop, jump time, drop time
|
||||||
|
byte counter = 0; // We use this to know when to send a new pressure reading to the configuration tool. We increment it every time we send a pitchBend message, to use as a simple timer wihout needing to set another actual timer.
|
||||||
|
byte fingeringReceiveMode = 0; // indicates the mode (instrument) for which a fingering pattern is going to be sent
|
||||||
|
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
|
||||||
|
DIDR0 = 0xff; // disable digital input circuits for analog pins
|
||||||
|
DIDR2 = 0xf3;
|
||||||
|
|
||||||
|
pinMode2(ledPin, OUTPUT); // Initialize the LED pin as an output (using the fast DIO2 library).
|
||||||
|
pinMode2(17, INPUT_PULLUP); //this pin is used to detect when the bell sensor is plugged in (high when plugged in).
|
||||||
|
|
||||||
|
for (byte i = 0; i < 9; i++) { //Initialize the tonehole sensor IR LEDs.
|
||||||
|
pinMode2f(pins[i], OUTPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
pinMode2f(DP15, INPUT_PULLUP); //set buttons as inputs and enable internal pullup
|
||||||
|
pinMode2f(DP16, INPUT_PULLUP);
|
||||||
|
pinMode2f(DP14, INPUT_PULLUP);
|
||||||
|
|
||||||
|
//EEPROM.update(44,255); //can be uncommented to force factory settings to be resaved for debugging (after making changes to factory settings). Needs to be recommented again after.
|
||||||
|
|
||||||
|
|
||||||
|
if (EEPROM.read(44) != 3 || EEPROM.read(1011) != VERSION) {
|
||||||
|
EEPROM.update(1011, VERSION); //update the stored software version
|
||||||
|
saveFactorySettings(); //If we're running the software for the first time, if a factory reset has been requested, or if the software version has changed, copy all settings to EEPROM.
|
||||||
|
}
|
||||||
|
|
||||||
|
if (EEPROM.read(37) == 3) {
|
||||||
|
loadCalibration(); //If there has been a calibration saved, reload it at startup.
|
||||||
|
}
|
||||||
|
|
||||||
|
loadFingering();
|
||||||
|
loadSettingsForAllModes();
|
||||||
|
mode = defaultMode; //set the startup instrument
|
||||||
|
|
||||||
|
analogRead(A4); // the first analog readings are sometimes nonsense, so we read a few times and throw them away.
|
||||||
|
analogRead(A4);
|
||||||
|
sensorCalibration = analogRead(A4);
|
||||||
|
sensorValue = sensorCalibration; //an initial reading to "seed" subsequent pressure readings
|
||||||
|
|
||||||
|
loadPrefs(); //load the correct user settings based on current instrument.
|
||||||
|
|
||||||
|
//prepare sensors
|
||||||
|
Timer1.initialize(100); //this timer is only used to add some additional time after reading all sensors, for power savings.
|
||||||
|
Timer1.attachInterrupt(timerDelay);
|
||||||
|
Timer1.stop(); //stop the timer because we don't need it until we've read all the sensors once.
|
||||||
|
ADC_init(); //initialize the ADC and start conversions
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
|
||||||
|
//cycles ++; //for testing
|
||||||
|
|
||||||
|
receiveMIDI();
|
||||||
|
|
||||||
|
if ((millis() - buttonReadTimer) >= 5) { //read the state of the control buttons every so often
|
||||||
|
checkButtons();
|
||||||
|
buttonReadTimer = millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buttonUsed) {
|
||||||
|
handleButtons(); //if a button had been used, process the command. We only do this when we need to, so we're not wasting time.
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blinkNumber > 0) {
|
||||||
|
blink(); //blink the LED if necessary (indicating control changes, etc.)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (calibration > 0) {
|
||||||
|
calibrate(); //calibrate/continue calibrating if the command has been received.
|
||||||
|
}
|
||||||
|
|
||||||
|
noInterrupts();
|
||||||
|
for (byte i = 0; i < 9; i++) {
|
||||||
|
toneholeRead[i] = tempToneholeRead[i]; //transfer sensor readings to a variable that won't get modified in the ISR
|
||||||
|
}
|
||||||
|
interrupts();
|
||||||
|
|
||||||
|
|
||||||
|
for (byte i = 0; i < 9; i++) {
|
||||||
|
if (calibration == 0) { //if we're not calibrating, compensate for baseline sensor offset (the stored sensor reading with the hole completely uncovered)
|
||||||
|
toneholeRead[i] = toneholeRead[i] - toneholeBaseline[i];
|
||||||
|
}
|
||||||
|
if (toneholeRead[i] < 0) { //in rare cases the adjusted readings can end up being negative.
|
||||||
|
toneholeRead[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get_fingers(); //find which holes are covered
|
||||||
|
|
||||||
|
|
||||||
|
if (prevHoleCovered != holeCovered) {
|
||||||
|
fingersChanged = 1;
|
||||||
|
|
||||||
|
tempNewNote = get_note(holeCovered); //get the next MIDI note from the fingering pattern if it has changed
|
||||||
|
send_fingers(); //send new fingering pattern to the Configuration Tool
|
||||||
|
prevHoleCovered = holeCovered;
|
||||||
|
if (pitchBendMode == kPitchBendSlideVibrato || pitchBendMode == kPitchBendLegatoSlideVibrato) {
|
||||||
|
findStepsDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (tempNewNote != -1 && newNote != tempNewNote) { //If a new note has been triggered
|
||||||
|
if (pitchBendMode != kPitchBendNone) {
|
||||||
|
holeLatched = holeCovered; //remember the pattern that triggered it (it will be used later for vibrato)
|
||||||
|
for (byte i = 0; i < 9; i++) {
|
||||||
|
iPitchBend[i] = 0; //and reset pitchbend
|
||||||
|
pitchBendOn[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newNote = tempNewNote; //update the next note if the fingering pattern is valid
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHANGES: I moved the line that was here before inside previous if block, otherwise it does not seem to be actually checked for validity
|
||||||
|
// Plus, it has no use to update newNote if it is equal to tempNewNote.
|
||||||
|
|
||||||
|
// Handle Drone notes in Boha mode
|
||||||
|
if (modeSelector[mode] == kModeBoha) {
|
||||||
|
// Get the drone note
|
||||||
|
tempNewNote = get_noteDrone(holeCovered);
|
||||||
|
if (tempNewNote != -1 && newDroneNote != tempNewNote) {
|
||||||
|
newDroneNote = tempNewNote;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (sensorDataReady) {
|
||||||
|
get_state();//get the breath state from the pressure sensor if there's been a reading.
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long nowtime = millis();
|
||||||
|
|
||||||
|
if (switches[mode][SEND_VELOCITY]) { //if we're sending NoteOn velocity based on pressure
|
||||||
|
if (prevState == 1 && newState != 1) {
|
||||||
|
velocityDelayTimer = nowtime; //reset the delay timer used for calculating velocity when a note is turned on after silence.
|
||||||
|
}
|
||||||
|
prevState = newState;
|
||||||
|
}
|
||||||
|
|
||||||
|
get_shift(); //shift the next note up or down based on register, key, and characteristics of the current fingering pattern.
|
||||||
|
|
||||||
|
if ((nowtime - pressureTimer) >= ((nowtime - noteOnTimestamp) < 20 ? 2 : 5)) {
|
||||||
|
pressureTimer = nowtime;
|
||||||
|
if (abs(prevSensorValue - sensorValue) > 1) { //if pressure has changed more than a little, send it.
|
||||||
|
if (ED[mode][SEND_PRESSURE]) {
|
||||||
|
calculatePressure(0);
|
||||||
|
}
|
||||||
|
if (switches[mode][SEND_VELOCITY]) {
|
||||||
|
calculatePressure(1);
|
||||||
|
}
|
||||||
|
if (switches[mode][SEND_AFTERTOUCH] & 1) {
|
||||||
|
calculatePressure(2);
|
||||||
|
}
|
||||||
|
if (switches[mode][SEND_AFTERTOUCH] & 2) {
|
||||||
|
calculatePressure(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendPressure(false);
|
||||||
|
|
||||||
|
if (communicationMode) {
|
||||||
|
sendUSBMIDI(CC, 7, 116, sensorValue & 0x7F); //send LSB of current pressure to configuration tool
|
||||||
|
sendUSBMIDI(CC, 7, 118, sensorValue >> 7); //send MSB of current pressure
|
||||||
|
}
|
||||||
|
prevSensorValue = sensorValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((nowtime - pitchBendTimer) >= 9) { //check pitchbend and send pressure data every so often
|
||||||
|
pitchBendTimer = nowtime;
|
||||||
|
|
||||||
|
calculateAndSendPitchbend();
|
||||||
|
|
||||||
|
|
||||||
|
counter++;
|
||||||
|
|
||||||
|
if (counter == 10) { //we check every 10 ticks to see if the bell sensor has been plugged/unplugged.
|
||||||
|
|
||||||
|
counter = 0;
|
||||||
|
bellSensor = (digitalRead2(17)); //check if the bell sensor is plugged in
|
||||||
|
if (prevBellSensor != bellSensor) {
|
||||||
|
prevBellSensor = bellSensor;
|
||||||
|
if (communicationMode) {
|
||||||
|
sendUSBMIDI(CC, 7, 102, 120 + bellSensor); //if it's changed, tell the configuration tool.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Serial.println(newState);
|
||||||
|
//Serial.println(ED[mode][VELOCITY_INPUT_PRESSURE_MIN]);
|
||||||
|
//Serial.println(outputBounds[0][0]);
|
||||||
|
//Serial.println(inputPressureBounds[0][3]);
|
||||||
|
//FREERAM_PRINT
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sendNote(); //send the MIDI note
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user