Created
September 5, 2020 01:19
-
-
Save ceiborg/7ebb6e8b0b315f5507feea0ef3a68dbf to your computer and use it in GitHub Desktop.
Almohadón Musical (Tutorial YouTube)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Joe Marshall 2011 | |
// Resonant filter based on Meeblip (meeblip.noisepages.com) | |
// Interrupt setup code based on code by Martin Nawrath (http://interface.khm.de/index.php/lab/experiments/arduino-dds-sinewave-generator/) | |
// oscillators and inline assembler optimisation by me. | |
// | |
// key input is from 8 capacitive inputs on digital input 6,7, and analog inputs 0-6 | |
// each input is a single wire, going to something metal to touch | |
// (I used a bunch of big carriage bolts) | |
// | |
// sensing of this is done by getNoteKeys, using the method described at: | |
// http://www.arduino.cc/playground/Code/CapacitiveSensor | |
// | |
// I use assembler with an unrolled loop using 16 registers to detect this | |
// this makes things much more accurate than the C loop described on the link above | |
// as we are measuring the relevant delay in single processor cycles. | |
// It seems to be happy even with battery power, detecting up to 8 concurrent | |
// touches. | |
// The waves are all defined at the very top, because we are forcing them to align to 256 byte boundaries | |
// doing this makes the oscillator code quicker (as calculating a wave offset is just | |
// a matter of replacing the low byte of the address). | |
// Having said that, other arduino stuff probably gets loaded in here first | |
// because the aligned attribute seems to add a couple of hundred bytes to the code | |
#define TEST_PATTERN_INTRO | |
//#define FILTER_LPF_NONE | |
#define FILTER_LPF_HACK | |
// table of 256 sine values / one sine period / stored in flash memory | |
char sine256[256] __attribute__ ((aligned(256))) = { | |
0 , 3 , 6 , 9 , 12 , 15 , 18 , 21 , 24 , 27 , 30 , 33 , 36 , 39 , 42 , 45 , | |
48 , 51 , 54 , 57 , 59 , 62 , 65 , 67 , 70 , 73 , 75 , 78 , 80 , 82 , 85 , 87 , | |
89 , 91 , 94 , 96 , 98 , 100 , 102 , 103 , 105 , 107 , 108 , 110 , 112 , 113 , 114 , 116 , | |
117 , 118 , 119 , 120 , 121 , 122 , 123 , 123 , 124 , 125 , 125 , 126 , 126 , 126 , 126 , 126 , | |
127 , 126 , 126 , 126 , 126 , 126 , 125 , 125 , 124 , 123 , 123 , 122 , 121 , 120 , 119 , 118 , | |
117 , 116 , 114 , 113 , 112 , 110 , 108 , 107 , 105 , 103 , 102 , 100 , 98 , 96 , 94 , 91 , | |
89 , 87 , 85 , 82 , 80 , 78 , 75 , 73 , 70 , 67 , 65 , 62 , 59 , 57 , 54 , 51 , | |
48 , 45 , 42 , 39 , 36 , 33 , 30 , 27 , 24 , 21 , 18 , 15 , 12 , 9 , 6 , 3 , | |
0 , -3 , -6 , -9 , -12 , -15 , -18 , -21 , -24 , -27 , -30 , -33 , -36 , -39 , -42 , -45 , | |
-48 , -51 , -54 , -57 , -59 , -62 , -65 , -67 , -70 , -73 , -75 , -78 , -80 , -82 , -85 , -87 , | |
-89 , -91 , -94 , -96 , -98 , -100 , -102 , -103 , -105 , -107 , -108 , -110 , -112 , -113 , -114 , -116 , | |
-117 , -118 , -119 , -120 , -121 , -122 , -123 , -123 , -124 , -125 , -125 , -126 , -126 , -126 , -126 , -126 , | |
-127 , -126 , -126 , -126 , -126 , -126 , -125 , -125 , -124 , -123 , -123 , -122 , -121 , -120 , -119 , -118 , | |
-117 , -116 , -114 , -113 , -112 , -110 , -108 , -107 , -105 , -103 , -102 , -100 , -98 , -96 , -94 , -91 , | |
-89 , -87 , -85 , -82 , -80 , -78 , -75 , -73 , -70 , -67 , -65 , -62 , -59 , -57 , -54 , -51 , | |
-48 , -45 , -42 , -39 , -36 , -33 , -30 , -27 , -24 , -21 , -18 , -15 , -12 , -9 , -6 , -3 | |
}; | |
char square256[256] __attribute__ ((aligned(256))) = { | |
127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , | |
127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , | |
127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , | |
127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , | |
127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , | |
127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , | |
127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , | |
127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , 127 , | |
-127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , | |
-127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , | |
-127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , | |
-127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , | |
-127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , | |
-127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , | |
-127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , | |
-127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 , -127 | |
}; | |
char triangle256[256] __attribute__ ((aligned(256))) = { | |
-127 , -125 , -123 , -121 , -119 , -117 , -115 , -113 , -111 , -109 , -107 , -105 , -103 , -101 , -99 , -97 , | |
-95 , -93 , -91 , -89 , -87 , -85 , -83 , -81 , -79 , -77 , -75 , -73 , -71 , -69 , -67 , -65 , | |
-63 , -61 , -59 , -57 , -55 , -53 , -51 , -49 , -47 , -45 , -43 , -41 , -39 , -37 , -35 , -33 , | |
-31 , -29 , -27 , -25 , -23 , -21 , -19 , -17 , -15 , -13 , -11 , -9 , -7 , -5 , -3 , -1 , | |
1 , 3 , 5 , 7 , 9 , 11 , 13 , 15 , 17 , 19 , 21 , 23 , 25 , 27 , 29 , 31 , | |
33 , 35 , 37 , 39 , 41 , 43 , 45 , 47 , 49 , 51 , 53 , 55 , 57 , 59 , 61 , 63 , | |
65 , 67 , 69 , 71 , 73 , 75 , 77 , 79 , 81 , 83 , 85 , 87 , 89 , 91 , 93 , 95 , | |
97 , 99 , 101 , 103 , 105 , 107 , 109 , 111 , 113 , 115 , 117 , 119 , 121 , 123 , 125 , 127 , | |
129 , 127 , 125 , 123 , 121 , 119 , 117 , 115 , 113 , 111 , 109 , 107 , 105 , 103 , 101 , 99 , | |
97 , 95 , 93 , 91 , 89 , 87 , 85 , 83 , 81 , 79 , 77 , 75 , 73 , 71 , 69 , 67 , | |
65 , 63 , 61 , 59 , 57 , 55 , 53 , 51 , 49 , 47 , 45 , 43 , 41 , 39 , 37 , 35 , | |
33 , 31 , 29 , 27 , 25 , 23 , 21 , 19 , 17 , 15 , 13 , 11 , 9 , 7 , 5 , 3 , | |
1 , -1 , -3 , -5 , -7 , -9 , -11 , -13 , -15 , -17 , -19 , -21 , -23 , -25 , -27 , -29 , | |
-31 , -33 , -35 , -37 , -39 , -41 , -43 , -45 , -47 , -49 , -51 , -53 , -55 , -57 , -59 , -61 , | |
-63 , -65 , -67 , -69 , -71 , -73 , -75 , -77 , -79 , -81 , -83 , -85 , -87 , -89 , -91 , -93 , | |
-95 , -97 , -99 , -101 , -103 , -105 , -107 , -109 , -111 , -113 , -115 , -117 , -119 , -121 , -123 , -125 | |
}; | |
char sawtooth256[256] __attribute__ ((aligned(256))) = { | |
-127 , -127 , -126 , -125 , -124 , -123 , -122 , -121 , -120 , -119 , -118 , -117 , -116 , -115 , -114 , -113 , | |
-112 , -111 , -110 , -109 , -108 , -107 , -106 , -105 , -104 , -103 , -102 , -101 , -100 , -99 , -98 , -97 , | |
-96 , -95 , -94 , -93 , -92 , -91 , -90 , -89 , -88 , -87 , -86 , -85 , -84 , -83 , -82 , -81 , | |
-80 , -79 , -78 , -77 , -76 , -75 , -74 , -73 , -72 , -71 , -70 , -69 , -68 , -67 , -66 , -65 , | |
-64 , -63 , -62 , -61 , -60 , -59 , -58 , -57 , -56 , -55 , -54 , -53 , -52 , -51 , -50 , -49 , | |
-48 , -47 , -46 , -45 , -44 , -43 , -42 , -41 , -40 , -39 , -38 , -37 , -36 , -35 , -34 , -33 , | |
-32 , -31 , -30 , -29 , -28 , -27 , -26 , -25 , -24 , -23 , -22 , -21 , -20 , -19 , -18 , -17 , | |
-16 , -15 , -14 , -13 , -12 , -11 , -10 , -9 , -8 , -7 , -6 , -5 , -4 , -3 , -2 , -1 , | |
0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 , 15 , | |
16 , 17 , 18 , 19 , 20 , 21 , 22 , 23 , 24 , 25 , 26 , 27 , 28 , 29 , 30 , 31 , | |
32 , 33 , 34 , 35 , 36 , 37 , 38 , 39 , 40 , 41 , 42 , 43 , 44 , 45 , 46 , 47 , | |
48 , 49 , 50 , 51 , 52 , 53 , 54 , 55 , 56 , 57 , 58 , 59 , 60 , 61 , 62 , 63 , | |
64 , 65 , 66 , 67 , 68 , 69 , 70 , 71 , 72 , 73 , 74 , 75 , 76 , 77 , 78 , 79 , | |
80 , 81 , 82 , 83 , 84 , 85 , 86 , 87 , 88 , 89 , 90 , 91 , 92 , 93 , 94 , 95 , | |
96 , 97 , 98 , 99 , 100 , 101 , 102 , 103 , 104 , 105 , 106 , 107 , 108 , 109 , 110 , 111 , | |
112 , 113 , 114 , 115 , 116 , 117 , 118 , 119 , 120 , 121 , 122 , 123 , 124 , 125 , 126 , 127 | |
}; | |
#include "avr/pgmspace.h" | |
// log table for 128 filter cutoffs | |
unsigned char logCutoffs[128] = {0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x03,0x04,0x04,0x04,0x04,0x04,0x05,0x05,0x05,0x05,0x06,0x06,0x06,0x06,0x06,0x06,0x07,0x08,0x08,0x08,0x09,0x09,0x0A,0x0A,0x0A,0x0A,0x0B,0x0C,0x0C,0x0C,0x0C,0x0D,0x0E,0x0F,0x10,0x11,0x12,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1E,0x20,0x21,0x22,0x23,0x24,0x26,0x28,0x2A,0x2C,0x2E,0x30,0x32,0x34,0x36,0x38,0x3A,0x40,0x42,0x44,0x48,0x4C,0x4F,0x52,0x55,0x58,0x5D,0x61,0x65,0x68,0x6C,0x70,0x76,0x7E,0x85,0x8A,0x90,0x96,0x9D,0xA4,0xAB,0xB0,0xBA,0xC4,0xCE,0xD8,0xE0,0xE8,0xF4,0xFF}; | |
volatile unsigned int WAIT_curTime; | |
#define WAIT_UNTIL_INTERRUPT() WAIT_curTime=loopSteps; while(WAIT_curTime==loopSteps){} | |
#define SERIAL_OUT 0 | |
// attack,decay are in 1/64ths per 125th of a second - ie. 1 = 0->1 in half a second | |
const int DECAY=3; | |
const int ATTACK=4; | |
volatile char* curWave=square256; | |
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) | |
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) | |
// this is supposedly the audio clock frequency - as | |
// you can see, measured freq may vary a bit from supposed clock frequency | |
// I'm not quite sure why | |
// const double refclk=31372.549; // =16MHz / 510 | |
// const double refclk=31376.6; // measured | |
// variables used inside interrupt service declared as voilatile | |
// these variables allow you to keep track of time - as delay / millis etc. are | |
// made inactive due to interrupts being disabled. | |
volatile unsigned char loopSteps=0; // once per sample | |
volatile unsigned int loopStepsHigh=0; // once per 256 samples | |
// information about the current state of a single oscillator | |
struct oscillatorPhase | |
{ | |
unsigned int phaseStep; | |
char volume; | |
unsigned int phaseAccu; | |
}; | |
// the oscillators (8 of them) | |
struct oscillatorPhase oscillators[8]; | |
// tword_m=pow(2,32)*dfreq/refclk; // calulate DDS new tuning word | |
// to get hz -> tuning word do: (pow(2,16) * frequency) / 31376.6 | |
const unsigned int NOTE_FREQS[25]={273,289,307,325,344,365,386,409,434,460,487,516,546,579,613,650,688,729,773,819,867,919,974,1032,1093}; | |
// thresholds for the capacitive sensing buttons | |
int calibrationThresholds[8]={0,0,0,0,0,0,0,0}; | |
inline int getNoteKeys(boolean calibrate=false) | |
{ | |
char PORTD_PINS=0b11000000; // (pins 6-7 - avoid pins 0,1 as they are used for serial port comms) | |
char PORTC_PINS=0b111111; //(analog pins 0-5) | |
const int MAX_LOOPS=16; | |
char port_values[MAX_LOOPS*2]; | |
WAIT_UNTIL_INTERRUPT(); | |
asm volatile ( | |
// port D reading loop: | |
// DDRD &= ~(PORTD_PINS = 0x3f); // set pins 8-12 to input mode | |
"in %[temp],0x0a" "\n\t" | |
"andi %[temp],0x3f" "\n\t" | |
"out 0x0a,%[temp]" "\n\t" | |
// PORTD |= (PORTD_PINS); // set pins 8-12 pullup on | |
"in %[temp],0x0b" "\n\t" | |
"ori %[temp],0xC0" "\n\t" | |
"out 0x0b,%[temp]" "\n\t" | |
"in %0,0x09" "\n\t" | |
"in %1,0x09" "\n\t" | |
"in %2,0x09" "\n\t" | |
"in %3,0x09" "\n\t" | |
"in %4,0x09" "\n\t" | |
"in %5,0x09" "\n\t" | |
"in %6,0x09" "\n\t" | |
"in %7,0x09" "\n\t" | |
"in %8,0x09" "\n\t" | |
"in %9,0x09" "\n\t" | |
"in %10,0x09" "\n\t" | |
"in %11,0x09" "\n\t" | |
"in %12,0x09" "\n\t" | |
"in %13,0x09" "\n\t" | |
"in %14,0x09" "\n\t" | |
"in %15,0x09" "\n\t" | |
: | |
// outputs | |
"=r" (port_values[0]), | |
"=r" (port_values[2]), | |
"=r" (port_values[4]), | |
"=r" (port_values[6]), | |
"=r" (port_values[8]), | |
"=r" (port_values[10]), | |
"=r" (port_values[12]), | |
"=r" (port_values[14]), | |
"=r" (port_values[16]), | |
"=r" (port_values[18]), | |
"=r" (port_values[20]), | |
"=r" (port_values[22]), | |
"=r" (port_values[24]), | |
"=r" (port_values[26]), | |
"=r" (port_values[28]), | |
"=r" (port_values[30]) | |
:[temp] "d" (0)); | |
WAIT_UNTIL_INTERRUPT(); | |
asm volatile ( | |
// port C reading loop: | |
// DDRC &= ~(PORTC_PINS = 0xc0); // set pins 5-7 to input mode | |
"in %[temp],0x07" "\n\t" | |
"andi %[temp],0xc0" "\n\t" | |
"out 0x07,%[temp]" "\n\t" | |
// PORTC |= (PORTC_PINS); // set pins 5-7 pullup on | |
"in %[temp],0x08" "\n\t" | |
"ori %[temp],0x3F" "\n\t" | |
"out 0x08,%[temp]" "\n\t" | |
"in %0,0x06" "\n\t" | |
"in %1,0x06" "\n\t" | |
"in %2,0x06" "\n\t" | |
"in %3,0x06" "\n\t" | |
"in %4,0x06" "\n\t" | |
"in %5,0x06" "\n\t" | |
"in %6,0x06" "\n\t" | |
"in %7,0x06" "\n\t" | |
"in %8,0x06" "\n\t" | |
"in %9,0x06" "\n\t" | |
"in %10,0x06" "\n\t" | |
"in %11,0x06" "\n\t" | |
"in %12,0x06" "\n\t" | |
"in %13,0x06" "\n\t" | |
"in %14,0x06" "\n\t" | |
"in %15,0x06" "\n\t" | |
: | |
// outputs | |
"=r" (port_values[1]), | |
"=r" (port_values[3]), | |
"=r" (port_values[5]), | |
"=r" (port_values[7]), | |
"=r" (port_values[9]), | |
"=r" (port_values[11]), | |
"=r" (port_values[13]), | |
"=r" (port_values[15]), | |
"=r" (port_values[17]), | |
"=r" (port_values[19]), | |
"=r" (port_values[21]), | |
"=r" (port_values[23]), | |
"=r" (port_values[25]), | |
"=r" (port_values[27]), | |
"=r" (port_values[29]), | |
"=r" (port_values[31]) | |
:[temp] "d" (0)); | |
PORTC &= ~(PORTC_PINS); // pullup off pins 8-12 | |
PORTD &= ~(PORTD_PINS); // pullup off pins 5-7 | |
DDRC |= (PORTC_PINS); // discharge | |
DDRD |= (PORTD_PINS); // discharge | |
if(calibrate) | |
{ | |
for(int c=0;c<8;c++) | |
{ | |
for(int d=0;d<MAX_LOOPS;d++) | |
{ | |
int liveNotes=((int*)port_values)[d]; | |
liveNotes&=0x3fc0; | |
liveNotes>>=6; | |
if(liveNotes&(1<<c)) | |
{ | |
if(calibrationThresholds[c]<=d) | |
{ | |
calibrationThresholds[c]=d+1; | |
} | |
break; | |
} | |
} | |
} | |
} | |
int liveNotes=0; | |
for(int c=0;c<8;c++) | |
{ | |
int val = ((int*)port_values)[calibrationThresholds[c]+1]; | |
val&=0x3fc0; | |
val>>=6; | |
if((val&(1<<c))==0) | |
{ | |
liveNotes|=(1<<c); | |
} | |
} | |
return liveNotes; | |
} | |
// get capacitive touch on input 4 and output 3 | |
// used for filter modulator | |
inline int getfiltermodulationtime() | |
{ | |
static int running_average=0; | |
static int running_min=1024; | |
static int running_min_inc_count=0; | |
static boolean initialise_running_min=true; | |
unsigned int delayTime=0; | |
char PINNUM_OUT=3; | |
char PINNUM_IN=4; | |
char PIN_OUT=1<<PINNUM_OUT; | |
char PIN_IN=1<<PINNUM_IN; | |
// make sure inputs / outputs are set right | |
DDRD|=PIN_OUT; | |
DDRD&=~(PIN_IN); | |
WAIT_UNTIL_INTERRUPT(); | |
PORTD|=PIN_OUT; | |
asm volatile ( | |
"loopstart%=:" "\n\t" | |
"sbic 0x09,%[PINNUM_IN]" "\n\t" | |
"rjmp outloop%=" "\n\t" | |
"adiw %[delayTime],0x01" "\n\t" | |
"cpi %B[delayTime],0x02" "\n\t" | |
"brne loopstart%=" "\n\t" | |
"outloop%=:" "\n\t" | |
:[delayTime] "+&w" (delayTime) | |
:[PINNUM_IN] "I" (PINNUM_IN)); | |
// set pin down - maybe don't bother timing, if it doesn't seem to add | |
// much accuracy? | |
WAIT_UNTIL_INTERRUPT(); | |
PORTD&=~PIN_OUT; | |
asm( | |
"loopstart%=:" "\n\t" | |
"sbis 0x09,%[PINNUM_IN]" "\n\t" | |
"rjmp outloop%=" "\n\t" | |
"adiw %[delayTime],0x01" "\n\t" | |
"cpi %B[delayTime],0x02" "\n\t" | |
"brne loopstart%=" "\n\t" | |
"outloop%=:" "\n\t" | |
:[delayTime] "+&w" (delayTime) | |
:[PINNUM_IN] "I" (PINNUM_IN)); | |
running_average=(running_average-(running_average>>4))+(delayTime>>4); | |
running_min_inc_count++; | |
if(running_min_inc_count==255) | |
{ | |
if(initialise_running_min) | |
{ | |
running_min=running_average; | |
running_min_inc_count=0; | |
initialise_running_min=false; | |
}else{ | |
running_min_inc_count=0; | |
running_min++; | |
} | |
} | |
if(running_average<running_min) | |
{ | |
running_min=running_average; | |
} | |
int touchVal=running_average-running_min; | |
if(touchVal>15) | |
{ | |
touchVal-=15; | |
if(touchVal>99) | |
{ | |
touchVal=99; | |
} | |
}else{ | |
touchVal=0; | |
} | |
return touchVal; | |
} | |
// get capacitive touch on input 5 and output 3 | |
// used for pitch bend | |
inline int getpitchbendtime() | |
{ | |
static int running_average=0; | |
static int running_min=1024; | |
static int running_min_inc_count=0; | |
static boolean initialise_running_min=true; | |
unsigned int delayTime=0; | |
char PINNUM_OUT=3; | |
char PINNUM_IN=5; | |
char PIN_OUT=1<<PINNUM_OUT; | |
char PIN_IN=1<<PINNUM_IN; | |
// make sure inputs / outputs are set right | |
DDRD|=PIN_OUT; | |
DDRD&=~(PIN_IN); | |
WAIT_UNTIL_INTERRUPT(); | |
PORTD|=PIN_OUT; | |
asm volatile ( | |
"loopstart%=:" "\n\t" | |
"sbic 0x09,%[PINNUM_IN]" "\n\t" | |
"rjmp outloop%=" "\n\t" | |
"adiw %[delayTime],0x01" "\n\t" | |
"cpi %B[delayTime],0x02" "\n\t" | |
"brne loopstart%=" "\n\t" | |
"outloop%=:" "\n\t" | |
:[delayTime] "+&w" (delayTime) | |
:[PINNUM_IN] "I" (PINNUM_IN)); | |
// set pin down - maybe don't bother timing, if it doesn't seem to add | |
// much accuracy? | |
WAIT_UNTIL_INTERRUPT(); | |
PORTD&=~PIN_OUT; | |
asm( | |
"loopstart%=:" "\n\t" | |
"sbis 0x09,%[PINNUM_IN]" "\n\t" | |
"rjmp outloop%=" "\n\t" | |
"adiw %[delayTime],0x01" "\n\t" | |
"cpi %B[delayTime],0x02" "\n\t" | |
"brne loopstart%=" "\n\t" | |
"outloop%=:" "\n\t" | |
:[delayTime] "+&w" (delayTime) | |
:[PINNUM_IN] "I" (PINNUM_IN)); | |
running_average=(running_average-(running_average>>4))+(delayTime>>4); | |
running_min_inc_count++; | |
if(running_min_inc_count==255) | |
{ | |
if(initialise_running_min) | |
{ | |
running_min=running_average; | |
running_min_inc_count=0; | |
initialise_running_min=false; | |
}else{ | |
running_min_inc_count=0; | |
running_min++; | |
} | |
} | |
if(running_average<running_min) | |
{ | |
running_min=running_average; | |
} | |
int touchVal=running_average-running_min; | |
if(touchVal>15) | |
{ | |
touchVal-=15; | |
if(touchVal>99) | |
{ | |
touchVal=99; | |
} | |
}else{ | |
touchVal=0; | |
} | |
return touchVal; | |
} | |
unsigned int pitchBendTable[201]={241, 241, 241, 242, 242, 242, 242, 242, 242, 242, 243, 243, 243, 243, 243, 243, 243, 244, 244, 244, 244, 244, 244, 244, 245, 245, 245, 245, 245, 245, 245, 246, 246, 246, 246, 246, 246, 246, 247, 247, 247, 247, 247, 247, 247, 248, 248, 248, 248, 248, 248, 248, 249, 249, 249, 249, 249, 249, 249, 250, 250, 250, 250, 250, 250, 250, 251, 251, 251, 251, 251, 251, 251, 252, 252, 252, 252, 252, 252, 253, 253, 253, 253, 253, 253, 253, 254, 254, 254, 254, 254, 254, 254, 255, 255, 255, 255, 255, 255, 256, | |
256,256, 256, 256, 256, 256, 256, 256, 257, 257, 257, 257, 257, 257, 257, 258, 258, 258, 258, 258, 258, 259, 259, 259, 259, 259, 259, 259, 260, 260, 260, 260, 260, 260, 260, 261, 261, 261, 261, 261, 261, 262, 262, 262, 262, 262, 262, 262, 263, 263, 263, 263, 263, 263, 264, 264, 264, 264, 264, 264, 264, 265, 265, 265, 265, 265, 265, 266, 266, 266, 266, 266, 266, 266, 267, 267, 267, 267, 267, 267, 268, 268, 268, 268, 268, 268, 269, 269, 269, 269, 269, 269, 269, 270, 270, 270, 270, 270, 270, 271, 271}; | |
void setupNoteFrequencies(int baseNote,int pitchBendVal /*-100 -> 100*/) | |
{ | |
oscillators[0].phaseStep=NOTE_FREQS[baseNote]; | |
oscillators[1].phaseStep=NOTE_FREQS[baseNote+2]; | |
oscillators[2].phaseStep=NOTE_FREQS[baseNote+4]; | |
oscillators[3].phaseStep=NOTE_FREQS[baseNote+5]; | |
oscillators[4].phaseStep=NOTE_FREQS[baseNote+7]; | |
oscillators[5].phaseStep=NOTE_FREQS[baseNote+9]; | |
oscillators[6].phaseStep=NOTE_FREQS[baseNote+11]; | |
oscillators[7].phaseStep=NOTE_FREQS[baseNote+12]; | |
if(pitchBendVal<-99) | |
{ | |
pitchBendVal=-99; | |
}else if(pitchBendVal>99) | |
{ | |
pitchBendVal=99; | |
} | |
// Serial.print("*"); | |
// Serial.print(pitchBendVal); | |
unsigned int pitchBendMultiplier=pitchBendTable[pitchBendVal+100]; | |
// Serial.print(":"); | |
// Serial.print(pitchBendMultiplier); | |
for(int c=0;c<8;c++) | |
{ | |
// multiply 2 16 bit numbers together and shift 8 without precision loss | |
// requires assembler really | |
volatile unsigned char zeroReg=0; | |
volatile unsigned int multipliedCounter=oscillators[c].phaseStep; | |
asm volatile | |
( | |
// high bytes mult together = high byte | |
"ldi %A[outVal],0" "\n\t" | |
"mul %B[phaseStep],%B[pitchBend]" "\n\t" | |
"mov %B[outVal],r0" "\n\t" | |
// ignore overflow into r1 (should never overflow) | |
// low byte * high byte -> both bytes | |
"mul %A[phaseStep],%B[pitchBend]" "\n\t" | |
"add %A[outVal],r0" "\n\t" | |
// carry into high byte | |
"adc %B[outVal],r1" "\n\t" | |
// high byte* low byte -> both bytes | |
"mul %B[phaseStep],%A[pitchBend]" "\n\t" | |
"add %A[outVal],r0" "\n\t" | |
// carry into high byte | |
"adc %B[outVal],r1" "\n\t" | |
// low byte * low byte -> round | |
"mul %A[phaseStep],%A[pitchBend]" "\n\t" | |
// the adc below is to round up based on high bit of low*low: | |
"adc %A[outVal],r1" "\n\t" | |
"adc %B[outVal],%[ZERO]" "\n\t" | |
"clr r1" "\n\t" | |
:[outVal] "=&d" (multipliedCounter) | |
:[phaseStep] "d" (oscillators[c].phaseStep),[pitchBend] "d"( pitchBendMultiplier),[ZERO] "d" (zeroReg) | |
:"r1","r0" | |
); | |
oscillators[c].phaseStep=multipliedCounter; | |
} | |
// Serial.print(":"); | |
// Serial.print(NOTE_FREQS[baseNote]); | |
// Serial.print(":"); | |
// Serial.println(oscillators[0].phaseStep); | |
} | |
void setup() | |
{ | |
Serial.begin(9600); // connect to the serial port | |
#ifndef FILTER_LPF_NONE | |
setFilter(127, 0); | |
#endif | |
pinMode(11, OUTPUT); // pin11= PWM output / frequency output | |
setupNoteFrequencies(12,0); | |
for(int c=0;c<8;c++) | |
{ | |
oscillators[c].volume=0; | |
} | |
Setup_timer2(); | |
// disable interrupts to avoid timing distortion | |
cbi (TIMSK0,TOIE0); // disable Timer0 !!! delay() is now not available | |
sbi (TIMSK2,TOIE2); // enable Timer2 Interrupt | |
// calibrate the unpressed key values | |
for(int x=0;x<1024;x++) | |
{ | |
getNoteKeys(true); | |
int steps=loopSteps; | |
WAIT_UNTIL_INTERRUPT(); | |
// int afterSteps=loopSteps; | |
// Serial.println(afterSteps-steps); | |
} | |
// test pattern intro | |
#ifdef TEST_PATTERN_INTRO | |
int filtValue=255; | |
byte notes[]={0x1,0x4,0x10,0x80,0x80,0x80,0x1,0x2,0x4,0x8,0x10,0x20,0x40,0x80,0x40,0x20,0x10,0x8,0x4,0x2,0x1,0x1,0x1,0x00,0x00,0x1,0x1,0x1,0x1,0x00,0x00,0x5,0x5,0x5,0x5,0x00,0x00,0x15,0x15,0x15,0x15,0x15,0x00,0x00,0x95,0x95,0x95,0x95,0x95,0x95,0x00}; | |
for(int note=0;note<sizeof(notes)/sizeof(byte);note++) | |
{ | |
int noteCount=0; | |
for(int c=0;c<8;c++) | |
{ | |
if(notes[note]&(1<<c)) | |
{ | |
noteCount+=1; | |
} | |
} | |
for(int c=0;c<8;c++) | |
{ | |
if(notes[note]&(1<<c)) | |
{ | |
oscillators[c].volume=63/noteCount; | |
}else | |
{ | |
oscillators[c].volume=0; | |
} | |
} | |
for(int c=0;c<50;c++) | |
{ | |
// might as well keep calibrating here | |
// nb: each calibration loop = at least 1 interrupt | |
getNoteKeys(true); | |
#ifndef FILTER_LPF_NONE | |
setFilter(127-c, 64); | |
#endif | |
} | |
} | |
#else | |
// just beep to show calibration is done | |
oscillators[0].volume=63; | |
for(int c=0;c<20;c++) | |
{ | |
WAIT_UNTIL_INTERRUPT(); | |
} | |
oscillators[0].volume=63; | |
#endif | |
Serial.println("Calibrations:"); | |
for(int c=0;c<8;c++) | |
{ | |
Serial.print(c); | |
Serial.print(":"); | |
Serial.println(calibrationThresholds[c]); | |
} | |
} | |
void loop() | |
{ | |
// we keep a list of 'raw' volumes - and turn down the volume if a chord is taking >64 volume total | |
// this is to allow chording without reducing the volume of single notes | |
int rawVolumes[8]={0,0,0,0,0,0,0,0}; | |
int curNote=0; | |
unsigned int filterSweep=64; | |
const int MIN_SWEEP=64; | |
const int MAX_SWEEP=127; | |
const int SWEEP_SPEED=3; | |
int sweepDir=SWEEP_SPEED; | |
unsigned int lastStep=loopStepsHigh; | |
unsigned curStep=loopStepsHigh; | |
while(1) | |
{ | |
lastStep=curStep; | |
curStep=loopStepsHigh; | |
// NOTE: timers do not work in this code (interrupts disabled / speeds changed), so don't even think about calling: delay(), millis / micros etc. | |
// each loopstep is roughly 31250 / second | |
// this main loop will get called once every 3 or 4 samples if the serial output is turned off, maybe slower otherwise | |
int liveNotes=getNoteKeys(); | |
// we are right after an interrupt (as loopStep has just been incremented) | |
// so we should have enough time to do the capacitative key checks | |
if(lastStep!=curStep) | |
{ | |
int totalVolume=0; | |
for(int c=0;c<8;c++) | |
{ | |
if((liveNotes&(1<<c))==0) | |
{ | |
rawVolumes[c]-=DECAY*(curStep-lastStep); | |
if(rawVolumes[c]<0)rawVolumes[c]=0; | |
if(SERIAL_OUT)Serial.print("."); | |
} | |
else | |
{ | |
rawVolumes[c]+=ATTACK*(curStep-lastStep); | |
if(rawVolumes[c]>63)rawVolumes[c]=63; | |
if(SERIAL_OUT)Serial.print(c); | |
} | |
totalVolume+=rawVolumes[c]; | |
} | |
WAIT_UNTIL_INTERRUPT(); | |
if( totalVolume<64 ) | |
{ | |
for(int c=0;c<8;c++) | |
{ | |
oscillators[c].volume=rawVolumes[c]; | |
} | |
}else | |
{ | |
// total volume too much, scale down to avoid clipping | |
for(int c=0;c<8;c++) | |
{ | |
oscillators[c].volume=(rawVolumes[c]*63)/totalVolume; | |
} | |
} | |
} | |
if(SERIAL_OUT)Serial.println(""); | |
#ifndef FILTER_LPF_NONE | |
/* if(liveNotes==0) | |
{ | |
filterSweep=64; | |
sweepDir=SWEEP_SPEED; | |
} | |
filterSweep+=sweepDir; | |
if(filterSweep>=MAX_SWEEP) | |
{ | |
filterSweep=MAX_SWEEP; | |
sweepDir=-sweepDir; | |
} | |
else if (filterSweep<=MIN_SWEEP) | |
{ | |
sweepDir=-sweepDir; | |
filterSweep=MIN_SWEEP; | |
}*/ | |
// Serial.println((int)filterValue); | |
// filterSweep=127-(getpitchbendtime()>>1); | |
WAIT_UNTIL_INTERRUPT(); | |
setFilter(150-(getfiltermodulationtime()),220); | |
#endif | |
WAIT_UNTIL_INTERRUPT(); | |
setupNoteFrequencies(12,-getpitchbendtime()); | |
// we are right after an interrupt again (as loopStep has just been incremented) | |
// so we should have enough time to check the pitch bend capacitance without going over another sample, timing is quite important here | |
// need to balance using a big enough resistor to get decent sensing distance with taking too long to sample | |
// check the pitch bend input | |
} | |
} | |
//****************************************************************** | |
// timer2 setup | |
// set prscaler to 1, PWM mode to phase correct PWM, 16000000/510 = 31372.55 Hz clock | |
void Setup_timer2() { | |
// Timer2 Clock Prescaler to : 1 | |
sbi (TCCR2B, CS20); | |
cbi (TCCR2B, CS21); | |
cbi (TCCR2B, CS22); | |
// Timer2 PWM Mode set to Phase Correct PWM | |
cbi (TCCR2A, COM2A0); // clear Compare Match | |
sbi (TCCR2A, COM2A1); | |
sbi (TCCR2A, WGM20); // Mode 1 / Phase Correct PWM | |
cbi (TCCR2A, WGM21); | |
cbi (TCCR2B, WGM22); | |
} | |
#ifdef FILTER_LPF_BIQUAD | |
char filtValueA1=0,filtValueA2=0,filtValueA3=0,filtValueB1=0,filtValueB2=0; | |
volatile unsigned char filtCoeffA1=255; | |
volatile char filtCoeffB1=127; | |
volatile unsigned char filtCoeffB2=255; | |
#endif | |
#ifdef FILTER_LPF_HACK | |
// hacked low pass filter - 2 pole resonant - | |
// a += f*((in-a)+ q*(a-b) | |
// b+= f* (a-b) | |
int filterA=0; | |
int filterB=0; | |
unsigned char filterQ=0; | |
unsigned char filterF=255; | |
inline void setFilterRaw(unsigned char filterF, unsigned char resonance) | |
{ | |
unsigned char tempReg=0,tempReg2=0; | |
asm volatile("ldi %[tempReg], 0xff" "\n\t" | |
"sub %[tempReg],%[filtF] " "\n\t" | |
"lsr %[tempReg]" "\n\t" | |
"ldi %[tempReg2],0x04" "\n\t" | |
"add %[tempReg],%[tempReg2]" "\n\t" | |
"sub %[reso],%[tempReg]" "\n\t" | |
"brcc Res_Overflow%=" "\n\t" | |
"ldi %[reso],0x00" "\n\t" | |
"Res_Overflow%=:" "\n\t" | |
"mov %[filtQ],%[reso]" "\n\t" | |
:[tempReg] "=&d" (tempReg),[tempReg2] "=&d" (tempReg2),[filtQ] "=&d" (filterQ): [reso] "d" (resonance), [filtF] "d" (filterF) ); | |
} | |
inline void setFilter(unsigned char f, unsigned char resonance) | |
{ | |
if(f>127)f=127; | |
filterF=logCutoffs[f]; | |
setFilterRaw(filterF,resonance); | |
} | |
#endif | |
#define HIBYTE(__x) ((char)(((unsigned int)__x)>>8)) | |
#define LOBYTE(__x) ((char)(((unsigned int)__x)&0xff)) | |
// oscillator main loop (increment wavetable pointer, and add it to the output registers) | |
// 13 instructions - should take 14 processor cycles according to the datasheet | |
// in theory I think this means that each oscillator should take 1.5% of cpu | |
// (plus a constant overhead for interrupt calls etc.) | |
// Note: this used to do all the stepvolume loads near the start, but they are now interleaved in the | |
// code, this is because ldd (load with offset) takes 2 instructions, | |
// versus ld,+1 (load with post increment) and st,+1 which are 1 instruction - we can do this because: | |
// | |
// a)the step (which doesn't need to be stored back) is in memory before the | |
// phase accumulator (which does need to be stored back once the step is added | |
// | |
// b)the phase assumulator is stored in low byte, high byte order, meaning that we | |
// can add the first bytes together, then store that byte incrementing the pointer, | |
// then load the high byte, add the high bytes together and store incrementing the pointer | |
// | |
// I think this is the minimum number of operations possible to code this oscillator in, because | |
// 1)There are 6 load operations required (to load stepHigh/Low,phaseH/L,volume, and the value from the wave) | |
// 2)There are 2 add operations required to add to the phase accumulator | |
// 3)There are 2 store operations required to save the phase accumulator | |
// 4)There is 1 multiply (2 instruction cycles) required to do the volume | |
// 5)There are 2 add operations required to add to the final output | |
// | |
// 6+2+2+2+2 = 14 instruction cycles | |
#define OSCILLATOR_ASM \ | |
/* load phase step and volume*/ \ | |
"ld %A[tempStep],%a[stepVolume]+" "\n\t" \ | |
"ld %B[tempStep],%a[stepVolume]+" "\n\t" \ | |
"ld %[tempVolume],%a[stepVolume]+" "\n\t" \ | |
/* load phase accumulator - high byte goes straight*/ \ | |
/* into the wave lookup array (wave is on 256 byte boundary*/ \ | |
/* so we can do this without any adds */ \ | |
/* Do the phase adds in between the two loads, as load with offset is slower than | |
just a normal load | |
*/\ | |
"ld %A[tempPhaseLow],%a[stepVolume]" "\n\t" \ | |
/* add phase step low */ \ | |
"add %[tempPhaseLow],%A[tempStep]" "\n\t"\ | |
/* store phase accumulator low */ \ | |
"st %a[stepVolume]+,%[tempPhaseLow]" "\n\t" \ | |
/* load phase accumulator high*/\ | |
"ld %A[waveBase],%a[stepVolume]" "\n\t" \ | |
/* add phase step high - with carry from the add above */\ | |
"adc %A[waveBase],%B[tempStep]" "\n\t"\ | |
/* store phase step high */\ | |
"st %a[stepVolume]+,%A[waveBase]" "\n\t" \ | |
/* now lookup from the wave - high byte = wave pointer, low byte=offset*/ \ | |
"ld %[tempPhaseLow],%a[waveBase]" "\n\t" \ | |
/* now multiply by volume*/ \ | |
"muls %[tempPhaseLow],%[tempVolume]" "\n\t" \ | |
/* r0 now contains a sample - add it to output value*/ \ | |
"add %A[outValue],r0" "\n\t" \ | |
"adc %B[outValue],r1" "\n\t" \ | |
/* go to next oscillator - stepVolume is pointing at next*/ \ | |
/* oscillator already */ \ | |
//****************************************************************** | |
// Timer2 Interrupt Service at 31372,550 KHz = 32uSec | |
// this is the timebase REFCLOCK for the DDS generator | |
// FOUT = (M (REFCLK)) / (2 exp 32) | |
// runtime : ? | |
ISR(TIMER2_OVF_vect) { | |
// now set up the next value | |
// this loop takes roughly 172 cycles (214 including the push/pops) - we have 510, so roughly 50% of the processor going spare for non-audio tasks | |
// the low pass filter also takes some cycles | |
int outValue; | |
// pointers: | |
// X = oscillator phase accumulator | |
// Y = oscillator step and volume | |
// Z = wave pos - needs to add to base | |
int tempStep=0; | |
char tempPhaseLow=0,tempVolume=0; | |
int tempWaveBase=0; | |
asm volatile ( | |
"ldi %A[outValue],0" "\n\t" | |
"ldi %B[outValue],0" "\n\t" | |
// oscillator 0 | |
// uncomment the code below to check | |
// that registers aren't getting double assigned | |
/* "lds %A[outValue],0x00" "\n\t" | |
"lds %B[outValue],0x01" "\n\t" | |
"lds %A[tempPhaseLow],0x02" "\n\t" | |
// "lds %B[tempPhase],0x03" "\n\t" | |
"lds %A[tempStep],0x04" "\n\t" | |
"lds %B[tempStep],0x05" "\n\t" | |
"lds %[tempVolume],0x06" "\n\t" | |
"lds %[ZERO],0x07" "\n\t" | |
"lds %A[tempWaveBase],0x08" "\n\t" | |
"lds %B[tempWaveBase],0x09" "\n\t" | |
"lds %A[phaseAccu],0x0a" "\n\t" | |
"lds %B[phaseAccu],0x0b" "\n\t" | |
"lds %A[stepVolume],0x0c" "\n\t" | |
"lds %B[stepVolume],0x0d" "\n\t" | |
"lds %A[waveBase],0x0e" "\n\t" | |
"lds %B[waveBase],0x0f" "\n\t"*/ | |
OSCILLATOR_ASM | |
OSCILLATOR_ASM | |
OSCILLATOR_ASM | |
OSCILLATOR_ASM | |
OSCILLATOR_ASM | |
OSCILLATOR_ASM | |
OSCILLATOR_ASM | |
OSCILLATOR_ASM | |
: | |
// outputs | |
[tempPhaseLow] "=&d" (tempPhaseLow), | |
[tempStep] "=&d" (tempStep), | |
[tempVolume] "=&d" (tempVolume), | |
[outValue] "=&d" (outValue) | |
: | |
// inputs | |
[stepVolume] "y" (&oscillators[0].phaseStep), | |
[waveBase] "z" (256*(((unsigned int)curWave)>>8)) | |
: | |
// other registers we clobber (by doing multiplications) | |
"r1" | |
); | |
// at this point outValue = oscillator value | |
// it is currently maxed to full volume / 4 | |
// to allow some headroom for filtering | |
#ifdef FILTER_LPF_HACK | |
// a low pass filter based on the one from MeeBlip (http://meeblip.noisepages.com) | |
// a += f*((in-a)+ q*(a-b) | |
// b+= f* (a-b) | |
// outValue>>=3; | |
// started at 4700 | |
// 4686 | |
int tempReg,tempReg2=0; | |
unsigned char zeroRegFilt=0; | |
// de-volatilisati | |
unsigned char filtF=filterF; | |
unsigned char filtQ=filterQ; | |
asm volatile( | |
"sub %A[outVal],%A[filtA]" "\n\t" | |
"sbc %B[outVal],%B[filtA]" "\n\t" | |
"brvc No_overflow1%=" "\n\t" | |
"ldi %A[outVal],0b00000001" "\n\t" | |
"ldi %B[outVal],0b10000000" "\n\t" | |
"No_overflow1%=:" "\n\t" | |
// outVal = (in - filtA) | |
"mov %A[tempReg],%A[filtA]" "\n\t" | |
"mov %B[tempReg],%B[filtA]" "\n\t" | |
"sub %A[tempReg],%A[filtB]" "\n\t" | |
"sbc %B[tempReg],%B[filtB]" "\n\t" | |
"brvc No_overflow3%=" "\n\t" | |
"ldi %A[tempReg],0b00000001" "\n\t" | |
"ldi %B[tempReg],0b10000000" "\n\t" | |
"No_overflow3%=:" "\n\t" | |
// tempReg = (a-b) | |
"mulsu %B[tempReg],%[filtQ]" "\n\t" | |
"movw %A[tempReg2],r0" "\n\t" | |
// tempReg2 = (HIBYTE(a-b))*Q | |
"mul %A[tempReg],%[filtQ]" "\n\t" | |
"add %A[tempReg2],r1" "\n\t" | |
"adc %B[tempReg2],%[ZERO]" "\n\t" | |
"rol r0" "\n\t" | |
"brcc No_Round1%=" "\n\t" | |
"inc %A[tempReg2]" "\n\t" | |
"No_Round1%=:" "\n\t" | |
// at this point tempReg2 = (a-b)*Q (shifted appropriately and rounded) | |
// "clc" "\n\t" | |
"lsl %A[tempReg2]" "\n\t" | |
"rol %B[tempReg2]" "\n\t" | |
// "clc" "\n\t" | |
"lsl %A[tempReg2]" "\n\t" | |
"rol %B[tempReg2]" "\n\t" | |
// tempReg2 = (a-b)*Q*4 | |
"add %A[outVal],%A[tempReg2]" "\n\t" | |
"adc %B[outVal],%B[tempReg2]" "\n\t" | |
"brvc No_overflow4%=" "\n\t" | |
"ldi %A[outVal],0b11111111" "\n\t" | |
"ldi %B[outVal],0b01111111" "\n\t" | |
"No_overflow4%=:" "\n\t" | |
// outVal = ((in-a) + (a-b)*(Q>>8)*4) - clipped etc | |
"mulsu %B[outVal],%[filtF]" "\n\t" | |
"movw %A[tempReg],r0" "\n\t" | |
"mul %A[outVal],%[filtF]" "\n\t" | |
"add %A[tempReg],r1" "\n\t" | |
"adc %B[tempReg],%[ZERO]" "\n\t" | |
"rol r0" "\n\t" | |
"brcc No_Round2%=" "\n\t" | |
"inc %A[tempReg]" "\n\t" | |
// tempReg = f* ((in-a) + (a-b)*(Q>>8)*4) | |
"No_Round2%=:" "\n\t" | |
"add %A[filtA],%A[tempReg]" "\n\t" | |
"adc %B[filtA],%B[tempReg]" "\n\t" | |
// A= A+ f* ((in-a) + (a-b)*(Q>>8)*4) | |
"brvc No_overflow5%=" "\n\t" | |
"ldi %A[outVal],0b11111111" "\n\t" | |
"ldi %B[outVal],0b01111111" "\n\t" | |
"No_overflow5%=:" "\n\t" | |
// now calculate B= f* (a - b) | |
"mov %A[tempReg],%A[filtA]" "\n\t" | |
"mov %B[tempReg],%B[filtA]" "\n\t" | |
"sub %A[tempReg],%A[filtB]" "\n\t" | |
"sbc %B[tempReg],%B[filtB]" "\n\t" | |
"brvc No_overflow6%=" "\n\t" | |
"ldi %A[tempReg],0b00000001" "\n\t" | |
"ldi %B[tempReg],0b10000000" "\n\t" | |
"No_overflow6%=:" "\n\t" | |
// tempReg = (a-b) | |
"mulsu %B[tempReg],%[filtF]" "\n\t" | |
"movw %A[tempReg2],r0" "\n\t" | |
"mul %A[tempReg],%[filtF]" "\n\t" | |
"add %A[tempReg2],r1" "\n\t" | |
"adc %B[tempReg2],%[ZERO]" "\n\t" | |
// tempReg2 = f*(a-b) | |
"add %A[filtB],%A[tempReg2]" "\n\t" | |
"adc %B[filtB],%B[tempReg2]" "\n\t" | |
"brvc No_overflow7%=" "\n\t" | |
"ldi %A[filtB],0b11111111" "\n\t" | |
"ldi %B[filtB],0b01111111" "\n\t" | |
"No_overflow7%=:" "\n\t" | |
// now b= b+f*(a-b) | |
"mov %A[outVal],%A[filtB]" "\n\t" | |
"mov %B[outVal],%B[filtB]" "\n\t" | |
// multiply outval by 4 and clip | |
"add %A[outVal],%A[filtB]" "\n\t" | |
"adc %B[outVal],%B[filtB]" "\n\t" | |
"brbs 3, Overflow_End%=" "\n\t" | |
"add %A[outVal],%A[filtB]" "\n\t" | |
"adc %B[outVal],%B[filtB]" "\n\t" | |
"brbs 3, Overflow_End%=" "\n\t" | |
"add %A[outVal],%A[filtB]" "\n\t" | |
"adc %B[outVal],%B[filtB]" "\n\t" | |
"brbs 3, Overflow_End%=" "\n\t" | |
"rjmp No_overflow%=" "\n\t" | |
"Overflow_End%=:" "\n\t" | |
"brbs 2,Overflow_High%=" "\n\t" | |
"ldi %A[outVal],0b00000001" "\n\t" | |
"ldi %B[outVal],0b10000000" "\n\t" | |
"rjmp No_overflow%=" "\n\t" | |
"Overflow_High%=:" "\n\t" | |
"ldi %A[outVal],0b11111111" "\n\t" | |
"ldi %B[outVal],0b01111111" "\n\t" | |
"No_overflow%=:" "\n\t" | |
//char valOut=((unsigned int)(outValue))>>8; | |
//valOut+=128; | |
// OCR2A=(byte)valOut; | |
"subi %B[outVal],0x80" "\n\t" | |
"sts 0x00b3,%B[outVal]" "\n\t" | |
// uncomment the lines below to see the register allocations | |
/* | |
"lds %A[filtA],0x01" "\n\t" | |
"lds %B[filtA],0x02" "\n\t" | |
"lds %A[filtB],0x03" "\n\t" | |
"lds %B[filtB],0x04" "\n\t" | |
"lds %[filtQ],0x05" "\n\t" | |
"lds %[filtF],0x06" "\n\t" | |
"lds %A[outVal],0x07" "\n\t" | |
"lds %B[outVal],0x08" "\n\t" | |
"lds %A[tempReg],0x09" "\n\t" | |
"lds %B[tempReg],0x0a" "\n\t" | |
"lds %A[tempReg2],0x0b" "\n\t" | |
"lds %B[tempReg2],0x0c" "\n\t" | |
"lds %[ZERO],0x0d" "\n\t"*/ | |
: | |
// outputs / read/write arguments | |
[filtA] "+&w" (filterA), | |
[filtB] "+&w" (filterB), | |
[tempReg] "=&a" (tempReg), | |
[tempReg2] "=&d" (tempReg2) | |
: | |
[filtQ] "a" (filtQ), | |
[filtF] "a" (filtF), | |
[outVal] "a" (outValue), | |
[ZERO] "d" (zeroRegFilt) | |
// inputs | |
: "r1"); | |
#endif | |
// output is done in the filter assembler code if filters are on | |
// otherwise we output it by hand here | |
#ifdef FILTER_LPF_NONE | |
// full gain | |
outValue*=4; | |
// at this point, outValue is a 16 bit signed version of what we want ie. 0 -> 32767, then -32768 -> -1 (0xffff) | |
// we want 0->32767 to go to 32768-65535 and -32768 -> -1 to go to 0-32767, then we want only the top byte | |
// take top byte, then add 128, then cast to unsigned. The (unsigned int) in the below is to avoid having to shift (ie.just takes top byte) | |
char valOut=((unsigned int)(outValue))>>8; | |
valOut+=128; | |
OCR2A=(byte)valOut; | |
#endif | |
// increment loop step counter (and high counter) | |
// these are used because we stop the timer | |
// interrupt running, so have no other way to tell time | |
// this asm is probably not really needed, but it does save about 10 instructions | |
// because the variables have to be volatile | |
asm( | |
"inc %[loopSteps]" "\n\t" | |
"brbc 1,loopend%=" "\n\t" | |
"inc %A[loopStepsHigh]" "\n\t" | |
"brbc 1,loopend%=" "\n\t" | |
"inc %B[loopStepsHigh]" "\n\t" | |
"loopend%=:" "\n\t" | |
:[loopSteps] "+a" (loopSteps),[loopStepsHigh] "+a" (loopStepsHigh):); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment