Учебный курс. Подключение lcd к микроконтроллеру. Знакомимся с указателями. Добавляем функцию вывода строки

07/11/2009 - 21:29

__flash unsigned char str[] = “test”;

   Заметьте, при объявлении массива мы не указали его размерность. Она будет равна количеству символов строки + 1. Первый элемент массива будет содержать символ ‘t’, второй  – символ ‘e’ и так далее. Последним элементом массива будет  ‘\0’. Видите ‘\0’? И я не вижу. А он есть.
   Если ключевое слово __flash не использовать – строка будет размещена в ОЗУ. Здесь есть один нюанс. В ассемблерном листинге строка будет объявлена во флэш памяти микроконтроллера, но перед вызовом функции main, компилер разместит код, который скопирует строку в оперативную память. Поэтому объявлять таким образом строки довольно расточительно.

unsigned char str[] = “test”;

   Строка объявлена и нужно разобраться как вывести ее на lcd. Функция вывода одного символа у нас есть. Строка это последовательность символов. Значит нам нужно запустить эту функцию в цикле и скармливать ей нашу строку до тех пор, пока не дойдем до конца. Что то вроде:

unsigned char i = 0;
while (test[i] != ‘\0’){
   LCD_WriteData(test[i]);
   i++;
}

    Но только в виде функции, которой можно передавать любую строку. Вот здесь начинается самое интересное. Когда мы передаем функции переменную, то на самом деле передается ее копия.  Перед тем как вызвать функцию, значение переменной копируется в специально отведенный регистр (или регистры, смотря сколько аргументов передается).  
   Нам нужно передать в функцию массив. Неужели компилятор будет создавать копию массива? Регистров общего назначения мало, не хватит ведь. Правильно. Копия массива не создается, потому что передача массива в функцию осуществляется с помощью  указателя (создается копия указателя).
   Указатель – это переменная, в которой хранится адрес другой переменной. Звучит мудрено, но разобраться можно.

Вот так объявляется переменная

unsigned char tmp= 0;

А вот так указатель на нее:

unsigned char *pTmp = &tmp;

unsigned char – это тип переменной, адрес которой хранится в указателе! Заметьте, не тип указателя! (Для микроконтроллеров AVR указатели  это двухбайтные переменные)
* означает -  указатель на.
& - это оператор взятия адреса.

   Вообщем эта запись означает следующее. Объявляем указатель на переменную типа unsigned char  и присваиваем ему адрес переменной tmp.

Указатель нужно обязательно инициализировать! Не инициализированный указатель – указывает черти куда.

Хорошо, объявили указатель, а что с ним можно делать?
С помощью указателя можно обращаться к переменной, на которую он указывает.  

tmp = 10;
*pTmp = 20;
//теперь переменная tmp равна 20.

Не запутайтесь – звездочка использовалась при объявлении указателя, а еще она используется для доступа к переменной! *pTmp означает – взять значение переменной на которую указывает pTmp.

Можно передать функции в качестве аргумента указатель на переменную, и тогда функция сможет менять ее значение.

Передача значения

void Inc(unsigned char count)
{
   count++;
}

timer = 10;
Inc(timer);

В этом случае после вызова функции значение переменной timer будет по прежнему равно 10.

Передача указателя

void Inc(unsigned char *pCont)
{
    (*pCount)++;
}

timer = 10;
Inc(&timer);

   В этом случае после вызова функции значение переменной timer будет равно 11.
Функция Inc в качестве параметра принимает указатель на переменные типа unsigned char.
Вызывая функцию, мы передаем ей адрес переменной timer - Inc(&timer)

С простыми переменными немного разобрались, перейдем к массивам. Как объявить указатель на массив? Дело в том, что имя массива и есть указатель на него!! Поэтому дополнительно объявлять ничего не нужно.

__flash unsigned char str[] = “test”;

str – указывает на первый элемент массива/строки. Только это не просто указатель, а константный указатель. Он не может менять своего значения!!
Чтобы передать функции указатель на массив, нужно передать ей имя массива. Например так.

__flash unsigned char str[] = “test”;

//функция вывода строки, которая принимает
//в качестве аргумента константный указатель

void LCD_SendStringFlash(unsigned char __flash *str)
{
     // код
}


LCD_SendStringFlash(str);// Вызов функциии и передача указателя

   На этом пока закончим. Это не вся информация об указателях, но теперь вы знаете достаточно, чтобы разобраться с функцией вывода строки. Приведу ее всю сразу, а ниже разберем построчно.

void LCD_SendStringFlash(unsigned char __flash *str)
{
  SetBit(PORT_SIG, RS);            
  while (*str)
  {
    PORT_DATA = *str++;
    SetBit(PORT_SIG, EN);    
    _delay_us(2);
    ClearBit(PORT_SIG, EN);    
    _delay_us(40);
  }
}

Функция принимает в качестве параметра константный указатель.
Перед тем как попасть в цикл, проверяется значение *str. Если оно не равно нулю, цикл выполняется. В цикле значение *str считывается в  PORT_DATA, а затем инкрементируется. Можно было бы написать это так:

PORT_DATA = *str;
str++;

Далее идет стандартная последовательность с которой мы уже разбирались.
Цикл выполняется до тех пор, пока str не будет указывать на нулевой символ.

Функция вывода строки из ОЗУ будет выглядеть идентично.

void LCD_SendString(unsigned char *str)
{
  SetBit(PORT_SIG, RS);            
  while (*str)
  {
    PORT_DATA = *str++;
    SetBit(PORT_SIG, EN);    
    _delay_us(2);
    ClearBit(PORT_SIG, EN);    
    _delay_us(40);
  }
}

Ее удобнее использовать. Не нужно объявлять во флэше строку. Можно сразу писать:

LCD_SendString(“test”);

Но, как я уже говорил, этот вариант более расточителен.

PS: В проекте я еще добавил макрос для позиционирования курсора и общий для всех четырех функций кусок кода вынес в отдельную встраиваемую функцию.

#pragma inline = forced
void LCD_CommonFunc(unsigned char data)
{
  PORT_DATA = data;       //вывод данных на шину индикатора
  SetBit(PORT_SIG, EN);    //установка E в 1
  _delay_us(2);
  ClearBit(PORT_SIG, EN);   //установка E в 0 - записывающий фронт
  _delay_us(40);
}

   Запись #pragma inline = forced – это указание для компилятора сделать функцию встраиваемой. То есть тело функции просто скопируется в те места, где она вызывается.
   Зачем я это сделал? Гораздо рациональнее хранить одинаковые фрагменты кода в одном месте. Если я, например, захочу изменить величину программной задержки, мне не придется делать это в четырех местах.

Comments   

# Guest 2010-04-25 07:03
благодарю за статью!
# Guest 2010-05-14 15:22
подскажите пожалуйста как быть с отображением русских символов на winstar и иже с ним... писать табличку соответствия hex?
# Pashgan 2010-05-16 20:54
Можно создать таблицу перекодировки. А можно использовать специальные утилиты, которые преобразуют русские символы в соответствующие им символы ЖК дисплеев. Я такими утилитами не пользовался, но мне они попадались.
# Guest 2010-08-30 08:15
Подскажите, как вывести строкой русские символы? Если можно, пример.
# Серж 2010-10-21 17:57
Помогите, плииз, с отображением русских символов...
# Серж 2010-10-22 06:23
Разобрался !!
1. Создаем массив таблицы перекодировки:
Code:
const unsigned char rusTable[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, 0xAD, 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};

2. Далее в функции отправки символа в ЖКИ (обычно "lcd_putc()"), непосредственно перед командой отправки, добавляем строчку: Code:if (c > 0x7F) {c = rusTable [(c-0xC0)];}, где c - входная переменная функции lcd_putc().

Работает, проверенно !!
# Pashgan 2010-10-31 20:31
Расточительно, но вариант рабочий.
# EricRed 2010-11-29 19:05
Спасибо!
# Андрей_Серг 2011-11-24 09:33
Серж, подскажите поподробнее, как выводить русские буквы, не получается пока никак
# МУТАБОР 2010-11-26 23:39
Супер статья!!! Наконец-то у меня все получилось :) Я этот LCD экран уже начал ненавидеть. Очень помогли.
# Pashgan 2010-11-30 17:21
Да пожалуйста.
# Busel 2011-08-22 08:13
Скажите, плиз, как со ссылками и указателями на массивы работать?
# Alex1001000 2011-11-30 07:02
подскажите пожалуйста как обьявить строку во flash в WinAVR
# Виталий 2012-10-18 13:58
В прикрепленных исходниках есть функция Goto(0,1) что она делает?
# Pashgan 2012-10-18 20:04
Это макрос для установки курсора в нужное место дисплея. LCD_Goto(x, y): x- номер знакоместа, y - номер строки.
# Сергей 2013-01-18 06:15
Паш, а функция LCD_SendStringF lash по объему кода и по быстроте выполнения сильно отличается от библиотечной __load_program_ memory?
# Pashgan 2013-01-18 06:36
Это разные функции. LCD_SendStringF lash берет строку из флэша и выводит ее на LCD, а __load_program_ memory - это встроенная низкоуровневая функция просто для чтения данных из флэш памяти.
# Сергей 2013-01-18 14:49
Получается,что __load_program_ memory это типа аналога pgm_read_byte в GCC? И ещё вопрос, но уже не в тему... Не планируется ли какая либо статья по информационному обмену хотя бы 3-х устройств на МК по UART?
# Pashgan 2013-01-19 21:23
Получается что так.
В ближайшее время нет.
# sasha-posit 2013-02-03 18:36
Здравствуйте! задался вопросом как перевернуть текст на lcd, а ответа не могу найти , может подскажете?
# JoJo 2013-02-03 18:44
У стандартных LCD нет такой функции. Можно добавить несколько своих перевернутых букв и выводить тест с помощью них. Но там всего 8 букв можно добавить, если не ошибаюсь.
# sasha-posit 2013-02-03 21:34
дело в том что я видел перевернутый текст на стандартном дисплее и там было более 8 символов???????
# Слава 2013-05-21 08:48
Запускаю индикатор WH2002. Делаю все как описано в последних трёх упражнениях по LCD у Pashgana. Все работает кроме инициализации вывода курсора, немного изменил:
[#define LCD_Goto(x,y) LCD_WRITE_COMMA ND((((y)& 1)*40)+((x)+128))]

Да и еще вопрос: компилятор ругается в main на
1)Warning[Pe223 ]: function "LCD_Send_Strin g_OZU" declared implicitly

2)Warning[Pe223]: function "LCD_Send_Strin g_Flash" declared implicitly

Хотя функции бъявлены как положено, программа работает как надо...
# Pashgan 2013-05-26 16:37
Если ругается, значит в main`e вызывается функция, которая не объявлена в заголовочном файле. Может ты просто ошибся в названии в каком-то месте.

У вас недостаточно прав для комментирования.