From 318d3ab8ea77206a97a46c740716d083cc5e149e Mon Sep 17 00:00:00 2001
From: Doug Szumski <d.s.szumski@gmail.com>
Date: Tue, 5 Aug 2014 11:49:30 +0100
Subject: [PATCH] examples: Added SSD1306 example board

atmega32_ssd1306.c 	Example avr firmware
ssd1306.*		SSD1306 avr driver
images.*		simavr logo used in example firmware
ssd1306demo.c		simavr demo for ssd1306 part
---
 examples/board_ssd1306/Makefile           |  55 +++++
 examples/board_ssd1306/README             |  10 +
 examples/board_ssd1306/atmega32_ssd1306.c | 170 ++++++++++++++
 examples/board_ssd1306/images.c           |  61 +++++
 examples/board_ssd1306/images.h           |  30 +++
 examples/board_ssd1306/ssd1306.c          | 270 ++++++++++++++++++++++
 examples/board_ssd1306/ssd1306.h          | 139 +++++++++++
 examples/board_ssd1306/ssd1306demo.c      | 177 ++++++++++++++
 8 files changed, 912 insertions(+)
 create mode 100644 examples/board_ssd1306/Makefile
 create mode 100644 examples/board_ssd1306/README
 create mode 100644 examples/board_ssd1306/atmega32_ssd1306.c
 create mode 100644 examples/board_ssd1306/images.c
 create mode 100644 examples/board_ssd1306/images.h
 create mode 100644 examples/board_ssd1306/ssd1306.c
 create mode 100644 examples/board_ssd1306/ssd1306.h
 create mode 100644 examples/board_ssd1306/ssd1306demo.c

diff --git a/examples/board_ssd1306/Makefile b/examples/board_ssd1306/Makefile
new file mode 100644
index 0000000..985f1ea
--- /dev/null
+++ b/examples/board_ssd1306/Makefile
@@ -0,0 +1,55 @@
+# 
+# 	Copyright 2008-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/>.
+
+target=	ssd1306demo
+firm_src = ${wildcard at*${board}.c}
+firmware = ${firm_src:.c=.axf}
+simavr = ../../
+
+IPATH = .
+IPATH += ../parts
+IPATH += ${simavr}/include
+IPATH += ${simavr}/simavr/sim
+IPATH += ${simavr}/simavr/sim
+IPATH += ${simavr}/simavr/sim
+
+VPATH = .
+VPATH += ../parts
+
+LDFLAGS += -lpthread
+
+include ../Makefile.opengl
+
+all: obj atmega32_ssd1306.axf ${target}
+
+atmega32_ssd1306.axf: atmega32_ssd1306.c ssd1306.c images.c
+
+include ${simavr}/Makefile.common
+
+board = ${OBJ}/${target}.elf
+
+${board} : ${OBJ}/ac_input.o
+${board} : ${OBJ}/ssd1306_virt.o
+${board} : ${OBJ}/ssd1306_glut.o
+${board} : ${OBJ}/${target}.o
+
+${target}: ${board}
+	@echo $@ done
+
+clean: clean-${OBJ}
+	rm -rf *.hex *.a *.axf ${target} *.vcd .*.swo .*.swp .*.swm .*.swn
diff --git a/examples/board_ssd1306/README b/examples/board_ssd1306/README
new file mode 100644
index 0000000..5a3096d
--- /dev/null
+++ b/examples/board_ssd1306/README
@@ -0,0 +1,10 @@
+board_ssd1306
+
+(C) 2014 Doug Szumski <d.s.szumski@gmail.com>
+
+This sample code demonstrates the use of the SSD1306 OLED driver. It 
+runs identically in real life. 
+
+The part has been tested using only the supplied driver.
+
+In the future it could be extended to allow parallel addressing and I2C mode.
\ No newline at end of file
diff --git a/examples/board_ssd1306/atmega32_ssd1306.c b/examples/board_ssd1306/atmega32_ssd1306.c
new file mode 100644
index 0000000..bf9cc25
--- /dev/null
+++ b/examples/board_ssd1306/atmega32_ssd1306.c
@@ -0,0 +1,170 @@
+/*
+ atmega32_ssd1306.c
+
+ 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 <string.h>
+#include <avr/io.h>
+#include <util/delay.h>
+
+#undef F_CPU
+#define F_CPU 7380000
+
+#include "avr_mcu_section.h"
+AVR_MCU(F_CPU, "atmega32");
+
+#include "ssd1306.h"
+#include "images.h"
+
+#define DEFAULT_DELAY 10000
+
+void
+spi_init (void)
+{
+	DDRB |= (1 << PB4) | (1 << PB5) | (1 << PB7) | (1 << PB3) | (1 << PB1);
+	SPCR |= (1 << SPE) | (1 << MSTR);
+	// Double the speed
+	SPSR |= (1 << SPI2X);
+}
+
+void
+demo_set_contrast (void)
+{
+	for (uint16_t contrast = 0; contrast <= 255; contrast++)
+	{
+		PORTB ^= (1 << PB0);
+		ssd1306_set_contrast (contrast);
+		_delay_ms (DEFAULT_DELAY/200);
+	}
+}
+
+void
+demo_show_image (void)
+{
+	ssd1306_write_image_fb (logo);
+	ssd1306_display_fb ();
+	_delay_ms (DEFAULT_DELAY);
+}
+
+/* Draw some dots by writing directly to the VRAM */
+void
+demo_set_byte_direct (void)
+{
+	ssd1306_clear_screen ();
+	uint8_t x = 0;
+	for (uint8_t page = 0; page < SSD1306_PIXEL_PAGES; ++page)
+	{
+		for (x = 0; x < SSD1306_X_PIXELS; x += 2)
+		{
+			ssd1306_write_byte (x, page, 0xAA);
+		}
+	}
+	_delay_ms (DEFAULT_DELAY);
+}
+
+/* Draw some stripes by setting individual pixels on the frame buffer */
+void
+demo_set_pixels (void)
+{
+	ssd1306_clear_fb ();
+	uint8_t x = 0;
+	for (uint8_t y = 0; y < SSD1306_Y_PIXELS; ++y)
+	{
+		for (x = 0; x < SSD1306_X_PIXELS; x += 2)
+		{
+			ssd1306_set_pixel_fb (x, y, PIXEL_STATE_ON);
+		}
+	}
+	ssd1306_display_fb ();
+	_delay_ms (DEFAULT_DELAY);
+}
+
+void
+demo_clear_screen (void)
+{
+	// Turn all pixels on
+	memset (ssd1306_frame_buffer_g, 0xFF, SSD1306_PIXEL_BYTES);
+	ssd1306_display_fb ();
+	_delay_ms (DEFAULT_DELAY);
+
+	// Clear screen
+	ssd1306_clear_screen ();
+	_delay_ms (DEFAULT_DELAY);
+}
+
+void
+demo_set_power_state (void)
+{
+	ssd1306_set_power_state (POWER_STATE_SLEEP);
+	_delay_ms (DEFAULT_DELAY);
+	ssd1306_set_power_state (POWER_STATE_ON);
+	_delay_ms (DEFAULT_DELAY);
+}
+
+void
+demo_rotate_display (void)
+{
+	for (uint8_t i = DISP_ORIENT_NORMAL;
+	                i <= DISP_ORIENT_UPSIDE_DOWN_MIRRORED; ++i)
+	{
+		ssd1306_set_display_orientation (i);
+		ssd1306_write_image_fb (logo);
+		ssd1306_display_fb ();
+		_delay_ms (DEFAULT_DELAY);
+	}
+}
+
+void
+demo_invert_image ()
+{
+	ssd1306_set_display_orientation (DISP_ORIENT_NORMAL);
+	for (uint8_t i = DISPLAY_MODE_NORMAL; i <= DISPLAY_MODE_INVERTED; ++i)
+	{
+		ssd1306_set_display_mode (i);
+		ssd1306_write_image_fb (logo);
+		ssd1306_display_fb ();
+		// Check inverted contrast works
+		demo_set_contrast ();
+	}
+}
+
+int
+main ()
+{
+	spi_init ();
+
+	/*
+	 * Demonstrate the virtual part functionality. Runs approx 10 times
+	 * faster on an i7-3740QM CPU @ 2.70GHz than in real life.
+	 */
+	for (;;)
+	{
+		ssd1306_init_display ();
+
+		demo_show_image ();
+		demo_set_power_state ();
+		demo_set_contrast ();
+		demo_set_byte_direct ();
+		demo_set_pixels ();
+		demo_clear_screen ();
+		demo_rotate_display ();
+		demo_invert_image ();
+	}
+
+}
diff --git a/examples/board_ssd1306/images.c b/examples/board_ssd1306/images.c
new file mode 100644
index 0000000..354f62c
--- /dev/null
+++ b/examples/board_ssd1306/images.c
@@ -0,0 +1,61 @@
+/*
+ images.c
+
+ 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 "images.h"
+
+/* 128x64 run length encoded simavr logo */
+const uint8_t logo[] PROGMEM =
+{
+	0x0, 0x0, 0xff, 0x0, 0x0, 0x7, 0xfe, 0xfe, 0x4, 0xe, 0x6, 0x6, 0x8, 0xe, 0xfe,
+	0xfe, 0x3, 0xfc, 0x0, 0x0, 0x5, 0x54, 0xfe, 0xfe, 0x2, 0x0, 0x0,
+	0x3, 0xfe, 0xfe, 0x2, 0xff, 0x6, 0x1e, 0x7e, 0xfc, 0xe0, 0xc0,
+	0x0, 0x80, 0xc0, 0xf0, 0x7c, 0x3e, 0xe, 0x5e, 0xff, 0xfe, 0xfe,
+	0x2, 0xff, 0xfe, 0xfe, 0x2, 0x0, 0x0, 0x6, 0xfe, 0xfe, 0x3, 0x6,
+	0x6, 0xa, 0x2e, 0xfe, 0xfe, 0x3, 0x0, 0x0, 0x2, 0xfe, 0xff,
+	0xba, 0x0, 0x0, 0x7, 0xf4, 0xff, 0xff, 0x5, 0xfe, 0x0, 0x0, 0x2,
+	0xfe, 0xff, 0xff, 0x2, 0xf, 0x7, 0x7, 0xa, 0xff, 0xff, 0x3, 0x0,
+	0x0, 0xf, 0x7f, 0x7f, 0x4, 0x70, 0x60, 0x60, 0x8, 0xe0, 0xe0,
+	0x2, 0xe1, 0xe1, 0x2, 0xc0, 0x0, 0x0, 0x2, 0x80, 0x80, 0x3,
+	0xe5, 0xff, 0xff, 0x2, 0x0, 0x0, 0x3, 0xff, 0xff, 0x3, 0x0, 0x0,
+	0x3, 0x1, 0x7, 0x1f, 0x1f, 0x2, 0xf, 0x7, 0x1, 0x0, 0x0, 0x3,
+	0x55, 0xff, 0xff, 0x6, 0x0, 0x0, 0x2, 0x80, 0x80, 0x3, 0xc0,
+	0xff, 0xff, 0x3, 0xf0, 0x60, 0x60, 0x8, 0xf0, 0xfd, 0xff, 0xff,
+	0x3, 0x0, 0x0, 0x2, 0xff, 0xff, 0x2, 0xbb, 0x0, 0x0, 0x7, 0xf7,
+	0xff, 0xff, 0x6, 0x0, 0x0, 0x2, 0xff, 0xff, 0x3, 0xf0, 0xf0,
+	0x2, 0xe0, 0xe0, 0x3, 0x60, 0x60, 0x4, 0x70, 0xf0, 0xff, 0xff,
+	0x3, 0xf8, 0xe0, 0xe0, 0x3, 0x0, 0x0, 0xb, 0xf0, 0xf8, 0xf8,
+	0x5, 0xe0, 0x0, 0x0, 0x7, 0xff, 0xff, 0x4, 0x0, 0x0, 0x2, 0xff,
+	0xff, 0x6, 0x0, 0x0, 0x3, 0xff, 0xff, 0x3, 0x0, 0x0, 0x3, 0x40,
+	0xf8, 0xf8, 0x5, 0xf0, 0x0, 0x0, 0x3, 0xb5, 0xff, 0xff, 0x6,
+	0x0, 0x0, 0x2, 0xff, 0xff, 0x7, 0x0, 0x0, 0xa, 0x77, 0xff, 0xff,
+	0x3, 0x0, 0x0, 0x2, 0x7, 0x1f, 0x3f, 0xf8, 0xe0, 0xc0, 0x0, 0x0,
+	0x4, 0xf7, 0xff, 0xff, 0x4, 0x7f, 0x3f, 0x0, 0x0, 0x2, 0xff,
+	0xff, 0x7, 0x0, 0x0, 0x6, 0x1, 0xff, 0xff, 0x7, 0x0, 0x0, 0xb,
+	0x7, 0xf, 0xf, 0x6, 0xe, 0xe, 0x7, 0xf, 0xf, 0x3, 0x7, 0x0, 0x0,
+	0x2, 0xf, 0xf, 0x6, 0x0, 0x0, 0x3, 0xf, 0xf, 0x2, 0x7, 0x0, 0x0,
+	0x3, 0x3, 0xf, 0xf, 0x6, 0x0, 0x0, 0x3, 0x2, 0xf, 0xf, 0x6, 0x0,
+	0x0, 0x2, 0x7, 0xf, 0xf, 0x5, 0x7, 0x0, 0x0, 0xa, 0x3, 0xf, 0xf,
+	0x3, 0x0, 0x0, 0x6, 0x3, 0x7, 0xf, 0xe, 0xe, 0x2, 0xf, 0x7, 0x3,
+	0x3, 0x2, 0x1, 0x0, 0x0, 0x5, 0x7, 0xf, 0xf, 0x5, 0x7, 0x0, 0x0,
+	0x7, 0x7, 0xf, 0xf, 0x5, 0x7, 0x0, 0x0, 0xff, 0x0, 0x0, 0x6
+};
+
+
diff --git a/examples/board_ssd1306/images.h b/examples/board_ssd1306/images.h
new file mode 100644
index 0000000..3534ade
--- /dev/null
+++ b/examples/board_ssd1306/images.h
@@ -0,0 +1,30 @@
+/*
+ images.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 IMAGES_H_
+#define IMAGES_H_
+
+#include <stdint.h>
+#include <avr/pgmspace.h>
+
+extern const uint8_t logo[] PROGMEM;
+
+#endif /* IMAGES_H_ */
diff --git a/examples/board_ssd1306/ssd1306.c b/examples/board_ssd1306/ssd1306.c
new file mode 100644
index 0000000..801a8cb
--- /dev/null
+++ b/examples/board_ssd1306/ssd1306.c
@@ -0,0 +1,270 @@
+/*
+ ssd1306.c
+
+ SSD1306 display driver (SPI mode)
+
+ Copyright 2014 Doug Szumski <d.s.szumski@gmail.com>
+
+ Inspired by the work of Gabriel Anzziani.
+
+ This program 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.
+
+ This program 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 this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+#include <string.h>
+
+#include <avr/pgmspace.h>
+#include <avr/io.h>
+#include <util/delay.h>
+
+#include "ssd1306.h"
+
+uint8_t ssd1306_frame_buffer_g[SSD1306_PIXEL_PAGES][SSD1306_X_PIXELS];
+
+void
+ssd1306_reset_display (void)
+{
+  PORTB |= (1 << SSD1306_RESET_PIN);
+  _delay_us (3);
+  PORTB &= ~(1 << SSD1306_RESET_PIN);
+  _delay_us (3);
+  PORTB |= (1 << SSD1306_RESET_PIN);
+}
+
+static inline void
+ssd1306_tx_spi_byte(const uint8_t byte)
+{
+  SPDR = byte;
+  // Wait for transmission to complete
+  while (!(SPSR & (1 << SPIF)));
+}
+
+void
+ssd1306_write_data (const uint8_t byte)
+{
+  PORTB |= (1 << SSD1306_DATA_INST);
+  PORTB &= ~(1 << SSD1306_CHIP_SELECT);
+  ssd1306_tx_spi_byte(byte);
+  PORTB |= (1 << SSD1306_CHIP_SELECT);
+}
+
+void
+ssd1306_write_instruction (const uint8_t byte)
+{
+  PORTB &= ~((1 << SSD1306_DATA_INST) | (1 << SSD1306_CHIP_SELECT));
+  ssd1306_tx_spi_byte(byte);
+  PORTB |= (1 << SSD1306_CHIP_SELECT);
+}
+
+/* Initialise display mostly as per p64 of the datasheet */
+void
+ssd1306_init_display (void)
+{
+  ssd1306_reset_display ();
+
+  ssd1306_set_power_state (POWER_STATE_SLEEP);
+
+  ssd1306_write_instruction (SSD1306_SET_MULTIPLEX_RATIO);
+  ssd1306_write_instruction (0x3F);
+
+  ssd1306_write_instruction (SSD1306_SET_VERTICAL_OFFSET);
+  ssd1306_write_instruction (0x00);
+
+  ssd1306_write_instruction (SSD1306_SET_DISP_START_LINE);
+
+  ssd1306_set_display_orientation(DISP_ORIENT_NORMAL);
+
+  ssd1306_write_instruction (SSD1306_SET_WIRING_SCHEME);
+  ssd1306_write_instruction (0x12);
+
+  ssd1306_set_contrast (SSD1306_DEFAULT_CONTRAST);
+
+  ssd1306_write_instruction (SSD1306_RESUME_TO_RAM_CONTENT);
+
+  ssd1306_set_display_mode (DISPLAY_MODE_NORMAL);
+
+  // Horizontal memory addressing mode
+  ssd1306_write_instruction (SSD1306_MEM_ADDRESSING);
+  ssd1306_write_instruction (0x00);
+
+  ssd1306_write_instruction (SSD1306_SET_DISP_CLOCK);
+  ssd1306_write_instruction (0x80);
+
+  ssd1306_write_instruction (SSD1306_CHARGE_PUMP_REGULATOR);
+  ssd1306_write_instruction (SSD1306_CHARGE_PUMP_ON);
+
+  ssd1306_set_power_state (POWER_STATE_ON);
+}
+
+void
+ssd1306_set_display_orientation (const disp_orient_t disp_orient)
+{
+  switch (disp_orient)
+    {
+    case DISP_ORIENT_NORMAL:
+      ssd1306_write_instruction (SSD1306_SET_SEG_REMAP_0);
+      ssd1306_write_instruction (SSD1306_SET_COM_SCAN_NORMAL);
+      break;
+    case DISP_ORIENT_NORMAL_MIRRORED:
+      // The display is mirrored from the upper edge
+      ssd1306_write_instruction (SSD1306_SET_SEG_REMAP_0);
+      ssd1306_write_instruction (SSD1306_SET_COM_SCAN_INVERTED);
+      break;
+    case DISP_ORIENT_UPSIDE_DOWN:
+      ssd1306_write_instruction (SSD1306_SET_SEG_REMAP_127);
+      ssd1306_write_instruction (SSD1306_SET_COM_SCAN_INVERTED);
+      break;
+    case DISP_ORIENT_UPSIDE_DOWN_MIRRORED:
+      // The upside down display is mirrored from the upper edge
+      ssd1306_write_instruction (SSD1306_SET_SEG_REMAP_127);
+      ssd1306_write_instruction (SSD1306_SET_COM_SCAN_NORMAL);
+      break;
+    default:
+      break;
+    }
+}
+
+/* Move the cursor to the start */
+static void
+ssd1306_reset_cursor (void)
+{
+  ssd1306_write_instruction (SSD1306_SET_PAGE_START_ADDR);
+  ssd1306_write_instruction (SSD1306_SET_COL_HI_NIBBLE);
+  ssd1306_write_instruction (SSD1306_SET_COL_LO_NIBBLE);
+}
+
+void
+ssd1306_set_contrast (const uint8_t contrast)
+{
+  ssd1306_write_instruction (SSD1306_SET_CONTRAST);
+  ssd1306_write_instruction (contrast);
+}
+
+void
+ssd1306_set_display_mode(const display_mode_t display_mode)
+{
+  switch (display_mode) {
+    case DISPLAY_MODE_NORMAL:
+      ssd1306_write_instruction (SSD1306_DISP_NORMAL);
+      break;
+    case DISPLAY_MODE_INVERTED:
+      ssd1306_write_instruction (SSD1306_DISP_INVERTED);
+      break;
+    default:
+      ssd1306_write_instruction (SSD1306_DISP_NORMAL);
+      break;
+  }
+}
+
+void
+ssd1306_set_power_state (const power_state_t power_state)
+{
+  switch (power_state)
+    {
+    case POWER_STATE_ON:
+      ssd1306_write_instruction (SSD1306_DISP_ON);
+      break;
+    case POWER_STATE_SLEEP:
+      ssd1306_write_instruction (SSD1306_DISP_SLEEP);
+      break;
+    default:
+      break;
+    }
+}
+
+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);
+}
+
+void
+ssd1306_clear_screen (void)
+{
+  ssd1306_reset_cursor ();
+
+  for (uint16_t byte = 0; byte < SSD1306_PIXEL_BYTES; byte++)
+    {
+      ssd1306_write_data (0x00);
+    }
+}
+
+/*  Transfer display buffer to LCD */
+void
+ssd1306_display_fb (void)
+{
+  ssd1306_reset_cursor ();
+
+  for (uint8_t page = 0; page < SSD1306_PIXEL_PAGES; page++)
+    {
+      for (uint8_t column = 0; column < SSD1306_X_PIXELS; column++)
+	{
+	  ssd1306_write_data (ssd1306_frame_buffer_g[page][column]);
+	}
+    }
+}
+
+void
+ssd1306_clear_fb (void)
+{
+  memset(ssd1306_frame_buffer_g, 0, SSD1306_PIXEL_BYTES);
+}
+
+void
+ssd1306_set_pixel_fb (const uint8_t x, const uint8_t y, const pixel_state_t pixel_state)
+{
+  switch (pixel_state)
+    {
+    case PIXEL_STATE_ON:
+      ssd1306_frame_buffer_g[y / SSD1306_PIXEL_PAGES][x] |= (1 << y % SSD1306_PIXEL_PAGES);
+      break;
+    case PIXEL_STATE_OFF:
+      ssd1306_frame_buffer_g[y / SSD1306_PIXEL_PAGES][x] &= ~(1 << y % SSD1306_PIXEL_PAGES);
+      break;
+    default:
+      break;
+    }
+}
+
+/* Writes a run length encoded image to the display buffer */
+void
+ssd1306_write_image_fb (const uint8_t * image)
+{
+  uint8_t image_byte = 0, next_image_byte, write_byte_count = 0;
+
+  for (uint8_t page = 0; page < SSD1306_PIXEL_PAGES; page++)
+    {
+      for (uint8_t column = 0; column < SSD1306_X_PIXELS; column++)
+	{
+	  if (!write_byte_count)
+	    {
+	      image_byte = pgm_read_byte_near (image++);
+	      next_image_byte = pgm_read_byte_near (image++);
+	      if (image_byte == next_image_byte)
+		{
+		  write_byte_count = pgm_read_byte_near (image++);
+		}
+	      else
+		{
+		  write_byte_count = 1;
+		  image--;
+		}
+	    }
+	  write_byte_count--;
+	  ssd1306_frame_buffer_g[page][column] = image_byte;
+	}
+    }
+}
diff --git a/examples/board_ssd1306/ssd1306.h b/examples/board_ssd1306/ssd1306.h
new file mode 100644
index 0000000..7860fa4
--- /dev/null
+++ b/examples/board_ssd1306/ssd1306.h
@@ -0,0 +1,139 @@
+/*
+ ssd1306.h
+
+ SSD1306 display driver (SPI mode)
+
+ Copyright 2014 Doug Szumski <d.s.szumski@gmail.com>
+
+ Inspired by the work of Gabriel Anzziani.
+
+ This program 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.
+
+ This program 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 this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+#ifndef SSD1306_H
+#define SSD1306_H
+
+#include <stdint.h>
+
+// SPI pin config
+#define SSD1306_RESET_PIN   		PB3
+#define	SSD1306_DATA_INST   		PB1
+#define	SSD1306_CHIP_SELECT		PB4
+
+#define SSD1306_X_PIXELS 		128
+#define SSD1306_Y_PIXELS 		64
+#define SSD1306_PIXEL_PAGES 		(SSD1306_Y_PIXELS / 8)
+#define SSD1306_PIXEL_BYTES		(SSD1306_X_PIXELS * SSD1306_PIXEL_PAGES)
+
+
+// Default settings
+#define SSD1306_DEFAULT_CONTRAST 0x7F
+
+// Fundamental commands
+#define SSD1306_CHARGE_PUMP_REGULATOR 	0x8D
+#define SSD1306_CHARGE_PUMP_ON   	0x14
+#define SSD1306_SET_CONTRAST		0x81
#define SSD1306_RESUME_TO_RAM_CONTENT	0xA4
+#define SSD1306_IGNORE_RAM_CONTENT	0xA5
+#define SSD1306_DISP_NORMAL		0xA6
+#define SSD1306_DISP_INVERTED		0xA7
+#define SSD1306_DISP_SLEEP 		0xAE
+#define SSD1306_DISP_ON			0xAF
+
+// Scroll commands
+#define SSD1306_SCROLL_RIGHT		0x26
+#define SSD1306_SCROLL_LEFT		0x27
+#define SSD1306_SCROLL_VERTICAL_RIGHT	0x29
+#define SSD1306_SCROLL_VERTICAL_LEFT	0x2A
+#define SSD1306_SCROLL_OFF		0x2E
+#define SSD1306_SCROLL_ON   		0x2F
+#define SSD1306_VERT_SCROLL_AREA 	0xA3
+
+// Address setting commands
+#define SSD1306_SET_COL_LO_NIBBLE	0x00
+#define SSD1306_SET_COL_HI_NIBBLE	0x10
+#define SSD1306_MEM_ADDRESSING 		0x20
+#define SSD1306_SET_COL_ADDR		0x21
+#define SSD1306_SET_PAGE_ADDR		0x22
+#define SSD1306_SET_PAGE_START_ADDR	0xB0
+
+// Hardware configuration
+#define SSD1306_SET_DISP_START_LINE	0x40
+#define SSD1306_SET_SEG_REMAP_0  	0xA0
#define SSD1306_SET_SEG_REMAP_127	0xA1
+#define SSD1306_SET_MULTIPLEX_RATIO     0xA8
+#define SSD1306_SET_COM_SCAN_NORMAL	0xC0
+#define SSD1306_SET_COM_SCAN_INVERTED	0xC8
+#define SSD1306_SET_VERTICAL_OFFSET	0xD3
+#define SSD1306_SET_WIRING_SCHEME	0xDA
+#define SSD1306_SET_DISP_CLOCK		0xD5
+#define SSD1306_SET_PRECHARGE_PERIOD  	0xD9
+#define SSD1306_SET_VCOM_DESELECT_LEVEL 0xDB
+#define SSD1306_NOP			0xE3
+
+typedef enum
+{
+  DISPLAY_MODE_NORMAL, DISPLAY_MODE_INVERTED
+} display_mode_t;
+
+typedef enum
+{
+  POWER_STATE_SLEEP, POWER_STATE_ON
+} power_state_t;
+
+typedef enum
+{
+  PIXEL_STATE_OFF, PIXEL_STATE_ON
+} pixel_state_t;
+
+typedef enum
+{
+  DISP_ORIENT_NORMAL,
+  DISP_ORIENT_NORMAL_MIRRORED,
+  DISP_ORIENT_UPSIDE_DOWN,
+  DISP_ORIENT_UPSIDE_DOWN_MIRRORED
+} disp_orient_t;
+
+extern uint8_t ssd1306_frame_buffer_g[SSD1306_PIXEL_PAGES][SSD1306_X_PIXELS];
+
+void
+ssd1306_write_data (const uint8_t);
+void
+ssd1306_write_instruction (const uint8_t);
+void
+ssd1306_reset_display (void);
+void
+ssd1306_init_display (void);
+void
+ssd1306_set_display_orientation (const disp_orient_t disp_orient);
+void
+ssd1306_set_contrast (const uint8_t contrast);
+void
+ssd1306_set_power_state (const power_state_t power_state);
+void
+ssd1306_set_display_mode (const display_mode_t display_mode);
+void
+ssd1306_write_byte (const uint8_t x, const uint8_t page, const uint8_t byte);
+void
+ssd1306_clear_screen (void);
+
+/* Frame buffer operations */
+void
+ssd1306_display_fb (void);
+void
+ssd1306_set_pixel_fb (const uint8_t x, const uint8_t y, const pixel_state_t pixel_state);
+void
+ssd1306_clear_fb (void);
+void
+ssd1306_write_image_fb (const uint8_t * image);
+
+#endif
diff --git a/examples/board_ssd1306/ssd1306demo.c b/examples/board_ssd1306/ssd1306demo.c
new file mode 100644
index 0000000..fb729c5
--- /dev/null
+++ b/examples/board_ssd1306/ssd1306demo.c
@@ -0,0 +1,177 @@
+/*
+ charlcd.c
+
+ Copyright Luki <humbell@ethz.ch>
+ 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 <libgen.h>
+
+#include "sim_avr.h"
+#include "avr_ioport.h"
+#include "sim_elf.h"
+#include "sim_gdb.h"
+#include "sim_vcd_file.h"
+
+#if __APPLE__
+#include <GLUT/glut.h>
+#else
+#include <GL/glut.h>
+#endif
+
+#include <pthread.h>
+
+#include "ac_input.h"
+#include "ssd1306_glut.h"
+
+int window_identifier;
+
+avr_t * avr = NULL;
+avr_vcd_t vcd_file;
+ac_input_t ac_input;
+ssd1306_t ssd1306;
+
+static void *
+avr_run_thread (void * ignore)
+{
+	while (1)
+	{
+		avr_run (avr);
+	}
+	return NULL;
+}
+
+/* Called on a key press */
+void
+keyCB (unsigned char key, int x, int y)
+{
+	switch (key)
+	{
+		case 'q':
+			exit (0);
+			break;
+	}
+}
+
+/* Function called whenever redisplay needed */
+void
+displayCB (void)
+{
+	glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+	// Select modelview matrix
+	glMatrixMode (GL_MODELVIEW);
+	glPushMatrix ();
+	// Start with an identity matrix
+	glLoadIdentity ();
+	ssd1306_gl_draw (&ssd1306);
+	glPopMatrix ();
+	glutSwapBuffers ();
+}
+
+// gl timer. if the lcd is dirty, refresh display
+void
+timerCB (int i)
+{
+	// restart timer
+	glutTimerFunc (1000 / 64, timerCB, 0);
+	glutPostRedisplay ();
+}
+
+int
+initGL (int w, int h, float pix_size)
+{
+	w *= pix_size;
+	h *= pix_size;
+
+	// Double buffered, RGB disp mode.
+	glutInitDisplayMode (GLUT_RGB | GLUT_DOUBLE);
+	glutInitWindowSize (w * 4, h * 4);
+	window_identifier = glutCreateWindow ("SSD1306 128x64 OLED");
+
+	// Set up projection matrix
+	glMatrixMode (GL_PROJECTION);
+	// Start with an identity matrix
+	glLoadIdentity ();
+	glOrtho (0, w, 0, h, 0, 10);
+	glScalef (1, -1, 1);
+	glTranslatef (0, -1 * h, 0);
+
+	// Set window's display callback
+	glutDisplayFunc (displayCB);
+	// Set window's key callback
+	glutKeyboardFunc (keyCB);
+
+	glutTimerFunc (1000 / 24, timerCB, 0);
+
+	ssd1306_gl_init (pix_size, SSD1306_GL_WHITE);
+
+	return 1;
+}
+
+int
+main (int argc, char *argv[])
+{
+	elf_firmware_t f;
+	const char * fname = "atmega32_ssd1306.axf";
+	char path[256];
+	sprintf (path, "%s/%s", dirname (argv[0]), fname);
+	printf ("Firmware pathname is %s\n", path);
+	elf_read_firmware (fname, &f);
+
+	printf ("firmware %s f=%d mmcu=%s\n", fname, (int) f.frequency, f.mmcu);
+
+	avr = avr_make_mcu_by_name (f.mmcu);
+	if (!avr)
+	{
+		fprintf (stderr, "%s: AVR '%s' not known\n", argv[0], f.mmcu);
+		exit (1);
+	}
+
+	avr_init (avr);
+	avr_load_firmware (avr, &f);
+	ac_input_init (avr, &ac_input);
+
+	ssd1306_init (avr, &ssd1306, 128, 64);
+
+	// SSD1306 wired to the SPI bus, with the following additional pins:
+	ssd1306_wiring_t wiring =
+	{
+		.chip_select.port = 'B',
+		.chip_select.pin = 4,
+		.data_instruction.port = 'B',
+		.data_instruction.pin = 1,
+		.reset.port = 'B',
+		.reset.pin = 3,
+	};
+
+	ssd1306_connect (&ssd1306, &wiring);
+
+	printf ("SSD1306 display demo\n   Press 'q' to quit\n");
+
+	// Initialize GLUT system
+	glutInit (&argc, argv);
+	initGL (ssd1306.columns, ssd1306.rows, 0.5);
+
+	pthread_t run;
+	pthread_create (&run, NULL, avr_run_thread, NULL);
+
+	glutMainLoop ();
+}
-- 
2.39.5