NuttX RTOS for PinePhone: UART Driver

📝 9 Sep 2022

PinePhone Hacking with Pinebook Pro and BLÅHAJ

PinePhone Hacking with Pinebook Pro and BLÅHAJ

UPDATE: PinePhone is now officially supported by Apache NuttX RTOS (See this)

Last week we spoke about creating our own Operating System for Pine64 PinePhone

Our PinePhone OS will be awfully quiet until we implement UART Input and Output. (For the Serial Debug Console)

Today we’ll learn about the UART Controller for the Allwinner A64 SoC inside PinePhone…

And how we implemented PinePhone’s UART Driver for Apache NuttX RTOS.

Let’s dive into our NuttX Porting Journal and learn how we made PinePhone chatty over UART…

Allwinner A64 UART Controller Registers

Allwinner A64 UART Controller Registers

§1 UART Controller

Our operating system will print some output on PinePhone’s Serial Debug Console as it runs. (And receive input too)

To do that, we’ll talk to the UART Controller on the Allwinner A64 SoC…

Flip the A64 User Manual to page 562 (“UART”) and we’ll see the UART Registers. (Pic above)

PinePhone’s Serial Console is connected to UART0 at Base Address 0x01C2 8000

Which we define like so: a64_memorymap.h

/* A64 UART0 Base Address */
#define A64_UART0_ADDR      0x1C28000

/* A64 UART0 IRQ */
#define A64_UART0_IRQ       32         

(A64_UART0_IRQ is defined here)

(We’ll talk about A64_UART0_IRQ in a while)

PinePhone connected to USB Serial Debug Cable

Check that PinePhone is connected to our computer with the USB Serial Debug Cable (pic above) at 115.2 kbps…

Let’s read and write UART Data the easier (inefficient) way, via Polling…

A64 UART Receive and Transmit Registers UART_RBR and UART_THR

A64 UART Receive and Transmit Registers UART_RBR and UART_THR

§2 UART With Polling

Page 563 of the Allwinner A64 User Manual tells us the UART Registers for reading and writing UART Data (pic above)…

Let’s write some UART Data…

§2.1 Transmit UART

The Transmit Holding Register (THR) is at address 0x01C2 8000. (Since Offset is 0)

We’ll write our output data to 0x01C2 8000, byte by byte, and the data will appear in the Serial Console: a64_serial.c

// 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;
}

So this code…

a64_uart_send(NULL, 'H');
a64_uart_send(NULL, 'E');
a64_uart_send(NULL, 'Y');

Will print this to PinePhone’s Serial Console…

HEY

Will this work if we send a huge chunk of text?

Nope, we’ll overflow the Transmit FIFO Buffer!

The pic below shows what happens if we print too much… The overflow characters will get dropped. (Hence the solitary “f”)

To fix this, we wait for the UART Port to be ready before we transmit. We’ll see how in the next section.

What’s uart_dev_s?

That’s the convention that NuttX RTOS expects for UART Drivers.

We may drop the parameter if we’re not on NuttX.

Why we wait for the UART Port before we transmit

Why we wait for the UART Port before we transmit

§2.2 Wait To Transmit

Let’s check if the UART Port is ready to accept output data for transmission.

We read Bit 5 of the Line Status Register (UART_LSR) at Offset 0x14: a64_serial.c

// 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)
}

Now we can print to the Serial Console without dropping characters

// Wait for UART Port to be ready
while (!a64_uart_txready(NULL)) {}

// Send one byte of data
a64_uart_send(NULL, 'A');

Busy Wait in an Empty Loop? That’s wasteful ain’t it?

Yes we’re wasting CPU Cycles waiting for UART.

That’s why NuttX and other Operating Systems will insist that we implement UART with Interrupts (instead of Polling).

We’ll cover this in a while.

Also note that PinePhone’s UART Port has a Transmit FIFO Buffer of 16 characters.

Our UART Driver doesn’t check for the available space in the Transmit FIFO Buffer.

For efficiency, we should probably fix this: a64_serial.c

// 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);
}

Moving on from UART Transmit to Receive…

A64 UART Registers UART_RBR and UART_THR

A64 UART Registers UART_RBR and UART_THR

§2.3 Receive UART

Now that PinePhone can talk to us, let’s make sure we can talk back!

Anything that we type into PinePhone’s Serial Console will appear in the Receiver Buffer Register (RBR), byte by byte.

The Receiver Buffer Register is at address 0x01C2 8000. (Since Offset is 0). This how we read it: a64_serial.c

// 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;
}

(We may drop the dev and status parameters if we’re not on NuttX)

But don’t read the UART Input yet! We need to wait for the UART Input to be available…

§2.4 Wait To Receive

Let’s check if there’s UART Input ready to be read from the UART Port.

We read Bit 0 of the Line Status Register (UART_LSR) at Offset 0x14: a64_serial.c

// 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
}

Now we’re ready to read UART Input…

// 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);

Again… This looks like a waste of CPU Cycles?

Indeed, UART Input won’t work well on multitasking operating systems unless we do it with Interrupts. (Coming up in a sec!)

§2.5 Arm64 Assembly

Is it safe to do UART Output when our PinePhone OS is booting?

Yep we may call a64_uart_send and a64_uart_txready when our OS is booting.

For Arm64 Assembly we have something similar: This Arm64 Assembly Macro is super helpful for printing debug messages in our Arm64 Startup Code…

Don’t we need to set the Baud Rate for the UART Port?

Right now we don’t initialise the UART Port because U-Boot has kindly done it for us. (At 115.2 kbps)

We’ll come back to this in a while.

§3 UART With Interrupts

Earlier we saw UART with Polling, and how inefficient it can get. Now we talk about UART with Interrupts and how we…

Does NuttX use UART Polling or Interrupts?

NuttX uses both Polling-based UART and Interrupt-driven UART. NuttX OS writes System Logs (syslog) the UART Polling way…

sinfo("This is printed on UART with Polling\n");

(By calling up_putc)

And NuttX Apps print App Messages the UART Interrupt Way…

printf("This is printed on UART with Interrupts\n");

So if we don’t see any App Messages in NuttX, check that the UART Interrupts are OK.

Shared Peripheral Interrupts for Allwinner A64’s Generic Interrupt Controller

Shared Peripheral Interrupts for Allwinner A64’s Generic Interrupt Controller

§3.1 Attach Interrupt Handler

PinePhone’s UART Controller will trigger an Interrupt for Transmit and Receive Events when…

The Allwinner A64 User Manual (page 211, “GIC”) reveals that UART0 Interrupts will be triggered at Interrupt Number 32. (Pic above)

Let’s attach our Interrupt Handler to handle the UART Interrupts: a64_serial.c

// 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;
}

a64_uart_irq_handler is our UART Interrupt Handler, we’ll explain in a while.

What’s irq_attach?

// Attach UART Interrupt Handler
int ret = irq_attach(
  UART_IRQ,              // Interrupt Number
  a64_uart_irq_handler,  // Interrupt Handler
  dev                    // NuttX Device
);

On NuttX, we call irq_attach to attach an Interrupt Handler to the UART Controller.

What’s arm64_gic_irq_set_priority?

// 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
);

Arm64 Interrupts are managed on PinePhone by the Generic Interrupt Controller in Allwinner A64…

The code above calls the Generic Interrupt Controller to set the priority of the UART Interrupt.

Later when we’re done with UART Interrupts, we should detach the Interrupt Handler: a64_serial.c

// 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);
}

A64 UART Interrupt Enable Register UART_IER

A64 UART Interrupt Enable Register UART_IER

§3.2 Enable Interrupt

UART Interupts won’t happen until we enable UART Interrupts.

Page 565 of the Allwinner A64 User Manual tells us the UART Register for enabling UART Interrupts (pic above)…

This is how we enable (or disable) UART Receive Interrupts: a64_serial.c

// 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; }
}

And this is how we enable (or disable) UART Transmit Interrupts: a64_serial.c

// 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; }
}

§3.3 Handle Interrupt

Earlier we’ve attached a64_uart_irq_handler as our Interrupt Handler for UART Interrupts…

// Attach UART Interrupt Handler
int ret = irq_attach(
  UART_IRQ,              // Interrupt Number
  a64_uart_irq_handler,  // Interrupt Handler
  dev                    // NuttX Device
);

Let’s look inside the Interrupt Handler.

When UART triggers an Interrupt, it stores the cause of the Interrupt in the Interrupt Identity Register (UART_IIR), Offset 0x08.

Bits 0 to 3 of the Interrupt Identity Register are…

This is how we handle these conditions in our Interrupt Handler: a64_serial.c

// 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;
}

Let’s talk about uart_recvchars and uart_xmitchars

§3.4 UART Transmit

What’s uart_xmitchars?

// 0010: If THR is empty (Transmit Holding Register)...
if (int_id == 0b0010) {
  // Transmit the data
  uart_xmitchars(dev);

If the Transmit Holding Register is empty, our Interrupt Handler calls uart_xmitchars to transmit more data.

uart_xmitchars is a NuttX System Function that calls a64_uart_send to transmit data to UART, while buffering the UART Output Data.

(We’ve seen a64_uart_send earlier)

uart_xmitchars will also call a64_uart_txready to check if the UART Port is ready to accept more data, before transmitting the data.

Now for the other direction…

§3.5 UART Receive

What’s uart_recvchars?

// 0100: If received data is available...
if (int_id == 0b0100) {
  // Receive the data
  uart_recvchars(dev);

If Received Data is available, our Interrupt Handler calls uart_recvchars to read the Received Data.

uart_recvchars is a NuttX System Function that calls a64_uart_receive to receive data from UART, while buffering the UART Input Data.

(We’ve seen a64_uart_receive earlier)

uart_recvchars will also call a64_uart_rxavailable to check if Received Data is actually available, before reading the data.

And that’s how we transmit and receive UART Data with Interrupts!

§4 Initialise UART

Did we forget something?

Rightfully we should initialise the UART Baud Rate: a64_serial.c

// Setup PinePhone Allwinner A64 UART
static int a64_uart_setup(struct uart_dev_s *dev)
{
  // TODO: Set the Baud Rate
  return 0;
}

PinePhone’s U-Boot Bootloader has kindly set the Baud Rate for us (115.2 kbps), so we skip this for now. More about the bootloader…

Later when need to set the UART Baud Rate for other UART Ports, the steps are explained here…

What about UART Shutdown?

To shutdown a UART Port, we disable the interrupts: a64_serial.c

// 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);
}

The Shutdown Function will never be called for UART0 because it’s always active as the Serial Console.

But for UART1 to UART4, the Shutdown Function will be called when our NuttX App closes the UART.

Anything else?

One last thing: For NuttX we need to implement a simple I/O Control Handler ioctl: a64_serial.c

// 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;
}

We’re almost done with our PinePhone UART Driver for NuttX!

§5 NuttX UART Driver

How do we create a PinePhone UART Driver for NuttX?

We’ve implemented all the UART Operations for our PinePhone UART Driver…

NuttX expects us to wrap the UART Operations into a uart_ops_s Struct like so: a64_serial.c

//  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,
};

We should start our UART Driver like this: a64_serial.c

// 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

(g_uart0port contains the UART Operations g_uart_ops)

Then we expose UART0 as /dev/console and /dev/ttyS0: a64_serial.c

// 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

And we’re done with our PinePhone UART Driver for NuttX!

§6 UART In Action

Let’s watch our UART Driver in action!

Follow these steps to build NuttX and copy to Jumpdrive microSD…

Insert the microSD into PinePhone and power it on. We should see…

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> 

(Yeah the output is slightly garbled, here’s the workaround)

Now that we handle UART Interrupts, NuttX Shell works perfectly OK on PinePhone…

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

Watch the Demo on YouTube

What about other UART Ports? (Besides UART0)

We’re adding support for other UART Ports, like UART3 for PinePhone’s 4G LTE Modem…

The changes have been upstreamed to NuttX Mainline…

How do we enable a UART Port? Like UART3?

Head over to the NuttX Build Configuration…

make menuconfig

Then select…

To configure the Baud Rate, Bits, Parity and Stop Bits…

(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)

§7 What’s Next

Today we talked about PinePhone UART and how we created the NuttX UART Driver.

There’s plenty to be done for NuttX on PinePhone, please lemme know if you would like to join me 🙏

Please check out the other articles on NuttX for PinePhone…

Many Thanks to my GitHub Sponsors for supporting my work! This article wouldn’t have been possible without your support.

Got a question, comment or suggestion? Create an Issue or submit a Pull Request here…

lupyuen.github.io/src/serial.md

§8 Appendix: UART Ports on PinePhone

Which Allwinner A64 UART Ports are used in PinePhone?

According to the PinePhone Schematic, the following UART Ports in Allwinner A64 are connected…

TODO: Disable UART2 and /dev/ttyS2 so that UART3 maps neatly to /dev/ttyS3. (See this)