SSD1306 OLED display driver  1.7.17
This library is developed to control SSD1306/SSD1331/SSD1351/IL9163/PCD8554 RGB i2c/spi LED displays
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  * [To upper level](@ref index)
16 
17 [tocend]: # (toc end)
18 
19 <a name="introduction"></a>
20 ## Introduction
21 
22 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.
23 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.
24 
25 <a name="main-idea-of-nanoengine"></a>
26 ## Main idea of NanoEngine
27 
28 There are 2 issues with tiny controllers:
29  * they have a little RAM
30  * they support low frequencies
31 
32 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.
33 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.
34 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
35 
36 <a name="simple-nanoengine-demo"></a>
37 ## Simple NanoEngine demo
38 
39 ```cpp
40 #include "ssd1306.h"
41 #include "nano_engine.h"
42 
43 NanoEngine8 engine;
44 
45 bool drawAll()
46 {
47  engine.canvas.clear();
48  engine.canvas.setColor(RGB_COLOR8(255,255,0));
49  engine.canvas.drawRect(15,12,70,55);
50  return true; // if to return false, the engine will skip this part of screen update
51 }
52 
53 void setup()
54 {
55  /* Init SPI 96x64 RBG oled. 3 - RESET, 4 - CS (can be omitted, oled CS must be pulled down), 5 - D/C */
56  ssd1331_96x64_spi_init(3, 4, 5);
57 
58  engine.begin();
59  engine.setFrameRate(30);
60  /* Set callback to draw parts, when NanoEngine8 asks */
61  engine.drawCallback( drawAll );
62 }
63 
64 void loop()
65 {
66  if (!engine.nextFrame()) return;
67  engine.refresh(); // Makes engine to refresh whole display content
68  engine.display();
69 }
70 ```
71 
72 <a name="reading-keys-with-nanoengine"></a>
73 ## Reading keys with NanoEngine
74 
75 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.
76 
77 Example of using Z-keypad to move rectangle.
78 ```cpp
79 #include "ssd1306.h"
80 #include "nano_engine.h"
81 
82 NanoEngine8 engine;
83 
84 NanoRect rect = { {15,12}, {60,35} }; // Lets make rect smaller than in previous example
85 
86 bool drawAll()
87 {
88  engine.canvas.clear();
89  engine.canvas.setColor(RGB_COLOR8(255,255,0));
90  engine.canvas.drawRect(rect); // draw rect in buffer
91  return true;
92 }
93 
94 void setup()
95 {
96  /* Init SPI 96x64 RBG oled. 3 - RESET, 4 - CS (can be omitted, oled CS must be pulled down), 5 - D/C */
97  ssd1331_96x64_spi_init(3, 4, 5);
98 
99  engine.begin();
100  engine.setFrameRate(30);
101  engine.drawCallback( drawAll ); // Set callback to draw parts, when NanoEngine8 asks
102  engine.connectZKeypad(0); // Connect ADC-buttons Z-keypad to analog A0 pin
103  engine.refresh(); // Makes engine to refresh whole display content at start-up
104 }
105 
106 void loop()
107 {
108  if (!engine.nextFrame()) return;
109  NanoPoint point = {0,0};
110  if (engine.pressed( BUTTON_RIGHT )) point.x = +1;
111  if (engine.pressed( BUTTON_LEFT )) point.x = -1;
112  if (engine.pressed( BUTTON_UP )) point.y = -1;
113  if (engine.pressed( BUTTON_DOWN )) point.y = +1;
114  engine.refresh(rect); // Update screen content at old rect position
115  rect += point; // Move rect according to pressed keys
116  engine.refresh(rect); // Update screen content at new rect position
117  engine.display(); // refresh display content
118 }
119 ```
120 
121 <a name="draw-monochrome-bitmap"></a>
122 ## Draw monochrome bitmap
123 
124 ```cpp
125 #include "ssd1306.h"
126 #include "nano_engine.h"
127 
128 NanoEngine8 engine;
129 
130 const uint8_t heartSprite[8] PROGMEM =
131 {
132  0B00001110,
133  0B00011111,
134  0B00111111,
135  0B01111110,
136  0B01111110,
137  0B00111101,
138  0B00011001,
139  0B00001110
140 };
141 
142 bool drawAll()
143 {
144  engine.canvas.clear();
145  engine.canvas.setMode(0); // We want to draw non-transparent bitmap
146  engine.canvas.setColor(RGB_COLOR8(255,0,0)); // draw with red color
147  engine.canvas.drawBitmap1(10, 20, 8, 8, heartSprite);
148  return true;
149 }
150 
151 void setup()
152 {
153  /* Init SPI 96x64 RBG oled. 3 - RESET, 4 - CS (can be omitted, oled CS must be pulled down), 5 - D/C */
154  ssd1331_96x64_spi_init(3, 4, 5);
155  engine.begin();
156  engine.drawCallback( drawAll ); // Set callback to draw parts, when NanoEngine8 asks
157  engine.refresh(); // Makes engine to refresh whole display content at start-up
158 }
159 
160 void loop()
161 {
162  if (!engine.nextFrame()) return;
163  engine.display(); // refresh display content
164 }
165 ```
166 
167 <a name="draw-moving-bitmap"></a>
168 ## Draw moving bitmap
169 
170 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.
171 
172 ```cpp
173 #include "ssd1306.h"
174 #include "nano_engine.h"
175 
176 const uint8_t heartSprite[8] PROGMEM =
177 {
178  0B00001110,
179  0B00011111,
180  0B00111111,
181  0B01111110,
182  0B01111110,
183  0B00111101,
184  0B00011001,
185  0B00001110
186 };
187 
188 NanoEngine8 engine;
189 NanoSprite<NanoEngine8, engine> sprite( {0, 0}, {8, 8}, heartSprite );
190 
191 bool drawAll()
192 {
193  engine.canvas.clear();
194  engine.canvas.setMode(0); // We want to draw non-transparent bitmap
195  engine.canvas.setColor(RGB_COLOR8(255,0,0)); // draw with red color
196  sprite.draw();
197  return true;
198 }
199 
200 void setup()
201 {
202  /* Init SPI 96x64 RBG oled. 3 - RESET, 4 - CS (can be omitted, oled CS must be pulled down), 5 - D/C */
203  ssd1331_96x64_spi_init(3, 4, 5);
204  engine.begin();
205  engine.drawCallback( drawAll ); // Set callback to draw parts, when NanoEngine8 asks
206  engine.refresh(); // Makes engine to refresh whole display content at start-up
207 }
208 
209 void loop()
210 {
211  if (!engine.nextFrame()) return;
212  // You will see horizontal flying heart
213  sprite.moveBy( { 1, 0 } );
214  engine.display(); // refresh display content
215 }
216 ```
217 
218 <a name="what-if-not-to-use-draw-callbacks"></a>
219 ## What if not to use draw callbacks
220 
221 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()`.
222 
223 ```cpp
224 #include "ssd1306.h"
225 #include "nano_engine.h"
226 
227 NanoEngine<BUFFER_128x64_MONO> engine;
228 
229 void setup()
230 {
231  // Init SPI 128x64 monochrome oled.
232  // 3 - RESET, 4 - CS (can be omitted, oled CS must be pulled down), 5 - D/C
233  ssd1306_128x64_spi_init(3, 4, 5);
234 
235  engine.begin();
236  engine.setFrameRate(30);
237 }
238 
239 void loop()
240 {
241  if (!engine.nextFrame()) return;
242  engine.canvas.clear(); // This step can be removed, if you don't want to clear buffer
243  engine.canvas.drawRect(15,12,70,55);
244  engine.display();
245 }
246 ```
247 
248 <a name="using-adafruit-gfx-with-nanoengine"></a>
249 ## Using Adafruit GFX with NanoEngine
250 
251 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.
252 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.
253 
254 ```cpp
255 // Define this before including library header, this will give Adafruit GFX support
256 // !!! Don't forget to install AdafruitGFX library to your Arduino IDE !!!
257 #define CONFIG_ADAFRUIT_GFX_ENABLE
258 
259 #include "ssd1306.h"
260 #include "nano_engine.h"
261 
262 // Now you can use AdafruitGFX by referencing engine.canvas
263 NanoEngine<ADATILE_8x8_RGB8> engine;
264 
265 const PROGMEM uint8_t heartImage[8] =
266 {
267  0B01100110,
268  0B11111001,
269  0B11111101,
270  0B11111111,
271  0B01111110,
272  0B00111100,
273  0B00011000,
274  0B00000000
275 };
276 
277 bool drawAll()
278 {
279  engine.canvas.fillScreen( 0 );
280  engine.canvas.drawBitmap(10, 20, heartSprite, 8, 8, RGB_COLOR8(255,0,0)); // draw bitmap with red color
281  return true;
282 }
283 
284 void setup()
285 {
286  /* Init SPI 96x64 RBG oled. 3 - RESET, 4 - CS (can be omitted, oled CS must be pulled down), 5 - D/C */
287  ssd1331_96x64_spi_init(3, 4, 5);
288  engine.begin();
289  engine.drawCallback( drawAll ); // Set callback to draw parts, when NanoEngine8 asks
290  engine.refresh(); // Makes engine to refresh whole display content at start-up
291 }
292 
293 void loop()
294 {
295  if (!engine.nextFrame()) return;
296  engine.display(); // refresh display content
297 }
298 ```
299