News:

SMF for DIYStompboxes.com!

Main Menu

ADC trouble on attiny85

Started by Ksander, March 16, 2024, 09:08:15 AM

Previous topic - Next topic

Ksander

Since building some other effects with the Attiny85, I wondered what else could be done. I thought it might be nice to use it as a guitar envelope follower/conditioner, and then to use that in combination with a VCA to put some dynamics, for instance, back into a fuzzed guitar signal. However, I have run into some trouble with the first step: getting a rectified ADC reading into an 8-bit variable.

I have conditioned the guitar input signal with an op-amp to get a 4.3v peak-to-peak signal (clipped, such that it won't exceed that). In the attiny, bipolar differential ADC is set, with one ADC pin grounded, and the other receiving the signal. There is a 1M resistor between the input pins. Monitoring with an oscilloscope, I see a perfectly centered (i.e., around 0v) signal, swinging +/-2.15v between the ADC pins. So far so good, I think.

The ADC reading should be a 10 bits, right aligned, with the MSB being a sign bit. This is stored in two bytes: ADCH should have the sign bit and MSB of the reading, and ADCL should have the other 7 data bits. I want to rectify this (ignore the sign bit) and take the 8 MSB of the 9 data bits. Then, as a starting point for the project, I expect the 'Sample' to be (near) 0 when there is no input -- no strumming, and near 255 when I'm shredding. However, this is not the case. As a way of debugging, I hooked up and an LED to the PWM. This pin is always putting out some signal. When I strum the guitar, it responds: some increase in intensity, then turning off shortly, and then going back to an 'on' state. It is also on when I ground both ADC inputs.

I have tried everything I could think of (e.g., abs(ADCH) for a left-aligned reading, different reference voltages, different IC even, ...) but without success. I hope one of you can spot my mistake?

Here is the code:

/* Envelope conditioner
 
   CC BY 4.0
   Licensed under a Creative Commons Attribution 4.0 International license:
   http://creativecommons.org/licenses/by/4.0/
*/

volatile uint8_t Sample;

// Everything done by interrupts

// ADC conversion complete - save sample in buffer
ISR (ADC_vect) {
 
  uint8_t low = ADCL;
  int16_t rawSample = (ADCH << 8) | low; // Combine high and low bytes to form a 16-bit signed integer
  uint8_t adjSample = (rawSample >> 1) & 0xFF; // discard LSB and sign bit, and adjust to 8-bit 0-255
  Sample = adjSample; // Store the adjusted sample in Sample
 
}

// Timer interrupt - read from buffer and output to DAC
ISR (TIMER0_COMPA_vect) {
 
  OCR1A = Sample;
 
}

// Setup
void setup () {

  // Account for all pins
  // physical pin 1 is RESET
  // physical pin 2 (PB3) is ADC 3
  // physical pin 3 (PB4) is ADC 2
  // physical pin 4 is GND
  // physical pin 5 has 100nF bypass capacitor for Vref
  // physical pin 6 (PB1) is PWM output
  pinMode(2, INPUT_PULLUP); // PB2, physical pin 7 (unused, pull-up)
  // physical pin 8 is Vcc
 
  // 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 = 0;
  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
  TIMSK = TIMSK | 1<<OCIE0A;              // Enable interrupt
 
  // Set up ADC
  ADMUX = 7<<REFS0 | 0<<ADLAR | 6<<MUX0;  // Internal 2.56v reference voltage w. external bypass cap | right align | ADC2 (+) vs ADC3 (-) w. unity gain
  ADCSRA = 1<<ADEN | 1<<ADSC | 1<<ADATE | 1<<ADIE | 5<<ADPS0; // ADC enable | start conversion | auto trigger | interrupt enable | /32 prescaler = 250kHz ADC clock
  ADCSRB = 1<<7 | 0<<ADTS0;               // Bipolar | free-running

}

void loop () {
}

ElectricDruid

If the ADC is reading signed data, you'd need to flip the bits of the reading for the case where the sign bit is set. Otherwise you'll get 255 when you're supposed to get zero. Which would explain what you're seeing, maybe?

Ksander

Quote from: ElectricDruid on March 16, 2024, 09:24:06 AMIf the ADC is reading signed data, you'd need to flip the bits of the reading for the case where the sign bit is set. Otherwise you'll get 255 when you're supposed to get zero. Which would explain what you're seeing, maybe?


Thanks! I didn't think of that, and tried it immediately. It does seem to help, however, only when I flip the bits when the reading is larger than 0. I thought I had maybe reversed the inputs to the ADC, but that doesn't matter. Also, when I power the circuit, there is at first some flickering, then the LED settles at a somewhat dim brightness (but clearly not near-zero), then, when I strum I see the brightness increasing (yay) but as soon as I mute the strings, the brightness goes up a lot for a bit and then settles at the dim value again. This takes about a second, and there is no corresponding signal DC swing visible on the oscilloscope. Any ideas what this means?

// ADC conversion complete - save sample in buffer
ISR (ADC_vect) {
 
  uint8_t low = ADCL;
  int16_t rawSample = (ADCH << 8) | low; // Combine high and low bytes to form a 16-bit signed integer
  uint8_t adjSample = (rawSample >> 1) & 0xFF; // Adjust the range to 0-255 and discard sign bit
 
  // Flip the bits if the sign bit is set (rawSample < 0) ??
  if (rawSample >= 0) {
    adjSample = ~adjSample;
  }
 
  Sample = adjSample; // Store the adjusted sample in Sample variable

}

ElectricDruid

Quote from: Ksander on March 16, 2024, 12:06:12 PM// ADC conversion complete - save sample in buffer
ISR (ADC_vect) {
 
  uint8_t low = ADCL;
  int16_t rawSample = (ADCH << 8) | low; // Combine high and low bytes to form a 16-bit signed integer
  uint8_t adjSample = (rawSample >> 1) & 0xFF; // Adjust the range to 0-255 and discard sign bit
 
  // Flip the bits if the sign bit is set (rawSample < 0) ??
  if (rawSample >= 0) {
    adjSample = ~adjSample;
  }
 
  Sample = adjSample; // Store the adjusted sample in Sample variable

}
I'm very suspicious of that test "if (rawSample >= 0) ". That *only* works if the data in ADCH and ADCL lines up correctly with int16 format when shifted. If the ADC provides a signed 10-bit result (for example) then it probably won't. I'd look at exactly where the sign bit is going to be and then test that bit specifically:

if (rawSample & 0x20 > 0) {

etc

Hope that makes sense.

Ksander

#4
Quote from: ElectricDruid on March 16, 2024, 01:46:39 PMI'm very suspicious of that test "if (rawSample >= 0) ". That *only* works if the data in ADCH and ADCL lines up correctly with int16 format when shifted.

That did the trick! Thank you very much! Now I can try an exponential filter...  ;D

edit:

  // Flip the bits if the sign bit is set (rawSample < 0)
  if (high & B00000010) {
    adjSample = ~adjSample;
  }