Уроки MSP430 LaunchPad. Урок 14: Текстовый дисплей

Мы в одном шаге от создания полноценного измерительного прибора. Все, что нам осталось, это сделать результаты измерения доступными за пределами отладчика. Для этого есть несколько способов. Один из них, выводить результат измерения на ЖК-дисплей, как это делается в бытовой технике. В этом уроке, мы научимся использовать стандартный ЖК-модуль с LaunchPad. Так же, здесь, мы изучим концепцию создания пользовательской библиотеки. В конце урока мы получим библиотеку, которую вы сможете использовать в своих будущих проектах с ЖК-экраном, что сократит затрачиваемые усилия на написание кода.

ЖК-модуль (LCM)


Для этого урока вам необходим ЖК-дисплей, которого у вас может и не быть. Если нету, рекомендую купить один. Эти дисплеи очень распространены и стоят не дорого. Они бывают разных размеров и типов, здесь мы будем использовать стандартный текстовый 16х2 ЖК-модуль с интерфейсом HD44780. Их можно купить где угодно, например на eBay, можете выбрать любой, какой вам нравится, но убедитесь что, он работает от напряжения 3.3 В и не переделан для работы по последовательному интерфейсу (I2C/TWI). Если вам хочется большой модуль (например 20х4), как пожелаете, работают они одинаково. Цвет можете выбрать любой.

Я применил ЖК-модуль с управляющим чипом ST7066, который использует вездесущий интерфейс HD44780. (Если вам эти названия ни о чем не говорят, не беспокойтесь, просто примите к сведению, что у нас есть стандартный интерфейс для связи между MSP430 и дисплейным модулем). Этот интерфейс работает через 8-битную параллельную шину для обмена данными между микроконтроллером и дисплеем. Если представить себе 8 каналов данных, да плюс еще 3 дополнительных контрольных канала, можно быстро превысить количество доступных выводов портов вашего MSP430. По факту, даже если вы используете G2211 без внешнего кварца, что освобождает два вывода P2.5 и P2.6, мы получаем только 10 выводов на портах, трёх нам не хватает, 11 нужно для дисплея и 2 выхода компаратора для измерителя ёмкости. К счастью, интерфейс HD44780 (и тот, что у ST7066), позволяет посылать данные двумя 4-битными порциями, и до тех пор, пока у нас не возникнет необходимость читать данные из дисплея, мы можем использовать только 6 выводов для него, что позволит нам ужать компаратор и дисплей в восемь выводов порта P1 на нашем G2211. Мы подошли к лимиту этого микроконтроллера, это хорошая мотивация перейти на более продвинутые MSP430 в будущем! (Справедливости ради заметим, что LaunchPad версии 1.5 комплектуется G2553, самой продвинутой моделью Value Line, и выходов в нем побольше, а если нужно принципиальное увеличение количества выходов, придется отказаться от корпусов PDIP, по крайней мере в MSP430, например у AVR есть большие PDIP. – Прим. пер.)

Подключение дисплейного модуля


Стандартный ЖК-модуль имеет 16 выводов (14 без подсветки). Первые несколько предназначены для питания (могут быть в двух местах, при наличии подсветки) и настройки контраста. Вывод 1 (обозначен как Vss) должен быть соединен с землей на вашем LaunchPad. Вывод 2 (Vdd) соединен с питанием Vcc на вашем LaunchPad. Если вы используете вывод подсветки 15 (LED+), он так же подсоединяется к Vcc и вывод 16 (LED-) к земле. (Всё это проще всего располагать на макетной плате (breadboard)). Вывод 3 (V0), контролирует контрастность экрана. Если у вас есть переменный резистор на 10 кОм, присоедините этот вывод к среднему контакту резистора, а два крайних контакта соедините с землей и питанием, и вы сможете настраивать контраст. Если резистора нету, просто заземлите этот вывод, будет смотреться не так хорошо как могло бы, но работать будет.

Остается 11 выводов на контроль и данные. Три контрольных линии, это выводы 4 (RS), 5 (R/W) и 6 (E). Вывод чтения/записи (R/W) нам не понадобится, и соединив его с землей, мы включим постоянный режим чтения у дисплея. Мы не сможем что-либо считать с дисплея (вроде положения курсора, состояния флага занятости и т.д.), но это даст нам один свободный вывод на MSP430. Для контроля дисплея мы будем использовать выводы Выбор Регистра – Register Select (RS) и Готовность – Enable (E). Последние выводы 7-14 (D0-D7), это шина данных. Можно считать эти выводы такими же, как и восемь выводов порта P1 на MSP430, – D0 это первый бит, D1 второй, и т.д. Если мы используем полный порт ввода/вывода на MSP430 для шины данных, просто соединим соответствующий вывод порта с таким же по номеру выводом шины данных, и избавим себя от сложностей создания виртуального порта с нестандартным расположением выводов внутри программы. Но, т.к. на G2211 не хватает портов для этого, мы просто используем 4-битный режим ввода. Для него используются выводы D4-D7 дисплейного модуля. Выводы D0-D3 останутся не присоединенными.

В программе измерителя ёмкости нужно произвести замену рабочих выводов, из-за использования ЖК-модуля. Теперь мы будем использовать P1.1 как TA0.0 вместо CA1, и используем P1.2/CA2 как неинвертирующий вход компаратора. Вывод P1.0 будет контролировать RS, P1.3 контролировать E, выводы P1.4-P1.7 будут контролировать D4-D7 соответственно.

Обратите внимание на то, что вывод P1.3 в LaunchPad, соединен с кнопкой, это не должно мешать работе программы, но т.к. он подтянут резистором к питанию, это будет приводить к повышенной утечке тока, когда мы будем сбрасывать E. К несчастью, на LaunchPad не предусмотрена перемычка на кнопке S2, как на выводах P1.0 и P1.6 светодиодов, так что оставим это как есть. И пока мы об этом говорим, убедитесь, что сняли перемычки с двух светодиодов и TXD/RXD. (Здесь вам должно стать понятно, почему в новом LaunchPad отсутствует подтягивающий резистор на P1.3 – прим. пер.)

Управление дисплейным модулем


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

Для примера, посмотрим команды, необходимые нам для установки дисплейного модуля в 4-битный режим. Инструкция «Выбор Функционала» в 8-битном двоичном коде выглядит так: 0b001nnnxx. (Здесь указаны значения, используемые для выбора конфигурации и x, обозначает не используемые биты, какое бы у них не было значение, они ни на что не влияют). Бит 4, в данной инструкции, устанавливает способ взаимодействия: 1 – устанавливает 8-битный интерфейс, 0 – 4-битный. Таким образом, посылая инструкцию 0b00100000 (или 0x20), мы конфигурируем дисплейный модуль на прием команд и символов за два 4-битных посыла, вместо одного 8-битного. Эта команда, должна быть послана первой, что бы мы могли работать с нашей 6-проводной схемой подключения. В первую очередь устанавливаем значение на шине данных командой P1OUT |= 0x20 (одновременно сбросив RS (значит это команда, а не символ) и сбросив E (необходимо в нашей схеме подключения)), а затем отправляем команду импульсом на E.

Дисплейный модуль не отвечает на команду моментально, есть точные временные интервалы для его корректной работы. В особенности, RS должен быть сброшен в течении определенного времени, до начала импульса на E. Линии данных должны быть установлены за некоторое время до возникновения спадающего фронта импульса, и должны сохранять свое состояние достаточное время после импульса. Затем, некоторое определенное время должно пройти до того, как мы сможем послать импульс на E снова. К счастью для нас, точность временной задержки важна только между командными импульсами. Остальные временные задержи, порядка нескольких сотен наносекунд, и скорость выполнения команд MSP430 достаточно небольшая, для того, что бы эти задержки возникали естественным образом. Время, необходимое для завершения посыла команды, прежде чем можно посылать следующую, должно быть порядка 150 мс. Его можно получить через команды задержки delay().

Резюмируя все сказанное выше, вот набор инструкций для установки ЖК-модуля в 4-битный режим:

__delay_cycles(10000); // ожидание на «разогрев» дисплея, сразу после включения
P1OUT |= 0x20;   // команда переключиться в 4-битный режим
P1OUT |= BIT3;   // устанавливаем вывод E
__delay_cycles(200);
P1OUT &= ~BIT3;  // сбрасываем вывод E 
__delay_cycles(200);
P1OUT &= 0x0F;   // обнуляем 4 старших бита

Хотя установить E можно до установки шины данных, удобней поменять порядок, во избежание временных несовпадений. В случае, если вы используете другой порядок соединения выводов, в особенности если используете несколько портов, этот код не заработает. Это удобно использовать выводы порта P1.4-P1.7 для выводов D4-D7 ЖК-модуля, но не обязательно. Если вы поменяли порядок, например у вас P1.4-P1.7 соединены с D7-D4 соответственно, то вы должны записать 0b0100, а не 0b0010 в шину данных в этом коде. Будьте внимательны, используя другую конфигурацию выводов, вы должны настроить каждый бит шины данных соответственно. Последняя строка обнуляет шину данных, дабы облегчить правильный ввод следующей команды.

Посылка команд в 4-битном режиме


Теперь наш дисплей готов принимать команды в 4-битном режиме. Этот режим работает через посылку порции из 4 старших битов по пульсации E. Затем посылки второй порции из 4 младших битов, по второй пульсации. Для нашей схемы соединения мы можем это сделать с помощью такого кода:

P1OUT |= ( & 0xF0);  // отсылаем старшие 4 бита
pulse();
P1OUT &= 0x0F;   // обнуляем
P1OUT |= (( & 0x0F;) << 4);  // отсылаем младшие 4 бита
pulse();
P1OUT &= 0x0F;   // обнуляем

Здесь видно, что я собрал все команды отвечающие за пульсацию Е, включая временные задержки, в одну функцию void pulse(void). Конечно она подходит и для использования в 8-битном режиме. Если мы соберем описанный выше набор команд в функцию void SendByte(char), мы сможем записать последующие команды инициализации таким образом:

SendByte(0x28); // Функция установки 4-битного, 2-строчного режима
                      //(для двухстрочных дисплеев, само собой)
SendByte(0x0E);   // Включить дисплей, курсор-чёрточка, немигающий
SendByte(0x06);   // Режим символьного ввода: добавление адреса, без сдвига дисплея

После ввода этих команд, наш дисплей будет готов отобразить любые символы или текст, что мы ему пошлем. Важно понимать, что для отправки символов используются те же команды что и выше, но в P1OUT, должен быть установлен бит BIT0 (RS), что бы дисплей знал, что это символ, а не команда. В своем примере я, для этого сделал расширенную функцию SendByte, которая позволяет посылать как команды, так и символы. Так же, в нем используются другие команды, такие как очистка дисплея и перемещение курсора. Если у вас есть ЖК-дисплей, попробуйте эту программу сами. С выбранными там задержками, она не будет работать с DCO быстрее 2 мГц. Если будете экспериментировать с кодом, учтите это. Дальше рассмотрим, как инкапсулировать этот код в библиотеку, и как использовать её в нашем измерителе ёмкости.

(Так же, что бы узнать о других командах, рекомендуется почитать другие статьи про использование таких дисплеев, например вот и вот. – Прим. пер.)

Листинг программы (оригинал lcddemoG2211.c):

/* lcddemoG2211.c: демонстрирует использование модуля 
 * ЖК-дисплея выводя на нем простое сообщение.
 * 
 *   ::  Copyright 2011, MSPSCI
 *   ::  http://mspsci.blogspot.com
 */

#include <msp430g2211.h>

#define LCM_DIR P1DIR
#define LCM_OUT P1OUT

#define RS  BIT0
#define EN  BIT3
#define D4  BIT4
#define D5  BIT5
#define D6  BIT6
#define D7  BIT7
#define MASK    (RS + EN + D4 + D5 + D6 + D7)

#define FALSE 0
#define TRUE 1

/*  Глобальные переменные  */
unsigned char overflows;

/*  Объявления функций  */
void LCM_init(void);
void pulse(void);
void clear(void);
void SendByte(char, char);
void MoveCursor(char, char);
void PrintStr(char *);

void main(void) {
    WDTCTL = WDTPW + WDTHOLD;
    
    P1OUT = 0;
    P1DIR = 0xF;

    TACTL = TASSEL_2 + ID_2 + MC_2; // Используем SMCLK, делитель 4, непрерывный режим
    TACCTL0 = CCIE;          // Прерывание по достижении TACCR0
    overflows = 0;

    __enable_interrupt();
    
    LCM_init();
    clear();
    while(overflows < 20);
        
    for(;;) {
        while(overflows < 20);
        overflows = 0;
        MoveCursor(0,2);
        PrintStr("Hi from WE.");

        while(overflows < 14);
        overflows = 0;
        MoveCursor(1,0);
        __delay_cycles(10000);
        PrintStr("EASYELECTRONICS!");
        
        while(overflows < 20);
        overflows = 0;
        clear();
    }
} // main

void pulse(void) {    
    LCM_OUT |= EN;
    __delay_cycles(200);
    
    LCM_OUT &= ~EN;
    __delay_cycles(200);
} // pulse

void SendByte(char ByteToSend, char IsData) {
    LCM_OUT &= ~MASK;
    LCM_OUT |= (ByteToSend & 0xF0);
    
    if (IsData == TRUE) LCM_OUT |= RS;
    else LCM_OUT &= ~RS;
    pulse();
    
    LCM_OUT &= ~MASK;
    LCM_OUT |= ((ByteToSend & 0x0F) << 4);
    
    if (IsData == TRUE) LCM_OUT |= RS;
    else LCM_OUT &= ~RS;
    pulse();
} // pulse

void LCM_init(void) {
    LCM_DIR |= MASK;
    LCM_OUT &= ~MASK;
    
   __delay_cycles(100000);
   
   LCM_OUT = 0x20;
   pulse();
   
   SendByte(0x28, FALSE);
   SendByte(0x0C, FALSE);
   SendByte(0x06, FALSE);
} // LCM_init

void clear(void) {
    SendByte(0x01, FALSE);
    SendByte(0x02, FALSE);
    __delay_cycles(100000);
} // clear

void MoveCursor(char Row, char Col) {
    char address;
    if (Row == 0) address = 0;
    else address = 0x40;
    address |= Col;
    SendByte(0x80|address, FALSE);
} // MoveCursor

void PrintStr(char *Text) {
    char *c;
    c = Text;
    while ((c != 0) && (*c != 0)) {
        SendByte(*c, TRUE);
        c++;
    }
} // PrintStr

/*  Обработчики прерываний  */
#pragma vector = TIMERA0_VECTOR
__interrupt void CCR0_ISR(void) {
    overflows++;    // Считаем переполнения TACCR0.
} // TA0_ISR

Архив с программами из урока.

Добавляем новую библиотеку


У нас есть код, с помощью которого можно легко послать текст на ЖК-дисплей, и который было бы неплохо иметь в виде библиотеки, что бы его достаточно было просто подключить к проекту, и не заниматься каждый раз копипастингом. Язык программирования Си позволяет это делать очень легко. Так что посмотрим, как засунуть работу с дисплеем в библиотеку, и как настроить CCS для использования этой библиотеки. В эту библиотеку можно будет добавлять любой код, который вы собрались использовать неоднократно.

Во-первых, выберите папку, где будет храниться библиотека. Компилятору без разницы, но у вас должен быть простой путь для доступа к ней, что бы вносить изменения и доработки. С другой стороны, это должно быть достаточно безопасное место, что бы вы случайно её не стёрли, не изменили, не перенесли и т.п. Я например, просто создал папку “library” в моём каталоге с проектами.

Во-вторых, скопируйте все определения #include, #define, объявления функций и глобальные переменные в отдельный заголовочный файл. Я назвал его simple_LCM.h. Если вы собираетесь использовать специфические для вашей модели MSP430 определения, то необходимо включить ссылку на заголовочный файл этой модели. Для сохранения универсальности вашей библиотеки, лучше использовать #include <msp430.h>, вместо заголовочного файла специфической модели.

В-третьих, скопируйте оставшийся код функций в отдельный файл ”.c” с таким же именем как у заголовочного (в нашем случае simple_LCM.c). В начале этого файла должно быть добавлено #include <имя_файла.h>, где «имя_файла», это имя нашего заголовочного файла. Внимание: этот файл НЕ ДОЛЖЕН содержать функцию main()!

В-четвертых, в вашем новом проекте, правой кнокой кликните на папке проекта и выбирите new → folder. Кликните на кнопке [Advanced >>], и выберете “Link to folder in the filesystem” добавить ссылку на каталог. Потом вы должны найти каталог своей библиотеки и добавить его.

Теперь любые файлы из папки библиотеки, доступны для использования в программе. Правда компилятору требуется отдельно указывать путь к этой папке, что бы он заработал. (Это то, что мне больше всего не нравится, нужно это делать для каждого нового проекта, и я не нашел способа, как сделать путь к этой папке встроенным по умолчанию, при создании нового проекта CCS).

Итак, в-пятых, кликните правой кнопкой на папке проекта, и выберите “properties” свойства. Откройте раздел “C/C++ Build” и в “Tool Settings” настройках инструмента, найдите MSP430 Compiler → Include Options, а так же MSP430 Linker → File Search Path. В обоих этих местах, укажите путь к каталогу библиотеки, иначе ваш код не откомпилируется.

Есть один более быстрый путь. В CCS идите в меню Window → Preferences, затем General → Workspace → Linked Resources. Здесь вы можете изменить переменные пути (My_Library и др.) в них может содержаться ссылка на ваш каталог. Когда вы добавляете новый каталог в проект, вместо поиска папки с библиотеками, можно кликнуть [Variables...] и выбрать нужную переменную из списка. К несчастью, я не увидел, что бы свойства проекта менялись, и компилятор смог бы распознать переменную пути, хотя по логике, это должно было бы случиться.

Теперь мы готовы для постройки измерителя ёмкости с блэк-джеком дисплеем. Программа, которую я записал в CMeterLCMG2211.c, демонстрирует несколько новых идей использования ЖК-модуля. Просмотрите код и прочтите комментарии, что бы понять как он работает. Заметьте, что в нем используются команды, подобные MoveCursor(row,col); для управления выводом на дисплей.

Раз библиотека simle_LCM имеет шаблон для вывода строк, что случится, если мы захотим вывести цифровое значение, такое как время задержки таймера (из переменной long int time;)? Один, стандартный для Си метод, это использовать библиотеку “stdio” и функцию sprintf(); из неё. Все что нам нужно, это создать массив символов, например print_time[10], и использовать sprintf(print_time, “%d”, time); что бы поместить число в строку print_time, а затем передать её в PrintStr() для вывода на экран. К несчастью, этот метод имеет некоторые недостатки, при использовании его для микроконтроллеров. Во-первых, несмотря на очень хорошую оптимизацию кода компилятором CCS, любая программа использующая функцию printf, получится большой. Наша программа может превысить 2 кБ, доступные в G2211. Во-вторых, оптимизация усложняет получение корректного форматированного вывода. В идеале, мы должны использовать форматирование %10d, для размещения значения таймера точно в 10 байт переменной print_time. Мы не можем сделать этого с включенной оптимизацией. Мы можем изменить уровень оптимизации для printf в свойствах проекта, но это еще увеличит размер кода.

К счастью, есть методы справиться с этой проблемой. Из переменной типа integer, мы можем вытащить отдельные цифры, используя оператор целочисленного деления и оператор остатка от деления. Так x%10; вернет последнюю цифру числа, хранящегося в x. Затем x/=10; удалит последнюю цифру из числа, оставив остальные. Запустив цикл из таких операций, до достижения x==0 (больше нет цифр), мы можем извлечь все цифры из числа по одной и послать их на печать. Кодовая таблица ASCII, используемая в дисплейном модуле, устроена так, что мы можем получить код любой цифры, просто прибавляя её к одной и той же константе, так 0x30+0 будет “0”, 0x30+7 будет “7” и т.д.

Недостаток такого метода с циклом, в том, что мы получаем цифры в обратном порядке, справа налево. У ЖК-модуля есть режим работы, когда он перемещает курсор справа налево после печати символа, таким образом, есть возможность выводить текст в таком порядке. (На самом деле, так работает большинство обычных калькуляторов). Загляните в нашу программу, что бы узнать, какими командами настраивается такой способ вывода.

Итак, мы смогли создать завершенный измерительный прибор, используя MSP430. Мы использовали комбинацию таймера и компаратора с калиброванным генератором тактового сигнала, для измерения времени задержки в RC-цепи. Дисплей показывает измеренное время в микросекундах. Зная значение сопротивления R и время, мы можем посчитать реальное значение ёмкости C.

Упражнение: Программа работает прекрасно, но не лучше было бы, имея ЖК-экран, видеть сразу ёмкость вместо времени? Вы можете делать операции с плавающей запятой на MSP430 (хотя неэффективно), но как показать число с плавающей запятой на экране? Если sprintf оказался великоват для нашей программы, то данный функционал, несомненно, займет слишком много памяти. Сможете ли вы вывести ёмкость на экран, не превысив лимит в 2 кБ на G2211? Если у вас не получается, один из способов показан здесь: CMeterLCMFull.c. В этой программе так же реализован авто-выбор единиц измерения. Её код занимает 1934 байта, как раз достаточно, что бы уместиться в G2211.

Примечание переводчика: Чтобы не увеличивать размер текста, я не стал приводить полные листинги, а просто присоединил архив с файлами библиотеки и двумя примерами её использования.

Оригиналы урока на английском: Tutorial 14a: The Alphanumeric LCD и Tutorial 14b: Adding a New Library

Предыдущий урок этого цикла: Урок 13: Комбинируем периферию
Следующий урок этого цикла: Урок 15: Преобразование аналогового сигнала
  • +7
  • 23 ноября 2012, 03:36
  • Tabke
  • 1
Файлы в топике: lesson14.zip

Комментарии (8)

RSS свернуть / развернуть
когда, не знаете, где ставить, запятые, ставьте их, везде.
0
Нашли ошибки? Пишите в личку, я исправлю, мне не трудно.
0
Да это цитата из какой-то статьи про грамматику и пунктуацию в интернете. Просто не удержался.
Ну вот, например:
В этом уроке, мы научимся использовать стандартный ЖК-модуль с LaunchPad. Так же, здесь, мы изучим концепцию создания пользовательской библиотеки.

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

Или может кто статейку черкнет здесь не большую, но убойную, что-то в современном стиле: «Русский язык для чайников» / «Грамматика для начинающего эмбеддера-блоггера» / «Учимся писать грамотно за полчаса».
0
В личном блоге _YS_ (совсем личном, на вордпрессе) есть статейка про грамматику. Не помню правда, как там с пунктуацией.
0
помойму лучше всех данные статейки черкают в класе 7мом начальной школы ;)
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.