Данный двухцветный (чёрно-белый) экранчик имеет разрешение 128х64 пиксела и контроллер SSD1306 с управлением либо по параллельному 8-битному интерфейсу 6800/8080 либо по последовательному I2C/SPI. Параллельный интерфейс в целом похож на HD44780 - для одностороннего обмена данными (записи) выставляются данные 8 бит, ещё один бит указывает на команду/данные, и один бит для синхронизации этой парочки итого минимум 10 ног микроконтроллера. Последовательные интерфейсы позволяют обойтись меньшим количеством соединений, но теоретически уступают в скорости обмена данными. У микроконтроллера msp430f1611 предостаточно выводов и в данном обучающем материале рассматривается параллельный интерфейс для которого не требуется дополнительно настраивать периферию I2C/SPI, что позволяет сосредоточиться непосредственно на особенностях SSD1306. При переходе на последовательный интерфейс достаточно будет изменить только две функции - ssd1306_write_data
и ssd1306_write_instruction
.
Управление SSD1306 ограничивается двумя функциями - запись данных и запись команд. Карта памяти GDDRAM организована 8-ю страницами по 128 байт. Система координат имеет начало в левом верхнем углу. Для записи произвольного отображаемого байта страницы по заданному адресу используется функция ssd1306_write_byte
. Организация памяти SSD1306 не даёт возможность задавать отдельную точку на дисплее, только байт. Если потребуется прорисовывать отдельные пиксели вместо байт нужно держать все пиксели в памяти микроконтроллера (буфер 128x8) и периодически скидывать их на дисплей побайтово. При записи последовательности байт достаточно задать начальный адрес т.к. каждое обращение к GDDRAM приводит к автоматическому инкременту адреса столбца, по достижении конечного значения (128) счётчик адреса столбца сбрасывается в начальное значение (0), а счётчик адреса страницы увеличивается на единицу. После достижения счётчиком адреса страницы конечного значения (8) оба счётчика сбросятся в начальные значения и процесс пойдёт по второму кругу.
ssd1306.c
// Address setting commands
#define SSD1306_SET_COL_LO_NIBBLE 0x00
#define SSD1306_SET_COL_HI_NIBBLE 0x10
#define SSD1306_SET_PAGE_START_ADDR 0xB0
// pins
#define SSD1306_DATA_INST 0
#define SSD1306_DATA_E 1
static void strobe() {
P3OUT |= (1 << SSD1306_DATA_E);
P3OUT &= ~(1 << SSD1306_DATA_E);
}
void
ssd1306_write_data (const uint8_t byte)
{
P2OUT = byte;
P3OUT |= (1 << SSD1306_DATA_INST);
strobe();
}
void
ssd1306_write_instruction (const uint8_t byte)
{
P2OUT = byte;
P3OUT &= ~(1 << SSD1306_DATA_INST);
strobe();
}
void
ssd1306_write_byte (const uint8_t x, const uint8_t page, const uint8_t byte)
{
ssd1306_write_instruction (SSD1306_SET_PAGE_START_ADDR | page);
ssd1306_write_instruction (SSD1306_SET_COL_LO_NIBBLE | (x & 0xF));
ssd1306_write_instruction (SSD1306_SET_COL_HI_NIBBLE | (x >> 4));
ssd1306_write_data(byte);
}
Любая картинка или текст это всего-лишь набор пикселей. В случае SSD1306 цвета всего два один из них чёрный. Для преобразования любого файла изображения в Си массив послужит следующий скрипт на питоне + ImageMagick:
# http://grep.js.org/?gist=96caa480d220062b18a693f1b2e9f275
~$ alias c128x64='convert -resize 128x64!'
~$ c128x64 eyes.eps xbm:- | grep -o '0[xX][0-9a-fA-F]\+' | python3 -c '
import sys
# parse stdin image
xbm = [int(line, 16) for line in sys.stdin]
# convert
out = [[0]*128 for i in range(8)]
for y in range(64):
for x in range(128):
if x & 7:
byte >>= 1
else:
# 128 / 8 = 16 bytes in one xbm row
byte = xbm[y * 16 + x // 8]
if not byte & 1:
out[y // 8][x] |= (1 << y % 8)
# print array
for p in out:
print("{" + ",".join(hex(x) for x in p) + "},")
'
Для отображения текста тоже понадобится набор готовых изображений для каждого символа. Наиболее простой случай - моноширинный шрифт все знаки (площадки знаков) которого имеют одинаковую ширину. Например шрифт для ASCII символов 5x7.
/*
reset && (export GCC_DIR=~/ti/msp430_gcc ;
$GCC_DIR/bin/msp430-elf-gcc \
-I $GCC_DIR/include -L $GCC_DIR/include \
-Werror -Wall -mmcu=msp430f1611 -O2 test.c ssd1306.c)
*/
#include <msp430.h>
#include <stdint.h>
#include "ssd1306.h"
#include "font.c"
#define STATIC_ASSERT(cond) typedef int foo[(cond) ? 1 : -1]
STATIC_ASSERT(sizeof(font) == 256 * 5 /* uint8_t values 0..255 == 256 */);
#undef STATIC_ASSERT
static void drawchar(const uint8_t x, const uint8_t page, const uint8_t c) {
for (uint8_t i = 0; i < 5; i++ ) {
ssd1306_write_byte(x + i, page, *(font+(c*5)+i));
}
}
static void drawstr(uint8_t x, uint8_t page, const char *s) {
while (s && *s) {
drawchar(x, page, *s++);
x += 6; // 6 pixels wide
if (x + 6 >= SSD1306_X_PIXELS) {
x = 0; // ran out of this page
page++;
}
if (page >= SSD1306_PIXEL_PAGES)
break; // ran out of space :(
}
}
int main(void) {
WDTCTL = WDTPW | WDTHOLD; // Stop watchdog timer
// DCO = 3, RSEL = 0, f = 0.13 MHz
DCOCTL = /* DCO2 + */ DCO1 + DCO0;
BCSCTL1 = XT2OFF /* + RSEL1 + RSEL0 + RSEL2 */;
P2OUT = 0; P3OUT = 0;
P2DIR = P3DIR = 0xFF;
ssd1306_init_display();
for(;;) {
drawstr(0, 0, "!\"#$%&'()*+,-./0123456789:;<=>?@"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`"
"abcdefghijklmnopqrstuvwxyz{|}~");
}
return 0;
}
Здесь STATIC_ASSERT
это идиома Си, проверка на этапе компиляции будет полезна в случае смены шрифта - на ту же кириллицу например. Текущая реализация конечно простейшая, смещение по горизонтали задаётся в пикселях, но накладывается ограничение на смещение по вертикали - максимум 8 строк. В более сложных случаях можно задействовать какую-нибудь готовую графическую библиотеку для графического дисплея наподобие uGFX.
Далее операционная система реального времени FreeRTOS.