FT232Hを使ってPCでGPIOやSPIを使えるようにする。

· 3min

SPI接続のデバイスの制御をMCU、例えばCH32VシリーズとかESP32で行おうという場合、デバイスによっては結構SPIで送受信するデータが複雑で、これをデバイス上で開発、デバッグするのは骨が折れる。PCで開発しておいて、最後にハードウェア制御のところだけMCU用に差し替えるということで、もっと簡単に開発出来ないだろうか。

FT232Hは、USBでGPIOやSPI制御ができるチップだ。FT232Hで検索すれば、ボードになっているものが幾つか見つかる。AliExpressで発見したものを買ってみた。

準備

今回は、Lubuntu 26.04とRustを使用した。

まずUSBに接続してlsusbで確認してみる。

$ lsusb
...
Bus 001 Device 027: ID 0403:6014 Future Technology Devices International, Ltd FT232H Single HS USB-UART/FIFO IC

IDは0403:6014だ。このデバイス、デフォルトではカーネルにUSBシリアルと認識されて掴まれてしまうので、udevで細工する。/etc/udev/rules.d/99-ft232h.rulesを作成。

SUBSYSTEM=="usb", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6014", ACTION=="add", RUN+="/bin/sh -c 'echo 0 > /sys/$devpath/authorized_default'"
SUBSYSTEM=="usb", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6014", MODE="0666", GROUP="plugdev"

udevを反映する。

$ sudo udevadm control --reload-rules
$ sudo udevadm trigger

カーネルに掴まれていないか確認する。

$ usb-devices | grep -E -A 4 "Vendor=0403 ProdID=6014"
P:  Vendor=0403 ProdID=6014 Rev=09.00
S:  Manufacturer=FTDI
S:  Product=Single RS232-HS
C:  #Ifs= 1 Cfg#= 1 Atr=80 MxPwr=500mA
I:  If#= 0 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=ff Prot=ff Driver=(none)

Driver=(none)になればOK。FTDI用のライブラリをインストールしておく。

$ sudo apt install libftdi1-dev

Rustのクレート作成

これで準備ができたのでRustでコードを書く。クレートを作ってライブラリを追加する。

$ cargo new ft232h
$ cd ft232h
$ cargo add ftdi-embedded-hal
$ cargo add embedded-hal
$ cargo add ftdi

私が試した時点でのCargo.tmolはこんな感じ。

[dependencies]
embedded-hal = "1.0.0"
ftdi = "0.1.3"
ftdi-embedded-hal = { version = "0.24", features = ["ftdi"] }

まずはLチカ(GPIO制御)

src/bin/blink.rsを作成する。

use ftdi_embedded_hal as hal;
use embedded_hal::digital::OutputPin; 
use std::thread::sleep;
use std::time::Duration;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 1. デバイスのオープン
    let device = ftdi::find_by_vid_pid(0x0403, 0x6014)
        .interface(ftdi::Interface::A)
        .open()?;

    // 2. HALコンテキスト初期化
    let ftdi_hal = hal::FtHal::init_default(device)?;
    
    // 3. ピンの取得
    let mut led = ftdi_hal.ad0()?;

    println!("FT232H GPIO(Lチカ)を開始します");

    loop {
        led.set_high()?;
        println!("LED ON");
        sleep(Duration::from_millis(500));

        led.set_low()?;
        println!("LED OFF");
        sleep(Duration::from_millis(500));
    }
}

実行。

$ cargo run --bin blink

SPI

bin/spi_cs.rsを作る。

use ftdi_embedded_hal as hal;
use embedded_hal::spi::SpiDevice;
use std::thread::sleep;
use std::time::Duration;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 1. デバイスのオープン
    let device = ftdi::find_by_vid_pid(0x0403, 0x6014)
        .interface(ftdi::Interface::A)
        .open()?;

    // 2. 2MHz で HAL 初期化
    let ftdi_hal = hal::FtHal::init_freq(device, 2_000_000)?;
    
    // 3. CS
    let mut spi_device = ftdi_hal.spi_device(3)?;

    println!("FT232H 自動CS制御テストを開始します(周波数: 2MHz)");
    println!("ピン配置: AD0=SCK, AD1=MOSI, AD2=MISO, AD3=CS(自動制御)");

    let mut counter: u8 = 0;

    loop {
        let tx_data = counter;
        let mut buffer = [tx_data];

        // 4. SpiDevice トレイトを介した通信
        // 内部で自動的に CS Low -> SPI転送 -> CS High が完結します。
        spi_device.transfer_in_place(&mut buffer)?;

        println!(
            "SPI通信結果 -> 送信: 0x{:02X} | 受信: 0x{:02X}",
            tx_data, buffer[0]
        );

        counter = counter.wrapping_add(1);
        sleep(Duration::from_secs(1));
    }
}

実行。

$ cargo run --bin spi_cs

コードに書かれている通り接続は以下のようになる。

ピン番号信号名
D0SCK
D1MOSI
D2MISO
D3CS

オシロで見てみる。ちゃんと2MHzで通信しているね。