-
-
Notifications
You must be signed in to change notification settings - Fork 14
/
de3.html
736 lines (683 loc) · 43.5 KB
/
de3.html
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="generator" content="rustdoc">
<title>NuttX RTOS for PinePhone: Display Engine</title>
<!-- Begin scripts/articles/*-header.html: Article Header for Custom Markdown files processed by rustdoc, like chip8.md -->
<meta property="og:title"
content="NuttX RTOS for PinePhone: Display Engine"
data-rh="true">
<meta property="og:description"
content="Apache NuttX Kernel now supports Allwinner A64 Display Engine on Pine64 PinePhone! Here's how we call it to render graphics on PinePhone's LCD Display"
data-rh="true">
<meta name="description"
content="Apache NuttX Kernel now supports Allwinner A64 Display Engine on Pine64 PinePhone! Here's how we call it to render graphics on PinePhone's LCD Display">
<meta property="og:image"
content="https://lupyuen.github.io/images/de3-title.jpg">
<meta property="og:type"
content="article" data-rh="true">
<link rel="canonical" href="https://lupyuen.org/articles/de3.html" />
<!-- End scripts/articles/*-header.html -->
<!-- Begin scripts/rustdoc-header.html: Header for Custom Markdown files processed by rustdoc, like chip8.md -->
<link rel="alternate" type="application/rss+xml" title="RSS Feed for lupyuen" href="/rss.xml" />
<link rel="stylesheet" type="text/css" href="../normalize.css">
<link rel="stylesheet" type="text/css" href="../rustdoc.css" id="mainThemeStyle">
<link rel="stylesheet" type="text/css" href="../dark.css">
<link rel="stylesheet" type="text/css" href="../light.css" id="themeStyle">
<link rel="stylesheet" type="text/css" href="../prism.css">
<script src="../storage.js"></script><noscript>
<link rel="stylesheet" href="../noscript.css"></noscript>
<link rel="shortcut icon" href="../favicon.ico">
<style type="text/css">
#crate-search {
background-image: url("../down-arrow.svg");
}
</style>
<!-- End scripts/rustdoc-header.html -->
</head>
<body class="rustdoc">
<!--[if lte IE 8]>
<div class="warning">
This old browser is unsupported and will most likely display funky
things.
</div>
<![endif]-->
<!-- Begin scripts/rustdoc-before.html: Pre-HTML for Custom Markdown files processed by rustdoc, like chip8.md -->
<!-- Begin Theme Picker -->
<div class="theme-picker" style="left: 0"><button id="theme-picker" aria-label="Pick another theme!"><img src="../brush.svg"
width="18" alt="Pick another theme!"></button>
<div id="theme-choices"></div>
</div>
<!-- Theme Picker -->
<!-- End scripts/rustdoc-before.html -->
<h1 class="title">NuttX RTOS for PinePhone: Display Engine</h1>
<nav id="rustdoc"><ul>
<li><a href="#allwinner-a64-display-engine" title="Allwinner A64 Display Engine">1 Allwinner A64 Display Engine</a><ul></ul></li>
<li><a href="#ui-channels" title="UI Channels">2 UI Channels</a><ul></ul></li>
<li><a href="#nuttx-framebuffer" title="NuttX Framebuffer">3 NuttX Framebuffer</a><ul></ul></li>
<li><a href="#render-framebuffers" title="Render Framebuffers">4 Render Framebuffers</a><ul>
<li><a href="#initialise-display-engine" title="Initialise Display Engine">4.1 Initialise Display Engine</a><ul></ul></li>
<li><a href="#initialise-ui-blender" title="Initialise UI Blender">4.2 Initialise UI Blender</a><ul></ul></li>
<li><a href="#initialise-ui-channels" title="Initialise UI Channels">4.3 Initialise UI Channels</a><ul></ul></li>
<li><a href="#enable-display-engine" title="Enable Display Engine">4.4 Enable Display Engine</a><ul></ul></li></ul></li>
<li><a href="#test-pattern" title="Test Pattern">5 Test Pattern</a><ul></ul></li>
<li><a href="#complete-display-driver" title="Complete Display Driver">6 Complete Display Driver</a><ul></ul></li>
<li><a href="#upcoming-drivers" title="Upcoming Drivers">7 Upcoming Drivers</a><ul></ul></li>
<li><a href="#whats-next" title="What’s Next">8 What’s Next</a><ul></ul></li>
<li><a href="#appendix-calibrate-nuttx-delay" title="Appendix: Calibrate NuttX Delay">9 Appendix: Calibrate NuttX Delay</a><ul></ul></li></ul></nav><p>📝 <em>23 Dec 2022</em></p>
<p><img src="https://lupyuen.github.io/images/de3-title.jpg" alt="Rendering graphics on PinePhone with Apache NuttX RTOS" /></p>
<p><a href="https://nuttx.apache.org/docs/latest/"><strong>Apache NuttX RTOS</strong></a> for <a href="https://wiki.pine64.org/index.php/PinePhone"><strong>Pine64 PinePhone</strong></a> (pic above) now supports <a href="https://lupyuen.github.io/articles/de"><strong>Allwinner A64 Display Engine</strong></a>!</p>
<p>We’re one step closer to completing our <a href="https://lupyuen.github.io/articles/dsi3#complete-display-driver-for-pinephone"><strong>NuttX Display Driver</strong></a> for PinePhone.</p>
<p>Let’s find out how our NuttX Display Driver will call A64 Display Engine to <strong>render graphics on PinePhone’s LCD Display</strong>…</p>
<p><img src="https://lupyuen.github.io/images/dsi3-steps.jpg" alt="Complete Display Driver for PinePhone" /></p>
<p><a href="https://lupyuen.github.io/articles/dsi3#complete-display-driver-for-pinephone"><em>Complete Display Driver for PinePhone</em></a></p>
<h1 id="allwinner-a64-display-engine"><a class="doc-anchor" href="#allwinner-a64-display-engine">§</a>1 Allwinner A64 Display Engine</h1>
<p>Inside PinePhone’s Allwinner A64 SoC (pic above) is the <strong>A64 Display Engine</strong> that…</p>
<ul>
<li>
<p>Pulls pixels from <strong>Multiple Framebuffers</strong> in RAM</p>
<p>(Up to 3 Framebuffers)</p>
</li>
<li>
<p><strong>Blends the pixels</strong> into a single image</p>
<p>(720 x 1440 for PinePhone)</p>
</li>
<li>
<p>Pushes the image to the <strong>A64 Timing Controller TCON0</strong></p>
<p>(Connected via MIPI Display Serial Interface to LCD Display)</p>
</li>
<li>
<p>Does all this automatically in Hardware via <strong>Direct Memory Access</strong> (DMA)</p>
<p>(No interrupts needed)</p>
</li>
</ul>
<p>Previously we talked about the A64 Display Engine and coding it with Zig…</p>
<ul>
<li>
<p><a href="https://lupyuen.github.io/articles/de"><strong>“Rendering PinePhone’s Display (DE and TCON0)”</strong></a></p>
</li>
<li>
<p><a href="https://lupyuen.github.io/articles/de2"><strong>“NuttX RTOS for PinePhone: Render Graphics in Zig”</strong></a></p>
</li>
</ul>
<p>Today we’ll program it with the <a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/a64/a64_de.c"><strong>NuttX Kernel Driver</strong></a> for the Display Engine.</p>
<p><img src="https://lupyuen.github.io/images/de2-overlay.jpg" alt="3 Framebuffers for 3 UI Channels" /></p>
<h1 id="ui-channels"><a class="doc-anchor" href="#ui-channels">§</a>2 UI Channels</h1>
<p>A64 Display Engine supports up to <strong>3 Framebuffers</strong> in RAM (pic above). Each pixel has <strong>32-bit ARGB 8888</strong> format.</p>
<p>The Display Engine renders the 3 Framebuffer as <strong>3 UI Channels</strong>, blended together into the displayed image…</p>
<p><img src="https://lupyuen.github.io/images/de2-blender.jpg" alt="Blending the UI Channels" /></p>
<p>Let’s start with the <strong>3 Framebuffers</strong>: <a href="https://github.com/lupyuen/pinephone-nuttx/blob/main/test/test_a64_de.c#L5-L89">test_a64_de.c</a></p>
<ul>
<li>
<p><strong>Framebuffer 0</strong> (UI Channel 1) is a 720 x 1440 Fullscreen Framebuffer (pic below)…</p>
<div class="example-wrap"><pre class="language-c"><code>// PinePhone LCD Panel Width and Height (pixels)
#define PANEL_WIDTH 720
#define PANEL_HEIGHT 1440
// Framebuffer 0: (Base UI Channel)
// Fullscreen 720 x 1440 (4 bytes per XRGB 8888 pixel)
static uint32_t fb0[PANEL_WIDTH * PANEL_HEIGHT];</code></pre></div>
<p>Later we’ll fill Framebuffer 0 with <strong>Blue, Green and Red</strong> blocks.</p>
</li>
<li>
<p><strong>Framebuffer 1</strong> (UI Channel 2) is a 600 x 600 Square…</p>
<div class="example-wrap"><pre class="language-c"><code>// Framebuffer 1: (First Overlay UI Channel)
// Square 600 x 600 (4 bytes per ARGB 8888 pixel)
#define FB1_WIDTH 600
#define FB1_HEIGHT 600
static uint32_t fb1[FB1_WIDTH * FB1_HEIGHT];</code></pre></div>
<p>We’ll fill it with <strong>Semi-Transparent White</strong> later.</p>
</li>
<li>
<p><strong>Framebuffer 2</strong> (UI Channel 3) is also a Fullscreen Framebuffer…</p>
<div class="example-wrap"><pre class="language-c"><code>// Framebuffer 2: (Second Overlay UI Channel)
// Fullscreen 720 x 1440 (4 bytes per ARGB 8888 pixel)
static uint32_t fb2[PANEL_WIDTH * PANEL_HEIGHT];</code></pre></div>
<p>We’ll fill it with a <strong>Semi-Transparent Green Circle</strong>.</p>
</li>
</ul>
<p>Let’s wrap the 3 Framebuffers (<strong>fb0</strong>, <strong>fb1</strong> and <strong>fb2</strong>) with the NuttX Framebuffer Interface…</p>
<p><img src="https://lupyuen.github.io/images/de2-fb.jpg" alt="PinePhone Framebuffer" /></p>
<h1 id="nuttx-framebuffer"><a class="doc-anchor" href="#nuttx-framebuffer">§</a>3 NuttX Framebuffer</h1>
<p>NuttX expects our PinePhone Display Driver to provide a <a href="https://nuttx.apache.org/docs/latest/components/drivers/special/framebuffer.html"><strong>Framebuffer Interface</strong></a> for rendering graphics.</p>
<p>Let’s define the <strong>NuttX Framebuffer</strong>: <a href="https://github.com/lupyuen/pinephone-nuttx/blob/main/test/test_a64_de.c#L5-L89">test_a64_de.c</a></p>
<div class="example-wrap"><pre class="language-c"><code>// TODO: Run `make menuconfig`
// Select "System Type > Allwinner A64 Peripheral Selection > DE"
// Select "System Type > Allwinner A64 Peripheral Selection > RSB"
// Select "Build Setup > Debug Options > Graphics Debug Features > Error + Warnings + Info"
// Select "Build Setup > Debug Options > Battery-related Debug Features > Error + Warnings + Info"
// Select "Device Drivers > Framebuffer Overlay Support"
// Save config and exit menuconfig
// NuttX Framebuffer Interface
#include <nuttx/video/fb.h>
// 3 UI Channels: 1 Base Channel + 2 Overlay Channels
#define CHANNELS 3
// NuttX Video Controller for PinePhone (3 UI Channels)
static struct fb_videoinfo_s videoInfo = {
.fmt = FB_FMT_RGBA32, // Pixel format (XRGB 8888)
.xres = PANEL_WIDTH, // Horizontal resolution in pixel columns
.yres = PANEL_HEIGHT, // Vertical resolution in pixel rows
.nplanes = 1, // Number of color planes supported (Base UI Channel)
.noverlays = 2 // Number of overlays supported (2 Overlay UI Channels)
};</code></pre></div>
<p>The <a href="https://github.com/apache/nuttx/blob/master/include/nuttx/video/fb.h#L472-L487"><strong>fb_videoinfo_s</strong></a> struct defines the overall PinePhone Display Interface…</p>
<ul>
<li>720 x 1440 resolution</li>
<li>32-bit ARGB 8888 pixels</li>
<li>1 Base UI Channel (Framebuffer 0)</li>
<li>2 Overlay UI Channels (Framebuffers 1 and 2)</li>
</ul>
<p>This is how we define <strong>Framebuffer 0 (UI Channel 1)</strong>: <a href="https://github.com/lupyuen/pinephone-nuttx/blob/main/test/test_a64_de.c#L5-L89">test_a64_de.c</a></p>
<div class="example-wrap"><pre class="language-c"><code>// NuttX Color Plane for PinePhone (Base UI Channel):
// Fullscreen 720 x 1440 (4 bytes per XRGB 8888 pixel)
static struct fb_planeinfo_s planeInfo = {
.fbmem = &fb0, // Start of frame buffer memory
.fblen = sizeof(fb0), // Length of frame buffer memory in bytes
.stride = PANEL_WIDTH * 4, // Length of a line in bytes (4 bytes per pixel)
.display = 0, // Display number (Unused)
.bpp = 32, // Bits per pixel (XRGB 8888)
.xres_virtual = PANEL_WIDTH, // Virtual Horizontal resolution in pixel columns
.yres_virtual = PANEL_HEIGHT, // Virtual Vertical resolution in pixel rows
.xoffset = 0, // Offset from virtual to visible resolution
.yoffset = 0 // Offset from virtual to visible resolution
};</code></pre></div>
<p><a href="https://github.com/apache/nuttx/blob/master/include/nuttx/video/fb.h#L488-L504">(<strong>fb_planeinfo_s</strong> is defined here)</a></p>
<p>And <strong>Framebuffers 1 and 2</strong> (UI Channels 2 and 3): <a href="https://github.com/lupyuen/pinephone-nuttx/blob/main/test/test_a64_de.c#L5-L89">test_a64_de.c</a></p>
<div class="example-wrap"><pre class="language-c"><code>/// NuttX Overlays for PinePhone (2 Overlay UI Channels)
static struct fb_overlayinfo_s overlayInfo[2] = {
// First Overlay UI Channel:
// Square 600 x 600 (4 bytes per ARGB 8888 pixel)
{
.fbmem = &fb1, // Start of frame buffer memory
.fblen = sizeof(fb1), // Length of frame buffer memory in bytes
.stride = FB1_WIDTH * 4, // Length of a line in bytes
.overlay = 0, // Overlay number (First Overlay)
.bpp = 32, // Bits per pixel (ARGB 8888)
.blank = 0, // TODO: Blank or unblank
.chromakey = 0, // TODO: Chroma key argb8888 formatted
.color = 0, // TODO: Color argb8888 formatted
.transp = { .transp = 0, .transp_mode = 0 }, // TODO: Transparency
.sarea = { .x = 52, .y = 52, .w = FB1_WIDTH, .h = FB1_HEIGHT }, // Selected area within the overlay
.accl = 0 // TODO: Supported hardware acceleration
},
// Second Overlay UI Channel:
// Fullscreen 720 x 1440 (4 bytes per ARGB 8888 pixel)
{
.fbmem = &fb2, // Start of frame buffer memory
.fblen = sizeof(fb2), // Length of frame buffer memory in bytes
.stride = PANEL_WIDTH * 4, // Length of a line in bytes
.overlay = 1, // Overlay number (Second Overlay)
.bpp = 32, // Bits per pixel (ARGB 8888)
.blank = 0, // TODO: Blank or unblank
.chromakey = 0, // TODO: Chroma key argb8888 formatted
.color = 0, // TODO: Color argb8888 formatted
.transp = { .transp = 0, .transp_mode = 0 }, // TODO: Transparency
.sarea = { .x = 0, .y = 0, .w = PANEL_WIDTH, .h = PANEL_HEIGHT }, // Selected area within the overlay
.accl = 0 // TODO: Supported hardware acceleration
},
};</code></pre></div>
<p><a href="https://github.com/apache/nuttx/blob/master/include/nuttx/video/fb.h#L524-L540">(<strong>fb_overlayinfo_s</strong> is defined here)</a></p>
<p><em>What’s sarea?</em></p>
<div class="example-wrap"><pre class="language-c"><code>.sarea = {
.x = 52,
.y = 52,
.w = FB1_WIDTH, // Width is 600
.h = FB1_HEIGHT // Height is 600
}</code></pre></div>
<p>Remember that Framebuffer 1 is <strong>600 pixels</strong> wide… But the PinePhone Screen is <strong>720 pixels</strong> wide.</p>
<p>We use <strong>sarea</strong> to specify that Framebuffer 1 will be rendered <strong>52 pixels</strong> from the left (X Offset), <strong>52 pixels</strong> from the top (Y Offset).</p>
<p>(So it will be centered horizontally)</p>
<h1 id="render-framebuffers"><a class="doc-anchor" href="#render-framebuffers">§</a>4 Render Framebuffers</h1>
<p>We’ve defined the NuttX Framebuffers… Let’s <strong>render them with the Display Engine</strong>!</p>
<p>We’ll walk through the steps…</p>
<ol>
<li>
<p>Initialise Display Engine</p>
</li>
<li>
<p>Initialise UI Blender</p>
</li>
<li>
<p>Initialise UI Channels</p>
</li>
<li>
<p>Enable Display Engine</p>
</li>
</ol>
<h2 id="initialise-display-engine"><a class="doc-anchor" href="#initialise-display-engine">§</a>4.1 Initialise Display Engine</h2>
<p>We begin by <strong>initialising the Display Engine</strong>…</p>
<div class="example-wrap"><pre class="language-c"><code>// Init Display Engine
int ret = a64_de_init();
DEBUGASSERT(ret == OK);
// Wait 160 milliseconds
up_mdelay(160);
// Render Graphics with Display Engine
ret = pinephone_render_graphics();
DEBUGASSERT(ret == OK);</code></pre></div>
<p><a href="https://github.com/lupyuen/pinephone-nuttx/blob/main/render.zig#L1146-L1196">(Source)</a></p>
<p><a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/a64/a64_de.c#L386-L655"><strong>a64_de_init</strong></a> comes from our NuttX Kernel Driver for Display Engine.</p>
<p><a href="https://lupyuen.github.io/articles/de#appendix-initialising-the-allwinner-a64-display-engine">(How it works)</a></p>
<p>We call <a href="https://lupyuen.github.io/articles/de3#appendix-calibrate-nuttx-delay"><strong>up_mdelay</strong></a> to wait 160 milliseconds. <a href="https://lupyuen.github.io/articles/de3#appendix-calibrate-nuttx-delay">(Explained here)</a></p>
<p>Then we call <strong>pinephone_render_graphics</strong>…</p>
<h2 id="initialise-ui-blender"><a class="doc-anchor" href="#initialise-ui-blender">§</a>4.2 Initialise UI Blender</h2>
<p>Inside <strong>pinephone_render_graphics</strong>, we <strong>initialise the UI Blender</strong> that will blend our UI Channels into a single image: <a href="https://github.com/lupyuen/pinephone-nuttx/blob/main/test/test_a64_de.c#L91-L157">test_a64_de.c</a></p>
<div class="example-wrap"><pre class="language-c"><code>// Render graphics with A64 Display Engine
int pinephone_render_graphics(void) {
// Init the UI Blender for A64 Display Engine
int ret = a64_de_blender_init();
DEBUGASSERT(ret == OK);</code></pre></div>
<p><a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/a64/a64_de.c#L655-L711">(<strong>a64_de_blender_init</strong> comes from our Display Engine Driver)</a></p>
<p><a href="https://lupyuen.github.io/articles/de#appendix-programming-the-allwinner-a64-display-engine">(How it works)</a></p>
<h2 id="initialise-ui-channels"><a class="doc-anchor" href="#initialise-ui-channels">§</a>4.3 Initialise UI Channels</h2>
<p>Next we <strong>initialise UI Channel 1</strong> with Framebuffer 0…</p>
<div class="example-wrap"><pre class="language-c"><code> // Init the Base UI Channel (Channel 1)
ret = a64_de_ui_channel_init(
1, // UI Channel Number (1 for Base UI Channel)
planeInfo.fbmem, // Start of Frame Buffer Memory (address should be 32-bit)
planeInfo.fblen, // Length of Frame Buffer Memory in bytes
planeInfo.xres_virtual, // Horizontal resolution in pixel columns
planeInfo.yres_virtual, // Vertical resolution in pixel rows
planeInfo.xoffset, // Horizontal offset in pixel columns
planeInfo.yoffset // Vertical offset in pixel rows
);
DEBUGASSERT(ret == OK);</code></pre></div>
<p><a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/a64/a64_de.c#L711-L927">(<strong>a64_de_ui_channel_init</strong> comes from our Display Engine Driver)</a></p>
<p><a href="https://lupyuen.github.io/articles/de2#configure-framebuffer">(How it works)</a></p>
<p>Then we <strong>initialise UI Channels 2 and 3</strong> (with Framebuffers 1 and 2)…</p>
<div class="example-wrap"><pre class="language-c"><code> // For each of the 2 Overlay UI Channels (Channels 2 and 3)...
for (int i = 0; i < sizeof(overlayInfo) / sizeof(overlayInfo[0]); i++) {
// Get the NuttX Framebuffer for the UI Channel
const struct fb_overlayinfo_s *ov = &overlayInfo[i];
// Init the UI Channel.
// We pass NULL if the UI Channel should be disabled.
ret = a64_de_ui_channel_init(
i + 2, // UI Channel Number (2 and 3 for Overlay UI Channels)
(CHANNELS == 3) ? ov->fbmem : NULL, // Start of Frame Buffer Memory (address should be 32-bit)
ov->fblen, // Length of Frame Buffer Memory in bytes
ov->sarea.w, // Horizontal resolution in pixel columns
ov->sarea.h, // Vertical resolution in pixel rows
ov->sarea.x, // Horizontal offset in pixel columns
ov->sarea.y // Vertical offset in pixel rows
);
DEBUGASSERT(ret == OK);
}</code></pre></div>
<p><a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/a64/a64_de.c#L711-L927">(<strong>a64_de_ui_channel_init</strong> comes from our Display Engine Driver)</a></p>
<p><a href="https://lupyuen.github.io/articles/de2#configure-framebuffer">(How it works)</a></p>
<h2 id="enable-display-engine"><a class="doc-anchor" href="#enable-display-engine">§</a>4.4 Enable Display Engine</h2>
<p>Finally we <strong>enable the Display Engine</strong>…</p>
<div class="example-wrap"><pre class="language-c"><code> // Set UI Blender Route, enable Blender Pipes
// and apply the settings
ret = a64_de_enable(CHANNELS);
DEBUGASSERT(ret == OK); </code></pre></div>
<p><a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/a64/a64_de.c#L927-L1017">(<strong>a64_de_enable</strong> comes from our Display Engine Driver)</a></p>
<p><a href="https://lupyuen.github.io/articles/de2#configure-blender">(How it works)</a></p>
<p>The Display Engine starts <strong>pulling pixels from our Framebuffers</strong> over Direct Memory Access (DMA). And pushes the rendered image to PinePhone’s LCD Display.</p>
<p>But we won’t see anything until we <strong>populate our 3 Framebuffers</strong> with a Test Pattern…</p>
<div class="example-wrap"><pre class="language-c"><code> // Fill Framebuffer with Test Pattern.
// Must be called after Display Engine is Enabled,
// or missing rows will appear.
test_pattern();
return OK;
}</code></pre></div>
<p>Let’s do a simple Test Pattern…</p>
<p><img src="https://lupyuen.github.io/images/de2-overlay.jpg" alt="3 Framebuffers for 3 UI Channels" /></p>
<h1 id="test-pattern"><a class="doc-anchor" href="#test-pattern">§</a>5 Test Pattern</h1>
<p>We fill our 3 Framebuffers with a simple <strong>Test Pattern</strong> (pic above)…</p>
<ul>
<li>
<p><strong>Framebuffer 0:</strong> Blue, Green and Red Blocks</p>
<p>(720 x 1440 pixels)</p>
</li>
<li>
<p><strong>Framebuffer 1:</strong> Semi-Transparent White Square</p>
<p>(600 x 600 pixels)</p>
</li>
<li>
<p><strong>Framebuffer 2:</strong> Semi-Transparent Green Circle</p>
<p>(720 x 1440 pixels)</p>
</li>
</ul>
<p>Note that Framebuffers 1 and 2 are <strong>Semi-Transparent</strong>, to show that the UI Blender works correctly.</p>
<p>This is how we <strong>populate our 3 Framebuffers:</strong> <a href="https://github.com/lupyuen/pinephone-nuttx/blob/main/test/test_a64_de.c#L159-L243">test_a64_de.c</a></p>
<div class="example-wrap"><pre class="language-c"><code>// Fill the Framebuffers with a Test Pattern.
// Must be called after Display Engine is Enabled,
// or missing rows will appear.
static void test_pattern(void) {
// Zero the Framebuffers
memset(fb0, 0, sizeof(fb0));
memset(fb1, 0, sizeof(fb1));
memset(fb2, 0, sizeof(fb2));</code></pre></div>
<p><strong>Framebuffer 0</strong> (UI Channel 1) will have Blue, Green and Red Blocks…</p>
<div class="example-wrap"><pre class="language-c"><code> // Init Framebuffer 0:
// Fill with Blue, Green and Red
const int fb0_len = sizeof(fb0) / sizeof(fb0[0]);
// For every pixel...
for (int i = 0; i < fb0_len; i++) {
// Colours are in XRGB 8888 format
if (i < fb0_len / 4) {
// Blue for top quarter
fb0[i] = 0x80000080;
} else if (i < fb0_len / 2) {
// Green for next quarter
fb0[i] = 0x80008000;
} else {
// Red for lower half
fb0[i] = 0x80800000;
}
// Fixes the missing rows, not sure why
ARM64_DMB(); ARM64_DSB(); ARM64_ISB();
}</code></pre></div>
<p>(We’ll talk about <strong>ARM64_DMB</strong> later)</p>
<p><strong>Framebuffer 1</strong> (UI Channel 2) will be Semi-Transparent White…</p>
<div class="example-wrap"><pre class="language-c"><code> // Init Framebuffer 1:
// Fill with Semi-Transparent White
const int fb1_len = sizeof(fb1) / sizeof(fb1[0]);
// For every pixel...
for (int i = 0; i < fb1_len; i++) {
// Set the pixel to Semi-Transparent White
fb1[i] = 0x40FFFFFF; // ARGB 8888 format
// Fixes the missing rows, not sure why
ARM64_DMB(); ARM64_DSB(); ARM64_ISB();
}</code></pre></div>
<p>And <strong>Framebuffer 2</strong> (UI Channel 3) will have a Semi-Transparent Green Circle…</p>
<div class="example-wrap"><pre class="language-c"><code> // Init Framebuffer 2:
// Fill with Semi-Transparent Green Circle
const int fb2_len = sizeof(fb2) / sizeof(fb2[0]);
// For every pixel row...
for (int y = 0; y < PANEL_HEIGHT; y++) {
// For every pixel column...
for (int x = 0; x < PANEL_WIDTH; x++) {
// Get pixel index
const int p = (y * PANEL_WIDTH) + x;
DEBUGASSERT(p < fb2_len);
// Shift coordinates so that centre of screen is (0,0)
const int half_width = PANEL_WIDTH / 2;
const int half_height = PANEL_HEIGHT / 2;
const int x_shift = x - half_width;
const int y_shift = y - half_height;
// If x^2 + y^2 < radius^2, set the pixel to Semi-Transparent Green
if (x_shift*x_shift + y_shift*y_shift < half_width*half_width) {
fb2[p] = 0x80008000; // Semi-Transparent Green in ARGB 8888 Format
} else { // Otherwise set to Transparent Black
fb2[p] = 0x00000000; // Transparent Black in ARGB 8888 Format
}
// Fixes the missing rows, not sure why
ARM64_DMB(); ARM64_DSB(); ARM64_ISB();
}
}
}</code></pre></div>
<p>We’re done with our Test Pattern! Let’s talk about <strong>ARM64_DMB</strong>…</p>
<p><img src="https://lupyuen.github.io/images/de-rgb.jpg" alt="Missing Rows" /></p>
<p><em>Why the Arm Barriers?</em></p>
<div class="example-wrap"><pre class="language-c"><code>// Fixes the missing rows, not sure why
ARM64_DMB(); ARM64_DSB(); ARM64_ISB();</code></pre></div>
<p>These are <a href="https://developer.arm.com/documentation/dui0489/c/arm-and-thumb-instructions/miscellaneous-instructions/dmb--dsb--and-isb"><strong>Arm64 Barrier Instructions</strong></a> that prevent caching and out-of-order execution. <a href="https://developer.arm.com/documentation/dui0489/c/arm-and-thumb-instructions/miscellaneous-instructions/dmb--dsb--and-isb">(See this)</a></p>
<p>If we omit these Barrier Instructions, the rendered image will have <strong>missing rows</strong>. (Pic above)</p>
<p>We’re not sure why this happens. Maybe it’s the CPU Cache? DMA? Framebuffer Alignment? Memory Corruption?</p>
<p>(Doesn’t happen in the original Zig version)</p>
<p><em>Why do we fill the Framebuffers after enabling the Display Engine?</em></p>
<p>Since we’re running on DMA (Direct Memory Access), rightfully we can fill the Framebuffers (with our Test Pattern) <em>before</em> enabling the Display Engine…</p>
<p>But this creates mysterious missing rows (pic above). So we fill the Framebuffers <strong>after enabling the Display Engine</strong>.</p>
<p>Let’s run our Test Code…</p>
<p><a href="https://lupyuen.github.io/images/de3-title.jpg">(We’re still missing a row at the bottom of the circle)</a></p>
<p><img src="https://lupyuen.github.io/images/dsi3-steps.jpg" alt="Complete Display Driver for PinePhone" /></p>
<p><a href="https://lupyuen.github.io/articles/dsi3#complete-display-driver-for-pinephone"><em>Complete Display Driver for PinePhone</em></a></p>
<h1 id="complete-display-driver"><a class="doc-anchor" href="#complete-display-driver">§</a>6 Complete Display Driver</h1>
<p><em>Are we done yet with our Display Driver for PinePhone?</em></p>
<p>Not quite! PinePhone needs a <strong>super complex Display Driver</strong> that will handle 11 steps (pic above)…</p>
<ul>
<li><a href="https://lupyuen.github.io/articles/dsi3#complete-display-driver-for-pinephone"><strong>“Complete Display Driver for PinePhone”</strong></a></li>
</ul>
<p>We’ve implemented most of this in the NuttX Kernel, we’re now converting the remaining bits <strong>from Zig to C</strong>.</p>
<p><em>So how do we test this hodgepodge of Zig and C?</em></p>
<p>We created a <strong>Zig Test Program</strong> that glues together the Zig and C bits for testing.</p>
<p>Here are <strong>all 11 steps</strong> of our upcoming Display Driver, hodgepodged with Zig: <a href="https://github.com/lupyuen/pinephone-nuttx/blob/main/render.zig#L1146-L1196">render.zig</a></p>
<div class="example-wrap"><pre class="language-zig"><code>// Zig Test Program that renders 3 UI Channels in Zig and C...
// Turn on PinePhone Display Backlight (in Zig)
backlight.backlight_enable(90); // 90% brightness
// Init A64 Timing Controller TCON0 (in C)
// PANEL_WIDTH is 720, PANEL_HEIGHT is 1440
_ = a64_tcon0_init(PANEL_WIDTH, PANEL_HEIGHT);
// Init PinePhone Power Management Integrated Circuit (in C)
_ = pinephone_pmic_init();
// Wait 15 milliseconds for power supply and power-on init
up_mdelay(15);</code></pre></div>
<p>In the code above, we do these steps…</p>
<ul>
<li>
<p>Turn on PinePhone’s <a href="https://lupyuen.github.io/articles/de#appendix-display-backlight"><strong>Display Backlight</strong></a></p>
<p><a href="https://github.com/lupyuen/pinephone-nuttx/blob/main/backlight.zig">(<strong>backlight_enable</strong> is in Zig)</a></p>
</li>
<li>
<p>Initialise the A64 <a href="https://lupyuen.github.io/articles/de#appendix-timing-controller-tcon0"><strong>Timing Controller TCON0</strong></a></p>
<p><a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/a64/a64_tcon0.c#L180-L474">(<strong>a64_tcon0_init</strong> comes from our NuttX Driver for Timing Controller TCON0)</a></p>
</li>
<li>
<p>Initialise PinePhone’s <a href="https://lupyuen.github.io/articles/de#appendix-power-management-integrated-circuit"><strong>Power Management Integrated Circuit (PMIC)</strong></a> to power on the LCD Panel</p>
<p><a href="https://github.com/lupyuen/pinephone-nuttx/blob/main/test/test_a64_rsb.c">(<strong>pinephone_pmic_init</strong> will be added to NuttX Kernel)</a></p>
</li>
<li>
<p>Wait 15 milliseconds</p>
</li>
</ul>
<div class="example-wrap"><pre class="language-zig"><code>// Enable A64 MIPI Display Serial Interface (in C)
_ = a64_mipi_dsi_enable();
// Enable A64 MIPI Display Physical Layer (in C)
_ = a64_mipi_dphy_enable();</code></pre></div>
<p>Here we enable the A64 <a href="https://lupyuen.github.io/articles/dsi3"><strong>MIPI Display Serial Interface</strong></a> and <a href="https://lupyuen.github.io/articles/dsi3"><strong>MIPI Display Physical Layer</strong></a>.</p>
<p><a href="https://lupyuen.github.io/articles/dsi3#enable-mipi-dsi-and-d-phy">(<strong>a64_mipi_dsi_enable</strong> comes from our NuttX Driver for MIPI Display Serial Interface)</a></p>
<p><a href="https://lupyuen.github.io/articles/dsi3#enable-mipi-dsi-and-d-phy">(<strong>a64_mipi_dphy_enable</strong> too)</a></p>
<div class="example-wrap"><pre class="language-zig"><code>// Reset LCD Panel (in Zig)
panel.panel_reset();
// Wait 15 milliseconds for LCD Panel
up_mdelay(15);
// Init LCD Panel (in C)
_ = pinephone_panel_init();</code></pre></div>
<p>Next we reset the <a href="https://lupyuen.github.io/articles/de#appendix-reset-lcd-panel"><strong>LCD Panel</strong></a>, wait 15 milliseconds and send the <a href="https://lupyuen.github.io/articles/dsi3#send-mipi-dsi-packet"><strong>Initialisation Commands</strong></a> to the LCD Controller.</p>
<p><a href="https://github.com/lupyuen/pinephone-nuttx/blob/main/panel.zig">(<strong>panel_reset</strong> is in Zig)</a></p>
<p><a href="https://github.com/lupyuen/pinephone-nuttx/blob/main/test/test_a64_mipi_dsi.c#L43-L453">(<strong>pinephone_panel_init</strong> will be added to NuttX Kernel)</a></p>
<p><a href="https://lupyuen.github.io/articles/dsi3#send-mipi-dsi-packet">(Which calls <strong>a64_mipi_dsi_write</strong> from our NuttX Driver for MIPI Display Serial Interface)</a></p>
<div class="example-wrap"><pre class="language-zig"><code>// Start A64 MIPI Display Serial Interface (in C)
_ = a64_mipi_dsi_start();</code></pre></div>
<p>We start A64’s <a href="https://lupyuen.github.io/articles/dsi3#enable-mipi-dsi-and-d-phy"><strong>MIPI Display Serial Interface</strong></a>.</p>
<p><a href="https://lupyuen.github.io/articles/dsi3#enable-mipi-dsi-and-d-phy">(<strong>a64_mipi_dsi_start</strong> comes from our NuttX Driver for MIPI Display Serial Interface)</a></p>
<div class="example-wrap"><pre class="language-zig"><code>// Init A64 Display Engine (in C)
_ = a64_de_init();
// Wait 160 milliseconds for Display Engine
up_mdelay(160);</code></pre></div>
<p>We <strong>initialise the Display Engine</strong>.</p>
<p><a href="https://lupyuen.github.io/articles/de3#initialise-display-engine">(We’ve seen <strong>a64_de_init</strong> earlier)</a></p>
<div class="example-wrap"><pre class="language-zig"><code>// Render Graphics with Display Engine (in C)
_ = pinephone_render_graphics();</code></pre></div>
<p>Finally we <strong>render the framebuffers</strong> with the Display Engine.</p>
<p><a href="https://lupyuen.github.io/articles/de3#initialise-ui-blender">(We’ve seen <strong>pinephone_render_graphics</strong> earlier)</a></p>
<p>This is how we compile our Zig Test Program…</p>
<ul>
<li>
<p><a href="https://github.com/lupyuen/pinephone-nuttx#test-mipi-dsi-for-nuttx-kernel"><strong>“Compile Zig Test Program”</strong></a></p>
<p><a href="https://github.com/lupyuen/pinephone-nuttx/releases/tag/v1.2.1">(Download the binaries here)</a></p>
</li>
</ul>
<p>We boot NuttX on PinePhone <a href="https://nuttx.apache.org/docs/latest/platforms/arm/a64/boards/pinephone/index.html">(with a microSD Card)</a> and run our Zig Test Program…</p>
<div class="example-wrap"><pre class="language-text"><code>NuttShell (NSH) NuttX-11.0.0-pinephone
nsh> uname -a
NuttX 11.0.0-pinephone 64a54d2-dirty
Dec 21 2022 21:48:25 arm64 pinephone
nsh> hello 0</code></pre></div>
<p><a href="https://gist.github.com/lupyuen/bd943bea51c6f90debd05cd4f4a8585d">(Source)</a></p>
<p>PinePhone renders our Test Pattern on the LCD Display (pic below). Yep our (work-in-progress) PinePhone Display Driver has been tested successfully!</p>
<p>Here’s the <strong>Debug Log</strong> from our Zig Test Program…</p>
<ul>
<li><a href="https://gist.github.com/lupyuen/bd943bea51c6f90debd05cd4f4a8585d"><strong>Test Log for Zig Test Program</strong></a></li>
</ul>
<p><em>Won’t the Debug Logging create extra latency that might affect the driver?</em></p>
<p>That’s why we also test with <strong>Debug Logging disabled</strong>…</p>
<ul>
<li><a href="https://gist.github.com/lupyuen/df50aa74b33b39069e89eefda5957423"><strong>Test Log with Debug Logging Disabled</strong></a></li>
</ul>
<p>Let’s talk about the upcoming drivers that we’re adding to NuttX Kernel…</p>
<p><img src="https://lupyuen.github.io/images/de3-title.jpg" alt="Rendering graphics on PinePhone with Apache NuttX RTOS" /></p>
<h1 id="upcoming-drivers"><a class="doc-anchor" href="#upcoming-drivers">§</a>7 Upcoming Drivers</h1>
<p><em>Which bits of our NuttX Display Driver are still in Zig?</em></p>
<p>These parts are still in Zig, <strong>pending conversion to C</strong>…</p>
<ul>
<li>
<p>Driver for PinePhone <a href="https://lupyuen.github.io/articles/de#appendix-display-backlight"><strong>Display Backlight</strong></a></p>
</li>
<li>
<p>Driver for PinePhone <a href="https://lupyuen.github.io/articles/de#appendix-reset-lcd-panel"><strong>LCD Panel</strong></a></p>
</li>
</ul>
<p>These have just been <strong>converted from Zig to C</strong>, now adding to NuttX Kernel…</p>
<ul>
<li>
<p>Driver for PinePhone <a href="https://github.com/lupyuen/pinephone-nuttx/blob/main/test/test_a64_rsb.c"><strong>Power Management Integrated Circuit (PMIC)</strong></a></p>
<p><a href="https://lupyuen.github.io/articles/de#appendix-power-management-integrated-circuit">(Which powers the LCD Panel)</a></p>
</li>
<li>
<p>Driver for A64 <a href="https://lupyuen.github.io/articles/de#appendix-reduced-serial-bus"><strong>Reduced Serial Bus (RSB)</strong></a></p>
<p>(Needed for PinePhone PMIC)</p>
</li>
</ul>
<p><em>Where will the new drivers live inside the NuttX Kernel?</em></p>
<p>The drivers for Display Backlight, LCD Panel and PMIC will go into the new <strong>PinePhone LCD Driver</strong>.</p>
<p>Which will follow the design of the <a href="https://github.com/apache/nuttx/blob/master/boards/arm/stm32f7/stm32f746g-disco/src/stm32_lcd.c"><strong>STM32F7 LCD Driver</strong></a> in NuttX…</p>
<ol>
<li>
<p>At startup, <strong>stm32_bringup</strong> calls <strong>fb_register</strong></p>
<p><a href="https://github.com/apache/nuttx/blob/master/boards/arm/stm32f7/stm32f746g-disco/src/stm32_bringup.c#L100">(<strong>stm32_bringup.c</strong>)</a></p>
</li>
<li>
<p>To initialise the Framebuffer, <strong>fb_register</strong> calls <strong>up_fbinitialize</strong></p>
<p><a href="https://github.com/apache/nuttx/blob/master/drivers/video/fb.c#L795-L805">(<strong>fb.c</strong>)</a></p>
</li>
<li>
<p>To initialise the Display Driver, <strong>up_fbinitialize</strong> calls <strong>stm32_ltdcinitialize</strong></p>
<p><a href="https://github.com/apache/nuttx/blob/master/boards/arm/stm32f7/stm32f746g-disco/src/stm32_lcd.c#L72">(<strong>stm32_lcd.c</strong>)</a></p>
</li>
<li>
<p>Inside the Display Driver, <strong>stm32_ltdcinitialize</strong> creates the NuttX Framebuffer</p>
<p><a href="https://github.com/apache/nuttx/blob/master/arch/arm/src/stm32f7/stm32_ltdc.c#L2971">(<strong>stm32_ltdc.c</strong>)</a></p>
</li>
<li>
<p>NuttX Framebuffer is here: <a href="https://github.com/apache/nuttx/blob/master/arch/arm/src/stm32f7/stm32_ltdc.c#L864"><strong>stm32_ltdc.c</strong></a></p>
</li>
</ol>
<p>Our new PinePhone LCD Driver shall execute all 11 steps as described earlier…</p>
<ul>
<li><a href="https://lupyuen.github.io/articles/de3#complete-display-driver"><strong>“Complete Display Driver”</strong></a></li>
</ul>
<p>Probably inside our new implementation of <strong>up_fbinitialize</strong>. Work-in-progress…</p>
<ul>
<li><a href="https://github.com/lupyuen2/wip-nuttx/blob/lcd/boards/arm64/a64/pinephone/src/pinephone_display.c"><strong>boards/arm64/a64/pinephone/src/pinephone_display.c</strong></a></li>
</ul>
<p><img src="https://lupyuen.github.io/images/de3-run.png" alt="Zig Test Program running on Apache NuttX RTOS for PinePhone" /></p>
<h1 id="whats-next"><a class="doc-anchor" href="#whats-next">§</a>8 What’s Next</h1>
<p>Very soon the official NuttX Kernel will be rendering graphics on PinePhone’s LCD Display… Stay tuned for updates!</p>
<p>Please check out the other articles on NuttX for PinePhone…</p>
<ul>
<li><a href="https://github.com/lupyuen/pinephone-nuttx"><strong>“Apache NuttX RTOS for PinePhone”</strong></a></li>
</ul>
<p>Many Thanks to my <a href="https://lupyuen.github.io/articles/sponsor"><strong>GitHub Sponsors</strong></a> for supporting my work! This article wouldn’t have been possible without your support.</p>
<ul>
<li>
<p><a href="https://lupyuen.github.io/articles/sponsor"><strong>Sponsor me a coffee</strong></a></p>
</li>
<li>
<p><a href="https://news.ycombinator.com/item?id=34100614"><strong>Discuss this article on Hacker News</strong></a></p>
</li>
<li>
<p><a href="https://www.reddit.com/r/PINE64official/comments/zt1181/nuttx_rtos_for_pinephone_display_engine/"><strong>Discuss this article on Reddit</strong></a></p>
</li>
<li>
<p><a href="https://github.com/lupyuen/nuttx-ox64"><strong>My Current Project: “Apache NuttX RTOS for Ox64 BL808”</strong></a></p>
</li>
<li>
<p><a href="https://github.com/lupyuen/nuttx-star64"><strong>My Other Project: “NuttX for Star64 JH7110”</strong></a></p>
</li>
<li>
<p><a href="https://github.com/lupyuen/pinephone-nuttx"><strong>Older Project: “NuttX for PinePhone”</strong></a></p>
</li>
<li>
<p><a href="https://lupyuen.github.io"><strong>Check out my articles</strong></a></p>
</li>
<li>
<p><a href="https://lupyuen.github.io/rss.xml"><strong>RSS Feed</strong></a></p>
</li>
</ul>
<p><em>Got a question, comment or suggestion? Create an Issue or submit a Pull Request here…</em></p>
<p><a href="https://github.com/lupyuen/lupyuen.github.io/blob/master/src/de3.md"><strong>lupyuen.github.io/src/de3.md</strong></a></p>
<h1 id="appendix-calibrate-nuttx-delay"><a class="doc-anchor" href="#appendix-calibrate-nuttx-delay">§</a>9 Appendix: Calibrate NuttX Delay</h1>
<p><em>Can we call sleep() or usleep() in our NuttX Display Driver?</em></p>
<p>Sorry Nope! Most of our Display Driver code runs in the NuttX Kernel at startup.</p>
<p>Calling <code>sleep()</code> or <code>usleep()</code> will <strong>crash the kernel</strong>…</p>
<p>Because the kernel is still starting up!</p>
<p><em>So how do we wait a while in our NuttX Display Driver?</em></p>
<p>We call <strong><code>up_mdelay()</code></strong> like so…</p>
<div class="example-wrap"><pre class="language-c"><code>// Wait 160 milliseconds
up_mdelay(160);</code></pre></div>
<p><em>How does up_mdelay() work?</em></p>
<p>It’s a very simple loop: <a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/common/arm64_assert.c#L64-L75">arm64_assert.c</a></p>
<div class="example-wrap"><pre class="language-c"><code>// Wait for the specified milliseconds
void up_mdelay(unsigned int milliseconds) {
volatile unsigned int i;
volatile unsigned int j;
for (i = 0; i < milliseconds; i++) {
for (j = 0; j < CONFIG_BOARD_LOOPSPERMSEC; j++) {
}
}
}</code></pre></div>
<p><em>Huh? Won’t the compiler optimise the code and remove the loop?</em></p>
<p>It won’t because we declared the variables as <strong><code>volatile</code></strong>.</p>
<p>The NuttX Disassembly shows that the loop is still intact: <a href="https://github.com/lupyuen/pinephone-nuttx/releases/download/v1.2.1/nuttx.S">nuttx.S</a></p>
<div class="example-wrap"><pre class="language-text"><code>arm64_assert.c:69 (discriminator 2)
for (i = 0; i < milliseconds; i++)
40081830: b9400be1 ldr w1, [sp, #8]
40081834: 11000421 add w1, w1, #0x1
40081838: b9000be1 str w1, [sp, #8]
4008183c: 17fffff4 b 4008180c <up_mdelay+0x10>
arm64_assert.c:71 (discriminator 3)
for (j = 0; j < CONFIG_BOARD_LOOPSPERMSEC; j++)
40081840: b9400fe1 ldr w1, [sp, #12]
40081844: 11000421 add w1, w1, #0x1
40081848: b9000fe1 str w1, [sp, #12]
4008184c: 17fffff6 b 40081824 <up_mdelay+0x28></code></pre></div>
<p><em>What’s CONFIG_BOARD_LOOPSPERMSEC?</em></p>
<p>That’s a magic constant computed by the <strong>NuttX Calibration Tool For udelay</strong>.</p>
<p>To install the calibration tool…</p>
<div class="example-wrap"><pre class="language-bash"><code>make menuconfig</code></pre></div>
<p>Then select…</p>
<div class="example-wrap"><pre class="language-text"><code>Application Configuration > Examples > Calibration Tool For udelay </code></pre></div>
<p>And rebuild NuttX.</p>
<p>Boot NuttX on PinePhone and run <strong><code>calib_udelay</code></strong>…</p>
<div class="example-wrap"><pre class="language-text"><code>nsh> calib_udelay
Calibrating timer for main calibration...
Performing main calibration for udelay.This will take approx. 17.280 seconds.
Calibration slope for udelay:
Y = m*X + b, where
X is loop iterations,
Y is time in nanoseconds,
b is base overhead,
m is nanoseconds per loop iteration.
m = 8.58195489 nsec/iter
b = -347067.66917297 nsec
Correlation coefficient, R² = 1.0000
Without overhead, 0.11652356 iterations per nanosecond and 116523.57 iterations per millisecond.
Recommended setting for CONFIG_BOARD_LOOPSPERMSEC:
CONFIG_BOARD_LOOPSPERMSEC=116524</code></pre></div>
<p><a href="https://gist.github.com/lupyuen/5c7de17bfe6cd192a852182cdf217c43">(See the Complete Log)</a></p>
<p>We update the <strong>NuttX Board Configuration</strong> for PinePhone with the computed value: <a href="https://github.com/apache/nuttx/blob/master/boards/arm64/a64/pinephone/configs/nsh/defconfig">pinephone/configs/nsh/defconfig</a></p>
<div class="example-wrap"><pre class="language-text"><code>CONFIG_BOARD_LOOPSPERMSEC=116524</code></pre></div>
<p>(PinePhone is probably the fastest NuttX Board ever!)</p>
<p><em>What if our driver needs to wait a while AFTER the NuttX Kernel has been started?</em></p>
<p>Call <a href="https://github.com/apache/nuttx/blob/master/sched/signal/sig_usleep.c#L38-L86"><strong><code>nxsig_usleep()</code></strong></a> instead.</p>
<p>It suspends the current thread, instead of doing a busy-wait loop.</p>
<!-- Begin scripts/rustdoc-after.html: Post-HTML for Custom Markdown files processed by rustdoc, like chip8.md -->
<!-- Begin Theme Picker and Prism Theme -->
<script src="../theme.js"></script>
<script src="../prism.js"></script>
<!-- Theme Picker and Prism Theme -->
<!-- End scripts/rustdoc-after.html -->
</body>
</html>