Термометр с часами на 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 г.