Термометр с часами на Arduino Mega и LCD 1602 без использования библиотеки LiquidCrystal.h
Термометр с часами на Arduino Mega и LCD 1602 без использования библиотеки LiquidCrystal.h
Практика для школьников. Мясищев А.А.
Рассмотрим устройство для вывода времени в минутах, секундах и десятичных долей секунды на LCD индикатор с контроллером аналогичным HD44780. Устройство также должно с помощью датчика DS18B20 измерять температуру и выводить ее на LCD.
Программирование индикатора будет проводиться без библиотеки, которая используется в проектах Ардуино - LiquidCrystal.h. Замечено, что при использовании этой библиотеки при интенсивном выводе информации - например секунд, на экране LCD появляется "мусор" из символов и экран приходиться периодически очищать.
Для простоты индикатор будет подключен к Arduino Mega по 8-ми разрядной шине данных. Arduino Mega используется из-за того, что имеет большое количество портов. Подключение LCD к Arduino Mega выполнено ко всем битам PORTA, а управляющие сигналы будут формировать биты PC0 - RS, PC2 - E порта PORTC.
Рис 1. Структура LCD. Выводы LCD индикатора
Рис.2. Назначение и нумерация выводов LCD
Рис.3. Схема подключений
При программировании LCD индикатора необходимо учесть следующее. Для индикации данных на экране
необходимо, чтобы микроконтроллер посылал по правилам, определенным контроллером индикатора, соответствующие команды и синхронизирующие импульсы. Сигнал RS должен быть установлен в 0, когда необходимо в LCD передать команды, и в 1 – при передаче данных. Поэтому в листинге программы выделяются две функции: lcd_com – запись команд и lcd_dat – запись данных. По перепаду сигнала EN с 1 в 0 выполняется запись в LCD команды. Сигнал R/W равен 0 при записи данных в LCD и 1 при чтении данных с LCD. Так как данные для устройства всегда записываются в LCD, вывод RD занулен. В таблице 1 представлены некоторые команды LCD на базе HD44780, которые используются в программе.
Из таблицы 3 видно, что LCD может использовать две ширины интерфейса связи – 8 бит и 4 бита. Согласно рис.3 LCD подключен к микроконтроллеру по шине связи шириной 8 бита. Для примера на рис.4 показаны временные диаграммы выполнения команды 0x80 “Установка курсора в первое знакоместо верхней строки экрана” и индикация в нем цифры “4” пересылкой кода данных 0x34.
Рис.4. Временные диаграммы выполнения команд
В таблице 1 показано распределение адресов на верхней и нижней строках экрана LCD.
В таблице 2 показана прошивка знакогенератора LCD дисплея. Внизу таблицы представлен пример шестнадцатеричного представления цифры «4» и буквы «И», которые можно передать командой lcd_dat(0x34) и lcd_dat(0xA5) для их отображения на LCD.
Таблица 1
Таблица 2
Таблица 3
Примечания к таблице 3
1. Указанное время выполнения команд является максимальным. Его не обязательно выдерживать при условии чтения флага занятости BS — как только флаг BS=0, так сразу можно писать следующую команду или данные. Если же флаг BS перед выдачей команд не проверяется — необходимо формировать паузу между командами не менее указанного времени для надежной работы индикатора.
2. При чтении бита статуса никакую паузу делать не надо.
3. X — любое значение (0 или 1).
4. Биты C и B в команде «Display ON/OFF control»: C=0, B=0 — курсора нет, ничего не мигает; C=0, B=1 — курсора нет, мигает весь символ в позиции курсора; C=1, B=0 — курсор есть (подчёркивание), ничего не мигает; C=1, B=1 — курсор есть (подчёркивание) и только он и мигает.
5. A0 соответствует RS.
В листинге программы в соответствии с диаграммой (рис.4) представлены отдельно функция передачи команд (lcd_com, RS=0), функция передачи данных (lcd_dat, RS=1) и функция инициализации LCD (lcd_init) Последняя функция очень важна и соответствует DATASHEET на HD44780(рис.5)
Рис.5. Инициализация LCD
Программа 1. Выводит с 1-й позиции 1- строки мигающую надпись m:s:ds -
#include <avr/io.h>
#include <avr/delay.h>
#define RS PC0
#define EN PC2
void setup(){
DDRA = 0xFF;// Порт А работает на выход
PORTA = 0xFF; // Выход порта А подтянут к 5 Вольтам
DDRC= 0b00000101; //Биты PC0, PC2 работают на выход
PORTC=0x00;
lcd_init();
}
//-----------Функция записи команды LCD--
void lcd_com(unsigned char p) // p-байт команды
{
PORTC&=~(1<<RS); // Сигнал RS=0 (Запись команд)
PORTC|=(1<<EN) ; // Сигнал EN=1
PORTA = p; // Установка байта
_delay_ms(1); // Длительность сигнала EN
PORTC &= ~(1<<EN ); // Сигнал EN=0 - байта в LCD
_delay_ms(2);
}
//-----------Функция записи данных LCD--
void lcd_dat(unsigned char p) // p-байт данных
{
PORTC|=(1<<RS); // Сигнал RS=0 (Запись данных)
PORTC|=(1<<EN) ; // Сигнал EN=1
PORTA = p; // Установка байта
_delay_ms(1); // Длительность сигнала EN
PORTC &= ~(1<<EN ); // Сигнал EN=0 - запись байта в LCD
_delay_ms(2);
}
//---------Функция инициализации LCD-----
void lcd_init(void)
{
_delay_ms(30);
lcd_com(0x30);_delay_ms(1); // Инициализация по DATASHEET на HD44780
lcd_com(0x30);_delay_ms(1); // Инициализация по DATASHEET на HD44780
lcd_com(0x30); _delay_ms(1);// Инициализация по DATASHEET на HD44780
lcd_com(0x38); _delay_ms(1);// 8-bit
lcd_com(0x08);_delay_ms(1);// Выкл. дисплей
lcd_com(0x01);_delay_ms(2);//Очищает дисплей и помещает курсор в левую позицию
lcd_com(0x06);_delay_ms(1);//Направление сдвига курсора слева направо
lcd_com(0x0C);// Включает дисплей без курсора
}
void loop(){
lcd_com(0x80); // 1-я строка 2-я позиция
lcd_dat(0x6D);lcd_dat(':');lcd_dat(0x73);
lcd_dat(':');lcd_dat('d');lcd_dat('s');lcd_dat('-');
_delay_ms(1000);
lcd_com(0x01);
_delay_ms(500);
}
Примечание.
PORTC|=(1<<EN) ; - Установить бит PC2(EN) в единицу не меняя содержимого PORTC.
PORTC &= ~(1<<EN ); - Сбросить бит PC2(EN) в ноль не меняя содержимого PORTC.
Программа 2. Должен быть вывод минут, секунд и десятых долей секунд в формате m:s:ds - 36:02:7 Должен быть использован таймер/счетчик 1. Таймер должен тактироваться частотой Clock/64
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/delay.h>
#define RS PC0
#define EN PC2
byte s=0;
byte m=0;
byte ds=0;
void setup(){
DDRA = 0xFF;// Порт А работает на выход
PORTA = 0xFF; // Выход порта А подтянут к 5 Вольтам
DDRC= 0b00000101; //Биты PC0, PC2 работают на выход
PORTC=0x00;
// Устанавливаем регистры для прерывания по таймеру 1 при совпадении по сравнению в регистре А
TCCR1A=0b00000000; // Регистр TCCR1 Задает режим работы Time 1, т.е.
TCCR1B=0b00000000; // Предварительно обнуляем
TCCR1B = (1<<WGM12) | (1<<CS11) | (1<<CS10); //Тактирование таймера 16000000/64 (16МГц)
TCNT1H=0x00; // В этих регистрах происходит
TCNT1L=0x00; // счет
ICR1H=0x00; // Обнуляем эту старшую часть регистра (можно убрать) - Регистр захвата
ICR1L=0x00; // Обнуляем эту младшую часть регистра (можно убрать)
// 16000000/64/10 = 25000 = 0х61A8
OCR1AH=0x61; // Регистры сравнения А. Старший байт 0x61. Настройка на 0.1 секунд
OCR1AL=0xA8; // Регистры сравнения. Младший байт 0xA8
OCR1BH=0x00; // Регистры сравнения В (можно убрать)
OCR1BL=0x00; // Обнуляем их (можно убрать)
TIMSK1 |= (1 << OCIE1A); // В регистре TIMSK1 устанавливаем бит OCIE1A для прерывания по сравнению
lcd_init();
}
ISR(TIMER1_COMPA_vect) // Функция обработки прерывания для вывода информации на экран
{
TCNT1H=0; // Каждый раз обнуляем регистры счета
TCNT1L=0;
ds++;
// Условия часов
if(ds==10){ s++; ds=0; }
if(s==60) // Если число секунд = 60
{ m++; s=0;} // к минутам добавляется 1
if(m==60) // Если число минут = 60
{s=0; m=0; ds=0;} // к часам добавляется 1
lcd_com(0x80); // 1-я строка 1-я позиция
lcd_dat(0x6D);lcd_dat(':');lcd_dat(0x73);
lcd_dat(':');lcd_dat('d');lcd_dat('s');lcd_dat(0x20);lcd_dat('-');lcd_dat(0x20);
lcd_com(0x89);// 1-я строка 10-я позиция
lcd_dat(m/10+0x30); lcd_dat(m%10+0x30); lcd_dat(':');
lcd_dat(s/10+0x30); lcd_dat(s%10+0x30); lcd_dat(':');
lcd_dat(ds+0x30);
}
//-----------Функция записи команды LCD--
void lcd_com(unsigned char p) // p-байт команды
{
PORTC&=~(1<<RS); // Сигнал RS=0 (Запись команд)
PORTC|=(1<<EN) ; // Сигнал EN=1
PORTA = p; // Установка байта
_delay_ms(1); // Длительность сигнала EN
PORTC &= ~(1<<EN ); // Сигнал EN=0 - байта в LCD
_delay_ms(2);
}
//-----------Функция записи данных LCD--
void lcd_dat(unsigned char p) // p-байт данных
{
PORTC|=(1<<RS); // Сигнал RS=0 (Запись данных)
PORTC|=(1<<EN) ; // Сигнал EN=1
PORTA = p; // Установка байта
_delay_ms(1); // Длительность сигнала EN
PORTC &= ~(1<<EN ); // Сигнал EN=0 - запись байта в LCD
_delay_ms(2);
}
//---------Функция инициализаии LCD-----
void lcd_init(void)
{
_delay_ms(30);
lcd_com(0x30);_delay_ms(1); // Инициализация по DATASHEET на HD44780
lcd_com(0x30);_delay_ms(1); // Инициализация по DATASHEET на HD44780
lcd_com(0x30); _delay_ms(1);// Инициализация по DATASHEET на HD44780
lcd_com(0x38); _delay_ms(1);// 8-bit
lcd_com(0x08);_delay_ms(1);// Выключает дисплей
lcd_com(0x01);_delay_ms(2);//Очищает дисплей и помещает курсор в левую позицию
lcd_com(0x06);_delay_ms(1);//Направление сдвига курсора слева направо
lcd_com(0x0C);// Включает дисплей без курсора
}
void loop(){
}
Программа 3. Третья программа такая же как и вторая, но должна быть дополнена выводом температуры. Формат вывода
m:s:ds - 01:33:9
Temp=23C
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/delay.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#define RS PC0
#define EN PC2
#define ONE_WIRE_BUS 13 // Подключение датчика температуры к 8 выводу
OneWire oneWire(ONE_WIRE_BUS); // Создание экземпляра класса
DallasTemperature sensors(&oneWire); // Создание экземпляра класса
byte s=0;
byte m=0;
byte ds=0;
byte buf[10];
void setup(){
DDRA = 0xFF;// Порт А работает на выход
PORTA = 0xFF; // Выход порта А подтянут к 5 Вольтам
DDRC= 0b00000101;
PORTC=0x00;
// Устанавливаем регистры для прерывания по таймеру 1 при совпадении по сравнению в регистре А
TCCR1A=0b00000000; // Регистр TCCR1 Задает режим работы Time 1, т.е.
TCCR1B=0b00000000; // Предварительно обнуляем
TCCR1B = (1<<WGM12) | (1<<CS11) | (1<<CS10); // Тактирование таймера 16000000/64 (16МГц)
TCNT1H=0x00; // В этих регистрах происходит
TCNT1L=0x00; // счет
ICR1H=0x00; // Обнуляем эту старшую часть регистра (можно убрать) - Регистр захвата
ICR1L=0x00; // Обнуляем эту младшую часть регистра (можно убрать)
// 16000000/64/10 = 25000 = 0х61A8
OCR1AH=0x61; // Регистры сравнения А. Старший байт 0x61. Настройка на 0.1 секунду
OCR1AL=0xA8; // Регистры сравнения. Младший байт 0xA8
OCR1BH=0x00; // Регистры сравнения В (можно убрать)
OCR1BL=0x00; // Обнуляем их (можно убрать)
TIMSK1 |= (1 << OCIE1A); // В регистре TIMSK1 устанавливаем бит OCIE1A для прерывания по сравнению
lcd_init();
sensors.begin(); // Вызов метода класса
}
ISR(TIMER1_COMPA_vect) // Функция обработки прерывания для вывода информации на экран и мигания диодами
{
TCNT1H=0; // Каждый раз обнуляем регистры счета
TCNT1L=0;
ds++;
// Условия часов
if(ds==10){ s++; ds=0; }
if(s==60) // Если число секунд = 60
{ m++; s=0;} // к минутам добавляется 1
if(m==60) // Если число минут = 60
{s=0; m=0; ds=0;} // к часам добавляется 1
lcd_com(0x80); // 1-я строка 1-я позиция
lcd_dat(0x6D);lcd_dat(':');lcd_dat(0x73);
lcd_dat(':');lcd_dat('d');lcd_dat('s');lcd_dat(0x20);lcd_dat('-');lcd_dat(0x20);
lcd_com(0x89);// 1-я строка 10-я позиция
lcd_dat(m/10+0x30); lcd_dat(m%10+0x30); lcd_dat(':');
lcd_dat(s/10+0x30); lcd_dat(s%10+0x30); lcd_dat(':');
lcd_dat(ds+0x30);
}
//-----------Функция записи команды LCD--
void lcd_com(unsigned char p) // p-байт команды
{
PORTC&=~(1<<RS); // Сигнал RS=0 (Запись команд)
PORTC|=(1<<EN) ; // Сигнал EN=1
PORTA = p; // Установка байта
_delay_us(50); // Длительность сигнала EN
PORTC &= ~(1<<EN ); // Сигнал EN=0 - байта в LCD
_delay_us(100);
}
//-----------Функция записи данных LCD--
void lcd_dat(unsigned char p) // p-байт данных
{
PORTC|=(1<<RS); // Сигнал RS=0 (Запись данных)
PORTC|=(1<<EN) ; // Сигнал EN=1
PORTA = p; // Установка байта
_delay_us(50); // Длительность сигнала EN
PORTC &= ~(1<<EN ); // Сигнал EN=0 - запись байта в LCD
_delay_us(100);
}
//---------Функция инициализации LCD-----
void lcd_init(void)
{
_delay_ms(30);
lcd_com(0x30);_delay_us(50); // Инициализация по DATASHEET на HD44780
lcd_com(0x30);_delay_us(50); // Инициализация по DATASHEET на HD44780
lcd_com(0x30); _delay_us(50);// Инициализация по DATASHEET на HD44780
lcd_com(0x38); _delay_us(50);// 8-bit
lcd_com(0x08);_delay_us(50);// Выкл. дисплей
lcd_com(0x01);_delay_ms(2);//Очищает дисплей и помещает курсор в левую позицию
lcd_com(0x06);_delay_us(50);//Направление сдвига курсора слева направо
lcd_com(0x0C);_delay_us(50);// Включает дисплей без курсора
}
void loop(){
sensors.requestTemperatures();
float cel=sensors.getTempCByIndex(0); // Определение температуры
dtostrf(cel,2,0,buf);
lcd_com(0xC3); lcd_dat('T');lcd_dat('e');lcd_dat('m');lcd_dat('p');lcd_dat('=');
lcd_dat(buf[0]);
lcd_dat(buf[1]);
lcd_dat('C');
delay(1000);
}
Замечание
В последней программе уменьшены задержки, так как с предыдущими задержками в двух первых программах не всегда корректно выводилась температура.
Рис.6. Фото стенда
Задание
1. Написать программу по выводу на индикатор кроме времени температуру с одним числом после запятой и номер своего варианта в формате:
M:S:DS = 06:43:8
T=21.3C P-12
2. Добавить в схему светодиод, который будет загораться после 15 секунд работы таймера. Дополнить для этого программу в первом пункте.
3. Оформить отчет в котором дать полное описание работы индикатора с управляющими командами. Представить составленную программу по пункту 2 с поясняющими комментариями.
Литература
1. Работа с регистрами AVR микроконтроллера на Си, битовые операции. https://ph0en1x.net/81-howto-work-with-ports-register-bits-in-microcontroller.html
2. С.М. Рюмик. Микроконтроллеры AVR. http://radio-stv.ru/wp-content/files/_AVR_..__2005.pdf
3. Жидкокристаллический индикатор МТ–16S2H. http://files.amperka.ru/datasheets/MT-16S2H.pdf
15.11.2019 г.