В этой работе мы рассмотрим управление жидкокристаллическим дисплеем на основе ИС PCD8544[1] на примере дисплея, примененного в Nokia 5110, используя встроенный в МК ATmega8 порт последовательного периферийного интерфейса (англ. SPI.)[2]
ИС PCD8544 ориентирована на использование напряжения (2.7 ÷ 3.3) V. Без принятия особых мер, использование дисплея в схеме с напряжением питания вне этого диапазона (в т. ч. принятом в Arduino 5 V) может привести к повреждению дисплея.
Ввиду сложности монтажа самого дисплея, может иметь смысл использовать готовый модуль для монтажа на макетной панели.
Для данной работы нам потребуется присоединить к простейшему устройству дисплей Nokia 5110 следующим образом.
Питание — «общий» к выводу Gnd и + Uи. п. к выводу Vcc дисплея.
Линии данных:
последовательный периферийный интерфейс: выводы PB2, MOSI, SCk (16, 17, 19; D 10, D 11, D 13) МК ATmega8 — к выводам /CE, SDIn, SClk дисплея;
управление: выводы PC4, PC5 (27, 28; AI 4, AI 5) МК — к выводам Data (/Command), /Res дисплея;
отметим, что эти входы дисплея могут быть соединены с МК как непосредственно (при обязательном нахождении напряжений в допустимом для дисплея диапазоне), так и через ограничивающие резисторы номиналом порядка 10 kΩ.
/*** bitm8544.c — Send a bitmap to a PCD8544-based LCD via SPI -*- C -*- */#include<assert.h> /* for _Static_assert () */#include<avr/interrupt.h> /* for sei (), ISR () */#include<avr/io.h> /* for DDRB, PORTB, etc. */#include<avr/pgmspace.h> /* for PROGMEM, etc. */#include<avr/sleep.h> /* for sleep_enable (), etc. */#include<avr/wdt.h> /* for wdt_enable (), etc. */#include<stdbool.h> /* for bool */#include<stdint.h> /* for int16_t, etc. *//* FIXME: huh? */typedefint16_tptrdiff_t;#define LCD_WIDTH (84)#define LCD_HEIGHT (48)#define PCD8544_CMD_FUNC(pd_p, vrt_p, ext_p) \ (0x20 | ((pd_p) ? 4 : 0) | ((vrt_p) ? 2 : 0) | ((ext_p) ? 1 : 0))/* extended mode commands */#define PCD8544_EXT_BIAS(bias) \ (0x10 | (007 & (bias)))#define PCD8544_EXT_V_OP(v_op) \ (0x80 | (077 & (v_op)))/* normal mode commands */#define PCD8544_CMD_DISP(mem_p, inv_p) \ (0x08 | ((mem_p) ? 4 : 0) | ((inv_p) ? 1 : 0))#define PCD8544_CMD_SET_X(x) \ (0x80 | (127 & (x)))#define PCD8544_CMD_SET_Y(y) \ (0x40 | (007 & (y)))/* Source: [[commons:File:Wikimedia Community Logo optimized.svg]] * Command: * $ convert -ordered-dither c5x5b,2 -resize 82x46 \ * -bordercolor white -border 1x1 \ * -transpose \ * commons/b/b4/Wikimedia_Community_Logo_optimized.svg \ * xbm:community_logo_xp.xbm *//* NB: ensure the bitmap ends up in the flash */#define static static const PROGMEM#include"community_logo_xp.xbm"#undef staticstaticvolatileboolseen_spi_interrupt_p=0;ISR(SPI_STC_vect){seen_spi_interrupt_p=1;}staticvoidspi_send(charc){/* NB: assumes interrupts are enabled */SPDR=c;while(!seen_spi_interrupt_p){/* sleep until the next event */sleep_cpu();}seen_spi_interrupt_p=0;/* . */}staticvoidspi_send_P(constchar*s,size_tlen){for(ptrdiff_ti=0;i<len;i++){spi_send(pgm_read_byte(s+i));}/* . */}intmain(){/* expecting to finish in less than 100 ms */wdt_enable(WDTO_2S);/* Set up Port B: – PB2 (D 10) is /CE (/SS) – PB3 (D 11) is MOSI – PB5 (D 13) is SCk *//* raise /CE */PORTB|=(1<<PB2);DDRB|=((1<<DDB5)|(1<<DDB3)|(1<<DDB2));/* Set up Port C: – PC4 (AI 4) is Data (/Command) – PC5 (AI 5) is /Reset */DDRC|=((1<<DDC5)|(1<<DDC4));/* Set up the SPI master */SPCR=((1<<SPIE)|(1<<SPE)|(1<<MSTR)|(1<<SPR1)|(1<<SPR0));/* NB: ignoring the datasheet recommendation! */sleep_enable();/* enable interrupts */sei();/* lower /CE */PORTB&=~((1<<PB2));/* lower /Reset for 8 SPI ticks */PORTC&=~((1<<PC5));/* wait for a complete transmission */spi_send(42);/* raise /Reset back */PORTC|=(1<<PC5);/* initialize PCD8544 */_Static_assert(community_logo_xp_height<=LCD_WIDTH,"The height of the bitmap exceeds LCD width");staticconstPROGMEMcharinit_s[]={PCD8544_CMD_FUNC(0,1,1),/* ¬ power down, vert., extended */PCD8544_EXT_BIAS(4),PCD8544_EXT_V_OP(0x2f),PCD8544_CMD_FUNC(0,1,0),/* ¬ power down, vert., normal */PCD8544_CMD_DISP(1,0),/* access memory, ¬ inverse */PCD8544_CMD_SET_Y(0),/* ensure the bitmap is X-centered centered by setting X */PCD8544_CMD_SET_X(((LCD_WIDTH-community_logo_xp_height)>>1))};/* lower /Command (Data) */PORTC&=~((1<<PC4));spi_send_P(init_s,sizeof(init_s));/* send the bitmap */_Static_assert(community_logo_xp_width==LCD_HEIGHT,"The width of the bitmap has to match LCD height");/* raise Data (/Command) */PORTC|=(1<<PC4);spi_send_P(community_logo_xp_bits,sizeof(community_logo_xp_bits));/* raise /CE back */PORTB|=(1<<PB2);/* halt */wdt_disable();cli();sleep_enable();sleep_cpu();/* not reached *//* . */return0;}/*** bitm8544.c ends here */
Создадим файл bitm8544.c приведенного выше содержания, а также файл с целевым растром community_logo_xp.xbm, содержание которого можно найти в приложении к данной работе.
Соберем рассматриваемый пример выполнив команду make:
При использовании микроконтроллера, отличного от ATmega8, или же кварцевого резонатора на частоту, отличную от 7.3728 MHz следует явно указать параметры сборки MCU или F_CPU, соответственно.
Так, для платы Arduino Uno R3, поставляемой с МК ATmega328P и кварцевым резонатором на 20 MHz, команда сборки может быть следующей:
$ make MCU=atmega328p F_CPU=20000000 bitm8544.hex
Впрочем, на деле данный пример никак не использует значение тактовой частоты процессора (передаваемое через определение препроцессора F_CPU), и должен сохранить работоспособность вне зависимости от ее конкретного значения.
Удостоверимся в отсутствии ошибок сборки и в появлении файла bitm8544.hex, содержащего результирующий машинный код в формате Intel hex.
/*** char8544.c — Send a bitmap to a PCD8544-based LCD via SPI -*- C -*- */#include<assert.h> /* for _Static_assert () */#include<avr/interrupt.h> /* for sei (), ISR () */#include<avr/io.h> /* for DDRB, PORTB, etc. */#include<avr/pgmspace.h> /* for PROGMEM, etc. */#include<avr/sleep.h> /* for sleep_enable (), etc. */#include<avr/wdt.h> /* for wdt_enable (), etc. */#include<stdbool.h> /* for bool */#include<stdint.h> /* for int16_t, etc. *//* NB: ensure bitmap ends up in the flash */#define static static const PROGMEM#include"misc_fixed.xbm"#undef static#define font_bits \ (misc_fixed_bits)#define font_coding_offset (-32)#define font_glyph_spacing (1)#define font_glyph_height (misc_fixed_width)#define font_glyph_width (5)#define font_glyphs \ (misc_fixed_height / font_glyph_width)/* FIXME: huh? */typedefint16_tptrdiff_t;#define LCD_WIDTH (84)#define LCD_HEIGHT (48)#define PCD8544_CMD_FUNC(pd_p, vrt_p, ext_p) \ (0x20 | ((pd_p) ? 4 : 0) | ((vrt_p) ? 2 : 0) | ((ext_p) ? 1 : 0))/* extended mode commands */#define PCD8544_EXT_BIAS(bias) \ (0x10 | (007 & (bias)))#define PCD8544_EXT_V_OP(v_op) \ (0x80 | (077 & (v_op)))/* normal mode commands */#define PCD8544_CMD_DISP(mem_p, inv_p) \ (0x08 | ((mem_p) ? 4 : 0) | ((inv_p) ? 1 : 0))#define PCD8544_CMD_SET_X(x) \ (0x80 | (127 & (x)))#define PCD8544_CMD_SET_Y(y) \ (0x40 | (007 & (y)))staticvolatileboolseen_spi_interrupt_p=0;ISR(SPI_STC_vect){seen_spi_interrupt_p=1;}staticvoidspi_send(charc){/* NB: assumes interrupts are enabled */SPDR=c;while(!seen_spi_interrupt_p){/* sleep until the next event */sleep_cpu();}seen_spi_interrupt_p=0;/* . */}staticvoidspi_send_P(constchar*s,size_tlen){for(ptrdiff_ti=0;i<len;i++){spi_send(pgm_read_byte(s+i));}/* . */}staticvoidpcd8544_draw_char(unsignedcharc){_Static_assert(font_glyph_height==8,"The height of the font has to be 8");ptrdiff_toffset=(font_glyph_width*((ptrdiff_t)c+font_coding_offset));spi_send_P(font_bits+offset,font_glyph_width);_Static_assert(font_glyph_spacing<INT8_MAX,"Glyph spacing has to be less than INT8_MAX");for(int8_ti=0;i<font_glyph_spacing;i++){spi_send(0);}/* . */}staticvoidpcd8544_draw_text_P(constchar*s){/* raise Data (/Command) */PORTC|=(1<<PC4);constchar*p;for(p=s;;p++){charc=pgm_read_byte(p);if(c=='\0'){break;}pcd8544_draw_char(c);}/* . */}intmain(){/* expecting to finish in less than 100 ms */wdt_enable(WDTO_2S);/* Set up Port B: – PB2 (D 10) is /CE (/SS) – PB3 (D 11) is MOSI – PB5 (D 13) is SCk *//* raise /CE */PORTB|=(1<<PB2);DDRB|=((1<<DDB5)|(1<<DDB3)|(1<<DDB2));/* Set up Port C: – PC4 (AI 4) is Data (/Command) – PC5 (AI 5) is /Reset */DDRC|=((1<<DDC5)|(1<<DDC4));/* Set up the SPI master */SPCR=((1<<SPIE)|(1<<SPE)|(1<<MSTR)|(1<<SPR1)|(1<<SPR0));/* NB: ignoring the datasheet recommendation! */sleep_enable();/* enable interrupts */sei();/* lower /CE */PORTB&=~((1<<PB2));/* lower /Reset for 8 SPI ticks */PORTC&=~((1<<PC5));/* wait for a complete transmission */spi_send(42);/* raise /Reset back */PORTC|=(1<<PC5);/* initialize PCD8544 */staticconstPROGMEMcharinit_s[]={PCD8544_CMD_FUNC(0,0,1),/* ¬ power down, horiz, extended */PCD8544_EXT_BIAS(4),PCD8544_EXT_V_OP(0x2f),PCD8544_CMD_FUNC(0,0,0),/* ¬ power down, horiz, normal */PCD8544_CMD_DISP(1,0),/* access memory, ¬ inverse */PCD8544_CMD_SET_Y(0),PCD8544_CMD_SET_X(0)};/* lower /Command (Data) */PORTC&=~((1<<PC4));spi_send_P(init_s,sizeof(init_s));/* draw the text */staticconstcharPROGMEMtext[]=(" \317\360\350\342\345\362, \354\350\360! "" ""\253\300\360\365\350\362\345\352\362\363\360\340 "" AVR "" \342\357\360\350\354\345\360\340\365\273""(\302\350\352\350\342\345\360\361\350\362\345\362)");pcd8544_draw_text_P(text);/* raise /CE back */PORTB|=(1<<PB2);/* halt */wdt_disable();cli();sleep_enable();sleep_cpu();/* not reached *//* . */return0;}/*** char8544.c ends here */
Создадим файл char8544.c приведенного выше содержания, а также файл шрифта misc_fixed.xbm, содержание которого можно найти в приложении к данной работе.
Соберем рассматриваемый пример выполнив команду make:
Указанное в примере вывода изображения условие возможности spi_send_P для «битового массива» в целом несколько более строго, нежели действительно необходимо. Исправьте это условие (в форме _Static_assert перед последним вызовом spi_send_P) так, чтобы ошибка на этапе сборки диагностировалась для изображений с шириной вне диапазона (41 ÷ 48) пикселей, и удостоверьтесь в правильности работы такого варианта кода используя изображения:
соответствующее новому (но не старому) условию;
не соответствующее ни одному из этих условий.
Предложенный код вывода изображения предполагает, что ширина транспонированного изображения в пикселях (или, что то же, — высота исходного) равна высоте самого дисплея. Несложно дополнить код возможностью вывода изображений меньшего размера одним из следующих двух способов:
дополнением нулями до нужной высоты — подобно тому, как в функции pcd8544_draw_charкода знакогенератораширина каждого глифа увеличивается на font_glyph_spacing пикселей;
пропуском позиций видеопамяти, не покрываемых изображением, — через периодическую переустановку координат командами дисплея PCD8544_CMD_SET_X, PCD8544_CMD_SET_Y.
Реализуйте оба подхода и включите в код подходящее изображение. Сравните объемы результирующего кода. Убедитесь в том, что результат работы программы в обоих случаях совершенно одинаков (если дисплей, конечно, действительно «пуст» перед началом работы кода.)
В предложенном варианте знакогенератора, функция pcd8544_draw_text_P не обрабатывает никаких управляющих кодов, включая код разрыва строки'\n' (ASCII LF, 10.) Поэтому, чтобы обеспечить «читаемый» результат, каждая строка включенного в код примера текста дополнена до ширины дисплея (14 символов.)
Обработку кода разрыва строки можно опять-таки выполнить одним из двух способов:
дополняя выводимую строку нулями до полной ширины;
Ясно, что в обоих случаях потребуется следить за текущей позицией по-горизонтали (которая, в свою очередь, может считаться как в символах, так и в пикселях.) Может показаться, кроме того, что во втором случае требуется учитывать также позицию по-вертикали, однако в данном знакогенераторе присутствует особенность, делающая это необязательным.
Попробуйте реализовать оба подхода. Измените выводимый текст для использования разрывов строк (\n) вместо дополнения пробелами и сравните объемы результирующего кода. Удостоверьтесь, что результат работы программы не изменился по отношению к данному здесь варианту. Добавьте к тексту еще несколько (неполных) строк, чтобы различия между подходами проявились в результирующем изображении.
Функция pcd8544_draw_text_Pкода знакогенератора выводит на дисплей текст, хранящийся во flash-памяти МК, что подходит прежде всего для не меняющихся в процессе работы программы сообщений. Реализуйте функцию pcd8544_draw_text, выводящую текст, хранящийся в ОЗУ. Удостоверьтесь в правильности работы функции, объявив в коде буфер подходящего размера и заполнив его функцией sprintf, подобно: