About:
UARTunes is a AT90S2313 (Predecessor to the ATtiny2313) clocked at 3.686400 MHz with and 8 bit R2R DAC. The purpose of the project is to allow devices (such as the Casio PRIZM) with a UART to output complex sounds and music to enhance gameplay or demos. The UARTunes unit will use a MIDI protocol with a few added commands for extra features (Idle, low power consumption, etc). The MIDI protocol allows for easy creation of music and easier parsing of files on the master device. Ideally the UARTunes unit would read the wavetables from and external EEPROM, allowing the master to device to modify and add new sounds.

My progress (or lack thereof) will be post here. Feel free to add suggestions or comments!
MPoupe posted a Prizm port of his fx-9860g music player, which sends data over the serial port to create monophonic music. I initially thought that that was what this was, but I now see that you're proposing using the serial port to communicate with an external device. I think this is a superb idea; have you considered making it use the standard MOD protocol? Load the wavetable to the ATtiny chip over the UART, then send waveform/note/duration information to play back the music.
Using the MOD protocol was something I considered, but seeing as it's not very well documented, I decided to go with MIDI. Using MIDI also allows for the master device to stream a MIDI file right over the serial port and allows the UARTunes module to also function as a standalone synth.
pcb_master wrote:
Using the MOD protocol was something I considered, but seeing as it's not very well documented, I decided to go with MIDI.
I very much beg to differ. See, for example:
http://www.aes.id.au/modformat.html

Quote:
Using MIDI also allows for the master device to stream a MIDI file right over the serial port and allows the UARTunes module to also function as a standalone synth.
True, but the same follows of a MOD synth, if you have a default waveform table of some sort.
Oh hey! Look! It's my failure at a wavetable synth!
Edit: It's supposed to play a D4 right now, but it sounds more like an A4. Help!

Code:

#include <avr\io.h>
#include <util\delay.h>

#define F_CPU = 3.6834;


int sawwave[52] = {0,5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100,105,110,115,120,125,130,135,140,145,150,155,160,165,170,175,180,185,190,195,200,205,210,215,220,225,230,235,240,245,250,255};

double notes[8] = {3.82, 3.41, 3.03, 2.86, 2.55, 2.27, 2.02, 1.91};


int main()
{
   int delay = 0;
   DDRB = 255;
   int j = 2;
   while(1)
   {
      for(int i = 0; i < 52; i++)
      {
         PORTB = sawwave[i];
      }
      switch (j)
      {
         case 1:
         _delay_ms(3.82);
         break;
         case 2:
         _delay_ms(3.41);
         break;
         case 3:
         _delay_ms(3.03);
         break;
         case 4:
         _delay_ms(2.86);
         break;
         case 5:
         _delay_ms(2.55);
         break;
         case 6:
         _delay_ms(2.27);
         break;
         case 7:
         _delay_ms(2.02);
         break;
         case 8:
         _delay_ms(1.91);
         break;
      }
      //j++;
      if (j == 9)
         j = 1;
   }
}
Care to explain a little bit about that code, and what each part is for? F_CPU is obvious, sawwave seems to just be a Y=5X line, and I'm not sure about notes (other than the obvious). I'm pretty sure your switch(j) { ... } structure is supposed to be inside the for { } loop?
Great. Leave it to AVR Studio 4 to screw up the formatting.
The switch is just c4 to c5, I put it in so I could switch note values easily. Ignore the j++ and the if statement thing at the bottom. The note delay values is (1/f)*1000 to get millisecond values.
That's fine, but I still think you need to have your delays in between changing the PORTB value, hence my suggestion to move the whole switch block into the for loop.
Quote:

That's fine, but I still think you need to have your delays in between changing the PORTB value, hence my suggestion to move the whole switch block into the for loop.


If you do that, the note is much too low.
But in your current code, it will play the entire slope of the wave, then pause, then play the entire slope of the wave again, etc. That's just a square wave, basically.
So you have a delay of 3.41ms between each sample, and a full cycle is 52 samples long, which means a complete cycle is 177.32ms. The frequency would be 5.6Hz. That's outside the range of human hearing (though some of the harmonics of the sawtooth wave can be heard).

Why not have a constant delay between samples (constant sample rate) and vary the rate of incrementation of the index into the wave table? Look up how a numerically controlled oscillator (NCO) works. Here's a good start: http://en.m.wikipedia.org/wiki/Numerically_controlled_oscillator
christop wrote:

Why not have a constant delay between samples (constant sample rate) and vary the rate of incrementation of the index into the wave table?


I'm guessing I would have to ditch the for loop and use a while loop and manually increment the variable?
pcb_master wrote:
christop wrote:

Why not have a constant delay between samples (constant sample rate) and vary the rate of incrementation of the index into the wave table?


I'm guessing I would have to ditch the for loop and use a while loop and manually increment the variable?

Frankly I would not even use an explicit loop at all, but let's break this down one step at a time.

An NCO consists of two parts: a phase accumulator (PA) and a phase-to-amplitude converter (PAC):

Code:
unsigned short phase = 0; // modulus is 65536

Then you also need a frequency:

Code:
unsigned short fscaled; // scaled frequency

(This value is the frequency of the wave form, but scaled; more on that later)

Since you're generating a sawtooth wave, for each sample all you have to do is something like this:

Code:
phase += fscaled; // accumulate phase; this is the PA
PORTB = phase / 256; // output a sawtooth wave sample between 0 and 255; this is the PAC


That's it. You don't need an array containing values for the sawtooth wave. If you want to use a different waveform (eg, sine wave or a banjo sound Smile), you'll need an array whose size is a power of 2 (eg, 64) and then use phase/1024 (or another value, depending on the size of the waveform array) as an index into that array:

Code:
extern unsigned char sinewave[64];
phase += fscaled; // accumulate phase; this is the PA
PORTB = sinewave[phase/1024]; // output a sinewave sample

(Note: 1024 = 65536 (PA modulus) / 64 (number of samples in the waveform array))

One issue remains: how to generate these samples at a constant rate.

The way you're doing it is to run some code to output a sample and then delay for a small time. This fails because the code that does the output takes a small amount of time (maybe a few microseconds), and the delay must take that into account. This is easier to do in assembly language where you can count instruction times exactly, but it's a pain in the butt. Even then it is suboptimal because any change in the code path can affect timing. You also can't do anything else during the delay.

The way I would do it is to emit one sample inside an interrupt handler. The interrupt handler would have to be called at a fixed rate (say, 8192Hz or more; this is your SAMPLE_RATE) by a hardware clock source. You'll have to figure out how to do that exactly since I am not very familiar with the AVR. With this method you can also do other stuff inside the main function between interrupts, such as change the frequency.

Now about the frequency scaling...

The frequency scale (ie, multiplier) depends on the modulus of the PA (65536) and the sample rate (SAMPLE_RATE):

FSCALER = 65536 / SAMPLE_RATE

Simply multiply your frequency f by the scale to get the scaled frequency:

fscaled = f * FSCALER

For example, with a sample rate of 8192Hz and a frequency of 440, the scaled frequency fscale becomes:

fscale = 440 * 65536 / 8192 = 3520

Other frequencies, especially those with fractional parts, will be rounded. With an FSCALER of 8 (65536/8192) this error in rounding will not be noticeable above about 62.5Hz. A larger FSCALER (larger PA modulus or smaller SAMPLE_RATE or both) increases the frequency precision.

Notes:
- I'm assuming PORTB is configured to be an 8-bit PWM output.
- Floating point is slow on embedded systems in general. This is the case with the AVR systems. NCO's use integer math which is fast.
- You can use a 32-bit (or smaller like 24-bit) phase accumulator instead and adjust the frequency scaler FSCALER to match. Be careful about unintentional integer overflows when calculating the frequency.
- This works with virtually any sample rate, but a sample rate should be chosen to match the PWM rate (whatever it is on the AVR).
- Turning off the oscillator is as simple as setting the frequency (fscaled) to zero. The sawtooth ramp stops at whatever value it was before it was stopped.
- You can extend this into multiple oscillators ("voices" or "channels") by adding up the values coming out of the PAC's for each oscillator before sending the final sum to the output (PORTB). But start with a single oscillator until you understand how to manipulate it.
Thanks! The Wikipedia article was really hard to understand.
PORTB is the output register of 8 IO lines, hooked to a R2R DAC.
pcb_master wrote:
PORTB is the output register of 8 IO lines, hooked to a R2R DAC.

Ah, a good ol' R2R ladder. That doesn't affect anything in my previous post, except where I mentioned the PWM rate. An R2R DAC doesn't have an inherent sample rate, so any rate can be used (though powers of two are still preferred to make calculations easier).

Also, if you're just starting out, a simple while loop might be fine, even if it doesn't give accurate timing:

Code:
while (1) {
   phase += fscaled; // accumulate phase; this is the PA
   PORTB = phase / 256; // output a sawtooth wave sample between 0 and 255; this is the PAC
   _delay_us(1000000./SAMPLE_RATE); // this delay value should be calculated by the compiler at compile time
}

If the sample rate is 8192, this delays for 122.0703125 microseconds after each sample. If the phase accumulator and phase-to-amplitude conversion (the two C statements before the delay) takes 5 microseconds, for example, that is a sample rate error of about 4.1%, which will make all notes sound slightly flat.

If you already know how to use interrupts and how to set the interrupt rate, though, I recommend that you use those instead.

Edit: I just noticed you set F_CPU incorrectly in your code. Here's what it should look like (assuming 3.6834 MHz):

Code:
#define F_CPU 3683400

Are you sure the frequency isn't really 3.6864 MHz?


Progress! (well sort of)
I was testing the UART of the AT90S2313 with this code:

Code:

#include <avr/io.h>

/* Prototypes */
void InitUART( unsigned char baudrate );
unsigned char ReceiveByte( void );
void TransmitByte( unsigned char data );

/* Main - a simple test program*/
int main( void )
{
   InitUART( 6 );

   for(;;) {   /* Forever */
      TransmitByte( 0x48 ); //H
     TransmitByte( 0x65 ); //e
     TransmitByte( 0x6C ); //l
     TransmitByte( 0x6C ); //l
     TransmitByte( 0x6F ); //o
     TransmitByte( 0x20 ); //(space)
     TransmitByte( 0x57 ); //W
     TransmitByte( 0x6F ); //o
     TransmitByte( 0x72 ); //r
     TransmitByte( 0x6C ); //l
     TransmitByte( 0x64 ); //d
     TransmitByte( 0x21 ); //!
   }
}

/* Initialize UART */
void InitUART( unsigned char baudrate )
{
   UBRR = baudrate;                  /* Set the baud rate */
   UCR = ( (1<<RXEN) | (1<<TXEN) );  /* Enable UART receiver and transmitter */
}

/* Read and write functions */
unsigned char ReceiveByte( void )
{
   while ( !(USR & (1<<RXC)) )    /* Wait for incomming data */
      ;                         /* Return the data */
   return UDR;
}

void TransmitByte( unsigned char data )
{
   while ( !(USR & (1<<UDRE)) )
      ;                          /* Wait for empty transmit buffer */
   UDR = data;                  /* Start transmittion */
}


The baud rate is 31250. It seems to work okay some of the time, but mostly outputs:

Code:

 World!Hello World!Hello ×ïòìä¡Èåììï ×ïòìä¡Èello Wïòìä¡Èåì쯠ïòìä¡È¥¬¬¯ ¯²¬¤¡¥¬¬¯·¯²¬¤¨¥¬¬¯·¯²¬¤¨µ¼¬¿§¿²¼´¨µ¼´·¯·º´´ µ´´¿¯·º´° µ´´·¯·º´´ µ´´·¯·º´´ µ´´·¯·º´°¤±´´·«·¸¶²¤³¶¶·«·¹¶²¤Y[[
                    éõɱ¡ÕñÑÝA­ÝáÙÉAÊØlo World!Helìï ¯²¬¤¨µ´´·«·¹¶²HRY[
                                                                       õɱ
!±±½A
     éõɱE¡ÕÑÑÝA­ÝáÑÁEÅÑÑÝA­ÝáÙÉAËØØo World!Hello World¡È¥¬¬¿¯·º¶²¤²¶¶·«·¹¶²¤²¶¶
                                                                               éõɱ
!±±½A
     éõɱ¡±±½A
              éõɱ¡Õññ½Ý½É±¡ÕÑÑÝA­ÝåÙÉBelìï ·¯²¬¤¨µ´´·«·º´°¤³¶¶
                                                               éõɱ
!±±ýA½ÝéÙÉAËÙÙß@®orld!Hello World!Hello World!Håììï ×ïòìä¡Èåììï ×ïòìä¡Èåììï ×ïòìä¡¥¬¬¯·¯²¬¤¡¥¬¬¯ ¯²¬¤¡¥¬¬¯·¯²¬¤¨¥¬¬¯§¿º´´ µ´´·¯·º´°¤±´´·«·¸¶²¤³¶¶·«·¹¶²¤²¶¶·ÕK±
!±±½]½É±
!±±½]½É±
!±±½]½É±
!±±ýA½ÝéÑÉEÍÙÙÝA­ÝåÙÉAÍÙÙßA¯ÝåØÈ!Hello ×ïòìä¡Èå쬯·¿²´´¤±´¶·«·¸¶²¤Y[Û
                                                                     éõɱ¡ÕñÑÝA­ÝáÙÉAÉÙÙß@®orlä¡¥¬¬¯§¿²¼´¨µ¼¼¿§¯²¬´¨µ´´·«·¹¶²HRY[[
                                                  éõɱ
!±±½]½É±
!±±½A


What the heck is going on?
If my math is right, the CPU clock speed would have to be exactly 3.5MHz to achieve that bit rate:

F_CPU = bitrate * 16 * (UBRR + 1)
3500000 = 31250 * 16 * (6 + 1)

If the clock rate is what it was previously (3.6864Mhz), the actual bit rate is slightly higher:

bitrate = F_CPU / (UBRR + 1) / 16
32914.3 = 3686400 / (6 + 1) / 16

Any error in the rate above a few percent will give you some garbage data as you saw. (32914.3/31250 = 1.053 = 5.3% too fast)


As an aside, the clock rate 3.6864MHz is used as it is an integer multiple of common (standard) serial bit rates:

115200 = 3686400 / (1 + 1) / 16
57600 = 3686400 / (3 + 1) / 16
38400 = 3686400 / (5 + 1) / 16
9600 = 3686400 / (23 + 1) / 16

(And obviously many more common bit rates are possible)

UBRR would be set to 1, 3, 5, or 23 for those bit rates.

Edit: if the bit rate has to be exactly 31250, I would use a CPU clock source that is an integer multiple of 500000 (31250*16), such as 4MHz (with a UBRR value of 7).
Incidentally, a while ago I wrote an exceedingly simple program to brute-force the closest register configuration to a target bit rate for RX UARTs and also spits out the error in the settings it gives you.

Might be useful or neat or something to look at. *shrug* I'm sure the logic could be easily adapter to other micros.
https://www.taricorp.net/projects/rx-brr-calc
There are sites that can calculate that stuff too, which is good for people who aren't stuck on a Wintel machine (I'm on an Android phone at the moment, my main desktop runs Fedora, and my other desktop computers have non-Intel processors (68k, PowerPC, ...)).

http://www.wormfood.net/avrbaudcalc.php
http://app.josephn.net/avr_ubrr/
  
Register to Join the Conversation
Have your own thoughts to add to this or any other topic? Want to ask a question, offer a suggestion, share your own programs and projects, upload a file to the file archives, get help with calculator and computer programming, or simply chat with like-minded coders and tech and calculator enthusiasts via the site-wide AJAX SAX widget? Registration for a free Cemetech account only takes a minute.

» Go to Registration page
Page 1 of 2
» All times are UTC - 5 Hours
 
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum

 

Advertisement