Print this page

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

Файлы

Проект для IARa. Проект для WINAVR.

Related items