20msec毎にタイマー0で割り込みを発生させます。内部クロックは4MHzですので、1マシンサイクルは1μsecです。タイマー0のプリスケーラーを256にし、タイマーの初期値を178にすると、1μsec×(256−178)×256=19968μsec≒20msec
になります。サーボモーターに与える制御パルスはタイマー1で生成します。1500μsecを中心に±900μsec変化させ約180度回転させます。PICのアナログ入力、制御出力は2系統になります。タイマー1は、16ビットですのでカウント値は65536になります。タイマー1の割込みフラグは0C番地のPIRレジスタのbit0にあります。
サーボモーターの内部では、駆動軸につけられた可変抵抗器の抵抗値に応じたパルスが発生し、制御用のパルスとの幅が同じになるように駆動軸が回転します。PICは制御用のパルスを発生させます。制御用のパルスの幅は、PICに取り付けたスライドボリュームを動かすと変わるようにしています。電源電圧は5VでPICは12F675を使用します。内部発振回路を使用しますので、PICの周辺回路はシンプルです。制御用のパルスの周期は約20msec、サーボモーターの駆動軸角度はパルス幅が約0.6msec〜2.4msecに設定します。
参考のために、SG90をばらしてみました。中身はギヤボックス、モーター、両面実装基板で、この基板にFZMOS−2と記されたSOP8と、無印のSOP8が各々各面に実装されていました。この型番で検索してみたのですが、何も引っ掛かりませんでした。検索一覧の中に、TIのLM5110がありましたが、このICの英文のタイトルを直訳すると「負の出力電圧機能を備えたデュアル5-A複合ゲートドライバ」となっていました。更に検索を進めると、ロームのBD6210Fに突き当りましたが、このICは、ブラシ付きモーター用Hブリッジドライバでした。どうやらこちらの方が近いようです。
上図について、横軸はPICのAD変換が8ビット、最大電圧がVccの5V(5000mV)ですので、ビットステップとそれに対する電圧を表しています。このPICのAD変換は8ビットと10ビットの選択ができますが、ここでは8ビットにしています。縦軸はPWMの幅を時間で表しています。PWMの周期は20msecで、パルス幅は600μsecから2400μsecまで変化します。変化幅は1800μsecです。0Vのときは600μsecで、5Vのときは2400μsecです。これらの関係を式で表すと
パルス幅=(1800/255)*V+600 となります。
#byte PIR1=0x0cは#byteがプリプロセッサで、12F675のBank0の0ch番地にマッピングされており、TMR1IFビットのあるアドレスを指しています。PIR1はInterval関数の中で、クリアしておく必要あります。
#include <12F675.h>
#byte PIR1=0x0c //各モジュールの割込制御レジスタ
#fuses INTRC_IO,NOWDT,PUT,NOPROTECT,NOMCLR
//内部クロック使用
#DEVICE ADC=8 //AD変換8ビット
#use delay(clock=4000000) //クロック4M
Long haba0;
Long haba1;
int V0;
int V1;
#int_timer0 //タイマー0割込みルーティン
void Interval(void)
{
output_Low(PIN_A4); //サーボ1初期設定
output_Low(PIN_A5); //サーボ2初期設定
set_timer0(176); //タイマー0数値設定
haba0=(1800/255)*V0+600; //パルス幅と電圧の関係
PIR1=0; //割込み制御レジスタのリセット
set_timer1(65536-haba0); //タイマー1数値設定
output_high(PIN_A5); //タイマー1の割込みフラグが
while((PIR1 & 0x01)==0){ }; //1になるまでサーボ1を動かす
output_Low(PIN_A5);
haba1=(1800/255)*V1+600;
PIR1=0;
set_timer1(65536-haba1);
output_high(PIN_A4);
while((PIR1 & 0x01)==0){ };
output_Low(PIN_A4);
PIR1=0; //割込み制御レジスタのリセット
}
void main()
{
setup_timer_0(RTCC_INTERNAL | RTCC_DIV_256);
//タイマー0設定
//内部クロック使用、プリスケーラ―256
set_timer0(178); //タイマー0の設定
enable_interrupts(INT_TIMER0); //タイマー0の割込みをイネーブル
setup_timer_1(T1_INTERNAL | T1_DIV_BY_1);
//内部クロック使用、プリスケールなし
setup_adc_ports(sAN0 | sAN1 | VSS_VDD);
//アナログ入力に設定
setup_adc(ADC_CLOCK_DIV_8); // クロック4Mのときは2μsec
enable_interrupts(GLOBAL); //全体割込み許可
while(1)
{
set_adc_channel(0); //チャンネル0をアナログ入力とする
delay_us(50);
V0=read_adc(); //アナログデータを読み込む
set_adc_channel(1); //チャンネル1をアナログ入力とする
delay_us(50);
V1=read_adc(); //アナログデータを読み込む
}
}