/* * Microcontroller "piano" demo using AVR timers. * * Similar to a piano, each key (button) produces a different tone, from * Middle-C, up to High-C. Each tone is at a frequency specified in the code * below. When a button is pressed, the corresponding tone is generated on * a piezo speaker and its associated LED is toggled. * * The eight LEDs are on PORTA, and eight switches on PORTC. * NB: both the switches and the LEDs on the STK500 are active-low. * * This program prescales the timer input frequency to 1MHz (1 million cycles/sec) * by prescaling the 8MHz clock by 8 to produce the 1MHz timer input. * * The timer is configured to automagically toggle (that is, to change state * from 0 to 1, or 1 to 0) an output pin when the timer counter reaches a * designated value set in the Output Compare Register (OCR) that is a function * of the desired tone frequency. * * For Timer1 (the 16-bit timer on the ATmega16), there are two possible output * channels, one each for OCR1A and OCR1B. In this program we only use OCR1B * (that is, Timer1 ChannelB). The timer is configured to toggle a * particular (fixed) output pin, namely PD4 (shorthand for PORTD pin 4). * * By changing the magnitude of the timer's TOP value, the time that it takes * for the timer to count up to its maximum value varies, producing different * output squarewave periods/frequencies. * * Last modified 2010-09-14 17:42 */ #include #if (F_CPU == 8000000) # define TIMER_HZ ((uint32_t)1000000) // timer clk speed after prescaling #else # error "MCU clock speed must be 8MHz for this program to work correctly." #endif /* * One-time initialization code. * * Initialize Timer1 for Waveform Generation (COM1B1), prescaled by 8 (CS11). * * For detailed information on the Timer Control registers for Timer1, see p110 * of the ATmega16 datasheet. WGMs are documented on p112. */ void init() { TCCR1A |= _BV(COM1B1); // toggle OCR1A when Timer1 reaches value in OCR1 TCCR1B |= _BV(CS11); // prescale the timer clock by 8 TCCR1A |= _BV(WGM10); // Waveform Generation Mode 9, OCR1A as TOP, TCCR1B |= _BV(WGM13); // update OCR1A when we reach BOTTOM value DDRA = 0xFF; // all eight LED pins are outputs PORTA = 0xFF; // initially set high (off) DDRC = 0x00; // all eight switch pins are inputs PORTC = 0xFF; // turn on the pullup resistors DDRD = _BV(PD4); // OC1B pin (output for waveform) an output } /* * Use input from switches (piano keys) to determine the tone frequency to play. * Use the frequency value to determine OCR1A (TOP) which sets the period of the * waveform, while OCR1B determines the duty cycle (50%). This is done by * setting OCR1B to be half of OCR1A. * * Q: How can the handling of PORTA be simplified in the code below? */ int main() { uint8_t switches; uint16_t freq; init(); while (1) { switches = PINC; switch (switches) { case 0b11111110: freq = 262; PORTA = 0b11111110; break; case 0b11111101: freq = 294; PORTA = 0b11111101; break; case 0b11111011: freq = 330; PORTA = 0b11111011; break; case 0b11110111: freq = 349; PORTA = 0b11110111; break; case 0b11101111: freq = 392; PORTA = 0b11101111; break; case 0b11011111: freq = 440; PORTA = 0b11011111; break; case 0b10111111: freq = 494; PORTA = 0b10111111; break; case 0b01111111: freq = 523; PORTA = 0b01111111; break; default: freq = 0; PORTA = ~0; break; } OCR1A = (TIMER_HZ/freq + 1)/2; OCR1B = OCR1A/2; } }