Check out Erik's project. He demonstrates using an external timer for his ATMega 168
microcontroller to achieve a more accurate timing rather than using the internal timer.
Instead of showing his output on an LCD, since he didn't have enough pins available for
the LCD's 8-bit interface, he chose to show the results through serial communication on
his computer.
I have a larger ATMEGA324P, but I wanted to try doing this with a smaller
ATMEGA168-20PU microcontroller. The original goal was just a timer, which
shows how to use a crystal and capacitors to make it be more accurate than
the internal timer. However, then I had to figure out how to get the fuses
to work, so I used a fuse calculator to get the ATMEGA168's fuses for my
10Mhz clock.
(http://www.engbedded.com/fusecalc/).
I then had to figure out how to change my USBTiny stuff in ATMEL Studio from
your ATMEGA324P to the ATMEGA168, which I did, and then add those fuse
comments. I added them in the comments of my C code.
Then I was going to output the clock on an LCD, but your demo only works for
8-bit bus, and I didn't have a freed up enough bus to do that. The ATMEGA only
has two full 8-bit buses, port B and D, and I need some of port B for the
crystal, and some of port D for interrupt for your LCD demo, so I couldn't
do it. So I tried my hand at figuring out RS-232.
So, I had a spare MAX232N chip sitting around and some 0.1uF caps and put
that all together.
/*
* atmega168_test.c
* Created: 12/1/2014 10:02:10 AM
* Author: Erik Vincent
*/
// Use this command for avrdude to work with 10Mhz crystal fuse on ATMEGA168
// -c usbtiny -p m168 -U flash:w:$(ProjectDir)Release\$(ItemFileName).hex:i -U lfuse:w:0xff:m -U hfuse:w:0xd7:m
// for this code, we are using the external 10 MHz crystal, so we have F_CPU defined as 10 million
#ifndef F_CPU
#define F_CPU 10000000UL
#endif
#include <avr/io.h>
#include <util/delay.h>
#include <string.h>
#include <avr/interrupt.h>
// define baud-rate of my serial port
#define BAUD 9600
#define MYUBRR F_CPU/16/BAUD-1
// set volatile memory to hold values that interrupt will be collecting
volatile int8_t the_time; // holds the actual "ticks" of the crystal, which will be 40 ticks a second
volatile int16_t the_day; // holds how many days have passed
volatile int8_t the_hour; // holds how many hours have passed
volatile int8_t the_minute; // holds how many minutes have passed
volatile int8_t the_second; // holds how many seconds have passed
// function to initialize my USART port on the ATMEGA168
void USART_Init(unsigned int ubrr)
{
// Set baud rate
UBRR0H = (unsigned char)(ubrr>>8);
UBRR0L = (unsigned char)ubrr;
// Enable receiver and transmitter
UCSR0B = (1<<RXEN0)|(1<<TXEN0);
// Set frame format: 8data, 1 stop bit
UCSR0C = (1<<UCSZ00) | (1 << UCSZ01);
}
// function to transmit/TX stuff out of the ATMEGA168 and into my laptop
void USART_Transmit(unsigned char data )
{
// Wait for empty transmit buffer
while ( !( UCSR0A & (1<<UDRE0)) );
// Put data into buffer, sends the data
UDR0 = data;
}
// function to transmit/write a whole null terminated string to serial port
void serial_write_str(const char* str)
{
int len = strlen(str); // get length of string
int i; // used in for next loop
for (i = 0; i < len; i++)
{
// transmit/write each character in string (character array), one at a time
USART_Transmit(str[i]);
}
}
// function to read a character, one at a time from the serial port
char USARTReadChar()
{
// Wait until a data is available
while(!(UCSR0A & (1<<RXC0)))
{
// Do nothing
}
// Now USART has got data from host and is available is buffer
return UDR0;
}
// function used to display timer info
void show_the_time(void)
{
char buff[16]; // needed a buffer to handle my output correctly
memset(&buff,16,0); // clear my buffer
serial_write_str("\r\n"); // return carriage
sprintf(buff, "Day: %03d", the_day); // display days as a 3 digit integer with trailing 0's
serial_write_str(buff); // write to serial port
memset(&buff,16,0); // clear my buffer
sprintf(buff, " %02d:", the_hour); // display hours as a 2 digit integer with trailing 0's
serial_write_str(buff); // write to serial port
memset(&buff,16,0); // clear my buffer
sprintf(buff, "%02d:", the_minute); // display minutes as a 2 digit integer with trailing 0's
serial_write_str(buff); // write to serial port
memset(&buff,16,0); // clear my buffer
sprintf(buff, "%02d", the_second); // display seconds as a 2 digit integer with trailing 0's
serial_write_str(buff); // write to serial port
serial_write_str("\r\n"); // return carriage
}
// function used to initialize crystal timer for interrupts
void init_crystal_timer(void)
{
TIMSK0 = _BV(OCIE0A); // Enable Interrupt TimerCounter0 Compare Match A (SIG_OUTPUT_COMPARE0A)
TCCR0A = _BV(WGM01); // Set 8 bit timer register counter 0, register a to Clear Timer on Compare (CTC) mode
TCCR0B = _BV(CS02) | _BV(CS00); // Set clock pre-scaler to clock/1024, so 1024 (10-bit) / clock (10,000,000 Hz), 0.0001024 seconds per tick
OCR0A = 244; // 0.0001024 * 244 ~= 0.025 SIG_OUTPUT_COMPARE0A will be triggered every 0.025 seconds, or 40 times per second.
sei(); // Sets up global interrupt flags
}
// function used to clear all the characters out of my buffer
void clear_buffer(char* str)
{
int len = strlen(str); // get length of string/buffer
int i; // used in for next loop
for (i = 0; i < len; i++)
{
// write 0's aka NULL characters to the whole buffer
str[i] = 0;
}
}
// main function
int main (void)
{
int8_t buffer_index = 0; // variable used to hold where I am in the character array/string buffer
char data; // variable used to hold the data character I am getting from the serial port
char charbuff[8]; // variable used to hold the data character in a character array, as to write out to serial port
char strbuff[32]; // variable used to capture 32 characters at a time coming in from my laptop's COM port
// Put an LED on PD4 (pin 6)
DDRD |= (1<<4); // PD4 as output
PORTD |= (1<<4); // Set high
USART_Init(MYUBRR); // initialize USART
init_crystal_timer(); // initialize crystal timer for interrupts
memset(&strbuff,32,0); // initialize string buffer
// main loop
while (1)
{
memset(&charbuff,8,0); // clear character buffer
data = USARTReadChar(); // Read character data from USART port
charbuff[0] = data; // put the character into the character buffer
serial_write_str(charbuff); // echo the character back to the laptop COM port
// If the character I received was a return/Enter character...
if(charbuff[0] == '\r')
{
// Check to see if my 32 character buffer starts with -s, and if so...
if((strbuff[0] == '-') && (strbuff[1] == 's'))
{
// show the timer
show_the_time();
}
else
{
// otherwise, write out what was typed into the buffer, out to the laptop COM port
serial_write_str("\r\n");
serial_write_str("You typed ");
serial_write_str(strbuff);
serial_write_str("\r\n");
}
// reset my buffer index
buffer_index = 0;
// clear my buffer of characters
clear_buffer(strbuff);
}
else
{
// else, if my character received was not a return/Enter character, put the character into string buffer
strbuff[buffer_index] = charbuff[0];
// and increment my buffer index by one for the next character to be put in
buffer_index++;
}
// if my buffer index has grown larger than what I can take in, which is 32...
if(buffer_index >= 32)
{
// let user know that's a bad thing they tried to do
serial_write_str("\r\n");
serial_write_str("Invalid Entry. Please try again.");
serial_write_str("\r\n");
// reset my buffer index
buffer_index = 0;
// clear my buffer of characters
clear_buffer(strbuff);
}
}
return 1;
}
// Interrupt for my crystal clock timer
ISR(TIMER0_COMPA_vect)
{
the_time++; // tick the time, which is 40 times a second, per crystal setup
// if the time has hit 40...
if(the_time >= 40)
{
// reset tick counter
the_time = 0;
// increment the second counter by 1
the_second++;
// blink my LED
PORTD ^= (1<<4);
}
// if the second counter has hit 60...
if(the_second >= 60)
{
// reset the second counter
the_second = 0;
// increment my minute counter by 1
the_minute++;
}
// if the minute counter has hit 60...
if(the_minute >= 60)
{
// reset the minute counter
the_minute = 0;
// increment my hour counter by 1
the_hour++;
}
// if the hour counter has hit 24...
if(the_hour >= 24)
{
// reset the hour counter
the_hour = 0;
// increment my day counter by 1
the_day++;
}
}