PWM Audio

Go To Last Post
24 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I am wondering, why this code doesn't work (avr-gcc linux),
I only here a low pulse zoom (error like).

#include 
#include 
#include 
#include 

const uint8_t  sinewave[] PROGMEM= //256 values
{
    0x80,0x83,0x86,0x89,0x8c,0x8f,0x92,0x95,0x98,0x9c,0x9f,0xa2,0xa5,0xa8,0xab,0xae,
    0xb0,0xb3,0xb6,0xb9,0xbc,0xbf,0xc1,0xc4,0xc7,0xc9,0xcc,0xce,0xd1,0xd3,0xd5,0xd8,
    0xda,0xdc,0xde,0xe0,0xe2,0xe4,0xe6,0xe8,0xea,0xec,0xed,0xef,0xf0,0xf2,0xf3,0xf5,
    0xf6,0xf7,0xf8,0xf9,0xfa,0xfb,0xfc,0xfc,0xfd,0xfe,0xfe,0xff,0xff,0xff,0xff,0xff,
    0xff,0xff,0xff,0xff,0xff,0xff,0xfe,0xfe,0xfd,0xfc,0xfc,0xfb,0xfa,0xf9,0xf8,0xf7,
    0xf6,0xf5,0xf3,0xf2,0xf0,0xef,0xed,0xec,0xea,0xe8,0xe6,0xe4,0xe2,0xe0,0xde,0xdc,
    0xda,0xd8,0xd5,0xd3,0xd1,0xce,0xcc,0xc9,0xc7,0xc4,0xc1,0xbf,0xbc,0xb9,0xb6,0xb3,
    0xb0,0xae,0xab,0xa8,0xa5,0xa2,0x9f,0x9c,0x98,0x95,0x92,0x8f,0x8c,0x89,0x86,0x83,
    0x80,0x7c,0x79,0x76,0x73,0x70,0x6d,0x6a,0x67,0x63,0x60,0x5d,0x5a,0x57,0x54,0x51,
    0x4f,0x4c,0x49,0x46,0x43,0x40,0x3e,0x3b,0x38,0x36,0x33,0x31,0x2e,0x2c,0x2a,0x27,
    0x25,0x23,0x21,0x1f,0x1d,0x1b,0x19,0x17,0x15,0x13,0x12,0x10,0x0f,0x0d,0x0c,0x0a,
    0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x03,0x02,0x01,0x01,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x02,0x03,0x03,0x04,0x05,0x06,0x07,0x08,
    0x09,0x0a,0x0c,0x0d,0x0f,0x10,0x12,0x13,0x15,0x17,0x19,0x1b,0x1d,0x1f,0x21,0x23,
    0x25,0x27,0x2a,0x2c,0x2e,0x31,0x33,0x36,0x38,0x3b,0x3e,0x40,0x43,0x46,0x49,0x4c,
    0x4f,0x51,0x54,0x57,0x5a,0x5d,0x60,0x63,0x67,0x6a,0x6d,0x70,0x73,0x76,0x79,0x7c
};

uint8_t i=0;

SIGNAL (SIG_OUTPUT_COMPARE1A){
	OCR1A=pgm_read_byte(&sinewave[i]);
    i++;
}
int main(void) {
    //Port D pins as input
    DDRD=0x00;
    //Enable internal pull ups
    PORTD=0xFF;
    //Set PORTB1 pin as output
    DDRB=0xFF;
    // initial OCR1A value
    OCR1A=80;
    //Output compare OC1A 8 bit non inverted PWM
    TCCR1A=0x91;
    //start timer without prescaler
    TCCR1B=0x01;
    //enable output compare interrupt for OCR1A
    TIMSK=0x10;
    //enable global interrups
    sei();
    while (1) {
        //loop for ever. Interrupts will do the job.
    }
}

[/code]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

In the simulator with a breakpoint in the SIGNAL routine (it should be an ISR these days!!) I certainly see each value of the array being loaded into the low byte of the OCR1A register pair (OCR1AL) each time. So that part of it is working. But because you used "magic numbers" like 0x91, 0x01 and 0x10 when setting up the timer it makes it almost impossible to see how you are configuring the timer. It would be much more readable if you used symbolic bit names.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If the gcc guys can ask a question in the general c forum, can I go ask an iccavr question in the gcc forum?

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Bob, I don't think his question was specifically related to GCC just because the example is written in C. I've noticed lately a lot of sarcasm on these forums and one-upmanship going on. Isn't the point of these forums so that people can help one another?

Jst, the timer does seems to be set-up correctly (it's late so I might have missed something). Some thoughts:

Would it not make more sense though to interrupt on overflow and set OCR1A then?

Also, with no prescaler I'm not sure you have enough time to load OCR1A before its new value is due to clear the output (not with the ISR written in C maybe?). Too tired to think what the effects of that might be.

Matt.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

BTW, SIGNAL() has been deprecated, ie. not recommended and should be changed as it might disappear in the near future, ISR() is the correct way now.

Edward

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Perceptive, Matt. I have in fact been employing sarcasm as a technique to highlight the imbalance in the way the forums are laid out. As in any engineering discipline, I think there should be some top down tree structure to the forums. Right now all AVR 8 bit hardware and software problems must be triaged from the one AVR forum. The ONLY exception is GCC, which has its own forum. I think there should at least be a hw branch and a sw branch, but sw comprises several compilers and assembler. Anyway, if his example had been written for CV, and asked in the GCC compiler forum, then same smoke would arise.

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I guess the problem is that if the PWM mode is used, here the pulse width is changed, not the frequency.

Debugging is for sissies and delivery for surgeons. Real men do demonstration.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Update your OCR register on a timer overflow not an OCR match.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ok, the fact is,
I (newbie in hardware) can't get any audio out of my atmega16/32 (on a STK500v2).
First I tried (very optimistically) an 16-bit i2s DAC (TDA1543), could not get a simple sawtooth frequency out of it (even not with a lot of pro help), only some sort of pulse-like-sawtooth with only high bits.
Then I tried a simple (supposed to be) 8 bit parallel DAC (DAC0832), also no valid frequency, then added an op-amp as defined by the DACs application notes (Passthrough mode) but still no valid result.
Then I found out, that the easiest method to output audio is to use an internal PWM, and all tutorials and examples I tried failed! :cry:
And all hardware ideas/plans I got, are (lofi) audio related.
There is a thing I know that doesn't fail, and that is: programming the ATMega on the STK500 using Linux version of avr-gcc toolchain and 'avr dude' (installed on macosx).
By now, I discovered that the theory of hardware is easier to understand than the practice (besides switches and LEDs).
Can someone give me any hope on simple audio (DA) output?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I have the line in of my soundcard on this pc here going to a 10k R and a .1 C coming from OC2 on my mega8. I'm using fast 8 bit pwm (62500hz), no interrupt, on timer2. Timer0 gives a 125usec interrupt (8khz). In the timer0 int handler, I grab the next unsigned 8 bit sample and put it in OCR2. It does work. Keep trying! Get a scope.

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

JST, keep going along the lines of what you've already tried. You're almost there - just update the OCR register on the timer overflow and it should give you the expected result. Then you can change your sine wave into something more interesting. Agreed, a scope is invaluable.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

'Magic Numbers' are a pain to support. Just imagine it was someone elses code you had to try and figure out the intent.
That's just what this code will look like to you after a surprisingly short time.

Your code could better be written as -

    TCCR1A = (1<<COM1A1|(1<<COM1B0)|(1<<WGM10); // Output compare OC1A 8 bit non inverted PWM
    TCCR1B = (1<<CS10);                         // start timer without prescaler

But to be obviously helpful, rather than apparently critical, I suggest -

    TCCR1A = (1<<COM1A1|(1<<COM1B0)|(1<<WGM10); // Output compare OC1A 8 bit non inverted FAST PWM
    TCCR1B = (1<<WGM12)|(1<<CS10);              // start timer without prescaler

to use Fast PWM mode, which is the more easily tested option.

Good Luck.

I have coded for about 2 dozen architectures, and they all have their place.
Don't get too religious about them (but AVR is excellent!)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

So i tried this sawtooth generation:

volatile uint8_t i=0;

SIGNAL(SIG_OUTPUT_COMPARE1A){
	OCR2=i;
    i++;
}

int main(void) {

    DDRD=(1<<PD7); //OC2 pin as output
    PORTD=0x00;
    
    // Output compare OC1A 8 bit non inverted FAST PWM
    TCCR1A = (1<<COM1A1)|(1<<COM1B0)|(1<<WGM10);
    
    // start timer without prescaler
    TCCR1B = (1<<WGM12)|(1<<CS10);
    
    //enable output compare interrupt for OCR1A 
    TIMSK=0x10;
    
    //enable global interrups 
    sei(); 
    while (1) { 
        //loop for ever. Interrupts will do the job. 
    } 
    
}

The timer works, I tested it with LEDs, but I think I have to define to use OC2 as PWM output, instead of OC1A (COM1A1), how do I do that?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Oh come on. Load the datasheet pdf up into adobe reader and search for OC2

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

uhm, what do u mean by that?
I always have the atmega datasheet opened.
Don't reply, if it doesn't add anything usefull.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm guessing COM20 and or COM21, and then there are different PWM modes etc. and do I still need the COM1A1, and which signal does it call.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

bobgardner wrote:
I have the line in of my soundcard on this pc here going to a 10k R and a .1 C coming from OC2 on my mega8. I'm using fast 8 bit pwm (62500hz), no interrupt, on timer2. Timer0 gives a 125usec interrupt (8khz). In the timer0 int handler, I grab the next unsigned 8 bit sample and put it in OCR2.
Bob, so you're creating a low-pass RC filter. I don't quite understand your time constant. What value is your ".1 C" capacitor? I imagine you'd want a corner frequency of around 4-8kHz since your max audio frequency is 4kHz and your PWM fundemental frequency is 62.5khz

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I have an 8khz tic from timer0, and I change the OCR in the 8khz handler. The f=1/2piRC should be about 4k... I'm typing this at work and I can't remember if its 10k and .1 at home on the bench or 10K and .01... just do what I mean instead of what I typed. Someone said use the overflow interrupt, but that might not be the right 'sample rate' that you recorded the wave with. Thats why I use 62500 khz fast 8 bit pwm, no interrupt on timer 2 for the pwm. Hope this makes sense to someone. It sounds just like me when I play it back! eerie!

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

My guess is that 0.1 C cap means 0.1uF which is 100nF ?

And to get audio out from OC2, you'll have to use timer2 for that. Timer1 can only control OC1 pins. You'll have to figure out yourself if it is possible.

The code would suggest that it generates the audio on OC2 pin PWM (timer2) and timer1 is used to generate the triangle wave. They can be done with the same timer too if wanted. Or just use Direct Digital Synthesis and create an array of triangle or sine and stuff that out.

- Jani

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Okay, for a 4kHz corner frequency, C would be 3nF. I had previously calculated that, but since 3nF was fairly far from "0.1 C" I wanted to make sure I understood correctly.

Thanks for the low filtering tip, I'm looking to do this in the near future on an audio project.

One question, I've seen some designs taking the OC2 output and sending it parallel into a few buffers as a class-D amplifier before the RC filtering. Have you found such drivers to be useful? Probably not for your high-impedance line-in, but maybe for driving a piezo or a magnetic speaker.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I've just driven a single FET with a PWM output. The FET was big enough to handle the current from 5V to 8 Ohm speaker.

But I guess taking the 5V PWM signal, inverting it and then driving them both to a speaker or piezo to get 10Vpp over the piezo would add some more volume.

What I would like to see is a PPM output too to create better DAC for audio than PWM. Heck, maybe even a sigma-delta DAC output, while we are at it.

- Jani

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Jepael, the XMega with integrated DAC will be nice when they arrive.

I'll trying the simple RC filter on a PWM line as a test for moderate fidelity audio. Next, I have a 8 input R-R2 monolithic ladder that I'm planning to use for a function generator. I suspect that'd give higher quality audio than the RC filtered PWM.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

hmmm... buffer then filter? 5v can drive 20ma into 250 ohms... so the pin can drive a 250 ohm impedance filter... I guess a buffer could take it down lower. Seems better to filter THEN buffer with a beefy hi output current opamp or something. Am I sposed to plug the wah into the fuzz, or the fuzz into the wah?

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

That makes sense, Bob. ISTR that the buffered PWM signal was driving a 2 or 3 pole filter, so I suppose it needed the lower impedance for the multiple filter stages. The analog opamp buffer for a single RC filter seems smart.