-
Notifications
You must be signed in to change notification settings - Fork 0
/
SH1122Oled.cpp
1763 lines (1511 loc) · 55.5 KB
/
SH1122Oled.cpp
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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#include "SH1122Oled.hpp"
sh1122_oled_cfg_t SH1122Oled::oled_cfg;
uint8_t SH1122Oled::frame_buffer[FRAME_BUFFER_LENGTH];
SH1122Oled::sh1122_oled_font_info_t SH1122Oled::font_info;
SH1122Oled::FontDirection SH1122Oled::font_dir = SH1122Oled::FontDirection::left_to_right;
/**
* @brief SH1122Oled constructor.
*
* Construct a SH1122Oled object for managing a SH1122Oled driven OLED display.
* Initializes required GPIO pins and SPI peripheral then sends out several commands
* to initialize SH1122. Commands can be seen in default_init().
*
* @param oled_cfg Configuration settings (optional), default settings can be seen in sh1122_oled_cfg_t and are selectable.
* @return void, nothing to return
*/
SH1122Oled::SH1122Oled(sh1122_oled_cfg_t settings)
{
oled_cfg = settings;
font_info.font = nullptr; //used to check if user has loaded font for string/glyph functions
// set-up data command pin and rst pin
gpio_config_t io_dc_rst_cs_cfg;
io_dc_rst_cs_cfg.pin_bit_mask = (1 << oled_cfg.io_rst) | (1 << oled_cfg.io_dc) | (1 << oled_cfg.io_cs);
io_dc_rst_cs_cfg.mode = GPIO_MODE_OUTPUT;
io_dc_rst_cs_cfg.pull_down_en = GPIO_PULLDOWN_DISABLE;
io_dc_rst_cs_cfg.pull_up_en = GPIO_PULLUP_DISABLE;
io_dc_rst_cs_cfg.intr_type = GPIO_INTR_DISABLE;
gpio_config(&io_dc_rst_cs_cfg);
gpio_set_level(oled_cfg.io_rst, 1); // put rst pin initially high
spi_bus_cfg.mosi_io_num = oled_cfg.io_mosi;
spi_bus_cfg.miso_io_num = GPIO_NUM_NC;
spi_bus_cfg.sclk_io_num = oled_cfg.io_sclk;
spi_bus_cfg.quadhd_io_num = GPIO_NUM_NC;
spi_bus_cfg.quadwp_io_num = GPIO_NUM_NC;
spi_bus_cfg.max_transfer_sz = FRAME_CHUNK_3_LENGTH;
spi_bus_cfg.flags = SPICOMMON_BUSFLAG_MASTER;
spi_bus_cfg.isr_cpu_id = ESP_INTR_CPU_AFFINITY_AUTO;
oled_interface_cfg.address_bits = 0;
oled_interface_cfg.dummy_bits = 0;
oled_interface_cfg.command_bits = 0;
oled_interface_cfg.mode = 0;
oled_interface_cfg.clock_source = SPI_CLK_SRC_DEFAULT;
oled_interface_cfg.clock_speed_hz = SPI_CLK_SPEED_HZ;
oled_interface_cfg.spics_io_num = GPIO_NUM_NC;
oled_interface_cfg.queue_size = SPI_INTERRUPT_MODE_QUEUE_SIZE;
oled_interface_cfg.pre_cb = NULL;
oled_interface_cfg.flags = SPI_DEVICE_3WIRE | SPI_DEVICE_HALFDUPLEX;
// initialize SPI peripheral
ESP_ERROR_CHECK(spi_bus_initialize(oled_cfg.spi_peripheral, &spi_bus_cfg, oled_cfg.dma_cha));
ESP_ERROR_CHECK(spi_bus_add_device(oled_cfg.spi_peripheral, &oled_interface_cfg, &spi_hdl));
default_init();
}
/**
* @brief Updates OLED display with current frame buffer.
*
* Sends frame buffer to SH1122 over SPI, should be called after performing draw operations.
*
* @return void, nothing to return
*/
void SH1122Oled::update_screen()
{
gpio_set_level(oled_cfg.io_cs, 0);
// update display in 3 chunks, SPI max DMA size is 4096 bytes at a time, but display buffer is 8192
send_data(frame_buffer, FRAME_CHUNK_1_LENGTH);
send_data(frame_buffer + (FRAME_CHUNK_1_LENGTH), FRAME_CHUNK_2_LENGTH);
send_data(frame_buffer + (FRAME_CHUNK_1_LENGTH + FRAME_CHUNK_2_LENGTH), FRAME_CHUNK_3_LENGTH);
gpio_set_level(oled_cfg.io_cs, 1);
}
/**
* @brief Sets respective pixel to specified grayscale intensity.
*
* @param x Pixel x location.
* @param y Pixel y location.
* @param intensity Grayscale intensity of the drawn pixel.
* @return void, nothing to return
*/
void SH1122Oled::set_pixel(uint16_t x, uint16_t y, PixelIntensity intensity)
{
int16_t x_it = 0;
int16_t y_it = 0;
int16_t high_byte = 0;
if (intensity != PixelIntensity::level_transparent)
if (SH1122_PIXEL_IN_BOUNDS(x, y))
{
if (x != 0)
{
x_it = x / 2;
high_byte = x % 2;
}
if (y != 0)
y_it = (y * WIDTH) / 2;
uint8_t* pixel = (frame_buffer + x_it + y_it);
if (high_byte == 1)
*pixel = ((uint8_t) intensity & 0x0F) | (*pixel & 0xF0);
else
*pixel = (((uint8_t) intensity << 4) & 0xF0) | (*pixel & 0x0F);
}
}
/**
* @brief Draws a line between two points.
*
* @param x_1 Line starting x location.
* @param y_1 Line starting y location.
* @param x_2 Line ending x location.
* @param y_2 Line ending y location.
* @param intensity Grayscale intensity of the drawn line.
* @return void, nothing to return
*/
void SH1122Oled::draw_line(int16_t x_1, int16_t y_1, int16_t x_2, int16_t y_2, PixelIntensity intensity)
{
const int16_t delta_x = abs(x_2 - x_1);
const int16_t delta_y = abs(y_2 - y_1);
const int16_t sign_x = x_1 < x_2 ? 1 : -1;
const int16_t sign_y = y_1 < y_2 ? 1 : -1;
int16_t error = delta_x - delta_y;
set_pixel(x_2, y_2, intensity);
while (x_1 != x_2 || y_1 != y_2)
{
set_pixel(x_1, y_1, intensity);
const int16_t error_2 = error * 2;
if (error_2 > -delta_y)
{
error -= delta_y;
x_1 += sign_x;
}
if (error_2 < delta_x)
{
error += delta_x;
y_1 += sign_y;
}
}
}
/**
* @brief Draws rectangular frame at the specified location.
*
* @param x Frame x location (upper left corner of frame)
* @param y Frame y location (upper left corner of frame)
* @param width Frame width.
* @param height Frame height.
* @param thickness Frame thickness (drawn towards center of rectangle)
* @param intensity Grayscale intensity of the drawn frame.
* @return void, nothing to return
*/
void SH1122Oled::draw_rectangle_frame(int16_t x_1, int16_t y_1, int16_t width, int16_t height, int16_t thickness, PixelIntensity intensity)
{
for (int i = 0; i < thickness; i++)
draw_line(x_1 + i, y_1 + thickness, x_1 + i, (y_1 + height - 1) - thickness, intensity);
for (int i = 0; i < thickness; i++)
draw_line((x_1 + width - 1) - i, y_1 + thickness, (x_1 + width - 1) - i, (y_1 + height - 1) - thickness, intensity);
for (int i = 0; i < thickness; i++)
draw_line(x_1, y_1 + i, (x_1 + width - 1), y_1 + i, intensity);
for (int i = 0; i < thickness; i++)
draw_line(x_1, (y_1 + height - 1) - i, (x_1 + width - 1), (y_1 + height - 1) - i, intensity);
}
/**
* @brief Draws a filled rectangle at the specified location.
*
* @param x Rectangle x location (upper left corner of rectangle)
* @param y Rectangle y location (upper left corner of rectangle)
* @param width Rectangle width.
* @param height Rectangle height.
* @param intensity Grayscale intensity of the drawn rectangle.
* @return void, nothing to return
*/
void SH1122Oled::draw_rectangle(int16_t x_1, int16_t y_1, int16_t width, int16_t height, PixelIntensity intensity)
{
for (uint16_t j = 0; j < height; j++)
{
for (uint16_t i = 0; i < width; i++)
set_pixel((x_1 + i), (y_1 + j), intensity);
}
}
/**
* @brief Draws a circular frame at the specified location.
*
* @param x_c Circle center x position.
* @param y_c Circle center y position.
* @param r Circle radius.
* @param thickness Frame thickness (drawn towards center of circle)
* @param intensity Grayscale intensity of the drawn circle.
* @return void, nothing to return
*/
void SH1122Oled::draw_circle_frame(int16_t x_c, int16_t y_c, int16_t r, int16_t thickness, PixelIntensity intensity)
{
draw_ellipse_frame(x_c, y_c, r, r, thickness, intensity);
}
/**
* @brief Draws a filled circle at the specified location.
*
* @param x_c Circle center x position.
* @param y_c Circle center y position.
* @param r Circle radius.
* @param intensity Grayscale intensity of the drawn circle.
* @return void, nothing to return
*/
void SH1122Oled::draw_circle(int16_t x_c, int16_t y_c, int16_t r, PixelIntensity intensity)
{
draw_circle_frame(x_c, y_c, r, r, intensity);
}
/**
* @brief Draws an ellipse frame at the specified location.
*
* @param x_c Ellipse center x position.
* @param y_c Ellipse center y position.
* @param r_x Horizontal radius (ellipse parameter a)
* @param r_y Vertical radius (ellipse parameter b)
* @param thickness Frame thickness (drawn towards center of ellipse)
* @param intensity Grayscale intensity of the drawn ellipse.
* @return void, nothing to return
*/
void SH1122Oled::draw_ellipse_frame(int16_t x_c, int16_t y_c, int16_t r_x, int16_t r_y, int16_t thickness, PixelIntensity intensity)
{
int32_t d_x;
int32_t d_y;
float d_1;
float d_2;
int16_t x = 0;
int16_t y = 0;
int32_t r_x_sq = 0;
int32_t r_y_sq = 0;
std::vector<std::vector<sh1122_2d_point_t>> quad_1_points(2); // element 0 is outter points, element 1 is inner points
std::vector<std::vector<sh1122_2d_point_t>> quad_2_points(2); // element 0 is outter points, element 1 is inner points
std::vector<std::vector<sh1122_2d_point_t>> quad_3_points(2); // element 0 is outter points, element 1 is inner points
std::vector<std::vector<sh1122_2d_point_t>> quad_4_points(2); // element 0 is outter points, element 1 is inner points
if (thickness <= 0 || thickness > r_y || thickness > r_x)
return;
if (r_y == thickness)
set_pixel(x_c, y_c, intensity);
else if (r_x == thickness)
{
draw_line(x_c, y_c - r_y, x_c, y_c + r_y, intensity);
}
for (int i = 0; (thickness > 1) ? (i < 2) : (i < 1); i++)
{
x = 0; // ellipse is centered about origin, assume (0, r_y) as first point and transform by x_c and y_c later
y = (r_y - i * (thickness - 1));
r_x_sq = (int32_t) (r_x - i * (thickness - 1)) * (r_x - i * (thickness - 1));
r_y_sq = (int32_t) (r_y - i * (thickness - 1)) * (r_y - i * (thickness - 1));
// calculate initial decision parameter for region 1 of quadrants, d_1_0 = r_y^2 + (1/4)*r_x^2 -r_x^2*r_y
d_1 = (float) r_y_sq + (0.25f * (float) r_x_sq) - (float) (r_x_sq * (r_y - i * (thickness - 1)));
// next decision parameter modifiers
d_x = 2 * r_y_sq * x; // if(d_1[k] < 0): d_1[k+1] = d_1[k] + 2 * r_y^2 * x[k + 1] + r_y^2
d_y = 2 * r_x_sq * y; // if(d_1[k] >= 0): d_1[k+1] = d_1[k] + 2 * r_y^2 * x[k + 1] - 2 * r_x^2 * y[k] + r_y^2
while (d_x < d_y)
{
// draw region 1 points
if (SH1122_PIXEL_IN_BOUNDS(x + x_c, y + y_c))
{
// quadrant 1
set_pixel(x + x_c, y + y_c, intensity);
quad_1_points[i].push_back((sh1122_2d_point_t){(int16_t) (x + x_c), (int16_t) (y + y_c)});
}
if (SH1122_PIXEL_IN_BOUNDS(-x + x_c, y + y_c))
{
// quadrant 2
set_pixel(-x + x_c, y + y_c, intensity);
quad_2_points[i].push_back((sh1122_2d_point_t){(int16_t) (-x + x_c), (int16_t) (y + y_c)});
}
if (SH1122_PIXEL_IN_BOUNDS(x + x_c, -y + y_c))
{
// quadrant 3
set_pixel(x + x_c, -y + y_c, intensity);
quad_3_points[i].push_back((sh1122_2d_point_t){(int16_t) (x + x_c), (int16_t) (-y + y_c)});
}
if (SH1122_PIXEL_IN_BOUNDS(-x + x_c, -y + y_c))
{
// quadrant 4
set_pixel(-x + x_c, -y + y_c, intensity);
quad_4_points[i].push_back((sh1122_2d_point_t){(int16_t) (-x + x_c), (int16_t) (-y + y_c)});
}
// find next point and next d_1 parameter
if (d_1 < 0) // if(d_1[k] < 0)
{
x++; // next point is (x[k+1], y[k])
d_x += 2 * r_y_sq;
d_1 += d_x + r_y_sq; // d_1[k+1] = d_1[k] + 2 * r_y^2 * x[k+1] + r_y^2
}
else // if(d_1[k] >= 0)
{
x++; // next point is (x[k+1], y[k-1])
y--;
d_x += 2 * r_y_sq;
d_y += -2 * r_x_sq;
d_1 += d_x - d_y + r_y_sq; // d_1[k+1] = d_1[k] + 2 * r_y^2 * x[k+1] - 2 * r_x^2 * y[k] + r_y^2
}
}
// calculate initial decision parameter of region 2 for quadrants d_2_0 = r_y^2 * (x_0 + 1/2)^2 + r_x^2 * (y_9 - 1)^2 - r_x^2 *r_y^2
d_2 = ((float) r_y_sq * ((float) x + 0.5f) * ((float) x + 0.5f)) + (float) (r_x_sq * (y - 1) * (y - 1)) - (float) (r_x_sq * r_y_sq);
while (y >= 0)
{
// draw region 2 points
if (SH1122_PIXEL_IN_BOUNDS(x + x_c, y + y_c))
{
// quadrant 1
set_pixel(x + x_c, y + y_c, intensity);
quad_1_points[i].push_back((sh1122_2d_point_t){(int16_t) (x + x_c), (int16_t) (y + y_c)});
}
if (SH1122_PIXEL_IN_BOUNDS(-x + x_c, y + y_c))
{
// quadrant 2
set_pixel(-x + x_c, y + y_c, intensity);
quad_2_points[i].push_back((sh1122_2d_point_t){(int16_t) (-x + x_c), (int16_t) (y + y_c)});
}
if (SH1122_PIXEL_IN_BOUNDS(x + x_c, -y + y_c))
{
// quadrant 3
set_pixel(x + x_c, -y + y_c, intensity);
quad_3_points[i].push_back((sh1122_2d_point_t){(int16_t) (x + x_c), (int16_t) (-y + y_c)});
}
if (SH1122_PIXEL_IN_BOUNDS(-x + x_c, -y + y_c))
{
// quadrant 4
set_pixel(-x + x_c, -y + y_c, intensity);
quad_4_points[i].push_back((sh1122_2d_point_t){(int16_t) (-x + x_c), (int16_t) (-y + y_c)});
}
// find next point and next d_2 parameter
if (d_2 > 0) // if(d_2[k] > 0)
{
y--; // next point is (x[k], y[k-1])
d_y += -2 * r_x_sq;
d_2 += r_x_sq - d_y; // d_2[k+1] = d_2[k] - 2 * r_x^2 * y[k+1] + r_x^2
}
else // if(d_2[k] <= 0)
{
// next point is (x[k+1], y[k-1])
y--;
x++;
d_x += 2 * r_y_sq;
d_y += -2 * r_x_sq;
d_2 += d_x - d_y + r_x_sq; // d_2[k+1] = d_2[k] + 2 * r_y^2 * x[k+1] - 2 * r_x^2 * y[k+1] + r_x^2
}
}
}
if (quad_1_points.at(0).size() != 0 && quad_1_points.at(1).size() != 0)
fill_ellipse_frame_quadrant(quad_1_points.at(0), quad_1_points.at(1), y_c, intensity);
if (quad_2_points.at(0).size() != 0 && quad_2_points.at(1).size() != 0)
fill_ellipse_frame_quadrant(quad_2_points.at(0), quad_2_points.at(1), y_c, intensity);
if (quad_3_points.at(0).size() != 0 && quad_3_points.at(1).size() != 0)
fill_ellipse_frame_quadrant(quad_3_points.at(0), quad_3_points.at(1), y_c, intensity);
if (quad_4_points.at(0).size() != 0 && quad_4_points.at(1).size() != 0)
fill_ellipse_frame_quadrant(quad_4_points.at(0), quad_4_points.at(1), y_c, intensity);
}
/**
* @brief Draws a filled ellipse at the specified location.
*
* @param x_c Ellipse center x position.
* @param y_c Ellipse center y position.
* @param r_x Horizontal radius (ellipse parameter a)
* @param r_y Vertical radius (ellipse parameter b)
* @param intensity Grayscale intensity of the drawn ellipse.
* @return void, nothing to return
*/
void SH1122Oled::draw_ellipse(int16_t x_c, int16_t y_c, int16_t r_x, int16_t r_y, PixelIntensity intensity)
{
int16_t thickness = std::min(r_x, r_y);
draw_ellipse_frame(x_c, y_c, r_x, r_y, thickness, intensity);
}
/**
* @brief Draws the selected glyph/character using the currently loaded font.
*
* @param x Glyph x location (upper left corner of glyph)
* @param y Glyph y location (upper left corner of glyph)
* @param intensity Grayscale intensity of the drawn glyph
* @param encoding The encoding of the character to be drawn, supports UTF-8 and UTF-16.
* @return The change in x required to draw the next glyph in string without overlapping.
*/
uint16_t SH1122Oled::draw_glyph(uint16_t x, uint16_t y, PixelIntensity intensity, uint16_t encoding)
{
const uint8_t* glyph_ptr = NULL;
sh1122_oled_font_decode_t decode;
uint16_t dx = 0;
// must load font before attempting to write glyphs
if (font_info.font == nullptr)
{
ESP_LOGE(TAG, "No font loaded.");
return 0;
}
// set up the decode structure
decode.target_x = x;
switch (font_dir)
{
case FontDirection::left_to_right:
y += font_info.ascent_A;
break;
case FontDirection::top_to_bottom:
break;
case FontDirection::right_to_left:
break;
case FontDirection::bottom_to_top:
decode.target_x += font_info.ascent_A;
break;
}
decode.target_y = y;
decode.fg_intensity = (uint8_t) intensity;
glyph_ptr = NULL;
if (encoding != 0x0ffff)
{
glyph_ptr = font_get_glyph_data(encoding); // get glyph data from lookup table
if (glyph_ptr != NULL)
{
font_setup_glyph_decode(&decode, glyph_ptr); // setup decode structure with important values from table
dx = font_decode_and_draw_glyph(&decode, glyph_ptr); // decode and draw the glyph
}
}
return dx;
}
/**
* @brief Draws a string at the specified location using the currently loaded font.
*
* @param x String x location (upper left corner of string)
* @param y String y location (upper left corner of string)
* @param intensity Grayscale intensity of the drawn string
* @param format The string to be drawn, supports variable arguments (ie printf style formatting)
* @return The width of the drawn string.
*/
uint16_t SH1122Oled::draw_string(uint16_t x, uint16_t y, PixelIntensity intensity, const char* format, ...)
{
uint16_t delta = 0;
uint16_t encoding = 0;
uint16_t sum = 0;
char* buffer = nullptr;
uint8_t* str = nullptr;
va_list args;
uint16_t size;
// must load font before attempting to write
if (font_info.font == nullptr)
{
ESP_LOGE(TAG, "No font loaded.");
return 0;
}
va_start(args, format);
size = vsnprintf(nullptr, 0, format, args) + 1;
va_end(args);
buffer = new char[size];
if (!buffer)
return 0;
va_start(args, format);
vsnprintf(buffer, size, format, args);
va_end(args);
str = (uint8_t*) buffer;
while (1)
{
encoding = get_ascii_next(*str); // check to ensure character is not null or new line (end of string)
if (encoding == 0x0ffff)
break;
if (encoding != 0x0fffe)
{
delta = draw_glyph(x, y, intensity, encoding);
switch (font_dir)
{
case FontDirection::left_to_right:
x += delta;
break;
case FontDirection::top_to_bottom:
y += delta;
break;
case FontDirection::right_to_left:
x -= delta;
break;
case FontDirection::bottom_to_top:
y -= delta;
break;
}
sum += delta;
}
str++;
}
delete[] buffer;
return sum;
}
/**
* @brief Draws a sh1122 custom run-length-encoded bitmap created with sh1122_encode_bitmap.py.
*
* @param x Bitmap x location (upper left corner of bitmap)
* @param y Bitmap y location (upper left corner of bitmap)
* @param bg_intensity Background intensity (optional, default transparent), fills transparent pixels with bg_intensity if used
* @return void, nothing to return
*/
void SH1122Oled::draw_bitmap(uint16_t x, uint16_t y, const uint8_t* bitmap, PixelIntensity bg_intensity)
{
const int16_t bitmap_col_sz = *(bitmap + 2);
const int16_t bitmap_row_sz = *(bitmap + 3);
const uint8_t* data_ptr = bitmap + 4;
int16_t repeated_value_lim = 0;
PixelIntensity intensity = PixelIntensity::level_transparent;
int16_t repeated_value_count = 0;
bitmap_decode_pixel_block(&data_ptr, repeated_value_lim, intensity);
for (int row = 0; row < bitmap_row_sz; row++)
{
for (int col = 0; col < bitmap_col_sz; col++)
{
if (intensity == PixelIntensity::level_transparent)
intensity = bg_intensity;
set_pixel(x + col, y + row, intensity);
repeated_value_count++;
if (repeated_value_count >= repeated_value_lim)
{
bitmap_decode_pixel_block(&data_ptr, repeated_value_lim, intensity);
repeated_value_count = 0;
}
}
}
}
/**
* @brief Returns the width of specified glyph using the currently loaded font.
*
* @param encoding The encoding of the character for which width is desired, supports UTF-8 and UTF-16.
* @return The width of the specified glyph.
*/
uint16_t SH1122Oled::font_get_glyph_width(uint16_t encoding)
{
const uint8_t* glyph_data;
sh1122_oled_font_decode_t decode;
if (font_info.font == nullptr)
{
ESP_LOGE(TAG, "No font loaded.");
return 0;
}
glyph_data = font_get_glyph_data(encoding);
if (glyph_data == NULL)
return 0;
font_setup_glyph_decode(&decode, glyph_data);
font_decode_get_signed_bits(&decode, font_info.bits_per_char_x);
font_decode_get_signed_bits(&decode, font_info.bits_per_char_y);
return font_decode_get_signed_bits(&decode, font_info.bits_per_delta_x);
}
/**
* @brief Returns the width of specified glyph using the currently loaded font. Overloaded with decode structure for calls to font_get_string_width()
*
* @param decode The decode structure to save the glyph x offset in, for use within get_string_width()
* @param encoding The encoding of the character for which width is desired, supports UTF-8 and UTF-16.
* @return The width of the specified glyph.
*/
uint16_t SH1122Oled::font_get_glyph_width(sh1122_oled_font_decode_t* decode, uint16_t encoding)
{
const uint8_t* glyph_data;
if (font_info.font == nullptr)
{
ESP_LOGE(TAG, "No font loaded.");
return 0;
}
glyph_data = font_get_glyph_data(encoding);
if (glyph_data == NULL)
return 0;
font_setup_glyph_decode(decode, glyph_data);
decode->glyph_x_offset = font_decode_get_signed_bits(decode, font_info.bits_per_char_x);
font_decode_get_signed_bits(decode, font_info.bits_per_char_y);
return font_decode_get_signed_bits(decode, font_info.bits_per_delta_x);
}
/**
* @brief Returns the height of specified glyph using the currently loaded font.
*
* @param encoding The encoding of the character for which height is desired, supports UTF-8 and UTF-16.
* @return The height of the specified glyph.
*/
uint16_t SH1122Oled::font_get_glyph_height(uint16_t encoding)
{
const uint8_t* glyph_data;
sh1122_oled_font_decode_t decode;
if (font_info.font == nullptr)
{
ESP_LOGE(TAG, "No font loaded.");
return 0;
}
glyph_data = font_get_glyph_data(encoding);
if (glyph_data == NULL)
return 0;
font_setup_glyph_decode(&decode, glyph_data);
return decode.glyph_height;
return 0;
}
/**
* @brief Returns the width of specified string using the currently loaded font.
*
* @param format The string for which width is desired, supports variable arguments (ie printf style formatting)
* @return The width of the specified string.
*/
uint16_t SH1122Oled::font_get_string_width(const char* format, ...)
{
uint16_t encoding;
uint16_t width;
uint16_t dx;
int8_t initial_x_offset = -64;
sh1122_oled_font_decode_t decode;
char* buffer = nullptr;
uint8_t* str = nullptr;
va_list args;
uint16_t size;
// cannot get string width without font info
if (font_info.font == nullptr)
{
ESP_LOGE(TAG, "No font loaded.");
return 0;
}
// find length of variable argument string
va_start(args, format);
size = vsnprintf(nullptr, 0, format, args) + 1;
va_end(args);
buffer = new char[size]; // allocate correct amount of memory for string
if (!buffer)
return 0;
va_start(args, format);
vsnprintf(buffer, size, format, args); // save variable argument string to buffer
va_end(args);
str = (uint8_t*) buffer; // cast char * to uint8_t for use with other member methods
width = 0;
dx = 0;
while (1)
{
encoding = get_ascii_next(*str); // get next character
if (encoding == 0x0ffff)
break;
if (encoding != 0x0fffe)
{
dx = font_get_glyph_width(&decode, encoding); // get the glyph width
if (initial_x_offset == -64)
initial_x_offset = decode.glyph_x_offset;
width += dx; // increment width counter
}
str++; // increment string pointer to next glyph
}
// if glyph_width is greater than 0, apply the respective glyph x offset.
if (decode.glyph_width != 0)
{
width -= dx;
width += decode.glyph_width;
width += decode.glyph_x_offset;
if (initial_x_offset > 0)
width += initial_x_offset;
}
delete[] buffer; // free allocated buffer memory
return width;
}
/**
* @brief Returns the height (tallest character height) of specified string using the currently loaded font.
*
* @param format The string for which height is desired, supports variable arguments (ie printf style formatting)
* @return The width of the specified string.
*/
uint16_t SH1122Oled::font_get_string_height(const char* format, ...)
{
char* buffer = nullptr;
uint8_t* str = nullptr;
va_list args;
uint16_t size;
uint16_t current_height = 0;
uint16_t max_height = 0;
// cannot get string height without font info
if (font_info.font == nullptr)
{
ESP_LOGE(TAG, "No font loaded.");
return 0;
}
// allocate correct amount of memory and save string from variable argument list
va_start(args, format);
size = vsnprintf(nullptr, 0, format, args) + 1;
va_end(args);
buffer = new char[size];
if (!buffer)
return 0;
va_start(args, format);
vsnprintf(buffer, size, format, args);
va_end(args);
str = (uint8_t*) buffer;
// so long as the current glyph is not NULL (EOL) get its height and increment string pointer to next glyph
while (*str != '\0')
{
current_height = font_get_glyph_height(*str);
// if the current height is greater than the largest height detected
if (current_height > max_height)
max_height = current_height; // overwrite max_height with tallest character height detected
str++;
}
delete[] buffer; // free memory allocated for string
return max_height;
}
/**
* @brief Returns the x position required to horizontally center a given string.
*
* @param str The string for which a horizontal centering is desired.
* @return The x position the string should be drawn at to center it horizontally.
*/
uint16_t SH1122Oled::font_get_string_center_x(const char* str)
{
uint16_t str_width;
if (font_info.font == nullptr)
{
ESP_LOGE(TAG, "No font loaded.");
return 0;
}
str_width = font_get_string_width(str);
return (WIDTH - str_width) / 2;
}
/**
* @brief Returns the y position required to vertically center a given string.
*
* @param str The string for which a vertical centering is desired.
* @return The y position the string should be drawn at to center it vertically.
*/
uint16_t SH1122Oled::font_get_string_center_y(const char* str)
{
uint16_t max_char_height;
if (font_info.font == nullptr)
{
ESP_LOGE(TAG, "No font loaded.");
return 0;
}
max_char_height = font_get_string_height(str);
return (HEIGHT - max_char_height) / 2;
}
/**
* @brief Sends frame buffer to sh1122_screenshot.py to create a screen shot.
*
* @return void, nothing to return
*/
void SH1122Oled::take_screen_shot()
{
const constexpr int CHUNK_BUFFER_LENGTH = 512;
int index = 0;
char chunk_buffer[CHUNK_BUFFER_LENGTH];
uint16_t encoded_val = 0;
printf("SCREENSHOT_S\n\r");
printf("TIMESTAMP %lld\n\r", esp_timer_get_time());
for (int i = 0; i < FRAME_BUFFER_LENGTH; i += 2)
{
encoded_val = (((uint16_t) frame_buffer[i + 1] << 8) & 0xFF00) | ((uint16_t) frame_buffer[i] & 0x00FF);
index += snprintf(chunk_buffer + index, CHUNK_BUFFER_LENGTH - index, " %d,", encoded_val);
if (index > CHUNK_BUFFER_LENGTH - 50)
{
printf(" %s \n\r", chunk_buffer);
index = 0;
}
else if (i == FRAME_BUFFER_LENGTH - 2)
printf(" %s \n\r", chunk_buffer);
}
printf("SCREENSHOT_P\n\r");
}
/**
* @brief Loads a font for drawing strings and glyphs.
*
* @param font A pointer to the first element of the respective font lookup table, font tables are located in fonts directory.
* @return void, nothing to return
*/
void SH1122Oled::load_font(const uint8_t* font)
{
font_info.font = font;
font_info.glyph_cnt = font_lookup_table_read_char(font, 0);
font_info.bbx_mode = font_lookup_table_read_char(font, 1);
font_info.bits_per_0 = font_lookup_table_read_char(font, 2);
font_info.bits_per_1 = font_lookup_table_read_char(font, 3);
font_info.bits_per_char_width = font_lookup_table_read_char(font, 4);
font_info.bits_per_char_height = font_lookup_table_read_char(font, 5);
font_info.bits_per_char_x = font_lookup_table_read_char(font, 6);
font_info.bits_per_char_y = font_lookup_table_read_char(font, 7);
font_info.bits_per_delta_x = font_lookup_table_read_char(font, 8);
font_info.max_char_width = font_lookup_table_read_char(font, 9);
font_info.max_char_height = font_lookup_table_read_char(font, 10);
font_info.x_offset = font_lookup_table_read_char(font, 11);
font_info.y_offset = font_lookup_table_read_char(font, 12);
font_info.ascent_A = font_lookup_table_read_char(font, 13); // capital a usually the highest pixels of any characters
font_info.descent_g = font_lookup_table_read_char(font, 14); // lower case usually has the lowest pixels of any characters
font_info.ascent_para = font_lookup_table_read_char(font, 15);
font_info.descent_para = font_lookup_table_read_char(font, 16);
font_info.start_pos_upper_A = font_lookup_table_read_word(font, 17);
font_info.start_pos_lower_a = font_lookup_table_read_word(font, 19);
font_info.start_pos_unicode = font_lookup_table_read_word(font, 21);
}
/**
* @brief Sets the draw direction for strings and glyphs, default is left to right.
*
* @param dir The direction strings and glyphs should be drawn in, see FontDirection definition.
* @return void, nothing to return
*/
void SH1122Oled::set_font_direction(FontDirection dir)
{
font_dir = dir;
}
/**
* @brief Clears the buffer containing the pixel data sent to SH1122.
*
* @return void, nothing to return
*/
void SH1122Oled::clear_buffer()
{
memset(frame_buffer, 0, sizeof(frame_buffer));
}
/**
* @brief Hard resets the SH1122 using the RST pin.
*
* @return void, nothing to return
*/
void SH1122Oled::reset()
{
gpio_set_level(oled_cfg.io_rst, 0); // bring oled into reset (rst low)
vTaskDelay(200 / portTICK_PERIOD_MS); // wait 200ms
gpio_set_level(oled_cfg.io_rst, 1); // bring oled out of rest (rst high)
vTaskDelay(200 / portTICK_PERIOD_MS); // wait 200ms to boot
}
/**
* @brief Sends power off command.
*
* @return void, nothing to return
*/
void SH1122Oled::power_off()
{
uint8_t cmd = OLED_CMD_POWER_OFF;
send_commands(&cmd, 1);
}
/**
* @brief Sends power on command.
*
* @return void, nothing to return
*/
void SH1122Oled::power_on()
{
uint8_t cmd = OLED_CMD_POWER_ON;
send_commands(&cmd, 1);
}
/**
* @brief Sets the contrast of the display. Display must be powered off before calling.
*
* @param contrast_reg_val The desired contrast, SH1122 has 256 contrast steps from 0x00 to 0xFF.
* @return void, nothing to return
*/
void SH1122Oled::set_contrast(uint8_t contrast_reg_val)
{
uint8_t cmds[2] = {OLED_CMD_SET_DISP_CONTRAST, contrast_reg_val};
send_commands(cmds, 2);
}
/**
* @brief Sets the multiplex ratio of the display. Display must be powered off before calling.
*
* @param multiplex_ratio_reg_val Desired multiplex ratio step, from 1 to 64
* @return void, nothing to return
*/
void SH1122Oled::set_multiplex_ratio(uint8_t multiplex_ratio_reg_val)
{
uint8_t cmds[2] = {OLED_CMD_SET_MULTIPLEX_RATION, multiplex_ratio_reg_val};
send_commands(cmds, 2);
}
/**
* @brief Sets the DC-DC voltage converter status and switch frequency. Display must be powered off before calling.
*
* @param mod DC-DC register value, see section 12 of commands in SH1122 datasheet.
* @return void, nothing to return
*/
void SH1122Oled::set_dc_dc_control_mod(uint8_t mod)
{