Issues generating frequency for musical note with PWM on ATMega328p

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

Hi all - using avr-gcc and an ATMega328p (datasheet is http://atmel.com/Images/Atmel-42735-8-bit-AVR-Microcontroller-ATmega328-328P_Datasheet.pdf).

 

I'm trying to make a music project using a passive piezo speaker, and having trouble generating the correct frequencies using Fast PWM.  I'm using the 16-bit Timer1, and I have an oscilloscope hooked up to the output pin of OC1A.  I noticed that the frequency my scope is reading is not the correct one, and does not seem to change when I change the note/value in OCR1A.  It seems to be consistently reading 7.8khz, but that may not be too telling since I'm still learning how to use my scope and may not be setting it up right.

 

Eventually I would like to change the note being played after some duration to make songs, but am starting with just getting a single note right.  

 

Here's my code - what am I doing wrong, and how can I generate the specified frequency for a note?  

#define F_CPU 16000000UL
#define TIMER1_PRESCALER (uint8_t) 8

#include <avr/io.h>

const uint16_t A4_FREQ = 440;
const uint16_t C5_FREQ = 523;
const uint16_t D6_FREQ = 1175;

int main(void)
{
    // Set OC1A as output pin
    DDRB = (1 << PINB1);

    /*
        - Set COM1A1 to clear OC1A output on TOP (compare match in our case), set output on BOTTOM
        - Set WGM13, WGM12, WGM11, WGM10 for Wave Generation Mode 15.  This mode behaves like so:
          Fast PWM, TOP is OCR1A, update OCR1A at BOTTOM, set TOV1 flag (used for the interrupt) at TOP
    */
    TCCR1A = (1 << COM1A1) | (1 << WGM13) | (1 << WGM12) | (1 << WGM11) | (1 << WGM10);
    
    // Calculate frequency for specified note
    OCR1A = F_CPU / (A4_FREQ * TIMER1_PRESCALER * 2);
    
    /* Start Timer1 with prescaler of 8 */
    TCCR1B = (1 << CS10);

    while (1)
    {

    }
}

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

You do need code for changing OCR1A inside while loop.
What you did is set a single not only.
.
MG

I don't know why I'm still doing this hobby

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

Normally you’d use CTC mode with output set to toggle.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
TCCR1A = (1 << COM1A1) | (1 << WGM13) | (1 << WGM12) | (1 << WGM11) | (1 << WGM10);

I'd have to dig out a datasheet but does 328 really have all four WGMs bits in the A register? Many/Most AVRs have them split with 2 bits in A and two in B

 

Is mode 15 the right mode anyway? (again I'll have to dog out the data).

EDIT: yup, thought so:

 

 

So you need to split your setting of WGM bits.

 

Also mode 15 is:

 

Do you really want to use a mode where TOP is set by OCR? If you do then that OCR channel is not available as an output (it can't do two things at once!)

 

Last Edited: Fri. Oct 13, 2017 - 12:57 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Wow, great catch!  To answer your question, I'm honestly not really sure which mode would be best - this was my first serious foray into hardware PWM.  What mode would you recommend for my use case?  My main goal is to be able to play different notes for varying durations to string together a song.  Being able to change the volume would be nice too (which I've learned is controlled by the duty cycle), but is less important to me.

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

If you have a Mega328P then you are possibly using the Arduino UNO or Nano as a hardware platform.  Try looking at the tone() examples and source code, along with the source code for analogWrite(), which creates PWM on the Arduino.  Tone() creates precise frequencies consisting of square waves on a port pin (not sure which one).  I believe that it uses PWM to make these waveforms.

 

PWM has two basic variables: the frequency of the square wave and the duty cycle of the square wave.  The duty cycle will affect the tone of the note.  The volume of the square wave note can not be adjusted with PWM, since you are constantly switching between Vcc (usually +5V, sometimes 3.3V) and ground (0 V).

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

Use 

Tylerlee12 wrote:

 What mode would you recommend for my use case? 

 

For a square wave 1:1 use CTC mode 

 

const uint16_t A4_FREQ = 440;
const uint16_t C5_FREQ = 523;
const uint16_t D6_FREQ = 1175;

// OCR1A values
#define A4 (F_CPU / (A4_FREQ * TIMER1_PRESCALER * 2) - 1)
#define C5 (F_CPU / (C5_FREQ * TIMER1_PRESCALER * 2) - 1)
#define D6 (F_CPU / (D6_FREQ * TIMER1_PRESCALER * 2) - 1)


int main(void)
{
   // Set OC1A as output pin
   DDRB = (1 << PINB1);

   // Set Timer1, mode CTC, toggle on compare, prescale 8
   TCCR1A = (1 << COM1A0);
   TCCR1B = (1 << WGM12)|(1 << CS10);;

    while (1)
    {
      // play A4 for 0.5 sec
      OCR1A = A4;   
      _delay_ms(500);

      // play C5 for 0.5 sec
      OCR1A = C5;   
      _delay_ms(500);

      // play D6 for 0.5 sec
      OCR1A = D6;   
      _delay_ms(500);

    }
}

 

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

Thanks, this seems to be working.  My only question is, where did you get this

(F_CPU / (A4_FREQ * TIMER1_PRESCALER * 2) - 1)

equation from?  Is it different for other types of PWM?  If so, where can I find these other equations for the other types of PWM?

Last Edited: Sun. Oct 15, 2017 - 03:29 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

That equation is a rearrangement of the formula shown in the description for CTC mode.

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

A square wave is going to sound crappy. Why not do it "properly", and use the PWM as a primitive DAC? Use a table of sine values, change the amplitude, mix two sine waves. The possibilities, while not endless, are far greater than a single, fixed amplitude square wave.

 

Quebracho seems to be the hardest wood.

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

To see what you can do with a 328 have a look, and listen, to...

 

https://janostman.wordpress.com/...

 

'This forum helps those who help themselves.'

 

pragmatic  adjective dealing with things sensibly and realistically in a way that is based on practical rather than theoretical consideration.

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

John_A_Brown wrote:

A square wave is going to sound crappy. Why not do it "properly", and use the PWM as a primitive DAC? Use a table of sine values, change the amplitude, mix two sine waves. The possibilities, while not endless, are far greater than a single, fixed amplitude square wave.

 

 

And when you combine with phase modulation, phase distortion and stuff like that, the possibilities really become endless.

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

Brian Fairchild wrote:

To see what you can do with a 328 have a look, and listen, to...

 

https://janostman.wordpress.com/...

 

Great find! If that doesn't inspire the OP to forget the simple square wave, I don't know what will.

I'd build one if I thought I could ever learn to play it.

Quebracho seems to be the hardest wood.

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

John_A_Brown wrote:

I'd build one if I thought I could ever learn to play it.

 

I can't play but I'm still going to build one. I've even bought a cheap secondhand keyboard from eBay to do it.

'This forum helps those who help themselves.'

 

pragmatic  adjective dealing with things sensibly and realistically in a way that is based on practical rather than theoretical consideration.

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

John_A_Brown wrote:
A square wave is going to sound crappy.

 

@Tylerlee12 For comparison, this is about the best you could ever hope for using a square wave:

 

https://www.youtube.com/watch?v=...

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

Actually, late ZX spectrum games show that you can do a bit better.

 

https://www.youtube.com/watch?v=...

https://www.youtube.com/watch?v=...