Урок 12
Часть 5
LCD индикатор 16×2
Сегодня мы продолжим изучение жидкокристаллического индикатора символьного, который способен выводить определённые символы в две строки по 16 символов в каждую.
В прошлой части мы завершили и проверили написание кода функции, которая выводит любой символ на экран дисплея.
Теперь пришло время написать функцию вывода на экран целой строки, так как выводить посимвольно не совсем удобно, хотелось бы работать со строками. Добавим данную функцию прямо перед функцией main() и передавать мы ей будем массив символов неопределённой размерности
//—————————————-
void str_lcd (char str1[])
{
}
//—————————————-
Вызовем данную функцию в main(), удалив перед этим весь код посимвольного вывода на дисплей
LCD_ini(); //Инициализируем дисплей
setpos(0,0);
str_lcd(«Hello World!»);
Дальше начнём писать тело функции вывода строки. Объявим в теле функции переменную для символа. Переменная у нас будет несколько иного типа. Как правило с таким типом лучше распознаются коды символов. Вы можете, конечно, поэксперементировать с другими типами
void str_lcd (char str1[])
{
wchar_t n;
Далее мы, соответственно, организуем цикл и будем попеременно перебирать все переданные символы в массиве и выводить их на дисплей. Применим также мы вариант представления нулевого симвлова 'n' и именно до него мы и будем перебирать символы
wchar_t n;
for(n=0;str1[n]!=»;n++)
sendchar(str1[n]);
}
Соберём код и проверим в протеусе работу кода
Теперь можно попробовать вывести строку ещё и в другое место экрана. Напишем код в main()
str_lcd(«Hello World!»);
setpos(2,1);
str_lcd(«String 2»);
while(1)
Соберём код и посмотрим результат
Всё работает! Отлично!
Ну конечно нужно ещё помотреть, как будет код работать на живом дисплее с живым контроллером. Для этого мы прошьём контроллер
Оформляем функции в отдельный модуль
Дошли мы с кодом до такого состояния, что наш главный и единственный файл с кодом переполнился до того, что в нём теперь тяжело уже вообще что-то найти. Как же мы с этим можем боротья? Бороться с этим мы будем путём оформления кода функций для отдельно взятого устройства или шины или какой-то технологии в отдельный модуль. Грамотный модуль состоит как правило из заголовочного файла и файла реализации функций. Поэтому давайте для нашего LCD дисплея мы так и поступим. Также это всё дело нужно для того, что если мы будем писать новый проект, то мы данные файлы будем просто к нему подключать, если нам потребуется воспользоваться LCD дисплеем. Это будет нашей так называемой библиотекой для дисплея. Конечно, библиотеки обычно пишутся и компилируются в отдельный файл lib, но в этом случае обычно нет исходного кода и данные библиотеки не могут быть подправлены. А наша библиотека будет вполне исправимой и нам ещё ой как пригодится в будущем.
Но прежде, чем создать данную библиотеку, мы создадим главный заголовочный файл и назовём его main.h, чтобы убрать в данный файл все подключенные библиотеки, различные глобальные переменные и макроподставновки
Для этого мы в дереве проектов щёлкаем правой кнопкой по нашему проекту Test09 и выбираем в контекстном меню субменю Add, а в нём уже выбираем пункт New Item
И в открывшемся диалоге выбираем тип файла, который мы будем создавать, «Include File» И внизу в имени файла меняем IncFile1 на main, затем жмём кнопку Add.
Соответственно, у нас создастся файл main.c вот с таким содержимым
#ifndef MAIN_H_
#define MAIN_H_
#endif /* MAIN_H_ */
Это очень хорошо. Например. в Keil, когда мы занимается программированием контроллеров STM, мы должны это всё писать в ручную. Здесь данная директива говорит о том, что, если файл уже подключался в исполняемый код, чтобы прероцессор его повторно не включал.
В данный файл мы посместим подключения всех заголовочных файлов библиотек и все макроподстановки, а в файле Test09.c всё это, конечно, мы удалим
#ifndef MAIN_H_
#define MAIN_H_
#define F_CPU 8000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdio.h>
#include <stdlib.h>
//—————————————-
#define e1 PORTD|=0b00001000 // установка линии E в 1
#define e0 PORTD&=0b11110111 // установка линии E в 0
#define rs1 PORTD|=0b00000100 // установка линии RS в 1 (данные)
#define rs0 PORTD&=0b11111011 // установка линии RS в 0 (команда)
//—————————————-
#endif /* MAIN_H_ */
Но недостаточно данный заголовочный файл подключить в Solution Explorer, его также мы должны в файле Test09.c подключить в самом начале в код
#include «main.h»
//—————————————-
Соберём проект, ещё раз проверим его работоспособность.
Теперь начнём создавать нашу библиотеку для дисплея.
Для этого мы таким же образом, как и main.h, создадим заголовочный файл lcd.h
Содержание получится аналогичным, только вместо MAIN везде будет LCD. Подключим данный файл в файле main.h
#include <stdlib.h>
#include «lcd.h»
И наоборот, в файл lcd.h мы подключим файл main.h
#ifndef LCD_H_
#define LCD_H_
#include «main.h»
Насчет того, что получится какое-то перекрёстное зацикливание, можно не беспокоиться — директивы не дадут такому случиться.
Также все макроподстановки из файла main.h мы заберём в файл lcd,h, а в main.h удалим
#include «main.h»
//—————————————-
void LCD_ini(void);
void setpos(unsigned char x, unsigned y);
void str_lcd (char str1[]);
void sendchar(unsigned char c);
//—————————————-
А, чтобы забрать все функции по работе с дисплеем из файла Test09.c, мы создадим теперь уже другой файл — lcd.с. В нём и будет код реализации всех функций
Создаётся файл точно таким же образом, только вместо «Include File» мы выбираем тип файла «C File».
Файл lcd.c создан. В нём уже не будет никаких директив, единственное, будет авторский комментарий, который мы удалим, чтоб не мешался.
В данном файле мы также подключим заголовочный файл lcd.h
#include «lcd.h»
//—————————————-
Теперь мы в данный файл перенесём полностью все функции, предназначенные для работы с дисплеем, из файла Test09.c. В нём останутся только две фунции — port_ini и main().
Тем самым мы очень серьёзно разгрузим главный файл приложения, сделав его удобочитаемым.
Но этого нам недостаточно. Ни одна функция теперь не будет «видна» в файле Test09.c. Поэтому те функции, которые мы будем использовать в других файлах, мы обязаны объявить, или, как говорят в народе, создать на них прототипы. Делается это обычно в заголовочном файле. Поэтому мы создадим прототипы в заголовочном файле lcd.h. Прототип делается очень легко. Пишется, или обычно копируется заголовок функции со всеми аргументами (всё кроме тела) и в конце ставится точка с запятой. Нам нужны будут функции инициализации дисплея, позиционирования на дисплее и вывода строки на дисплее. Символы мы отдельно пока выводить не будем, поэтому на соответствующую функцию мы прототип не создаём. Вот наши прототипы
#include «main.h»
//—————————————-
void LCD_ini(void);
void setpos(unsigned char x, unsigned y);
void str_lcd (char str1[]);
//—————————————-
#define e1 PORTD|=0b00001000 // установка линии E в 1
Теперь соберём файл, запустим его в протеусе, и проверим его работоспособность. Также проверим на практике. Если всё работает, то мы всё сделали правильно. Проект на весь урок приложен внизу и доступен по ссылке «Исходный код».
Таким образом, в сегодняшнем уроке мы много чему научились. Мы научились работать с символьным дисплеем и подключать его к контроллеру AVR. Также мы в рамках данного урока научились грамотному оформлению кода и использованию модульного программирования.
Предыдущая часть Программирование МК AVR Следующий урок
Техническая документация на дисплей
Техническая документация на контроллер дисплея HD44780
Программатор и дисплей можно приобрести здесь:
Программатор USBASP USBISP с адаптером USBASP USBISP 3.3 с адаптером
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Добрый день. У вас
void str_lcd (char str1[])
{
wchar_t n;
здесь ошибка?
Мой авр студия ругаеться
Я изменил на char n;
И спасибо за уроки.
Нет, не ошибка. У меня работает нормально.
Видимо, возможно, у Вас уже другая версия студии и данный тип она не знает.
Вооще здеь можно использовать, да, возможно и нужно char.
Просто я. видимо, из какого-то древнего проекта у себя по Visual C взял это, здесь уже не очень актуально.
Это вообще тип для Unicode.
Ну пусть остаётся.
Вам также спасибо за оценку моего труда!
#include <stdlib.h>
Спасибо Alex подключил #include , заработало на ATmega88PA.
Добрый день.
Хотел вывести счетчик на экран.
Неработает.
char test = 0;
while(1)
{
test ++;
setpos(1,0);
//sendchar('o');
str_lcd("Hello");
setpos(1,1);
//sendchar('o');
sendchar(test);
//TODO:: Please write your application code
_delay_ms(200);
}
Доброго времени суток . хочется выразить вам благодарность за ваши труды -для нас , очень кратко и понятно все что за чем и как .
и хочется сказать большое спасибо за такие уроки ибо когда учился я эту тему не мог понять , а оказывается тут легко !
нам бы такого преподавателя !))))))))
Всё ввел буква в букву — не работает
Попробуйте скачать проект и запустить. Возможно, что-то недоввели.
Также дисплеи могут различаться в зависимости от производителя. Так что внимательно читайте документацию именно на своё устройство.
Здравствуйте. В строке for(n=0;str1[n]!='';n++) компилятор ругается на отсутствие костанты ' '.\0 исправляет ситуацию, хотелось бы пояснений.
Здравствуйте!
Это автоматика текстовая на сайте так срабатывает, она такие вещи убирает. Поэтомоу пользуйтесь лучше скачанным проектом.
for (n=0; str1[n]!='\0';n++)
в тексте рассказа надо добавить \0, чтоб работало. В проекте все есть.
Спасибо на труд. Полезно. Коротко. Эффективно.
Здравствуйте. При компиляции выдает предупреждение: deprecated conversion from string constant to 'char*'.
Версия AtmelStudio 7.0.1188
Почитайте про типы и указатели в си.
str_lcd(«Hello World!»); нужно заменить на char string1[] = «Hello World!»; str_lcd(string1);
Совершенно верно! Это другой вариант инициализации массива данных.
Спасибо Вам за ваши уроки.
Хотелось бы прочитать, как менять символы с заданными кодами для LCD индикаторов. Это необходимо делать для их руссофикации в частности. Знаю, что это возможно. Но информации на эту тему не могу найти.
Думал на эту тему, но всё некогда этим загнаться.
Добрый день! Хочу поблагодарить за уроки, благодаря Вам перестаю потихоньку смотреть на си как на китайскую грамоту. С Вашего позволения хочу поделиться дополнением к Вашему уроку про вывод своего символа на дисплей. Как известно у этого контроллера дисплея есть память для восьми программируемых пользователем символов. Сваял функцию:
//Пишем в память свой символ(64-команда 01000000 из даташита)
void RAM_write( char CGRAM_adress, char d0, char d1, char d2, char d3, char d4, char d5, char d6, char d7)
{
sendbyte(64 + (CGRAM_adress << 3), 0);//Выбираем адрес ячейки памяти(от 0 до 7)
sendbyte(d0,1); //Пишем строки символа
sendbyte(d1,1);
sendbyte(d2,1);
sendbyte(d3,1);
sendbyte(d4,1);
sendbyte(d5,1);
sendbyte(d6,1);
sendbyte(d7,1);
}
//_______________________________________________________
Теперь пример(человечек):
RAM_write(0,0x0E,0x04,0x0E,0x15,0x04,0x0A,0x0A,0x00);//Пишем в память коды своего символа
sendchar(0);//Выводим свой символ из адреса 0
Если дисплей руссифицированный, то в даташите в таблице символов есть русские буквы. Выводите функцией segchar код символа. В другом случае только самому рисовать и писать в память дисплея(но не больше восьми).
Привет.Спасибо за уроки. хотел сказать,1то в видеоуроке ошибка и я не совсем пойму как это у тебя работало но доказательство на Видео 🙂
В Датачите у них распиновка
RS R/W DB7 DB6 DB5 DB4
а по твой распиновки
RS R/W DB4 DB5 DB6 DB7
У меня заработало только после того,.как я сделал по датачиту и загнал при инитиализации 3 раза
00001100
либо нужно перекинуть ножки
lcd.c никуда не подключен ?
Файлы c никуда не подключаются, они только добавляются в дерево проекта. Подключают заголовочные файлы.
Здравствуйте как поменять подключения выводов LCD к МК к примеру DB4-PD5..DB7-PD2
Ну просто переписать проект). У меня не заработал, на атмеге88. Хотя проверял на 8 битном подключении с другим проектом, и лсдишку, и мегу. Инициализацию не проходит, строка первая только. Придется ковыряться в подключении(
Одна мега88 оказалась с битыми портами D6, D7, поэтому взял другую, проверил на проекте с 8 бит подключением для меги328 — работает. Автор, похоже, не читает комменты к старым урокам. Протеус 7 глючит с мегой88, так что проверить только в железе остается. У меня смд переходник, придется купить соик мегу8. Печалька, конечно.
Как передать значения переменной
unsigned char menu_tar=1;
if(menu_tar==1){setpos(0,0); str_lcd(«Menu PWM:»); sendbyte(menu_tar, 1);}
if(menu_tar==1){setpos(0,0); str_lcd(«Menu PWM:»); str_lcd(menu_tar);} Думал так должно работать. Но тоже не то. Ну очень нужно. Всю голову сломал.
Доброго вечера. Хочу выразить огромную благодарность за уроки. Самые доступные и понятные на просторах интернета.
Создав подобный проект для Атмеги328 (разница лишь в названии переменных и позиционировании символов, т.к. у меня дисплей 16ч4) столкнулся с такой проблемой: проект собирается, запускается и работает в протеусе, но при сборке схемы на ЖКИ ни чего нет (регулировка контраста ничего не дает). Чтобы проверить прошивается ли микроконтроллер, сделал дополнительно включение светодиода от ножки В0, диод горит. В чем может быть проблема?
Алексей,
Спасибо за интерес и внимание к ресурсу!
Скорее всего дело в том, что дисплеи все немного разного качества, всё-таки сборка даёт своё. И что-то из-за этого может не успевать. Попробуйте поиграть с задержками в инициализации. Посмотрите аналогичный урок по контроллерам PIC, код приблизительно похожий с AVR, там мы вроде уже немного скорректировали задежки, так как у меня теперь появилось несколько экземпляров таких дисплеев и я пробую на разных.
Спасибо, что помогаете разобраться новичкам. Напишу после устранения ошибки о результатах.
спасибо вам большое
Доброго дня. Я разобрался почему в Протеус у меня все работало, а в железе нет. Спалив два ЖКИ я определил ,что дело было в плохих контактах (цанговые зажимы макетной платы). Соединил ножки микроконтроллера и дисплея напрямую, все пропаял. Экран отобразил одну строку квадратиков. С этим тоже долго разбирался. Оказалось, ножку R/W необходимо обязательно подсоединять к общему проводу. Теперь все работает. Ещё раз выражаю огромную благодарность за крайне понятные видео уроки и помощь в комментариях. Спасибо.
почему нет прототипа на функцию очистки дисплея??
или она где-то спрятана?
еще желательно добавить команду очистки дисплея в код инициализации дисплея
Вообщем долго мучился как вывести значение переменной на дисплей, и в итоге родил такой жирненький но и в то же время рабочий код.
В код автора нужно добавить переменные
volatile unsigned int number_digit, n;
long long power=10;
Ну и естественно функцию
void lcd_data (long long Data_variable, unsigned int Set_p_a, unsigned int Set_p_b)
{
n = Data_variable;
// Считаем количество разрядов
while( n > 0)
{
n = n / 10;
++number_digit;
}
// Объявляем массив равный количеству разрядов переменной
unsigned char data[number_digit];
//
for (int i=1; i<number_digit; i++)
{
power = (power * 10);
}
// Заполняем массив, в каждый элемент массива свой символ
for (int i=0; i<number_digit; i++)
{
data[i] = (((Data_variable/(power=(power/10)))%10)+'0');
}
// Выводим содержимое массива
for (int i=0; i<number_digit; i++)
{
setpos(Set_p_a, Set_p_b );
sendchar(data[i]);
Set_p_a++;
}
// Задаем переменным первоначальное значение
number_digit = 0;
n=0;
power=10;
}
Функция работает следующим образом, lcd_data (Ваша переменная с числом, Set_p_a, Set_p_b) где Set_p_a, Set_p_b это положение вывода результата на экран. Если есть более простой вариант, буду рад увидеть, но у меня получилось вывести только таким образом.
Можно что-то типа такого:
#include «main.h»
//—————————————————
void port_ini(void)
{
DDRD = 0xFF;
PORTD = 0x00;
}
//—————————————————
char mass[30];
long int per = 0;
int main(void)
{
port_ini(); // инициализируем порты
LCD_ini(); // инициализируем дисплей
clearlcd(); // очищаем экран
while(1)
{
sprintf(mass, «%2li», per);
setpos(0,0);
str_lcd(mass);
_delay_ms(1000);
per++;
}
}
О, спасибо! Так гораздо короче получается.
Не выводит данные типа float и double.
int main(void)
{
char buffer [30];
float abc = 1.234;
int bcd = 123;
port_ini(); //Инициализируем порты
LCD_ini(); //Инициализируем дисплей
setpos(0, 0);
sprintf(buffer,»%.2f Hellp %d», abc, bcd);
str_lcd(buffer);
setpos(0,1);
str_lcd(«asdf»);
while(1)
{
}
}
Переменные int почему то выводит, а float не хочет, и в протеусе на месте где должно быть значение float выводит знак ?
Все разобрался, для тех у кого не выводит переменные типа float, нужно в настройках проекта включить чтобы float заработала в sprintf.
Просто как на картинках прощёлкать и все.
https://startingelectronics.org/articles/atmel-AVR-8-bit/print-float-atmel-studio-7/
Собрал все по схеме. В Протеус все заработало. В железе нет. Подключил другой индикатор — не работает. Поменял МК три штуки. Не работает. Пробовал другую схему — тоже не работает, хотя в Протеус работает. Перепробовал все варианты, которые нашел в сети — не работает. Пока не обратил внимание на переменный резистор для регулировки контраста. Стоило его подключить и покрутить, как вдруг появилась надпись. Оказывается, «дело было не в бобине».
Спасибо за уроки.
Подскажите пожалуйста, как найти информацию про создание, взаимодействие файлов *.с и *.h Непонятно, как работают эти заголовочные файлы. Информация слишком общая и не отвечает на многие вопросы. Пытался разобраться в стандартных библиотеках avrgcc, стало только хуже. В хедерах только объявление функций, описание видимо должно быть в *.с файле. Но и в *.с файле нет описания. Странно в общем, как это работает?
в чем может быть проблема? в протеусе все работает, в железе все незадействованные квадраты светятся. изменил wchar на char, теперь светится постоянно первый квадрат 0 строки
Спасибо , статья хорошая . Но если это урок надо иметь возможность увидеть уже собранную прошивку .Чтобы было понятно как в итоге она собирается .
Простите , перечитал еще раз . Я просто думал что этот проект поддерживает терминальный режим (serial) . Может кто подскажет , как доделать для приема терминалки ,с возможностью изменять скорость потока .
У автора дар «все запутывать». Непонятные движения с созданием новых проектов, а куча заголовочных файлов в этой части урока — так это вообще капец.
Владимир,
спасибо за интересные уроки.Учился по вашим урокам!Всё получилось
Хотелось бы узнать как можно теперь русифицировать ЖКД??
Почему-то функция вывода строки в таком виде не работает
void Str_LCD (char str[])
{
char n=0;
for(n=0;str[n]!='\0';n++)
sendchar(str[n]);
}
А в таком виде все прекрасно работает
void Str_LCD (char str[]){
char n=0;
while(str[n]!='\0'){
SendChar(str[n]);
n++;
}
}
В чем может быть причина? Atmel 7.0.2397.
Спасибо за Ваш колоссальный труд, и за то что делитесь сконцентрированными знаниями!
А неподскажете в чём загвоздка: попробовал перенести ваш код на 2313,соблюдая логику переделал схему ,порты в коде перенес на В,и ничего в протеусе не завелось, вобще не видно активности проца, этот переделаный код работает на меге8,Спасибо
Доброе утро или день)
по вашему уроку написал прошивку для lcd 20×4 в 8-битном режиме.
Суть прошивки в том что я в начале экрана вывожу «свой» символ стрелки (обозначающий выбранную строку).
после я вывожу на экран строки которые хранятся в памяти как список структур из указанием порядкового номера, длинны строки и самого текста (и пока что текст один и тот же).
Так же есть 2 кнопки при помощи которых я перемещаю стрелку-курсор вниз или вверх реализовав скроллинг.
Все происходит в таймере по перерыванию. Строки переписываю только если нужно перед этим очистив экран.
когда нажимаются кнопки то курсор то на месте старого положения я вывожу пробел а на новом соответственно символ.
Вроде как все просто. Но у меня есть маленькая проблема. Изображение на екране в первых 2 строках идеальное а во вторых (нижних 2 строки) текст начинает искажаться. И все при том что все строки и команды выводятся одинаково и для одних и для других строк.
Когда я подключил экран к ардуние то все на экране нормально. С чем это может быть связанно? я бы показал код но учитывая что есть и работа со списками и структурами то все разбросано по разным файлам и толком не выйдет.
вот сама реализация перерывания.
ISR (TIMER0_OVF_vect) {
if (g_delitel == 0) { // делитель для опроса кнопок.
g_delitel = 1000;
if (B_UP == 0) { // если нажата кнопка вверх
if (g_row == 0 && TopLine > 0) // если есть куда листать вверх
TopLine—; // эта переменная запоминает какая строка сейчас наверху экрана
else if (g_row == 0 && TopLine == 0) { // если листать вверх некуда то перемещаемся в самый низ списка (вывод будет таким чтоб последняя строка была внизу)
TopLine = ROWS — 4; // учитывая что экран — 4строчный а ROWS это указание количества строк
g_row = 3; // переменная отвечающая за стрелку(указатель в начале выбранной строки)
}
else if (g_row != 0) // или просто изменяю позицию стрелки
g_row—;
}
else if (B_DW == 0) { // аналогично кнопке вверх обрабатываю кнопку вниз
if (g_row == 3 && TopLine < ROWS) // если есть куда листать вниз
TopLine++;
else if (g_row == 3 && TopLine == ROWS) {
TopLine = 1;
g_row = 0;
}
else if (g_row != 3)
g_row++;
}
if (prewTopLine != TopLine) { // если листание списка нужно сделать
LCD_Command (0x01); // clear
print_string_array(lstString); // печатаю 4 строки на екран начиная из TopLine
prewTopLine = TopLine; // запоминаю новую позицию
}
if (g_prew_row != g_row) { // если позицию стрелки изменили
LCD_goto_xy(g_prew_row, 0); // на старой позиции печатаю пробел
LCD_Char(' ');
LCD_goto_xy(g_row, 0); // на новой позиции печатаю символ
LCD_Char(0x00);
g_prew_row = g_row; // запоминаю новую позицию
}
}
else g_delitel—;
TCNT0 = 0x01;
}
А где это дерево проектов находится? На такой мелочи работа остановилась…
Очень путано объясняете внесение данных в создаваемые файлы из файла Test09.c и делаете ошибки в тексте, путая расширения у вновь созданных файлов, что ещё больше усложняет понимание.
Спасибо за урок. Но материал заранее нужно изучить, потом рассказывать.Сами путаетесь и тем более мы за вами.
А можете объяснить поподробнее, где именно автор нас запутал?
Здравствуйте! А что нужно поменять в программе, чтобы она заработала на атмеге 128. А то некоторые символы не работают на дисплее, а «пропускаются».
Благодарю автора за труд и стремление поделиться своими знаниями с новичками.
—————-
Как у многих, у меня была ошибка function argument #1 of type 'flash unsigned char [13]' is incompatible with required parameter of type 'unsigned char []' .
Решил её объявлением типа данных char flash для аргумента функции str_lcd (долго возился).
// —————————— //
void str_lcd (char flash str1[])
{
char n;
for(n=0;str1[n]!='\0';n++)
sendchar(str1[n]);
}