-
clock, delay function, timer interrupt, and PWM
10/28/2017 at 04:28 • 3 commentsThe ATtiny9 has an internal oscillator of 8 MHz, which is enabled by default on reset, with a prescaler of 8, resulting in a CPU frequency of 1 MHz. The GNU compiler has a builtin delay function _delay_ms. To use this function, you have to define the CPU frequency with the F_CPU macro and then include util/delay.h. For 1 MHz it can delay up to 4.2 seconds.
Example for a 1 Hz blinker:
#define F_CPU 1000000UL #include <avr/io.h> #include <util/delay.h> int main(void) { DDRB |= 1 << PINB2; while (1) { PORTB &= ~(1 << PINB2); _delay_ms(500); PORTB |= 1 << PINB2; _delay_ms(500); } }
Internally it uses delay loops, so you can still do other things with the internal timers.
The accuracy of the 8 MHz oscillator is guaranteed to be within +/-10% with factory calibration, says the full datasheet, see chapter 17.4.1. Accuracy of Calibrated Internal Oscillator. It can be calibrated to +/-1% manually with the OSCCAL value. Unlike with the PIC microcontrollers, looks like the IDE has no integrated support to do the calibration and to save it in a protected flash location, but there are instructions how to do it, like this one. When I tested it at 20°C room temperature and with 5V power supply, the factory calibration was already better than 1%.
The _delay_ms function uses a delay loop, which has some disadvantages, e.g. if you need an exact time interval, you have to adjust the delay value depending on the rest of the program.
A better way to do the delay is by using a timer. The following program uses the 16 bit Timer0 and the capture compare functionality to generate an 1 kHz interrupt. The CPU clock is also changed to 8 MHz, so that you can do more things in the interrupt, if necessary:
#include <avr/io.h> #include <avr/interrupt.h> #include <stdint.h> volatile static uint16_t millis = 0; // interrupt for compare match SIGNAL (TIM0_COMPA_vect) { if (millis < 500) { PORTB &= ~(1 << PINB2); } else { PORTB |= 1 << PINB2; } millis++; if (millis == 1000) millis = 0; } int main(void) { // unprotect the CLKPSR register CCP = 0xd8; // set prescaler to 1 for 8 MHz CPU clock CLKPSR = 0; // configure pin B2 as output DDRB |= 1 << PINB2; // count up to 1000 for 1 kHz interrupts OCR0A = 1000; // clear timer on compare match (CTC) mode // clock source clk/8 TCCR0A = 0; TCCR0B = (1 << WGM02) | (1 << CS01); // enable interrupt on OCR0A match TIMSK0 = 1 << OCF0A; // clear all interrupt flags TIFR0 = 0xff; // global interrupt enable sei(); // the rest is done in the interrupt while(1) { } }
This needs about 3 mA (without the LED connected).
Finally you can use the PWM output for the blinking LED. For this connect it to PB0 and use this code:
#include <avr/io.h> #include <avr/sleep.h> int main(void) { // unprotect the CLKPSR register CCP = 0xd8; // set prescaler to 1 for 8 MHz CPU clock CLKPSR = 0; // configure pin B0 as output DDRB |= 1 << DDB0; // 1 HZ PWM frequency (8 MHz / 1024 / 2 - 1) OCR0A = 3905; // toggle OC0A on compare match // clear timer on compare match (CTC) mode // clock source clk/1024 TCCR0A = 1 << COM0A0; TCCR0B = (1 << WGM02) | (1 << CS00) | (1 << CS02); // go to sleep for the CPU set_sleep_mode(SLEEP_MODE_IDLE); sleep_enable(); sleep_mode(); }
Enabling the sleep mode at the end reduces the power consumption to about 1.3 mA (without the LED).
With a different prescaler this could be used as a simple high resolution, fixed frequency square wave generator. For example 1 kHz:
#include <avr/io.h> #include <avr/sleep.h> int main(void) { // unprotect the CLKPSR register CCP = 0xd8; // set prescaler to 1 for 8 MHz CPU clock CLKPSR = 0; // configure pin B0 as output DDRB |= 1 << DDB0; // 1 kHZ PWM frequency (8 MHz / 2 / 1000 - 1) OCR0A = 3999; // toggle OC0A on compare match // clear timer on compare match (CTC) mode // clock source clk/1 (no prescaler) TCCR0A = 1 << COM0A0; TCCR0B = (1 << WGM02) | (1 << CS00); // go to sleep for the CPU set_sleep_mode(SLEEP_MODE_IDLE); sleep_enable(); sleep_mode(); }