mirror of
https://github.com/lupyuen/lupyuen.github.io.git
synced 2025-01-13 10:18:33 +08:00
736 lines
No EOL
44 KiB
HTML
736 lines
No EOL
44 KiB
HTML
<!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> |