Шаг 4.2. Продолжаем мучать LCD
Итак, "низкий" уровень протокола общения с дисплеем мы реализовали - у нас есть функция, которая отсылает байт команды или данных. Посмотрим, как можно улучшить наш код. Для начала, очевидно, что нужно создать ещё одну функцию для обработки не символов, а целых строк. То же верно и для команд. Ещё один открытый вопрос - нужно ли разделять функцию для строк и для массивов команд? С одной стороны, мы получим в два раза больше кода, с другой - он будет намного понятней. Так как памяти у нас пока хватает, сделаем код понятным :) Начнем с программы на Си.
Так, и команды, и символы у нас можно определить типом "byte", а массивы или строки передать через указатели на нулевой элемент - "byte*".
Мы знаем, что строки в Си (в MikroPascal-е тоже так) всегда заканчиваются нулём - можно на этом сыграть!
//функция отправки дисплею строки, заканчивающейся нулём ('\0') void LCD_SendStr(byte* Str) { byte i; for (i = 0; Str[i] != 0; i++) //просто идём по строке и отправляем байты до тех пор, пока не встретим '\0' LCD_SendByte(Str[i], 1); }
Выглядит совсем просто! Теперь подумаем о массивах команд: очень заманчиво точно так же дописывать на конце массива ноль и не запоминать, сколько у нас команд, - тем более, что в списке команд в документации нет команды, содержащей одни нули. Так что в принципе, почему бы и нет)
//функция отправки дисплею массива команд, заканчивающейся нулём void LCD_SendCommands(byte* CommandArr) { byte i; for (i = 0; CommandArr[i] != 0; i++) LCD_SendByte(CommandArr[i], 0); }
Единственное, что может смущать - что обычно команды, или массивы команд, записываются как константы. И вот тут-то нас поджидает неприятный момент.
Дело в том, что наш микроконтроллер имеет гарвардскую архитектуру - слоны отдельно, мухи отдельно, то есть ОЗУ (переменные) отдельно, ПЗУ (константы) отдельно. Весело и задорно про память можно прочитать здесь, у наших товарищей. И поэтому нельзя просто так взять, и написать так:
//функция отправки дисплею массива команд, заканчивающейся нулём void LCD_SendCommands(byte* CommandArr) ... void main() { //объявление переменных и констант const byte aInit[] = {0b00110011, //команды для инициализации модуля 0b00110010, 0b00101000, 0b00001111, 0b00000001, 0b00000110, 0}; ... LCD_SendCommands(aInit); //инициализация дисплея
На такой код компилятор выдаст ошибку: Illegal pointer conversion, то есть недопустимое преобразование указателя.
Но решение есть - ОЗУ и ПЗУ могут воздействовать через указатели, а именно указатели на константы: const byte*.
В этом случае наш код будет работать:
//программа работает с LCD-дисплеем МТ-10S1; инициализирует его и выводит на экран приветствие "Hello" //переименование типов typedef unsigned char byte; //определяем назначение портов ввода-вывода #define portLCD PORTB #define ddrLCD DDRB //константы для дисплея const byte LCD_A0_OUT_NUMBER = 5; //номер вывода, к которому подключён вывод A0 дисплея const byte LCD_E_OUT_NUMBER = 4; //номер вывода, к которому подключён E дисплея const byte LCD_DATA_MASK = 0x0F; const byte LCD_DATA_SHIFT = 0; //задержки const byte DL_LCD_SWITCH_ON_MS = 20; //задержка после подачи питания на дисплей в мс const byte DL_LCD_BETWEEN_E_CHANGE_MCS = 1; //задержка перед изменением вывода Е дисплея (отправка полубайта команд) в мкс const byte DL_LCD_DATA_SEND_MS = 2; //задержка после отправки одного байта данных или команды в мс void LCD_SendByte(byte DataByte, byte IsSymbol) { portLCD.LCD_A0_OUT_NUMBER = IsSymbol; //определяем, что отправляем - команду или данные portLCD.LCD_E_OUT_NUMBER = 1; //показываем, что сейчас будет устанавливать данные portLCD = portLCD & (˜LCD_DATA_MASK) | (DataByte >> 4); //устанавливаем на выводах DB7-DB4 значение старшего полубайта Delay_us(DL_LCD_BETWEEN_E_CHANGE_MCS); //на всякий случай делаем задержку перед отправкой portLCD.LCD_E_OUT_NUMBER = 0; //отправляем значение старшего полубайта! Delay_us(DL_LCD_BETWEEN_E_CHANGE_MCS); //на всякий случай делаем задержку перед изменением вывода Е; далее аналогично для младшего полубайта portLCD.LCD_E_OUT_NUMBER = 1; portLCD = portLCD & (˜LCD_DATA_MASK) | (DataByte & 0x0F); Delay_us(DL_LCD_BETWEEN_E_CHANGE_MCS); portLCD.LCD_E_OUT_NUMBER = 0; Delay_ms(DL_LCD_DATA_SEND_MS); //делаем задержку для того, чтобы дисплей мог обработать команду или данные } //функция отправки дисплею строки, заканчивающейся нулём ('\0') void LCD_SendStr(byte* Str) { byte i; for (i = 0; Str[i] != 0; i++) //просто идём по строке и отправляем байты до тех пор, пока не встретим '\0' LCD_SendByte(Str[i], 1); } //функция отправки дисплею массива команд, заканчивающейся нулём void LCD_SendCommands(const byte* CommandArr) { byte i; for (i = 0; CommandArr[i] != 0; i++) LCD_SendByte(CommandArr[i], 0); } void main() { //объявление переменных и констант const byte aInit[] = {0b00110011, //команды для инициализации модуля 0b00110010, 0b00101000, 0b00001111, 0b00000001, 0b00000110, 0}; //строка на вывод char sText[] = "Hello"; //инициализация портов ввода-вывода DDRA = 0; PORTA = 0; DDRB = 0; PORTB = 0; DDRD = 0; PORTD = 0; //инициализация выводов для дисплея - на выход и на 0 ddrLCD |= (1 << LCD_A0_OUT_NUMBER) | (1 << LCD_E_OUT_NUMBER) | LCD_DATA_MASK; Delay_ms(DL_LCD_SWITCH_ON_MS); //задержка для дисплея после включения LCD_SendCommands(aInit); //инициализация дисплея набором константных команд LCD_SendStr(sText); //отправка текста while(1) { Delay_ms(1); } }
Ещё одно маленькое лирическое отступление: не забываем, что в Си можно встретить следующие выражения:
- const int* a;
- int const* b;
- int *const c;
Первые два определения, по сути своей, являются одним и тем же: указателем на целое, которое нельзя менять. Сам же указатель может "двигаться", например, по массиву.
Третье же определение - int *const c; - является константным указателем на целое. Но есть значение этого указателя задается единожды, при инициализации, и потом поставить его на другой адрес не удастся. При этом указатель можно разыменовать и присвоить по адресу новое значение.
В общем, следите - не отстрелите себе ногу, будьте осторожны!
Будем считать, что с программой на Си в этом плане мы разобрались. Переходим к Паскалю:
//программа работает с LCD-дисплеем МТ-10S1; инициализирует его и выводит на экран приветствие "Hello" program LCD; const //константы для дисплея LCD_A0_OUT_NUMBER = 5; //номер вывода, к которому подключён вывод A0 дисплея LCD_E_OUT_NUMBER = 4; //номер вывода, к которому подключён E дисплея LCD_DATA_MASK = 0x0F; LCD_DATA_SHIFT = 0; //задержки DL_LCD_SWITCH_ON_MS = 20; //задержка после подачи питания на дисплей в мс DL_LCD_BETWEEN_E_CHANGE_MCS = 1; //задержка перед изменением вывода Е дисплея (отправка полубайта команд) в мкс DL_LCD_DATA_SEND_MS = 2; //задержка после отправки одного байта данных или команды в мс type TCommandArr = array [0.. 1] of Byte; //тип для передачи массива констант в функцию (размер минимальный; MikroPascal не проверяет соответствие размерам) var //определяем назначение портов ввода-вывода portLCD: byte at PORTB; ddrLCD: byte at DDRB; procedure LCD_SendByte(DataByte: byte; IsSymbol: byte); begin portLCD.LCD_A0_OUT_NUMBER := IsSymbol; //определяем, что отправляем - команду или данные portLCD.LCD_E_OUT_NUMBER := 1; //показываем, что сейчас будет устанавливать данные portLCD := portLCD and (not LCD_DATA_MASK) or (DataByte shr 4); //устанавливаем на выводах DB7-DB4 значение старшего полубайта Delay_us(DL_LCD_BETWEEN_E_CHANGE_MCS); //на всякий случай делаем задержку перед отправкой portLCD.LCD_E_OUT_NUMBER := 0; //отправляем значение старшего полубайта! Delay_us(DL_LCD_BETWEEN_E_CHANGE_MCS); //на всякий случай делаем задержку перед изменением вывода Е; далее аналогично для младшего полубайта portLCD.LCD_E_OUT_NUMBER := 1; portLCD := portLCD and (not LCD_DATA_MASK) or (DataByte and 0x0F); Delay_us(DL_LCD_BETWEEN_E_CHANGE_MCS); portLCD.LCD_E_OUT_NUMBER := 0; Delay_ms(DL_LCD_DATA_SEND_MS); //делаем задержку для того, чтобы дисплей мог обработать команду или данные end; //функция отправки дисплею строки, заканчивающейся нулём ('\0') procedure LCD_SendStr(var Str: String); var i: Integer; begin i := 0; while (Str[i] <> 0) do //просто идём по строке и отправляем байты до тех пор, пока не встретим '\0' begin LCD_SendByte(Str[i], 1); inc(i); end; end; //функция отправки дисплею массива команд, заканчивающейся нулём; на вход указатель на массив констант procedure LCD_SendCommands(const pCommandArr: ^TCommandArr); var i: Integer; begin i := 0; while (pCommandArr^[i] <> 0) do //отправляем команды последовательно до тех пор, пока не встретим команду = 0 begin LCD_SendByte(pCommandArr^[i], 0); inc(i); end; end; const Init: array[0.. 6] of Byte = (%00110011, //команды для инициализации модуля %00110010, %00101000, %00001111, %00000001, %00000110, 0); var sText: string[15]; begin sText := 'Hello'; //инициализация портов ввода-вывода DDRA := 0; PORTA := 0; DDRB := 0; PORTB := 0; DDRD := 0; PORTD := 0; //инициализация выводов для дисплея - на выход и на 0 ddrLCD := ddrLCD or (1 shl LCD_A0_OUT_NUMBER) or (1 shl LCD_E_OUT_NUMBER) or LCD_DATA_MASK; Delay_ms(DL_LCD_SWITCH_ON_MS); //задержка для дисплея после включения LCD_SendCommands(@Init[0]); //инициализация дисплея LCD_SendStr(sText); //отправка текста while(1) do Delay_ms(1); end.
Итак, для работы с массивами комманд мы использовали тут свой тип - TCommandArr = array [0.. 1] of Byte;. Связано появление этого типа с тем, что у MikroPascal-я есть некоторые сложности с передачей массивов в функции, тем более - массивов констант. Мы взяли минимально необходимый размер массива (одна команда и ноль) - но функция будет работать и с длинными массивами команд, так как MikroPascal не проверяет соответствие размеров (да-да, это можно называть костылём). При этом Паскаль не считает автоматически указатель началом массива, как это делает у нас Си, поэтому для получения i-той команды необходимо сначала разыменовать указатель - pCommandArr^ - а потом уже сослаться на i-ый элемент: pCommandArr^[i].
Для передачи строки мы используем var Str: String (используя ключевое слово var, мы работаем по ссылке со строкой напрямую, не создавая её локальную копию - и это позволяет избежать указания размера строки).
Автор - Moriam
Обсудить на форуме