31 июля 2014 г.

nokia 5110 LCD дисплей AVR


Теперь я расскажу как работать с дисплеем от Нокии 5110.

Матрица состоит из 48 × 84 пикселей. 48 строчек, 84 столбца.
Но питать микросхему отвечающую за работу дисплея нужно от 2.7 В до 3.3 В.
Подсветка также питается от 3.3 В или можно от 5 В через резистор 1К.

Также советуют ставить резисторы 10К на все остальные выводы, если МК питается от 5 В.
Можно работать и с 5 В, но могут быть проблемы с отображением и дисплей быстрее придёт в негодность.

Я заказывал с Ебея и в моём случае для того чтобы включить подсветку нужно было заземлить вывод LED.

Есть несколько вариантов расположения выводов:



Номер выводаОбозначениеФункцияПримечание
1VCCПитание
2GNDЗемля
3SCE (CE)Выбор чипаможно просто заземлить
4RSTRESETактивно при лог 0
5D/C (DC)выбор режимакоманды лог 0
дата лог 1
6DN (DIN)Датапередаётся байт данных
7SCLK (CLK)Serial clock,
линия синхронизации

8LED (LIGHT)Подсветка3.3V

Теперь, как же работать с дисплеем. Каждый раз когда нужно общаться с дисплеем необходима на SCE подать лог 0, я его просто заземлил.

Когда на SCE лог 0, нужно определиться мы передаем данные D/C = 0 или
команду D/C = 1.

На самом деле это считывается когда мы передаём последний бит данных, но нет разницы когда мы выберем работаем ли мы с данными или командами, главное чтобы при передаче последнего (0) бита стояло нужное нам значение.

Передача данных происходит следующим образом: Мы передаём байт данных по выводу DN, начиная с последнего 7 бита. Нужно выставить этот бит и установить на SCLK лог 1, в этот момент начинает передаваться информация к контроллеру, затем нужно сбросить SCLK, выставить 6 бит на DN, снова выставить 1 на SCLK и продолжать так до тех пор пока не передадим целый байт.

Рис. 1. Передача 1 байта

После этого можно сразу передавать следующий байт, если произошла ошибка и вы передали не тот бит или просто нужно отменить команду последний бит которой ещё не отправлен, то для этого нужно подать лог 0 на RST. Команда не передастся и можно сразу начать передачу новой команды.
Рис. 2. Передача нескольких байт

Рис. 3. Отмена передачи

Не позже чем через 30 мс после подачи питания, необходимо подать отрицательный импульс на RESET длиной 100 нс, иначе можно испортить дисплей ! Таким образом происходит начальная инициализация дисплея.

В принципе ширина всех импульсов <= 100 нс. Поэтому если МК работает на 8 МГц или ниже, можно не ждать при передаче данных, изменении состояний выводов и т.д.

Команда
D/C
DB7
DB6
DB5
DB4
DB3
DB2
DB1
DB0
(H = 0 or 1)
Выбор функции
0
0
0
1
0
0
PD
V
H
Записать данные
1
 D7
 D6
 D5
 D4
 D3
 D2
 D1
 D0
(H = 0)
настройка дисплея
0
0
0
0
0
1
D
0
E
выставить Y адрес
RAM
0
0
1
0
0
0
Y2
Y1
Y0
выставить X адрес
RAM
0
1
X6
X5
X4
X3
X2
X1
X0
(H = 1)
Настройка
температуры
0
0
0
0
0
0
1
TC1
TC0
Смещение системы
0
0
0
0
1
0
 BS2
 BS1
 BS0
выставить VOP
0
1
VOP6
VOP5
VOP4
VOP3
VOP2
VOP1
VOP0

BIT 0 1
PD Чип активен Режим низкого энергопотребления
V горизонтальная адресация вертикальная адресация
H обычные команды дополнительные команды
и E 00
01
10
11
Пустой дисплей
Обычный режим
Зажечь все пиксели
Инвертированный режим
TC1 и TC0 00
01
10
11
температурный коэффициент 0
1
2
3

VOP - определяет контраст дисплея, у меня всё прекрасно видно при 0xc8.
TC - температурный коэффициент отвечает за изменение напряжения при изменении температуры, может быть полезно если дисплей работает при низких или переменных температурах (дом-улица).


Чтобы выбрать какие пиксели зажечь нужно передать байт данных, мы выбираем как отображать сразу группу из 8 пикселей, информация об изображении на дисплее (о том какие пиксели горят) хранится в RAM, после того как мы передали байт данных мы автоматически переходим к следующей группе пикселей. 

Рис. 4. Адресация RAM

Когда мы передаём 1 байт он всегда записывается сверху вниз, дисплей как бы состоит из столбцов по 8 пикселей (1 байт), когда мы записываем столбец то переходим к следующему и тут есть на варианта, либо идти вправо по строчке, дойдя до конца строки опуститься на строку ниже, вернуться к 1 столбцу и продолжить дальше.
(Горизонтальная адресация)

Либо опускаться вниз по столбцу, дойдя до низа, подняться наверх но сдвинуться вправо.
(Вертикальная адресация)



Рис. 5. Горизонтальная адресация

Вертикальная
Рис. 6. Вертикальная адресация

Когда мы заполняем правый нижний блок,то адрес обнуляется и мы снова в правом верхнем углу (X = 0, Y = 0).


выставить X (Y) адрес RAM - здесь мы численно указываем к какому блоку памяти перейти как указано на рис. 4.

Смещение системы - Не понял что это, в даташите сказано выставлять 0x13.

На Ебее продавец также выставил готовую библиотеку для работы с дисплеем, которую я и буду использовать, она взята с сайта http://icstation.com/

Здесь удобно сделаны define чтобы можно было выставлять или сбрасывать определённые биты.
#define F_CPU 8000000UL


#define LCD_RST_set  PORTD |=  (1<<0)    //external reset input
#define LCD_RST_clr  PORTD &=~ (1<<0)

#define LCD_DC_set   PORTD |=  (1<<1)    //data/commande
#define LCD_DC_clr   PORTD &=~ (1<<1)

#define SDIN_set     PORTD |=  (1<<2)    //serial data input
#define SDIN_clr     PORTD &=~ (1<<2)

#define SCLK_set     PORTD |=  (1<<3)    //serial clock input
#define SCLK_clr     PORTD &= ~(1<<3)

#include <avr/io.h> 
#include <util/delay.h> 
#include "english_font.h"

Запись байта, в параметры указываем байт и говорим команда это или нет. Выставляем DC если команда в противном случае заземляем.

В цикле передаём каждый бит: выставляем SDIN в соответствии с последним (крайним левым) битом нашего байта, передаём его и сдвигаем наш байт влево, теперь 6 бит крайний и повторяем процесс до тех пор пока не передадим всё.
void LCD_write_byte(unsigned char dat, unsigned char command)
{
 unsigned char i;

 if (command == 1)
 LCD_DC_clr;
 else
 LCD_DC_set;

 for(i=0;i<8;i++)
 {
  if(dat&0x80)
  SDIN_set;
  else
  SDIN_clr;
  SCLK_clr;
  dat = dat << 1;
  SCLK_set;
 }
}

Весь код
#define F_CPU 8000000UL


#define LCD_RST_set  PORTD |=  (1<<0)    //external reset input
#define LCD_RST_clr  PORTD &=~ (1<<0)

#define LCD_DC_set   PORTD |=  (1<<1)    //data/commande
#define LCD_DC_clr   PORTD &=~ (1<<1)

#define SDIN_set     PORTD |=  (1<<2)    //serial data input
#define SDIN_clr     PORTD &=~ (1<<2)

#define SCLK_set     PORTD |=  (1<<3)    //serial clock input
#define SCLK_clr     PORTD &= ~(1<<3)

#include <avr/io.h> 
#include <util/delay.h> 
#include "english_font.h"

void LCD_write_byte(unsigned char dat, unsigned char command);
void LCD_init();
void LCD_clear();
void LCD_set_XY(unsigned char X, unsigned char Y);
void LCD_write_char(unsigned char c);
void LCD_write_english_string(unsigned char X,unsigned char Y,char *s);

int main(void)
{
 DDRD = 0x0F;

 LCD_init();       //LCD initialization

 LCD_write_english_string(0,0," Hello World ! ");   
 LCD_write_english_string(0,1," bananas ");    
 LCD_write_english_string(0,2,"dancing potato");
 LCD_write_english_string(0,3," tomato");
 LCD_write_english_string(0,4,"   with love ");
 LCD_write_english_string(0,5,"   from 4a4ik ");


    while(1)
    {
    }
}

void LCD_write_byte(unsigned char dat, unsigned char command)
{
 unsigned char i;

 if (command == 1)
 LCD_DC_clr;
 else
 LCD_DC_set;

 for(i=0;i<8;i++)
 {
  if(dat&0x80)
  SDIN_set;
  else
  SDIN_clr;
  SCLK_clr;
  dat = dat << 1;
  SCLK_set;
 }
}

void LCD_init() 
{
 LCD_RST_clr;
 _delay_us(1);
 LCD_RST_set;

 _delay_us(1);

 LCD_write_byte(0x21, 1); // set LCD mode
 LCD_write_byte(0xc8, 1); // set bias voltage
 LCD_write_byte(0x06, 1); // temperature correction
 LCD_write_byte(0x13, 1); // 1:48
 LCD_write_byte(0x20, 1); // use bias command, vertical
 LCD_write_byte(0x0c, 1); // set LCD mode,display normally
 LCD_clear();             // clear the LCD
}

void LCD_clear() 
{
 unsigned int i;

 LCD_write_byte(0x0c, 1);
 LCD_write_byte(0x80, 1);

 for (i=0; i<504; i++)
 {
  LCD_write_byte(0, 0);
 }
}

void LCD_set_XY(unsigned char X, unsigned char Y)
{
 LCD_write_byte(0x40 | Y, 1); // column
 LCD_write_byte(0x80 | X, 1);    // row
}

void LCD_write_char(unsigned char c)
{
 unsigned char line;

 c -= 32;

 for (line=0; line<6; line++)
 LCD_write_byte(font6x8[c][line], 0);
}

void LCD_write_english_string(unsigned char X,unsigned char Y,char *s)
{
 LCD_set_XY(X,Y);
 while (*s)
 {
  LCD_write_char(*s);
  s++;
 }
}


Github

Также есть удобная прога для того чтобы отображать изображения на дисплее.
LCD Assistant
Но на нашем дисплее правильно отображается только горизонтальная адресация. Из-за этого я написал свою программу обработки .bmp изображений.
Bmp to nokia 5110       Github
Она из изображения создаёт текстовый файл где прописаны все байты по табличкам в 1 массиве. Если попробовать изобразить изображение Бендера (84х48).


полученный файл выглядит следующим образом.
Это изобразить можно с помощью простого цикла:
for(int n = 0; n < 504; n++)
{
    LCD_write_byte( frame_1[ n ], 0);
}


Результат


При добавлении изображения, размер прошивки увеличился на 1461 байт.

Также я решил сделать бегущую строку:


Для движения изображения, я использовал вертикальную адресацию и этот код:
    
while(1)
{
  for( int i = 83; i >= 0; i-- )
  {
    for(int n = 0; n < 504; n++)
    {
      LCD_write_byte( frame_1[ n ], 0);
    }
   
    _delay_ms(50);
    LCD_set_XY( i, 0);
  }
}



Datasheet

Спасибо сайтам:
Sparkfun
http://icstation.com/

8 комментариев :

  1. У Вас ошибка тут... Команды это 0, а информация это 1... Исходя из этого при инициализации дисплея нужно использовать параметр 0, а не 1...

    ОтветитьУдалить
  2. Скажи, а как тут кириллицу использовать?

    ОтветитьУдалить
  3. И еще вопрос: как здесь вывести строку с UART ?

    ОтветитьУдалить
  4. Нужно смещение в таблице для русских символов.Вот так работает:
    void lcd_chr(char chr)
    {
    lcd_base_addr(lcdCacheIdx);

    // 5 pixel wide characters and add space
    for(unsigned char i=0;i<5;i++)
    {

    if ( (chr >= 0x20) && (chr <= 0x7F) )

    lcd_send(pgm_read_byte(&font5x7[chr-32][i]) << 1, LCD_DATA);
    else if ( chr >= 0xC0 )
    lcd_send(pgm_read_byte(&font5x7[chr-96][i]) << 1, LCD_DATA);
    else
    {
    // Остальные игнорируем (их просто нет в таблице для экономии памяти)
    chr = 95;
    }
    }
    lcd_send(0, LCD_DATA);

    lcdCacheIdx += 6;
    }

    ОтветитьУдалить
  5. И в таблицу вписать это:
    { 0x7C, 0x12, 0x11, 0x12, 0x7C }, // А 0xC0 192
    { 0x7F, 0x49, 0x49, 0x49, 0x31 }, // Б 0xC1 193
    { 0x7F, 0x49, 0x49, 0x49, 0x36 }, // В 0xC2 194
    { 0x7F, 0x01, 0x01, 0x01, 0x01 }, // Г 0xC3 195
    { 0x60, 0x3F, 0x21, 0x3F, 0x60 }, // Д 0xC4 196
    { 0x7F, 0x49, 0x49, 0x49, 0x41 }, // Е 0xC5 197
    { 0x77, 0x08, 0x7F, 0x08, 0x77 }, // Ж 0xC6 198
    { 0x22, 0x41, 0x49, 0x49, 0x36 }, // З 0xC7 199
    { 0x7F, 0x10, 0x08, 0x04, 0x7F }, // И 0xC8 200
    { 0x7E, 0x10, 0x09, 0x04, 0x7E }, // Й 0xC9 201
    { 0x7F, 0x08, 0x14, 0x22, 0x41 }, // К 0xCA 202
    { 0x40, 0x3E, 0x01, 0x01, 0x7F }, // Л 0xCB 203
    { 0x7F, 0x02, 0x0C, 0x02, 0x7F }, // М 0xCC 204
    { 0x7F, 0x08, 0x08, 0x08, 0x7F }, // Н 0xCD 205
    { 0x3E, 0x41, 0x41, 0x41, 0x3E }, // О 0xCE 206
    { 0x7F, 0x01, 0x01, 0x01, 0x7F }, // П 0xCF 207
    { 0x7F, 0x09, 0x09, 0x09, 0x06 }, // Р 0xD0 208
    { 0x3E, 0x41, 0x41, 0x41, 0x22 }, // С 0xD1 209
    { 0x01, 0x01, 0x7F, 0x01, 0x01 }, // Т 0xD2 210
    { 0x07, 0x48, 0x48, 0x48, 0x3F }, // У 0xD3 211
    { 0x0E, 0x11, 0x7F, 0x11, 0x0E }, // Ф 0xD4 212
    { 0x63, 0x14, 0x08, 0x14, 0x63 }, // Х 0xD5 213
    { 0x3F, 0x20, 0x20, 0x3F, 0x60 }, // Ц 0xD6 214
    { 0x07, 0x08, 0x08, 0x08, 0x7F }, // Ч 0xD7 215
    { 0x7F, 0x40, 0x7E, 0x40, 0x7F }, // Ш 0xD8 216
    { 0x3F, 0x20, 0x3F, 0x20, 0x7F }, // Щ 0xD9 217
    { 0x01, 0x7F, 0x48, 0x48, 0x30 }, // Ъ 0xDA 218
    { 0x7F, 0x48, 0x30, 0x00, 0x7F }, // Ы 0xDB 219
    { 0x00, 0x7F, 0x48, 0x48, 0x30 }, // Ь 0xDC 220
    { 0x22, 0x41, 0x49, 0x49, 0x3E }, // Э 0xDD 221
    { 0x7F, 0x08, 0x3E, 0x41, 0x3E }, // Ю 0xDE 222
    { 0x46, 0x29, 0x19, 0x09, 0x7F }, // Я 0xDF 223
    { 0x20, 0x54, 0x54, 0x54, 0x78 }, // а 0xE0 224
    { 0x3C, 0x4A, 0x4A, 0x4A, 0x31 }, // б 0xE1 225
    { 0x7C, 0x54, 0x54, 0x28, 0x00 }, // в 0xE2 226
    { 0x7C, 0x04, 0x04, 0x0C, 0x00 }, // г 0xE3 227
    { 0x60, 0x3C, 0x24, 0x3C, 0x60 }, // д 0xE4 228
    { 0x38, 0x54, 0x54, 0x54, 0x18 }, // е 0xE5 229
    { 0x6C, 0x10, 0x7C, 0x10, 0x6C }, // ж 0xE6 230
    { 0x00, 0x44, 0x54, 0x54, 0x28 }, // з 0xE7 231
    { 0x7C, 0x20, 0x10, 0x08, 0x7C }, // и 0xE8 232
    { 0x7C, 0x21, 0x12, 0x09, 0x7C }, // й 0xE9 233
    { 0x7C, 0x10, 0x28, 0x44, 0x00 }, // к 0xEA 234
    { 0x40, 0x38, 0x04, 0x04, 0x7C }, // л 0xEB 235
    { 0x7C, 0x08, 0x10, 0x08, 0x7C }, // м 0xEC 236
    { 0x7C, 0x10, 0x10, 0x10, 0x7C }, // н 0xED 237
    { 0x38, 0x44, 0x44, 0x44, 0x38 }, // о 0xEE 238
    { 0x7C, 0x04, 0x04, 0x04, 0x7C }, // п 0xEF 239
    { 0x7C, 0x14, 0x14, 0x14, 0x08 }, // р 0xF0 240
    { 0x38, 0x44, 0x44, 0x44, 0x00 }, // с 0xF1 241
    { 0x04, 0x04, 0x7C, 0x04, 0x04 }, // т 0xF2 242
    { 0x0C, 0x50, 0x50, 0x50, 0x3C }, // у 0xF3 243
    { 0x08, 0x14, 0x7C, 0x14, 0x08 }, // ф 0xF4 244
    { 0x44, 0x28, 0x10, 0x28, 0x44 }, // х 0xF5 245
    { 0x3C, 0x20, 0x20, 0x3C, 0x60 }, // ц 0xF6 246
    { 0x0C, 0x10, 0x10, 0x10, 0x7C }, // ч 0xF7 247
    { 0x7C, 0x40, 0x7C, 0x40, 0x7C }, // ш 0xF8 248
    { 0x3C, 0x20, 0x3C, 0x20, 0x7C }, // щ 0xF9 249
    { 0x04, 0x7C, 0x50, 0x50, 0x20 }, // ъ 0xFA 250
    { 0x7C, 0x50, 0x20, 0x00, 0x7C }, // ы 0xFB 251
    { 0x00, 0x7C, 0x50, 0x50, 0x20 }, // ь 0xFC 252
    { 0x28, 0x44, 0x54, 0x54, 0x38 }, // э 0xFD 253
    { 0x7C, 0x10, 0x38, 0x44, 0x38 }, // ю 0xFE 254
    { 0x48, 0x54, 0x34, 0x14, 0x7C } // я 0xFF 255

    ОтветитьУдалить
  6. Код актуален и работоспособен .Автору большое спасибо за простоту и не нагроможденность

    ОтветитьУдалить