PWM for frequency output
From Wiki
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);
