Короткая библиотека для LCD дисплея
Есть у меня LCD дисплей с 16-ю ножками PIN-ами, про который было тут:
https://akostina76.ucoz.ru/blog/2022-01-25-7444
Я, вообще говоря, знать не хочу про то, как он там внутри работает. Я хочу подключить его проводами и потом просто писать какой-то функцией на нём какие-то буквы. Точнее двумя функциями. Я знаю размеры дисплея (4 строки, 20 колонок), потому вначале нужно устанавливать курсор в нужную строку и колонку (первой функцией) и потом писать букву (второй функцией).
Но дисплей - электронное устройство. Оно подключено 8 (данные)+2 (счётчик, сброс) проводами к контроллеру. Даже эту информацию надо где-то хранить или передавать при каждом обращении к дисплею. Но это хоть понятная информация. Я знаю, что я к нему и куда подключала. Но ещё у дисплея хитрое внутреннее устройство, набор конкретных внутренних команд, например. Этого я не вижу и видеть не хочу. Все это должно быть какой-то внутренней информацией, которая хранится и как-то сама внутри используется. Требуется хранилище для этой информации.
Дальше - больше. Надо полагать, кроме одной команды иногда надо отправлять их последовательности. И мало ли какую внутреннюю работу там надо делать, потому во внутреннем хранилище неплохо бы хранить целые программные тексты.
Проблемы информационного описания сложных объектов и устройств возникли давно. Результатом стало объектно-ориентированное программирование как стандарт программирования. Первый программный переход - переход от обычного Си к Си++.
Библиотека Arduino это описание объекта на языке Си (точнее Си++). Чтобы хотя бы чуть-чуть сократить программный текст, я взяла стандартную библиотеку и убрала из неё некоторые возможности. В библиотеке есть тексты примеров и сам программный текст. Обычно он состоит из двух файлов. Описание – объекта - LCD_short.h (расширение *.h) и программные тексты LCD_short.cpp (расширение *.cpp).
Полный текст LCD_short.h:
#ifndef LCD_short_h
#define LCD_short_h
#include <inttypes.h>
#include "Print.h"
// commands
#define LCD_CLEARDISPLAY 0x01 // 0000 0001 void LCD_short::clear()
#define LCD_RETURNHOME 0x02 // 0000 0010 void LCD_short::home()
#define LCD_ENTRYMODESET 0x04 // 0000 0100 Задание направления прокрутки
#define LCD_DISPLAYCONTROL 0x08 // 0000 1000 Задание состояния дисплея (включен, курсор...)
#define LCD_CURSORSHIFT 0x10 // 0001 0000
#define LCD_FUNCTIONSET 0x20 // 0010 0000 Задание установок дисплея
#define LCD_
GRAMADDR 0x40 // 0100 0000
#define LCD_SETDDRAMADDR 0x80 // 1000 0000 Положение курсора
// Флаги направления прокрутки, все меньше LCD_ENTRYMODESET=0000 0100
#define LCD_ENTRYRIGHT 0x00
#define LCD_ENTRYLEFT 0x02
#define LCD_ENTRYSHIFTINCREMENT 0x01
#define LCD_ENTRYSHIFTDECREMENT 0x00
// Флаги состояния дисплея, все меньше LCD_DISPLAYCONTROL=0000 1000:
#define LCD_DISPLAYON 0x04 // 0000 0100
#define LCD_DISPLAYOFF 0x00 // 0000 0000 0
#define LCD_CURSORON 0x02 // 0000 0010
#define LCD_CURSOROFF 0x00 // 0000 0000 0
#define LCD_BLINKON 0x01 // 0000 0001
#define LCD_BLINKOFF 0x00 // 0000 0000 0
// flags for display/cursor shift
#define LCD_DISPLAYMOVE 0x08 // 0000 1000
#define LCD_CURSORMOVE 0x00 // 0000 0000 0
#define LCD_MOVERIGHT 0x04 // 0000 0100
#define LCD_MOVELEFT 0x00 // 0000 0000 0
// Флаги установок диспля, все меньше LCD_FUNCTIONSET=0010 0000
#define LCD_8BITMODE 0x10 // 0001 0000
#define LCD_4BITMODE 0x00 // 0000 0000 0
#define LCD_2LINE 0x08 // 0000 1000
#define LCD_1LINE 0x00 // 0000 0000 0
class LCD_short : public Print {
public:
// Функции, которые можно использовать:
LCD_short(uint8_t rs, uint8_t enable,
uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3,
uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7); // Создание объекта, передача номеров PIN-ов
void begin(uint8_t cols, uint8_t rows); // Размеры экрана
void clear(); // command(LCD_CLEARDISPLAY);
void home(); // command(LCD_RETURNHOME)
void noDisplay(); // displaycontrol &= ~LCD_DISPLAYON; command(LCD_DISPLAYCONTROL | _displaycontrol);
void display(); // _displaycontrol |= LCD_DISPLAYON; command(LCD_DISPLAYCONTROL | _displaycontrol);
void noBlink(); // displaycontrol &= ~LCD_BLINKON; command(LCD_DISPLAYCONTROL | _displaycontrol);
void blink(); // displaycontrol |= LCD_BLINKON; command(LCD_DISPLAYCONTROL | _displaycontrol);
void noCursor(); // _displaycontrol &= ~LCD_CURSORON; command(LCD_DISPLAYCONTROL | _displaycontrol);
void cursor(); // _displaycontrol |= LCD_CURSORON; command(LCD_DISPLAYCONTROL | _displaycontrol);
void setCursor(uint8_t, uint8_t);
virtual size_t write(uint8_t); // send(value, HIGH); _rs_pin = HIGH
using Print::write;
private:
// Внутренние функции объекта:
void init(uint8_t fourbitmode, uint8_t rs, uint8_t rw, uint8_t enable,
uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3,
uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7);
void command(uint8_t); // send(value, LOW); _rs_pin = LOW
void send(uint8_t, uint8_t); // digitalWrite(_rs_pin, mode=HIGH или LOW); write8bits(value);
void write8bits(uint8_t); // for { digitalWrite(_data_pins[i], (value >> i) & 0x01); } ; pulseEnable();
void pulseEnable(); // digitalWrite(_enable_pin, HIGH); delayMicroeconds(1); digitalWrite(_enable_pin, LOW);
// Внутренние переменные объекта:
uint8_t _rs_pin; // LOW: команда. HIGH: символ .
uint8_t _enable_pin; // счетчик - часы.
uint8_t _data_pins[8]; // массив PIN-ов данных
uint8_t _displayfunction; // Настройки дисплея (способ передачи, количнство строк и т.д
uint8_t _displaycontrol; // Дисплей включен/выключен, с курсором/безкурсора, с мигающем символом/без него
uint8_t _displaymode; // Направление прокрутки
uint8_t _numlines; // Количество строк дисплея
uint8_t _row_offsets[4]; // Адреса начала строк дисплея
};
#endif
Хорош он тем, что с одной стороны настоящий, а с другой достаточно короткий. В строках define – внутренние команды дисплея. Это - двоичная информаций, которую дисплей обрабатывает. Дальше идёт название объекта, работающего с дисплеем – «class LCD_short». После слова public – список функций, которые я буду использовать. Я всех этих длинных текстов обычно и не увижу, потому что я сразу возьму из примера четыре, которые мне нужны:
LCD_short lcd(12, 11, 5, 4, 3, 2,6,7,8,9);
void setup() {
lcd.begin(20, 4);
lcd.setCursor(0, 1);
lcd.write("A");
}
После слова private: - внутренняя (скрытая) информация объекта. Это внутренние функции и переменные. Например, номера PIN-ов я передаю один раз при создании объекта. В тот момент они (для последующего использования) записываются во внутренние переменные _rs_pin; _enable_pin; _data_pins[8];.
Это происходит при создании объекта:
// Начальные настройки объекта (вызов из конструктора)
void LCD_short::init(uint8_t rs, uint8_t enable,
uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3,
uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7)
{
// Присвоить внутренним переменным объекта переданные значения номеров PIN-ов:
_rs_pin = rs;
_enable_pin = enable;
_data_pins[0] = d0;
_data_pins[1] = d1;
_data_pins[2] = d2;
_data_pins[3] = d3;
_data_pins[4] = d4;
_data_pins[5] = d5;
_data_pins[6] = d6;
_data_pins[7] = d7;
// Все PIN-ы предназначены для записи данных:
pinMode(_rs_pin, OUTPUT);
pinMode(_enable_pin, OUTPUT);
for (int i = 0; i < 8; i++) {
pinMode(_data_pins[i], OUTPUT);
}
Наличие сигнала на PIN-е сброса тут определяет, что передаётся. Нет сигнала – команда, есть сигнал – буква (которую надо нарисовать).
И то и другое (код команды и код символа) засовывается чрез PIN-ы данных внутренней функцией send:
inline void LCD_short::command(uint8_t value) {
send(value, LOW);
}
inline size_t LCD_short::write(uint8_t value) {
send(value, HIGH);
return 1; // assume sucess
}
Сама функция Send:
void LCD_short::send(uint8_t value, uint8_t mode) {
digitalWrite(_rs_pin, mode);
write8bits(value);
}
.. т.е вначале она устанавливает нужное значение того самого сброса, а потом вызывает функцию записи данных в шину данных.
А эта функция, в свою очередь:
void LCD_short::write8bits(uint8_t value) {
for (int i = 0; i < 8; i++) {
// Вытаскивание отдельных бит переданного байта:
digitalWrite(_data_pins[i], (value >> i) & 0x01);
// Пусть value = 1000 1000
// Сдвиг ан 4 вправо (i=4 => (value >> 4) даёт 0001 0001
// Обнуление всех биткроме последнего командой И с единицей (& 0x01)): 0001 0001 И 0000 0001 =0000 0001
}
pulseEnable();
}
… вначале рассовывает биты байта по проводам (PIN-ам) а потом инициирует тик часов (pulseEnable) чтобы информация записалась:
void LCD_short::pulseEnable(void) {
digitalWrite(_enable_pin, LOW);
delayMicroseconds(1);
digitalWrite(_enable_pin, HIGH);
delayMicroseconds(1); // enable pulse must be >450ns
digitalWrite(_enable_pin, LOW);
delayMicroseconds(100); // commands need > 37us to settle
}
… длиной в одну микросекунду.
Командой идёт установка курсора в нужную позицию:
void LCD_short::Cursor(uint8_t col, uint8_t row)
{
// Если передан ошибочно большой номер строки - запись на последнюю:
if ( row >= _numlines ) {
row = _numlines - 1; // we count rows starting w/0
}
// LCD_SETDDRAMADDR = 0x80= 1000 0000. К нему добавляется сдвиг на сдвиг строки и колонку.
// Добавляемое меньше 0x80, потому можно использоваь команду ИЛИ
command(LCD_SETDDRAMADDR | (col + _row_offsets[row]));
.. а вывод символа в том месте – функция write(символ).
Исходный и укороченный вариант лежат тут:
https://disk.yandex.ru/d/LOT780WrYkg2QQ
Работоспособность укороченного я не проверяла, хотя старалась удалять другие варианты использования аккуратно. Смысл варианта – демонстрация того как это всё вообще работает.
Это я про то, что «страшная» проблема перевешивания дисплея на другие PIN-ы:
https://akostina76.ucoz.ru/blog/2022-01-25-7445
…, скорее всего, решается если посмотреть в тексты библиотек и, может поменять там то, что установлено по умолчанию. Но обычно не хочется таким заниматься, потому неудобное расположение по-умолчательных часто используемых PIN-ов… неудобно.
|