Skip to main content

Baremetal Raspberry Pi 4 with FT2232H


In the past I have written Bare Metal on Raspberry Pi 4: Getting Started. This was my first experience with hooking up a raw FTDI Module To the Raspberry Pi 4 for JTAG. After getting this setup working, I quickly realized that it would be significantly more helpful to have a UART serial interface available for sending and receiving data at the application level while bit fiddling at the JTAG level.

As a result, I purchased an FTDI FT2232H MiniModule. This is a PCB that has the FT2232H IC. The FT2232H (unlike the FT232H) has 2 channels that can be used for accessing UART and JTAG simultaneously (over the same USB cable). The module comes with headers pre-attached so all you need to connect it to the Raspberry Pi 4 (Rpi4) is some female to female jumper wire.

Material List

Software List

  • Get OpenOCD [Windows] [GitHub] Free - For primary JTAG control and communicating between GDB and JTAG port. (Highly Recommended: Get the xpack version of OpenOCD.)

  • Get Zadig Free - Required for proper USB IO control in Windows.

  • Get VirtualBox Free - Optional Hypervisor for isolation of your embedded development or for enabling Linux capabilities on a Windows host. While this is my hypervisor of choice, using something like WSL2, HyperV, VMWare, KVM, or other hypervisors should work just as well.

  • Get Ubuntu Free - Optional Linux distribution. I do nearly everything with Ubuntu, so that's what my instructions will be based on.

  • Note: There is a bit of setup required for the Ubuntu base line. This write up assumes a basic knowledge with how to install a Linux distribution and install relevant software (e.g. docker).

Pre-requisite Environment Setup

Before doing anything hardware specific, you'll need to do several steps:

  1. (Windows Hosts Only) Install Zadig and ensure WinUSB is the installed for the FTDI devices (Interface 0 and Interface 1).
  2. Install Virtual Box
  3. Install Virtual Box Extension Pack (no USB 2 without this.)
  4. Install Ubuntu >= 18 in a Virtual Machine.
  5. Install docker in the Ubuntu VM. See Docker Setup Assistant for more information.

Docker Setup

Once docker is installed into the virtualized environment, you'll need to build a docker image that is capable of accomplishing everything we want to do. There are a bazillion way to organize your docker capabilities. Our strategy here is to be able to access OpenOCD Server, OpenOCD CLI, GDB, and the UART all from the same docker image.

For simplicity I've included some helper files that you should be able to use out of the box to generate an expected good image:

  • Dockerfile - The primary Dockerfile
  • - A script for running the build.
  • - A template script for running a temporary container.

Download each of these into a single directory (e.g. ./docker/). Then from within the directory simply run:

$ ./

When everything goes well, you should see the following line as the final line of the

Successfully tagged chenz/openocd-dev:latest

To test the new image, you can run ./ bash and you'll find yourself inside a container in a /workspace folder. This /workspace folder is mapped to whatever folder you ran ./ from. I use this pattern to allow me to run the same docker images for multiples projects while only allowing each container a view of the specific project I'm working on. (Tip: I usually copy the * scripts to the top level of relevant projects.)

Lastly, you'll want to verify that you can see the USB devices from host by running ./ lsusb. When I run this, I get something like the following (although it will likely differ from system to system.):

$ ./ lsusb
Bus 001 Device 002: ID 80ee:0021 VirtualBox USB Tablet
Bus 001 Device 001: ID 1d6b:0001

Note: If you really don't want to use sudo within the container, you can opt to add the user to the plugdev group in both the host and docker image.

VM USB Pass-Through

Once docker is running in the virtualized environment, now is a good time to verify you have access to real USB devices connected to the host. This can be done with any USB accessory, but I'll use the FT2232H in my example. Note: You can only access the USB device from one operating system at a time (i.e. do not try to simultaneously access from both or risk corruption).

First, to even get the host to recognize the MiniModule, we'll configure the module to be USB bus powered. You accomplish this by connecting pins CN3-1 to CN3-3 on the module. While we're at it, we also need to connect CN2-1 to CN2-11 as another pre-requisite for powering the IO pins. You can see the wiring in the diagram below:

Pre Wiring Diagram

Once the two connections have been made, you can plugin the FT2232H MiniModule to the host. Hopefully you'll see or hear an indication of the connection. If you don't, that doesn't necessarily mean anything is wrong yet, you may just have an unusual setup.

In Virtual Box there are two primary ways to connect USB host devices to the virtual machine: temporarily or persistently.

The former method is achieved by right clicking the USB icon in the bottom bar of the Virtual Machine window. This will reveal a dropdown menu where you can select the device that you want to be passed through to the virtual machine. You can see this happen if you first watch lsusb from the virtual machine and then connect the device.

The latter involves setting up USB device filters. The Virtual Box service will then monitor the state of the USB enumerations on the host and when it sees a device that matches its filters, it'll automatically connect it to the virtual machine.

You can add a USB device filter in Virtual Box by going into the Virtual Machine settings and selecting "USB". If you only see a selection for "USB 1.1", you need to go back and install the Virtual Box extension that is provided by Oracle. Depending on your setup, you should be able to create a filter from scratch or from an already connected device.

The settings I'm using for my FT2232H MiniModule are:

Name: FTDI FT2232H MiniModule [0700]
Vendor ID: 0403
Product ID: 6010
Revision: 0700
Manufacturer: FTDI
Product: FT2232H MiniModule

Tip: You can put in less information is get a less specific filter. I recommend the Vendor ID and Product ID as a minimum. If you have multiple FT2232H devices connected to the same host, you'll want to include a Serial No.

Wiring Up The Hardware

Once you have some confidence that the USB device is being passed from the host through to the virtual machine and subsequently the docker image, you are in an ideal position to start wiring up the JTAG and the UART connections.

The studious individual will find what to connect in the following texts:

I like to reference because its pretty and easy on my eyes.

If you did it the same way I did, you'll end up with a mess of jumper wire connections that looks something like:

Wiring Diagram

In real life it looks something like:

Real Wiring

Configuring OpenOCD

Configuring OpenOCD is probably the largest pain in this whole process. Don't get me wrong, what the OpenOCD developers have done is amazing. Until OpenOCD, I always just assumed that you'd spend hundreds if not thousands of dollars to get JTAG on an embedded system. Although with the decreased price, you inherently get decreased support and have to depend on the generosity of the OpenOCD community.

When configuring OpenOCD you have to have quite an intimate knowledge of the target you are working on. Going in blind, this means there is a large front in investment. But once you get things working, tweaking and adjusting various settings is quite simple. Understanding the JTAG state machine and tricks for troubleshooting JTAG is a whole other topic.

In any case, there are three things that get configured in a standard OpenOCD environment:

  • The interface - In our case this is the FT2232H. Generally the interface is the device that is used to interface a developer machine with the embedded device that has JTAG access. Other interfaces that I've used are JLink, Abatron BDI, Xilinx, Flyswatter2, Olimex, and so forth.
  • The module - This is the Raspberry Pi module in our case. By module I am referring to the printed circuit board that the integrated circuits (ICs) are stuck to and interconnected. The way that various ICs are connected on a PCB define the JTAG chain and therefore can vary from board to board.
  • The target(s) - These are the various individual ICs that exist in the JTAG chain. The RPi4 uses a BCM2711 SoC micro controller. The BCM2711 is our target. Within this target exists 4 cores that we can control independently, so all of these are referenced in the target configuration.

Like I've already mentioned, understanding JTAG and the why/how of getting things setup is outside the scope of this article so for now I'll leave you with a configuration that includes the setup for the interface (FT2232H), module (RPi4), and target (BCM2711) all in one file: ft2232h-to-rpi4.cfg

Configuring the Raspberry Pi 4

A critical part of setting everything up is getting the RPi4 boot up setting correct. This bit me when I first get started because I am used to embedded systems where the default reset state is to have JTAG and UART enabled. This is not the case with RPi4. I'm guessing this is because most folks can just monitor and keyboard into the system.

On the SDCard of the RPi4 there is a file in the BOOT partition called config.txt. This is the file that the RPi4 boot loader reads to setup various peripherals before the kernel is kicked off. For us to use the JTAG and UART, we at a minimum the following lines added to the config.txt file:


Once added, save and reboot.

Running Everything

Before powering on the Raspberry Pi 4, ensure that all the wiring is correct and that the device isn't sitting on anything that may cause an unintentional short. (e.g. a metal bench or table is a bad idea).

You'll eventually need to open a number of terminals, so if you are familiar with tmux, I recommended starting a session now.

Terminal 1 - Serial Console

The first thing to do is get a serial console to the RPi4 over UART. This will allow you to see kernel boot up messages, console only messages, and have access to the RPi4 without connecting to a monitor or network connection. I use minicom to connect to the RPi4. Using the docker image you can run minicom with:

./ sudo minicom usb1

If you want to avoid using root, you can give the user dialout group permissions. Otherwise, minicom needs root to access the /dev/ttyUSB1 device. The usb1 parameter is referencing a minicom configuration in /etc/minicom/minirc.usb1. Unfortunately these files are not considered stable API, but here is what minicom created with the given configuration.

The configuration in minicom:

| A - Serial Device : /dev/ttyUSB1 |
| B - Lockfile Location : /var/lock |
| C - Callin Program : |
| D - Callout Program : |
| E - Bps/Par/Bits : 115200 8N1 |
| F - Hardware Flow Control : No |
| G - Software Flow Control : No |
| |
| Change which setting? |

The configuration file generated (/etc/minicom/minirc.usb1):

# Machine-generated file - use "minicom -s" to change parameters.
pu port /dev/ttyUSB1
pu rtscts No

Once you have minicom up and running you should get a login prompt from the terminal (Tip: You may have to hit enter for a prompt to show up if the RPi4 has already finished booting up.):

Welcome to minicom 2.7.1

Compiled on Dec 23 2019, 02:06:26.
Port /dev/ttyUSB1, 01:09:45

Press CTRL-A Z for help on special keys

raspberrypi login:

Note: The default username is pi and the default password is raspberry.

Terminal 2 - OpenOCD Server

Once you have a serial console up, you should setup the OpenOCD server.

./ openocd -f ft2232h-module.cfg

This results in the following output:

xPack OpenOCD, x86_64 Open On-Chip Debugger 0.11.0-00155-ge392e485e (2021-03-15-16:43)
Licensed under GNU GPL v2
For bug reports, read
Warn : Transport "jtag" was already selected
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : clock speed 1000 kHz
Info : JTAG tap: auto0.tap tap/device found: 0x4ba00477 (mfg: 0x23b (ARM Ltd), part: 0xba00, ver: 0x4)
Info : bcm2711.a72.0: hardware has 6 breakpoints, 4 watchpoints
Info : bcm2711.a72.1: hardware has 6 breakpoints, 4 watchpoints
Info : bcm2711.a72.2: hardware has 6 breakpoints, 4 watchpoints
Info : bcm2711.a72.3: hardware has 6 breakpoints, 4 watchpoints
Info : starting gdb server for bcm2711.a72.0 on 3333
Info : Listening on port 3333 for gdb connection

As you should be able to see in the above output, there are 3 different service that you can use:

  • Port 6666 allows you to drive complex TCL procedures in the OpenOCD environment.
  • Port 4444 allows the user to use a more restricted shell for running typical JTAG and ICE commands (e.g. run, halt, continue).
  • Port 3333 allows the user to attach a gdb debugger and debug the processor cores state.

Terminal 3 - GDB With OpenOCD

While usage of GDB itself is outside the scope of this article, you can start up a gdb session by running:

./ gdb-multiarch -ex "target ext :3333"

This will result in output that looks similar to:

GNU gdb (Ubuntu 9.2-0ubuntu1~20.04) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
Find the GDB manual and other documentation resources online at:

For help, type "help".
Type "apropos word" to search for commands related to "word".
pwndbg: loaded 183 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Remote debugging using :3333

Note: I have pwndbg installed in my gdb environment and you may not see this in your setup.


  • Ctrl-C in GDB to break RPi4.
  • c in GDB to continue from break.
  • Ctrl-C in OpenOCD to stop debugging.


Well, there you go. A setup from FT2232H to RPi4 that enables UART and JTAG access. Additionally, everything is encapsulated in a Virtual Machine and docker container for maximum portability. Hopefully this information can help some others with getting started in low level development with the RPi4 or a FT2232H based interface.

Where to continue?

  • Checkout pwndbg. Its a suite of additional goodies for gdb. I love to use pwndbg because its portable (i.e. works in a terminal) and provides a full visualization of the code, assembler, and memory in a single screen. Also, its fully customizable and has some support for decompilation with Ghidra with radare/rizin.

  • Learn aarch64 instruction set.

Some Troubleshooting

If you find the serial interface is missing lots of characters and feels slow. This could be due to interference being captured by the unshielded jumper wires between the FT2232H and the RPi4. It can also occur due to a long USB cable (>6feet) or unshielded USB cable.

Ways to resolve the interference issue is to move the setup far from things that are oscillating (e.g. fans, compressors, motors, CRTs, microwaves, radio enabled devices). If possible, its also advised to keep the exposed RPi4 on a static resistent matte and/or grounded to prevent interference or damage from static discharge. (Wool socks on the rug are a bad idea.)

OpenOCD error when interface not setup correctly:

xPack OpenOCD, x86_64 Open On-Chip Debugger 0.11.0-00155-ge392e485e (2021-03-15-16:43)
Licensed under GNU GPL v2
For bug reports, read
Warn : Transport "jtag" was already selected
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : clock speed 1000 kHz
Error: JTAG scan chain interrogation failed: all ones
Error: Check JTAG interface, timings, target power, etc.
Error: Trying to use configured scan chain anyway...
Error: auto0.tap: IR capture error; saw 0x0f not 0x01
Warn : Bypassing JTAG setup events due to errors
Error: Invalid ACK (7) in DAP response

Notice the Error: JTAG scan chain interrogation failed: all ones

OpenOCD when interface setup without board or target configuration:

Open On-Chip Debugger 0.10.0
Licensed under GNU GPL v2
For bug reports, read
adapter speed: 1000 kHz
Warn : Transport "jtag" was already selected
trst_and_srst separate srst_gates_jtag trst_push_pull srst_open_drain connect_deassert_srst
Info : clock speed 1000 kHz
Info : JTAG tap: auto0.tap tap/device found: 0x4ba00477 (mfg: 0x23b (ARM Ltd.), part: 0xba00, ver: 0x4)
Warn : gdb services need one or more targets defined