diff --git a/Open_Theremin_V3/Open_Theremin_V3.ino b/Open_Theremin_V3/Open_Theremin_V3.ino index 4022591..b70f0b9 100644 --- a/Open_Theremin_V3/Open_Theremin_V3.ino +++ b/Open_Theremin_V3/Open_Theremin_V3.ino @@ -1,6 +1,8 @@ /* * Open Theremin V3 with MIDI interface control software for Arduino UNO * Based on Open Theremin V3 version 3.0 Copyright (C) 2010-2016 by Urs Gaudenz + * + * Also integrate changes from Open Theremin V3 Version 3.1 Copyright (C) 2010-2020 by Urs Gaudenz * * * Open Theremin V3 with MIDI interface control software is free software: @@ -19,9 +21,10 @@ * the Open Theremin V3 with MIDI interface control software. * If not, see . * - * Urs Gaudenz also credits for their important contributions to Open Theremin V3: + * Also credited for their important contributions to Open Theremin V3: * David Harvey * Michael Margolis + * "Theremingenieur" Thierry Frenkel */ /* Midi added by Vincent Dhamelincourt - September 2017. diff --git a/Open_Theremin_V3/SPImcpDAC.h b/Open_Theremin_V3/SPImcpDAC.h new file mode 100644 index 0000000..e87f9e8 --- /dev/null +++ b/Open_Theremin_V3/SPImcpDAC.h @@ -0,0 +1,104 @@ +/* Control the mcp 4921/4922 DACs with hardware SPI of the Arduino UNO + * ...without all the overhead of the Arduino SPI lib... + * Just the needed functions in a runtime optimized way by "Theremingenieur" Thierry Frenkel + * This file 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. + */ + +#ifndef SPImcpDac_h +#define SPImcpDac_h + +#include + +// Data direction & Port register & Bit number for DAC Latch: +#define MCP_DAC_LDAC_DDR DDRD +#define MCP_DAC_LDAC_PORT PORTD +#define MCP_DAC_LDAC_BIT 7 +// Data direction & Port register & Bit number for DAC CS +#define MCP_DAC_CS_DDR DDRB +#define MCP_DAC_CS_PORT PORTB +#define MCP_DAC_CS_BIT 2 +// Data direction & Port register & Bit number for DAC2 CS +#define MCP_DAC2_CS_DDR DDRB +#define MCP_DAC2_CS_PORT PORTB +#define MCP_DAC2_CS_BIT 1 +// Data direction & Port registers & Bit numbers for Hardware SPI +#define HW_SPI_DDR DDRB +#define HW_SPI_SCK_BIT 5 +#define HW_SPI_MISO_BIT 4 // unused in this configuration +#define HW_SPI_MOSI_BIT 3 + +static inline void SPImcpDACinit() +{ + // initialize the latch pin: + MCP_DAC_LDAC_DDR |= _BV(MCP_DAC_LDAC_BIT); + MCP_DAC_LDAC_PORT |= _BV(MCP_DAC_LDAC_BIT); + // initialize the CS pins: + MCP_DAC_CS_DDR |= _BV(MCP_DAC_CS_BIT); + MCP_DAC_CS_PORT |= _BV(MCP_DAC_CS_BIT); + MCP_DAC2_CS_DDR |= _BV(MCP_DAC2_CS_BIT); + MCP_DAC2_CS_PORT |= _BV(MCP_DAC2_CS_BIT); + // initialize the hardware SPI pins: + HW_SPI_DDR |= _BV(HW_SPI_SCK_BIT); + HW_SPI_DDR |= _BV(HW_SPI_MOSI_BIT); + // initialize the hardware SPI registers + SPCR = _BV(SPE) | _BV(MSTR); // no interrupt, SPI enable, MSB first, SPI master, SPI mode 0, clock = f_osc/4 (maximum) + SPSR = _BV(SPI2X); // double the SPI clock, ideally we get 8 MHz, so that a 16bit word goes out in 3.5us (5.6us when called from an interrupt) including CS asserting/deasserting +} + +static inline void SPImcpDACtransmit(uint16_t data) +{ + // Send highbyte and wait for complete + SPDR = highByte(data); + asm("nop"); + while (!(SPSR & _BV(SPIF))) + ; + // Send lowbyte and wait for complete + SPDR = lowByte(data); + asm("nop"); + while (!(SPSR & _BV(SPIF))) + ; +} + +static inline void SPImcpDAClatch() +{ + MCP_DAC_LDAC_PORT &= ~_BV(MCP_DAC_LDAC_BIT); + MCP_DAC_LDAC_PORT |= _BV(MCP_DAC_LDAC_BIT); +} + +static inline void SPImcpDACsend(uint16_t data) +{ + MCP_DAC_CS_PORT &= ~_BV(MCP_DAC_CS_BIT); + // Sanitize input data and add DAC config MSBs + data &= 0x0FFF; + data |= 0x7000; + SPImcpDACtransmit(data); + MCP_DAC_CS_PORT |= _BV(MCP_DAC_CS_BIT); + // Do not latch immpediately, let's do it at the very beginning of the next interrupt to get consistent timing +} + +static inline void SPImcpDAC2Asend(uint16_t data) +{ + MCP_DAC2_CS_PORT &= ~_BV(MCP_DAC2_CS_BIT); + // Sanitize input data and add DAC config MSBs + data &= 0x0FFF; + data |= 0x7000; + SPImcpDACtransmit(data); + MCP_DAC2_CS_PORT |= _BV(MCP_DAC2_CS_BIT); + SPImcpDAClatch(); +} + +static inline void SPImcpDAC2Bsend(uint16_t data) +{ + MCP_DAC2_CS_PORT &= ~_BV(MCP_DAC2_CS_BIT); + // Sanitize input data and add DAC config MSBs + data &= 0x0FFF; + data |= 0xF000; + SPImcpDACtransmit(data); + MCP_DAC2_CS_PORT |= _BV(MCP_DAC2_CS_BIT); + SPImcpDAClatch(); +} + +#endif diff --git a/Open_Theremin_V3/application.cpp b/Open_Theremin_V3/application.cpp index 69ade8f..b51bce0 100644 --- a/Open_Theremin_V3/application.cpp +++ b/Open_Theremin_V3/application.cpp @@ -196,7 +196,6 @@ AppMode Application::nextMode() { void Application::loop() { int32_t pitch_v = 0, pitch_l = 0; // Last value of pitch (for filtering) int32_t vol_v = 0, vol_l = 0; // Last value of volume (for filtering) - uint16_t tmpVolume; uint16_t volumePotValue = 0; uint16_t pitchPotValue = 0; diff --git a/Open_Theremin_V3/ihandlers.cpp b/Open_Theremin_V3/ihandlers.cpp index a4da417..32a1e4b 100644 --- a/Open_Theremin_V3/ihandlers.cpp +++ b/Open_Theremin_V3/ihandlers.cpp @@ -1,7 +1,7 @@ #include "Arduino.h" #include "ihandlers.h" -#include "mcpDac.h" +#include "SPImcpDAC.h" #include "timer.h" #include "build.h" @@ -15,7 +15,7 @@ #include "theremin_sintable7.c" #include "theremin_sintable8.c" -const int16_t* const wavetables[] PROGMEM = { +const int16_t* const wavetables[] = { //Fixed following a suggestion by Michael Freitas, does not need to be in PROGMEM sine_table, sine_table2, sine_table3, @@ -26,12 +26,12 @@ const int16_t* const wavetables[] PROGMEM = { sine_table8 }; -static const uint32_t MCP_DAC_BASE = 2047; +static const uint32_t MCP_DAC_BASE = 2048; #define INT0_STATE (PIND & (1<>6) & 0x3ff; #if CV_ENABLED // Generator for CV output vPointerIncrement = min(vPointerIncrement, 4095); - mcpDacSend(vPointerIncrement); //Send result to Digital to Analogue Converter (audio out) (9.6 us) + SPImcpDACsend(vPointerIncrement); //Send result to Digital to Analogue Converter (audio out) (5.5 us) #else //Play sound - // Read next wave table value (3.0us) - // The slightly odd tactic here is to provide compile-time expressions for the wavetable - // positions. Making addr1 the index into the wavtables array breaks the time limit for - // the interrupt handler - switch (vWavetableSelector) { - case 1: waveSample = (int16_t) pgm_read_word_near(wavetables[1] + offset); break; - case 2: waveSample = (int16_t) pgm_read_word_near(wavetables[2] + offset); break; - case 3: waveSample = (int16_t) pgm_read_word_near(wavetables[3] + offset); break; - case 4: waveSample = (int16_t) pgm_read_word_near(wavetables[4] + offset); break; - case 5: waveSample = (int16_t) pgm_read_word_near(wavetables[5] + offset); break; - case 6: waveSample = (int16_t) pgm_read_word_near(wavetables[6] + offset); break; - case 7: waveSample = (int16_t) pgm_read_word_near(wavetables[7] + offset); break; - default: waveSample = (int16_t) pgm_read_word_near(wavetables[0] + offset); break; - }; + // Read next wave table value + waveSample = (int16_t)pgm_read_word_near(wavetables[vWavetableSelector] + offset); + + scaledSample = ((int32_t)waveSample * (uint32_t)vScaledVolume) >> 16; // The compiler optimizes this better than any assembly written by hand !!! - if (waveSample > 0) { // multiply 16 bit wave number by 8 bit volume value (11.2us / 5.4us) - scaledSample = MCP_DAC_BASE + (mul_16_8(waveSample, vScaledVolume) >> 8); - } else { - scaledSample = MCP_DAC_BASE - (mul_16_8(-waveSample, vScaledVolume) >> 8); - } - mcpDacSend(scaledSample); //Send result to Digital to Analogue Converter (audio out) (9.6 us) + SPImcpDACsend(scaledSample + MCP_DAC_BASE); //Send result to Digital to Analogue Converter (audio out) (5.5 us) - pointer = pointer + vPointerIncrement; // increment table pointer (ca. 2us) + pointer += vPointerIncrement; // increment table pointer #endif //CV play sound incrementTimer(); // update 32us timer diff --git a/Open_Theremin_V3/ihandlers.h b/Open_Theremin_V3/ihandlers.h index d6de5d0..07c3c71 100644 --- a/Open_Theremin_V3/ihandlers.h +++ b/Open_Theremin_V3/ihandlers.h @@ -3,7 +3,7 @@ extern volatile uint16_t pitch; // Pitch value extern volatile uint16_t vol; // Volume value -extern volatile uint8_t vScaledVolume; // Volume byte +extern volatile uint16_t vScaledVolume; // Volume byte extern volatile uint16_t pitch_counter; // Pitch counter extern volatile uint16_t pitch_counter_l; // Last value of pitch counter @@ -41,4 +41,4 @@ void resetVolFlag(); void savePitchCounter(); void saveVolCounter(); -#endif // _IHANDLERS_H +#endif // _IHANDLERS_H