diff --git a/examples/i2c_oled/Makefile b/examples/i2c_oled/Makefile
index b9b2d1a02788c3f9d2bd9736a8e4a6004cf9253b..d5253892070f6b59e7b0d1016cc2077fdf5e6220 100644
--- a/examples/i2c_oled/Makefile
+++ b/examples/i2c_oled/Makefile
@@ -2,7 +2,7 @@ all : flash
 
 TARGET:=i2c_oled
 
-CFLAGS+=-DSTDOUT_UART
+#CFLAGS+=-DSTDOUT_UART
 
 include ../../ch32v003fun/ch32v003fun.mk
 
diff --git a/examples/i2c_oled/README.md b/examples/i2c_oled/README.md
index facc372c93fcf37bdaaaa47a73731e704bc6cd08..c8ea30e96730526aa06ebd16aa9fc43eada196d2 100644
--- a/examples/i2c_oled/README.md
+++ b/examples/i2c_oled/README.md
@@ -10,31 +10,38 @@ different graphic screens to test out the various drawing primitives.
 https://user-images.githubusercontent.com/1132011/230734071-dee305de-5aad-4ca0-a422-5fb31d2bb0e0.mp4
 
 ## Build options
-There are a few build-time options in the i2c.h source:
-* I2C_CLKRATE - defines the I2C bus clock rate. Both 100kHz and 400kHz are supported.
-800kHz has been seen to work when I2C_PRERATE is 1000000, but 1MHz did not. To
-use higher bus rates you must increase I2C_PRERATE at the expense of higher power
-consumption.
-* I2C_PRERATE - defines the I2C logic clock rate. Must be higher than I2C_CLKRATE.
-Keep this value as low as possible (but not lower than 1000000) to ensure low power
-operaton.
-* I2C_DUTY - for I2C_CLKRATE > 100kHz this specifies the duty cycle, either 33% or 36%.
-* TIMEOUT_MAX - the amount of tries in busy-wait loops before giving up. This value
-depends on the I2C_CLKRATE and should not affect normal operation.
-* I2C_IRQ - chooses IRQ-based operation instead of busy-wait polling. Useful to
-free up CPU resources but should be used carefully since it has more potential
-mysterious effects and less error checking.
+There are a few build-time options in the source - 
+
+In i2c_oled.c:
+* SSD1306_64X32, SSD1306_128X32, SSD1306_128X64 - choose only one of these
+depending on the type of OLED you've got.
+
+In ssd1306.h
+* SSD1306_PSZ - the number of bytes to send per I2C data packet. The default value
+of 32 seems to work well. Smaller values are allowed but may result in slower
+refresh rates.
+
+In ssd1306_i2c.h
+* SSD1306_I2C_ADDR - the I2C address of your OLED display. The default is 0x3c
+which should work for most devices. Use 0x3d if you've pulled the SA0 line high.
+* SSD1306_I2C_CLKRATE - defines the I2C bus clock rate. Both 100kHz and 400kHz
+are supported. 800kHz has been seen to work when I2C_PRERATE is 1000000, but 1MHz
+did not. To use higher bus rates you must increase I2C_PRERATE at the expense of
+higher power consumption.
+* SSD1306_I2C_PRERATE - defines the I2C logic clock rate. Must be higher than
+I2C_CLKRATE. Keep this value as low as possible (but not lower than 1000000) to
+ensure low power operaton.
+* SSD1306_I2C_DUTY - for I2C_CLKRATE > 100kHz this specifies the duty cycle,
+either 33% or 36%.
+* TIMEOUT_MAX - the amount of tries in busy-wait loops before giving up. This
+value depends on the I2C_CLKRATE and should not affect normal operation.
+* SSD1306_I2C_IRQ - chooses IRQ-based operation instead of busy-wait polling.
+Useful to free up CPU resources but should be used carefully since it has more
+potential mysterious effects and less error checking.
 * IRQ_DIAG - enables timing analysis via GPIO toggling. Don't enable this unless
 you know what you're doing.
 
 There are a few build-time options in the oled.h source:
-* OLED_ADDR - the I2C address of your OLED display. The default is 0x3c which
-should work for most devices. Use 0x3d if you've pulled the SA0 line high.
-* OLED_PSZ - the number of bytes to send per I2C data packet. The default value
-of 32 seems to work well. Smaller values are allowed but may result in slower
-refresh rates.
-* OLED_64X32, OLED_128X32, OLED_128X64 - choose only one of these depending on
-the type of OLED you've got.
 
 ## Use
 Connect an SSD1306-based OLED in I2C interface mode to pins PC1 (SDA) and PC2 (SCL)
diff --git a/examples/i2c_oled/i2c_oled.c b/examples/i2c_oled/i2c_oled.c
index f2f43ff5d9a8035ba87538ca63ec60f890f76b7a..94e6ce6390af94aa88c6d4020f01e71d1016b563 100644
--- a/examples/i2c_oled/i2c_oled.c
+++ b/examples/i2c_oled/i2c_oled.c
@@ -7,9 +7,15 @@
 #define SYSTEM_CORE_CLOCK 48000000
 #define APB_CLOCK SYSTEM_CORE_CLOCK
 
+// what type of OLED - uncomment just one
+//#define SSD1306_64X32
+#define SSD1306_128X32
+//#define SSD1306_128X64
+
 #include "ch32v003fun.h"
 #include <stdio.h>
-#include "oled.h"
+#include "ssd1306_i2c.h"
+#include "ssd1306.h"
 
 int main()
 {
@@ -17,39 +23,44 @@ int main()
 	SystemInit48HSI();
 
 	// start serial @ default 115200bps
+#ifdef STDOUT_UART
 	SetupUART( UART_BRR );
 	Delay_Ms( 100 );
+#else
+	SetupDebugPrintf();
+#endif
 	printf("\r\r\n\ni2c_oled example\n\r");
 
 	// init i2c and oled
 	Delay_Ms( 100 );	// give OLED some more time
 	printf("initializing i2c oled...");
-	if(!oled_init())
+	if(!ssd1306_i2c_init())
 	{
+		ssd1306_init();
 		printf("done.\n\r");
 		
 		printf("Looping on test modes...");
 		while(1)
 		{
-			for(uint8_t mode=0;mode<(OLED_H>32?8:7);mode++)
+			for(uint8_t mode=0;mode<(SSD1306_H>32?8:7);mode++)
 			{
 				// clear buffer for next mode
-				oled_setbuf(0);
+				ssd1306_setbuf(0);
 
 				switch(mode)
 				{
 					case 0:
 						printf("buffer fill with binary\n\r");
-						for(int i=0;i<sizeof(oled_buffer);i++)
-							oled_buffer[i] = i;
+						for(int i=0;i<sizeof(ssd1306_buffer);i++)
+							ssd1306_buffer[i] = i;
 						break;
 					
 					case 1:
 						printf("pixel plots\n\r");
-						for(int i=0;i<OLED_W;i++)
+						for(int i=0;i<SSD1306_W;i++)
 						{
-							oled_drawPixel(i, i/(OLED_W/OLED_H), 1);
-							oled_drawPixel(i, OLED_H-1-(i/(OLED_W/OLED_H)), 1);
+							ssd1306_drawPixel(i, i/(SSD1306_W/SSD1306_H), 1);
+							ssd1306_drawPixel(i, SSD1306_H-1-(i/(SSD1306_W/SSD1306_H)), 1);
 						}
 						break;
 					
@@ -57,61 +68,61 @@ int main()
 						{
 							printf("Line plots\n\r");
 							uint8_t y= 0;
-							for(uint8_t x=0;x<OLED_W;x+=16)
+							for(uint8_t x=0;x<SSD1306_W;x+=16)
 							{
-								oled_drawLine(x, 0, OLED_W, y, 1);
-								oled_drawLine(OLED_W-x, OLED_H, 0, OLED_H-y, 1);
-								y+= OLED_H/8;
+								ssd1306_drawLine(x, 0, SSD1306_W, y, 1);
+								ssd1306_drawLine(SSD1306_W-x, SSD1306_H, 0, SSD1306_H-y, 1);
+								y+= SSD1306_H/8;
 							}
 						}
 						break;
 						
 					case 3:
 						printf("Circles empty and filled\n\r");
-						for(uint8_t x=0;x<OLED_W;x+=16)
+						for(uint8_t x=0;x<SSD1306_W;x+=16)
 							if(x<64)
-								oled_drawCircle(x, OLED_H/2, 15, 1);
+								ssd1306_drawCircle(x, SSD1306_H/2, 15, 1);
 							else
-								oled_fillCircle(x, OLED_H/2, 15, 1);
+								ssd1306_fillCircle(x, SSD1306_H/2, 15, 1);
 						break;
 					
 					case 4:
 						printf("Unscaled Text\n\r");
-						oled_drawstr(0,0, "This is a test", 1);
-						oled_drawstr(0,8, "of the emergency", 1);
-						oled_drawstr(0,16,"broadcasting", 1);
-						oled_drawstr(0,24,"system.",1);
-						if(OLED_H>32)
+						ssd1306_drawstr(0,0, "This is a test", 1);
+						ssd1306_drawstr(0,8, "of the emergency", 1);
+						ssd1306_drawstr(0,16,"broadcasting", 1);
+						ssd1306_drawstr(0,24,"system.",1);
+						if(SSD1306_H>32)
 						{
-							oled_drawstr(0,32, "Lorem ipsum", 1);
-							oled_drawstr(0,40, "dolor sit amet,", 1);
-							oled_drawstr(0,48,"consectetur", 1);
-							oled_drawstr(0,56,"adipiscing",1);
+							ssd1306_drawstr(0,32, "Lorem ipsum", 1);
+							ssd1306_drawstr(0,40, "dolor sit amet,", 1);
+							ssd1306_drawstr(0,48,"consectetur", 1);
+							ssd1306_drawstr(0,56,"adipiscing",1);
 						}
-						oled_xorrect(OLED_W/2, 0, OLED_W/2, OLED_W);
+						ssd1306_xorrect(SSD1306_W/2, 0, SSD1306_W/2, SSD1306_W);
 						break;
 						
 					case 5:
 						printf("Scaled Text 1, 2\n\r");
-						oled_drawstr_sz(0,0, "sz 8x8", 1, fontsize_8x8);
-						oled_drawstr_sz(0,16, "16x16", 1, fontsize_16x16);
+						ssd1306_drawstr_sz(0,0, "sz 8x8", 1, fontsize_8x8);
+						ssd1306_drawstr_sz(0,16, "16x16", 1, fontsize_16x16);
 						break;
 					
 					case 6:
 						printf("Scaled Text 4\n\r");
-						oled_drawstr_sz(0,0, "32x32", 1, fontsize_32x32);
+						ssd1306_drawstr_sz(0,0, "32x32", 1, fontsize_32x32);
 						break;
 					
 					
 					case 7:
 						printf("Scaled Text 8\n\r");
-						oled_drawstr_sz(0,0, "64", 1, fontsize_64x64);
+						ssd1306_drawstr_sz(0,0, "64", 1, fontsize_64x64);
 						break;
 
 					default:
 						break;
 				}
-				oled_refresh();
+				ssd1306_refresh();
 			
 				Delay_Ms(2000);
 			}
diff --git a/examples/i2c_oled/oled.h b/examples/i2c_oled/ssd1306.h
similarity index 62%
rename from examples/i2c_oled/oled.h
rename to examples/i2c_oled/ssd1306.h
index 378a27db98063cc95b6a60f0c6b81dfa59b389ea..19ea88db1053ad211038656b1dbe1452f8f6e2bb 100644
--- a/examples/i2c_oled/oled.h
+++ b/examples/i2c_oled/ssd1306.h
@@ -1,73 +1,59 @@
 /*
- * Single-File-Header for using I2C OLED
- * 03-29-2023 E. Brombaugh
+ * Single-File-Header for using SPI OLED
+ * 05-05-2023 E. Brombaugh
  */
 
-#ifndef _OLED_H
-#define _OLED_H
+#ifndef _SSD1306_H
+#define _SSD1306_H
 
 #include <stdint.h>
 #include <string.h>
-#include "i2c.h"
 #include "font_8x8.h"
 
-// OLED I2C address
-#define OLED_ADDR 0x3c
-
-// comfortable I2C packet size for this OLED
-#define OLED_PSZ 32
-
-// what type of OLED - uncomment just one
-//#define OLED_64X32
-#define OLED_128X32
-//#define OLED_128X64
+// comfortable packet size for this OLED
+#define SSD1306_PSZ 32
 
 // characteristics of each type
-#ifdef OLED_64X32
-#define OLED_W 64
-#define OLED_H 32
-#define OLED_FULLUSE
-#define OLED_OFFSET 32
+#if !defined (SSD1306_64X32) && !defined (SSD1306_128X32) && !defined (SSD1306_128X64)
+	#error "Please define the SSD1306_WXH resolution used in your application"
 #endif
 
-#ifdef OLED_128X32
-#define OLED_W 128
-#define OLED_H 32
-#define OLED_OFFSET 0
+#ifdef SSD1306_64X32
+#define SSD1306_W 64
+#define SSD1306_H 32
+#define SSD1306_FULLUSE
+#define SSD1306_OFFSET 32
 #endif
 
-#ifdef OLED_128X64
-#define OLED_W 128
-#define OLED_H 64
-#define OLED_FULLUSE
-#define OLED_OFFSET 0
+#ifdef SSD1306_128X32
+#define SSD1306_W 128
+#define SSD1306_H 32
+#define SSD1306_OFFSET 0
+#endif
+
+#ifdef SSD1306_128X64
+#define SSD1306_W 128
+#define SSD1306_H 64
+#define SSD1306_FULLUSE
+#define SSD1306_OFFSET 0
 #endif
 
 /*
  * send OLED command byte
  */
-uint8_t oled_cmd(uint8_t cmd)
+uint8_t ssd1306_cmd(uint8_t cmd)
 {
-	uint8_t pkt[2];
-	
-	pkt[0] = 0;
-	pkt[1] = cmd;
-	return i2c_send(OLED_ADDR, pkt, 2);
+	ssd1306_pkt_send(&cmd, 1, 1);
+	return 0;
 }
 
 /*
  * send OLED data packet (up to 32 bytes)
  */
-uint8_t oled_data(uint8_t *data, uint8_t sz)
+uint8_t ssd1306_data(uint8_t *data, uint8_t sz)
 {
-	uint8_t pkt[33];
-	
-	// max 32 bytes
-	sz = sz > 32 ? 32 : sz;
-	
-	pkt[0] = 0x40;
-	memcpy(&pkt[1], data, sz);
-	return i2c_send(OLED_ADDR, pkt, sz+1);
+	ssd1306_pkt_send(data, sz, 0);
+	return 0;
 }
 
 #define SSD1306_SETCONTRAST 0x81
@@ -104,13 +90,13 @@ uint8_t oled_data(uint8_t *data, uint8_t sz)
 #define vccstate SSD1306_SWITCHCAPVCC
 
 // OLED initialization commands for 128x32
-const uint8_t oled_init_array[] =
+const uint8_t ssd1306_init_array[] =
 {
     SSD1306_DISPLAYOFF,                    // 0xAE
     SSD1306_SETDISPLAYCLOCKDIV,            // 0xD5
     0x80,                                  // the suggested ratio 0x80
     SSD1306_SETMULTIPLEX,                  // 0xA8
-#ifdef OLED_64X32
+#ifdef SSD1306_64X32
 	0x1F,                                  // for 64-wide displays
 #else
 	0x3F,                                  // for 128-wide displays
@@ -139,17 +125,17 @@ const uint8_t oled_init_array[] =
 };
 
 // the display buffer
-uint8_t oled_buffer[OLED_W*OLED_H/8];
+uint8_t ssd1306_buffer[SSD1306_W*SSD1306_H/8];
 
 /*
  * set the buffer to a color
  */
-void oled_setbuf(uint8_t color)
+void ssd1306_setbuf(uint8_t color)
 {
-	memset(oled_buffer, color ? 0xFF : 0x00, sizeof(oled_buffer));
+	memset(ssd1306_buffer, color ? 0xFF : 0x00, sizeof(ssd1306_buffer));
 }
 
-#ifndef OLED_FULLUSE
+#ifndef SSD1306_FULLUSE
 /*
  * expansion array for OLED with every other row unused
  */
@@ -165,48 +151,48 @@ const uint8_t expand[16] =
 /*
  * Send the frame buffer
  */
-void oled_refresh(void)
+void ssd1306_refresh(void)
 {
 	uint16_t i;
 	
-	oled_cmd(SSD1306_COLUMNADDR);
-	oled_cmd(OLED_OFFSET);   // Column start address (0 = reset)
-	oled_cmd(OLED_OFFSET+OLED_W-1); // Column end address (127 = reset)
+	ssd1306_cmd(SSD1306_COLUMNADDR);
+	ssd1306_cmd(SSD1306_OFFSET);   // Column start address (0 = reset)
+	ssd1306_cmd(SSD1306_OFFSET+SSD1306_W-1); // Column end address (127 = reset)
 	
-	oled_cmd(SSD1306_PAGEADDR);
-	oled_cmd(0); // Page start address (0 = reset)
-	oled_cmd(7); // Page end address
+	ssd1306_cmd(SSD1306_PAGEADDR);
+	ssd1306_cmd(0); // Page start address (0 = reset)
+	ssd1306_cmd(7); // Page end address
 
-#ifdef OLED_FULLUSE
+#ifdef SSD1306_FULLUSE
 	/* for fully used rows just plow thru everything */
-    for(i=0;i<sizeof(oled_buffer);i+=OLED_PSZ)
+    for(i=0;i<sizeof(ssd1306_buffer);i+=SSD1306_PSZ)
 	{
 		/* send PSZ block of data */
-		oled_data(&oled_buffer[i], OLED_PSZ);
+		ssd1306_data(&ssd1306_buffer[i], SSD1306_PSZ);
 	}
 #else
 	/* for displays with odd rows unused expand bytes */
-	uint8_t tbuf[OLED_PSZ], j, k;
-    for(i=0;i<sizeof(oled_buffer);i+=128)
+	uint8_t tbuf[SSD1306_PSZ], j, k;
+    for(i=0;i<sizeof(ssd1306_buffer);i+=128)
 	{
 		/* low nybble */
-		for(j=0;j<128;j+=OLED_PSZ)
+		for(j=0;j<128;j+=SSD1306_PSZ)
 		{
-			for(k=0;k<OLED_PSZ;k++)
-				tbuf[k] = expand[oled_buffer[i+j+k]&0xf];
+			for(k=0;k<SSD1306_PSZ;k++)
+				tbuf[k] = expand[ssd1306_buffer[i+j+k]&0xf];
 			
 			/* send PSZ block of data */
-			oled_data(tbuf, OLED_PSZ);
+			ssd1306_data(tbuf, SSD1306_PSZ);
 		}
 		
 		/* high nybble */
-		for(j=0;j<128;j+=OLED_PSZ)
+		for(j=0;j<128;j+=SSD1306_PSZ)
 		{
-			for(k=0;k<OLED_PSZ;k++)
-				tbuf[k] = expand[(oled_buffer[i+j+k]>>4)&0xf];
+			for(k=0;k<SSD1306_PSZ;k++)
+				tbuf[k] = expand[(ssd1306_buffer[i+j+k]>>4)&0xf];
 			
 			/* send PSZ block of data */
-			oled_data(tbuf, OLED_PSZ);
+			ssd1306_data(tbuf, SSD1306_PSZ);
 		}
 	}
 #endif
@@ -215,71 +201,71 @@ void oled_refresh(void)
 /*
  * plot a pixel in the buffer
  */
-void oled_drawPixel(uint8_t x, uint8_t y, uint8_t color)
+void ssd1306_drawPixel(uint8_t x, uint8_t y, uint8_t color)
 {
 	uint16_t addr;
 	
 	/* clip */
-	if(x >= OLED_W)
+	if(x >= SSD1306_W)
 		return;
-	if(y >= OLED_H)
+	if(y >= SSD1306_H)
 		return;
 	
 	/* compute buffer address */
-	addr = x + OLED_W*(y/8);
+	addr = x + SSD1306_W*(y/8);
 	
 	/* set/clear bit in buffer */
 	if(color)
-		oled_buffer[addr] |= (1<<(y&7));
+		ssd1306_buffer[addr] |= (1<<(y&7));
 	else
-		oled_buffer[addr] &= ~(1<<(y&7));
+		ssd1306_buffer[addr] &= ~(1<<(y&7));
 }
 
 /*
  * plot a pixel in the buffer
  */
-void oled_xorPixel(uint8_t x, uint8_t y)
+void ssd1306_xorPixel(uint8_t x, uint8_t y)
 {
 	uint16_t addr;
 	
 	/* clip */
-	if(x >= OLED_W)
+	if(x >= SSD1306_W)
 		return;
-	if(y >= OLED_H)
+	if(y >= SSD1306_H)
 		return;
 	
 	/* compute buffer address */
-	addr = x + OLED_W*(y/8);
+	addr = x + SSD1306_W*(y/8);
 	
-	oled_buffer[addr] ^= (1<<(y&7));
+	ssd1306_buffer[addr] ^= (1<<(y&7));
 }
 
 /*
  *  fast vert line
  */
-void oled_drawFastVLine(uint8_t x, uint8_t y, uint8_t h, uint8_t color)
+void ssd1306_drawFastVLine(uint8_t x, uint8_t y, uint8_t h, uint8_t color)
 {
 	// clipping
-	if((x >= OLED_W) || (y >= OLED_H)) return;
-	if((y+h-1) >= OLED_H) h = OLED_H-y;
+	if((x >= SSD1306_W) || (y >= SSD1306_H)) return;
+	if((y+h-1) >= SSD1306_H) h = SSD1306_H-y;
 	while(h--)
 	{
-        oled_drawPixel(x, y++, color);
+        ssd1306_drawPixel(x, y++, color);
 	}
 }
 
 /*
  *  fast horiz line
  */
-void oled_drawFastHLine(uint8_t x, uint8_t y, uint8_t w, uint8_t color)
+void ssd1306_drawFastHLine(uint8_t x, uint8_t y, uint8_t w, uint8_t color)
 {
 	// clipping
-	if((x >= OLED_W) || (y >= OLED_H)) return;
-	if((x+w-1) >= OLED_W)  w = OLED_W-x;
+	if((x >= SSD1306_W) || (y >= SSD1306_H)) return;
+	if((x+w-1) >= SSD1306_W)  w = SSD1306_W-x;
 
 	while (w--)
 	{
-        oled_drawPixel(x++, y, color);
+        ssd1306_drawPixel(x++, y, color);
 	}
 }
 
@@ -304,7 +290,7 @@ void gfx_swap(uint16_t *z0, uint16_t *z1)
 /*
  * Bresenham line draw routine swiped from Wikipedia
  */
-void oled_drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint8_t color)
+void ssd1306_drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint8_t color)
 {
 	int16_t steep;
 	int16_t deltax, deltay, error, ystep, x, y;
@@ -341,10 +327,10 @@ void oled_drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint8_t c
 		/* plot point */
 		if(steep)
 			/* flip point & plot */
-			oled_drawPixel(y, x, color);
+			ssd1306_drawPixel(y, x, color);
 		else
 			/* just plot */
-			oled_drawPixel(x, y, color);
+			ssd1306_drawPixel(x, y, color);
 
 		/* update error */
 		error = error - deltay;
@@ -361,7 +347,7 @@ void oled_drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint8_t c
 /*
  *  draws a circle
  */
-void oled_drawCircle(int16_t x, int16_t y, int16_t radius, int8_t color)
+void ssd1306_drawCircle(int16_t x, int16_t y, int16_t radius, int8_t color)
 {
     /* Bresenham algorithm */
     int16_t x_pos = -radius;
@@ -370,10 +356,10 @@ void oled_drawCircle(int16_t x, int16_t y, int16_t radius, int8_t color)
     int16_t e2;
 
     do {
-        oled_drawPixel(x - x_pos, y + y_pos, color);
-        oled_drawPixel(x + x_pos, y + y_pos, color);
-        oled_drawPixel(x + x_pos, y - y_pos, color);
-        oled_drawPixel(x - x_pos, y - y_pos, color);
+        ssd1306_drawPixel(x - x_pos, y + y_pos, color);
+        ssd1306_drawPixel(x + x_pos, y + y_pos, color);
+        ssd1306_drawPixel(x + x_pos, y - y_pos, color);
+        ssd1306_drawPixel(x - x_pos, y - y_pos, color);
         e2 = err;
         if (e2 <= y_pos) {
             err += ++y_pos * 2 + 1;
@@ -390,7 +376,7 @@ void oled_drawCircle(int16_t x, int16_t y, int16_t radius, int8_t color)
 /*
  *  draws a filled circle
  */
-void oled_fillCircle(int16_t x, int16_t y, int16_t radius, int8_t color)
+void ssd1306_fillCircle(int16_t x, int16_t y, int16_t radius, int8_t color)
 {
     /* Bresenham algorithm */
     int16_t x_pos = -radius;
@@ -399,12 +385,12 @@ void oled_fillCircle(int16_t x, int16_t y, int16_t radius, int8_t color)
     int16_t e2;
 
     do {
-        oled_drawPixel(x - x_pos, y + y_pos, color);
-        oled_drawPixel(x + x_pos, y + y_pos, color);
-        oled_drawPixel(x + x_pos, y - y_pos, color);
-        oled_drawPixel(x - x_pos, y - y_pos, color);
-        oled_drawFastHLine(x + x_pos, y + y_pos, 2 * (-x_pos) + 1, color);
-        oled_drawFastHLine(x + x_pos, y - y_pos, 2 * (-x_pos) + 1, color);
+        ssd1306_drawPixel(x - x_pos, y + y_pos, color);
+        ssd1306_drawPixel(x + x_pos, y + y_pos, color);
+        ssd1306_drawPixel(x + x_pos, y - y_pos, color);
+        ssd1306_drawPixel(x - x_pos, y - y_pos, color);
+        ssd1306_drawFastHLine(x + x_pos, y + y_pos, 2 * (-x_pos) + 1, color);
+        ssd1306_drawFastHLine(x + x_pos, y - y_pos, 2 * (-x_pos) + 1, color);
         e2 = err;
         if (e2 <= y_pos) {
             err += ++y_pos * 2 + 1;
@@ -421,18 +407,18 @@ void oled_fillCircle(int16_t x, int16_t y, int16_t radius, int8_t color)
 /*
  *  draw a rectangle
  */
-void oled_drawRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t color)
+void ssd1306_drawRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t color)
 {
-	oled_drawFastVLine(x, y, h, color);
-	oled_drawFastVLine(x+w-1, y, h, color);
-	oled_drawFastHLine(x, y, w, color);
-	oled_drawFastHLine(x, y+h-1, w, color);
+	ssd1306_drawFastVLine(x, y, h, color);
+	ssd1306_drawFastVLine(x+w-1, y, h, color);
+	ssd1306_drawFastHLine(x, y, w, color);
+	ssd1306_drawFastHLine(x, y+h-1, w, color);
 }
 
 /*
  * fill a rectangle
  */
-void oled_fillRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t color)
+void ssd1306_fillRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t color)
 {
 	uint8_t m, n=y, iw = w;
 	
@@ -445,7 +431,7 @@ void oled_fillRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t color)
 		while(w--)
 		{
 			/* invert pixels */
-			oled_drawPixel(m++, n, color);
+			ssd1306_drawPixel(m++, n, color);
 		}
 		n++;
 	}
@@ -454,7 +440,7 @@ void oled_fillRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t color)
 /*
  * invert a rectangle in the buffer
  */
-void oled_xorrect(uint8_t x, uint8_t y, uint8_t w, uint8_t h)
+void ssd1306_xorrect(uint8_t x, uint8_t y, uint8_t w, uint8_t h)
 {
 	uint8_t m, n=y, iw = w;
 	
@@ -467,7 +453,7 @@ void oled_xorrect(uint8_t x, uint8_t y, uint8_t w, uint8_t h)
 		while(w--)
 		{
 			/* invert pixels */
-			oled_xorPixel(m++, n);
+			ssd1306_xorPixel(m++, n);
 		}
 		n++;
 	}
@@ -476,7 +462,7 @@ void oled_xorrect(uint8_t x, uint8_t y, uint8_t w, uint8_t h)
 /*
  * Draw character to the display buffer
  */
-void oled_drawchar(uint8_t x, uint8_t y, uint8_t chr, uint8_t color)
+void ssd1306_drawchar(uint8_t x, uint8_t y, uint8_t chr, uint8_t color)
 {
 	uint16_t i, j, col;
 	uint8_t d;
@@ -491,7 +477,7 @@ void oled_drawchar(uint8_t x, uint8_t y, uint8_t chr, uint8_t color)
 			else
 				col = (~color)&1;
 			
-			oled_drawPixel(x+j, y+i, col);
+			ssd1306_drawPixel(x+j, y+i, col);
 			
 			// next bit
 			d <<= 1;
@@ -502,13 +488,13 @@ void oled_drawchar(uint8_t x, uint8_t y, uint8_t chr, uint8_t color)
 /*
  * draw a string to the display
  */
-void oled_drawstr(uint8_t x, uint8_t y, char *str, uint8_t color)
+void ssd1306_drawstr(uint8_t x, uint8_t y, char *str, uint8_t color)
 {
 	uint8_t c;
 	
 	while((c=*str++))
 	{
-		oled_drawchar(x, y, c, color);
+		ssd1306_drawchar(x, y, c, color);
 		x += 8;
 		if(x>120)
 			break;
@@ -528,7 +514,7 @@ typedef enum {
 /*
  * Draw character to the display buffer, scaled to size
  */
-void oled_drawchar_sz(uint8_t x, uint8_t y, uint8_t chr, uint8_t color, font_size_t font_size)
+void ssd1306_drawchar_sz(uint8_t x, uint8_t y, uint8_t chr, uint8_t color, font_size_t font_size)
 {
     uint16_t i, j, col;
     uint8_t d;
@@ -554,7 +540,7 @@ void oled_drawchar_sz(uint8_t x, uint8_t y, uint8_t chr, uint8_t color, font_siz
             // Draw the pixel at the original size and scaled size using nested for-loops
             for (uint8_t k = 0; k < font_scale; k++) {
                 for (uint8_t l = 0; l < font_scale; l++) {
-                    oled_drawPixel(x + (j * font_scale) + k, y + (i * font_scale) + l, col);
+                    ssd1306_drawPixel(x + (j * font_scale) + k, y + (i * font_scale) + l, col);
                 }
             }
 
@@ -567,13 +553,13 @@ void oled_drawchar_sz(uint8_t x, uint8_t y, uint8_t chr, uint8_t color, font_siz
 /*
  * draw a string to the display buffer, scaled to size
  */
-void oled_drawstr_sz(uint8_t x, uint8_t y, char *str, uint8_t color, font_size_t font_size)
+void ssd1306_drawstr_sz(uint8_t x, uint8_t y, char *str, uint8_t color, font_size_t font_size)
 {
 	uint8_t c;
 	
 	while((c=*str++))
 	{
-		oled_drawchar_sz(x, y, c, color, font_size);
+		ssd1306_drawchar_sz(x, y, c, color, font_size);
 		x += 8 * font_size;
 		if(x>128 - 8 * font_size)
 			break;
@@ -583,22 +569,22 @@ void oled_drawstr_sz(uint8_t x, uint8_t y, char *str, uint8_t color, font_size_t
 /*
  * initialize I2C and OLED
  */
-uint8_t oled_init(void)
+uint8_t ssd1306_init(void)
 {
-	// initialize port
-	i2c_init();
+	// pulse reset
+	ssd1306_rst();
 	
 	// initialize OLED
-	uint8_t *cmd_list = (uint8_t *)oled_init_array;
+	uint8_t *cmd_list = (uint8_t *)ssd1306_init_array;
 	while(*cmd_list != SSD1306_TERMINATE_CMDS)
 	{
-		if(oled_cmd(*cmd_list++))
+		if(ssd1306_cmd(*cmd_list++))
 			return 1;
 	}
 	
 	// clear display
-	oled_setbuf(0);
-	oled_refresh();
+	ssd1306_setbuf(0);
+	ssd1306_refresh();
 	
 	return 0;
 }
diff --git a/examples/i2c_oled/i2c.h b/examples/i2c_oled/ssd1306_i2c.h
similarity index 57%
rename from examples/i2c_oled/i2c.h
rename to examples/i2c_oled/ssd1306_i2c.h
index 335aafa969ffee617a09d8346f9ebc895f7ffb37..3ae7ce23b0277156cfb4571ed757eeb6c4b54c5f 100644
--- a/examples/i2c_oled/i2c.h
+++ b/examples/i2c_oled/ssd1306_i2c.h
@@ -1,29 +1,34 @@
 /*
- * Single-File-Header for I2C
- * 04-07-2023 E. Brombaugh
+ * Single-File-Header for SSD1306 I2C interface
+ * 05-07-2023 E. Brombaugh
  */
 
-#ifndef _I2C_H
-#define _I2C_H
+#ifndef _SSD1306_I2C_H
+#define _SSD1306_I2C_H
+
+#include <string.h>
+
+// SSD1306 I2C address
+#define SSD1306_I2C_ADDR 0x3c
 
 // I2C Bus clock rate - must be lower the Logic clock rate
-#define I2C_CLKRATE 1000000
+#define SSD1306_I2C_CLKRATE 1000000
 
 // I2C Logic clock rate - must be higher than Bus clock rate
-#define I2C_PRERATE 2000000
+#define SSD1306_I2C_PRERATE 2000000
 
 // uncomment this for high-speed 36% duty cycle, otherwise 33%
-#define I2C_DUTY
+#define SSD1306_I2C_DUTY
 
 // I2C Timeout count
 #define TIMEOUT_MAX 100000
 
 // uncomment this to enable IRQ-driven operation
-#define I2C_IRQ
+//#define SSD1306_I2C_IRQ
 
-#ifdef I2C_IRQ
+#ifdef SSD1306_I2C_IRQ
 // some stuff that IRQ mode needs
-volatile uint8_t i2c_send_buffer[64], *i2c_send_ptr, i2c_send_sz, i2c_irq_state;
+volatile uint8_t ssd1306_i2c_send_buffer[64], *ssd1306_i2c_send_ptr, ssd1306_i2c_send_sz, ssd1306_i2c_irq_state;
 
 // uncomment this to enable time diags in IRQ
 //#define IRQ_DIAG
@@ -32,7 +37,7 @@ volatile uint8_t i2c_send_buffer[64], *i2c_send_ptr, i2c_send_sz, i2c_irq_state;
 /*
  * init just I2C
  */
-void i2c_setup(void)
+void ssd1306_i2c_setup(void)
 {
 	uint16_t tempreg;
 	
@@ -43,34 +48,34 @@ void i2c_setup(void)
 	// set freq
 	tempreg = I2C1->CTLR2;
 	tempreg &= ~I2C_CTLR2_FREQ;
-	tempreg |= (APB_CLOCK/I2C_PRERATE)&I2C_CTLR2_FREQ;
+	tempreg |= (APB_CLOCK/SSD1306_I2C_PRERATE)&I2C_CTLR2_FREQ;
 	I2C1->CTLR2 = tempreg;
 	
 	// Set clock config
 	tempreg = 0;
-#if (I2C_CLKRATE <= 100000)
+#if (SSD1306_I2C_CLKRATE <= 100000)
 	// standard mode good to 100kHz
-	tempreg = (APB_CLOCK/(2*I2C_CLKRATE))&I2C_CKCFGR_CCR;
+	tempreg = (APB_CLOCK/(2*SSD1306_I2C_CLKRATE))&SSD1306_I2C_CKCFGR_CCR;
 #else
 	// fast mode over 100kHz
-#ifndef I2C_DUTY
+#ifndef SSD1306_I2C_DUTY
 	// 33% duty cycle
-	tempreg = (APB_CLOCK/(3*I2C_CLKRATE))&I2C_CKCFGR_CCR;
+	tempreg = (APB_CLOCK/(3*SSD1306_I2C_CLKRATE))&SSD1306_I2C_CKCFGR_CCR;
 #else
 	// 36% duty cycle
-	tempreg = (APB_CLOCK/(25*I2C_CLKRATE))&I2C_CKCFGR_CCR;
+	tempreg = (APB_CLOCK/(25*SSD1306_I2C_CLKRATE))&I2C_CKCFGR_CCR;
 	tempreg |= I2C_CKCFGR_DUTY;
 #endif
 	tempreg |= I2C_CKCFGR_FS;
 #endif
 	I2C1->CKCFGR = tempreg;
 
-#ifdef I2C_IRQ
+#ifdef SSD1306_I2C_IRQ
 	// enable IRQ driven operation
 	NVIC_EnableIRQ(I2C1_EV_IRQn);
 	
 	// initialize the state
-	i2c_irq_state = 0;
+	ssd1306_i2c_irq_state = 0;
 #endif
 	
 	// Enable I2C
@@ -80,37 +85,6 @@ void i2c_setup(void)
 	I2C1->CTLR1 |= I2C_CTLR1_ACK;
 }
 
-/*
- * init I2C and GPIO
- */
-void i2c_init(void)
-{
-	// Enable GPIOC and I2C
-	RCC->APB2PCENR |= RCC_APB2Periph_GPIOC;
-	RCC->APB1PCENR |= RCC_APB1Periph_I2C1;
-	
-	// PC1 is SDA, 10MHz Output, alt func, open-drain
-	GPIOC->CFGLR &= ~(0xf<<(4*1));
-	GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_OD_AF)<<(4*1);
-	
-	// PC2 is SCL, 10MHz Output, alt func, open-drain
-	GPIOC->CFGLR &= ~(0xf<<(4*2));
-	GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_OD_AF)<<(4*2);
-	
-#ifdef IRQ_DIAG
-	// GPIO diags on PC3/PC4
-	GPIOC->CFGLR &= ~(0xf<<(4*3));
-	GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*3);
-	GPIOC->BSHR = (1<<(16+3));
-	GPIOC->CFGLR &= ~(0xf<<(4*4));
-	GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*4);
-	GPIOC->BSHR = (1<<(16+4));
-#endif
-
-	// load I2C regs
-	i2c_setup();
-}
-
 /*
  * error descriptions
  */
@@ -126,37 +100,37 @@ char *errstr[] =
 /*
  * error handler
  */
-uint8_t i2c_error(uint8_t err)
+uint8_t ssd1306_i2c_error(uint8_t err)
 {
 	// report error
-	printf("i2c_error - timeout waiting for %s\n\r", errstr[err]);
+	printf("ssd1306_i2c_error - timeout waiting for %s\n\r", errstr[err]);
 	
 	// reset & initialize I2C
-	i2c_setup();
+	ssd1306_i2c_setup();
 
 	return 1;
 }
 
 // event codes we use
-#define  I2C_EVENT_MASTER_MODE_SELECT ((uint32_t)0x00030001)  /* BUSY, MSL and SB flag */
-#define  I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED ((uint32_t)0x00070082)  /* BUSY, MSL, ADDR, TXE and TRA flags */
-#define  I2C_EVENT_MASTER_BYTE_TRANSMITTED ((uint32_t)0x00070084)  /* TRA, BUSY, MSL, TXE and BTF flags */
+#define  SSD1306_I2C_EVENT_MASTER_MODE_SELECT ((uint32_t)0x00030001)  /* BUSY, MSL and SB flag */
+#define  SSD1306_I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED ((uint32_t)0x00070082)  /* BUSY, MSL, ADDR, TXE and TRA flags */
+#define  SSD1306_I2C_EVENT_MASTER_BYTE_TRANSMITTED ((uint32_t)0x00070084)  /* TRA, BUSY, MSL, TXE and BTF flags */
 
 /*
  * check for 32-bit event codes
  */
-uint8_t i2c_chk_evt(uint32_t event_mask)
+uint8_t ssd1306_i2c_chk_evt(uint32_t event_mask)
 {
 	/* read order matters here! STAR1 before STAR2!! */
 	uint32_t status = I2C1->STAR1 | (I2C1->STAR2<<16);
 	return (status & event_mask) == event_mask;
 }
 
-#ifdef I2C_IRQ
+#ifdef SSD1306_I2C_IRQ
 /*
  * packet send for IRQ-driven operation
  */
-uint8_t i2c_send(uint8_t addr, uint8_t *data, uint8_t sz)
+uint8_t ssd1306_i2c_send(uint8_t addr, uint8_t *data, uint8_t sz)
 {
 	int32_t timeout;
 	
@@ -165,11 +139,11 @@ uint8_t i2c_send(uint8_t addr, uint8_t *data, uint8_t sz)
 #endif
 	
 	// error out if buffer under/overflow
-	if((sz > sizeof(i2c_send_buffer)) || !sz)
+	if((sz > sizeof(ssd1306_i2c_send_buffer)) || !sz)
 		return 2;
 	
 	// wait for previous packet to finish
-	while(i2c_irq_state);
+	while(ssd1306_i2c_irq_state);
 	
 #ifdef IRQ_DIAG
 	GPIOC->BSHR = (1<<(16+3));
@@ -177,37 +151,37 @@ uint8_t i2c_send(uint8_t addr, uint8_t *data, uint8_t sz)
 #endif
 	
 	// init buffer for sending
-	i2c_send_sz = sz;
-	i2c_send_ptr = i2c_send_buffer;
-	memcpy((uint8_t *)i2c_send_buffer, data, sz);
+	ssd1306_i2c_send_sz = sz;
+	ssd1306_i2c_send_ptr = ssd1306_i2c_send_buffer;
+	memcpy((uint8_t *)ssd1306_i2c_send_buffer, data, sz);
 	
 	// wait for not busy
 	timeout = TIMEOUT_MAX;
 	while((I2C1->STAR2 & I2C_STAR2_BUSY) && (timeout--));
 	if(timeout==-1)
-		return i2c_error(0);
+		return ssd1306_i2c_error(0);
 
 	// Set START condition
 	I2C1->CTLR1 |= I2C_CTLR1_START;
 
 	// wait for master mode select
 	timeout = TIMEOUT_MAX;
-	while((!i2c_chk_evt(I2C_EVENT_MASTER_MODE_SELECT)) && (timeout--));
+	while((!ssd1306_i2c_chk_evt(SSD1306_I2C_EVENT_MASTER_MODE_SELECT)) && (timeout--));
 	if(timeout==-1)
-		return i2c_error(1);
+		return ssd1306_i2c_error(1);
 	
 	// send 7-bit address + write flag
 	I2C1->DATAR = addr<<1;
 
 	// wait for transmit condition
 	timeout = TIMEOUT_MAX;
-	while((!i2c_chk_evt(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) && (timeout--));
+	while((!ssd1306_i2c_chk_evt(SSD1306_I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) && (timeout--));
 	if(timeout==-1)
-		return i2c_error(2);
+		return ssd1306_i2c_error(2);
 
 	// Enable TXE interrupt
 	I2C1->CTLR2 |= I2C_CTLR2_ITBUFEN | I2C_CTLR2_ITEVTEN;
-	i2c_irq_state = 1;
+	ssd1306_i2c_irq_state = 1;
 
 #ifdef IRQ_DIAG
 	GPIOC->BSHR = (1<<(16+4));
@@ -237,20 +211,20 @@ void I2C1_EV_IRQHandler(void)
 	if(STAR1 & I2C_STAR1_TXE)
 	{
 		/* check for remaining data */
-		if(i2c_send_sz--)
-			I2C1->DATAR = *i2c_send_ptr++;
+		if(ssd1306_i2c_send_sz--)
+			I2C1->DATAR = *ssd1306_i2c_send_ptr++;
 
 		/* was that the last byte? */
-		if(!i2c_send_sz)
+		if(!ssd1306_i2c_send_sz)
 		{
 			// disable TXE interrupt
 			I2C1->CTLR2 &= ~(I2C_CTLR2_ITBUFEN | I2C_CTLR2_ITEVTEN);
 			
 			// reset IRQ state
-			i2c_irq_state = 0;
+			ssd1306_i2c_irq_state = 0;
 			
 			// wait for tx complete
-			while(!i2c_chk_evt(I2C_EVENT_MASTER_BYTE_TRANSMITTED));
+			while(!ssd1306_i2c_chk_evt(SSD1306_I2C_EVENT_MASTER_BYTE_TRANSMITTED));
 
 			// set STOP condition
 			I2C1->CTLR1 |= I2C_CTLR1_STOP;
@@ -263,9 +237,9 @@ void I2C1_EV_IRQHandler(void)
 }
 #else
 /*
- * packet send for polled operation
+ * low-level packet send for blocking polled operation via i2c
  */
-uint8_t i2c_send(uint8_t addr, uint8_t *data, uint8_t sz)
+uint8_t ssd1306_i2c_send(uint8_t addr, uint8_t *data, uint8_t sz)
 {
 	int32_t timeout;
 	
@@ -273,25 +247,25 @@ uint8_t i2c_send(uint8_t addr, uint8_t *data, uint8_t sz)
 	timeout = TIMEOUT_MAX;
 	while((I2C1->STAR2 & I2C_STAR2_BUSY) && (timeout--));
 	if(timeout==-1)
-		return i2c_error(0);
+		return ssd1306_i2c_error(0);
 
 	// Set START condition
 	I2C1->CTLR1 |= I2C_CTLR1_START;
 	
 	// wait for master mode select
 	timeout = TIMEOUT_MAX;
-	while((!i2c_chk_evt(I2C_EVENT_MASTER_MODE_SELECT)) && (timeout--));
+	while((!ssd1306_i2c_chk_evt(SSD1306_I2C_EVENT_MASTER_MODE_SELECT)) && (timeout--));
 	if(timeout==-1)
-		return i2c_error(1);
+		return ssd1306_i2c_error(1);
 	
 	// send 7-bit address + write flag
 	I2C1->DATAR = addr<<1;
 
 	// wait for transmit condition
 	timeout = TIMEOUT_MAX;
-	while((!i2c_chk_evt(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) && (timeout--));
+	while((!ssd1306_i2c_chk_evt(SSD1306_I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) && (timeout--));
 	if(timeout==-1)
-		return i2c_error(2);
+		return ssd1306_i2c_error(2);
 
 	// send data one byte at a time
 	while(sz--)
@@ -300,7 +274,7 @@ uint8_t i2c_send(uint8_t addr, uint8_t *data, uint8_t sz)
 		timeout = TIMEOUT_MAX;
 		while(!(I2C1->STAR1 & I2C_STAR1_TXE) && (timeout--));
 		if(timeout==-1)
-			return i2c_error(3);
+			return ssd1306_i2c_error(3);
 		
 		// send command
 		I2C1->DATAR = *data++;
@@ -308,9 +282,9 @@ uint8_t i2c_send(uint8_t addr, uint8_t *data, uint8_t sz)
 
 	// wait for tx complete
 	timeout = TIMEOUT_MAX;
-	while((!i2c_chk_evt(I2C_EVENT_MASTER_BYTE_TRANSMITTED)) && (timeout--));
+	while((!ssd1306_i2c_chk_evt(SSD1306_I2C_EVENT_MASTER_BYTE_TRANSMITTED)) && (timeout--));
 	if(timeout==-1)
-		return i2c_error(4);
+		return ssd1306_i2c_error(4);
 
 	// set STOP condition
 	I2C1->CTLR1 |= I2C_CTLR1_STOP;
@@ -320,4 +294,70 @@ uint8_t i2c_send(uint8_t addr, uint8_t *data, uint8_t sz)
 }
 #endif
 
+/*
+ * high-level packet send for I2C
+ */
+uint8_t ssd1306_pkt_send(uint8_t *data, uint8_t sz, uint8_t cmd)
+{
+	uint8_t pkt[33];
+	
+	/* build command or data packets */
+	if(cmd)
+	{
+		pkt[0] = 0;
+		pkt[1] = *data;
+	}
+	else
+	{
+		pkt[0] = 0x40;
+		memcpy(&pkt[1], data, sz);
+	}
+	return ssd1306_i2c_send(SSD1306_I2C_ADDR, pkt, sz+1);
+}
+
+/*
+ * init I2C and GPIO
+ */
+uint8_t ssd1306_i2c_init(void)
+{
+	// Enable GPIOC and I2C
+	RCC->APB2PCENR |= RCC_APB2Periph_GPIOC;
+	RCC->APB1PCENR |= RCC_APB1Periph_I2C1;
+	
+	// PC1 is SDA, 10MHz Output, alt func, open-drain
+	GPIOC->CFGLR &= ~(0xf<<(4*1));
+	GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_OD_AF)<<(4*1);
+	
+	// PC2 is SCL, 10MHz Output, alt func, open-drain
+	GPIOC->CFGLR &= ~(0xf<<(4*2));
+	GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_OD_AF)<<(4*2);
+	
+#ifdef IRQ_DIAG
+	// GPIO diags on PC3/PC4
+	GPIOC->CFGLR &= ~(0xf<<(4*3));
+	GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*3);
+	GPIOC->BSHR = (1<<(16+3));
+	GPIOC->CFGLR &= ~(0xf<<(4*4));
+	GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*4);
+	GPIOC->BSHR = (1<<(16+4));
+#endif
+
+	// load I2C regs
+	ssd1306_i2c_setup();
+	
+#if 0
+	// test if SSD1306 is on the bus by sending display off command
+	uint8_t command = 0xAF;
+	return ssd1306_pkt_send(&command, 1, 1);
+#else
+	return 0;
+#endif
+}
+
+/*
+ * reset is not used for SSD1306 I2C interface
+ */
+void ssd1306_rst(void)
+{
+}
 #endif
diff --git a/examples/spi_oled/Makefile b/examples/spi_oled/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..77b7065cb887fa2af72cdfcb440dcd92c528fee0
--- /dev/null
+++ b/examples/spi_oled/Makefile
@@ -0,0 +1,12 @@
+all : flash
+
+TARGET:=spi_oled
+
+CFLAGS+=-I../i2c_oled
+#CFLAGS+=-DSTDOUT_UART
+
+include ../../ch32v003fun/ch32v003fun.mk
+
+flash : cv_flash
+clean : cv_clean
+
diff --git a/examples/spi_oled/README.md b/examples/spi_oled/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..36eb8b3fcacda15cd7710bf74a02d315e5e6da20
--- /dev/null
+++ b/examples/spi_oled/README.md
@@ -0,0 +1,36 @@
+# SPI OLED demonstration
+This example code demonstrates use of the CH32V003 SPI peripheral with an SSD1306
+OLED. Three different OLED sizes are supported - 64x32, 128x32, and 128x64.
+It provides a generic SPI port initialization and transmit (write-only) low level
+interface and a high-level graphics driver with pixels, lines, circles, rectangles
+and 8x8 character font rendering. Out of the box this demo runs Conway's Life.
+
+
+
+https://user-images.githubusercontent.com/1132011/236727389-7b778260-87c0-43a8-89e1-8a5459719fdd.mp4
+
+
+
+## Build options
+There are two build-time options in the source-
+
+In spi_oled.c:
+* SSD1306_64X32, SSD1306_128X32, SSD1306_128X64 - choose only one of these
+depending on the type of OLED you've got.
+
+in ssd1306.h:
+* SSD1306_PSZ - the number of bytes to send per SPI data packet. The default value
+of 32 seems to work well. Smaller values are allowed but may result in slower
+refresh rates.
+
+## Use
+Connect an SSD1306-based OLED in SPI interface mode as follows:
+* PC2 - RST
+* PC3 - CS
+* PC4 - DC
+* PC5 - SCK / D0
+* PC6 - MOSI / D1
+
+Observe Conway's Life starting with random pixels, running for 500 iterations and
+then restarting with new random pixels.
+
diff --git a/examples/spi_oled/debug.sh b/examples/spi_oled/debug.sh
new file mode 100755
index 0000000000000000000000000000000000000000..bb05a9465959cb153fad95bba4b2175667830528
--- /dev/null
+++ b/examples/spi_oled/debug.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+# before running this you should start OOCD server
+#../../../MRS_Toolchain_Linux_x64_V1.70/OpenOCD/bin/openocd -f ../../../MRS_Toolchain_Linux_x64_V1.70/OpenOCD/bin/wch-riscv.cfg
+ 
+../../../MRS_Toolchain_Linux_x64_V1.70/RISC-V\ Embedded\ GCC/bin/riscv-none-embed-gdb
diff --git a/examples/spi_oled/spi_oled.c b/examples/spi_oled/spi_oled.c
new file mode 100644
index 0000000000000000000000000000000000000000..f5231e8833bc00c821be9c87fb8749363ed3125e
--- /dev/null
+++ b/examples/spi_oled/spi_oled.c
@@ -0,0 +1,277 @@
+/*
+ * Example for using SPI with 128x32 graphic OLED
+ * 03-29-2023 E. Brombaugh
+ */
+
+// Could be defined here, or in the processor defines.
+#define SYSTEM_CORE_CLOCK 48000000
+#define APB_CLOCK SYSTEM_CORE_CLOCK
+
+// what type of OLED - uncomment just one
+//#define SSD1306_64X32
+//#define SSD1306_128X32
+#define SSD1306_128X64
+
+#include "ch32v003fun.h"
+#include <stdio.h>
+#include "ssd1306_spi.h"
+#include "ssd1306.h"
+
+/* White Noise Generator State */
+#define NOISE_BITS 8
+#define NOISE_MASK ((1<<NOISE_BITS)-1)
+#define NOISE_POLY_TAP0 31
+#define NOISE_POLY_TAP1 21
+#define NOISE_POLY_TAP2 1
+#define NOISE_POLY_TAP3 0
+uint32_t lfsr = 1;
+
+/*
+ * random byte generator
+ */
+uint8_t rand8(void)
+{
+	uint8_t bit;
+	uint32_t new_data;
+	
+	for(bit=0;bit<NOISE_BITS;bit++)
+	{
+		new_data = ((lfsr>>NOISE_POLY_TAP0) ^
+					(lfsr>>NOISE_POLY_TAP1) ^
+					(lfsr>>NOISE_POLY_TAP2) ^
+					(lfsr>>NOISE_POLY_TAP3));
+		lfsr = (lfsr<<1) | (new_data&1);
+	}
+	
+	return lfsr&NOISE_MASK;
+}
+
+/*
+ * return pixel value in buffer
+ */
+uint8_t getpix(uint8_t *buf, int16_t x, int16_t y)
+{
+	if((x<0) || (x>=SSD1306_W))
+		return 0;
+	if((y<0) || (y>=SSD1306_H))
+		return 0;
+	return buf[x+SSD1306_W*(y>>3)] & (1<<(y&7)) ? 1 : 0;
+}
+
+/*
+ * conway's life on B/W OLED
+ */
+void conway(uint8_t *buf)
+{
+	uint8_t col[2][(SSD1306_H>>3)];
+	int16_t x, y, B, b, sum, d, colidx = 0;
+	
+	/* iterate over columns */
+	for(x=0;x<SSD1306_W;x++)
+	{
+		/* iterate bytes in column */
+		for(B=0;B<(SSD1306_H>>3);B++)
+		{
+			/* init byte accum */
+			d = 0;
+			
+			/* iterate over bits in byte */
+			for(b=0;b<8;b++)
+			{
+				/* which row */
+				y = B*8+b;
+				
+				/* prep byte accum for this bit */
+				d >>= 1;
+				
+				/* count live neighbors */
+				sum = getpix(buf, x-1, y-1)+getpix(buf, x, y-1)+getpix(buf, x+1, y-1)+
+					getpix(buf, x-1, y)+getpix(buf, x+1, y)+
+					getpix(buf, x-1, y+1)+getpix(buf, x, y+1)+getpix(buf, x+1, y+1);
+				
+				/* check life for next cycle */
+				if(getpix(buf, x, y))
+				{
+					/* live cell */
+					if((sum==2)||(sum==3))
+						d |= 128;
+				}
+				else
+				{
+					/* dead cell */
+					if(sum == 3)
+						d |= 128;
+				}
+				
+				//printf("x=%3d, B=%1d, b=%1d, y=%2d\n\r", x, B, b, y);
+			}
+
+			col[colidx][B] = d;
+		}
+		
+		colidx ^= 1;
+		
+		if(x>0)
+		{
+			/* update previous column */
+			for(y=0;y<(SSD1306_H>>3);y++)
+				buf[(x-1)+SSD1306_W*y] = col[colidx][y];
+		}
+	}
+	
+	colidx ^= 1;
+
+	/* update final column */
+	for(y=0;y<(SSD1306_H>>3);y++)
+		buf[127+SSD1306_W*y] = col[colidx][y];
+}
+
+int count = 0;
+
+int main()
+{
+	// 48MHz internal clock
+	SystemInit48HSI();
+
+	// start serial @ default 115200bps
+#ifdef STDOUT_UART
+	SetupUART( UART_BRR );
+	Delay_Ms( 100 );
+#else
+	SetupDebugPrintf();
+#endif
+	printf("\r\r\n\nspi_oled example\n\r");
+
+	// init spi and oled
+	Delay_Ms( 100 );	// give OLED some more time
+	printf("initializing spi oled...");
+	if(!ssd1306_spi_init())
+	{
+		ssd1306_init();
+		printf("done.\n\r");
+		
+#if 0
+		printf("Looping on test modes...");
+		while(1)
+		{
+			for(uint8_t mode=0;mode<(SSD1306_H>32?8:7);mode++)
+			{
+				// clear buffer for next mode
+				ssd1306_setbuf(0);
+
+				switch(mode)
+				{
+					case 0:
+						printf("buffer fill with binary\n\r");
+						for(int i=0;i<sizeof(ssd1306_buffer);i++)
+							ssd1306_buffer[i] = i;
+						break;
+					
+					case 1:
+						printf("pixel plots\n\r");
+						for(int i=0;i<SSD1306_W;i++)
+						{
+							ssd1306_drawPixel(i, i/(SSD1306_W/SSD1306_H), 1);
+							ssd1306_drawPixel(i, SSD1306_H-1-(i/(SSD1306_W/SSD1306_H)), 1);
+						}
+						break;
+					
+					case 2:
+						{
+							printf("Line plots\n\r");
+							uint8_t y= 0;
+							for(uint8_t x=0;x<SSD1306_W;x+=16)
+							{
+								ssd1306_drawLine(x, 0, SSD1306_W, y, 1);
+								ssd1306_drawLine(SSD1306_W-x, SSD1306_H, 0, SSD1306_H-y, 1);
+								y+= SSD1306_H/8;
+							}
+						}
+						break;
+						
+					case 3:
+						printf("Circles empty and filled\n\r");
+						for(uint8_t x=0;x<SSD1306_W;x+=16)
+							if(x<64)
+								ssd1306_drawCircle(x, SSD1306_H/2, 15, 1);
+							else
+								ssd1306_fillCircle(x, SSD1306_H/2, 15, 1);
+						break;
+					
+					case 4:
+						printf("Unscaled Text\n\r");
+						ssd1306_drawstr(0,0, "This is a test", 1);
+						ssd1306_drawstr(0,8, "of the emergency", 1);
+						ssd1306_drawstr(0,16,"broadcasting", 1);
+						ssd1306_drawstr(0,24,"system.",1);
+						if(SSD1306_H>32)
+						{
+							ssd1306_drawstr(0,32, "Lorem ipsum", 1);
+							ssd1306_drawstr(0,40, "dolor sit amet,", 1);
+							ssd1306_drawstr(0,48,"consectetur", 1);
+							ssd1306_drawstr(0,56,"adipiscing",1);
+						}
+						ssd1306_xorrect(SSD1306_W/2, 0, SSD1306_W/2, SSD1306_W);
+						break;
+						
+					case 5:
+						printf("Scaled Text 1, 2\n\r");
+						ssd1306_drawstr_sz(0,0, "sz 8x8", 1, fontsize_8x8);
+						ssd1306_drawstr_sz(0,16, "16x16", 1, fontsize_16x16);
+						break;
+					
+					case 6:
+						printf("Scaled Text 4\n\r");
+						ssd1306_drawstr_sz(0,0, "32x32", 1, fontsize_32x32);
+						break;
+					
+					
+					case 7:
+						printf("Scaled Text 8\n\r");
+						ssd1306_drawstr_sz(0,0, "64", 1, fontsize_64x64);
+						break;
+
+					default:
+						break;
+				}
+				ssd1306_refresh();
+			
+				printf("count = %d\n\r", count++);
+				
+				Delay_Ms(2000);
+			}
+		}
+#else
+		printf("Looping...\n\r");
+		while(1)
+		{
+			int i;
+			
+			/* fill with random */
+			for(i=0;i<sizeof(ssd1306_buffer);i++)
+			{
+				ssd1306_buffer[i] = rand8();
+			}
+			ssd1306_refresh();
+
+			/* run conway iterations */
+			for(i=0;i<500;i++)
+			{
+				conway(ssd1306_buffer);
+				
+				/* refresh */
+				ssd1306_refresh();
+			}
+			
+			printf("count = %d\n\r", count++);
+		
+			Delay_Ms(2000);
+		}
+#endif
+	}
+	else
+		printf("failed.\n\r");
+	
+	printf("Stuck here forever...\n\r");
+	while(1);
+}
diff --git a/examples/spi_oled/ssd1306_spi.h b/examples/spi_oled/ssd1306_spi.h
new file mode 100644
index 0000000000000000000000000000000000000000..471e616b2d5aa4c0887b486901c275678b81f553
--- /dev/null
+++ b/examples/spi_oled/ssd1306_spi.h
@@ -0,0 +1,103 @@
+/*
+ * Single-File-Header for SSD1306 SPI interface
+ * 05-05-2023 E. Brombaugh
+ */
+
+#ifndef _SSD1306_SPI_H
+#define _SSD1306_SPI_H
+
+// control pins
+#define SSD1306_RST_PORT GPIOC
+#define SSD1306_RST_PIN 2
+#define SSD1306_RST_HIGH() SSD1306_RST_PORT->BSHR = (1<<(SSD1306_RST_PIN))
+#define SSD1306_RST_LOW() SSD1306_RST_PORT->BSHR = (1<<(16+SSD1306_RST_PIN))
+#define SSD1306_CS_PORT GPIOC
+#define SSD1306_CS_PIN 3
+#define SSD1306_CS_HIGH() SSD1306_CS_PORT->BSHR = (1<<(SSD1306_CS_PIN))
+#define SSD1306_CS_LOW() SSD1306_CS_PORT->BSHR = (1<<(16+SSD1306_CS_PIN))
+#define SSD1306_DC_PIN 4
+#define SSD1306_DC_PORT GPIOC
+#define SSD1306_DC_HIGH() SSD1306_DC_PORT->BSHR = (1<<(SSD1306_DC_PIN))
+#define SSD1306_DC_LOW() SSD1306_DC_PORT->BSHR = (1<<(16+SSD1306_DC_PIN))
+
+/*
+ * init SPI and GPIO for SSD1306 OLED
+ */
+uint8_t ssd1306_spi_init(void)
+{
+	// Enable GPIOC and SPI
+	RCC->APB2PCENR |= RCC_APB2Periph_GPIOC | RCC_APB2Periph_SPI1;
+	
+	// setup GPIO for reset, chip select and data/cmd
+	SSD1306_RST_PORT->CFGLR &= ~(0xf<<(4*SSD1306_RST_PIN));
+	SSD1306_RST_PORT->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*SSD1306_RST_PIN);
+	SSD1306_RST_HIGH();
+	SSD1306_CS_PORT->CFGLR &= ~(0xf<<(4*SSD1306_CS_PIN));
+	SSD1306_CS_PORT->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*SSD1306_CS_PIN);
+	SSD1306_CS_HIGH();
+	SSD1306_DC_PORT->CFGLR &= ~(0xf<<(4*SSD1306_DC_PIN));
+	SSD1306_DC_PORT->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*SSD1306_DC_PIN);
+	SSD1306_DC_LOW();
+
+	// PC5 is SCK, 10MHz Output, alt func, p-p
+	GPIOC->CFGLR &= ~(0xf<<(4*5));
+	GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP_AF)<<(4*5);
+	
+	// PC6 is MOSI, 10MHz Output, alt func, p-p
+	GPIOC->CFGLR &= ~(0xf<<(4*6));
+	GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP_AF)<<(4*6);
+	
+	// Configure SPI 
+	SPI1->CTLR1 = 
+		SPI_NSS_Soft | SPI_CPHA_1Edge | SPI_CPOL_Low | SPI_DataSize_8b |
+		SPI_Mode_Master | SPI_Direction_1Line_Tx |
+		SPI_BaudRatePrescaler_2;
+
+	// enable SPI port
+	SPI1->CTLR1 |= CTLR1_SPE_Set;
+	
+	// always succeed
+	return 0;
+}
+
+/*
+ * toggle reset line
+ */
+void ssd1306_rst(void)
+{
+	SSD1306_RST_LOW();
+	Delay_Ms(10);
+	SSD1306_RST_HIGH();
+}
+
+/*
+ * packet send for blocking polled operation via spi
+ */
+uint8_t ssd1306_pkt_send(uint8_t *data, uint8_t sz, uint8_t cmd)
+{
+	if(cmd)
+		SSD1306_DC_LOW();
+	else
+		SSD1306_DC_HIGH();
+	SSD1306_CS_LOW();
+	
+	// send data
+	while(sz--)
+	{
+		// wait for TXE
+		while(!(SPI1->STATR & SPI_STATR_TXE));
+		
+		// Send byte
+		SPI1->DATAR = *data++;
+	}
+	
+	// wait for not busy before exiting
+	while(SPI1->STATR & SPI_STATR_BSY);
+	
+	SSD1306_CS_HIGH();
+	
+	// we're happy
+	return 0;
+}
+
+#endif