Commit b59997c36d5cec94732c17c04ab858c279731eaa
authorDoug Szumski <d.s.szumski@gmail.com>
Tue, 5 Aug 2014 10:43:25 +0000 (11:43 +0100)
committerDoug Szumski <d.s.szumski@gmail.com>
Tue, 5 Aug 2014 10:59:45 +0000 (11:59 +0100)
4 files changed:
examples/parts/ssd1306_glut.c [new file with mode: 0644]
examples/parts/ssd1306_glut.h [new file with mode: 0644]
examples/parts/ssd1306_virt.c [new file with mode: 0644]
examples/parts/ssd1306_virt.h [new file with mode: 0644]

diff --git a/examples/parts/ssd1306_glut.c b/examples/parts/ssd1306_glut.c
new file mode 100644 (file)
index 0000000..9846aa5
--- /dev/null
@@ -0,0 +1,193 @@
+/*
+ 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);
+       }
+}
diff --git a/examples/parts/ssd1306_glut.h b/examples/parts/ssd1306_glut.h
new file mode 100644 (file)
index 0000000..4a51d1a
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ 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
diff --git a/examples/parts/ssd1306_virt.c b/examples/parts/ssd1306_virt.c
new file mode 100644 (file)
index 0000000..24d2372
--- /dev/null
@@ -0,0 +1,326 @@
+/*
+ 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));
+}
diff --git a/examples/parts/ssd1306_virt.h b/examples/parts/ssd1306_virt.h
new file mode 100644 (file)
index 0000000..8ad4b35
--- /dev/null
@@ -0,0 +1,184 @@
+/*
+ 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