--- /dev/null
+/*
+ ssd1306_glut.c
+
+ Copyright 2014 Doug Szumski <d.s.szumski@gmail.com>
+
+ Based on the hd44780 part:
+ Copyright Luki <humbell@ethz.ch>
+ Copyright 2011 Michel Pollet <buserror@gmail.com>
+
+ This file is part of simavr.
+
+ simavr is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ simavr is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with simavr. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include "ssd1306_glut.h"
+
+#if __APPLE__
+#include <GLUT/glut.h>
+#else
+#include <GL/glut.h>
+#endif
+
+ssd1306_colour_t oled_colour_g;
+float pix_size_g = 1.0;
+float pix_gap_g = 0.0;
+
+// Keep colours in sync with enum in header.
+float ssd1306_colours[][3] =
+{
+ { 1.0f, 1.0f, 1.0f }, // White
+ { 0.5f, 0.9f, 1.0f }, // Blue
+};
+
+void
+ssd1306_gl_init (float pix_size, ssd1306_colour_t oled_colour)
+{
+ pix_size_g = pix_size;
+ oled_colour_g = oled_colour;
+}
+
+void
+ssd1306_gl_set_colour (uint8_t invert, float opacity)
+{
+ if (invert)
+ {
+ glColor4f (ssd1306_colours[oled_colour_g][0],
+ ssd1306_colours[oled_colour_g][1],
+ ssd1306_colours[oled_colour_g][2],
+ opacity);
+ } else
+ {
+ glColor4f (0.0f, 0.0f, 0.0f, 1.0f);
+ }
+}
+
+float
+ssd1306_gl_get_pixel_opacity (uint8_t contrast)
+{
+ // Typically the screen will be clearly visible even at 0 contrast
+ return contrast / 512.0 + 0.5;
+}
+
+uint8_t
+ssd1306_gl_reverse_byte (uint8_t byte)
+{
+ byte = (byte & 0xF0) >> 4 | (byte & 0x0F) << 4;
+ byte = (byte & 0xCC) >> 2 | (byte & 0x33) << 2;
+ byte = (byte & 0xAA) >> 1 | (byte & 0x55) << 1;
+ return byte;
+}
+
+void
+ssd1306_gl_put_pixel_column (uint8_t block_pixel_column, float pixel_opacity,
+ int invert)
+{
+ glEnable (GL_BLEND);
+ glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glBegin (GL_QUADS);
+
+ ssd1306_gl_set_colour (!invert, pixel_opacity);
+
+ for (int i = 0; i < 8; ++i)
+ {
+ if (block_pixel_column & (1 << i))
+ {
+ glVertex2f (pix_size_g, pix_size_g * (i + 1));
+ glVertex2f (0, pix_size_g * (i + 1));
+ glVertex2f (0, pix_size_g * i);
+ glVertex2f (pix_size_g, pix_size_g * i);
+ }
+ }
+ glEnd ();
+}
+
+/*
+ * Controls the mapping between the VRAM and the display.
+ */
+static uint8_t
+ssd1306_gl_get_vram_byte (ssd1306_t *part, uint8_t page, uint8_t column)
+{
+ uint8_t seg_remap_default = ssd1306_get_flag (
+ part, SSD1306_FLAG_SEGMENT_REMAP_0);
+ uint8_t seg_comscan_default = ssd1306_get_flag (
+ part, SSD1306_FLAG_COM_SCAN_NORMAL);
+
+ if (seg_remap_default && seg_comscan_default)
+ {
+ // Normal display
+ return part->vram[page][column];
+ } else if (seg_remap_default && !seg_comscan_default)
+ {
+ // Normal display, mirrored from upper edge
+ return ssd1306_gl_reverse_byte (
+ part->vram[part->pages - 1 - page][column]);
+ }
+
+ else if (!seg_remap_default && !seg_comscan_default)
+ {
+ // Upside down display
+ return ssd1306_gl_reverse_byte (
+ part->vram[part->pages - 1 - page][part->columns - 1 - column]);
+ } else if (!seg_remap_default && seg_comscan_default)
+ {
+ // Upside down display, mirrored from upper edge
+ return part->vram[page][part->columns - 1 - column];
+ }
+
+ return 0;
+}
+
+static void
+ssd1306_gl_draw_pixels (ssd1306_t *part, float opacity, uint8_t invert)
+{
+ for (int p = 0; p < part->pages; p++)
+ {
+ glPushMatrix ();
+ for (int c = 0; c < part->columns; c++)
+ {
+ uint8_t vram_byte = ssd1306_gl_get_vram_byte (part, p, c);
+ ssd1306_gl_put_pixel_column (vram_byte, opacity, invert);
+ // Next column
+ glTranslatef (pix_size_g + pix_gap_g, 0, 0);
+ }
+ glPopMatrix ();
+ // Next page
+ glTranslatef (0,
+ (part->rows / part->pages) * pix_size_g + pix_gap_g,
+ 0);
+ }
+}
+
+void
+ssd1306_gl_draw (ssd1306_t *part)
+{
+ ssd1306_set_flag (part, SSD1306_FLAG_DIRTY, 0);
+
+ glEnable (GL_BLEND);
+ glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glBegin (GL_QUADS);
+
+ // Draw background
+ float opacity = ssd1306_gl_get_pixel_opacity (part->contrast_register);
+ int invert = ssd1306_get_flag (part, SSD1306_FLAG_DISPLAY_INVERTED);
+ ssd1306_gl_set_colour (invert, opacity);
+
+ glTranslatef (0, 0, 0);
+ glBegin (GL_QUADS);
+ glVertex2f (part->rows, 0);
+ glVertex2f (0, 0);
+ glVertex2f (0, part->columns);
+ glVertex2f (part->rows, part->columns);
+ glEnd ();
+
+ // Draw pixels
+ if (ssd1306_get_flag (part, SSD1306_FLAG_DISPLAY_ON))
+ {
+ ssd1306_gl_draw_pixels (part, opacity, invert);
+ }
+}
--- /dev/null
+/*
+ ssd1306_glut.h
+
+ Copyright 2014 Doug Szumski <d.s.szumski@gmail.com>
+
+ This file is part of simavr.
+
+ simavr is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ simavr is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with simavr. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __SSD1306_GLUT_H__
+#define __SSD1306_GLUT_H__
+
+#include "ssd1306_virt.h"
+
+// Keep colours in sync with array
+typedef enum
+{
+ SSD1306_GL_WHITE, SSD1306_GL_BLUE
+} ssd1306_colour_t;
+
+void
+ssd1306_gl_draw (ssd1306_t *part);
+
+void
+ssd1306_gl_init (float pix_size, ssd1306_colour_t oled_colour);
+
+#endif
--- /dev/null
+/*
+ ssd1306_virt.c
+
+ Copyright 2011 Michel Pollet <buserror@gmail.com>
+ Copyright 2014 Doug Szumski <d.s.szumski@gmail.com>
+
+ This file is part of simavr.
+
+ simavr is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ simavr is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with simavr. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include "sim_time.h"
+
+#include "ssd1306_virt.h"
+#include "avr_spi.h"
+#include "avr_ioport.h"
+
+/*
+ * Write a byte at the current cursor location and then scroll the cursor.
+ */
+static void
+ssd1306_write_data (ssd1306_t *part)
+{
+ part->vram[part->cursor.page][part->cursor.column] = part->spi_data;
+
+ // Scroll the cursor
+ if (++(part->cursor.column) >= SSD1306_VIRT_COLUMNS)
+ {
+ part->cursor.column = 0;
+ if (++(part->cursor.page) >= SSD1306_VIRT_PAGES)
+ {
+ part->cursor.page = 0;
+ }
+ }
+
+ ssd1306_set_flag (part, SSD1306_FLAG_DIRTY, 1);
+}
+
+/*
+ * Called on the first command byte sent. For setting single
+ * byte commands and initiating multi-byte commands.
+ */
+void
+ssd1306_update_command_register (ssd1306_t *part)
+{
+ switch (part->spi_data)
+ {
+ case SSD1306_VIRT_SET_CONTRAST:
+ part->command_register = part->spi_data;
+ //printf ("SSD1306: CONTRAST SET COMMAND: 0x%02x\n", part->spi_data);
+ return;
+ case SSD1306_VIRT_DISP_NORMAL:
+ ssd1306_set_flag (part, SSD1306_FLAG_DISPLAY_INVERTED,
+ 0);
+ ssd1306_set_flag (part, SSD1306_FLAG_DIRTY, 1);
+ //printf ("SSD1306: DISPLAY NORMAL\n");
+ SSD1306_CLEAR_COMMAND_REG(part);
+ return;
+ case SSD1306_VIRT_DISP_INVERTED:
+ ssd1306_set_flag (part, SSD1306_FLAG_DISPLAY_INVERTED,
+ 1);
+ ssd1306_set_flag (part, SSD1306_FLAG_DIRTY, 1);
+ //printf ("SSD1306: DISPLAY INVERTED\n");
+ SSD1306_CLEAR_COMMAND_REG(part);
+ return;
+ case SSD1306_VIRT_DISP_SUSPEND:
+ ssd1306_set_flag (part, SSD1306_FLAG_DISPLAY_ON, 0);
+ ssd1306_set_flag (part, SSD1306_FLAG_DIRTY, 1);
+ //printf ("SSD1306: DISPLAY SUSPENDED\n");
+ SSD1306_CLEAR_COMMAND_REG(part);
+ return;
+ case SSD1306_VIRT_DISP_ON:
+ ssd1306_set_flag (part, SSD1306_FLAG_DISPLAY_ON, 1);
+ ssd1306_set_flag (part, SSD1306_FLAG_DIRTY, 1);
+ //printf ("SSD1306: DISPLAY ON\n");
+ SSD1306_CLEAR_COMMAND_REG(part);
+ return;
+ case SSD1306_VIRT_SET_PAGE_START_ADDR
+ ... SSD1306_VIRT_SET_PAGE_START_ADDR
+ + SSD1306_VIRT_PAGES - 1:
+ part->cursor.page = part->spi_data
+ - SSD1306_VIRT_SET_PAGE_START_ADDR;
+ //printf ("SSD1306: SET PAGE ADDRESS: 0x%02x\n", part->spi_data);
+ SSD1306_CLEAR_COMMAND_REG(part);
+ return;
+ case SSD1306_VIRT_SET_COLUMN_LOW_NIBBLE
+ ... SSD1306_VIRT_SET_COLUMN_LOW_NIBBLE + 0xF:
+ part->spi_data -= SSD1306_VIRT_SET_COLUMN_LOW_NIBBLE;
+ part->cursor.column = (part->cursor.column & 0xF0)
+ | (part->spi_data & 0xF);
+ //printf ("SSD1306: SET COLUMN LOW NIBBLE: 0x%02x\n",part->spi_data);
+ SSD1306_CLEAR_COMMAND_REG(part);
+ return;
+ case SSD1306_VIRT_SET_COLUMN_HIGH_NIBBLE
+ ... SSD1306_VIRT_SET_COLUMN_HIGH_NIBBLE + 0xF:
+ part->spi_data -= SSD1306_VIRT_SET_COLUMN_HIGH_NIBBLE;
+ part->cursor.column = (part->cursor.column & 0xF)
+ | ((part->spi_data & 0xF) << 4);
+ //printf ("SSD1306: SET COLUMN HIGH NIBBLE: 0x%02x\n", part->spi_data);
+ SSD1306_CLEAR_COMMAND_REG(part);
+ return;
+ case SSD1306_VIRT_SET_SEG_REMAP_0:
+ ssd1306_set_flag (part, SSD1306_FLAG_SEGMENT_REMAP_0,
+ 1);
+ //printf ("SSD1306: SET COLUMN ADDRESS 0 TO OLED SEG0 to \n");
+ SSD1306_CLEAR_COMMAND_REG(part);
+ return;
+ case SSD1306_VIRT_SET_SEG_REMAP_127:
+ ssd1306_set_flag (part, SSD1306_FLAG_SEGMENT_REMAP_0,
+ 0);
+ //printf ("SSD1306: SET COLUMN ADDRESS 127 TO OLED SEG0 to \n");
+ SSD1306_CLEAR_COMMAND_REG(part);
+ return;
+ case SSD1306_VIRT_SET_COM_SCAN_NORMAL:
+ ssd1306_set_flag (part, SSD1306_FLAG_COM_SCAN_NORMAL,
+ 1);
+ //printf ("SSD1306: SET COM OUTPUT SCAN DIRECTION NORMAL \n");
+ SSD1306_CLEAR_COMMAND_REG(part);
+ return;
+ case SSD1306_VIRT_SET_COM_SCAN_INVERTED:
+ ssd1306_set_flag (part, SSD1306_FLAG_COM_SCAN_NORMAL,
+ 0);
+ //printf ("SSD1306: SET COM OUTPUT SCAN DIRECTION REMAPPED \n");
+ SSD1306_CLEAR_COMMAND_REG(part);
+ return;
+ default:
+ // Unknown command
+ return;
+ }
+}
+
+/*
+ * Multi-byte command setting
+ */
+void
+ssd1306_update_setting (ssd1306_t *part)
+{
+ switch (part->command_register)
+ {
+ case SSD1306_VIRT_SET_CONTRAST:
+ part->contrast_register = part->spi_data;
+ ssd1306_set_flag (part, SSD1306_FLAG_DIRTY, 1);
+ SSD1306_CLEAR_COMMAND_REG(part);
+ //printf ("SSD1306: CONTRAST SET: 0x%02x\n", part->contrast_register);
+ return;
+ default:
+ // Unknown command
+ return;
+ }
+}
+
+/*
+ * Determines whether a new command has been sent, or
+ * whether we are in the process of setting a multi-
+ * byte command.
+ */
+static void
+ssd1306_write_command (ssd1306_t *part)
+{
+ if (!part->command_register)
+ {
+ // Single byte or start of multi-byte command
+ ssd1306_update_command_register (part);
+ } else
+ {
+ // Multi-byte command setting
+ ssd1306_update_setting (part);
+ }
+}
+
+/*
+ * Called when a SPI byte is sent
+ */
+static void
+ssd1306_spi_in_hook (struct avr_irq_t * irq, uint32_t value, void * param)
+{
+ ssd1306_t * part = (ssd1306_t*) param;
+
+ // Chip select should be pulled low to enable
+ if (part->cs_pin)
+ return;
+
+ part->spi_data = value & 0xFF;
+
+ switch (part->di_pin)
+ {
+ case SSD1306_VIRT_DATA:
+ ssd1306_write_data (part);
+ break;
+ case SSD1306_VIRT_INSTRUCTION:
+ ssd1306_write_command (part);
+ break;
+ default:
+ // Invalid value
+ break;
+ }
+}
+
+/*
+ * Called when chip select changes
+ */
+static void
+ssd1306_cs_hook (struct avr_irq_t * irq, uint32_t value, void * param)
+{
+ ssd1306_t * p = (ssd1306_t*) param;
+ p->cs_pin = value & 0xFF;
+ //printf ("SSD1306: CHIP SELECT: 0x%02x\n", value);
+
+}
+
+/*
+ * Called when data/instruction changes
+ */
+static void
+ssd1306_di_hook (struct avr_irq_t * irq, uint32_t value, void * param)
+{
+ ssd1306_t * part = (ssd1306_t*) param;
+ part->di_pin = value & 0xFF;
+ //printf ("SSD1306: DATA / INSTRUCTION: 0x%08x\n", value);
+}
+
+/*
+ * Called when a RESET signal is sent
+ */
+static void
+ssd1306_reset_hook (struct avr_irq_t * irq, uint32_t value, void * param)
+{
+ //printf ("SSD1306: RESET\n");
+ ssd1306_t * part = (ssd1306_t*) param;
+ if (irq->value && !value)
+ {
+ // Falling edge
+ memset (part->vram, 0, part->rows * part->pages);
+ part->cursor.column = 0;
+ part->cursor.page = 0;
+ part->flags = 0;
+ part->command_register = 0x00;
+ part->contrast_register = 0x7F;
+ ssd1306_set_flag (part, SSD1306_FLAG_COM_SCAN_NORMAL, 1);
+ ssd1306_set_flag (part, SSD1306_FLAG_SEGMENT_REMAP_0, 1);
+ }
+
+}
+
+static const char * irq_names[IRQ_SSD1306_COUNT] =
+{ [IRQ_SSD1306_SPI_BYTE_IN] = "=ssd1306.SDIN", [IRQ_SSD1306_RESET
+ ] = "<ssd1306.RS", [IRQ_SSD1306_DATA_INSTRUCTION
+ ] = "<ssd1306.RW", [IRQ_SSD1306_ENABLE] = "<ssd1306.E",
+ [IRQ_SSD1306_ADDR] = "7>hd44780.ADDR" };
+
+void
+ssd1306_connect (ssd1306_t * part, ssd1306_wiring_t * wiring)
+{
+ avr_connect_irq (
+ avr_io_getirq (part->avr, AVR_IOCTL_SPI_GETIRQ(0),
+ SPI_IRQ_OUTPUT),
+ part->irq + IRQ_SSD1306_SPI_BYTE_IN);
+
+ avr_connect_irq (
+ avr_io_getirq (part->avr,
+ AVR_IOCTL_IOPORT_GETIRQ(
+ wiring->chip_select.port),
+ wiring->chip_select.pin),
+ part->irq + IRQ_SSD1306_ENABLE);
+
+ avr_connect_irq (
+ avr_io_getirq (part->avr,
+ AVR_IOCTL_IOPORT_GETIRQ(
+ wiring->data_instruction.port),
+ wiring->data_instruction.pin),
+ part->irq + IRQ_SSD1306_DATA_INSTRUCTION);
+
+ avr_connect_irq (
+ avr_io_getirq (part->avr,
+ AVR_IOCTL_IOPORT_GETIRQ(
+ wiring->reset.port),
+ wiring->reset.pin),
+ part->irq + IRQ_SSD1306_RESET);
+}
+
+void
+ssd1306_init (struct avr_t *avr, struct ssd1306_t * part, int width, int height)
+{
+ if (!avr || !part)
+ return;
+
+ memset (part, 0, sizeof(*part));
+ part->avr = avr;
+ part->columns = width;
+ part->rows = height;
+ part->pages = height / 8; // 8 pixels per page
+
+ /*
+ * Register callbacks on all our IRQs
+ */
+ part->irq = avr_alloc_irq (&avr->irq_pool, 0, IRQ_SSD1306_COUNT,
+ irq_names);
+
+ avr_irq_register_notify (part->irq + IRQ_SSD1306_SPI_BYTE_IN,
+ ssd1306_spi_in_hook, part);
+ avr_irq_register_notify (part->irq + IRQ_SSD1306_RESET,
+ ssd1306_reset_hook, part);
+ avr_irq_register_notify (part->irq + IRQ_SSD1306_ENABLE,
+ ssd1306_cs_hook, part);
+ avr_irq_register_notify (part->irq + IRQ_SSD1306_DATA_INSTRUCTION,
+ ssd1306_di_hook, part);
+
+ printf ("SSD1306: %duS is %d cycles for your AVR\n", 37,
+ (int) avr_usec_to_cycles (avr, 37));
+ printf ("SSD1306: %duS is %d cycles for your AVR\n", 1,
+ (int) avr_usec_to_cycles (avr, 1));
+}
--- /dev/null
+/*
+ ssd1306_virt.h
+
+ Copyright 2011 Michel Pollet <buserror@gmail.com>
+ Copyright 2014 Doug Szumski <d.s.szumski@gmail.com>
+
+ This file is part of simavr.
+
+ simavr is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ simavr is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with simavr. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * This "Part" simulates the SSD1306 OLED display driver.
+ *
+ * The following functions are currently supported:
+ *
+ * > Display reset
+ * > Display on / suspend
+ * > Setting of the contrast
+ * > Inversion of the display
+ * > Rotation of the display
+ * > Writing to the VRAM using horizontal addressing mode
+ *
+ * It has been tested on a "JY MCU v1.5 OLED" in 4 wire SPI mode
+ * with the E/RD and R/W lines hard wired low as per the datasheet.
+ *
+ */
+
+#ifndef __SSD1306_VIRT_H__
+#define __SSD1306_VIRT_H__
+
+#include "sim_irq.h"
+
+#define SSD1306_VIRT_DATA 1
+#define SSD1306_VIRT_INSTRUCTION 0
+
+#define SSD1306_VIRT_PAGES 8
+#define SSD1306_VIRT_COLUMNS 128
+
+/* Fundamental commands. */
+#define SSD1306_VIRT_SET_CONTRAST 0x81
+#define SSD1306_VIRT_RESUME_TO_RAM_CONTENT 0xA4
+#define SSD1306_VIRT_IGNORE_RAM_CONTENT 0xA5
+#define SSD1306_VIRT_DISP_NORMAL 0xA6
+#define SSD1306_VIRT_DISP_INVERTED 0xA7
+#define SSD1306_VIRT_DISP_SUSPEND 0xAE
+#define SSD1306_VIRT_DISP_ON 0xAF
+
+/* Scrolling commands */
+#define SSD1306_VIRT_SCROLL_RIGHT 0x26
+#define SSD1306_VIRT_SCROLL_LEFT 0x27
+#define SSD1306_VIRT_SCROLL_VR 0x29
+#define SSD1306_VIRT_SCROLL_VL 0x2A
+#define SSD1306_VIRT_SCROLL_OFF 0x2E
+#define SSD1306_VIRT_SCROLL_ON 0x2F
+#define SSD1306_VIRT_VERT_SCROLL_A 0xA3
+
+/* Address setting commands */
+#define SSD1306_VIRT_SET_COLUMN_LOW_NIBBLE 0x00
+#define SSD1306_VIRT_SET_COLUMN_HIGH_NIBBLE 0x10
+#define SSD1306_VIRT_MEM_ADDRESSING 0x20
+#define SSD1306_VIRT_SET_COL_ADDR 0x21
+#define SSD1306_VIRT_SET_PAGE_ADDR 0x22
+#define SSD1306_VIRT_SET_PAGE_START_ADDR 0xB0
+
+/* Hardware config. commands */
+#define SSD1306_VIRT_SET_LINE 0x40
+#define SSD1306_VIRT_SET_SEG_REMAP_0 0xA0
+#define SSD1306_VIRT_SET_SEG_REMAP_127 0xA1
+#define SSD1306_VIRT_MULTIPLEX 0xA8
+#define SSD1306_VIRT_SET_COM_SCAN_NORMAL 0xC0
+#define SSD1306_VIRT_SET_COM_SCAN_INVERTED 0xC8
+#define SSD1306_VIRT_SET_OFFSET 0xD3
+#define SSD1306_VIRT_SET_PADS 0xDA
+
+/* Timing & driving scheme setting commands */
+#define SSD1306_VIRT_SET_RATIO_OSC 0xD5
+#define SSD1306_VIRT_SET_CHARGE 0xD9
+#define SSD1306_VIRT_SET_VCOM 0xDB
+#define SSD1306_VIRT_NOP 0xE3
+
+/* Charge pump command table */
+#define SSD1306_VIRT_CHARGE_PUMP 0x8D
+#define SSD1306_VIRT_PUMP_ON 0x14
+
+#define SSD1306_CLEAR_COMMAND_REG(part) part->command_register = 0x00
+
+enum
+{
+ //IRQ_SSD1306_ALL = 0,
+ IRQ_SSD1306_SPI_BYTE_IN,
+ IRQ_SSD1306_ENABLE,
+ IRQ_SSD1306_RESET,
+ IRQ_SSD1306_DATA_INSTRUCTION,
+ //IRQ_SSD1306_INPUT_COUNT,
+ IRQ_SSD1306_ADDR, // << For VCD
+ IRQ_SSD1306_COUNT
+//TODO: Add IRQs for VCD: Internal state etc.
+};
+
+enum
+{
+ SSD1306_FLAG_DISPLAY_INVERTED = 0,
+ SSD1306_FLAG_DISPLAY_ON,
+ SSD1306_FLAG_SEGMENT_REMAP_0,
+ SSD1306_FLAG_COM_SCAN_NORMAL,
+
+ /*
+ * Internal flags, not SSD1306
+ */
+ SSD1306_FLAG_BUSY, // 1: Busy between instruction, 0: ready
+ SSD1306_FLAG_REENTRANT, // 1: Do not update pins
+ SSD1306_FLAG_DIRTY, // 1: Needs redisplay...
+};
+
+/*
+ * Cursor position in VRAM
+ */
+struct ssd1306_virt_cursor_t
+{
+ uint8_t page;
+ uint8_t column;
+};
+
+typedef struct ssd1306_t
+{
+ avr_irq_t * irq;
+ struct avr_t * avr;
+ uint8_t columns, rows, pages;
+ struct ssd1306_virt_cursor_t cursor;
+ uint8_t vram[SSD1306_VIRT_PAGES][SSD1306_VIRT_COLUMNS];
+ uint16_t flags;
+ uint8_t command_register;
+ uint8_t contrast_register;
+ uint8_t cs_pin;
+ uint8_t di_pin;
+ uint8_t spi_data;
+} ssd1306_t;
+
+typedef struct ssd1306_pin_t
+{
+ char port;
+ uint8_t pin;
+} ssd1306_pin_t;
+
+typedef struct ssd1306_wiring_t
+{
+ ssd1306_pin_t chip_select;
+ ssd1306_pin_t data_instruction;
+ ssd1306_pin_t reset;
+} ssd1306_wiring_t;
+
+void
+ssd1306_init (struct avr_t *avr, struct ssd1306_t * b, int width, int height);
+
+static inline int
+ssd1306_set_flag (ssd1306_t *b, uint16_t bit, int val)
+{
+ int old = b->flags & (1 << bit);
+ b->flags = (b->flags & ~(1 << bit)) | (val ? (1 << bit) : 0);
+ return old != 0;
+}
+
+static inline int
+ssd1306_get_flag (ssd1306_t *b, uint16_t bit)
+{
+ return (b->flags & (1 << bit)) != 0;
+}
+
+void
+ssd1306_connect (ssd1306_t * part, ssd1306_wiring_t * wiring);
+
+#endif