PICでVDDの供給電圧を自己測定する

PICマイコンは電池で動かすことも多いのですが、電源電圧が低下したときに、誤動作が起きる前に警告できないかと思ってためしてみました。

使っていたのは12F1822、このデータシートを眺めても、VDD電圧を直接得る方法はなさそうなのですが、ADコンバーターの記述の中で内臓の基準電圧があることや、これを入力チャンネル選べることが分かりました。
これを使って、外付け部品は一切なしで、しかもIOピンも消費せずにVDDが測れそうな気がしたので実験してみました。

 //100分の1V単位で電圧を返す。
//VDDを基準としてFVRの2.048VをAD変換する。
//2.048:VDD = AD値:1023 より、VDD = 2.048*1023/AD値
unsigned short VDD_get(void){
ADCON0bits.GO_nDONE=1;
while(ADCON0bits.GO_nDONE==0);
return 209510 / ADRES;
}

int main(int argc, char** argv) {
OSCCON = 0b01011010 ; // 内部クロックは1MHzとする
FVRCON = 0b10000010 ; // FVR 2.048V
ADCON0 = 0b01111101 ; // ADCのチャンネルをFVRに設定
ADCON1 = 0b10000000 ; // ADCの基準電圧をVDDに、データは右詰め
  unsigned short v;
while(1){
v= VDD_get();
__delay_ms(500);
  }
}

このようにしたところ、無事VDDが測定できました。0.1V単位でテスターの値とよく一致します。ただ、定期的に0.1V程度低い値を出力することがあったのですが、複数回測定して平均すれば問題ない範囲だと。

MAX7219搭載のユニットで7セグLEDを制御

PICマイコンで7セグLEDを表示するには、かなりのIOピンを必要とする。なんとかならないかと思っていたら、MAX7219搭載でシリアル通信で8桁のLEDを表示できる部品を見つけた。300円程度で購入できたので早速試してみた。

電源のほかには、3つのIOが必要で、I2CよりSPIに近いが、一方的に2バイト送り付けるだけなので、デジタルIOで送信する関数を使ったほうがよさそう。16F18325で試してみた。

/*
* 13 RA0 DIN 送信データ
* 12 RA1 CS 開始フラグ。Lowでデータ送信開始
* 11 RA2 CLK クロック供給
*/
void max_write(unsigned char addr,unsigned char data){
    int i;
    const unsigned char BitMask = 0b10000000;
    RA1 = 0;//CSをLowにして受信待機させる。
    for(i=0;i<8;i++){
        RA2 = 0;//クロックをLowに
        RA0 = (addr&BitMask)!=0;
        addr <<= 1;
        RA2=1; //クロックをHighに
    }
    for(i=0;i<8;i++){
        RA2=0;
        RA0=(data&BitMask)!=0;
        data <<=1;
        RA2=1;
    } 
    RA1=1;
    RA2=0;
}

この関数にアドレスとデータをパラメーターにして呼び出すと表示するはずが、なかなか動かなくてかなり苦戦した。
単純なミスで、初期化をきっちりしていなかったのが原因。mainで以下のようにすると無事動作した。

unsigned char i,j;

max_write(0x0C,0x01);//nomal oparation
max_write(0x09,0x00);//No Decode
max_write(0x0A,0x07);//LED輝度
max_write(0x0B,0x07);//ScanLimit
for(i=1;i<9;i++){
    max_write(i,0);
}

特に1つ目と4つ目が肝心、ShutDownモードになっていると全く動かない。Display
Testをすると、ShutDownが解除されそうな記述を見たけれど、そんなことはなかった。
ScanLimitを8桁全部にしておかないと、いくらデータを送っても反映されない。

できてしまえば、あとは非常に楽。数字だけなら、Decodeモードを使ったほうがいい。

PIC16F18325を使ってみた

DCモーターとRCサーボをコントロールするために、PIC16F1503を試してマイコンの勉強をしてみたのだが、ある程度なれたので別の石もと買ってみたのが16F18325。DIPの14pinというのが、ブレッドボードですぐに試せるのでありがたい。この石、同じ16Fシリーズでも1503とは全くの別物。メモリはでかいし、内臓オシレーターで32MHz駆動、タイマ等の機能がグレードアップしている。多機能な分、MPLAB XのプラグインMCCをうまく使う必要がある。

嬉しい発見があった。CLCを使うことで、意外と扱いが難しいRCサーボを内蔵PWMを使い、200~550程度の約350段階でデューティを制御できた。方法は、拡張されたタイマ0とタイマ2,CLCを使う。

まず、16MHzで駆動する。タイマ0は8ビットモードとし1:512のプリスケーラー、20480usでオーバーフローする設定にする。タイマ2は1:64のプリスケーラー、PR2の値はタイマ0と合わせる。すると、プリスケーラーに8倍の差があるので、同時スタートさせると、タイマ0が1回OFするときに、タイマ2は8回OFする。このタイマ2を発信源にPWMを動かす。

CLCで入力にTimer0overflowを選ぶと、overflowのタイミングでHighとなり、次のカウントでLowとなる。今回1:512のPSを設定しているので、128usHighが続きその後Lowとなる。

一方Timer2matchも同様だが、こちらは1:64のPSなので、High時間は16us。普通のSRラッチでも同時発生すれば必ずTimer0が勝つ。

 

MCCのデザイナを使い、CLCにT0overflowとT2matchを入力源として、図のようにする。

次に、このCLC1の出力とPWMの出力をCLC2でANDとれば出来上がり。

CLC2の出力は、PWM5を一回出力したら7回休み。20480us秒の最初2560us秒だけがduty変更の対象となる。RCサーボはおおむね800us~2200usが操作範囲だから、dutyは200~550が有効範囲となる。

 

 

 

このロジアナの表示は、D0がCLC0の出力、D1が、PWM5の出力、D2が最終の出力。これで割り込みを使わずハードウェア制御でRCサーボを滑らかにコントロール可能。ただし、CLCの数-1台までに限る。

ちなみに、eepromに書き込もうと思ったら、MCC3.36が吐き出すコードでは、ダメだったので注意。

さらに、simulatorのロジアナではCLCはうまく表示されないので、これも要注意。

PICマイコンで赤外線リモコンを受信する。

秋月電子で、赤外線リモコンが\300であった。面白そうなので、PICマイコンで受信するプログラムを書いたが、意外と苦戦したので、備忘録として書いておこう。

卓球の練習マシンを作っていたが、上ローラー、下ローラー、発射角度、発射周期、首振りなど調整項目が多く、アナログボリュームだと配線が大変だし、なによりマイコンのアナログ入力のピン数が増える。赤外線リモコンが使えれば、入力はデジタル×1で済むし、パラメーターを記録しておける。

で、使用したPICは16F1503 14pinDIPパッケージ。PWMが4つあり、モーター制御に向いている。ただ、DCモーターとラジコンサーボでは、PWM周期が違いすぎて両立はできない。よって、サーボはタイマ割り込みを使うこととする。

上記のことから、赤外線の受信には16F1503を内蔵オシレーターでの最高周波数16MHzで使用、タイマ1のゲート機能を使うこととする。赤外線受信モジュールは、常時Highで受信するとLowと逆転するが、かえって都合がいい。Highの部分だけを見れば受信可能だ。

リーダーが4500us、0が560us、1が1600us、リピートが2300usということで、16ビットタイマの上位8ビットのみを使いカウントする。

//リモコン受信データ
unsigned char IRBUFF[4];
unsigned char read_state=0;
unsigned char ir_repeat;

void interrupt isr(void){
    unsigned static char byte_pos;
    unsigned static char bit_pos;
    unsigned static char skip=0;
    unsigned char cnt;

    if (TMR1IF){            //タイマ1がオーバーフローした場合。
        read_state = 0;     //読み取り状態をリセットする。
        ir_repeat = 0;
        skip = 1;           // タイマがオーバーフローしたら、そのときのパルスは無効。
        TMR1IF = 0;
    }
    if (TMR1GIF){
        cnt = TMR1H;    //パルス High時間を取得 2us/count(L) 512us/count(H)
        TMR1L = 200;    //タイマの値を初期化。0.5ms程度のパルスでカウントできるよう下駄はかせ
        TMR1H = 0;
        if (skip) skip = 0;
        else switch (read_state){
            case 0:     //ヘッダ待ちモード
                if(7<cnt && 11>cnt){  //3.8ms~4.9ms ならヘッダ
                    IRBUFF[0]=IRBUFF[1]=IRBUFF[2]=IRBUFF[3]=0;
                    read_state = 1;
                    bit_pos = 0;
                    byte_pos = 0;
                    ir_repeat = 0;
                }
                break;
            case 1:     //ヘッダ完了。データ待ちモード
                if(1 > cnt || 4 < cnt){
                    read_state = 0;
                }else{
                    IRBUFF[byte_pos] |= (cnt > 2) << bit_pos++;
                    if (bit_pos==8){ //8bit読んだら、次のバイトへ
                        byte_pos++;
                        bit_pos = 0;
                    };
                    if (byte_pos == 4){ //4byte読んだら終了、次のパルスは無視
                        skip = 1;
                        read_state = 2;
                    }
                }
                break;
            case 2:     //読み込み完了。リピート待ち。
                 if (6 > cnt && cnt > 3){
                    skip=1;
                    ir_repeat = 1;
                }else ir_repeat = 0;
            break;
        }
        TMR1GIF = 0;    //割り込みフラグを解除して
        T1GGO_nDONE = 1;//ゲート待機モードに
    }

}