Дисплей от мобильного телефона Nokia 5110/3310 - это довольно известный графический дисплей начального уровня на управляющем контроллере pcd8544. Цена на него колеблется в пределах ста рублей, что сделало его широко распространенным "народным" дисплеем, наряду со знакогенерирующим LCD1602. Несмотря на широкое распространение, имеющаяся информация по этому дисплею или противоречива, или обрывочна(например). Ниже приводится лог недельного изучения девайса с примерами кода от простого к сложному. Вначале я пытался использовать Proteus для большей наглядности, но его возможностей, увы, хватило только на три примера. Поэтому остальные примеры идут для реальной связки ATmega8+дисплей. Так или иначе примеры с Proteus работают и на настоящем устройстве. Ко всем примерами, исходники к ним, скомпилированные прошивками и сборочные Makefile можно скачать здесь https://gitlab.com/flank1er/pcd8544_atmega8.
Должен сразу сказать, что для русификации дисплея я использовал кодировку CP866, и сделал я это удобным для Linux-пользователей способом. В Windows это получится использовать разве что из CYGWIN (понадобится утилита iconv).
Также должен упомянуть, что у меня были сложности с управлением дисплеем через аппаратный SPI. Она заключается в том, что и USBasp, и дисплей используют SPI порт ATmega8, и чтобы загрузить прошивку в микроконтроллер, дисплей приходится отключать. Это просто неудобно. Поэтому почти во всех примерах используется подключение дисплея по программному SPI. Подключение через аппаратный SPI используется только во втором и в последнем (финишном) примере (там не сложно, лишь нужно поменять пару строчек в коде и переподключиться к нужным пинам).
В целом, говоря про графические дисплеи, скажу, что эти штуки быстро "съедают" оперативную и флеш память и легко занимают всю пропускную способность SPI шины ;)
Нужно осознавать, что дисплеи покупаемые на али или ибэе, это не оригинальные дисплеи на контроллере pcd8544, а совместимые с ним noname девайсы(подробности здесь). По крайней мере в продаже на mouser я их не нашел. Это не плохо, но параметры устройства могут не соответствовать документации на оригинальный чип, что как бы уже нехорошо.
Далее немного справочной информации.
Datasheet на дисплей можно скачать например отсюда: https://www.sparkfun.com/datasheets/LCD/Monochrome/Nokia5110.pdf
Распиновка. Модули с экраном от Nokia 5110/3310 продаются в двух вариантах: красные и синие. Синий модуль имеет следующий ряд контактов:
Здесь:
Gnd - это "земля",
BL - подсветка +3.3 Вольта,
Vcc - питание +3.3 Вольта,
Clk - линия тактирования,
Din - линия данных "Data input",
DC или D/C- выбор между регистрами данных или команд, аналогичен RS в HD44780,
CE или SCE - Chip Enable, он же CS/SS, Chip Select,
RST - это линия сброса контроллера PCD8544.
Мой экземпляр экрана имеет красную расцветку, от синего он отличается тем, что подсветка LIGHT включается подключением к "земле", а не к питанию. В остальном все тоже самое.
Стрелочкой обозначен верх дисплея.
Адресация видеопамяти:
Начало координат располагается в верхнем левом углу. Очередность байтов при линейном заполнении видеопамяти такая:
Также хочется сказать пару слов о подключении дисплея с 3.3 вольтовой логикой к микроконтроллеру ATmega8. Все знают, что в частности Аrduino - это 5 вольтовая логика. Однако микроконтроллеры ATmega могут работать на 3.3 Вольтах.
Я лично использовал такую самодельную DevBoard:
Линейный стабилизатор LM1117 3.3 позволяет переключать внешнее питание с 5 Вольт на 3.3Вольт. Однако при подключении программатора USBasp линейный стабилизатор не задействуется, вместо этого на самом программаторе переключается джампик питания микроконтроллера на 3.3V.
На моей DevBoard установлен кварц на 16MHz, хотя по документации на ATmega8, при напряжении питания 3.3Вольт частота clkcpu не должна превышать 10МГц. И хотя при комнатной температуре это будет работать скорее всего без проблем, технически это является overclocking'ом.
Для проверки дисплея на работоспособность, можно попробовать подключить его к Arduino.
На официальном сайте Arduino имеется небольшой скетч - HelloWorld, с помощью которого можно проверить работоспособность дисплея. Этот скетч не требует установки каких-либо библиотек для своей работы.
#define PIN_SCE 7 #define PIN_RESET 6 #define PIN_DC 5 #define PIN_SDIN 4 #define PIN_SCLK 3 #define LCD_C LOW #define LCD_D HIGH #define LCD_X 84 #define LCD_Y 48 static const byte ASCII[][5] = { {0x00, 0x00, 0x00, 0x00, 0x00} // 20 ,{0x00, 0x00, 0x5f, 0x00, 0x00} // 21 ! ,{0x00, 0x07, 0x00, 0x07, 0x00} // 22 " ,{0x14, 0x7f, 0x14, 0x7f, 0x14} // 23 # ,{0x24, 0x2a, 0x7f, 0x2a, 0x12} // 24 $ ,{0x23, 0x13, 0x08, 0x64, 0x62} // 25 % ,{0x36, 0x49, 0x55, 0x22, 0x50} // 26 & ,{0x00, 0x05, 0x03, 0x00, 0x00} // 27 ' ,{0x00, 0x1c, 0x22, 0x41, 0x00} // 28 ( ,{0x00, 0x41, 0x22, 0x1c, 0x00} // 29 ) ,{0x14, 0x08, 0x3e, 0x08, 0x14} // 2a * ,{0x08, 0x08, 0x3e, 0x08, 0x08} // 2b + ,{0x00, 0x50, 0x30, 0x00, 0x00} // 2c , ,{0x08, 0x08, 0x08, 0x08, 0x08} // 2d - ,{0x00, 0x60, 0x60, 0x00, 0x00} // 2e . ,{0x20, 0x10, 0x08, 0x04, 0x02} // 2f / ,{0x3e, 0x51, 0x49, 0x45, 0x3e} // 30 0 ,{0x00, 0x42, 0x7f, 0x40, 0x00} // 31 1 ,{0x42, 0x61, 0x51, 0x49, 0x46} // 32 2 ,{0x21, 0x41, 0x45, 0x4b, 0x31} // 33 3 ,{0x18, 0x14, 0x12, 0x7f, 0x10} // 34 4 ,{0x27, 0x45, 0x45, 0x45, 0x39} // 35 5 ,{0x3c, 0x4a, 0x49, 0x49, 0x30} // 36 6 ,{0x01, 0x71, 0x09, 0x05, 0x03} // 37 7 ,{0x36, 0x49, 0x49, 0x49, 0x36} // 38 8 ,{0x06, 0x49, 0x49, 0x29, 0x1e} // 39 9 ,{0x00, 0x36, 0x36, 0x00, 0x00} // 3a : ,{0x00, 0x56, 0x36, 0x00, 0x00} // 3b ; ,{0x08, 0x14, 0x22, 0x41, 0x00} // 3c < ,{0x14, 0x14, 0x14, 0x14, 0x14} // 3d = ,{0x00, 0x41, 0x22, 0x14, 0x08} // 3e > ,{0x02, 0x01, 0x51, 0x09, 0x06} // 3f ? ,{0x32, 0x49, 0x79, 0x41, 0x3e} // 40 @ ,{0x7e, 0x11, 0x11, 0x11, 0x7e} // 41 A ,{0x7f, 0x49, 0x49, 0x49, 0x36} // 42 B ,{0x3e, 0x41, 0x41, 0x41, 0x22} // 43 C ,{0x7f, 0x41, 0x41, 0x22, 0x1c} // 44 D ,{0x7f, 0x49, 0x49, 0x49, 0x41} // 45 E ,{0x7f, 0x09, 0x09, 0x09, 0x01} // 46 F ,{0x3e, 0x41, 0x49, 0x49, 0x7a} // 47 G ,{0x7f, 0x08, 0x08, 0x08, 0x7f} // 48 H ,{0x00, 0x41, 0x7f, 0x41, 0x00} // 49 I ,{0x20, 0x40, 0x41, 0x3f, 0x01} // 4a J ,{0x7f, 0x08, 0x14, 0x22, 0x41} // 4b K ,{0x7f, 0x40, 0x40, 0x40, 0x40} // 4c L ,{0x7f, 0x02, 0x0c, 0x02, 0x7f} // 4d M ,{0x7f, 0x04, 0x08, 0x10, 0x7f} // 4e N ,{0x3e, 0x41, 0x41, 0x41, 0x3e} // 4f O ,{0x7f, 0x09, 0x09, 0x09, 0x06} // 50 P ,{0x3e, 0x41, 0x51, 0x21, 0x5e} // 51 Q ,{0x7f, 0x09, 0x19, 0x29, 0x46} // 52 R ,{0x46, 0x49, 0x49, 0x49, 0x31} // 53 S ,{0x01, 0x01, 0x7f, 0x01, 0x01} // 54 T ,{0x3f, 0x40, 0x40, 0x40, 0x3f} // 55 U ,{0x1f, 0x20, 0x40, 0x20, 0x1f} // 56 V ,{0x3f, 0x40, 0x38, 0x40, 0x3f} // 57 W ,{0x63, 0x14, 0x08, 0x14, 0x63} // 58 X ,{0x07, 0x08, 0x70, 0x08, 0x07} // 59 Y ,{0x61, 0x51, 0x49, 0x45, 0x43} // 5a Z ,{0x00, 0x7f, 0x41, 0x41, 0x00} // 5b [ ,{0x02, 0x04, 0x08, 0x10, 0x20} // 5c ¥ ,{0x00, 0x41, 0x41, 0x7f, 0x00} // 5d ] ,{0x04, 0x02, 0x01, 0x02, 0x04} // 5e ^ ,{0x40, 0x40, 0x40, 0x40, 0x40} // 5f _ ,{0x00, 0x01, 0x02, 0x04, 0x00} // 60 ` ,{0x20, 0x54, 0x54, 0x54, 0x78} // 61 a ,{0x7f, 0x48, 0x44, 0x44, 0x38} // 62 b ,{0x38, 0x44, 0x44, 0x44, 0x20} // 63 c ,{0x38, 0x44, 0x44, 0x48, 0x7f} // 64 d ,{0x38, 0x54, 0x54, 0x54, 0x18} // 65 e ,{0x08, 0x7e, 0x09, 0x01, 0x02} // 66 f ,{0x0c, 0x52, 0x52, 0x52, 0x3e} // 67 g ,{0x7f, 0x08, 0x04, 0x04, 0x78} // 68 h ,{0x00, 0x44, 0x7d, 0x40, 0x00} // 69 i ,{0x20, 0x40, 0x44, 0x3d, 0x00} // 6a j ,{0x7f, 0x10, 0x28, 0x44, 0x00} // 6b k ,{0x00, 0x41, 0x7f, 0x40, 0x00} // 6c l ,{0x7c, 0x04, 0x18, 0x04, 0x78} // 6d m ,{0x7c, 0x08, 0x04, 0x04, 0x78} // 6e n ,{0x38, 0x44, 0x44, 0x44, 0x38} // 6f o ,{0x7c, 0x14, 0x14, 0x14, 0x08} // 70 p ,{0x08, 0x14, 0x14, 0x18, 0x7c} // 71 q ,{0x7c, 0x08, 0x04, 0x04, 0x08} // 72 r ,{0x48, 0x54, 0x54, 0x54, 0x20} // 73 s ,{0x04, 0x3f, 0x44, 0x40, 0x20} // 74 t ,{0x3c, 0x40, 0x40, 0x20, 0x7c} // 75 u ,{0x1c, 0x20, 0x40, 0x20, 0x1c} // 76 v ,{0x3c, 0x40, 0x30, 0x40, 0x3c} // 77 w ,{0x44, 0x28, 0x10, 0x28, 0x44} // 78 x ,{0x0c, 0x50, 0x50, 0x50, 0x3c} // 79 y ,{0x44, 0x64, 0x54, 0x4c, 0x44} // 7a z ,{0x00, 0x08, 0x36, 0x41, 0x00} // 7b { ,{0x00, 0x00, 0x7f, 0x00, 0x00} // 7c | ,{0x00, 0x41, 0x36, 0x08, 0x00} // 7d } ,{0x10, 0x08, 0x08, 0x10, 0x08} // 7e ← ,{0x78, 0x46, 0x41, 0x46, 0x78} // 7f → }; void LcdCharacter(char character) { LcdWrite(LCD_D, 0x00); for (int index = 0; index < 5; index++) { LcdWrite(LCD_D, ASCII[character - 0x20][index]); } LcdWrite(LCD_D, 0x00); } void LcdClear(void) { for (int index = 0; index < LCD_X * LCD_Y / 8; index++) { LcdWrite(LCD_D, 0x00); } } void LcdInitialise(void) { pinMode(PIN_SCE, OUTPUT); pinMode(PIN_RESET, OUTPUT); pinMode(PIN_DC, OUTPUT); pinMode(PIN_SDIN, OUTPUT); pinMode(PIN_SCLK, OUTPUT); digitalWrite(PIN_RESET, LOW); digitalWrite(PIN_RESET, HIGH); LcdWrite(LCD_C, 0x21 ); // LCD Extended Commands. LcdWrite(LCD_C, 0xBA ); // Set LCD Vop (Contrast). Здесь константа в оригинале была B1 (c)flanker LcdWrite(LCD_C, 0x04 ); // Set Temp coefficent. //0x04 LcdWrite(LCD_C, 0x14 ); // LCD bias mode 1:48. //0x13 LcdWrite(LCD_C, 0x20 ); // LCD Basic Commands LcdWrite(LCD_C, 0x0C ); // LCD in normal mode. } void LcdString(char *characters) { while (*characters) { LcdCharacter(*characters++); } } void LcdWrite(byte dc, byte data) { digitalWrite(PIN_DC, dc); digitalWrite(PIN_SCE, LOW); shiftOut(PIN_SDIN, PIN_SCLK, MSBFIRST, data); digitalWrite(PIN_SCE, HIGH); } void setup(void) { LcdInitialise(); LcdClear(); LcdString("Hello World!"); } void loop(void) { }
При успешном подключении, на экране дисплея появится строка: Hello World!
Данный скетч я буду использовать как образец для написания кода под ATmega8.
В Proteus 8.5 дисплей на контролере pcd8544 уже имеется в библиотеке, так что ничего искать и скачивать не надо, нужно просто добавить его в проект:
После статьи о сдвиговых регистрах, у меня в Proteus остался проект с ATmega8 настроенный на работу на 2МГц, его я и буду продолжать использовать:
Код для микроконтроллера я пишу в самом Proteus, и вариант приведенного выше кода для ATmega8 будет выглядеть так: показать код
#include <avr/io.h> #include <util/delay.h> #define PIN_SCE PD7 #define PIN_RESET PD6 #define PIN_DC PD5 #define PIN_SDIN PD4 #define PIN_SCLK PD3 #define LCD_C 0x00 #define LCD_D 0x01 #define LCD_X 84 #define LCD_Y 48 #define PORT_PCD8544 PORTD #define DDR_PCD8544 DDRD static const uint8_t ASCII[][5] = { {0x00, 0x00, 0x00, 0x00, 0x00} // 20 ,{0x00, 0x00, 0x5f, 0x00, 0x00} // 21 ! ,{0x00, 0x07, 0x00, 0x07, 0x00} // 22 " ,{0x14, 0x7f, 0x14, 0x7f, 0x14} // 23 # ,{0x24, 0x2a, 0x7f, 0x2a, 0x12} // 24 $ ,{0x23, 0x13, 0x08, 0x64, 0x62} // 25 % ,{0x36, 0x49, 0x55, 0x22, 0x50} // 26 & ,{0x00, 0x05, 0x03, 0x00, 0x00} // 27 ' ,{0x00, 0x1c, 0x22, 0x41, 0x00} // 28 ( ,{0x00, 0x41, 0x22, 0x1c, 0x00} // 29 ) ,{0x14, 0x08, 0x3e, 0x08, 0x14} // 2a * ,{0x08, 0x08, 0x3e, 0x08, 0x08} // 2b + ,{0x00, 0x50, 0x30, 0x00, 0x00} // 2c , ,{0x08, 0x08, 0x08, 0x08, 0x08} // 2d - ,{0x00, 0x60, 0x60, 0x00, 0x00} // 2e . ,{0x20, 0x10, 0x08, 0x04, 0x02} // 2f / ,{0x3e, 0x51, 0x49, 0x45, 0x3e} // 30 0 ,{0x00, 0x42, 0x7f, 0x40, 0x00} // 31 1 ,{0x42, 0x61, 0x51, 0x49, 0x46} // 32 2 ,{0x21, 0x41, 0x45, 0x4b, 0x31} // 33 3 ,{0x18, 0x14, 0x12, 0x7f, 0x10} // 34 4 ,{0x27, 0x45, 0x45, 0x45, 0x39} // 35 5 ,{0x3c, 0x4a, 0x49, 0x49, 0x30} // 36 6 ,{0x01, 0x71, 0x09, 0x05, 0x03} // 37 7 ,{0x36, 0x49, 0x49, 0x49, 0x36} // 38 8 ,{0x06, 0x49, 0x49, 0x29, 0x1e} // 39 9 ,{0x00, 0x36, 0x36, 0x00, 0x00} // 3a : ,{0x00, 0x56, 0x36, 0x00, 0x00} // 3b ; ,{0x08, 0x14, 0x22, 0x41, 0x00} // 3c < ,{0x14, 0x14, 0x14, 0x14, 0x14} // 3d = ,{0x00, 0x41, 0x22, 0x14, 0x08} // 3e > ,{0x02, 0x01, 0x51, 0x09, 0x06} // 3f ? ,{0x32, 0x49, 0x79, 0x41, 0x3e} // 40 @ ,{0x7e, 0x11, 0x11, 0x11, 0x7e} // 41 A ,{0x7f, 0x49, 0x49, 0x49, 0x36} // 42 B ,{0x3e, 0x41, 0x41, 0x41, 0x22} // 43 C ,{0x7f, 0x41, 0x41, 0x22, 0x1c} // 44 D ,{0x7f, 0x49, 0x49, 0x49, 0x41} // 45 E ,{0x7f, 0x09, 0x09, 0x09, 0x01} // 46 F ,{0x3e, 0x41, 0x49, 0x49, 0x7a} // 47 G ,{0x7f, 0x08, 0x08, 0x08, 0x7f} // 48 H ,{0x00, 0x41, 0x7f, 0x41, 0x00} // 49 I ,{0x20, 0x40, 0x41, 0x3f, 0x01} // 4a J ,{0x7f, 0x08, 0x14, 0x22, 0x41} // 4b K ,{0x7f, 0x40, 0x40, 0x40, 0x40} // 4c L ,{0x7f, 0x02, 0x0c, 0x02, 0x7f} // 4d M ,{0x7f, 0x04, 0x08, 0x10, 0x7f} // 4e N ,{0x3e, 0x41, 0x41, 0x41, 0x3e} // 4f O ,{0x7f, 0x09, 0x09, 0x09, 0x06} // 50 P ,{0x3e, 0x41, 0x51, 0x21, 0x5e} // 51 Q ,{0x7f, 0x09, 0x19, 0x29, 0x46} // 52 R ,{0x46, 0x49, 0x49, 0x49, 0x31} // 53 S ,{0x01, 0x01, 0x7f, 0x01, 0x01} // 54 T ,{0x3f, 0x40, 0x40, 0x40, 0x3f} // 55 U ,{0x1f, 0x20, 0x40, 0x20, 0x1f} // 56 V ,{0x3f, 0x40, 0x38, 0x40, 0x3f} // 57 W ,{0x63, 0x14, 0x08, 0x14, 0x63} // 58 X ,{0x07, 0x08, 0x70, 0x08, 0x07} // 59 Y ,{0x61, 0x51, 0x49, 0x45, 0x43} // 5a Z ,{0x00, 0x7f, 0x41, 0x41, 0x00} // 5b [ ,{0x02, 0x04, 0x08, 0x10, 0x20} // 5c ? ,{0x00, 0x41, 0x41, 0x7f, 0x00} // 5d ] ,{0x04, 0x02, 0x01, 0x02, 0x04} // 5e ^ ,{0x40, 0x40, 0x40, 0x40, 0x40} // 5f _ ,{0x00, 0x01, 0x02, 0x04, 0x00} // 60 ` ,{0x20, 0x54, 0x54, 0x54, 0x78} // 61 a ,{0x7f, 0x48, 0x44, 0x44, 0x38} // 62 b ,{0x38, 0x44, 0x44, 0x44, 0x20} // 63 c ,{0x38, 0x44, 0x44, 0x48, 0x7f} // 64 d ,{0x38, 0x54, 0x54, 0x54, 0x18} // 65 e ,{0x08, 0x7e, 0x09, 0x01, 0x02} // 66 f ,{0x0c, 0x52, 0x52, 0x52, 0x3e} // 67 g ,{0x7f, 0x08, 0x04, 0x04, 0x78} // 68 h ,{0x00, 0x44, 0x7d, 0x40, 0x00} // 69 i ,{0x20, 0x40, 0x44, 0x3d, 0x00} // 6a j ,{0x7f, 0x10, 0x28, 0x44, 0x00} // 6b k ,{0x00, 0x41, 0x7f, 0x40, 0x00} // 6c l ,{0x7c, 0x04, 0x18, 0x04, 0x78} // 6d m ,{0x7c, 0x08, 0x04, 0x04, 0x78} // 6e n ,{0x38, 0x44, 0x44, 0x44, 0x38} // 6f o ,{0x7c, 0x14, 0x14, 0x14, 0x08} // 70 p ,{0x08, 0x14, 0x14, 0x18, 0x7c} // 71 q ,{0x7c, 0x08, 0x04, 0x04, 0x08} // 72 r ,{0x48, 0x54, 0x54, 0x54, 0x20} // 73 s ,{0x04, 0x3f, 0x44, 0x40, 0x20} // 74 t ,{0x3c, 0x40, 0x40, 0x20, 0x7c} // 75 u ,{0x1c, 0x20, 0x40, 0x20, 0x1c} // 76 v ,{0x3c, 0x40, 0x30, 0x40, 0x3c} // 77 w ,{0x44, 0x28, 0x10, 0x28, 0x44} // 78 x ,{0x0c, 0x50, 0x50, 0x50, 0x3c} // 79 y ,{0x44, 0x64, 0x54, 0x4c, 0x44} // 7a z ,{0x00, 0x08, 0x36, 0x41, 0x00} // 7b { ,{0x00, 0x00, 0x7f, 0x00, 0x00} // 7c | ,{0x00, 0x41, 0x36, 0x08, 0x00} // 7d } ,{0x10, 0x08, 0x08, 0x10, 0x08} // 7e < ,{0x78, 0x46, 0x41, 0x46, 0x78} // 7f > }; void pcd8544_init(void); void pcd8544_send(uint8_t dc, uint8_t data); void pcd8544_print_string(char *str); void pcd8544_send_char(uint8_t ch); void pcd8544_clear(void); int main(void) { pcd8544_init(); pcd8544_clear(); pcd8544_print_string("Hello World!"); for (;;){ _delay_ms(1000); } return 0; } void pcd8544_init(void) { // GPIO Setup DDR_PCD8544 |= (1<<PIN_SCE) | (1<<PIN_RESET) | (1<<PIN_DC) | (1<<PIN_SDIN) | (1<<PIN_SCLK); PORT_PCD8544&=~(1<<PIN_RESET); PORT_PCD8544|=(1<<PIN_RESET); pcd8544_send(LCD_C, 0x21 ); // LCD Extended Commands. pcd8544_send(LCD_C, 0xBA ); // Set LCD Vop (Contrast). pcd8544_send(LCD_C, 0x04 ); // Set Temp coefficent. //0x04 pcd8544_send(LCD_C, 0x14 ); // LCD bias mode 1:48. //0x13 pcd8544_send(LCD_C, 0x20 ); // LCD Basic Commands pcd8544_send(LCD_C, 0x0C ); // LCD in normal mode. } void pcd8544_send(uint8_t dc, uint8_t data) { uint8_t i; if (dc == LCD_D) PORT_PCD8544 |= (1<<PIN_DC); else PORT_PCD8544 &= ~(1<<PIN_DC); PORT_PCD8544&=~(1<<PIN_SCE); for (i=0; i<8; i++) { PORT_PCD8544=(data & 0x80) ? PORT_PCD8544 | (1<<PIN_SDIN) : PORT_PCD8544 & ~(1<<PIN_SDIN); data=(data<<1); PORT_PCD8544|=(1<<PIN_SCLK); PORT_PCD8544&=~(1<<PIN_SCLK); } PORT_PCD8544|=(1<<PIN_SCE); } void pcd8544_print_string(char *str) { while (*str) { pcd8544_send_char(*str++); } } void pcd8544_send_char(uint8_t ch) { int i; if (ch >= 0x20 && ch <= 0x80) { pcd8544_send(LCD_D, 0x00); for (i = 0; i < 5; i++) { pcd8544_send(LCD_D, ASCII[ch - 0x20][i]); } pcd8544_send(LCD_D, 0x00); } } void pcd8544_clear(void) { int i; for (i=0; i < LCD_X * LCD_Y / 8; i++) { pcd8544_send(LCD_D, 0x00); } }
Результат работы:
Начало положено, начнём добавлять функционал. Что мы можем сделать на этом этапе? Первое, что хотелось бы сделать, это чтение ASCII таблицы из флеш-памяти, и поддержку кириллицы. Но Proteus такие штуки не осилит. Поэтому начнем с малого, установка курсора на определенную позицию экрана, сейчас это нам вполне по силам.
Добавляем функцию установки курсора:
void pcd8544_set_cursor(uint8_t x, uint8_t y) { x=x%12; y=y%6; pcd8544_send(LCD_C, 0x40+y); pcd8544_send(LCD_C, 0x80+x*7); }
Чтобы каждый раз не вызывать эту функцию, добавим функцию печати строки с определенной позиции:
void pcd8544_print_at(char *str, uint8_t x, uint8_t y) { pcd8544_set_cursor(x,y); while (*str) { pcd8544_send_char(*str++); } break; }
Теперь можно написать на дисплее уже что-то более осмысленное:
int main(void) { pcd8544_init(); pcd8544_clear(); pcd8544_print_at("Nokia 3310",1,0); pcd8544_print_at("Connecting",1,2); pcd8544_print_at("People",6,3); for (;;){ _delay_ms(1000); } return 0; }показать полный код
#include <avr/io.h> #include <util/delay.h> #define PIN_SCE PD7 #define PIN_RESET PD6 #define PIN_DC PD5 #define PIN_SDIN PD4 #define PIN_SCLK PD3 #define LCD_C 0x00 #define LCD_D 0x01 #define LCD_X 84 #define LCD_Y 48 #define PORT_PCD8544 PORTD #define DDR_PCD8544 DDRD static const uint8_t ASCII[][5] = { {0x00, 0x00, 0x00, 0x00, 0x00} // 20 ,{0x00, 0x00, 0x5f, 0x00, 0x00} // 21 ! ,{0x00, 0x07, 0x00, 0x07, 0x00} // 22 " ,{0x14, 0x7f, 0x14, 0x7f, 0x14} // 23 # ,{0x24, 0x2a, 0x7f, 0x2a, 0x12} // 24 $ ,{0x23, 0x13, 0x08, 0x64, 0x62} // 25 % ,{0x36, 0x49, 0x55, 0x22, 0x50} // 26 & ,{0x00, 0x05, 0x03, 0x00, 0x00} // 27 ' ,{0x00, 0x1c, 0x22, 0x41, 0x00} // 28 ( ,{0x00, 0x41, 0x22, 0x1c, 0x00} // 29 ) ,{0x14, 0x08, 0x3e, 0x08, 0x14} // 2a * ,{0x08, 0x08, 0x3e, 0x08, 0x08} // 2b + ,{0x00, 0x50, 0x30, 0x00, 0x00} // 2c , ,{0x08, 0x08, 0x08, 0x08, 0x08} // 2d - ,{0x00, 0x60, 0x60, 0x00, 0x00} // 2e . ,{0x20, 0x10, 0x08, 0x04, 0x02} // 2f / ,{0x3e, 0x51, 0x49, 0x45, 0x3e} // 30 0 ,{0x00, 0x42, 0x7f, 0x40, 0x00} // 31 1 ,{0x42, 0x61, 0x51, 0x49, 0x46} // 32 2 ,{0x21, 0x41, 0x45, 0x4b, 0x31} // 33 3 ,{0x18, 0x14, 0x12, 0x7f, 0x10} // 34 4 ,{0x27, 0x45, 0x45, 0x45, 0x39} // 35 5 ,{0x3c, 0x4a, 0x49, 0x49, 0x30} // 36 6 ,{0x01, 0x71, 0x09, 0x05, 0x03} // 37 7 ,{0x36, 0x49, 0x49, 0x49, 0x36} // 38 8 ,{0x06, 0x49, 0x49, 0x29, 0x1e} // 39 9 ,{0x00, 0x36, 0x36, 0x00, 0x00} // 3a : ,{0x00, 0x56, 0x36, 0x00, 0x00} // 3b ; ,{0x08, 0x14, 0x22, 0x41, 0x00} // 3c < ,{0x14, 0x14, 0x14, 0x14, 0x14} // 3d = ,{0x00, 0x41, 0x22, 0x14, 0x08} // 3e > ,{0x02, 0x01, 0x51, 0x09, 0x06} // 3f ? ,{0x32, 0x49, 0x79, 0x41, 0x3e} // 40 @ ,{0x7e, 0x11, 0x11, 0x11, 0x7e} // 41 A ,{0x7f, 0x49, 0x49, 0x49, 0x36} // 42 B ,{0x3e, 0x41, 0x41, 0x41, 0x22} // 43 C ,{0x7f, 0x41, 0x41, 0x22, 0x1c} // 44 D ,{0x7f, 0x49, 0x49, 0x49, 0x41} // 45 E ,{0x7f, 0x09, 0x09, 0x09, 0x01} // 46 F ,{0x3e, 0x41, 0x49, 0x49, 0x7a} // 47 G ,{0x7f, 0x08, 0x08, 0x08, 0x7f} // 48 H ,{0x00, 0x41, 0x7f, 0x41, 0x00} // 49 I ,{0x20, 0x40, 0x41, 0x3f, 0x01} // 4a J ,{0x7f, 0x08, 0x14, 0x22, 0x41} // 4b K ,{0x7f, 0x40, 0x40, 0x40, 0x40} // 4c L ,{0x7f, 0x02, 0x0c, 0x02, 0x7f} // 4d M ,{0x7f, 0x04, 0x08, 0x10, 0x7f} // 4e N ,{0x3e, 0x41, 0x41, 0x41, 0x3e} // 4f O ,{0x7f, 0x09, 0x09, 0x09, 0x06} // 50 P ,{0x3e, 0x41, 0x51, 0x21, 0x5e} // 51 Q ,{0x7f, 0x09, 0x19, 0x29, 0x46} // 52 R ,{0x46, 0x49, 0x49, 0x49, 0x31} // 53 S ,{0x01, 0x01, 0x7f, 0x01, 0x01} // 54 T ,{0x3f, 0x40, 0x40, 0x40, 0x3f} // 55 U ,{0x1f, 0x20, 0x40, 0x20, 0x1f} // 56 V ,{0x3f, 0x40, 0x38, 0x40, 0x3f} // 57 W ,{0x63, 0x14, 0x08, 0x14, 0x63} // 58 X ,{0x07, 0x08, 0x70, 0x08, 0x07} // 59 Y ,{0x61, 0x51, 0x49, 0x45, 0x43} // 5a Z ,{0x00, 0x7f, 0x41, 0x41, 0x00} // 5b [ ,{0x02, 0x04, 0x08, 0x10, 0x20} // 5c ? ,{0x00, 0x41, 0x41, 0x7f, 0x00} // 5d ] ,{0x04, 0x02, 0x01, 0x02, 0x04} // 5e ^ ,{0x40, 0x40, 0x40, 0x40, 0x40} // 5f _ ,{0x00, 0x01, 0x02, 0x04, 0x00} // 60 ` ,{0x20, 0x54, 0x54, 0x54, 0x78} // 61 a ,{0x7f, 0x48, 0x44, 0x44, 0x38} // 62 b ,{0x38, 0x44, 0x44, 0x44, 0x20} // 63 c ,{0x38, 0x44, 0x44, 0x48, 0x7f} // 64 d ,{0x38, 0x54, 0x54, 0x54, 0x18} // 65 e ,{0x08, 0x7e, 0x09, 0x01, 0x02} // 66 f ,{0x0c, 0x52, 0x52, 0x52, 0x3e} // 67 g ,{0x7f, 0x08, 0x04, 0x04, 0x78} // 68 h ,{0x00, 0x44, 0x7d, 0x40, 0x00} // 69 i ,{0x20, 0x40, 0x44, 0x3d, 0x00} // 6a j ,{0x7f, 0x10, 0x28, 0x44, 0x00} // 6b k ,{0x00, 0x41, 0x7f, 0x40, 0x00} // 6c l ,{0x7c, 0x04, 0x18, 0x04, 0x78} // 6d m ,{0x7c, 0x08, 0x04, 0x04, 0x78} // 6e n ,{0x38, 0x44, 0x44, 0x44, 0x38} // 6f o ,{0x7c, 0x14, 0x14, 0x14, 0x08} // 70 p ,{0x08, 0x14, 0x14, 0x18, 0x7c} // 71 q ,{0x7c, 0x08, 0x04, 0x04, 0x08} // 72 r ,{0x48, 0x54, 0x54, 0x54, 0x20} // 73 s ,{0x04, 0x3f, 0x44, 0x40, 0x20} // 74 t ,{0x3c, 0x40, 0x40, 0x20, 0x7c} // 75 u ,{0x1c, 0x20, 0x40, 0x20, 0x1c} // 76 v ,{0x3c, 0x40, 0x30, 0x40, 0x3c} // 77 w ,{0x44, 0x28, 0x10, 0x28, 0x44} // 78 x ,{0x0c, 0x50, 0x50, 0x50, 0x3c} // 79 y ,{0x44, 0x64, 0x54, 0x4c, 0x44} // 7a z ,{0x00, 0x08, 0x36, 0x41, 0x00} // 7b { ,{0x00, 0x00, 0x7f, 0x00, 0x00} // 7c | ,{0x00, 0x41, 0x36, 0x08, 0x00} // 7d } ,{0x10, 0x08, 0x08, 0x10, 0x08} // 7e < ,{0x78, 0x46, 0x41, 0x46, 0x78} // 7f > }; void pcd8544_init(void); void pcd8544_send(uint8_t dc, uint8_t data); void pcd8544_print_string(char *str); void pcd8544_send_char(uint8_t ch); void pcd8544_clear(void); void pcd8544_set_cursor(uint8_t x, uint8_t y); void pcd8544_print_at(char *str, uint8_t x, uint8_t y); int main(void) { pcd8544_init(); pcd8544_clear(); pcd8544_print_at("Nokia 3310",1,0); pcd8544_print_at("Connecting",1,2); pcd8544_print_at("People",6,3); for (;;){ _delay_ms(1000); } return 0; } void pcd8544_init(void) { // GPIO Setup DDR_PCD8544 |= (1<<PIN_SCE) | (1<<PIN_RESET) | (1<<PIN_DC) | (1<<PIN_SDIN) | (1<<PIN_SCLK); PORT_PCD8544&=~(1<<PIN_RESET); PORT_PCD8544|=(1<<PIN_RESET); pcd8544_send(LCD_C, 0x21 ); // LCD Extended Commands. pcd8544_send(LCD_C, 0xBA ); // Set LCD Vop (Contrast). pcd8544_send(LCD_C, 0x04 ); // Set Temp coefficent. //0x04 pcd8544_send(LCD_C, 0x14 ); // LCD bias mode 1:48. //0x13 pcd8544_send(LCD_C, 0x20 ); // LCD Basic Commands pcd8544_send(LCD_C, 0x0C ); // LCD in normal mode. } void pcd8544_send(uint8_t dc, uint8_t data) { uint8_t i; if (dc == LCD_D) PORT_PCD8544 |= (1<<PIN_DC); else PORT_PCD8544 &= ~(1<<PIN_DC); PORT_PCD8544&=~(1<<PIN_SCE); for (i=0; i<8; i++) { PORT_PCD8544=(data & 0x80) ? PORT_PCD8544 | (1<<PIN_SDIN) : PORT_PCD8544 & ~(1<<PIN_SDIN); data=(data<<1); PORT_PCD8544|=(1<<PIN_SCLK); PORT_PCD8544&=~(1<<PIN_SCLK); } PORT_PCD8544|=(1<<PIN_SCE); } void pcd8544_print_string(char *str) { while (*str) { pcd8544_send_char(*str++); } } void pcd8544_send_char(uint8_t ch) { int i; if (ch >= 0x20 && ch <= 0x80) { pcd8544_send(LCD_D, 0x00); for (i = 0; i < 5; i++) { pcd8544_send(LCD_D, ASCII[ch - 0x20][i]); } pcd8544_send(LCD_D, 0x00); } } void pcd8544_clear(void) { int i; for (i=0; i < LCD_X * LCD_Y / 8; i++) { pcd8544_send(LCD_D, 0x00); } } void pcd8544_set_cursor(uint8_t x, uint8_t y) { x=x%12; y=y%6; pcd8544_send(LCD_C, 0x40+y); pcd8544_send(LCD_C, 0x80+x*7); } void pcd8544_print_at(char *str, uint8_t x, uint8_t y) { pcd8544_set_cursor(x,y); while (*str) { pcd8544_send_char(*str++); } }
Результат:
Теперь мы можем переподключить дисплей на аппаратную SPI шину ATmega8.
Для использования SPI, дисплей нужно будет переподключить следующим образом:
Обращаю внимание, что вывод D/C- дисплея я переподключил с PD5 на PD7. В этом нет особой необходимости, просто мне удобнее использовать крайние пины.
Именованные константы:
#define PIN_SCE PD7 #define PIN_RESET PD6 #define PIN_DC PD5 #define PIN_SDIN PD4 #define PIN_SCLK PD3
меняются на:
// PORT D #define PIN_RESET PD6 #define PIN_DC PD7 // PORT B #define PIN_SCE PB2 #define PIN_SDIN PB3 #define PIN_SCLK PB5
В функции инициализации дисплея void pcd8544_init(void), строка:
DDR_PCD8544 |= (1<<PIN_SCE) | (1<<PIN_RESET) | (1<<PIN_DC) | (1<<PIN_SDIN) | (1<<PIN_SCLK);
заменяется на:
// GPIO Setup DDR_PCD8544 |=(1<<PIN_RESET) | (1<<PIN_DC); DDRB |= (1<<PIN_SCE) | (1<<PIN_SDIN) | (1<<PIN_SCLK); // SPI setup SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR0); // SPI mode 0, master, prescaler 1/16
Ну и последнее, в функции void pcd8544_send(uint8_t dc, uint8_t data), программный SPI:
PORT_PCD8544&=~(1<<PIN_SCE); for (i=0; i<8; i++) { PORT_PCD8544=(data & 0x80) ? PORT_PCD8544 | (1<<PIN_SDIN) : PORT_PCD8544 & ~(1<<PIN_SDIN); data=(data<<1); PORT_PCD8544|=(1<<PIN_SCLK); PORT_PCD8544&=~(1<<PIN_SCLK); } PORT_PCD8544|=(1<<PIN_SCE);
заменяется на аппаратный:
PORTB&=~(1<<PIN_SCE); SPDR=data; while(!(SPSR & (1<<SPIF))); PORTB|=(1<<PIN_SCE);
Это все. Полный исходник под спойлером.
показать полный код#include <avr/io.h> #include <util/delay.h> // PORT D #define PIN_RESET PD6 #define PIN_DC PD7 // PORT B #define PIN_SCE PB2 #define PIN_SDIN PB3 #define PIN_SCLK PB5 // LCD #define LCD_C 0x00 #define LCD_D 0x01 #define LCD_X 84 #define LCD_Y 48 #define PORT_PCD8544 PORTD #define DDR_PCD8544 DDRD static const uint8_t ASCII[][5] = { {0x00, 0x00, 0x00, 0x00, 0x00} // 20 ,{0x00, 0x00, 0x5f, 0x00, 0x00} // 21 ! ,{0x00, 0x07, 0x00, 0x07, 0x00} // 22 " ,{0x14, 0x7f, 0x14, 0x7f, 0x14} // 23 # ,{0x24, 0x2a, 0x7f, 0x2a, 0x12} // 24 $ ,{0x23, 0x13, 0x08, 0x64, 0x62} // 25 % ,{0x36, 0x49, 0x55, 0x22, 0x50} // 26 & ,{0x00, 0x05, 0x03, 0x00, 0x00} // 27 ' ,{0x00, 0x1c, 0x22, 0x41, 0x00} // 28 ( ,{0x00, 0x41, 0x22, 0x1c, 0x00} // 29 ) ,{0x14, 0x08, 0x3e, 0x08, 0x14} // 2a * ,{0x08, 0x08, 0x3e, 0x08, 0x08} // 2b + ,{0x00, 0x50, 0x30, 0x00, 0x00} // 2c , ,{0x08, 0x08, 0x08, 0x08, 0x08} // 2d - ,{0x00, 0x60, 0x60, 0x00, 0x00} // 2e . ,{0x20, 0x10, 0x08, 0x04, 0x02} // 2f / ,{0x3e, 0x51, 0x49, 0x45, 0x3e} // 30 0 ,{0x00, 0x42, 0x7f, 0x40, 0x00} // 31 1 ,{0x42, 0x61, 0x51, 0x49, 0x46} // 32 2 ,{0x21, 0x41, 0x45, 0x4b, 0x31} // 33 3 ,{0x18, 0x14, 0x12, 0x7f, 0x10} // 34 4 ,{0x27, 0x45, 0x45, 0x45, 0x39} // 35 5 ,{0x3c, 0x4a, 0x49, 0x49, 0x30} // 36 6 ,{0x01, 0x71, 0x09, 0x05, 0x03} // 37 7 ,{0x36, 0x49, 0x49, 0x49, 0x36} // 38 8 ,{0x06, 0x49, 0x49, 0x29, 0x1e} // 39 9 ,{0x00, 0x36, 0x36, 0x00, 0x00} // 3a : ,{0x00, 0x56, 0x36, 0x00, 0x00} // 3b ; ,{0x08, 0x14, 0x22, 0x41, 0x00} // 3c < ,{0x14, 0x14, 0x14, 0x14, 0x14} // 3d = ,{0x00, 0x41, 0x22, 0x14, 0x08} // 3e > ,{0x02, 0x01, 0x51, 0x09, 0x06} // 3f ? ,{0x32, 0x49, 0x79, 0x41, 0x3e} // 40 @ ,{0x7e, 0x11, 0x11, 0x11, 0x7e} // 41 A ,{0x7f, 0x49, 0x49, 0x49, 0x36} // 42 B ,{0x3e, 0x41, 0x41, 0x41, 0x22} // 43 C ,{0x7f, 0x41, 0x41, 0x22, 0x1c} // 44 D ,{0x7f, 0x49, 0x49, 0x49, 0x41} // 45 E ,{0x7f, 0x09, 0x09, 0x09, 0x01} // 46 F ,{0x3e, 0x41, 0x49, 0x49, 0x7a} // 47 G ,{0x7f, 0x08, 0x08, 0x08, 0x7f} // 48 H ,{0x00, 0x41, 0x7f, 0x41, 0x00} // 49 I ,{0x20, 0x40, 0x41, 0x3f, 0x01} // 4a J ,{0x7f, 0x08, 0x14, 0x22, 0x41} // 4b K ,{0x7f, 0x40, 0x40, 0x40, 0x40} // 4c L ,{0x7f, 0x02, 0x0c, 0x02, 0x7f} // 4d M ,{0x7f, 0x04, 0x08, 0x10, 0x7f} // 4e N ,{0x3e, 0x41, 0x41, 0x41, 0x3e} // 4f O ,{0x7f, 0x09, 0x09, 0x09, 0x06} // 50 P ,{0x3e, 0x41, 0x51, 0x21, 0x5e} // 51 Q ,{0x7f, 0x09, 0x19, 0x29, 0x46} // 52 R ,{0x46, 0x49, 0x49, 0x49, 0x31} // 53 S ,{0x01, 0x01, 0x7f, 0x01, 0x01} // 54 T ,{0x3f, 0x40, 0x40, 0x40, 0x3f} // 55 U ,{0x1f, 0x20, 0x40, 0x20, 0x1f} // 56 V ,{0x3f, 0x40, 0x38, 0x40, 0x3f} // 57 W ,{0x63, 0x14, 0x08, 0x14, 0x63} // 58 X ,{0x07, 0x08, 0x70, 0x08, 0x07} // 59 Y ,{0x61, 0x51, 0x49, 0x45, 0x43} // 5a Z ,{0x00, 0x7f, 0x41, 0x41, 0x00} // 5b [ ,{0x02, 0x04, 0x08, 0x10, 0x20} // 5c ? ,{0x00, 0x41, 0x41, 0x7f, 0x00} // 5d ] ,{0x04, 0x02, 0x01, 0x02, 0x04} // 5e ^ ,{0x40, 0x40, 0x40, 0x40, 0x40} // 5f _ ,{0x00, 0x01, 0x02, 0x04, 0x00} // 60 ` ,{0x20, 0x54, 0x54, 0x54, 0x78} // 61 a ,{0x7f, 0x48, 0x44, 0x44, 0x38} // 62 b ,{0x38, 0x44, 0x44, 0x44, 0x20} // 63 c ,{0x38, 0x44, 0x44, 0x48, 0x7f} // 64 d ,{0x38, 0x54, 0x54, 0x54, 0x18} // 65 e ,{0x08, 0x7e, 0x09, 0x01, 0x02} // 66 f ,{0x0c, 0x52, 0x52, 0x52, 0x3e} // 67 g ,{0x7f, 0x08, 0x04, 0x04, 0x78} // 68 h ,{0x00, 0x44, 0x7d, 0x40, 0x00} // 69 i ,{0x20, 0x40, 0x44, 0x3d, 0x00} // 6a j ,{0x7f, 0x10, 0x28, 0x44, 0x00} // 6b k ,{0x00, 0x41, 0x7f, 0x40, 0x00} // 6c l ,{0x7c, 0x04, 0x18, 0x04, 0x78} // 6d m ,{0x7c, 0x08, 0x04, 0x04, 0x78} // 6e n ,{0x38, 0x44, 0x44, 0x44, 0x38} // 6f o ,{0x7c, 0x14, 0x14, 0x14, 0x08} // 70 p ,{0x08, 0x14, 0x14, 0x18, 0x7c} // 71 q ,{0x7c, 0x08, 0x04, 0x04, 0x08} // 72 r ,{0x48, 0x54, 0x54, 0x54, 0x20} // 73 s ,{0x04, 0x3f, 0x44, 0x40, 0x20} // 74 t ,{0x3c, 0x40, 0x40, 0x20, 0x7c} // 75 u ,{0x1c, 0x20, 0x40, 0x20, 0x1c} // 76 v ,{0x3c, 0x40, 0x30, 0x40, 0x3c} // 77 w ,{0x44, 0x28, 0x10, 0x28, 0x44} // 78 x ,{0x0c, 0x50, 0x50, 0x50, 0x3c} // 79 y ,{0x44, 0x64, 0x54, 0x4c, 0x44} // 7a z ,{0x00, 0x08, 0x36, 0x41, 0x00} // 7b { ,{0x00, 0x00, 0x7f, 0x00, 0x00} // 7c | ,{0x00, 0x41, 0x36, 0x08, 0x00} // 7d } ,{0x10, 0x08, 0x08, 0x10, 0x08} // 7e < ,{0x78, 0x46, 0x41, 0x46, 0x78} // 7f > }; void pcd8544_init(void); void pcd8544_send(uint8_t dc, uint8_t data); void pcd8544_print_string(char *str); void pcd8544_send_char(uint8_t ch); void pcd8544_clear(void); void pcd8544_set_cursor(uint8_t x, uint8_t y); void pcd8544_print_at(char *str, uint8_t x, uint8_t y); int main(void) { pcd8544_init(); pcd8544_clear(); pcd8544_print_at("Nokia 3310",1,0); pcd8544_print_at("Connecting",1,2); pcd8544_print_at("People",6,3); for (;;){ _delay_ms(1000); } return 0; } void pcd8544_init(void) { // GPIO Setup DDR_PCD8544 |=(1<<PIN_RESET) | (1<<PIN_DC); DDRB |= (1<<PIN_SCE) | (1<<PIN_SDIN) | (1<<PIN_SCLK); // SPI setup SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR0); // master mode, PORT_PCD8544&=~(1<<PIN_RESET); PORT_PCD8544|=(1<<PIN_RESET); pcd8544_send(LCD_C, 0x21 ); // LCD Extended Commands. pcd8544_send(LCD_C, 0xBA ); // Set LCD Vop (Contrast). pcd8544_send(LCD_C, 0x04 ); // Set Temp coefficent. //0x04 pcd8544_send(LCD_C, 0x14 ); // LCD bias mode 1:48. //0x13 pcd8544_send(LCD_C, 0x20 ); // LCD Basic Commands pcd8544_send(LCD_C, 0x0C ); // LCD in normal mode. } void pcd8544_send(uint8_t dc, uint8_t data) { //uint8_t i; if (dc == LCD_D) PORT_PCD8544 |= (1<<PIN_DC); else PORT_PCD8544 &= ~(1<<PIN_DC); PORTB&=~(1<<PIN_SCE); SPDR=data; while(!(SPSR & (1<<SPIF))); PORTB|=(1<<PIN_SCE); } void pcd8544_print_string(char *str) { while (*str) { pcd8544_send_char(*str++); } } void pcd8544_send_char(uint8_t ch) { int i; if (ch >= 0x20 && ch <= 0x80) { pcd8544_send(LCD_D, 0x00); for (i = 0; i < 5; i++) { pcd8544_send(LCD_D, ASCII[ch - 0x20][i]); } pcd8544_send(LCD_D, 0x00); } } void pcd8544_clear(void) { int i; for (i=0; i < LCD_X * LCD_Y / 8; i++) { pcd8544_send(LCD_D, 0x00); } } void pcd8544_set_cursor(uint8_t x, uint8_t y) { x=x%12; y=y%6; pcd8544_send(LCD_C, 0x40+y); pcd8544_send(LCD_C, 0x80+x*7); } void pcd8544_print_at(char *str, uint8_t x, uint8_t y) { pcd8544_set_cursor(x,y); while (*str) { pcd8544_send_char(*str++); } }
Пока отбросим аппаратный SPI в сторону и продолжим работать с программным.
Т.к. дисплей маленький, одной из самых востребованных функций является алгоритмическое увеличение шрифта.
Для увеличения шрифта в два раза добавим такую функцию:
void pcd8544_send_char_size2(uint8_t ch, uint8_t x, uint8_t y) { uint8_t s[5]; // source uint8_t r[20]; // result uint8_t i,j; // get littera if (ch >= 0x20 && ch <= 0x80) { for (i=0; i < 5; i++) { s[i]=ASCII[ch-0x20][i]; } } // scale for(i=0;i<5;i++) { uint8_t b=0; uint8_t a=0; for(j=0;j<4;j++) { b=(s[i]>>j) & 0x01; a|=(b<<(j<<1)) | (b<<((j<<1)+1)); } r[(i<<1)]=a; r[(i<<1)+1]=a; } for(i=0;i<5;i++) { uint8_t b=0; uint8_t a=0; for(j=0;j<4;j++) { b=(s[i]>>(j+4)) & 0x01; a|=(b<<(j<<1)) | (b<<((j<<1)+1)); } r[(i<<1)+10]=a; r[(i<<1)+11]=a; } // print pcd8544_set_cursor(x,y); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); for(i=0;i<10;i++) pcd8544_send(LCD_D, r[i]); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); pcd8544_set_cursor(x,y+1); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); for(i=10;i<20;i++) pcd8544_send(LCD_D, r[i]); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); }
А увеличение в три раза будет делать такая функция:
void pcd8544_send_char_size3(uint8_t ch, uint8_t x, uint8_t y) { uint8_t s[5]; // source uint8_t r[45]; // result uint8_t i; // get littera if (ch >= 0x20 && ch <= 0x80) { for (i=0; i < 5; i++) { s[i]=ASCII[ch-0x20][i]; } } // scale for(i=0;i<5;i++) { uint8_t b,a; b=(s[i] & 0x01); a=(b) ? 0x7 : 0; b=(s[i]>>1) & 0x01; if (b) a|=0x38; b=(s[i]>>2) & 0x01; a|=(b<<6)|(b<<7); r[i*3]=a; r[i*3+1]=a; r[i*3+2]=a; r[i*3+15]=b; r[i*3+16]=b; r[i*3+17]=b; } for(i=0;i<5;i++) { uint8_t b,a; b=(s[i]>>3) & 0x01; a=(b) ? 0x0e : 0; b=(s[i]>>4) & 0x01; if (b) a|=0x70; b=(s[i]>>5) & 0x01; a|=(b<<7); r[i*3+15]|=a; r[i*3+16]|=a; r[i*3+17]|=a; } for(i=0;i<5;i++) { uint8_t b,a; b=(s[i]>>5) & 0x01; a=(b) ? 0x3 : 0; b=(s[i]>>6) & 0x01; if (b) a|=0x1c; b=(s[i]>>7) & 0x01; if (b) a|=0xe0; r[i*3+30]=a; r[i*3+31]=a; r[i*3+32]=a; } // print pcd8544_set_cursor(x,y); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); for(i=0;i<15;i++) pcd8544_send(LCD_D, r[i]); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); pcd8544_set_cursor(x,y+1); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); for(i=15;i<30;i++) pcd8544_send(LCD_D, r[i]); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); pcd8544_set_cursor(x,y+2); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); for(i=30;i<45;i++) pcd8544_send(LCD_D, r[i]); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); }
Чтобы можно было печатать строки нужным размером шрифта, недавно введенную функцию void pcd8544_print_at() приведем к такому виду:
void pcd8544_print_at(char *str, uint8_t size, uint8_t x, uint8_t y) { uint8_t i=0; pcd8544_set_cursor(x,y); switch (size) { case 3: while (*str) { pcd8544_send_char_size3(*str++,x+i,y); i+=3; } break; case 2: while (*str) { pcd8544_send_char_size2(*str++,x+i,y); i+=2; } break; default: while (*str) { pcd8544_send_char(*str++); } break; } }
И последнее, что нам нужно от шрифтов, это печать небольшого целого числа.
void pcd8544_print_uint8_at(uint8_t num, uint8_t size, uint8_t x, uint8_t y){ uint8_t sym[3]; int8_t i=2; do { if (num == 0 && i<2) sym[i]=0x20; // space else sym[i]=0x30+num%10; num=num/10; i--; } while (i>=0); uint8_t j=0; for (i=0;i<3;i++) { if (!(i<2 && sym[i] == 0x20)) { switch(size) { case 3: pcd8544_send_char_size3(sym[i],x+j*size,y); break; case 2: pcd8544_send_char_size2(sym[i],x+j*size,y); break; default: pcd8544_send_char(sym[i]); break; } j++; } }
Для теста, в функции main() сделаем печать шрифта крупным шрифтом:
int main(void) { pcd8544_init(); uint8_t i=0; for (;;){ pcd8544_clear(); pcd8544_print_at(">",3,0,2); pcd8544_print_uint8_at(i++,3,3,2); _delay_ms(1000); } return 0; }
Результат работы, без анимации:
Полный листинг программы под спойлером:
показать полный код#include <avr/io.h> #include <util/delay.h> #define PIN_SCE PD7 #define PIN_RESET PD6 #define PIN_DC PD5 #define PIN_SDIN PD4 #define PIN_SCLK PD3 #define LCD_C 0x00 #define LCD_D 0x01 #define LCD_X 84 #define LCD_Y 48 #define PORT_PCD8544 PORTD #define DDR_PCD8544 DDRD static const uint8_t ASCII[][5] = { {0x00, 0x00, 0x00, 0x00, 0x00} // 20 ,{0x00, 0x00, 0x5f, 0x00, 0x00} // 21 ! ,{0x00, 0x07, 0x00, 0x07, 0x00} // 22 " ,{0x14, 0x7f, 0x14, 0x7f, 0x14} // 23 # ,{0x24, 0x2a, 0x7f, 0x2a, 0x12} // 24 $ ,{0x23, 0x13, 0x08, 0x64, 0x62} // 25 % ,{0x36, 0x49, 0x55, 0x22, 0x50} // 26 & ,{0x00, 0x05, 0x03, 0x00, 0x00} // 27 ' ,{0x00, 0x1c, 0x22, 0x41, 0x00} // 28 ( ,{0x00, 0x41, 0x22, 0x1c, 0x00} // 29 ) ,{0x14, 0x08, 0x3e, 0x08, 0x14} // 2a * ,{0x08, 0x08, 0x3e, 0x08, 0x08} // 2b + ,{0x00, 0x50, 0x30, 0x00, 0x00} // 2c , ,{0x08, 0x08, 0x08, 0x08, 0x08} // 2d - ,{0x00, 0x60, 0x60, 0x00, 0x00} // 2e . ,{0x20, 0x10, 0x08, 0x04, 0x02} // 2f / ,{0x3e, 0x51, 0x49, 0x45, 0x3e} // 30 0 ,{0x00, 0x42, 0x7f, 0x40, 0x00} // 31 1 ,{0x42, 0x61, 0x51, 0x49, 0x46} // 32 2 ,{0x21, 0x41, 0x45, 0x4b, 0x31} // 33 3 ,{0x18, 0x14, 0x12, 0x7f, 0x10} // 34 4 ,{0x27, 0x45, 0x45, 0x45, 0x39} // 35 5 ,{0x3c, 0x4a, 0x49, 0x49, 0x30} // 36 6 ,{0x01, 0x71, 0x09, 0x05, 0x03} // 37 7 ,{0x36, 0x49, 0x49, 0x49, 0x36} // 38 8 ,{0x06, 0x49, 0x49, 0x29, 0x1e} // 39 9 ,{0x00, 0x36, 0x36, 0x00, 0x00} // 3a : ,{0x00, 0x56, 0x36, 0x00, 0x00} // 3b ; ,{0x08, 0x14, 0x22, 0x41, 0x00} // 3c < ,{0x14, 0x14, 0x14, 0x14, 0x14} // 3d = ,{0x00, 0x41, 0x22, 0x14, 0x08} // 3e > ,{0x02, 0x01, 0x51, 0x09, 0x06} // 3f ? ,{0x32, 0x49, 0x79, 0x41, 0x3e} // 40 @ ,{0x7e, 0x11, 0x11, 0x11, 0x7e} // 41 A ,{0x7f, 0x49, 0x49, 0x49, 0x36} // 42 B ,{0x3e, 0x41, 0x41, 0x41, 0x22} // 43 C ,{0x7f, 0x41, 0x41, 0x22, 0x1c} // 44 D ,{0x7f, 0x49, 0x49, 0x49, 0x41} // 45 E ,{0x7f, 0x09, 0x09, 0x09, 0x01} // 46 F ,{0x3e, 0x41, 0x49, 0x49, 0x7a} // 47 G ,{0x7f, 0x08, 0x08, 0x08, 0x7f} // 48 H ,{0x00, 0x41, 0x7f, 0x41, 0x00} // 49 I ,{0x20, 0x40, 0x41, 0x3f, 0x01} // 4a J ,{0x7f, 0x08, 0x14, 0x22, 0x41} // 4b K ,{0x7f, 0x40, 0x40, 0x40, 0x40} // 4c L ,{0x7f, 0x02, 0x0c, 0x02, 0x7f} // 4d M ,{0x7f, 0x04, 0x08, 0x10, 0x7f} // 4e N ,{0x3e, 0x41, 0x41, 0x41, 0x3e} // 4f O ,{0x7f, 0x09, 0x09, 0x09, 0x06} // 50 P ,{0x3e, 0x41, 0x51, 0x21, 0x5e} // 51 Q ,{0x7f, 0x09, 0x19, 0x29, 0x46} // 52 R ,{0x46, 0x49, 0x49, 0x49, 0x31} // 53 S ,{0x01, 0x01, 0x7f, 0x01, 0x01} // 54 T ,{0x3f, 0x40, 0x40, 0x40, 0x3f} // 55 U ,{0x1f, 0x20, 0x40, 0x20, 0x1f} // 56 V ,{0x3f, 0x40, 0x38, 0x40, 0x3f} // 57 W ,{0x63, 0x14, 0x08, 0x14, 0x63} // 58 X ,{0x07, 0x08, 0x70, 0x08, 0x07} // 59 Y ,{0x61, 0x51, 0x49, 0x45, 0x43} // 5a Z ,{0x00, 0x7f, 0x41, 0x41, 0x00} // 5b [ ,{0x02, 0x04, 0x08, 0x10, 0x20} // 5c ? ,{0x00, 0x41, 0x41, 0x7f, 0x00} // 5d ] ,{0x04, 0x02, 0x01, 0x02, 0x04} // 5e ^ ,{0x40, 0x40, 0x40, 0x40, 0x40} // 5f _ ,{0x00, 0x01, 0x02, 0x04, 0x00} // 60 ` ,{0x20, 0x54, 0x54, 0x54, 0x78} // 61 a ,{0x7f, 0x48, 0x44, 0x44, 0x38} // 62 b ,{0x38, 0x44, 0x44, 0x44, 0x20} // 63 c ,{0x38, 0x44, 0x44, 0x48, 0x7f} // 64 d ,{0x38, 0x54, 0x54, 0x54, 0x18} // 65 e ,{0x08, 0x7e, 0x09, 0x01, 0x02} // 66 f ,{0x0c, 0x52, 0x52, 0x52, 0x3e} // 67 g ,{0x7f, 0x08, 0x04, 0x04, 0x78} // 68 h ,{0x00, 0x44, 0x7d, 0x40, 0x00} // 69 i ,{0x20, 0x40, 0x44, 0x3d, 0x00} // 6a j ,{0x7f, 0x10, 0x28, 0x44, 0x00} // 6b k ,{0x00, 0x41, 0x7f, 0x40, 0x00} // 6c l ,{0x7c, 0x04, 0x18, 0x04, 0x78} // 6d m ,{0x7c, 0x08, 0x04, 0x04, 0x78} // 6e n ,{0x38, 0x44, 0x44, 0x44, 0x38} // 6f o ,{0x7c, 0x14, 0x14, 0x14, 0x08} // 70 p ,{0x08, 0x14, 0x14, 0x18, 0x7c} // 71 q ,{0x7c, 0x08, 0x04, 0x04, 0x08} // 72 r ,{0x48, 0x54, 0x54, 0x54, 0x20} // 73 s ,{0x04, 0x3f, 0x44, 0x40, 0x20} // 74 t ,{0x3c, 0x40, 0x40, 0x20, 0x7c} // 75 u ,{0x1c, 0x20, 0x40, 0x20, 0x1c} // 76 v ,{0x3c, 0x40, 0x30, 0x40, 0x3c} // 77 w ,{0x44, 0x28, 0x10, 0x28, 0x44} // 78 x ,{0x0c, 0x50, 0x50, 0x50, 0x3c} // 79 y ,{0x44, 0x64, 0x54, 0x4c, 0x44} // 7a z ,{0x00, 0x08, 0x36, 0x41, 0x00} // 7b { ,{0x00, 0x00, 0x7f, 0x00, 0x00} // 7c | ,{0x00, 0x41, 0x36, 0x08, 0x00} // 7d } ,{0x10, 0x08, 0x08, 0x10, 0x08} // 7e < ,{0x78, 0x46, 0x41, 0x46, 0x78} // 7f > }; void pcd8544_init(void); void pcd8544_send(uint8_t dc, uint8_t data); void pcd8544_print_string(char *str); void pcd8544_send_char(uint8_t ch); void pcd8544_clear(void); void pcd8544_set_cursor(uint8_t x, uint8_t y); void pcd8544_print_at(char *str, uint8_t size, uint8_t x, uint8_t y); void pcd8544_send_char_size2(uint8_t ch, uint8_t x, uint8_t y); void pcd8544_send_char_size3(uint8_t ch, uint8_t x, uint8_t y); void pcd8544_print_uint8_at(uint8_t num, uint8_t size, uint8_t x, uint8_t y); int main(void) { pcd8544_init(); uint8_t i=0; for (;;){ pcd8544_clear(); pcd8544_print_at(">",3,0,2); pcd8544_print_uint8_at(i++,3,3,2); _delay_ms(1000); } return 0; } void pcd8544_init(void) { // GPIO Setup DDR_PCD8544 |= (1<<PIN_SCE) | (1<<PIN_RESET) | (1<<PIN_DC) | (1<<PIN_SDIN) | (1<<PIN_SCLK); PORT_PCD8544&=~(1<<PIN_RESET); PORT_PCD8544|=(1<<PIN_RESET); pcd8544_send(LCD_C, 0x21 ); // LCD Extended Commands. pcd8544_send(LCD_C, 0xBA ); // Set LCD Vop (Contrast). pcd8544_send(LCD_C, 0x04 ); // Set Temp coefficent. //0x04 pcd8544_send(LCD_C, 0x14 ); // LCD bias mode 1:48. //0x13 pcd8544_send(LCD_C, 0x20 ); // LCD Basic Commands pcd8544_send(LCD_C, 0x0C ); // LCD in normal mode. } void pcd8544_send(uint8_t dc, uint8_t data) { uint8_t i; if (dc == LCD_D) PORT_PCD8544 |= (1<<PIN_DC); else PORT_PCD8544 &= ~(1<<PIN_DC); PORT_PCD8544&=~(1<<PIN_SCE); for (i=0; i<8; i++) { PORT_PCD8544=(data & 0x80) ? PORT_PCD8544 | (1<<PIN_SDIN) : PORT_PCD8544 & ~(1<<PIN_SDIN); data=(data<<1); PORT_PCD8544|=(1<<PIN_SCLK); PORT_PCD8544&=~(1<<PIN_SCLK); } PORT_PCD8544|=(1<<PIN_SCE); } void pcd8544_print_string(char *str) { while (*str) { pcd8544_send_char(*str++); } } void pcd8544_send_char(uint8_t ch) { int i; if (ch >= 0x20 && ch <= 0x80) { pcd8544_send(LCD_D, 0x00); for (i = 0; i < 5; i++) { pcd8544_send(LCD_D, ASCII[ch - 0x20][i]); } pcd8544_send(LCD_D, 0x00); } } void pcd8544_clear(void) { int i; for (i=0; i < LCD_X * LCD_Y / 8; i++) { pcd8544_send(LCD_D, 0x00); } } void pcd8544_set_cursor(uint8_t x, uint8_t y) { x=x%12; y=y%6; pcd8544_send(LCD_C, 0x40+y); pcd8544_send(LCD_C, 0x80+x*7); } void pcd8544_print_at(char *str, uint8_t size, uint8_t x, uint8_t y) { uint8_t i=0; pcd8544_set_cursor(x,y); switch (size) { case 3: while (*str) { pcd8544_send_char_size3(*str++,x+i,y); i+=3; } break; case 2: while (*str) { pcd8544_send_char_size2(*str++,x+i,y); i+=2; } break; default: while (*str) { pcd8544_send_char(*str++); } break; } } void pcd8544_send_char_size2(uint8_t ch, uint8_t x, uint8_t y) { uint8_t s[5]; // source uint8_t r[20]; // result uint8_t i,j; // get littera if (ch >= 0x20 && ch <= 0x80) { for (i=0; i < 5; i++) { s[i]=ASCII[ch-0x20][i]; } } // scale for(i=0;i<5;i++) { uint8_t b=0; uint8_t a=0; for(j=0;j<4;j++) { b=(s[i]>>j) & 0x01; a|=(b<<(j<<1)) | (b<<((j<<1)+1)); } r[(i<<1)]=a; r[(i<<1)+1]=a; } for(i=0;i<5;i++) { uint8_t b=0; uint8_t a=0; for(j=0;j<4;j++) { b=(s[i]>>(j+4)) & 0x01; a|=(b<<(j<<1)) | (b<<((j<<1)+1)); } r[(i<<1)+10]=a; r[(i<<1)+11]=a; } // print pcd8544_set_cursor(x,y); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); for(i=0;i<10;i++) pcd8544_send(LCD_D, r[i]); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); pcd8544_set_cursor(x,y+1); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); for(i=10;i<20;i++) pcd8544_send(LCD_D, r[i]); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); } void pcd8544_send_char_size3(uint8_t ch, uint8_t x, uint8_t y) { uint8_t s[5]; // source uint8_t r[45]; // result uint8_t i; // get littera if (ch >= 0x20 && ch <= 0x80) { for (i=0; i < 5; i++) { s[i]=ASCII[ch-0x20][i]; } } // scale for(i=0;i<5;i++) { uint8_t b,a; b=(s[i] & 0x01); a=(b) ? 0x7 : 0; b=(s[i]>>1) & 0x01; if (b) a|=0x38; b=(s[i]>>2) & 0x01; a|=(b<<6)|(b<<7); r[i*3]=a; r[i*3+1]=a; r[i*3+2]=a; r[i*3+15]=b; r[i*3+16]=b; r[i*3+17]=b; } for(i=0;i<5;i++) { uint8_t b,a; b=(s[i]>>3) & 0x01; a=(b) ? 0x0e : 0; b=(s[i]>>4) & 0x01; if (b) a|=0x70; b=(s[i]>>5) & 0x01; a|=(b<<7); r[i*3+15]|=a; r[i*3+16]|=a; r[i*3+17]|=a; } for(i=0;i<5;i++) { uint8_t b,a; b=(s[i]>>5) & 0x01; a=(b) ? 0x3 : 0; b=(s[i]>>6) & 0x01; if (b) a|=0x1c; b=(s[i]>>7) & 0x01; if (b) a|=0xe0; r[i*3+30]=a; r[i*3+31]=a; r[i*3+32]=a; } // print pcd8544_set_cursor(x,y); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); for(i=0;i<15;i++) pcd8544_send(LCD_D, r[i]); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); pcd8544_set_cursor(x,y+1); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); for(i=15;i<30;i++) pcd8544_send(LCD_D, r[i]); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); pcd8544_set_cursor(x,y+2); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); for(i=30;i<45;i++) pcd8544_send(LCD_D, r[i]); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); } void pcd8544_print_uint8_at(uint8_t num, uint8_t size, uint8_t x, uint8_t y){ uint8_t sym[3]; int8_t i=2; do { if (num == 0 && i<2) sym[i]=0x20; // space else sym[i]=0x30+num%10; num=num/10; i--; } while (i>=0); uint8_t j=0; for (i=0;i<3;i++) { if (!(i<2 && sym[i] == 0x20)) { switch(size) { case 3: pcd8544_send_char_size3(sym[i],x+j*size,y); break; case 2: pcd8544_send_char_size2(sym[i],x+j*size,y); break; default: pcd8544_send_char(sym[i]); break; } j++; } } }
К сожалению, это все на был способен Proteus, поэтому переходим к реальному железу. Сейчас мы создадим проект и заодно перепишем код на хранение ascii-таблицы в флеш-памяти, с последующим чтением ее оттуда. Так следовало бы сделать с самого начала, если бы не Proteus :(
Итак, предполагаем, что используется Linux или CYGWIN в случае с Windows. Т.к. работа с флеш-памятью может меняться в зависимости от то ли версии avr-gcc то ли версии avr-libc, скажу что avr-libc у меня версии 1.8.1, а версия avr-gcc:
$ avr-gcc --version avr-gcc (GCC) 4.9.2 Copyright (C) 2014 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Создаем структуру каталогов будущего проекта:
$ mkdir -p ./06_flash_ascii/{inc,src}
Заходим:
$ cd ./06_flash_ascii
Создаем Makefile такого содержания:
MCU=atmega8 OBJCOPY=avr-objcopy CC=avr-gcc CFLAGS=-mmcu=$(MCU) -Os -fdata-sections -ffunction-sections -DF_CPU=16000000UL -Wall -I ./inc LDFLAGS=-mmcu=$(MCU) -Wl,--gc-sections -Werror OBJ=main.o pcd8544_soft.o TARGET=lcd .PHONY: all install clean %.o: ./src/%.c $(CC) -c -o $@ $< $(CFLAGS) all: $(OBJ) $(CC) $(LDFLAGS) -o $(TARGET).elf $(OBJ) $(OBJCOPY) -O ihex $(TARGET).elf $(TARGET).hex install: avrdude -p$(MCU) -c usbasp -p m8 -U flash:w:./$(TARGET).hex:i clean: @rm -v *.elf *.hex $(OBJ)
Не забывайте, что в Makefile отступы формируются табуляцией, а не пробелами, как будет если скопировать из браузера(ну нету в HTML табуляции).
Теперь создадим файл ./inc/ascii.h с нашей табличкой:
показать ./inc/ascii.h#ifndef __ASCII_H__ #define __ASCII_H__ #include <avr/pgmspace.h> const uint8_t ASCII[][5] PROGMEM = { {0x00, 0x00, 0x00, 0x00, 0x00} // 20 ,{0x00, 0x00, 0x5f, 0x00, 0x00} // 21 ! ,{0x00, 0x07, 0x00, 0x07, 0x00} // 22 " ,{0x14, 0x7f, 0x14, 0x7f, 0x14} // 23 # ,{0x24, 0x2a, 0x7f, 0x2a, 0x12} // 24 $ ,{0x23, 0x13, 0x08, 0x64, 0x62} // 25 % ,{0x36, 0x49, 0x55, 0x22, 0x50} // 26 & ,{0x00, 0x05, 0x03, 0x00, 0x00} // 27 ' ,{0x00, 0x1c, 0x22, 0x41, 0x00} // 28 ( ,{0x00, 0x41, 0x22, 0x1c, 0x00} // 29 ) ,{0x14, 0x08, 0x3e, 0x08, 0x14} // 2a * ,{0x08, 0x08, 0x3e, 0x08, 0x08} // 2b + ,{0x00, 0x50, 0x30, 0x00, 0x00} // 2c , ,{0x08, 0x08, 0x08, 0x08, 0x08} // 2d - ,{0x00, 0x60, 0x60, 0x00, 0x00} // 2e . ,{0x20, 0x10, 0x08, 0x04, 0x02} // 2f / ,{0x3e, 0x51, 0x49, 0x45, 0x3e} // 30 0 ,{0x00, 0x42, 0x7f, 0x40, 0x00} // 31 1 ,{0x42, 0x61, 0x51, 0x49, 0x46} // 32 2 ,{0x21, 0x41, 0x45, 0x4b, 0x31} // 33 3 ,{0x18, 0x14, 0x12, 0x7f, 0x10} // 34 4 ,{0x27, 0x45, 0x45, 0x45, 0x39} // 35 5 ,{0x3c, 0x4a, 0x49, 0x49, 0x30} // 36 6 ,{0x01, 0x71, 0x09, 0x05, 0x03} // 37 7 ,{0x36, 0x49, 0x49, 0x49, 0x36} // 38 8 ,{0x06, 0x49, 0x49, 0x29, 0x1e} // 39 9 ,{0x00, 0x36, 0x36, 0x00, 0x00} // 3a : ,{0x00, 0x56, 0x36, 0x00, 0x00} // 3b ; ,{0x08, 0x14, 0x22, 0x41, 0x00} // 3c < ,{0x14, 0x14, 0x14, 0x14, 0x14} // 3d = ,{0x00, 0x41, 0x22, 0x14, 0x08} // 3e > ,{0x02, 0x01, 0x51, 0x09, 0x06} // 3f ? ,{0x32, 0x49, 0x79, 0x41, 0x3e} // 40 @ ,{0x7e, 0x11, 0x11, 0x11, 0x7e} // 41 A ,{0x7f, 0x49, 0x49, 0x49, 0x36} // 42 B ,{0x3e, 0x41, 0x41, 0x41, 0x22} // 43 C ,{0x7f, 0x41, 0x41, 0x22, 0x1c} // 44 D ,{0x7f, 0x49, 0x49, 0x49, 0x41} // 45 E ,{0x7f, 0x09, 0x09, 0x09, 0x01} // 46 F ,{0x3e, 0x41, 0x49, 0x49, 0x7a} // 47 G ,{0x7f, 0x08, 0x08, 0x08, 0x7f} // 48 H ,{0x00, 0x41, 0x7f, 0x41, 0x00} // 49 I ,{0x20, 0x40, 0x41, 0x3f, 0x01} // 4a J ,{0x7f, 0x08, 0x14, 0x22, 0x41} // 4b K ,{0x7f, 0x40, 0x40, 0x40, 0x40} // 4c L ,{0x7f, 0x02, 0x0c, 0x02, 0x7f} // 4d M ,{0x7f, 0x04, 0x08, 0x10, 0x7f} // 4e N ,{0x3e, 0x41, 0x41, 0x41, 0x3e} // 4f O ,{0x7f, 0x09, 0x09, 0x09, 0x06} // 50 P ,{0x3e, 0x41, 0x51, 0x21, 0x5e} // 51 Q ,{0x7f, 0x09, 0x19, 0x29, 0x46} // 52 R ,{0x46, 0x49, 0x49, 0x49, 0x31} // 53 S ,{0x01, 0x01, 0x7f, 0x01, 0x01} // 54 T ,{0x3f, 0x40, 0x40, 0x40, 0x3f} // 55 U ,{0x1f, 0x20, 0x40, 0x20, 0x1f} // 56 V ,{0x3f, 0x40, 0x38, 0x40, 0x3f} // 57 W ,{0x63, 0x14, 0x08, 0x14, 0x63} // 58 X ,{0x07, 0x08, 0x70, 0x08, 0x07} // 59 Y ,{0x61, 0x51, 0x49, 0x45, 0x43} // 5a Z ,{0x00, 0x7f, 0x41, 0x41, 0x00} // 5b [ ,{0x02, 0x04, 0x08, 0x10, 0x20} // 5c ¥ ,{0x00, 0x41, 0x41, 0x7f, 0x00} // 5d ] ,{0x04, 0x02, 0x01, 0x02, 0x04} // 5e ^ ,{0x40, 0x40, 0x40, 0x40, 0x40} // 5f _ ,{0x00, 0x01, 0x02, 0x04, 0x00} // 60 ` ,{0x20, 0x54, 0x54, 0x54, 0x78} // 61 a ,{0x7f, 0x48, 0x44, 0x44, 0x38} // 62 b ,{0x38, 0x44, 0x44, 0x44, 0x20} // 63 c ,{0x38, 0x44, 0x44, 0x48, 0x7f} // 64 d ,{0x38, 0x54, 0x54, 0x54, 0x18} // 65 e ,{0x08, 0x7e, 0x09, 0x01, 0x02} // 66 f ,{0x0c, 0x52, 0x52, 0x52, 0x3e} // 67 g ,{0x7f, 0x08, 0x04, 0x04, 0x78} // 68 h ,{0x00, 0x44, 0x7d, 0x40, 0x00} // 69 i ,{0x20, 0x40, 0x44, 0x3d, 0x00} // 6a j ,{0x7f, 0x10, 0x28, 0x44, 0x00} // 6b k ,{0x00, 0x41, 0x7f, 0x40, 0x00} // 6c l ,{0x7c, 0x04, 0x18, 0x04, 0x78} // 6d m ,{0x7c, 0x08, 0x04, 0x04, 0x78} // 6e n ,{0x38, 0x44, 0x44, 0x44, 0x38} // 6f o ,{0x7c, 0x14, 0x14, 0x14, 0x08} // 70 p ,{0x08, 0x14, 0x14, 0x18, 0x7c} // 71 q ,{0x7c, 0x08, 0x04, 0x04, 0x08} // 72 r ,{0x48, 0x54, 0x54, 0x54, 0x20} // 73 s ,{0x04, 0x3f, 0x44, 0x40, 0x20} // 74 t ,{0x3c, 0x40, 0x40, 0x20, 0x7c} // 75 u ,{0x1c, 0x20, 0x40, 0x20, 0x1c} // 76 v ,{0x3c, 0x40, 0x30, 0x40, 0x3c} // 77 w ,{0x44, 0x28, 0x10, 0x28, 0x44} // 78 x ,{0x0c, 0x50, 0x50, 0x50, 0x3c} // 79 y ,{0x44, 0x64, 0x54, 0x4c, 0x44} // 7a z ,{0x00, 0x08, 0x36, 0x41, 0x00} // 7b { ,{0x00, 0x00, 0x7f, 0x00, 0x00} // 7c | ,{0x00, 0x41, 0x36, 0x08, 0x00} // 7d } ,{0x10, 0x08, 0x08, 0x10, 0x08} // 7e ← ,{0x78, 0x46, 0x41, 0x46, 0x78} // 7f → }; #endif
А также заголовочный файл библиотеки pcd8544 ./scr/pcd8544_soft.h
#ifndef __PCD8544_SOFT_H__ #define __PCD8544_SOFT_H__ #define PIN_SCE PD7 #define PIN_RESET PD6 #define PIN_DC PD5 #define PIN_SDIN PD4 #define PIN_SCLK PD3 #define LCD_C 0x00 #define LCD_D 0x01 #define LCD_X 84 #define LCD_Y 48 #define PORT_PCD8544 PORTD #define DDR_PCD8544 DDRD extern void pcd8544_init(void); extern void pcd8544_send(uint8_t dc, uint8_t data); extern void pcd8544_print_string(char *str); extern void pcd8544_send_char(uint8_t ch); extern void pcd8544_clear(void); extern void pcd8544_set_cursor(uint8_t x, uint8_t y); extern void pcd8544_print_at(char *str, uint8_t size, uint8_t x, uint8_t y); extern void pcd8544_send_char_size2(uint8_t ch, uint8_t x, uint8_t y); extern void pcd8544_send_char_size3(uint8_t ch, uint8_t x, uint8_t y); extern void pcd8544_print_uint8_at(uint8_t num, uint8_t size, uint8_t x, uint8_t y); #endif
В функции pcd8544_send_char(uint8_t ch):
void pcd8544_send_char(uint8_t ch) { int i; if (ch >= 0x20 && ch <= 0x80) { pcd8544_send(LCD_D, 0x00); for (i = 0; i < 5; i++) { pcd8544_send(LCD_D, ASCII[ch - 0x20][i]); } pcd8544_send(LCD_D, 0x00); } }
- нужно будет добавить чтение из флеш-памяти(тема рассматривалась здесь)
void pcd8544_send_char(uint8_t ch) { int i; char * ptr= (char *)(&ASCII); if (ch >= 0x20 && ch <= 0x80) { pcd8544_send(LCD_D, 0x00); for (i = 0; i < 5; i++) { uint8_t c=ch - 0x20; c=pgm_read_byte(ptr+c*5+i); pcd8544_send(LCD_D, c); } pcd8544_send(LCD_D, 0x00); } }
Тоже самое нужно будет сделать в функциях void pcd8544_send_char_size2(uint8_t ch, uint8_t x, uint8_t y) и void pcd8544_send_char_size3(uint8_t ch, uint8_t x, uint8_t y).
Все функции мы скинем в библиотечный файл /src/pcd8544_soft.c
показать /src/pcd8544_soft.c#include "ascii.h" #include "pcd8544_soft.h" void pcd8544_init(void) { // GPIO Setup DDR_PCD8544 |= (1<<PIN_SCE) | (1<<PIN_RESET) | (1<<PIN_DC) | (1<<PIN_SDIN) | (1<<PIN_SCLK); PORT_PCD8544&=~(1<<PIN_RESET); PORT_PCD8544|=(1<<PIN_RESET); pcd8544_send(LCD_C, 0x21 ); // LCD Extended Commands. pcd8544_send(LCD_C, 0xBA ); // Set LCD Vop (Contrast). pcd8544_send(LCD_C, 0x04 ); // Set Temp coefficent. //0x04 pcd8544_send(LCD_C, 0x14 ); // LCD bias mode 1:48. //0x13 pcd8544_send(LCD_C, 0x20 ); // LCD Basic Commands pcd8544_send(LCD_C, 0x0C ); // LCD in normal mode. } void pcd8544_send(uint8_t dc, uint8_t data) { uint8_t i; if (dc == LCD_D) PORT_PCD8544 |= (1<<PIN_DC); else PORT_PCD8544 &= ~(1<<PIN_DC); PORT_PCD8544&=~(1<<PIN_SCE); for (i=0; i<8; i++) { PORT_PCD8544=(data & 0x80) ? PORT_PCD8544 | (1<<PIN_SDIN) : PORT_PCD8544 & ~(1<<PIN_SDIN); data=(data<<1); PORT_PCD8544|=(1<<PIN_SCLK); PORT_PCD8544&=~(1<<PIN_SCLK); } PORT_PCD8544|=(1<<PIN_SCE); } void pcd8544_print_string(char *str) { while (*str) { pcd8544_send_char(*str++); } } void pcd8544_send_char(uint8_t ch) { int i; char * ptr= (char *)(&ASCII); if (ch >= 0x20 && ch <= 0x80) { pcd8544_send(LCD_D, 0x00); for (i = 0; i < 5; i++) { uint8_t c=ch - 0x20; c=pgm_read_byte(ptr+c*5+i); pcd8544_send(LCD_D, c); } pcd8544_send(LCD_D, 0x00); } } void pcd8544_clear(void) { int i; for (i=0; i < LCD_X * LCD_Y / 8; i++) { pcd8544_send(LCD_D, 0x00); } } void pcd8544_set_cursor(uint8_t x, uint8_t y) { x=x%12; y=y%6; pcd8544_send(LCD_C, 0x40+y); pcd8544_send(LCD_C, 0x80+x*7); } void pcd8544_print_at(char *str, uint8_t size, uint8_t x, uint8_t y) { uint8_t i=0; pcd8544_set_cursor(x,y); switch (size) { case 3: while (*str) { pcd8544_send_char_size3(*str++,x+i,y); i+=3; } break; case 2: while (*str) { pcd8544_send_char_size2(*str++,x+i,y); i+=2; } break; default: while (*str) { pcd8544_send_char(*str++); } break; } } void pcd8544_send_char_size2(uint8_t ch, uint8_t x, uint8_t y) { uint8_t s[5]; // source uint8_t r[20]; // result uint8_t i,j; // get littera char * ptr= (char *)(&ASCII); if (ch >= 0x20 && ch <= 0x80) { for (i=0; i < 5; i++) { uint8_t c=ch - 0x20; s[i]=pgm_read_byte(ptr+c*5+i); } } // scale for(i=0;i<5;i++) { uint8_t b=0; uint8_t a=0; for(j=0;j<4;j++) { b=(s[i]>>j) & 0x01; a|=(b<<(j<<1)) | (b<<((j<<1)+1)); } r[(i<<1)]=a; r[(i<<1)+1]=a; } for(i=0;i<5;i++) { uint8_t b=0; uint8_t a=0; for(j=0;j<4;j++) { b=(s[i]>>(j+4)) & 0x01; a|=(b<<(j<<1)) | (b<<((j<<1)+1)); } r[(i<<1)+10]=a; r[(i<<1)+11]=a; } // print pcd8544_set_cursor(x,y); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); for(i=0;i<10;i++) pcd8544_send(LCD_D, r[i]); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); pcd8544_set_cursor(x,y+1); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); for(i=10;i<20;i++) pcd8544_send(LCD_D, r[i]); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); } void pcd8544_send_char_size3(uint8_t ch, uint8_t x, uint8_t y) { uint8_t s[5]; // source uint8_t r[45]; // result uint8_t i; // get littera char * ptr= (char *)(&ASCII); if (ch >= 0x20 && ch <= 0x80) { for (i=0; i < 5; i++) { uint8_t c=ch - 0x20; s[i]=pgm_read_byte(ptr+c*5+i); } } // scale for(i=0;i<5;i++) { uint8_t b,a; b=(s[i] & 0x01); a=(b) ? 0x7 : 0; b=(s[i]>>1) & 0x01; if (b) a|=0x38; b=(s[i]>>2) & 0x01; a|=(b<<6)|(b<<7); r[i*3]=a; r[i*3+1]=a; r[i*3+2]=a; r[i*3+15]=b; r[i*3+16]=b; r[i*3+17]=b; } for(i=0;i<5;i++) { uint8_t b,a; b=(s[i]>>3) & 0x01; a=(b) ? 0x0e : 0; b=(s[i]>>4) & 0x01; if (b) a|=0x70; b=(s[i]>>5) & 0x01; a|=(b<<7); r[i*3+15]|=a; r[i*3+16]|=a; r[i*3+17]|=a; } for(i=0;i<5;i++) { uint8_t b,a; b=(s[i]>>5) & 0x01; a=(b) ? 0x3 : 0; b=(s[i]>>6) & 0x01; if (b) a|=0x1c; b=(s[i]>>7) & 0x01; if (b) a|=0xe0; r[i*3+30]=a; r[i*3+31]=a; r[i*3+32]=a; } // print pcd8544_set_cursor(x,y); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); for(i=0;i<15;i++) pcd8544_send(LCD_D, r[i]); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); pcd8544_set_cursor(x,y+1); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); for(i=15;i<30;i++) pcd8544_send(LCD_D, r[i]); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); pcd8544_set_cursor(x,y+2); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); for(i=30;i<45;i++) pcd8544_send(LCD_D, r[i]); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); pcd8544_send(LCD_D, 0x00); } void pcd8544_print_uint8_at(uint8_t num, uint8_t size, uint8_t x, uint8_t y){ uint8_t sym[3]; int8_t i=2; do { if (num == 0 && i<2) sym[i]=0x20; // space else sym[i]=0x30+num%10; num=num/10; i--; } while (i>=0); uint8_t j=0; for (i=0;i<3;i++) { if (!(i<2 && sym[i] == 0x20)) { switch(size) { case 3: pcd8544_send_char_size3(sym[i],x+j*size,y); break; case 2: pcd8544_send_char_size2(sym[i],x+j*size,y); break; default: pcd8544_send_char(sym[i]); break; } j++; } } }
В итоге остается непосредственно сама программа в main.c
#include "avr/io.h" #include "util/delay.h" #include "pcd8544_soft.h" int main(void) { pcd8544_init(); uint8_t i=0; for (;;){ pcd8544_clear(); pcd8544_print_at(">",3,0,2); pcd8544_print_uint8_at(i++,3,3,2); _delay_ms(1000); } return 0; }
По-моему гораздо, лучше, чем все в одном файле.
Результат работы:
Полные исходники к этому примеру можно посмотреть здесь:
https://gitlab.com/flank1er/pcd8544_atmega8/tree/master/06_flash_ascii
Сложность с выводом русского текста на экран графического дисплея заключается в том, что тексты программ мы пишем в двухбайтовом юникоде. Не будем же вы кодировку юникода записывать в микроконтроллер? Поэтому вспоминаются былинные 8-битные кодировки: cp866, koi8-r и cp1251. И значит возникает необходимость преобразования одной кодировки в другую. Доходит до того, что это преобразование пытаются выполнять на стороне микроконтроллера. Нда..
Я выбрал кодировку CP866 из-за того, что там есть псевдографика. В свое время с ее использованием много чего делали: Лексикон, Дос Навигатор, Нортон Командер и т.д. Однако пока псевдографику трогать не будем, а будем заниматься непосредственно кириллицей. Кодовую таблицу CP866 можно посмотреть например здесь Таблица CP 866 DOS — Kvodo
Обращаю внимание, что строчными буквами "п" и "р" имеется разрыв на 48 символа псевдографики. В своей ASCII таблице мы все символы запишем единым массивом, соответственно индекс символа нужно будет корректировать.
Это значит, что вместо строки:
uint8_t c=ch - 0x20;
нужно будет использовать:
uint8_t c=(ch<0xe0) ? ch - 0x20 : ch - 0x50;
Для преобразования кодировок, в Makefile добавим новую цель:
prepare: cat main.c|iconv -f utf8 -t cp866 > cp866.c
Теперь для того чтобы перекодировать и сразу скомпилировать программу достаточно будет ввести команду:
$ make prepare && make all
Осталось табличный файл c ASCII дополнить русскими буквами:
показать /inc/ascii.h#ifndef __ASCII_H__ #define __ASCII_H__ #include <avr/pgmspace.h> // 96x5 = 480 bytes const uint8_t ASCII[][5] PROGMEM = { {0x00, 0x00, 0x00, 0x00, 0x00} // 20 ,{0x00, 0x00, 0x5f, 0x00, 0x00} // 21 ! ,{0x00, 0x07, 0x00, 0x07, 0x00} // 22 " ,{0x14, 0x7f, 0x14, 0x7f, 0x14} // 23 # ,{0x24, 0x2a, 0x7f, 0x2a, 0x12} // 24 $ ,{0x23, 0x13, 0x08, 0x64, 0x62} // 25 % ,{0x36, 0x49, 0x55, 0x22, 0x50} // 26 & ,{0x00, 0x05, 0x03, 0x00, 0x00} // 27 ' ,{0x00, 0x1c, 0x22, 0x41, 0x00} // 28 ( ,{0x00, 0x41, 0x22, 0x1c, 0x00} // 29 ) ,{0x14, 0x08, 0x3e, 0x08, 0x14} // 2a * ,{0x08, 0x08, 0x3e, 0x08, 0x08} // 2b + ,{0x00, 0x50, 0x30, 0x00, 0x00} // 2c , ,{0x08, 0x08, 0x08, 0x08, 0x08} // 2d - ,{0x00, 0x60, 0x60, 0x00, 0x00} // 2e . ,{0x20, 0x10, 0x08, 0x04, 0x02} // 2f / ,{0x3e, 0x51, 0x49, 0x45, 0x3e} // 30 0 ,{0x00, 0x42, 0x7f, 0x40, 0x00} // 31 1 ,{0x42, 0x61, 0x51, 0x49, 0x46} // 32 2 ,{0x21, 0x41, 0x45, 0x4b, 0x31} // 33 3 ,{0x18, 0x14, 0x12, 0x7f, 0x10} // 34 4 ,{0x27, 0x45, 0x45, 0x45, 0x39} // 35 5 ,{0x3c, 0x4a, 0x49, 0x49, 0x30} // 36 6 ,{0x01, 0x71, 0x09, 0x05, 0x03} // 37 7 ,{0x36, 0x49, 0x49, 0x49, 0x36} // 38 8 ,{0x06, 0x49, 0x49, 0x29, 0x1e} // 39 9 ,{0x00, 0x36, 0x36, 0x00, 0x00} // 3a : ,{0x00, 0x56, 0x36, 0x00, 0x00} // 3b ; ,{0x08, 0x14, 0x22, 0x41, 0x00} // 3c < ,{0x14, 0x14, 0x14, 0x14, 0x14} // 3d = ,{0x00, 0x41, 0x22, 0x14, 0x08} // 3e > ,{0x02, 0x01, 0x51, 0x09, 0x06} // 3f ? ,{0x32, 0x49, 0x79, 0x41, 0x3e} // 40 @ ,{0x7e, 0x11, 0x11, 0x11, 0x7e} // 41 A ,{0x7f, 0x49, 0x49, 0x49, 0x36} // 42 B ,{0x3e, 0x41, 0x41, 0x41, 0x22} // 43 C ,{0x7f, 0x41, 0x41, 0x22, 0x1c} // 44 D ,{0x7f, 0x49, 0x49, 0x49, 0x41} // 45 E ,{0x7f, 0x09, 0x09, 0x09, 0x01} // 46 F ,{0x3e, 0x41, 0x49, 0x49, 0x7a} // 47 G ,{0x7f, 0x08, 0x08, 0x08, 0x7f} // 48 H ,{0x00, 0x41, 0x7f, 0x41, 0x00} // 49 I ,{0x20, 0x40, 0x41, 0x3f, 0x01} // 4a J ,{0x7f, 0x08, 0x14, 0x22, 0x41} // 4b K ,{0x7f, 0x40, 0x40, 0x40, 0x40} // 4c L ,{0x7f, 0x02, 0x0c, 0x02, 0x7f} // 4d M ,{0x7f, 0x04, 0x08, 0x10, 0x7f} // 4e N ,{0x3e, 0x41, 0x41, 0x41, 0x3e} // 4f O ,{0x7f, 0x09, 0x09, 0x09, 0x06} // 50 P ,{0x3e, 0x41, 0x51, 0x21, 0x5e} // 51 Q ,{0x7f, 0x09, 0x19, 0x29, 0x46} // 52 R ,{0x46, 0x49, 0x49, 0x49, 0x31} // 53 S ,{0x01, 0x01, 0x7f, 0x01, 0x01} // 54 T ,{0x3f, 0x40, 0x40, 0x40, 0x3f} // 55 U ,{0x1f, 0x20, 0x40, 0x20, 0x1f} // 56 V ,{0x3f, 0x40, 0x38, 0x40, 0x3f} // 57 W ,{0x63, 0x14, 0x08, 0x14, 0x63} // 58 X ,{0x07, 0x08, 0x70, 0x08, 0x07} // 59 Y ,{0x61, 0x51, 0x49, 0x45, 0x43} // 5a Z ,{0x00, 0x7f, 0x41, 0x41, 0x00} // 5b [ ,{0x02, 0x04, 0x08, 0x10, 0x20} // 5c ¥ ,{0x00, 0x41, 0x41, 0x7f, 0x00} // 5d ] ,{0x04, 0x02, 0x01, 0x02, 0x04} // 5e ^ ,{0x40, 0x40, 0x40, 0x40, 0x40} // 5f _ ,{0x00, 0x01, 0x02, 0x04, 0x00} // 60 ` ,{0x20, 0x54, 0x54, 0x54, 0x78} // 61 a ,{0x7f, 0x48, 0x44, 0x44, 0x38} // 62 b ,{0x38, 0x44, 0x44, 0x44, 0x20} // 63 c ,{0x38, 0x44, 0x44, 0x48, 0x7f} // 64 d ,{0x38, 0x54, 0x54, 0x54, 0x18} // 65 e ,{0x08, 0x7e, 0x09, 0x01, 0x02} // 66 f ,{0x0c, 0x52, 0x52, 0x52, 0x3e} // 67 g ,{0x7f, 0x08, 0x04, 0x04, 0x78} // 68 h ,{0x00, 0x44, 0x7d, 0x40, 0x00} // 69 i ,{0x20, 0x40, 0x44, 0x3d, 0x00} // 6a j ,{0x7f, 0x10, 0x28, 0x44, 0x00} // 6b k ,{0x00, 0x41, 0x7f, 0x40, 0x00} // 6c l ,{0x7c, 0x04, 0x18, 0x04, 0x78} // 6d m ,{0x7c, 0x08, 0x04, 0x04, 0x78} // 6e n ,{0x38, 0x44, 0x44, 0x44, 0x38} // 6f o ,{0x7c, 0x14, 0x14, 0x14, 0x08} // 70 p ,{0x08, 0x14, 0x14, 0x18, 0x7c} // 71 q ,{0x7c, 0x08, 0x04, 0x04, 0x08} // 72 r ,{0x48, 0x54, 0x54, 0x54, 0x20} // 73 s ,{0x04, 0x3f, 0x44, 0x40, 0x20} // 74 t ,{0x3c, 0x40, 0x40, 0x20, 0x7c} // 75 u ,{0x1c, 0x20, 0x40, 0x20, 0x1c} // 76 v ,{0x3c, 0x40, 0x30, 0x40, 0x3c} // 77 w ,{0x44, 0x28, 0x10, 0x28, 0x44} // 78 x ,{0x0c, 0x50, 0x50, 0x50, 0x3c} // 79 y ,{0x44, 0x64, 0x54, 0x4c, 0x44} // 7a z ,{0x00, 0x08, 0x36, 0x41, 0x00} // 7b { ,{0x00, 0x00, 0x7f, 0x00, 0x00} // 7c | ,{0x00, 0x41, 0x36, 0x08, 0x00} // 7d } ,{0x10, 0x08, 0x08, 0x10, 0x08} // 7e ← ,{0x78, 0x46, 0x41, 0x46, 0x78} // 7f → ,{0x7e, 0x11, 0x11, 0x11, 0x7e}//A 0x80 ,{0x7f, 0x49, 0x49, 0x49, 0x33}//Б 0x81 ,{0x7f, 0x49, 0x49, 0x49, 0x36}//В 0x82 ,{0x7f, 0x01, 0x01, 0x01, 0x03}//Г 0x83 ,{0xe0, 0x51, 0x4f, 0x41, 0xff}//Д 0x84 ,{0x7f, 0x49, 0x49, 0x49, 0x41}//E 0x85 ,{0x77, 0x08, 0x7f, 0x08, 0x77}//Ж 0x86 ,{0x41, 0x49, 0x49, 0x49, 0x36}//З 0x87 ,{0x7f, 0x10, 0x08, 0x04, 0x7f}//И 0x88 ,{0x7c, 0x21, 0x12, 0x09, 0x7c}//Й 0x89 ,{0x7f, 0x08, 0x14, 0x22, 0x41}//K 0x8A ,{0x20, 0x41, 0x3f, 0x01, 0x7f}//Л 0x8B ,{0x7f, 0x02, 0x0c, 0x02, 0x7f}//M 0x8C ,{0x7f, 0x08, 0x08, 0x08, 0x7f}//H 0x8D ,{0x3e, 0x41, 0x41, 0x41, 0x3e}//O 0x8E ,{0x7f, 0x01, 0x01, 0x01, 0x7f}//П 0x8F ,{0x7f, 0x09, 0x09, 0x09, 0x06}//P 0x90 ,{0x3e, 0x41, 0x41, 0x41, 0x22}//C 0x91 ,{0x01, 0x01, 0x7f, 0x01, 0x01}//T 0x92 ,{0x47, 0x28, 0x10, 0x08, 0x07}//У 0x93 ,{0x1c, 0x22, 0x7f, 0x22, 0x1c}//Ф 0x94 ,{0x63, 0x14, 0x08, 0x14, 0x63}//X 0x95 ,{0x7f, 0x40, 0x40, 0x40, 0xff}//Ц 0x96 ,{0x07, 0x08, 0x08, 0x08, 0x7f}//Ч 0x97 ,{0x7f, 0x40, 0x7f, 0x40, 0x7f}//Ш 0x98 ,{0x7f, 0x40, 0x7f, 0x40, 0xff}//Щ 0x99 ,{0x01, 0x7f, 0x48, 0x48, 0x30}//Ъ 0x9A ,{0x7f, 0x48, 0x30, 0x00, 0x7f}//Ы 0x9B ,{0x00, 0x7f, 0x48, 0x48, 0x30}//Э 0x9C ,{0x22, 0x41, 0x49, 0x49, 0x3e}//Ь 0x9D ,{0x7f, 0x08, 0x3e, 0x41, 0x3e}//Ю 0x9E ,{0x46, 0x29, 0x19, 0x09, 0x7f}//Я 0x9F ,{0x20, 0x54, 0x54, 0x54, 0x78}//a 0xA0 ,{0x3c, 0x4a, 0x4a, 0x49, 0x31}//б 0xA1 ,{0x7c, 0x54, 0x54, 0x28, 0x00}//в 0xA2 ,{0x7c, 0x04, 0x04, 0x04, 0x0c}//г 0xA3 ,{0xe0, 0x54, 0x4c, 0x44, 0xfc}//д 0xA4 ,{0x38, 0x54, 0x54, 0x54, 0x18}//e 0xA5 ,{0x6c, 0x10, 0x7c, 0x10, 0x6c}//ж 0xA6 ,{0x44, 0x44, 0x54, 0x54, 0x28}//з 0xA7 ,{0x7c, 0x20, 0x10, 0x08, 0x7c}//и 0xA8 ,{0x7c, 0x41, 0x22, 0x11, 0x7c}//й 0xA9 ,{0x7c, 0x10, 0x28, 0x44, 0x00}//к 0xAA ,{0x20, 0x44, 0x3c, 0x04, 0x7c}//л 0xAB ,{0x7c, 0x08, 0x10, 0x08, 0x7c}//м 0xAC ,{0x7c, 0x10, 0x10, 0x10, 0x7c}//н 0xAD ,{0x38, 0x44, 0x44, 0x44, 0x38}//o 0xAE ,{0x7c, 0x04, 0x04, 0x04, 0x7c}//п 0xAF ,{0x7C, 0x14, 0x14, 0x14, 0x08}//p 0xE0 ,{0x38, 0x44, 0x44, 0x44, 0x20}//c 0xE1 ,{0x04, 0x04, 0x7c, 0x04, 0x04}//т 0xE2 ,{0x0C, 0x50, 0x50, 0x50, 0x3C}//у 0xE3 ,{0x30, 0x48, 0xfc, 0x48, 0x30}//ф 0xE4 ,{0x44, 0x28, 0x10, 0x28, 0x44}//x 0xE5 ,{0x7c, 0x40, 0x40, 0x40, 0xfc}//ц 0xE6 ,{0x0c, 0x10, 0x10, 0x10, 0x7c}//ч 0xE7 ,{0x7c, 0x40, 0x7c, 0x40, 0x7c}//ш 0xE8 ,{0x7c, 0x40, 0x7c, 0x40, 0xfc}//щ 0xE9 ,{0x04, 0x7c, 0x50, 0x50, 0x20}//ъ 0xEA ,{0x7c, 0x50, 0x50, 0x20, 0x7c}//ы 0xEB ,{0x7c, 0x50, 0x50, 0x20, 0x00}//ь 0xEC ,{0x28, 0x44, 0x54, 0x54, 0x38}//э 0xED ,{0x7c, 0x10, 0x38, 0x44, 0x38}//ю 0xEE ,{0x08, 0x54, 0x34, 0x14, 0x7c}//я 0xEF }; #endif
Шрифты брал вроде бы на arduino.ru, кто их первоначальный автор, я не знаю.
Ну и теперь, мы можем вывести на экран что-то осмысленное. main.c:
#include "avr/io.h" #include "util/delay.h" #include <avr/pgmspace.h> #include "pcd8544_soft.h" #define LEN 72 const unsigned char mystr[LEN] PROGMEM = {"оператор Лапласа это векторный дифференциальный оператор второго порядка"}; int main(void) { pcd8544_init(); pcd8544_clear(); char * ptr= (char *)(&mystr); uint8_t i; for(i=0;i<LEN;i++) { pcd8544_send_char(pgm_read_byte(ptr+i)); } for (;;){ _delay_ms(1000); } return 0; }
Результат работы:
Полные исходники к этому примеру можно посмотреть здесь:
https://gitlab.com/flank1er/pcd8544_atmega8/tree/master/07_cp866
Фреймбуфер - это буфер в оперативной памяти микроконтроллера, размер которого равен размеру видеопамяти дисплея.
Фреймбуфер нужен для того чтобы нарисовать на экране точку. Т.к. вся видеопамять дисплея pcd8544 упакована в 504 байта, для того чтобы нарисовать точку на экране, нужно определить номер байта к которому относится данная точка, прочитать этот байт, изменить в нем нужный бит относящийся к точке, и записать этот байт обратно. Однако с нашего дисплея ничего читать нельзя, дисплей доступен только для записи. Поэтому нужен фреймбуфер, чтобы поставить простую точку.
Микроконтроллер ATmega8 имеет 1 Kбайт оперативной памяти, и фреймбуфер будет занимать почти половину этой памяти. Но например такие микроконтроллеры как MSP430G2553 с 512 байтами SRAM, оказываются уже не у дел.
Для организации фреймбуфера, в файле pcd8544_soft.c объявим массив:
#define FB_LEN 504 static uint8_t fb[FB_LEN]; uint16_t pos;
Так же пригодиться переменная pos которая будет служить позицией виртуального курсора во фреймбуфере.
Для работы с фреймбуфер ом, понадобятся дополнительные функции для очистки содержимого фреймбуфера:
void pcd8544_clear_fb(void) { for(pos=0;pos<FB_LEN; pos++) fb[pos]=0; pos=0; }
и для вывода его содержимого на дисплей:
void pcd8544_display_fb() { int i; for(i=0;i<FB_LEN; i++) pcd8544_send(LCD_D,fb[i]); }
Ну и само собой, потребуется небольшая работа по переписываю существующих функций для обеспечения поддержки работы с фреймбуфером.
Например в void pcd8544_send_char_fb(uint8_t ch) меняется всего одна строчка:
void pcd8544_send_char_fb(uint8_t ch) { int i; char * ptr= (char *)(&ASCII); if (ch >= 0x20 && ch <= 0xf0 && pos <= (FB_LEN-7)) { for (i=0; i < 5; i++) { uint8_t c=(ch<0xe0) ? ch - 0x20 : ch - 0x50; c=pgm_read_byte(ptr+c*5+i); fb[pos+i]|=c; } pos+=7; } }
После завершения всей работы хорошо было бы проверить что все работает как надо. К примеру main.c из предыдущего примера у меня получился таким:
#include "avr/io.h" #include "util/delay.h" #include "pcd8544_soft.h" int main(void) { pcd8544_init(); pcd8544_clear(); uint8_t i=0; for (;;){ pcd8544_clear_fb(); pcd8544_print_at_fb(">",3,0,2); pcd8544_print_uint8_at_fb(i++,3,3,2); pcd8544_display_fb(); _delay_ms(1000); } return 0; }
Теперь для работы с дисплеем нужно сначала очищать содержимое фреймбуфера, а в конце выводить его содержимое на экран функцией pcd8544_display_fb();
Теперь мы можем добавить функцию для ради которой все и затевалось, т.е. для рисования точки:
void pcd8544_set_point(uint8_t x, uint8_t y) { if (x < LCD_X && y < LCD_Y) { uint16_t index = ((y>>3)*LCD_X)+x; fb[index]|=(1<<(y&0x07)); } }
Выглядит гадко, но это работает.
Ок. Теперь можно что-либо нарисовать. Я адаптировал программу из старой книжки по ZX Spectrum для отрисовки простенького фрактала:
#include "avr/io.h" #include "util/delay.h" #include "pcd8544_soft.h" int main(void) { int8_t x[17]; int8_t y[17]; pcd8544_init(); pcd8544_clear(); for (;;) { pcd8544_clear_fb(); uint8_t i,j,k; for(i=1;i<=4;i++) { for(j=1;j<=4;j++) { k=4*i+j-4; x[k]=j-3; y[k]=i-3; } } x[2]=0; y[2]=-3; x[8]=2; y[8]=0; x[9]=-3; y[9]=-1; x[15]=-1; y[15]=2; for(i=1;i<=16;i++) { for(j=1;j<=16;j++) { for(k=1;k<=16;k++) { int8_t xx=(((x[i]<<4)+(x[j]<<2)+x[k])>>1); int8_t yy=(((y[i]<<4)+(y[j]<<2)+y[k])>>1); pcd8544_set_point(50+xx,28+yy); } } } pcd8544_display_fb(); _delay_ms(1000); } return 0; }
Результат работы выглядит так:
Полные исходники к этому примеру можно посмотреть здесь:
https://gitlab.com/flank1er/pcd8544_atmega8/tree/master/08_2_fractal
Фрактал штука интересная, но абсолютно бесполезная. Попробуем сделать что-нибудь более полезное. Допустим, что у нашего устройства имеется ждущий режим, во время которого на дисплее будет запускаться простая анимационная заставка.
На мой взгляд самая простая заставка это - "полет сквозь звездное небо".
Наиболее простой алгоритм этой заставки работает так: a) случайным образом формируется звездное небо для первой четверти координатной плоскости; б) картинка из первой четверти зеркалится на остальные четверти; в) при каждой итерации к х и у координатам "звезд" прибавляется по единице; г) при выходе "звезды" за пределы дисплея, случайно генерируется новая "звезда".
В итоге у меня получилась такая программа:
#include "avr/io.h" #include "util/delay.h" #include "stdlib.h" #include "pcd8544_soft.h" #define NUM 40 #define LCD_X 84 #define LCD_Y 48 uint8_t x[NUM]; uint8_t y[NUM]; int main(void) { pcd8544_init(); pcd8544_clear(); uint8_t i,j; for(i=0;i<NUM;i++) { x[i]=rand()%(LCD_X/2); y[i]=rand()%(LCD_Y/2); } for (;;){ for(i=0;i<NUM;i++) { if (x[i] == 0 || y[i] == 0) { x[i]=rand()%(LCD_X/2); y[i]=rand()%(LCD_Y/2); } else { x[i]=x[i]-1; y[i]=y[i]-1; } } pcd8544_clear_fb(); for(i=0;i<4;i++) { for(j=0;j<NUM;j++) pcd8544_set_point(x[j],y[j]); for(j=0;j<NUM;j++) pcd8544_set_point(LCD_X-x[j],y[j]); for(j=0;j<NUM;j++) pcd8544_set_point(LCD_X-x[j],LCD_Y-y[j]); for(j=0;j<NUM;j++) pcd8544_set_point(x[j],LCD_Y-y[j]); } pcd8544_display_fb(); _delay_ms(300); } return 0; }
Результат:
исходники:
https://gitlab.com/flank1er/pcd8544_atmega8/tree/master/09_1_starsky
На мой взгляд, за счет симметрии выглядит несколько странно.
Второй вариант алгоритма отличается от первого тем, что "звезды" не зеркалятся, а генерируются случайным образом по всей плоскости дисплея.
Программа:
#include "avr/io.h" #include "util/delay.h" #include "stdlib.h" #include "pcd8544_soft.h" #define NUM 120 #define LCD_X 84 #define LCD_Y 48 uint8_t x[NUM]; uint8_t y[NUM]; int main(void) { pcd8544_init(); pcd8544_clear(); uint8_t i,j; for(i=0;i<NUM;i++) { x[i]=rand()%(LCD_X); y[i]=rand()%(LCD_Y); } for (;;) { for(i=0;i<NUM;i++) { if (x[i] == 0 || y[i] == 0 || x[i] == LCD_X || y[i] == LCD_Y) // border control { x[i]=rand()%(LCD_X); y[i]=rand()%(LCD_Y); } else if (x[i] < LCD_X/2 && y[i] < LCD_Y/2) { x[i]=x[i]-1; y[i]=y[i]-1; } else if (x[i] < LCD_X/2 && y[i] >= LCD_Y/2) { x[i]=x[i]-1; y[i]=y[i]+1; } else if (x[i] >= LCD_X/2 && y[i] <LCD_Y/2) { x[i]=x[i]+1; y[i]=y[i]-1; } else if (x[i] >= LCD_X/2 && y[i] >= LCD_Y/2) { x[i]=x[i]+1; y[i]=y[i]+1; } } pcd8544_clear_fb(); for(j=0;j<NUM;j++) pcd8544_set_point(x[j],y[j]); pcd8544_display_fb(); _delay_ms(200); } return 0; }
Результат:
Теперь выглядит получше, но все-равно не так как я ожидал. Звездное небо хорошо смотрится на черном фоне 14-дюймового ЭЛТ монитора, а здесь, не то не сё. Ну да ладно.
исходники:
https://gitlab.com/flank1er/pcd8544_atmega8/tree/master/09_2_starsky
Целочисленные алгоритмы рисования прямых линий и окружностей можно скопировать из википедии Реализации алгоритмов/Алгоритм Брезенхэма
Т.о. функция рисования прямой линии получается такой:
void pcd8544_draw_line(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2) { const int deltaX = myabs(x2 - x1); const int deltaY = myabs(y2 - y1); const int signX = x1 < x2 ? 1 : -1; const int signY = y1 < y2 ? 1 : -1; int error = deltaX - deltaY; pcd8544_set_point(x2,y2); while(x1 != x2 || y1 != y2) { pcd8544_set_point(x1,y1); const int error2 = error * 2; if(error2 > -deltaY) { error -= deltaY; x1 += signX; } if(error2 < deltaX) { error += deltaX; y1 += signY; } } }
А функция рисования окружности такой:
void pcd8544_draw_circle(uint8_t x0, uint8_t y0, uint8_t radius) { int x = 0; int y = radius; int delta = 1 - 2 * radius; int error = 0; while(y >= 0) { pcd8544_set_point(x0 + x, y0 + y); pcd8544_set_point(x0 + x, y0 - y); pcd8544_set_point(x0 - x, y0 + y); pcd8544_set_point(x0 - x, y0 - y); error = 2 * (delta + y) - 1; if(delta < 0 && error <= 0) { ++x; delta += 2 * x + 1; continue; } error = 2 * (delta - x) - 1; if(delta > 0 && error > 0) { --y; delta += 1 - 2 * y; continue; } ++x; delta += 2 * (x - y); --y; } }
Какого-то впечатляющего примера для проверки этих функций я не нашел, поэтому предлагаю проверить их сразу в деле.
Теперь собственно о том, ради чего всё это делалось. Для начала попробуем сделать интерфейс для самодельной метеостанции. Допустим, что у нас имеется уличный датчик DS18B20, и комнатный DHT11. Информация с уличного датчика будет более приоритетна, поэтому она будет выводиться крупным шрифтом, а вся остальная сопутствующая информация - мелким.
Но для начала, чтобы рисовать интерфейсы, нам понадобиться еще одна функция для рисования иконок размером 7х8. Функция для рисования букв нам не подойдёт, т.к. она рисует растр 5x8, т.е. с межбуквенным интервалом по краям. В целях оптимизации иконки будем выводить построчно, т.е. в пределах одного банка.
void pcd8544_draw_icon_fb(char * img, uint8_t x,uint8_t y, uint8_t num) { pos=x+y*84; uint8_t i; for(i=0;i<(num*7);i++) { uint8_t c=pgm_read_byte(img+i); if ((pos+i)<504) fb[pos+i]|=c; } pos+=num; }
Здесь y - это номер строки или банка, x - это номер пикселя в пределах от 0 до 83. num - это ширина изображения в знакоместах, т.е. в пикселях ширина будет равна num*7.
В итоге программа для отрисовки интерфейса у меня получилась такой:
#include "avr/io.h" #include "util/delay.h" #include <avr/pgmspace.h> #include "pcd8544_soft.h" #define myabs(n) ((n) < 0 ? -(n) : (n)) const uint8_t blob[7] PROGMEM={112,216,134,129,134,216,112}; const uint8_t degree[7] PROGMEM={0,0,6,9,9,6,0}; const uint8_t degree_big[7] PROGMEM={0,14,27,17,27,14,0}; const uint8_t termometer[7] PROGMEM={0,96,158,129,158,96,0}; int main(void) { // LCD setup pcd8544_init(); /// set variables int16_t temp=-117; int8_t t1,t2; char i=+1; // main loop for (;;){ pcd8544_clear_fb(); // PRINT TIME pcd8544_print_at_fb("10:32", FONT_SIZE_2,0,4); pcd8544_print_at_fb("07", FONT_SIZE_1,10,5); /// print temperature t1=temp/10;t2=temp%10; if (myabs(t1)>=10) { pcd8544_print_uint8_at_fb(myabs(t1),FONT_SIZE_3,4,1); if (t1>=0) pcd8544_print_at_fb("+", FONT_SIZE_3,1,1); else pcd8544_print_at_fb("-", FONT_SIZE_3,1,1); } else { if (t1>=0) pcd8544_print_at_fb("+", FONT_SIZE_3,4,1); else pcd8544_print_at_fb("-", FONT_SIZE_3,4,1); pcd8544_print_uint8_at_fb(myabs(t1),FONT_SIZE_3,7,1); } pcd8544_print_uint8_at_fb(myabs(t2),FONT_SIZE_2,10,2); // draw dot pcd8544_set_point(68,27); pcd8544_set_point(60,27); pcd8544_set_point(70,27); pcd8544_set_point(68,28); pcd8544_set_point(69,28); pcd8544_set_point(70,28); pcd8544_set_point(68,29); pcd8544_set_point(69,29); pcd8544_set_point(70,29); // draw blob char * ptr= (char *)(&blob); pcd8544_draw_icon_fb(ptr,0,0,1); // draw degree ptr= (char *)(°ree); pcd8544_draw_icon_fb(ptr,67,0,1); ptr= (char *)(°ree_big); pcd8544_draw_icon_fb(ptr,67,1,1); // draw termometer ptr= (char *)(&termometer); pcd8544_draw_icon_fb(ptr,48 ,0,1); // status bar pcd8544_print_at_fb("35%", FONT_SIZE_1,1,0); pcd8544_print_at_fb("26", FONT_SIZE_1,8,0); pcd8544_display_fb(); if (temp >= 350) i=-1; else if (temp <= -350) i=1; temp+=i; _delay_ms(1000); } return 0; }
Результат:
Полные исходники к этому примеру можно посмотреть здесь:
https://gitlab.com/flank1er/pcd8544_atmega8/tree/master/11_meteo
Еще одна распространенная задача - дисплей для FM-приемника. Я не стал изобретать велосипед и попытался скопировать интерфейс со своего старенького плеера iRiver ifp-180tc. Там тоже монохромный дисплей, правда не квадратный, а прямоугольный.
В итоге у меня получилась такая программа:
#include "avr/io.h" #include "util/delay.h" #include <avr/pgmspace.h> #include "pcd8544_soft.h" const uint8_t pointer[7] PROGMEM={0x2,0x6,0xc,0x18,0xc,0x6,0x2}; const uint8_t stereo[7] PROGMEM={0x3c,0x66,0xc3,0x81,0xc3,0x66,0x3c}; static const uint8_t battery[14] PROGMEM={28,34,65,93,93,65,93, 93,65,93,93,65,127,0}; int main(void) { // LCD setup pcd8544_init(); /// set variables uint16_t freq=1017; char i=1; uint8_t f1,f2; uint8_t j; // main loop for(;;) { pcd8544_clear_fb(); /// print frequency f1=freq/10;f2=freq%10; if (f1>=100) pcd8544_print_uint8_at_fb(f1,FONT_SIZE_3,0,1); else pcd8544_print_uint8_at_fb(f1,FONT_SIZE_3,3,1); pcd8544_print_uint8_at_fb(f2,FONT_SIZE_2,9,2); // draw dot pcd8544_set_point(61,27); pcd8544_set_point(62,27); pcd8544_set_point(63,27); pcd8544_set_point(61,28); pcd8544_set_point(62,28); pcd8544_set_point(63,28); pcd8544_set_point(61,29); pcd8544_set_point(62,29); pcd8544_set_point(63,29); // status bar pcd8544_print_at_fb("MГц", FONT_SIZE_1,9,1); pcd8544_print_at_fb("ПМ", FONT_SIZE_1,3,0); pcd8544_print_at_fb("60dB", FONT_SIZE_1,6,0); pcd8544_print_at_fb("7-НАШЕ РАДИО", FONT_SIZE_1,0,4); // pcd8544_draw_line(0,46,83,46); for(j=0;j<14;j++) pcd8544_draw_line(j*6,44,j*6,47); pcd8544_draw_line(83,44,83,47); char * ptr= (char *)(&pointer); uint8_t p=(freq-880)/2-(freq-880)/10; pcd8544_draw_icon_fb(ptr,p,5,1); ptr= (char *)(&stereo); pcd8544_draw_icon_fb(ptr,0,0,1); pcd8544_draw_icon_fb(ptr,7,0,1); ptr= (char *)(&battery); pcd8544_draw_icon_fb(ptr,70,0,2); pcd8544_display_fb(); if (freq >= 1080) i=-1; else if (freq <=880) i=1; freq+=i; _delay_ms(1000); } return 0; }
Результат:
исходники:
https://gitlab.com/flank1er/pcd8544_atmega8/tree/master/12_radio
Когда происходит нажатие на кнопки регулировки громкости, то шкала FM диапазона на пару секунд меняется на шкалу регулировки громкости.
#include "avr/io.h" #include "util/delay.h" #include <avr/pgmspace.h> #include "pcd8544_soft.h" const uint8_t pointer[7] PROGMEM={0x2,0x6,0xc,0x18,0xc,0x6,0x2}; const uint8_t stereo[7] PROGMEM={0x3c,0x66,0xc3,0x81,0xc3,0x66,0x3c}; static const uint8_t battery[14] PROGMEM={28,34,65,93,93,65,93, 93,65,93,93,65,127,0}; int main(void) { // LCD setup pcd8544_init(); /// set variables char volume=100; uint16_t freq=1017; char i=1; uint8_t f1,f2; // main loop for(;;) { pcd8544_clear_fb(); /// print frequency f1=freq/10;f2=freq%10; if (f1>=100) pcd8544_print_uint8_at_fb(f1,FONT_SIZE_3,0,1); else pcd8544_print_uint8_at_fb(f1,FONT_SIZE_3,3,1); pcd8544_print_uint8_at_fb(f2,FONT_SIZE_2,9,2); // draw dot pcd8544_set_point(61,27); pcd8544_set_point(62,27); pcd8544_set_point(63,27); pcd8544_set_point(61,28); pcd8544_set_point(62,28); pcd8544_set_point(63,28); pcd8544_set_point(61,29); pcd8544_set_point(62,29); pcd8544_set_point(63,29); // status bar pcd8544_print_at_fb("MГц", FONT_SIZE_1,9,1); pcd8544_print_at_fb("ПМ", FONT_SIZE_1,3,0); pcd8544_print_at_fb("60dB", FONT_SIZE_1,6,0); pcd8544_print_at_fb("7-НАШЕ РАДИО", FONT_SIZE_1,0,4); // pcd8544_print_at_fb("гр", FONT_SIZE_1,0,5); pcd8544_print_uint8_at_fb(volume, FONT_SIZE_1,3,5); pcd8544_draw_line(33,45,83,45); // char * ptr= (char *)(&pointer); uint8_t p=33+(volume>>1); pcd8544_draw_icon_fb(ptr,p,5,1); ptr= (char *)(&stereo); pcd8544_draw_icon_fb(ptr,0,0,1); pcd8544_draw_icon_fb(ptr,7,0,1); ptr= (char *)(&battery); pcd8544_draw_icon_fb(ptr,70,0,2); pcd8544_display_fb(); if (volume >= 100) i=-1; else if (volume <= 0) i=1; volume+=i; _delay_ms(1000); } return 0; }
Результат:
Здесь можно видеть, что цифры налезают на статусную строку, хотя внизу есть пустое место. В целом, я бы для больших цифр сделал персональные шрифты, примерно как на HD44780.
исходники:
https://gitlab.com/flank1er/pcd8544_atmega8/tree/master/12_radio_volume
Ну и в завершение, можно перевести библиотеку на аппаратный SPI. Напомню, что для корректной работы устройства при заливке прошивки через ISP-программатор, дисплей нужно отключать от микроконтроллера.
В библиотечном файле нужно будет задать переменную, в которой будет храниться номер CS пина:
static uint8_t CSpin;
Соответственно функция инициализации дополниться одним входным параметром:
void pcd8544_init(uint8_t cs) { CSpin=cs; // GPIO Setup DDR_PCD8544 |= (1<<PIN_RESET) | (1<<PIN_DC); PORT_PCD8544&=~(1<<PIN_RESET); PORT_PCD8544|=(1<<PIN_RESET); pcd8544_send(LCD_C, 0x21 ); // LCD Extended Commands. pcd8544_send(LCD_C, 0xBA ); // Set LCD Vop (Contrast). pcd8544_send(LCD_C, 0x04 ); // Set Temp coefficent. //0x04 pcd8544_send(LCD_C, 0x14 ); // LCD bias mode 1:48. //0x13 pcd8544_send(LCD_C, 0x20 ); // LCD Basic Commands pcd8544_send(LCD_C, 0x0C ); // LCD in normal mode. }
Инициализацию SPI я предпочитаю делать в main() функции, или через библиотеку SPI.
Функция void pcd8544_send(uint8_t dc, uint8_t data) примет в таком случае следующий вид:
void pcd8544_send(uint8_t dc, uint8_t data) { if (dc == LCD_D) PORT_PCD8544 |= (1<<PIN_DC); else PORT_PCD8544 &= ~(1<<PIN_DC); PORT_PCD8544&=~(1<<CSpin); SPDR=data; while(!(SPSR & (1<<SPIF))); PORT_PCD8544|=(1<<CSpin); }
И в принципе это все. Все исходники в примерам можно скачать с этого сайта www.count-zero.ru/files/pcd8544_atmega8.zip или c gitlab.com https://gitlab.com/flank1er/pcd8544_atmega8.