- Родительская категория: Статьи
- Категория: Программирование
- Автор: ARV
- Просмотров: 31415
WinAVR: работа с ЖКИ
Продолжая опровергать миф о том, что WinAVR сложен и неудобен, в этой статье я расскажу о том, как просто и легко организуется вывод информации на символьные ЖКИ.
{ads2}Как обычно, начнем с традиционного скачивания архива с парой файлов. Это свободно распространяемая библиотечка-модуль (информация об авторе в комментариях сохранена), в которой я исправил несколько серьезных ошибок. Правда, эти ошибки проявляются в довольно экзотических случаях, но я на это попал, поэтому вам предлагаю уже свободную от (этих) проблем версию. Собственно говоря, это проявление плюса свободного ПО, к которому относится и WinAVR: возможность исправления багов оперативно и самостоятельно, в отличие от закрытого кода библиотек CVAVR.
Файл lcd.c должен быть включен в список компилируемых файлов. Любители makefile должны вручную вписать его в строку с перечнем файлов исходных текстов проекта, пользователи AVR Studio добавляют этот файл при помощи меню, ну а любителям Eclipse повезло больше всех - им достаточно просто скопировать оба файла в папку своего проекта.
Файл lcd.h, как могут догадаться читавшие предыдущую статью, служит для «настройки» модуля: в нем выбираются необходимые порты ввода-вывода, конкретизируются аппаратные особенности выбранного ЖКИ и делается ряд некоторых других настроек. Этот файл обязан быть в папке проекта и, кроме этого, должен подключаться директивой #include во все файлы, где будет требоваться работа с ЖКИ. Рассмотрим его содержимое подробно.
До 61-й строки находится информация, котрая никакой принципиальной роли не играет. Я не рекомендую вносить в нее какие-то правки, но подробно на ней останавливаться не намерен.
А вот с 61-й строки начинаются действительно важные вещи. Первый макрос в этой строке служит для выбора типа управляющего контроллера ЖКИ. Модуль поддерживает два типа контроллеров: популярный HD44780 и чуть более редкий KS0073. К сожалению, контроллер второго типа в моих руках никогда не был, и поэтому я могу лишь доверять разработчику модуля, а вот ЖКИ с HD44780 я тестировал лично, и могу гарантировать полную работоспособность всех функций.
Даолее я перечислю все важные макросы из файла lcd.h.
LCD_CONTROLLER_KS0073
Если в ЖКИ использован контроллер KS0073, задать значение макроса 1.
LCD_LINES
Число строк ЖКИ, может принимать значение 1,2,3 или 4.
LCD_DISP_LENGTH
Число позиций в строке ЖКИ. Должно соответствовать реальности.
LCD_LINE_LENGTH
Количество байтов встроенного ОЗУ контроллера на 1 строку. Обычно изменять не требуется, хотя для надежности стоит свериться с документацией на ваш ЖКИ - если там будет указано иное значение, нужно ввести его.
LCD_START_LINE1
Адрес начала первой строки во встроенном ОЗУ контроллера. Для всех известных мне ЖКИ всегда равно 0.
LCD_START_LINE2
Адрес начала второй строки. Обычно связано со значением LCD_LINE_LENGTH, однако, могут быть исключения, поэтому для страховки рекомендую свериться с документацией на ваш ЖКИ и при необходимости изменить это значение.
LCD_START_LINE3
Адрес начала третьей строки. Обычно равно LCD_START_LINE1 + LCD_DISP_LENGTH. Обратите внимание, что треться строка начинается раньше второй, т. е. является как бы продолжением первой. Такая вот экзотика - сверьтесь с документацией, чтобы убедиться, что в вашем ЖКИ именно так и есть.
LCD_START_LINE4
Адрес начала четвертой строки. Обычно равно LCD_START_LINE2 + LCD_DISP_LENGTH. Снова экзотика: четвертая строка есть продолжение второй.
LCD_WRAP_LINES
Как видите по предыдущим значениям, размещение символов в ОЗУ не совсем логично увязано с реальными строками на дисплее. Функции модуля lcd.c могут автоматически осуществлять перенос текста на следующую строку, если он выходит за пределы текущей - не по ячейкам ОЗУ, а по позициям дисплея. Если вам нужен такой автоперенос, установите значение этого макроса в 1, если вы самостоятельно будете контролировать вывод, оставьте нулевое значение.
LCD_IO_MODE
Некоторые МК имеют возможность работы с внешним адресным пространством, например, для расширения ОЗУ. ЖКИ может быть включен в это адресное пространство, и тогда значение этого макроса вы должны установить в 0. В этом случае программная работа с ЖКИ будет вестись, как будто это какие-то ячейки в ОЗУ микроконтроллера. Значение по умолчанию подразумевает более традиционный способ работы через обычные порты ввода-вывода.
LCD_PORT PORTD
Порт микроконтроллера, к которому подключены сигналы ЖКИ.
LCD_DATA0_PORT, LCD_DATA1_PORT, LCD_DATA2_PORT, LCD_DATA3_PORT
ЖКИ для рассматриваемого модуля всегда подключается по 4-проводной схеме обмена данными. Четыре рассматриваемых макроса позволяют задать 4 разных порта МК для каждой из этих линий - просто укажите наименование портов, если нужно. А если у вас все 4 линии подключены к одному порту - оставьте все, как есть, тогда будет использовано значение LCD_PORT
LCD_DATA0_PIN, LCD_DATA1_PIN, LCD_DATA2_PIN, LCD_DATA3_PIN
4 линии данных ЖКИ могут подключаться к портам МК в произвольном порядке. При помощи этих макросов вы должны указать правильную распиновку.
LCD_RS_PORT, LCD_RS_PIN
Линия RS для ЖКИ может подключаться так же к произволной линии любого порта МК. Пара этих макросов служит для этого.
LCD_RW_PORT, LCD_RW_PIN
Линия RW для ЖКИ может подключаться так же к произволной линии любого порта МК. Пара этих макросов служит для этого.
LCD_E_PORT, LCD_E_PIN
Линия E для ЖКИ может подключаться так же к произволной линии любого порта МК. Пара этих макросов служит для этого.
Все макросы в оставшейся части файла изменять не нужно, во всяком случае до тех пор, пока вы не созреете до глубинных переделок в коде модуля.
{ads1}
Как видите, гибкость в настройках весьма впечатляющая. Благодаря возможности разбросать сигналы управления ЖКИ по любым ножкам микроконтроллера, вы можете добиться идеальной трассировки печатной платы. Однако, такая гибкость оборачивается объемом кода и скоростью его работы, правда, все эти прибавки незначительные и в большинстве случаев ими можно пренебречь. Для достижения минимального объема кода и максимального быстродействия вы будете должны обязательно расположить все сигналы управления на одном порту МК, причем сигналы линий данных должны быть расположены последовательно от младшего бита к старшему.
Закончив возню с распределением сигналов, можно приступать к рассмотрению имеющихся в модуле функций. Их прототипы приведены в конце файла lcd.h.
void lcd_init(uint8_t dispAttr)
Инициализация ЖКИ. Эта функция должна быть вызвана до всех остальных обращений к ЖКИ. В качестве параметра функция принимает одну из следующих констант:
LCD_DISP_OFF - дисплей выключен
LCD_DISP_ON - дисплей включен, курсор невидим
LCD_DISP_ON_CURSOR - дисплей включен, немигающий курсор видим
LCD_DISP_ON_CURSOR_BLINK - дисплей включен, виден мигающий курсор.
void lcd_clrscr(void)
Очистка дисплея. Все позиции дисплея заполняются пробелами, курсор устанавливается в начало первой строки.
void lcd_home(void)
Установка позиции курсора в первую позицию первой строки ЖКИ.
void lcd_gotoxy(uint8_t x, uint8_t y)
Установка курсора в указанную позицию. Значения x и y должны быть в допустимых пределах (см. LCD_LINES и LCD_DISP_LENGTH). Нумерация строк и позиций начинается с нуля.
void lcd_putc(char c)
Вывод одного символа в позицию курсора. После вывода позиция курсора автоматически смещается в следующую позицию по строке, а если задано LCD_WRAP_LINES == 1, то при достижении конца текущей строки курсор сместится в начало следующей.
void lcd_puts(const char *s)
Вывод строки на ЖКИ, начиная с текущей позиции курсора. При выводе может осуществляться автоматический перенос текста на следующую строку, если задано LCD_WRAP_LINES == 1. Кроме этого, встреченный символ '\n' автоматически переводит курсор в начало следующей строки ЖКИ. После вывода курсор оказывается в следующей за последним символом строки позиции.
void lcd_puts_p(const char *progmem_s)
То же самое, что и lcd_puts, но для строки, хранящейся во flash микроконтроллера, т. е. в PROGMEM.
void lcd_command(uint8_t cmd)
Низкоуровневая функция отправки управляющей команды в контроллер ЖКИ.
void lcd_data(uint8_t data)
Низкоуровневая функция отправки данных в контроллер ЖКИ.
lcd_puts_P(s)
Макрос для удобного вывода строк, которые надо сохранить в PROGMEM. Идеология работы с данными во flash, принятая в WinAVR, требует, чтобы для сохранения строки «HELLO» во flash и последующего вывода ее на ЖКИ, мы выполнили следующее:
{code}PROGMEM char str[] = "HELLO";
lcd_puts_p(str);{/code}
Если строка, выводимая на ЖКИ, повторно не будет использована в программе, можно воспользоваться макросом PSTR и сократить запись до lcd_puts_p(PSTR("HELLO")). А воспользовавшись макросом lcd_puts_P можно упростить запись еще больше: lcd_puts_P("HELLO");
В принципе, это все функции и макросы. Немного, но достаточно для всех нужд. Если вы располагаете МК с достаточно большим объемом ОЗУ и FLASH, можно совместить стандартный вывод при помощи семейства функций printf с рассматриваемым модулем. Для этого нужно сделать следующее.
Создаем новый исходный файл с названием, например, lcd_io.c (намек на com_io.c), в котором подключаем модуль стандартного ввода-вывода stdio.h и модуль поддержки ЖКИ lcd.h:
{code} #include <avr/io.h>
#include "lcd.h"{/code}
Теперь нам необходимо определить функцию потокового вывода одного символа так, чтобы символ выводился на наш дисплей:
{code} static int lcd_putchar(char c, FILE *stream){
lcd_putc(c);
return 0;
}{/code}
Ничего сложного, как видите. Ничего сложного не будет и в описании переменной для потока, связанного с дисплеем:
{code} FILE lcd_out = FDEV_SETUP_STREAM(lcd_putchar, NULL, _FDEV_SETUP_WRITE); {/code}
Как видите, мы определили для потока ЖКИ переменную lcd_out типа FILE и сразу присвоили ей значение (при помощи макроса FDEV_SETUP_STREAM), указав ранее сделанную функцию вывода символа на дисплей. Чтобы эта переменная была видна в других файлах нашего проекта, нужно создать еще заголовочный файл lcd_io.h такого содержимого:
{code} #ifndef __LCD_IO_H__
#define __LCD_IO_H__
#include "lcd.h"
#include "stdio.h"
extern FILE lcd_out;
#endif {/code}
После этого в любом файле нашей программы мы можем проинклюдить lcd_io.h и использовать функцию fprintf для вывода на дисплей, например, так:
{code}fprintf(lcd_out, "Value = %d", var); {/code}
Однако, fprintf требует постоянно указывать, в какой именно поток осуществляется вывод, что может быть лишним для многих проектов. Можно назначить созданный нами поток дисплейного вывода в качестве потока стандартного вывода и пользоваться функцией printf. Такое назначение заключается в единственном операторе stdout = &lcd_out; который мы должны ввести в функции main одним из первых.
Но я предложу вам еще более удобный путь: воспользоваться модулем avr_helper.h, который был в архиве из предыдущей статьи, и его макросом INIT для автоматической инициализации дисплея и связанного с ним потока. Для этого в файле lcd_io.h мы добавляем строку #include , затем в файле lcd_io.c подключаем файл lcd_io.h директивой #include "lcd_io.h", а после этого в самом конце файла lcd_io.c дописываем макрос автоматической инициализации:
{code} INIT(7){
stdout = &lcd_out;
lcd_init(LCD_DISP_ON);
lcd_clrscr();
}{/code}
Все операторы в этом макросе будут выполнены автоматически ДО НАЧАЛА функции main, поэтому вы можете, не переживая на счет ЖКИ, сразу, как принято, поприветствовать мир:
{code}#include <avr/io.h>
#include "lcd_io.h"
int main(void){
printf("HELLO, WORLD\nI like WinAVR!");
return 0;
}{/code}
Важное примечание: все функции работы с ЖКИ из рассматриваемых модулей являются блокирующими, т. е. пока обмен с ЖКИ не будет полностью завершен, ни одна из функций не возвратит управление. Обмен с ЖКИ в том числе подразумевает получение от него определенных данных, поэтому ваша программа «зависнет» на функции lcd_init, если ЖКИ подключен к МК неправильно или вообще не подключен! Имейте это ввиду.
{ads1}
Комментарии
Но Автору Спасибо :) Написано доступно
stdout = &lcd_out;
ошибку исправил.
да у вас, батенька, с кириллицей действительно проблемы
чтобы выводить кириллицу на ЖКИ, необходимо, во-первых, чтобы сам ЖКИ содержал кириллические символы в знакогенераторе , а во-вторых, чтобы кодировка этого знакогенератора совпадала с кодировкой хост-системы, на которой работает компилятор.
чаще всего первое соблюдается, а второе - нет (производители ЖКИ не сильно озабочены тем, чтобы встроенный знакогенератор ЖКИ имел ввообще какую-то общепринятую кодировку).
в этом случае помогают всякие вспомогательные утилиты, например, я такую делал: http://arv.radioliga.com/content/view/183/44/
RSS лента комментариев этой записи