megaAVR® Oscillator Example Project
Objective
This page provides a simple project demonstrating dynamic adjustment of the System Clock Frequency via modification of the System Clock Prescaler Register (CLKPR) on megaAVR® devices. The code example runs on the ATmega328PB MCU.
The System Clock Source is set via configuration fuse bits to be the external 16MHz clock provided by the mEDBG chip (see connection diagram below).
The project configures the Timer/Counter1 module to operate in Clear-Timer-On-Compare (CTC) mode, and, on a period match, generates a tick interrupt every 100 mS. The timer clock source is configured to be SYS_CLK/64.
The Timer/Counter1 ISR toggles LED0, and increments a Counter. The main program loop monitors the Counter value and updates CLKPR value every 10 seconds to dynamically change the SYS_CLK frequency.
CLKPR is toggled between div/1 and div/4 setting, thereby changing the toggle (interrupt) interval from 100 ms to 400 mS respectively. This can be seen as the LED0 blink rate changes every 10 seconds.
Reference Material
ATmega328PB Xplained Mini
MPLAB® X IDE
MPLAB® XC8
Connection Diagram
The mEDBG chip controls the programming/debug interface, as well as supplying a 16 MHz clock when the Xplained board is connected via USB cable to a PC.
Procedure
Attach the ATmega328PB Xplained Mini board
Using a USB-A-male-to-Micro-B-male cable, attached the Xplained Mini to your computer. Start MPLAB X IDE®. If the board has been successfully enumerated, you should see the board image come up in Studio as shown in the accompanying image.
Create a New Project
In MPLAB X IDE, select File > New Project.
Choose Project
Select Microchip Embedded and then Standalone Project and then select Next.
Select Device
Use the pulldown or type into the boxes to indicate the Family, ATmega328PB, and Tool. Then Select Next.
Select Compiler
Select the XC8 Compiler and then Next.
Name the Project
Choose a name for the project, browse to a location to save it, and select Finish.
Create a New main.c file
Right-click on Source Files in the file explorer window and select New > avr-main.c and rename the File Name to main. Then select Finish.
Add the Code
Delete the contents of the newly created main.c file. Copy and Paste the following code into it.
* File: main.c
*
* Project: 8avr-mega-oscillator-example
*
* Description: Timer/Counter1 ISR used to toggle LED0, and increment a Counter.
*
* Main loop monitors the Counter. CLKPR is updated Every 10 sec
* to dynamically change the SYS_CLK frequency.
*
* CLKPR is toggled between div/1 and div/4 setting, which changes
* the toggle from 100ms to 400mS respectively.
*
* Date: 22-Aug-2017
* Revision:
* Author:
*
* Hardware: ATmega328PB Xplained Mini PCB Rev 3
*
* Clock: 16MHz (External - from EDBG IC)
* LED0: PB5
* SW0: PB7
*
* Fuses: (Programmed in Atmel Studio)
*
* NOTE: Target ATmega328PB CKSEL fuse bits are unchangeable on the
* Xplained Mini board within Atmel Studio.
*
* Ext: 0xFC
* High: 0xDF
* Low: 0xC0 (EXT CLK, Fast VDD rise, CLKDIV = 1)
*
******************************************************************************/
#include <stdint.h> // Std integral type definitions
#include <avr/io.h> // SFR definitions
#include <avr/interrupt.h> // ISR macros
#define LONG_TOGGLE_DURATION 25 // 10 second delay @ 400mS tick
#define SHORT_TOGGLE_DURATION 100 // 10 second delay @ 100mS tick
#define CLKPR_DIV_1 0x00 // CLKPR values
#define CLKPR_DIV_2 0x01
#define CLKPR_DIV_4 0x02
#define CLKPR_DIV_8 0x03
#define CLKPR_DIV_16 0x04
#define CLKPR_DIV_32 0x05
#define CLKPR_DIV_64 0x06
#define CLKPR_DIV_128 0x07
#define CLKPR_DIV_256 0x08
// variables used by main loop to track when to switch CLKPR value
enum {SHORT, LONG} toggleDurationState = SHORT;
volatile uint8_t toggleCounter = 0;
uint8_t toggleDurationTripThreshold = SHORT_TOGGLE_DURATION;
// CLKPR update function signature
void clkPrescaleSet(uint8_t divisionFactor);
// Define An Interrupt Handler for TIMER1_COMPA Interrupt (Nesting Disabled)
ISR(TIMER1_COMPA_vect, ISR_BLOCK){
PORTB ^= (1<<PORTB5); // Toggle LED0 (PB5)
toggleCounter++; // Increment counter
}
int main(void)
{
// Set up Timer/Counter1
TCCR1B |= (1 << WGM12); // Configure timer 1 for CTC mode
OCR1A = 25000; // Set CTC compare value to 10Hz (100mS) at
// 16MHz AVR clock , with a prescaler of 64
TIMSK1 |= (1 << OCIE1A); // Enable CTC interrupt
TCCR1B |= ((1 << CS10) | (1 << CS11)); // Start Timer/Counter1 at F_CPU/64
// Set LED as output
DDRB |= (1<<PORTB5); // Configure PB5 as digital output
PORTB &= ~(1<<PORTB5); // Set initial level for PB5
// enable interrupts
sei();
while (1)
{
switch(toggleDurationState){
case SHORT:
if(toggleCounter >= toggleDurationTripThreshold){
toggleCounter = 0;
clkPrescaleSet(CLKPR_DIV_4);
toggleDurationTripThreshold = LONG_TOGGLE_DURATION;
toggleDurationState = LONG;
}
break;
case LONG:
if(toggleCounter >= toggleDurationTripThreshold){
toggleCounter = 0;
clkPrescaleSet(CLKPR_DIV_1);
toggleDurationTripThreshold = SHORT_TOGGLE_DURATION;
toggleDurationState = SHORT;
}
break;
default:
break;
}
}
}
void clkPrescaleSet(uint8_t divisionFactor){
cli(); // disable interrupts
CLKPR = (1<<CLKPCE); // enable change of the CLKPSx bits
CLKPR = divisionFactor; // update the CLKPSx bits
sei(); // re-enable interrupts
}
Program the Device
At the top of the MPLAB X IDE, select the Make and Program device button.
Observe the Results
LED0 will light at a fixed frequency which will change every 10 seconds.
Conclusions
This project has provided an example of how to dynamically adjust the system clock frequency on the megaAVR® MCU.