以前、PIC12F1840でI2Cを介したLチカをしましたが、ちょっと改良して、複数バイトを送信できるようにしました。
PICをスレーブ、ATOM Lite(ESP32-PICO搭載)をマスタとして、MicroPythonで文字列を読み込みます。
追記:不具合発覚につき、参考程度にしてください(解決法模索中)
■ 回路
前回と同じです。LEDは無くてもいいですが。
■ プログラム
PICF121840(スレーブ)側
ベースは前回記事のソースです。
下記のサイトのスレーブのソースを丸コピさせてもらって、PWM部分をそぎ落としたものです。
PIC12F1840 i2cの設定とPWMの設定 (XC8編)
https://karappi.mydns.jp/kousaku/pic12f1840/pic12f1840_c.html
送信処理は、上記サイトにもフロー図と共に解説されていますが、データソースの記載も確認ながら進めたので記載します。
PIC12F1840のデータシートは英語なので姉妹品であるPIC12F1822のデータシートを参照します。
PIC12F1822 データシート(日本語)
http://ww1.microchip.com/downloads/jp/DeviceDoc/41413C_JP.pdf
PIC12F1840 Datasheet
https://ww1.microchip.com/downloads/en/DeviceDoc/41441B.pdf
「25.5.3.2 7ビット送信」のところの記載が以下。
- マスタがSDAとSCLにスタート条件を送信する。
- SSP1STAT の S ビットがセットされる。スタート検出割り込みが有効の場合は SSP1IF がセットされる。
- SSP1IF をセットするスレーブが R/W ビットがセットされた一致アドレスを受信する。
- スレーブ ハードウェアがACKを生成してSSP1IFをセットする。
- ユーザが SSP1IF ビットをクリアする。
- ソフトウェアが SSP1BUF から受信アドレスを読み出して、BF をクリアする。
- R/W がセットされているため、CKP は ACK の後に自動的にクリアされている。
- スレーブ ソフトウェアがSSP1BUFに送信データを書き込む。
- CKPビットがセットされてSCLが解放されると、マスタはスレーブからデータを読み出す事ができる。
- マスタからの ACK 応答が ACKSTAT レジスタに読み込まれると SSP1IF がセットされる。
- SSP1IF ビットがクリアされる。
- スレーブ ソフトウェアは、ACKSTAT ビットをチェックしてマスタに後続の送信データがあるかどうか確認する。
Note 1: マスタが ACK を生成すると、クロックがストレッチされます。
Note 2: ACKSTAT は、SCL の 9 番目の立ち上がりエッジ ( 立ち下がりエッジではない ) で更新される唯一のビットです。- 各送信バイトに対して、手順 9 ~ 13 を繰り返す。
- マスタが NOT ACK を送信した場合、クロックはホールドされないが、SSP1IF はセットされる。
- マスタがリスタート条件またはストップ条件を送信する。
- スレーブのアドレス指定が解除される。
手順13の9~13を繰り返すのとこは、8~12ではないかと思いますが、前回のソースに足りない部分はそこなので、送信要求の処理の関数を下記に書き換えました。
追記:I2Cで読み取りを行った後、メインループに戻らない不具合を確認(解決模索中)。
void i_write(void){ int n = 0; do{ SSP1BUF = message[n]; SSP1CON1bits.CKP=1; //stretch解除 PIR1bits.SSP1IF=0; //割込フラグクリア while(PIR1bits.SSP1IF==0);//送信完了まで待機 if(SSPCON2bits.ACKSTAT==0){ //ACK受信:後続データ処理 n++; }else{ //NACK受信:送信終了 break; } }while(SSP1STATbits.P==0); }
(全体ソースは最後に。要messageの定義)
ATOM Lite(マスタ)側
MicroPython側では、下記ソースを実行すると、messageを読み込みます。
from machine import Pin, SoftI2C i2c = SoftI2C(scl=Pin(21), sda=Pin(25), freq=100000) ls = i2c.scan() if len(ls) == 0: print('no i2c slave') return else: print('find device %s' % hex(ls[0])) i2c.start() v = i2c.readfrom(ls[0], 12) #12バイト読込 i2c.stop() print(v.decode())
当初、データを読み込んだ後は、MicroPython側で自動でスタート・ストップ条件が送信されるものだと思い込んでいて、PIC側のループを抜けてくれなくて、悩みました。その結果が、NACK受信したときのbreakの処理。なので、ちゃんとストップ条件を送るなら不要です。が、MicroPythonでI2C使うとき、あまりスタート条件とかストップ条件とか意識しない気がするので、それでも動くようにしときたいような。
たったこれだけですが、めちゃ悩んだし、検索している時間を含めるとかなり時間がかかりました。
やっぱりPIC難しい。でも、やりたい。勘所がわかってくるまでは修行修行。
参考にしたサイト
PIC内蔵MSSPモジュール基本ルーチン群のまとめ
http://ja0qon.my.coocan.jp/labo/trivial_exp_pic01/PIC_MSSP_TEST01.html
はじめてのPIC PIC12F1822の MSSP – I2C
http://machoto2.g2.xrea.com/page/P1822/P12_A05.htm
I2C通信・・複数バイトの送受信テスト・・
https://www.over-rabbit.com/pc/mycom-diary/17166/
I2Cのスレーブモードの使い方
http://www.picfun.com/pic18/mod18x14.html
PIC全ソース
main.c
#include#include #include #include //config1 #pragma config FOSC=INTOSC //Oscillator Selection 内部クロック使用 #pragma config WDTE=OFF //Watchdog Timer off #pragma config PWRTE=ON //Power up timer on(スイッチを入れた直後電源が安定するまで待つ) #pragma config MCLRE=OFF //MCLR PIN off (ハードウェアリセットのピンの用途を無しにしてdigital pinとして使えるようにする) #pragma config CP=OFF //CODE PROTECT OFF プログラムの読み出しのプロテクトoff #pragma config CPD=OFF //Data Protect OFF データ領域の読み出しのプロテクトoff #pragma config BOREN=ON // Brownout on (もし電源が不安定のとき一時停止する) #pragma config CLKOUTEN=OFF // CLOCK 信号の外部出力をOFF #pragma config IESO=OFF // 2段階クロックoff(立ち上がりで即安定する内部クロックを使いその後外部クロックに切り替える設定) #pragma config FCMEN=OFF //Fale-safe Clock Monitor off(外部クロックが壊れたとき内部クロックに切り替える設定) //config2 #pragma config WRT=OFF //Write Protection プログラム領域の書き込み禁止 off #pragma config PLLEN=OFF //クロック逓倍off (onにすると発生したクロックが4倍になる) #pragma config STVREN=ON //スタック領域をオーバーしたらリセットする(offは何もしない) #pragma config BORV=HI // Brounout電圧設定 (HI:電源電圧がちょっとさがるとリセット、LO:うんと下がるとリセット) #pragma config LVP=OFF //プログラム書き込み電圧(ON;低電圧書き込み有効 マイコンの電圧で書き込める off:低電圧書き込み無効 PICKIT3のときoffに) #define _XTAL_FREQ 2000000 //内部clock2MHz for delay macro #define SLVADR 0xB6 //SLAVE ADDRESS void i_read(void); //i2c 受信要求処理 void i_write(void); //i2c 送信要求処理 //void __interrupt() i2cslave(void); //割込処理 char message[30]; /**************************** * 割込処理 *****************************/ void __interrupt() i2cslave(void){ char x; x=SSP1STAT & 0x2C; if(x==0x08){ i_read(); //受信要求 }else if(x==0x0C){ i_write(); //送信要求 } } /***************************** * 受信要求処理 *****************************/ void i_read(void){ char d_val; do{ SSP1CON1bits.CKP=1; //stretch enable PIR1bits.SSP1IF=0; //割込フラグクリア if(SSP1STATbits.BF==0){ if(SSP1STATbits.P==1){ break; //stop検出 } }else{ //受信有り データ受信処理 d_val=SSP1BUF; if(d_val == 0){ RA4 = 0; }else{ RA4 = 1; } } }while(1); } /***************************** * 送信要求処理 *****************************/ void i_write(void){ int n = 0; do{ SSP1BUF = message[n]; SSP1CON1bits.CKP=1; //stretch解除 PIR1bits.SSP1IF=0; //割込フラグクリア while(PIR1bits.SSP1IF==0);//送信完了まで待機 if(SSPCON2bits.ACKSTAT==0){ //ACK受信:後続データ処理 n++; }else{ //NACK受信:送信終了 break; } }while(SSP1STATbits.P==0); } /********************************** main **********************************/ int main(void){ OSCCON=0x60; //内部Clock 2MHz INTCONbits.GIE=0; //割り込み禁止 INTCONbits.PEIE=0; //割り込み禁止 //PORTAをディジタルI/Oにする PORTA=0; LATA=0; ANSELA=0; TRISA=0x06; //SCL,SDAのRA1,RA2は入力 //ここからI2Cの設定 SSP1STAT=0x80; //SMP ON SSP1CON1=0x36; //SSP1EN=ON,I2C slave mode 7bit address SSP1ADD=SLVADR; //SLAVE ADDR SET SSP1CON2bits.SEN=1; //stretch enabled SSP1CON1bits.CKP=1; //stretch enabled PIR1bits.SSP1IF=0; //割り込みフラグクリア //ここまでI2Cの設定 strcpy(message, "Hello World!"); // ここに割り込み許可をしてからメインループへ INTCON=0; INTCONbits.INTE=1; //外部割込み許可 INTCONbits.PEIE=1; //周辺割込許可 INTCONbits.GIE=1; //周辺割込許可 PIE1bits.SSP1IE=1; //i2c 割り込み 許可 Enables the MSSP interrupt //loop while(1){ } return 0; }