Визуальные эффекты на символьном ЖКИ - часть 1 |
Автор ARV | ||||||
03.07.2010 г. | ||||||
Символьные ЖКИ с управляющим контроллером, совместимым с HD44780, очень популярны: они недороги и удобны, требуют (в минимальном случае) всего 7 линий ввода-вывода для подключения к микроконтроллеру. Однако, порой хочется чего-то такого эдакого... ибо слишком уж они по-деловому отображают информацию. На рисунках показан ряд текстовых визуальных эффектов, которые могут порой немного украсить вашу конструкцию. Нравится? Интересно, как это сделано? Об этом и пойдет речь. Сначала я решил просто выложить исходники функций, которые выполняют эти эффекты, но потом подумал, что в этом мало пользы для тех, кто хочет сам научиться писать программы. И поэтому я передумал: исходники, само собой, я выложу, но помимо этого в данной статье подробным олбразом расскажу, как и что сделано, и что еще можно сделать впридачу.
Мтак, самое простое: эффект бегущей строки. Я поставил задачу максимум: сделать так. чтобы строка могла бежать слева направо и справа налево, чтобы можно было изменять скорость ее бега, чтобы строка могла при выползании из-за края затирать ранее выведенную строку, а так же выползать на заранее очищенную строку, чтобы функция могла работать со строками, размещенными в ОЗУ или во FLASH... И чтобы все это было сделано в виде одной функции. Подумав, я решил описать эту функцию таким образом:
// Реализация бегущей строки Никакого значения функция не должна возвращать, потому ее тип void. Имя функции говорит само за себя - running_str. Параметров у этой функции всего два: собственно указатель на текст, который будет бегать и указатель на структуру с различными режимами вывода. Я неспроста заостряю внимание на столь очевидных и, казалось бы, совершенно малозначительных фактах, как название функции и имена ее параметров. Не раз приходилось встречать в чужих программах определения функций типа такого: void rstr(char *t, o_m *m); Да, автору такой функции пришлось чуть ли не в 3 раза меньше набирать текста, однако непосвященному уже не понять, что эта функция делает без дополнительных комментариев. Параметр t - это указатель на char, но из этого отнюдь не следует, что это обязательно будет строка символов, ведь char в Си - это обычное число типа байт (в диалектах Си, ориентированных на микроконтроллеры). Согласитесь, что char *text уже не даст возможности интерпретировать себя как-то иначе, нежели "указатель на текстовую строку". Второй параметр функции я обозвал одной буковй m, однако его тип развернут, и опять же не даст усомниться в его назначении - режим вывода. Суффикс _t (по соглашению, принятом в стандарте C99) однозначно обозначает тип, введенный пользователем, - я стараюсь придерживаться этого соглашения, чтобы меньше путаться в своих программах. То есть я хочу сказать, что давая переменным, типам, функциям и константам "говорящие сами за себя" наименования, вы создаете удобный, хорошо читаемый, красивый и профессиональный код, который затем почти не будет требовать комментариев. Итак, функция описана. Мы представляем, как она будет работать: вызываем ее с нужными парметрами, и она выполняет то, что требуется - выводит бегущую строку. Остается определить собственно сам тип 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){ В общем, все должно быть понятно: функция получает кучу параметров: *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){ В соответствии с ранее изложенным алгоритмом, мы в цикле вычисляем длину и начало выводимого кусочка из заданного текста, а так же позицию на дисплее, с которой это надо выводить, и используем уже известную функцию 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) | Просмотров: 36035
Только зарегистрированные пользователи могут оставлять коментарии. |
« Пред. | След. » |
---|