Учебный курс. Подключение 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 – это указание для компилятора сделать функцию встраиваемой. То есть тело функции просто скопируется в те места, где она вызывается.
   Зачем я это сделал? Гораздо рациональнее хранить одинаковые фрагменты кода в одном месте. Если я, например, захочу изменить величину программной задержки, мне не придется делать это в четырех местах.

Комментарии   

# Guest 25.04.2010 07:03
благодарю за статью!
Ответить | Ответить с цитатой | Цитировать
# Guest 14.05.2010 15:22
подскажите пожалуйста как быть с отображением русских символов на winstar и иже с ним... писать табличку соответствия hex?
Ответить | Ответить с цитатой | Цитировать
# Pashgan 16.05.2010 20:54
Можно создать таблицу перекодировки. А можно использовать специальные утилиты, которые преобразуют русские символы в соответствующие им символы ЖК дисплеев. Я такими утилитами не пользовался, но мне они попадались.
Ответить | Ответить с цитатой | Цитировать
# Guest 30.08.2010 08:15
Подскажите, как вывести строкой русские символы? Если можно, пример.
Ответить | Ответить с цитатой | Цитировать
# Серж 21.10.2010 17:57
Помогите, плииз, с отображением русских символов...
Ответить | Ответить с цитатой | Цитировать
# Серж 22.10.2010 06:23
Разобрался !!
1. Создаем массив таблицы перекодировки:
Код:
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()"), непосредственно перед командой отправки, добавляем строчку: Код:if (c > 0x7F) {c = rusTable [(c-0xC0)];}, где c - входная переменная функции lcd_putc().

Работает, проверенно !!
Ответить | Ответить с цитатой | Цитировать
# Pashgan 31.10.2010 20:31
Расточительно, но вариант рабочий.
Ответить | Ответить с цитатой | Цитировать
# EricRed 29.11.2010 19:05
Спасибо!
Ответить | Ответить с цитатой | Цитировать
# Андрей_Серг 24.11.2011 09:33
Серж, подскажите поподробнее, как выводить русские буквы, не получается пока никак
Ответить | Ответить с цитатой | Цитировать
# МУТАБОР 26.11.2010 23:39
Супер статья!!! Наконец-то у меня все получилось :) Я этот LCD экран уже начал ненавидеть. Очень помогли.
Ответить | Ответить с цитатой | Цитировать
# Pashgan 30.11.2010 17:21
Да пожалуйста.
Ответить | Ответить с цитатой | Цитировать
# Busel 22.08.2011 08:13
Скажите, плиз, как со ссылками и указателями на массивы работать?
Ответить | Ответить с цитатой | Цитировать
# Alex1001000 30.11.2011 07:02
подскажите пожалуйста как обьявить строку во flash в WinAVR
Ответить | Ответить с цитатой | Цитировать
# Виталий 18.10.2012 13:58
В прикрепленных исходниках есть функция Goto(0,1) что она делает?
Ответить | Ответить с цитатой | Цитировать
# Pashgan 18.10.2012 20:04
Это макрос для установки курсора в нужное место дисплея. LCD_Goto(x, y): x- номер знакоместа, y - номер строки.
Ответить | Ответить с цитатой | Цитировать
# Сергей 18.01.2013 06:15
Паш, а функция LCD_SendStringF lash по объему кода и по быстроте выполнения сильно отличается от библиотечной __load_program_ memory?
Ответить | Ответить с цитатой | Цитировать
# Pashgan 18.01.2013 06:36
Это разные функции. LCD_SendStringF lash берет строку из флэша и выводит ее на LCD, а __load_program_ memory - это встроенная низкоуровневая функция просто для чтения данных из флэш памяти.
Ответить | Ответить с цитатой | Цитировать
# Сергей 18.01.2013 14:49
Получается,что __load_program_ memory это типа аналога pgm_read_byte в GCC? И ещё вопрос, но уже не в тему... Не планируется ли какая либо статья по информационному обмену хотя бы 3-х устройств на МК по UART?
Ответить | Ответить с цитатой | Цитировать
# Pashgan 19.01.2013 21:23
Получается что так.
В ближайшее время нет.
Ответить | Ответить с цитатой | Цитировать
# sasha-posit 03.02.2013 18:36
Здравствуйте! задался вопросом как перевернуть текст на lcd, а ответа не могу найти , может подскажете?
Ответить | Ответить с цитатой | Цитировать
# JoJo 03.02.2013 18:44
У стандартных LCD нет такой функции. Можно добавить несколько своих перевернутых букв и выводить тест с помощью них. Но там всего 8 букв можно добавить, если не ошибаюсь.
Ответить | Ответить с цитатой | Цитировать
# sasha-posit 03.02.2013 21:34
дело в том что я видел перевернутый текст на стандартном дисплее и там было более 8 символов???????
Ответить | Ответить с цитатой | Цитировать
# Слава 21.05.2013 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 26.05.2013 16:37
Если ругается, значит в main`e вызывается функция, которая не объявлена в заголовочном файле. Может ты просто ошибся в названии в каком-то месте.
Ответить | Ответить с цитатой | Цитировать

Добавить комментарий

Защитный код
Обновить