mirror of
https://github.com/lupyuen/lupyuen.github.io.git
synced 2025-01-13 09:08:30 +08:00
854 lines
No EOL
47 KiB
HTML
854 lines
No EOL
47 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: UART Driver</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: UART Driver"
|
||
data-rh="true">
|
||
<meta property="og:description"
|
||
content="Our PinePhone Operating System will be awfully quiet if we don't implement UART Input and Output... Here's how we implemented the UART Driver for Apache NuttX RTOS"
|
||
data-rh="true">
|
||
<meta name="description"
|
||
content="Our PinePhone Operating System will be awfully quiet if we don't implement UART Input and Output... Here's how we implemented the UART Driver for Apache NuttX RTOS">
|
||
<meta property="og:image"
|
||
content="https://lupyuen.github.io/images/serial-title.jpg">
|
||
<meta property="og:type"
|
||
content="article" data-rh="true">
|
||
<link rel="canonical" href="https://lupyuen.org/articles/serial.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: UART Driver</h1>
|
||
<nav id="rustdoc"><ul>
|
||
<li><a href="#uart-controller" title="UART Controller">1 UART Controller</a><ul></ul></li>
|
||
<li><a href="#uart-with-polling" title="UART With Polling">2 UART With Polling</a><ul>
|
||
<li><a href="#transmit-uart" title="Transmit UART">2.1 Transmit UART</a><ul></ul></li>
|
||
<li><a href="#wait-to-transmit" title="Wait To Transmit">2.2 Wait To Transmit</a><ul></ul></li>
|
||
<li><a href="#receive-uart" title="Receive UART">2.3 Receive UART</a><ul></ul></li>
|
||
<li><a href="#wait-to-receive" title="Wait To Receive">2.4 Wait To Receive</a><ul></ul></li>
|
||
<li><a href="#arm64-assembly" title="Arm64 Assembly">2.5 Arm64 Assembly</a><ul></ul></li></ul></li>
|
||
<li><a href="#uart-with-interrupts" title="UART With Interrupts">3 UART With Interrupts</a><ul>
|
||
<li><a href="#attach-interrupt-handler" title="Attach Interrupt Handler">3.1 Attach Interrupt Handler</a><ul></ul></li>
|
||
<li><a href="#enable-interrupt" title="Enable Interrupt">3.2 Enable Interrupt</a><ul></ul></li>
|
||
<li><a href="#handle-interrupt" title="Handle Interrupt">3.3 Handle Interrupt</a><ul></ul></li>
|
||
<li><a href="#uart-transmit" title="UART Transmit">3.4 UART Transmit</a><ul></ul></li>
|
||
<li><a href="#uart-receive" title="UART Receive">3.5 UART Receive</a><ul></ul></li></ul></li>
|
||
<li><a href="#initialise-uart" title="Initialise UART">4 Initialise UART</a><ul></ul></li>
|
||
<li><a href="#nuttx-uart-driver" title="NuttX UART Driver">5 NuttX UART Driver</a><ul></ul></li>
|
||
<li><a href="#uart-in-action" title="UART In Action">6 UART In Action</a><ul></ul></li>
|
||
<li><a href="#whats-next" title="What’s Next">7 What’s Next</a><ul></ul></li>
|
||
<li><a href="#appendix-uart-ports-on-pinephone" title="Appendix: UART Ports on PinePhone">8 Appendix: UART Ports on PinePhone</a><ul></ul></li></ul></nav><p>📝 <em>9 Sep 2022</em></p>
|
||
<p><img src="https://lupyuen.github.io/images/serial-title.jpg" alt="PinePhone Hacking with Pinebook Pro and BLÅHAJ" /></p>
|
||
<p><em>PinePhone Hacking with Pinebook Pro and BLÅHAJ</em></p>
|
||
<p><strong>UPDATE:</strong> PinePhone is now officially supported by Apache NuttX RTOS <a href="https://lupyuen.github.io/articles/what">(See this)</a></p>
|
||
<p>Last week we spoke about creating our own <strong>Operating System</strong> for <a href="https://wiki.pine64.org/index.php/PinePhone"><strong>Pine64 PinePhone</strong></a>…</p>
|
||
<ul>
|
||
<li><a href="https://lupyuen.github.io/articles/uboot"><strong>“PinePhone boots Apache NuttX RTOS”</strong></a></li>
|
||
</ul>
|
||
<p>Our PinePhone OS will be awfully quiet until we implement <strong>UART Input and Output</strong>. (For the Serial Debug Console)</p>
|
||
<p>Today we’ll learn about the <strong>UART Controller</strong> for the Allwinner A64 SoC inside PinePhone…</p>
|
||
<ul>
|
||
<li>
|
||
<p>Transmit and receive UART Data the Polling Way</p>
|
||
</li>
|
||
<li>
|
||
<p>Also the Interrupt-Driven Way</p>
|
||
</li>
|
||
<li>
|
||
<p>Enabling UART Interrupts</p>
|
||
</li>
|
||
<li>
|
||
<p>Handling UART Interrupts</p>
|
||
</li>
|
||
</ul>
|
||
<p>And how we implemented PinePhone’s <strong>UART Driver</strong> for <a href="https://lupyuen.github.io/articles/uboot"><strong>Apache NuttX RTOS</strong></a>.</p>
|
||
<p>Let’s dive into our <strong>NuttX Porting Journal</strong> and learn how we made PinePhone chatty over UART…</p>
|
||
<ul>
|
||
<li><a href="https://github.com/lupyuen/pinephone-nuttx"><strong>lupyuen/pinephone-nuttx</strong></a></li>
|
||
</ul>
|
||
<p><img src="https://lupyuen.github.io/images/uboot-uart1.png" alt="Allwinner A64 UART Controller Registers" /></p>
|
||
<p><a href="https://dl.linux-sunxi.org/A64/A64_Datasheet_V1.1.pdf"><em>Allwinner A64 UART Controller Registers</em></a></p>
|
||
<h1 id="uart-controller"><a class="doc-anchor" href="#uart-controller">§</a>1 UART Controller</h1>
|
||
<p>Our operating system will print some output on PinePhone’s <strong>Serial Debug Console</strong> as it runs. (And receive input too)</p>
|
||
<p>To do that, we’ll talk to the <strong>UART Controller</strong> on the Allwinner A64 SoC…</p>
|
||
<ul>
|
||
<li><a href="https://dl.linux-sunxi.org/A64/A64_Datasheet_V1.1.pdf"><strong>Allwinner A64 User Manual</strong></a></li>
|
||
</ul>
|
||
<p>Flip the <a href="https://dl.linux-sunxi.org/A64/A64_Datasheet_V1.1.pdf"><strong>A64 User Manual</strong></a> to page 562 (“UART”) and we’ll see the <strong>UART Registers</strong>. (Pic above)</p>
|
||
<p>PinePhone’s Serial Console is connected to <strong>UART0</strong> at Base Address <strong><code>0x01C2</code> <code>8000</code></strong></p>
|
||
<p>Which we define like so: <a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/a64/hardware/a64_memorymap.h#L42-L46">a64_memorymap.h</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>/* A64 UART0 Base Address */
|
||
#define A64_UART0_ADDR 0x1C28000
|
||
|
||
/* A64 UART0 IRQ */
|
||
#define A64_UART0_IRQ 32 </code></pre></div>
|
||
<p><a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/a64/a64_serial.h#L53-L59">(<code>A64_UART0_IRQ</code> is defined here)</a></p>
|
||
<p>(We’ll talk about <code>A64_UART0_IRQ</code> in a while)</p>
|
||
<p><img src="https://lupyuen.github.io/images/arm-uart2.jpg" alt="PinePhone connected to USB Serial Debug Cable" /></p>
|
||
<p>Check that PinePhone is connected to our computer with the <strong>USB Serial Debug Cable</strong> (pic above) at 115.2 kbps…</p>
|
||
<ul>
|
||
<li>
|
||
<p><a href="https://lupyuen.github.io/articles/uboot#usb-serial-debug-cable"><strong>“USB Serial Debug Cable”</strong></a></p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://lupyuen.github.io/articles/uboot#boot-log"><strong>“Boot Log”</strong></a></p>
|
||
</li>
|
||
</ul>
|
||
<p>Let’s read and write UART Data the easier (inefficient) way, via Polling…</p>
|
||
<p><img src="https://lupyuen.github.io/images/uboot-uart2.png" alt="A64 UART Receive and Transmit Registers UART_RBR and UART_THR" /></p>
|
||
<p><a href="https://dl.linux-sunxi.org/A64/A64_Datasheet_V1.1.pdf"><em>A64 UART Receive and Transmit Registers UART_RBR and UART_THR</em></a></p>
|
||
<h1 id="uart-with-polling"><a class="doc-anchor" href="#uart-with-polling">§</a>2 UART With Polling</h1>
|
||
<p>Page 563 of the <a href="https://dl.linux-sunxi.org/A64/A64_Datasheet_V1.1.pdf"><strong>Allwinner A64 User Manual</strong></a> tells us the UART Registers for <strong>reading and writing UART Data</strong> (pic above)…</p>
|
||
<ul>
|
||
<li>
|
||
<p><strong>Receiver Buffer Register (RBR)</strong></p>
|
||
<p>(At Offset <code>0x00</code>)</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>Transmit Holding Register (THR)</strong></p>
|
||
<p>(Also at Offset <code>0x00</code>)</p>
|
||
</li>
|
||
</ul>
|
||
<p>Let’s write some UART Data…</p>
|
||
<h2 id="transmit-uart"><a class="doc-anchor" href="#transmit-uart">§</a>2.1 Transmit UART</h2>
|
||
<p>The <strong>Transmit Holding Register (THR)</strong> is at address <strong><code>0x01C2</code> <code>8000</code></strong>. (Since Offset is 0)</p>
|
||
<p>We’ll write our output data to <strong><code>0x01C2</code> <code>8000</code></strong>, byte by byte, and the data will appear in the Serial Console: <a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/a64/a64_serial.c#L815-L838">a64_serial.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// Send one byte to PinePhone Allwinner A64 UART
|
||
static void a64_uart_send(struct uart_dev_s *dev, int ch)
|
||
{
|
||
// Write to UART Transmit Holding Register (UART_THR)
|
||
// Offset: 0x0000
|
||
uint8_t *uart_thr = (uint8_t *)
|
||
(A64_UART0_ADDR + 0x0);
|
||
|
||
// Bits 7 to 0: Transmit Holding Register (THR)
|
||
// Data to be transmitted on the serial output port . Data should only be
|
||
// written to the THR when the THR Empty (THRE) bit (UART_LSR[5]) is set.
|
||
|
||
// If in FIFO mode and FIFOs are enabled (UART_FCR[0] = 1) and THRE is set,
|
||
// 16 number of characters of data may be written to the THR before the
|
||
// FIFO is full. Any attempt to write data when the FIFO is full results in the
|
||
// write data being lost.
|
||
*uart_thr = ch;
|
||
}</code></pre></div>
|
||
<p>So this code…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code>a64_uart_send(NULL, 'H');
|
||
a64_uart_send(NULL, 'E');
|
||
a64_uart_send(NULL, 'Y');</code></pre></div>
|
||
<p>Will print this to PinePhone’s Serial Console…</p>
|
||
<div class="example-wrap"><pre class="language-text"><code>HEY</code></pre></div>
|
||
<p><em>Will this work if we send a huge chunk of text?</em></p>
|
||
<p>Nope, we’ll overflow the <strong>Transmit FIFO Buffer</strong>!</p>
|
||
<p>The pic below shows what happens if we print too much… The overflow characters <strong>will get dropped</strong>. (Hence the solitary “<code>f</code>”)</p>
|
||
<p>To fix this, we <strong>wait for the UART Port</strong> to be ready before we transmit. We’ll see how in the next section.</p>
|
||
<p><em>What’s <code>uart_dev_s</code>?</em></p>
|
||
<p>That’s the convention that NuttX RTOS expects for UART Drivers.</p>
|
||
<p>We may drop the parameter if we’re not on NuttX.</p>
|
||
<p><img src="https://lupyuen.github.io/images/uboot-title.png" alt="Why we wait for the UART Port before we transmit" /></p>
|
||
<p><em>Why we wait for the UART Port before we transmit</em></p>
|
||
<h2 id="wait-to-transmit"><a class="doc-anchor" href="#wait-to-transmit">§</a>2.2 Wait To Transmit</h2>
|
||
<p>Let’s check if the UART Port is <strong>ready to accept output data</strong> for transmission.</p>
|
||
<p>We read Bit 5 of the <strong>Line Status Register (UART_LSR)</strong> at Offset <code>0x14</code>: <a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/a64/a64_serial.c#L876-L898">a64_serial.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// Return true if Transmit FIFO is not full for PinePhone Allwinner A64 UART
|
||
static bool a64_uart_txready(struct uart_dev_s *dev)
|
||
{
|
||
// Read from UART Line Status Register (UART_LSR)
|
||
// Offset: 0x0014
|
||
const uint8_t *uart_lsr = (const uint8_t *)
|
||
(A64_UART0_ADDR + 0x14);
|
||
|
||
// Bit 5: TX Holding Register Empty (THRE)
|
||
// If the FIFOs are disabled, this bit is set to "1" whenever the TX Holding
|
||
// Register is empty and ready to accept new data and it is cleared when the
|
||
// CPU writes to the TX Holding Register.
|
||
|
||
// If the FIFOs are enabled, this bit is set to "1" whenever the TX FIFO is
|
||
// empty and it is cleared when at least one byte is written
|
||
// to the TX FIFO.
|
||
return (*uart_lsr & 0b100000) != 0; // Transmit FIFO is ready if THRE=1 (Bit 5)
|
||
}</code></pre></div>
|
||
<p>Now we can print to the Serial Console <strong>without dropping characters</strong>…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// Wait for UART Port to be ready
|
||
while (!a64_uart_txready(NULL)) {}
|
||
|
||
// Send one byte of data
|
||
a64_uart_send(NULL, 'A');</code></pre></div>
|
||
<p><em>Busy Wait in an Empty Loop? That’s wasteful ain’t it?</em></p>
|
||
<p>Yes we’re wasting CPU Cycles waiting for UART.</p>
|
||
<p>That’s why NuttX and other Operating Systems will insist that we implement <strong>UART with Interrupts</strong> (instead of Polling).</p>
|
||
<p>We’ll cover this in a while.</p>
|
||
<p>Also note that PinePhone’s UART Port has a <strong>Transmit FIFO Buffer</strong> of 16 characters.</p>
|
||
<p>Our UART Driver doesn’t check for the available space in the Transmit FIFO Buffer.</p>
|
||
<p>For efficiency, we should probably fix this: <a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/a64/a64_serial.c#L900-L919">a64_serial.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// Return true if Transmit FIFO is empty for PinePhone Allwinner A64 UART
|
||
static bool a64_uart_txempty(struct uart_dev_s *dev)
|
||
{
|
||
// Transmit FIFO is empty if Transmit FIFO is not full (for now)
|
||
return a64_uart_txready(dev);
|
||
}</code></pre></div>
|
||
<p>Moving on from UART Transmit to Receive…</p>
|
||
<p><img src="https://lupyuen.github.io/images/uboot-uart2.png" alt="A64 UART Registers UART_RBR and UART_THR" /></p>
|
||
<p><a href="https://dl.linux-sunxi.org/A64/A64_Datasheet_V1.1.pdf"><em>A64 UART Registers UART_RBR and UART_THR</em></a></p>
|
||
<h2 id="receive-uart"><a class="doc-anchor" href="#receive-uart">§</a>2.3 Receive UART</h2>
|
||
<p>Now that PinePhone can talk to us, let’s make sure we can talk back!</p>
|
||
<p>Anything that we type into PinePhone’s Serial Console will appear in the <strong>Receiver Buffer Register (RBR)</strong>, byte by byte.</p>
|
||
<p>The Receiver Buffer Register is at address <strong><code>0x01C2</code> <code>8000</code></strong>. (Since Offset is 0). This how we read it: <a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/a64/a64_serial.c#L727-L753">a64_serial.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// Receive data from PinePhone Allwinner A64 UART
|
||
static int a64_uart_receive(struct uart_dev_s *dev, unsigned int *status)
|
||
{
|
||
// Status is always OK
|
||
*status = 0;
|
||
|
||
// Read from UART Receiver Buffer Register (UART_RBR)
|
||
// Offset: 0x0000
|
||
const uint8_t *uart_rbr = (const uint8_t *)
|
||
(A64_UART0_ADDR + 0x00);
|
||
|
||
// Bits 7 to 0: Receiver Buffer Register (RBR)
|
||
// Data byte received on the serial input port . The data in this register is
|
||
// valid only if the Data Ready (DR) bit in the UART Line Status Register
|
||
// (UART_LCR) is set.
|
||
//
|
||
// If in FIFO mode and FIFOs are enabled (UART_FCR[0] set to one), this
|
||
// register accesses the head of the receive FIFO. If the receive FIFO is full
|
||
// and this register is not read before the next data character arrives, then
|
||
// the data already in the FIFO is preserved, but any incoming data are lost
|
||
// and an overrun error occurs.
|
||
return *uart_rbr;
|
||
}</code></pre></div>
|
||
<p>(We may drop the <strong><code>dev</code></strong> and <strong><code>status</code></strong> parameters if we’re not on NuttX)</p>
|
||
<p>But don’t read the UART Input yet! We need to wait for the UART Input to be available…</p>
|
||
<h2 id="wait-to-receive"><a class="doc-anchor" href="#wait-to-receive">§</a>2.4 Wait To Receive</h2>
|
||
<p>Let’s check if there’s <strong>UART Input</strong> ready to be read from the UART Port.</p>
|
||
<p>We read Bit 0 of the <strong>Line Status Register (UART_LSR)</strong> at Offset <code>0x14</code>: <a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/a64/a64_serial.c#L791-L813">a64_serial.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// Return true if Receive FIFO is not empty for PinePhone Allwinner A64 UART
|
||
static bool a64_uart_rxavailable(struct uart_dev_s *dev)
|
||
{
|
||
// Read from UART Line Status Register (UART_LSR)
|
||
// Offset: 0x0014
|
||
const uint8_t *uart_lsr = (const uint8_t *)
|
||
(A64_UART0_ADDR + 0x14);
|
||
|
||
// Bit 0: Data Ready (DR)
|
||
// This is used to indicate that the receiver contains at least one character in
|
||
// the RBR or the receiver FIFO.
|
||
// 0: no data ready
|
||
// 1: data ready
|
||
// This bit is cleared when the RBR is read in non-FIFO mode, or when the
|
||
// receiver FIFO is empty, in FIFO mode.
|
||
return (*uart_lsr) & 1; // DR=1 if data is ready
|
||
}</code></pre></div>
|
||
<p>Now we’re ready to read UART Input…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// Wait for UART Input to be ready
|
||
while (!a64_uart_rxavailable(NULL)) {}
|
||
|
||
// Read one byte of data
|
||
int status;
|
||
int ch = a64_uart_receive(NULL, &status);</code></pre></div>
|
||
<p><em>Again… This looks like a waste of CPU Cycles?</em></p>
|
||
<p>Indeed, UART Input won’t work well on multitasking operating systems unless we do it with Interrupts. (Coming up in a sec!)</p>
|
||
<h2 id="arm64-assembly"><a class="doc-anchor" href="#arm64-assembly">§</a>2.5 Arm64 Assembly</h2>
|
||
<p><em>Is it safe to do UART Output when our PinePhone OS is booting?</em></p>
|
||
<p>Yep we may call <a href="https://lupyuen.github.io/articles/serial#transmit-uart"><strong><code>a64_uart_send</code></strong></a> and <a href="https://lupyuen.github.io/articles/serial#wait-to-transmit"><strong><code>a64_uart_txready</code></strong></a> when our OS is booting.</p>
|
||
<p>For Arm64 Assembly we have something similar: This <strong>Arm64 Assembly Macro</strong> is super helpful for printing debug messages in our Arm64 Startup Code…</p>
|
||
<ul>
|
||
<li><a href="https://lupyuen.github.io/articles/uboot#nuttx-uart-macros"><strong>“NuttX UART Macros”</strong></a></li>
|
||
</ul>
|
||
<p><em>Don’t we need to set the Baud Rate for the UART Port?</em></p>
|
||
<p>Right now we don’t initialise the UART Port because U-Boot has kindly done it for us. (At 115.2 kbps)</p>
|
||
<p>We’ll come back to this in a while.</p>
|
||
<h1 id="uart-with-interrupts"><a class="doc-anchor" href="#uart-with-interrupts">§</a>3 UART With Interrupts</h1>
|
||
<p>Earlier we saw UART with Polling, and how inefficient it can get. Now we talk about <strong>UART with Interrupts</strong> and how we…</p>
|
||
<ul>
|
||
<li>
|
||
<p>Attach a UART Interrupt Handler</p>
|
||
</li>
|
||
<li>
|
||
<p>Enable UART Interrupts</p>
|
||
</li>
|
||
<li>
|
||
<p>Handle UART Interrupts</p>
|
||
</li>
|
||
</ul>
|
||
<p><em>Does NuttX use UART Polling or Interrupts?</em></p>
|
||
<p>NuttX uses both Polling-based UART and Interrupt-driven UART. NuttX OS writes <strong>System Logs</strong> (<code>syslog</code>) the UART Polling way…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code>sinfo("This is printed on UART with Polling\n");</code></pre></div>
|
||
<p><a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/a64/a64_serial.c#L1400-L1432">(By calling <strong><code>up_putc</code></strong>)</a></p>
|
||
<p>And NuttX Apps print <strong>App Messages</strong> the UART Interrupt Way…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code>printf("This is printed on UART with Interrupts\n");</code></pre></div>
|
||
<p>So if we don’t see any App Messages in NuttX, check that the <strong>UART Interrupts</strong> are OK.</p>
|
||
<p><img src="https://lupyuen.github.io/images/interrupt-peripheral.jpg" alt="Shared Peripheral Interrupts for Allwinner A64’s Generic Interrupt Controller" /></p>
|
||
<p><a href="https://lupyuen.github.io/articles/interrupt#generic-interrupt-controller"><em>Shared Peripheral Interrupts for Allwinner A64’s Generic Interrupt Controller</em></a></p>
|
||
<h2 id="attach-interrupt-handler"><a class="doc-anchor" href="#attach-interrupt-handler">§</a>3.1 Attach Interrupt Handler</h2>
|
||
<p>PinePhone’s UART Controller will trigger an Interrupt for <strong>Transmit and Receive Events</strong> when…</p>
|
||
<ul>
|
||
<li>
|
||
<p>Transmit Buffer becomes empty</p>
|
||
</li>
|
||
<li>
|
||
<p>Received Data becomes available</p>
|
||
</li>
|
||
</ul>
|
||
<p>The <a href="https://dl.linux-sunxi.org/A64/A64_Datasheet_V1.1.pdf"><strong>Allwinner A64 User Manual</strong></a> (page 211, “GIC”) reveals that UART0 Interrupts will be triggered at <strong>Interrupt Number 32</strong>. (Pic above)</p>
|
||
<p>Let’s <strong>attach our Interrupt Handler</strong> to handle the UART Interrupts: <a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/a64/a64_serial.c#L607-L657">a64_serial.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// UART0 IRQ Number for PinePhone Allwinner A64 UART
|
||
#define UART_IRQ 32
|
||
|
||
// Attach Interrupt Handler for PinePhone Allwinner A64 UART
|
||
static int a64_uart_attach(struct uart_dev_s *dev)
|
||
{
|
||
// Attach UART Interrupt Handler
|
||
int ret = irq_attach(
|
||
UART_IRQ, // Interrupt Number
|
||
a64_uart_irq_handler, // Interrupt Handler
|
||
dev // NuttX Device
|
||
);
|
||
|
||
// Set Interrupt Priority in
|
||
// Generic Interrupt Controller version 2
|
||
arm64_gic_irq_set_priority(
|
||
UART_IRQ, // Interrupt Number
|
||
0, // Interrupt Flags
|
||
IRQ_TYPE_LEVEL // Trigger Interrupt on High
|
||
);
|
||
|
||
// Enable UART Interrupt
|
||
if (ret == OK) {
|
||
up_enable_irq(UART_IRQ);
|
||
} else {
|
||
sinfo("error ret=%d\n", ret);
|
||
}
|
||
return ret;
|
||
}</code></pre></div>
|
||
<p><strong>a64_uart_irq_handler</strong> is our UART Interrupt Handler, we’ll explain in a while.</p>
|
||
<p><em>What’s irq_attach?</em></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// Attach UART Interrupt Handler
|
||
int ret = irq_attach(
|
||
UART_IRQ, // Interrupt Number
|
||
a64_uart_irq_handler, // Interrupt Handler
|
||
dev // NuttX Device
|
||
);</code></pre></div>
|
||
<p>On NuttX, we call <strong>irq_attach</strong> to attach an Interrupt Handler to the UART Controller.</p>
|
||
<p><em>What’s arm64_gic_irq_set_priority?</em></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// Set Interrupt Priority in
|
||
// Generic Interrupt Controller version 2
|
||
arm64_gic_irq_set_priority(
|
||
UART_IRQ, // Interrupt Number
|
||
0, // Interrupt Flags
|
||
IRQ_TYPE_LEVEL // Trigger Interrupt on High
|
||
);</code></pre></div>
|
||
<p>Arm64 Interrupts are managed on PinePhone by the <strong>Generic Interrupt Controller</strong> in Allwinner A64…</p>
|
||
<ul>
|
||
<li><a href="https://lupyuen.github.io/articles/interrupt#generic-interrupt-controller"><strong>“Generic Interrupt Controller”</strong></a></li>
|
||
</ul>
|
||
<p>The code above calls the Generic Interrupt Controller to set the priority of the UART Interrupt.</p>
|
||
<p>Later when we’re done with UART Interrupts, we should <strong>detach the Interrupt Handler</strong>: <a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/a64/a64_serial.c#L659-L688">a64_serial.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// Detach Interrupt Handler for PinePhone Allwinner A64 UART
|
||
static void a64_uart_detach(struct uart_dev_s *dev)
|
||
{
|
||
// Disable UART Interrupt
|
||
up_disable_irq(UART_IRQ);
|
||
|
||
// Detach UART Interrupt Handler
|
||
irq_detach(UART_IRQ);
|
||
}</code></pre></div>
|
||
<p><img src="https://lupyuen.github.io/images/serial-enable.jpg" alt="A64 UART Interrupt Enable Register UART_IER" /></p>
|
||
<p><a href="https://dl.linux-sunxi.org/A64/A64_Datasheet_V1.1.pdf"><em>A64 UART Interrupt Enable Register UART_IER</em></a></p>
|
||
<h2 id="enable-interrupt"><a class="doc-anchor" href="#enable-interrupt">§</a>3.2 Enable Interrupt</h2>
|
||
<p>UART Interupts won’t happen until we <strong>enable UART Interrupts</strong>.</p>
|
||
<p>Page 565 of the <a href="https://dl.linux-sunxi.org/A64/A64_Datasheet_V1.1.pdf"><strong>Allwinner A64 User Manual</strong></a> tells us the UART Register for enabling UART Interrupts (pic above)…</p>
|
||
<ul>
|
||
<li>
|
||
<p><strong>Interrupt Enable Register (UART_IER)</strong></p>
|
||
<p>(At Offset <code>0x04</code>)</p>
|
||
</li>
|
||
</ul>
|
||
<p>This is how we enable (or disable) <strong>UART Receive Interrupts</strong>: <a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/a64/a64_serial.c#L755-L789">a64_serial.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// Enable or disable Receive Interrupts for PinePhone Allwinner A64 UART
|
||
static void a64_uart_rxint(struct uart_dev_s *dev, bool enable)
|
||
{
|
||
// Write to UART Interrupt Enable Register (UART_IER)
|
||
// Offset: 0x0004
|
||
uint8_t *uart_ier = (uint8_t *)
|
||
(A64_UART0_ADDR + 0x04);
|
||
|
||
// Bit 0: Enable Received Data Available Interrupt (ERBFI)
|
||
// This is used to enable/disable the generation of Received Data Available Interrupt and the Character Timeout Interrupt (if in FIFO mode and FIFOs enabled). These are the second highest priority interrupts.
|
||
// 0: Disable
|
||
// 1: Enable
|
||
if (enable) { *uart_ier |= 0b00000001; }
|
||
else { *uart_ier &= 0b11111110; }
|
||
}</code></pre></div>
|
||
<p>And this is how we enable (or disable) <strong>UART Transmit Interrupts</strong>: <a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/a64/a64_serial.c#L840-L874">a64_serial.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// Enable or disable Transmit Interrupts for PinePhone Allwinner A64 UART
|
||
static void a64_uart_txint(struct uart_dev_s *dev, bool enable)
|
||
{
|
||
// Write to UART Interrupt Enable Register (UART_IER)
|
||
// Offset: 0x0004
|
||
uint8_t *uart_ier = (uint8_t *)
|
||
(A64_UART0_ADDR + 0x04);
|
||
|
||
// Bit 1: Enable Transmit Holding Register Empty Interrupt (ETBEI)
|
||
// This is used to enable/disable the generation of Transmitter Holding Register Empty Interrupt. This is the third highest priority interrupt.
|
||
// 0: Disable
|
||
// 1: Enable
|
||
if (enable) { *uart_ier |= 0b00000010; }
|
||
else { *uart_ier &= 0b11111101; }
|
||
}</code></pre></div><h2 id="handle-interrupt"><a class="doc-anchor" href="#handle-interrupt">§</a>3.3 Handle Interrupt</h2>
|
||
<p>Earlier we’ve attached <strong><code>a64_uart_irq_handler</code></strong> as our Interrupt Handler for UART Interrupts…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// Attach UART Interrupt Handler
|
||
int ret = irq_attach(
|
||
UART_IRQ, // Interrupt Number
|
||
a64_uart_irq_handler, // Interrupt Handler
|
||
dev // NuttX Device
|
||
);</code></pre></div>
|
||
<p>Let’s look inside the Interrupt Handler.</p>
|
||
<p>When UART triggers an Interrupt, it stores the cause of the Interrupt in the <strong>Interrupt Identity Register (UART_IIR)</strong>, Offset <code>0x08</code>.</p>
|
||
<p><strong>Bits 0 to 3</strong> of the Interrupt Identity Register are…</p>
|
||
<ul>
|
||
<li>
|
||
<p><strong>Binary <code>0010</code></strong> if the Transmit Holding Register is empty</p>
|
||
<p>(Hence we should transmit more data)</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>Binary <code>0100</code></strong> if there’s Receive Data available</p>
|
||
<p>(Hence we should read the data received)</p>
|
||
</li>
|
||
</ul>
|
||
<p>This is how we handle these conditions in our Interrupt Handler: <a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/a64/a64_serial.c#L301-L415">a64_serial.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// Interrupt Handler for PinePhone Allwinner A64 UART
|
||
static int a64_uart_irq_handler(int irq, void *context, void *arg)
|
||
{
|
||
// Get the UART Device
|
||
struct uart_dev_s *dev = (struct uart_dev_s *)arg;
|
||
UNUSED(irq);
|
||
UNUSED(context);
|
||
DEBUGASSERT(dev != NULL && dev->priv != NULL);
|
||
|
||
// Read UART Interrupt Identity Register (UART_IIR)
|
||
// Offset: 0x0008
|
||
const uint8_t *uart_iir = (const uint8_t *) (A64_UART0_ADDR + 0x08);
|
||
|
||
// Bits 3 to 0: Interrupt ID
|
||
// This indicates the highest priority pending interrupt which can be one of the following types:
|
||
// 0000: modem status
|
||
// 0001: no interrupt pending
|
||
// 0010: THR empty
|
||
// 0100: received data available
|
||
// 0110: receiver line status
|
||
// 0111: busy detect
|
||
// 1100: character timeout
|
||
// Bit 3 indicates an interrupt can only occur when the FIFOs are enabled and used to distinguish a Character Timeout condition interrupt.
|
||
uint8_t int_id = (*uart_iir) & 0b1111;
|
||
|
||
// 0100: If received data is available...
|
||
if (int_id == 0b0100) {
|
||
// Receive the data
|
||
uart_recvchars(dev);
|
||
|
||
// 0010: If THR is empty (Transmit Holding Register)...
|
||
} else if (int_id == 0b0010) {
|
||
// Transmit the data
|
||
uart_xmitchars(dev);
|
||
|
||
}
|
||
return OK;
|
||
}</code></pre></div>
|
||
<p>Let’s talk about <strong><code>uart_recvchars</code></strong> and <strong><code>uart_xmitchars</code></strong>…</p>
|
||
<h2 id="uart-transmit"><a class="doc-anchor" href="#uart-transmit">§</a>3.4 UART Transmit</h2>
|
||
<p><em>What’s <code>uart_xmitchars</code>?</em></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// 0010: If THR is empty (Transmit Holding Register)...
|
||
if (int_id == 0b0010) {
|
||
// Transmit the data
|
||
uart_xmitchars(dev);</code></pre></div>
|
||
<p>If the Transmit Holding Register is empty, our Interrupt Handler calls <strong><code>uart_xmitchars</code></strong> to transmit more data.</p>
|
||
<p><strong><code>uart_xmitchars</code></strong> is a NuttX System Function that calls <a href="https://lupyuen.github.io/articles/serial#transmit-uart"><strong><code>a64_uart_send</code></strong></a> to transmit data to UART, while buffering the UART Output Data.</p>
|
||
<p><a href="https://lupyuen.github.io/articles/serial#transmit-uart">(We’ve seen <strong><code>a64_uart_send</code></strong> earlier)</a></p>
|
||
<p><strong><code>uart_xmitchars</code></strong> will also call <a href="https://lupyuen.github.io/articles/serial#wait-to-transmit"><strong><code>a64_uart_txready</code></strong></a> to check if the UART Port is ready to accept more data, before transmitting the data.</p>
|
||
<p>Now for the other direction…</p>
|
||
<h2 id="uart-receive"><a class="doc-anchor" href="#uart-receive">§</a>3.5 UART Receive</h2>
|
||
<p><em>What’s <code>uart_recvchars</code>?</em></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// 0100: If received data is available...
|
||
if (int_id == 0b0100) {
|
||
// Receive the data
|
||
uart_recvchars(dev);</code></pre></div>
|
||
<p>If Received Data is available, our Interrupt Handler calls <strong><code>uart_recvchars</code></strong> to read the Received Data.</p>
|
||
<p><strong><code>uart_recvchars</code></strong> is a NuttX System Function that calls <a href="https://lupyuen.github.io/articles/serial#receive-uart"><strong><code>a64_uart_receive</code></strong></a> to receive data from UART, while buffering the UART Input Data.</p>
|
||
<p><a href="https://lupyuen.github.io/articles/serial#receive-uart">(We’ve seen <strong><code>a64_uart_receive</code></strong> earlier)</a></p>
|
||
<p><strong><code>uart_recvchars</code></strong> will also call <a href="https://lupyuen.github.io/articles/serial#wait-to-receive"><strong><code>a64_uart_rxavailable</code></strong></a> to check if Received Data is actually available, before reading the data.</p>
|
||
<p>And that’s how we transmit and receive UART Data with Interrupts!</p>
|
||
<h1 id="initialise-uart"><a class="doc-anchor" href="#initialise-uart">§</a>4 Initialise UART</h1>
|
||
<p><em>Did we forget something?</em></p>
|
||
<p>Rightfully we should initialise the <strong>UART Baud Rate</strong>: <a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/a64/a64_serial.c#L301-L582">a64_serial.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// Setup PinePhone Allwinner A64 UART
|
||
static int a64_uart_setup(struct uart_dev_s *dev)
|
||
{
|
||
// TODO: Set the Baud Rate
|
||
return 0;
|
||
}</code></pre></div>
|
||
<p>PinePhone’s <strong>U-Boot Bootloader</strong> has kindly set the Baud Rate for us (115.2 kbps), so we skip this for now. More about the bootloader…</p>
|
||
<ul>
|
||
<li><a href="https://lupyuen.github.io/articles/uboot"><strong>“PinePhone boots Apache NuttX RTOS”</strong></a></li>
|
||
</ul>
|
||
<p>Later when need to set the <strong>UART Baud Rate</strong> for other UART Ports, the steps are explained here…</p>
|
||
<ul>
|
||
<li>
|
||
<p><a href="https://github.com/lupyuen/pinephone-nuttx#configure-uart-port"><strong>“Configure UART Port”</strong></a></p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://github.com/lupyuen/pinephone-nuttx#test-uart3-port"><strong>“Test UART3 Port”</strong></a></p>
|
||
</li>
|
||
</ul>
|
||
<p><em>What about UART Shutdown?</em></p>
|
||
<p>To shutdown a UART Port, we disable the interrupts: <a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/a64/a64_serial.c#L584-L605">a64_serial.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// Shutdown PinePhone Allwinner A64 UART
|
||
static void a64_uart_shutdown(struct uart_dev_s *dev)
|
||
{
|
||
// Disable the Receive and Transmit Interrupts
|
||
a64_uart_rxint(dev, false);
|
||
a64_uart_txint(dev, false);
|
||
}</code></pre></div>
|
||
<p>The Shutdown Function will <strong>never be called for UART0</strong> because it’s always active as the Serial Console.</p>
|
||
<p>But for UART1 to UART4, the Shutdown Function will be called when our NuttX App closes the UART.</p>
|
||
<p><em>Anything else?</em></p>
|
||
<p>One last thing: For NuttX we need to implement a simple <strong>I/O Control Handler <code>ioctl</code></strong>: <a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/a64/a64_serial.c#L690-L725">a64_serial.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// I/O Control for PinePhone Allwinner A64 UART
|
||
static int a64_uart_ioctl(struct file *filep, int cmd, unsigned long arg)
|
||
{
|
||
int ret = OK;
|
||
switch (cmd)
|
||
{
|
||
case TIOCSBRK: /* BSD compatibility: Turn break on, unconditionally */
|
||
case TIOCCBRK: /* BSD compatibility: Turn break off, unconditionally */
|
||
default:
|
||
{
|
||
ret = -ENOTTY;
|
||
break;
|
||
}
|
||
}
|
||
return ret;
|
||
}</code></pre></div>
|
||
<p>We’re almost done with our PinePhone UART Driver for NuttX!</p>
|
||
<h1 id="nuttx-uart-driver"><a class="doc-anchor" href="#nuttx-uart-driver">§</a>5 NuttX UART Driver</h1>
|
||
<p><em>How do we create a PinePhone UART Driver for NuttX?</em></p>
|
||
<p>We’ve implemented all the <strong>UART Operations</strong> for our PinePhone UART Driver…</p>
|
||
<ul>
|
||
<li>
|
||
<p><a href="https://lupyuen.github.io/articles/serial#initialise-uart"><strong><code>a64_uart_setup</code></strong></a>: Initialise UART Driver</p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://lupyuen.github.io/articles/serial#initialise-uart"><strong><code>a64_uart_shutdown</code></strong></a>: Shutdown UART Driver</p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://lupyuen.github.io/articles/serial#attach-interrupt-handler"><strong><code>a64_uart_attach</code></strong></a>: Attach Interrupt Handler</p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://lupyuen.github.io/articles/serial#attach-interrupt-handler"><strong><code>a64_uart_detach</code></strong></a>: Detach Interrupt Handler</p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://lupyuen.github.io/articles/serial#initialise-uart"><strong><code>a64_uart_ioctl</code></strong></a>: I/O Control</p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://lupyuen.github.io/articles/serial#receive-uart"><strong><code>a64_uart_receive</code></strong></a>: Receive Data</p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://lupyuen.github.io/articles/serial#enable-interrupt"><strong><code>a64_uart_rxint</code></strong></a>: Enable / Disable Receive Interrupt</p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://lupyuen.github.io/articles/serial#wait-to-receive"><strong><code>a64_uart_rxavailable</code></strong></a>: Is Received Data Available</p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://lupyuen.github.io/articles/serial#transmit-uart"><strong><code>a64_uart_send</code></strong></a>: Transmit Data</p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://lupyuen.github.io/articles/serial#enable-interrupt"><strong><code>a64_uart_txint</code></strong></a>: Enable / Disable Transmit Interrupt</p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://lupyuen.github.io/articles/serial#wait-to-transmit"><strong><code>a64_uart_txready</code></strong></a>: Is UART Ready to Transmit</p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://lupyuen.github.io/articles/serial#wait-to-transmit"><strong><code>a64_uart_txempty</code></strong></a>: Is Transmit Buffer Empty</p>
|
||
</li>
|
||
</ul>
|
||
<p>NuttX expects us to wrap the UART Operations into a <strong><code>uart_ops_s</code></strong> Struct like so: <a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/a64/a64_serial.c#L1002-L1021">a64_serial.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// Serial driver UART operations for PinePhone Allwinner A64 UART
|
||
static const struct uart_ops_s g_uart_ops =
|
||
{
|
||
.setup = a64_uart_setup,
|
||
.shutdown = a64_uart_shutdown,
|
||
.attach = a64_uart_attach,
|
||
.detach = a64_uart_detach,
|
||
.ioctl = a64_uart_ioctl,
|
||
.receive = a64_uart_receive,
|
||
.rxint = a64_uart_rxint,
|
||
.rxavailable = a64_uart_rxavailable,
|
||
#ifdef CONFIG_SERIAL_IFLOWCONTROL
|
||
.rxflowcontrol = NULL,
|
||
#endif
|
||
.send = a64_uart_send,
|
||
.txint = a64_uart_txint,
|
||
.txready = a64_uart_txready,
|
||
.txempty = a64_uart_txempty,
|
||
};</code></pre></div>
|
||
<p>We should <strong>start our UART Driver</strong> like this: <a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/a64/a64_serial.c#L1323-L1398">a64_serial.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// UART0 is console and ttyS0
|
||
#define CONSOLE_DEV g_uart0port
|
||
#define TTYS0_DEV g_uart0port
|
||
|
||
// Performs the low level UART initialization early in
|
||
// debug so that the serial console will be available
|
||
// during bootup. This must be called before arm_serialinit.
|
||
void a64_earlyserialinit(void)
|
||
{
|
||
// NOTE: This function assumes that low level hardware configuration
|
||
// -- including all clocking and pin configuration -- was performed
|
||
// earlier by U-Boot Bootloader.
|
||
|
||
// Enable the console UART. The other UARTs will be initialized if and
|
||
// when they are first opened.
|
||
CONSOLE_DEV.isconsole = true;
|
||
a64_uart_setup(&CONSOLE_DEV);
|
||
|
||
// Omitted: Init UART1 to UART4, if required</code></pre></div>
|
||
<p><a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/a64/a64_serial.c#L1051-L1069">(<strong><code>g_uart0port</code></strong> contains the UART Operations <strong><code>g_uart_ops</code></strong>)</a></p>
|
||
<p>Then we expose UART0 as <strong><code>/dev/console</code></strong> and <strong><code>/dev/ttyS0</code></strong>: <a href="https://github.com/apache/nuttx/blob/master/arch/arm64/src/a64/a64_serial.c#L143-L1501">a64_serial.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// Register serial console and serial ports. This assumes
|
||
// that imx_earlyserialinit was called previously.
|
||
void arm64_serialinit(void)
|
||
{
|
||
// Register UART0 as /dev/console
|
||
int ret = uart_register("/dev/console", &CONSOLE_DEV);
|
||
if (ret < 0) { _err("Register /dev/console failed, ret=%d\n", ret); }
|
||
|
||
// Register UART0 as /dev/ttyS0
|
||
ret = uart_register("/dev/ttyS0", &TTYS0_DEV);
|
||
if (ret < 0) { _err("Register /dev/ttyS0 failed, ret=%d\n", ret); }
|
||
|
||
// Omitted: Register UART1 to UART4 as /dev/ttyS1 to /dev/ttyS4
|
||
// TTY Numbering is always Sequential:
|
||
// If UART1 is disabled, then UART2 becomes /dev/ttyS1</code></pre></div>
|
||
<p>And we’re done with our PinePhone UART Driver for NuttX!</p>
|
||
<h1 id="uart-in-action"><a class="doc-anchor" href="#uart-in-action">§</a>6 UART In Action</h1>
|
||
<p>Let’s watch our UART Driver in action!</p>
|
||
<p>Follow these steps to <strong>build NuttX</strong> and copy to Jumpdrive microSD…</p>
|
||
<ul>
|
||
<li><a href="https://lupyuen.github.io/articles/uboot#pinephone-boots-nuttx"><strong>“PinePhone Boots NuttX”</strong></a></li>
|
||
</ul>
|
||
<p>Insert the microSD into PinePhone and power it on. We should see…</p>
|
||
<div class="example-wrap"><pre class="language-text"><code>Starting kernel ...
|
||
|
||
HELLO NUTTX ON PINEPHONE!
|
||
- Ready to Boot CPU
|
||
- Boot from EL2
|
||
- Boot from EL1
|
||
- Boot to C runtime for OS Initialize
|
||
|
||
nx_start: Entry
|
||
up_allocate_heap: heap_start=0x0x400c4000, heap_size=0x7f3c000
|
||
|
||
arm64_gic_initialize: TODO: Init GIC for PinePhone
|
||
arm64_gic_initialize: CONFIG_GICD_BASE=0x1c81000
|
||
arm64_gic_initialize: CONFIG_GICR_BASE=0x1c82000
|
||
arm64_gic_initialize: GIC Version is 2
|
||
|
||
up_timer_initialize: up_timer_initialize: cp15 timer(s) running at 24.00MHz, cycle 24000
|
||
up_timer_initialize: _vector_table=0x400a7000
|
||
up_timer_initialize: Before writing: vbar_el1=0x40227000
|
||
up_timer_initialize: After writing: vbar_el1=0x400a7000
|
||
|
||
uart_register: Registering /dev/console
|
||
uart_register: Registering /dev/ttyS0
|
||
|
||
work_start_highpri: Starting high-priority kernel worker thread(s)
|
||
nx_start_application: Starting init thread
|
||
lib_cxx_initialize: _sinit: 0x400a7000 _einit: 0x400a7000 _stext: 0x40080000 _etext: 0x400a8000
|
||
nsh: sysinit: fopen failed: 2
|
||
|
||
nshn:x _msktfaarttf:s :C PcUo0m:m aBnedg innonti nfgo uInddle L oNouptt
|
||
Shell (NSH) NuttX-10.3.0-RC2
|
||
nsh> </code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/pinephone-nuttx#garbled-console-output">(Yeah the output is slightly garbled, here’s the workaround)</a></p>
|
||
<p>Now that we handle UART Interrupts, <strong>NuttX Shell</strong> works perfectly OK on PinePhone…</p>
|
||
<div class="example-wrap"><pre class="language-text"><code>nsh> uname -a
|
||
NuttX 10.3.0-RC2 fc909c6-dirty Sep 1 2022 17:05:44 arm64 pinephone
|
||
|
||
nsh> help
|
||
help usage: help [-v] [<cmd>]
|
||
|
||
. cd dmesg help mount rmdir true xd
|
||
[ cp echo hexdump mv set truncate
|
||
? cmp exec kill printf sleep uname
|
||
basename dirname exit ls ps source umount
|
||
break dd false mkdir pwd test unset
|
||
cat df free mkrd rm time usleep
|
||
|
||
Builtin Apps:
|
||
getprime hello nsh ostest sh
|
||
|
||
nsh> hello
|
||
task_spawn: name=hello entry=0x4009b1a0 file_actions=0x400c9580 attr=0x400c9588 argv=0x400c96d0
|
||
spawn_execattrs: Setting policy=2 priority=100 for pid=3
|
||
Hello, World!!
|
||
|
||
nsh> ls /dev
|
||
/dev:
|
||
console
|
||
null
|
||
ram0
|
||
ram2
|
||
ttyS0
|
||
zero</code></pre></div>
|
||
<p><a href="https://youtube.com/shorts/WmRzfCiWV6o?feature=share"><strong>Watch the Demo on YouTube</strong></a></p>
|
||
<p><em>What about other UART Ports? (Besides UART0)</em></p>
|
||
<p>We’re adding support for <strong>other UART Ports</strong>, like UART3 for PinePhone’s 4G LTE Modem…</p>
|
||
<ul>
|
||
<li>
|
||
<p><a href="https://lupyuen.github.io/articles/serial#appendix-uart-ports-on-pinephone"><strong>“UART Ports on PinePhone”</strong></a></p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://github.com/lupyuen/pinephone-nuttx#configure-uart-port"><strong>“Configure UART Port”</strong></a></p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://github.com/lupyuen/pinephone-nuttx#test-uart3-port"><strong>“Test UART3 Port”</strong></a></p>
|
||
</li>
|
||
</ul>
|
||
<p>The changes have been upstreamed to NuttX Mainline…</p>
|
||
<ul>
|
||
<li><a href="https://github.com/apache/nuttx/pull/9243"><strong>Pull Request: Support multiple UART Ports</strong></a></li>
|
||
</ul>
|
||
<p><em>How do we enable a UART Port? Like UART3?</em></p>
|
||
<p>Head over to the NuttX Build Configuration…</p>
|
||
<div class="example-wrap"><pre class="language-bash"><code>make menuconfig</code></pre></div>
|
||
<p>Then select…</p>
|
||
<ul>
|
||
<li>System Type > Allwinner A64 Peripheral Selection > UART3</li>
|
||
</ul>
|
||
<p>To configure the Baud Rate, Bits, Parity and Stop Bits…</p>
|
||
<ul>
|
||
<li>Device Drivers > Serial Driver Support > UART3 Configuration</li>
|
||
</ul>
|
||
<p>(The default Baud Rate / Bits / Parity / Stop Bits work OK with the PinePhone LTE Modem on UART3: 115.2 kbps / 8 Bits / No Parity / 1 Stop Bit)</p>
|
||
<h1 id="whats-next"><a class="doc-anchor" href="#whats-next">§</a>7 What’s Next</h1>
|
||
<p>Today we talked about PinePhone UART and how we created the NuttX UART Driver.</p>
|
||
<p>There’s plenty to be done for NuttX on PinePhone, please lemme know if you would like to join me 🙏</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://www.reddit.com/r/PINE64official/comments/xafz9o/nuttx_rtos_for_pinephone_uart_driver/"><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/serial.md"><strong>lupyuen.github.io/src/serial.md</strong></a></p>
|
||
<h1 id="appendix-uart-ports-on-pinephone"><a class="doc-anchor" href="#appendix-uart-ports-on-pinephone">§</a>8 Appendix: UART Ports on PinePhone</h1>
|
||
<p><em>Which Allwinner A64 UART Ports are used in PinePhone?</em></p>
|
||
<p>According to the <a href="https://files.pine64.org/doc/PinePhone/PinePhone%20v1.2b%20Released%20Schematic.pdf"><strong>PinePhone Schematic</strong></a>, the following <strong>UART Ports</strong> in Allwinner A64 are connected…</p>
|
||
<ul>
|
||
<li>
|
||
<p><strong>UART0:</strong> Serial Console</p>
|
||
<p>Pins <strong>PB8</strong> <em>(TX)</em> and <strong>PB9</strong> <em>(RX)</em></p>
|
||
<p>(Assigned as <strong>/dev/ttyS0</strong>)</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>UART1:</strong> Bluetooth Module (Realtek RTL8723CS)</p>
|
||
<p>Pins <strong>PG6</strong> <em>(TX)</em>, <strong>PG7</strong> <em>(RX)</em>, <strong>PG8</strong> <em>(RTS)</em> and <strong>PG9</strong> <em>(CTS)</em></p>
|
||
<p>(TODO: Assign as <strong>/dev/ttyS1</strong>)</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>UART2:</strong> Unused</p>
|
||
<p>Pins <strong>PB0</strong> and <strong>PB1</strong></p>
|
||
<p>(Wired to Light Sensor STK3311 and Compass Sensor AK09911)</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>UART3:</strong> 4G LTE Modem (Quectel EG25-G)</p>
|
||
<p>Pins <strong>PD0</strong> <em>(TX)</em> and <strong>PD1</strong> <em>(RX)</em></p>
|
||
<p>(TODO: Assign as <strong>/dev/ttyS3</strong>)</p>
|
||
<p><a href="https://lupyuen.github.io/articles/lte#test-uart-with-nuttx">(More about this)</a></p>
|
||
</li>
|
||
<li>
|
||
<p><strong>UART4:</strong> 4G LTE Modem (Quectel EG25-G)</p>
|
||
<p>Pins <strong>PD4</strong> and <strong>PD5</strong></p>
|
||
<p>(Wired to RTS and CTS, not really a UART)</p>
|
||
<p><a href="https://lupyuen.github.io/articles/lte#test-uart-with-nuttx">(More about this)</a></p>
|
||
</li>
|
||
</ul>
|
||
<p>TODO: Disable UART2 and /dev/ttyS2 so that UART3 maps neatly to /dev/ttyS3. <a href="https://github.com/apache/nuttx/pull/9304#discussion_r1195862416">(See this)</a></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> |