# Desktop or Laptop TFT Sidekick With FT232H

## Overview

![](https://cdn-learn.adafruit.com/assets/assets/000/084/159/medium800/graphic_tfts_banner.jpg?1573665399)

The [FT232H breakout](https://www.adafruit.com/product/2264) board allows you to add I2C, SPI, and GPIO ports to [almost any PC with a USB port](https://learn.adafruit.com/circuitpython-on-any-computer-with-ft232h). The SPI port capability allows for driving SPI based graphical TFT displays. And by running on a PC, you have plenty of processing power and access to the entire Python ecosystem. This combo allows for some fun use cases.

In this guide we will show you how to use this approach to display real time system status using an FT232H breakout and a small TFT display. It will be your little system status Sidekick.

### Adafruit FT232H Breakout - General Purpose USB to GPIO, SPI, I2C

[Adafruit FT232H Breakout - General Purpose USB to GPIO, SPI, I2C](https://www.adafruit.com/product/2264)
Wouldn't it be cool to drive a [tiny&nbsp;OLED display](https://www.adafruit.com/categories/98), read a [color...](https://www.adafruit.com/products/1334)

In Stock
[Buy Now](https://www.adafruit.com/product/2264)
[Related Guides to the Product](https://learn.adafruit.com/products/2264/guides)
![Angled Shot of the Adafruit FT232H Breakout - General Purpose USB to GPIO, SPI, I2C](https://cdn-shop.adafruit.com/640x480/2264-07.jpg)

You can use just about _any_ of our TFT, OLED or E-Ink displays as we've got Python drivers for all of them. However, we recommend one of our color TFT's cause they update fast and look great with nice bright colors

### 2.2" 18-bit color TFT LCD display with microSD card breakout

[2.2" 18-bit color TFT LCD display with microSD card breakout](https://www.adafruit.com/product/1480)
This lovely little display breakout is the best way to add a small, colorful, and bright display to any project. Since the display uses 4-wire SPI to communicate and has its own pixel-addressable frame buffer, it can be used with every kind of microcontroller. Even a very small one with low...

Out of Stock
[Buy Now](https://www.adafruit.com/product/1480)
[Related Guides to the Product](https://learn.adafruit.com/products/1480/guides)
![Additional video of bootup animation test on TFT screen.](https://cdn-shop.adafruit.com/product-videos/640x480/1480-14.jpg)

### 2.0" 320x240 Color IPS TFT Display with microSD Card Breakout

[2.0" 320x240 Color IPS TFT Display with microSD Card Breakout](https://www.adafruit.com/product/4311)
This gorgeous IPS display breakout is the best way to add a small, colorful, and bright display to any project, with excellent visibility from any angle. Since the display uses 4-wire SPI to communicate and has its own pixel-addressable frame buffer, it can be used with every kind of...

Out of Stock
[Buy Now](https://www.adafruit.com/product/4311)
[Related Guides to the Product](https://learn.adafruit.com/products/4311/guides)
![Overhead video of TFT display running boot-up animation.](https://cdn-shop.adafruit.com/product-videos/640x480/4311-06.jpg)

# Desktop or Laptop TFT Sidekick With FT232H

## Hardware Setup

Here's the wiring diagram for connecting the FT232H to the TFT display. A breadboard setup is fine for getting started, but it might be nice to package this up prettier if you want to really Sidekick it up.

![](https://cdn-learn.adafruit.com/assets/assets/000/084/160/medium800/graphic_tfts_fritz.jpg?1573667523)

The FT232H to TFT wiring is as follows:

- **5V** to **VIN**
- **GND** to **GND**
- **D0** to **SCK**
- **D1** to **MOSI**
- **D4** to **CS**
- **D5** to **D/C**
- **D6** to **RST**

If you're using a different display than the 2.2" TFT make sure its in SPI mode and follow the descriptive wiring table above

# Desktop or Laptop TFT Sidekick With FT232H

## Software Setup

We will need several software components to get this all working. Here's the run down.

## Python

You may already have this installed. If not, here's the official website you can go to for install info:

[Python Main Page](https://www.python.org/)
To check if it's already installed, try running this at a command line:

```python
python3 --version
```

We are showing running with explicit `python3` just to make sure you run with Python 3 instead of 2. Don't use Python 2.

## FT232H Setup

This is covered in the guide here:

[FT232H Setup](https://learn.adafruit.com/circuitpython-on-any-computer-with-ft232h/overiew)
Don't forget to set the **BLINKA\_FT232H** environment variable so that the Adafruit Blinka library knows it should probe the USB bus to find an FT232H chip. This is OS specific, so see details in the guide.

## Matplotlib

This is used to generate plots. Use the official installation instructions here to install for your platform:

[Matplotlib Install](https://matplotlib.org/users/installing.html)
## Pillow/PIL

This is the Python Imaging Library which is used to render the plots onto bitmaps that can be sent to the TFT display. Install instructions are here:

[Pillow Install](https://pillow.readthedocs.io/en/stable/installation.html)
## psutil

This is a Python library used to gather system data. The PyPi page is [here](https://pypi.org/project/psutil/) and the install instructions are here:

[psutil Install](https://github.com/giampaolo/psutil/blob/master/INSTALL.rst)
# Desktop or Laptop TFT Sidekick With FT232H

## Basic Test

Before we get to plotting various system status parameters, let's use a stand alone test to make sure all the essential software pieces are working. We'll also use this example as a way to explain how the code works. If you wanted to adapt this to plot something else, this would be a good starting point.

Here's the code:

https://github.com/adafruit/Adafruit_Learning_System_Guides/blob/main/TFT_Sidekick_With_FT232H/tft_sidekick_basic.py

And then to run it do the following. If you've already set the BLINKA\_FT232H environment variable, you don't need to do that again.

Danger: 

```none
$ export BLINKA_FT232H=1
$ python3 tft_sidekick_basic.py
```

You should get something like this on the display.

![](https://cdn-learn.adafruit.com/assets/assets/000/084/070/medium800thumb/graphic_tfts_test.jpg?1573583526)

The top plot is two lines of random numbers. The bottom line is a sine curve. In the next section we'll go through this code and talk about how it works.

[Also, check the FT232H SPI page for a simplified test for SPI TFT displays](https://learn.adafruit.com/circuitpython-on-any-computer-with-ft232h/tft-display)

# Desktop or Laptop TFT Sidekick With FT232H

## How It Works

The code basically works like this:

1. Initial setup
2. Update data
3. Update plots
4. Goto 2

Let's go through each of these.

## Initial Setup

The initial setup creates the lists (actually [deques](https://docs.python.org/3/library/collections.html#collections.deque)) the data will be stored in, the TFT setup, and the plot setup including the axis and the plot lines. This ends up being a fair amount of the code. It's all this:

```python
# Setup X data storage
x_time = [x * REFRESH_RATE for x in range(HIST_SIZE)]
x_time.reverse()

# Setup Y data storage
y_data = [ [deque([None] * HIST_SIZE, maxlen=HIST_SIZE) for _ in plot['line_config']]
           for plot in PLOT_CONFIG
         ]

# Setup display
disp = ili9341.ILI9341(board.SPI(), baudrate = 24000000,
                       cs  = digitalio.DigitalInOut(board.D4),
                       dc  = digitalio.DigitalInOut(board.D5),
                       rst = digitalio.DigitalInOut(board.D6))

# Setup plot figure
plt.style.use('dark_background')
fig, ax = plt.subplots(2, 1, figsize=(disp.width / 100, disp.height / 100))

# Setup plot axis
ax[0].xaxis.set_ticklabels([])
for plot, a in enumerate(ax):
    # add grid to all plots
    a.grid(True, linestyle=':')
    # limit and invert x time axis
    a.set_xlim(min(x_time), max(x_time))
    a.invert_xaxis()
    # custom settings
    if 'title' in PLOT_CONFIG[plot]:
        a.set_title(PLOT_CONFIG[plot]['title'], position=(0.5, 0.8))
    if 'ylim' in PLOT_CONFIG[plot]:
        a.set_ylim(PLOT_CONFIG[plot]['ylim'])

# Setup plot lines
#pylint: disable=redefined-outer-name
plot_lines = []
for plot, config in enumerate(PLOT_CONFIG):
    lines = []
    for index, line_config in enumerate(config['line_config']):
        # create line
        line, = ax[plot].plot(x_time, y_data[plot][index])
        # custom settings
        if 'color' in line_config:
            line.set_color(line_config['color'])
        if 'width' in line_config:
            line.set_linewidth(line_config['width'])
        if 'style' in line_config:
            line.set_linestyle(line_config['style'])
        # add line to list
        lines.append(line)
    plot_lines.append(lines)
```

## Update Data

Updating the data is done in the function `update_data()`. You would change this to be whatever you want (more info below). Wherever the new data comes from, you would then just add each new data point to the data stores. For the basic example, this is just random numbers and a sine curve:

```python
def update_data():
    ''' Do whatever to update your data here. General form is:
           y_data[plot][line].append(new_data_point)
    '''
    # upper plot data
    for data in y_data[0]:
        data.append(random.random())

    # lower plot data
    y_data[1][0].append(math.sin(0.5 * time.monotonic()))
```

Each line of the plot has a dedicated deque to store its data. Each plot stores these deques in a list. These lists are then stored in the variable `y_data`. Therefore, to access the `line` for a given `plot`, you'd use the syntax `y_data[plot][line]`. You'll generally want to use the `append()` method to add each new data point. The result is something like:

```python
y_data[plot][line].append(new_data_point)
```

You can also use Python iterator syntax if that works for your scenario. That's how the random data is appended in the example above.

Also remember that indexing is 0 based. So the top plot is 0 and the bottom plot is 1.

## Update Plots

Updating the plots is where Matplotlib and Pillow are used. This is all done in the function `update_plot()`. The basic idea is to update the `ydata` for each line with the current data. The plot is then re-rendered. Pillow is then used to generate an image object that can be sent to the TFT display.

You generally won't have to deal with this function.

```python
def update_plot():
    # update lines with latest data
    for plot, lines in enumerate(plot_lines):
        for index, line in enumerate(lines):
            line.set_ydata(y_data[plot][index])
        # autoscale if not specified
        if 'ylim' not in PLOT_CONFIG[plot].keys():
            ax[plot].relim()
            ax[plot].autoscale_view()
    # draw the plots
    canvas = plt.get_current_fig_manager().canvas
    plt.tight_layout()
    canvas.draw()
    # transfer into PIL image and display
    image = Image.frombytes('RGB', canvas.get_width_height(),
                            canvas.tostring_rgb())
    disp.image(image)
```

## Goto 2

And that's it. The whole thing is driven by the simple loop at the bottom:

```python
print("looping")
while True:
    update_data()
    update_plot()
    time.sleep(REFRESH_RATE)
```

Warning: 

## Customization

We skipped over a bunch of additional "initial setup" at the top of the code. This is all the code between the two comment lines:

```python
#==| User Config |======================
```

This is where you'll edit the code to customize it for your use. There are two general parts:

1. Configure behavior and aesthetics via the `CONSTANTS`
2. Change the `update_data()` function for your use case

### Behavior and Aesthetics

The two constants `REFRESH_RATE` and `HIST_SIZE` are pretty straight forward. They determine how often the data plot is updated and how much total data to store. The two together define the total time window for the plot as `REFRESH_RATE * HIST_SIZE`.

The more interesting constant is the `PLOT_CONFIG` structure. As the name implies, this is how you will configure the two plots. It's a bit of a nested mess of tuples and dictionaries. But hopefully it's laid out in a way to make it easy to edit.

At a minimum, you need the `line_config` entry for each plot with one empty dictionary for each line of the plot. So the absolute minimum `PLOT_CONFIG` would look like:

```python
PLOT_CONFIG = (
    #--------------------
    # PLOT 1 (upper plot)
    #--------------------
    {
    'line_config' : (
        { },
        )
    },
    #--------------------
    # PLOT 2 (lower plot)
    #--------------------
    {
    'line_config' : (
        { },
        )
    }
)
```

This would create a single plot line for both the upper and lower plots with default values used. A plot line is created for each dictionary entry in `line_config`. So if you wanted to add a second plot line to the upper plot, the above would become:

```python
PLOT_CONFIG = (
    #--------------------
    # PLOT 1 (upper plot)
    #--------------------
    {
    'line_config' : (
        { },
        { },
        )
    },
    #--------------------
    # PLOT 2 (lower plot)
    #--------------------
    {
    'line_config' : (
        { },
        )
    }
)
```

The only change is one additional line with an empty dictionary `{ }`.

The `PLOT_CONFIG` in the basic test example tries to demonstrate your various customization options. For any of them, if they are not specified, default values are used. So, looking at the basic test example:

```python
PLOT_CONFIG = (
    #--------------------
    # PLOT 1 (upper plot)
    #--------------------
    {
    'line_config' : (
        {'color' : '#FF0000', 'width' : 2, 'style' : '--'},
        { },
        )
    },
    #--------------------
    # PLOT 2 (lower plot)
    #--------------------
    {
    'title' : 'sin()',
    'ylim' : (-1.5, 1.5),
    'line_config' : (
        {'color' : '#00FF00', 'width' : 4},
        )
    }
)
```

The upper plot has no title or y axis limits set. The y axis will autoscale. Two plot lines are setup in `line_config`. The first line has `color`, `width`, and `style` specified. The second line has nothing specified so will use default settings.

The lower plot specifies a plot `title`. This is simply some text that will be shown at the top of the plot. It also species specific y axis limits via `ylim`, therefore the y axis will not autoscale. A single plot line is setup in `line_config` with `color` and `width` specified. A default `style` will be used.

In general, you should match up the number of lines configured in `line_config` with the data that is updated in `update_data()`.

Danger: 

### Custom Data

The `update_data()` function can be whatever you want. As mentioned above, the general syntax for a given `line` of a given `plot` is:

```python
y_data[plot][line].append(new_data_point)
```

The system status examples that follow will provide more examples on how this function is used.

# Desktop or Laptop TFT Sidekick With FT232H

## CPU Load

OK, now let's plot something actually fun and useful. This is where using [psutil](https://pypi.org/project/psutil/) comes in. This is an excellent cross-platform system monitoring Python library. We'll use it for a couple of examples.

First up - CPU load and temperature. Here's the updated code:

https://github.com/adafruit/Adafruit_Learning_System_Guides/blob/main/TFT_Sidekick_With_FT232H/tft_sidekick_cpu.py

Save that as **tft\_sidekick\_cpu.py** and run it:

```python
python3 tft_sidekick_cpu.py
```

Danger: 

and you should get something like this:

![](https://cdn-learn.adafruit.com/assets/assets/000/084/072/medium800thumb/graphic_tfts_cpu_load.jpg?1573587705)

Note that pretty much all of the changes have been made in the user configuration section at the top. The plots are set up to track up to 4 cores. And then `update_data()` has been modified to use psutil to get the CPU load and temperature data and add it to the plot data.

And that's pretty much it. The one change that was done outside of this area was to remove the `time.sleep()` delay in the main loop. That's because we are using a blocking call to `psutil.cpu_percent()` , so we just let it set the pace.

# Desktop or Laptop TFT Sidekick With FT232H

## Memory Usage

Next up memory usage. Precious precious memory. It comes in two precious flavors:

- **virtual** - the memory programs have access to (you want a lot of this)
- **swap** - when you run out of the above, this gets used (it's slow)

And for each of these, two things are tracked:

- **free** - amount of memory available
- **used** - amount of memory actually being used

Here's the code that tracks memory usage. It attempts to determine the total amount available on your system and use that to set the y axis limits.

https://github.com/adafruit/Adafruit_Learning_System_Guides/blob/main/TFT_Sidekick_With_FT232H/tft_sidekick_mem.py

Save that as **tft\_sidekick\_mem.py** and run it with:

```python
python3 tft_sidekick_mem.py
```

Danger: 

and you should get something like this:

![](https://cdn-learn.adafruit.com/assets/assets/000/084/073/medium800thumb/graphic_tfts_mem.jpg?1573589719)

# Desktop or Laptop TFT Sidekick With FT232H

## Network Traffic

Next up, network traffic. Precious precious internet. How fast are the 1's and 0's going up and down the pipe? How much in total have gone up and down the pipe? With this example, we track both of these.

There's no easy way to pre-determine your max bandwidth. Therefore you may want to adjust the `ylim` values and the divisors in `update_data()`if you find the data too squished or going off the chart.

https://github.com/adafruit/Adafruit_Learning_System_Guides/blob/main/TFT_Sidekick_With_FT232H/tft_sidekick_net.py

Save that as **tft\_sidekick\_net.py** and run it with:

```python
python3 tft_sidekick_net.py
```

Danger: 

and you should get something like:

![](https://cdn-learn.adafruit.com/assets/assets/000/084/080/medium800thumb/graphic_tfts_net.jpg?1573592338)


## Featured Products

### Adafruit FT232H Breakout - General Purpose USB to GPIO, SPI, I2C

[Adafruit FT232H Breakout - General Purpose USB to GPIO, SPI, I2C](https://www.adafruit.com/product/2264)
Wouldn't it be cool to drive a [tiny&nbsp;OLED display](https://www.adafruit.com/categories/98), read a [color...](https://www.adafruit.com/products/1334)

In Stock
[Buy Now](https://www.adafruit.com/product/2264)
[Related Guides to the Product](https://learn.adafruit.com/products/2264/guides)
### 2.2" 18-bit color TFT LCD display with microSD card breakout

[2.2" 18-bit color TFT LCD display with microSD card breakout](https://www.adafruit.com/product/1480)
This lovely little display breakout is the best way to add a small, colorful, and bright display to any project. Since the display uses 4-wire SPI to communicate and has its own pixel-addressable frame buffer, it can be used with every kind of microcontroller. Even a very small one with low...

Out of Stock
[Buy Now](https://www.adafruit.com/product/1480)
[Related Guides to the Product](https://learn.adafruit.com/products/1480/guides)

## Related Guides

- [Adafruit FT232H With SPI & I2C Devices](https://learn.adafruit.com/adafruit-ft232h-with-spi-and-i2c-libraries.md)
- [CircuitPython Hardware: ILI9341 TFT & FeatherWing](https://learn.adafruit.com/micropython-hardware-ili9341-tft-and-featherwing.md)
- [Programming SPI flash with an FT232H breakout](https://learn.adafruit.com/programming-spi-flash-prom-with-an-ft232h-breakout.md)
- [Programming Microcontrollers using OpenOCD on a Raspberry Pi](https://learn.adafruit.com/programming-microcontrollers-using-openocd-on-raspberry-pi.md)
- [CircuitPython NeoPixel Library Using SPI](https://learn.adafruit.com/circuitpython-neopixels-using-spi.md)
- [CircuitPython Libraries on any Computer with FT232H](https://learn.adafruit.com/circuitpython-on-any-computer-with-ft232h.md)
- [User-space SPI TFT Python Library - ILI9341](https://learn.adafruit.com/user-space-spi-tft-python-library-ili9341-2-8.md)
- [CircuitPython Libraries and Jupyter Notebook on any Computer with MCP2221](https://learn.adafruit.com/jupyter-on-any-computer-with-circuitpython-libraries-and-mcp2221.md)
- [Adafruit EYESPI Pi Beret](https://learn.adafruit.com/eyespi-pi-beret.md)
- [2.2" TFT Display](https://learn.adafruit.com/2-2-tft-display.md)
- [Running PyPortal Code on Blinka with Displayio](https://learn.adafruit.com/running-pyportal-code-on-blinka-with-displayio.md)
- [Adafruit FT232H Breakout](https://learn.adafruit.com/adafruit-ft232h-breakout.md)
- [Google Docs Sensor Logging From Your PC](https://learn.adafruit.com/gdocs-sensor-logging-from-your-pc.md)
- [Adafruit SEN54 or SEN55 Adapter Breakout](https://learn.adafruit.com/adafruit-sen54-or-sen55-adapter-breakout.md)
- [Analog Knobs on Raspberry Pi 400 with CYBERDECK Hat](https://learn.adafruit.com/analog-knobs-on-raspberrypi-400-with-cyberdeck-hat.md)
