PICとメカな日々

Webにも萌えにも限界を感じたオタクがPICマイコンをいじる日々。

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

いろいろあった

 冬になったんでコタツ用ノートPCをヤフオクで買ったりインストールしたり録画用鯖が死んだり再インストールしたりコミケに行ったりといろいろあったりして、最近PICをいじってない。

 とはいえ何もしないのも進歩がないので、電気電子板@2ちゃんのPICスレで見たLEDのPWM点灯をマネしてみる。つまりなんだ、デジタル信号だと電圧を半分にしたりするのは難しいし、D/A変換とか言ったら別に部品がいる。それで代替策として、「onの時間とoffの時間を高速に切り替えたら、長い目で見ると中間値を出力してるように見える」って技ですな。

 つまりこんな感じ。

PWMのイメージ


 プログラム的にはタイマ割り込みを使用して実装してみる。つまり、タイマ割り込みしてきたときに0からカウントアップして30まで上がる変数を用意(30になったら0に戻す)して、その数字でONにするかOFFにするかを判断。

 ソースコードはこんな感じ。

/*

  割り込みでLEDをPWM点灯させてみるテスト
  
*/
#include<pic.h>
//#include<delay.h>
__CONFIG(0x3FF2);            //CONFIG:CP=OFF,PWRT=ON,WDT=OFF,OSC=HS
unsigned char led_outdata=0b00000000;  //出力データを初期化
unsigned char timer_count=0;      //タイマ定数設定
unsigned char vector=1;          //フラッシュ方向は左=1,右=0
void main(void)
{
  PSA=1;                //プリスケーラはWDTに割り当て
  PS2=0;
  PS1=0;
  PS0=0;                //プリスケーラ分周比は1:1
  
  T0CS=0;                //クロックソースは内部命令サイクル
  TMR0=0;                //TMR0をリセット
  T0IF=0;                //T0IFをリセット
  
  T0IE=1;                //TMR0割り込み発生を許可
  GIE=1;                //割り込みを許可
  
  PORTA=0b00000000;          //PORTAをリセット
  PORTB=0b00000000;          //PORTBをリセット
  TRISA=0b00000011;          //RA0,RA1を入力に設定
  TRISB=0b00000000;          //PORTBを出力に設定

  PORTB=led_outdata;

  while(1)              //無限ループ
  {
  }
}

interrupt led_pwm()            //割り込みルーチン
{
  if(T0IF){
    T0IF = 0;            //割り込みフラグクリア
    timer_count++;          //カウンタを進める
    if(timer_count == 30){
      timer_count = 0;      //カウンタは30でリセット
    }
    
    RB0 = 1;            //RB0=常時点灯
    
    if(timer_count < 20){
      RB1 = 1;
    } else {
      RB1 = 0;          //RB1=20/30点灯
    }
    if(timer_count < 15){
      RB2 = 1;
    } else {
      RB2 = 0;          //RB2=15/30点灯
    }
    if(timer_count < 7){
      RB3 = 1;
    } else {
      RB3 = 0;          //RB3=7/30点灯
    }
    if(timer_count < 3){
      RB4 = 1;
    } else {
      RB4 = 0;          //RB4=3/30点灯
    }
    
  }
}



 実行してみたらこんな感じ。
pwm点灯


 LED自体のばらつきもあるかもしれんが、半分くらいの点灯時間にしてもあまり暗く見えませんな。1/3くらいでようやく暗くなったとわかるような感じか。
スポンサーサイト

センサをどないしよ

 そういうわけで「エアロRCのモーター2個をPICで制御して走らせる」ということを考え始めたわけだが、仮にPICでモーターを制御するにしてもそのままだとプログラム動作で右往左往させるのが関の山である。

 やっぱセンサだよなセンサ。センサがなければどんなロボット作ってもツ(ピー)でメ(ピー)なので自律行動もなにもあったもんじゃない。

 とはいえ今のところ手持ちのライタで書き込み可能なPICは16F84のみ。PIC電子工作の作例を見ると超音波距離センサとかはあるが、パルス発信にタイマ割り込みで矩形波作ってたりして、一組の超音波センサをドライブするのがやっと。これはこれでPICの限界に近いことをやる凝った工作な訳で、モーター動かす片手間でやるのは難しい。

 となると、センサの工作だけで労力割かれるのもなんだし、最初は無難に「ピアノ線で触角作って壁にぶつかったらセンシングするセンサ」から始めるか。これならマイクロスイッチを入力ポートにつなぐだけですむし、16F84の13ビットあるポートのうち4ビットをモーターに回してもあと9ビット。タッチセンサとして「右前・前・左前・右後・後・左後」の6通りを用意しても十分足りる。

受信部の中身

 さらにエアロRCのシャーシを分解してみる。

open.jpg


 購入時に既に取り付け済みの受信部カバーを外すと、中から受信機の基盤が出てくる。

board.jpg


 カバーを空けるとトランジスタとかのディスクリート部品だけで「こんな簡単に作ってあるのか?」と一瞬思うが、

board2.jpg


 裏側にも部品が付いてる。

chip.jpg


 しかも表面実装部品とかチップとかあったりしてそれなりの基盤が。

 まあとにかくPICマイコンを組み込む際にはこの基盤スペースを利用して組み込むことになるか。

 問題は、

chip2.jpg


 この赤丸のところに、受信基盤の下に付いてる電池ボックスの端子も取り付けてあるわけで、マイコンボードに端子も取り付けるかもしくは別途取り付けになりそうな悪寒。

 あとPICマイコン基盤の大きさだな。この受信基盤と同じスペースに押し込むとなると、30mm×50mmくらいの大きさに納める必要がある。

エアロアールシーの中身

 このエアロアールシーだが、組立式ゆえ組み立てた人間はメカの部分がよくわかるようになってる。

 まずボディを外すと、全車種共通のシャーシがある。
chassis.jpg


 んで駆動部分はFA-130モーターから低速/高速切り替え式のギアを介して後輪を駆動。
MOTOR1.jpg


 ポイントは前輪のステアリングにある。おもちゃラジコンでは電磁石とかでステアリングする例が多いが、これはステアリングにもFA-130モーターを使用する。
MOTOR2.jpg


 FA-130モーターからはラック&ピニオンでステアリング。
STAIR.jpg


 エアロアールシーのキモはここ。ピニオンギヤは、FA-130モーターに直結してる訳ではなく、遠心クラッチを介してつながっている。
ST_MOTOR.jpg

CLUTCH.jpg


 つまり、受信機アンプから出た信号でFA-130モーターを駆動して、遠心クラッチを介してステアリングを動かしてるわけだな。切れ角が一定になるとそこから先は、モーターは回ってるが遠心クラッチが滑って回転を吸収するようになってる。

 てことはだ、2個のFA-130モーターの回転制御だけでコントロールできるわけだな。となればドライバICを2個用意すれば良いわけだ。

エアロアールシーをいじってみようか

 さて、開発環境は整ったし、ソース作成→コンパイル→ファームウェア書き込みと、PICマイコンの使い方はある程度わかってきた(とはいえアセンブラで命令数数えてパルス幅調整するような濃い使い方はしてないが)ので、最初から考えていた「なんかメカをPICで動かしてみる」を次のテーマにすることに決定。

 とりあえずメカを動かす基本としてこの本を読んでみると、最近はモーターを動かすドライバICってのがあってだいぶ楽に使える事が判明。



 この本に載ってたのが、モータドライバICのTA7291Pというシロモノ。細かい話を抜きにすると、要するに「停止・正転・逆転・ブレーキの4状態をデジタル回路からの2ビットで引っ張ってくれば、モーターにつなぐとその旨モーターへ電流を送ってくれる」というモノ。しかもデジタルICの電圧とモーターの電圧は別に設定出来るわけで、PICからの出力をコレにつないで別電源のモーターを回すこともできる。

TA7291Pのデータシート
http://www.semicon.toshiba.co.jp/td/ja/Linear_ICs/Motor_Driver_ICs/20040524_TA7291F_datasheet.pdf

こんな感じのIC
TA7291P.gif


 さすがにこれは秋月通商では売ってなかったが、ぐぐって見たところ楽天市場のツクモロボット王国http://www.rakuten.co.jp/tsukumo/で売ってることが判明。やっぱロボット作る人はみんなコレがいるって事ですな。そんなわけで5、6個ほど注文。

 んで、どげなメカにメロディーマークを貼ろうかと考えて見渡したところ、たまたまあったのがコレ。
 
fairladyZ.jpg


 トミーから出てるラジコンカーのエアロアールシーシリーズ。おもちゃ屋行くと1000円前後とえらい安く売ってる。これの特徴は、おもちゃラジコンに良くある完成品じゃなくて半完成の組立キットになってること。あと、ボディがスポーツカーじゃなくてミニバンだのファミリーカーだのセダンだのと微妙に地味な車種がたくさん並んでる。

TOMY エアロアールシー http://www.tomy.co.jp/aero_rc/top01.htm

割り込みと鉛フリーハンダ

 ハードウェアの方は一段落したんで、しばらくソフトウェアの方をいろいろ実験してみようと思ったり。
 そんでこんどのテーマは「割り込み」。いわゆるインタラプト。

 PICはいろいろ割り込みがあるが、一番典型的なものとしてタイマ割り込みを使ってみる。参考書として使ってるこの本



 でタイマ割り込みのあたりを読んでみる。正確に言うとこの本は同じPICでも8ピンの12F675を使ってるが、基本的には似たようなモノらしい。

 そんでこのタイマだが、機能的には

・クロックをそのまま、あるいはクロック何回かでカウントアップ
・8ビットのタイマがオーバーフローするたびに割り込み発生

 が基本らしい。C言語からは、関数定義のときにアタマに

interrupt hogehoge()
{
}



 とinterruptを書き加えてやると割り込み発生時にこの関数をコールするそうな。今まではDelayMs()関数とかでウェイトをかけてたが、正確に時間を計りたい場合(時計とか)はコレで一定時間ごとに処理ルーチンが作動するようにしてやればよいわけか。

 あとは割り込みを使うためになんぼか設定が必要になる。これもデータシートを眺めた結果、OPTIONレジスタとINTCONレジスタの中にある設定ビットをいじってやればよいらしい。

 ちなみに割り込み関数の中では、割り込みを発動したフラグを0に戻してやる処理がまず最初に必要だそうな。今回のソースで言うとタイマ割り込みが発動したときに立つT0IFフラグだな。そうでないと割り込み処理後にまたすぐ割り込みになると。

 そんなこんなで今回のソースはこうなった。なんかif文ばかりで見苦しいが。


/*
 割り込みのテスト
 最初はカウントアップ方向にフラッシュ
 RA0のキーで割り込み→カウントダウン方向に変更
*/
#include<pic.h>
#include<delay.h>
__CONFIG(0x3FF2); //CONFIG:CP=OFF,PWRT=ON,WDT=OFF,OSC=HS
unsigned char led_outdata=0b00000001; //出力データを初期化
unsigned char timer_count=0; //タイマ定数設定
unsigned char vector=1; //フラッシュ方向は左=1,右=0
void main(void)
{
 PSA=0; //プリスケーラはTMR0に割り当て
 PS2=1;
 PS1=1;
 PS0=1; //プリスケーラ分周比は1:256

 T0CS=0; //クロックソースは内部命令サイクル
 TMR0=0; //TMR0をリセット
 T0IF=0; //T0IFをリセット

 T0IE=1; //TMR0割り込み発生を許可
 GIE=1; //割り込みを許可

 PORTA=0b00000000; //PORTAをリセット
 PORTB=0b00000000; //PORTBをリセット
 TRISA=0b00000011; //RA0,RA1を入力に設定
 TRISB=0b00000000; //PORTBを出力に設定

 PORTB=led_outdata;

 while(1) //無限ループ
 {
  if(RA0){ //RA0スイッチ
   DelayMs(30); //チャタリング待ち
   if(RA0){
    if(vector){ //vectorが1なら
     vector = 0; //vector=0に
    }else{
     vector = 1; //さもなくば1に
    }
   }
  }
 }
}

interrupt led_shift() //割り込みルーチン
{
 if(T0IF){
  T0IF = 0; //割り込みフラグクリア
  timer_count++;
  if(timer_count > 10){
   timer_count =0; //タイマカウント
   if(vector == 1){
    led_outdata <<= 1; //vector==1なら左シフト
    if(led_outdata == 0){
     led_outdata=0b00000001;//右に戻す
    }
   }else{
    led_outdata > >= 1; //vector!=1なら→シフト
    if(led_outdata == 0){
     led_outdata=0b10000000;//左に戻す
    }
   }
  }
 }
 PORTB=led_outdata; //出力
}



 そんで例によってコンパイルして、MPLAB上でシミュレーションしてもだいたい予想通りになってるんでチップに転送したんだが、なんかうまく行かない。というか、RB4~7につながってるLEDの上4ビットが点灯しない。

 基盤をあちこちさわってるうちに点灯することもあったりするんで、こりゃソフトの方じゃなくてハードの方らしいと目星はつく。最初はICSP用に付いてるコネクタの接触不良かと思ってたが、どうもLEDのグランド線のほうが接触不良らしい。テスタで調べたらやっぱし導通不良になってる。

 思い当たるフシはあった。LEDを取り付けるとき、部品といっしょに買った鉛フリーハンダ。通常のハンダより融点が高いんで、やたらとハンダ付けが難儀なことになるんだよな。普通のハンダならしばらくコテをあててると液体になったハンダがすっと隙間に流れ込んでハンダ付け完了になるんだが、Sn-Agだけの鉛フリーハンダの場合はコテが当たったとこだけハンパに液化して他の部分まで溶けてくれない。

 しょうがないので30Wの大きめのコテを持ち出してきていったん鉛フリーハンダを全部溶かし吸い取り線でハンダを外す。しかるのちに通常のハンダで再度ハンダ付けし直してようやくまともな動作に。

チャタリング対策

 PORTBの出力からLEDを点灯させるのはうまくいったので、こんどは入力のほうを試してみる。

 そろそろ回路図を書かないとヤマカンではユニバーサル基盤の配線がわけわからん様になってきたので、回路図CADのBSshをダウンロード。

水魚堂の回路図エディタhttp://www.suigyodo.com/online/schsoft.htm

 これのライブラリにPIC16F84などのPICチップも入ってるので、かなり楽に回路図を書くことが出来る。これを出力しておけば配線するときもおおむね悩まないで済む。

 しかしユニバーサル基盤の使い方ほとんど忘れてるし、ずいぶんハンダ付けの腕も落ちたなあ。10年以上ブランクがあるとやっぱ大変だわ。

今回の回路図

 配線はほどなく完成したが、ソフトウェアでスイッチの入力を処理する際には単純に

if(RA0){
  hogehoge
}

 みたいな訳にはいかない。スイッチにはチャタリングというもんがあって、入力された瞬間に何度かon/offを繰り返す性質がある。
 いくつか参考書読んだりして、とりあえずもっとも処理が簡単そうな「スイッチがonになった→30ミリ秒待つ→やっぱりスイッチがonなら処理」という方式をとる事にした。

 例によってコンパイルのあとICPROGでPICに転送して実験。RA0につながったスイッチを押す→PORTBにつないである8ビット分のLEDがカウントアップ。RA1のスイッチを押す→カウントダウンということでおおむね成功。

 今回のソースコードはこんな感じ。

/*
  スイッチのテスト
  RA0のスイッチを押すたびにPORTBの出力をインクリメント
  RA1のスイッチを押すとPORTBの出力をデクリメント
*/
#include<pic.h>
#include<delay.h>
__CONFIG(0x3FF2);        //CONFIG:CP=OFF,PWRT=ON,WDT=OFF,OSC=HS
unsigned char led_outdata=0;  //出力データを初期化
void main(void)
{
  OPTION=0b00000000;     //オプションレジスタ
  INTCON=0b00000000;     //割り込みレジスタ
  PORTA=0b00000000;      //PORTAをリセット
  PORTB=0b00000000;      //PORTBをリセット
  TRISA=0b00000011;      //RA0,RA1を出力に設定
  TRISB=0b00000000;      //PORTBを出力に設定
  while(1)               //無限ループ
  {
    if(RA0){         //RA0が押されたときの処理
      DelayMs(30);     //チャタリング待ち
      if(RA0){
        led_outdata++;            //インクリメント
        led_outdata=led_outdata % 256 ; //8ビット以内の表示
        PORTB=led_outdata;       //PORTBに出力
      }
      while(RA0)
        {}                 //swがoffになるまで待つ
      DelayMs(100);            //も一回押すには0.1秒待ち
    }

    if(RA1){                  //RA1が押されたときの処理
      DelayMs(30);            //チャタリング待ち
      if(RA1){
        led_outdata--;         //デクリメント
        led_outdata=led_outdata % 256 ; //8ビット以内の表示
        PORTB=led_outdata;       //PORTBに出力
      }
      while(RA1)
        {}                 //swがoffになるまで待つ
      DelayMs(100);            //も一回押すには0.1秒待ち
    }
  }
}



 実際には完成したあとで某氏から指摘を受けたが、PORTA,PORTBの初期化はTRISA,TRISBレジスタの初期化よりも先にやらないといかんらしい。電源を入れた瞬間のPORTA,PORTBの値は不定なので、入出力の向きをTRISレジスタで指定する前に初期化しておかないと、TRISレジスタで入出力を指定したときに妙な値が出力されて妙なことになる恐れがあるらしい。今回みたいにつながってるのがLEDだけなら良いが、もっと重要なもんがつながってる場合を考えると先にデータの方を初期化しないといかんと。またひとつ勉強になった。

LEDを点灯させてみる

 なにはともあれLEDと抵抗その他が届いたので、PICボードに取り付けてみる。動作電圧が単3×3本で4.5V、レギュレータなしって事でLEDの順方向電圧が秋月のサイトを見ると2V。よって20mAくらい流すとするとLEDにつなぐ抵抗は150Ωくらいになる。

 しかしPICからの流出電流と言うことで8つ全部点灯しても50mAくらいに押さえる必要もあって、もっと大きな抵抗と言うことで1kΩを使った。これだと電流は2.5mAなので8つ全部点灯でも20mAで済む。

 接続先だが、16F84AにはPORTA、PORTBのふたつのI/OポートがあってPORTAのほうはRA0~4の5ビット、PORTBのほうはRB0~8の8ビットが使える。本当はPORTBが内部プルアップされてて入力に使うのが本筋らしいが、char型変数をまるごと出力できてわかりやすいPORTBの方を出力に使ってみる。

 プログラムの方は、さしあたって一番簡単に作ると言うことで「0.2秒ごとにPORTBに1ずつ増えた数字を出力する」というものに。0.2秒をカウントするためにはPICC-Liteのsampleディレクトリに入ってるDelayMs関数を使う。この場合MPLABのプロジェクト設定でdelay.cとdelay.hを追加しないといかんが、よく使う関数なんだから最初から#includeだけで使えるようになってた方がいいと思うんだがなあ。

 ソースコードは以下の通り。例によってこれをMPLAB上でコンパイルして.hexファイルをICPROGでチップに転送。

 んで電源を入れてみる。チカチカとLEDがカウントアップしていって成功。電流を抑えめにしたがLEDの明るさもそこそこ。

 基板用のタクトスイッチも取り付けてあるが、まだ配線はなし。PORTAを使っての入力は次回の宿題とするか。

LEDを付けてみた



#include < pic.h >
#include " delay.h "
__CONFIG(0x3FF2); //CONFIG:CP=OFF,PWRT=ON,WDT=OFF,OSC=HS
unsigned char led_outdata=0; //出力データを初期化
void main(void)
{
 OPTION=0b00000000; //オプションレジスタ
 INTCON=0b00000000; //割り込みレジスタ
 TRISB=0b00000000; //PORTBを出力に設定
 PORTB=0b00000000; //PORTBをリセット
 while(1)      //無限ループ
 {
  led_outdata++;
  led_outdata=led_outdata % 256 ;
  PORTB=led_outdata;   //PORTBに出力
  DelayMs(200);     //0.2秒待ち
 }
}



LEDが届いた。

 秋月通販で買った部品が届いた。
 今回買ったモノのリスト:

  [I-00562] 赤色LED 3mm(100個入り) [OSDR3133A]  1袋 ¥350
  [I-00639] 7セグLED 超高輝度赤色1文字(アノードコモン)[ボディ:黒] [A-551SRD] 5個 ¥300
  [R-25102] カーボン抵抗(炭素皮膜抵抗)1/4W 1KΩ(100本入) [RD25S 1K] 1パック ¥100
  [R-25512] カーボン抵抗(炭素皮膜抵抗)1/4W 5.1KΩ(100本入) [RD25S 5K1] 1パック ¥100
  [R-25202] カーボン抵抗(炭素皮膜抵抗)1/4W 2KΩ(100本入) [RD25S 2K] 1パック ¥100
  [R-25511] カーボン抵抗(炭素皮膜抵抗)1/4W 510Ω(100本入) [RD25S 510E] 1パック ¥100
  [R-25103] カーボン抵抗(炭素皮膜抵抗)1/4W 10KΩ(100本入) [RD25S 10K] 1パック ¥100
  [R-25513] カーボン抵抗(炭素皮膜抵抗)1/4W 51KΩ(100本入) [RD25S 51K] 1パック ¥100
  [R-25201] カーボン抵抗(炭素皮膜抵抗)1/4W 200Ω(100本入) [RD25S 200E] 1パック ¥100
  [R-25104] カーボン抵抗(炭素皮膜抵抗)1/4W 100KΩ(100本入) [RD25S 100K] 1パック ¥100
  [I-00881] トランジスタ 2SC1815GR(20個入) [2SC1815GR] 1袋 ¥100
  [I-00097] PICマイコン PIC16F84A-20/P [PIC16F84A-20/P] 3個 ¥900
  [P-00030] 丸ピンICソケット(18P) 3個 ¥150
  [P-00147] セラミック発振子(セラロック)コンデンサ内蔵タイプ 20MHz [CSTLS20M0X53] 3個 ¥120
  [I-00537] 3端子レギュレータ(低ドロップタイプ) 5V 1A TA4805S [TA4805S] 3個 ¥300
  [P-00207] バッテリースナップ(電池スナップ)プラスチック製 3個 ¥30
  [P-00517] 片面ガラスエポキシ・ユニバーサル基板 Cタイプ(72x48mm) 5枚 ¥300
  [P-00156] 鉛フリーはんだ(1.5m巻) [LLS-220] 5巻 ¥150
  [P-01084] タクトスイッチ 200個セット 1袋 ¥800

 というわけでおおむねの部品は買ったわけだが、見てわかるとおりコンデンサが入ってない。
 なんでかはしらんが、秋月通販だとコンデンサの品揃えが異様に少なかったり単価が高かったりなのだな。コンデンサの使い道ってのはだいたい電源回りかチップの近くにパスコン入れるか、クロック回りかもしくはモーターにノイズ吸収用に取り付けるというあたりなので、当面は困らんが。

FC2Ad

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。