Кто сказал, что символьные дисплеи для символов? Какие-то скучные чуваки. Если бы это было так, эти дисплеи не были бы настолько любимы и популярны в Arduino среде (купить можно тут https://alexgyver.ru/arduino_shop/). Подробного гайда лично от меня пока что нет, но суть такая: в этих дисплеях есть 8 ячеек для хранения “кастомных” символов, то есть символов, которые можно нарисовать самому, например при помощи различных онлайн сервисов типа такого http://maxpromer.github.io/LCD-Character-Creator/. Полученный массив байтов вставляется в скетч, передаётся в дисплей, и затем можно им пользоваться при помощи команды write(). Простой пример есть на офф сайте Ардуино https://www.arduino.cc/en/Reference/LiquidCrystalCreateChar. К слову эти ячейки в памяти занимают места с 0 по 7

Был у меня оч крутой проект, PCdisplay https://alexgyver.ru/pcdisplay/, в котором на дисплее 2004 отображалась в реальном времени информация о железе ПК: температуры и проценты загрузки, для красоты я выводил показатели не только числами, но и графически, в виде “полос загрузки” и графиков.

Проект получился очень классный, но вот я подумал, а почему бы не вынести построение графиков и полос загрузки удобными отдельными функциями? Ведь по-любому пригодятся кому-нибудь из вас, а достать код из PCdisplay новичку не под силу (к тому же он там чутка кривоват…). Так что представляю вашему вниманию GyverLCDbars – набор удобных инструментов для украшения ваших проектов графическими элементами: полосками загрузки и графиками. Актуальная версия всегда лежит у меня на GitHub https://github.com/AlexGyver/GyverLCDbars. ТЫКНИ для прямой загрузки архива.
В библиотеке вас ждёт 6 примеров: 4 типа полос загрузки и два примера с графиком.

Как этим пользоваться? Да очень просто. Одна функция для инициализации “кастомных” символов, вторая – для вывода нужного элемента с настройками его размера и позиции на дисплее!

Полоса загрузки: fillBar(столбец, строка, ширина, значение)

  • Столбец: отвечает за положение левой точки полосы, нумерация идёт слева направо с нуля
  • Строка: отвечает за положение левой точки полосы, нумерация идёт сверху вниз с нуля
  • Ширина: полная ширина полосы по горизонтали. Очевидно, что ширина + стартовая позиция по горизонтали (столбец) не должны превышать ширину дисплея в символах по горизонтали
  • Значение: число от 0 до 100 – процент заполнения полосы. Любая ваша величина приводится к диапазону 0-100 при помощи ардуиновской функции map
  • Особенность: если вы используете свои кастомные символы, то перед выводом полосок нужно обязательно вызвать initBar() для загрузки в память дисплея символов полосы! Полоски занимают разное количество мест в зависимости от типа, подробнее смотрите в самих примерах

График из массива: drawPlotArray(столбец, строка, ширина, высота, мин. значение, макс. значение, массив) – смотри пример!

График в реальном времени: drawPlot(столбец, строка, ширина, высота, мин. значение, макс. значение, величина)

  • Столбец: нумерация идёт слева направо с нуля. Начало координат графика – нижняя левая точка!
  • Строка: нумерация идёт сверху вниз с нуля. Начало координат графика – нижняя левая точка!
  • Ширина: ширина графика по горизонтали. Очевидно, что ширина + стартовая позиция по горизонтали (столбец) не должны превышать ширину дисплея в символах по горизонтали
  • Высота: высота графика по вертикали. Очевидно, что высота+ стартовая позиция по вертикали (строка) не должны превышать высоту дисплея в символах по вертикали. То  есть для 2004 максимум высота 4, для 1602 максимум 2.
  • Мин. значение: минимальное значение для графика, ниже него строиться не будет (тип данных int -32,768 to 32,767)
  • Макс. значение: максимальное значение для графика, выше него строиться не будет (тип данных int -32,768 to 32,767)
  • Величина: значение, которое будет построено на графике с краю, предыдущие столбики автоматически сдвинутся в сторону при вызове функции drawPlot. Тип данных int -32,768 to 32,767
  • Также в примере есть готовый кусок кода для расчёта и вывода максимального и минимального значения на текущем графике!
  • Особенность: если вы используете свои кастомные символы, то перед выводом графика нужно обязательно вызвать initPlot() для загрузки в память дисплея символов графика! Они занимают все места, с 0 по 7!

/*
  Скетч примера полосы загрузки на символьном LCD дисплее 1602/2004 итд.
*/

// библиотеки дисплея
#include 
#include 

// создаём дисплей
// дисплей 1602. Если не работает, используйте другой адрес
LiquidCrystal_I2C lcd(0x3f, 16, 2);
//LiquidCrystal_I2C lcd(0x27, 16, 2);

// дисплей 2004. Если не работает, используйте другой адрес!
//LiquidCrystal_I2C lcd(0x3f, 20, 4);
//LiquidCrystal_I2C lcd(0x27, 20, 4);

void setup() {
  Serial.begin(9600);
  lcd.init();
  lcd.backlight();
  lcd.clear();
}

void loop() {
  // для примера потенциометр подключен к A0
  int perc = map(analogRead(0), 0, 1023, 0, 100);

  //fillBar0 принимает аргументы (столбец, строка, длина полосы, значение в % (0 - 100) )
  fillBar0(0, 0, 10, perc);
  fillBar0(0, 1, 16, perc);
  delay(50);
}

void fillBar0(byte start_pos, byte row, byte bar_length, byte fill_percent) {
  byte infill = round((float)bar_length * fill_percent / 100);
  lcd.setCursor(start_pos, row);
  if (infill == 0) lcd.write(16);
  else lcd.write(255);
  for (int n = 1; n < bar_length - 1; n++) {
    if (n < infill) lcd.write(255); if (n >= infill) lcd.write(16);
  }
  if (infill == bar_length) lcd.write(255);
  else lcd.write(16);
}
/*
  Скетч примера полосы загрузки на символьном LCD дисплее 1602/2004 итд.
*/

// библиотеки дисплея
#include 
#include 

// создаём дисплей
// дисплей 1602. Если не работает, используйте другой адрес
LiquidCrystal_I2C lcd(0x3f, 16, 2);
//LiquidCrystal_I2C lcd(0x27, 16, 2);

// дисплей 2004. Если не работает, используйте другой адрес!
//LiquidCrystal_I2C lcd(0x3f, 20, 4);
//LiquidCrystal_I2C lcd(0x27, 20, 4);

void setup() {
  Serial.begin(9600);
  lcd.init();
  lcd.backlight();
  lcd.clear();
  
  // инициализация символов для отрисовки. При использовании каких то своих символов,
  // нужно вызывать эту функцию перед отрисовкой графика/полосы!
  initBar1();
}

void loop() {
  // для примера потенциометр подключен к A0
  int perc = map(analogRead(0), 0, 1023, 0, 100);

  //fillBar1 принимает аргументы (столбец, строка, длина полосы, значение в % (0 - 100) )
  fillBar1(0, 0, 10, perc);
  fillBar1(0, 1, 16, perc);
  delay(50);
}

void initBar1() {
  // необходимые символы для работы
  // создано в http://maxpromer.github.io/LCD-Character-Creator/
  byte right_empty[8] = {0b11111,  0b00001,  0b00001,  0b00001,  0b00001,  0b00001,  0b00001,  0b11111};
  byte left_empty[8] = {0b11111,  0b10000,  0b10000,  0b10000,  0b10000,  0b10000,  0b10000,  0b11111};
  byte center_empty[8] = {0b11111, 0b00000,  0b00000,  0b00000,  0b00000,  0b00000,  0b00000,  0b11111};
  lcd.createChar(0, left_empty);
  lcd.createChar(1, center_empty);
  lcd.createChar(2, right_empty);
}

void fillBar1(byte start_pos, byte row, byte bar_length, byte fill_percent) {
  byte infill = round((float)bar_length * fill_percent / 100);
  lcd.setCursor(start_pos, row);
  if (infill == 0) lcd.write(0);
  else lcd.write(255);
  for (int n = 1; n < bar_length - 1; n++) {
    if (n < infill) lcd.write(255); if (n >= infill) lcd.write(1);
  }
  if (infill == bar_length) lcd.write(255);
  else lcd.write(2);
}
/*
  Скетч примера полосы загрузки на символьном LCD дисплее 1602/2004 итд.
*/

// библиотеки дисплея
#include 
#include 

// создаём дисплей
// дисплей 1602. Если не работает, используйте другой адрес
LiquidCrystal_I2C lcd(0x3f, 16, 2);
//LiquidCrystal_I2C lcd(0x27, 16, 2);

// дисплей 2004. Если не работает, используйте другой адрес!
//LiquidCrystal_I2C lcd(0x3f, 20, 4);
//LiquidCrystal_I2C lcd(0x27, 20, 4);

void setup() {
  Serial.begin(9600);
  lcd.init();
  lcd.backlight();
  lcd.clear();
  
  // инициализация символов для отрисовки. При использовании каких то своих символов,
  // нужно вызывать эту функцию перед отрисовкой графика/полосы!
  initBar2();
}

void loop() {
  // для примера потенциометр подключен к A0
  int perc = map(analogRead(0), 0, 1023, 0, 100);

  //fillBar2 принимает аргументы (столбец, строка, длина полосы, значение в % (0 - 100) )
  fillBar2(0, 0, 10, perc);
  fillBar2(0, 1, 16, perc);
  delay(50);
}

void initBar2() {
  // необходимые символы для работы
  // создано в http://maxpromer.github.io/LCD-Character-Creator/
  byte right_empty[8] = {0b11111,  0b00001,  0b00001,  0b00001,  0b00001,  0b00001,  0b00001,  0b11111};
  byte left_empty[8] = {0b11111,  0b10000,  0b10000,  0b10000,  0b10000,  0b10000,  0b10000,  0b11111};
  byte center_empty[8] = {0b11111, 0b00000,  0b00000,  0b00000,  0b00000,  0b00000,  0b00000,  0b11111};
  byte left_full[8] = {0b11111, 0b10000, 0b10111, 0b10111, 0b10111, 0b10111, 0b10000, 0b11111};
  byte right_full[8] = {0b11111, 0b00001, 0b11101, 0b11101, 0b11101, 0b11101, 0b00001, 0b11111};
  byte center_full[8] = {0b11111, 0b00000, 0b11111, 0b11111, 0b11111, 0b11111, 0b00000, 0b11111};
  lcd.createChar(0, left_empty);
  lcd.createChar(1, center_empty);
  lcd.createChar(2, right_empty);
  lcd.createChar(3, left_full);
  lcd.createChar(4, center_full);
  lcd.createChar(5, right_full);
}

void fillBar2(byte start_pos, byte row, byte bar_length, byte fill_percent) {
  byte infill = round((float)bar_length * fill_percent / 100);
  lcd.setCursor(start_pos, row);
  if (infill == 0) lcd.write(0);
  else lcd.write(3);
  for (int n = 1; n < bar_length - 1; n++) {
    if (n < infill) lcd.write(4); if (n >= infill) lcd.write(1);
  }
  if (infill == bar_length) lcd.write(5);
  else lcd.write(2);
}
/*
Скетч примера полосы загрузки на символьном LCD дисплее 1602/2004 итд.
*/
// библиотеки дисплея
#include 
#include 
// создаём дисплей
// дисплей 1602. Если не работает, используйте другой адрес
LiquidCrystal_I2C lcd(0x3f, 16, 2);
//LiquidCrystal_I2C lcd(0x27, 16, 2);
// дисплей 2004. Если не работает, используйте другой адрес!
//LiquidCrystal_I2C lcd(0x3f, 20, 4);
//LiquidCrystal_I2C lcd(0x27, 20, 4);
boolean change_flag = true;     // флаг для 3 типа полосы
void setup() {
Serial.begin(9600);
lcd.init();
lcd.backlight();
lcd.clear();
// инициализация символов для отрисовки. При использовании каких то своих символов,
// нужно вызывать эту функцию перед отрисовкой графика/полосы!
initBar3();
}
void loop() {
// для примера потенциометр подключен к A0
int perc = map(analogRead(0), 0, 1023, 0, 100);
//fillBar3 принимает аргументы (столбец, строка, длина полосы, значение в % (0 - 100) )
fillBar3(0, 0, 10, perc);
fillBar3(0, 1, 16, perc);
delay(50);
}
void initBar3() {
// необходимые символы для работы
// создано в http://maxpromer.github.io/LCD-Character-Creator/
byte right_empty[8] = {0b11111,  0b00001,  0b00001,  0b00001,  0b00001,  0b00001,  0b00001,  0b11111};
byte left_empty[8] = {0b11111,  0b10000,  0b10000,  0b10000,  0b10000,  0b10000,  0b10000,  0b11111};
byte center_empty[8] = {0b11111, 0b00000,  0b00000,  0b00000,  0b00000,  0b00000,  0b00000,  0b11111};
byte bar2[] = {0b11111, 0b11000, 0b11000, 0b11000, 0b11000, 0b11000, 0b11000, 0b11111};
byte bar3[] = {  B11111,  B11100,  B11100,  B11100,  B11100,  B11100,  B11100,  B11111};
byte bar4[] = {  B11111,  B11110,  B11110,  B11110,  B11110,  B11110,  B11110,  B11111};
lcd.createChar(0, left_empty);
lcd.createChar(1, center_empty);
lcd.createChar(2, right_empty);
lcd.createChar(3, bar2);
lcd.createChar(4, bar3);
lcd.createChar(5, bar4);
}
void fillBar3(byte start_pos, byte row, byte bar_length, byte fill_percent) {
byte infill = bar_length * fill_percent / 10;
byte fract = infill % 10;
infill = infill / 10;
// change_flag - true слева, false справа
if (infill < bar_length - 1) { if (!change_flag) { change_flag = true; byte bar2[] = {0b11111, 0b11000, 0b11000, 0b11000, 0b11000, 0b11000, 0b11000, 0b11111}; byte bar3[] = { B11111, B11100, B11100, B11100, B11100, B11100, B11100, B11111}; byte bar4[] = { B11111, B11110, B11110, B11110, B11110, B11110, B11110, B11111}; lcd.createChar(3, bar2); lcd.createChar(4, bar3); lcd.createChar(5, bar4); } } else { if (change_flag) { change_flag = false; byte leftbar1[] = { B11111, B10001, B10001, B10001, B10001, B10001, B10001, B11111}; byte leftbar2[] = { B11111, B11001, B11001, B11001, B11001, B11001, B11001, B11111}; byte leftbar3[] = { B11111, B11101, B11101, B11101, B11101, B11101, B11101, B11111}; lcd.createChar(3, leftbar1); lcd.createChar(4, leftbar2); lcd.createChar(5, leftbar3); } } lcd.setCursor(start_pos, row); if (infill == 0) { if (fract >= 0 && fract < 2) lcd.write(0); else if (fract >= 2 && fract < 4) lcd.write(0); else if (fract >= 4 && fract < 6) lcd.write(3); else if (fract >= 6 && fract < 8) lcd.write(4); else if (fract >= 8) lcd.write(5);
}
else lcd.write(255);
for (int n = 1; n < bar_length - 1; n++) {
if (n < infill) lcd.write(255); if (n == infill) { if (fract >= 0 && fract < 2) lcd.write(1); else if (fract >= 2 && fract < 4) lcd.write(0); else if (fract >= 4 && fract < 6) lcd.write(3); else if (fract >= 6 && fract < 8) lcd.write(4); else if (fract >= 8) lcd.write(5);
}
if (n > infill) lcd.write(1);
}
if (infill == bar_length - 1) {
if (fract >= 0 && fract < 2) lcd.write(2); else if (fract >= 2 && fract < 4) lcd.write(3); else if (fract >= 4 && fract < 6) lcd.write(4); else if (fract >= 6 && fract < 8) lcd.write(5); else if (fract >= 8) lcd.write(255);
}
else if (infill == bar_length) lcd.write(255);
else lcd.write(2);
}
/*
Скетч примера графика на символьном LCD дисплее 1602/2004 итд.
*/
// библиотеки дисплея
#include 
#include 
// создаём дисплей
// дисплей 1602. Если не работает, используйте другой адрес
//LiquidCrystal_I2C lcd(0x3f, 16, 2);
//LiquidCrystal_I2C lcd(0x27, 16, 2);
// дисплей 2004. Если не работает, используйте другой адрес!
//LiquidCrystal_I2C lcd(0x3f, 20, 4);
LiquidCrystal_I2C lcd(0x27, 20, 4);
int plot_array[20];         // массив данных для графика
void setup() {
Serial.begin(9600);
lcd.init();
lcd.backlight();
lcd.clear();
// инициализация символов для отрисовки. При использовании каких то своих символов,
// нужно вызывать эту функцию перед отрисовкой графика/полосы!
initPlot();
}
void loop() {
// для примера потенциометр подключен к A0
int value = map(analogRead(0), 0, 1023, 10, 80);
// drawPlot принимает аргументы (столбец, строка, ширина, высота, мин. значение, макс. значение, величина)
// строка, столбец: начало графика - нижняя левая точка!
// значения целочисленные (int) и могут быть отрицательными!
// для примера отрисовка графика 12х4 занимает 140 миллисекунд
// график сдвигается на 1 шаг АВТОМАТИЧЕСКИ при вызове функции!
drawPlot(0, 3, 12, 4, 10, 80, value);
// также можно вывести минимум и максимум по отображаемому участку графика
/*
int max_value = -32000;
int min_value = 32000;
for (byte i = 0; i < 12; i++) { if (plot_array[i] > max_value) max_value = plot_array[i];
if (plot_array[i] < min_value) min_value = plot_array[i];
}
lcd.setCursor(17, 1);
lcd.print(value); lcd.print(" ");
lcd.setCursor(13, 0);
lcd.print("max "); lcd.print(max_value); lcd.print(" ");
lcd.setCursor(13, 3);
lcd.print("min "); lcd.print(min_value); lcd.print(" ");
*/
delay(300);
}
void initPlot() {
// необходимые символы для работы
// создано в http://maxpromer.github.io/LCD-Character-Creator/
byte row8[8] = {0b11111,  0b11111,  0b11111,  0b11111,  0b11111,  0b11111,  0b11111,  0b11111};
byte row7[8] = {0b00000,  0b11111,  0b11111,  0b11111,  0b11111,  0b11111,  0b11111,  0b11111};
byte row6[8] = {0b00000,  0b00000,  0b11111,  0b11111,  0b11111,  0b11111,  0b11111,  0b11111};
byte row5[8] = {0b00000,  0b00000,  0b00000,  0b11111,  0b11111,  0b11111,  0b11111,  0b11111};
byte row4[8] = {0b00000,  0b00000,  0b00000,  0b00000,  0b11111,  0b11111,  0b11111,  0b11111};
byte row3[8] = {0b00000,  0b00000,  0b00000,  0b00000,  0b00000,  0b11111,  0b11111,  0b11111};
byte row2[8] = {0b00000,  0b00000,  0b00000,  0b00000,  0b00000,  0b00000,  0b11111,  0b11111};
byte row1[8] = {0b00000,  0b00000,  0b00000,  0b00000,  0b00000,  0b00000,  0b00000,  0b11111};
lcd.createChar(0, row8);
lcd.createChar(1, row1);
lcd.createChar(2, row2);
lcd.createChar(3, row3);
lcd.createChar(4, row4);
lcd.createChar(5, row5);
lcd.createChar(6, row6);
lcd.createChar(7, row7);
}
void drawPlot(byte pos, byte row, byte width, byte height, int min_val, int max_val, int fill_val) {
for (byte i = 0; i < width; i++) {
plot_array[i] = plot_array[i + 1];
}
fill_val = constrain(fill_val, min_val, max_val);
plot_array[width] = fill_val;
for (byte i = 0; i < width; i++) {                  // каждый столбец параметров
byte infill, fract;
// найти количество целых блоков с учётом минимума и максимума для отображения на графике
infill = floor((float)(plot_array[i] - min_val) / (max_val - min_val) * height * 10);
fract = (infill % 10) * 8 / 10;                   // найти количество оставшихся полосок
infill = infill / 10;
for (byte n = 0; n < height; n++) {     // для всех строк графика
if (n < infill && infill > 0) {       // пока мы ниже уровня
lcd.setCursor(i, (row - n));        // заполняем полными ячейками
lcd.write(0);
}
if (n >= infill) {                    // если достигли уровня
lcd.setCursor(i, (row - n));
if (fract > 0) lcd.write(fract);          // заполняем дробные ячейки
else lcd.write(16);                       // если дробные == 0, заливаем пустой
for (byte k = n + 1; k < height; k++) {   // всё что сверху заливаем пустыми
lcd.setCursor(i, (row - k));
lcd.write(16);
}
break;
}
}
}
}

На этом сегодня всё, надеюсь вам пригодятся эти наработки! Как говорится, лайк-подписка, до новых встреч =)

Строим графики на LCD дисплеях 1602 и 2004
5 (100%) 6 votes

2018-09-05T16:06:55+03:00