LCDGFX LCD display driver  1.2.0
Lightweight graphics library for SSD1306, SSD1325, SSD1327, SSD1331, SSD1351, SH1106, SH1107, IL9163, ST7735, ST7789, ILI9341, PCD8544 displays over I2C/SPI
ssd1306_4bit.inl
1 /*
2  MIT License
3 
4  Copyright (c) 2019-2021, Alexey Dynda
5 
6  Permission is hereby granted, free of charge, to any person obtaining a copy
7  of this software and associated documentation files (the "Software"), to deal
8  in the Software without restriction, including without limitation the rights
9  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  copies of the Software, and to permit persons to whom the Software is
11  furnished to do so, subject to the following conditions:
12 
13  The above copyright notice and this permission notice shall be included in all
14  copies or substantial portions of the Software.
15 
16  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22  SOFTWARE.
23 */
24 
25 #include "lcd_hal/io.h"
26 
28 //
29 // COMMON GRAPHICS
30 //
32 
33 // template class NanoDisplayOps4<I>;
34 
35 // template <class I>
36 // void NanoDisplayOps4<I>::printFixed(lcdint_t xpos, lcdint_t y, const char *ch, EFontStyle style)
37 //{
38 // m_cursorX = xpos;
39 // m_cursorY = y;
40 // m_fontStyle = style;
41 // print( ch );
42 //}
43 
45 //
46 // 4-BIT GRAPHICS
47 //
49 
50 template <class I> void NanoDisplayOps4<I>::putPixel(lcdint_t x, lcdint_t y)
51 {
52  lcdint_t newColumn = x >> 1;
53  if ( m_lastRow != y || newColumn != m_lastColumn )
54  {
55  m_lastRow = y;
56  m_lastColumn = newColumn;
57  m_lastByte = this->m_bgColor | (this->m_bgColor << 4); // Fill with BG color
58  }
59  // Clear 4-bits for the new pixel
60  m_lastByte &= 0xF0 >> (4 * (x & 1));
61  // Add 4-bits for of the new pixel
62  m_lastByte |= (this->m_color & 0x0F) << (4 * (x & 1));
63  this->m_intf.startBlock(x, y, 0);
64  this->m_intf.send(m_lastByte);
65  this->m_intf.endBlock();
66 }
67 
68 template <class I> void NanoDisplayOps4<I>::drawHLine(lcdint_t x1, lcdint_t y1, lcdint_t x2)
69 {
70  uint8_t data = 0;
71  this->m_intf.startBlock(x1, y1, 0);
72  while ( x1 <= x2 )
73  {
74  data |= (this->m_color & 0x0F) << (4 * (x1 & 1));
75  if ( x1 & 1 )
76  {
77  this->m_intf.send(data);
78  data = 0;
79  }
80  x1++;
81  }
82  if ( x1 & 1 )
83  {
84  this->m_intf.send(data);
85  }
86  this->m_intf.endBlock();
87 }
88 
89 template <class I> void NanoDisplayOps4<I>::drawVLine(lcdint_t x1, lcdint_t y1, lcdint_t y2)
90 {
91  this->m_intf.startBlock(x1, y1, 1);
92  while ( y1 <= y2 )
93  {
94  this->m_intf.send((this->m_color & 0x0F) << (4 * (x1 & 1)));
95  y1++;
96  }
97  this->m_intf.endBlock();
98 }
99 
100 template <class I> void NanoDisplayOps4<I>::fillRect(lcdint_t x1, lcdint_t y1, lcdint_t x2, lcdint_t y2)
101 {
102  if ( y1 > y2 )
103  {
104  ssd1306_swap_data(y1, y2, lcdint_t);
105  }
106  if ( x1 > x2 )
107  {
108  ssd1306_swap_data(x1, x2, lcdint_t);
109  }
110  // 4bpp displays pack 2 horizontally adjacent pixels into one byte
111  // (low nibble = even column, high nibble = odd column). The GDDRAM
112  // window opened by startBlock spans column-pairs [x1/2 .. x2/2],
113  // so we must emit exactly (x2/2 - x1/2 + 1) bytes per row.
114  //
115  // When x1 is odd or x2 is even, the rectangle's left/right edges
116  // sit inside a column-pair whose other pixel falls outside the
117  // requested rect. With no GDDRAM read-modify-write available we
118  // mask the outside pixel to zero — this is a documented hardware
119  // limitation, see docs/controller_matrix.md.
120  const uint8_t c = this->m_color & 0x0F;
121  const uint8_t full = (uint8_t)((c << 4) | c);
122  const uint8_t left_byte = (x1 & 1) ? (uint8_t)(c << 4) : full; // odd x1 → only high nibble
123  const uint8_t right_byte = ((x2 & 1) == 0) ? c : full; // even x2 → only low nibble
124  const lcdint_t pair_start = x1 >> 1;
125  const lcdint_t pair_end = x2 >> 1;
126  this->m_intf.startBlock(x1, y1, x2 - x1 + 1);
127  for ( lcdint_t row = y1; row <= y2; row++ )
128  {
129  if ( pair_start == pair_end )
130  {
131  // Single column-pair: combine left and right masks.
132  uint8_t both = full;
133  if ( x1 & 1 ) both &= 0xF0; // suppress low nibble (col x1-1)
134  if ( (x2 & 1) == 0 ) both &= 0x0F; // suppress high nibble (col x2+1)
135  this->m_intf.send(both);
136  continue;
137  }
138  this->m_intf.send(left_byte);
139  for ( lcdint_t k = pair_start + 1; k < pair_end; k++ )
140  {
141  this->m_intf.send(full);
142  }
143  this->m_intf.send(right_byte);
144  }
145  this->m_intf.endBlock();
146 }
147 
148 template <class I> void NanoDisplayOps4<I>::fill(uint16_t color)
149 {
150  this->m_intf.startBlock(0, 0, 0);
151  uint32_t count = (uint32_t)this->m_w * (uint32_t)this->m_h / 2;
152  while ( count > 0 )
153  {
154  this->m_intf.send(color);
155  count--;
156  }
157  this->m_intf.endBlock();
158 }
159 
160 template <class I> void NanoDisplayOps4<I>::clear()
161 {
162  fill(0x00);
163 }
164 
165 template <class I>
166 void NanoDisplayOps4<I>::drawXBitmap(lcdint_t x, lcdint_t y, lcduint_t w, lcduint_t h, const uint8_t *bitmap)
167 {
168  // TODO:
169 }
170 
171 template <class I>
172 void NanoDisplayOps4<I>::drawBitmap1(lcdint_t xpos, lcdint_t ypos, lcduint_t w, lcduint_t h, const uint8_t *bitmap)
173 {
174  uint8_t bit = 1;
175  uint8_t blackColor = this->m_bgColor | (this->m_bgColor << 4);
176  uint8_t color = this->m_color | (this->m_color << 4);
177  this->m_intf.startBlock(xpos, ypos, w);
178  while ( h-- )
179  {
180  lcduint_t wx;
181  uint8_t pixels = 0;
182  for ( wx = xpos; wx < xpos + (lcdint_t)w; wx++ )
183  {
184  uint8_t data = pgm_read_byte(bitmap);
185  uint8_t mask = (wx & 0x01) ? 0xF0 : 0x0F;
186  if ( data & bit )
187  pixels |= color & mask;
188  else
189  pixels |= blackColor & mask;
190  bitmap++;
191  if ( wx & 0x01 )
192  {
193  this->m_intf.send(pixels);
194  pixels = 0;
195  }
196  }
197  if ( wx & 0x01 )
198  {
199  this->m_intf.send(pixels);
200  }
201  bit <<= 1;
202  if ( bit == 0 )
203  {
204  bit = 1;
205  }
206  else
207  {
208  bitmap -= w;
209  }
210  }
211  this->m_intf.endBlock();
212 }
213 
214 template <class I>
215 void NanoDisplayOps4<I>::drawBitmap4(lcdint_t x, lcdint_t y, lcduint_t w, lcduint_t h, const uint8_t *bitmap)
216 {
217  this->m_intf.startBlock(x, y, w);
218  for ( lcdint_t _y = y; _y < y + h; _y++ )
219  {
220  uint8_t data = 0;
221  for ( lcdint_t _x = x; _x < x + w; _x++ )
222  {
223  uint8_t bmp = pgm_read_byte(bitmap);
224  if ( (_x - x) & 1 )
225  bmp >>= 4;
226  else
227  bmp &= 0x0F;
228  data |= bmp << (4 * (_x & 1));
229  if ( (_x - x) & 1 )
230  {
231  bitmap++;
232  }
233  if ( _x & 1 )
234  {
235  this->m_intf.send(data);
236  data = 0;
237  }
238  }
239  if ( (x + w) & 1 )
240  {
241  this->m_intf.send(data);
242  }
243  }
244  this->m_intf.endBlock();
245 }
246 
247 template <class I>
248 void NanoDisplayOps4<I>::drawBitmap8(lcdint_t x, lcdint_t y, lcduint_t w, lcduint_t h, const uint8_t *bitmap)
249 {
250  this->m_intf.startBlock(x, y, w);
251  uint32_t count = (w) * (h);
252  while ( count > 1 )
253  {
254  uint8_t data1 = pgm_read_byte(bitmap++);
255  uint8_t data2 = pgm_read_byte(bitmap++);
256  this->m_intf.send(RGB8_TO_GRAY4(data1) | RGB8_TO_GRAY4(data2) << 4);
257  count -= 2;
258  }
259  this->m_intf.endBlock();
260 }
261 
262 template <class I>
264 {
265  // NOT IMPLEMENTED
266 }
267 
268 template <class I>
269 void NanoDisplayOps4<I>::drawBuffer1(lcdint_t xpos, lcdint_t ypos, lcduint_t w, lcduint_t h, const uint8_t *buffer)
270 {
271  uint8_t bit = 1;
272  uint8_t blackColor = this->m_bgColor | (this->m_bgColor << 4);
273  uint8_t color = this->m_color | (this->m_color << 4);
274  this->m_intf.startBlock(xpos, ypos, w);
275  while ( h-- )
276  {
277  lcduint_t wx = w;
278  uint8_t pixels = 0;
279  while ( wx-- )
280  {
281  uint8_t data = *buffer;
282  uint8_t mask = (wx & 0x01) ? 0xF0 : 0x0F;
283  if ( data & bit )
284  pixels |= color & mask;
285  else
286  pixels |= blackColor & mask;
287  if ( (wx & 0x01) == 0x00 )
288  {
289  this->m_intf.send(pixels);
290  pixels = 0;
291  }
292  buffer++;
293  }
294  bit <<= 1;
295  if ( bit == 0 )
296  {
297  bit = 1;
298  }
299  else
300  {
301  buffer -= w;
302  }
303  }
304  this->m_intf.endBlock();
305 }
306 
307 template <class I>
309 {
310  this->drawBuffer1(x, y, w, h, buf);
311 }
312 
313 template <class I>
314 void NanoDisplayOps4<I>::drawBuffer4(lcdint_t x, lcdint_t y, lcduint_t w, lcduint_t h, const uint8_t *buffer)
315 {
316  this->m_intf.startBlock(x, y, w);
317  for ( lcdint_t _y = y; _y < y + h; _y++ )
318  {
319  uint8_t data = 0;
320  for ( lcdint_t _x = x; _x < x + w; _x++ )
321  {
322  uint8_t bmp = *buffer;
323  if ( (_x - x) & 1 )
324  bmp >>= 4;
325  else
326  bmp &= 0x0F;
327  data |= bmp << (4 * (_x & 1));
328  if ( (_x - x) & 1 )
329  {
330  buffer++;
331  }
332  if ( _x & 1 )
333  {
334  this->m_intf.send(data);
335  data = 0;
336  }
337  }
338  if ( (x + w) & 1 )
339  {
340  this->m_intf.send(data);
341  }
342  }
343  this->m_intf.endBlock();
344 }
345 
346 template <class I>
347 void NanoDisplayOps4<I>::drawBuffer8(lcdint_t x, lcdint_t y, lcduint_t w, lcduint_t h, const uint8_t *buffer)
348 {
349  this->m_intf.startBlock(x, y, w);
350  uint32_t count = (w) * (h);
351  while ( count > 1 )
352  {
353  uint8_t data1 = *buffer;
354  buffer++;
355  uint8_t data2 = *buffer;
356  buffer++;
357  this->m_intf.send(RGB8_TO_GRAY4(data1) | RGB8_TO_GRAY4(data2) << 4);
358  count -= 2;
359  }
360  this->m_intf.endBlock();
361 }
362 
363 template <class I>
365 {
366  // NOT IMPLEMENTED
367 }
368 
369 template <class I> uint8_t NanoDisplayOps4<I>::printChar(uint8_t c)
370 {
371  uint16_t unicode = this->m_font->unicode16FromUtf8(c);
372  if ( unicode == SSD1306_MORE_CHARS_REQUIRED )
373  return 0;
374  SCharInfo char_info;
375  this->m_font->getCharBitmap(unicode, &char_info);
376  uint8_t mode = this->m_textMode;
377  for ( uint8_t i = 0; i < (this->m_fontStyle == STYLE_BOLD ? 2 : 1); i++ )
378  {
379  this->drawBitmap1(this->m_cursorX + i, this->m_cursorY, char_info.width, char_info.height, char_info.glyph);
380  this->m_textMode |= CANVAS_MODE_TRANSPARENT;
381  }
382  this->m_textMode = mode;
383  this->m_cursorX += (lcdint_t)(char_info.width + char_info.spacing);
384  if ( ((this->m_textMode & CANVAS_TEXT_WRAP_LOCAL) &&
385  (this->m_cursorX > ((lcdint_t)this->m_w - (lcdint_t)this->m_font->getHeader().width))) ||
386  ((this->m_textMode & CANVAS_TEXT_WRAP) &&
387  (this->m_cursorX > ((lcdint_t)this->m_w - (lcdint_t)this->m_font->getHeader().width))) )
388  {
389  this->m_cursorY += (lcdint_t)this->m_font->getHeader().height;
390  this->m_cursorX = 0;
391  if ( (this->m_textMode & CANVAS_TEXT_WRAP_LOCAL) &&
392  (this->m_cursorY > ((lcdint_t)this->m_h - (lcdint_t)this->m_font->getHeader().height)) )
393  {
394  this->m_cursorY = 0;
395  }
396  }
397  return 1;
398 }
399 
400 template <class I> size_t NanoDisplayOps4<I>::write(uint8_t c)
401 {
402  if ( c == '\n' )
403  {
404  this->m_cursorY += (lcdint_t)this->m_font->getHeader().height;
405  this->m_cursorX = 0;
406  }
407  else if ( c == '\r' )
408  {
409  // skip non-printed char
410  }
411  else
412  {
413  return printChar(c);
414  }
415  return 1;
416 }
417 
418 template <class I> void NanoDisplayOps4<I>::printFixed(lcdint_t xpos, lcdint_t y, const char *ch, EFontStyle style)
419 {
420  // TODO: fontstyle not supported
421  // m_fontStyle = style;
422  this->m_cursorX = xpos;
423  this->m_cursorY = y;
424  while ( *ch )
425  {
426  this->write(*ch);
427  ch++;
428  }
429 }
void fill(uint16_t color)
Fill screen content with specified color.
void drawHLine(lcdint_t x1, lcdint_t y1, lcdint_t x2)
Draws horizontal or vertical line.
uint8_t height
char height in pixels
Definition: canvas_types.h:144
uint8_t lcduint_t
internal int type, used by the library.
Definition: canvas_types.h:79
void drawXBitmap(lcdint_t x, lcdint_t y, lcduint_t w, lcduint_t h, const uint8_t *bitmap)
Draws bitmap, located in Flash, on the display The bitmap should be in XBMP format.
Structure describes single char information.
Definition: canvas_types.h:141
void putPixel(lcdint_t x, lcdint_t y) __attribute__((noinline))
Draws pixel on specified position.
void drawVLine(lcdint_t x1, lcdint_t y1, lcdint_t y2)
Draws horizontal or vertical line.
If the flag is specified, text cursor is moved to new line when end of canvas is reached.
Definition: canvas_types.h:103
void drawBuffer4(lcdint_t x, lcdint_t y, lcduint_t w, lcduint_t h, const uint8_t *buffer) __attribute__((noinline))
Draws 4-bit bitmap, located in RAM, on the display Each byte represents two pixels in 4-4 format: ref...
This flag make bitmaps transparent (Black color)
Definition: canvas_types.h:101
int8_t lcdint_t
internal int type, used by the library.
Definition: canvas_types.h:77
SSD1306 HAL IO communication functions.
#define SSD1306_MORE_CHARS_REQUIRED
Flag means that more chars are required to decode utf-8.
Definition: canvas_types.h:43
void printFixed(lcdint_t xpos, lcdint_t y, const char *ch, EFontStyle style=STYLE_NORMAL) __attribute__((noinline))
Print text at specified position to canvas.
void drawBuffer8(lcdint_t x, lcdint_t y, lcduint_t w, lcduint_t h, const uint8_t *buffer)
Draws 8-bit bitmap, located in RAM, on the display Each byte represents one pixel in 2-2-3 format: re...
void drawBitmap4(lcdint_t x, lcdint_t y, lcduint_t w, lcduint_t h, const uint8_t *bitmap) __attribute__((noinline))
Draws 4-bit gray-color bitmap in color buffer.
void drawBitmap8(lcdint_t x, lcdint_t y, lcduint_t w, lcduint_t h, const uint8_t *bitmap)
Draws 8-bit color bitmap in color buffer.
uint8_t printChar(uint8_t c)
Draws single character to canvas.
void drawBuffer16(lcdint_t xpos, lcdint_t ypos, lcduint_t w, lcduint_t h, const uint8_t *buffer) __attribute__((noinline))
Draws 16-bit bitmap, located in RAM, on the display Each pixel occupies 2 bytes (5-6-5 format): refer...
size_t write(uint8_t c) __attribute__((noinline))
Writes single character to canvas.
void clear()
Clears canvas.
void drawBitmap16(lcdint_t xpos, lcdint_t ypos, lcduint_t w, lcduint_t h, const uint8_t *bitmap)
Draw 16-bit color bitmap, located in Flash, directly to OLED display GDRAM.
If the flag is specified, text cursor is moved to new line when end of screen is reached.
Definition: canvas_types.h:99
uint8_t width
char width in pixels
Definition: canvas_types.h:143
#define RGB8_TO_GRAY4(rgb)
Macro to convert 8-bit RGB to 4-bit monochrome format.
Definition: canvas_types.h:61
void drawBuffer1Fast(lcdint_t x, lcdint_t y, lcduint_t w, lcduint_t h, const uint8_t *buffer)
Implements the same behavior as drawBuffer1, but much faster.
void fillRect(lcdint_t x1, lcdint_t y1, lcdint_t x2, lcdint_t y2) __attribute__((noinline))
Fills rectangle area.
const uint8_t * glyph
char data, located in progmem.
Definition: canvas_types.h:146
void drawBitmap1(lcdint_t x, lcdint_t y, lcduint_t w, lcduint_t h, const uint8_t *bitmap) __attribute__((noinline))
Draws monochrome bitmap in color buffer using color, specified via setColor() method Draws monochrome...
void drawBuffer1(lcdint_t x, lcdint_t y, lcduint_t w, lcduint_t h, const uint8_t *buffer) __attribute__((noinline))
Draws bitmap, located in RAM, on the display Each byte represents 8 vertical pixels.
uint8_t spacing
additional spaces after char in pixels
Definition: canvas_types.h:145
EFontStyle
Supported font styles.
Definition: canvas_types.h:88
#define ssd1306_swap_data(a, b, type)
swaps content of a and b variables of type type
Definition: io.h:114