A project to inspect LFOs, with code

Started by bioroids, May 01, 2006, 08:03:52 PM

Previous topic - Next topic

bioroids

Hi, I've been working on this stuff for a few weeks.

It is basically an AVR based ADC converter, which in turn sends the captured data using the on-chip UART.
Sampling frequency is about 1.8KHz, with a 10-bit resolution.

The idea is to have a nice tool to be able to inspect the waveforms of different LFOs. This can be done with PC based scopes too, but most soundcards have trouble with stuff under 20Hz, and that's is were LFOs usually are... so enter the AVR.

This project is on a very initial level, though most of the coding at the AVR itself it's done.
I have to keep working on the hardware stuff (at the moment it runs directly on the STK500) and, most importantly, the computer code which interprets the data (I have a working version but I don't think it is worth posting it yet).


I'm using the old ATmega163 which is the only I have at hand that has on-chip ADC. I'm sure it can be ported easily to other chips.

Well here's the code. Commentaries and criticism are welcomed and needed :)

// LFO Meter v1.1
// 01/05/2006 Miguel Canel
// *** This is a VERY precarious version ***
// To be updated

#include <avr/io.h>
#include <avr/interrupt.h>

unsigned char dataHi, dataLo, temp;
unsigned char send = 0;
unsigned char error = 0;

ISR(SIG_UART_DATA) {
//UDR Empty Interrupt Vector
//Thanks to the send flag, this interrupt runs only when
//the low part of data has been sent to the shift register
//and the we disable it

  UDR = dataHi; //Sends the next byte (hi part)
  UCSRB ^= 0x01 << UDRIE; //Disables UDRE Interrupt (toggle)
  send--; //Resets the flag to allow the next transmision
}


ISR(SIG_OVERFLOW0) {
//Timer0 Overflow Vector

  ADCSR |= 0x01 << ADSC; //Starts a new ADC conversion
  TCNT0 = 0;  //Resets Timer0 counter
}

ISR(SIG_ADC) {
//ADC Conversion Complete Interrupt Vector
  if (send == 0) {
  //If there is no transmision in course
    send = 1; //Sets the flag of transmision in course
    dataLo = ADCL; //Reads the low part of the data
    temp = ADCH; //Reads the hi part of the data
    //Bit conversions to format the bytes to send
//Low byte always starts with 0xxxxxxx (7 data bits)
//Low byte always starts with 10000xxx (3 data bits)
//The hi part is right shifted and his LSB is made equal to the MSB of the low part
dataHi = dataLo >> 7;
dataHi |= temp << 1;
dataHi |= 0x80;
dataLo &= 0x7F;
UDR = dataLo; //Sends the low part thru the UART
    UCSRB ^= 0x01 << UDRIE; //Enables UDRE Interrupt (toggle)
  } else {
  //The previous transmision hasn't ended, so this data is lost
    error++; //Error count
    PORTB = !error; //Shows the error count on the leds
  }
}

void initialize_timer() {
  //Uses Timer0 (8 bits)
  //Sets the in CK/8 that is 461khz with a 3.69Mhz Clock
  //As the count goes to 256, the interrupts are generated at 1.8Khz
  //That is, every 0.55ms
  TCCR0 = 0x02; //0000 0010 Sets the prescaler
}

void initialize_adc() {
  //Selects the pin for ADC (ADC0)
  //Result is right adjusted
  //Voltage reference is AVCC (5v)
  ADMUX = 0x40; //0100 0000
  //Enables the ADC in Single-Conversion mode
  //Enables ADC Complete interrupt
  //Sets the prescaler at CK/32, that is 115Khz with a 3.69Mhz Clock
  //Conversion takes up to 25 ADC Clock cycles, so we have conversions at 4.6Khz in the worst case
  //That is, each 0.22ms
  ADCSR = 0x8D; //1000 1101
}

void initialize_UART() {
  UCSRA = 0x00; //00000000
  //Enables transmision
  UCSRB = 0x08; //00001000
  //Selects transmision speed with the Prescaler
  //UBR is 5, that is 38400 bauds at 3.69Mhz clock
  //That is, sending a byte takes 0.26ms counting start and stop bits
  //Keep in mind we have 0.44ms to send 2 bytes (the time it will take to finish a new conversion in the worst case)
  UBRR = 0x05;
  UBRRHI = 0x00;
}

void start_timer() {
  TIMSK = 0x01; //Enables Timer0 interrupt
  TCNT0 = 0;  //Resets Timer0 counter
}

int main (void)
{
  DDRB = 0xFF; // Set all PORTB pins as output
  PORTB = 0xFF; // Set all PORTB output pins high (leds off).
 
  initialize_timer();
  initialize_adc();
  initialize_UART();
  sei();

  start_timer();
  while(1) // Main Loop
  {
  }
  return 0;
}

//End of code


Luck! and thanks everyone for making me possible to start messing with this stuff

Miguel
Eramos tan pobres!

bioroids

Ok here's an ugly screenshot of an LFO:



I'll post the source code (Micro$oft Visual C++ ) when it's done.

Luck

Miguel
Eramos tan pobres!

Peter Snowberg

Eschew paradigm obfuscation

bioroids

Thanks Peter!

It will take probably a long time to complete this project, as I have to work on the spare time of my spare time  :icon_sad:

Miguel
Eramos tan pobres!

free electron

Cool idea!
Since all the variables are accessed from the interrupt subroutines I'd declare them as 'static volatile', e.g.

static volatile unsigned char dataHi;
static volatile unsigned char dataLo;

To store one bit info - flags ( 'send' in your code) it is better to build so called "bitfields". In one byte we can store up to 8 flags. First we'll have to build a structure:

typedef struct
{
unsigned bit0:1;
unsigned bit1:1;
unsigned bit2:1;
unsigned bit3:1;
unsigned bit4:1;
unsigned bit5:1;
unsigned bit6:1;
unsigned bit7:1;
} bitfield;

Then declare our flag container, it can be static volatile if flags are accessed from the ISR's

static volatile bitfield flags;

Then in code we can set, reset, test the flags in a simple way:

flags.bit0 = 1; //set the bit0 flag
flags.bit1 = 0; //reset the bit1 flag
if (flags.bit2)
    {
    //do something if bit2 flag is set
    }

Of course we can use other names than bit0, bit1... for example let's use first  bit as 'send' flag

typedef struct
{
unsigned send:1;
unsigned bit1:1;
unsigned bit2:1;
unsigned bit3:1;
unsigned bit4:1;
unsigned bit5:1;
unsigned bit6:1;
unsigned bit7:1;
} bitfield;

static volatile bitfield flags; //delcare the flag variable

... somewhere in code:

flags.send = 0;
...


Bitfields are very useful in many applications, they save the RAM and make the code easier to read.
Next improvement could be use of the circular buffer in the UART transmitter to ensure that there won't be missing bytes. Peter Fleury wrote a nice UART library with buffered transmitter and receiver.


bioroids

Hi, thanks for your comments!

What is the difference between using static or not? I never been much of a C programmer really

The bitfield stuff is pretty good, in particular if you're gonna use a lot of flags.

I started implementing a circular buffer, but then I realised the error count never grows from 0. I think everything synchronises fine with the correct prescaler settings for the UART, ADC and Timer, so no samples are missing.

I just finished some mods including oversampling 4 times to gain 1 (very necessary) bit.

I'd like to hear some comments on the "protocol" I'm using:
I use the MSB of each byte to flag if its the low part or the high part of the word. This way the PC can interpret the data correctly. I have to transmit 7 bits on the low part and 4 bits on the high part, and the software has to rearm the whole data.

Is there a more "standard" way to implement this?

Luck!

Miguel
Eramos tan pobres!

rogeryu_ph

Static variable are variable that can not be alter or change by a calling procedure or function... Bitwise or Bitfield is most suitable specially for digital application: 0 or 1 bit flag.....  8bits for 1byte... Hope that would help.... :icon_neutral:

Roger

The Tone God

Quote from: rogeryu_ph on November 21, 2006, 03:26:11 AM
Static variable are variable that can not be alter or change by a calling procedure or function... Bitwise or Bitfield is most suitable specially for digital application: 0 or 1 bit flag.....  8bits for 1byte... Hope that would help.... :icon_neutral:

You maybe thinking of a constant. A static variable is one that will hold it's value even when the variable is out of scope of the excuting code. This is handy if you want to retain the value of variable even when it is not being used at the current scope.

An example might be a function that increaments a counter at every call. With a normal variable each time you call the function a new counter will be created, incremented, then destroyed at the end of the function loosing the counter's value. When you call the function again you will be starting all over thus not really keeping count. If you declare the variable static it will not be destoryed at the end of the function call. When you call the function again the variable will still be there with it's last assigned value.

Andrew