SSD1306 OLED display driver  1.4.4
This library is developed to control SSD1306/SSD1331 RGB i2c/spi OLED displays and spi PCD8544 LED display
ssd1306.c
1 /*
2  MIT License
3 
4  Copyright (c) 2016-2018, 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 "font6x8.h"
26 #include "ssd1306.h"
27 #include "lcd/lcd_common.h"
28 #include "i2c/ssd1306_i2c.h"
29 #include "spi/ssd1306_spi.h"
30 #include "intf/ssd1306_interface.h"
31 #include <stdlib.h>
32 
33 // TODO: remove
34 #include "lcd/ssd1306_commands.h"
35 
39 static uint8_t s_invertByte = 0x00000000;
40 const uint8_t *s_font6x8 = &ssd1306xled_font6x8[4];
41 SFixedFontInfo s_fixedFont = { 0 };
42 
44 {
45  return s_displayHeight;
46 }
47 
49 {
50  return s_displayWidth;
51 }
52 
53 void ssd1306_fillScreen(uint8_t fill_Data)
54 {
55  fill_Data ^= s_invertByte;
56  ssd1306_setRamBlock(0, 0, 0);
58  for(uint8_t m=(s_displayHeight >> 3); m>0; m--)
59  {
60  for(uint8_t n=s_displayWidth; n>0; n--)
61  {
62  ssd1306_sendPixels(fill_Data);
63  }
65  }
67 }
68 
70 {
71  ssd1306_setRamBlock(0, 0, 0);
73  for(uint8_t m=(s_displayHeight >> 3); m>0; m--)
74  {
75  for(uint8_t n=s_displayWidth; n>0; n--)
76  {
77  ssd1306_sendPixels( s_invertByte );
78  }
80  }
82 }
83 
84 
86 {
87  ssd1306_sendCommand(SSD1306_DISPLAYOFF);
88 }
89 
90 
92 {
93  ssd1306_sendCommand(SSD1306_DISPLAYON);
94 }
95 
96 void ssd1306_setContrast(uint8_t contrast)
97 {
99  ssd1306_sendByte(SSD1306_SETCONTRAST);
100  ssd1306_sendByte(contrast);
102 }
103 
104 uint8_t ssd1306_printFixed(uint8_t xpos, uint8_t y, const char ch[], EFontStyle style)
105 {
106  uint8_t i, j=0;
107  uint8_t text_index = 0;
108  uint8_t page_offset = 0;
109  uint8_t x = xpos;
110  y >>= 3;
111  ssd1306_setRamBlock(xpos, y, s_displayWidth - xpos);
113  for(;;)
114  {
115  if( (x > s_displayWidth - s_fixedFont.width) || (ch[j] == '\0') )
116  {
117  x = xpos;
118  y++;
119  if (y >= (s_displayHeight >> 3))
120  {
121  break;
122  }
123  page_offset++;
124  if (page_offset == s_fixedFont.pages)
125  {
126  text_index = j;
127  page_offset = 0;
128  if (ch[j] == '\0')
129  {
130  break;
131  }
132  }
133  else
134  {
135  j = text_index;
136  }
138  ssd1306_setRamBlock(xpos, y, s_displayWidth - xpos);
140  }
141  uint8_t c = ch[j];
142  if ( c >= s_fixedFont.ascii_offset )
143  {
144  c -= s_fixedFont.ascii_offset;
145  }
146  uint8_t ldata = 0;
147  uint16_t offset = (c * s_fixedFont.pages + page_offset) * s_fixedFont.width;
148  for( i=s_fixedFont.width; i>0; i--)
149  {
150  uint8_t data;
151  if ( style == STYLE_NORMAL )
152  {
153  data = pgm_read_byte(&s_fixedFont.data[offset]);
154  }
155  else if ( style == STYLE_BOLD )
156  {
157  uint8_t temp = pgm_read_byte(&s_fixedFont.data[offset]);
158  data = temp | ldata;
159  ldata = temp;
160  }
161  else
162  {
163  uint8_t temp = pgm_read_byte(&s_fixedFont.data[offset + 1]);
164  data = (temp & 0xF0) | ldata;
165  ldata = (temp & 0x0F);
166  }
167  ssd1306_sendPixels(data^s_invertByte);
168  offset++;
169  }
170  x += s_fixedFont.width;
171  j++;
172  }
174  return j;
175 }
176 
177 uint8_t ssd1306_printFixed2x(uint8_t xpos, uint8_t y, const char ch[], EFontStyle style)
178 {
179  uint8_t i, j=0;
180  uint8_t text_index = 0;
181  uint8_t page_offset = 0;
182  uint8_t x = xpos;
183  y >>= 3;
184  ssd1306_setRamBlock(xpos, y, s_displayWidth - xpos);
186  for(;;)
187  {
188  if( (x > s_displayWidth - (s_fixedFont.width << 1)) || (ch[j] == '\0') )
189  {
190  x = xpos;
191  y++;
192  if (y >= (s_displayHeight >> 3))
193  {
194  break;
195  }
196  page_offset++;
197  if (page_offset == (s_fixedFont.pages << 1))
198  {
199  text_index = j;
200  page_offset = 0;
201  if (ch[j] == '\0')
202  {
203  break;
204  }
205  }
206  else
207  {
208  j = text_index;
209  }
211  ssd1306_setRamBlock(xpos, y, s_displayWidth - xpos);
213  }
214  uint8_t c = ch[j];
215  if ( c >= s_fixedFont.ascii_offset )
216  {
217  c -= s_fixedFont.ascii_offset;
218  }
219  uint8_t ldata = 0;
220  uint16_t offset = (c * s_fixedFont.pages + (page_offset >> 1)) * s_fixedFont.width;
221  for( i=s_fixedFont.width; i>0; i--)
222  {
223  uint8_t data;
224  if ( style == STYLE_NORMAL )
225  {
226  data = pgm_read_byte(&s_fixedFont.data[offset]);
227  }
228  else if ( style == STYLE_BOLD )
229  {
230  uint8_t temp = pgm_read_byte(&s_fixedFont.data[offset]);
231  data = temp | ldata;
232  ldata = temp;
233  }
234  else
235  {
236  uint8_t temp = pgm_read_byte(&s_fixedFont.data[offset + 1]);
237  data = (temp & 0xF0) | ldata;
238  ldata = (temp & 0x0F);
239  }
240  if (page_offset & 1) data >>= 4;
241  data = ((data & 0x01) ? 0x03: 0x00) |
242  ((data & 0x02) ? 0x0C: 0x00) |
243  ((data & 0x04) ? 0x30: 0x00) |
244  ((data & 0x08) ? 0xC0: 0x00);
245  ssd1306_sendPixels(data^s_invertByte);
246  ssd1306_sendPixels(data^s_invertByte);
247  offset++;
248  }
249  x += (s_fixedFont.width << 1);
250  j++;
251  }
253  return j;
254 }
255 
256 
257 uint8_t ssd1306_charF6x8(uint8_t x, uint8_t y, const char ch[], EFontStyle style)
258 {
259  uint8_t i, j=0;
262  while(ch[j] != '\0')
263  {
264  uint8_t c = ch[j] - 32;
265  if ( c > 224 )
266  {
267  c = 0;
268  }
269  if(x > s_displayWidth - 6)
270  {
271  x=0;
272  y++;
273  }
274  uint8_t ldata = 0;
275  for(i=0;i<6;i++)
276  {
277  uint8_t data;
278  if ( style == STYLE_NORMAL )
279  {
280  data = pgm_read_byte(&s_font6x8[c*6+i]);
281  }
282  else if ( style == STYLE_BOLD )
283  {
284  uint8_t temp = pgm_read_byte(&s_font6x8[c*6+i]);
285  data = temp | ldata;
286  ldata = temp;
287  }
288  else
289  {
290  uint8_t temp = pgm_read_byte(&s_font6x8[c*6+i + 1]);
291  data = (temp & 0xF0) | ldata;
292  ldata = (temp & 0x0F);
293  }
294  ssd1306_sendPixels(data^s_invertByte);
295  }
296  x += 6;
297  j++;
298  }
300  return j;
301 }
302 
303 uint8_t ssd1306_charF12x16(uint8_t xpos, uint8_t y, const char ch[], EFontStyle style)
304 {
305  uint8_t i, j=0;
306  uint8_t text_index = 0;
307  uint8_t odd = 0;
308  uint8_t x = xpos;
309  ssd1306_setRamBlock(xpos, y, s_displayWidth - xpos);
311  for(;;)
312  {
313  if( (x > s_displayWidth-12) || (ch[j] == '\0') )
314  {
315  x = xpos;
316  y++;
317  if (y >= (s_displayHeight >> 3))
318  {
319  break;
320  }
321  if (odd)
322  {
323  text_index = j;
324  if (ch[j] == '\0')
325  {
326  break;
327  }
328  }
329  else
330  {
331  j = text_index;
332  }
333  odd = !odd;
335  ssd1306_setRamBlock(xpos, y, s_displayWidth - xpos);
337  }
338  uint8_t c = ch[j] - 32;
339  if ( c > 224 )
340  {
341  c = 0;
342  }
343  uint8_t ldata = 0;
344  for(i=0;i<6;i++)
345  {
346  uint8_t data;
347  if ( style == STYLE_NORMAL )
348  {
349  data = pgm_read_byte(&s_font6x8[c*6+i]);
350  }
351  else if ( style == STYLE_BOLD )
352  {
353  uint8_t temp = pgm_read_byte(&s_font6x8[c*6+i]);
354  data = temp | ldata;
355  ldata = temp;
356  }
357  else
358  {
359  uint8_t temp = pgm_read_byte(&s_font6x8[c*6+i + 1]);
360  data = (temp & 0xF0) | ldata;
361  ldata = (temp & 0x0F);
362  }
363  if (odd) data >>= 4;
364  data = ((data & 0x01) ? 0x03: 0x00) |
365  ((data & 0x02) ? 0x0C: 0x00) |
366  ((data & 0x04) ? 0x30: 0x00) |
367  ((data & 0x08) ? 0xC0: 0x00);
368  ssd1306_sendPixels(data^s_invertByte);
369  ssd1306_sendPixels(data^s_invertByte);
370  }
371  x += 12;
372  j++;
373  }
375  return j;
376 }
377 
378 uint8_t ssd1306_charF6x8_eol(uint8_t left,
379  uint8_t y,
380  const char ch[],
381  EFontStyle style,
382  uint8_t right)
383 {
384  uint8_t len = ssd1306_charF6x8(left, y, ch, style);
385  uint8_t text_end_pos = len * 6 + left;
386  if (text_end_pos <= right)
387  {
388  ssd1306_clearBlock(text_end_pos, y, right - text_end_pos + 1, 8);
389  }
390  return len;
391 }
392 
393 void ssd1306_setFixedFont(const uint8_t * progmemFont)
394 {
395  s_fixedFont.width = pgm_read_byte(&progmemFont[1]);
396  s_fixedFont.pages = (pgm_read_byte(&progmemFont[2]) + 7) >> 3;
397  s_fixedFont.ascii_offset = pgm_read_byte(&progmemFont[3]);
398  s_fixedFont.data = progmemFont + 4;
399 }
400 
401 void ssd1306_setFont6x8(const uint8_t * progmemFont)
402 {
403  s_font6x8 = progmemFont + 4;
404 }
405 
406 void ssd1306_putPixel(uint8_t x, uint8_t y)
407 {
408  ssd1306_setRamBlock(x, y >> 3, 1);
410  ssd1306_sendPixels((1 << (y & 0x07))^s_invertByte);
412 }
413 
414 void ssd1306_putPixels(uint8_t x, uint8_t y, uint8_t pixels)
415 {
416  ssd1306_setRamBlock(x, y >> 3, 1);
418  ssd1306_sendPixels(pixels^s_invertByte);
420 }
421 
422 void ssd1306_drawHLine(uint8_t x1, uint8_t y1, uint8_t x2)
423 {
424  ssd1306_setRamBlock(x1, y1 >> 3, x2 - x1 + 1);
426  for (uint8_t x = x1; x <= x2; x++)
427  {
428  ssd1306_sendPixels((1 << (y1 & 0x07))^s_invertByte);
429  }
431 }
432 
433 void ssd1306_drawVLine(uint8_t x1, uint8_t y1, uint8_t y2)
434 {
435  uint8_t topPage = y1 >> 3;
436  uint8_t bottomPage = y2 >> 3;
437  uint8_t height = y2-y1;
438  ssd1306_setRamBlock(x1, topPage, 1);
440  if (topPage == bottomPage)
441  {
442  ssd1306_sendPixels( ((0xFF >> (0x07 - height)) << (y1 & 0x07))^s_invertByte );
444  return;
445  }
446  ssd1306_sendPixels( (0xFF << (y1 & 0x07))^s_invertByte );
447  uint8_t y;
448  for ( y = (topPage + 1); y <= (bottomPage - 1); y++)
449  {
451  ssd1306_sendPixels( 0xFF^s_invertByte );
452  }
454  ssd1306_sendPixels( (0xFF >> (0x07 - (y2 & 0x07)))^s_invertByte );
456 }
457 
458 void ssd1306_drawRect(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2)
459 {
460  ssd1306_drawHLine(x1+1, y1, x2-1);
461  ssd1306_drawHLine(x1+1, y2, x2-1);
462  ssd1306_drawVLine(x1, y1, y2);
463  ssd1306_drawVLine(x2, y1, y2);
464 }
465 
466 void ssd1306_drawBuffer(uint8_t x, uint8_t y, uint8_t w, uint8_t h, const uint8_t *buf)
467 {
468  uint8_t i, j;
469  ssd1306_setRamBlock(x, y, w);
471  for(j=(h >> 3); j>0; j--)
472  {
473  for(i=w;i>0;i--)
474  {
475  ssd1306_sendPixels(s_invertByte^*buf++);
476  }
478  }
480 }
481 
482 void ssd1306_drawBitmap(uint8_t x, uint8_t y, uint8_t w, uint8_t h, const uint8_t *buf)
483 {
484  uint8_t i, j;
485  uint8_t remainder = (s_displayWidth - x) < w ? (w + x - s_displayWidth): 0;
486  w -= remainder;
487  ssd1306_setRamBlock(x, y, w);
489  for(j=(h >> 3); j>0; j--)
490  {
491  for(i=w;i>0;i--)
492  {
493  ssd1306_sendPixels(s_invertByte^pgm_read_byte(buf++));
494  }
495  buf += remainder;
497  }
499 }
500 
501 
502 void ssd1306_clearBlock(uint8_t x, uint8_t y, uint8_t w, uint8_t h)
503 {
504  uint8_t i, j;
505  ssd1306_setRamBlock(x, y, w);
507  for(j=(h >> 3); j>0; j--)
508  {
509  for(i=w;i>0;i--)
510  {
511  ssd1306_sendPixels(s_invertByte);
512  }
514  }
516 }
517 
518 
519 void ssd1306_drawSpriteEx(uint8_t x, uint8_t y, uint8_t w, const uint8_t *sprite)
520 {
521  uint8_t i;
522  ssd1306_setRamBlock(x,y,w);
524  for(i=0;i<w;i++)
525  {
526  ssd1306_sendPixels(s_invertByte^pgm_read_byte(&sprite[i]));
527  }
529 }
530 
531 
533 {
534  uint8_t offsety = sprite->y & 0x7;
535  if (sprite->y < s_displayHeight)
536  {
537  ssd1306_setRamBlock(sprite->x, sprite->y >> 3, sprite->w);
539  for (uint8_t i=0; i < sprite->w; i++)
540  {
541  ssd1306_sendPixels( s_invertByte^(pgm_read_byte( &sprite->data[i] ) << offsety) );
542  }
544  }
545  if (offsety && (sprite->y + 8 < s_displayHeight))
546  {
547  ssd1306_setRamBlock(sprite->x, (sprite->y >> 3) + 1, sprite->w);
549  for (uint8_t i=0; i < sprite->w; i++)
550  {
551  ssd1306_sendPixels( s_invertByte^(pgm_read_byte( &sprite->data[i] ) >> (8 - offsety)) );
552  }
554  }
555  sprite->lx = sprite->x;
556  sprite->ly = sprite->y;
557 }
558 
559 
561 {
562  uint8_t posy = sprite->y >> 3;
563  uint8_t offsety = sprite->y & 0x7;
564  ssd1306_setRamBlock(sprite->x, posy, sprite->w);
566  for (uint8_t i=sprite->w; i > 0; i--)
567  {
568  ssd1306_sendPixels( s_invertByte );
569  }
571  if (offsety)
572  {
573  ssd1306_setRamBlock(sprite->x, posy + 1, sprite->w);
575  for (uint8_t i=sprite->w; i > 0; i--)
576  {
577  ssd1306_sendPixels( s_invertByte );
578  }
579  }
581 }
582 
583 
585 {
586  uint8_t y1 = sprite->ly >> 3;
587  uint8_t y2 = (sprite->ly + 7) >> 3;
588  if (sprite->ly < sprite->y)
589  y2 = min(y2, (uint8_t)((sprite->y >> 3) - 1));
590  else if (sprite->y + 8 > sprite->ly)
591  y1 = max(y1, (sprite->ly + 7) >> 3);
592  for(uint8_t y = y1; y <= y2; y++)
593  {
594  ssd1306_setRamBlock(sprite->lx, y, sprite->w);
596  for(uint8_t x = sprite->w; x > 0; x--)
597  {
598  ssd1306_sendPixels( s_invertByte );
599  }
601  }
602  if (sprite->lx != sprite->x)
603  {
604  uint8_t x1 = sprite->lx;
605  uint8_t x2 = sprite->lx + sprite->w - 1;
606  if (sprite->x < sprite->lx)
607  x1 = max(x1, sprite->x + sprite->w);
608  else
609  x2 = min((uint8_t)(sprite->x - 1), x2);
610  for(uint8_t y = sprite->ly >> 3; y <= (sprite->ly + 7) >> 3; y++)
611  {
612  ssd1306_setRamBlock(x1, y, x2 - x1 + 1 );
614  for(uint8_t x = x2 - x1 + 1; x > 0; x--)
615  {
616  ssd1306_sendPixels( s_invertByte );
617  }
619  }
620  }
621 }
622 
623 SPRITE ssd1306_createSprite(uint8_t x, uint8_t y, uint8_t w, const uint8_t *data)
624 {
625  return (SPRITE){x,y,w,x,y,data,NULL};
626 }
627 
628 void ssd1306_replaceSprite(SPRITE *sprite, const uint8_t *data)
629 {
630  sprite->data = data;
631 }
632 
634 {
636  {
637  ssd1306_sendCommand(SSD1306_INVERTDISPLAY);
638  }
639 }
640 
642 {
644  {
645  ssd1306_sendCommand(SSD1306_NORMALDISPLAY);
646  }
647 }
648 
650 {
651  s_invertByte = 0xFF;
652 }
653 
655 {
656  s_invertByte = 0x00;
657 }
void ssd1306_drawBitmap(uint8_t x, uint8_t y, uint8_t w, uint8_t h, const uint8_t *buf)
Definition: ssd1306.c:482
void ssd1306_negativeMode()
Definition: ssd1306.c:649
void ssd1306_setContrast(uint8_t contrast)
Definition: ssd1306.c:96
const uint8_t * data
Pointer to PROGMEM data, representing sprite image.
void(* ssd1306_sendByte)(uint8_t data)
void ssd1306_displayOff()
Definition: ssd1306.c:85
void(* ssd1306_dataStart)()
void(* ssd1306_endTransmission)()
uint8_t ssd1306_displayHeight()
Definition: ssd1306.c:43
void ssd1306_replaceSprite(SPRITE *sprite, const uint8_t *data)
Definition: ssd1306.c:628
void ssd1306_drawHLine(uint8_t x1, uint8_t y1, uint8_t x2)
Definition: ssd1306.c:422
void(* ssd1306_nextRamPage)()
void ssd1306_displayOn()
Definition: ssd1306.c:91
void ssd1306_drawVLine(uint8_t x1, uint8_t y1, uint8_t y2)
Definition: ssd1306.c:433
void ssd1306_positiveMode()
Definition: ssd1306.c:654
const PROGMEM uint8_t ssd1306xled_font6x8[]
Definition: font6x8.c:18
uint8_t ascii_offset
ascii offset
void ssd1306_putPixels(uint8_t x, uint8_t y, uint8_t pixels)
Definition: ssd1306.c:414
SPRITE ssd1306_createSprite(uint8_t x, uint8_t y, uint8_t w, const uint8_t *data)
Definition: ssd1306.c:623
void ssd1306_drawSpriteEx(uint8_t x, uint8_t y, uint8_t w, const uint8_t *sprite)
Definition: ssd1306.c:519
void ssd1306_sendCommand(uint8_t command)
void ssd1306_fillScreen(uint8_t fill_Data)
Definition: ssd1306.c:53
void ssd1306_drawRect(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2)
Definition: ssd1306.c:458
void ssd1306_clearScreen()
Definition: ssd1306.c:69
#define max(a, b)
uint8_t y
draw position Y on the screen
#define min(a, b)
void ssd1306_clearBlock(uint8_t x, uint8_t y, uint8_t w, uint8_t h)
Definition: ssd1306.c:502
void ssd1306_setFixedFont(const uint8_t *progmemFont)
Definition: ssd1306.c:393
uint8_t width
width in pixels
uint8_t ssd1306_printFixed2x(uint8_t xpos, uint8_t y, const char ch[], EFontStyle style)
Definition: ssd1306.c:177
uint8_t ssd1306_charF6x8_eol(uint8_t left, uint8_t y, const char ch[], EFontStyle style, uint8_t right)
Definition: ssd1306.c:378
void ssd1306_invertMode()
Definition: ssd1306.c:633
uint8_t x
draw position X on the screen
void ssd1306_setFont6x8(const uint8_t *progmemFont)
Definition: ssd1306.c:401
uint8_t ssd1306_displayWidth()
Definition: ssd1306.c:48
void ssd1306_drawSprite(SPRITE *sprite)
Definition: ssd1306.c:532
void ssd1306_eraseTrace(SPRITE *sprite)
Definition: ssd1306.c:584
void ssd1306_drawBuffer(uint8_t x, uint8_t y, uint8_t w, uint8_t h, const uint8_t *buf)
Definition: ssd1306.c:466
void ssd1306_eraseSprite(SPRITE *sprite)
Definition: ssd1306.c:560
void(* ssd1306_commandStart)()
uint8_t g_lcd_type
Definition: ssd1306.c:38
uint8_t s_displayHeight
Definition: ssd1306.c:36
void ssd1306_normalMode()
Definition: ssd1306.c:641
uint8_t w
sprite width
uint8_t ssd1306_charF12x16(uint8_t xpos, uint8_t y, const char ch[], EFontStyle style)
Definition: ssd1306.c:303
void(* ssd1306_setRamBlock)(uint8_t x, uint8_t y, uint8_t w)
void ssd1306_putPixel(uint8_t x, uint8_t y)
Definition: ssd1306.c:406
uint8_t lx
last draw position X on the screen
uint8_t ssd1306_printFixed(uint8_t xpos, uint8_t y, const char ch[], EFontStyle style)
Definition: ssd1306.c:104
const uint8_t * data
font chars bits
void(* ssd1306_sendPixels)(uint8_t data)
EFontStyle
uint8_t pages
height in pages
uint8_t s_displayWidth
Definition: ssd1306.c:37
uint8_t ssd1306_charF6x8(uint8_t x, uint8_t y, const char ch[], EFontStyle style)
Definition: ssd1306.c:257
uint8_t ly
last draw position Y on the screen