wo

PIC-Cの関数

  1. PIC-Cの特徴

    1. データ型

       integer型は8ビット(1バイト)の符号なし整数となります。したがって、0-255の範囲を記録できますが、符号の判断や負になる計算はできませんから注意してください。符号を考慮する場合は
       signed int
      と宣言する必要があります。short は1ビット、long は2バイトデータです。char 型は、int と同じになります。他に、
       int1 int8,int16,int32
      の宣言が可能です。これは、変数のサイズをビット数で指定します。
      float は4バイトです。doubleは利用できません。

    2. 定数定義

       定数として、16進以外に2進数の定数定義が可能です。
          int some=0b00001010;
      int ss=0x7B;

    3. 定数機能

       PICでは、ROMのプログラムメモリとRAMのデータメモリは分離されています。プログラムエリアに記録した定数を利用するには、const を指定します。

      例:
           int const seg_data[ ]={0x3f,0x6,0x5b,4f,0x66,0x6d,0x7c,0x07,0x7f,0x67};
           int v=4;
           pb=seg_data[v];

    4. 構造体、enum

      構造体のビット指定も利用できます。
      struct data_record  {
          byte   a [2];
          byte  b : 2; /*2 bits */
          byte  c : 3; /*3  bits*/
          int d;
      }
      以下は列挙型のenumの例です。
      enum boolean  {false, true};

    5. 式、文

       式や文の形式は通常のC言語と同じです。算術関数も利用できます。また、シフトやビット演算用の関数も提供されています。
       enum や struct も利用できます。

    6. 数値、文字の表示

       RS232Cを接続すると、printf() でRS232Cに文字を出力します。format は少し特殊です。1バイトの整数にはu(符号なし)またはd(符号付)を利用します。2バイトの int の表示には、lu(符号なし)またはld(符号付)を利用します。浮動少数表示はfですが、小数点以下の桁数を指定するにはwを利用します。

  2. 入出力制御(関数)

    1. ポート出力

      ピン番号を指定して、値を設定します。ピン番号は、PIN_A0、とかPIN_B7 で指定します。値を変数で指定する方法と、値を定めた関数があります。 
       output_bit(pin,value)
       output_high(pin)
       output_low(pin)
       output_toggle(pin)
       端子の値を表示するには、発光ダイオードが便利です。output_low(PIN_A0) で、A0端子は L 出力になります。下図のように、LEDをA0端子に接続すると 330オームを通して発光ダイオード(LED)に電流が流れますから、LEDが点灯します。


       output_toggle(PIN_A0); を実行すると、呼び出すごとに、点灯、消燈と状態を変更できます。これは、動作の確認に便利です。4ビットの値をまとめて表示するには次のようにします。

       
      ポート全体の値を設定するには、次のようにします。val の値が ポートA 全体に出力され、下位4ビットがLEDで表示されます。この場合、A3,A2 端子のLEDが点灯し、A1,A0 のLEDが消灯します。val の第0ビット(この場合 1) がA0 に出力され A0 の端子は High レベル(5V)になります。A0に接続するLEDには電流が流れないため、点灯しません。
       val=0x3;
        output_a(val)
       val の第3ビット(この場合 0) はA3 に出力され A3 の端子は Low レベル(0V)になります。A3に接続するLEDの電流が流れ、点灯します。

    2. 入力回路とポート入力

      スイッチは最も簡単な入力素子です。下図は押しボタンスイッチで、スイッチが押されていないときは、A1 端子は電源(VCC)に接続していますからHレベルです。スイッチを押すと PICの A1端子はGNDに接続されるため、L レベルになります。


      ビット単位の入力は、次の関数で行います。
        val = input(pin);
      例 val = input(PIN_A1);
       スイッチが押されているとval の値は0、押されていないときは1になります。抵抗はスイッチを押したとき、VCCとGNDが短絡(ショート)するのを防ぎます。
       複数のスイッチが組み込まれた複合型スイッチもあります。下図は左は4個のスイッチが組み込まれています。中央はの回転型スイッチです。回転とともに、4個のスイッチが2進数の形でオン、オフします。
       

      このスイッチを入力するには以下のように、各スイッチと端子を接続します。


      この場合、次のように、Aポート全体をまとめて入力できます。
       val = input_a();
      val の第0ビットに、端子A0の値、第一ビットに端子A1の値が入ります。上の4ビットの値は不明ですから、AND演算で0にします。また、スイッチがオンのとき、入力1にするには  ~ 演算で0 と 1 を反転します。
           val = ~(input_a() & 0x0F)
      で、回転スイッチの値が2進数で val に入ります。

    3. まとめと入出力指定

       一般に、各端子を入出力端子として利用する場合、あらかじめ入出力の区別を設定しておく必要があります。しかし、ccs 社のコンパイラは、関数を利用して入出力を行うと、自動的に入出力の区別を設定してくれます。しかし、自動的な区別が困難な場合や時間を節約したい場合、set_tris_a(val) で、aポートの入出力の区別を指定します(0で出力、1で入力を指定)。
      set_tris_a(int:value) aポートの入出力を設定する
      output_a(int:value) aポートに値を出力する
      output_toggle(pin) 指定ピンの値を反転する
      output_bit(pin,value) 指定ピンに値を設定する
      int input(pin) 指定ピンに値を入力する
      int input_x() 指定ポートの値を入力する(x=a,b,c)
      out_b_pullups(boolean:flag) ポートBのプルアップを指定
       _a の替わりに他のポートを指定できます。

      また、入出力の設定自動を変更されたくない場合は
        #use fast_io(a)
      を宣言します。この場合、指定したポートの入出力の指定は、自己責任で行う必要があります。この設定は高速化にも有効です。

      例:ポートAを入力、ポートBを出力に設定し、ポートAの0ビットをポートBの0ビットに出力することを繰り返します。
      #include <16f84.h>
      #use fast_io(a)
      #use fast_io(b)
      
      main() {
        set_tris_a(0xff);  //ポートAを入力に設定
        set_tris_b(0);
      
        while(1){ //繰り返し処理
          output_bit(PIN_B0,input(PIN_A0));
        }
      }
      このプログラムを以下の回路で実行すると、スイッチがオンのとき、LEDが点灯します。


    4. 注意:PIN_A4

       16F87Xでは、RA4 端子はオープンコレクタ出力になります。出力として利用する場合はプルアップが必要です。また、16F648、88 系では RA4は入力専用で、出力はできません。

  3. タイマー処理

    1. 遅延指定

       絶対時間を指定した遅延を指定できます。まず、使用するクロックを指定します。これはプリプロセッサで指定します。

      #use delay(clock 10000000)
       
      この指定を行うと時間待ちを行う関数が利用できます。ミリ秒単位の遅延は、
       delay_ms()
      を利用します。
       delay_us(val)
      は、マイクロ秒単位での時間指定ですが、10マイクロ以下の細かい時間に指定は正確な動作はできません。

    2. タイマー0処理

      タイマーは、指定された信号のパルスの数を計数します。タイマー0は8bitの計数を行います。また、あらかじめ計数する信号を、1/2,1/4, ,1/128,1/256 に分周することもできます。以下に設定例を示します。

       setup_counters(RTCC_INTERNAL,RTCC_DIV_256)

      RTCC_INTERNALは計数信号を内部クロックの 1/4 の周波数の信号とします。RTCC_DIV_256 はこの信号をさらに 1/256 で使用することを指定します。他に、RTCC_DIV_128、RTCC_DIV_64 なども利用できます。

      カウンターの値は
       get_timer0();
      で読み込み
       set_timer0(val)
      で値を設定できます。タイマー0で設定できる val は8ビットです。プリスケーラも8ビットですから、20MHzのクロックの場合、最大計数時間は10m秒程度です。

    3. タイマー1

      タイマー1は

       setup_timer_1(T1_INTERNAL | T1_DIV_BY_8)

      で設定します。外部クロックを使用する場合、T1_EXTERNAL とします。また、分周は BY_8 以外に、BY_1、BY_2、BY_4 が利用できます。カウンターの値は
       get_timer1();
      で読み込み
       set_timer1(val)
      で値を設定できます。タイマー1は2バイトの計数値が利用できます。したがって、T1_DIV_BY_8 を組み合わせると、合計19ビットなります。20MHzのクロックの場合、最大計数時間は約100m秒程度まで係数できます。

    4. タイマー2

       タイマー2は、setup_timer_2 (mode, period, postscale)
      で設定します。modeは T2_DISABLED, T2_DIV_BY_1, T2_DIV_BY_4, T2_DIV_BY_16 で指定します。T2_DISABLED は利用しない場合、他は内部クロックの分割数です。period は8bit で周期を設定します。オーバーフローすると、2bit の postscale で分周したのち割り込みを起こします。カウンターの値は
       get_timer2(); で読み込み set_timer1(val) で値を設定できます。


      setup_timer_2 ( T2_DIV_BY_4, 0xc0, 2);
       20MHzの場合、800nSごとに増加し、153.6μ秒でオーバーフローし、307.2μ秒毎に割り込みを起こします。

    5. タイマー1の自動取り込み、比較

       タイマー1と2は連携して、CCPと呼ばれる機能を提供します。CCPでは capture:取り込み、compare:比較、pwm::発振、の機能があります。capture モードでは内部または外部からの信号で自動的に ccp_1(2) に取り込むことができます。正確な計数値の取り込みが必要な場合に利用します。また、compare モードでは、計数値とccp_1 の値が等しくなると、外部信号をセットしたり割り込みを起こすことができます。
       モードは次のように設定します。
       setup_ccp1(mode);
      modeは以下のような定数です。

      CCP_OFF: ccpモードを使用しない

      CCP_CAPTURE_FE:外部信号の立下りで取り込む
      CCP_CAPTURE_RE:外部信号の立上がりで取り込む
      CCP_CAPTURE_DIV_4:内部クロックの1/4で取り込む
      CCP_CAPTURE_DIV_16:内部クロックの1/16で取り込む

      CCP_COMPARE_SET_ON_MATCH:値が等しくなったら外部信号をセットする
      CCP_COMPARE_CLR_ON_MATCH:値が等しくなったら外部信号をクリアする
      CCP_COMPARE_INT:値が等しくなったら割り込みを起こす
      CCP_COMPARE_RESET_TIMER:値が等しくなったらタイマーをリセットする

      この機能は、次のPWM モードでも利用されるので、PWMと同時に利用することはできません。

    6. 発振:pwm

       PWMは 指定した周期と幅の発振信号を生成します。PWMを利用する場合
       setup_ccp1(CCP_PWM);
      でPWMモードを指定します。次に、
       setup_timer_2(T2_DIV_BY_16, period, postScaler);
      で、PWMのパルスの周期を指定します。T2_DIV_16はプリスケーラで、periodは周期の指定で最大値は255です。T2_DIV_BY_16 は他にT2_DIV_BY_4、T2_DIV_BY_1 が利用できます。period は内部で2bit追加されるため、合計10bitまで指定可能です。
      PWMの周期は内部クロックの周期をTckをすると、
       (period+1)*Tck*n*4
      となります。n は T2_DIV_n で指定する値です。postScalerは プリスケ-ラ*周期 の信号をさらに分周し、割り込みのタイミングを指定します。パルスの幅は
       set_pwm1_duty(width);
      で設定します。widthは10bitで1023まで指定できます。8ビットの変数を指定すると、4倍の値が設定されます、PWMモードの場合、割り込みはありません。

      例 周期25μSの方形波の場合、
      20MHzクロックで
      setup_ccp1(CCP_PWM);
      setup_timer_2(T2_DIV_BY_1, 124, 1);
      set_pwm1_duty(62);
       16F873の出力端子は PC2

  4. アナログ処理

    1. 参照電圧生成と電圧比較

       電圧生成と電圧比較機能を内蔵しているPICがあります。参照電圧の指定は16レベルのみです。このため、LOWとHIGHの二つのモードがあります。参照電圧は外部に取り出すこともできますが、次の比較器の比較電圧としても利用できます。

       setup_vref(VREF_LOW | val);  VCC*value/24 の電圧出力
       setup_vref(VREF_HIGH | val);  VCC*(1/4+value/32)の電圧出力

      なぜか、<16f648a.h> には、以下の定義がありません。
      #define VREF_LOW 0xa0
      #define VREF_HIGH 0x80
      追加が必要です。

      比較器は2組あり、次のように設定します。
       setup_comparater(mode);
      modeは A0_VR_A1_VR のように比較器への入力を指定します。PICにより異なりますから、詳細はヘダファイルを参照してください。最初の2信号が比較器1、次の2信号が比較2への入力です。VRはvrefで設定する参照電圧です。NCは無接続を意味します。OUTは比較結果を出力する端子を指定します。16F873Aは以下のように定義されています。最後の16進数値はハードウエアに設定される定数です。

      #define A0_A3_A1_A3 0xfff04
      #define A0_A3_A1_A2_OUT_ON_A4_A5 0xfcf03
      #define A0_A3_A1_A3_OUT_ON_A4_A5 0xbcf05
      #define NC_NC_NC_NC 0x0ff07
      #define A0_A3_A1_A2 0xfff02
      #define A0_A3_NC_NC_OUT_ON_A4 0x9ef01
      #define A0_VR_A1_VR 0x3ff06
      #define A3_VR_A2_VR 0xcff0e

      比較器の出力は、C1OUT, C2OUT として参照します。指定した入力で先頭の信号が次の信号より高いと 1 になります。比較電圧は設定可能ですから、比較にいわゆるヒステリシス機能を利用して雑音の影響を軽減することができます。

      例(12f675)
       A0とA1を参照電圧で比較します。
       setup_comparator(A0_VR_A1_VR);
       setup_vref(VREF_LOW | 15);
       if (C1OUT == 0){......}

    2. AD変換

       AD変換機能が内蔵されたPICがあります。AD変換を利用するには、まず、初期設定をします。以下はクロックの指定で、DIV_8,DIV_32 も可能です。

      setup_adc(ADC_CLOCK_DIV_2)

      以下は、アナログ入力可能な端子はすべてアナログ入力とする指定です。
      setup_adc_ports( ALL_ANALOG)

      次は入力するチャンネルを番号で指定します。
      setup_channnel(0)

      読み取りは、以下のように行います。
       long i = read_adc()
      チャンネル指定から、実際読み出すまで、A/Dクロック1*10+15 マイクロ秒 待つ必要があります。

  5. 通信処理

    1. 非同期シリアル通信

       RS232Cシリアル通信により COM ソケットのあるPCとデータの送受信が可能です。ただし、RS232Cは信号レベルがTTLでないため、外付けのレベル変換ICが必要です。RS232C機能が内臓されていないPICの場合はソフトウエアで処理を行います。

       パソコンのC言語の標準入出力装置はキーボードとディスプレイですが、PICの場合は シリアル(RS232C)になります。非同期シリアルを利用するには、最初にuse を宣言します。 XMIT、RCV、で送受信を行う端子を指定します。

      #include <16f84.h>
      #use delay(CLOCK=10000000)
      #use RS232(BAUD=9600, XMIT=PIN_A3 ,RCV=PIN_A4)

      送受信はprintf()、getc()、putc()、などの標準入出力関数を利用できます。printf()では、書式が指定できます。受信を行うと、受信完了まで戻りません。

    2. SSP(同期通信):SPIモード

       SSP(Synchronous Serial port)は、クロック信号を同時に送ることで、非同期通信より高速な(数MBPS)通信が可能です。SSPにも、1:1で高速な通信を行う SPI(Serial Peripheral Interface)と同時に複数のPICに接続できるI2Cがあります。
       SPIは各1台のマスタとスレーブで構成します。クロック(SCK)を送り出すPCがマスタになり、SDOから相手のSDIにデータを送ります。このとき同時に、相手のSDOから自分のSDIにデータを取り込むことができます。
       SPIの設定は以下のように行います。

      setup_spi(mode)
       ここで、modeは以下の設定をORして設定します。
       mode; SPI_MASTER,SPI_SLAVE,
           SPI_L_TOH,SPI_H_TO_L
           SPI_CLK_DIV_4,SPI_CLK_DIV_16,SPI_CLK_DIV_64,

      データの受信は、

       b = spi_read()
      で行います。この関数は、受信完了するまで戻りません。スレーブ側では必要なら

       b = spi_data_is_in()
      で受信完了をチェックします。

      送信は、
       spi_write(value)
      で行います。先に送信したデータは受信することでクリアされます。送受信完了に伴い SSPIF 割り込みが発生します。

    3. SSP(同期通信):I2Cモード

       マスタのクロック(SCL)とデータ(SDA)を複数のスレーブで受信して、同時に複数のPCにデータを送信することができます。通信速度は100または400kbpsです。データの中にアドレスデータが含まれますから、特定の相手にデータを送ることもできます。SCLとSDAは1kΩでプルアップが必要です。
       I2Cを利用するには、プリプロセッサで use を行います。機能を内蔵していない場合ソフトライブラリを取り込みます。
      #use I2C(MASTER, SDA=pin, SCI = pin,STREAM=id,fast=speed) //マスタ
      #use I2C(SLAVE, SDA=pin, SCI= pin, ADDRESS = val, fast=speed, STREAM=id,NOFORCE_SW) //スレーブ
      FASTで通信速度を指定できます。また、stream でI2Cの区別ができますから、ソフトで複数のI2CやSPIの送受信が可能です。マスタは送信開始、終了時に次の関数を実行します。
           i2c_start()
       i2c_stop()
      データ送信の先頭でアクセスする対象のチップアドレスを書き出します。その後、複数のデータを連続送信できます。
           i2c_write(byte)
      受信側では、
           byte = i2c_read()
      でデータを受信します。この関数は、受信が完了するまで戻りません。SSP機能を内蔵している場合
           byte = i2c_poll()
      で、データ受信をチェックできます。ハードウエアによる割り込み処理の内部では、以下の関数で、割り込み原因を判断できます。
       state=i2c_isr_state()
        0:アドレス一致
       1-0x7f:マスタからの送信があった
        0x80:マスタ送信でアドレスが一致した
       0x81-0xff:送信が完了し応答があった


    4. 以下は RTC 素子を例とします。 rtc_control_init() は 内部番地 0,1 のコントロールレジスタを初期設定します。0xa2 はRTC の固定チップアドレスで、このチップアドレスを指定すると、rtc素子が応答します。したがって、他の素子のチップアドレスは 0xa2 以外を選択します。チップアドレスの最後のビットは、読み:1、書き:0 を指定します。したがって、同じチップアドレスでも、マスタが読み出す場合、チップアドレスに 0xa3 を指定します。先頭の、delay_ms(700); は、rtc 独特で、素子の安定までの時間です。
      void rtc_control_init() { 
       delay_ms(700);
       i2c_start();
       i2c_write(0xa2); // 書き込みモード 
       i2c_write(0x00); // control0のアドレス
       i2c_write(0x0);
       i2c_stop();
      }
      以下は、1番地以後のデータを順に読み出します。最初にチップアドレス、次に内部アドレスを指定します。再度、
       i2c_start(); を行い、i2c_read(0) でデータを順に読み出します。読み出すとき 1 を渡すと次のデータを読み出し、0を渡すと、最終バイトの読み出しになります。
      void rtc_date_read()
      {
          i2c_start();
          i2c_write(0xa2); // 書き込みモード
          i2c_write(0x02); // 秒のアドレス
          i2c_start();
          i2c_write(0xa3); // 読み込みモード
          sec=  i2c_read(1); // 秒の値
          min=  i2c_read(1); // 分の値
          hour= i2c_read(1); // 時の値
          day=  i2c_read(1); // 日の値
          week= i2c_read(1); // 曜の値
          month=i2c_read(1); // 月の値
          year= i2c_read(0); // 年の値
          i2c_stop();
      }

    5. 割り込み処理

      以下は、クライアント側の割り込みによるハードウエア処理の例です。
      #INT_SSP
         void i2c_isr() {
            state = i2c_isr_state();
            if(state >= 0x80)
               i2c_write(send_buffer[state - 0x80]);
            else if(state > 0)
               rcv_buffer[state - 1] = i2c_read();
      }

    6. 注意

       I2Cのメモリに連続書き込みする場合、ページ境界に注意する必要があります。I2Cメモリは内部のアドレスカウンタは8ビットのため、255の境界をまたいで連続書き込みを行うと現在のページの0番地を書き直してしまいます。

  6. メモリ処理

    1. EEROM

       読み出し、書き込み用の関数があります。書き込みは数m秒が必要ですから、注意してください。
       val = read_eerom(adrs);
       write_eerom(adrs,val);