Adding UART to Linux Yocto

In this example you will add single UART Lite from FPGA resources and connect it to PMOD connector of Zybo board.

Procedure

  1. Add new hardware with SPI in Vivado and generate XSA file.
  1. Add new XSA file in Yocto.
  1. Configure Linux kernel to use UARTLITE.

Results

Finally, after these steps you will have in Yocto:

  • add single UART Lite in PL connected to AXI and externally to PMOD JC pins 1 (TX) and 2 (RX).

1. In Vivado: add new SPI hardware to obtain XSA file

Add AXI Uartlite in FPGA

  • Add AXI Uartlite IP core
  • Run Connection Automation
  • Remove the UART external connection, click + near Uartlite UART port to see rx and tx lines, then make rx and tx as External and name them as uart0_rx and uart0_tx. Alternatively, you can leave the connections, generate wrapper and see the port names in the wrapper's HDL code.
  • Set UART's baud rate: 115200 bps
  • 8 Data Bits
  • No Parity
  • Connect interrupt:
  • Enable interrupts in Zynq (in not already enabled): Zynq/Interrupts/Fabric interrupts/PL-PS Interrupt Ports/IRQ_F2P[15:0].
  • Connect AXI Uartlite interrupt  pin to Zynq IRQ_F2P input. If you already have something connected to Zynq's interrupt input (i.e. SPI), then add Concat IP block and use it to join interrupts from various sources. If you already have Concat block, increment its input size, then connect interrupt by dragging a wire connection with the mouse.

Edit constrains

Example of XDC (watch out of pin assignments, adjust to your needs!):

##Pmod Header JC                                                                                                                  
set_property -dict { PACKAGE_PIN T11   IOSTANDARD LVCMOS33     } [get_ports { uart0_rx }]; #IO_L1P_T0_34 Sch=jc_p[2]    JC-3          
set_property -dict { PACKAGE_PIN T10   IOSTANDARD LVCMOS33     } [get_ports { uart0_tx }]; #IO_L1N_T0_34 Sch=jc_n[2]    JC-4

Generate and export           

  • Generate bitstream
  • Export hardware with bitstream to XSA file

2. Configure Linux Yocto - add changed XSA to Yocto

  • Copy newly generated XSA to:
lab_msw_spi/yocto-scarthgap/poky/meta-custom-zybo-z7/recipes-bsp/external-hdf/files/<your_XSA_FILE>
  • Edit the file: lab_msw_spi/yocto-scarthgap/poky/meta-custom-zybo-z7/recipes-bsp/external-hdf/external-hdf_%.bbappend

and update the XSA filename (if changed after adding SPI hardware) :

FILESEXTRAPATHS:prepend := "${THISDIR}/files:"
HDF_EXT  = "xsa"
HDF_URI = file://zybo-z7-20-hw-platform_spi_uart.xsa

3. Configure Linux Yocto - enable CONFIG_SERIAL_UARTLITE in the Yocto kernel

Find the name for the recipe

  • Find the name of virtual provider for the kernel in your project:
bitbake -e core-image-minimal-dev | grep ^PREFERRED_PROVIDER_virtual/kernel=
à PREFERRED_PROVIDER_virtual/kernel="linux-xlnx"
  • Use the  name of the provider (here linux-xlnx) to find the name of the kernel recipe used in your project:
bitbake -e linux-xlnx | grep ^FILE=
à FILE="/home2/ldap/Users/wujek_ldap/lab_msw_spi/yocto-scarthgap/poky/meta-xilinx/meta-xilinx-core/recipes-kernel/linux/linux-xlnx_6.1-v2023.2.bb"
  • Use the name of the recipe (here linux-xlnx_6.1-v2023.2.bb) to create BBAPPEND file to include it in your kernel recipe.

Create BBAPPEND file

  • Create file with the name of  the recipe found above:
nano recipes-kernel/linux/linux-xlnx_6.1-v2023.2.bbappend

with lines (if you are not using SPI, remove parts for spi.cfg):

# Look for files (in folder recipes-kernel/linux/)
FILESEXTRAPATHS:prepend := "${THISDIR}:"
SRC_URI:append = " \
    file://spidev.cfg \
    file://uartlite.cfg \
"
do_configure:append() {
    bbnote "Merging custom kernel config fragments (spidev + uartlite)"
    KCONFIG_CONFIG="${B}/.config" \
       bash ${S}/scripts/kconfig/merge_config.sh -m -O ${B} \
         ${B}/.config ${WORKDIR}/spidev.cfg ${WORKDIR}/uartlite.cfg    oe_runmake -C ${S} O=${B} olddefconfig 
}

The function do_configure:append() injects uartlite.cfg after the XSCT tools from Vivado builds the kernel for Zybo and sets Zybo defaults. The function provides, that the setting CONFIG_SERIAL_UARTLITE=y, CONFIG_SERIAL_UARTLITE_CONSOLE=y will be applied at the end and will not be overwritten by defaults.

Create kernel configuration fragment file

  • Create file:
nano recipes-kernel/linux/linux-xlnx/uartlite.cfg

with line:

CONFIG_SERIAL_UARTLITE=y
CONFIG_SERIAL_UARTLITE_CONSOLE=y

Final check

You should have the following files:

meta-custom-zybo-z7/
├── conf
│   ├── layer.conf
│   └── machine
│       └── zybo-z7.conf
├── COPYING.MIT
├── README
├── recipes-bsp
│   ├── bootfiles
│   │   ├── bootfiles.bb
│   │   └── files
│   │       ├── boot.cmd
│   │       └── uEnv.txt
│   ├── device-tree
│   │   ├── device-tree.bbappend
│   │   └── files
│   │       └── system-user.dtsi
│   └── external-hdf
│       ├── external-hdf_%.bbappend
│       └── files
│           └── zybo-z7-20-hw-platform_spi_uart.xsa (updated XSA)
├── recipes-core
│   └── base-files
│   |   └── base-files_%.bbappend
├── recipes-kernel (new)
│   ├── linux (new)
│   │   ├── linux-xlnx_6.1-v2023.2.bbappend ß(added some lines)
│   │   └── uartlite.cfg ß(added CONFIG_SERIAL_UARTLITE_*)
└── wks
    └── wic-zynq.wks
meta-custom-zybo-z7/
└── recipes-kernel/
    └── linux/
        ├── linux-xlnx_6.1-v2023.2.bbappend
        └── spidev.cfg

Check if setting will work (replace ~/yocto_tmp with your $TMPDIR):

bitbake -c cleansstate linux-xlnx
bitbake -c configure linux-xlnx
grep CONFIG_SERIAL_UARTLITE $(find ~/yocto_tmp/work -type f -name ".config" -path "*/linux-xlnx/*"); 

You should get from the grep:

CONFIG_SERIAL_UARTLITE=y
CONFIG_SERIAL_UARTLITE_CONSOLE=y
CONFIG_SERIAL_UARTLITE_NR_UARTS=16

4. Configure Linux Yocto - correct the order of UART ports in  Device Tree

  • Build the system and decode your current device tree:
cp ~/yocto_tmp/deploy/images/zybo-z7/devicetree/system-top.dtb .
dtc -I dtb -O dts system-top.dtb  > system-top.dts 

Adding new UART, assigned the Linux console to different number, you can see your current Device Tree:

chosen {
       bootargs = "earlycon";
       stdout-path = "serial0:115200n8";
   };
   aliases {
       ethernet0 = "/axi/ethernet@e000b000";
      serial0 = "/amba_pl/serial@42c00000";
      serial1 = "/axi/serial@e0001000";
       spi0 = "/axi/spi@e000d000";
       spi1 = "/axi/spi@e0006000";
       spi2 = "/axi/spi@e0007000";
       spi3 = "/amba_pl/axi_quad_spi@41e00000";
   };

Basing on your Device Tree, change the PS console again to serial0 by adding at the end of file:

nano poky/meta-custom-zybo-z7/recipes-bsp/device-tree/files/system-user.dtsi

this reversed info (also repeat the chosen section):

/ {
    aliases {
        serial0 = "/axi/serial@e0001000";      /* PS UART1 */
        serial1 = "/amba_pl/serial@42c00000";  /* AXI UartLite */
    };
    chosen {
        stdout-path = "serial0:115200n8";
        bootargs = "console=ttyPS1,115200 earlycon";
    };
};
  • Rebuild project:
 bitbake core-image-minimal-dev

5. Test new UART

  • Connect TX and RX pins of your UART. In this example, connect pin 3 and pin 4 of PMOD JC.
  • Run microcom:
microcom -s 115200 /dev/ttyUL0
  • Type any text, you should see it on the terminal. If you remove the wired connection, you will stop seeing the text on the terminal.