Монохромный OLED дисплей на контроллере SSD1306

Данный двухцветный (чёрно-белый) экранчик имеет разрешение 128х64 пиксела и контроллер SSD1306 с управлением либо по параллельному 8-битному интерфейсу 6800/8080 либо по последовательному I2C/SPI. Параллельный интерфейс в целом похож на HD44780 - для одностороннего обмена данными (записи) выставляются данные 8 бит, ещё один бит указывает на команду/данные, и один бит для синхронизации этой парочки итого минимум 10 ног микроконтроллера. Последовательные интерфейсы позволяют обойтись меньшим количеством соединений, но теоретически уступают в скорости обмена данными. У микроконтроллера msp430f1611 предостаточно выводов и в данном обучающем материале рассматривается параллельный интерфейс для которого не требуется дополнительно настраивать периферию I2C/SPI, что позволяет сосредоточиться непосредственно на особенностях SSD1306. При переходе на последовательный интерфейс достаточно будет изменить только две функции - ssd1306_write_data и ssd1306_write_instruction.

MSP430.js | исходники

screenshot

Управление 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.

screenshot

MSP430.js | исходники

/*

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.

links

social