PIC16F88 による シリアル接続型PIC-ADC

はてブ数 2007/10/08デバイス

PIC16F88を用いたシリアル接続型のAD変換プログラムと回路図です。PCに接続出来ますので、そのままPCでデータ処理が行えます。(写真は動作確認時のもの)

pic_adc.jpg

仕様

AD変換回数1000sps(1msに1回)
AD変換解像度10bit
AD変換誤差1bit
シリアル通信速度RS-232C 38400bps
シリアル通信オプションパリティなし、フロー制御なし
電源電圧3.3~5V程度

PIC16F88用ですが、USARTとADコンバータの乗っているPICならば簡単に移植できます。きちんとレベルコンバータIC(MAX232C)を通してますので、いわゆるUSBシリアルでも動作します。

変換レートの1000spsは内部タイマで割り込みをかけているので、(クロックと同じ程度)正確な間隔で計測されます。外部に8MHz水晶発振器などをつければ、正確に1kHzの間隔でサンプリングできます。

回路図とファームウェア

pic_adc.gif

シンプルな回路構成です。電源は3.3~5Vで動作すると思います。もっと違う電圧でも動くでしょう。簡便のためPICの内部発振を使っていますが、より正確な計測が必要な場合は外部に水晶をつけてください。

PICの電源に大容量低インピーダンスコンデンサをつけていますが、たまたま手元にあっただけでここまで大きい必要はありません。100uF程度で十分でしょう。AD変換の精度のことを考えると、低インピーダンス品は使いたいところですが。

使い方

RS-232C経由でコマンドを送信することで制御を行います。コマンドは文字 1byte です。

送信文字機能
Rデバイスをリセットします。AD変換を停止させます。
SAD変換を開始します。
VPICファームウェアのバージョン情報を表示します。

AD変換中に送られるデータは2byteで1つのデータとなっています。並びはビッグエンディアンです。例えば、受信データ配列をchar buf[size]、電源電圧を5Vとすれば、次のようにAD変換電圧を取得できます。

for(int i=0; i<size; i+=2) {
    float volt = ((buf[i]<<8) + buf[i+1])*5/(float)1024;
         :
         :(データ処理)
}

ライセンス

修正BSDライセンスとします。質問とか相談とか、開発とかありましたらコメント欄にどうぞ。

PICを少し便利に使うアセンブラマクロ

はてブ数 2007/10/02デバイス

PICを初めてさわってみたんですが、アセンブラが癖ありますね。x86(やZ80)に慣れた人間には少々使いにくいのでマクロ集を作ってみました。あまり大がかりにならず、適当な規模にしてあります。

PICマクロ集

マクロ集

利用は用途を問わず自由にどうぞ。

マクロW保存機能
bank0bank0に切り替え
bank1bank1に切り替え
bank2bank2に切り替え
bank3bank3に切り替え
mov reg,reg2×reg ← reg2
movi reg,imm×reg ← imm
and reg,reg2×reg ← reg and reg2
andi reg,imm×reg ← reg and imm
test reg,reg2×w ← reg and reg2
testi reg,imm×w ← reg and imm
or reg,reg2×reg ← reg or reg2
ori reg,imm×reg ← reg or imm
xor reg,reg2×reg ← reg xor reg2
xori reg,imm×reg ← reg xor imm
add reg,reg2×reg ← reg + reg2
addi reg,imm×reg ← reg + imm
sub reg,reg2×reg ← reg - reg2
subi reg,imm×reg ← reg - imm
cmp reg,reg2×w ← reg - reg2
cmpi reg,imm×w ← reg - imm
xor reg,reg2×reg ← reg xor reg2
xori reg,imm×reg ← reg xor imm
jz labelif (zero) goto label
jnz labelif (non zero) goto label
ja labelif (a>b) goto label
jae labelif (a>=b) goto label
jb labelif (a<b) goto label
jbe labelif (a<=b) goto label
  • reg … レジスタファイル
  • imm … 即値
  • label … ラベル

注意

  • mov と movi、and と andi のように第2オペランドをレジスタ/即値で書き分けますが、互いに書き間違えてもアセンブルは通過してしまうのでご注意ください。
  • マクロは1命令ではないので、btfsc などで「次の1命令を飛ばす」を併用する場合は特にご注意ください。

条件分岐(jxx)の使用例

cmpi MEM0, h'20'  ; W = MEMO - 20h
ja   LABEL        ; if (MEMO > 20h) goto LABEL

メモ

2007/07/26(木)verilog HDLのalwaysで悩み

verilog HDLを勉強がてら遊んでいるのですが、つまずいています。

よくあるサンプルですが、

input [1:0] SW;
reg   [3:0] counter;

always @(posedge SW[0] or posedge SW[1]) begin
	if (SW[1] == 1)	// reset
		counter <= 0;
	else
		counter <= counter + 1;
	end
end

と書くと正しく動作します。ですが、

always @(posedge SW[0] or posedge SW[1]) begin
	if (SW[0] == 1)
		counter <= counter + 1;
	else
		counter <= 0;
	end
end

と書くとまともに動作しません。SW[0]を押したとき、値が不定値になります。またSW[0]を押している間、counterの値が随時変化し全く定まりません。どなたかお助け。

追記

シミュレーションだとどちらも正常に動きます。なんかバグ(不具合?)っぽい予感がヒシヒシと(汗)

2007/04/05(木)PL2303X + Linuxで高速通信はできるか

FT232RLはとりあえず諦めて、PL-2303Xのドライバの動作を検討しました。

パッチを当ててインストール

Linuxソースの drivers/usb/serial/pl2303.c がデバイスドライバです。カーネルを再構築するのは大変なので、FT232のデバイスドライバから Makefile と Rules.make を拝借し、適当に書き換えてカーネルモジュールを作り増した。

1Mbpsは対応していませんので、ドライバをごにょごにょと書き換えます。

baud = 0;
switch (cflag & CBAUD) {
	case B0:	baud = 0;	break;
	case B75:	baud = 75;	break;
	case B150:	baud = 150;	break;
	case B300:	baud = 300;	break;
	case B600:	baud = 600;	break;
	case B1200:	baud = 1200;	break;
	case B1800:	baud = 1800;	break;
	case B2400:	baud = 2400;	break;
	case B4800:	baud = 4800;	break;
	case B9600:	baud = 9600;	break;
	case B19200:	baud = 19200;	break;
	case B38400:	baud = 38400;	break;
	case B57600:	baud = 57600;	break;
	case B115200:	baud = 115200;	break;
	case B230400:	baud = 230400;	break;
	case B460800:	baud = 460800;	break;
	default:
		err ("pl2303 driver does not support the baudrate requested (fix it)");
		break;
}
baud = 1000000;
dbg("%s - baud = %d", __FUNCTION__, baud);
if (baud) {
	buf[0] = baud & 0xff;
	buf[1] = (baud >> 8) & 0xff;
	buf[2] = (baud >> 16) & 0xff;
	buf[3] = (baud >> 24) & 0xff;
}
<中略>
i = usb_control_msg (serial->dev, usb_sndctrlpipe (serial->dev, 0),
		     SET_LINE_REQUEST, SET_LINE_REQUEST_TYPE, 
		     0, 0, buf, 7, 100);

ソースをみれば分かりますが、このデバイスはかなり変わっていて、通信速度(Baud rate)を直接デバイスに送る(設定する)仕組みになっています(普通は分周カウンタの値を送る)。

さてこれで通信……としてみたものの、データがすべて化けてしまって全くだめでした。100Kbpsでもダメでした。これは通信速度が合っていないときに起こる症状です。どうやら、うまく分周されていないようなのですが、データシートには 6Mbps まで対応と書かれています。

原因

ここでみつけたPL-2303の資料(PDF)(Xなし)によれば(表3の少し手前)、

The programmable baud rate generator supports baud rates up to 1.2M bps

とのことで、どうやらドライバが PL-2303X に対応しておらず、PL-2303互換デバイスとして使用しているためではないかと思うのですが。つまり、PL-2303Xのデバイスドライバをみつければよいのではないかと……。

訂正。PL-2303X資料(pdf)によれば、高速な baud として設定可能な値は

921600, 1228800, 2457600, 3000000, 6000000

だそうです。微妙に使えない PL-2303X。

2007/04/02(月)FT232RL を Linux kernel 2.4.17 で認識させる

FTDI製FT232RL(FT232R)とは、3Mbpsまでの速度に対応した高速USBシリアルチップ(Serial Conveter)です。USBに接続するだけで、TTLレベルで高速シリアル通信が実現できます。

デバイスドライバの組み込み

ドライバ配布サイトでWindows用、Mac用、Linux用のドライバが配布されています。Linux kernel 2.4.20以降には標準でドライバが組み込まれていますが、2.4.17には組み込まれていません。

実験中ドライバの利用

Kernel 2.4.17にはFTDI USB Serialのドライバが実験中として組み込まれています。

USB support
 -> USB Serial Converter support
   -> <*> USB FTDI Single Port Serial Driver (EXPERIMENTAL)
usbserial.c: FTDI 8U232AM converter detected
usbserial.c: FTDI 8U232AM converter now attached to ttyUSB0 (or usb/tts/0 for devfs)

カーネルにこれを組み込むことで一応認識はするのですが、次の問題が起きました。

  • 9600, 19200, 38400, 78600……921600(最大)といった系列しか指定できない。*1
  • 試しに460800に設定したところ、使用したマシンが非力なのかKernelごと落ちた(応答しなくなった)

公式配布ドライバの使用

配布されているドライバソース ftdi_sio.tar.gz を展開し make します。make するためにはカーネルのソース(の一部、ヘッダ類)が必要です。

kernelソースを展開し適切にパスを設定後 make してみると、コンパイルエラーが発生しました。どうやらkernelが古いせいか

	.owner =		THIS_MODULE,

という項目が存在しない*2のが原因のようです。これらの行をすべて削除(またはコメントアウト)することでコンパイルできるようになります。make が無事成功すると、ftdi_sio.oというファイルができます。これがドライバ本体(カーネルモジュール)です。

このドライバをカーネルに組み込むためには root で次のようにコマンドを実行します。

組み込み
# insmod ftdi_sio.o
Kernelから削除
# rmmod ftdi_sio.o
組み込み済カーネルモジュールの確認
# lsmod

組み込んでみたのですが、エラーが出てしまい、ドライバが動作しません。

ftdi_sio.c: v1.3.5r1:USB FTDI Serial Converters Driver
hub.c: USB new device connect on bus1/1, assigned device number 3
usbserial.c: descriptors matched, but endpoints did not
usb.c: USB device 3 (vend/prod 0x403/0x6001) is not claimed by any active driver.

0x403/0x6001は、FTDIの8U232AMを示し、ドライバソースには実際その記述があるのですが、「but endpoints did not」と出て認識しません。endpoints とは何であるかということですが、調べてみるとUSBデバイスがホスト側と通信する際に利用するバッファのことのようです。

要するにエンドポイントの設定がおかしいと言っているのですが、ドライバのソースにはきちんと記述されています。

static struct usb_serial_device_type ftdi_8U232AM_device = {
/*	.owner =		THIS_MODULE, */
	.name =			"FTDI 8U232AM Compatible",
	.id_table =		id_table_8U232AM,
	.num_interrupt_in =	0,
	.num_bulk_in =		1,
	.num_bulk_out =		1,

usbserial.c をみてみると

/* verify that we found all of the endpoints that we need */
if (!((interrupt_pipe & type->needs_interrupt_in) &&
      (bulk_in_pipe & type->needs_bulk_in) &&
      (bulk_out_pipe & type->needs_bulk_out))) {
	/* nope, they don't match what we expected */
	info("descriptors matched, but endpoints did not");
	return NULL;
}

ということで、この項目が設定されてないことが原因のようです。

公式配布ドライバへのパッチ

ftdi_sio.c の中に記述されているすべてのデバイス情報(8U232AM, FT232BM等々)について、次のような書き換えを行います。

static struct usb_serial_device_type ftdi_8U232AM_device = {
/*	.owner =		THIS_MODULE,	*/
	.name =			"FTDI 8U232AM Compatible",
	.id_table =		id_table_8U232AM,
	.needs_interrupt_in =	DONT_CARE,
	.needs_bulk_in =	MUST_HAVE,
	.needs_bulk_out =	MUST_HAVE,
	.num_interrupt_in =	0,

色が変わっているところが追加部分です。これをコンパイルしてカーネルモジュールを作成しインストールすると、

usbserial.c: FTDI FT232BM converter detected
usbserial.c: FTDI FT232BM converter now attached to ttyUSB0 (or usb/tts/0 for devfs)

として認識されます。

FT232RLと表示されませんが仕様です。コンピューターデバイスではよくあることで、デバイスとして異なってもソフトウェア的に同一の場合は、同じプロダクトIDを持つことがあります。また1つのデバイスで仮想的に(ソフト的に)複数のデバイスが存在するということもあるため、デバイスの型とソフト的に見える型の不一致などが頻繁に起きます。

*1 : あたらしいドライバならば任意の速度が設定出来ることはソースより確認済

*2 : struct usb_serial_device_type

ターミナルによる通信 2007/04/03

シリアルポートの設定を変更したり、任意の速度で通信するためにはターミナルや設定ソフトが必要ですが、minicom や stty などはお節介なことに"9600, 19200……"系列しか指定できません。kermit もダメでした。

いろいろ調べたところ、cu というコマンドで指定できました。cu コマンドは uucp と一緒に配布されています(リンク先、左下のソースから uucp_1.07.orig.tar.gz をダウンロード)。

# tar zxvf uucp_1.07.orig.tar.gz
# cd uucp-1.07
# ./configure
# make
# make install
# mkdir /usr/spool
# mkdir /usr/spool/uucp
# chown uucp /usr/spool/uucp

利用方法は次のようになります。

# cu -l /dev/ttyUSB0 -s 38400

1Mbps通信はできるか?

早速

# cu -l /dev/ttyUSB0 -s 1000000 -d
cu: fconn_open: Opening port /dev/ttyUSB0 (speed 1000000)
cu: fconn_set: Changing setting to 1, 2, 2

と設定してみます(-d はデバッグフラグです)。ところが、公式ドライバでも、2.4.17付属ドライバでも通信速度が変更できないようです。ドライバソースを改造してデバッグメッセージを出力させてみると、

ftdi_sio.c: Set to 9600

などと出てしまいます。カーネルの段階(tty?)で通信速度が書き換えられている模様です(詳細不明)。

仕方がないので、ドライバのソース(ftdi_sio.c)を書き換えて、無理矢理 1Mbps 通信をさせてみました。

  • Linux 2.4.17付属ドライバ …… 99%程度取りこぼし、使い物にならない
  • 公式配布ドライバ …… 通信速度の設定によらず、1byteも受信できない

以上うまく通信できませんでした。Linux 2.4.17のドライバは未成熟である可能性は捨てきれず、逆に公式配布ドライバはカーネルが古すぎるために(非互換により)通信すら行えていない可能性が高いと思います。

Linux 2.4.17のドライバを改変し受信したデータ

00001,00001,00001,00001,00001,00001,00001,00001,00001,00001

00002,00002,00002,00002,00002,00002,00002,00002,00002,00002

00003,00003,00003,00003,00003,00003,00003,00003,00003,00003

00004,00004,00004,00004,00004,00004,00004,00004,00004,00004

00005,00005,00005,00005,00005,00005,00005,00005,00005,00005

00006,00006,00006,00006,00006,00006,00006,00006,00006,00006

00007,0000720,359205921,35921,35921,35921,35921,35921,35921,35921

35922,35922,35935922,35922

35923,35923,35923,35923,35923,35923,35923,35923,3535924,35924,35924,35924,35924,35924

35925,35925,35925,35925,35

35926,35926,35926,35926,35926,35926,35926,359

FT232Rとして認識しない原因 2007/04/04

ftdi_sio.h では

#define FTDI_8U232AM_PID 0x6001 /* Similar device to SIO above */

となっていて、いくつかの製品で同じプロダクトID(PID)を持っています。複数の製品をどうやって見分けているのかなと思ったのですが、productversion という2バイトの値で識別しているようです。

USB_DEVICE_VER (vendorId, productId, lo, hi)
	... like USB_DEVICE with lo <= productversion <= hi
<略>
static struct usb_device_id id_table_8U232AM [] = {
	{ USB_DEVICE_VER(FTDI_VID, FTDI_IRTRANS_PID, 0, 0x3ff) },
	{ USB_DEVICE_VER(FTDI_VID, FTDI_8U232AM_PID, 0, 0x3ff) },
<略>
static struct usb_device_id id_table_FT232BM [] = {
	{ USB_DEVICE_VER(FTDI_VID, FTDI_IRTRANS_PID, 0x400, 0xffff) },
	{ USB_DEVICE_VER(FTDI_VID, FTDI_8U232AM_PID, 0x400, 0xffff) },
<略>
static struct usb_device_id id_table_FT232R[] = {
	{ USB_DEVICE_VER(FTDI_VID, FTDI_8U232AM_PID, 0x600, 0xffff) },

0xffff0x5ff に書き換えることで、FT232R と認識しますが、特に挙動は変わりませんでした。usb_serial_device_type をみても表示文字列以外、内部処理に特に違いはないようです。

その後の検討とまとめ

  • 公式ドライバの ftdi_read_bulk_callback() 内の "port->open_count" の条件文を無効化(常に真)することでデータ通信はできましたが、Linux 2.4.17ドライバ同様に99%取りこぼしでした。
  • 試しに usbserial.c を別バージョンのカーネルから引っ張ってきましたが、案の定コンパイルすらできませんでした。
  • Linux 2.4.17で 100kbps で受信させてみましたが、それでも多少取りこぼしが発生しました。
  • Linux 2.6.17カーネルで試したところ、何の問題*3もなく動作しました。
    Linux version 2.6.17
    [17179838.796000] usb 1-1: configuration #1 chosen from 1 choice
    [17179838.800000] ftdi_sio 1-1:1.0: FTDI USB Serial Device converter detected
    [17179838.800000] drivers/usb/serial/ftdi_sio.c: Detected FT232BM
    [17179838.800000] usb 1-1: FTDI USB Serial Device converter now attached to ttyUSB0
    
    # stty -F /dev/ttyUSB0 1000000
    # cat /dev/ttyUSB0
    

Linux 2.4.17時代にはおそらく高速シリアル通信は考えられておらず、そのためまともに通信できないのではないかと思います。

もっとも、使用したマシンが非力すぎるという可能性も捨てきれない面はありますが、PowerPCベース200MHz程度あれば、いくら何でも100KB/sぐらい受信してよさそうですので(LANは3Mbps以上でてますし)。

残された道

  • Linux 2.4.17カーネルのドライバを適当に最新カーネルのものに入れ替える。
  • Linux 2.4.17カーネルのドライバを元に地道にパッチを当てる。
  • Linux 2.4.34等の最新カーネルをベースに、特殊ハード(組み込み)向けの改造を加える。
  • VCPドライバではなく、D2XXドライバの利用を検討する。x86バイナリのみの提供のため利用不可。

*3 : 取りこぼしもおそらくなし