PIC12F1840  i2cの設定とPWMの設定 (MPASM編)

2020.10.10
2021.02.18


1.概要

  i2cとPWMが使えて安価な8pinのPICをスレーブに使えば、ArduinoやRaspberryPi等のマイコンから制御可能なモジュールをいろ いろ作れると思いました。
 この条件にあうPICはPIC12F1840-I/P(秋月電子価格 税込110円)またはPIC12F1822-I/P(同 110円)があ ります。サーボモータやDCモータ等ちょっとした
小物をi2cでマスターに繋ぐ役割を果たすにはどちらでも差はないと思います。実験ではi2cのマスター側にも12F1840を使い、スレーブ側 にLCDと温度センサADT7840、12F1840を経由したサーボモータの3つを接続しました。

 2.仕様概要
 3.PIC12F1840のコンフィギュレーション等の設定
 4.PIC12F1840 i2c Master の設定
 5.PIC12F1840 i2c Master側の送受信プログラム
  6.i2c接続のLCDに表示(マスター側 プログラム)
  7.温度センサーADT7410との通信(マスター側 プログラム)
  8.サーボモータのDUTY値の送受信(マスター側 プログラム)
  9.マスター側プログラム全体
  10.PIC12F1840 スレーブ側のi2cプログラム
  11.PWM制御の設定
  12 スレーブ側プログラム全体

2.仕様概要(温度表示以外なんの役にもたたない仕様です)

 ハードウェア
    i2cマスター PIC12F1840-I/P
    i2cスレーブ LCD  AQM1602A (16文字✕2行),温度センサADT7410,PIC12F1840-I/P+サーボモータsg-90
  動作
  マスター側
   温度センサから温度を読み取りLCDの2行目に表示する
      温度は摂氏とし、10進数で表示。
   温度の1桁目が偶数ならスレーブ側のサーボモータを右いっぱいに回すPWM値を送信、
   温度の2桁目が偶数ならスレーブ側のサーボモータを右いっぱいに回すPWM値を送信、
   PWMの送信値はLCD 1行目左端に16進数で表示する。また約4秒毎にスレーブ側のPWM値を受信してLCDに表示する

  スレーブ側PIC12F1840
      電源投入時はサーボモータの角度を中央にする。
      受信割り込みによりマスターから受信したPWM値をサーボモータに出力する
   マスターからの送信要求に対して現在のサーボモータのPWM値を送信する


回路図



写真
実際のハード 単3電池2本はサーボ用、CPUとADT7410、LCDは左上の方から別途給電
LCDの1行目TX:0Fはマスターからスレーブに送ったDUTY値、RXは逆に受信したDUTY値
2行目の4桁のs数字はADT7410モジュールから受け取った数値、右側はそれを変換した温度

3.PIC12F1840のコンフィギュレーション等の設定

 クロックは内部発振器を使う。

        LIST    P=PIC12F1840,ST=OFF,R=DEC
        INCLUDE  "p12f1840.inc"

        __CONFIG _CONFIG1 , _FOSC_INTOSC & _WDTE_OFF & _PWRTE_ON &  _MCLRE_OFF & _CP_OFF & _CPD_OFF & _BOREN_ON & _CLKOUTEN_OFF & _IESO_OFF & _FCMEN_OFF
        __CONFIG _CONFIG2 , _WRT_OFF & _PLLEN_OFF & _STVREN_ON &  _BORV_HI & _LVP_OFF    

;CONFIG1の設定
;_FOSC_INTOSC 内部クロック使用(INTOSC),_WDTE_OFF WatchDogTimer無し, _PWRTE_ON 電源ONから64ms後プログラム開始
;_MCLRE_OFF 外部リセット信号使用しない RA3はデジタルピン  _CP_OFF プログラムメモリの保護無  _CPD_OFF dataメモリ保護無し
;_BOREN_ON 電源電圧降下常時監視機能ON  _CLKOUTEN_OFF ClockoutピンをRA4ピンで使用
;_IESO_OFF 外部・内部クロックの切り替えでの起動なし  _FCMEN_OFF 外部クロック監視無し   

OSCCONレジスタに値を設定してCPUのクロックを2MHzに設定する。(スレーブ側ではサーボモータSG-90をPWM制御するの に、これ以上高い 周波数は使えない。)

    BANKSEL OSCCON
    MOVLW   60H     ; 内部クロック2MHzの設定
    MOVWF   OSCCON 

4.PIC12F1840 i2c Master の設定

(1)ポートの設定

 マスター側PICもスレーブ側PICもi2cのポートとなるSCL(RA1),SDA(RA2)はデジタルピンの入力に設定す る

    ;PORTAをディジタルI/Oにする
    BANKSEL  PORTA
    CLRF    PORTA
    BANKSEL LATA
    CLRF    LATA
    BANKSEL ANSELA  ; digital I/O
    CLRF    ANSELA
    ;
    BANKSEL TRISA
    MOVLW  B'00000110' ; SCL,SDAのRA2,RA1入力,
    MOVWF  TRISA

(2)マスターの設定

   SSP1STATSMPビットをONにすることでI2Cマスターモードになる。

(3)I2Cポート(SCL,SDA)enableの設定

 SSP1CON1レジスタのSSP1EN をONにする

(4)I2Cマスターモードのクロックの設定など

 I2Cのクロックはマスター側のSSP1CON1<3:0>とSSP1ADDの値で決まる。
 
 まずSSP1CON1レジスタの<3:0>にb'1000'をセットすると
 CPUのクロックFosc[Hz]とi2cのバスクロックSCL[Hz]の関係式は次のようになる
        Fosc/(4*(SSP1ADD+1))=SCL[Hz]

  上の式からSSP1ADD=Fosc/(4*SCL) - 1
 
  Fosc=2MHz,SCL=100KHz=0.1MHz を代入するとSSP1ADD=4をセット すればよい。



 SSP1CON2をクリアする

  (2)から(4)のコード

  BANKSEL SSP1STAT
    MOVLW   80H
    MOVWF   SSP1STAT ; SMP=ON
    MOVLW   028H    ;SSP1EN=ON I2C master mode
    MOVWF   SSP1CON1
    CLRF    SSP1CON2
    MOVLW   04H ; 2MHz/((04H+1)*4)=100kHz
    MOVWF   SSP1ADD        ;100KBps設定
    ;
    BANKSEL PIR1
    BCF        PIR1,SSP1IF
    BANKSEL PORTA ;BANK0へ切り替え

i2c関連のレジスタは下記のとおり。データシートの240pにも記載されている。

INTCON
PIE1
PIE2
PIR1
PIR2
SSP1ADD
SSP1BUF
SSP1CON1
SSP1CON2
SSP1CON3
SSP1MSK
SSP1STAT
TRISA


5.PIC12F1840 i2c Master側の送受信プログラム

 Fig2はi2c通信の7bitアドレス方式のデータの流れである。
 上段がマスターからスレーブへのデータ書き込みである。
マスターが開始条件を送信したのち7bitのスレーブアドレスにWrite=0のフラグを付加して8bitにして送信する。
バス上のアドレスが一致したスレーブからACKが返信された後マスター側は8bit毎にデータを送る。
送信データが尽きたら終了条件を送信する。

 下段はマスターがスレーブからデータを受信する場合である。
マスターが開始条件を送信したのち7bitのスレーブアドレスにRead=1のフラグを付加して8bitにして送信する。
バス上のアドレスが一致したスレーブからAckが返信される。その後、スレーブが8ビットのデータをが送られるのでマスターは受信の都度 ACKを返す。

 i2cモードで動作している場合はデータの送受信はSSP1BUFを介して自動で行われる。送受信の完了はそれぞれ関連するフ ラグをチェックすることで判定できる。

  i2c データフォーム

i2c master


;=========================================================================
;   I2C  制御モジュール群(マスター側)
;=========================================================================
;**********************************************************************
; 開始条件送信
;   (1)通信状態ではないときSDA,SCLともにHIGH状態(22KΩの抵抗でこの2本をプルアップ
;   (2) SCLがHのままSDAがH --> L に変化したときが開始状態
;**********************************************************************
I2CSTART
    BANKSEL SSP1CON1
    BCF    SSP1CON1,WCOL    ; SSPCONの衝突フラグクリア
    BSF    SSP1CON2,SEN    ; 開始条件送信
    BTFSC    SSP1CON1,WCOL    ; 衝突あり?
    GOTO    I2CSTART    ; 衝突あり
    BTFSC    SSP1CON2,SEN    ; スタート終了?
    GOTO    $-1
    BANKSEL PORTA   ;BANK0へ切り替え
    RETURN
;**********************************************************************
; 受信処理の再開始条件送信
;**********************************************************************
I2CRESTART
    BANKSEL PIR1
    BCF    PIR1,SSP1IF    ;割り込みフラグクリア
    BANKSEL SSP1CON2    ;BANK切り替え
    BSF    SSP1CON2,RSEN    ; 再開始条件送信
    BTFSC   SSP1CON2,RSEN    ; 再開始条件出力?
    GOTO    $-1
    BANKSEL PORTA   ;BANK0へ切り替え
    RETURN
;**********************************************************************
;    入力 W: 送信データ 
;;  終了時BANK0のままになっていることに注意
;**********************************************************************
I2CSEND
    BCF    PIR1,SSP1IF    ; 割り込みフラグクリア
    BANKSEL SSP1BUF
    MOVWF   SSP1BUF        ; 送信
    BANKSEL PIR1
    BTFSS   PIR1,SSP1IF    ; 送信完了?
    GOTO    $-1
;
    BANKSEL SSP1CON2 ;BANKへ切り替え
    BTFSC   SSP1CON2,ACKSTAT ; ACK受信?
    GOTO    $-1    ;
;
    BANKSEL PORTA    ;BANK0へ切り替え
    RETURN
;**********************************************************************
;  1バイトデータ受信
;    出力 W 受信データ
;**********************************************************************
I2CRCV
    BCF    PIR1,SSP1IF    ; 割り込みフラグクリア
    BANKSEL SSP1CON2    ;BANKへ切り替え
    BSF    SSP1CON2,RCEN    ; 受信許可
    BTFSC   SSP1CON2,RCEN    ; 受信完了か?
    GOTO    $-1
    BCF    SSP1CON2,ACKDT    ;0:ACKをセット
    BSF    SSP1CON2,ACKEN    ;ACK送信
    BTFSC    SSP1CON2,ACKEN ;ACK送信完了?
    GOTO    $-1
    BANKSEL SSP1BUF   ;BANK切り替え
    MOVF    SSP1BUF,W    ;データ取得
    BANKSEL PORTA
    RETURN
;**********************************************************************
;   最終バイト受信   (最終バイトを受信して終了の時はCPUからNACKを送信)
;    出力 W 受信データ
;**********************************************************************
I2CREND
    BCF    PIR1,SSP1IF    ; 割り込みフラグクリア
    BANKSEL SSP1CON2    ;BANKへ切り替え
    BSF    SSP1CON2,RCEN    ; 受信許可
    BTFSC   SSP1CON2,RCEN    ; 受信完了か?
    GOTO    $-1
    BSF    SSP1CON2,ACKDT    ;1:NACKをセット
    BSF    SSP1CON2,ACKEN    ;NACK送信
    BTFSC   SSP1CON2,ACKEN ;NACK送信完了?
    GOTO    $-1
    BANKSEL SSP1BUF   ;BANK切り替え
    MOVF    SSP1BUF,W    ;データ取得
    BANKSEL PORTA
    RETURN
;**********************************************************************
; 終了条件送信
;**********************************************************************
I2CEND
    BCF    PIR1,SSP1IF    ; 割り込みフラグクリア
    BANKSEL SSP1CON2    ;BANK1へ切り替え
    BSF    SSP1CON2,PEN    ; ストップ出力
    BTFSC   SSP1CON2,PEN    ; 出力完了か?
    GOTO    $-1
    BANKSEL PORTA   ;BANK0へ切り替え
    BCF    PIR1,SSP1IF    ; 割り込みフラグクリア
    RETURN  


6.i2c接続のLCDに表示(マスター側 プログラム)

 LCDに文字が表示できるとデバッグがとても捗る。
秋月電子で購入したAQM1602A (16文字2行)のLCDを接続してみた。
初期設定とデータの表示位置を添付のdatasheetsでおさえておけばできる。このLCDはコントラストもプログラム制御で便利なのだが
設定は2つのレジスタにセットするのでちょっとわかりにくかった。
 尚、他のi2c接続のLCDはそれぞれデータの書式に作法があるので、単純に差し替えても表示できるとは限らない。

まず定数のワーキングレジスタの定義
LCDADR  EQU  7CH ; i2c LCD AQM1602XAのアドレス+R/wビット
                 ; Arduinoで使う場合はたぶん3EH
   以下中略


CBLOCK    020H
    CNT0        ;タイマ用カウンタ
    CNT1        ;タイマ用カウンタ
    CNT2        ;タイマ用カウンタ
    CNT3        ;汎用カウンタ、信号解析、LCD表示用など
    HEX2_WRK    ; HEX2LCDモジュールWRK
    LCDWBF      ; LCD書き込み用バッファ
  以下 略
ENDC

プログラム
 以下のプログラムは LCD初期化、LCDへのコマンド送信、LCDへのデータ送信、その他のLCDへの送信、でメイン処理で各モジュールをCALLで呼んで使うものである。
;+++++++++++++++++++++++++++++++++++++++++++++
;+        LCD初期化処 理                     +
;+++++++++++++++++++++++++++++++++++++++++++++
LCD_INIT
    CALL    TIM_50ms ; 40ms以上待つ
    CALL    I2CSTART    ;開始条件送信

    MOVLW   LCDADR ;スレーブアドレス+Wセット
    CALL    I2CSEND ; スレーブアドレス+W送信
; Function set  #1
    MOVLW   80H ;Co=ON RS=0
    CALL    I2CSEND ;control byte 送信
;   
    MOVLW   38H     ;Function Set(instruction table #0)
    CALL    I2CSEND; Instruction 送信
    CALL    TIM_10us ;30μS待つ
    CALL    TIM_10us
    CALL    TIM_10us
   
; Function set #2
    MOVLW   80H ;Co=ON RS=0
    CALL    I2CSEND ;Control byte 送信
;   
    MOVLW   39H     ;Function Set(instruction table #1)
    CALL    I2CSEND; Instruction 送信
    CALL    TIM_10us ;30μS待つ
    CALL    TIM_10us
    CALL    TIM_10us
   
; Internal OSC Frequency
    MOVLW   80H ;Co=ON RS=0
    CALL    I2CSEND ;control byte 送信
;   
    MOVLW   16H     ; 
    CALL    I2CSEND; Instruction 送信
    CALL    TIM_10us ;30μS待つ
    CALL    TIM_10us
    CALL    TIM_10us
      
; Contrast set
    MOVLW   80H ;Co=ON RS=0
    CALL    I2CSEND;control byte 送信
;   
    MOVLW   73H     ;
    CALL    I2CSEND; Instruction 送信
    CALL    TIM_10us ;30μS待つ
    CALL    TIM_10us
    CALL    TIM_10us  

; Power /Con /Contrast
    MOVLW   80H ;Co=ON RS=0
    CALL    I2CSEND;control byte 送信
;   
    MOVLW   54H     ;
    CALL    I2CSEND; Instruction 送信
    CALL    TIM_10us ;30μS待つ
    CALL    TIM_10us
    CALL    TIM_10us      

; Follower Control
    MOVLW   80H ;Co=ON RS=0
    CALL    I2CSEND;control byte 送信
;   
    MOVLW   6CH     ;
    CALL    I2CSEND; Instruction 送信
    CALL    TIM_100ms ;200ms待つ
    CALL    TIM_100ms
   
; Function set
    MOVLW   80H ;Co=ON RS=0
    CALL    I2CSEND;control byte 送信
;   
    MOVLW   38H     ;Function Set(instruction table #0に戻す)
    CALL    I2CSEND; Instruction 送信
    CALL    TIM_10us;30μS待つ
    CALL    TIM_10us
    CALL    TIM_10us      

; Clear Display
    MOVLW   80H ;Co=ON RS=0
    CALL    I2CSEND;control byte 送信
;   
    MOVLW   01H     ;
    CALL    I2CSEND; Instruction 送信
    CALL    TIM_10us;30μS待つ
    CALL    TIM_10us
    CALL    TIM_10us             

;  Display ON/OFF control
    MOVLW   00H ;Co=OFF RS=0
    CALL    I2CSEND;control byte 送信
;   
    MOVLW   0CH     ;
    CALL    I2CSEND ; Instruction 送信
    CALL    TIM_10us;30μS待つ
    CALL    TIM_10us
    CALL    TIM_10us
    GOTO    I2CEND; I2C終了条件送信の処理をしてリターン

;+++++++++++++++++++++++++++++++
; LCD COMMAND 実行 コマンド1個
; 入力 W
;+++++++++++++++++++++++++++++++
LCD_EXE
    BANKSEL PORTA
    MOVWF   LCDWBF    ;データ退避
   
    CALL    I2CSTART ;i2cスタート条件送信
;
    MOVLW   LCDADR ;LCDスレーブアドレス+W
    CALL    I2CSEND ;スレーブアドレス送信B

    MOVLW   00H ; Co=OFF RS=0
    CALL    I2CSEND ;control byte 送信
   
    MOVF    LCDWBF,W
    CALL    I2CSEND ; Instruction(=コマンド) 送信
   
    GOTO    I2CEND; I2C終了条件送信の処理をしてリターン

;+++++++++++++++++++++++++++++++
; LCD DATA WRITE
; 入力 W : (データ値)
;+++++++++++++++++++++++++++++++
LCD_WRITE
    BANKSEL PORTA
    MOVWF   LCDWBF    ;データ退避

    CALL    I2CSTART ;i2cスタート条件送信
;
    MOVLW   LCDADR ;LCDスレーブアドレス+W
    CALL    I2CSEND ;スレーブアドレス送信

    MOVLW   40H ; Co=OFF RS=1
    CALL    I2CSEND ;control byte 送信
   
    MOVF    LCDWBF,W
    CALL    I2CSEND ;data 送信
   
    GOTO    I2CEND ; I2C終了条件送信の処理をしてリターン

;+++++++++++++++++++++++++++++++
; LCD OFF
;+++++++++++++++++++++++++++++++
LCD_OFF
    MOVLW    08H
    GOTO    LCD_EXE
;+++++++++++++++++++++++++++++++
; LCD ON
;    表示ON カーソルはoff (カーソルonの場合は0EHを設定)
;+++++++++++++++++++++++++++++++
LCD_ON
    MOVLW    0CH
    GOTO    LCD_EXE
;+++++++++++++++++++++++++++++++
; LCD CLEAR
;+++++++++++++++++++++++++++++++
LCD_CLEAR
    MOVLW    01H
    CALL    LCD_EXE
    CALL    TIM_600us
    GOTO    TIM_1ms
;+++++++++++++++++++++++++++++++++++
; カーソルの位置を1行目の先頭にセット
;+++++++++++++++++++++++++++++++++++
LCD_L1  ;MSB=1 +DDRAMADDRESS(000 0000)
    MOVLW   80H
    GOTO    LCD_EXE
;+++++++++++++++++++++++++++++++++++
; カーソルの位置を2行目の先頭にセット
;+++++++++++++++++++++++++++++++++++
LCD_L2 ; MSB=1 +DDRAMADDRESS(100 0000)
    MOVLW   0C0H
    GOTO    LCD_EXE
;+++++++++++++++++++++++++++++++++++
; カーソルの位置を右シフト(つかってないけど)
;+++++++++++++++++++++++++++++++++++
LCD_RIGHT
    MOVLW   14H
    CALL    LCD_EXE
    GOTO    TIM_50us
   
;************************
;  16進数--> LCD 表示
;    例 3AH  --> 3A とカレントのカーソル位置から表示
;    入力  W レジスタ
;*************************
HEX2LCD
    MOVWF   HEX2_WRK    ; W 退避
    SWAPF   HEX2_WRK,W  ; 上位4ビット
    ANDLW   0FH        ;
    SUBLW   09H   ; 09H   - W --> W
    BTFSC   STATUS,C    ; 9よりおおきいとCはOffで次をスキップ
    GOTO    HEX2_01     ; 上位4bit9以下
    ; 上位4bit 10以上
    SUBLW   40H
    GOTO    HEX2_02

HEX2_01 ; 上位4ビット 9以下
    SUBLW   39H

HEX2_02
    CALL    LCD_WRITE

    MOVF    HEX2_WRK,W
    ANDLW   0FH
     SUBLW   09H   ; 09H   - W --> W
    BTFSC   STATUS,C    ; 9よりおおきいとCはOffで次をスキップ
    GOTO    HEX2_03     ; 上位4bit9以下
    ; 上位4bit 10以上
    SUBLW   40H
    GOTO    HEX2_04

HEX2_03 ; 上位4ビット 9以下
    SUBLW   39H
HEX2_04
    GOTO    LCD_WRITE  
 
; TIM_50us等の時間稼ぎモジュールはマスタープログラム全体のところを参照


7 温度センサーADT7410との通信(マスター側 プログラム)

 秋月電子でADT7410使用のi2c接続温度センサーモジュール(税込み500円)を購入。
 上記LCDの2行目に読み取ったデータと10進数の温度(摂氏)にした値を表示する

定数とワークの定義
LCDADR    EQU    7CH     ; i2c LCD AQM1602XAのアドレス+R/wビット
ADTADR_W    EQU    90H    ; ADT7410スレーブアドレス書き込みモード
ADTADR_R    EQU    91H    ; ADT7410スレーブアドレス読み込みモード

CBLOCK    020H
    CNT0        ;タイマ用カウンタ
    CNT1        ;タイマ用カウンタ
    CNT2        ;タイマ用カウンタ
    CNT3        ;汎用カウンタ、信号解析、LCD表示用など
    HEX2_WRK    ; HEX2LCDモジュールWRK
    TEMPH        ;温度上位バイト
    TEMPL        ;温度下位バイト 
    ONDO2        ; 10進温度の + - の符号
    ONDO1        ; 10進温度の2桁目
    ONDO0        ; 10進温度の1桁目
    T_WRKH        ; 温度計算ワーク
    T_WRKL        ; 温度計算ワーク
    ; (TEMPH,TEMPL) 10℃=0500H,1℃=0080H,0.5℃=0040H
    LCDWBF      ; LCD書き込み用バッファ
ENDC

温度センサーに対する初期設定は特にない。i2c通信がLCDとできていることを前提にする。回路はこのページに最初の方に接続端子として
描いてある。
mainプログラムのループの中で温度センサーから温度を読み取りLCDのに表示するところと、温度読み取りのためのモジュール、読み取り後の表 示のための計算モジュールを下に示す。

MAINLOOP
; 温度計測&表示
    CALL    ADT_READ; 温度取得 ---> TEMPH,TEMPL
    MOVLW 0C0H ;LCD 2行目0文字目に位置設定
    CALL  LCD_EXE
    MOVF    TEMPH,W
    CALL    HEX2LCD
    MOVF    TEMPL,W
    CALL    HEX2LCD
    ;
    CALL    T_CAL; 温度の10進数変換--->ONDO2,ONDO1,ONDO0
    CALL    T_MOD ; 温度表示調整
;    CALL    LCD_L2 ; LCD 2行目
    MOVLW 0C8H ;2行目8文字目に位置設定
    CALL  LCD_EXE   
    MOVF    ONDO2,W
    CALL    LCD_WRITE
    MOVF    ONDO1,W
    CALL    LCD_WRITE
    MOVF    ONDO0,W
    CALL    LCD_WRITE
    MOVLW   0F2H ;℃
    CALL    LCD_WRITE
    MOVLW   "C"
    CALL    LCD_WRITE   
  
    CALL    TIM_500ms
    CALL    TIM_500ms
    CALL    TIM_500ms
    CALL    TIM_500ms
 ;   
 ; Serbo motorモジュールからDuty値受信
    
    GOTO    MAINLOOP
 
;*****************************
;  ADT7410から温度を読み込む
;*****************************
ADT_READ
    CALL    I2CSTART    ;開始条件送信
    MOVLW    ADTADR_W        ;スレーブアドレスセット書き込みモード
    CALL    I2CSEND        ;
    MOVLW    0        ;温度レジスタHigh 0番地
    CALL    I2CSEND
;
    CALL    I2CRESTART    ; 開始条件再送信
    MOVLW    ADTADR_R    ;スレーブアドレスセット(読み出しモード)
    CALL    I2CSEND   
   
    CALL    I2CRCV    ;ADTより温度上位バイトを受信 --> W
    MOVWF    TEMPH        ;温度をTEMPHに格納
;
    CALL    I2CREND    ;ADTより温度下位を受信 --> W
    MOVWF    TEMPL
    GOTO    I2CEND
   
;*************************************************************
; 温度計算
;  (TEMPH,TEMPL)から10進数の温度(ONDO2,ONDO1,ONDO0)に変換
; (1)ONDO2にスペース20H,ONDO1,ONDO0はそれぞれ0クリア
; (2)TEMPHのMSBが1なら ONDO2に"-"を入れる
; (3)TEMPHのMSBが1なら (TEMPH,TEMPL)の0と1をひっくり返して+1する
; (4)(TEMPH,TEMPL)を2倍(左シフト)してT_WRKH,T_WRKLに入れる
; (5)T_WRKLのMSBが1なら小数点以下0.5以上なのでT_WRKHに+1する。
; (6)T_WRKHを10進数にして十の位をONDO1に、一の位をONDO0に入れる
; (7)ONDO1,ONDO0の数字を文字に変換
;*************************************************************
T_CAL
    MOVLW   20H
    MOVWF   ONDO2  ;十進温度の符号
    CLRF    ONDO1  ;十進温度十の位
    CLRF    ONDO0  ;十進温度一の位
    ;計算ワークに転送
    MOVF    TEMPH,W
    MOVWF   T_WRKH
    MOVF    TEMPL,W
    MOVWF   T_WRKL
    ;
    BTFSS   TEMPH,7 ; マイナス(氷点下)
    GOTO    T_CAL1
    ;(2)の処理
    MOVLW   "-"
    MOVWF   ONDO2
    ;(3)の処理 ひっくり返して+1
    COMF    T_WRKH,F
    COMF    T_WRKL,F
    INCF    T_WRKL,F
    BTFSC   STATUS,Z
    INCF    T_WRKH,F
T_CAL1
    ;(4)の処理
    RLF        T_WRKL,F
    RLF        T_WRKH,F
    ;(5)の処理
    BTFSC   T_WRKL,7 ; 0.5以上は四捨五入
    INCF    T_WRKH,F ;
    ;(6)の処理
T_CAL2
    MOVLW   0AH
    SUBWF   T_WRKH,F ; T_WRKH - W ---> T_WRKH
    BTFSS   STATUS,C
    GOTO    T_CAL3 ; T_WRKH -W <0
    ;                T_WRKH - W >=0
    INCF    ONDO1,F
    GOTO    T_CAL2
T_CAL3
    MOVLW   0AH
    ADDWF   T_WRKH,F ; 引きすぎたので戻す
    BTFSC   STATUS,Z
    GOTO    T_CAL5 ; ちょうど10の倍数だった
T_CAL4   
    INCF    ONDO0,F
    DECFSZ  T_WRKH,F
    GOTO    T_CAL4
T_CAL5
; 文字に変換
    MOVLW   30H
    ADDWF   ONDO1,F
    MOVLW   30H
    ADDWF   ONDO0,F
    RETURN
;***************************************************
; 温度表示補正
; ONDO1が0はスペースに置き換える
; ONDO1がスペースかつONDO0が0かつONDO2がマイナスの
; のときはマイナス表示をしない - 0  を  0の表示にする
;****************************************************
T_MOD
    MOVLW   30H
    SUBWF   ONDO1,W
    BTFSS   STATUS,Z
    GOTO    T_MOD1
    ; ONDO1 == 0
    MOVLW   20H
    MOVWF   ONDO1 ; スペースを入れる
    MOVLW   30H
    SUBWF   ONDO0,W
    BTFSS   STATUS,Z
    GOTO    T_MOD1
    ; ONDO0 == 0 かつONDO0 == 0 だった
    MOVLW   20H
    MOVWF   ONDO2 ; 氷点下でも氷点下でなくても符号をとる - 0 表示をしない
T_MOD1
    RETURN



8.サーボモータのDUTY値の送受信(マスター側 プログラム)


   温度(摂氏の10進表示)の1桁目が奇数なら、スレーブ側のサーボを制御するPICに右いっぱいにサーボを動かすためのDuty値を送信し、
奇数の場合は左いっぱいにサーボを動かすDUTY値を送信する。また送信値はLCDBの1行目に表示し、さらにスレーブ側から現在のサーボの DUTY値を受け取り表示する。
 スレーブ側CPUへの送信はアドレスを指定したあと、サーボモータのDUTY値1byteを送信するだけである。受信はスレーブ側 CPUがサーボモータに出力しているDuty値を1バイト受け取るだけである。
定数とワークの定義
LCDADR    EQU    7CH     ; i2c LCD AQM1602XAのアドレス+R/wビット
ADTADR_W    EQU    90H    ; ADT7410スレーブアドレス書き込みモード
ADTADR_R    EQU    91H    ; ADT7410スレーブアドレス読み込みモード
SERVOADR_W  EQU    0B6H    ;サーボモータモジュール書き込みアドレス
SERVOADR_R  EQU    0B7H    ; サーボモータモジュール読み込みアドレス
D_RIGHT    EQU 0FH ; Duty 2.5% Servo Left
D_CENTER    EQU    2FH ; Duty 7.25% Servo Center
D_LEFT    EQU 4EH ; Duty 12%  Servo Right

CBLOCK    020H
    CNT0        ;タイマ用カウンタ
    CNT1        ;タイマ用カウンタ
    CNT2        ;タイマ用カウンタ
    CNT3        ;汎用カウンタ、信号解析、LCD表示用など
    HEX2_WRK    ; HEX2LCDモジュールWRK
    D_VAL        ; Duty 5%〜10% 初期値 7.25%
    TEMPH        ;温度上位バイト
    TEMPL        ;温度下位バイト 
    ONDO2        ; 10進温度の + - の符号
    ONDO1        ; 10進温度の2桁目
    ONDO0        ; 10進温度の1桁目
    T_WRKH       ; 温度計算ワーク
    T_WRKL       ; 温度計算ワーク
    ; (TEMPH,TEMPL) 10℃=0500H,1℃=0080H,0.5℃=0040H
    LCDWBF      ; LCD書き込み用バッファ
ENDC

メインループとDuty値の送受信のプログラム部分
MAINLOOP
; 温度計測&表示
    CALL    ADT_READ; 温度取得 ---> TEMPH,TEMPL
    MOVLW 0C0H ;LCD 2行目0文字目に位置設定
    CALL  LCD_EXE
    MOVF    TEMPH,W
    CALL    HEX2LCD
    MOVF    TEMPL,W
    CALL    HEX2LCD
    ;
    CALL    T_CAL; 温度の10進数変換--->ONDO2,ONDO1,ONDO0
    CALL    T_MOD ; 温度表示調整

    MOVLW 0C8H ;2行目8文字目に位置設定
    CALL  LCD_EXE   
    MOVF    ONDO2,W
    CALL    LCD_WRITE
    MOVF    ONDO1,W
    CALL    LCD_WRITE
    MOVF    ONDO0,W
    CALL    LCD_WRITE
    MOVLW   0F2H ;℃
    CALL    LCD_WRITE
    MOVLW   "C"
    CALL    LCD_WRITE   
  
; ONDO0が偶数ならサーボを右いっぱいに、偶数なら左いっぱいに動かす
    BANKSEL PORTA
    MOVLW   D_RIGHT
    BTFSC   ONDO0,0 ;偶数?
    MOVLW   D_LEFT ;奇数
    MOVWF   D_VAL
    CALL    SEND_DUTY ;SLAVEにDuty値を送信
 ;  D_VAL 送信値の表示
    MOVLW   080H  ; 表示位置セット
    CALL    LCD_EXE
    MOVLW   "T"
    CALL    LCD_WRITE
    MOVLW   "X"
    CALL    LCD_WRITE
    MOVLW   ":"
    CALL    LCD_WRITE  
    MOVF    D_VAL,W
    CALL    HEX2LCD ;D_VALを表示  
 
    CALL    TIM_500ms
    CALL    TIM_500ms
    CALL    TIM_500ms
    CALL    TIM_500ms
 ;   
 ; Serbo motorモジュールからDuty値受信
    CALL    RECEIVE_DUTY ;duty値-->RCV_WRK
    MOVLW   088H  ; 表示位置セット
    CALL    LCD_EXE
    MOVLW   "R"
    CALL    LCD_WRITE
    MOVLW   "X"
    CALL    LCD_WRITE
    MOVLW   ":"
    CALL    LCD_WRITE     
    MOVF    RCV_WRK,W
    CALL    HEX2LCD ;受信D_VALを表示
   
    GOTO    MAINLOOP

;************************************************
; DUTY値送信
;************************************************
SEND_DUTY
    BANKSEL PORTA
    CALL    I2CRESTART    ;開始条件送信

    MOVLW    SERVOADR_W        ;スレーブアドレスセット書き込みモード
    CALL    I2CSEND        ;

    BANKSEL    PORTA
    MOVF    D_VAL,W ;D_VAL送信
    CALL    I2CSEND
    GOTO    I2CEND   

;************************************************
; DUTY値受信
;************************************************
RECEIVE_DUTY
    BANKSEL PORTA
    CALL    I2CRESTART    ;開始条件送信

    MOVLW    SERVOADR_R        ;スレーブアドレスセット読み込みモード
    CALL    I2CSEND        ;

    BANKSEL    PORTA
    CALL    I2CREND
    MOVWF    RCV_WRK ; 受信用WRKに退避
    GOTO    I2CEND
 


9.マスター側プログラム全体

; CPU ピン配置
;                 |--u--|   
;             VDD=|1   8|=VSS
;        PWM(RA5)=|2   7|=(RA0)
;           (RA4)=|3   6|=(RA1)SCL
;           (RA3)=|4   5|=(RA2)SDA
;                  ~~~~~~
        LIST    P=PIC12F1840,ST=OFF,R=DEC
        INCLUDE  "p12f1840.inc"

        __CONFIG _CONFIG1 , _FOSC_INTOSC & _WDTE_OFF & _PWRTE_ON &  _MCLRE_OFF & _CP_OFF & _CPD_OFF & _BOREN_ON & _CLKOUTEN_OFF & _IESO_OFF & _FCMEN_OFF
        __CONFIG _CONFIG2 , _WRT_OFF & _PLLEN_OFF & _STVREN_ON &  _BORV_HI & _LVP_OFF    
;CONFIG1の設定
;_FOSC_INTOSC 内部クロック使用(INTOSC),_WDTE_OFF WatchDogTimer無し, _PWRTE_ON 電源ONから64ms後プログラム開始
;_MCLRE_OFF 外部リセット信号使用しない RA3はデジタルピン  _CP_OFF プログラムメモリの保護無  _CPD_OFF dataメモリ保護無し
;_BOREN_ON 電源電圧降下常時監視機能ON  _CLKOUTEN_OFF ClockoutピンをRA4ピンで使用
;_IESO_OFF 外部・内部クロックの切り替えでの起動なし  _FCMEN_OFF 外部クロック監視無し   
   
;CONFIG2の設定
;_WRT_OFF flashmemory保護無し _PLLEN_OFF 32MHzでは動作させない _STVREN_ON stack overはリセットする
;_BORV_HI 電源電圧降下常時監視電圧2.5V設定 _LVP_OFF 低電圧プログラミング機能不使用
   
LCDADR    EQU    7CH     ; i2c LCD AQM1602XAのアドレス+R/wビット
ADTADR_W    EQU    90H    ; ADT7410スレーブアドレス書き込みモード
ADTADR_R    EQU    91H    ; ADT7410スレーブアドレス読み込みモード
SERVOADR_W  EQU    0B6H    ;サーボモータモジュール書き込みアドレス
SERVOADR_R  EQU    0B7H    ; サーボモータモジュール読み込みアドレス

D_RIGHT    EQU 0FH ; Duty 2.5% Servo Left
D_CENTER    EQU    2FH ; Duty 7.25% Servo Center
D_LEFT    EQU 4EH ; Duty 12%  Servo Right
 
CBLOCK    020H
        CNT0        ;タイマ用カウンタ
        CNT1        ;タイマ用カウンタ
        CNT2        ;タイマ用カウンタ
        CNT3        ;汎用カウンタ、信号解析、LCD表示用など
        HEX2_WRK    ; HEX2LCDモジュールWRK
      D_VAL        ; Duty 5%〜10% 初期値 7.25%
        TEMPH        ;温度上位バイト
        TEMPL        ;温度下位バイト 
        ONDO2        ; 10進温度の + - の符号
        ONDO1        ; 10進温度の2桁目
        ONDO0        ; 10進温度の1桁目
        T_WRKH        ; 温度計算ワーク
        T_WRKL        ; 温度計算ワーク
        ; (TEMPH,TEMPL) 10℃=0500H,1℃=0080H,0.5℃=0040H
   
        RCV_WRK        ;受信用Wrk (Duty値受信処理用)
        LCDWBF      ; LCD書き込み用バッファ
ENDC

RES_VECT  CODE    0x0000            ; processor reset vector
    GOTO    MAIN_START                   ; go to beginning of program

;===================================
;   今回は割り込みは使わないけれど
;  PICの割り込みはすべてここにくる
;==================================
    ORG    4
       GOTO    INTERRUPT  
;===================================
; 割り込み処理
;===================================
INTERRUPT
;-----レジスタ退避--------------------
        ;コンテキスト自動保存
    GOTO    GAIBU_INT    ; 外部割り込み

INTEND    ; 割り込み処理の終了処理
    BCF     INTCON,INTF ; 割り込みフラグクリア
;-----レジスタ復帰-------------------
    ;コンテキスト自動復帰
;------------------------------------
    RETFIE    ;    割り込み許可で戻る
;=============================
;  外部割り込み(今回使っていないから何もしない)
;=============================
GAIBU_INT
    GOTO    INTEND  
   
MAIN_START
    BANKSEL OSCCON
    MOVLW   60H     ; 内部クロック2MHzの設定
    MOVWF   OSCCON 

    BANKSEL INTCON
    BCF INTCON,GIE  ; 割り込み禁止
    BCF INTCON,PEIE ; 割り込み禁止
   
    ;PORTAをディジタルI/Oにする
    BANKSEL  PORTA
    CLRF    PORTA
    BANKSEL LATA
    CLRF    LATA
    BANKSEL ANSELA  ; digital I/O
    CLRF    ANSELA
    ;
    BANKSEL TRISA
    MOVLW  B'00000110' ; SCL,SDAのRA2,RA1入力,RA5出力
    MOVWF  TRISA
   
;ここからI2Cの設定  
    BANKSEL SSP1STAT
    MOVLW   80H
    MOVWF   SSP1STAT ; SMP=ON
    MOVLW   028H    ;SSP1EN=ON I2C master mode
    MOVWF   SSP1CON1
    CLRF    SSP1CON2
    MOVLW   04H ; 2MHz/((04H+1)*4)=100kHz
    MOVWF   SSP1ADD        ;100KBps設定
    ;
    BANKSEL PIR1
    BCF        PIR1,SSP1IF
    BANKSEL PORTA ;BANK0へ切り替え   
;ここまでi2cの設定   

    CALL    LCD_INIT
    CALL    LCD_ON
  
MAINLOOP
; 温度計測&表示
    CALL    ADT_READ; 温度取得 ---> TEMPH,TEMPL
    MOVLW 0C0H ;LCD 2行目0文字目に位置設定
    CALL  LCD_EXE
    MOVF    TEMPH,W
    CALL    HEX2LCD
    MOVF    TEMPL,W
    CALL    HEX2LCD
    ;
    CALL    T_CAL; 温度の10進数変換--->ONDO2,ONDO1,ONDO0
    CALL    T_MOD ; 温度表示調整
;    CALL    LCD_L2 ; LCD 2行目
    MOVLW 0C8H ;2行目8文字目に位置設定
    CALL  LCD_EXE   
    MOVF    ONDO2,W
    CALL    LCD_WRITE
    MOVF    ONDO1,W
    CALL    LCD_WRITE
    MOVF    ONDO0,W
    CALL    LCD_WRITE
    MOVLW   0F2H ;℃
    CALL    LCD_WRITE
    MOVLW   "C"
    CALL    LCD_WRITE   
  
; ONDO0が偶数ならサーボを右いっぱいに、偶数なら左いっぱいに動かす
    BANKSEL PORTA
    MOVLW   D_RIGHT
    BTFSC   ONDO0,0 ;偶数?
    MOVLW   D_LEFT ;奇数
    MOVWF   D_VAL
    CALL    SEND_DUTY ;SLAVEにDuty値を送信
 ;  D_VAL 送信値の表示
    MOVLW   080H  ; 表示位置セット
    CALL    LCD_EXE
    MOVLW   "T"
    CALL    LCD_WRITE
    MOVLW   "X"
    CALL    LCD_WRITE
    MOVLW   ":"
    CALL    LCD_WRITE  
    MOVF    D_VAL,W
    CALL    HEX2LCD ;D_VALを表示  
 
    CALL    TIM_500ms
    CALL    TIM_500ms
    CALL    TIM_500ms
    CALL    TIM_500ms
 ;   
 ; Serbo motorモジュールからDuty値受信
    CALL    RECEIVE_DUTY ;duty値-->RCV_WRK
    MOVLW   088H  ; 表示位置セット
    CALL    LCD_EXE
    MOVLW   "R"
    CALL    LCD_WRITE
    MOVLW   "X"
    CALL    LCD_WRITE
    MOVLW   ":"
    CALL    LCD_WRITE     
    MOVF    RCV_WRK,W
    CALL    HEX2LCD ;受信D_VALを表示
   
    GOTO    MAINLOOP
 
;*****************************
;  ADT7410から温度を読み込む
;*****************************
ADT_READ
    CALL    I2CSTART    ;開始条件送信
    MOVLW    ADTADR_W        ;スレーブアドレスセット書き込みモード
    CALL    I2CSEND        ;
    MOVLW    0        ;温度レジスタHigh 0番地
    CALL    I2CSEND
;
    CALL    I2CRESTART    ; 開始条件再送信
    MOVLW    ADTADR_R    ;スレーブアドレスセット(読み出しモード)
    CALL    I2CSEND   
   
    CALL    I2CRCV    ;ADTより温度上位バイトを受信 --> W
    MOVWF    TEMPH        ;温度をTEMPHに格納
;
    CALL    I2CREND    ;ADTより温度下位を受信 --> W
    MOVWF    TEMPL
    GOTO    I2CEND
   
;*************************************************************
; 温度計算
;  (TEMPH,TEMPL)から10進数の温度(ONDO2,ONDO1,ONDO0)に変換
; (1)ONDO2にスペース20H,ONDO1,ONDO0はそれぞれ0クリア
; (2)TEMPHのMSBが1なら ONDO2に"-"を入れる
; (3)TEMPHのMSBが1なら (TEMPH,TEMPL)の0と1をひっくり返して+1する
; (4)(TEMPH,TEMPL)を2倍(左シフト)してT_WRKH,T_WRKLに入れる
; (5)T_WRKLのMSBが1なら小数点以下0.5以上なのでT_WRKHに+1する。
; (6)T_WRKHを10進数にして十の位をONDO1に、一の位をONDO0に入れる
; (7)ONDO1,ONDO0の数字を文字に変換
;*************************************************************
T_CAL
    MOVLW   20H
    MOVWF   ONDO2  ;十進温度の符号
    CLRF    ONDO1  ;十進温度十の位
    CLRF    ONDO0  ;十進温度一の位
    ;計算ワークに転送
    MOVF    TEMPH,W
    MOVWF   T_WRKH
    MOVF    TEMPL,W
    MOVWF   T_WRKL
    ;
    BTFSS   TEMPH,7 ; マイナス(氷点下)
    GOTO    T_CAL1
    ;(2)の処理
    MOVLW   "-"
    MOVWF   ONDO2
    ;(3)の処理 ひっくり返して+1
    COMF    T_WRKH,F
    COMF    T_WRKL,F
    INCF    T_WRKL,F
    BTFSC   STATUS,Z
    INCF    T_WRKH,F
T_CAL1
    ;(4)の処理
    RLF        T_WRKL,F
    RLF        T_WRKH,F
    ;(5)の処理
    BTFSC   T_WRKL,7 ; 0.5以上は四捨五入
    INCF    T_WRKH,F ;
    ;(6)の処理
T_CAL2
    MOVLW   0AH
    SUBWF   T_WRKH,F ; T_WRKH - W ---> T_WRKH
    BTFSS   STATUS,C
    GOTO    T_CAL3 ; T_WRKH -W <0
    ;                T_WRKH - W >=0
    INCF    ONDO1,F
    GOTO    T_CAL2
T_CAL3
    MOVLW   0AH
    ADDWF   T_WRKH,F ; 引きすぎたので戻す
    BTFSC   STATUS,Z
    GOTO    T_CAL5 ; ちょうど10の倍数だった
T_CAL4   
    INCF    ONDO0,F
    DECFSZ  T_WRKH,F
    GOTO    T_CAL4
T_CAL5
; 文字に変換
    MOVLW   30H
    ADDWF   ONDO1,F
    MOVLW   30H
    ADDWF   ONDO0,F
    RETURN
;***************************************************
; 温度表示補正
; ONDO1が0はスペースに置き換える
; ONDO1がスペースかつONDO0が0かつONDO2がマイナスの
; のときはマイナス表示をしない - 0  を  0の表示にする
;****************************************************
T_MOD
    MOVLW   30H
    SUBWF   ONDO1,W
    BTFSS   STATUS,Z
    GOTO    T_MOD1
    ; ONDO1 == 0
    MOVLW   20H
    MOVWF   ONDO1 ; スペースを入れる
    MOVLW   30H
    SUBWF   ONDO0,W
    BTFSS   STATUS,Z
    GOTO    T_MOD1
    ; ONDO0 == 0 かつONDO0 == 0 だった
    MOVLW   20H
    MOVWF   ONDO2 ; 氷点下でも氷点下でなくても符号をとる - 0 表示をしない
T_MOD1
    RETURN

;************************************************
; DUTY値送信
;************************************************
SEND_DUTY
    BANKSEL PORTA
    CALL    I2CRESTART    ;開始条件送信

    MOVLW    SERVOADR_W        ;スレーブアドレスセット書き込みモード
    CALL    I2CSEND        ;

    BANKSEL    PORTA
    MOVF    D_VAL,W ;D_VAL送信
    CALL    I2CSEND
    GOTO    I2CEND   

;************************************************
; DUTY値受信
;************************************************
RECEIVE_DUTY
    BANKSEL PORTA
    CALL    I2CRESTART    ;開始条件送信

    MOVLW    SERVOADR_R        ;スレーブアドレスセット読み込みモード
    CALL    I2CSEND        ;

    BANKSEL    PORTA
    CALL    I2CREND
    MOVWF    RCV_WRK ; 受信用WRKに退避
    GOTO    I2CEND   
;=========================================================================
;   I2C  制御モジュール群
;=========================================================================
;**********************************************************************
; 開始条件送信
;   (1)通信状態ではないときSDA,SCLともにHIGH状態(22KΩの抵抗でこの2本をプルアップ
;   (2) SCLがHのままSDAがH --> L に変化したときが開始状態
;**********************************************************************
I2CSTART
    BANKSEL SSP1CON1
    BCF    SSP1CON1,WCOL    ; SSPCONの衝突フラグクリア
    BSF    SSP1CON2,SEN    ; 開始条件送信
    BTFSC    SSP1CON1,WCOL    ; 衝突あり?
    GOTO    I2CSTART    ; 衝突あり
    BTFSC    SSP1CON2,SEN    ; スタート終了?
    GOTO    $-1
    BANKSEL PORTA   ;BANK0へ切り替え
    RETURN
;
;**********************************************************************
; 受信処理の再開始条件送信
;**********************************************************************
I2CRESTART
    BANKSEL PIR1
    BCF    PIR1,SSP1IF    ;割り込みフラグクリア
    BANKSEL SSP1CON2    ;BANK切り替え
    BSF    SSP1CON2,RSEN    ; 再開始条件送信
    BTFSC   SSP1CON2,RSEN    ; 再開始条件出力?
    GOTO    $-1
    BANKSEL PORTA   ;BANK0へ切り替え
    RETURN

;**********************************************************************
;   データ送信
;    入力 W: 送信データ 
;**********************************************************************
I2CSEND
    BCF    PIR1,SSP1IF    ; 割り込みフラグクリア
    BANKSEL SSP1BUF
    MOVWF   SSP1BUF        ; 送信
    BANKSEL PIR1
    BTFSS   PIR1,SSP1IF    ; 送信完了?
    GOTO    $-1
;
    BANKSEL SSP1CON2 ;BANKへ切り替え
    BTFSC   SSP1CON2,ACKSTAT ; ACK受信?
    GOTO    $-1    ;
;
    BANKSEL PORTA    ;BANK0へ切り替え
    RETURN

;**********************************************************************
;  1バイトデータ受信
;    出力 W 受信データ
;**********************************************************************
I2CRCV
    BCF    PIR1,SSP1IF    ; 割り込みフラグクリア
    BANKSEL SSP1CON2    ;BANKへ切り替え
    BSF    SSP1CON2,RCEN    ; 受信許可
    BTFSC   SSP1CON2,RCEN    ; 受信完了か?
    GOTO    $-1
    BCF    SSP1CON2,ACKDT    ;0:ACKをセット
    BSF    SSP1CON2,ACKEN    ;ACK送信
    BTFSC    SSP1CON2,ACKEN ;ACK送信完了?
    GOTO    $-1
    BANKSEL SSP1BUF   ;BANK切り替え
    MOVF    SSP1BUF,W    ;データ取得
    BANKSEL PORTA
    RETURN

;**********************************************************************
;   最終バイト受信   (最終バイトを受信して終了の時はCPUからNACKを送信)
;    出力 W 受信データ
;**********************************************************************
I2CREND
    BCF    PIR1,SSP1IF    ; 割り込みフラグクリア
    BANKSEL SSP1CON2    ;BANKへ切り替え
    BSF    SSP1CON2,RCEN    ; 受信許可
    BTFSC   SSP1CON2,RCEN    ; 受信完了か?
    GOTO    $-1
    BSF    SSP1CON2,ACKDT    ;1:NACKをセット
    BSF    SSP1CON2,ACKEN    ;NACK送信
    BTFSC   SSP1CON2,ACKEN ;NACK送信完了?
    GOTO    $-1
    BANKSEL SSP1BUF   ;BANK切り替え
    MOVF    SSP1BUF,W    ;データ取得
    BANKSEL PORTA
    RETURN

;**********************************************************************
; 終了条件送信
;**********************************************************************
I2CEND
    BCF    PIR1,SSP1IF    ; 割り込みフラグクリア
    BANKSEL SSP1CON2    ;BANK1へ切り替え
    BSF    SSP1CON2,PEN    ; ストップ出力
    BTFSC   SSP1CON2,PEN    ; 出力完了か?
    GOTO    $-1
    BANKSEL PORTA   ;BANK0へ切り替え
    BCF    PIR1,SSP1IF    ; 割り込みフラグクリア
    RETURN  

;=======================================================================
;     LCD制御モジュール群
;=======================================================================
;+++++++++++++++++++++++++++++++++++++++++++++
;+        LCD初期化処 理                     +
;+++++++++++++++++++++++++++++++++++++++++++++
LCD_INIT
    CALL    TIM_50ms ; 40ms以上待つ
    CALL    I2CSTART    ;開始条件送信

    MOVLW   LCDADR ;スレーブアドレス+Wセット
    CALL    I2CSEND ; スレーブアドレス+W送信
; Function set  #1
    MOVLW   80H ;Co=ON RS=0
    CALL    I2CSEND ;control byte 送信
;   
    MOVLW   38H     ;Function Set(instruction table #0)
    CALL    I2CSEND; Instruction 送信
    CALL    TIM_10us ;30μS待つ
    CALL    TIM_10us
    CALL    TIM_10us
   
; Function set #2
    MOVLW   80H ;Co=ON RS=0
    CALL    I2CSEND ;Control byte 送信
;   
    MOVLW   39H     ;Function Set(instruction table #1)
    CALL    I2CSEND; Instruction 送信
    CALL    TIM_10us ;30μS待つ
    CALL    TIM_10us
    CALL    TIM_10us
   
; Internal OSC Frequency
    MOVLW   80H ;Co=ON RS=0
    CALL    I2CSEND ;control byte 送信
;   
    MOVLW   16H     ; 
    CALL    I2CSEND; Instruction 送信
    CALL    TIM_10us ;30μS待つ
    CALL    TIM_10us
    CALL    TIM_10us
      
; Contrast set
    MOVLW   80H ;Co=ON RS=0
    CALL    I2CSEND;control byte 送信
;   
    MOVLW   73H     ;
    CALL    I2CSEND; Instruction 送信
    CALL    TIM_10us ;30μS待つ
    CALL    TIM_10us
    CALL    TIM_10us  

; Power /Con /Contrast
    MOVLW   80H ;Co=ON RS=0
    CALL    I2CSEND;control byte 送信
;   
    MOVLW   54H     ;
    CALL    I2CSEND; Instruction 送信
    CALL    TIM_10us ;30μS待つ
    CALL    TIM_10us
    CALL    TIM_10us      

; Follower Control
    MOVLW   80H ;Co=ON RS=0
    CALL    I2CSEND;control byte 送信
;   
    MOVLW   6CH     ;
    CALL    I2CSEND; Instruction 送信
    CALL    TIM_100ms ;200ms待つ
    CALL    TIM_100ms
   
; Function set
    MOVLW   80H ;Co=ON RS=0
    CALL    I2CSEND;control byte 送信
;   
    MOVLW   38H     ;Function Set(instruction table #0に戻す)
    CALL    I2CSEND; Instruction 送信
    CALL    TIM_10us;30μS待つ
    CALL    TIM_10us
    CALL    TIM_10us      

; Clear Display
    MOVLW   80H ;Co=ON RS=0
    CALL    I2CSEND;control byte 送信
;   
    MOVLW   01H     ;
    CALL    I2CSEND; Instruction 送信
    CALL    TIM_10us;30μS待つ
    CALL    TIM_10us
    CALL    TIM_10us             

;  Display ON/OFF control
    MOVLW   00H ;Co=OFF RS=0
    CALL    I2CSEND;control byte 送信
;   
    MOVLW   0CH     ;
    CALL    I2CSEND ; Instruction 送信
    CALL    TIM_10us;30μS待つ
    CALL    TIM_10us
    CALL    TIM_10us
    GOTO    I2CEND; I2C終了条件送信の処理をしてリターン

;+++++++++++++++++++++++++++++++
; LCD COMMAND 実行 コマンド1個
; 入力 W
;+++++++++++++++++++++++++++++++
LCD_EXE
    BANKSEL PORTA
    MOVWF   LCDWBF    ;データ退避
   
    CALL    I2CSTART ;i2cスタート条件送信
;
    MOVLW   LCDADR ;LCDスレーブアドレス+W
    CALL    I2CSEND ;スレーブアドレス送信B

    MOVLW   00H ; Co=OFF RS=0
    CALL    I2CSEND ;control byte 送信
   
    MOVF    LCDWBF,W
    CALL    I2CSEND ; Instruction(=コマンド) 送信
   
    GOTO    I2CEND; I2C終了条件送信の処理をしてリターン

;+++++++++++++++++++++++++++++++
; LCD DATA WRITE
; 入力 W : (データ値)
;+++++++++++++++++++++++++++++++
LCD_WRITE
    BANKSEL PORTA
    MOVWF   LCDWBF    ;データ退避

    CALL    I2CSTART ;i2cスタート条件送信
;
    MOVLW   LCDADR ;LCDスレーブアドレス+W
    CALL    I2CSEND ;スレーブアドレス送信

    MOVLW   40H ; Co=OFF RS=1
    CALL    I2CSEND ;control byte 送信
   
    MOVF    LCDWBF,W
    CALL    I2CSEND ;data 送信
   
    GOTO    I2CEND ; I2C終了条件送信の処理をしてリターン

;+++++++++++++++++++++++++++++++
; LCD OFF
;+++++++++++++++++++++++++++++++
LCD_OFF
    MOVLW    08H
    GOTO    LCD_EXE
;+++++++++++++++++++++++++++++++
; LCD ON
;    表示ON カーソルはoff (カーソルonの場合は0EHを設定)
;+++++++++++++++++++++++++++++++
LCD_ON
    MOVLW    0CH
    GOTO    LCD_EXE
;+++++++++++++++++++++++++++++++
; LCD CLEAR
;+++++++++++++++++++++++++++++++
LCD_CLEAR
    MOVLW    01H
    CALL    LCD_EXE
    CALL    TIM_600us
    GOTO    TIM_1ms
;+++++++++++++++++++++++++++++++++++
; カーソルの位置を1行目の先頭にセット
;+++++++++++++++++++++++++++++++++++
LCD_L1  ;MSB=1 +DDRAMADDRESS(000 0000)
    MOVLW   80H
    GOTO    LCD_EXE
;+++++++++++++++++++++++++++++++++++
; カーソルの位置を2行目の先頭にセット
;+++++++++++++++++++++++++++++++++++
LCD_L2 ; MSB=1 +DDRAMADDRESS(100 0000)
    MOVLW   0C0H
    GOTO    LCD_EXE
;+++++++++++++++++++++++++++++++++++
; カーソルの位置を右シフト(つかってないけど)
;+++++++++++++++++++++++++++++++++++
LCD_RIGHT
    MOVLW   14H
    CALL    LCD_EXE
    GOTO    TIM_50us
   
;************************
;  16進数--> LCD 表示
;    例 3AH  --> 3A とカレントのカーソル位置から表示
;    入力  W レジスタ
;*************************
HEX2LCD
    MOVWF   HEX2_WRK    ; W 退避
    SWAPF   HEX2_WRK,W  ; 上位4ビット
    ANDLW   0FH        ;
    SUBLW   09H   ; 09H   - W --> W
    BTFSC   STATUS,C    ; 9よりおおきいとCはOffで次をスキップ
    GOTO    HEX2_01     ; 上位4bit9以下
    ; 上位4bit 10以上
    SUBLW   40H
    GOTO    HEX2_02

HEX2_01 ; 上位4ビット 9以下
    SUBLW   39H

HEX2_02
    CALL    LCD_WRITE

    MOVF    HEX2_WRK,W
    ANDLW   0FH
     SUBLW   09H   ; 09H   - W --> W
    BTFSC   STATUS,C    ; 9よりおおきいとCはOffで次をスキップ
    GOTO    HEX2_03     ; 上位4bit9以下
    ; 上位4bit 10以上
    SUBLW   40H
    GOTO    HEX2_04

HEX2_03 ; 上位4ビット 9以下
    SUBLW   39H
HEX2_04
    GOTO    LCD_WRITE  
 
;************************
;   TIM_10us 10μ秒待ち
;************************
TIM_10us        ; CALLで2
    GOTO    $+1        ;2
    GOTO    $+1        ;2
    GOTO    $+1        ;2
    RETURN        ;2

;************************
;  TIM_50us  50μ秒待ち
;************************
TIM_50us    ;2
    MOVLW   7   ;1
    MOVWF   CNT0    ;1
    DECFSZ  CNT0,F  ;1(2)
    GOTO    $-1     ;2
    NOP            ;1
    RETURN          ;2
;***************************
; TIM_100us   100μ秒待ち
;***************************
TIM_100us  ;100 micro sec timer   ;2
    MOVLW    15      ;1
    MOVWF   CNT0    ;1
    DECFSZ  CNT0,F  ;1(2)
    GOTO    $-1     ;2
    GOTO    $+1        ;2
    RETURN          ;2
;****************************
; TIM_170us     170μ秒待ち
;****************************
TIM_170us          ;2
    MOVLW    26     ;1
    MOVWF   CNT0    ;1
    DECFSZ  CNT0,F  ;1(2)
    GOTO    $-1     ;2
    GOTO    $+1     ;2
    NOP            ;1
    RETURN          ;2
;****************************
; TIM_220us     220μ秒待ち
;****************************
TIM_220us         ;2
    MOVLW   35   ;1
    MOVWF   CNT0    ;1
    DECFSZ  CNT0,F  ;1(2)
    GOTO    $-1     ;2
    RETURN          ;2
;****************************
; TIM_276us     276μ秒待ち
;****************************
TIM_276us        ;2
    MOVLW   44     ;1
    MOVWF   CNT0    ;1
    DECFSZ  CNT0,F  ;1(2)
    GOTO    $-1     ;2
    NOP            ;1
    RETURN          ;2
;****************************
; TIM_300us     300μ秒待ち
;****************************
TIM_300us        ;2
    MOVLW   48    ;1
    MOVWF   CNT0    ;1
    DECFSZ  CNT0,F  ;1(2)
    GOTO    $-1     ;2
    NOP             ;1
    RETURN          ;2
;****************************
; TIM_450us     450μ秒待ち
;****************************
TIM_450us
    MOVLW   73     ;1
    MOVWF   CNT0    ;1
    DECFSZ  CNT0,F  ;1(2)
    GOTO    $-1     ;2
    GOTO    $+1        ;2
    NOP            ;1
    RETURN          ;2
;****************************
; TIM_500us     500μ秒待ち
;****************************
TIM_500us        ;2
    MOVLW   81     ;1
    MOVWF   CNT0    ;1
    GOTO    $+1     ;2
    DECFSZ  CNT0,F  ;1(2)
    GOTO    $-1     ;2
    RETURN          ;2
;****************************
; TIM_550us     550μ秒待ち
;****************************
TIM_550us        ;2
    MOVLW   89     ;1
    MOVWF   CNT0    ;1
    GOTO    $+1     ;2
    DECFSZ  CNT0,F  ;1(2)
    GOTO    $-1   ;2
    NOP            ;1
    RETURN          ;2
;****************************
; TIM_600us     600μ秒待ち
;****************************
TIM_600us        ;2
    MOVLW   97     ;1
    MOVWF   CNT0    ;1
    GOTO    $+1     ;2
    DECFSZ  CNT0,F  ;1(2)
    GOTO    $-1     ;2
    GOTO    $+1        ;2
    RETURN          ;2
;****************************
; TIM_900us     900μ秒待ち
;****************************
TIM_900us    ; 2
    MOVLW   147    ;1
    MOVWF   CNT0    ;1
    DECFSZ  CNT0,F  ;1(2)
    GOTO    $-1        ;2
    GOTO    $+1        ;2
    GOTO    $+1        ;2   
    RETURN          ;2
;*************************
;  TIM_1ms  1m秒待ち
;*************************
TIM_1ms            ;2
    MOVLW   164     ;1
    MOVWF   CNT0    ;1
    DECFSZ  CNT0,F  ;1(2)
    GOTO    $-1     ;2
    GOTO    $+1     ;2
    NOP             ;1
    RETURN          ;2
;+++++++++++++++++++++++++
;  TIM_5ms   5m秒待ち
;*************************
TIM_5ms            ;2
    MOVLW   249     ;1
    MOVWF   CNT0    ;1
    GOTO    $+1     ;2
    GOTO    $+1     ;2
    GOTO    $+1     ;2
    NOP            ;1
    DECFSZ  CNT0,F  ;1(2)
    GOTO    $-5     ;2
    GOTO    $+1     ;2
    RETURN          ;2
;***************************
; TIM_10ms   10mS秒待ち
;***************************
TIM_10ms  ; 10mili sec timer
    CALL    TIM_5ms
    GOTO    TIM_5ms
;***************************
; TIM_50ms   50mS秒待ち
;***************************
TIM_50ms  ; 50mili sec timer
       MOVLW    10
    MOVWF    CNT1
    CALL    TIM_5ms
    DECFSZ  CNT1,F
    GOTO    $-2
    RETURN
;***************************
; TIM_100ms   100mS秒待ち
;***************************
TIM_100ms  ; 100ms timer
    MOVLW    20
    MOVWF    CNT1
    CALL    TIM_5ms
    DECFSZ  CNT1,F
    GOTO    $-2
    RETURN
;***************************
; TIM_250ms   250mS待ち
;***************************
TIM_250ms  ; 250msec timer
    MOVLW    50
    MOVWF    CNT1
    CALL    TIM_5ms
    DECFSZ  CNT1,F
    GOTO    $-2
    RETURN
;***************************
; TIM_500ms   500mS待ち
;***************************
TIM_500ms  ; 500msec timer
    MOVLW    100
    MOVWF    CNT1
    CALL    TIM_5ms
    DECFSZ  CNT1,F
    GOTO    $-2
    RETURN
;***************************
; TIM_1S   1秒待ち
;***************************
TIM_1S  ; 1sec timer
    MOVLW    0C8H
    MOVWF    CNT1
    CALL    TIM_5ms
    DECFSZ  CNT1,F
    GOTO    $-2
    RETURN
    END


10. PIC12F1840 スレーブ側のi2cプログラム

(1)スレーブ側初期設定 

  コンフィギュレーションはマスターと同じ。CPUのクロックはSG-90を動かす関係から2MHzに設定
  3.PIC12F1840 のコンフィギュレーション等の設定
  
    BANKSEL INTCON
    BCF INTCON,GIE  ; 割り込み禁止
    BCF INTCON,PEIE ; 割り込み禁止
   
    ;PORTAをディジタルI/Oにする
    BANKSEL  PORTA
    CLRF    PORTA
    BANKSEL LATA
    CLRF    LATA
    BANKSEL ANSELA  ; digital I/O
    CLRF    ANSELA
    ;
    ;SCL,SDAのRA1,RA2も入力
    ;RA5はPWMの出力
    BANKSEL TRISA
    MOVLW  B'00000110' ; RA2,RA1入力,RA5出力
    MOVWF  TRISA
   
;ここからI2Cの設定  
    BANKSEL SSP1STAT
    MOVLW   80H
    MOVWF   SSP1STAT ; SMP=ON
    MOVLW   36H    ;SSP1EN=ON I2C slave mode 7bit Address
    MOVWF   SSP1CON1
    MOVLW   SLVADR ;スレーブアドレスセット
    MOVWF   SSP1ADD
    BSF        SSP1CON2,SEN ;stretch enabled
    BSF        SSP1CON1,CKP ;stretch enabled
;    MOVLW   04H ; 2MHz/((04H+1)*4)=100kHz  <=この設定はマスターモード
;    MOVWF   SSP1ADD        ;100KBps設定
    ;
    BANKSEL PIR1
    BCF        PIR1,SSP1IF
    BANKSEL PORTA ;BANK0へ切り替え
;ここまでi2cの設定  

 ; ここに割り込み許可をしてからメインループへ
    BANKSEL  INTCON
    CLRF    INTCON
    BSF     INTCON,INTE ;外部割込み許可
    BSF        INTCON,PEIE ;周辺割込許可
    BSF     INTCON,GIE  ;周辺割込許可  
    BANKSEL PIE1
    BSF    PIE1,SSP1IE ; i2c 割り込み 許可 Enables the MSSP interrupt
    BANKSEL PORTA
 

(2)スレーブ側i2c送受信処理(割込処理)

i2c スレーブ側フローチャート


;===================================
;  PICの割り込みはすべてここにくる
;==================================
    ORG    4
    GOTO    INTERRUPT  
   
;===================================
; 割り込み処理
;===================================
INTERRUPT
;-----レジスタ退避--------------------
    ;コンテキスト自動保存
    BANKSEL SSP1STAT
    BTFSS   SSP1STAT,3 ; Start ?
    BRA    INTEND
    BTFSC   SSP1STAT,5 ; DATA ? (0:Address,1:DATA)
    BRA    INTEND
    BTFSS   SSP1STAT,2 ; R/W (1:Write(送信要求),0:Read(受信要求))
    BRA    I_READ
    BRA    I_WRITE
    
INTEND    ; 割り込み処理の終了処理
    BANKSEL INTCON
    BCF    INTCON,INTF ; 割り込みフラグクリア
    BCF    PIR1,SSP1IF ; 割込フラグクリア
;-----レジスタ復帰-------------------
    ;コンテキスト自動復帰
;------------------------------------
    RETFIE    ;    割り込み許可で戻る
   
;*******割り込み(受信要求処理)**********
I_READ
    BANKSEL SSP1BUF
    MOVF    SSP1BUF,W ; 最初は読み捨てる
R_LOOP ; 受信ループ
    BANKSEL SSP1CON1
    BSF    SSP1CON1,CKP ; stretch Enable
    BANKSEL PIR1
    BCF    PIR1,SSP1IF ;割り込みフラグクリア
    BANKSEL SSP1STAT
    BTFSC   SSP1STAT,BF ;データ受信待ち?
    BRA    DATAGET    ;受信有り
    BTFSS   SSP1STAT,P ;STOP検出?
    BRA    R_LOOP;受信待ちへ
    BRA    INTEND; 終了
DATAGET ;----データ受信処理
    BANKSEL SSP1BUF
    MOVF    SSP1BUF,W
    BANKSEL PORTA
    MOVWF   D_VAL ; Duty値セット
    SUBLW   D_RIGHT ; D_RIGHT - D_VAL --> W
    BTFSC   STATUS,C ; CがOFFなら D_RIGHT < D_VALなので次とばす
    BRA    R_LIMIT ; D_RIGHT >= D_VAL
L_CHK
    MOVF    D_VAL,W
    SUBLW   D_LEFT ; D_LEFT - D_VAL --> W
    BTFSS   STATUS,C ; CがONなら D_LEFT > D_VALなので次とばす
    BRA L_LIMIT ; D_LEFT < D_VAL
DUTYSET
    CALL    D_SET
    BRA    R_LOOP
   
R_LIMIT    ;右限界値オーバー ,修正
    MOVLW   D_RIGHT
    MOVWF   D_VAL
    BRA    L_CHK

L_LIMIT ; 左限界値オーバー、修正
    MOVLW   D_LEFT
    MOVWF   D_VAL
    BRA        DUTYSET

 ;*******割り込み(送信要求処理)**********
I_WRITE
    BANKSEL PORTA
    MOVF    D_VAL,W ;duty値取り出し
;    XORLW   0FFH ;0,1反転
    BANKSEL SSP1BUF
    MOVWF   SSP1BUF ;I2Cにduty値送信
    BSF    SSP1CON1,CKP ; Stretch 解除
    BANKSEL PIR1
    BCF    PIR1,SSP1IF ;割り込みフラグクリア
    BTFSC   PIR1,SSP1IF     ; Ready ?
    BRA    I_WRITE
    BANKSEL SSP1STAT
    BTFSS   SSP1STAT,P ;STOP?
    BRA    I_WRITE
    BRA    INTEND


11 PWMの設定

 ここではサーボモータSG-90用を制御するための設定を行う。

Pwm Period
 
 SG-90のPWM Period(PWM周期)は20ms (f=50Hz) 
  Pulse width(=Duty cycle)は0.5ms〜2.4ms
 0.5msのとき-90°(上から見て右回り限界)
 2.4msのとき 90°(上から見て左回り限界)

設定手順
     pic12f1840のdatasheetの188pにPWM設定に関するレジスタの一覧表がある。
(1)CPUのクロックの確認
  コンフィギュレーション(マスター用と同じ)から内部発振器を使っていることがわかる。
         3.PIC12F1840 のコンフィギュレーション等の設定
  クロック周波数はOSCCONレジスタ<6:3>で定義される。  (datasheet 53p)
    OSCCONに60Hをセットして2MHzになっている

(2)PWMの出力ピンの決定
    APFCONレジスタのCCP1SELレジスタ(datasheet 99p)   1:RA5   0:RA2
    1をセットしてRA5を出力ピンに設定

(3)PWMモード設定
   CCP1CONレジスタ(datasheet189p)のCCP1Mに1100をセット
  
(4)PWM Period(PWM周期)の設定
 
  PWM Periodを20msに設定したい。
 PWM Periodは次式で決まる

  PWM Period [sec] = (PR2+1)*4*Tosc * Prescaler

  ToscはこのCPUのクロック  Tosc=1/f=1/(2*10^6)=0.5μs=0.0005ms
  PrescalerはT2CONレジスタのT2CKPSビットの設定によって決まる
 T2CKPS=11をセット prescalerは64
 あとはPR2の値を求めればよい。


 PR2=PWM Period /(4*Tosc*Prescaler)-1
   
     =20[ms]/(4*0.0005[ms]*64) - 1 =  155 (小数点は四捨五入)

(4)Pulse widh の値の算出

 Pulse width(Duty値)はCCPR1Lレジスタの 8bit とCCPICONレジスタ2ビットの全10bitで構成される。
 CCPR1Lレジスタが上位8bit,CCPICON<5:4>の2bitが下位になる。
 この10bitをCCPR1L:CCP1CON<5:4> とすると

 PulseWidth = CCPR1L:CCP1CON<5:4> * Tosc * Prescaler

  という関係になる。
 
 PulseWidth = PWM Period つまりDuty比100%となるCCPR1L:CCP1CON<5:4>の値は

 CCPR1L:CCP1CON<5:4> = PulseWidth/(Tosc*Prescaler)

  =20[ms]/(0.0005[ms]*64)=625

 サーボモータsg-90は0.5[ms]〜2.4[ms]のPulseWidthでこれはPWM Period=20[ms]の2.5%〜12%である。
 -90° 0.5[ms]のCCPR1L:CCP1CON<5:4>の値は625*0.025=15=0FH
  90° 2.4[ms]のCCPR1L:CCP1CON<5:4>の値は625*0.12=75=4BH
 になる。

(5)PWM制御スタート
 (4)のCCPR1L:CCP1CON<5:4>の値が設定できたら T2CONレジスタのTMR2をONにするとPWMがスタートする
 

12 スレーブ側プログラム全体

; TODO INSERT CONFIG CODE HERE USING CONFIG BITS GENERATOR
; CPU ピン配置
;                 |--u--|   
;             VDD=|1   8|=VSS
;        PWM(RA5)=|2   7|=(RA0)
;           (RA4)=|3   6|=(RA1)SCL
;       MCLR(RA3)=|4   5|=(RA2)SDA
;                 ~~~~~~
   
        LIST    P=PIC12F1840,ST=OFF,R=DEC
        INCLUDE  "p12f1840.inc"

        __CONFIG _CONFIG1 , _FOSC_INTOSC & _WDTE_OFF & _PWRTE_ON &  _MCLRE_OFF & _CP_OFF & _CPD_OFF & _BOREN_ON & _CLKOUTEN_OFF & _IESO_OFF & _FCMEN_OFF
        __CONFIG _CONFIG2 , _WRT_OFF & _PLLEN_OFF & _STVREN_ON &  _BORV_HI & _LVP_OFF    
;CONFIG1の設定
;_FOSC_INTOSC 内部クロック使用(INTOSC),_WDTE_OFF WatchDogTimer無し, _PWRTE_ON 電源ONから64ms後プログラム開始
;_MCLRE_OFF 外部リセット信号使用しない RA3はデジタルピン  _CP_OFF プログラムメモリの保護無  _CPD_OFF dataメモリ保護無し
;_BOREN_ON 電源電圧降下常時監視機能ON  _CLKOUTEN_OFF ClockoutピンをRA4ピンで使用
;_IESO_OFF 外部・内部クロックの切り替えでの起動なし  _FCMEN_OFF 外部クロック監視無し   
   
;CONFIG2の設定
;_WRT_OFF flashmemory保護無し _PLLEN_OFF 32MHzでは動作させない _STVREN_ON stack overはリセットする
;_BORV_HI 電源電圧降下常時監視電圧2.5V設定 _LVP_OFF 低電圧プログラミング機能不使用
   
SLVADR  EQU    0B6H ; このプログラムのスレーブアドレス; 1011011+0
;
R_BTN    EQU 0        ; Right tact ボタン A port ビット
L_BTN    EQU 4        ; Left Tact ボタン A port ビット

PWMPRD    EQU 159 ; PWM period for PR2

D_LEFT    EQU 4EH ; Duty 2.5% Servo Left
D_CENTER    EQU    2FH ; Duty 7.25% Servo Center
D_RIGHT    EQU 0FH ; Duty 12%  Servo Right
 
    CBLOCK    020H
    CNT0        ;タイマ用カウンタ
    CNT1    ;タイマ用カウンタ
    CNT2    ;タイマ用カウンタ
    CNT3    ;汎用カウンタ、信号解析、LCD表示用など
    HEX2_WRK    ; HEX2LCDモジュールWRK
    D_VAL    ; Duty 5%〜10% 初期値 7.25%
    D_VAL_WRK   ; Duty wrk
    G_WRK    ; 汎用WRK

    W_SAVE      ; 割り込み処理 W退避
    PL_SAVE     ; 割り込み処理 PCLATH 退避
    FS_SAVE     ; 割り込み処理  FSR退避
    S_SAVE      ; 割り込み処理  STATUS退避

    ENDC
 
RES_VECT  CODE    0x0000            ; processor reset vector
    GOTO    MAIN_START                   ; go to beginning of program

;===================================
;  PICの割り込みはすべてここにくる
;==================================
    ORG    4
    GOTO    INTERRUPT  
   
;===================================
; 割り込み処理
;===================================
INTERRUPT
;-----レジスタ退避--------------------
    ;コンテキスト自動保存
    BANKSEL SSP1STAT
    BTFSS   SSP1STAT,3 ; Start ?
    BRA    INTEND
    BTFSC   SSP1STAT,5 ; DATA ? (0:Address,1:DATA)
    BRA    INTEND
    BTFSS   SSP1STAT,2 ; R/W (1:Write(送信要求),0:Read(受信要求))
    BRA    I_READ
    BRA    I_WRITE
    
INTEND    ; 割り込み処理の終了処理
    BANKSEL INTCON
    BCF    INTCON,INTF ; 割り込みフラグクリア
    BCF    PIR1,SSP1IF ; 割込フラグクリア
;-----レジスタ復帰-------------------
    ;コンテキスト自動復帰
;------------------------------------
    RETFIE    ;    割り込み許可で戻る
   
;*******割り込み(受信要求処理)**********
I_READ
    BANKSEL SSP1BUF
    MOVF    SSP1BUF,W ; 最初は読み捨てる
R_LOOP ; 受信ループ
    BANKSEL SSP1CON1
    BSF    SSP1CON1,CKP ; stretch Enable
    BANKSEL PIR1
    BCF    PIR1,SSP1IF ;割り込みフラグクリア
    BANKSEL SSP1STAT
    BTFSC   SSP1STAT,BF ;データ受信待ち?
    BRA    DATAGET    ;受信有り
    BTFSS   SSP1STAT,P ;STOP検出?
    BRA    R_LOOP;受信待ちへ
    BRA    INTEND; 終了
DATAGET ;----データ受信処理
    BANKSEL SSP1BUF
    MOVF    SSP1BUF,W
    BANKSEL PORTA
    MOVWF   D_VAL ; Duty値セット
    SUBLW   D_RIGHT ; D_RIGHT - D_VAL --> W
    BTFSC   STATUS,C ; CがOFFなら D_RIGHT < D_VALなので次とばす
    BRA    R_LIMIT ; D_RIGHT >= D_VAL
L_CHK
    MOVF    D_VAL,W
    SUBLW   D_LEFT ; D_LEFT - D_VAL --> W
    BTFSS   STATUS,C ; CがONなら D_LEFT > D_VALなので次とばす
    BRA L_LIMIT ; D_LEFT < D_VAL
DUTYSET
    CALL    D_SET
    BRA    R_LOOP
   
R_LIMIT    ;右限界値オーバー ,修正
    MOVLW   D_RIGHT
    MOVWF   D_VAL
    BRA    L_CHK

L_LIMIT ; 左限界値オーバー、修正
    MOVLW   D_LEFT
    MOVWF   D_VAL
    BRA        DUTYSET

 ;*******割り込み(送信要求処理)**********
I_WRITE
    BANKSEL PORTA
    MOVF    D_VAL,W ;duty値取り出し
    BANKSEL SSP1BUF
    MOVWF   SSP1BUF ;I2Cにduty値送信
    BSF    SSP1CON1,CKP ; Stretch 解除
    BANKSEL PIR1
    BCF    PIR1,SSP1IF ;割り込みフラグクリア
    BTFSC   PIR1,SSP1IF     ; Ready ?
    BRA    I_WRITE
    BANKSEL SSP1STAT
    BTFSS   SSP1STAT,P ;STOP?
    BRA    I_WRITE
    BRA    INTEND

;==============================
;  MAIN START
;==============================   
MAIN_START
    BANKSEL OSCCON
    MOVLW   60H     ; 内部クロック2MHzの設定
    MOVWF   OSCCON 

    BANKSEL INTCON
    BCF INTCON,GIE  ; 割り込み禁止
    BCF INTCON,PEIE ; 割り込み禁止
   
    ;PORTAをディジタルI/Oにする
    BANKSEL  PORTA
    CLRF    PORTA
    BANKSEL LATA
    CLRF    LATA
    BANKSEL ANSELA  ; digital I/O
    CLRF    ANSELA
    ;
    ;SCL,SDAのRA1,RA2も入力
    ;RA5はPWMの出力
    BANKSEL TRISA
    MOVLW  B'00000110' ; RA2,RA1入力,RA5出力
    MOVWF  TRISA
   
;ここからI2Cの設定  
    BANKSEL SSP1STAT
    MOVLW   80H
    MOVWF   SSP1STAT ; SMP=ON
    MOVLW   36H    ;SSP1EN=ON I2C slave mode 7bit Address
    MOVWF   SSP1CON1
    MOVLW   SLVADR ;スレーブアドレスセット
    MOVWF   SSP1ADD
    BSF        SSP1CON2,SEN ;stretch enabled
    BSF        SSP1CON1,CKP ;stretch enabled
;    MOVLW   04H ; 2MHz/((04H+1)*4)=100kHz  <=この設定はマスターモード
;    MOVWF   SSP1ADD        ;100KBps設定
    ;
    BANKSEL PIR1
    BCF        PIR1,SSP1IF
    BANKSEL PORTA ;BANK0へ切り替え
;ここまでi2cの設定   

; Servo initialize
    BANKSEL APFCON
    BSF    APFCON,CCP1SEL ; 1:RA5をPWM出力PINに設定
    BANKSEL CCP1CON ; CCP1CONの下位4ビットに1100をセットしてPWMを有効にする
    MOVLW   0CH
    MOVWF   CCP1CON
    ;pwmの周期20msを設定
    BANKSEL PR2
    MOVLW   PWMPRD
    MOVWF   PR2
    BANKSEL T2CON ;T2CONのb1,b0を11(プリスケーラ64)
    BSF    T2CON,0
    BSF    T2CON,1
    BANKSEL PORTA
    ; Duty値設定
    MOVLW   D_CENTER ;Duty Center
    MOVWF   D_VAL  ;
    CALL    D_SET ;Duty値をセット
    ;PWMスタート
    BANKSEL T2CON
    BSF    T2CON,TMR2ON
    BANKSEL PORTA
   
 ; ここに割り込み許可をしてからメインループへ
    BANKSEL  INTCON
    CLRF    INTCON
    BSF     INTCON,INTE ;外部割込み許可
    BSF        INTCON,PEIE ;周辺割込許可
    BSF     INTCON,GIE  ;周辺割込許可  
    BANKSEL PIE1
    BSF    PIE1,SSP1IE ; i2c 割り込み 許可 Enables the MSSP interrupt
    BANKSEL PORTA
   
MAINLOOP ;i2c割込待ち とくにメインループの処理はなし
    BANKSEL PORTA
    CALL    TIM_500ms
    CALL    TIM_500ms
    GOTO    MAINLOOP
   
;********************************
;  D_VALの値をCCPR1L:CCP1CON<5:4>
;  に転送
;******************************** 
D_SET
    BANKSEL CCP1CON
    BCF        CCP1CON,4  ;4ビット目クリア
    BCF        CCP1CON,5  ;5ビット目クリア
    BANKSEL PORTA
    BTFSC   D_VAL,0
    GOTO    D_SET0
D_SET1
    BANKSEL PORTA
    BTFSC   D_VAL,1
    GOTO    D_SET2
D_SET_H ; High 側の設定
    BANKSEL PORTA
    MOVF    D_VAL,W
    MOVWF   D_VAL_WRK
    BCF        STATUS,C
    RRF        D_VAL_WRK,F
    BCF        STATUS,C
    RRF        D_VAL_WRK,F
    MOVF    D_VAL_WRK,W
    BANKSEL CCPR1L
    MOVWF   CCPR1L
    BANKSEL PORTA
    RETURN
D_SET0
    BANKSEL CCP1CON
    BSF        CCP1CON,4
    GOTO   D_SET1   
D_SET2
    BANKSEL CCP1CON
    BSF        CCP1CON,5
    GOTO    D_SET_H

;======================================================================
; タイマーモジュール群
;======================================================================
; 命令1cycleは4クロック 4/2MHz=2μS
; CALLやRETURN、DECFSZのスキップするときなどPCLを変化させる命令は2cyles
; 他の命令は1cyles
; 上位モジュールのCALL命令も考慮するとn μS のウエイトでセットするときの
; カウンタの初期値はloop(a)の場合 (n/0.5 - 10)/3+1
;                   loop(b)の場合
;************************
;   TIM_10us 10μ秒待ち
;************************
TIM_10us        ; CALLで2
    GOTO    $+1        ;2
    GOTO    $+1        ;2
    GOTO    $+1        ;2
    RETURN        ;2

;************************
;  TIM_50us  50μ秒待ち
;************************
TIM_50us    ;2
    MOVLW   7   ;1
    MOVWF   CNT0    ;1
    DECFSZ  CNT0,F  ;1(2)
    GOTO    $-1     ;2
    NOP            ;1
    RETURN          ;2
;***************************
; TIM_100us   100μ秒待ち
;***************************
TIM_100us  ;100 micro sec timer   ;2
    MOVLW    15      ;1
    MOVWF   CNT0    ;1
    DECFSZ  CNT0,F  ;1(2)
    GOTO    $-1     ;2
    GOTO    $+1        ;2
    RETURN          ;2
;****************************
; TIM_170us     170μ秒待ち
;****************************
TIM_170us          ;2
    MOVLW    26     ;1
    MOVWF   CNT0    ;1
    DECFSZ  CNT0,F  ;1(2)
    GOTO    $-1     ;2
    GOTO    $+1     ;2
    NOP            ;1
    RETURN          ;2
;****************************
; TIM_220us     220μ秒待ち
;****************************
TIM_220us         ;2
    MOVLW   35   ;1
    MOVWF   CNT0    ;1
    DECFSZ  CNT0,F  ;1(2)
    GOTO    $-1     ;2
    RETURN          ;2
;****************************
; TIM_276us     276μ秒待ち
;****************************
TIM_276us        ;2
    MOVLW   44     ;1
    MOVWF   CNT0    ;1
    DECFSZ  CNT0,F  ;1(2)
    GOTO    $-1     ;2
    NOP            ;1
    RETURN          ;2
;****************************
; TIM_300us     300μ秒待ち
;****************************
TIM_300us        ;2
    MOVLW   48    ;1
    MOVWF   CNT0    ;1
    DECFSZ  CNT0,F  ;1(2)
    GOTO    $-1     ;2
    NOP             ;1
    RETURN          ;2
;****************************
; TIM_450us     450μ秒待ち
;****************************
TIM_450us
    MOVLW   73     ;1
    MOVWF   CNT0    ;1
    DECFSZ  CNT0,F  ;1(2)
    GOTO    $-1     ;2
    GOTO    $+1        ;2
    NOP            ;1
    RETURN          ;2
;****************************
; TIM_500us     500μ秒待ち
;****************************
TIM_500us        ;2
    MOVLW   81     ;1
    MOVWF   CNT0    ;1
    GOTO    $+1     ;2
    DECFSZ  CNT0,F  ;1(2)
    GOTO    $-1     ;2
    RETURN          ;2
;****************************
; TIM_550us     550μ秒待ち
;****************************
TIM_550us        ;2
    MOVLW   89     ;1
    MOVWF   CNT0    ;1
    GOTO    $+1     ;2
    DECFSZ  CNT0,F  ;1(2)
    GOTO    $-1   ;2
    NOP            ;1
    RETURN          ;2
;****************************
; TIM_600us     600μ秒待ち
;****************************
TIM_600us        ;2
    MOVLW   97     ;1
    MOVWF   CNT0    ;1
    GOTO    $+1     ;2
    DECFSZ  CNT0,F  ;1(2)
    GOTO    $-1     ;2
    GOTO    $+1        ;2
    RETURN          ;2
;****************************
; TIM_900us     900μ秒待ち
;****************************
TIM_900us    ; 2
    MOVLW   147    ;1
    MOVWF   CNT0    ;1
    DECFSZ  CNT0,F  ;1(2)
    GOTO    $-1        ;2
    GOTO    $+1        ;2
    GOTO    $+1        ;2   
    RETURN          ;2
;*************************
;  TIM_1ms  1m秒待ち
;*************************
TIM_1ms            ;2
    MOVLW   164     ;1
    MOVWF   CNT0    ;1
    DECFSZ  CNT0,F  ;1(2)
    GOTO    $-1     ;2
    GOTO    $+1     ;2
    NOP             ;1
    RETURN          ;2
;+++++++++++++++++++++++++
;  TIM_5ms   5m秒待ち
;*************************
TIM_5ms            ;2
    MOVLW   249     ;1
    MOVWF   CNT0    ;1
    GOTO    $+1     ;2
    GOTO    $+1     ;2
    GOTO    $+1     ;2
    NOP            ;1
    DECFSZ  CNT0,F  ;1(2)
    GOTO    $-5     ;2
    GOTO    $+1     ;2
    RETURN          ;2
;***************************
; TIM_10ms   10mS秒待ち
;***************************
TIM_10ms  ; 10mili sec timer
    CALL    TIM_5ms
    GOTO    TIM_5ms
;***************************
; TIM_50ms   50mS秒待ち
;***************************
TIM_50ms  ; 50mili sec timer
       MOVLW    10
    MOVWF    CNT1
    CALL    TIM_5ms
    DECFSZ  CNT1,F
    GOTO    $-2
    RETURN
;***************************
; TIM_100ms   100mS秒待ち
;***************************
TIM_100ms  ; 100ms timer
    MOVLW    20
    MOVWF    CNT1
    CALL    TIM_5ms
    DECFSZ  CNT1,F
    GOTO    $-2
    RETURN
;***************************
; TIM_250ms   250mS待ち
;***************************
TIM_250ms  ; 250msec timer
    MOVLW    50
    MOVWF    CNT1
    CALL    TIM_5ms
    DECFSZ  CNT1,F
    GOTO    $-2
    RETURN
;***************************
; TIM_500ms   500mS待ち
;***************************
TIM_500ms  ; 500msec timer
    MOVLW    100
    MOVWF    CNT1
    CALL    TIM_5ms
    DECFSZ  CNT1,F
    GOTO    $-2
    RETURN
;***************************
; TIM_1S   1秒待ち
;***************************
TIM_1S  ; 1sec timer
    MOVLW    0C8H
    MOVWF    CNT1
    CALL    TIM_5ms
    DECFSZ  CNT1,F
    GOTO    $-2
    RETURN
    END