ESP8266 NodeMCU - OLED display using SPI

ESP8266 NodeMCU - OLED display using SPI

I bought a $4 1 inch OLED SPI display that I wanted to use with my ESP8266 development board. By using an online service to create a streamlined NodeMCU firmware this was a breeze!

NodeMCU with the correct modules

Flash the ESP8266 with a version of NodeMCU that contains the following modules:

  • bit
  • SPI
  • U8G

Follow my post here on how to built a custom NodeMCU firmware the easy way!

Custom NodeMCU build for OLED SPI displays

If you need help flashing the firmware you can take a look at my guide here.

Parts

If you don’t mind waiting a few weeks I would recommend buying from AliExpress as I have done for the lowest price and free shipping, even to Europe! Amazon, even with Amazon Prime is just a bit too expensive.

Disclaimer

This post contains links to Amazon where I get a small commission if you purchase anything after clicking on these links - at no extra cost to you! I have purchased all the mentioned products myself and I only link to products that I believe are the best for my readers. If you want to help out even more, take a look here.
PartAli ExpressAmazon
1x ESP8266 development board$4.2$9
1x 0.96 inch 128X64 OLED SPI module$4.1$10.7
1x breadboard$1.2$5.2
1x 10K resistor$0.7$4.9
Various breadboard wires$2.6$5.9
Total$12.8$35.7

Hardware setup

Connecting an inexpensive OLED display using SPI

Code

To get started I cheated a bit and used the test code from the U8G library from Github here. I downloaded the file u8g_graphics_test.lua and changed line 158 to

--init_i2c_display()

and line 159 to

init_spi_display()

Then I renamed the file to init.lua and uploaded the file to the ESP8266. The modified file can also be copied from here:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
-- ***************************************************************************
-- Graphics Test
--
-- This script executes several features of u8glib to test their Lua bindings.
--
-- Note: It is prepared for SSD1306-based displays. Select your connectivity
--       type by calling either init_i2c_display() or init_spi_display() at
--       the bottom of this file.
--
-- ***************************************************************************

-- setup I2c and connect display
function init_i2c_display()
    -- SDA and SCL can be assigned freely to available GPIOs
    local sda = 5 -- GPIO14
    local scl = 6 -- GPIO12
    local sla = 0x3c
    i2c.setup(0, sda, scl, i2c.SLOW)
    disp = u8g.ssd1306_128x64_i2c(sla)
end

-- setup SPI and connect display
function init_spi_display()
    -- Hardware SPI CLK  = GPIO14
    -- Hardware SPI MOSI = GPIO13
    -- Hardware SPI MISO = GPIO12 (not used)
    -- CS, D/C, and RES can be assigned freely to available GPIOs
    local cs  = 8 -- GPIO15, pull-down 10k to GND
    local dc  = 4 -- GPIO2
    local res = 0 -- GPIO16

    spi.setup(1, spi.MASTER, spi.CPOL_LOW, spi.CPHA_LOW, 8, 8)
    disp = u8g.ssd1306_128x64_hw_spi(cs, dc, res)
end


-- graphic test components
function prepare()
    disp:setFont(u8g.font_6x10)
    disp:setFontRefHeightExtendedText()
    disp:setDefaultForegroundColor()
    disp:setFontPosTop()
end

function box_frame(a)
    disp:drawStr(0, 0, "drawBox")
    disp:drawBox(5, 10, 20, 10)
    disp:drawBox(10+a, 15, 30, 7)
    disp:drawStr(0, 30, "drawFrame")
    disp:drawFrame(5, 10+30, 20, 10)
    disp:drawFrame(10+a, 15+30, 30, 7)
end

function disc_circle(a)
    disp:drawStr(0, 0, "drawDisc")
    disp:drawDisc(10, 18, 9)
    disp:drawDisc(24+a, 16, 7)
    disp:drawStr(0, 30, "drawCircle")
    disp:drawCircle(10, 18+30, 9)
    disp:drawCircle(24+a, 16+30, 7)
end

function r_frame(a)
    disp:drawStr(0, 0, "drawRFrame/Box")
    disp:drawRFrame(5, 10, 40, 30, a+1)
    disp:drawRBox(50, 10, 25, 40, a+1)
end

function stringtest(a)
    disp:drawStr(30+a, 31, " 0")
    disp:drawStr90(30, 31+a, " 90")
    disp:drawStr180(30-a, 31, " 180")
    disp:drawStr270(30, 31-a, " 270")
end

function line(a)
    disp:drawStr(0, 0, "drawLine")
    disp:drawLine(7+a, 10, 40, 55)
    disp:drawLine(7+a*2, 10, 60, 55)
    disp:drawLine(7+a*3, 10, 80, 55)
    disp:drawLine(7+a*4, 10, 100, 55)
end

function triangle(a)
    local offset = a
    disp:drawStr(0, 0, "drawTriangle")
    disp:drawTriangle(14,7, 45,30, 10,40)
    disp:drawTriangle(14+offset,7-offset, 45+offset,30-offset, 57+offset,10-offset)
    disp:drawTriangle(57+offset*2,10, 45+offset*2,30, 86+offset*2,53)
    disp:drawTriangle(10+offset,40+offset, 45+offset,30+offset, 86+offset,53+offset)
end

function ascii_1()
    local x, y, s
    disp:drawStr(0, 0, "ASCII page 1")
    for y = 0, 5, 1 do
        for x = 0, 15, 1 do
            s = y*16 + x + 32
            disp:drawStr(x*7, y*10+10, string.char(s))
        end
    end
end

function extra_page(a)
    disp:drawStr(0, 12, "setScale2x2")
    disp:setScale2x2()
    disp:drawStr(0, 6+a, "setScale2x2")
    disp:undoScale()
end


-- the draw() routine
function draw(draw_state)
    local component = bit.rshift(draw_state, 3)

    prepare()

    if (component == 0) then
        box_frame(bit.band(draw_state, 7))
    elseif (component == 1) then
        disc_circle(bit.band(draw_state, 7))
    elseif (component == 2) then
        r_frame(bit.band(draw_state, 7))
    elseif (component == 3) then
        stringtest(bit.band(draw_state, 7))
    elseif (component == 4) then
        line(bit.band(draw_state, 7))
    elseif (component == 5) then
        triangle(bit.band(draw_state, 7))
    elseif (component == 6) then
        ascii_1()
    elseif (component == 7) then
        extra_page(bit.band(draw_state, 7))
    end
end

function graphics_test()

    disp:firstPage()
    repeat
        draw(draw_state)
    until disp:nextPage() == false

    if (draw_state <= 7 + 8*8) then
        draw_state = draw_state + 1
    else
        print("--- Restarting Graphics Test ---")
        draw_state = 0
    end

    print("Heap: " .. node.heap())
    -- retrigger timer to give room for system housekeeping
    tmr.start(0)
end

draw_state = 0

--init_i2c_display()
init_spi_display()

-- set up timer 0 with short interval, will be retriggered in graphics_test()
tmr.register(0, 100, tmr.ALARM_SEMI, function() graphics_test() end)

print("--- Starting Graphics Test ---")
tmr.start(0)

Result

Running the U8G test program

Everything works and from here it is just a matter on using the U8G library to show what you need. Take a look at the NodeMCU U8G documentation here.

Enjoyed this Content?

Help keep it free by sending a donation or purchase something using my affiliate links. You can also subscribe to various site feeds to get notified of new posts, follow me on social media, and more.

6 Comments

Poul Serek

Hi Andy

I use it because many other people do, like here, and it seems that is to prevent flaky behaviour from the ESP8266 as described here.

/Poul

andy

I use STM8L with this OLED. All pins are connected without any resistors. I am running some of them for months without any issue.

I suspect the problem is caused by insufficient supply current to ESP8266 as it draws a lot power. Using resistors do reduce current consumption. But the root of cause may be the supply.

Poul Serek

Hi Andy

You might be right, I have seen many similar problems when using batteries which does not occur if using a brand name power supply.

/Poul

Ben Anderson

Hi There,

You wouldn’t happen to have a fritzing part for the SPI OLED display? The adafruit one uses 8 pins and is not a good match for the chinese 0.96” OLED’s that I have.

Many thanks,

Ben

Poul Serek

Hi Ben

Unfortunately I don’t have a fritzing part the OLED display, I used a standard one in the post.

/Poul

Leave a reply