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