В 1950-х годах в мире электроники произошла настоящая революция, характеризующаяся стремительным внедрением цифровой электроники во все сферы жизни человечества. Даже будильники, которые до этого времени были преимущественно механические, стали стремительно заменяться на электронные. И в данной статье мы рассмотрим создание электронного будильника на основе микроконтроллера PIC.
Отображение и установку времени в нашем проекте будильника мы будем производить с помощью ЖК дисплея 16x2. Установку времени срабатывания будильника мы будем производить с помощью кнопок. Точное время мы будем считывать из модуля часов реального времени DS3231, подключение которого к микроконтроллеру PIC мы рассматривали в этой статье. Если вы начинающий в изучении микроконтроллеров PIC, то для лучшего понимания материала данной статьи рекомендуем ознакомиться со статьями про подключение ЖК дисплея к микроконтроллеру PIC и использованию интерфейса I2C в данном микроконтроллере.
Также на нашем сайте мы рассматривали проекты будильников на основе других электронных плат:
- будильник на микроконтроллере AVR ATmega32;
- часы реального времени с будильником на основе Arduino;
- будильник на Raspberry Pi и модуле DS1307;
- говорящий будильник на Raspberry Pi в стиле "Железного человека".
Необходимые компоненты
- Микроконтроллер PIC16F877A (купить на AliExpress).
- Модуль часов реального времени DS3231 (купить на AliExpress).
- ЖК дисплей 16х2 (купить на AliExpress).
- Программатор PICkit 3 (купить на AliExpress).
- Кварцевый генератор 20 МГц (купить на AliExpress).
- Конденсаторы 22 пФ (2 шт.) (купить на AliExpress).
- Резисторы 4,7 кОм (2 шт.), 10 кОм (купить на AliExpress).
- Потенциометр 10 кОм (купить на AliExpress).
- Кнопки – 5 шт.
- Зуммер (купить на AliExpress).
- Источник напряжения питания 5V.
- Светодиод (купить на AliExpress).
- Макетная плата – 2 шт.
- Соединительные провода.
Схема проекта
Схема будильника на основе микроконтроллера PIC представлена на следующем рисунке.
5 кнопок в схеме используются для установки времени срабатывания будильника. Один контакт кнопок подключен к земле (общему проводу), а второй – к контактам порта PORTB микроконтроллера. Зуммер, подающий сигнал срабатывания будильника, подключен к PORT S. Точное время хранится в модуле DS3231 и оттуда передается в микроконтроллер PIC по интерфейсу I2C, соответственно, линии SCL и SDA модуля DS3231 подключены к линиям SCL и SDA микроконтроллера. ЖК дисплей подключен к PORTD микроконтроллера.
Внешний вид собранной на двух макетных платах конструкции проекта показан на следующем рисунке.
Объяснение программы будильника
Полный код программы приведен в конце статьи, здесь же мы кратко рассмотрим его основные фрагменты.
Кроме текста программы проект также содержит библиотеки для работы с ЖК дисплеем, интерфейсом I2C и модулем часов реального времени. Все эти файлы в виде ZIP архива можно скачать по следующей ссылке. После этого их можно открыть с помощью программы MPLABX.
Первым делом в программе настроим биты конфигурации и дадим используемым контактам микроконтроллера осмысленные имена.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
//Define the LCD pins #define RS RD2 //Reset pin of LCD #define EN RD3 //Enable pin of LCD #define D4 RD4 //Data bit 0 of LCD #define D5 RD5 //Data bit 1 of LCD #define D6 RD6 //Data bit 2 of LCD #define D7 RD7 //Data bit 3 of LCD //Define Buttons #define MB RB1 //The middle button #define LB RB0 //Left button #define RB RB2 //Right button #define UB RB3 //Upper Button #define BB RB4 //Bottom button //Define Buzz #define BUZZ RD1 //Buzzer is connected to RD1 |
Далее внутри функции main мы зададим режимы работы используемых контактов (на ввод или вывод данных). Контакты port B сконфигурируем для режима работы с внутренними подтягивающими резисторами – это предотвратит от ложного срабатывания кнопки, подключенные к ним.
1 2 3 4 |
TRISD = 0x00; //Make Port D pins as outptu for LCD interfacing TRISB = 0xFF; //Switchs are declared as input pins OPTION_REG = 0b00000000; //Enable pull up Resistor on port B for switchs BUZZ = 0; //Turn of buzzer |
Далее инициализируем ЖК дисплей и связь по протоколу I2C с частотой 100 кГц (поскольку модуль часов реального времени работает на этой частоте). Мы это делаем очень просто, с помощью вызова соответствующих функций поскольку в программе мы подключили заголовочные файлы библиотек для работы с ЖК дисплеем и интерфейсом I2C.
1 2 |
Lcd_Start(); // Initialize LCD module I2C_Initialize(100); //Initialize I2C Master with 100KHz clock |
Следующая строчка используется для первоначальной установки времени и даты в модуле часов реального времени, после этого ее можно удалить (закомментарить) иначе при каждом последующем запуске программы время и дата будут устанавливаться в модуле снова и снова.
1 2 |
//Remove the below line once time and date is set for the first time. Set_Time_Date(); //set time and date on the RTC module |
Далее покажем на экране ЖК дисплея приветственное сообщение.
1 2 3 4 5 6 7 |
//Give an intro message on the LCD Lcd_Clear(); Lcd_Set_Cursor(1,1); Lcd_Print_String("Alarm Clock"); Lcd_Set_Cursor(2,1); Lcd_Print_String(" -Circuit Digest"); __delay_ms(1500); |
Затем внутри цикла while мы будем считывать текущие дату и время из модуля часов реального времени.
1 |
Update_Current_Date_Time(); //Read the current date and time from RTC module |
Затем разделим значения времени на отдельные символы чтобы в последующем их можно было отобразить на экране ЖК дисплея.
1 2 3 4 5 6 7 |
//Split the into char to display on lcd char sec_0 = sec % 10; char sec_1 = (sec / 10); char min_0 = min % 10; char min_1 = min / 10; char hour_0 = hour % 10; char hour_1 = hour / 10; |
Затем обновим информацию на экране ЖК дисплея. Текущее время будет отображаться на первой строчке дисплея, а время срабатывания будильника – на второй.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
//Display the Current Time on the LCD screen Lcd_Clear(); Lcd_Set_Cursor(1, 1); Lcd_Print_String("TIME: "); Lcd_Print_Char(hour_1 + '0'); Lcd_Print_Char(hour_0 + '0'); Lcd_Print_Char(':'); Lcd_Print_Char(min_1 + '0'); Lcd_Print_Char(min_0 + '0'); Lcd_Print_Char(':'); Lcd_Print_Char(sec_1 + '0'); Lcd_Print_Char(sec_0 + '0'); //Display the Date on the LCD screen Lcd_Set_Cursor(2, 1); Lcd_Print_String("Alarm: "); Lcd_Print_Char(alarm_val[0] + '0'); Lcd_Print_Char(alarm_val[1] + '0'); Lcd_Print_Char(':'); Lcd_Print_Char(alarm_val[2] + '0'); Lcd_Print_Char(alarm_val[3] + '0'); |
Пользователю, чтобы установить время срабатывания будильника, необходимо будет нажимать на среднюю кнопку. При нажатии этой кнопки будильник переходит в режим установки времени – для этого мы переключаем состояние переменной set_alarm. Повторное нажатие средней кнопки служит подтверждением того, что время установлено, и приводит к выводу будильника из режима установки времени.
1 2 3 4 5 6 7 8 9 |
//Use middle button to check if alarm has to be set if (MB == 0 && set_alarm == 0) { //If middle button is pressed and alarm is not turned on while (!MB); //Wait till button is released set_alarm = 1; //start setting alarm value } if (MB == 0 && set_alarm == 1) { //If middle button is pressed and alarm is not turned off while (!MB); //Wait till button is released set_alarm = 0; //stop setting alarm value } |
Если пользователь нажал среднюю кнопку это значит что он пытается установить время срабатывания будильника. При этом программа переходит в режим установки времени с помощью фрагмента кода программы, представленного выше. Если в этом режиме пользователь нажимает левую или правую кнопку нам необходимо передвинуть курсор на экране ЖК дисплея вправо или влево. Для этого мы просто инкрементируем или декрементируем позицию курсора.
1 2 3 4 5 6 7 8 |
if (LB == 0) { //If left button is pressed while (!LB); //Wait till button is released pos--; //Then move the cursor to left } if (RB == 0) { //If right button is pressed while (!RB); //Wait till button is released pos++; //Move cursor to right } |
При работе с кнопками известна проблема под названием дребезга контактов, которая приводит к тому, что одиночное нажатие кнопки приводит к серии ложных (шумовых) импульсов и поэтому одиночное нажатие кнопки воспринимается микроконтроллером как несколько нажатий. Эту проблему можно решить либо добавлением конденсатора параллельно кнопке, либо использованием функции задержки, которая останавливает выполнение программы до тех пор пока кнопка не будет отпущена. В нашем случае мы будем использовать цикл while, который будет останавливать выполнение программы до отжатия кнопки.
1 |
while (!RB); |
Также как в случае с правой и левой кнопкой верхняя и нижняя кнопки могут использоваться для увеличения или уменьшения времени срабатывания будильника. При этом необходимо отметить, что каждый символ времени срабатывания будильника адресуется конкретным индексом элемента массива, что позволяет сравнительно просто его изменять.
1 2 3 4 5 6 7 8 |
if (UB == 0) { //If upper button is pressed while (!UB); //Wait till button is released alarm_val[(pos - 8)]++; //Increase that particular char value } if (BB == 0) { //If lower button is pressed while (!UB); //Wait till button is released alarm_val[(pos - 8)]--; //Decrease that particular char value } |
После того как время срабатывания будильника будет установлено, пользователь снова нажмет среднюю кнопку. После этого мы начнем сравнивать текущее время с установленным временем срабатывания будильника. Сравнение времени осуществляется с помощью посимвольного сравнения текущего времени и времени срабатывания будильника. Если эти значения будут равны, то присваиваем переменной trigger_alarm значение 1.
1 2 3 |
//IF alarm is set Check if the set value is equal to current value if (set_alarm == 0 && alarm_val[0] == hour_1 && alarm_val[1] == hour_0 && alarm_val[2] == min_1 && alarm_val[3] == min_0) trigger_alarm = 1; //Turn on trigger if value match |
Если значение переменной trigger_alarm равно 1, то мы будем включать и выключать сигнал зуммера через определенные интервалы времени, что будет означать срабатывание будильника.
1 2 3 4 5 6 7 |
if (trigger_alarm) { //If alarm is triggered //Beep the buzzer BUZZ = 1; __delay_ms(500); BUZZ = 0; __delay_ms(500); } |
Моделирование работы проекта
Смоделировать работу схемы можно с помощью симулятора proteus. Для этого просто нарисуйте в нем схему проекта и загрузите hex файл программы в микроконтроллер PIC. Данный hex файл можно найти в ZIP архиве, ссылка на скачивание которого приводилась ранее в статье. Схема проекта, нарисованная в симуляторе proteus, представлена на следующем рисунке.
Моделирование работы проекта особенно полезно когда вы хотите добавить какие-нибудь новые функции в проект. Также в симуляторе proteus можно использовать инструмент под названием I2C debugger, с помощью которого можно мониторить данные, передаваемые по шине I2C. Также в симуляторе вы можете с помощью кнопок протестировать установку времени срабатывания будильника.
Тестирование работы проекта
Соберите схему проекта на макетной плате, загрузите код программы в микроконтроллер PIC с помощью MplabX и XC8. Если вы загрузили код программы из приведенного ZIP архива, то проблем с его компиляцией возникнуть не должно поскольку в нем уже подключены все необходимые заголовочные файлы.
После компиляции программы ее можно загрузить в микроконтроллер с помощью программатора PicKit3 – контакты для его подключения показаны на схеме проекта, приведенной выше в статье. После этого вы можете приступить к тестированию работы проекта и с помощью кнопок приступить к установке времени срабатывания будильника.
Более подробно работу проекта вы можете посмотреть на видео, приведенном в конце статьи.
Данный проект будильника можно модернизировать и под выполнение других задач. Например, можно в заданное время включать какие-либо домашние устройства.
Исходный код программы
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
/* * Program: Alarm Clock Using PIC * Author: B.Aswinth Raj * More info: www.circuitdigest.com * Created on 11 May, 2018, 3:02 PM */ #pragma config FOSC = HS // Oscillator Selection bits (HS oscillator) #pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT disabled) #pragma config PWRTE = ON // Power-up Timer Enable bit (PWRT enabled) #pragma config BOREN = ON // Brown-out Reset Enable bit (BOR enabled) #pragma config LVP = OFF // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming) #pragma config CPD = OFF // Data EEPROM Memory Code Protection bit (Data EEPROM code protection off) #pragma config WRT = OFF // Flash Program Memory Write Enable bits (Write protection off; all program memory may be written to by EECON control) #pragma config CP = OFF // Flash Program Memory Code Protection bit (Code protection off) #define _XTAL_FREQ 20000000 //We are running on 20MHz crystal //контакты к которым подключен ЖК дисплей #define RS RD2 //Reset pin of LCD #define EN RD3 //Enable pin of LCD #define D4 RD4 //Data bit 0 of LCD #define D5 RD5 //Data bit 1 of LCD #define D6 RD6 //Data bit 2 of LCD #define D7 RD7 //Data bit 3 of LCD //контакты, к которым подключены кнопки #define MB RB1 //The middle button (средняя кнопка) #define LB RB0 //Left button (левая кнопка) #define RB RB2 //Right button (правая кнопка) #define UB RB3 //Upper Button (верхняя кнопка) #define BB RB4 //Bottom button (нижняя кнопка) //Define Buzz #define BUZZ RD1 //Buzzer is connected to RD1 (зуммер подключен к контакту RD1) /*устанавливаем текущую дату и время*/ int sec = 00; int min = 55; int hour = 10; int date = 06; int month = 05; int year = 18; /*Time and Date Set*/ /*переменные для установки времени срабатывания будильника*/ char set_alarm = 0; char trigger_alarm = 0; char pos = 8; char jump = 0; char alarm_h0 = 0; char alarm_h1 = 0; char alarm_m0 = 0; char alarm_m1 = 0; int alarm_val[4] = {0, 0, 0, 0}; /*End of var declaration*/ #include <xc.h> #include "lcd.h" //библиотека для работы с ЖК дисплеем #include "PIC16F877a_I2C.h" // библиотека для работы с протоколом I2C #include "PIC16F877a_DS3231.h" //библиотека для работы с модулем DS3231 int main() { TRISD = 0x00; //контакты Port D конфигурируем в режим работы на вывод данных – к ним подключен ЖК дисплей TRISB = 0xFF; //на ввод данных, к ним подключены кнопки OPTION_REG = 0b00000000; //задействуем внутренние подтягивающие резисторы на port B – что контакты кнопок не были в "неопределенном" состоянии BUZZ = 0; //выключаем зуммер Lcd_Start(); // инициализируем ЖК дисплей I2C_Initialize(100); //инициализируем I2C Master с частотой синхронизации 100KHz //удалите следующую строку после того как дата и время будут установлены в первый раз Set_Time_Date(); // устанавливаем время и дату в модуле часов реального времени //показываем приветственное сообщение на экране ЖК дисплея Lcd_Clear(); Lcd_Set_Cursor(1,1); Lcd_Print_String("Alarm Clock"); Lcd_Set_Cursor(2,1); Lcd_Print_String(" -Circuit Digest"); __delay_ms(1500); while (1) { Update_Current_Date_Time(); //считываем текущие время и дату из модуля часов реального времени //разделяем значения времени на отдельные символы чтобы потом их можно было отобразить на экране ЖК дисплея char sec_0 = sec % 10; char sec_1 = (sec / 10); char min_0 = min % 10; char min_1 = min / 10; char hour_0 = hour % 10; char hour_1 = hour / 10; //отображаем текущее время на экране ЖК дислпея Lcd_Clear(); Lcd_Set_Cursor(1, 1); Lcd_Print_String("TIME: "); Lcd_Print_Char(hour_1 + '0'); Lcd_Print_Char(hour_0 + '0'); Lcd_Print_Char(':'); Lcd_Print_Char(min_1 + '0'); Lcd_Print_Char(min_0 + '0'); Lcd_Print_Char(':'); Lcd_Print_Char(sec_1 + '0'); Lcd_Print_Char(sec_0 + '0'); //отображаем на ЖК дисплее время срабатывания будильника Lcd_Set_Cursor(2, 1); Lcd_Print_String("Alarm: "); Lcd_Print_Char(alarm_val[0] + '0'); Lcd_Print_Char(alarm_val[1] + '0'); Lcd_Print_Char(':'); Lcd_Print_Char(alarm_val[2] + '0'); Lcd_Print_Char(alarm_val[3] + '0'); __delay_ms(50); //Use middle button to check if alarm has to be set if (MB == 0 && set_alarm == 0) { //если средняя кнопка нажата и сигнал будильника не включен while (!MB); //ждем пока кнопка не будет отпущена set_alarm = 1; //начинаем установку времени срабатывания будильника } if (MB == 0 && set_alarm == 1) { //If middle button is pressed and alarm is not turned off while (!MB); // ждем пока кнопка не будет отпущена set_alarm = 0; // заканчиваем установку времени срабатывания будильника } //If alarm has to be navigate through each digit if (set_alarm == 1) { if (LB == 0) { //если нажата левая кнопка while (!LB); // ждем пока кнопка не будет отпущена pos--; //передвигаем курсор влево } if (RB == 0) { // если нажата правая кнопка while (!RB); // ждем пока кнопка не будет отпущена pos++; // передвигаем курсор вправо } if (pos >= 10) //исключаем символ ":" если на нем находится курсор { jump = 1; //перепрыгиваем через символ ":" } else jump = 0; //возвращаемся к нормальному перемещению курсора if (UB == 0) { //если нажата верхняя кнопка while (!UB); // ждем пока кнопка не будет отпущена alarm_val[(pos - 8)]++; //увеличиваем необходимое значение } if (BB == 0) { // если нажата нижняя кнопка while (!UB); // ждем пока кнопка не будет отпущена alarm_val[(pos - 8)]--; // уменьшаем необходимое значение } Lcd_Set_Cursor(2, pos + jump); Lcd_Print_Char(95); //отображаем "_" чтобы показать позицию курсора } //сравниваем текущее время с временем срабатывания будильника if (set_alarm == 0 && alarm_val[0] == hour_1 && alarm_val[1] == hour_0 && alarm_val[2] == min_1 && alarm_val[3] == min_0) trigger_alarm = 1; //Turn on trigger if value match if (trigger_alarm) { //если будильник сработал //Beep the buzzer (включаем зуммер) BUZZ = 1; __delay_ms(500); BUZZ = 0; __delay_ms(500); } __delay_ms(200);//Update interval } return 0; } |