MiniMO Compatible 4-Pot ADSR

May 21, 2020 at 12:18 pm (computers, maker, music) (, , , )

I’ve been enjoying having a play with the MiniMO synth, now I can programme it easily, but although I seriously like the idea of a piece of common hardware with software-defined synth personalities, I just found the ADSR with a single potentiometer just too limiting and not responsive enough for trying things out.

So I’ve sacrificed the common hardware and created a MiniMO compatible (i.e. based on the same circuit and code) 4-potentiometer ADSR instead.  To continue to use an ATtiny85 though requires a few compromises:

  • There is no button or LED.
  • There is no CV input to control the ADSR parameters (although this could be added).
  • The pots are always “live” – changing them will instantly change the parameter they represent.
  • And I needed to reclaim the ATtiny85 RST pin (pin 1) as an I/O pin.

If you are familiar with the ATtiny85 you’ll know that there is a fuse setting that will disable its response to the RST signal on pin 1 allowing you to use it as an additional I/O pin.

But you will also therefore probably know that this means you can no longer program it using the normal methods!  Once that fuse is set the only way to get back into your device is using a High Voltage Serial Programmer (HVSP).  There are a number of designs around the Internet for these and I’ve build some of them myself so can confirm they work.  The one I tend to use is a variation on this one with both 8-pin and 14-pin sockets, driven by an ATtiny2313 and some tweaked code to act as a “fuse resitter”.  I’ll perhaps post about that at some point.

You can’t set the RSTDISBL fuse from within the Arduino environment, so the general procedure is as follows:

  • Set your parameters as required for your device.
  • In “preferences” enable verbose messages on upload.
  • “Burn bootloader” to set the existing fuses – this will give you the command used to set the fuses.
  • In “preferences” disable verbose messages on upload again (if you want).
  • Upload your sketch to the ATtiny85.

THE NEXT COMMAND WILL “BRICK” YOUR DEVICE IF YOU DON’T HAVE A HVSP.

  • Copy the command to set the fuses and update the hfuse value to set the RSTDISBL flag (i.e. put it to zero) and run it in a command window.

Running through the above, my “burn bootloader” command looks like this:

[PATH TO AVRDUDE]/avrdude -C[PATH TO AVRDUDE CONF]/avrdude.conf -v      -pattiny85 -cusbtiny -e -Uefuse:w:0xFF:m -Uhfuse:w:0b11010111:m         -Ulfuse:w:0xE2:m -Uflash:w:[PATH TO BOOTLOADER]empty_all.hex:i

I am using a USBtiny programmer. We only want the hfuse “write” command (and really don’t want the -e option which erases the sketch in memory). You can use a ATtiny85 fuse calculator to see what the value should be – for example, see this one.

First I read back the just set hfuse value.

[PATH TO AVRDUDE]/avrdude -C[PATH TO AVRDUDE CONF]/avrdude.conf -v      -pattiny85 -cusbtiny -Uhfuse:r:out.txt

Then write the new value as calculated by the fuse calculator – RSTDISBL is the top bit of the hfuse, so for me, it read back 0xD7, so I write out 0x57 – running this command WILL “BRICK” YOUR DEVICE if you don’t have a HVSP.

[PATH TO AVRDUDE]/avrdude -C[PATH TO AVRDUDE CONF]/avrdude.conf -v      -pattiny85 -cusbtiny -Uhfuse:w:0x57:m

So with all that out of the way, this is the circuit and code I used to get a MiniMO compatible 4-potentiometer ADSR.MinoMo-ADSR2-2_schem

As I said, I’ve sacrificed some of the hardware, but the pot input stage is the same (just repeated four times), and the output and gate stages are the same as the original MinoMO design, but the output is on a different pin to free up all four ADCs. It has moved from using OC1B as the PWM output to using OC1A instead.

The code is a lot simpler as it isn’t having to handle the button and shared parameter setting via the single pot.  Instead the potentiometer values are read “live” in my version.

/*
//************************
//*      miniMO ADSR     *
//*   2016 by enveloop   *
//************************
//
   
Home
CC BY 4.0 Licensed under a Creative Commons Attribution 4.0 International license: http://creativecommons.org/licenses/by/4.0/ // --- Kevin --- Updated to build on recent Arduino IDE with ATTiny Core from: https://github.com/SpenceKonde/ATTinyCore ADSR Re-written to use four individual potentiometers and reduced inputs. Note: this sacrifices the LED output, button for input, control inputs on the pots and relies on repurposing RST as an I/O pin (see below). It also switches the output from digital I/O 4 (pin 3) to digital I/O 1 (pin 6) so that ADC4 can be used. This means switching the PWM output from OC1B on compare to OC1A. I/O 1 and 2: Outputs - control voltage (usually for amplitude) 3: Not connected - but could optionally double with the potentiometers as per the original design 4: Input - gate (note ON/OFF) Mapped to ATtiny85 I/O: --------- ADC0 - 5/A0 - PB5 - | 1 8 | - VCC ADC3 - 3/A3 - PB3 - | 2 7 | - PB2 - 2/A1 - ADC1 ADC2 - 4/A2 - PB4 - | 3 6 | - PB1 - 1 - OC1A - PWM (Output) GND - | 4 5 | - PB0 - 0 - dig I/P (Gate) --------- Map potentiometers to ADSR: ADC0 (5) - (A)ttack ADC1 (2) - (D)ecay ADC3 (3) - (S)ustain ADC2 (4) - (R)elease NOTE: To use ADC0 need to programme the fuses to repurpose RST as an I/O pin which means it is no longer possible to programme the ATtiny85 except with a high voltage serial programmer (HVSP). --- Kevin --- */ #include <avr/io.h> #include <util/delay.h> // Define Arduino pin numbers - NB: Digital and Analogue have different numbers! #define ADSR_A 5 // PB5 = ADC0 - digital I/O 5 (pin 1) is the repurposed RST pin #define ADSR_ALG_A A0 #define ADSR_D 2 // PB2 = ADC1 #define ADSR_ALG_D A1 #define ADSR_S 3 // PB3 = ADC3 #define ADSR_ALG_S A3 #define ADSR_R 4 // PB4 = ADC2 #define ADSR_ALG_R A2 #define ADSR_G 0 // Gate input #define ADSR_O 1 // OC1A output int ADSR_adc[] = { ADSR_ALG_A, ADSR_ALG_D, ADSR_ALG_S, ADSR_ALG_R }; volatile unsigned int globalTicks; //output int envelopeValue; //envelope stage control; bool readyToAttack = true; bool readyToRelease = false; const int attackLevel = 255; int currentStep = 0; int ADSR[] = { 0, //attackLength 1, //decayLength 255, //sustainLevel 0 //releaseLength }; void setup() { //disable USI to save power as we are not using it PRR = 1<<PRUSI; ADMUX = 0; //reset multiplexer settings pinMode(ADSR_G, INPUT); //digital input (gate) pinMode(ADSR_O, OUTPUT); //output (PWM on OC1A) pinMode(ADSR_A, INPUT); //analog- ADC0 - (A)ttack pinMode(ADSR_D, INPUT); //analog- ADC1 - (D)ecay pinMode(ADSR_S, INPUT); //analog- ADC2 - (S)ustain pinMode(ADSR_R, INPUT); //analog- ADC3 - (R)elease //set clock source for PWM -datasheet p94 PLLCSR |= (1 << PLLE); // Enable PLL (64 MHz) while (!(PLLCSR & (1 << PLOCK))); // Ensure PLL lock PLLCSR |= (1 << PCKE); // Enable PLL as clock source for timer 1 cli(); // Interrupts OFF (disable interrupts globally) //PWM Generation -timer 1 TCCR1 = (1 << PWM1A) | // PWM, output on PB1, compare with OCR1A (see interrupt below), reset on match with OCR1C (1 << COM1A1) | (1 << CS10); // no prescale OCR1C = 0xff; // 255 //Timer Interrupt Generation -timer 0 TCCR0A = (1 << WGM01); // Clear Timer on Compare (CTC) with OCR0A TCCR0B = (1 << CS01); // prescaled by 8 OCR0A = 0x64; // 0x64 = 100 //10000hz - 10000 ticks per second TIMSK = (1 << OCIE0A); // Enable Interrupt on compare with OCR0A sei(); // Interrupts ON (enable interrupts globally) } //Timer0 interrupt ISR(TIMER0_COMPA_vect) { //10000 ticks per second globalTicks++; } void loop() { setADSR(); triggerADSR(); } void setADSR(){ // Read one each scan if (currentStep == 2) setLevel(currentStep); // S is a level not a length else setLength (currentStep); // ADR are all lengths currentStep++; currentStep &= 0x03; } void setLength(int step) { int lengthRead = analogRead(ADSR_adc[step]) >> 6 ; //values between 0-15 ADSR[step] = lengthRead; } void setLevel(int step) { int levelRead = analogRead(ADSR_adc[step]) >> 2 ; //values between 0-255 ADSR[step] = levelRead; } int checkGATE() { return (PINB & (1<<ADSR_G)); } void triggerADSR() { // Check the Gate input if (checkGATE()) { if (readyToAttack){ readyToAttack = false; readyToRelease = true; readADS(); } } else{ if (readyToRelease){ readyToAttack = true; readyToRelease = false; readR(); } } } void readADS() { int attackLength = ADSR[0]; int decayLength = ADSR[1]; int sustainLevel = ADSR[2]; //ATTACK if (attackLength == 0) OCR1A = attackLevel; else { globalTicks = 0; for (envelopeValue = 0; envelopeValue <= 255; ){ OCR1A = envelopeValue; if (globalTicks >= attackLength) { envelopeValue++; globalTicks = 0; } } } //DECAY globalTicks = 0; if ((decayLength == 0) || (sustainLevel == attackLevel)) OCR1A = sustainLevel; else{ if (sustainLevel < attackLevel){ for (envelopeValue = attackLevel; envelopeValue >= sustainLevel; ){ OCR1A = envelopeValue; if (globalTicks >= decayLength) { envelopeValue--; globalTicks = 0; } } } } //SUSTAIN --nothing to do, we keep the last value until the "note off" trigger } void readR() { int sustainLevel = ADSR[2]; int releaseLength = ADSR[3]; //RELEASE if (releaseLength == 0) OCR1A = 0; else { OCR1A = sustainLevel; globalTicks = 0; for (envelopeValue = sustainLevel; envelopeValue >= 0;){ if (checkGATE()){ //if during R stage there's a trigger, silence and exit OCR1A = 0; return; } OCR1A = envelopeValue; if (globalTicks >= releaseLength) { envelopeValue--; globalTicks = 0; } } } }

I made one up and am pretty pleased with the results!

2020-05-21 13.10.52

Kevin

 

Permalink Leave a Comment

ATtiny85 VGA Sync

May 3, 2020 at 8:52 pm (computers, maker, Uncategorized) (, )

I’ve been reading Ben Eater’s inspiring series on producing a VGA video output from breadboards containing basic logic gates implementing counters, so wanted to see for myself how to create the timing signals for a VGA output.  There are loads of tutorials for how to produce VGA using an Arduino, with the main source appearing to be from Nick Gammon.  So for the sake of variety, I decided to see how it might work on an ATtiny85 (although naturally, many have also been there before too).

I’ve only implemented the sync signals to see if a monitor would recognise my signal.  I might see if I can get some data out at some point at some relatively low-level resolution.

The key decision is all centred around what clock frequency the ATtiny can support and how that relates to VGA video timings.  After a bit of messing around with a calculator, I settled on the following:

VESA Signal 640×480 @ 75 Hz and used the 64 MHz PLL clock mode of the ATtiny85 using a /8 prescaler – so a 8MHz clock, with each “tick” being 0.125uS.

So taking the timing values from the above linked tinyvga site, and translating that to “ticks” gives:

Scanline Pixels Time (uS) ‘Ticks’ @ 8Mhz
Visible Area 640 20.31746031746 16.546
Front Porch 16 0.50793650793651 4.063
Sync Pulse 64 2.031746031746 16.254
Back Porch 120 3.8095238095238 30.478
Whole Line 840 26.666666666667 213.333

Naturally there will have to be some rounding – I won’t be able to get fractions of a “tick” out of the ATtiny85 Timers.

This all rounds up to using the ATtiny85 Timer1 in PWM mode providing an output on OC1A to provide the H_sync pulse, then manually counting lines in the overflow interrupt routine to manually drive the V_sync pulse.

I originally wanted to use OC1B which made the pin assignments on the ATtiny85 much simpler, but ended losing an afternoon attempting to work out why I was getting no signal on OC1B. Turns out there is a bug in the ATtiny85 Compare on B for Timer1 – the compare mode bits in GTCCR are ignored unless the same bits are set for Compare on A in TCCR1.  Having found reference to this on the Internet, this kind of sounds familiar so I’m wondering if I’ve fallen foul of this before! Anyway, OC1A it is.

Pin assignments are as follows:

  • H_sync = PB1 (pin 6) OC1A
  • V_sync = PB4 (pin 3)

And the Timer1 settings are as follows:

  // Run a timer interrupt off a 64 MHz clock
  PLLCSR |= (1<<PLLE);            // Enable 64 MHz PLL clock
  while (!(PLLCSR & (1<<PLOCK))); // Wait for the PLL lock
  PLLCSR |= (1<<PCKE);            // Enable PLL as source for Timer 1

  // Timer 1 for CTC mode; set on compare with OCR1A; overflow interrupt
  TCCR1 = (1<<CTC1) |                // Reset on match with OCR1C
          (1<<CS12) |                // Prescale /8
          (1<<PWM1A) |               // PWM mode
          (1<<COM1A1) | (1<<COM1A0); // Set OC1A on compare; clear on rest
  GTCCR = 0;
  OCR1A = 15;
  OCR1C = 218;

There was some experimentation required to adjust the timings from the theoretical 213 ticks – 218 seems to give the most accurate horizontal sync frequency for some reason.  I haven’t done the maths to see why.  I was assuming I’d use 213 with a possible “leap tick” required every now and again to account for the lost 0.333 ticks to keep things in check, but turns out this worked ok for me instead.

Note that VESA 640×480@75Hz requires negative pulses, hence using “Set on Compare” rather than “Clear on Compare”.  Some modes require positive pulses so this would have to change.

The rest is just counting lines for the vertical refresh (using the line timings from tinyvga: 480-1-3-16=500), which is all done using the Timer 1 overflow interrupt, which signals the end (or in my case, start) of each horizontal line.

The final code is shown below, and sure enough, hooking this up to a monitor does indeed bring up the required mode to be recognised.

Next up, I’ll have a think about sending some data over.  Although if I want RGB outputs, that basically uses up the rest of the ATtiny85 I/O pins. That is an experiment for another day.

Kevin

// Use a ATtiny85 to output a VGA signal using
// its 5 output pins
//
//    (RST) ---------- | 1   8 | --------- (Vcc)
//    V_sync -- PB3 -- | 2   7 | -- PB2 -- R (0-0.7v)
//(0-0.7v) G -- PB4 -- | 3   6 | -- PB1 -- H_sync
//    (GND) ---------- | 4   5 | -- PB0 -- B (0-0.7v)
//
// NOTE: Wanted to use OC1B but there seems to be a bug in many ATtiny85 that
//       means that OC1B doesn't work properly unless OC1A has the same mode.
//       Consequently, using OC1A on its own to serve the purpose required.
//
// Note: Use ATtiny85 PORTB numbers
#define H_SYNC 1  // OC1A
#define V_SYNC 3
#define VGA_R  2
#define VGA_G  4
#define VGA_B  0

// Basic operating principle:
//  Timer configured to automatically create the H_sync pulses by using Timer 1
//  compare values.  H_sync needs to drop negative for a short pulse at the end
//  of each line.
//
//    <----- H pixel display ------><HFP><H_sync><HBP>
//
//  Or alternatively:
//    <H_sync><HBP><----- H pixel display -----><HFP>
//
//  Use Timer 1 to signal frequency of H_sync based on OCR1A.
//  Use Timer 1 OCR1A to specify duration of H_sync.
//  So attach H_sync signal to OC1A output.
//  Configure OC1A output as Clear or Set on Match as required
//
//    V_sync pulses are created by counting horizontal lines.
//    V scanning will be in one of the following modes:
//         In Display Area - hence H-scan is drawing pixels
//         In V front porch or back porch areas
//         In V_sync area
//
// VGA Timing Modes are taken from: http://www.tinyvga.com/vga-timing
//
// Based on the option of a 64 Mhz timing signal, choosing VESA 640x480 @ 75Hz
// gives us the following timing parameters
//
//     Screen refresh rate = 75 Hz
//     Verticle refresh    = 37.5 kHz
//     Pixel frequency     = 31.5 MHz
//
//     Timings are as follows
//      H visible area        640     20.31746031746 uS
//      H front porch          16      0.50793650793651 uS
//      H sync pulse           64      2.031746031746 uS
//      H back porch          120      3.8095238095238 uS
//      Total H line          840     26.666666666667 uS
//
//      H pulse is negative.
//
//      V visible area        480     12.8 mS
//      V front porch           1     0.026666666666667 mS
//      V sync pulse            3     0.08 mS
//      V back porch           16     0.426666666666667 mS
//      Total V frame         500     13.333333333333 mS
//
//      V pulse is negative
//
//  Using 64MHz PLL Clock as source for Timer 1 and setting Prescale to /8 gives
//  a frequency of 8 MHz and a single "tick" of 0.125 uS.
//
//  Total horizontal line timing is thus 26.666666666667/0.125 = 213.333 "ticks"
//  use 212 for OCR1C to define the line duration (same as the Timer 1 period).
//
//  At this resolution, H_sync is 2.031746031746/0.125 = 16.254 "ticks"
//  so use 15 for OCR1B to define the H_sync pulse.
//
//  As the required pulses are negative, need to use Set on Compare Match.

int V_Sync_Line;

void setup() {
  // Configure Output I/O pins
  PORTB = 0;
  DDRB = (1<<H_SYNC) | (1<<V_SYNC);

  // Run a timer interrupt off a 64 MHz clock
  PLLCSR |= (1<<PLLE);            // Enable 64 MHz PLL clock
  while (!(PLLCSR & (1<<PLOCK))); // Wait for the PLL lock
  PLLCSR |= (1<<PCKE);            // Enable PLL as source for Timer 1

  // Timer 1 for CTC mode and set on compare with OCR1A and overflow interrupt
  TCCR1 = (1<<CTC1) |                // Reset on match with OCR1C
          (1<<CS12) |                // Prescale /8
          (1<<PWM1A) |               // PWM mode
          (1<<COM1A1) | (1<<COM1A0); // Set OC1A on compare; clear on rest
  GTCCR = 0;
  OCR1A = 15;
  OCR1C = 218;  // Experimentation shows this is the best value

  // Enable Overflow Interrupt
  TIMSK = (1<<TOIE1);

  V_Sync_Line = 0;
}

void loop() {
  // put your main code here, to run repeatedly:
}

// Timer 1 Match with OCR1A interrupt
ISR (TIMER1_OVF_vect) {
   // Triggered every H_Sync line
   //
   // It automatically outputs the H_Sync pulse, so need to complete the line:
   // <---- H line Pixel output ---->
   //
   // From timings,  is approx 5.8 uS i.e. approx 23 ticks
   // So read the timer register and ensure it is > 23
   //
   while (TCNT1 < 24) {};

   if (V_Sync_Line <= 479) {
      // Visible line output
      // todo next!
    } else if (V_Sync_Line == 480) {
      // V Front Porch
    } else if ((V_Sync_Line >= 481) && (V_Sync_Line <= 483)) {
      // V Sync Pulse
     PORTB &= ~(1<<V_SYNC);
   } else { // 484 onwards
      // V Back Porch
     PORTB |= (1<<V_SYNC);
   }

   V_Sync_Line++;
   if (V_Sync_Line >= 500) {
     V_Sync_Line = 0;
   }
}

Permalink Leave a Comment

Programming a MiniMO Synth

April 19, 2020 at 12:03 pm (computers, maker, music, Uncategorized) (, , )

I’ve been playing with a home-grown version of the MiniMO synth as the creator has very kindly put the designs out into the public domain.

But a key issue with programming the ATtiny85 devices used in the synth is incompatibility with the latest versions of the Arduino IDE and the SpenceKonde ATTiny85 core that is now easily installed within it.

Warning: The official advice is still to build using Arduino 1.5.7, so treat all this as unverified and experimental.

I did have a look at this in the past and the issue seems to be one of incompatible timers, that I’ve described before.  The MinoMO uses both timers of the ATTiny85, but by default the core assumes the use of Timer 0 overflow interrupt for the delay/millis function, but several of the programmes for the MiniMO also want to use the overflow interrupt.

Expanding on the solution described in my previous post  – if we are assuming an 8-bit timer then it will overflow at 255, so setting the compare-on-match to 255 should have the same effect, but generate the TIMER0_COMPA_vect interrupt instead (at least for the mode being used here).

However, there is one caveat to all this.  The MiniMO synth code (I’m looking at the DCO code right now) sets the following parameters for Timer 0:

  //Timer Interrupt Generation -timer 0
  TCCR0A = (1 << WGM01) | (1 << WGM00); // fast PWM
  TCCR0B = (1 << CS00);                // no prescale
  TIMSK = (1 << TOIE0);                // Enable Interrupt on overflow

As far as I can see the original use of Timer 0 in the ATTiny Core is Fast PWM but with a prescalar value of 64.  Changing it to no prescale value here means that the “tick” used for the delay and millis functions is now running 64 times faster than previously assumed.

I’m guessing the author had the same issue in the original code though (although presumably with the settings for Timer 1), as in almost all other cases he uses the library function _delay_ms() rather than the Arduino function delay() or millis() – there is one exception – a couple of functions called on power-up prior to changing the timer values, which use the Arduino delay() function.

So from what I can see for the few programmes I’ve used with the MiniMO so far, I believe this is probably the only thing that needs changing if programming your own from the latest Arduino IDE and SpenceKonde ATTiny85 Core.  At least, on manual review of the code so far, I’ve not spotted any potential issues with having delay() and millis() running too fast!  But this isn’t an extensive review, and I repeat, the official advice is still to use an older version!

I don’t have an original MiniMO to compare waveforms to see if all the timing appears correct or not, but so far, I’ve been able to calibrate the frequency of the DCO (required as my ATTiny85 had no pre-set values stored in EEPROM), change waveforms, and see all three frequency ranges.

The fuse settings I used (as detailed by the menus in the SpenceKonde Core for ATTiny85 and then set using “burn bootloader”) were:

  • 8 MHz Internal
  • B.O.D disabled
  • EEPROM not retained (removes calibration data on re-programming)
  • Timer 1 Clock = CPU
  • LTO = Enabled
  • millis = Enabled

Which translates over into the following fuse settings:

  • efuse: 0xFF
  • hfuse: 0xDF
  • lfuse: 0xE2

I’d really like to know if anyone can compare the waveforms and frequencies generated by an original MinoMO DCO with one programmed with the above to see if they are the same.  Either way, for me I have a functioning unit, programmed using a current version of the Arduino IDE and ATTiny85 support and look forward to trying some of the other programmes for it too.

Of course, a massive thanks to Jose of course for putting the designs out there for experimentation like this in the first place.

Kevin

 

Permalink Leave a Comment

ATtiny85 MIDI to CV

March 2, 2019 at 4:07 pm (maker, music) (, , , )

There are a number of projects out there that provide a MIDI to CV function utilising some flavour of the ATtiny family of microcontrollers.  But most of them go from USB MIDI to CV but I wanted a genuine 5-pin DIN MIDI to CV.  This was the result.

It has taken a basic MIDI in circuit from the Internet (Google will find a few of these kicking around) and pairs it with the ATtiny85 CV out section of Jan Ostman’s cheap USB MIDI 2 CV design.

Update: Jan Ostman’s site is no more.  I’ve left my playing here for reference.

The result is as follows (excuse the poor representation in Fritzing, it served its purpose). Note that the 6N138 only had 5 active pins in the Fritzing part, so the extra dodgy link shown below is a fudged link for pin 7 to GND via a 4.7k resistor.

MIDItoCV2_schem

I also have a version for the ATtiny2313, but the main changes are as you’d expect.  Basically I was having problems with the ATtiny85 missing MIDI messages and wondered if a hardware UART would be better.  Turned out it was just my dodgy code with no real error checking getting out of sync with the MIDI stream.  But it took trying it on a 2313 to highlight the real issue, so back to the ATtiny85 and now all is well.

Design wise, its fairly simple ATtiny85 wise with the pin usage as follows:

  • SoftwareSerial receive on D3 (PB3) which is physical pin 2.
  • Gate output on D2 (PB2) which is physical pin 7.
  • CV output using the PWM signal tied to OC1A triggered off timer 1, which is D1 (PB1) on physical pin 6.

The code uses the same trick that Jan Ostman used in his code – if the top compare value for PWM operation is 239 then there are 240 graduations for PWM.  To cover a MIDI range of C2 (note 36) to C7 (note 96) is 60, so the PWM compare value required for a linear CV voltage output is basically (note-36)*4.

In terms of timer control registers, this all translates over to (refer to the ATtiny85 data sheet):

  • Set PWM1A i.e. PWM based on OCR1A
  • Set COM1A1 i.e. Clear OC1A (PB1) output line
  • Set CS10 i.e. Prescaler = PCK/CK i.e. run at Clock speed
  • Clear PWM1B is not enabled (GTCCR = 0)

The value for the PWM cycle is set in OCR1C to 239, and the compare value is set in OCR1A between 0 and 239, thus representing a range of 0 to 5v giving 1v per octave, assuming a 5v power supply.

When porting to the ATtiny2313, a similar scheme was used, but timer 1 is a 16 bit timer, and the control registers were slightly different, but I still used the 0-239 range.

Reading around the different modes, I ended opting for the use of Fast PWM with the compare value in OCR1A and the maximum PWM cycle value (239) in ICR1.  The timer register settings were thus as follows:

Timer 1 Control Register A (TCCR1A):

  • 7 COM1A1 = 1 COM1A1(1); COM1A0(0) = Clear OC1A on compare match; set at TOP
  • 6 COM1A0 = 0
  • 5 COM1B1 = 0
  • 4 COM1B0 = 0
  • 3 Resv = 0
  • 2 Resv = 0
  • 1 WGM11 = 1 WGM11(1); WGM10(0) = PWM from OCR1A based on TOP=ICR1
  • 0 WGM10 = 0

Timer 1 Control register B (TCCR1B):

  • 7 ICNC1 = 0
  • 6 ICES1 = 0
  • 5 Resv = 0
  • 4 WGM13 = 1 WGM13(1); WGM12(1) = PWM from OCR1A based on TOP=ICR1
  • 3 WGM12 = 1
  • 2 CS12 = 0 CS12(0); CS11(0); CS10(1) = Prescaler = PCK/CK i.e. run at Clock speed
  • 1 CS11 = 0
  • 0 CS10 = 1

Timer 1 Control Register C left all zeros.

I don’t know if it was the version of the ATtinyCore I was using, but the bit and register definitions for Timer1 for the ATtiny2313 didn’t seem to match the datasheet, so I just used the bit codes directly.

In terms of ATtiny2313 pin definitions, the following were used:

  • Hardware serial receive on D0 (PD0) which is physical pin 2.
  • Gate output on D11 (PB2) which is physical pin 14.
  • CV output using the PWM signal tied to OC1A triggered off timer 1, which is D12 (PB3) on physical pin 15.

A quick note on the MIDI serial handling.  My first code was very lazy and basically said:

Loop:
  IF (serial data received) THEN
    read MIDI command value
    IF (MIDI note on received) THEN
      read MIDI note value
      read MIDI velocity value
      set CV out value based on MIDI note value
      set Gate signal HIGH
    ELSE IF (MIDI note off received) THEN
      read MIDI note value
      read MIDI velocity value
      set CV out value based on MIDI note value
      set Gate signal LOW
    ELSE
      ignore and go round again waiting for serial data
    ENDIF
  ENDIF
END Loop

This generated a very quirky set of issues.  Basically when there was serial data available and a MIDI note on or off command detected, the read of the note and velocity data was returning and error (-1) which I never bothered checking.  Basically the code was running too fast and the next MIDI byte hadn’t registered yet.  So when (-1) was passed on as the MIDI note, it was resulting in a note on code thinking the MIDI note was 255, which was rounded up to the highest note (96).

The result was that I could see the gate pulsing in response to MIDI note on and off messages, but the CV voltage went high as soon as the first MIDI message was received.

The next version used test that said

IF (at least three bytes of serial data received) THEN

which means that if things get out of sync, eventually bytes are skipped until there are three bytes that equate to a note on/off message.  Crude, but it worked enough to show the principle.

The final code includes proper handling of the “Running Status” of MIDI, as described here: http://midi.teragonaudio.com/tech/midispec/run.htm

I used the 8MHz internal clock for the ATtiny85.

To test all of it together, I used my ATtiny85 MIDI Tester.

I might add some kind of selection for the MIDI channel.  Right now its hard-coded in a #define.  One option might be using an analogue input and a multi-position switch with a resistor network.  Or maybe a “tap to increase the channel” digital input switch.  Or if I use the 2313 version, I could use more pins and use a BCD or hex rotary switch or DIP switches.

2019-03-02 16.00.27

Here is the full code for the ATtiny85 version, which can be loaded up from the Arduino environment using the ATtiny85 core by Spence Konde. 

// MIDI to CV using ATTiny85
// NB: Use Sparkfun USB ATTiny85 Programmer
//     Set Arduino env to USBTinyISP
//     Set to 8MHz Internal Clock (required for MIDI baud)
#include <SoftwareSerial.h>

#define MIDIRX 3  // 3=PB3/D3 in Arduino terms = Pin 2 for ATTiny85
#define MIDITX 4  // 4=PB4/D4 in Arduino terms = Pin 3 for ATTiny85
#define MIDICH 2
#define MIDILONOTE 36
#define MIDIHINOTE 96

// Output:
//  PB2 (Ardiuno) = Pin 7 = Gate Output
//  PB1 (Arduino) = Pin 6 = Pitch CV Output
//
// PB5 set as digital output
// PB1 used as PWM output for Timer 1 compare OC1A
#define GATE    2  // PB2 (Pin 7) Gate
#define PITCHCV 1  // PB1 (Pin 6) Pitch CV

SoftwareSerial midiSerial(MIDIRX, MIDITX);

void setup() {
  // put your setup code here, to run once:
  midiSerial.begin (31250); // MIDI Baud rate

  pinMode (GATE, OUTPUT);
  pinMode (PITCHCV, OUTPUT);

  // Use Timer 1 for PWM output based on Compare Register A
  // However, set max compare value to 239 in Compare Register C
  // This means that output continually swings between 0 and 239
  // MIDI note ranges accepted are as follows:
  //    Lowest note = 36 (C2)
  //    Highest note = 96 (C7)
  // So there are 60 notes that can be received, thus making each
  // PWM compare value 240/60 i.e. steps of 4.
  //
  // So, for each note received, PWM Compare value = (note-36)*4.
  //
  // Timer 1 Control Register:
  //   PWM1A = PWM based on OCR1A
  //   COM1A1 = Clear OC1A (PB1) output line
  //   CS10 = Prescaler = PCK/CK i.e. run at Clock speed
  //   PWM1B is not enabled (GTCCR = 0)
  //
  TCCR1 = _BV(PWM1A)|_BV(COM1A1)|_BV(CS10);
  GTCCR = 0;
  OCR1C = 239;
  OCR1A = 0; // Initial Pitch CV = 0 (equivalent to note C2)
  digitalWrite(GATE,LOW); // Initial Gate = low
}

void setTimerPWM (uint16_t value) {
  OCR1A = value;
}

void loop() {
  if (midiSerial.available()) {
    // pass any data off to the MIDI handler a byte at a time
    doMIDI (midiSerial.read());
  }
}

uint8_t MIDIRunningStatus=0;
uint8_t MIDINote=0;
uint8_t MIDILevel=0;
void doMIDI (uint8_t midibyte) {
  // MIDI supports the idea of Running Status.
  // If the command is the same as the previous one, 
  // then the status (command) byte doesn't need to be sent again.
  //
  // The basis for handling this can be found here:
  //  http://midi.teragonaudio.com/tech/midispec/run.htm
  //
  // copied below:
  //   Buffer is cleared (ie, set to 0) at power up.
  //   Buffer stores the status when a Voice Category Status (ie, 0x80 to 0xEF) is received.
  //   Buffer is cleared when a System Common Category Status (ie, 0xF0 to 0xF7) is received.
  //   Nothing is done to the buffer when a RealTime Category message is received.
  //   Any data bytes are ignored when the buffer is 0.
  //

  if ((midibyte >= 0x80) && (midibyte <= 0xEF)) {
    //
    // MIDI Voice category message
    //
    // Start handling the RunningStatus
    if ((midibyte & 0x0F) == (MIDICH-1)) {
      // Store, but remove channel information now we know its for us
      MIDIRunningStatus = midibyte & 0xF0;
      MIDINote = 0;
      MIDILevel = 0;
    } else {
      // Not on our channel, so ignore
    }
  }
  else if ((midibyte >= 0xF0) && (midibyte <= 0xF7)) {
    //
    // MIDI System Common Category message
    //
    // Reset RunningStatus
    MIDIRunningStatus = 0;
  }
  else if ((midibyte >= 0xF8) && (midibyte <= 0xFF)) {
    //
    // System real-time message
    //
    // Ignore these and no effect on the RunningStatus
  } else {
    //
    // MIDI Data
    //
    if (MIDIRunningStatus == 0) {
      // No record of state, so not something we can
      // process right now, so ignore until we've picked
      // up a command to process
      return;
    }
    // Note: Channel handling has already been performed
    //       (and removed) above, so only need consider
    //       ourselves with the basic commands here.
    if (MIDIRunningStatus == 0x80) {
      // First find the note
      if (MIDINote == 0) {
        MIDINote = midibyte;
      } else {
        // If we already have a note, assume its the level
        MIDILevel = midibyte;

        // Now we have a note/velocity pair, act on it
        midiNoteOff (MIDINote, MIDILevel);
        MIDINote = 0;
        MIDILevel = 0;
      }
    } else if (MIDIRunningStatus == 0x90) {
      if (MIDINote == 0) {
        MIDINote = midibyte;
      } else {
        // If we already have a note, assume its the level
        MIDILevel = midibyte;
        
        // Now we have a note/velocity pair, act on it
        if (MIDILevel == 0) {
          midiNoteOff (MIDINote, MIDILevel);
        } else {
          midiNoteOn (MIDINote, MIDILevel);
        }
        MIDINote = 0;
        MIDILevel = 0;
      }
    } else {
      // MIDI command we don't process
    }
  }
}

void midiNoteOn (byte midi_note, byte midi_level) {
  // check note in the correct range of 36 (C2) to 90 (C7)
  if (midi_note < MIDILONOTE) midi_note = MIDILONOTE;
  if (midi_note > MIDIHINOTE) midi_note = MIDIHINOTE;

  // Scale to range 0 to 239, with 1 note = 4 steps
  midi_note = midi_note - MIDILONOTE;

  // Set the voltage of the Pitch CV and Enable the Gate
  digitalWrite (GATE, HIGH);
  setTimerPWM(midi_note*4);
}

void midiNoteOff (byte midi_note, byte midi_level) {
  // check note in the correct range of 36 (C2) to 90 (C7)
  if (midi_note < MIDILONOTE) midi_note = MIDILONOTE;
  if (midi_note > MIDIHINOTE) midi_note = MIDIHINOTE;

  // Scale to range 0 to 239, with 1 note = 4 steps
  midi_note = midi_note - MIDILONOTE;

  // Set the voltage of the Pitch CV and Enable the Gate
  digitalWrite (GATE, LOW);
  setTimerPWM(midi_note*4);
}

 

Permalink 10 Comments

ATtiny85 MIDI Tester

January 25, 2019 at 10:28 pm (maker, music) (, , )

Having spent some time messing about with building simple synthesizer circuits, I’m putting together a simple MIDI to CV converter.  I have one using an ATtiny85 but think I’m struggling from the fact it is only using SoftwareSerial, so I plan to have another go with an ATtiny231w pretty soon now.

One thing I was missing though was a simple “hands free” MIDI tester.  Now it would be fairly simple to hook up my laptop or a keyboard to a MIDI cable and use that, but I wanted something I could just plug in and leave sending MIDI data out to whatever I was building.  So the idea of using a simple USB-powered ATTiny85 to creating a continuous set of MIDI note on and not off messages was born.

I’m using one of those cheap Digispark USB clones you can buy. I had no luck ever getting the USB programming side of it to work, (its supposed to be able to have the nucleus boot loader installed to provide a software USB implementation), but its easy to programme if you have an 8-pin DIL test clip, in my cased hooked up to a sparkfun tiny programmer.

2019-01-25 21.22.26

Basic design notes for the board:

  • P1 (equivalent to Arduino D1 and the ATtiny85 pin 6) has the built-in LED.
  • I’m using P2 as MIDI TX and P3 as (unused) MIDI RX (D2 and D3, mapped to ATtiny85 pins 7 and 2).
  • P0 (ATtiny85 pin 5) as a digital input with internal pull-up resistors enabled.

I’m using a simple MIDI out circuit from the Internet that shows:

  • DIN pin 5 – MIDI OUT signal directly connected to P2.
  • DIN pin 2 – MIDI ground.
  • DIN pin 4 – MIDI +5v via a 220R resistor.

The resistor was soldered inside an in-line female MIDI DIN socket.

2019-01-25 21.36.522019-01-25 21.42.10

A switch was soldered across from P0 to GND on the Digispark board.  The code will flash the LED when the switch is registered so you know you’ve done something.

That is pretty much it.

2019-01-25 21.51.47

2019-01-25 21.51.56

In terms of code, I just tested it with an increasing scale of a few octaves, with the switch being used to increase the tempo (by reducing the delay between notes). Of course, you can use whatever test pattern works for you.

My initial (simple) code below.

Important: You must “set the fuses” to use the internal 16MHz clock in order to get the MIDI baud rates for the SoftwareSerial implementation.

Kevin

// MIDI Code Test Generator using ATtiny85
// NB: Use Sparkfun USB ATtiny85 Programmer
//     Set Arduino env to USBTinyISP
//     Set 16MHz Internal Clock (required for MIDI baud)
#include <SoftwareSerial.h>

// Pin Mapping for DigiSpark USB/ATtiny85
//  P0 = PB0/D0 = Pin 5 Attiny85
//  P1 = PB1/D1 = Pin 6 - built-in LED
//  P2 = PB2/D2 = Pin 7
//  P3 = PB3/D3 = Pin 2 - wired to USB+
//  P4 = PB4/D4 = Pin 3 - wired to USB-
//  P5 = PB5/D5 = Pin 1 - wired to RESET
//
// Use the Arduino D numbers below (which are the same as Digispark P numbers)
#define MIDITX   2
#define MIDIRX   3
#define BUTTON   0
#define BLTINLED 1

// MIDI Parameters for testing
#define MIDI_CHANNEL     1
#define MIDI_LOWNOTE     36
#define MIDI_HIGHNOTE    90
#define MIDI_VELOCITY    64
#define MIDI_DELAYMAX    550
#define MIDI_DELAYMIN    50
#define MIDI_DELAYSTEP   100

#define MIDI_NOTEON      0x90
#define MIDI_NOTEOFF     0x80

SoftwareSerial midiSerial(MIDIRX, MIDITX);

int delayRate;
int buttonState;
int lastButtonState;
byte midiNote;

void setup() {
  // Switch will trigger HIGH->LOW
  pinMode (BUTTON, INPUT_PULLUP);
  pinMode (BLTINLED, OUTPUT);
  digitalWrite (BLTINLED, LOW);
  buttonState = HIGH;
  lastButtonState = HIGH;
  
  midiSerial.begin (31250); // MIDI Baud rate

  delayRate = MIDI_DELAYMAX;
  midiNote  = MIDI_LOWNOTE;
}

void loop() {
  buttonState = digitalRead (BUTTON);
  if ((lastButtonState == HIGH) && (buttonState == LOW)) {
    ledOn();
    delayRate = delayRate - MIDI_DELAYSTEP;
    if (delayRate < MIDI_DELAYMIN) delayRate = MIDI_DELAYMAX;
  }
  lastButtonState = buttonState;

  midiNoteOn (MIDI_CHANNEL, midiNote, MIDI_VELOCITY);
  delay (400); // Need note on long enough to sound
  midiNoteOff (MIDI_CHANNEL, midiNote);
  delay (delayRate);

  midiNote++;
  if (midiNote > MIDI_HIGHNOTE) midiNote = MIDI_LOWNOTE;
  
  ledOff();
}

void midiNoteOn (byte midi_channel, byte midi_note, byte midi_vel) {
  midiSerial.write (midi_channel+MIDI_NOTEON);
  midiSerial.write (midi_note);
  midiSerial.write (midi_vel);
}

void midiNoteOff (byte midi_channel, byte midi_note) {
  midiSerial.write (midi_channel+MIDI_NOTEOFF);
  midiSerial.write (midi_note);
  midiSerial.write ((byte)0);
}

void ledOn () {
  digitalWrite (BLTINLED, HIGH);
}

void ledOff () {
  digitalWrite (BLTINLED, LOW);  
}

Permalink 1 Comment

Arduino, ATtiny85, and Timers

April 29, 2018 at 2:57 pm (maker) (, )

I’m still playing around with using the ATtiny85 as the basis for a synthesizer, using a range of code and circuits from the Internet, but had an irritating problem today with some open code.  The author describes complete build instructions for their code and circuit for v1.5.7 of the Arduino environment and the arduino-tiny library, which as far as I can see was last updated in 2013 from its google code repository.

But I have v1.8.5 and the now easy to use, built-in support for the ATtiny85 from David Mellis (https://github.com/damellis/attiny).

So when it comes to build it on my system I get the following error:

wiring.c.o (symbol from plugin): In function `delayMicroseconds':

(.text+0x0): multiple definition of `__vector_5'

Which is obviously a linker error, but it has proven quite difficult to find a detailed hint as to what the actual problem is.  It says that it has redefined in my .ino file, but wasn’t immediately obvious this related to interrupt routines (although the word vector should have been a clue I guess).

Well it turns out that this is a problem you get if you attempt to defined an interrupt service routine twice.  The code in question had defined

ISR(TIMER0_OVF_vect)

And in any modern Arduino core timer 0 is used for the millisecond timer, as defined in hardware/arduino/avr/cores/arduino/wiring.c.  This is indeed vector 5 (see http://ee-classes.usc.edu/ee459/library/documents/avr_intr_vectors/).  This is defined in wiring.c, but not directly related to the delayMicroseconds function (oh the joys of tracing C linker errors).

I’m skipping over the part where I was attempting to work out where the actual source code being built was stored – not in <program files>\Arduino; not in my own Arduino source area; no it was finding mention of <user>\AppData\Local\Arduino15 that found any ATtiny support in my installation at all.

There seems quite a lot of confusion online (at least from the searching I was doing) about timers on the ATtiny85 and the Arduino environment.  It looks like an earlier version of the Arduino core (although not obviously in the v1.5.7 suggested by the author of the code I was using, so that is still a puzzle) had a method for changing the timer usage for different microcontrollers.

The arduino-tiny library uses a core_build_options.h options file and uses it to set the following:

#if defined( __AVR_ATtiny25__ ) || defined( __AVR_ATtiny45__ ) || defined( __AVR_ATtiny85__ )
#define __AVR_ATtinyX5__
#endif

#if defined( __AVR_ATtinyX5__ )

/*
 For various reasons, Timer 1 is a better choice for the millis timer on the
 '85 processor.
*/
#define TIMER_TO_USE_FOR_MILLIS 1

However, this isn’t in the latest builds of the core or in the version of the ATtiny core support I’m using, which doesn’t seem to have the option to change which timer is used for the delay functions.  In fact, I couldn’t quite pin down when the use of this header file disappeared from the Arduino builds, but to be honest I didn’t spend ages looking.

The code I’m trying to use needs the use of both timers, and as there are only two timers on the attiny85, hence the clash.  I’m not totally clear why it builds when the delay timer is swapped to use timer1 (as in the older arduino-tiny core), but I guess the code isn’t hanging an ISR of the other timer.

Interestingly in other code by the same author, does build ok, but in that case, even though it is installing an ISR for timer0, it is using a different vector, so it doesn’t seem to clash:

ISR(TIMER0_COMPA_vect)

So whilst you can completely mess up the functioning of the timers in your own sketch, by changing the control registers and so on directly, I haven’t found a way to clear and attach a new ISR to the interrupt.

I need to dig around a bit more now into the internals of the ATtiny timers to see how the code might be modified to support timer 1 instead of timer 0, but that will have to be a job for another day now.

Some possible options might include forgoing the use of ‘setup’ and ‘loop’ in the sketch; integrating it better alongside its use with delay; attempting to port over from triggering on overflow to triggering on compare; or I may end up trying to reverse the timer usage in the sketch (but I read somewhere that the two ATtiny85 timers are quite different, so that might not be possible).   I may end up with a special ‘hacked’ core board definition to re-specify the use of timer1 rather than 0 as per the older library, but that would be a bit of a mess…

Anyway, the upshot of all this is that this took quite a lot of untangling and googling and following dead-ends, so if you know of a definitive resource or document that details the history of ATtiny85 support within Arduino and when this changed, I’d like to hear about it!

Update: In my case it turned out that I was pretty much able to replace the use of the Timer 0 overflow interrupt with the Timer 0 compare A interrupt and get largely the same thing.  I don’t know if the two use of the different timers needs to be swapped though, but as far as I can tell right now, changing interrupt does remove the clash.

So in my case, I did this:

#ifdef ORIGINAL_CODE_
 TIMSK = (1 << TOIE0); // Interrupt on overflow
#else
 // Set compare value to same that would trigger overflow
 // of the 8-bit counter (pretty much)
 OCR0A = 0xff;
 TIMSK = (1 << OCIE0A); // Interrupt on compare with OCR0A
#endif

and then later on, when defining the ISR:

#ifdef ORIGINAL_CODE_
ISR(TIMER0_OVF_vect) {
#else
ISR(TIMER0_COMPA_vect) {
#endif

Of course there may still be other side effects, but I need to dig into the details to see.

Kevin

Permalink 2 Comments

ATtiny85 Synth

March 31, 2018 at 3:38 pm (maker, music) (, , , )

I’ve wanted to make a simple synth for a while and stumbled across the DSPSynth site from Jan Ostman, which provides a range of designs for Euro-module compatible synthesizer modules.  One thing that really caught my eye was the CZ1 chip which implements the Casio Phase Distortion method of sound synthesis (I used to have a Casio CZ synth).  The chip is based on an ATtiny85 and the code was available as open source, but the original site appears to be no more.

Unfortunately, being a bit of an ATtiny85 novice, it wasn’t totally clear to me quite how to put this together from the bits and pieces I had lying around.  So this is by way of documenting how I got this going based on the circuits and code from the original site.

Note I can’t help anyone with the original code or details of the build, sorry.

Building the Synth

Parts list for me:

I originally grabbed a couple of very cheap “ATtiny85 devboards” off ebay that include a micro-USB connection, but these turned out just to be a way of powering the boards, not programming them.  I tried using an Arduino as an in-circuit programmer, but in the end the Sparkfun programmer was so easy to use, I just use that now all the time.

dspsynth provides two circuits related to the CZ1 chip.  On the main HP3 paperface module page is a complete euro-module compatible circuit including power regulator and jacks for inputs and outpus.  In the “CZ1 manual” is a much simpler circuit that just shows a simple output stage as follows:

dsp-cz1

So I used this as my output side.  For the inputs, I took two 10k pots connected to the input pins via a 22k resistor each as shown in the full dsp paperface circuit (but without the jack connectors).

The whole thing was powered using the 5v and GND pins from one of those cheap USB “devboards” I mentioned, although I didn’t use that to host the ATtiny85 itself as they aren’t really breadboard friendly.  I did need a simple 8-pin socket adaptor to breadboard adaptor to seat the ATtiny85 nicely though.  Pics below.

2018-03-31 16.04.292018-03-31 16.05.112018-03-31 16.05.17

2018-03-31 16.03.58

Programming the ATTiny85

The source code was provided on the original site, but that seems now to be offline.  The official module used the TinyAudioBoot system which allows you to upload firmware over one of the audio inputs.  I didn’t bother with that for my tinkering, so I was just loading the pdvco.ino source directly using the Arduino IDE and the Sparkfun programmer.

One thing that had to be done was “set the fuses”.  As I say, as a ATTiny85 novice, this took a bit of googling.  But it turns out all I really needed was to set the internal clock for the device to 16MHz (I uploaded the code without this step and there were some very interesting audio effects coming out of the thing – as you’d expect the digital to analogue conversion was all off sync).

If you select the right parameters in the Arduino IDE (ATtiny85; Clock: Internal 16MHz) and then select “burn bootloader” this has the effect of setting the fuses for the clock speed.  At this point, when the code fired up it all seemed to work and sounded a lot better.

Next Steps

Now I’ve had a bit of a play and an see what the ATtiny85 can do, I plan to explore some more of his designs.  Of particular interest is seeing if I can create a MIDI in to CV module using the principles in his USB MIDI to CV interface. But I want real MIDI so will be experimenting with the serial ports on the ATTIny85 (and worrying about getting MIDI to 5v input levels).

Kevin

 

Permalink 1 Comment