How2Computing

10年前の工作物を活かすために、初代Raspberry PiでFT245RLを使用した。 Raspberry PiにはGPIOがあるので、これから何かを作る際に、FT245RLを使うことはあまり無いかもしれない。 (2022/09/18)。

FTDI社USB-パラレル変換器 FT245RL を Raspberry Pi で使う

秋月電子で購入.一個980円.10年以上前からあるけど、今現在でも売られている(2022/09/18)。 FTDI社のFT245RLにUSBコネクタ付けて,パスコンなど取り付けて,使い易くした製品. FTDI社の製品は,USB-シリアル変換器チップで有名なシリーズであるが, これは,USB-シリアル変換に,バッファとパラレル入出力を付けたものらしい.

USBケーブルで接続する方法には2種類ある。

がある。

仮想シリアルを使う方法

これを、Linux, Windows, macOSマシンのUSBに接続するとシリアルポートとして見える。 昔は、FTDI社が配布している仮想シリアルドライバが必要だった。 でも現在は、標準的に含まれているのか、だいたい、挿せばシリアルとして見える。

例えば、Raspberry piでは、

$ ls -al /dev/ttyAMA0 
crw--w---- 1 root tty 204, 64 Sep 18 02:33 /dev/ttyAMA0
$ ls -al /dev/serial/by-id/usb-FTDI_FT245R_USB_FIFO_AM00A7S3-if00-port0 
lrwxrwxrwx 1 root root 13 Sep  6 20:07 /dev/serial/by-id/usb-FTDI_FT245R_USB_FIFO_AM00A7S3-if00-port0 -> ../../ttyUSB0

というように見える。実体は1個で、/dev/serial/by-idにある方は、/dev/ttyAMA0のシンボリックリンク。

ただ、FT245がシリアルとして見えて、読み書きできるが、その結果をパラレルピンに入出力するには、

を使用してデータのやり取りを行う必要がある。具体的には、

FT245RLからRaspberry Piにデータを送るためには,

  1. TXEがlowであればFIFOに空きがあるのでパラレルピンに電圧をセット
  2. WR端子をhighからlowにするとパラレルピンの1,0状態が送信FIFOに書き込まれる
  3. Raspberry Piでシリアル経由で読み取る

という手順をする。逆に、Raspberry PiからFT245RLにデータを送る場合は

  1. Raspberry Piからシリアル経由でFT245RLにデータを送る
  2. RXFがlowならば受信FIFOにデータが来た
  3. RDをhighからlowにすると受信FIFOのデータがパラレルピンに出力される

といった手順になる。パラレルピンに入出力される値のタイミングがどうでも良ければ、

だけで良いらしい。実際に、RXFとRDを直結したところ、

echo -en '\x55' > /dev/serial/by-id/usb-FTDI_FT245R_USB_FIFO_AM00A7S3-if00-port0

というような操作で、奇数ポートがhigh, 偶数ポートがlowになった。 追加の配線が必要だけど、出力にしか使わない場合は、これが一番簡単かもしれない。

Bit-Bangモードを使う方法

FTDI社が配布しているライブラリーをインストールして呼び出すと, Bit-Bangモードという方式でパラレル入出力できる. Big-Bangモードは,外部からバッファー・パラレル端子間の変換タイミング信号を 与えなくても,内部で定期的に更新してくれるモード.

FTDI社のページ http://www.ftdichip.com/Drivers/D2XX.htm から,各種プラットフォーム用のドライバ/サンプルプログラムが入手可能. ここからダウンロードしたうちの,Bit Modeのサンプルが, お手軽にパラレル入出力するためのBit-Bangモードのサンプルプログラムだった.

以下はBit-BangモードをRaspberry Piから使う方法の説明。

ドライバのインストールとテスト

http://www.ftdichip.com/Drivers/D2XX.htm からドライバを入手する。 Raspberry Piなので、OSはLinux、CPUはARMになる。 該当するドライバとして、以下が用意されていた。

使っているRaspberry PiのCPUを確認する必要がある。そのためにはuname -aコマンドを使う。

$ uname -a
Linux raspberrypi 5.15.61+ #1579 Fri Aug 26 11:08:59 BST 2022 armv6l GNU/Linux

この結果、初代Raspberry PiはARMv6だとわかる。なので、それ用のドライバ

https://ftdichip.com/wp-content/uploads/2022/07/libftd2xx-arm-v6-hf-1.4.27.tgz

をダウンロードする。(Raspberry Pi 4ならばARMv7)。

適当なディレクトリを作って、curlコマンドでダウンロードして、展開する。

$ curl -O https://ftdichip.com/wp-content/uploads/2022/07/libftd2xx-arm-v6-hf-1.4.27.tgz
$ tar xvfz libftd2xx-arm-v6-hf-1.4.27.tgz 

するとreleaseというディレクトリができて、その中に展開される。そこでreleaseディレクトリに移動する。 この中のRead Me.txtファイルにこの先の手順が書いてある。それに従う。実際に行った手順は、以下。

$ cd build
$ sudo cp libftd2xx.* /usr/local/lib
$ sudo chmod 0755 /usr/local/lib/libftd2xx.so.1.4.27 
$ sudo ln -sf /usr/local/lib/libftd2xx.so.1.4.27 /usr/local/lib/libftd2xx.so
$ cd ..
$ sudo cp ftd2xx.h /usr/local/include/
$ sudo ldconfig -v

examplesのディレクトリの中に,サンプルプログラムが多数ある. examplesの中でmakeすると全部コンパイルされる.それぞれのディレクトリで個別にmakeしても良い.

たとえば,Bit Modeのプログラムは,1バイトの値を出力して,読み込むサンプル. main.cがソースコード。

がコンパイルした結果で、実行できる。0xAAが出力されて読み込まれる。ので、パラレルピンが互い違いになる。 dynamicとstaticの違いは、ライブラリを動的にリンクする、静的にリンクするの違いだと思う。 別の環境に持って行って動かすことを考えると、その時にライブラリの引っ越しを忘れるかもしれない。 なので静的にリンクするほうが良いかと思う。

ハードウェアを作ってテストする

ブレッドボードの上に,LEDとスイッチを作って,接続した.

DSC_0028s.jpg

このスイッチを押すとLEDが光るCプログラムを,bitmodeのサンプルを元に作ってみた. LEDはData 0 (LSBのビット) に,スイッチはData 4に接続している. コンパイルの方法は冒頭のコメントの書かれている通り。 (Pukiwikiの関係で、逆スラッシュ¥が疑問符?に化けているので注意)

/*		
	To build use the following gcc statement 
	(assuming you have the d2xx library in the /usr/local/lib directory).
	gcc -o switchled main.c -L. -lftd2xx -Wl,-rpath /usr/local/lib
*/

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include "../ftd2xx.h"
#include <unistd.h> //for usleep()

// Globals
FT_HANDLE ftHandle = NULL;


void ft245close()
{  
	if(ftHandle != NULL) {
		FT_Close(ftHandle);
		ftHandle = NULL;
		printf("Closed device?n");
	}
}

void ft245quit()//コントロールCで止められた時の修了処理
{  
	ft245close();
	exit(1);
}

void ft245open()
{
	FT_STATUS	ftStatus;
	
	ftStatus = FT_Open(0, &ftHandle);//接続されている0番目のデバイスを設定.
	if(ftStatus != FT_OK) {
		printf("FT_Open(0) failed = %d?n", ftStatus);
		exit(1);
	}
	
	FT_SetBaudRate(ftHandle, 9600);//読み書きする周期を指定
	//上位4ビットを入力に,下位4ビットを出力に,Bit-Bangモードに設定
	ftStatus = FT_SetBitMode(ftHandle, 0x0F/*1=out 0=in*/, 1/*bit-bang mode on*/);
	if(ftStatus != FT_OK) {
		printf("Failed to set bit mode?n");	
		exit(1);
	}
}

void ft245write( unsigned char data)//1バイト出力
{
	DWORD dwBytesInQueue = 0;
	FT_Write(ftHandle, &data, 1, &dwBytesInQueue);
}

unsigned char ft245get()//1バイト入力
{
	FT_STATUS	ftStatus;
	unsigned char ucdata;
	ftStatus = FT_GetBitMode(ftHandle, &ucdata);
	if(ftStatus != FT_OK) {
		printf("Failed to get bit mode?n");
		exit(1);
	}
	else return(ucdata);
}

int main(void)
{ 	
	unsigned char data;
	signal(SIGINT, ft245quit);		// trap ctrl-c call quit fn 
	
	ft245open();
	
	for(;;) {
		data=ft245get();
		printf("data=0x%X?n", data);
		if ( (data & 0x10) ==0 ) 
			ft245write(0x01);
		else 
			ft245write(0x00);
		usleep(100000L);
	}
	
	ft245close();
	
	return 0;
}

openに失敗することへの対策

上記のbitmode-staticなどを起動すると、開けないというエラーが出ることがあった。

$ ./bitmode-static 
FT_Open(0) failed (error 3).
Use lsmod to check if ftdi_sio (and usbserial) are present.
If so, unload them using rmmod, as they conflict with ftd2xx.

OSがシリアルとしてこのUSBポートを使っているためらしい。 FTDI社のUSBシリアル変換チップが標準でサポートされていることが裏目に出ている様子。 そこで、指示に従い、

$ lsmod | grep ftdi_sio
ftdi_sio               45056  0
usbserial              36864  1 ftdi_sio

で確認して、

$ sudo rmmod ftdi_sio

でunloadした。でもネットの情報を見ると、今は、modprobe -rコマンドを使うのがおすすめらしい。

$ sudo modprobe -r ftdi_sio

ただ、再起動するとUSBシリアルポートが復活する。FTDI社のUSBシリアルが標準で使えるのは嬉しいけど、毎回、モジュールを外す作業は面倒だし、忘れたら困る。 ということで、正しい方法かどうかわからないけど、ftdi_sioが読み込まれないように名前を変えた。

$ cd /lib/modules/`uname -r`/kernel/drivers/usb/serial
$ sudo mv ftdi_sio.ko.xz ftdi_sio.ko.xz.bak 

これで起動時にftdi_sioが読み込まれなくなった。

参考リンク

Pythonから動かしてみる

C言語だとネットワーク関連のプログラムが面倒なのでモダンな言語から使えるようにしたい。そこでPythonから使えるようにする。

上記で紹介したドライバがインストールされていることが前提である。

ラッパーをダウンロード

https://github.com/snmishra/ftd2xx からPython用のラッパーをダウンロードする。 ダウンロードすると ftd2xx-master/setup.py があるのでインストールする。

python setup.py build
python setup.py install

というコマンドでインストールできるはずだけど、ここで、

$ python setup.py build
Traceback (most recent call last):
  File "/home/pi/python/ftd2xx-master/setup.py", line 5, in <module>
    from distutils.command.build_py import build_py
ModuleNotFoundError: No module named 'distutils.command'

というようなエラーが続出する。そのため、色々と再インストールした。

$ sudo apt install --reinstall python3-distutils
$ sudo apt install --reinstall python3-setuptools
$ sudo apt install --reinstall python3-git

そのあと、以下を実行。installの方はsudoが必要みたいだった。

python setup.py build
sudo python setup.py install

少しエラーが出てるけど、気にしないで進めた。 プログラムの中で import ftd2xx とすれば使えた!

Pythonでスイッチ読んでLED点滅

上のC言語のサンプルと同様な動作をするプログラムをPythonで作った。 サンプルは無さそうなので、 ftd2xx-git1/ftd2xx/ftd2xx.py などを見ながら自力で頑張る。 結果は以下。

#!/usr/bin/python
# -*- coding:utf-8 -*-

#DB4が接地されたらDB0に取り付けたLEDを点灯させるプログラム

import ftd2xx
import time

def ft245open():
    ftHandle = ftd2xx.open(0)
    ftd2xx.FTD2XX.setBaudRate(ftHandle, 9600)
    ftd2xx.FTD2XX.setBitMode(ftHandle, 0x0F, 1)
    return ftHandle

def ft245write(ftHandle, data):
    #dataは1バイト配列で受け取る、例:b'\xFF'
    ftd2xx.FTD2XX.write(ftHandle, data) 

def ft245read(ftHandle):
  ftd2xx.FTD2XX.purge(ftHandle)
  return ftd2xx.FTD2XX.read(ftHandle,1)

def ft245close(ftHandle):
    if ftHandle:
        ft245write(ftHandle, b'\x00')
        ftd2xx.FTD2XX.close(ftHandle)
        ftHandle = None
        print('Closed device')

ftHandle = ft245open()

#control-cを押したら終了
try:
  while 1:
     input=ft245read(ftHandle)
     print(input)
     if(input[0] & 0x10)==0:
       ft245write(ftHandle, b'\x01')
     else:
       ft245write(ftHandle, b'\x00')
    time.sleep(1)
except KeyboardInterrupt:
  ft245close(ftHandle)

MQTTでon/off

上で示したブレッドボードの設定で、

  1. /light/setOnトピックに{true, false]が来たらLEDを{on, off}にして、
  2. 人がスイッチを操作したらやはりLEDを{on, off}にして/light/getOnトピックに{true, false}を流す

プログラム。 MQTTブローカはlocalhostで動いているという設定。ユーザ、パスワードの設定は無し。PythonでMQTTするプログラムに関しては、こちらを参照のこと。

#!/usr/bin/python

import ftd2xx
import time
from paho.mqtt import client as mqtt_client

########### FT245 part ###########

def ft245open(): 
    ftHandle = ftd2xx.open(0)
    ftd2xx.FTD2XX.setBaudRate(ftHandle, 9600)
    ftd2xx.FTD2XX.setBitMode(ftHandle, 0x0F, 1)
    return ftHandle

def ft245write(ftHandle, data):
    ftd2xx.FTD2XX.write(ftHandle, data) 

def ft245read(ftHandle):
  ftd2xx.FTD2XX.purge(ftHandle)
  return ftd2xx.FTD2XX.read(ftHandle,1)

def ft245close(ftHandle):
    if ftHandle:
        ft245write(ftHandle, b'\x00')
        ftd2xx.FTD2XX.close(ftHandle)
        ftHandle = None
        print('\nClosed device')

########### MQTT part ###########

address='localhost'
port=1883
debug_topic='light/debug'
sub_topic='light/setOn'
pub_topic='light/getOn'
client_id=f'python_856389663' #something random
#username=''
#passwor=''

def on_connect(client, userdata, flags, rc):
    if rc==0:
        print("Connection established.")
        client.publish(debug_topic,"Python client connected.") 
    else:
        print("Failed to connect: %d\n",rc)

def on_message(client, userdata, msg):
    print(msg.payload)
    client.publish(debug_topic,"Python client on_message.") 
    if(msg.payload == b'true'):
        ft245write(ftHandle, b'\x01')
    else:
        ft245write(ftHandle, b'\x00')

############ main part ###############

ftHandle = ft245open()
client=mqtt_client.Client(client_id)
#client.username_pw_set(username,password)
client.on_connect=on_connect
client.connect(address,port)
client.subscribe(sub_topic)
client.on_message=on_message

last_state=False
try:
    while True:
        client.loop()
        input = ft245read(ftHandle)
        new_state=((input[0] & 0x10) == 0)
        if last_state != new_state:
            last_state=new_state
            if new_state:
                ft245write(ftHandle, b'\x01')
                client.publish(pub_topic,b'true')
            else:
                ft245write(ftHandle, b'\x00')
                client.publish(pub_topic,b'false')
except KeyboardInterrupt:
    ft245close(ftHandle)

MQTTに対応すれば、Apple HomeKitからもアクセスできるようになる。どこからでも、iPhone, MacからLEDがon/offできるし、スイッチを押すとiPhoneの表示もon/offする。


トップ   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS