Tiny Ensemble - an Attiny85 powered chorus effect

Started by Ksander, October 27, 2023, 04:33:25 PM

Previous topic - Next topic

ElectricDruid

The sample output rate and the PWM rate don't have to be the same. You can increase the PWM rate, but only update it occasionally. What will prove most limiting is that as you push the PWM rate higher, the resolution gets smaller. So with a given clock, a desired resolution will have a maximum speed.
Say we're using the 8MHz system clock for the PWM. 10-bit output is 1024 values, so 8MHz/1024 = 7812Hz maximum PWM output rate. Not very good.
I don't know the ATTiny chip very well, but on the PICs I was able to get this up to 31.25KHz by running with a 32MHz system clock. For 8-bit output, you can get much higher PWM frequencies (x4 higher, clearly) which helps a lot.

TBH, if there's a NCO peripheral on this chip, pulse density modulation (PDM) produces a much better output than PWM for "cheap DAC" applications like this.

Ksander

Quote from: niektb on October 29, 2023, 10:34:31 AMI'm not super well-versed with the Attiny register nor do I have the setup to test it so here is some pseudo-code. I think something like this could work.
...


Thanks. I get the idea now and will try in the coming days!

niektb

Quote from: ElectricDruid on October 29, 2023, 03:44:59 PMThe sample output rate and the PWM rate don't have to be the same. You can increase the PWM rate, but only update it occasionally. What will prove most limiting is that as you push the PWM rate higher, the resolution gets smaller. So with a given clock, a desired resolution will have a maximum speed.
Say we're using the 8MHz system clock for the PWM. 10-bit output is 1024 values, so 8MHz/1024 = 7812Hz maximum PWM output rate. Not very good.
I don't know the ATTiny chip very well, but on the PICs I was able to get this up to 31.25KHz by running with a 32MHz system clock. For 8-bit output, you can get much higher PWM frequencies (x4 higher, clearly) which helps a lot.

TBH, if there's a NCO peripheral on this chip, pulse density modulation (PDM) produces a much better output than PWM for "cheap DAC" applications like this.

I think what you're describing is in fact a form of interpolation, namely zero order hold interpolation. I think it's by definition that the pwm rate is equal to the output rate, even if you decide to output the same value for multiple pwm time periods. (But I need to read some more to understand what different interpolation methods do to signal quality.

ElectricDruid

Quote from: niektb on October 29, 2023, 06:27:12 PMI think what you're describing is in fact a form of interpolation, namely zero order hold interpolation. I think it's by definition that the pwm rate is equal to the output rate, even if you decide to output the same value for multiple pwm time periods. (But I need to read some more to understand what different interpolation methods do to signal quality.
Yes, outputting the same value for multiple PWM cycles would be zero order hold. A simple linear interp would be better if there's time to do it. But my point is really that we need to get the PWM frequency much higher if we're to have any chance of filtering it effectively.

Ksander

Also, there are some parts in the circuit I'd like to ask about:

In purple, there is a feedback path? Is this a concern?
In blue, the signal is split after the buffer using two resistors. In other schematics, I instead see a signal split before being split by being sent to two op-amps. Any considerations on this?



ElectricDruid

There's various things I'd do differently on the schematic.

The two-stage output filter, for example: R3/C4, R8/C6. This currently has 220K/100p, 10K/2n2 values. The usual rule-of-thumb for cascading passive filters like this is that the second stage should have a resistor ten times larger than the first to avoid loading it down too much. So these stages would be better the other way around.
R2/10K is there to make a divider to reduce the output level (because it was boosted in the firmware) if I understood correctly. It also acts to make that first filter into a lowpass shelf rather than a simple lowpass, so it's reducing it's effectiveness. It should go.
Using a larger resistor in the R8 position wouuld reduce the amount of delyed signal in the mix, so it's unnecessary. And a larger R8 is what you'll have if you swap the two filter stages over.
The signal splitting is ok. You've got a low impedance output from the input buffer, which is then taken to the R9/C10 input filter and via resistor R1 to the output. Not much to argue with there.
The signal mixing is a simple passive mix. Will there be some feedback around the loop you show in magenta? Possibly, yes. If it's a problem, the output op-amp could be turned into a proper inverting mixer, at the minor cost of an inverting-overall signal path. For bonus points, it could be turned into a wet/dry blender (example here: https://electricdruid.net/two-stomplfo-projects-ptwobble/ )

niektb

#26
I'm also a little concerned on DC-biasing. You have C2 to remove the DC-bias but then it seems you try to use R7 to bias the voltage up again but this won't work (properly) because you have R2 and R8 pulling the voltage down?
@ElectricDruid: don't you need R2 to create a HPF in conjunction with C3 though? (although I don't think this HPF works very effective with the 220k in series).


Edit: In fact, I thought I'd simulate it but because the output signal is still more or less centered around zero it gets clipped away by the TL072 (which is not rail-to-rail). Are you sure the schematic is reflecting the status of your prototype?


Ksander

#27
Thanks, both of you. I'll check the schematic against the prototype. It seems there should be at least a capacitor between the signals going into the second op-amp and its bias source...

EDIT: and there was: a 100nF electrolytic. I couldn't edit the opening post anymore, so here is the revised schematic (as it is now)



And a photo of the (working) prototype

Ksander

Quote from: niektb on October 28, 2023, 05:51:28 PM...
You could for example sum 2 adjacent adc samples together (the sum of 2 10-bit numbers fit inside 16-bit) and store that single number in the buffer
...

I have also tried to implement the proposed 'oversampling', but by adding 8 bit samples to a 16 bit variable (to keep things simple). In the original code, sampling happens at 8MHz/32, and with each ADC conversion taking 14 clock cycles, this works out to 17.9kHz. In the first attempt at oversampling, sampling was done at 8MHz/16; 2 samples were summed and, after two samples were collected, divided by 2 and cast to an 8-bit variable for the PWM. This worked, but was more noisy than before. 8MHz/8 with 4 samples also worked, but is also more noisy. With higher speeds, the Attiny didn't produce output anymore.

niektb

#29
oversampling is a way to increase the bit depth so there is not really a point in it if you throw the added bits away at the output :)
On the other hand though I wouldn't expect it worsen, I'm not really sure what's going on? One thing I could think of, is that you don't have any bypass capacitors at the AREF and VCC of the ATTiny?

Maybe we should take a step back first and reduce noise at the hardware level first. Put also a decoupling capacitor at the Opamp VCC and increasing the size of the decoupling at the LDO (I would make the output 10uF, input 100nF is probably fine with a battery) The output stage with the big volume reduction isn't quite helping either (as ElectricDruid pointed out previously)

Do you have any way to quantify the amount of noise you have? (lets say a sine wave generator and an audio interface + spectrum analyzer program on your computer)

I have a Atmega32U4 board here where I wanted to test it on but alas I discovered I need to pay attention to which timer the pin is connected to when designing the board :icon_mrgreen:

Ksander

#30
Quote from: niektb on November 01, 2023, 04:53:00 AMoversampling is a way to increase the bit depth so there is not really a point in it if you throw the added bits away at the output :)
On the other hand though I wouldn't expect it worsen, I'm not really sure what's going on? One thing I could think of, is that you don't have any bypass capacitors at the AREF and VCC of the ATTiny?

Maybe we should take a step back first and reduce noise at the hardware level first. Put also a decoupling capacitor at the Opamp VCC and increasing the size of the decoupling at the LDO (I would make the output 10uF, input 100nF is probably fine with a battery) The output stage with the big volume reduction isn't quite helping either (as ElectricDruid pointed out previously)

Do you have any way to quantify the amount of noise you have? (lets say a sine wave generator and an audio interface + spectrum analyzer program on your computer)

I have a Atmega32U4 board here where I wanted to test it on but alas I discovered I need to pay attention to which timer the pin is connected to when designing the board :icon_mrgreen:

Sure! The LDO is a 78L05. The datasheet specifies a 330nF capacitor between input and GND, and a minimum 10nF capacitor between output and GND (I put 100nF there). I take it you suggest to increase the 330nF to 10uF?

I have also tried placing 100nF capacitors directly between Vcc and GND pins of both IC's. This didn't make any audible difference. Note - there is not that much noise, but I do think there is some digital/squealing present. I'm not sure the AREF pin is used, but will consult the datasheet.

EDIT: You can choose to use a bypass capacitor by setting REFS0 = 7 instead of 6 (both values set an internal 2.56v reference voltage). The datasheet doesn't specify a value for the bypass capacitor, but I tried 100nF. This indeed reduces the noise, at least together with 100nF bypass caps across Vcc and GND of both ICs. In fact, I think the only remaining noise is due to clipping. I need to so some more testing, and:

I will also try do collect some data on the noise!

niektb

Aah glad to header that the decoupling caps helped a big deal! Noo I would think the capacitor on the input of the LDO is fine (I would make it bigger if you used a DC adapter but a battery is already considered low-noise). I would increase the LDO  output capacitance to at least 10uF (but bigger doesn't hurt)

Ksander

Taking the suggestions together: I have swapped the stages of the filter and got rid of the first, added 100nF capacitors from Vcc to GND for both IC's (the TL072 doesn't have Vcc/GND pins in Fritzing, so those are not shown in the schematic) and added a 100nF decoupling capacitor for AREF. The voltage divider also had to be changed and the value of the filter cap now is 220pF for a 3.5kHz LP filter. I am quite happy with the result.

This is the schematic now:



And this is the code:


/* Attiny85 Chorus by Ksander N. de Winkel

   8-Bit Variable delay for audio (guitar/microphone) signals.
   Combining the (scaled down) output with the original input signal yields a chorus effect.
   The maximum delay is 14.3ms. The amount can be scaled by changing the value of Shift.
   The period of the variation can be changed by setting the wrapping value of Counter.
   Note that these variables affect each other.
 
   Based on Attiny85 pitch shifter by David Johnson-Davies - www.technoblogy.com - 11th February 2017
   ATtiny85 @ 8MHz (internal oscillator; BOD disabled)
   
   CC BY 4.0
   Licensed under a Creative Commons Attribution 4.0 International license:
   http://creativecommons.org/licenses/by/4.0/

*/

volatile uint8_t Buffer[256];               // Circular buffer
volatile uint8_t WritePtr, LastPtr, New, Ptr, Counter, Shift;
volatile bool Direction = true;
// Everything done by interrupts

// Write to buffer when ADC conversion is complete
ISR (ADC_vect) {

  Buffer[LastPtr] = New;
  New = ADCH + 128;
  Buffer[WritePtr] = (Buffer[WritePtr] + New)>>1;
  LastPtr = WritePtr;
  WritePtr = (WritePtr + 1) & 0xFF;

}

// Read from buffer and output to DAC
ISR (TIMER0_COMPA_vect) {

  Counter = (Counter + 1) & 0xFF;

  if (Counter == 0) {
   
    if (Direction) {
      Shift = (Shift + 1) & 0xFF;
    } else {
      Shift = (Shift - 1) & 0xFF;
    }

    if (Shift == 255 | Shift == 0) Direction = !Direction;
   
  }
 
  Ptr = (LastPtr - Shift) & 0xFF;

  OCR1A = Buffer[Ptr];

}

// Setup

void setup () {
 
  // Enable 64 MHz PLL and use as source for Timer1
  PLLCSR = 1<<PCKE | 1<<PLLE;     

  // Set up Timer/Counter1 for PWM output
  TIMSK = 0;                              // Timer interrupts OFF
  TCCR1 = 1<<PWM1A | 2<<COM1A0 | 1<<CS10; // PWM OCR1A, clear on match, 1:1 prescale
  OCR1A = 128;
  pinMode(1, OUTPUT);                     // Enable OC1A PWM output pin

  // Set up Timer/Counter0 to generate 20kHz interrupt
  TCCR0A = 2<<WGM00;                      // CTC mode
  TCCR0B = 2<<CS00;                       // /8 prescaler
  OCR0A = 55;                             // 17.9kHz interrupt (1M/56)
  TIMSK = TIMSK | 1<<OCIE0A;              // Enable interrupt
 
  // Set up ADC - REFS0 = 6 w/o ext. bypass cap; REFS0 = 7 w ext. bypass capacitor.
  ADMUX = 7<<REFS0 | 1<<ADLAR | 7<<MUX0;  // Internal 2.56V ref, ADC2 vs ADC3, (6<<MUX = gain 1; 7<<MUX0 = gain 20);
  // Enable, auto trigger, interrupt, 250kHz ADC clock:
  ADCSRA = 1<<ADEN | 1<<ADSC | 1<<ADATE | 1<<ADIE | 5<<ADPS0; 
  ADCSRB = 1<<7 | 0<<ADTS0;               // Bipolar, free-running

  pinMode(2, INPUT_PULLUP);

}

void loop () {
}

I did not continue with the oversampling, as my efforts seemed to worsen noise. There is some clipping due to the gain of 20 in the Attiny. This can probably be resolved by instead using the first opamp to amplify the signal and set the gain to 1 in the code, but this will probably also require other changes to the schematic to tune the input/delated signal levels to each other.

ElectricDruid

Quote from: Ksander on November 02, 2023, 03:55:18 PMThere is some clipping due to the gain of 20 in the Attiny. This can probably be resolved by instead using the first opamp to amplify the signal and set the gain to 1 in the code, but this will probably also require other changes to the schematic to tune the input/delated signal levels to each other.
In order to get the best performance out of the limited resolution of the ADC, you need the input signal to push it as close to full-scale as you dare (but not over!). So the gain should definitely be in the op-amp, in my view. You can then see how much you need to reduce the signal coming out, but since the amount of delayed signal to mix back in is often a variable in a chorus pedal, I don't think that's such a big deal. Put a pot on it it and leave it to the user.


Ksander

#34
As per ElectricDruid's suggestion, I moved the gain to the op-amp, and changed the cascade of RC filters on the output. There are also two LEDs to clip the signal in case it reaches too high voltages.



In the code, this line has to be changed to set the gain to 1

  // Set up ADC - REFS0 = 6 w/o ext. bypass cap; REFS0 = 7 w ext. bypass capacitor.
  ADMUX = 7<<REFS0 | 1<<ADLAR | 6<<MUX0;  // Internal 2.56V ref, ADC2 vs ADC3, (6<<MUX = gain 1; 7<<MUX0 = gain 20);

and the lines with INPUT_PULLUP should be omitted.

With the pot, the effect can be varied between no notable effect at all, subtle chorus to fat flanging-like, which is nice. Despite the 3rd order filter, there is still some carrier frequency present, which I don't know how to get rid of...

niektb

Don't know man, the >100k output impedance for the wet signal feels a bit fishy... The schematic isn't very clear if I might add. The way it is drawn suggests that you have an amplifier before the signal goes into the Attiny so it would affect both dry and wet?
A third order filter with an fc of 1.5kHz should filter quite hard but I'm not sure if that still works if you have to gain it up at your amp because you lose a bunch of signal

ElectricDruid

Sorry, I agree. That schematic is confused.

What's the second op-amp actually doing?

Can we start with input buffer on the left, move to ATTiny delay in the centre, and then out via filter to output on the right?

Where does the second op-amp come into that scheme? Are the clipping diodes supposed to be before the delay or after? (before would make more sense to me)

It'd be clearer if you could separate some stuff out. Can you put the power supply and Vref off to one side, linked to "+9V", and "Vref" labels, for example, and then just use those labels in the rest of the diagram? Can Fritzing do that sort of stuff? If not, it's limiting you, since it's making it harder for you to see what's going on.

Ksander

Quote from: ElectricDruid on November 08, 2023, 06:35:05 PMSorry, I agree. That schematic is confused.

...

It'd be clearer if you could separate some stuff out. Can you put the power supply and Vref off to one side, linked to "+9V", and "Vref" labels, for example, and then just use those labels in the rest of the diagram? Can Fritzing do that sort of stuff? If not, it's limiting you, since it's making it harder for you to see what's going on.


The clipping stages indeed go before the delay, but I can see that the schematic is confusing. I'll try if Fritzing can work with labels. I've also downloaded LTSpice but find it very difficult to work with  :(

cordobes

#38
You have the initiative, which is the important thing.
I agree with Tom, from the beginning you have to be very clear about the scheme and the steps.
Check how the big companies make their schematics (at least in effects pedals), there is plenty of material.
Build functional blocks, at each stage, don't try to implement any improvements until you have something solid, and work from there.
I would recommend that you start with just filters on the input and output, and a simple SW that reads the ADC and updates PWM, at the rate you decide.
As for building the HW, there's also tons of information on ground wiring, separating analog and digital signals, etc.
Get organized, you can get help, but you must have perfectly located where you are having problems.

And all our yesterdays have lighted fools the way to dusty death.
Out, out, brief candle! Life's but a walking shadow.

ElectricDruid

Quote from: Ksander on November 08, 2023, 11:20:30 PMI've also downloaded LTSpice but find it very difficult to work with  :(

Yeah, LTSpice wouldn't have been my recommendation for a schematic-drawing package either!

Fritzing is a diagramming tool really, good for drawing pictures of breadboards with parts on them, not so hot for a more abstract schematic. You want something that is actually designed for drawing schematics.