Допилка библиотеки для LCD на базе HD44780

Эта статья, продолжение этой статьи.

На днях появилось время, и вот решил допилить свою древнюю библиотеку для вывода данных на дисплей. Решил избавиться от крякозябр, при выводе кирилицы на LCD. Часто кирилицу выводят таким образом. Во флешь запихивают массив типа:

const uint8_t PROGMEM text_2[]={0xA8,0x79,0xC0,0x65,0xB4,0xBB,
                                0x61,0xB7,0xBA,0x61};//Пучеглазка 
Потом этот массив выводят на дисплей. Не самый удобный способ вывода русских символов на LCD, ввиду того что вместо букв в массиве цифры. Если текста выводится много, то в итоге видна километровая портянка/рябь из цифр. Причем эта портянка не только длинная, но и широкая. Намного удобнее ну и наверное правильнее/грамотнее выводить символы так.

const uint8_t PROGMEM text_1[]="Пучеглазка";
Но если такой массив передать на дисплей как есть, то получим крякозябры вот такие.


Видел второй вариант вывода русских символов. Например через макросы. Вот так.

#define RUS_t 0xDF
#define RUS_P 0xA8
#define RUS_i 0xB8
#define RUS_v 0xB3

lcd_str(RUS_P,'p', RUS_i,RUS_v,'e',RUS_t);//Привет

Но как мне показалось, это полная жесть. В итоге чтоб удобно было работать с кирилицей, надо всего навсего в библиотеку вывода данных вмонтировать функцию перекодировки символов. Что я и сделал. Второе что еще допилил, это возможность вывода на дисплей числовых данных. Для этого вмонтировал в свою библиотеку, нарытую мною чужую библиотеку перевода двоичных чисел в ASCII символы.

Итак библиотека вывода данных на LCD состоит из двух файлов которые подключаются к проекту.


Сразу надо бы выставить в проекте частоту кварца. Обычно это можно сделать в настройках компилятора, и в 4й студии это место легко было найти. В 6й рылся рылся, не нашел. Поэтому обычно делаю в файле define.h объявление

#define  F_CPU 16000000UL
и подключаю данный define.h файл к нужным модулям программы. Ну и в общем то можно приступить к выводу данных на дисплей. Не забыв сперва инициализировать дисплей функцией.

LCDinit();
Ну и как правило для вывода данных используются всего несколько основных функций.

1. Вывод по одному символу. Сам этим никогда не пользуюсь.



2.Вывод строки на дисплей. Уже лучше, но тоже этим никогда не пользуюсь.



3.Вывод массива из озу. Для начала надо в озу определить наш массив. Разумеется не в коде, а вверху где заголовочники. Тоже никогда этим методом не пользуюсь.



4.Вывод массива из флеша. Этим методом только и пользуюсь. Так же определяем массив во флешь не забывая подключить

#include <avr/pgmspace.h>




Осталось теперь разобраться с выводом чисел. Предположим есть переменная uint8_t count; которая то увеличивается то уменьшается. И нам надо видеть это на дисплее. Для этого пишем такой код.


Грузим прошивку в чип и видим на экране нашу переменную. Важный момент перед выводом числовых данных на LCD это не забыть:

1. Определить переменную указатель на буфер

uint8_t *pBuf;//определяем переменную указатель на буфер
Я обычно ее определяю где нибудь в начале проекта где все инициализируется. Потом делаю с помощью extern видимой для всех остальных модулей программы. И использую ее для вывода данных на дисплей из любого места программы.

2.Не забыть эту же переменную инициализировать.

pBuf=BCD_GetPointerBuf();//инициализация переменной pBuf для вывода данных на LCD.
Таким образом в наш единый буфер будут попадать значения после преобразований чисел. Ну и из буфера извлекаем на LCD.

LCDstring_of_sramXY(pBuf,0,1);
В библиотеке есть еще другие функции преобразования чисел.

void BCD_1(uint8_t value) - преобразует числа от 0 до 9
void BCD_2(uint8_t value) - преобразует числа от 0 до 99
void BCD_3(uint8_t value) - преобразует числа от 0 до 255
void BCD_3Int(uint16_t value) - преобразует числа от 0 до 999
void BCD_4Int(uint16_t value) - преобразует числа от 0 до 9999
void BCD_5Int(uint16_t value) - преобразует числа от 0 до 65535
void BCD_Uchar(uint8_t value) - преобразует числа от 0 до 255
void BCD_Uint(uint16_t value) - преобразует числа от 0 до 65535 
void BCD_Ulong(uint32_t value) - преобразует числа от 0 до 4294967295
Для нужной длинны переменной выбираем нужную функцию. В противном случае будут некорректные значения. За более детальной информацией по преобразованию чисел лучше глянуть первоисточник.

Файлы в топике: Mega_LCD.zip

Комментарии (23)

RSS свернуть / развернуть
Код лучше вставлять не картинками (которые, к тому же, все имеют разное разрешение), а тегом <code>.

В 6й рылся рылся, не нашел. Поэтому обычно делаю в файле define.h объявление
В пятой студии это делается добавлением F_CPU=16000000UL в список дефайнов в настройках компилятора, либо -DF_CPU=16000000UL в «дополнительные параметры». Правда, в пятой студии я так и не смог передать этот дефайн в ассемблер, пришлось вбивать его в один из подключаемых ассемблерными частями хедеров. Быть может, в 6-й и пофиксили.

Функция перевода числа в строку несколько странная. Чем не устроили стандартные средства вроде itoa?
0
  • avatar
  • Vga
  • 15 января 2014, 12:00
Картинки мне как то более сочными показались.
Функция перевода числа в строку несколько странная
Почему странная? Автор такую сделал. Работает четко. Ну да itoa тоже есть и даже как то ей помню пользовался.
0
Их же нельзя скопировать и вставить в проект.
+2
Почему странная? Автор такую сделал.
Достаточно неудобная. Несколько странно использование глобально выделяемого буфера. И если уж вносишь ее в библиотеку LCD (хотя там эти функции совершенно не нужны) — то хоть бы как-то интегрировал с функциями LCD.
Про код картинками уже написали. Плюс все эти IDE'шные пометки полезны в IDE, но тут только мусорят.
0
О! А теперь ещё на препроцессоре, и всё хорошо.
Я вот перелез на C++11, но не пишу классы. Перелез только для того, чтобы такие функции объявлять с constexpr и иметь уверенность, что они посчитаются ещё на стадии сборки.
0
А не пробовал поискать готовое? У Чана прекрасные готовые программные модули
Control Module for Character LCD
Embedded String Functions
Последняя очень удобна, поскольку позволяет форматировать вывод. Нет ограничений на отрицательные числа.
0
Ну конечно можно готовое взять. Но я как бы пытаюсь освоить СИ. Поэтому приходится изобретать велосипед. Когда сам что то делаешь, тогда и освоение лучше прет. В общем то взять готовое каждый может. А хочется что то самому сотворить и почувствовать прогресс в голове.
0
Но я как бы пытаюсь освоить СИ.
«Осваивать» С лучше на нормальных примерах. Скажем, от того-же Чана. Подсмотреть и поучиться как структурированы программа и данные у него очень даже полезно.
0
Зачастую мы не ищем готового специально, что бы набрать знаний и опыта. Я вот тоже написал для такого дисплея свою библиотеку, с таблицей символов, хотя готового везде много:) Но зато теперь я точно знаю как это работает, не только в теории:)
0
Ну да, когда сам сделал и через себя пропустил, то уже и сам не забудешь и другому подскажешь.
0
и другому подскажешь
Вот такими подсказками, как в этой статье, никому не посоветую пользоваться.
Например, при беглом просмотре, обнаруживается, что буквы «Ё» и «ё» в знакогенераторе индикатора имеются но «библиотека» не умеет с ними работать.
А уж ВОСЕМЬ разных функций преобразования числа в АSCII, лучше спрятать подальше и вообще никому не показывать. Тем более что, судя по всему, ни одна из них не работает с отрицательными числами.
0
В таком случае вообще лучше пользоваться готовыми библиотеками/функцими вывода данных которые уже поставляются с компиляторами. Типа itoa, ltoa, utoa, ultoa.
0
Выше я уже давал ссылку на Embedded String Functions. IMHO — лучше.
0
я в одном из своих проектов пошел еще дальше и сделал токен-печать…
у меня шрифт был только прописной (плотность высокая) — поэтому коды для строчных букв я использовал для токенов…
в прошивках много совпадающих строк например печатать слово или часть одним кодом и быстро и экономично для прошивки…

что то такое по коду
void LCD_token(unsigned char tknum, unsigned char mode) { // печать токена по номеру
char *tokenpointer;
tokenpointer=(char*)pgm_read_word(&(token_tbl[tknum])); // получим адрес указателя на строку (адрес элемента массива)

while (pgm_read_byte(tokenpointer)!='\0') {
LCD_prn(pgm_read_byte(tokenpointer), mode);
tokenpointer++;
}
}

и вот так примерно массивы токенов

// таблица токенов печати
#include <avr/pgmspace.h>
// ТОКЕН НОМЕР
char tkn_channel[] PROGMEM = «КАНАЛ»; // x64
char tkn_value[] PROGMEM = «ЗНАЧЕНИ»; // x65
char tkn_number[] PROGMEM = «НОМЕР»; // x66
char tkn_position[] PROGMEM = "\x68Я"; // x67 ПОЗИЦИЯ
char tkn_position1[] PROGMEM = «ПОЗИЦИ»; // x68
char tkn_name[] PROGMEM = «НАЗВАНИЕ »; //x69
char tkn_param[] PROGMEM = «ПАРАМ»; // x6A

char *token_tbl[] PROGMEM = { // таблица адресов токенов
tkn_channel,
tkn_value,
tkn_number,
tkn_position,
tkn_position1,
tkn_name,
tkn_param
};

это простенький пример, в больших проектах текстовые данные в 7-8 кб сжимались в фантастические 2 кб!!!
причем по скорости печати потерь практически нет (грубо говоря обрабатываем один лишний символ на токен)
0
посмотрите в токенах как печатается слово ПОЗИЦИЯ — печать токенов рекурсивна! то есть при желании можно токен составлять из другого токена и букв, или других токенов или в любых других комбинациях…
0
для гурманов вот перекодировка на лету из вин кодировки…
извиняюсь за асм, там по чипу ограничения были, поэтому ужал си до асма :-)))

;***************************************************************************
;*
;* ПЕЧАТЬ строки с кодировкой Win
;* Z — адрес строки для печати, в конце строки нулевой байт
;*
;***************************************************************************
PRN_WRSF:; печать строки, с кодировкой Windows!
PUSH R16
PUSH R17
PRN_WRSF_LOOP:
LPM R16, Z+; прочитали текущий символ
CPI R16, 0; проверим символ на маркер конца
BREQ PROC_WRSF_RET; если маркер конца — то выходим

RCALL PRN_CHAR; печатаем символ
RJMP PRN_WRSF_LOOP
PROC_WRSF_RET:; выход
POP R17
POP R16
RET

PRN_CHAR:; печать одного символа! с перекодировкой и токенами
PUSH ZL
PUSH ZH
PUSH R17
CPI R16, 0x09; проверка на код символа <0x09
BRSH PRN_CHAR_CH09; проверяем другие условия
; сюда попадаем когда код символа <9, но не ноль (его обработали выше)
; это имена моделей для печати!
DEC R16; номер модели с 1, а нам нужно с 0: уменьшаем на 1
LDI R17, MODEL_NAME-MODEL_AIL; 181 смещение имени от начала описателя модели
LDI ZL, low (FLASH_DISK_AREA*2)
LDI ZH, high(FLASH_DISK_AREA*2)
ADD ZL, R17; смещение имени модели
ADC ZH, R16; номер модели
; здесь в Z адрес строки для печати
RCALL PRN_WRSF; печатаем строку имени модели
PRN_CHAR_EXIT:
POP R17
POP ZH
POP ZL
RET

PRN_CHAR_PRNLINE:; печать символа
CALL LCD_CHAR
RJMP PRN_CHAR_EXIT; выходим с восстановлением Z

PRN_CHAR_CH09:
CPI R16, 0xC0; это русский символ
BRCS PRN_CHAR_NOTRUS; это не русский символ
; процедура перекодировки русских символов
LDI ZL, low (LCD_RUSTABLE*2)
LDI ZH, high(LCD_RUSTABLE*2)
SUBI R16, 0xC0; определим номер русского символа
CLR R17
ADD ZL, R16
ADC ZH, R17
LPM R17, Z
RJMP PRN_CHAR_PRNLINE

PRN_CHAR_NOTRUS:; в R16 у нас символ перекодированный если он вдруг был русский
MOV R17, R16
; проверка на печать строкового токена
CPI R17, 0x80
BRLO PRN_CHAR_PRNLINE; код символа меньше 80 — печатаем!
CPI R17, 0xA0
BRSH PRN_CHAR_PRNLINE; код символа больше A0 — печатаем!
; сюда попадаем когда нам нужно напечатать токен

CPI R17, 0x9F; печатаем токен задаваемый пользователем
BRNE PRN_CHAR_T9E
LDS ZL, LCD_TOKEN_9F
LDS ZH, LCD_TOKEN_9F+1
RJMP PRN_CHAR_TOKEN_PRN; печать токена
PRN_CHAR_T9E:
CPI R17, 0x9E; печатаем токен задаваемый пользователем
BRNE PRN_CHAR_T9D
LDS ZL, LCD_TOKEN_9E
LDS ZH, LCD_TOKEN_9E+1
RJMP PRN_CHAR_TOKEN_PRN; печать токена
PRN_CHAR_T9D:
CPI R17, 0x9D; печатаем токен задаваемый пользователем
BRNE PRN_CHAR_T
LDS ZL, LCD_TOKEN_9D
LDS ZH, LCD_TOKEN_9D+1
PRN_CHAR_TOKEN_PRN:
RCALL PRN_WRSF

RJMP PRN_CHAR_EXIT; и идем на выход
PRN_CHAR_T:
LDI ZL, low (LCD_TOKEN_LIB*2)
LDI ZH, high(LCD_TOKEN_LIB*2); в Z адрес библиотеки токенов
SUBI R16, 0x80; вычтем из кода символа 0x80

RCALL PRN_DICT; печать словарного значения
RJMP PRN_CHAR_EXIT; и выходим

; токены печати
LCD_TOKEN_LIB:

; ВНИМАНИЕ! токены 0x9D 0x9E 0x9F задаются пользователем и в этой таблице не обрабатываются!

; коды токенов ниже начинаются с 0x80!
.DB «НАСТР. », 0, «КАНАЛ», 0, «РАСХОД», 0, «ЭКСПО.», 0, «Р1 », 0, «Р2 », 0
; 0x80 0x81 0x82 0x83 0x84 0x85

.DB «Р3 », 0, 0, «MAX», 0, «ЦЕНТР», 0, «MIN», 0, «ДВИГ.», 0, «ПУЛЬТ» ,0, «ЭЛЕР», 0
; 0x86 0x87 0x88 0x89 0x8A 0x8B 0x8C 0x8D

.DB «РВ», 0, «РН» ,0
; 0x8E 0x8F

LCD_RUSTABLE: .DB 0x41, 0xA0; А Б
.DB 0x42, 0xA1; В Г
.DB 0xE0, 0x45; Д Е
.DB 0xA3, 0xA4; Ж З
.DB 0xA5, 0xA6; И Й
.DB 0x4B, 0xA7; К Л
.DB 0x4D, 0x48; М Н
.DB 0x4F, 0xA8; O П
.DB 0x50, 0x43; Р С
.DB 0x54, 0xA9; Т У
.DB 0xAA, 0x58; Ф Х
.DB 0xE1, 0xAB; Ц Ч
.DB 0xAC, 0xE2; Ш Щ
.DB 0xAD, 0xAE; Ъ Ы
.DB 0x62, 0xAF; Ь Э
.DB 0xB0, 0xB1; Ю Я

; в этом проекте маленькие буквы не используем, перекодировать их не нужно
; .DB 0x61, 0xB2; а б
; .DB 0xB3, 0xB4; в г
; .DB 0xE3, 0x65; д е
; .DB 0xB6, 0xB7; ж з
; .DB 0xB8, 0xB9; и й
; .DB 0xBA, 0xBB; к л
; .DB 0xBC, 0xBD; м н
; .DB 0x6F, 0xBE; о п
; .DB 0x70, 0x63; р с
; .DB 0xBF, 0x79; т у
; .DB 0xE4, 0x78; a x
; .DB 0xE5, 0xC0; ц ч
; .DB 0xC1, 0xE2; ш щ
; .DB 0xC2, 0xC3; ъ ы
; .DB 0xC4, 0xC5; ь э
; .DB 0xC6, 0xC7; ю я
0
эхх… а еще для графического дисплея писал как то вывод символов с попиксельной точностью и по X (это просто) и по Y (а вот это, если конечно не попиксельно рисовать, уже сложнее) — тоже где то лежит библиотечка… ааа вот например ноги -> vg.ucoz.ru/forum/9-100-1 и там же описание краткое как сделано...(если кому ссылка мешает — сотрите ее нафиг :-)
0
LCD, HD44780, библиотека, дисплей, вывод текста, пучеглазка
+1
Ну вот не хочется быть «граммар наци» аж ни разу, но «допилка» как-то гм… Режет глаз всё же, почему-то, да и звучит странновато… Может, всё же «доработка», «усовершенствование», «допиливание»? :)
0
Тьфу. Как-то все чересж…
IAR:
extern u08 __flash MsgManual [];

extern u08 __flash MsgSemiAutomat [];

extern u08 __flash MsgAutomat [];

extern u08 __flash MsgStop [];

extern u08 __flash MsgManual [] =     "НАЛАДКА";

extern u08 __flash MsgSemiAutomat [] = "ПОЛУАВТОМАТ";

extern u08 __flash MsgAutomat [] =     "АВТОМАТ";

extern u08 __flash MsgStop [] =        "СТОП";

//========================================================================
void ShowCurrTemp (void)
{
   clr_dsp_buf ();

   PrintMsgDspBuf (sStatus, 2, MsgCurrTemp);
   PrintMsgDspBuf (2, 2, MsgNumZoneTemp);
   PrintMsgDspBuf (3, 2, MsgCurrStatusTemp);
   PrintMsgDspBuf (4, 2, MsgCurrValTemp);

//   menu = MENU;
}
//------------------------------------------------------------------------
extern u08 __flash MsgCurrTemp [] =        "ТЕКУЩАЯ ТЕМПЕРАТУРА";
extern u08 __flash MsgNumZoneTemp [] =     "  Z1   Z2   Z3   Z4";
extern u08 __flash MsgCurrStatusTemp [] =  " ! *  ! *  ! *  ! *";
extern u08 __flash MsgCurrValTemp [] =     " 157  162  167  172";
//========================================================================
-1
Ну и еще вдогонку:

//========================================================================
#if (TYPE_DISPLAY==VFD)

#define QUANT_USERS_CHARS 4

#define ARROW_RIGHT 2
#define ARROW_LEFT 3

u08 __flash table_users_chars [8*QUANT_USERS_CHARS]  = // Таблица пользовательских символов.
{
0x00, 0x04, 0x0E, 0x15, 0x15, 0x0E, 0x04, 0x04, // Ф
0x00, 0x10, 0x10, 0x10, 0x1E, 0x11, 0x11, 0x1E, // Ь
0x00, 0x08, 0x0C, 0x1E, 0x1F, 0x1E, 0x0C, 0x08,
0x00, 0x02, 0x06, 0x0F, 0x1F, 0x0F, 0x06, 0x02,
};

void def_users_chars (u08 __flash *ptr)
{
   lcd_send_com (1<<6);

   u08 a;
   u08 b;

   for (a = QUANT_USERS_CHARS; a > 0; a--)
   {
      for (b = 8; b > 0; b--)
      {
         lcd_send_data (*ptr);
         ptr++;
      }
   }
}
//------------------------------------------------------------------------

//------------------------------------------------------------------------
__flash u08 table_rus_chars [32] = // Таблица кириллицы. VFD поддерживает только
// заглавные русские буквы. И не все. Ф и Ь в таблице пользовательских символов.
{
//АБВГДЕЖЗ
0x41, 0x80, 0x42, 0x92, 0x81, 0x45, 0x82, 0x83,
//ИЙКЛМНОП
0x84, 0x85, 0x4B, 0x86, 0x4D, 0x48, 0x4F, 0x87,
//РСТУФХЦЧ
0x50, 0x43, 0x54, 0x88, 0x00, 0x58, 0x89, 0x8A,
//ШЩЪЫЬЭЮЯ
0x8B, 0x8C, 0x8D, 0x8E, 0x01, 0x8F, 0xAC, 0xAD,
};

#endif
//========================================================================

//========================================================================
static u08 cnt_x;
static u08 cnt_y;

void drv_char_dsp (void)
{
static const u08 lines [4]={0x80, 0xC0, 0x94, 0xD4};

	switch (_drv_char_dsp)
	{
		case 	DRV_CHAR_DSP_INIT_1:
			init_char_dsp ();

         def_users_chars (table_users_chars);

			clr_dsp_buf ();

		case 	DRV_CHAR_DSP_INIT_2:
         cnt_x = 0;
         cnt_y = 0;

         _drv_char_dsp = DRV_CHAR_DSP_SEND_ADDR;
         set_timer (ST_DRV_CHAR_DSP, DEC_NO_RERUN, DRV_LCD_TIME);
			break;

		case DRV_CHAR_DSP_SEND_ADDR:
			if (wait (ST_DRV_CHAR_DSP))
			{
				lcd_send_com(lines[cnt_y]);
				_drv_char_dsp = DRV_CHAR_DSP_SEND_CHAR;
				set_timer (ST_DRV_CHAR_DSP, DEC_NO_RERUN, DRV_LCD_TIME);
			}
			break;

		case DRV_CHAR_DSP_SEND_CHAR:
			if (wait (ST_DRV_CHAR_DSP))
			{
            u08 data;
            data = dsp_buf [(cnt_y*MaxX) + cnt_x];
                  if (data >= 0xC0)
                  {
                     data = table_rus_chars [data - 0xC0];
                  }
				lcd_send_data (data);
				cnt_x++;
				if (cnt_x >= MaxX)
					{
						cnt_x = 0;
						cnt_y++;
						if (cnt_y >= MaxY)
							{
								cnt_y = 0;
							}
						_drv_char_dsp = DRV_CHAR_DSP_SEND_ADDR;
					}
				set_timer (ST_DRV_CHAR_DSP, DEC_NO_RERUN, DRV_LCD_TIME);
			}
			break;

		default:
			_drv_char_dsp = DRV_CHAR_DSP_INIT_1;
			break;
	}
}
//========================================================================
0
Ау, тема еще живая? Никак не могу заставить работать библиотеку. Такое ощущение, что инициализация дисплея не проходит. Долго тупил в код, по-моему, все должно работать. Железо тоже исправно. Если не трудно, подскажите, куда копать? Сам пока учусь.
0
По всей видимости, тема уже не актуальна. Нашел все свои ошибки, исправил — заработало. Всем спасибо!
И все таки, как показала неделя рысканья по просторам необъятной сети, более ли менее простой, удобной, отвечающей всем требованиям, библиотеки для работы с LCD дисплеями нет. Каждый пишет «под себя»…
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.