Урок 32
Дисплей LCD 20×4. Расширяем функционал
Сегодня мы продолжим работу с символьным дисплеем 20×4 и попробуем немного расширить его функционал. Зачем это потребуется, мы узнаем в более поздних уроках.
Дисплей мы подключим обычным 4-битным образом, ну, вернее мы его вообще не будем подключать, он у нас с прошлого урока подключен.
Сразу перейдём к проекту. Проект был назван MyLCD80, файлы lcd.h, lcd.c и main.h были перенесены и подключены из предыдущего урока по подключению посредством шины SPI двух контроллеров друг к другу. Ну, так как у нас контроллер ATmega8A, то файлы были взяты из проекта для данного контроллера.
В файл main.h подключим ещё библиотеку для работы со строками
#include <stdlib.h>
#include <string.h>
А в главный модуль подключим только main.h, а остальное оставим пустым. После этого иметь он будет вот такой простой вид
#include «main.h»
//—————————————-
int main(void)
{
while(1)
{
}
}
Добавим обычную инициализацию порта D
#include «main.h»
//—————————————-
void port_ini(void)
{
PORTD=0x00;
DDRD=0xFF;
}
//—————————————-
И вызовем данную функцию в main()
int main(void)
{
port_ini(); //инициализируем порты
А весь остальной код для main() возьмём с урока по переходнику I2C кроме инициализации I2C и в конце добавим задержку
port_ini(); //инициализируем порты
LCD_ini(); //инициализируем дисплей
clearlcd();//очистим дисплей
setpos(0,0);
str_lcd(«String 1»);
setpos(2,1);
str_lcd(«String 2»);
setpos(4,2);
str_lcd(«String 3»);
setpos(6,3);
str_lcd(«String 4»);
_delay_ms(2000);
while(1)
Соберём на всякий случай и проверим работу кода в протеусе
Теперь прошьём контроллер и посмотрим результат на живом дисплее
Всё у нас работает. Также мы видим резистор, подключенный к подсветке, чтобы ограничить ток и уберечь светодиоды от несвоевременного выхода из строя.
Продолжим дальше.
Вообщем, задача у нас такая. Нам нужно написать функцию, которая будет выводить все 80 символов из одного буфера. Зачем это нужно? Я планирую в будущем поработать с картой SD и там надо будет выводить немалые тексты, а короткими строками это делать не очень удобно. Да и вообще данная функция не помешает нашей библиотеке, и, может быть, когда-нибудь пригодится.
Зайдём в файл lcd.c и добавим туда функцию
//—————————————-
void str80_lcd (char str1[])
{
}
//—————————————-
Добавим данной функции прототип в хедере (я не буду больше постить прототипы, так как, я думаю все это уже давно умеют и не стоит лишним текстом засорять страницы). Затем добавим в нашу функцию переменную
void str80_lcd (char str1[])
{
unsigned char n;
И будем потихоньку выводить все строки.
Первые 20 символов строки буфера мы выведем в первую строку дисплея
unsigned char n;
sendbyte(0x80,0);//1 строка
for(n=0;n<=19;n++)
sendcharlcd(str1[n]);
Здесь мы сначала устанавливаем текущий адрес в DDRAM и в цикле отправляем байты в данную память дисплея.
Дальше мы применим хитрость, используя организацию памяти контроллера дисплея, лишний раз заглянув в его даташит
Как мы видим, адресация у нас продолжается на 3 строке, поэтому мы её и выведем
for(n=0;n<=19;n++)
sendcharlcd(str1[n]);
for(n=40;n<=59;n++)//на 3ю строку перейдём автоматически в силу организации DDRAM дисплея
sendcharlcd(str1[n]);
Затем установим указатель на 2 строку и выведем её на дисплей
for(n=40;n<=59;n++)//на 3ю строку перейдём автоматически в силу организации DDRAM дисплея
sendcharlcd(str1[n]);
sendbyte(0xC0,0);//2 строка
for(n=20;n<=39;n++)
sendcharlcd(str1[n]);
Также как и выше, мы используем таблицу и без позиционирования выведем строку 4
for(n=20;n<=39;n++)
sendcharlcd(str1[n]);
for(n=60;n<=79;n++)//на 4ю строку перейдём автоматически в силу организации DDRAM дисплея
sendcharlcd(str1[n]);
}
Вот, в принципе, и вся функция.
Вернёмся в главный модуль и добавим строку большой длины, чтобы её потом вывести. Символов в строке будет 512. А 512 потому, что в блоке SD-карты у нас ровно столько байтов. Текст возьмём любой. Здесь я его немного сокращу, а то мало ли она какая авторская и индексные роботы yandex и google нас за это по головке не погладят
#include «main.h»
//—————————————-
char buffer[512]=«The … read operation…»;//буфер данных для вывода
//—————————————-
Соответственно, в приложенном архиве с проектом строка будет полностью.
Вывод строк мы оставим в main() и после задержки попробуем вызвать нашу функцию. На не нужно использовать никакое позиционирование, так как у нас и так всё в функции позиционируется отлично
_delay_ms(2000);
str80_lcd(buffer);
Ну давайте соберём код и запустим его сначала в протеусе
Всё отлично отображается. Прошьём контроллер и проверим на настоящем дисплее
Точно так же всё работает прекрасно.
Только есть одна загвоздка, мы видим только часть текста, то есть от 512 символов мы видим только 80.
Ну давайте тогда придумаем, как нам пошагать по всему буферу.
Добавим ещё одну задержку в код и также ещё кое-что
str80_lcd(buffer);
_delay_ms(1000);
str80_lcd(buffer+20);
Если вы не знаете, как работать с указателями памяти, то мы указатель переместили на 20 элементов массива, в нашем случае на 20 символов вперёд.
Тем самым мы начнём вывод уже с тех символов, которые в предыдущем выводе у нас были на 2 строке, так как в строке у нас 20 символов.
Попробуем собрать код. В протеусе можно уже не смотреть, будем смотреть на настоящем дисплее. Поэтому прошьём контроллер и посмотрим
И через секунду текст у нас переместился вверх и дисплей нам в нижней строке показал следующие 20 символов.
Покажем ещё 20 символов
str80_lcd(buffer+20);
_delay_ms(1000);
str80_lcd(buffer+40);
Посмотрим результат
Текст сместился вверх ещё на одну строку.
А теперь, чтобы нам не мучиться и не писать так для каждой строки, сделаем всё это в цикле, добавив для начала локальную переменную для счётчика в main()
int main(void)
{
unsigned int i;
port_ini(); //инициализируем порты
По идее, здесь хватило бы и типа поменьше, но мы потом ещё попробуем посдвигать текст посимвольно. Закомментируем предыдущий код вывода наших 80-символьных страниц и добавим цикл
// str80_lcd(buffer+40);
for (i=0;i<=22;i++) {str80_lcd(buffer+i*20);_delay_ms(1000);}
Проверим результат. Конечно в рамках веб-страницы этот эффект не покажешь, поэтому смотрите видеоурок, ссылкой на который является картинка, размещённая в конце страницы. Покажу только последнюю страницу текста
Мы видим, что в конце у нас вывелась ещё дальнейшая область памяти, там у нас самая первая строка, которую мы вызывали в начале программы. Заодно мы увидели организацию памяти в контроллере.
Теперь закомментируем последнюю написанную страничку кода и напишем другой цикл, который будет сдвигать текст посимвольно
//for (i=0;i<=22;i++) {str80_lcd(buffer+i*20);_delay_ms(1000);}
for (i=0;i<=432;i++) {str80_lcd(buffer+i);_delay_ms(100);}
Здесь мы уже точно отняли 80 от величины буфера, поэтому текст должен будет вывести без лишней области памяти
Мы видим, что ничего лишнего уже не вывелось. Обязательно посмотрите видеоверсию урока. Выглядит очень красиво.
Итак, мы сегодня расширили свою библиотеку по работе с дисплеем, а также потренировались с указателями памяти, что, можно сказать, даже важнее.
Теперь мы можем на дисплее спокойно выводить и читать небольшие тексты.
Предыдущий урок Программирование МК AVR Следующий урок
Программатор и дисплей можно приобрести здесь:
Программатор USBASP USBISP с адаптером USBASP USBISP 3.3 с адаптером
Смотреть ВИДЕОУРОК (нажмите на картинку)
Подскажите, что я делаю не так. Текстовый экран LCD2004 подключен через I2C. При включении идет инициализация I2C, потом инициализация экрана. Все работает. Происходит (так и должно быть) пропадании питания с экрана (ATmega128 остается под питанием от батарейки, I2C работает, судя по протеусу). Линию питания экрана бдит нога с внешним прерыванием. При возобновлении питания происходит прерывание, где: останавливаю передачу данных по I2C и инициализирую заново экран. далее прерывание заканчивается и по коду он должен выводить на экран текс что был до пропажи питания. Приемлемый результат получается 1 из 100 раз. В остальных случаях либо срабатывает watchdog (если перед инициализацией поставить задержку), либо выводятся кракозябры в случайном порядке, как будто инициализации не было, либо чистый экран. Мозги работают, таймеры работают, на кнопки так же отзывается. Только не выводит на экран. Основа кода ваша. Пробовал отключать сторожевого пса, пробовал варьировать задержки перед инициализацией, пробовал без передачи команды стоп для i2c, отдельно опробовал работу внешнего прерывания, что следит за питанием. Изначально было в планах реализовать режим power-save, с пробуждением по внешнему прерыванию, но пока без него. Спасибо
Добрый день.
Не подскажете как вывести по строчно на дисплей,по символу конца строки (0x0a)?
Чтобы строка смещалась как у вас в примере.
Здравствуйте. Спасибо автору за столь подробное и информативное изложение.
Хотелось бы немного добавить по поводу отображения кириллицы. Контроллер дисплея корректно не отображает кириллицу, однако содержит в своей таблице знакогенератора все символы русского алфавита. Для удобного преобразования из ASCII в код, соответствующий этой таблице, предлагаю следующее преобразование кода:
const char convert[64] = {0x41,0xa0,0x42,0xa1,0xe0,0x45,0xa3,0xa4,0xa5,0xa6,0x4b,0xa7,0x4d,0x48,0x4f,0xa8,0x50,0x43,0x54,0xa9,0xaa,0x58,0xe1,0xab,0xac,0xe2,0xad,0xae,0x62,0xaf,0xb0,0xb1, 0x61, 0xb2, 0xb3, 0xb4, 0xe3, 0x65, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0x6f, 0xbe, 0x70, 0x63, 0xbf, 0x79, 0xe4, 0x78, 0xe5, 0xc0, 0xc1, 0xe6, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7}; // коды символов А-Я и а-я кроме букв Ё и ё
void sendcharlcd_rus(unsigned char c) {
switch(c) {
case 0xa8: c = 0xa2; break; // буква Ё
case 0xb8: c = 0xb5; break; // буква ё
default: break;
}
if (c > 0xbf) {
c = convert[c — 0xc0]; // преобразуем код символа согласно таблице знакогенератора контроллера дисплея
// 0xc0 = 192, соответствует букве A таблицы ASCII
}
sendbyte(c,1);
}
Для отображения символа, имеющего в таблице знакогенератора дисплея код в диапазоне 192-256,
вывод необходимо производить в обход преобразования кода.
Проверил на китайском no name дисплее, корректно отображает.
По поводу корректного отображения кириллицы на данном дисплее в Proteus подробно описано в посте #106 здесь: (внешняя сторонняя ссылка убрана, ссылками обмениваемся в чатах).
Также в приложениях к посту можно найти таблицу знакогенератора, на которой основано преобразование.
P.S. Перед использованием данного преобразования нужно быть уверенным, что таблица знакогенератора используемого Вами дисплея совпадает с приведённой по ссылке выше.
Надеюсь, этот материал окажется полезным, удачи в проектах)))