Визуальные эффекты на символьном ЖКИ - часть 1
Автор ARV   
03.07.2010 г.

Символьные ЖКИ с управляющим контроллером, совместимым с HD44780, очень популярны: они недороги и удобны, требуют (в минимальном случае) всего 7 линий ввода-вывода для подключения к микроконтроллеру. Однако, порой хочется чего-то такого эдакого... ибо слишком уж они по-деловому отображают информацию.

На рисунках показан ряд текстовых визуальных эффектов, которые могут порой немного украсить вашу конструкцию. Нравится? Интересно, как это сделано? Об этом и пойдет речь.

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

 

Мтак, самое простое: эффект бегущей строки. Я поставил задачу максимум: сделать так. чтобы строка могла бежать слева направо и справа налево, чтобы можно было изменять скорость ее бега, чтобы строка могла при выползании из-за края затирать ранее выведенную строку, а так же выползать на заранее очищенную строку, чтобы функция могла работать со строками, размещенными в ОЗУ или во FLASH... И чтобы все это было сделано в виде одной функции.

Подумав, я решил описать эту функцию таким образом:

// Реализация бегущей строки
void running_str(char *text, output_mode_t *m);

Никакого значения функция не должна возвращать, потому ее тип void. Имя функции говорит само за себя - running_str. Параметров у этой функции всего два: собственно указатель на текст, который будет бегать и указатель на структуру с различными режимами вывода. Я неспроста заостряю внимание на столь очевидных и, казалось бы, совершенно малозначительных фактах, как название функции и имена ее параметров. Не раз приходилось встречать в чужих программах определения функций типа такого:

void rstr(char *t, o_m *m);

Да, автору такой функции пришлось чуть ли не в 3 раза меньше набирать текста, однако непосвященному уже не понять, что эта функция делает без дополнительных комментариев. Параметр t - это указатель на char, но из этого отнюдь не следует, что это обязательно будет строка символов, ведь char в Си - это обычное число типа байт (в диалектах Си, ориентированных на микроконтроллеры). Согласитесь, что char *text уже не даст возможности интерпретировать себя как-то иначе, нежели "указатель на текстовую строку". Второй параметр функции я обозвал одной буковй m, однако его тип развернут, и опять же не даст усомниться в его назначении - режим вывода. Суффикс _t (по соглашению, принятом в стандарте C99) однозначно обозначает тип, введенный пользователем, - я стараюсь придерживаться этого соглашения, чтобы меньше путаться в своих программах. То есть я хочу сказать, что давая переменным, типам, функциям и константам "говорящие сами за себя" наименования, вы создаете удобный, хорошо читаемый, красивый и профессиональный код, который затем почти не будет требовать комментариев.

Итак, функция описана. Мы представляем, как она будет работать: вызываем ее с нужными парметрами, и она выполняет то, что требуется - выводит бегущую строку. Остается определить собственно сам тип output_mode_t, при помощи которого будет производиться "тонкая настройка" работы этой функции. Я определил его в виде структуры:

/// структура режима отображения бегущей строки
typedef struct{
   uint8_t r_to_l   :1; /// не ноль, если справа налево
   uint8_t loop_run :1; /// не ноль, если бесконечный бег
   uint8_t l_blank  :1; /// не ноль, если надо очищать экран левее строки
   uint8_t r_blank  :1; /// не ноль, если надо очищать экран правее строки
   uint8_t in_flash :1; /// не ноль, если строка во FLASH
   uint8_t row      :3; /// строка дисплея для вывода (от 0 до 7)
   uint8_t delay_10ms;  /// задержка перед сдвигом строки в десятках миллисекунд
   uint8_t reserved;    /// зарезервировано - НЕ ИЗМЕНЯТЬ!!!
} output_mode_t;

Не смотря на то, что каждое поле снабжено комментарием, я все-таки остановлюсь более подробно на каждом.

Битовое поле r_to_l служит для управления направлением движения строки. Если компилятор не поддерживает работу с битовыми полями - можно сделать это поле обычным, т.е. просто убрать битовую размерность :1 - функциональность остального текста не изменится. Наоборот, с целью более экономного расходования ОЗУ, следует использовать именно (одно)битовые поля для работы с логическими переменными1). Поле loop_run позволяет определить, как функция будет вести себя после того, как строка пробежит по дисплею: завершится после единственного пробега или же будет гонять строку циклически до.... (о том, как прервать такую "бесконечную" бегущую строку я расскажу чуть позже). Два поля l_blank и r_blank управляют очисткой места перед и после движущейся строки - если поле не обнулено, то движение текста будет происходить на предварительно очищенной строке индикатора, а если поле обнулено, то очистки не будет. Рисунки немного проливают свет на их применение.

Так как требуется, чтобы функция умела работать со строками и в ОЗУ, и во FLASH (а это, как вы понимаете, две большие разницы для любого компилятора!), введено поле in_flash, ненулевое значение которого укажет на то, что *text содержит адрес не ОЗУ, а ячейки в памяти программ. Трехбитовое поле row позволяет указать номер строки на ЖКИ, где. собственно, и надо бегать строке.  Три бита - это значит, что вы можете использовать индикаторы даже с 8-ю строками (если найдете такие, конечно).

Наконец, поле delay_10ms задает число 10-миллисекундных интервалов времени между шажками бегущей строки, т.е. чем больше значение этого поля, тем медленнее бег. Это обычное байтовое поле, т.е. можно даже сделать "черепашью" строку, которая будет передвигаться на 1 символ за 2,5 секунды - этого более чем достаточно для всех разумных применений.

Последнее поле - зарезервировано, его нельзя менять в программе пользователя или как-то иначе воздействовать на него - это важно!

Итак, пора приступать к написанию собственно реализации нужной нам функции. Собственно говря, что нам надо? Нам надо выделить из заданной строки сначала один символ (первый или последний - это зависит от направления движения строки) и вывести его в крайнюю позицию строки ЖКИ (опять же: левую или правую в завсимости от направления). Затем надо взять уже два символа из строки и вывести их, затем три и так далее... То есть, если обобщить, то нам надо ВЫВОДИТЬ ЧАСТЬ СТРОКИ В НУЖНУЮ ПОЗИЦИЮ ЖКИ в цикле, изменяя по ходу дела размер этой части и позицию ее вывода. Не знаю. как вам, а мне просто хочется сделать функцию вывода этой самой части строки! И я так и поступил:

static void print_mid(char *text, uint8_t pos, uint8_t from, uint8_t len, output_mode_t *m){
   for
(uint8_t x=0; x < LCD_DISP_LENGTH; x++){
      lcd_gotoxy(x,m->row);
      if
(x<pos){
         if
(m->l_blank) lcd_putc(' ');
         continue
;
      }
      if
(from >= m->reserved)
         if
(m->r_blank)
            lcd_putc(' ');
         else
 
            continue
;
     
else
        
put(text+from++, m);
      }
   }
}
 

В общем, все должно быть понятно: функция получает кучу параметров:  *text - указатель на текст, pos - номер позиции на ЖКИ, начиная с которой нужно вывести часть строки, from - номер символа, начиная с которого будет выводится кусочек, а так же len - длину этого кусочка строки, ну и, само собой, указатель на режимы вывода *m. Так как надо в зависимости от режимов вывода или очищать или не очищать строку, пришлось сделать цикл, в котором для каждой позиции строки ЖКИ определяется: надо ли вывести в нее пробел (т.е. очистить ее), или же надо вывести символ из заданного текста, или же не надо вообще ничего с нею делать. Обратите внимание, что зарезервированное поле структуры режимов вывода, о котором ранее упоминалось, используется в этой функции, как значение длины текста. То есть мы предполагаем, что в этом зарезервированном поле уже содержится число, равное количеству символов в строке текста - откуда оно там берется, нас пока не волнует.

 

Для собственно работы с ЖКИ я не стал изобретать велосипед, а просто взял готовую библиотеку функций стророннего разработчика, которой я пользуюсь уже давно и ни разу не пожалел об этом. Для вывода символа я использую библиотечную функцию lcd_putc(), для установки "курсора" в нужную позицию дисплея - функцию lcd_gotoxy(). Для вывода символа из строки, хранящейся во FLASH (а это должно быть предусмотрено), придется сначала загрузить его при помощи стандартной для WinAVR функции pgm_read_byte() - но вы не найдете ее в приведенном коде! А все потому, что я решил и тут немного упростить себе жизнь, сделав универсальную функцию. которой все равно, откуда берется символ - из ОЗУ или FLASH. Эта функция - put() (ее вы найдете!):

static void put(char *cс, output_mode_t *m){
  
char tmp = *cс;
  
if(m->in_flash)
      tmp =
pgm_read_byte(cс);
   lcd_putc(tmp);
}

Как видите, это очень простая функция, которая проверяет значение поля in_flash структуры режимов вывода и, если надо, загружает символ из FLASH, прежде чем его вывести на дисплей.

Обратите внимание, что эта и предыдущая функции определены мною как статические. Это позволит компилятору более гибко провести оптимизацию кода, в частности, функция put() скорее всего будет автоматически сделана inline-функцией. Так же статические функции "не видны" в других модулях проекта, так как носят вспомогательный характер и, по идее, не должны требоваться в основной программе.

Итак, пора уж и к нашей бегущей строке перейти:

void running_str(char *text, output_mode_t *m){
  
m->reserved = m->in_flash ? strlen_P(text) : strlen(text);
  
do{
     
uint8_t start;
     
uint8_t x;
     
if(m->r_to_l){
         start = m->
reserved-1;
        
x = 0;
      }
else {
        
start = 0;
         x =
LCD_DISP_LENGTH-1;
     
}
     
while(!stop_effect){
        
delay10ms(m->delay_10ms);
         print_mid(text, x, start,
LCD_DISP_LENGTH-x, m);
        
if(m->r_to_l){
           
if(start)
              
start--;
           
else if(x < LCD_DISP_LENGTH)
              
x++;
            
else
              
break;
        
} else {
           
if(x)
              
x--;
           
else if(start < m->reserved)
              
start++;
           
else
              
break;
        
}
      }
  
} while(m->loop_run && !stop_effect);
}

В соответствии с ранее изложенным алгоритмом, мы в цикле вычисляем длину и начало выводимого кусочка из заданного текста, а так же позицию на дисплее, с которой это надо выводить, и используем уже известную функцию print_mid(). При вычислении всех упомянутых значений мы учитываем режимы отображения, заданные в структуре m. И еще: вы можете видеть, что самой первой строкой вычисляется длина заданной строки и помещается в зарезервированное поле структуры настроек! Вот как это поле используется! Я применил такой подход потому, что указатель на структуру настроек мы постоянно испольуем - уже как минимум в двух вспомогательных функциях он требуется! А если бы для длины строки мы использовали локальные переменные - их так же пришлось бы передавать внутрь вспомогательных функций, что привело бы к лишним накладным расходам. Но принципиально в этом подходе ничего плохого нет.

И еще: вы наверняка заметили, что в условиях завершения циклов используется ранее нигде не упоминавшаяся переменная stop_effect - это как раз и есть тот самый способ остановить "бесконечную" бегущую строку. переменная stop_effect определяется, как внешняя - то есть она может быть изменена прямо в ходе работы нашей функции (например, в обработчике прерывания от кнопки или таймера) - и тогда бесконечный бег строки будет остановлен немедленно (точнее - примерно через 10 миллисекунд):

/// глобальная переменная немедленной остановки вывода на дисплей
extern uint8_t stop_effect;

И последнее замечание. Функция задержки delay10ms() - это вспомогательная функция. Возможно, вы захотите использовать библиотечную функцию WinAVR _delay_ms() вместо нее и будете разочарованы: точности никакой, а размер кода - дико большой. А все потому, что эта функция не рассчитана на использование в качетсве параметров переменных: при использовании констант оптимизатор создает очень компактный код, а вот при использовании переменной подключается библиотека плавающей точки со всеми вытекающими... Поэтому я сделал свою функцию для приблизительной задержки:

static void delay10ms(uint8_t d){
  
for( ; d && !stop_effect; d--)
     
_delay_ms(10);
}

 

Как можно видеть, это очень простая функция, которую так же можно досрочно остановить при помощи переменной stop_effect. Точность этой функции вполне приличная - накладные расходы на организацию цикла невелики, зато теперь мы можем делать задержки на любые интервалы, задаваемые переменными! 

Итак, подведем итоги. Нами определены необходимые структуры, переменные и функции, чтобы получить эффект бегущей строки на экране ЖКИ. Попробуйте самостоятельно создать проект, использующий эти возможности - надеюсь, все у вас получится! А через некоторое время обсудим, как получить и другие эффекты.

Примечания.
1) Битовые поля экономят ОЗУ, но могут привести к увеличению израсходованной программной памяти - FLASH, а так же к некоторому замедлению скорости исполнения кода, что для данного конкретного случая совершенно некритично.

···
Добавить в любимые (1) | Просмотров: 24423

  Коментарии (2)
 1 Написал(а) ARV, в 06:44 25.08.2010
движок форума немного "корректирует" текст программ: вместо знака "больше или равно", который в Си записиывается парой символов > и =, он вставляет один символ... Прошу учесть при использовании
 2 Написал(а) Юрий, в 13:35 16.01.2011
этому парню нуна преподрвать в универе!тогда в стране появится много толковых специалистов. это факт!

Только зарегистрированные пользователи могут оставлять коментарии.
Пожалуйста зарегистрируйтесь или войдите в ваш аккаунт.