さぁとうとうPICマイコンを学びはじめて休憩期間もありましたが1年が経とうとしてしております。
目標はミニ人工心肺装置の作成でどうしてもフィードバックのPID制御が必要です。
これまでの経過から解る通り、目標を達成させる為の予行練習では残りA/D変換を行えるようになれば、いよいよPICマイコンによるフィードバック制御プログラムの制作に移れます。
今回はA/D変換を行います。
具体的には電源電圧を可変抵抗で分圧した0-5VのアナログデータをA/D変換し、0‐255の8ビット数値に変換します。
本当は10ビットまで変換できます。
すごいでしょ。 そんで考えたんです。 さて、正しく変換できた事を確認するにはどうしよう??? そうだ!! 変換した値でサーボモータを動かしちゃえ!! そう思いつきました。
ちなみに過去にCCP機能を使ったサーボモータの動かし方はラーニング済みです。
詳しくはこちら CCP機能ではCCP1の値を10-40に設定する事でサーボの角度を可変できました。
ちなみに内部クロック1MHzを使用した場合です。 このCCP1の値ですが、可変抵抗をクリクリまわして、その可変抵抗のアナログ値をA/D変換して代入してやればいいわけです。
しかもA/D変換後は8ビット(256段階)になるので30段階(CCP1の値の範囲は10-40)にレベル変換します。
実際に動かしてみた動画はこちらです。かなり面白いんです。なんか工夫すればロボットなんかも作れそうですね。 https://www.youtube.com/watch?v=Oitjs92QqfY
PIC16F88でA/D変換する時の設定
じつはA/D変換はそれほどハードルは高くありませんでした。
LCD表示に比べれば朝飯前です。
A/D変換ではこのような流れになります。
①A/D変換する為の初期設定
②A/D変換で使う入力ポートの指定(一つだけなら一度の設定でOK)
③A/D変換する
④A/D変換した値を使う
じつはこの1-4の各章にこまかなポイントがあるので少し説明していきます。
①A/D変換する為の初期設定
PIC16F88でA/D変換する際の初期設定 アナログ、デジタルの設定 (ANSELの設定) A/D変換できる入力ポートはANS0-ANS6の7ポートあるのですが、そのポートをデジタルで使うのかアナログで使うのか決める設定です。
アナログで使うポートには1を設定します。
電圧というアナログ値をデジタル値に変換するので、ANS0をアナログポートとして扱います。
例 ANSEL=0b0000001;//これでAN0をアナログで使う事が決まる
変換する基準電圧の設定(VCFG0、VCFG1の設定)
VCFGの2ビットで基準電圧を設定します。
図からもVCFG0=0、VCFG=0で電源電圧を基準電圧にする事ができます。
変換クロック設定(ADCS0、ADCS1、ADCS2の設定) 変換クロックの設定は表の通りにします。
今回内部クロックの1MHzを使用しているのでADCS0〜ADCS2はすべて0を設定しました。
そうすると2Toscなので2μSecとなります。計算の仕方はTosc=1/クロック数で計算できます。
このOperation(TAD)の値が2-6μSecにしましょうねぇ〜とあります。
変換後の値の格納方法(ADFMの設定) ADFMはA/D変換後の値の格納方法です。
A/D変換は最大10ビット変換が可能で、格納の仕方が特殊です。
たとえばADFMを1に設定すると右づめで値が8ビットのADRESHとADRESLに格納されます。
ADFMを0に設定すると左詰めになるわけです。 今回は変換後のデータは8ビット程度で良いので、左詰めにして下位2ビットは切り捨てにします。 精度を必要としない場合はこのような使い方で十分です。
②A/D変換で使うポートの指定
どのポートからの入力値をA/D変換するのかを決める設定です。
CHS2-CHS0 の3ビットでANS0-ANS6を指定します。
ちなみにA/D変換で使えるポートは7ポートで RA0-RA4(ANS0-ANS4) RB6-RB7(ANS5-ANS6)です。
このポートの設定の仕方は一回のA/D変換で一つのポートしか使えない(同時にA/D変換できない)ので複数の違う種類の値をA/D変換する場合は、A/D変換するその都度CHS2‐CHS0の設定でポートを指定してやる必要があります。
CHS2 CHS1 CHS0 | A/D変換指定ポート |
0 0 0 | RA0(ANS0) |
0 0 1 | RA1(ANS1) |
0 1 0 | RA2(ANS2) |
0 1 1 | RA3(ANS3) |
1 0 0 | RA4(ANS4) |
1 0 1 | RB6(ANS5) |
1 1 1 | RB7(ANS6) |
③A/D変換する
さぁここまで設定できたら、あとはA/D変換するだけです。
指定したポートにつないだアナログデータである電圧値を読み取り今回は256段階(最大で10ビットまで可能)のデータに変換してくれます。
今回作成したプログラムではA/D変換して値を得るまでを一つの関数として設定しています。
そうすれば、メインプログラムでその関数を呼び出すだけでA/D変換されて値が返されて戻ってきます。
下記は例です メイン関数でadconv()という関数を呼び出しtmpに値を格納させるというプログラムです
while(1){ tmp = adconv();//adconv()という関数を呼び出し、その値をtmpに保存 あとはこのtmpを使うだけ簡単です。 } |
下記が関数の部分でメインプログラム前に定義しておきます。
unsigned int adconv() { ADON = 1; //AD変換ON __delay_us(20); //アクィジョン時間 20us GO_nDONE = 1; //AD変換開始 while(GO_nDONE); //変換完了待ち //return (ADRESH<<8) + ADRESL; return (ADRESH); } |
adconvという関数(私が定義した関数でーす)に関して ADONのビットを1にする事でA/D変換機能がONとなります。
ADONをONしたあとPIC内のA/D変換に関わるコンデンサをチャージさせなければならない為にアクジション時間という待ち時間を設けなければなりません。
その時間が20μ秒必要です。
その後 GO_nDONE のビットを1にする事でいよいよA/D変換開始です。
A/D変換できたら、GO_nDONEのビットが0になるので while(GO_nDONE)としてGO_nDONEが1の間はwhile文から抜けないようにしています。
その後データはADRESHに格納してあるのでreturnで値を返してメインループに帰ります。
意外と簡単です ちなみに今回はADRESH+ADRESLの左詰め10ビットデータを格納しておりADRESHだけを利用していることから下位の2ビットを無視した8ビットデータを利用しています。
10ビットすべてを利用するには return (ADRESH<<8) + ADRESL; とビットをずらして格納すれば10ビットデータを利用できます。
④A/D変換した値を使う
あとはデータを活用するだけです。
今回は入力された0-5Vの電圧をデジタルに変換後tmpに格納した256段階のデータを さらにCCP1に入力する10-40の範囲の値にレベル変換します。
レベル変換の方法はまたしても関数を定義しました。
関数にはhennkannという名前をつけて下記のような計算式を用います。
変換後の値=H 変換前の値=X 変換前の最低値=A 変換前の最大値=B 変換後の最低値=C 変換後の最大値=D H=(X-A)*(D-C)/(B-A)+C
下記がプログラム上での関数です。
// 変換関数(ある数値から他の数値へレベル変換を行う) unsigned int hennkann(unsigned int x, unsigned int in_min, unsigned int in_max, unsigned int out_min, unsigned int out_max) { return (x – in_min) * (out_max – out_min) / (in_max – in_min) + out_min ; } |
この変換でレベル変換した値をCCP1にいれてサーボモータをPWMにより作動させています。
回路図
プログラムも載せておきます
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
// CONFIG1 #pragma config FOSC = INTOSCIO #pragma config WDTE = OFF #pragma config PWRTE = OFF #pragma config MCLRE = OFF #pragma config BOREN = OFF #pragma config LVP = OFF #pragma config CPD = OFF #pragma config WRT = OFF #pragma config CCPMX = RB3 #pragma config CP = OFF// CONFIG2 #pragma config FCMEN = OFF #pragma config IESO = OFF #define _XTAL_FREQ 1000000 //#include <xc.h>unsigned int adconv(); unsigned int tmp; unsigned int x , i ; // レベル変換関数(ある数値から他の数値へレベル変換を行う) unsigned int hennkann(unsigned int x, unsigned int in_min, unsigned int in_max, unsigned int out_min, unsigned int out_max) { return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min ; } //A/D変換する関数 プログラム上でadconv() を呼べばA/D変換してくれる unsigned int adconv() { ADON = 1; //AD変換ON __delay_us(20); //アクィジョン時間 20us GO_nDONE = 1; //AD変換開始 while(GO_nDONE); //変換完了待ち //return (ADRESH<<8) + ADRESL; return (ADRESH); } //メインプログラム void main(void) { OSCCON = 0b01000000;//内部周波数1MHzに ANSEL = 0b00000001;//AN0だけアナログに設定 あとはデジタルに! CHS0 = 0;//AN0(RA0)を入力ポートに設定 CHS1 = 0; CHS2 = 0; VCFG0 =0;//A/D変換を行うポートの基準電圧をvdd-vssに設定 VCFG1 =0; ADCS0 =0;//a/d変換のクロック選択1.6usecに ADCS1 =0; ADCS2 =0; ADFM = 0;//a/d変換結果の格納方法 今回は左詰め TRISA = 0b00000001; //RA0を入力に設定 TRISB = 0b11110111; // BR3はpwmの出力に設定 PORTA = 0;//クリア PORTB = 0;//クリア //サーボを動かすpwmの設定 CCP1CON = 0b00001100; // PWMモードにする T2CON = 0b00000110; // プリスケール16倍 PR2 = 255; // PWM周期 256*16*4μsec=16384usec CCPR1L=25; // サーボを0°の真ん中の位置へ __delay_ms(1000); //1秒待つ //メインループスタート で永遠ループ while(1){ tmp = adconv(); x = hennkann(tmp,0,255,10,40) ; CCPR1L = x; __delay_ms(50); //可変抵抗を回すと追順してサーボモータが動くようにwait時間を50msecにする } return; } |