Главная arrow Начинающим arrow Пробуем программировать arrow Визуальные эффекты на символьном ЖКИ - часть 1  
14.10.2024 г.
Главная
Проекты
Статьи
Начинающим
Архив новостей
Ссылки
Контакты
Поиск
Файлы
Форум
Карта сайта
Авторизация





Забыли пароль?
Ещё не зарегистрированы? Регистрация
Поддержи наш сайт!
Через WebMoney

 R785211844650
 Z210696637574
 E368177590409

Визуальные эффекты на символьном ЖКИ - часть 1 Печать E-mail
Рейтинг: / 14
ХудшаяЛучшая 
Автор 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) | Просмотров: 36035

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

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

 
« Пред.   След. »
Полезные материалы по сходным темам
Кто на сайте?
Сейчас на сайте находятся:
2 гостей
Помощь on-line
BannerFans.com