PWM for frequency output

From Wiki

Jump to: navigation, search

This is the start of a program to play DTMF on the AVR butterfly. Problem was, the clock speed was too slow - it would work better if I added a 20MHz crystal and the appropriate loading caps.

I like to use interrupts for accurate timing, so this will show that off. You can also save a little power by putting the processor into sleep mode between interrupts.

Everything but the _delayFourCycles is my own original work - the delay routine is publicly available, but I apologize if I am violating the wiki's terms.

The first file is the .c, the next is the .h. To build it, just use
avr-gcc -mmcu=atmega169 -O2 -std=gnu99 -o file.elf

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <avr/sleep.h>
#include <avr/wdt.h>
#include <avr/signal.h>
#include <inttypes.h>
#include <stdio.h>
#include <string.h>

#include "avr_dtmf.h"
#ifdef __AVR_ATmega8__
#else
#endif

/*************************************************************************
 delay loop for small accurate delays: 16-bit counter, 4 cycles/loop
*************************************************************************/
static inline void _delayFourCycles(unsigned int __count)
{
  if ( __count == 0 )
    __asm__ __volatile__( "rjmp 1f\n 1:" );    // 2 cycles
  else
    __asm__ __volatile__ (
              "1: sbiw %0,1" "\n\t"
              "brne 1b"                              // 4 cycles/loop
              : "=w" (__count)
              : "0" (__count)
              );
}

#define sbiBF(port,bit)  (port |= (1<<bit))   //set bit in port
#define cbiBF(port,bit)  (port &= ~(1<<bit))  //clear bit in port

/*************************************************************************
delay for a minimum of <us> microseconds
the number of loops is calculated at compile-time from MCU clock frequency
*************************************************************************/
#define delay(us)  _delayFourCycles( (uint16_t)( (((uint32_t) 1*(XTAL/4000) )*us)/1000 ))

// The structure that keeps track of each note.  The image is bugs
// climbing the sine wave hill.  10 Bytes.
typedef struct _TimerBug {
  uint32_t  fraction;      // the divider count
  uint32_t  frequency;     // The frequency we're playing
  uint8_t   phaseCount;    // where we are in the sine table
  uint8_t   whole;         // whole number increment for phase.
} TimerBug;

TimerBug bugs[2];
short    rowFrequency[] = { 697, 770, 852, 941 };
short    colFrequency[] = { 1209, 1336, 1477, 1633 };
uint8_t  keys[]         = { '1','2','3','A','4','5','6','B','7','8','9','C','*','0','#','D' };

uint8_t sinTable[] = {
  128, 134, 140, 146, 152, 159, 165, 171,
  176, 182, 188, 193, 199, 204, 209, 213,
  218, 222, 226, 230, 234, 237, 240, 243,
  246, 248, 250, 252, 253, 254, 255, 255,
  255, 255, 255, 254, 253, 252, 250, 248,
  246, 243, 240, 237, 234, 230, 226, 222,
  218, 213, 209, 204, 199, 193, 188, 182,
  176, 171, 165, 159, 152, 146, 140, 134,
  128, 122, 116, 110, 104, 97, 91, 85,
  80, 74, 68, 63, 57, 52, 47, 43,
  38, 34, 30, 26, 22, 19, 16, 13,
  10, 8, 6, 4, 3, 2, 1, 1,
  0, 1, 1, 2, 3, 4, 6, 8,
  10, 13, 16, 19, 22, 26, 30, 34,
  38, 43, 47, 52, 57, 63, 68, 74,
  80, 85, 91, 97, 104, 110, 116, 122,
};

// Constants
// Clock prescaler.  Multiply by two for phase/frequency correct PWM modes.
static const uint8_t  prescale   = 2;
// Frequency in mhz
static const uint8_t  mhz        = (uint8_t)(XTAL / 1000000);
// Top value for the count
static const uint16_t top        = 256;
// Multiply the frequency by this and divide by XTAL to get phase increment.
static uint32_t multiplier       = 0;

// Synchronizes the wait for the next value
volatile uint8_t  syncflag       = 0;

// sizeof(sinTable) should work, but doesn't
const uint32_t samples           = 128;

// Function prototypes
void dialDigit (uint8_t digit);
void playNotes (uint16_t f1, uint16_t f2, short durationMS);


/*****************************************************************************
 *
 *   Function name : main
 *
 *   Returns :       None
 *
 *   Parameters :    None
 *
 *   Purpose :       Contains the main loop of the program
 *
 *****************************************************************************/
int main(void)
{
  // Initial state variables
  char *phoneNumber = "3426663#";

  // Program initalization
  Initialization();

  for (uint8_t i=0; i < strlen(phoneNumber); ++i) {
    dialDigit(phoneNumber[i]);
    delay(32767);
    delay(32767);
    delay(32767);
  }

  return 0;
}

void dialDigit(uint8_t digit) {
  for (uint8_t j=0; j < sizeof(keys); ++j) {
    if (digit == keys[j]) {
      uint8_t row = j >> 2;
      uint8_t col = j & 3;
      playNotes(rowFrequency[row], colFrequency[col], 100);
      return;
    }
  }
}

/*****************************************************************************
 *
 *   Function name : Initialization
 *
 *   Returns :       None
 *
 *   Parameters :    None
 *
 *   Purpose :       Initializate the different modules
 *****************************************************************************/
void Initialization(void)
{
  cli();
  wdt_disable();

  CLKPR = (1<<CLKPCE);        // set Clock Prescaler Change Enable

  // set prescaler = 8, Inter RC 8Mhz / 8 = 1Mhz
  //CLKPR = (1<<CLKPS1) | (1<<CLKPS0);
  CLKPR = 0; // set 8mHz

  // Disable Analog Comparator (power save)
  ACSR = (1<<ACD);

  // Disable Digital input on PF0-2 (power save)
  DIDR1 = (7<<ADC0D);

  // Set up timer 1
  sbiBF(DDRB, 5);               // set OC1A as output
  sbiBF(PORTB, 5);              // set OC1A high
  ICR1   = top - 1;             // TOP for phase/freq correct mode
  // Set normal pwm mode, oc1a on, oc1b off, bottom 2 bits of WGM 8
  TCCR1A = (3 << COM1A0) | (0 << COM1B0) | (0 << WGM10);
  // disable noise cancel, edge select, top 2 bits of wgm 8, no clock
  TCCR1B = (0 << ICNC1)  | (0 << ICES1)  | (2 << WGM12) | (0 << CS10);
  // Enable overflow interrupt, to synchronize calculating the next value
  TIMSK1 |= (1 << TOIE1);
  sei();

  //LCD_Init();                 // initialize the LCD
  multiplier       = samples * prescale * top;
}

/*****************************************************************************
 *
 *   Function name : Playnotes
 *
 *   Returns :       None
 *
 *   Parameters :    short f1, short t1, short f2, short t2
 *                   f's are in hz, t's are in ms.
 *
 *   Purpose :       Play polyphonic frequencies
 *
 *****************************************************************************/
void playNotes (uint16_t f1, uint16_t f2, short durationMS) {
  // Initialization: set syncflag to 0, OCR1A to 0, and start the clock.
  syncflag  = 0;
  OCR1A     = 0;

  short duration = ((long)durationMS * 1000 * mhz / (top * prescale));

  // Fill in the bugs
  bugs[0].whole      = (f1 * multiplier) / XTAL;
  bugs[0].frequency  = (f1 * multiplier) % XTAL;
  bugs[0].fraction   = 0;
  bugs[0].phaseCount = 0;

  bugs[1].whole      = (f2 * multiplier) / XTAL;
  bugs[1].frequency  = (f2 * multiplier) % XTAL;
  bugs[1].fraction   = 0;
  bugs[1].phaseCount = 0;

  TCNT1     = 1;
  TCCR1B   |= (1 << CS10);

  // start the loop
  while (1) {
    short total = 0;
    // Iterate over our bugs
    for (uint8_t i=0; i < 2; ++i) {
      TimerBug *bPtr = &bugs[i];

      // See if we're finished
      --duration;
      if (duration == 0)
    break;

      // Add the whole part
      bPtr->phaseCount += bPtr->whole;

      // Deal with the fraction.
      bPtr->fraction += bPtr->frequency;
      if (bPtr->fraction >= XTAL) {
    bPtr->fraction -= XTAL;
    ++bPtr->phaseCount;
      }

      // Ensure we're in range
      bPtr->phaseCount &= (samples - 1);

      // Update the sample
      total += sinTable[bPtr->phaseCount];
    }

    // Are we done?
    if (duration == 0)
      break;

    // divide by two to get a byte value
    total >>= 1;
    total &=  (top - 1);

    // And load it into the buffer register.
    OCR1A = total;
    // Wait for the next cycle
    syncflag = 0;
    while (syncflag == 0)
      set_sleep_mode(SLEEP_MODE_IDLE);
  }

  // Stop the timer.
  TCCR1B &= ~(7 << CS10);
}

// Handle Timer1 overflow, which marks the loading of the new OCR1A value,
// so we can start calculating the next one.
SIGNAL(SIG_OVERFLOW1) {
  syncflag = 1;
}

The .h file is very simple, just a few minimal defines:

#define BOOL    char

#define FALSE   0
#define TRUE    (!FALSE)

#define XTAL    8000000

void Initialization (void);

Personal tools