parent
d4c161eeeb
commit
b08b78404e
@ -0,0 +1,56 @@ |
||||
/**
|
||||
* \file |
||||
* Pin definitions |
||||
*/ |
||||
|
||||
#ifndef WavePinDefs_h |
||||
#define WavePinDefs_h |
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// DAC pin definitions
|
||||
|
||||
// LDAC may be connected to ground to save a pin
|
||||
/** Set USE_MCP_DAC_LDAC to 0 if LDAC is grounded. */ |
||||
#define USE_MCP_DAC_LDAC 1 |
||||
|
||||
// use arduino pins 2, 3, 4, 5 for DAC
|
||||
|
||||
// pin 2 is DAC chip select
|
||||
|
||||
/** Data direction register for DAC chip select. */ |
||||
#define MCP_DAC_CS_DDR DDRB |
||||
#define MCP_DAC2_CS_DDR DDRB |
||||
/** Port register for DAC chip select. */ |
||||
#define MCP_DAC_CS_PORT PORTB |
||||
/** Port bit number for DAC chip select. */ |
||||
#define MCP_DAC_CS_BIT 2 |
||||
#define MCP_DAC2_CS_BIT 1 |
||||
|
||||
// pin 3 is DAC serial clock
|
||||
/** Data direction register for DAC clock. */ |
||||
#define MCP_DAC_SCK_DDR DDRB |
||||
/** Port register for DAC clock. */ |
||||
#define MCP_DAC_SCK_PORT PORTB |
||||
/** Port bit number for DAC clock. */ |
||||
#define MCP_DAC_SCK_BIT 5 |
||||
|
||||
// pin 4 is DAC serial data in
|
||||
|
||||
/** Data direction register for DAC serial in. */ |
||||
#define MCP_DAC_SDI_DDR DDRB |
||||
/** Port register for DAC clock. */ |
||||
#define MCP_DAC_SDI_PORT PORTB |
||||
/** Port bit number for DAC clock. */ |
||||
#define MCP_DAC_SDI_BIT 3 |
||||
|
||||
// pin 5 is LDAC if used
|
||||
#if USE_MCP_DAC_LDAC |
||||
/** Data direction register for Latch DAC Input. */ |
||||
#define MCP_DAC_LDAC_DDR DDRD |
||||
/** Port register for Latch DAC Input. */ |
||||
#define MCP_DAC_LDAC_PORT PORTD |
||||
/** Port bit number for Latch DAC Input. */ |
||||
#define MCP_DAC_LDAC_BIT 7 |
||||
#endif // USE_MCP_DAC_LDAC
|
||||
|
||||
#endif // WavePinDefs_h
|
@ -0,0 +1,79 @@ |
||||
/*
|
||||
* Open.Theremin control software for Arduino.UNO |
||||
* Version 3.0 |
||||
* Copyright (C) 2010-2016 by Urs Gaudenz |
||||
* |
||||
* Open.Theremin.UNO control software 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. |
||||
* |
||||
* Open.Theremin.UNO control software 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 |
||||
* the Open.Theremin.UNO control software. If not, see <http://www.gnu.org/licenses/>.
|
||||
* |
||||
* With important contributions by
|
||||
* David Harvey |
||||
* Michael Margolis |
||||
*/ |
||||
|
||||
/**
|
||||
Building the code |
||||
================= |
||||
build.h contains #defines that control the compilation of the code |
||||
|
||||
ENABLE_SERIAL - if non-0, the build will include code to write the detected |
||||
pitch to the serial connection every 100 milliseconds. Set serial |
||||
receive baud to 115200 |
||||
|
||||
ENABLE_CV - if non-0, emit cv output on pin 6 (EXPERIMENTAL!) |
||||
|
||||
Structure of the code |
||||
===================== |
||||
** Open_Theremin_UNO.ino ** |
||||
This file. Creates and hooks up the application object to the arduino setup() |
||||
and loop() callbacks. |
||||
|
||||
** application.h/application.cpp ** |
||||
Main application object. Holds the state of the app (playing, calibrating), deals |
||||
with initialisation and the app main loop, reads pitch and volume changed flags |
||||
from the interrupt handlers and sets pitch and volume values which the timer |
||||
interrupt sends to the DAC. |
||||
|
||||
** OTPinDefs.h ** |
||||
Pin definitions for the DAC. |
||||
|
||||
** build.h ** |
||||
Preprocessor definitions for build (see above). |
||||
|
||||
** hw.h ** |
||||
Definitions for hardware button and LED. |
||||
|
||||
** ihandlers.h/ihandlers.cpp |
||||
Interrupt handler code and volatile variables implementing the communication between |
||||
the app and its input/output. |
||||
|
||||
** theremin_sinetable<N>.c ** |
||||
Wavetable data for a variety of sounds. Switchable via the potentiometer. |
||||
|
||||
** timer.h/timer.cpp ** |
||||
Definitions and functions for setting delays in tics and in milliseconds |
||||
|
||||
*/ |
||||
|
||||
#include "application.h" |
||||
|
||||
Application app; |
||||
|
||||
void setup() { |
||||
app.setup(); |
||||
} |
||||
|
||||
void loop() { |
||||
app.loop(); |
||||
} |
||||
|
@ -0,0 +1,480 @@ |
||||
#include "Arduino.h" |
||||
|
||||
#include "application.h" |
||||
|
||||
#include "hw.h" |
||||
#include "mcpDac.h" |
||||
#include "ihandlers.h" |
||||
#include "timer.h" |
||||
|
||||
const AppMode AppModeValues[] = {MUTE,NORMAL}; |
||||
|
||||
static int32_t pitchCalibrationBase = 0; |
||||
static int32_t pitchCalibrationBaseFreq = 0; |
||||
static int32_t pitchCalibrationConstant = 0; |
||||
static int32_t pitchSensitivityConstant = 70000; |
||||
static float qMeasurement = 0; |
||||
|
||||
static int32_t volCalibrationBase = 0; |
||||
|
||||
Application::Application() |
||||
: _state(PLAYING), |
||||
_mode(NORMAL) { |
||||
}; |
||||
|
||||
void Application::setup() { |
||||
#if SERIAL_ENABLED |
||||
Serial.begin(Application::BAUD); |
||||
#endif |
||||
|
||||
HW_LED1_ON;HW_LED2_OFF; |
||||
|
||||
pinMode(Application::BUTTON_PIN, INPUT_PULLUP); |
||||
pinMode(Application::LED_PIN_1, OUTPUT); |
||||
pinMode(Application::LED_PIN_2, OUTPUT); |
||||
|
||||
digitalWrite(Application::LED_PIN_1, HIGH); // turn the LED off by making the voltage LOW
|
||||
|
||||
|
||||
initialiseTimer(); |
||||
initialiseInterrupts(); |
||||
|
||||
mcpDacInit(); |
||||
/// TEST
|
||||
|
||||
calibrate_pitch(); |
||||
calibrate_volume(); |
||||
|
||||
|
||||
initialiseTimer(); |
||||
initialiseInterrupts(); |
||||
|
||||
|
||||
#if CV_ENABLED |
||||
initialiseCVOut(); |
||||
#endif |
||||
|
||||
playStartupSound(); |
||||
calibrate(); |
||||
|
||||
} |
||||
|
||||
void Application::initialiseTimer() { |
||||
ihInitialiseTimer(); |
||||
} |
||||
|
||||
void Application::initialiseInterrupts() { |
||||
ihInitialiseInterrupts(); |
||||
} |
||||
|
||||
void Application::InitialisePitchMeasurement() { |
||||
ihInitialisePitchMeasurement(); |
||||
} |
||||
|
||||
void Application::InitialiseVolumeMeasurement() { |
||||
ihInitialiseVolumeMeasurement(); |
||||
} |
||||
|
||||
unsigned long Application::GetQMeasurement() |
||||
{ |
||||
int qn=0; |
||||
|
||||
TCCR1B = (1<<CS10);
|
||||
|
||||
while(!(PIND & (1<<PORTD3))); |
||||
while((PIND & (1<<PORTD3))); |
||||
|
||||
TCNT1 = 0; |
||||
timer_overflow_counter = 0; |
||||
while(qn<31250){ |
||||
while(!(PIND & (1<<PORTD3))); |
||||
qn++; |
||||
while((PIND & (1<<PORTD3))); |
||||
}; |
||||
|
||||
|
||||
|
||||
TCCR1B = 0;
|
||||
|
||||
unsigned long frequency = TCNT1; |
||||
unsigned long temp = 65536*(unsigned long)timer_overflow_counter; |
||||
frequency += temp; |
||||
|
||||
return frequency; |
||||
|
||||
} |
||||
|
||||
|
||||
unsigned long Application::GetPitchMeasurement() |
||||
{ |
||||
TCNT1 = 0; |
||||
timer_overflow_counter = 0; |
||||
TCCR1B = (1<<CS12) | (1<<CS11) | (1<<CS10);
|
||||
|
||||
delay(1000);
|
||||
|
||||
TCCR1B = 0;
|
||||
|
||||
unsigned long frequency = TCNT1; |
||||
unsigned long temp = 65536*(unsigned long)timer_overflow_counter; |
||||
frequency += temp; |
||||
|
||||
return frequency; |
||||
|
||||
} |
||||
|
||||
unsigned long Application::GetVolumeMeasurement() |
||||
{timer_overflow_counter = 0; |
||||
|
||||
TCNT0=0; |
||||
TCNT1=49911; |
||||
TCCR0B = (1<<CS02) | (1<<CS01) | (1<<CS00); // //External clock source on T0 pin. Clock on rising edge.
|
||||
TIFR1 = (1<<TOV1); //Timer1 INT Flag Reg: Clear Timer Overflow Flag
|
||||
|
||||
while(!(TIFR1&((1<<TOV1)))); // on Timer 1 overflow (1s)
|
||||
TCCR0B = 0; // Stop TimerCounter 0
|
||||
unsigned long frequency = TCNT0; // get counter 0 value
|
||||
unsigned long temp = (unsigned long)timer_overflow_counter; // and overflow counter
|
||||
|
||||
frequency += temp*256; |
||||
|
||||
return frequency; |
||||
} |
||||
|
||||
|
||||
|
||||
#if CV_ENABLED // Initialise PWM Generator for CV output
|
||||
void initialiseCVOut() { |
||||
|
||||
} |
||||
#endif |
||||
|
||||
AppMode Application::nextMode() { |
||||
return _mode == NORMAL ? MUTE : AppModeValues[_mode + 1]; |
||||
} |
||||
|
||||
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)
|
||||
int32_t pitch_f = 0; // pitch in frequency (for filtering)
|
||||
|
||||
uint16_t volumePotValue = 0; |
||||
uint16_t pitchPotValue = 0; |
||||
|
||||
|
||||
|
||||
mloop: // Main loop avoiding the GCC "optimization"
|
||||
|
||||
pitchPotValue = analogRead(PITCH_POT); |
||||
volumePotValue = analogRead(VOLUME_POT); |
||||
|
||||
vWavetableSelector = analogRead(WAVE_SELECT_POT) >> 7; |
||||
|
||||
|
||||
if (_state == PLAYING && HW_BUTTON_PRESSED) { |
||||
_state = CALIBRATING; |
||||
resetTimer(); |
||||
} |
||||
|
||||
if (_state == CALIBRATING && HW_BUTTON_RELEASED) { |
||||
if (timerExpired(1500)) { |
||||
|
||||
_mode = nextMode(); |
||||
if (_mode==NORMAL) {HW_LED1_ON;HW_LED2_OFF;} else {HW_LED1_OFF;HW_LED2_ON;}; |
||||
// playModeSettingSound();
|
||||
|
||||
|
||||
} |
||||
_state = PLAYING; |
||||
}; |
||||
|
||||
if (_state == CALIBRATING && timerExpired(10000)) { |
||||
|
||||
HW_LED2_ON; |
||||
playCalibratingCountdownSound(); |
||||
|
||||
calibrate(); |
||||
|
||||
_mode=NORMAL; |
||||
HW_LED2_OFF; |
||||
|
||||
while (HW_BUTTON_PRESSED) |
||||
; // NOP
|
||||
_state = PLAYING; |
||||
}; |
||||
|
||||
#if CV_ENABLED |
||||
OCR0A = pitch & 0xff; |
||||
#endif |
||||
|
||||
#if SERIAL_ENABLED |
||||
if (timerExpired(TICKS_100_MILLIS)) { |
||||
resetTimer(); |
||||
Serial.write(pitch & 0xff); // Send char on serial (if used)
|
||||
Serial.write((pitch >> 8) & 0xff); |
||||
} |
||||
#endif |
||||
|
||||
if (pitchValueAvailable) { // If capture event
|
||||
|
||||
pitch_v=pitch; // Averaging pitch values
|
||||
pitch_v=pitch_l+((pitch_v-pitch_l)>>2); |
||||
pitch_l=pitch_v; |
||||
|
||||
pitch_f=FREQ_FACTOR/pitch_v; |
||||
|
||||
//HW_LED2_ON;
|
||||
|
||||
|
||||
// set wave frequency for each mode
|
||||
switch (_mode) { |
||||
case MUTE : /* NOTHING! */; break; |
||||
case NORMAL : setWavetableSampleAdvance((pitchCalibrationBase-pitch_v)/4+2048-(pitchPotValue<<2)); break; |
||||
}; |
||||
|
||||
// HW_LED2_OFF;
|
||||
|
||||
pitchValueAvailable = false; |
||||
} |
||||
|
||||
if (volumeValueAvailable) { |
||||
vol = max(vol, 5000); |
||||
|
||||
vol_v=vol; // Averaging volume values
|
||||
vol_v=vol_l+((vol_v-vol_l)>>2); |
||||
vol_l=vol_v; |
||||
|
||||
switch (_mode) { |
||||
case MUTE: vol_v = 0; break; |
||||
case NORMAL: vol_v = MAX_VOLUME-(volCalibrationBase-vol_v)/2+(volumePotValue<<2)-1024; break; |
||||
}; |
||||
|
||||
// Limit and set volume value
|
||||
vol_v = min(vol_v, 4095); |
||||
// vol_v = vol_v - (1 + MAX_VOLUME - (volumePotValue << 2));
|
||||
vol_v = vol_v ; |
||||
vol_v = max(vol_v, 0); |
||||
vScaledVolume = vol_v >> 4; |
||||
|
||||
volumeValueAvailable = false; |
||||
} |
||||
|
||||
goto mloop; // End of main loop
|
||||
} |
||||
|
||||
void Application::calibrate() |
||||
{ |
||||
resetPitchFlag(); |
||||
resetTimer(); |
||||
savePitchCounter(); |
||||
while (!pitchValueAvailable && timerUnexpiredMillis(10)) |
||||
; // NOP
|
||||
pitchCalibrationBase = pitch; |
||||
pitchCalibrationBaseFreq = FREQ_FACTOR/pitchCalibrationBase; |
||||
pitchCalibrationConstant = FREQ_FACTOR/pitchSensitivityConstant/2+200; |
||||
|
||||
resetVolFlag(); |
||||
resetTimer(); |
||||
saveVolCounter(); |
||||
while (!volumeValueAvailable && timerUnexpiredMillis(10)) |
||||
; // NOP
|
||||
volCalibrationBase = vol; |
||||
} |
||||
|
||||
void Application::calibrate_pitch() |
||||
{ |
||||
|
||||
static int16_t pitchXn0 = 0; |
||||
static int16_t pitchXn1 = 0; |
||||
static int16_t pitchXn2 = 0; |
||||
static float q0 = 0; |
||||
static long pitchfn0 = 0; |
||||
static long pitchfn1 = 0; |
||||
static long pitchfn = 0; |
||||
|
||||
Serial.begin(9600); |
||||
Serial.println("Pitch calibration"); |
||||
|
||||
|
||||
InitialisePitchMeasurement(); |
||||
interrupts(); |
||||
mcpDacInit(); |
||||
|
||||
qMeasurement = GetQMeasurement(); |
||||
Serial.print("Q "); |
||||
Serial.println(qMeasurement); |
||||
|
||||
q0 = (16000000/qMeasurement*500000); |
||||
Serial.println(q0); |
||||
|
||||
pitchXn0 = 0; |
||||
pitchXn1 = 4095; |
||||
|
||||
pitchfn = q0-600; |
||||
|
||||
mcpDac2BSend(1600); |
||||
|
||||
mcpDac2ASend(pitchXn0); |
||||
delay(100); |
||||
pitchfn0 = GetPitchMeasurement(); |
||||
|
||||
mcpDac2ASend(pitchXn1); |
||||
delay(100); |
||||
pitchfn1 = GetPitchMeasurement(); |
||||
|
||||
Serial.print(pitchfn0); |
||||
Serial.print(" "); |
||||
Serial.println(pitchfn1); |
||||
|
||||
|
||||
while(abs(pitchfn0-pitchfn1)>10){ |
||||
|
||||
mcpDac2ASend(pitchXn0); |
||||
delay(100); |
||||
pitchfn0 = GetPitchMeasurement()-pitchfn; |
||||
|
||||
mcpDac2ASend(pitchXn1); |
||||
delay(100); |
||||
pitchfn1 = GetPitchMeasurement()-pitchfn; |
||||
|
||||
pitchXn2=pitchXn1-((pitchXn1-pitchXn0)*pitchfn1)/(pitchfn1-pitchfn0); |
||||
|
||||
Serial.print(pitchXn0); |
||||
Serial.print(" "); |
||||
Serial.println(pitchfn0); |
||||
|
||||
Serial.print(pitchXn1); |
||||
Serial.print(" "); |
||||
|
||||
Serial.println(pitchfn1); |
||||
Serial.println(pitchXn2); |
||||
|
||||
Serial.println(); |
||||
pitchXn0 = pitchXn1; |
||||
pitchXn1 = pitchXn2; |
||||
|
||||
HW_LED2_TOGGLE; |
||||
|
||||
} |
||||
digitalWrite(Application::LED_PIN_2, LOW); // turn the LED off by making the voltage LOW
|
||||
|
||||
} |
||||
|
||||
void Application::calibrate_volume() |
||||
{ |
||||
|
||||
|
||||
static int16_t volumeXn0 = 0; |
||||
static int16_t volumeXn1 = 0; |
||||
static int16_t volumeXn2 = 0; |
||||
static float q0 = 0; |
||||
static long volumefn0 = 0; |
||||
static long volumefn1 = 0; |
||||
static long volumefn = 0; |
||||
|
||||
Serial.begin(9600); |
||||
Serial.println("Volume calibration"); |
||||
|
||||
InitialiseVolumeMeasurement(); |
||||
interrupts(); |
||||
mcpDacInit(); |
||||
|
||||
|
||||
volumeXn0 = 0; |
||||
volumeXn1 = 4095; |
||||
|
||||
q0 = (16000000/qMeasurement*460765); |
||||
|
||||
volumefn = q0-600; |
||||
|
||||
|
||||
mcpDac2BSend(volumeXn0); |
||||
delay_NOP(44316);//44316=100ms
|
||||
|
||||
volumefn0 = GetVolumeMeasurement(); |
||||
|
||||
mcpDac2BSend(volumeXn1); |
||||
|
||||
delay_NOP(44316);//44316=100ms
|
||||
volumefn1 = GetVolumeMeasurement(); |
||||
|
||||
|
||||
Serial.print(volumefn0); |
||||
Serial.print(" "); |
||||
Serial.println(volumefn1); |
||||
|
||||
|
||||
while(abs(volumefn0-volumefn1)>10){ |
||||
|
||||
mcpDac2BSend(volumeXn0); |
||||
delay_NOP(44316);//44316=100ms
|
||||
volumefn0 = GetVolumeMeasurement()-volumefn; |
||||
|
||||
mcpDac2BSend(volumeXn1); |
||||
delay_NOP(44316);//44316=100ms
|
||||
volumefn1 = GetVolumeMeasurement()-volumefn; |
||||
|
||||
volumeXn2=volumeXn1-((volumeXn1-volumeXn0)*volumefn1)/(volumefn1-volumefn0); |
||||
|
||||
Serial.print(volumeXn0); |
||||
Serial.print(" "); |
||||
Serial.println(volumefn0); |
||||
|
||||
Serial.print(volumeXn1); |
||||
Serial.print(" "); |
||||
|
||||
Serial.println(volumefn1); |
||||
Serial.println(volumeXn2); |
||||
|
||||
Serial.println(); |
||||
volumeXn0 = volumeXn1; |
||||
volumeXn1 = volumeXn2; |
||||
HW_LED2_TOGGLE; |
||||
|
||||
} |
||||
digitalWrite(Application::LED_PIN_2, HIGH); // turn the LED off by making the voltage LOW
|
||||
|
||||
} |
||||
|
||||
void Application::hzToAddVal(float hz) { |
||||
setWavetableSampleAdvance((uint16_t)(hz * HZ_ADDVAL_FACTOR)); |
||||
} |
||||
|
||||
void Application::playNote(float hz, uint16_t milliseconds = 500, uint8_t volume = 255) { |
||||
vScaledVolume = volume; |
||||
hzToAddVal(hz); |
||||
millitimer(milliseconds); |
||||
vScaledVolume = 0; |
||||
} |
||||
|
||||
void Application::playStartupSound() { |
||||
playNote(MIDDLE_C, 150, 25); |
||||
playNote(MIDDLE_C * 2, 150, 25); |
||||
playNote(MIDDLE_C * 4, 150, 25); |
||||
} |
||||
|
||||
void Application::playCalibratingCountdownSound() { |
||||
for (int i = 0; i < 5; i++) { |
||||
playNote(MIDDLE_C, 500, 25); |
||||
millitimer(150); |
||||
} |
||||
playNote(MIDDLE_C * 2, 1000, 25); |
||||
} |
||||
|
||||
void Application::playModeSettingSound() { |
||||
for (int i = 0; i <= _mode; i++) { |
||||
playNote(MIDDLE_C * 2, 200, 25); |
||||
millitimer(100); |
||||
} |
||||
} |
||||
|
||||
void Application::delay_NOP(unsigned long time) { |
||||
volatile unsigned long i = 0; |
||||
for (i = 0; i < time; i++) { |
||||
__asm__ __volatile__ ("nop"); |
||||
} |
||||
} |
||||
|
||||
|
||||
|
||||
|
@ -0,0 +1,69 @@ |
||||
#ifndef _APPLICATION_H |
||||
#define _APPLICATION_H |
||||
|
||||
#include <avr/io.h> |
||||
|
||||
#include "build.h" |
||||
|
||||
enum AppState {CALIBRATING = 0, PLAYING}; |
||||
enum AppMode {MUTE = 0, NORMAL}; |
||||
|
||||
class Application { |
||||
public: |
||||
Application(); |
||||
|
||||
void setup(); |
||||
void loop(); |
||||
|
||||
private: |
||||
static const uint16_t MAX_VOLUME = 4095; |
||||
static const uint32_t TRIM_PITCH_FACTOR = 33554432; |
||||
static const uint32_t FREQ_FACTOR = 1600000000; |
||||
|
||||
static const int16_t BUTTON_PIN = 6; |
||||
static const int16_t LED_PIN_1 = 18; |
||||
static const int16_t LED_PIN_2 = 19; |
||||
|
||||
|
||||
static const int16_t PITCH_POT = 0; |
||||
static const int16_t VOLUME_POT = 1; |
||||
static const int16_t WAVE_SELECT_POT = 2; |
||||
static const int16_t REGISTER_SELECT_POT = 3; |
||||
|
||||
|
||||
|
||||
#if SERIAL_ENABLED |
||||
static const int BAUD = 115200; |
||||
#endif |
||||
|
||||
AppState _state; |
||||
AppMode _mode; |
||||
|
||||
void calibrate(); |
||||
void calibrate_pitch(); |
||||
void calibrate_volume(); |
||||
|
||||
|
||||
AppMode nextMode(); |
||||
|
||||
void initialiseTimer(); |
||||
void initialiseInterrupts(); |
||||
void InitialisePitchMeasurement(); |
||||
void InitialiseVolumeMeasurement(); |
||||
unsigned long GetPitchMeasurement(); |
||||
unsigned long GetVolumeMeasurement(); |
||||
unsigned long GetQMeasurement(); |
||||
|
||||
|
||||
static const float HZ_ADDVAL_FACTOR = 2.09785; |
||||
static const float MIDDLE_C = 261.6; |
||||
|
||||
void playNote(float hz, uint16_t milliseconds, uint8_t volume); |
||||
void hzToAddVal(float hz); |
||||
void playStartupSound(); |
||||
void playCalibratingCountdownSound(); |
||||
void playModeSettingSound(); |
||||
void delay_NOP(unsigned long time); |
||||
}; |
||||
|
||||
#endif // _APPLICATION_H
|
@ -0,0 +1,14 @@ |
||||
// Build definitions
|
||||
|
||||
#ifndef _BUILD_H |
||||
#define _BUILD_H |
||||
|
||||
|
||||
// Set to build with serial support
|
||||
#define SERIAL_ENABLED 0 |
||||
|
||||
// Set to build with control voltage output (experimental)
|
||||
#define CV_ENABLED 0 |
||||
|
||||
|
||||
#endif // _BUILD_H
|
@ -0,0 +1,19 @@ |
||||
#ifndef _HW_H |
||||
#define _HW_H |
||||
|
||||
#define HW_BUTTON_STATE (PIND & (1<<PORTD6)) |
||||
#define HW_BUTTON_PRESSED (HW_BUTTON_STATE == LOW) |
||||
#define HW_BUTTON_RELEASED (HW_BUTTON_STATE != LOW) |
||||
|
||||
#define HW_LED1_ON (PORTC |= (1<<PORTC4)) |
||||
#define HW_LED1_OFF (PORTC &= ~(1<<PORTC4)) |
||||
|
||||
#define HW_LED2_ON (PORTC |= (1<<PORTC5)) |
||||
#define HW_LED2_OFF (PORTC &= ~(1<<PORTC5)) |
||||
|
||||
#define HW_LED1_TOGGLE (PORTC = PORTC ^ (1<<PORTC4)) |
||||
#define HW_LED2_TOGGLE (PORTC = PORTC ^ (1<<PORTC5)) |
||||
|
||||
#endif // _HW_H
|
||||
|
||||
|
@ -0,0 +1,209 @@ |
||||
#include "Arduino.h" |
||||
|
||||
#include "ihandlers.h" |
||||
#include "mcpDac.h" |
||||
#include "timer.h" |
||||
|
||||
#include "theremin_sintable.c" |
||||
#include "theremin_sintable2.c" |
||||
#include "theremin_sintable3.c" |
||||
#include "theremin_sintable4.c" |
||||
#include "theremin_sintable5.c" |
||||
#include "theremin_sintable6.c" |
||||
#include "theremin_sintable7.c" |
||||
#include "theremin_sintable8.c" |
||||
|
||||
const int16_t* const wavetables[] PROGMEM = { |
||||
sine_table, |
||||
sine_table2, |
||||
sine_table3, |
||||
sine_table4, |
||||
sine_table5, |
||||
sine_table6, |
||||
sine_table7, |
||||
sine_table8 |
||||
}; |
||||
|
||||
static const uint32_t MCP_DAC_BASE = 1748; |
||||
|
||||
#define INT0_STATE (PIND & (1<<PORTD2)) |
||||
#define PC_STATE (PINB & (1<<PORTB0)) |
||||
|
||||
volatile uint8_t vScaledVolume = 0; |
||||
volatile uint16_t vPointerIncrement = 0; |
||||
|
||||
volatile uint16_t pitch = 0; // Pitch value
|
||||
volatile uint16_t pitch_counter = 0; // Pitch counter
|
||||
volatile uint16_t pitch_counter_l = 0; // Last value of pitch counter
|
||||
|
||||
volatile bool volumeValueAvailable = 0; // Volume read flag
|
||||
volatile bool pitchValueAvailable = 0; // Pitch read flag
|
||||
volatile bool reenableInt1 = 0; // reeanble Int1
|
||||
|
||||
volatile uint16_t vol; // Volume value
|
||||
volatile uint16_t vol_counter = 0; |
||||
volatile uint16_t vol_counter_i = 0; // Volume counter
|
||||
volatile uint16_t vol_counter_l; // Last value of volume counter
|
||||
|
||||
volatile uint16_t timer_overflow_counter; // counter for frequency measurement
|
||||
|
||||
volatile uint8_t vWavetableSelector = 0; // wavetable selector
|
||||
|
||||
static volatile uint16_t pointer = 0; // Table pointer
|
||||
static volatile uint8_t debounce_p, debounce_v = 0; // Counters for debouncing
|
||||
|
||||
void ihInitialiseTimer() { |
||||
/* Setup Timer 1, 16 bit timer used to measure pitch and volume frequency */ |
||||
TCCR1A = 0; // Set Timer 1 to Normal port operation (Arduino does activate something here ?)
|
||||
TCCR1B = (1<<ICES1)|(1<<CS10); // Input Capture Positive edge select, Run without prescaling (16 Mhz)
|
||||
TIMSK1 = (1<<ICIE1); // Enable Input Capture Interrupt
|
||||
|
||||
TCCR0A = 3; //Arduino Default: Fast PWM
|
||||
TCCR0B = 3; //Arduino Default: clk I/O /64 (From prescaler)
|
||||
TIMSK0 = 1; //Arduino Default: TOIE0: Timer/Counter0 Overflow Interrupt Enable
|
||||
|
||||
} |
||||
|
||||
void ihInitialiseInterrupts() { |
||||
/* Setup interrupts for Wave Generator and Volume read */ |
||||
EICRA = (1<<ISC00)|(1<<ISC01)|(1<<ISC11)|(1<<ISC10) ; // The rising edges of INT0 and INT1 generate an interrupt request.
|
||||
reenableInt1 = true; |
||||
EIMSK = (1<<INT0)|(1<<INT1); // Enable External Interrupt INT0 and INT1
|
||||
} |
||||
|
||||
void ihInitialisePitchMeasurement() //Measurement of variable frequency oscillator on Timer 1
|
||||
{ reenableInt1 = false; |
||||
EIMSK = 0; // Disable External Interrupts
|
||||
TCCR1A = 0; //Normal port operation Timer 1
|
||||
TIMSK1 = (1<<TOIE1); //Timer/Counter1, Overflow Interrupt Enable
|
||||
|
||||
} |
||||
|
||||
void ihInitialiseVolumeMeasurement() //Measurement of variable frequency oscillator on Timer 0
|
||||
{ reenableInt1 = false; |
||||
EIMSK = 0; // Disable External Interrupts
|
||||
TIMSK1 = 0; //Timer/Counter1, Overflow Interrupt Disable
|
||||
|
||||
TCCR0A = 0; // Normal port operation, OC0A disconnected. Timer 0
|
||||
TIMSK0 = (1<<OCIE0A); //TOIE0: Timer/Counter0 Overflow Interrupt Enable
|
||||
OCR0A = 0xff; // set Output Compare Register0.
|
||||
|
||||
TCCR1A = 0; //Normal port operation Timer 1
|
||||
TCCR1B = (1<<CS10)|(1<<CS12); // clk I/O /1024 (From prescaler)
|
||||
TCCR1C=0; |
||||
|
||||
|
||||
} |
||||
|
||||
/* 16 bit by 8 bit multiplication */ |
||||
static inline uint32_t mul_16_8(uint16_t a, uint8_t b) |
||||
{ |
||||
uint32_t product; |
||||
asm ( |
||||
"mul %A1, %2\n\t" |
||||
"movw %A0, r0\n\t" |
||||
"clr %C0\n\t" |
||||
"clr %D0\n\t" |
||||
"mul %B1, %2\n\t" |
||||
"add %B0, r0\n\t" |
||||
"adc %C0, r1\n\t" |
||||
"clr r1" |
||||
: |
||||
"=&r" (product) |
||||
: |
||||
"r" (a), "r" (b)); |
||||
return product; |
||||
} |
||||
|
||||
/* Externaly generated 31250 Hz Interrupt for WAVE generator (32us) */ |
||||
ISR (INT1_vect) { |
||||
// Interrupt takes up a total of max 25 us
|
||||
|
||||
disableInt1(); // Disable External Interrupt INT1 to avoid recursive interrupts
|
||||
// Enable Interrupts to allow counter 1 interrupts
|
||||
interrupts(); |
||||
|
||||
int16_t waveSample; |
||||
uint32_t scaledSample; |
||||
uint16_t offset = (uint16_t)(pointer>>6) & 0x3ff; |
||||
|
||||
// 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; |
||||
}; |
||||
|
||||
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) >> 9); |
||||
} else { |
||||
scaledSample = MCP_DAC_BASE - (mul_16_8(-waveSample, vScaledVolume) >> 9); |
||||
} |
||||
|
||||
mcpDacSend(scaledSample); //Send result to Digital to Analogue Converter (audio out) (9.6 us)
|
||||
|
||||
pointer = pointer + vPointerIncrement; // increment table pointer (ca. 2us)
|
||||
incrementTimer(); // update 32us timer
|
||||
|
||||
if (PC_STATE) debounce_p++; |
||||
if (debounce_p == 3) { |
||||
noInterrupts(); |
||||
pitch_counter = ICR1; // Get Timer-Counter 1 value
|
||||
pitch = (pitch_counter - pitch_counter_l); // Counter change since last interrupt -> pitch value
|
||||
pitch_counter_l = pitch_counter; // Set actual value as new last value
|
||||
}; |
||||
|
||||
if (debounce_p == 5) { |
||||
pitchValueAvailable = true; |
||||
}; |
||||
|
||||
if (INT0_STATE) debounce_v++; |
||||
if (debounce_v == 3) { |
||||
noInterrupts(); |
||||
vol_counter = vol_counter_i; // Get Timer-Counter 1 value
|
||||
vol = (vol_counter - vol_counter_l); // Counter change since last interrupt
|
||||
vol_counter_l = vol_counter; // Set actual value as new last value
|
||||
}; |
||||
|
||||
if (debounce_v == 5) { |
||||
volumeValueAvailable = true; |
||||
}; |
||||
|
||||
noInterrupts(); |
||||
enableInt1(); |
||||
} |
||||
|
||||
/* VOLUME read - interrupt service routine for capturing volume counter value */ |
||||
ISR (INT0_vect) { |
||||
vol_counter_i = TCNT1; |
||||
debounce_v = 0; |
||||
}; |
||||
|
||||
|
||||
/* PITCH read - interrupt service routine for capturing pitch counter value */ |
||||
ISR (TIMER1_CAPT_vect) { |
||||
debounce_p = 0; |
||||
}; |
||||
|
||||
|
||||
/* PITCH read absolute frequency - interrupt service routine for calibration measurement */ |
||||
ISR(TIMER0_COMPA_vect) |
||||
{ |
||||
timer_overflow_counter++; |
||||
} |
||||
|
||||
/* VOLUME read absolute frequency - interrupt service routine for calibration measurement */ |
||||
ISR(TIMER1_OVF_vect) |
||||
{ |
||||
timer_overflow_counter++; |
||||
} |
||||
|
||||
|
@ -0,0 +1,44 @@ |
||||
#ifndef _IHANDLERS_H |
||||
#define _IHANDLERS_H |
||||
|
||||
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 pitch_counter; // Pitch counter
|
||||
extern volatile uint16_t pitch_counter_l; // Last value of pitch counter
|
||||
|
||||
extern volatile uint16_t vol_counter; // Pitch counter
|
||||
extern volatile uint16_t vol_counter_l; // Last value of pitch counter
|
||||
|
||||
extern volatile uint16_t timer_overflow_counter; // counter for frequency measurement
|
||||
|
||||
|
||||
extern volatile bool volumeValueAvailable; // Volume read flag
|
||||
extern volatile bool pitchValueAvailable; // Pitch read flag
|
||||
extern volatile bool reenableInt1; // Pitch read flag
|
||||
|
||||
extern volatile uint8_t vWavetableSelector; |
||||
extern volatile uint16_t vPointerIncrement; // Table pointer increment
|
||||
|
||||
inline void resetPitchFlag() { pitchValueAvailable = false; } |
||||
inline void resetVolFlag() { volumeValueAvailable = false; } |
||||
|
||||
inline void savePitchCounter() { pitch_counter_l=pitch_counter; } |
||||
inline void saveVolCounter() { vol_counter_l=vol_counter; }; |
||||
|
||||
inline void setWavetableSampleAdvance(uint16_t val) { vPointerIncrement = val;} |
||||
|
||||
inline void disableInt1() { EIMSK &= ~ (1 << INT1); } |
||||
inline void enableInt1() { if (reenableInt1) EIMSK |= (1 << INT1); } |
||||
|
||||
void ihInitialiseTimer(); |
||||
void ihInitialiseInterrupts(); |
||||
void ihInitialisePitchMeasurement(); |
||||
void ihInitialiseVolumeMeasurement(); |
||||
void resetPitchFlag(); |
||||
void resetVolFlag(); |
||||
void savePitchCounter(); |
||||
void saveVolCounter(); |
||||
|
||||
#endif // _IHANDLERS_H
|
@ -0,0 +1,151 @@ |
||||
/* Arduino WaveHC Library
|
||||
* Copyright (C) 2009 by William Greiman |
||||
*
|
||||
* This file is part of the Arduino WaveHC Library |
||||
*
|
||||
* This Library 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 Library 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 the Arduino WaveHC Library. If not, see |
||||
* <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
/**
|
||||
* Macros and inline functions for MCP4921 DAC |
||||
*/
|
||||
#ifndef mcpDac_h |
||||
#define mcpDac_h |
||||
|
||||
#include <avr/io.h> |
||||
#include "OTPinDefs.h" |
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
#define mcpDacCsLow() MCP_DAC_CS_PORT &= ~_BV(MCP_DAC_CS_BIT) |
||||
#define mcpDacCsHigh() MCP_DAC_CS_PORT |= _BV(MCP_DAC_CS_BIT) |
||||
|
||||
#define mcpDac2CsLow() MCP_DAC_CS_PORT &= ~_BV(MCP_DAC2_CS_BIT) |
||||
#define mcpDac2CsHigh() MCP_DAC_CS_PORT |= _BV(MCP_DAC2_CS_BIT) |
||||
|
||||
#define mcpDacSckLow() MCP_DAC_SCK_PORT &= ~_BV(MCP_DAC_SCK_BIT) |
||||
#define mcpDacSckHigh() MCP_DAC_SCK_PORT |= _BV(MCP_DAC_SCK_BIT) |
||||
#define mcpDacSckPulse() {mcpDacSckHigh();mcpDacSckLow();} |
||||
|
||||
#define mcpDacSdiLow() MCP_DAC_SDI_PORT &= ~_BV(MCP_DAC_SDI_BIT) |
||||
#define mcpDacSdiHigh() MCP_DAC_SDI_PORT |= _BV(MCP_DAC_SDI_BIT) |
||||
#define mcpDacSdiSet(v) if(v){mcpDacSdiHigh();}else{mcpDacSdiLow();} |
||||
|
||||
// send bit b of d
|
||||
#define mcpDacSendBit(d, b) {mcpDacSdiSet(d&_BV(b));mcpDacSckPulse();} |
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// init dac I/O ports
|
||||
inline void mcpDacInit(void) { |
||||
// set all to output mode
|
||||
MCP_DAC_CS_DDR |= _BV(MCP_DAC_CS_BIT); |
||||
MCP_DAC2_CS_DDR |= _BV(MCP_DAC2_CS_BIT); |
||||
|
||||
MCP_DAC_SCK_DDR |= _BV(MCP_DAC_SCK_BIT); |
||||
MCP_DAC_SDI_DDR |= _BV(MCP_DAC_SDI_BIT); |
||||
// chip select high
|
||||
mcpDacCsHigh(); |
||||
mcpDac2CsHigh(); |
||||
|
||||
#if USE_MCP_DAC_LDAC |
||||
// LDAC low always - use unbuffered mode
|
||||
MCP_DAC_LDAC_DDR |= _BV(MCP_DAC_LDAC_BIT); |
||||
MCP_DAC_LDAC_PORT &= ~_BV(MCP_DAC_LDAC_BIT); |
||||
#endif // USE_MCP_DAC_LDAC
|
||||
} |
||||
//------------------------------------------------------------------------------
|
||||
// send 12 bits to dac
|
||||
// trusted compiler to optimize and it does
|
||||
// csLow to csHigh takes 8 - 9 usec on a 16 MHz Arduino
|
||||
inline void mcpDacSend(uint16_t data) { |
||||
mcpDacCsLow(); |
||||
// send DAC config bits
|
||||
mcpDacSdiLow(); |
||||
mcpDacSckPulse(); // DAC A
|
||||
mcpDacSdiHigh(); |
||||
mcpDacSckPulse(); // buffered REF
|
||||
|
||||
mcpDacSckPulse(); // 1X gain
|
||||
mcpDacSckPulse(); // no SHDN
|
||||
// send 12 data bits
|
||||
mcpDacSendBit(data, 11); |
||||
mcpDacSendBit(data, 10); |
||||
mcpDacSendBit(data, 9); |
||||
mcpDacSendBit(data, 8); |
||||
mcpDacSendBit(data, 7); |
||||
mcpDacSendBit(data, 6); |
||||
mcpDacSendBit(data, 5); |
||||
mcpDacSendBit(data, 4); |
||||
mcpDacSendBit(data, 3); |
||||
mcpDacSendBit(data, 2); |
||||
mcpDacSendBit(data, 1); |
||||
mcpDacSendBit(data, 0); |
||||
mcpDacCsHigh(); |
||||
} |
||||
|
||||
inline void mcpDac2ASend(uint16_t data) { |
||||
mcpDac2CsLow(); |
||||
// send DAC config bits
|
||||
mcpDacSdiLow(); |
||||
mcpDacSckPulse(); // DAC A
|
||||
mcpDacSdiHigh(); |
||||
mcpDacSckPulse(); // buffered REF
|
||||
|
||||
mcpDacSckPulse(); // 1X gain
|
||||
mcpDacSckPulse(); // no SHDN
|
||||
// send 12 data bits
|
||||
mcpDacSendBit(data, 11); |
||||
mcpDacSendBit(data, 10); |
||||
mcpDacSendBit(data, 9); |
||||
mcpDacSendBit(data, 8); |
||||
mcpDacSendBit(data, 7); |
||||
mcpDacSendBit(data, 6); |
||||
mcpDacSendBit(data, 5); |
||||
mcpDacSendBit(data, 4); |
||||
mcpDacSendBit(data, 3); |
||||
mcpDacSendBit(data, 2); |
||||
mcpDacSendBit(data, 1); |
||||
mcpDacSendBit(data, 0); |
||||
mcpDac2CsHigh(); |
||||
} |
||||
|
||||
inline void mcpDac2BSend(uint16_t data) { |
||||
mcpDac2CsLow(); |
||||
// send DAC config bits
|
||||
mcpDacSdiHigh(); |
||||
mcpDacSckPulse(); // DAC A
|
||||
mcpDacSdiHigh(); |
||||
mcpDacSckPulse(); // buffered REF
|
||||
|
||||
mcpDacSckPulse(); // 1X gain
|
||||
mcpDacSckPulse(); // no SHDN
|
||||
// send 12 data bits
|
||||
mcpDacSendBit(data, 11); |
||||
mcpDacSendBit(data, 10); |
||||
mcpDacSendBit(data, 9); |
||||
mcpDacSendBit(data, 8); |
||||
mcpDacSendBit(data, 7); |
||||
mcpDacSendBit(data, 6); |
||||
mcpDacSendBit(data, 5); |
||||
mcpDacSendBit(data, 4); |
||||
mcpDacSendBit(data, 3); |
||||
mcpDacSendBit(data, 2); |
||||
mcpDacSendBit(data, 1); |
||||
mcpDacSendBit(data, 0); |
||||
mcpDac2CsHigh(); |
||||
} |
||||
|
||||
|
||||
|
||||
#endif //mcpDac_h
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,17 @@ |
||||
#include "Arduino.h" |
||||
|
||||
#include "timer.h" |
||||
|
||||
volatile uint16_t timer = 0; |
||||
|
||||
void ticktimer (uint16_t ticks) { |
||||
resetTimer(); |
||||
while (timerUnexpired(ticks)) |
||||
; // NOP
|
||||
}; |
||||
|
||||
void millitimer (uint16_t milliseconds) { |
||||
ticktimer(millisToTicks(milliseconds)); |
||||
} |
||||
|
||||
|
@ -0,0 +1,39 @@ |
||||
#ifndef _TIMER_H |
||||
#define _TIMER_H |
||||
|
||||
extern volatile uint16_t timer; |
||||
|
||||
inline uint16_t millisToTicks(uint16_t milliseconds) { |
||||
return milliseconds * (1000.0f/32); |
||||
} |
||||
|
||||
inline void resetTimer() { |
||||
timer = 0; |
||||
} |
||||
|
||||
inline void incrementTimer() { |
||||
timer++; |
||||
} |
||||
|
||||
inline bool timerExpired(uint16_t ticks) { |
||||
return timer >= ticks; |
||||
} |
||||
|
||||
inline bool timerUnexpired(uint16_t ticks) { |
||||
return timer < ticks; |
||||
} |
||||
|
||||
inline bool timerExpiredMillis(uint16_t milliseconds) { |
||||
return timerExpired(millisToTicks(milliseconds)); |
||||
} |
||||
|
||||
inline bool timerUnexpiredMillis(uint16_t milliseconds) { |
||||
return timerUnexpired(millisToTicks(milliseconds)); |
||||
} |
||||
|
||||
void ticktimer (uint16_t ticks); |
||||
void millitimer (uint16_t milliseconds); |
||||
|
||||
const uint16_t TICKS_100_MILLIS = millisToTicks(100); |
||||
|
||||
#endif // _TIMER_H
|
Loading…
Reference in new issue