Display Images on OLED Screen With Arduino (ATtiny85 and Others)

27K8014

Intro: Display Images on OLED Screen With Arduino (ATtiny85 and Others)

Hi everyone!

In this instructable, I'm going to be going through the process of how you can get images to display on OLED displays such as this one: https://amzn.com/B00O2LKEW2. For this tutorial I'll be using an ATtiny85, so some of the sketches I provide will not work on other Arduino devices. However, the process for making and preparing the image files should be exactly the same for most microcontrollers, so this tutorial should be helpful.

I'll be using the following software:

I use Photoshop to make monochromatic images and export them as bitmaps, but if you have an image you want to display you can use an online program to convert it to a bitmap file and load it into LCD Assistant.

LCD Assistant is a Windows-only program. I'm on a Mac, so I'm using Winebottler to run the .exe file. It works like a charm! You can download Winebottler here: http://winebottler.kronenberg.org. If you're having trouble getting anything to work don't hesitate to email me at info[at]coniferapps.com. TL;DR: this should work on any platform even if you don't have Photoshop.

STEP 1: Making a .bmp in Photoshop

Let's get to work! If you don't have Photoshop but have your 128x64 .bmp file then you can skip this step. (Step numbers correspond to the images above)

  1. Open up Photoshop, and create a new 128x64px image at 72ppi resolution in grayscale color mode.
  2. You're going to want to do all the work you need to do with rectangles and shapes while we're still in grayscale mode. We will be adding in text in bitmap mode. Additionally, keep your colors to only black and white. In the demo image I added a rectangle with a pattern fill for the background, then added a rectangle over that with a solid black fill.
  3. Go to Image>Mode>Bitmap. This will flatten down all your layers and get rid of any grays in the image. From here on out editing gets really primitive. No layers for you!
  4. Now we can start adding text. Make sure that if you want to write text on a black background, have white selected as the current color and vice-versa. Not all fonts look good at a really low resolution, so you'll just have to experiment with what looks best.
  5. Accept the text you created, and look at your image. You can use the eraser tool to fix some mistake pixels if need be. Make sure that everything is how you want it before we proceed to the next step.
  6. Go to File>Save As and select BMP as the format you want to save your image in. I'll be saving my image to my downloads folder. Use the default BMP options and select OK if a window pops up.
  7. Congrats! You now have probably the smallest image you'll ever make. Finder tells me that my resulting file is less than a kilobyte.

STEP 2: Generating a .h in LCD Assistant

Now we're going to turn the .bmp file into a format that can be read by your microcontroller (Step numbers correspond to the images above)

  1. Open up a new window of LCD Assistant
  2. Go to File>Load Image, navigate to your image file (yes the interface is horrid) and select it.
  3. Check the settings on your window to see if they're the same as mine. The first option, the byte orientation, is a matter of experimentation. First, try vertical orientation and if the end product when you display the image is a mess of pixels then try horizontal. The only other important option is Table Name. This can be whatever you want - it's how you reference the image in the Arduino IDE.
  4. Go to File>Save Output, and pick a place to save your file.
  5. You need to name it something that ends in a .h file extension. I usually just make it the table name.

Now we have a header file that can be imported used in the Arduino IDE! There's just one quick thing we need to do before we can use it.

STEP 3: Editing the .h File

We just need to make one quick addition to the .h file before we can use it.

  1. Open the .h file in your text-editor of choice (I'll be using Sublime)
  2. After the square brackets ([]), add the command 'PROGMEM', then save the file.

This tells the compiler that you want this variable loaded into the program memory and not the memory used for global variables. The ATtiny doesn't have enough space in the memory that stores global variables to hold this, so it needs to go in the flash storage devoted to programs.

The variable itself is an array of hexadecimal values specifying the state of each pixel. Our image has 8192 pixels (128x64), and there are 1024 hex values in that array. Each hex value can represent one byte of information (8 bits), and each pixel in our bitmap image is one bit (black=0, white=1). Isn't it awfully coincidental that 8 bits per byte x 1024 bytes is equal to 8192 bits, the exact number of pixels? (it's not a coincidence ☺)

STEP 4: FInally, the Arduino Code!

Attached is a sample Arduino file that you can run on an ATtiny85 connected to a 128x64 I2C OLED display. I'm using the library from the following instructable: https://www.instructables.com/id/ATTiny85-connects-to-I2C-OLED-display-Great-Things/. My program just displays the sample image I created. If you want to test your own, replace FileForOLED.h with your own file and rename things in the Arduino sketch as necessary. Thanks so much for reading, and I hope this was helpful. If you have any questions please email me at info[at]coniferapps.com or post them in the comments.

10 Comments

Arduino: 1.8.5 (Windows 7), Board: "Arduino/Genuino Uno"

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp: In function 'void USI_TWI_Master_Initialise()':

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:52:3: error: 'PORT_USI' was not declared in this scope

PORT_USI |= (1<<PIN_USI_SDA); // Enable pullup on SDA, to set high as released state.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:52:19: error: 'PIN_USI_SDA' was not declared in this scope

PORT_USI |= (1<<PIN_USI_SDA); // Enable pullup on SDA, to set high as released state.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:53:19: error: 'PIN_USI_SCL' was not declared in this scope

PORT_USI |= (1<<PIN_USI_SCL); // Enable pullup on SCL, to set high as released state.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:55:3: error: 'DDR_USI' was not declared in this scope

DDR_USI |= (1<<PIN_USI_SCL); // Enable SCL as output.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:58:3: error: 'USIDR' was not declared in this scope

USIDR = 0xFF; // Preload dataregister with "released level" data.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:59:3: error: 'USICR' was not declared in this scope

USICR = (0<<USISIE)|(0<<USIOIE)| // Disable Interrupts.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:59:19: error: 'USISIE' was not declared in this scope

USICR = (0<<USISIE)|(0<<USIOIE)| // Disable Interrupts.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:59:31: error: 'USIOIE' was not declared in this scope

USICR = (0<<USISIE)|(0<<USIOIE)| // Disable Interrupts.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:60:19: error: 'USIWM1' was not declared in this scope

(1<<USIWM1)|(0<<USIWM0)| // Set USI in Two-wire mode.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:60:31: error: 'USIWM0' was not declared in this scope

(1<<USIWM1)|(0<<USIWM0)| // Set USI in Two-wire mode.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:61:19: error: 'USICS1' was not declared in this scope

(1<<USICS1)|(0<<USICS0)|(1<<USICLK)| // Software stobe as counter clock source

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:61:31: error: 'USICS0' was not declared in this scope

(1<<USICS1)|(0<<USICS0)|(1<<USICLK)| // Software stobe as counter clock source

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:61:43: error: 'USICLK' was not declared in this scope

(1<<USICS1)|(0<<USICS0)|(1<<USICLK)| // Software stobe as counter clock source

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:62:19: error: 'USITC' was not declared in this scope

(0<<USITC);

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:63:3: error: 'USISR' was not declared in this scope

USISR = (1<<USISIF)|(1<<USIOIF)|(1<<USIPF)|(1<<USIDC)| // Clear flags,

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:63:19: error: 'USISIF' was not declared in this scope

USISR = (1<<USISIF)|(1<<USIOIF)|(1<<USIPF)|(1<<USIDC)| // Clear flags,

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:63:31: error: 'USIOIF' was not declared in this scope

USISR = (1<<USISIF)|(1<<USIOIF)|(1<<USIPF)|(1<<USIDC)| // Clear flags,

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:63:43: error: 'USIPF' was not declared in this scope

USISR = (1<<USISIF)|(1<<USIOIF)|(1<<USIPF)|(1<<USIDC)| // Clear flags,

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:63:54: error: 'USIDC' was not declared in this scope

USISR = (1<<USISIF)|(1<<USIOIF)|(1<<USIPF)|(1<<USIDC)| // Clear flags,

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:64:21: error: 'USICNT0' was not declared in this scope

(0x0<<USICNT0); // and reset counter.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp: In function 'unsigned char USI_TWI_Start_Transceiver_With_Data(unsigned char*, unsigned char)':

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:134:44: error: 'USISIF' was not declared in this scope

unsigned char const tempUSISR_8bit = (1<<USISIF)|(1<<USIOIF)|(1<<USIPF)|(1<<USIDC)| // Prepare register value to: Clear flags, and

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:134:56: error: 'USIOIF' was not declared in this scope

unsigned char const tempUSISR_8bit = (1<<USISIF)|(1<<USIOIF)|(1<<USIPF)|(1<<USIDC)| // Prepare register value to: Clear flags, and

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:134:68: error: 'USIPF' was not declared in this scope

unsigned char const tempUSISR_8bit = (1<<USISIF)|(1<<USIOIF)|(1<<USIPF)|(1<<USIDC)| // Prepare register value to: Clear flags, and

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:134:79: error: 'USIDC' was not declared in this scope

unsigned char const tempUSISR_8bit = (1<<USISIF)|(1<<USIOIF)|(1<<USIPF)|(1<<USIDC)| // Prepare register value to: Clear flags, and

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:135:40: error: 'USICNT0' was not declared in this scope

(0x0<<USICNT0); // set USI to shift 8 bits i.e. count 16 clock edges.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:200:7: error: 'PORT_USI' was not declared in this scope

PORT_USI &= ~(1<<PIN_USI_SCL); // Pull SCL LOW.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:200:24: error: 'PIN_USI_SCL' was not declared in this scope

PORT_USI &= ~(1<<PIN_USI_SCL); // Pull SCL LOW.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:201:7: error: 'USIDR' was not declared in this scope

USIDR = *(msg++); // Setup data.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:205:7: error: 'DDR_USI' was not declared in this scope

DDR_USI &= ~(1<<PIN_USI_SDA); // Enable SDA as input.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:205:24: error: 'PIN_USI_SDA' was not declared in this scope

DDR_USI &= ~(1<<PIN_USI_SDA); // Enable SDA as input.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:238:7: error: 'DDR_USI' was not declared in this scope

DDR_USI &= ~(1<<PIN_USI_SDA); // Enable SDA as input.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:238:25: error: 'PIN_USI_SDA' was not declared in this scope

DDR_USI &= ~(1<<PIN_USI_SDA); // Enable SDA as input.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:244:9: error: 'USIDR' was not declared in this scope

USIDR = 0xFF; // Load NACK to confirm End Of Transmission.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:248:9: error: 'USIDR' was not declared in this scope

USIDR = 0x00; // Load ACK. Set data register bit 7 (output for SDA) low.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp: In function 'unsigned char USI_TWI_Master_Transfer(unsigned char)':

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:270:3: error: 'USISR' was not declared in this scope

USISR = temp; // Set USISR according to temp.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:272:16: error: 'USISIE' was not declared in this scope

temp = (0<<USISIE)|(0<<USIOIE)| // Interrupts disabled

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:272:28: error: 'USIOIE' was not declared in this scope

temp = (0<<USISIE)|(0<<USIOIE)| // Interrupts disabled

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:273:16: error: 'USIWM1' was not declared in this scope

(1<<USIWM1)|(0<<USIWM0)| // Set USI in Two-wire mode.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:273:28: error: 'USIWM0' was not declared in this scope

(1<<USIWM1)|(0<<USIWM0)| // Set USI in Two-wire mode.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:274:16: error: 'USICS1' was not declared in this scope

(1<<USICS1)|(0<<USICS0)|(1<<USICLK)| // Software clock strobe as source.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:274:28: error: 'USICS0' was not declared in this scope

(1<<USICS1)|(0<<USICS0)|(1<<USICLK)| // Software clock strobe as source.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:274:40: error: 'USICLK' was not declared in this scope

(1<<USICS1)|(0<<USICS0)|(1<<USICLK)| // Software clock strobe as source.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:275:16: error: 'USITC' was not declared in this scope

(1<<USITC); // Toggle Clock Port.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:279:5: error: 'USICR' was not declared in this scope

USICR = temp; // Generate positve SCL edge.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:280:14: error: 'PIN_USI' was not declared in this scope

while( !(PIN_USI & (1<<PIN_USI_SCL)) );// Wait for SCL to go high.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:280:28: error: 'PIN_USI_SCL' was not declared in this scope

while( !(PIN_USI & (1<<PIN_USI_SCL)) );// Wait for SCL to go high.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:283:25: error: 'USIOIF' was not declared in this scope

}while( !(USISR & (1<<USIOIF)) ); // Check for transfer complete.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:286:11: error: 'USIDR' was not declared in this scope

temp = USIDR; // Read out data.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:288:3: error: 'DDR_USI' was not declared in this scope

DDR_USI |= (1<<PIN_USI_SDA); // Enable SDA as output.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:288:18: error: 'PIN_USI_SDA' was not declared in this scope

DDR_USI |= (1<<PIN_USI_SDA); // Enable SDA as output.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp: In function 'unsigned char USI_TWI_Master_Start()':

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:298:3: error: 'PORT_USI' was not declared in this scope

PORT_USI |= (1<<PIN_USI_SCL); // Release SCL.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:298:19: error: 'PIN_USI_SCL' was not declared in this scope

PORT_USI |= (1<<PIN_USI_SCL); // Release SCL.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:303:20: error: 'PIN_USI_SDA' was not declared in this scope

PORT_USI &= ~(1<<PIN_USI_SDA); // Force SDA LOW.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:309:9: error: 'USISR' was not declared in this scope

if( !(USISR & (1<<USISIF)) )

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:309:21: error: 'USISIF' was not declared in this scope

if( !(USISR & (1<<USISIF)) )

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp: In function 'unsigned char USI_TWI_Master_Stop()':

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:323:3: error: 'PORT_USI' was not declared in this scope

PORT_USI &= ~(1<<PIN_USI_SDA); // Pull SDA low.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:323:20: error: 'PIN_USI_SDA' was not declared in this scope

PORT_USI &= ~(1<<PIN_USI_SDA); // Pull SDA low.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:324:19: error: 'PIN_USI_SCL' was not declared in this scope

PORT_USI |= (1<<PIN_USI_SCL); // Release SCL.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:325:12: error: 'PIN_USI' was not declared in this scope

while( !(PIN_USI & (1<<PIN_USI_SCL)) ); // Wait for SCL to go high.

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:331:9: error: 'USISR' was not declared in this scope

if( !(USISR & (1<<USIPF)) )

^

C:\Users\chrisjlionel\Documents\Arduino\libraries\USI_TWI_Master\USI_TWI_Master.cpp:331:21: error: 'USIPF' was not declared in this scope

if( !(USISR & (1<<USIPF)) )

^

exit status 1

Error compiling for board Arduino/Genuino Uno.

This report would have more information with

"Show verbose output during compilation"

option enabled in File -> Preferences.

It looks like you have your board set incorrectly in the Arduino IDE. Change Tools->Board to ATtiny85. Also make sure the clock speed is set to 1MHz.
This display could be a good output for any information that the voice assistant needs to provide to the user. This would also eliminate the need for voice synthesis as an output.

it's pretty cool, you mentioned my instructable. Have fun!

Thanks, I really like your library. I thought that I'd make an Instructable to help anyone who was struggling to get images to display like me.