F/Vコンバータをつくる(3)~プログラム~

2024/02/04

PIC 計測 自作

t f B! P L

前回からの続きです。

F/Vコンバータのプログラム

プログラムの主な内容は、以下の2つです。

  • 入力パルスの間隔を測定(→周波数を読み取り)
  • 周波数に対応した値をD/AコンバータにSPI通信で送信

使用したマイコンは、PIC16F88です。

パルス間隔の測定

パルス間隔の測定は、CCP1モジュールの「キャプチャ機能」を使いました。

キャプチャは、CCP1ピンの信号(今回は立ち下がりエッジ)をトリガとして、その瞬間のタイマ1カウンタの値をキャプチャレジスタに保存する、という機能です。こういう用途にぴったりです。

マイコンのクロックは20MHz、プリスケーラ1としたとき、タイマ1の1カウントは

タイマ1の1カウント時間

です。タイマ1のカウンタは16bitなので、約13.1msでカウンタがオーバーフローします。周波数にすると76.33Hzとなり、この値以下の周波数を検知できないことになります。

プリスケーラを8とした場合でも、104.8msでカウンタがオーバーフローするため、9.54Hz以下は検知することができません。

そこで、タイマ1がオーバーフローしたことを検知し、オーバーフローした回数をカウントする変数を用意します。このオーバーフローカウント変数とタイマ1を合わせて32bitのカウンタとする ことで、低い周波数の入力に対応することにしました。(プリスケーラ設定は1としました。)

また、パルス間隔の測定を行う際に、キャプチャによってカウンタ値を保存した後にタイマ1カウンタを停止し、カウンタクリア、再スタートさせています。そのため、キャプチャ時点からカウンタが再スタートするまでの時間が誤差となります。

この誤差を補正するために、キャプチャした値に補正値を加えています。

カウンタストップ・再スタート分の補正

D/Aコンバータ送信用にデータを加工

今回使用したD/Aコンバータ「MCP4922」は12bitなので、デジタル値は0~4096です。また、D/Aコンバータの出力電圧は、Vref=2.495Vとしたので、0~4.99Vとなります(D/AにてVrefのゲイン2倍に設定)。出力電圧と入力周波数の関係は、1V/100Hzです。

パルス間隔の計測値と、D/Aへ送信する値の対応式は、このようになります。

D/A値に換算

D/Aコンバータに値を送信

SPI通信は、SSPモジュールによるSPIモード(マスタ側)を使いました。

使用したD/Aコンバータ「MCP4922」は、SPIモード0と3に対応しているので、「モード0」としました。

(SPI通信のモードについてはこちら→SPI通信は「モード」に注意! SPI通信は4種類ある!

フローチャート

プログラムのフローチャートはこのようになってます。

F/Vコンバータのプログラムフローチャート
F/Vコンバータの割込プログラムフローチャート

ソースコード

#include <xc.h>
#include <pic16f88.h>
#include <stdio.h>

// CONFIG1
#pragma config FOSC = HS        // Oscillator Selection bits (HS oscillator)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = ON       // Power-up Timer Enable bit (PWRT enabled)
#pragma config MCLRE = OFF      // RA5/MCLR/VPP Pin Function Select bit (RA5/MCLR/VPP pin function is digital I/O, MCLR internally tied to VDD)
#pragma config BOREN = ON       // Brown-out Reset Enable bit (BOR enabled)
#pragma config LVP = OFF        // Low-Voltage Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming)
#pragma config CPD = OFF        // Data EE Memory Code Protection bit (Code protection off)
#pragma config WRT = OFF        // Flash Program Memory Write Enable bits (Write protection off)
#pragma config CCPMX = RB0      // CCP1 Pin Selection bit (CCP1 function on RB0)
#pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)

// CONFIG2
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable bit (Fail-Safe Clock Monitor disabled)
#pragma config IESO = OFF       // Internal External Switchover bit (Internal External Switchover mode disabled)

#define LDAC    PORTBbits.RB6
#define CS      PORTBbits.RB5
#define Hosei   22 
#define debug   PORTAbits.RA0
#define FS          499UL             //Full Scale=499Hz
#define Clock       20000UL           //kHz
#define Edge        0               //1:Risig edge , 0:Falling edge
#define _XTAL_FREQ  20000000        //CLK:20MHz

/*グローバル変数*****************/
unsigned short  overflow_cnt;           //
unsigned short  overflow_buff;          //
unsigned char update_flag;            //
unsigned char overflow_lim;
unsigned long t_value;
unsigned short DAC_dat_buff;
unsigned long Tfs;                  //Full Scale time
unsigned long t_value_old;

/*関数のプロトタイプ宣言**********/
void DAC_update(unsigned short);
void update_check(void);
void SPI_Write(unsigned char dat);

/*割込み処理*********************/
void __interrupt() INT(void){

  if(PIR1bits.TMR1IF){          // Timer1 overflow interrupt
     if(overflow_cnt < 0xFFFF){
       overflow_cnt++;
     }
     else{
       overflow_lim = 1;
     }
     PIR1bits.TMR1IF = 0;          //TMR1IF flag clear
  }

  if(PIR1bits.CCP1IF){          // input capture interrupt
     T1CONbits.TMR1ON = 0;          // Timer1 stop
     TMR1H = 0;                 // Counter Clear
     TMR1L = 0;
     T1CONbits.TMR1ON = 1;          // Timer1 start

     overflow_buff = overflow_cnt;
     overflow_cnt = 0;
     update_flag = 1;
     PIR1bits.CCP1IF = 0;            //CCP1IF flag clear
  }
}
/*******************************************************/
void main(void){

unsigned long t_value_now;
unsigned short val_buff;
    
 //各種設定   
 OPTION_REG = 0b11000000;  //Pull-up:D
 INTCON = 0b01000000;      //Perioheral Interrupt Enable
 PIE1 = 0x05;              //CCP1, Timer1 Interrupt Enable
 PIR1 = 0x00;
 PIE2 = 0x00;
 PIR2 = 0x00;
 PCON = 0x03;
 TRISA = 0b00000000;       //all output
 TRISB = 0b00000011;       //SDI, RB0(CCP1) input
 T1CON = 0x08;             //
 
 if(Edge){
    CCP1CON = 0x05;        //Capture mode, every rising edge
 }
 else{
    CCP1CON = 0x04;        //Capture mode, every falling edge
 }
 
 SSPSTAT = 0b01000000;     //data sample Middle, CKE:1, (SPI_mode0)
 SSPCON = 0b00100001;      //SSP_Enable, idle_L, OSC/16
 
 RCSTA = 0x00;
 ANSEL = 0b00000000;         //all digital
 CMCON = 0b00000111;         //Comparator disable

 Tfs = 250 * Clock / FS;
 
 PORTA = 0x00;        //clear
 PORTB = 0x00;
 update_flag = 0;
 overflow_lim = 0;
 overflow_cnt = 0;
 overflow_buff = 0;
 TMR1H = 0;
 TMR1L = 0;
 t_value_old = 0xFFFFFFFF;

 DAC_update(0);

 INTCONbits.GIE = 1;                    //
 T1CONbits.TMR1ON = 1;                  //Timer1 Run    
    
//Mainloop   
while(1){    
    
    update_check();

    t_value_now = (((unsigned long)overflow_cnt)<<16) + (((unsigned long)TMR1H) << 8) + TMR1L + Hosei;
   
    update_check();
    
    if((t_value_now > t_value_old ) && !overflow_lim){
        val_buff = ((Tfs << 12) / t_value_now);
        if(val_buff > 4095){
           val_buff = 4095;
        }
        DAC_update(val_buff);
    }

    if(overflow_lim){
       DAC_update(0);
       overflow_lim = 0;
    }    
}
}
/*****************************************************/
//DAC_update
//
//DACの値を更新する
/*****************************************************/
void DAC_update(unsigned short Data){

unsigned char dataH, dataL;

 dataH = (unsigned char)((Data>>8) | 0x0050);
 dataL = (unsigned char)(Data & 0x00FF);

 CS = 0;
 LDAC = 1;

 SPI_Write(dataH);
 SPI_Write(dataL);

 LDAC = 0;
 CS = 1;
}
/*****************************************************/
// update_check
//
// update_flagが1であればDACを更新
/*****************************************************/
void update_check(void){  
    
   if(update_flag){

      t_value = (((unsigned long)overflow_buff)<<16) + (((unsigned long)CCPR1H)<<8) + CCPR1L + Hosei;
           
      DAC_dat_buff = ((Tfs << 12) / t_value);
     
      if(DAC_dat_buff > 4095){
         DAC_dat_buff = 4095;
      }

      t_value_old = t_value;
      
      DAC_update(DAC_dat_buff);
      update_flag = 0;
   }
}
/*****************************************************/
// SPI_Write
//
// SPIで1byte送信
/*****************************************************/
void SPI_Write(unsigned char dat){   
    
SSPBUF = dat;   //送信開始
while(!SSPIF);   //送信完了までwait
SSPIF = 0;

}

次回へ続く)

当ブログはアフィリエイト広告を利用しています。

このブログを検索

最新記事

ブログ運営者:そのへんの誰か


このブログのURL

QooQ