Будильник на микроконтроллере AVR ATmega32

В этой статье мы рассмотрим схему простого будильника на микроконтроллере ATmega32 (семейство AVR). Для реализации этой идеи мы используем 16 битный таймер, присутствующий в данном микроконтроллере.

Будильник на микроконтроллере AVR ATmega32: внешний вид

Все цифровые часы имеют в своем составе кварцевый резонатор, который является их «сердцем». Кварцевые резонаторы присутствуют практически во всех системах реального времени. Подобные резонаторы генерируют временные импульсы, которые необходимы для точного вычисления времени. Существуют и другие способы точного задания временных интервалов, но все таки использование кварцевых резонаторов является наиболее предпочтительным для решения этой задачи. Поэтому и мы в рассматриваемой схеме будем подсоединять кварцевый резонатор к микроконтроллеру ATmega32 чтобы обеспечить точный расчет временных интервалов.

Необходимые компоненты

Аппаратное обеспечение

Микроконтроллер ATmega32
Источник питания с напряжением 5 Вольт
Программатор AVR-ISP, USBASP или другой подобный
JHD_162ALCD (ЖК дисплей 16×2)
Кварцевый резонатор 11.0592 МГц
Конденсатор 22 пФ (2 шт.)
Конденсатор 100 нФ (4 шт.)
Конденсатор 100 мкФ (соединенный по питанию)
Кнопка (4 шт.)
Трехконтактный переключатель (2 шт.)
Транзистор 2N2222
Звонок
Резистор 10 кОм (6 шт.)
Резистор 200 Ом

Программное обеспечение

Atmel Studio версии 6.1 (или выше)
Progisp или flash magic (необязательно)

Работа схемы

Схема устройства приведена на следующем рисунке.

Схема будильника на микроконтроллере AVR ATmega32

Для точного подсчета интервалов времени мы подсоединили к микроконтроллеру кварцевый резонатор на 11.0592 МГц. Теперь для того, чтобы отключить внутренний генератор микроконтроллера ATmega32 мы должны соответствующим образом сконфигурировать его «нижние» фьюзы (fuse bits). Но мы не должны трогать «верхние» фьюзы чтобы JTAG интерфейс микроконтроллера был доступен (активирован).

Чтобы сообщить микроконтроллеру о том, чтобы он отключил внутренний генератор и работал от внешнего кварцевого резонатора мы должны выполнить команду

LOW USE BYTE = 0xFF или 0b11111111.

В представленной схеме PORTB микроконтроллера ATmega32 соединен с портом данным жидкокристаллического (ЖК) дисплея. Если вы не хотите трогать фьюзы (FUSE BITS) микроконтроллера, то не используйте PORTC, поскольку PORTC содержит специальные функции (типы связи), которые можно деактивировать только с помощью изменения фьюзов.

В ЖК дисплее (если мы не хотим использовать черный цвет) можно задействовать только 14 его контактов: 8 контактов для передачи данных (7-14 или D0-D7), 2 контакта для подачи питания (1&2 или VSS&VDD или gnd&+5v), 3-й контакт для управления контрастностью, 3 контакта для управления (RS&RW&E).

В представленной схеме мы использовали только 2 контакта управления ЖК дисплея для лучшего понимания работы схемы. Бит контраста и READ/WRITE используются нечасто, поэтому они могут быть замкнуты на землю. Это обеспечивает ЖК дисплею максимальную контрастность и переводит его в режим чтения. Теперь нам всего лишь нужно контролировать контакты ENABLE и RS чтобы передавать на ЖК дисплей символы и данные. Также на нашем сайте вы можете прочитать более подробную статью о подключении ЖК дисплея к микроконтроллеру AVR ATmega32.

В схеме необходимо сделать следующие соединения с ЖК дисплеем:
PIN1 или VSS — земля
PIN2 или VDD или VCC — +5v питание
PIN3 или VEE — земля (обеспечивает максимальный контраст ЖК дисплею)
PIN4 или RS (Register Selection) – контакт PD6 микроконтроллера
PIN5 или RW (Read/Write) — земля (переводит ЖК дисплей в режим чтения что упрощает взаимодействие с ним для начинающих)
PIN6 или E (Enable) — контакт PD5 микроконтроллера
PIN7 или D0 — контакт PB0 микроконтроллера
PIN8 или D1 — контакт PB1 микроконтроллера
PIN9 или D2 — контакт PB2 микроконтроллера
PIN10 или D3 — контакт PB3 микроконтроллера
PIN11 или D4 — контакт PB4 микроконтроллера
PIN12 или D5 — контакт PB5 микроконтроллера
PIN13 или D6 — контакт PB6 микроконтроллера
PIN14 или D7 — контакт PB7 микроконтроллера

В схеме мы использовали 8-битную связь (D0-D7) ЖК дисплея с микроконтроллером, хотя можно было ограничиться и 4-битной – но в этом случае код программы стал бы немного сложнее. Таким образом, мы использовали 10 контактов ЖК дисплея, 8 из которых будут использоваться для передачи данных и 2 для управления.

Первый переключатель в схеме используется для переключения между режимами часов и будильника. Если на контакте низкий уровень, мы можем настраивать время срабатывания будильника при помощи нажатия кнопок, присутствующих в схеме. Если на контакте высокий уровень, то с помощью кнопок мы настраиваем текущее время (время часов). Первая кнопка на схеме предназначена для инкрементирования минут, вторая – для декрементирования минут, третья – для инкрементирования часов, четвертая – для декрементирования часов.

Конденсаторы, включенные параллельно кнопкам, предназначены для устранения эффектов биений, возникающих при нажатии кнопок. Если их устранить, то однократное нажатие кнопок будут иногда распознаваться микроконтроллером как многократные. Резисторы, подсоединенные к выводам микроконтроллера, предназначены для ограничения тока при нажатии кнопок.

Если кнопка нажата, то на соответствующий контакт микроконтроллера подается «земля» и таким образом микроконтроллер распознает нажатие кнопки и предпринимает соответствующие действия

Перед началом написания программы для микроконтроллера мы должны правильным образом настроить регистры таймера для обеспечения точного подсчета временных интервалов. Частота кварцевого резонатора у нас 11059200 Гц, если мы разделим ее на 1024, то получим 10800. Таким образом, для каждой секунды мы будем иметь 10800 импульсов. Поэтому мы должны использовать коэффициент деления предделителя равный 1024 чтобы получить частоту счетного регистра равную 10800 Гц. Для подсчета времени мы будем использовать 16 битный таймер микроконтроллера ATmega32. Данный таймер способен использовать функцию сравнения, которая заключается в непрерывном (каждый машинный цикл) сравнении содержимого счетного регистра таймера/счетчика с числом, находящемся в регистре сравнения. При совпадении содержимого этих регистров устанавливается флаг соответствующего прерывания, а также могут выполняться другие действия.

Поскольку в регистр сравнения мы будем записывать число 10800 это значит что с высокой точностью у нас каждую секунду будет генерироваться прерывание от таймера – это обстоятельство мы можем использовать для точного подсчета времени.

Рассмотрим теперь необходимую нам конфигурацию регистров таймера.

Регистры таймера микроконтроллера AVR ATmega32

BROWN (коричневый, WGM10-WGM13): эти биты задают режим функционирования таймера в соответствии с нижеприведенной таблицей.

Биты для установки режима таймера микроконтроллера AVR ATmega32

Поскольку мы будем использовать режим сравнения (режим CTC — Clear Timer Counter) значения таймера с числом, находящимся в байте OCR1A, мы должны установить WGM12 в 1, а остальные эти биты не трогать (по умолчанию в них при инициализации микроконтроллера записывается 0).

RED (красный, CS10,CS11,CS12): эти биты используются для задания коэффициента деления предделителя и соответствующего конфигурирования счетного регистра (установки частоты его работы).

Биты для установки коэффициента деления предделителя таймера микроконтроллера AVR ATmega32

Поскольку мы будем использовать коэффициент деления предделителя равный 1024 мы должны установить CS12 и CS10 в 1.

Другой регистр, который потребуется нам для написания программы – это TIMSK (регистр маски прерываний от таймеров, осуществляет разрешение/запрещение прерываний от таймера).

Регистр маски прерываний таймера микроконтроллера AVR ATmega32

GREEN (зеленый, OCIE1A): этот бит должен быть установлен для разрешения прерывания при совпадении значения счетного регистра с числом, хранящимся в OCR1A (мы будем записывать туда число 10800).

Регистр сравнения микроконтроллера AVR ATmega32

Исходный код программы на языке С (Си) с пояснениями

Программа для рассматриваемой нами схемы будильника на микроконтроллере AVR ATmega32 представлена следующим фрагментом кода на языке С (Си). Комментарии к коду программу поясняют принцип работы отдельных команд.

#include <avr/io.h> // заголовок чтобы задействовать контроль данных на контактах
#define F_CPU 1000000 // задание тактовой частоты микроконтроллера #include <util/delay.h> // заголовок чтобы задействовать функции задержки в программе #define E 5
// задействуем 5-й контакт PORTD (“enable”), поскольку он соединен с контактом “enable” ЖК дисплея
#define RS 6
// задействуем выбор регистра (“registerselection”) на 6-м контакте PORTD, поскольку он соединен с контактом RS ЖК дисплея
void send_a_command(unsigned char command);
void send_a_character(unsigned char character);
void send_a_string(char *string_of_characters); ISR(TIMER1_COMPA_vect);
static volatile int SEC =0;//переменная для хранения числа секунд
static volatile int MIN =0;// переменная для хранения числа минут
static volatile int HOU =0;// переменная для хранения числа часов int main(void)
{
DDRA = 0b11000000;// устанавливаем на вывод только pin7 и pin8 порта A DDRD = 0xFF; // порт D конфигурием на вывод данных _delay_ms(50);//задержка 50ms

DDRB = 00FF;//устанавливаем portB на вывод данных

TCCR1B |=(1<<CS12)|(1<<CS10)|(1<<WGM12); // устанавливаем предделитель и режим CTC (режим сравнения)

OCR1A=10800;//записываем в OCR1A число 10800 чтобы получать точно каждую секунду прерывание от таймера
sei();// общее разрешение прерываний

TIMSK |=(1<<OCIE1A);//разрешаем прерывание в режиме сравнения

char SHOWSEC [2];//символьный массив для отображения секунд на экране ЖК дисплея

char SHOWMIN [2];// символьный массив для отображения минут на экране ЖК дисплея

char SHOWHOU [2];// символьный массив для отображения часов на экране ЖК дисплея

int ALSEC = 0;//переменная для хранения числа секунд будильника

int ALMIN = 0;// переменная для хранения числа минут будильника

int ALHOU = 0;// переменная для хранения числа часов будильника char SHOWALSEC [2];// символьный массив для отображения секунд будильника на экране ЖК дисплея

char SHOWALMIN [2];// символьный массив для отображения минут будильника на экране ЖК дисплея

char SHOWALHOU [2];// символьный массив для отображения часов будильника на экране ЖК дисплея

send_a_command(0x01); //очистить экран 0x01 = 00000001

_delay_ms(50); send_a_command(0x38);// сообщаем ЖК дисплею что мы будем использовать 8 битный режим передачи данных/команд
_delay_ms(50);
send_a_command(0b00001111);// включаем курсор и мигание курсора на ЖК дисплее

while(1)
{

itoa(HOU/10,SHOWHOU,10); //преобразование целого числа (десятичной части числа часов) в строку, 10 – десятичная система счисления

send_a_string(SHOWHOU);// отображение на ЖК дисплее числа часов // отображение десятичной части числа часов (первого разряда числа часов)
itoa(HOU%10,SHOWHOU,10);

send_a_string(SHOWHOU); // отображение единичной части числа часов (второго разряда числа часов)
send_a_string (":");//отображение символа ":"

send_a_command(0x80 + 3);// переводим курсор на 4 позицию itoa(MIN/10,SHOWMIN,10); //преобразование целого числа (десятичной части числа минут) в строку, 10 – десятичная система счисления

send_a_string(SHOWMIN); // отображение десятичной части числа минут (первого разряда числа минут)

itoa(MIN%10,SHOWMIN,10);

send_a_string(SHOWMIN); // отображение единичной части числа минут (первого разряда числа минут)

send_a_command(0x80 + 5); // переводим курсор на 6 позицию

send_a_string (":");

send_a_command(0x80 + 6);// переводим курсор на 7 позицию

if (bit_is_set(PINA,5)) // если на контакте будильника высокий уровень

{ send_a_string(" ALM:ON "); //отображение строки " ALM:ON "

if ((ALHOU==HOU)&(ALMIN==MIN)&(ALSEC==SEC))//alarm minute=min //and alarm hours= time hours and alarm seconds= time seconds – сравнение времени будильника со значением часов

{

PORTA|=(1<<PINB7);//включаем звонок

} }

if (bit_is_clear(PINA,5))// если на контакте будильника низкий уровень

{ send_a_string(" ALM:OFF");//показываем строку что будильник отключен PORTA&=~(1<<PINB7);//выключаем звонок

}

send_a_command(0x80 + 0x40 + 0);// перемещаем курсор на нулевую позицию 2 строки

send_a_string ("ALARM:");//отображение строки "ALARM:" send_a_command(0x80 + 0x40 + 7);// перемещаем курсор на восьмую позицию 2 строки

// отображение числа часов

itoa(ALHOU/10,SHOWALHOU,10);

send_a_string(SHOWALHOU);

itoa(ALHOU%10,SHOWALHOU,10);

send_a_string(SHOWALHOU);

send_a_command(0x80 + 0x40 +9);

send_a_string (":");

send_a_command(0x80 + 0x40 +10);

// отображение числа минут

itoa(ALMIN/10,SHOWALMIN,10);

send_a_string(SHOWALMIN);

itoa(ALMIN%10,SHOWALMIN,10);

send_a_string(SHOWALMIN);

send_a_command(0x80 + 0x40+ 12);

send_a_string (":");

send_a_command(0x80 + 0x40+ 13);

// отображение числа секунд

itoa(ALSEC/10,SHOWALSEC,10);

send_a_string(SHOWALSEC);

itoa(ALSEC%10,SHOWALSEC,10);

send_a_string(SHOWALSEC);

send_a_command(0x80 + 0);

send_a_command(0x80 + 0);// перемещаем курсор на нулевую позицию

if (bit_is_set(PINA,4)) // если переключатель установлен в положение настройки времени
{
if (bit_is_clear(PINA,0)) //если кнопка 1 нажата
{
if (MIN<60)
{
MIN++;// если число минут меньше 60 то инкрементировать число минут на 1
_delay_ms(220);
}
if (MIN==60)
{
if (HOU<24)
{
HOU++;// если число минут=60, кнопка нажата и число часов меньше 24, то инкрементировать число минут на 1
}
MIN=0;// если число минут=60 то обнулить число минут
_delay_ms(220);
}
}
if (bit_is_clear(PINA,1))
{
if (MIN>0)
{
MIN--; // если вторая кнопка нажата и число минут больше 0, то уменьшить число минут на 1
_delay_ms(220);
}
}
if (bit_is_clear(PINA,2))
{
if (HOU<24)
{
HOU++; //если третья кнопка нажата и число часов меньше 24, то увеличить число часов на 1
}
_delay_ms(220); if (HOU==24)
{
HOU=0;//если число часов равно 24, то обнулить число часов
}
} if (bit_is_clear(PINA,3))
{
if (HOU>0)
{
HOU--;//если четвертая кнопка нажата и число часов больше 0, то уменьшить число часов на 1
_delay_ms(220);
}
}
} if (bit_is_clear(PINA,4))//если выбран режим настройки будильника
{
if (bit_is_clear(PINA,0))
{
if (ALMIN<60)
{
ALMIN++;
_delay_ms(220);
}
if (ALMIN==60)
{
if (ALHOU<24)
{
ALHOU++;
}
ALMIN=0;
_delay_ms(220);
}
} if (bit_is_clear(PINA,1))
{
if (ALMIN>0)
{ ALMIN--;
_delay_ms(220);
}
} if (bit_is_clear(PINA,2))
{
if (ALHOU<24)
{
ALHOU++;
}
_delay_ms(220);
if (ALHOU==24)
{
ALHOU=0;
}
} if (bit_is_clear(PINA,3))
{
if (ALHOU>0)
{
ALHOU--;
_delay_ms(220);
}
}
}
}
}

// все то же самое, только теперь для режима настройки времени

ISR(TIMER1_COMPA_vect) //цикл который выполняется до тех пор пока значения в таймере не будет равно сравниваемому значению
{
if (SEC<60)
{SEC++;}
if (SEC==60)
{
if (MIN<60)
{MIN++;}
SEC=0;
}
if (MIN==60)
{
if (HOU<24)
{HOU++;}
MIN=0;
}
if (HOU==24)
{HOU=0;}
}

void send_a_command(unsigned char command)
{
PORTA = command;
PORTD &= ~ (1<<RS); // устанавливаем RS в 0 чтобы сообщить ЖК дисплею что мы будем передавать команду
PORTD |= 1<<E; // сообщаем ЖК дисплею чтобы он принял команду/данные
_delay_ms(50);
PORTD &= ~1<<E;// сообщаем ЖК дисплею что мы закончили передачу данных
PORTA= 0;
} void send_a_character(unsigned char character)
{
PORTA= character;
PORTD |= 1<<RS;// сообщаем ЖК дисплею что мы будем передавать данные (не команду)
PORTD |= 1<<E;// сообщаем ЖК дисплею чтобы он начал прием данных
_delay_ms(50);
PORTD &= ~1<<E;// сообщаем ЖК дисплею что мы закончили передачу данных
PORTA = 0;
}
}
void send_a_string(char *string_of_characters)
{
while(*string_of_characters > 0)
{
send_a_character(*string_of_characters++);
}
}

Теперь код программы без комментариев.

#include <avr/io.h>
#define F_CPU 11059200
#include <util/delay.h>
#include <stdlib.h>
#include <avr/interrupt.h> #define enable 5
#define registerselection 6 void send_a_command(unsigned char command);
void send_a_character(unsigned char character);
void send_a_string(char *string_of_characters); ISR(TIMER1_COMPA_vect); static volatile int SEC =0;
static volatile int MIN =0;
static volatile int HOU =0; int main(void)
{
DDRA = 0b11000000;
DDRB = 0xFF;
DDRD = 0xFF;
TCCR1B |=(1<<CS12)|(1<<CS10)|(1<<WGM12);
OCR1A=10800;
sei();
TIMSK |=(1<<OCIE1A);
char SHOWSEC [2];
char SHOWMIN [2];
char SHOWHOU [2];
int ALSEC = 0;
int ALMIN = 0;
int ALHOU = 0;
char SHOWALSEC [2];
char SHOWALMIN [2];
char SHOWALHOU [2];
send_a_command(0x01); //Clear Screen 0x01 = 00000001
_delay_ms(50);
send_a_command(0x38);
_delay_ms(50);
send_a_command(0b00001111);
_delay_ms(50);

while(1)
{
itoa(HOU/10,SHOWHOU,10);
send_a_string(SHOWHOU);
itoa(HOU%10,SHOWHOU,10);
send_a_string(SHOWHOU);
send_a_string (":");
send_a_command(0x80 + 3); itoa(MIN/10,SHOWMIN,10);
send_a_string(SHOWMIN);
itoa(MIN%10,SHOWMIN,10);
send_a_string(SHOWMIN);
send_a_command(0x80 + 5);
send_a_string (":");
send_a_command(0x80 + 6);
itoa(SEC/10,SHOWSEC,10);
send_a_string(SHOWSEC);
itoa(SEC%10,SHOWSEC,10);
send_a_string(SHOWSEC);
if (bit_is_set(PINA,5))
{
send_a_string(" ALM:ON ");
if ((ALHOU==HOU)&(ALMIN==MIN)&(ALSEC==SEC))
{
PORTA|=(1<<PINB7);
}
}
if (bit_is_clear(PINA,5))
{
send_a_string(" ALM:OFF");
PORTA&=~(1<<PINB7);
}
send_a_command(0x80 + 0x40 + 0);

send_a_string ("ALARM:");
send_a_command(0x80 + 0x40 + 7);
itoa(ALHOU/10,SHOWALHOU,10);
send_a_string(SHOWALHOU);
itoa(ALHOU%10,SHOWALHOU,10);
send_a_string(SHOWALHOU);
send_a_command(0x80 + 0x40 +9);
send_a_string (":");
send_a_command(0x80 + 0x40 +10); itoa(ALMIN/10,SHOWALMIN,10);
send_a_string(SHOWALMIN);
itoa(ALMIN%10,SHOWALMIN,10);
send_a_string(SHOWALMIN);
send_a_command(0x80 + 0x40+ 12);
send_a_string (":");
send_a_command(0x80 + 0x40+ 13);
itoa(ALSEC/10,SHOWALSEC,10);
send_a_string(SHOWALSEC);
itoa(ALSEC%10,SHOWALSEC,10);
send_a_string(SHOWALSEC);
send_a_command(0x80 + 0); if (bit_is_set(PINA,4))
{
if (bit_is_clear(PINA,0))
{
if (MIN<60)
{
MIN++;
_delay_ms(220);
}
if (MIN==60)
{
if (HOU<24)
{
HOU++;
}
MIN=0;
_delay_ms(220);
}
}
if (bit_is_clear(PINA,1))
{
if (MIN>0)
{
MIN--;
_delay_ms(220);
}
}
if (bit_is_clear(PINA,2))
{
if (HOU<24)
{
HOU++;
}
_delay_ms(220);
if (HOU==24)
{
HOU=0;
}
}
if (bit_is_clear(PINA,3))
{
if (HOU>0)
{
HOU--;
_delay_ms(220);
}
}
}
if (bit_is_clear(PINA,4))
{
if (bit_is_clear(PINA,0))
{
if (ALMIN<60)
{
ALMIN++;
_delay_ms(220);
}
if (ALMIN==60)
{
if (ALHOU<24)
{
ALHOU++;
}
ALMIN=0;
_delay_ms(220);
}
}
if (bit_is_clear(PINA,1))
{
if (ALMIN>0)
{
ALMIN--;
_delay_ms(220);
}
}
if (bit_is_clear(PINA,2))
{
if (ALHOU<24)
{
ALHOU++;
}
_delay_ms(220);
if (ALHOU==24)
{
ALHOU=0;
}
}
if (bit_is_clear(PINA,3))
{
if (ALHOU>0)
{
ALHOU--;
_delay_ms(220);
}
}
}
}
}

ISR(TIMER1_COMPA_vect)
{
if (SEC<60)
{
SEC++;
}
if (SEC==60)
{
if (MIN<60)
{
MIN++;
}
SEC=0;
}
if (MIN==60)
{
if (HOU<24)
{
HOU++;
}
MIN=0;
}
if (HOU==24)
{
HOU=0;
} }

void send_a_command(unsigned char command)
{
PORTB = command;
PORTD &= ~ (1<<registerselection);
PORTD |= 1<<enable;
_delay_ms(3);
PORTD &= ~1<<enable;
PORTB = 0xFF;
void send_a_character(unsigned char character)
{
PORTB = character;
PORTD |= 1<<registerselection;
PORTD |= 1<<enable;
_delay_ms(3);
PORTD &= ~1<<enable;
PORTB = 0xFF;
}
void send_a_string(char *string_of_characters)
{
while(*string_of_characters > 0)
{
send_a_character(*string_of_characters++);
}
}

Видео, демонстрирующее работу схемы



Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *