Problem converting BMP into correct OLED image. (SOLVED)

This is going to be very long, so I apologize in advance!!

I've been attempting this for a couple days now and I'm at the point where I'm just trying random things since I seem to have hit a wall. I.e. not being very productive.

What I am trying to do is take a 1-bit BMP, convert it into a hex array, load it onto the Arduino (Pro Mini 3.3V), and output it to a Newhaven 3.12 OLED display. I've finally gotten the display initialized but when I try to load images I just get junk, and I feel out of options on what to do next. SPI communication with the display is working and has been verified with a logic analyzer, though I will be happy to do so again if that may be a culprit.

First things first, here is the datasheet for the display controller: http://www.newhavendisplay.com/app_notes/SSD1322.pdf

The relevant parts are:

Page 32-36 - Command Table
Page 37-47 - Description of Commands

Of particular interest are pages 37-41, these seem to show how the display RAM is addressed, written to, and incremented.

Here is the display datasheet, no important info except for the initialization routine (Page 12): http://www.newhavendisplay.com/specs/NHD-3.12-25664UCB2.pdf

My main code includes a test array that should display: - Futurama joke - when it is working. The array was created in photoshop as a 1-bit BMP and converted using LCD Assistant. Here are the settings that I think are correct, based on the settings given in the initialization procedure and from examining the display controller datasheet:

My reasoning for this:

  • From the datasheet, it appears that each byte contains 2 pixels, a 4 bit grayscale value for each one. Shown on page 40
  • From my understanding of endianness, these bytes are all little endian (lowest address is least significant bit).
  • Byte orientation is horizontal as the display was initialized in horizontal increment mode - shown on page 39 of controller datasheet.

I have tried 4 pixels per byte, 8, along with both little and big endianness and vertical and horizontal byte order, still see gibberish the whole time. I also can't exactly make sense of the table shown on page 40 of the data sheet. So each byte is 2 pixels but each column address has 2 bytes (4 pixels), but I can only pass one byte at a time (hex value) into the display RAM. Does it wait for the second before incrementing the column address?

Here is a picture of my display loaded with the code and settings shown:

Here is my main code. I have severely truncated the "stern" character array because it was exceeding the max length, but I left a snippet there to display the non-grayscale bytes that I will address later on.

/* This is the main code for the control system. It has been modified to work with the Arduino
Pro Mini 3.3V board. Check that the pin assignments match the actual circuit. 

4-10-12
*/


#include <SPI.h>

const unsigned char stern [] = {
0x20, 0x30, 0x78, 0x30, 0x33, 0x2D, 0x23, 0x33, 0x7A, 0x30, 0x40, 0x2C, 0x20, 0x30, 0x78, 0x33,
0x33, 0x2C, 0x20, 0x30, 0x7B, 0x30, 0x30, 0x2C, 0x23, 0x33, 0x7B, 0x35, 0x3A, 0x2C, 0x20, 0x31,
0x7A, 0x30, 0x30, 0x2C, 0x23, 0x33, 0x7B, 0x32, 0x30, 0x2C, 0x23, 0x32, 0x78, 0x33, 0x43, 0x2F,
0x20, 0x33, 0x7B, 0x33, 0x32, 0x2F, 0x0C, 0x08, 0x30, 0x78, 0x30, 0x30, 0x2C, 0x20, 0x30, 0x78
};


const int displaySelect = 7;
const int displayReset = 6;
const int displayDC = 5;
const int valve = 3;
const int button = 4;
const int lightSensor = A0;

int brightness = 0;
int fadeamount = 1;  
int light = 0;
int oldLight = 0;

void setup(){
  
  digitalWrite(displaySelect,HIGH);
  digitalWrite(valve,HIGH);
  pinMode(valve, OUTPUT);
  pinMode(displaySelect, OUTPUT);
  pinMode(displayReset, OUTPUT);
  pinMode(displayDC, OUTPUT);

  SPI.setBitOrder(MSBFIRST);
  SPI.setClockDivider(SPI_CLOCK_DIV16);
  SPI.setDataMode(SPI_MODE3);
  SPI.begin();
  Serial.begin(9600);
  analogReference(DEFAULT);
  OLED_Init(); 
  
  Set_Column_Address(0x00,0x77);
  Set_Row_Address(0x00,0x7F);
  Set_Write_RAM();             // This clears all old data from the display RAM
  for(int i=0; i<16384; i++){
    oled_Data(0x00);
  }


  unsigned int i;
	
	Set_Column_Address(0x00,0x77);
	Set_Row_Address(0x00,0x7F);
	Set_Write_RAM();

	for(i=0;i<3072 ;i++)
	{
                oled_Data(stern[i]);
	}
  
}

// When an external button is held down, the code below fades the display brightness 
// from the max to the min using the contrast current command
void loop(){
  if (digitalRead(button)){
    Set_Contrast_Current(brightness);
    brightness = brightness + fadeamount;
    if (brightness == 0 || brightness == 255){
      fadeamount = -fadeamount;
    }
    delay(5);
  }
  
}

Here is the OLED initialization routine:

//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//  Initialization
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void OLED_Init()
{
unsigned char i;
        digitalWrite(displayReset,LOW);        // Reset the display
        delay(1);                              // Delay 1 ms
        digitalWrite(displayReset,HIGH);
        
       	Set_Command_Lock(0x12);			// Unlock Basic Commands (0x12/0x16)
	Set_Display_On_Off(0x00);		// Display Off (0x00/0x01)
	Set_Column_Address(0x1C,0x5B);
	Set_Row_Address(0x00,0x3F);
	Set_Display_Clock(0xF0);		// Set Clock as 80 Frames/Sec
	Set_Multiplex_Ratio(0x3F);		// 1/64 Duty (0x0F~0x3F)
	Set_Display_Offset(0x00);		// Shift Mapping RAM Counter (0x00~0x3F)
	Set_Start_Line(0x00);			// Set Mapping RAM Display Start Line (0x00~0x7F)
	Set_Remap_Format(0x12);			// Set Horizontal Address Increment
						//     Column Address 0 Mapped to SEG0
						//     Disable Nibble Remap
						//     Scan from COM[N-1] to COM0
						//     Disable COM Split Odd Even
						//     Enable Dual COM Line Mode
	Set_GPIO(0x00);				// Disable GPIO Pins Input
	Set_Function_Selection(0x01);		// Enable Internal VDD Regulator
	Set_Display_Enhancement_A(0xA0,0xFD);	// Enable External VSL 
	Set_Contrast_Current(0xFF);		// Set Segment Output Current
	Set_Master_Current(0x0F);		// Set Scale Factor of Segment Output Current Control
	//Set_Gray_Scale_Table();			// Set Pulse Width for Gray Scale Table
	Set_Linear_Gray_Scale_Table();	//set default linear gray scale table
	Set_Phase_Length(0xE2);			// Set Phase 1 as 5 Clocks & Phase 2 as 14 Clocks
	Set_Display_Enhancement_B(0x20);	// Enhance Driving Scheme Capability (0x00/0x20)
	Set_Precharge_Voltage(0x1F);		// Set Pre-Charge Voltage Level as 0.60*VCC
	Set_Precharge_Period(0x08);		// Set Second Pre-Charge Period as 8 Clocks
	Set_VCOMH(0x07);			// Set Common Pins Deselect Voltage Level as 0.86*VCC
	Set_Display_Mode(0x02);			// Normal Display Mode (0x00/0x01/0x02/0x03)
	Set_Partial_Display(0x01,0x00,0x00);	// Disable Partial Display
	Set_Display_On_Off(0x01);
}

Comments:

  1. I do not understand the hex data generated from the BMP. All pixels are either full on (1) or full off (0). Yet there are hex values such as 0x45, which would be 0x01000101. Clearly this is not grayscale! I would expect that the 4 MSBs are all identical, and the 4 LSBs are all identical, if each byte corresponds to 2 grayscale pixels. But clearly this is not the case! Is there a flaw in the LCD assistant program?

EDIT: I meant to say that while that example actually IS a grayscale value, it does not correspond to fully on or fully off pixels as it should, but rather to varying intensities.

  1. 256 pixels x 12 pixels = 3072 pixels. This is the number I have as the limit in this loop:
  unsigned int i;
	
	Set_Column_Address(0x00,0x77);
	Set_Row_Address(0x00,0x7F);
	Set_Write_RAM();

	for(i=0;i<3072 ;i++)
	{
                oled_Data(stern[i]);
	}

As you can see in the display, this does seem to at least illuminate the correct portion of the display, it is indeed 12 pixels tall and the full width. But the checkerboard function in the OLED initialization routine works correctly, and I do not understand how. Code here:

void Checkerboard()
{
unsigned char i,j;
	
	Set_Column_Address(0x00,0x77);
	Set_Row_Address(0x00,0x7F);
	Set_Write_RAM();

	for(i=0;i<64;i++)
	{
		for(j=0;j<120;j++)
		{
			oled_Data(0xF0);
			oled_Data(0xF0);
		}
		for(j=0;j<120;j++)
		{
			oled_Data(0x0F);
			oled_Data(0x0F);
		}
	}
}

NOTE: I think I figured out the paragraph below so you can skip it if this is all too long already!
The "i<64" part of the for loop makes sense; the display is 64 pixels tall. But the "j<120" part I do not understand. This will increment from 0-119. That is 120 values. Two bytes sent on each loop, for a total of 240 bytes sent, which should correspond to 120 pixels. Then the second loop, again with "j<120," again with 120 pixels. But that is only 240 pixels and the display is 256 pixels wide! So why does it display the checkerboard correctly? The "Set column address" command goes from 0x00 to 0x77, that is 0 to 119. 120 columns. But each column contains 4 pixels, which would be 480! That is the max of the controller, but not of the display which is only 256 px tall. Same for Row address, that is max of controller (0 to 127) but not the display which is 64 px tall. Is this then just filling the entire data ram and the display can only show the top left 1/4?

  1. I'm still not sure how to increment the counter. This is again referring to my loop that sends the test image. Say I was sending a full image - 256 x 64 = 16384 pixels. But I would think I should not increment the loop that many times, as on each iteration I send 1 full byte which should be 2 pixels! So I should only increment half that number - 8192. But if I do this only half the display is illuminated! With gibberish of course, but the point remains.

  2. After writing all of this I examined the init routine and thought about column addressing; in the routine the column address is given as going from 0x1C to 0x5B, that is from 28 to 91. That is 64 columns, at 4 pixels each should be 256 pixels. This is starting to make some sense. I modified my loop to that effect:

  unsigned int i;
	
	Set_Column_Address(0x1C,0x5B);
	Set_Row_Address(0x00,0x3F);
	Set_Write_RAM();

	for(i=0;i<1536 ;i++)
	{
                oled_Data(stern[i]);
	}

Changed the the 3072 to 1536 because the 3072 resulted in an area twice the correct height being display. Thinking again that 1 byte = 2 pixels, I halved the value. Again the correct section of the screen is illuminated, but still with what looks like gibberish:

So I think I have made a small discovery but to no practical benefit. :confused:

I'm sure there are other factors I have forgotten and I will add them when I remember. And I of course apologize for the length! I like to be thorough though and give as much information as possible. I appreciate any help at all! I am stuck trying to decipher how the display RAM works. It seems like a deceptively simple process but I just am not getting anywhere. In addition to this I am simultaneously designing the PCB and mechanical enclosure in CAD, and my head is just too full of too many different things so I may be missing some (or many) obvious things.

Again any help is tremendously appreciated!

In writing this I seem to have solved my confusion about the checkerboard routine. This is copy pasted from another forum I just posted to trying to help someone in the same situation. In writing it I think I figured it out. :slight_smile:

If you look at page 40 you can see that each column seems to address 4 pixels. So the columns from 0 to 119 is 120 columns, this totals 480 pixels. This is the maximum capability of the controller, but obviously more than the actual display we are using. I believe the checkerboard pattern just fills the entire display RAM, and the display only shows a corner of it, approximately 1/4 of the total. So really the display is acting as a "window" into the controller RAM and showing you just that portion of it. Because it is a checkerboard, it still looks correct.

I am still trying to decipher the checkerboard code myself to figure out exactly how it corresponds to the data written into RAM, and how that data is interpreted. But it would seem that the first "inner" loop write 240 bytes, or 480 pixels, alternating ON-OFF-ON-OFF. The second loop is there because if it wasn't, and "i" was incremented to 128, you would get not checkers but rows . So the second "inner" loop is actually writing to the second row, starting at the first column as the pointer increments at the end of the first row. This was the source of major confusion for me. Now it does the same sequence, 480 pixels long, but starting on the pattern OFF-ON-OFF-ON in order to create the checkerboard pattern. So each iteration of the "outer" loop actually writes 2 rows, and it loops 64 times for a total of 128 rows and 480 columns.

However, this is where the confusion about the initialization routine and column addresses comes in. In the init routine the column address is specified as 0x1C to 0x5B, or 28 to 91. This corresponds to (91-28)+1 = 64 columns. Since each column contains 4 pixels, that totals 256 pixels which is the correct width of the display. So to use this display it seems you have to limit the controller to those columns (not sure why they start at 28 and not 0) and rows 0x00 to 0x3F (0-63).

I'm a bit skeptical about your conversion ...

const unsigned char stern [] = {
0x20, 0x30, 0x78, 0x30, 0x33, 0x2D, 0x23, 0x33, 0x7A, 0x30, 0x40, 0x2C, 0x20, 0x30, 0x78, 0x33,
0x33, 0x2C, 0x20, 0x30, 0x7B, 0x30, 0x30, 0x2C, 0x23, 0x33, 0x7B, 0x35, 0x3A, 0x2C, 0x20, 0x31,
0x7A, 0x30, 0x30, 0x2C, 0x23, 0x33, 0x7B, 0x32, 0x30, 0x2C, 0x23, 0x32, 0x78, 0x33, 0x43, 0x2F,
0x20, 0x33, 0x7B, 0x33, 0x32, 0x2F, 0x0C, 0x08, 0x30, 0x78, 0x30, 0x30, 0x2C, 0x20, 0x30, 0x78
};

Just printing that gives:

 0x03-#3z0@, 0x33, 0{00,#3{5:, 1z00,#3{20,#2x3C/ 3{32/0x00, 0x

It looks a bit too "non binary" to be some sort of grayscale image.

I'm not sure what you mean, how are you printing it? It's my understanding that after the Set_Write_RAM() command, all following values are written directly to the display RAM at the designated addresses. I.e. it's not the same as sending ASCII characters to a character LCD for instance. I am (supposed to be) explicitly writing each pixel individually to the display. That array is taken straight from the output of LCD Assistant. I would be happy to try another conversion program if you can recommend one. Most of the others I've found look more like libraries that will convert a .bmp on the fly inside the code, but that's not what I need.

EDIT: I never really made it clear that this is a graphic OLED: http://www.newhavendisplay.com/index.php?main_page=product_info&cPath=119_577&products_id=3622

I'm also wondering if this could be an issue with running out of memory on the Arduino. I'm not sure where exactly the array is stored but as it is 1536 chars that would be 12.3 kbits. I'll try to implement this using the PROGMEM library...

SVFeingold:
I'm not sure what you mean, how are you printing it?

Thus:

const unsigned char stern [] = {
0x20, 0x30, 0x78, 0x30, 0x33, 0x2D, 0x23, 0x33, 0x7A, 0x30, 0x40, 0x2C, 0x20, 0x30, 0x78, 0x33,
0x33, 0x2C, 0x20, 0x30, 0x7B, 0x30, 0x30, 0x2C, 0x23, 0x33, 0x7B, 0x35, 0x3A, 0x2C, 0x20, 0x31,
0x7A, 0x30, 0x30, 0x2C, 0x23, 0x33, 0x7B, 0x32, 0x30, 0x2C, 0x23, 0x32, 0x78, 0x33, 0x43, 0x2F,
0x20, 0x33, 0x7B, 0x33, 0x32, 0x2F, 0x0C, 0x08, 0x30, 0x78, 0x30, 0x30, 0x2C, 0x20, 0x30, 0x78
};

void setup()
{
  Serial.begin(115200);
  Serial.println ((const char *) stern); 
}

void loop(){}

It just doesn't look like bitmap data to me.

Put it this way, when I converted your bitmap with that program I got something like this:

//------------------------------------------------------------------------------
// File generated by LCD Assistant
// http://en.radzio.dxp.pl/bitmap_converter/
//------------------------------------------------------------------------------

const unsigned char stern [] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x00, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0x30, 0x00, 0x00,
0x04, 0x54, 0x44, 0x64, 0x6B, 0x77, 0x04, 0x44, 0x64, 0x68, 0x77, 0x33, 0x04, 0x4C, 0x64, 0x64,
0x74, 0x00, 0x30, 0x00, 0x54, 0x6C, 0x70, 0x00, 0x24, 0x04, 0x54, 0x68, 0x64, 0x74, 0x68, 0x00,
0x30, 0x04, 0x48, 0x64, 0x68, 0x64, 0x68, 0x74, 0x00, 0x14, 0x08, 0x54, 0x60, 0x60, 0x4C, 0x70,
...

Ah, I see. Yes, the data you showed as the output is the same thing I got. I removed most of it from the code because it kept exceeding the max character limit for the post, but left a little bit (the last part) just to show the kind of data I was getting.

I see now about the serial output, but it seems as though it attempts to convert the hex data into ASCII characters. Shouldn't this be more or less irrelevant as I'm not sending data as an ASCII string but as direct pixel values? I just want to make sure we're on the same page.

Now I am really confused. It seemed strange that I'm getting so much gibberish and not even a semblance of an image. I tried loading a completely empty, white bitmap into the program, and here is the output I get:

const unsigned char test2 [] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x30, 0x38, 0x2C,
0x0C, 0x08, 0x30, 0x78, 0x30, 0x40, 0x2C, 0x20, 0x30, 0x78, 0x30, 0x30, 0x2C, 0x20, 0x30, 0x78,
0x30, 0x30, 0x2C, 0x20, 0x30, 0x78, 0x34, 0x38, 0x2C, 0x20, 0x30, 0x78, 0x30, 0x30, 0x2C, 0x20,
0x30, 0x78, 0x30, 0x30, 0x2C, 0x20, 0x30, 0x78, 0x30, 0x40, 0x2C, 0x20, 0x30, 0x78, 0x30, 0x30,
0x2C, 0x20, 0x30, 0x78, 0x30, 0x30, 0x2C, 0x20, 0x30, 0x78, 0x34, 0x40, 0x2C, 0x20, 0x30, 0x78,
0x30, 0x30, 0x2C, 0x20, 0x30, 0x78, 0x30, 0x30, 0x2C, 0x20, 0x30, 0x78, 0x30, 0x44, 0x2C, 0x20,
0x30, 0x78, 0x30, 0x30, 0x2C, 0x20, 0x30, 0x78, 0x30, 0x30, 0x2C, 0x20, 0x30, 0x78, 0x34, 0x40,
0x2C, 0x0C, 0x08, 0x30, 0x78, 0x30, 0x30, 0x2C, 0x20, 0x30, 0x78, 0x34, 0x34, 0x2C, 0x20, 0x30,
0x78, 0x30, 0x44, 0x2C, 0x20, 0x30, 0x78, 0x30, 0x30, 0x2C, 0x20, 0x30, 0x78, 0x30, 0x30, 0x2C,
0x20, 0x30, 0x78, 0x34, 0x40, 0x2C, 0x20, 0x30, 0x78, 0x30, 0x30, 0x2C, 0x20, 0x30, 0x78, 0x30,
0x30, 0x2C, 0x20, 0x30, 0x78, 0x30, 0x44, 0x2C, 0x20, 0x30, 0x78, 0x30, 0x30, 0x2C, 0x20, 0x30,
0x78, 0x30, 0x30, 0x2C, 0x20, 0x30, 0x78, 0x34, 0x38, 0x2C, 0x20, 0x30, 0x78, 0x30, 0x30, 0x2C,
0x20, 0x30, 0x78, 0x30, 0x30, 0x2C, 0x20, 0x30, 0x78, 0x30, 0x40, 0x2C, 0x20, 0x30, 0x78, 0x30,
0x30, 0x2C, 0x0C, 0x08, 0x30, 0x78, 0x30, 0x30, 0x2C, 0x20, 0x30, 0x78, 0x34, 0x40, 0x2C, 0x20,
0x30, 0x78, 0x30, 0x34, 0x2C, 0x20, 0x30, 0x78, 0x30, 0x40, 0x2C, 0x20, 0x30, 0x78, 0x30, 0x44,
0x2C, 0x20, 0x30, 0x78, 0x30, 0x30, 0x2C, 0x20, 0x30, 0x78, 0x30, 0x30, 0x2C, 0x20, 0x30, 0x78,
0x34, 0x38, 0x2C, 0x20, 0x30, 0x78, 0x30, 0x30, 0x2C, 0x20, 0x30, 0x78, 0x30, 0x30, 0x2C, 0x20,
0x30, 0x78, 0x30, 0x40, 0x2C, 0x20, 0x30, 0x78, 0x30, 0x30, 0x2C, 0x20, 0x30, 0x78, 0x30, 0x30,
0x2C, 0x20, 0x30, 0x78, 0x34, 0x40, 0x2C, 0x20, 0x30, 0x78, 0x30, 0x34, 0x2C, 0x20, 0x30, 0x78,
0x30, 0x34, 0x2C, 0x0C, 0x08, 0x30, 0x78, 0x30, 0x44, 0x2C, 0x20, 0x30, 0x78, 0x30, 0x30, 0x2C,
0x20, 0x30, 0x78, 0x30, 0x30, 0x2C, 0x20, 0x30, 0x78, 0x34, 0x40, 0x2C, 0x20, 0x30, 0x78, 0x30,
0x30, 0x2C, 0x20, 0x30, 0x78, 0x34, 0x30, 0x2C, 0x20, 0x30, 0x78, 0x30, 0x40, 0x2C, 0x20, 0x30,
0x78, 0x30, 0x40, 0x2C, 0x20, 0x30, 0x78, 0x30, 0x38, 0x2C, 0x20, 0x30, 0x78, 0x30, 0x30, 0x2C,
0x20, 0x30, 0x78, 0x34, 0x38, 0x2C, 0x20, 0x30, 0x78, 0x30, 0x30, 0x2C, 0x20, 0x30, 0x78, 0x30,
0x30, 0x2C, 0x20, 0x30, 0x78, 0x30, 0x40, 0x2C, 0x20, 0x30, 0x78, 0x30, 0x30, 0x2C, 0x20, 0x30,
0x78, 0x30, 0x30, 0x2C, 0x0C, 0x08, 0x30, 0x78, 0x34, 0x38, 0x2C, 0x20, 0x30, 0x78, 0x30, 0x30,
0x2C, 0x20, 0x30, 0x78, 0x30, 0x30, 0x2C, 0x20, 0x30, 0x78, 0x30, 0x40, 0x2C, 0x20, 0x30, 0x78,....

Starts out good for the first 16 lines, and then it looks just like a bunch of random values. I guess this program is garbage then?

Made a little progress, tried using a different program (bmp2h_conv) which is OK but doesn't seem to support the kind of output I need entirely. Also it is continually crashing over some error that looks like bad coding to me. Why a simple, robust program that can do this doesn't exist is beyond me, it seems like a common enough task that someone might just make a damn image converter that is straightforward.

No matter, after redoing the image with this program and adding in the Flash library to write this array to Flash and avoid memory limitations, I see this:

Still looks like garbage but at least it's halfway recognizable that there should be an image there! This may be the fault of the program, it is giving me 3072 bytes - too many. Should be half that number.

Yes your original program looks buggy.

Sweet merciful Jesus!

FINALLY I found a program that works for this. I must have downloaded every BMP converter from every shady website out there before finding this: http://www.novatronica.it/files/ODTv1.1.11278.zip

This is the ONLY program I have found that both supports 4 bit pixels (along with 1 bit) AND works.

I guess this means the problem is solved? I feel silly for such a long thread now...

It has been a long day!

It's been useful. Didn't Thomas Edison say something like "I haven't failed. I just found 1000 things that didn't work".

Well you have done something similar, and shared your findings which will hopefully help others.

I sure hope so, I feel so accomplished after slogging through this process (with your help of course!) that I would be sad if I couldn't help someone else with it. This display is very nice but wow, the only community "support" is people complaining about getting it to work.

EDIT: Just one more for fun. :smiley:

Now to delve into the issue of the horizontal banding...

Looks like I'm not completely out of the woods yet...now I am having problems with the display not clearing fully. Here is what it looks like with another sample image:

All those little random dots and lines on the top and bottom? That's bits of old images that were on the screen showing through. It's as if each new image is just being pasted on top of the old ones, with whatever "uncovered" parts showing through.

Comments:

  1. If I scale the image down to, say 50% and display it on the screen, I see just the image and the rest of the screen is blank as it should be.
  2. This only happens with a full size (256 x 64) image, but it shouldn't because the image should cover the entire screen! There are no "empty" parts. For instance if I upload an image with a few lines of text, in between the lines I will see old images that were on the screen. This doesn't make sense!
  3. I am completely clearing the screen with a command in my code; i.e. filling the ENTIRE display RAM with 0's. If I slow down the SPI bus enough I can even see it "wipe" the screen, but once the image is loaded...I still see the remnants on the edges!
  4. I tried using different variables for the array - which is being stored in FLASH memory using the Flash library. My thinking was that maybe, somehow, the old array was not being completely overwritten. Still does not make a difference.
  5. There is no way the old data is getting transmitted when I upload the sketch; there is only a single array and I am completely replacing it each time.

The "Tough Mudder" image I posted was the first full image to be displayed on the screen, every new image after that contains just a little bit of all previous images. I'm seriously confused!

My main code:

/* This is the main code for the control system. It has been modified to work with the Arduino
Pro Mini 3.3V board. Check that the pin assignments match the actual circuit. 

4-10-12
*/


#include <SPI.h>
#include <Flash.h>

FLASH_ARRAY(unsigned char, test,
DATA OMITTED FOR LENGTH
);


const int valve = 3;           // Output to SSR that switches valve
const int button = 4;          // 3.3V button signal
const int displayDC = 5;       // OLED display data control
const int displayReset = 6;    // OLED display reset 
const int displaySelect = 7;   // OLED display SPI slave select
const int interlock = 8;       // Input from interlock 
const int lightSensor = A0;    // Input from ambient light sensor


int brightness = 0;
int fadeamount = 1;  

void setup(){
  
  digitalWrite(displaySelect,HIGH);
  digitalWrite(valve,HIGH);
  pinMode(valve, OUTPUT);
  pinMode(displaySelect, OUTPUT);
  pinMode(displayReset, OUTPUT);
  pinMode(displayDC, OUTPUT);

  SPI.setBitOrder(MSBFIRST);
  SPI.setClockDivider(SPI_CLOCK_DIV8);
  SPI.setDataMode(SPI_MODE3);
  SPI.begin();
  analogReference(DEFAULT);
  OLED_Init(); 
  clear_Screen();


/* This code sends a full image to the display. Because each byte
contains two 4 bit grayscale pixels, 8192 bytes are needed to fill the
complete 16384 pixel screen. 

SPI.transfer is explicitly called because the "oled_Data()" function
toggles the display select and data control lines before and after each byte.
The greatly reduces the transfer speed and results in a noticeable "wipe" 
as the data is loaded to RAM. */ 
  unsigned int i;
	
	Set_Column_Address(0x1C,0x5B);
	Set_Row_Address(0x00,0x3F);
	Set_Write_RAM();
        digitalWrite(displaySelect,LOW);
        digitalWrite(displayDC,HIGH);
	for(i=0;i<8192;i++)
	{
               SPI.transfer(test[i]);
	}
        digitalWrite(displaySelect,HIGH);
}

void loop(){
  
  while (digitalRead(button)){
    Set_Contrast_Current(brightness);
    brightness = brightness + fadeamount;
    if (brightness == 0 || brightness == 255){
      fadeamount = -fadeamount;
    }
    delay(5);
  }
  
 // Toggles between normal and inverse display every second
  Set_Display_Mode(0x03);
  delay(1000);
  Set_Display_Mode(0x02);
  delay(1000);

}

There is no command in the OLED datasheet to clear the screen, only to write to the RAM whatever values you desire. I am using this function to do so:

void clear_Screen()
{
  Set_Column_Address(0x00,0x77);
  Set_Row_Address(0x00,0x7F);
  Set_Write_RAM();
  digitalWrite(displaySelect,LOW);
  digitalWrite(displayDC,HIGH);
  for(unsigned int i=0; i<30720; i++){
    SPI.transfer(0x00);
  }
  digitalWrite(displaySelect,HIGH);
}

This should clear the ENTIRE ram contents. 120 columns, 127 rows -> 480 pixels x 128 pixels = 61440 pixels / 2 pixels per byte = 30720 bytes.

I just uploaded this image:

...and I get this result:

I suspect something strange may be going on in the Arduino FLASH memory, or perhaps with the display itself even though there is nothing in the datasheet to suggest this kind of behavior should even be possible.

So, again, I am asking for help. I am sure someone has seen this type of behavior before.

EDIT: Now when I upload I get this:

Binary sketch size: 11134 bytes (of a 30720 byte maximum)
avrdude: verification error, first mismatch at byte 0x00bd
         0xff != 0xfe
avrdude: verification error; content mismatch

What?

  Set_Row_Address(0x00,0x7F);

Where's the datasheet? Why don't you start at row 0?

Binary sketch size: 11134 bytes (of a 30720 byte maximum)
avrdude: verification error, first mismatch at byte 0x00bd
0xff != 0xfe
avrdude: verification error; content mismatch

There a bug in the bootloader where trailing 0xFF are not written. Try to put some other byte (eg. 0x01) as the last data byte in the sketch.

The datasheet is linked in the first post. Here it is again: http://www.newhavendisplay.com/app_notes/SSD1322.pdf

Here are the row and column functions:

void Set_Column_Address(unsigned char a, unsigned char b)
{
	oled_Command(0x15);			// Set Column Address
	oled_Data(a);				//   Default => 0x00
	oled_Data(b);				//   Default => 0x77
}

//--------------------------------------------------------------------------

void Set_Row_Address(unsigned char a, unsigned char b)
{
	oled_Command(0x75);			// Set Row Address
	oled_Data(a);				//   Default => 0x00
	oled_Data(b);				//   Default => 0x7F
}

And then the command and data functions:

//--------------------------------------------------------------------------
//send Command to OLED
//--------------------------------------------------------------------------
void oled_Command(unsigned char Data)
{
  
unsigned char i;	  //begin 4-wire serial mode
  digitalWrite(displayDC,LOW);
  digitalWrite(displaySelect,LOW);
  SPI.transfer(Data);
  digitalWrite(displayDC,HIGH);
  digitalWrite(displaySelect,HIGH);
}

//--------------------------------------------------------------------------
//send Data to OLED
//--------------------------------------------------------------------------
void oled_Data(unsigned char Data)
{
unsigned char i;	 
  digitalWrite(displayDC,HIGH);
  digitalWrite(displaySelect,LOW);

  SPI.transfer(Data);
  
  digitalWrite(displaySelect,HIGH);
}

When using the row and column address functions, the first byte sent is the command byte. So when calling Set_Row_Address(A,B), A is the first address and B is the last address, and it writes to all addresses between the two.

The addresses corresponding to this display are column: 0x1C to 0x5B (28 - 91; 256 pixels with 4 pixels/column) and the row addresses are 0x00 to 0x3F (0 to 63; 64 pixels for the display height).

Does the bootloader error affect the performance of the program? If I leave the void loop() section blank, the last byte sent should be a 0x01, as that is the last byte in the image array and the last function performed in void setup() is to write the image and de-select the display. I still get the same error though:

Binary sketch size: 11036 bytes (of a 30720 byte maximum)
avrdude: verification error, first mismatch at byte 0x00bd
         0xff != 0xfe
avrdude: verification error; content mismatch

Last part of my image array:

.......0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x01
);

I should also add that I've tried using this code in "void loop()"

void loop(){

    if (digitalRead(button)){
        clear_Screen();
    }
}

So once the image is loaded (Same as the most recent image I posted with the two lines of text), pressing a button attached to a digital input runs the clear screen function. This function has been modified to only address the parts of the display RAM corresponding to this specific screen. Otherwise it takes much longer to clear the screen, but I also wanted to test if there was some error in my loop that iterates through each element of the image array.

Here is the modified clear_Screen():

void clear_Screen()
{
  Set_Column_Address(0x1C,0x5B);
  Set_Row_Address(0x00,0x3F);
  Set_Write_RAM();
  digitalWrite(displaySelect,LOW);
  digitalWrite(displayDC,HIGH);
  for(unsigned int i=0; i<8192; i++){
    SPI.transfer(0x00);
  }
  digitalWrite(displaySelect,HIGH);
}

After loading the code with this change, pushing the button does indeed clear the screen completely. All pixels are completely off. Resetting the Arduino reloads the same (faulty) image.

So this leads me to believe that the process of writing to the display is working as it should. I am now starting to suspect that the problem is on the Arduino end, but I don't know enough about how FLASH memory is written/read on the Arduino to know if that is the culprit.

I've also checked the array and there are exactly 8192 bytes there, as it should be.

Sounds a bit like when you load a small image you aren't putting it all into memory, so when you copy it to the OLED you are copying part of the earlier image.

When it's a small image though, nothing else shows except the image as it is supposed to look. It's only when I load a full size image.

I am having a difficult time understanding by what mechanism it is happening. For instance with the image I posted of the two lines of text. There is no compression happening, the program should be "dumb" and just copy every byte of image data as it is in the array. Why would only the text show up correctly but not the parts above/below the text?

It looks like rows with all "0xFF" data are just being skipped entirely, but why? If I invert the image and make another array, it shows up correctly. I can then send the display an "invert" command to get it back to how it is supposed to look, and I suppose I can work with that behavior although I don't like to without understanding what is really going on. Is it an issue with the display or with the Arduino?