mmote.ru/content/posts/avr-siemens-lcd/index.md

1204 lines
45 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: "Подключение дисплея от Siemens C55/A55/A52 к AVR"
categories: ["mcu", "archive"]
date: 2016-02-19T00:00:00+03:00
draft: false
featured_image: miniature.jpg
---
Вот на днях решил порыться в своих запасах запчастей от мобильных телефонах. И заметил дисплей с маркировкой LPH8694-3. Погуглив, я выяснил что это дисплей от телефонов Siemens C55/A55/A52. Ну что ж добру пропадать, будем подключать.
<!--more-->
> :hint: Статья не закончена.
> Когда-нибудь она будет доработана.
![Маркировка экрана|400](lcd.jpg)
Экран имеет весьма странное разрешение экрана 101 на 64 точки (в [даташите](PCF8812.pdf) вообще написано, что 102x64). Это намного больше, чем у дисплея, которым я обычно пользуюсь (у nokia 5110 64 на 48 точек).
За основу я решил использовать [библиотеку](https://github.com/gresolio/N3310Lib) уважаемого товарища [gresolio](https://github.com/gresolio). Дисплеи очень похожи, но немного отличается инициализация. Да и доработать библиотеку под новое разрешение экрана тоже нужно.
![Распиновка дисплея (вид со стороны контактов)|338](lph8684.png)
Главное отличие в инициализации, без чего дисплей не запустится вообще способ питания.
Нужно найти вот эту строку в **LcdInit**:
``` c
LcdSend( 0x13, LCD_CMD ); // Настройка питания (LCD bias mode 1:48)
```
И заменить на:
```c
LcdSend( 0x16, LCD_CMD );
```
**LcdGotoXYFont**, до:
```c
byte LcdGotoXYFont ( byte x, byte y )
{
// Проверка границ
if( x > 13 || y > 5 ) return OUT_OF_BORDER;
// Вычисление указателя. Определен как адрес в пределах 504 байт
LcdCacheIdx = x * 6 + y * 84;
return OK;
}
```
После:
```c
byte LcdGotoXYFont ( byte x, byte y )
{
// Проверка границ
if( x > LCD_COLS || y > LCD_LINES ) return OUT_OF_BORDER;
// Вычисление указателя. Определен как адрес в пределах 504 байт
LcdCacheIdx = x * LCD_LINES + y * LCD_X_RES;
return OK;
}
```
**LcdChr**, до:
```c
else if ( size == FONT_2X )
{
tmpIdx = LcdCacheIdx - 84;
```
После:
```c
else if ( size == FONT_2X )
{
tmpIdx = LcdCacheIdx - LCD_X_RES;
```
До:
```c
// Копируем две части в кэш
LcdCache[tmpIdx++] = b1;
LcdCache[tmpIdx++] = b1;
LcdCache[tmpIdx + 82] = b2;
LcdCache[tmpIdx + 83] = b2;
```
После:
```c
LcdCache[tmpIdx++] = b1;
LcdCache[tmpIdx++] = b1;
LcdCache[tmpIdx + LCD_X_RES - 2] = b2;
LcdCache[tmpIdx + LCD_X_RES - 1] = b2;
```
**LcdPixel**, до:
```c
// Пересчет индекса и смещения
index = ( ( y / 8 ) * 84 ) + x;
offset = y - ( ( y / 8 ) * 8 );
```
После
```c
// Пересчет индекса и смещения
index = ( ( y / LCD_LINES ) * LCD_X_RES ) + x;
offset = y - ( ( y / 8) * LCD_LINES );
```
**n3310.h**, до:
```c
// Разрешение дисплея в пикселях
#define LCD_X_RES 84 // разрешение по горизонтали
#define LCD_Y_RES 48 // разрешение по вертикали
```
После:
```c
// Разрешение дисплея в пикселях
#define LCD_X_RES 102 // разрешение по горизонтали
#define LCD_Y_RES 65 // разрешение по вертикали
#define LCD_LINES (LCD_Y_RES / 8) // количество строк
#define LCD_COLS (LCD_X_RES / 5) // количество столбцов
```
Возможно, это не всё.
Вот то, что получилось:
sc55.h:
```c
/*
* Имя : n3310.h
*
* Описание : Это заголовочный файл для драйвера графического LCD от Nokia 3310, а также его китайских клонов.
* Базируется на коде библиотек написанных Sylvain Bissonnette и Fandi Gunawan:
* http://www.microsyl.com/index.php/2010/03/24/nokia-lcd-library/
* http://fandigunawan.wordpress.com/2008/06/18/lcd-nokia-3310-pcd8544-driver-in-winavravr-gcc/
* Основные отличия между оригиналом и клоном хорошо описаны в статье от Aheir:
* http://radiokot.ru/articles/29/
*
* Автор : Xander Gresolio <xugres@gmail.com>
* Веб-страница : http://we.easyelectronics.ru/profile/XANDER/
*
* Лицензия : GPL v3.0
*
* Компилятор : WinAVR, GCC for AVR platform
*/
#ifndef _SC55_H_
#define _SC55_H_
#include <avr/pgmspace.h>
// Порт к которому подключен LCD (здесь пример распиновки для ATmega8)
#define LCD_PORT PORTB
#define LCD_DDR DDRB
// Распиновка порта
#define LCD_DC_PIN PB1
#define LCD_CE_PIN PB2
#define LCD_DATA_PIN PB3
#define LCD_RST_PIN PB4
#define LCD_CLK_PIN PB5
// Разрешение дисплея в пикселях
#define LCD_X_RES 102 // разрешение по горизонтали
#define LCD_Y_RES 65 // разрешение по вертикали
#define LCD_LINES (LCD_Y_RES / 8) // количество строк
#define LCD_COLS (LCD_X_RES / 5) // количество столбцов
// Настройки для рисования группы прямоугольников функцией LcdBars ( byte data[], byte numbBars, byte width, byte multiplier )
#define EMPTY_SPACE_BARS 2 // расстояние между прямоугольниками
#define BAR_X 30 // координата x
#define BAR_Y 47 // координата y
// Размер кэша ( 84 * 48 ) / 8 = 504 байта
#define LCD_CACHE_SIZE ( ( LCD_X_RES * LCD_Y_RES ) / 8 )
#define FALSE 0
#define TRUE 1
// Для возвращаемых значений
#define OK 0 // Безошибочная отрисовка
#define OUT_OF_BORDER 1 // Выход за границы дисплея
#define OK_WITH_WRAP 2 // Переход на начало (ситуация автоинкремента указателя курсора при выводе длинного текста)
typedef unsigned char byte;
// Перечисления
typedef enum
{
LCD_CMD = 0, // Команда
LCD_DATA = 1 // Данные
} LcdCmdData;
typedef enum
{
PIXEL_OFF = 0, // Погасить пиксели дисплея
PIXEL_ON = 1, // Включить пиксели дисплея
PIXEL_XOR = 2 // Инвертировать пиксели
} LcdPixelMode;
typedef enum
{
FONT_1X = 1, // Обычный размер шрифта 5x7
FONT_2X = 2 // Увеличенный размер шрифта
} LcdFontSize;
// Прототипы функций, детальную информацию смотрим внутри n3310lcd.c
void LcdInit ( void ); // Инициализация
void LcdClear ( void ); // Очистка буфера
void LcdUpdate ( void ); // Копирование буфера в ОЗУ дисплея
void LcdImage ( const byte *imageData ); // Рисование картинки из массива в Flash ROM
void LcdContrast ( byte contrast ); // Установка контрастности дисплея
byte LcdGotoXYFont ( byte x, byte y ); // Установка курсора в позицию x,y
byte LcdChr ( LcdFontSize size, byte ch ); // Вывод символа в текущей позиции
byte LcdStr ( LcdFontSize size, byte dataArray[] ); // Вывод строки сохраненной в RAM
byte LcdFStr ( LcdFontSize size, const byte *dataPtr ); // Вывод строки сохраненной в Flash ROM
byte LcdPixel ( byte x, byte y, LcdPixelMode mode ); // Точка
byte LcdLine ( byte x1, byte y1, byte x2, byte y2, LcdPixelMode mode ); // Линия
byte LcdCircle ( byte x, byte y, byte radius, LcdPixelMode mode); // Окружность
byte LcdRect ( byte x1, byte y1, byte x2, byte y2, LcdPixelMode mode ); // Прямоугольник
byte LcdSingleBar ( byte baseX, byte baseY, byte height, byte width, LcdPixelMode mode ); // Один
byte LcdBars ( byte data[], byte numbBars, byte width, byte multiplier ); // Несколько
/*
* Таблица для отображения символов (ASCII[0x20-0x7F] + CP1251[0xC0-0xFF] = всего 160 символов)
*/
static const byte FontLookup [][5] PROGMEM=
{
{ 0x00, 0x00, 0x00, 0x00, 0x00 }, // 0x20 32
{ 0x00, 0x00, 0x5F, 0x00, 0x00 }, // ! 0x21 33
{ 0x00, 0x07, 0x00, 0x07, 0x00 }, // " 0x22 34
{ 0x14, 0x7F, 0x14, 0x7F, 0x14 }, // # 0x23 35
{ 0x24, 0x2A, 0x7F, 0x2A, 0x12 }, // $ 0x24 36
{ 0x4C, 0x2C, 0x10, 0x68, 0x64 }, // % 0x25 37
{ 0x36, 0x49, 0x55, 0x22, 0x50 }, // & 0x26 38
{ 0x00, 0x05, 0x03, 0x00, 0x00 }, // ' 0x27 39
{ 0x00, 0x1C, 0x22, 0x41, 0x00 }, // ( 0x28 40
{ 0x00, 0x41, 0x22, 0x1C, 0x00 }, // ) 0x29 41
{ 0x14, 0x08, 0x3E, 0x08, 0x14 }, // * 0x2A 42
{ 0x08, 0x08, 0x3E, 0x08, 0x08 }, // + 0x2B 43
{ 0x00, 0x00, 0x50, 0x30, 0x00 }, // , 0x2C 44
{ 0x10, 0x10, 0x10, 0x10, 0x10 }, // - 0x2D 45
{ 0x00, 0x60, 0x60, 0x00, 0x00 }, // . 0x2E 46
{ 0x20, 0x10, 0x08, 0x04, 0x02 }, // / 0x2F 47
{ 0x3E, 0x51, 0x49, 0x45, 0x3E }, // 0 0x30 48
{ 0x00, 0x42, 0x7F, 0x40, 0x00 }, // 1 0x31 49
{ 0x42, 0x61, 0x51, 0x49, 0x46 }, // 2 0x32 50
{ 0x21, 0x41, 0x45, 0x4B, 0x31 }, // 3 0x33 51
{ 0x18, 0x14, 0x12, 0x7F, 0x10 }, // 4 0x34 52
{ 0x27, 0x45, 0x45, 0x45, 0x39 }, // 5 0x35 53
{ 0x3C, 0x4A, 0x49, 0x49, 0x30 }, // 6 0x36 54
{ 0x01, 0x71, 0x09, 0x05, 0x03 }, // 7 0x37 55
{ 0x36, 0x49, 0x49, 0x49, 0x36 }, // 8 0x38 56
{ 0x06, 0x49, 0x49, 0x29, 0x1E }, // 9 0x39 57
{ 0x00, 0x36, 0x36, 0x00, 0x00 }, // : 0x3A 58
{ 0x00, 0x56, 0x36, 0x00, 0x00 }, // ; 0x3B 59
{ 0x08, 0x14, 0x22, 0x41, 0x00 }, // < 0x3C 60
{ 0x14, 0x14, 0x14, 0x14, 0x14 }, // = 0x3D 61
{ 0x00, 0x41, 0x22, 0x14, 0x08 }, // > 0x3E 62
{ 0x02, 0x01, 0x51, 0x09, 0x06 }, // ? 0x3F 63
{ 0x32, 0x49, 0x79, 0x41, 0x3E }, // @ 0x40 64
{ 0x7E, 0x11, 0x11, 0x11, 0x7E }, // A 0x41 65
{ 0x7F, 0x49, 0x49, 0x49, 0x36 }, // B 0x42 66
{ 0x3E, 0x41, 0x41, 0x41, 0x22 }, // C 0x43 67
{ 0x7F, 0x41, 0x41, 0x22, 0x1C }, // D 0x44 68
{ 0x7F, 0x49, 0x49, 0x49, 0x41 }, // E 0x45 69
{ 0x7F, 0x09, 0x09, 0x09, 0x01 }, // F 0x46 70
{ 0x3E, 0x41, 0x49, 0x49, 0x7A }, // G 0x47 71
{ 0x7F, 0x08, 0x08, 0x08, 0x7F }, // H 0x48 72
{ 0x00, 0x41, 0x7F, 0x41, 0x00 }, // I 0x49 73
{ 0x20, 0x40, 0x41, 0x3F, 0x01 }, // J 0x4A 74
{ 0x7F, 0x08, 0x14, 0x22, 0x41 }, // K 0x4B 75
{ 0x7F, 0x40, 0x40, 0x40, 0x40 }, // L 0x4C 76
{ 0x7F, 0x02, 0x0C, 0x02, 0x7F }, // M 0x4D 77
{ 0x7F, 0x04, 0x08, 0x10, 0x7F }, // N 0x4E 78
{ 0x3E, 0x41, 0x41, 0x41, 0x3E }, // O 0x4F 79
{ 0x7F, 0x09, 0x09, 0x09, 0x06 }, // P 0x50 80
{ 0x3E, 0x41, 0x51, 0x21, 0x5E }, // Q 0x51 81
{ 0x7F, 0x09, 0x19, 0x29, 0x46 }, // R 0x52 82
{ 0x46, 0x49, 0x49, 0x49, 0x31 }, // S 0x53 83
{ 0x01, 0x01, 0x7F, 0x01, 0x01 }, // T 0x54 84
{ 0x3F, 0x40, 0x40, 0x40, 0x3F }, // U 0x55 85
{ 0x1F, 0x20, 0x40, 0x20, 0x1F }, // V 0x56 86
{ 0x3F, 0x40, 0x38, 0x40, 0x3F }, // W 0x57 87
{ 0x63, 0x14, 0x08, 0x14, 0x63 }, // X 0x58 88
{ 0x07, 0x08, 0x70, 0x08, 0x07 }, // Y 0x59 89
{ 0x61, 0x51, 0x49, 0x45, 0x43 }, // Z 0x5A 90
{ 0x00, 0x7F, 0x41, 0x41, 0x00 }, // [ 0x5B 91
{ 0x02, 0x04, 0x08, 0x10, 0x20 }, // \ 0x5C 92
{ 0x00, 0x41, 0x41, 0x7F, 0x00 }, // ] 0x5D 93
{ 0x04, 0x02, 0x01, 0x02, 0x04 }, // ^ 0x5E 94
{ 0x40, 0x40, 0x40, 0x40, 0x40 }, // _ 0x5F 95
{ 0x00, 0x01, 0x02, 0x04, 0x00 }, // ` 0x60 96
{ 0x20, 0x54, 0x54, 0x54, 0x78 }, // a 0x61 97
{ 0x7F, 0x48, 0x44, 0x44, 0x38 }, // b 0x62 98
{ 0x38, 0x44, 0x44, 0x44, 0x20 }, // c 0x63 99
{ 0x38, 0x44, 0x44, 0x48, 0x7F }, // d 0x64 100
{ 0x38, 0x54, 0x54, 0x54, 0x18 }, // e 0x65 101
{ 0x08, 0x7E, 0x09, 0x01, 0x02 }, // f 0x66 102
{ 0x0C, 0x52, 0x52, 0x52, 0x3E }, // g 0x67 103
{ 0x7F, 0x08, 0x04, 0x04, 0x78 }, // h 0x68 104
{ 0x00, 0x44, 0x7D, 0x40, 0x00 }, // i 0x69 105
{ 0x20, 0x40, 0x44, 0x3D, 0x00 }, // j 0x6A 106
{ 0x7F, 0x10, 0x28, 0x44, 0x00 }, // k 0x6B 107
{ 0x00, 0x41, 0x7F, 0x40, 0x00 }, // l 0x6C 108
{ 0x7C, 0x04, 0x18, 0x04, 0x78 }, // m 0x6D 109
{ 0x7C, 0x08, 0x04, 0x04, 0x78 }, // n 0x6E 110
{ 0x38, 0x44, 0x44, 0x44, 0x38 }, // o 0x6F 111
{ 0x7C, 0x14, 0x14, 0x14, 0x08 }, // p 0x70 112
{ 0x08, 0x14, 0x14, 0x18, 0x7C }, // q 0x71 113
{ 0x7C, 0x08, 0x04, 0x04, 0x08 }, // r 0x72 114
{ 0x48, 0x54, 0x54, 0x54, 0x20 }, // s 0x73 115
{ 0x04, 0x3F, 0x44, 0x40, 0x20 }, // t 0x74 116
{ 0x3C, 0x40, 0x40, 0x20, 0x7C }, // u 0x75 117
{ 0x1C, 0x20, 0x40, 0x20, 0x1C }, // v 0x76 118
{ 0x3C, 0x40, 0x30, 0x40, 0x3C }, // w 0x77 119
{ 0x44, 0x28, 0x10, 0x28, 0x44 }, // x 0x78 120
{ 0x0C, 0x50, 0x50, 0x50, 0x3C }, // y 0x79 121
{ 0x44, 0x64, 0x54, 0x4C, 0x44 }, // z 0x7A 122
{ 0x00, 0x08, 0x36, 0x41, 0x00 }, // { 0x7B 123
{ 0x00, 0x00, 0x7F, 0x00, 0x00 }, // | 0x7C 124
{ 0x00, 0x41, 0x36, 0x08, 0x00 }, // } 0x7D 125
{ 0x08, 0x04, 0x08, 0x10, 0x08 }, // ~ 0x7E 126
{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, //  0x7F 127
{ 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
};
#endif /* _N3310_H_ */
```
sc55.c:
```c
/*
* Имя : n3310.c
*
* Описание : Это драйвер для графического LCD от Nokia 3310, а также его китайских клонов.
* Базируется на коде библиотек написанных Sylvain Bissonnette и Fandi Gunawan:
* http://www.microsyl.com/index.php/2010/03/24/nokia-lcd-library/
* http://fandigunawan.wordpress.com/2008/06/18/lcd-nokia-3310-pcd8544-driver-in-winavravr-gcc/
* Основные отличия между оригиналом и клоном хорошо описаны в статье от Aheir:
* http://radiokot.ru/articles/29/
*
* Автор : Xander Gresolio <xugres@gmail.com>
* Веб-страница : http://we.easyelectronics.ru/profile/XANDER/
*
* Лицензия : GPL v3.0
*
* Компилятор : WinAVR, GCC for AVR platform
*
* История :
* Версия 1.0 (06.08.2011)
* + Первая версия
* + Добавлена поддержка китайских клонов LCD Nokia 3310
* + Полный перевод комментариев к исходному коду драйвера
* + Таблица символов драйвера дополнена кириллицей (упрощенная Windows-1251)
* + Добавлена функция рисования окружностей LcdCircle
* - Исправлены ошибки в проверке корректности координат при вызове функций рисования
* - Исправлена ошибка в функции LcdSingleBar (неверная отрисовка по y)
*/
#include <avr/io.h>
#include <string.h>
#include "sc55.h"
// Прототипы приватных функций драйвера
static void LcdSend ( byte data, LcdCmdData cd );
static void Delay ( void );
// Глобальные переменные
// Кэш в ОЗУ 84*48 бит или 504 байта
static byte LcdCache [ LCD_CACHE_SIZE ];
// Чтобы не обновлять весь дисплей, а лишь ту часть что изменилась,
// будем отмечать две границы кэша где произошли изменения. Затем
// можно копировать эту часть кэша между границами в ОЗУ дисплея.
static int LoWaterMark; // нижняя граница
static int HiWaterMark; // верхняя граница
// Указатель для работы с LcdCache[]
static int LcdCacheIdx;
// Флаг изменений кэша
static byte UpdateLcd;
/*
* Имя : LcdInit
* Описание : Производит инициализацию порта МК и контроллера LCD
* Аргумент(ы) : Нет
* Возвращаемое значение : Нет
*/
void LcdInit ( void )
{
// Pull-up на вывод подключенный к reset дисплея
LCD_PORT |= _BV ( LCD_RST_PIN );
// Устанавливаем нужные биты порта на выход
LCD_DDR |= _BV( LCD_RST_PIN ) | _BV( LCD_DC_PIN ) | _BV( LCD_CE_PIN ) | _BV(LCD_DATA_PIN) | _BV(LCD_CLK_PIN);
// Некалиброванная задержка
Delay();
// Дергаем reset
LCD_PORT &= ~( _BV( LCD_RST_PIN ) );
Delay();
LCD_PORT |= _BV ( LCD_RST_PIN );
// Отключаем LCD контроллер - высокий уровень на SCE
LCD_PORT |= _BV( LCD_CE_PIN );
// Отправляем команды дисплею
LcdSend( 0x21, LCD_CMD ); // Включаем расширенный набор команд (LCD Extended Commands)
LcdSend( 0xC8, LCD_CMD ); // Установка контрастности (LCD Vop)
LcdSend( 0x06, LCD_CMD ); // Установка температурного коэффициента (Temp coefficent)
LcdSend( 0x16, LCD_CMD ); // Настройка питания (Bias n=2), siemens
LcdSend( 0x20, LCD_CMD ); // Включаем стандартный набор команд и горизонтальную адресацию (LCD Standard Commands,Horizontal addressing mode)
LcdSend( 0x0C, LCD_CMD ); // Нормальный режим (LCD in normal mode)
// Первичная очистка дисплея
LcdClear();
LcdUpdate();
}
/*
* Имя : LcdClear
* Описание : Очищает дисплей. Далее необходимо выполнить LcdUpdate
* Аргумент(ы) : Нет
* Возвращаемое значение : Нет
*/
void LcdClear ( void )
{
// // Очистка кэша дисплея
// int i;
// for ( i = 0; i < LCD_CACHE_SIZE; i++ )
// {
// LcdCache[i] = 0x00;
// }
// Оптимизация от Jakub Lasinski (March 14 2009)
memset( LcdCache, 0x00, LCD_CACHE_SIZE );
// Сброс указателей границ в максимальное значение
LoWaterMark = 0;
HiWaterMark = LCD_CACHE_SIZE - 1;
// Установка флага изменений кэша
UpdateLcd = TRUE;
}
/*
* Имя : LcdUpdate
* Описание : Копирует кэш в ОЗУ дисплея
* Аргумент(ы) : Нет
* Возвращаемое значение : Нет
*/
void LcdUpdate (void)
{
int i;
if ( LoWaterMark < 0 )
LoWaterMark = 0;
else if ( LoWaterMark >= LCD_CACHE_SIZE )
LoWaterMark = LCD_CACHE_SIZE - 1;
if ( HiWaterMark < 0 )
HiWaterMark = 0;
else if ( HiWaterMark >= LCD_CACHE_SIZE )
HiWaterMark = LCD_CACHE_SIZE - 1;
// Устанавливаем начальный адрес в соответствии к LoWaterMark
LcdSend( 0x80 | ( LoWaterMark % LCD_X_RES ), LCD_CMD );
LcdSend( 0x40 | ( LoWaterMark / LCD_X_RES ), LCD_CMD );
// Обновляем необходимую часть буфера дисплея
for ( i = LoWaterMark; i <= HiWaterMark; i++ )
{
// Для оригинального дисплея не нужно следить за адресом в буфере,
// можно просто последовательно выводить данные
LcdSend( LcdCache[i], LCD_DATA );
}
// Сброс указателей границ в пустоту
LoWaterMark = LCD_CACHE_SIZE - 1;
HiWaterMark = 0;
// Сброс флага изменений кэша
UpdateLcd = FALSE;
}
/*
* Имя : LcdSend
* Описание : Отправляет данные в контроллер дисплея
* Аргумент(ы) : data -> данные для отправки
* cd -> команда или данные (смотри enum в n3310.h)
* Возвращаемое значение : Нет
*/
static void LcdSend ( byte data, LcdCmdData cd )
{
// Включаем контроллер дисплея (низкий уровень активный)
LCD_PORT &= ~( _BV( LCD_CE_PIN ) );
byte i;
if (cd == LCD_DATA)
LCD_PORT |= _BV(LCD_DC_PIN);
else
LCD_PORT &= ~_BV(LCD_DC_PIN);
for (i = 0; i < 8; i++) {
if ((data >> (7 - i)) & 1) {
LCD_PORT |= _BV(LCD_DATA_PIN);
} else {
LCD_PORT &= ~_BV(LCD_DATA_PIN);
}
LCD_PORT |= _BV(LCD_CLK_PIN);
LCD_PORT &= ~_BV(LCD_CLK_PIN);
}
LCD_PORT |= _BV(LCD_DATA_PIN);
LCD_PORT |= _BV(LCD_DC_PIN);
// Отключаем контроллер дисплея
LCD_PORT |= _BV( LCD_CE_PIN );
}
/*
* Имя : LcdContrast
* Описание : Устанавливает контрастность дисплея
* Аргумент(ы) : контраст -> значение от 0x00 к 0x7F
* Возвращаемое значение : Нет
*/
void LcdContrast ( byte contrast )
{
LcdSend( 0x21, LCD_CMD ); // Расширенный набор команд
LcdSend( 0x80 | contrast, LCD_CMD ); // Установка уровня контрастности
LcdSend( 0x20, LCD_CMD ); // Стандартный набор команд, горизонтальная адресация
}
/*
* Имя : Delay
* Описание : Некалиброванная задержка для процедуры инициализации LCD
* Аргумент(ы) : Нет
* Возвращаемое значение : Нет
*/
static void Delay ( void )
{
int i;
for ( i = -32000; i < 32000; i++ );
}
/*
* Имя : LcdGotoXYFont
* Описание : Устанавливает курсор в позицию x,y относительно стандартного размера шрифта
* Аргумент(ы) : x,y -> координаты новой позиции курсора. Значения: 0,0 .. 13,5
* Возвращаемое значение : смотри возвращаемое значение в n3310.h
*/
byte LcdGotoXYFont ( byte x, byte y )
{
// Проверка границ
if( x > LCD_COLS || y > LCD_LINES ) return OUT_OF_BORDER;
// Вычисление указателя. Определен как адрес в пределах 504 байт
LcdCacheIdx = x * LCD_LINES + y * LCD_X_RES;
return OK;
}
/*
* Имя : LcdChr
* Описание : Выводит символ в текущей позиции курсора, затем инкрементирует положение курсора
* Аргумент(ы) : size -> размер шрифта. Смотри enum в n3310.h
* ch -> символ для вывода
* Возвращаемое значение : смотри возвращаемое значение в n3310lcd.h
*/
byte LcdChr ( LcdFontSize size, byte ch )
{
byte i, c;
byte b1, b2;
int tmpIdx;
if ( LcdCacheIdx < LoWaterMark )
{
// Обновляем нижнюю границу
LoWaterMark = LcdCacheIdx;
}
if ( (ch >= 0x20) && (ch <= 0x7F) )
{
// Смещение в таблице для символов ASCII[0x20-0x7F]
ch -= 32;
}
else if ( ch >= 0xC0 )
{
// Смещение в таблице для символов CP1251[0xC0-0xFF]
ch -= 96;
}
else
{
// Остальные игнорируем (их просто нет в таблице для экономии памяти)
ch = 95;
}
if ( size == FONT_1X )
{
for ( i = 0; i < 5; i++ )
{
// Копируем вид символа из таблицы в кэш
LcdCache[LcdCacheIdx++] = pgm_read_byte( &(FontLookup[ch][i]) ) << 1;
}
}
else if ( size == FONT_2X )
{
tmpIdx = LcdCacheIdx - LCD_X_RES;
if ( tmpIdx < LoWaterMark )
{
LoWaterMark = tmpIdx;
}
if ( tmpIdx < 0 ) return OUT_OF_BORDER;
for ( i = 0; i < 5; i++ )
{
// Копируем вид символа из таблицы у временную переменную
c = pgm_read_byte(&(FontLookup[ch][i])) << 1;
// Увеличиваем картинку
// Первую часть
b1 = (c & 0x01) * 3;
b1 |= (c & 0x02) * 6;
b1 |= (c & 0x04) * 12;
b1 |= (c & 0x08) * 24;
c >>= 4;
// Вторую часть
b2 = (c & 0x01) * 3;
b2 |= (c & 0x02) * 6;
b2 |= (c & 0x04) * 12;
b2 |= (c & 0x08) * 24;
// Копируем две части в кэш
LcdCache[tmpIdx++] = b1;
LcdCache[tmpIdx++] = b1;
LcdCache[tmpIdx + LCD_X_RES - 2] = b2;
LcdCache[tmpIdx + LCD_X_RES - 1] = b2;
}
// Обновляем x координату курсора
LcdCacheIdx = (LcdCacheIdx + 11) % LCD_CACHE_SIZE;
}
if ( LcdCacheIdx > HiWaterMark )
{
// Обновляем верхнюю границу
HiWaterMark = LcdCacheIdx;
}
// Горизонтальный разрыв между символами
LcdCache[LcdCacheIdx] = 0x00;
// Если достигли позицию указателя LCD_CACHE_SIZE - 1, переходим в начало
if(LcdCacheIdx == (LCD_CACHE_SIZE - 1) )
{
LcdCacheIdx = 0;
return OK_WITH_WRAP;
}
// Иначе просто инкрементируем указатель
LcdCacheIdx++;
return OK;
}
/*
* Имя : LcdStr
* Описание : Эта функция предназначена для печати строки которая хранится в RAM
* Аргумент(ы) : size -> размер шрифта. Смотри enum в n3310.h
* dataArray -> массив содержащий строку которую нужно напечатать
* Возвращаемое значение : смотри возвращаемое значение в n3310lcd.h
*/
byte LcdStr ( LcdFontSize size, byte dataArray[] )
{
byte tmpIdx=0;
byte response;
while( dataArray[ tmpIdx ] != '\0' )
{
// Выводим символ
response = LcdChr( size, dataArray[ tmpIdx ] );
// Не стоит волноваться если произойдет OUT_OF_BORDER,
// строка будет печататься дальше из начала дисплея
if( response == OUT_OF_BORDER)
return OUT_OF_BORDER;
// Увеличиваем указатель
tmpIdx++;
}
return OK;
}
/*
* Имя : LcdFStr
* Описание : Эта функция предназначена для печати строки которая хранится в Flash ROM
* Аргумент(ы) : size -> размер шрифта. Смотри enum в n3310.h
* dataPtr -> указатель на строку которую нужно напечатать
* Возвращаемое значение : смотри возвращаемое значение в n3310lcd.h
* Пример : LcdFStr(FONT_1X, PSTR("Hello World"));
* LcdFStr(FONT_1X, &name_of_string_as_array);
*/
byte LcdFStr ( LcdFontSize size, const byte *dataPtr )
{
byte c;
byte response;
for ( c = pgm_read_byte( dataPtr ); c; ++dataPtr, c = pgm_read_byte( dataPtr ) )
{
// Выводим символ
response = LcdChr( size, c );
if(response == OUT_OF_BORDER)
return OUT_OF_BORDER;
}
return OK;
}
/*
* Имя : LcdPixel
* Описание : Отображает пиксель по абсолютным координатам (x,y)
* Аргумент(ы) : x,y -> абсолютные координаты пикселя
* mode -> Off, On или Xor. Смотри enum в n3310.h
* Возвращаемое значение : смотри возвращаемое значение в n3310lcd.h
*/
byte LcdPixel ( byte x, byte y, LcdPixelMode mode )
{
int index;
byte offset;
byte data;
// Защита от выхода за пределы
if ( x >= LCD_X_RES || y >= LCD_Y_RES) return OUT_OF_BORDER;
// Пересчет индекса и смещения
index = ( ( y / LCD_LINES ) * LCD_X_RES ) + x;
offset = y - ( ( y / 8) * LCD_LINES );
data = LcdCache[ index ];
// Обработка битов
// Режим PIXEL_OFF
if ( mode == PIXEL_OFF )
{
data &= ( ~( 0x01 << offset ) );
}
// Режим PIXEL_ON
else if ( mode == PIXEL_ON )
{
data |= ( 0x01 << offset );
}
// Режим PIXEL_XOR
else if ( mode == PIXEL_XOR )
{
data ^= ( 0x01 << offset );
}
// Окончательный результат копируем в кэш
LcdCache[ index ] = data;
if ( index < LoWaterMark )
{
// Обновляем нижнюю границу
LoWaterMark = index;
}
if ( index > HiWaterMark )
{
// Обновляем верхнюю границу
HiWaterMark = index;
}
return OK;
}
/*
* Имя : LcdLine
* Описание : Рисует линию между двумя точками на дисплее (алгоритм Брезенхэма)
* Аргумент(ы) : x1, y1 -> абсолютные координаты начала линии
* x2, y2 -> абсолютные координаты конца линии
* mode -> Off, On или Xor. Смотри enum в n3310.h
* Возвращаемое значение : смотри возвращаемое значение в n3310lcd.h
*/
byte LcdLine ( byte x1, byte y1, byte x2, byte y2, LcdPixelMode mode )
{
int dx, dy, stepx, stepy, fraction;
byte response;
// dy y2 - y1
// -- = -------
// dx x2 - x1
dy = y2 - y1;
dx = x2 - x1;
// dy отрицательное
if ( dy < 0 )
{
dy = -dy;
stepy = -1;
}
else
{
stepy = 1;
}
// dx отрицательное
if ( dx < 0 )
{
dx = -dx;
stepx = -1;
}
else
{
stepx = 1;
}
dx <<= 1;
dy <<= 1;
// Рисуем начальную точку
response = LcdPixel( x1, y1, mode );
if(response)
return response;
// Рисуем следующие точки до конца
if ( dx > dy )
{
fraction = dy - ( dx >> 1);
while ( x1 != x2 )
{
if ( fraction >= 0 )
{
y1 += stepy;
fraction -= dx;
}
x1 += stepx;
fraction += dy;
response = LcdPixel( x1, y1, mode );
if(response)
return response;
}
}
else
{
fraction = dx - ( dy >> 1);
while ( y1 != y2 )
{
if ( fraction >= 0 )
{
x1 += stepx;
fraction -= dy;
}
y1 += stepy;
fraction += dx;
response = LcdPixel( x1, y1, mode );
if(response)
return response;
}
}
// Установка флага изменений кэша
UpdateLcd = TRUE;
return OK;
}
/*
* Имя : LcdCircle
* Описание : Рисует окружность (алгоритм Брезенхэма)
* Аргумент(ы) : x, y -> абсолютные координаты центра
* radius -> радиус окружности
* mode -> Off, On или Xor. Смотри enum в n3310.h
* Возвращаемое значение : смотри возвращаемое значение в n3310lcd.h
*/
byte LcdCircle(byte x, byte y, byte radius, LcdPixelMode mode)
{
signed char xc = 0;
signed char yc = 0;
signed char p = 0;
if ( x >= LCD_X_RES || y >= LCD_Y_RES) return OUT_OF_BORDER;
yc = radius;
p = 3 - (radius<<1);
while (xc <= yc)
{
LcdPixel(x + xc, y + yc, mode);
LcdPixel(x + xc, y - yc, mode);
LcdPixel(x - xc, y + yc, mode);
LcdPixel(x - xc, y - yc, mode);
LcdPixel(x + yc, y + xc, mode);
LcdPixel(x + yc, y - xc, mode);
LcdPixel(x - yc, y + xc, mode);
LcdPixel(x - yc, y - xc, mode);
if (p < 0) p += (xc++ << 2) + 6;
else p += ((xc++ - yc--)<<2) + 10;
}
// Установка флага изменений кэша
UpdateLcd = TRUE;
return OK;
}
/*
* Имя : LcdSingleBar
* Описание : Рисует один закрашенный прямоугольник
* Аргумент(ы) : baseX -> абсолютная координата x (нижний левый угол)
* baseY -> абсолютная координата y (нижний левый угол)
* height -> высота (в пикселях)
* width -> ширина (в пикселях)
* mode -> Off, On или Xor. Смотри enum в n3310.h
* Возвращаемое значение : смотри возвращаемое значение в n3310lcd.h
*/
byte LcdSingleBar ( byte baseX, byte baseY, byte height, byte width, LcdPixelMode mode )
{
byte tmpIdxX,tmpIdxY,tmp;
byte response;
// Проверка границ
if ( ( baseX >= LCD_X_RES) || ( baseY >= LCD_Y_RES) ) return OUT_OF_BORDER;
if ( height > baseY )
tmp = 0;
else
tmp = baseY - height + 1;
// Рисование линий
for ( tmpIdxY = tmp; tmpIdxY <= baseY; tmpIdxY++ )
{
for ( tmpIdxX = baseX; tmpIdxX < (baseX + width); tmpIdxX++ )
{
response = LcdPixel( tmpIdxX, tmpIdxY, mode );
if(response)
return response;
}
}
// Установка флага изменений кэша
UpdateLcd = TRUE;
return OK;
}
/*
* Имя : LcdBars
* Описание : Рисует группу закрашенных прямоугольников (в режиме PIXEL_ON)
* Аргумент(ы) : data[] -> данные которые нужно отобразить
* numbBars -> количество прямоугольников
* width -> ширина (в пикселях)
* multiplier -> множитель для высоты
* Возвращаемое значение : смотри возвращаемое значение в n3310lcd.h
* Примечание : Пожалуйста проверьте значения EMPTY_SPACE_BARS, BAR_X, BAR_Y в n3310.h
* Пример : byte example[5] = {1, 2, 3, 4, 5};
* LcdBars(example, 5, 3, 2);
*/
byte LcdBars ( byte data[], byte numbBars, byte width, byte multiplier )
{
byte b;
byte tmpIdx = 0;
byte response;
for ( b = 0; b < numbBars ; b++ )
{
// Защита от выхода за пределы
if ( tmpIdx > LCD_X_RES - 1 ) return OUT_OF_BORDER;
// Расчет значения x
tmpIdx = ((width + EMPTY_SPACE_BARS) * b) + BAR_X;
// Рисуем один прямоугольник
response = LcdSingleBar( tmpIdx, BAR_Y, data[b] * multiplier, width, PIXEL_ON);
if(response == OUT_OF_BORDER)
return response;
}
// Установка флага изменений кэша
UpdateLcd = TRUE;
return OK;
}
/*
* Имя : LcdRect
* Описание : Рисует незакрашенный прямоугольник
* Аргумент(ы) : x1 -> абсолютная координата x левого верхнего угла
* y1 -> абсолютная координата y левого верхнего угла
* x2 -> абсолютная координата x правого нижнего угла
* y2 -> абсолютная координата y правого нижнего угла
* mode -> Off, On или Xor. Смотри enum в n3310.h
* Возвращаемое значение : смотри возвращаемое значение в n3310lcd.h
*/
byte LcdRect ( byte x1, byte y1, byte x2, byte y2, LcdPixelMode mode )
{
byte tmpIdx;
// Проверка границ
if ( ( x1 >= LCD_X_RES) || ( x2 >= LCD_X_RES) || ( y1 >= LCD_Y_RES) || ( y2 >= LCD_Y_RES) )
return OUT_OF_BORDER;
if ( ( x2 > x1 ) && ( y2 > y1 ) )
{
// Рисуем горизонтальные линии
for ( tmpIdx = x1; tmpIdx <= x2; tmpIdx++ )
{
LcdPixel( tmpIdx, y1, mode );
LcdPixel( tmpIdx, y2, mode );
}
// Рисуем вертикальные линии
for ( tmpIdx = y1; tmpIdx <= y2; tmpIdx++ )
{
LcdPixel( x1, tmpIdx, mode );
LcdPixel( x2, tmpIdx, mode );
}
// Установка флага изменений кэша
UpdateLcd = TRUE;
}
return OK;
}
/*
* Имя : LcdImage
* Описание : Рисует картинку из массива сохраненного в Flash ROM
* Аргумент(ы) : Указатель на массив картинки
* Возвращаемое значение : Нет
*/
void LcdImage ( const byte *imageData )
{
// // Инициализация указателя кэша
// LcdCacheIdx = 0;
// // В пределах кэша
// for ( LcdCacheIdx = 0; LcdCacheIdx < LCD_CACHE_SIZE; LcdCacheIdx++ )
// {
// // Копируем данные из массива в кэш
// LcdCache[LcdCacheIdx] = pgm_read_byte( imageData++ );
// }
// Оптимизация от Jakub Lasinski (March 14 2009)
memcpy_P( LcdCache, imageData, LCD_CACHE_SIZE ); // Тоже самое что и выше, но занимает меньше памяти и быстрее выполняется
// Сброс указателей границ в максимальное значение
LoWaterMark = 0;
HiWaterMark = LCD_CACHE_SIZE - 1;
// Установка флага изменений кэша
UpdateLcd = TRUE;
}
```
main.c:
```c
#include <util/delay.h>
#include "sc55.h"
int main() {
LcdInit();
LcdContrast(0x45);
while (1) {
LcdClear();
LcdRect(0, 0, LCD_X_RES - 2, LCD_Y_RES - 2, PIXEL_ON);
LcdLine(0, 0, LCD_X_RES - 2, LCD_Y_RES - 2, PIXEL_ON);
LcdLine(0, LCD_Y_RES - 2, LCD_X_RES - 2, 0, PIXEL_ON);
LcdUpdate();
_delay_ms(1000);
LcdClear();
LcdGotoXYFont(0, 2);
LcdStr(FONT_2X, "Привет,");
LcdGotoXYFont(0, 4);
LcdStr(FONT_2X, "мир!");
LcdUpdate();
_delay_ms(1000);
}
}
```
![helloworld](helloworld.jpg)
![lines](lines.jpg)