Текстовый экран 16×2 / i²c

Генерация пользовательских символов для LCD

Если вы находите символы на дисплее неподходящими и неинтересными, вы можете создать свои собственные символы (глиф) для своего ЖК-дисплея. Пользовательские символы чрезвычайно полезны в том случае, когда вы хотите отобразить символ, который не является частью стандартного набора символов ASCII.

Как мы уже обсуждали ранее в этом руководстве, символ на дисплее формируется в матрице 5×8 пикселей, поэтому вам нужно определить свой пользовательский символ в этой матрице. Для определения символа необходимо использовать функцию createChar() библиотеки LiquidCrystal.

Для использования  createChar()  сначала необходимо назначить массив из 8 байт. Каждый байт (учитывается только 5 бит) в массиве определяет одну строку символа в матрице 5×8. В то время как нули и единицы в байте указывают, какие пиксели в строке должны быть включены, а какие-выключены.

Генератор символов LCD

Создание собственного символа до сих пор было непросто! Поэтому было создано небольшое приложение под названием «Генератор пользовательских символов» для LCD.

Вы видите синюю сетку ниже? Вы можете нажать на любой из 5 × 8 пикселей, чтобы установить/очистить этот конкретный пиксель. И когда вы нажимаете на пиксели, код для символа генерируется рядом с сеткой. Этот код может быть непосредственно использован в вашем скетче Arduino.

Единственным ограничением является то, что библиотека LiquidCrystal поддерживает только восемь пользовательских символов.

Следующий скриншот демонстрирует, как вы можете использовать эти пользовательские символы на дисплее.

//  подключаем библиотеку LiquidCrystal:
#include <LiquidCrystal.h>

// Создаем LCD объект. Выводы: (rs, enable, d4, d5, d6, d7)
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

// создадим несколько пользовательских символов
byte Heart = {
0b00000,
0b01010,
0b11111,
0b11111,
0b01110,
0b00100,
0b00000,
0b00000
};

byte Bell = {
0b00100,
0b01110,
0b01110,
0b01110,
0b11111,
0b00000,
0b00100,
0b00000
};


byte Alien = {
0b11111,
0b10101,
0b11111,
0b11111,
0b01110,
0b01010,
0b11011,
0b00000
};

byte Check = {
0b00000,
0b00001,
0b00011,
0b10110,
0b11100,
0b01000,
0b00000,
0b00000
};

byte Speaker = {
0b00001,
0b00011,
0b01111,
0b01111,
0b01111,
0b00011,
0b00001,
0b00000
};


byte Sound = {
0b00001,
0b00011,
0b00101,
0b01001,
0b01001,
0b01011,
0b11011,
0b11000
};


byte Skull = {
0b00000,
0b01110,
0b10101,
0b11011,
0b01110,
0b01110,
0b00000,
0b00000
};

byte Lock = {
0b01110,
0b10001,
0b10001,
0b11111,
0b11011,
0b11011,
0b11111,
0b00000
};

void setup() 
{
  // инициализируем LCD и устанавливаем количество столбцов и строк: 
  lcd.begin(16, 2);

  // создание нового символа
  lcd.createChar(0, Heart);
  // создание нового символа
  lcd.createChar(1, Bell);
  // создание нового символа
  lcd.createChar(2, Alien);
  // создание нового символа
  lcd.createChar(3, Check);
  // создание нового символа
  lcd.createChar(4, Speaker);
  // создание нового символа
  lcd.createChar(5, Sound);
  // создание нового символа
  lcd.createChar(6, Skull);
  // создание нового символа
  lcd.createChar(7, Lock);

  // Очищаем LCD дисплей 
  lcd.clear();

  // Печатаем сообщение на LCD.
  lcd.print("Custom Character");
}

// Печатаем все пользовательские символы
void loop() 
{ 
  lcd.setCursor(0, 1);
  lcd.write(byte(0));

  lcd.setCursor(2, 1);
  lcd.write(byte(1));

  lcd.setCursor(4, 1);
  lcd.write(byte(2));

  lcd.setCursor(6, 1);
  lcd.write(byte(3));

  lcd.setCursor(8, 1);
  lcd.write(byte(4));

  lcd.setCursor(10, 1);
  lcd.write(byte(5));

  lcd.setCursor(12, 1);
  lcd.write(byte(6));

  lcd.setCursor(14, 1);
  lcd.write(byte(7));
}

После включения библиотеки нам нужно инициализировать пользовательский массив из восьми байтов.

byte Heart = {
0b00000,
0b01010,
0b11111,
0b11111,
0b01110,
0b00100,
0b00000,
0b00000
};

В настройках мы должны создать пользовательский символ, используя функцию createChar(). Эта функция принимает два параметра. Первый — это число от 0 до 7, чтобы зарезервировать один из 8 поддерживаемых пользовательских символов. Второй параметр — это имя массива байтов.

// создание нового символа
lcd.createChar(0, Heart);

Далее в цикле для отображения пользовательского символа мы используем функцию write(), а в качестве параметра мы используем номер символа, который мы зарезервировали.

// byte(0) покажет символ Heart (сердце).
lcd.write(byte(0));

Example#2: Display different information on 2 Grove — OLED Display 1.12¶

Connection

Here we will show you how this works via a simple demo. First of all, you need to prepare the below stuffs:

Seeeduino V4 Grove — OLED Display 1.12« Base Shield
Get ONE Now Get ONE Now Get ONE Now

Connect one Grove — OLED Display 1.12 to D2 port and other to D4 port.

Software

  • Click on here to download Grove-OLED-Display-1.12 library.
  • Copy SeeedGrayOLED.cpp and SeeedGrayOLED.h to Arduino_Software_I2C-master folder
  • Edit SeeedGrayOLED.cpp

    Step1: Change the library from Wire.h to SoftwareI2C.h

Step2: Add initSoftwareI2C function, we have to change the class name for different products.

Step3: Replace all Wire. to Wire-> For example, change Wire.endTransmission() to Wire->endTransmission().

  • Edit SeeedGrayOLED.h

    Step1: Change the library Wire.h to SoftwareI2C.h

Step2: Add initSoftwareI2C function into public class

Step3: Add SoftwareI2C *Wire into private class

Open the code directly by the path: File -> Example ->Arduino_Software_I2C-master->OLED_Display.

We have to define SoftwareI2C objects as well as SeeedGrayOLED objects.

We use initSoftwareI2C instead of Wire.begin during setup.

  • Upload to Sketch.
  • We will see 11111111 display on one screen while 00000000 is on other.

5Управление устройством по шине IIC

Рассмотрим диаграммы информационного обмена с цифровым потенциометром AD5171, представленные в техническом описании:

Рассмотрим диаграммы чтения и записи цифрового потенциометра AD5171

Нас тут интересует диаграмма записи данных в регистр RDAC. Этот регистр используется для управления сопротивлением потенциометра.

Откроем из примеров библиотеки «Wire» скетч: Файл Образцы Wire digital_potentiometer. Загрузим его в память Arduino.

#include <Wire.h> // подключаем библиотеку "Wire"
byte val = 0; // значение для передачи потенциометру

void setup() {
  Wire.begin();   // подключаемся к шине I2C как мастер
}

void loop() {
  Wire.beginTransmission(44); // начинаем обмен с устройством с I2C адресом "44" (0x2C)
  Wire.write(byte(0x00)); // посылаем инструкцию записи в регистр RDAC
  Wire.write(val); // задаём положение 64-позиционного потенциометра
  Wire.endTransmission(); // завершаем I2C передачу

  val++; // инкрементируем val на 1
  if (val == 63) { // по достижении максимума потенциометра
    val = 0; // сбрасываем val 
  }
  delay(500);
}

После включения вы видите, как яркость светодиода циклически нарастает, а потом гаснет. При этом мы управляем потенциометром с помощью Arduino по шине I2C.

По ссылкам внизу статьи, в разделе похожих материалов (по тегу), можно найти дополнительные примеры взаимодействия с различными устройствами по интерфейсу IIC, в том числе примеры чтения и записи.

3Библиотека «Wire» для работы с IIC

Для облегчения обмена данными с устройствами по шине I2C для Arduino написана стандартная библиотека Wire. Она имеет следующие функции:

Функция Назначение
begin(address) инициализация библиотеки и подключение к шине I2C; если не указан адрес, то присоединённое устройство считается ведущим; используется 7-битная адресация;
requestFrom() используется ведущим устройством для запроса определённого количества байтов от ведомого;
beginTransmission(address) начало передачи данных к ведомому устройству по определённому адресу;
endTransmission() прекращение передачи данных ведомому;
write() запись данных от ведомого в ответ на запрос;
available() возвращает количество байт информации, доступных для приёма от ведомого;
read() чтение байта, переданного от ведомого ведущему или от ведущего ведомому;
onReceive() указывает на функцию, которая должна быть вызвана, когда ведомое устройство получит передачу от ведущего;
onRequest() указывает на функцию, которая должна быть вызвана, когда ведущее устройство получит передачу от ведомого.

Часть первая, I2C и библиотека «Wire».

Последовательный протокол обмена данными IIC (также называемый I2C — Inter-Integrated Circuits, межмикросхемное соединение). Разработана фирмой Philips Semiconductors в начале 1980-х как простая 8-битная шина внутренней связи для создания управляющей электроники. Так как право на использование его стоит денег фарма Atmel назвала его TWI, но смысл от этого не меняется.

Как это работает ?

Для передачи данных используются две двунаправленные лини передачи данных. SDA (Serial Data) шина последовательных данных и SCL (Serial Clock) шина тактирования. Обе шины подтянуты резисторами к плюсовой шине питания. Передача/Прием сигналов осуществляется прижиманием линии в 0, в единичку устанавливается сама, за счет подтягивающих резисторов.

В сети есть хотя бы одно ведущее устройство (Master), которое инициализирует передачу данных и генерирует сигналы синхронизации и ведомые устройства (Slave), которые передают данные по запросу ведущего. У каждого ведомого устройства есть уникальный адрес, по которому ведущий и обращается к нему. Конечно понятно что Ведущий это наш микроконтроллер , а ведомый наша память. Ведущее устройство начинает прижимать шину SCL  к нулю с определенной частотой, а шину SDA прижимать или отпускать на определенное число тактов передавая Единичку или Нолик. Передача данных начинается с сигнала START потом передается 8 бит данных и 9-тым битом Ведомое устройство подтверждает прием байт  прижимая шину SDA к минусу. Заканчивается передача сигналом STOP.

Библиотека «Wire».

Для облегчения обмена данными с устройствами по шине I2C для Arduino написана стандартная библиотека Wire которая есть уже в комплекте IDE. Она имеет следующие основные функции:

Wire.begin(Address) вызывается один раз для инициализации  и подключения к шини как Ведущий или Ведомое устройство. Если Address не задан подключаемся как Мастер устройство.

Wire.beginTransmission(address)  начинает передачу на ведомое I2C устройство с заданным адресом.

Wire.endTransmission() прекращает передачу данных ведомому. Функция возвращает значение типа byte:

  • 0 — успех.
  • 1- данные слишком длинны для заполнения буфера передачи.
  • 2 — принят NACK при передаче адреса.
  • 3 — принят NACK при передаче данных.
  • 4 — остальные ошибки.

Wire.write() запись данных  от ведомого устройства в отклик на запрос от ведущего устройства, или ставит в очередь байты для передачи от мастера к ведомому устройству.Фактически записывает данные в буфер. Размер буфера 32 байта ( минус 2 байта адрес, фактически 30 байт), а передает буфер функция Wire.endTransmission().

  • Wire.write(value) — value: значение для передачи, один байт.
  • Wire.write(string) — string: строка для передачи, последовательность байтов.
  • Wire.write(data, length) — data: массив данных для передачи, байты. length: количество байтов для передачи.

Wire.read() Считывает байт, который был передан от ведомого устройства к ведущему или который был передан от ведущего устройства к ведомому. Возвращаемое значение byte : очередной принятый байт.

Это самые основные функции библиотеке, остальные мы рассмотрим по ходу пьесы ))

7Что находится «за» шиной I2C

В качестве бонуса рассмотрим временную диаграмму вывода латинских символов «A», «B» и «С» на ЖК дисплей. Эти символы имеются в ПЗУ дисплея и выводятся на экран просто передачей дисплею их адреса. Диаграмма снята с выводов RS, RW, E, D4, D5, D6 и D7 дисплея, т.е. уже после преобразователя FC-113 «I2C параллельная шина». Можно сказать, что мы погружаемся немного «глубже» в «железо».

Временная диаграмма вывода латинских символов «A», «B» и «С» на LCD дисплей 1602

На диаграмме видно, что символы, которые имеются в ПЗУ дисплея (см. стр.11 даташита, ссылка ниже), передаются двумя полубайтами,
первый из которых определяет номер столбца таблицы, а второй – номер строки. При этом данные «защёлкиваются» по фронту сигнала на линии E (Enable), а линия RS (Register select, выбор регистра) находится в состоянии логической единицы, что означает передачу данных. Низкое состояние линии RS означает передачу инструкций, что мы и видим перед передачей каждого символа. В данном случае передаётся код инструкции возврата каретки на позицию (0, 0) ЖК дисплея, о чём также можно узнать, изучив техническое описание дисплея.

И ещё один пример. На этой временной диаграмме показан вывод символа «Сердце» на ЖК дисплей.

Временная диаграмма вывода символа «Сердце» из ПЗУ на ЖК дисплей 1602

Опять, первые два импульса Enable соответствуют инструкции Home() (0000 00102) – возврат каретки на позицию (0; 0), а вторые два – вывод на ЖК дисплей хранящийся в ячейке памяти 310 (0000 00112) символ «Сердце» (инструкция lcd.createChar(3, heart); скетча).

3Чтение данных датчика BMP280с помощью микросхемы FTDI FT2232H

Программная часть готова для того чтобы обменяться данными с нашим старым знакомым – датчиком температуры и давления BMP280 (приобретаем у китайцев). Только на этот раз мы будем «общаться» с ним по интерфейсу I2C. Но сначала нужно подключить датчик по схеме:

Схема подключения BMP280 к FT2232 по интерфейсу I2C

Я подключаю ко второму каналу (выводам 38 и 39 микросхемы FT2232). Если будете подключать к первому, это выводы 16 и 17. А вообще, лучше свериться с техническим описанием (datasheet) на микросхему, т.к. фирма FTDI Chip выпускает большое число различных микросхем, и назначение выводов может не совпадать.

Обратите внимание, что вывод SDO датчика BMP280 необходимо подключить к питанию или к земле, он не должен «висеть» в воздухе. Это влияет на его I2C адрес:

Схема соединения Адрес I2C
SDO соединён с Vdd 0x77
SDO соединён с GND 0x76

Теперь всё готово. Прочитаем регистр ID датчика, в котором, как мы помним, хранится постоянный идентификатор, равный 0x58. Посмотрим в описании на датчик, как с ним должен происходить обмен по I2C:

Порядок записи и чтения BMP280 по интерфейсу I2C

Верхний рисунок показывает последовательность записи, а нижний – чтения.

Получается, что сначала нам нужно записать в I2C устройство с адресом 0x77 адрес регистра ID 0x0D, а затем прочитать из него же 1 байт. Используя только что написанные классы, сделаем это.

Первым делом импортируем пространства имён, в которых находятся наши классы для работы с устройствами FTDI:

Imports Ftdi
Imports Ftdi.MpsseI2c

Опишем параметры I2C, которыми мы хотим инициализировать канал (устройство). Используем, например, стандартные параметры:

Dim config As New I2cConfig() With {
    .ClockRate = I2cSpeed.I2C_CLOCK_STANDARD_MODE,
    .LatencyTimer = 2,
    .ConfigOptions = 0
}

Теперь подключимся ко второму каналу и инициализируем его только что созданными настройками:

Dim device As New MpsseI2c(1) 
device.InitChannel(config) 

Создадим переменную для хранения опций передачи. Потом запишем в устройство адрес регистра и прочитаем из него 1 байт с указанными опциями:

Dim options As I2C_TRANSFER_OPTIONS = I2C_TRANSFER_OPTIONS.START_BIT Or I2C_TRANSFER_OPTIONS.STOP_BIT Or I2C_TRANSFER_OPTIONS.FAST_TRANSFER
device.Write(&H77, {&HD0}, options)
Dim id As Byte() = device.Read(&H77, 1, options)

В первом (и единственном) байте ответа должно находиться число 0x58.

Вот как информационный обмен с сенсором bmp280 по интерфейсу IIC выглядит на временной диаграмме с логического анализатора:

Временная диаграмма обмена по I2C между BMP280 и FT2232

Мы помним, что I2C адрес устройства передаётся в старших 7-ми битах первого байта, а в младшем бите находится признак чтения (1) или записи (0). То есть в двоичном виде адрес 0x77 и маркер записи 0x0 дают: 01110111_0 = 0xEE, а адрес 0x77 и маркер чтения 0x1 дают 01110111_1 = 0xEF.

Зелёные и красные кружки на рисунке – это маркеры начала и конца передачи (те самые опции I2C_TRANSFER_OPTIONS.START_BIT и I2C_TRANSFER_OPTIONS.STOP_BIT). Биты ACK (каждый 9-ый бит) – это подтверждение, что в I2C сети имеется ведомое устройство с запрошенным адресом, и оно готово принять сообщение. Последний бит NAK сигнализирует об окончании обмена.

Выводы

Мы рассмотрели возможную реализацию взаимодействия по последовательному интерфейсу I2C, реализуемую с помощью динамически подключаемой библиотеки libMPSSE.dll фирмы FTDI Chip на языке VB.NET.

Мы установили связь по интерфейсу I2C с датчиком давления и температуры BMP280 и прочитали его регистр ID.

Пример кода (скетч) для дисплея “LCD2004” (интерфейс “I2C”) и Arduino:

Пример скетча для 20 символьного 4 строкового дисплея

Arduino

#include <Wire.h>
// библиотека для LCD I2C:
// https://bitbucket.org/fmalpartida/new-liquidcrystal/downloads
#include <LiquidCrystal_I2C.h>
// LCD адрес — 0x27 для 20 символьного 4 строкового дисплея
// назначение контактов на I2C LCD:
// addr, en,rw,rs,d4,d5,d6,d7,bl,blpol
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); // bl — backlight, blpol — полярность подсветки

void setup()
{
Serial.begin(9600); //

lcd.begin(20,4); // инициализация lcd 20 символьного 4 строкового дисплея, подсветка включена
delay(2000);
lcd.noBacklight(); // выключить подсветку
delay(2000);
lcd.backlight(); // включить подсветку

// нумерация позиции курсора для строки и символа начинается с 0
lcd.setCursor(0,0); // начало с символа 1 строка 1
lcd.print(«Char 1, Row 1»);
delay(1000);
lcd.setCursor(1,1); // начало с символа 2 строка 2
lcd.print(«Char 2, Row 2»);
delay(1000);
lcd.setCursor(2,2); // начало с символа 3 строка 3
lcd.print(«Char 3, Row 3»);
lcd.setCursor(3,3); // начало с символа 4 строка 4
delay(1000);
lcd.print(«Char 4, Row 4»);
delay(5000);
lcd.clear(); // очистка дисплея
lcd.setCursor(0,0);
lcd.print(«www.umnyjdomik.ru»);
lcd.setCursor(0,1);
lcd.print(«Start Serial Monitor»);
lcd.setCursor(0,2);
lcd.print(«Type chars on keyboard»);
}

void loop()
{
{
if (Serial.available()) {
delay(100); // задержка для получения всего сообщения
lcd.clear();
// чтение всех возможных символов
while (Serial.available() > 0) {
lcd.write(Serial.read()); // отображение каждого символа на дисплее LCD
}
}
}
}

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

#include <Wire.h>  
// библиотека для LCD I2C:
// https://bitbucket.org/fmalpartida/new-liquidcrystal/downloads
#include <LiquidCrystal_I2C.h>
//  LCD адрес — 0x27 для 20 символьного 4 строкового дисплея
// назначение контактов на I2C LCD:
//                    addr, en,rw,rs,d4,d5,d6,d7,bl,blpol

LiquidCrystal_I2Clcd(0x27,2,1,,4,5,6,7,3,POSITIVE);// bl — backlight, blpol — полярность подсветки

voidsetup()

{

Serial.begin(9600);//

lcd.begin(20,4);// инициализация lcd 20 символьного 4 строкового дисплея, подсветка включена

delay(2000);

lcd.noBacklight();// выключить подсветку

delay(2000);

lcd.backlight();// включить подсветку

// нумерация позиции курсора для строки и символа начинается с 0  

lcd.setCursor(,);// начало с символа 1 строка 1

lcd.print(«Char 1, Row 1»);

delay(1000);

lcd.setCursor(1,1);// начало с символа 2 строка 2

lcd.print(«Char 2, Row 2»);

delay(1000);

lcd.setCursor(2,2);// начало с символа 3 строка 3

lcd.print(«Char 3, Row 3»);

lcd.setCursor(3,3);// начало с символа 4 строка 4

delay(1000);

lcd.print(«Char 4, Row 4»);

delay(5000);

lcd.clear();// очистка дисплея

lcd.setCursor(,);

lcd.print(«www.umnyjdomik.ru»);

lcd.setCursor(,1);

lcd.print(«Start Serial Monitor»);

lcd.setCursor(,2);

lcd.print(«Type chars on keyboard»);

}
 

voidloop()

{

{

if(Serial.available()){

delay(100);// задержка для получения всего сообщения

lcd.clear();

// чтение всех возможных символов

while(Serial.available()>){

lcd.write(Serial.read());// отображение каждого символа на дисплее LCD

}

}

}

}

3Библиотека «Wire» для работы с IIC

Для облегчения обмена данными с устройствами по шине I2C для Arduino написана стандартная библиотека Wire. Она имеет следующие функции:

Функция Назначение
begin(address) инициализация библиотеки и подключение к шине I2C; если не указан адрес, то присоединённое устройство считается ведущим; используется 7-битная адресация;
requestFrom() используется ведущим устройством для запроса определённого количества байтов от ведомого;
beginTransmission(address) начало передачи данных к ведомому устройству по определённому адресу;
endTransmission() прекращение передачи данных ведомому;
write() запись данных от ведомого в ответ на запрос;
available() возвращает количество байт информации, доступных для приёма от ведомого;
read() чтение байта, переданного от ведомого ведущему или от ведущего ведомому;
onReceive() указывает на функцию, которая должна быть вызвана, когда ведомое устройство получит передачу от ведущего;
onRequest() указывает на функцию, которая должна быть вызвана, когда ведущее устройство получит передачу от ведомого.

4Скетч для вывода текста на LCD экран по шине I2C

#include <Wire.h>  // подключаем библиотеку Wire
#include <LiquidCrystal_I2C.h>  // подключаем библиотеку ЖКИ

#define printByte(args) write(args); //

uint8_t heart = {0x0,0xa,0x1f,0x1f,0xe,0x4,0x0}; // битовая маска символа «сердце»

LiquidCrystal_I2C lcd(0x27, 16, 2); // Задаём адрес 0x27 для LCD дисплея 16x2

void setup() {
  lcd.init();  // инициализация ЖК дисплея
  lcd.backlight();  // включение подсветки дисплея
  lcd.createChar(3, heart);  // создаём символ «сердце» в 3 ячейке памяти
  lcd.home();  // ставим курсор в левый верхний угол, в позицию (0,0)
  
  lcd.print("Hello SolTau.ru!");  // печатаем строку текста
  lcd.setCursor(0, 1);  // перевод курсора на строку 2, символ 1
  lcd.print(" i ");  // печатаем сообщение на строке 2
  lcd.printByte(3); // печатаем символ «сердце», находящийся в 3-ей ячейке
  lcd.print(" Arduino ");
}

void loop() { // мигание последнего символа
  lcd.setCursor(13, 1);   // перевод курсора на строку 2, символ 1
  lcd.print("\t");
  delay(500);             
  lcd.setCursor(13, 1);   // перевод курсора на строку 2, символ 1
  lcd.print(" ");
  delay(500);
}

Кстати, символы, записанные командой lcd.createChar();, остаются в памяти дисплея даже после выключения питания, т.к. записываются в ПЗУ дисплея 1602.

Оцените статью:
Оставить комментарий