SSD1306 OLED display driver 1.7.8 This library is developed to control SSD1306/SSD1331/SSD1351/IL9163/PCD8554 RGB i2c/spi LED displays

src/nano_engine/README.md
1 # Using NanoEngine for systems with low resources
2
3 ***
4
5 [tocstart]: # (toc start)
6
7 * [Introduction](#introduction)
8 * [Main idea of NanoEngine](#main-idea-of-nanoengine)
9 * [Simple NanoEngine demo](#simple-nanoengine-demo)
10 * [Reading keys with NanoEngine](#reading-keys-with-nanoengine)
11 * [Draw monochrome bitmap](#draw-monochrome-bitmap)
12 * [Draw moving bitmap](#draw-moving-bitmap)
13 * [What if not to use draw callbacks](#what-if-not-to-use-draw-callbacks)
14 * [Using Adafruit GFX with NanoEngine](#using-adafruit-gfx-with-nanoengine)
15
16 [tocend]: # (toc end)
17
18
19 ## Introduction
20
21 Many applications use double-buffered output to physical display to avoid flickering effect, when a user observed non-completed picture for a short time. When working with color OLED/LCD displays like ssd1331, some micro-controllers do not have enough RAM to fit display buffer in. For example, in 8-bit mode ssd1331 OLED display needs 6144 bytes (`96*64`). But simple micro-controller like Atmega328 has only 2KiB, and Attiny85 has only 512B of RAM. But with ssd1306 library you still can create applications for those ones with good graphics.
22 ssd1306 library represents NanoEngine8, intended to be used with color OLED displays. Digit 8 means that engine implements 8-bit color mode. Refer to arkanoid8 as example, which can be run even on small Attiny85 with color ssd1331 display. The system supports NanoEngine1 for monochrome OLED displays, NanoEngine8 for 8-bit RGB OLED displays, NanoEngine16 for 16-bit RGB OLED displays.
23
24 ## Main idea of NanoEngine
25
26 There are 2 issues with tiny controllers:
27 * they have a little RAM
28 * they support low frequencies
29
30 The first problem is solved in NanoEngine by using double-buffer to redraw only part of display content at once. By default NanoEngine uses 8x8 small buffer (64 bytes) and 24 bytes to store information on areas, which need to be refreshed.
31 The second problem is solved almost the same way: refresh only those part of display content, which were changed since last frame update. For example, ssd1331 oled display work on SPI at 8MHz frequency, that means in ideal conditions the screen content can be refreshed 162 times per second (`8000000/(96*64*8)`). But with the data, you need to send also commands to the display and to do some other stuff. And real tests with Atmega328p show that `ssd1306_clearScreen()` can run only at 58 FPS, coping data from buffer to OLED memory runs slower.
32 There is no such issue for Arduboy, since it uses monochrome OLED ssd1306 with only 1KiB of RAM buffer, and theoretical fps can be up to 976. For color display and small controllers the main solution is to refresh only part of display content. Arkanoid8 can give easily 60 fps with NanoEngine8
33
34 ## Simple NanoEngine demo
35
36 ```cpp
37 #include "ssd1306.h"
38 #include "nano_engine.h"
39
40 NanoEngine8 engine;
41
42 bool drawAll()
43 {
44 engine.canvas.clear();
45 engine.canvas.setColor(RGB_COLOR8(255,255,0));
46 engine.canvas.drawRect(15,12,70,55);
47 return true; // if to return false, the engine will skip this part of screen update
48 }
49
50 void setup()
51 {
52 /* Init SPI 96x64 RBG oled. 3 - RESET, 4 - CS (can be omitted, oled CS must be pulled down), 5 - D/C */
53 ssd1331_96x64_spi_init(3, 4, 5);
54
55 engine.begin();
56 engine.setFrameRate(30);
57 /* Set callback to draw parts, when NanoEngine8 asks */
58 engine.drawCallback( drawAll );
59 }
60
61 void loop()
62 {
63 if (!engine.nextFrame()) return;
64 engine.refresh(); // Makes engine to refresh whole display content
65 engine.display();
66 }
67 ```
68
69 ## Reading keys with NanoEngine
70
71 What, if we want to move yellow rectangle. There is easy way to do this with NanoEngine8. The engine supports up-to 6 keys: `BUTTON_DOWN`, `BUTTON_LEFT`, `BUTTON_RIGHT`, `BUTTON_UP`, `BUTTON_A`, `BUTTON_B`. All you need is to say the engine how it can get buttons state on your board. There are already 2 built-in implementations: `connectArduboyKeys()` allows using Arduboy platform hardware (remember that you need to replace ssd1306 oled with color ssd1331 oled), and `connectZKeypad()` allows using standard 5-keys Z-keypad (you can find it on E-bay). Or you can set custom key processing handler via `connectCustomKeys()` and implement your own hardware driver.
72
73 Example of using Z-keypad to move rectangle.
74 ```cpp
75 #include "ssd1306.h"
76 #include "nano_engine.h"
77
78 NanoEngine8 engine;
79
80 NanoRect rect = { {15,12}, {60,35} }; // Lets make rect smaller than in previous example
81
82 bool drawAll()
83 {
84 engine.canvas.clear();
85 engine.canvas.setColor(RGB_COLOR8(255,255,0));
86 engine.canvas.drawRect(rect); // draw rect in buffer
87 return true;
88 }
89
90 void setup()
91 {
92 /* Init SPI 96x64 RBG oled. 3 - RESET, 4 - CS (can be omitted, oled CS must be pulled down), 5 - D/C */
93 ssd1331_96x64_spi_init(3, 4, 5);
94
95 engine.begin();
96 engine.setFrameRate(30);
97 engine.drawCallback( drawAll ); // Set callback to draw parts, when NanoEngine8 asks
98 engine.connectZKeypad(0); // Connect ADC-buttons Z-keypad to analog A0 pin
99 engine.refresh(); // Makes engine to refresh whole display content at start-up
100 }
101
102 void loop()
103 {
104 if (!engine.nextFrame()) return;
105 NanoPoint point = {0,0};
106 if (engine.pressed( BUTTON_RIGHT )) point.x = +1;
107 if (engine.pressed( BUTTON_LEFT )) point.x = -1;
108 if (engine.pressed( BUTTON_UP )) point.y = -1;
109 if (engine.pressed( BUTTON_DOWN )) point.y = +1;
110 engine.refresh(rect); // Update screen content at old rect position
111 rect += point; // Move rect according to pressed keys
112 engine.refresh(rect); // Update screen content at new rect position
113 engine.display(); // refresh display content
114 }
115 ```
116
117 ## Draw monochrome bitmap
118
119 ```cpp
120 #include "ssd1306.h"
121 #include "nano_engine.h"
122
123 NanoEngine8 engine;
124
125 const uint8_t heartSprite[8] PROGMEM =
126 {
127 0B00001110,
128 0B00011111,
129 0B00111111,
130 0B01111110,
131 0B01111110,
132 0B00111101,
133 0B00011001,
134 0B00001110
135 };
136
137 bool drawAll()
138 {
139 engine.canvas.clear();
140 engine.canvas.setMode(0); // We want to draw non-transparent bitmap
141 engine.canvas.setColor(RGB_COLOR8(255,0,0)); // draw with red color
142 engine.canvas.drawBitmap1(10, 20, 8, 8, heartSprite);
143 return true;
144 }
145
146 void setup()
147 {
148 /* Init SPI 96x64 RBG oled. 3 - RESET, 4 - CS (can be omitted, oled CS must be pulled down), 5 - D/C */
149 ssd1331_96x64_spi_init(3, 4, 5);
150 engine.begin();
151 engine.drawCallback( drawAll ); // Set callback to draw parts, when NanoEngine8 asks
152 engine.refresh(); // Makes engine to refresh whole display content at start-up
153 }
154
155 void loop()
156 {
157 if (!engine.nextFrame()) return;
158 engine.display(); // refresh display content
159 }
160 ```
161
162 ## Draw moving bitmap
163
164 In some applications like games, there is need to move bitmaps. It is easy to do this with ssd1306 library using NanoSprite objects. NanoSprite object is responsible for refreshing areas, touched by sprite. So, you need only to move sprite, where you want, the engine will take care of updating display content.
165
166 ```cpp
167 #include "ssd1306.h"
168 #include "nano_engine.h"
169
170 const uint8_t heartSprite[8] PROGMEM =
171 {
172 0B00001110,
173 0B00011111,
174 0B00111111,
175 0B01111110,
176 0B01111110,
177 0B00111101,
178 0B00011001,
179 0B00001110
180 };
181
182 NanoEngine8 engine;
183 NanoSprite<NanoEngine8, engine> sprite( {0, 0}, {8, 8}, heartSprite );
184
185 bool drawAll()
186 {
187 engine.canvas.clear();
188 engine.canvas.setMode(0); // We want to draw non-transparent bitmap
189 engine.canvas.setColor(RGB_COLOR8(255,0,0)); // draw with red color
190 sprite.draw();
191 return true;
192 }
193
194 void setup()
195 {
196 /* Init SPI 96x64 RBG oled. 3 - RESET, 4 - CS (can be omitted, oled CS must be pulled down), 5 - D/C */
197 ssd1331_96x64_spi_init(3, 4, 5);
198 engine.begin();
199 engine.drawCallback( drawAll ); // Set callback to draw parts, when NanoEngine8 asks
200 engine.refresh(); // Makes engine to refresh whole display content at start-up
201 }
202
203 void loop()
204 {
205 if (!engine.nextFrame()) return;
206 // You will see horizontal flying heart
207 sprite.moveBy( { 1, 0 } );
208 engine.display(); // refresh display content
209 }
210 ```
211
212
213 ## What if not to use draw callbacks
214
215 If you don't want to use draw callbacks in your application, but still need a power of NanoEngine, then there is one way for you: to use full-screen double-buffering with NanoEngine. The example, you will find below, shows how to use full-screen double buffering for monochrome 128x64 ssd1306 oled display. This example can be run on Atmega328p and more powerful micro controllers. It clears back-buffer every time engine says to redraw the frame. But you can preserve previously prepared image by removing call to `engine.canvas.clear()`.
216
217 ```cpp
218 #include "ssd1306.h"
219 #include "nano_engine.h"
220
221 NanoEngine<BUFFER_128x64_MONO> engine;
222
223 void setup()
224 {
225 // Init SPI 128x64 monochrome oled.
226 // 3 - RESET, 4 - CS (can be omitted, oled CS must be pulled down), 5 - D/C
227 ssd1306_128x64_spi_init(3, 4, 5);
228
229 engine.begin();
230 engine.setFrameRate(30);
231 }
232
233 void loop()
234 {
235 if (!engine.nextFrame()) return;
236 engine.canvas.clear(); // This step can be removed, if you don't want to clear buffer
237 engine.canvas.drawRect(15,12,70,55);
238 engine.display();
239 }
240 ```
241
242 ## Using Adafruit GFX with NanoEngine
243
244 Many developers are familiar with nice AdafruitGFX library. It provides rich set of graphics functions. Starting with 1.7.0 ssd1306 library it is possible to use AdafruiGFX api in combination with NanoEngine. And it is really easy.
245 You need to remember only, that AdafruitGFX uses different approach, when working with rectangles and monochrome images. NanoCanvas expects monochrome bitmaps in native ssd1306 format, while Adafruit uses more native format for human. If you compare an example below with examples above you will understand, what's the difference (heartImage). Refer to AdafruitGFX documentation.
246
247 ```cpp
248 // Define this before including library header, this will give Adafruit GFX support
249 // !!! Don't forget to install AdafruitGFX library to your Arduino IDE !!!
250 #define CONFIG_ADAFRUIT_GFX_ENABLE
251
252 #include "ssd1306.h"
253 #include "nano_engine.h"
254
255 // Now you can use AdafruitGFX by referencing engine.canvas
256 NanoEngine<ADATILE_8x8_RGB8> engine;
257
258 const PROGMEM uint8_t heartImage[8] =
259 {
260 0B01100110,
261 0B11111001,
262 0B11111101,
263 0B11111111,
264 0B01111110,
265 0B00111100,
266 0B00011000,
267 0B00000000
268 };
269
270 bool drawAll()
271 {
272 engine.canvas.fillScreen( 0 );
273 engine.canvas.drawBitmap(10, 20, heartSprite, 8, 8, RGB_COLOR8(255,0,0)); // draw bitmap with red color
274 return true;
275 }
276
277 void setup()
278 {
279 /* Init SPI 96x64 RBG oled. 3 - RESET, 4 - CS (can be omitted, oled CS must be pulled down), 5 - D/C */
280 ssd1331_96x64_spi_init(3, 4, 5);
281 engine.begin();
282 engine.drawCallback( drawAll ); // Set callback to draw parts, when NanoEngine8 asks
283 engine.refresh(); // Makes engine to refresh whole display content at start-up
284 }
285
286 void loop()
287 {
288 if (!engine.nextFrame()) return;
289 engine.display(); // refresh display content
290 }
291 ```
292