/* Copyright (C) 2016-2019, Ernst Jung This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Correspondence concerning this program can be directed to dr.ernstjung@gmail.com */ #define VERSION "21-usb, Apr 12 2019" #include #include #include #include // -- Pulse generator code for following hardware: // - Arduino Uno or Nano // - MCP4131 digital potentiometer // // Connections: // MCP4131 pin -> Arduino pin // 1 -> D4 (CS) // 2 -> D13 (SCK) // 3 -> D11 (SDI/SDO) // 4 -> GND (Vss) // 5 -> D2 (P0A) // 6 (P0W, output) // 7 -> GND (P0B) // 8 -> 5V (Vdd) // // Other Arduino pins for shield: // D6 for switching driver // D7 for discharging capacitor // A0 for selecting pulse generating function // A1-A5 other analog inputs // // Other Arduino pins for control switches: // D3, D5, D8, D9, D10, D12 reserved // // Used in prototype: // D3 -> Start, via int.1 // D5 -> S5 // D8 -> S4 // D9 -> S3, // D10 -> S2 // // Timing parameters: // clock frequency = 16 MHz // TIMER1 with f/256 prescaler: // scaled frequency = 62.5 kHz // scaled period = 16 us // maximum delay = 1.05 s // TIMER2 with f/128 prescaler: // scaled frequency = 125 kHz // scaled period = 8 us // maximum delay = 2 ms // TIMER0 not used: // remains available to Arduino IDE timing functions. // pulse parameter generating function prototype. // functions of this type can modify volatile variables // p_amplitude, p_interval and p_width typedef void (*pulse_gen)(void); // generator mode handling function prototype. typedef void (*generator_function)(void); // -- SPI (mcp4131 digital potentiometer) byte address = 0x00; // address on chip int CS = 4; // chip select pin int PA = 2; // potentiometer high pin volatile int p_amplitude=0; // potentiometer setting 0..128 volatile int cur_ampl=0; // current setting volatile int pulsing=0; // pulses being generated // -- pins int driver_pin = 6; // driver int cd_pin = 7; // capacitor discharge pin // -- discharge time formula: // t = -ln(V2/V1)/RC // for 300 uF and 0.5 Ohm and 18 us per period that is 18.75 int cdtimes[20]={62, 56, 43, 35, 30, 25, 22, 19, 17, 14, 12, 11, 9, 8, 6, 5, 4, 3, 0, 0}; int ncdtimes=sizeof(cdtimes)/sizeof(int); int cdflag = 0; static bool running=false; // generator running? float loop_delay=0.01; // seconds delay in loop function // -- map 10-bit value from analog pin to a pulse interval // according to a quadratic scale. int map_interval(int adc_value) { float pval=adc_value*0.009775171065493646; return 1./((4.98*pval*pval+2)*16e-6); } // -- initial timer parameters volatile int c_width = 64; // capacitor charge time (1ms, TIMER1) volatile int p_width = 25; // pulse time (200us, TIMER2) volatile int cur_width=0; // current value volatile int p_interval = 125; // delay until next pulse // -- flag used by capacitor discharge code volatile int pulsed = 0; // driver activated? // -- ADC handler variables volatile int anapin=0; // current analog input pin for multiplexer volatile int analog[6]; // values from ADC volatile int anamask[6]={1,1,1,1,1,1}; // pins to convert // -- PC-interrupt handler variables volatile byte portBstate, portBpast, changedbits=0; int runstored=0; // currently running a stored setting; // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // -- collection of functions for setting pulse generating parameters // -- basic pulse. // parameters to be determined by generator static void basic_pulse() { } // -- constant frequency and amplitude pulses // - frequency derived from pin A1 // - pulse width derived from pin A5 // - amplitude determined by generator function static void constant_pulse() { p_interval = map_interval(analog[1]); p_width = map(analog[5], 0, 1023, 3, 50); // 24us .. 400us } // -- high frequency and amplitude pulses // - frequency derived from pin A1 // - pulse width derived from pin A5 // - amplitude determined by generator function static void high_pulse() { p_interval = map_interval(1023); // highest frequency p_width = 25; // 200us p_amplitude = 96; } // -- varying width pulses // - frequency derived from pin A1 // - pulse width from internal array 'widths' // - amplitude determined by generator function // static void varwid_pulse() { static int widths[]={4, 16, 27, 38, 50}; static int nw = sizeof(widths)/sizeof(int); static int index=0; p_interval = map_interval(analog[1]); p_width = widths[index]; index = (index+1)%nw; } // -- varying interval // - base frequency derived from pin A1 // - pulse width derived from pin A5 // - amplitude determined by generator function // - pulse interval varies randomly around interval corresponding // to base frequency. // static void varintv_pulse() { p_interval = map_interval(analog[1]); p_interval = max(random((0.75*p_interval),(1.25*p_interval)),125); p_width = map(analog[5], 0, 1023, 3, 50); // 24us .. 400us } // -- "spread" interval // - base frequency derived from pin A1 // - pulse width derived from pin A5 // - amplitude determined by generator function // - pulse interval is spread around interval corresponding // to base frequency. // static void spread_pulse() { // static float factors[]={0.667, 0.333, 0.333, 0.667}; // "reine kwint" // static float factors[]={1.0, 0.8, 0.5, 0.1}; // "chirp" static float factors[]={1.0, 0.5, 0.25, 0.125}; // "chirp" static int n_factors=sizeof(factors)/sizeof(float); static int index=0; p_interval = map_interval(analog[1])*factors[index]; index = (index+1)%n_factors; p_width = map(analog[5], 0, 1023, 3, 50); // 24us .. 400us } // -- random pulses // - pulse interval derived from random value between A1 and A2 // - pulse width derived from random value between A4 and A5 // - amplitude derived from random value between A3 and 127 static void random_pulse() { int f1, f2, w1, w2; f1 = min(analog[1], analog[2]); f2 = max(analog[1], analog[2]); w1 = min(analog[4], analog[5]); w2 = max(analog[4], analog[5]); p_interval = map_interval(random(f1, f2)); // min-max interval p_width = map(random(w1, w2), 0, 1023, 3, 50); // min-max width p_amplitude = random(analog[3]/8, 127); // min-max amplitude } // -- pattern // pulse interval, width and amplitude from data in function // static void pattern_pulse() { static int seqno=0; static int repeat=0; struct impulse { int interval; // periods until next timer compare int amplitude; // potmeter wiper setting (0..128) int width; // pulse width int repeat; // repeat count }; static impulse pattern[] = { { 500, 128, 50, 1}, // 8ms, max, 400us { 125, 128, 50, 1}, { 125, 64, 25, 2}, // 2ms, half, 200us { 125, 64, 3, 2} // 2ms, half, 24us }; int npattern = sizeof(pattern)/sizeof(impulse); impulse pulse; if (repeat==0) { pulse = pattern[seqno]; repeat = pulse.repeat; p_interval = pulse.interval; p_amplitude = pulse.amplitude; p_width = pulse.width; seqno = (++seqno)%npattern; } repeat--; } static pulse_gen pulse_variants[]={constant_pulse, varwid_pulse, spread_pulse}; //static pulse_gen pulse_variants[]={spread_pulse}; static int nvariants=3; static int variant=0; volatile pulse_gen get_pulse_parameters=basic_pulse; volatile pulse_gen constfreq_pulse=pulse_variants[variant]; // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // -- generator functions // // -- zero amplitude pulses static void generator_zero() { if (!running) { p_interval = 12500; // 5 Hz p_amplitude = 0; pulsing = 0; get_pulse_parameters = basic_pulse; running = true; // Serial.println("zero"); // TEST } } // -- constant frequency pulses of maximum amplitude; // static void generator_constant() { static float amplitude=0.0, increment; get_pulse_parameters = constfreq_pulse; if (!running) { p_amplitude = 0; amplitude=0.0; pulsing = 1; running = true; // Serial.println("constant"); // TEST } else { increment = loop_delay*128.0/(0.1+(float)(analog[2])/1.707); amplitude = min(128.0, amplitude+increment); p_amplitude = amplitude; // pulsing = 1; } } // -- repeatedly ramp amplitude up from base value to maximum, then drop // to base value. // pulses are generated by constfreq_pulse() // period (0.1 to 10 seconds) from A4. // base value (0..128) is derived from A3. static void generator_ramp() { get_pulse_parameters = constfreq_pulse; static float amplitude=0.0, increment, base; if (!running) { p_amplitude = 0; pulsing = 1; running = true; // Serial.println("ramp"); // TEST } base = float(analog[3]/8); // map 0..1023 to 0..127 increment = loop_delay*(128.0-base)/(0.1+(float)(analog[4])/102.40); amplitude = amplitude+increment; if (amplitude>128.0) amplitude = base; p_amplitude = amplitude; } // -- repeatedly ramp frequency up from the value from A1 to a second value // from A2, then drop to the first value. // Period (0.1 to 10 seconds) from A3. // pulses are generated by basic_pulse() which obtains its parameters from // this generator. static void generator_chirp() { static float frequency, increment, f1, f2; if (!running) { p_amplitude = 128; pulsing = 1; frequency = analog[1]; get_pulse_parameters = basic_pulse; running = true; // Serial.println("chirp"); // TEST } f1 = analog[1]; f2 = analog[2]; increment = loop_delay*(f2-f1)/(0.1+(float)(analog[3])/102.40); frequency += increment; if (frequency>max(f1,f2) || frequencylimit) { amplitude = 0.0; p_amplitude = 128; count = 0; phase = 1; } else { increment = loop_delay*128.0/(0.1+(float)(analog[2])/102.40); amplitude = amplitude+increment; p_amplitude = amplitude; } break; } case 1: { limit = (float)(analog[3]/102.40)/loop_delay; if (++count>limit) { p_amplitude = 0; count = 0; phase = 2; } break; } case 2: { limit = (float)(analog[4]/102.40)/loop_delay; if (++count>limit) { count = 0; phase = 0; } break; } } } // -- repeatedly ramp amplitude up from zero to maximum, stay at maximum for // some time, then drop to zero for some other time. // pulses are generated by constfreq_pulse() // on/off ratio by A3 and ramp/max ratio by A4. static void generator_burst() { static int phase; // ramp, maximum or zero int cycles, cycles_on, cycles_ramp, cycles_max, cycles_off, cycle_par; static int count, limit; // counts for maximum and zero phase static float increment, amplitude; get_pulse_parameters = constfreq_pulse; if (!running) { p_amplitude = 0; pulsing = 1; amplitude = 0.0; phase = 0; count = 0; running = true; // Serial.println("burst"); // TEST } cycle_par = (1023-analog[2])>>3; cycles = (float)(cycle_par*cycle_par/(1075.0*loop_delay))+10; // 0.1 - 15 seconds cycles_off = cycles*(float)(1023-analog[3])*0.0009765625; cycles_on = cycles-cycles_off; cycles_ramp = cycles_on*(float)(1023-analog[4])*0.0009765625; cycles_max = cycles_on-cycles_ramp; switch (phase) { case 0: { pulsing = 1; if ((count++)>=cycles_ramp) { amplitude = 0.0; p_amplitude = 128; count = 0; phase = 1; } else { increment = 127.0/(float)(cycles_ramp); amplitude = min(amplitude+increment, 128); p_amplitude = amplitude; } break; } case 1: { if (++count>cycles_max) { pulsing = 0; p_amplitude = 0; count = 0; phase = 2; } break; } case 2: { if (++count>cycles_off) { count = 0; phase = 0; } break; } } } // -- modulate frequency between values derived from A1 and A2. // Modulation period (0.1 to 5 seconds) from A3. static void generator_fm() { float min_interval, max_interval, increment; static float angle=0.0, factor, twopi=2.0*3.141592653589793;; if (!running) { factor = loop_delay*twopi; p_amplitude = 128; pulsing = 1; get_pulse_parameters = basic_pulse; running = true; // Serial.println("frequency modulation"); // TEST } p_width = map(analog[5], 0, 1023, 3, 50); min_interval = map_interval(analog[2]); max_interval = map_interval(analog[1]); if (max_interval=twopi) angle = 0.0; p_interval = ((1.0-cos(angle))/2.0)*(float)(max_interval-min_interval)+min_interval; } // -- random pulses static void generator_random() { if (!running) { get_pulse_parameters = random_pulse; pulsing = 1; running = true; // Serial.println("random"); // TEST } } // -- pattern pulses static void generator_pattern() { if (!running) { get_pulse_parameters = pattern_pulse; pulsing =1; running = true; // Serial.println("pattern"); // TEST } } #if 0 // not currently used // -- alternate frequencies static void generator_altfreq() { static int phase; // maximum or zero static int count, limit; // counts for maximum and zero phase static int ftoggle; // frequency toggle; if (!running) { p_amplitude = 0; pulsing = 1; phase = 1; count = 0; ftoggle = 0; get_pulse_parameters = basic_pulse; running = true; // Serial.println("alternate frequencies"); // TEST } p_width = map(analog[5], 0, 1023, 3, 50); switch (phase) { case 0: { // off-state limit = (float)(analog[4]/102.40)/loop_delay; p_amplitude = 0; if (++count>limit) { count = 0; phase = 1; } break; } case 1: { // on-state limit = (float)(analog[3]/102.40)/loop_delay; p_interval = map_interval(analog[1+ftoggle]); p_amplitude = 128; if (++count>limit) { count = 0; phase = 0; ftoggle ^= 1; // select other frequency } break; } } } #endif // not currently used // -- modulate amplitude with a period derived from A4. // pulses are generated by constfreq_pulse() static void generator_am() { float increment; static float angle, factor, twopi=2.0*3.141592653589793;; get_pulse_parameters = constfreq_pulse; if (!running) { angle = 0.0; factor = loop_delay*twopi; p_amplitude = 0; pulsing = 1; running = true; // Serial.println("amplitude modulation"); // TEST } increment = factor/(0.1+(float)analog[4]/511.0); angle += increment; if (angle>=twopi) angle = 0.0; p_amplitude = (1.0-cos(angle))*64.0; } // -- modulate frequency between values derived from A1 and A2 and // at the same time modulate amplitude fro zero to max. // Frequency modulation period (0.1 to 5 seconds) from A3. // Amplitude modulation period (0.1 to 5 seconds) from A4. static void generator_fm_am() { float min_interval, max_interval, f_increment, a_increment; static float f_angle=0.0, a_angle=0.0, factor, twopi=2.0*3.141592653589793;; if (!running) { factor = loop_delay*twopi; p_amplitude = 0; pulsing = 1; get_pulse_parameters = basic_pulse; running = true; // Serial.println("frequency and amplitude modulation"); // TEST } p_width = map(analog[5], 0, 1023, 3, 50); min_interval = map_interval(analog[2]); max_interval = map_interval(analog[1]); if (max_interval=twopi) f_angle = 0.0; a_increment = factor/(0.1+(float)analog[4]/511.0); a_angle += a_increment; p_amplitude = (1.0-cos(a_angle))*64.0; } // -- after a delay derived from A3, ramp amplitude up from zero to maximum, // stay at maximum for some time, then stop. The total of ramp- and // maximum time is derived from A2 and the ratio of ramp and maximum // from A4. static void generator_single() { static int count, upcount, count_ramp, count_max, delay_count, delay_pulses, phase; static float increment, amplitude; if (!running) { p_amplitude = 0; amplitude = 0; get_pulse_parameters = high_pulse; pulsing = 0; running = true; delay_count = (float)analog[3]/(loop_delay*51.2)+1.; delay_pulses = delay_count*loop_delay-1; // number of pulses during delay count = (float)analog[2]/(loop_delay*102.4)+10.; upcount = 0; count_max = count*(float)analog[4]*0.0009765625; count_ramp = count-count_max; increment = 127.0/(float)(count_ramp); phase = 0; // Serial.println("single shot"); // TEST } switch (phase) { case 0: { if (delay_count<=0) { phase = 1; get_pulse_parameters = constfreq_pulse; } else { // during initial delay short pulse train every second if ((upcount%100)==0) { if (delay_pulses>0) pulsing = 1; delay_pulses--; } else { pulsing = 0; } delay_count--; // decrement delay count upcount++; } break; } case 1: { pulsing = 1; if (count_ramp<=0) { phase = 2; } else { amplitude = min(amplitude+increment, 128); p_amplitude = amplitude; count_ramp--; } break; } case 2: { p_amplitude = 128; if (count_max<=0) { digitalWrite(2, LOW); pulsing = 0; } else { count_max--; } } } } generator_function generators[] = { generator_zero, generator_single, generator_constant, generator_burst, generator_ramp2, generator_am, generator_fm, generator_fm_am, generator_chirp, generator_random, generator_ramp, // generator_altfreq }; int n_generators=sizeof(generators)/sizeof(generator_function); int generator_index=0; volatile generator_function generator=generator_zero; // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // -- select pulse generator function based on argument 'analog'. // Argument 'analog' is the read-out of pin A0 connected // to a rotary switch populated with 1k resistors. // static void select_generator(int analog) { static int votes=0, previous=0; // variables used in contact bounce handling int index = map(analog+51, 0, 1023, 0, 10); if (index!=generator_index) { previous = index; if (votes>200) { // change generator after 200 identical indexes votes = 0; // running = false; if (digitalRead(3)) { digitalWrite(2, 0); // disable pulse generation pulsing = 0; p_amplitude = 0; } generator_index = index; if (!runstored) generator=generators[generator_index%n_generators]; } else { if (index==previous) votes++; else votes = 0; } } } // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // -- write to mcp4131 digital potentiometer // int digitalPotWrite(int value) { digitalWrite(CS, LOW); SPI.transfer(address); SPI.transfer(value); digitalWrite(CS, HIGH); } // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // -- start stop pulse generation void start_stop() { static long now, timestamp; now = millis(); if (now>(timestamp+500)) { timestamp = now; if (digitalRead(2)) { digitalWrite(2, LOW); p_amplitude = 0; pulsing = 0; } else { digitalWrite(2, HIGH); running = false; // restart generator } } } // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // -- status report void report() { char line[80]; sprintf(line, "\nSynthesizer version: %s", VERSION); Serial.println(line); sprintf(line, "--------------------------------------"); Serial.println(line); sprintf(line, "Analog: 0 1 2 3 4 5"); Serial.println(line); sprintf(line, "--------------------------------------"); Serial.println(line); sprintf(line, " %4d %4d %4d %4d %4d %4d", analog[0], analog[1], analog[2], analog[3], analog[4], analog[5]); Serial.println(line); sprintf(line, "--------------------------------------"); Serial.println(line); sprintf(line, "Generator: %8d", generator_index); Serial.println(line); sprintf(line, "Pulse interval: %8ld ms (%d Hz)", 16*(long)p_interval/1000, (int)(1.0/(p_interval*16e-6)+0.5)); Serial.println(line); sprintf(line, "Pulse width: %8d µs", 8*p_width); Serial.println(line); sprintf(line, "Pulse amplitude: %8d", p_amplitude); Serial.println(line); sprintf(line, "======================================\n"); Serial.println(line); } // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // -- monitor analog[] array void mtr_analog() { char line[80]; static int count=0; count = ++count%10; // display every 10th loop delay cycle if (!count) { sprintf(line, "Analog values: %4d %4d %4d %4d %4d %4d\r", analog[0], analog[1], analog[2], analog[3], analog[4], analog[5]); Serial.print(line); } } // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // -- process switches 3 and 4 void process_switches() { static int settings[6]; static int saved=0; int changed; cli(); changed = changedbits; changedbits = 0; sei(); if (changed) { if (changed&(1< signal light pinMode(driver_pin, OUTPUT); // driver pin pinMode(cd_pin, OUTPUT); // capacitor discharge pin pinMode(3, INPUT_PULLUP); // used for interrupt 1; gray pinMode(5, INPUT_PULLUP); // purple pinMode(8, INPUT_PULLUP); // blue pinMode(9, INPUT_PULLUP); // yellow pinMode(10, INPUT_PULLUP); // orange attachInterrupt(1, start_stop, FALLING); // -- SPI setup SPI.begin(); // -- prevent unwanted output because potentiometer's // POR wiper setting is mid-scale. digitalPotWrite(0); // set potentiometer to lowest value ... digitalWrite(PA, HIGH); // ... then apply voltage to it -- disabled, done via interrupt delay(250); // light for 250 ms (constant if zero generator is selected) cli(); // disable interrupts // -- TIMER1 setup TCCR1A = 0; // stop timer as initialized ... TCCR1B = 0; // ... by Arduino IDE TCCR1A |= 0b00000011; // fast PWM TCCR1B = 4; // f/256 prescaler TCCR1B |= (1 << WGM12); // clear timer on compare match TCCR1B |= (1 << WGM13); // fast PWM TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt TIMSK1 |= (1 << OCIE1B); // enable timer compare interrupt TCNT1 = 0; // clear counter 1 OCR1A = c_width+1; // arbitrary first period OCR1B = OCR1A-c_width; // before reset // -- TIMER2 setup TCCR2A = 0; // stop timer as initialized ... TCCR2B = 0; // ... by Arduino IDE TIMSK2 = 0; // TCCR2B = 5; // f/128 prescaler TIMSK2 |= (1 << OCIE2A); // enable timer compare interrupt TIMSK2 |= (1 << OCIE2B); OCR2A = p_width; OCR2B = p_width + 5; // -- adc setup ADMUX = 0; // right adjust result ADMUX |= 0b01000000; // reference voltage AV_CC ADMUX |= anapin; // first analog pin ADCSRA = 0b10000000; // enable ADC ADCSRA |= 0b00000111; // f/128 prescaler ADCSRA |= 0b00001000; // enable ADC interrupt ADCSRB = 0; ADCSRB |= 0b00000000; // free running trigger mode DIDR0 = 0b00111111; // disable digital input on analog pins // -- PC-interrupt setup portBstate = PINB; PCICR |= (1<4) { OCR2B = cur_width+cdtime; // add capacitor discharge time cdflag = 1; // set capacitor discharge flag } else { cdflag = 0; } } } ISR(TIMER1_COMPA_vect) { digitalPotWrite(0); TCNT2 = 0; // restart TIMER2 TCCR2B = 5; // f/128 prescaler if (pulsing) { digitalWrite(driver_pin, HIGH); // activate driver } pulsed = 1; // indicate pulse is being generated } // -- TIMER2 interrupts ISR(TIMER2_COMPA_vect) { if (pulsed) { digitalWrite(driver_pin, LOW); // deactivate driver if (cdflag) digitalWrite(cd_pin, HIGH); // discharge capacitor if necessary cdflag = 0; // clear capacitor discharge flag } pulsed = 0; // clear pulse-generation flag } ISR(TIMER2_COMPB_vect) { TCCR2B = 0; // stop timer digitalWrite(cd_pin, LOW); // stop discharging capacitor } // -- ADC interrupt ISR(ADC_vect) { if (anamask[anapin]) { analog[anapin] = ADCL | (ADCH << 8); // get value from A/D converter if (anapin==0) { select_generator(analog[anapin]); // special: A0 selects pulse generator } } anapin = (++anapin)%6; // cycle through available pins ADMUX = (ADMUX & 0xF0) | anapin; // select pin ADCSRA |= 0b01000000; // restart ADC } // -- pin-change interrupt ISR(PCINT0_vect) { static long now, timestamp; now = millis(); portBpast = portBstate; portBstate = PINB; if (now>(timestamp+1000)) { timestamp = now; changedbits = portBpast ^ portBstate; } } // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // -- Arduino IDE loop function // void loop() { check_running(); process_switches(); generator(); if (!digitalRead(5)) report(); if (!digitalRead(10)) mtr_analog(); delay((unsigned long)(loop_delay*1000.0)); }