Подключение LСD дисплея Nokia 5110 к микроконтроллеру ATtiny25.

LСD дисплей Nokia 5110.

Как-то на Али прикупил я себе три дисплея Nokia 5110. Раз прикупил, то надо научится ими пользоваться. Погуглив тему о подключении этих устройств к МК я понял, что подавляющее большинство примеров, это подключение их к ATmega8 с применением большого количества библиотек, которые упрощают задачу программирования, но в тоже время загружают контроллер так, что он уже не в состоянии ничего делать кроме как рисовать на экране кружочки, и конечно же выводить монументальное «Здравствуй мир» на английском. Более того, в статьях обычно ничего не написано о том, как осуществляется передача данных. Наверное, авторы, не без основания, считают, что подходить к изучению работы дисплея необходимо уже зная такие мелочи как передача данных по последовательному интерфейсу. Но как быть тем, у кого подключение этого дисплея сопряжено с первым знакомством со SPI? …


Эти дисплеи я собираюсь использовать в своих поделках и поэтому решение не применять готовые громоздкие библиотеки, а самостоятельно во всем разобраться продиктовано самой необходимостью. А чтобы не было желания увеличивать код до безобразия, я усложнил себе задачу и подключил дисплей не к Меге8 у которой 8 Кбайт флэш и 1 Кбайт оперативки, а к Тини 25 которая имеет более чем скромные объемы памяти. Да еще в довесок обрезанный SPI.

Забегая вперед скажу, что на мой взгляд с поставленной задачей по подключению я справился. В итоге у меня получилась небольшая библиотека основных подпрограмм, которая позволяет делать простейшие манипуляции с дисплеем, а также организовать вывод данных на экран. Конечно с помощью неё, написать «Hello World» в коде программы и вывести сточку на экран не получится, но с другой стороны, когда пишется программа под какое-то конкретное устройство на МК, работа со строковыми переменными в явном виде это скорей редкое исключение, которое должно иметь очень серьезное обоснование... Но, все по порядку.

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

Подключение дисплея к МК я проводил по классической схеме к ногам встроенного USI интерфейса. Чтобы не делать плату сопряжения я запитал всё устройство от стабилизатора 3,3V. Это сильно облегчает подключение дисплея и практически не ограничивает работу МК. Также я оставил одну линию контроллера для возможности подключить к нему какое-либо устройство, например, датчик температуры, для того чтобы из собранного модуля можно было бы сделать нечто большее чем просто «часы».

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

Для полного подключения дисплея необходимо задействовать шесть линий связи, не включая VCC – питание и GND - землю:
1. CE (SCE) – Включение дисплея. Эта линия, как я понял, нужна для указания контроллеру дисплея на то, что данные передаваемые по линии DIN предназначаются именно ему. Активный уровень низкий.
2. RST (RESET) – Сброс. Линия необходима при инициализации дисплея в начале его работы.
3. DC (D/C) – Флаг данные/команда. Линия указывает контроллеру дисплея как надо интерпретировать входящие данные. Низкий уровень (0) – команда, высокий (1) – данные.
4. DIN (SDIN) – Вход интерфейса. Линия приема данных.
5. CLC (SCLC) – Тактовый сигнал. Линия тактирования принимаемых данных.
6. BL (LED) – подсветка. Линия включения подсветки дисплея.

Я изначально не планировал какие-либо дополнительные подключения к МК кроме LСD дисплея. Поэтому немного упростил как само подключение, так и программное обеспечение.

Если подключать по упрошенной схеме, то можно линию BL либо вообще не подключать, либо подключить через кнопку или выключатель, а линию CE замкнуть на землю (GND) указывая тем самым, что дисплей должен находится всегда в рабочем режиме. Если совсем заморочится, то и на линии Reset можно сделать небольшую схему задержки, которая будет ненадолго сажать ее на землю при запуске устройства. Но стоит ли усложнять схему ради одной дополнительной линии? Не знаю. Поэтому считаю, что о специальной схеме для линии сброса надо задумываться только в крайнем случае. К тому же после подключения дисплея к Тини25 у МК осталась одна свободная линия на которую, как я уже говорил, у меня большие планы.

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

Итак, собрав схему, я приступил к изучению передачи данных по SPI. Надо сказать о том, что в МК семейства Tiny нет полноценного последовательного периферийного интерфейса (SPI) его замещает модуль универсального последовательного интерфейса (USI). Это сильно упрощённый SPI предоставляющий минимально необходимые аппаратные ресурсы для осуществления последовательного обмена данными. Поначалу это сильно напрягает, но затем, когда вникаешь в тонкости передачи данных становится ясно, что разработчики выкинули все навороты оставив только самое необходимое.

Обязательно надо упомянуть о том, что наименование выводов MOSI и MISO не имеют к модулю USI никакого отношения. Для подключения надо ориентироваться на название выводов DO и DI. Надо быть внимательным потому как там, где у Тини25 вход данных (MISO – мастер вход / подчиненный выход) у модуля USI выход (DO – данные выход). Я, начитавшись описания работы SPI, подключил линию MOSI к DIN и потом долго не мог понять почему ни чего не работает. Только после того как были подключены светодиоды к выходам контроллера и пошагово пройдена подпрограмма пересылки данных с фиксацией состояния выводов МК ко мне пришло понимания того, что я плохо разобрался в описании работы модуля. Потом пришлось немного скорректировать дорожки и все встало на свои места.

Теперь о подпрограмме пересылки данных из МК в контроллер дисплея.

//Подпрограмма пересылки данных через USI attiny 25 
1.	void usi_send (unsigned char data, unsigned char dc){ 
2.	          LSD_PORT.LSD_DC = dc;	
3.	          USIDR = data; 			
4.	          USISR |= (1<<USIOIF); 
5.	          USISR = (0<<USICNT3)|(0<<USICNT2)|(0<<USICNT1)|(0<<USICNT0);
6.	          while (USISR.USIOIF == F_OFF) USICR |= (1<<USIWM0)|(1<<USICS1)|(1<<USICLK)|(1<<USITC); 
	} //END Подпрограмма пересылки данных

Описание подпрограммы:
1. В подпрограмму usi_send передаются два байта (data) – передаваемый байт и (dc) – флаг данные – 0 /команда – 1.
2. В pin порта, линии (DC) записывается переданная информация dc (0/1). Сразу скажу, что я не проверял, что будет если в dc передать число больше единицы. Но обработчик ошибок вставлять сюда будет явно лишним. Так что будьте внимательны.
3. Записываем в регистр USIDR передаваемую переменную data.
4. Снимаем флаг переполнение счетчика модуля USI. Как не странно, но, чтобы его «снять» необходимо установить единицу в бит USIOIF регистра USISR.
5. На всякий случай обнуляем счетчик переданных бит.
6. Устанавливаем цикл по передачи данных пока не будет «установлен» (по факту сброшен) флаг переполнения счетчика (USIOIF). При этом осуществляется настройка тактовых сигналов и собственно тактирование. Единица в бит USIWM0 означает, что выбран трехпроводной режим, единицы в битах USICS1 и USICLK определяют источники тактового сигнала для сдвигового регистра и счетчика импульсов. Само тактирование происходит записью единицы в бит USITC регистра USICR.

После того как передача завершена МК организует выход из подпрограммы пересылки данных.

Временная диаграмма для данной подпрограммы выглядит так.

По первому такту на выходе данных (DO) появляется значения первого передаваемого бита. В этот момент также происходит его чтение. На диаграмме DI это pin 5 (PB0) который я использовал для передачи флага DC. Еще примечателен тот факт, что при передачи данных в приемо-передающий регистр записывается состояние линии DI из-за чего после передачи данных на линии DO устанавливается низкий уровень, а если передавалась команда, то высокий уровень. Также из диаграммы видно, что флаг DC после передачи данных не меняется. Он будет изменен только при следующей передачи информации в зависимости от вида передаваемых данных.

Вот как-то так. Я не претендую на оригинальность, но эта подпрограмма вполне работоспособна и по моему мнению оптимальна. Если же тактовый сигнал взять от блока сравнения счетчика Т0, (такая возможность существует) то, на мой взгляд, это не сильно уменьшит код, займет счетчик и не даст ощутимого эффекта в скорости выполнения как основной программы, так и пересылке данных. Подробней о том, как осуществляются пересылка данных по USI можно прочитать в расширенном руководстве на МК.

С пересылкой данных вроде разобрались. Теперь нам надо разобрать как провести инициализацию дисплея.

Вот моя версия с пояснениями.

//Подпрограмма инициализации дисплея
        void lsd_init(void)
       {  
                unsigned char c_pausa = 0xFF;  //переменная для паузы
                
                //Устанавливаем высокий уровень на линию RESET/
                LSD_PORT |= (1<<LSD_RST);  
                
                //Задействованные выводы для дисплея устанавливаем, как выходные	
                LSD_DDR |= (1<<LSD_CLK)|(1<<LSD_DC)|(1<<LSD_DIN)|(1<<LSD_RST);	
                
                //Сброс дисплея. Устанавливаем низкий уровень на линию RESET.  
                LSD_PORT &= (0<<LSD_RST);   
                
                //Небольшая пауза. В мануале на дисплей оговорено, что пауза должна быть не менее 100 нс.	    
                for (c_pausa; c_pausa >0; c_pausa--);         
                
                // Устанавливаем высокий уровень на линию RESET.
                LSD_PORT |= (1<<LSD_RST);  
                
                //передача команд инициализации 

                /* 1.  выбор расширенного набора команд 0x21 (0b00100001) */
                usi_send(0x21, LCD_COM);  //выбор расширенного набора команд
                
                /* 2. Смещение напряжения
                влияет на контрастность отображения, значения от 0х10 до 0х17, рекомендуют 13.
                Не спешите играться с этой настройкой. Лучше сначала выставить
                напряжения установками генератора повышенного напряжения. О нем ниже. */
                usi_send(0x13, LCD_COM);  //Смещение напряжения 
                
                /* 3. Температурная коррекция
                влияет на отображения дисплея при изменении температуры
                значения от 0х04 до 0х07 */
                usi_send(0x05, LCD_COM);  //Температурная коррекция
                
                /* 4. Генератор повышенного напряжения  
                влияет на контрастность и зависит от смещения напряжения и температурной коррекции   
                значения от 0х80 до 0хFF рекомендуемое значение 0хВ8 подбирается индивидуально.
                У меня по началу на экране почти ничего не было видно.				
                Потом после манипуляций с напряжением картинка прояснилась. */				
                usi_send(0xC8, LCD_COM);  //Генератор повышенного напряжения
                
                /* 5. Выбор обычного набора команд 0x20 (0b00100000)
                при этом устанавливается горизонтальный режим адресации 
                и включается дисплей */
                usi_send(0x20, LCD_COM);  //выбор обычного набора команд     
                
                /* 6. Графический режим 0x0C прямой 0х0D инвертированный */  
                lsd_regim(LCD_POSITIVE);     //Графический режим  
                
                /* 7. Необходимо очистить дисплей т.к. после включения в
                его памяти находится «грязь» которая рандомно проявляется на экране. */
                lsd_clean();             //Очистка дисплея  
				
        }  //END Подпрограмма инициализации дисплея 

Также в предлагаемой библиотеке есть подпрограммы:

Режим отображения - void lsd_regim (unsigned char lsd_reg)
Позволяет установить режим отображения данных как позитивный, так и негативный (инвертированный).

Установка позиции курсора - void lsd_position (unsigned char lsd_x, unsigned char lsd_y)
Подпрограмма принимает две переменные координаты Х (0…83) и Y (0…5) и переставляет курсор на новое место.

Очистка экрана - void lsd_clean(void)
Непосредственно очищается память дисплея путем записи в каждую ячейку 0х00.

Подпрограмма вывода на дисплей - void led_out (unsigned char *index, unsigned char width)
Думаю, ее надо рассмотреть подробнее.

//Подпрограмма вывода на дисплей (работает только с переменными из оперативной памяти)
    void led_out (unsigned char *index, unsigned char width){  //передается индекс на массив и его длинна
        unsigned char caunt; //счетчик для цикла
        for (caunt=0; caunt != (width); caunt++)usi_send((*(index + caunt)), LCD_DATA);//вывод знака
        usi_send(CLEAN, LCD_DATA); //пустая линяя после знака
	} //END Подпрограмма вывода на дисплей (работает толко с переменными из оперативной памяти)	

К сожалению толи из-за моего недопонимания, толи из-за проблем в самом МК, но передача в подпрограмму указателя на массив, расположенный во flash памяти невозможен. Напрямую обратиться можно, а вот передать указатель на массив нет. Как говориться, печалька. Поэтому данная подпрограмма хорошо работает только с массивами данных распложенных в оперативной памяти. Итак. В подпрограмму передаётся указатель на массив и его длинна. Затем организуется цикл, который по порядку вытаскивает из массива по одному байту и передает на дисплей. После передачи всего массива на дисплей передается дополнительно пустой байт. Это сделано для того, чтобы автоматически ставить разделитель между передаваемыми символами. При этом массив должен быть одномерным и не превышать длину в 256 элементов. Это чуть больше чем половина экрана.

Пример записи массива помещенного в оперативку.

unsigned  char ft_0 [] = {0x3E, 0x51, 0x49, 0x45, 0x3E}; //0	

Также можно выводить массивы, записанные во flash память.

Пример массива, записанного во flash.

flash const unsigned  char ft_RED_RESISTER_RU [] = 
{
0x7F, 0x09, 0x19, 0x29, 0x46, 0x00,\   //R
0x7F, 0x49, 0x49, 0x49, 0x41, 0x00,\   //E
0x7F, 0x41, 0x41, 0x22, 0x1C, 0x00,\   //D
0x10, 0x10, 0x10, 0x10, 0x10, 0x00,\   //-
0x7F, 0x09, 0x19, 0x29, 0x46, 0x00,\   //R
0x7F, 0x49, 0x49, 0x49, 0x41, 0x00,\   //E
0x46, 0x49, 0x49, 0x49, 0x31, 0x00,\   //S
0x00, 0x41, 0x7F, 0x41, 0x00, 0x00,\   //I
0x46, 0x49, 0x49, 0x49, 0x31, 0x00,\   //S
0x01, 0x01, 0x7F, 0x01, 0x01, 0x00,\   //T
0x3E, 0x41, 0x41, 0x41, 0x3E, 0x00,\   //O
0x7F, 0x09, 0x19, 0x29, 0x46, 0x00 \   //R
};	

Указатели flash const читаются как масло масленное, но так спокойней. А выводить такие массивы я смог только прямой работой с ним.

lsd_position(7,2);
width = FT_SIZE(ft_RED_RESISTER_RU); 
for (caunt = 0; caunt != width; caunt++)usi_send(ft_RED_RESISTER_RU [caunt], LCD_DATA);

Где: 
FT_SIZE(ft_RED_RESISTER_RU); 
это ни что иное как 
#define FT_SIZE(a) sizeof(a)/sizeof*(a)

Так, что если необходимо вывести какой-то массив из flash памяти, то такую конструкцию придется делать как отдельную подпрограмму, либо вставлять её по необходимости. Для чего я делаю акцент на flash памяти? Да для того, чтобы не занимать оперативку, которой и так не много, все массивы так или иначе уже имеется во flash памяти и поэтому переписывать их еще и в оперативку это слегка нерационально. Но и тут есть нюансы о которых сейчас говорить преждевременно.

Да чуть не забыл. Эти программы должны правильно работать в моделях ATtiny2313, 24/44/84, 25/45/85 и 26. А в других моделях tiny модуля USI, да и SPI просто нет. Вся моя тестовая программа заняла около 1 Кбайта flash и около 90 байт оперативки с учетом всех массивов. Так, что можно еще много чего сделать и на этом МК.

Ну, вот вроде бы и всё.

В приложенных файлах, полностью заархивированных проект по подключению LCD Nokia 5110 сделанный в CVavr и отдельно библиотека, вынутая оттуда, а также схема подключения. Разводку платы не предлагаю т.к. если у кого появится желание поэкспериментировать, то это лучше сделать на макетке.

А на сегодня всё. Удачи.

12.04.2017



Небольшая доработка библиотеки.

Когда делал плату управления клапаном пришло понимание, что необходимо переделать библиотеку и сделать общие названия подпрограмм как для библиотеки подключения Nokia 5110 к ATmega_8 так и для библиотеки подключения к ATtiny.

В прилагаемом файле новая библиотека для подключения Nokia 5110 к микроконтроллерам семейства ATtiny.

А на сегодня всё. Удачи.

06.03.2018



Небольшое дополнение к сказанному!

Прошу обратить внимание на то, что если вы пишите программы не в CV_AVR, то с большой вероятностью при компиляции вы столкнетесь с ошибкой в строке:

//Подпрограмма пересылки данных через USI attiny 25 

LSD_PORT.LSD_DC = dc;

Если ваша среда разработки не понимает прямое обращение к биту порта в формате:

Имя_порта.Номер_бита = 0/1;	

PORTC.4 = 1;

То эту строку необходимо заменить следующим кодом:

if (ds) LSD_PORT |= (1<<LSD_DC);
else LSD_PORT &= (0<<LSD_DC);

Спасибо за замечание Николаю Костину

А на сегодня всё. Удачи.

18.09.2018


Если вдруг найдете в статье неточности или заблуждения. Напишите мне об этом. Я подправлю.

Приложение:
Проект в CVavr, библиотека и схема подключения
Библиотека от 06.03.2018