背景
PIC16F1829とはmicrochip社が出している、PICマイコンと呼ばれることがある組み込み向けプロセッサの一つです。以前、Lチカに成功しました。
ubuntuに開発環境を作り、PIC16F1829でLチカする方法
このPICマイコンをI2Cスレーブとして動作させるのに苦労したので、やり方を共有します。
説明の例として、ArduinoからI2Cで信号をPICマイコンに送り、その情報に応じてLチカさせてみます。
全体像
- 使ったもの
- 配線
- PICマイコンのプログラム
- ProMicro(Arduino)のプログラム
- 動作確認
- まとめ
- 参考
ArduinoからPICマイコンにLEDの制御信号を送信し、PICマイコンでLEDをON OFFする装置を作ります。
使ったもの
- PC
OSとしてubuntu17.10 (Linux) をインストールしたもの使いました。 - MPLAB + XC8コンパイラ
PICマイコンのプログラム開発環境です。
最初はMPLAB v4系とxc8 v1系で動作を確認しましたが、MPLAB v5.20とxc8 v2.05にバージョンアップすると動かなくなったので、動くように記事を更新しました。
参考: ubuntuに開発環境を作り、PIC16F1829でLチカする方法 - Arduino IDE
Arduinoのプログラム開発環境です。
ProMicroにプログラムを書き込めれば良いので、PlatformIOでも良いです。
参考: Ubuntuに入れたArduino IDEをバージョンアップする方法 - Pro Micro
ブレッドボードに差し込める、Arduino互換機です。
スイッチサイエンスやaliexpressで買えます。 - PIC16F1829
今回使うPICマイコンです。 - PICkit3
PICマイコン用のプログラム書き込み装置です。 - ブレッドボード
はんだ付けせずに電子部品を接続するのに利用します。 - ジャンパワイヤ
回路の配線に利用します。 - LED 赤・緑・青
PICマイコンで光らせます。 - 1k抵抗
LEDの破壊防止のため、回路に組み込みます。 - 10k抵抗
PICkit3の信号線のプルアップ(電圧を上げること)に利用します。
配線
このように配線しました。PICマイコンのプログラムを期待通りに書き込めれば、PICkit3が無くても回路は動作します。
PICkit <-> PIC16F1829 <-> ProMicro(Arduino)
実際に組むとこうなりました。
PICマイコンのプログラム
0x10のアドレスでI2Cのスレーブデバイスとして動作し、受け取った情報に応じてLEDの光らせ方を変えるプログラムです。長いですが、部分的に紹介すると必要な情報が漏れる可能性があるので、全部貼りつけます。
main.c
#include <xc.h> #include <pic16f1829.h> #include <stdint.h> #include "i2c_slave.h" #define I2C_ADDRESS 0x10 #define _XTAL_FREQ 16000000 #define LED_ON 1 #define LED_OFF 0 #pragma config FOSC=INTOSC, WDTE=OFF, PWRTE=OFF, MCLRE=OFF, CP=OFF, CPD=OFF, BOREN=ON, CLKOUTEN=OFF, IESO=OFF, FCMEN=OFF #pragma config WRT=OFF, PLLEN=OFF, STVREN=OFF, LVP=OFF unsigned char r = LED_OFF, g = LED_OFF, b = LED_OFF; void ledR(int ledState) { if (ledState == LED_OFF) { r = LED_OFF; LATCbits.LATC5 = 0; } else { r = LED_ON; LATCbits.LATC5 = 1; } } void ledG(int ledState) { if (ledState == LED_OFF) { g = LED_OFF; LATCbits.LATC3 = 0; } else { g = LED_ON; LATCbits.LATC3 = 1; } } void ledB(int ledState) { if (ledState == LED_OFF) { b = LED_OFF; LATCbits.LATC6 = 0; } else { b = LED_ON; LATCbits.LATC6 = 1; } } void ledRGB(unsigned char data) { unsigned char portC = 0; // red if ((data & 0b0100) != 0) { portC = portC | 0b00100000; r = LED_ON; } else { r = LED_OFF; } // green if ((data & 0b0010) != 0) { portC = portC | 0b0001000; g = LED_ON; } else { g = LED_OFF; } // blue if ((data & 0b0001) != 0) { portC = portC | 0b01000000; b = LED_ON; } else { b = LED_OFF; } LATC = portC; } void onI2CReceiveCallback(unsigned char address, unsigned char data) { switch (address) { case 0x00: ledRGB(data); break; case 0x01: ledR(data); break; case 0x02: ledG(data); break; case 0x03: ledB(data); break; default: break; } } void setI2CWriteCharCallback(unsigned char address) { switch (address) { case 0x00: { unsigned char data = 0; if (r != LED_OFF) data |= 0b0100; if (g != LED_OFF) data |= 0b0010; if (b != LED_OFF) data |= 0b0001; I2CWriteChar = data; return; } case 0x01: I2CWriteChar = r; return; case 0x02: I2CWriteChar = g; return; case 0x03: I2CWriteChar = b; return; default: I2CWriteChar = 0; return; } } void main(void) { OSCCONbits.IRCF = 0b1111; // 16MHz OSCCONbits.SCS = 0b11; // Use internal oscillator as system clock TRISC = 0x00; LATC = 0x00; // Start as I2C slave setupI2CSlave(I2C_ADDRESS); while(1) {} }
i2c_slave.h
#ifndef XC_HEADER_TEMPLATE_H #define XC_HEADER_TEMPLATE_H #include <xc.h> // include processor files - each processor file is guarded. #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ #define RXBUFFER_SIZE 255 void setupI2CSlave(short address); void __interrupt() I2Cinterrupt(); void onI2CReceiveCallback(unsigned char address, unsigned char data); void setI2CWriteCharCallback(unsigned char address); unsigned char I2CWriteChar; #ifdef __cplusplus } #endif /* __cplusplus */ #endif /* XC_HEADER_TEMPLATE_H */
i2c_slave.c
#include "i2c_slave.h" void setupI2CSlave(short address) { // start I2C(1) setup SSP1CON2 = 0b00000001; // SEN is set to enable clock stretching SSP1CON3 = 0x00; // remove this line if use for PIC18F27J53 SSP1ADD = address << 1; // 7-bit address is stored in the 7 MSB's of the SSP1ADD register********** SSP1STAT = 0x00; SSP1CON1 = 0b00110110; // B7: WCOL = 0; // Clear write collisions // B6: SSPOV = 0; // Clear receive overflow indicator // B5: SSPEN = 1; // Enable SSP and configures SDA & SCL pins // B4: CKP = 1; // Releases clock stretching // B<3:0>: SSPM = 0b0110; // 7-bit addressing slave mode (Start and Stop bit interrupts disnabled) // 000...0101 SPI mode // 0110 I2C 7bit address // 0111 I2C 10bit address // 1000 I2C master /* Enable interrupts */ SSP1IF = 0; // Clear MSSP interrupt flag SSP1IE = 1; // I2C interrupt enable //end I2C(1) setup---------------------------------------------------------------- // Common Interrupt PEIE = 1; // Enable Peripheral interrupts GIE = 1; // Enable global interrupts } void I2CWrite(unsigned char data) { while(SSP1STATbits.BF); // Wait while buffer is full do { SSP1CON1bits.WCOL = 0; // Clear write collision flag SSP1BUF = data; } while (SSP1CON1bits.WCOL); // Do until write collision flag is clear if (SSP1CON2bits.SEN) SSP1CON1bits.CKP = 1; // Release the SCL line } volatile unsigned char RXBufferIndex = 0; #define STATE1 0b00001001 // 0x09 master write last was address #define STATE2 0b00101001 // 0x29 master write last was data #define STATE3 0b00001101 // 0x0d master read last was address #define STATE4 0b00101100 // 0x2c master write last was data #define STATE5 0b00101000 // 0x28 void countUpRXBufferIndex() { RXBufferIndex ++; if (RXBufferIndex >= RXBUFFER_SIZE) RXBufferIndex = 0; } void checkStateAndManageI2c() { static char DAStatus = 0; unsigned char i2cStatus, value; i2cStatus = SSP1STAT; i2cStatus = (i2cStatus & 0b00101101); // Mask out unimportant bits // _, _, D/A, _, S, R/W, _, BF switch (i2cStatus) { case STATE1: value = SSP1BUF; // Read buffer, clear BF RXBufferIndex = 0; // Clear index DAStatus = 1; // Next call is address inside memory if (SSP1CON1bits.SSPOV) SSP1CON1bits.SSPOV = 0; // Clear receive overflow indicator if (SSP1CON2bits.SEN) SSP1CON1bits.CKP = 1; // Release the SCL line break; case STATE2: value = SSP1BUF; // Read buffer, clear BF if (DAStatus == 1) { RXBufferIndex = value; if (RXBufferIndex >= RXBUFFER_SIZE) RXBufferIndex = 0; DAStatus = 2; } else { onI2CReceiveCallback(RXBufferIndex, value); countUpRXBufferIndex(); } if (SSP1CON2bits.SEN) SSP1CON1bits.CKP = 1; // Release the SCL line break; case STATE3: value = SSP1BUF; // Dummy read setI2CWriteCharCallback(RXBufferIndex); I2CWrite(I2CWriteChar); countUpRXBufferIndex(); break; case STATE4: setI2CWriteCharCallback(RXBufferIndex); I2CWrite(I2CWriteChar); countUpRXBufferIndex(); break; case STATE5: break; default: if (SSP1CON2bits.SEN) SSP1CON1bits.CKP = 1; // Release the SCL line break; } // End switch (i2cStatus) } void __interrupt() I2Cinterrupt() { if (SSP1IF) { SSP1IF = 0; // Clear interrupt flag checkStateAndManageI2c(); } }
上記のi2c_slaveをPIC18F27J53で利用する場合は「SP1CON3 = 0x00;」の行を削除してください。(その行があると、ビルドできるものの動きません。)
このプログラムを設定するためのMPLABのプロジェクトやファイルの作り方は、こちらを参考になると思います。
ubuntuに開発環境を作り、PIC16F1829でLチカする方法
ところどころ解説します。
#define _XTAL_FREQ 16000000 #pragma config FOSC = INTOSC // Use internal oscillator void main(void) { OSCCONbits.IRCF = 0b1111; // 16MHz OSCCONbits.SCS = 0b11; // Use internal oscillator as system clock }PIC16F1829は何もしなければ500kHzで動作しますが、I2Cのプログラムを動かす場合は、500kHzではI2C通信で2byte目以後が取得できなかったため、16MHzにしました。
ledRGB(data); ledR(data); ledG(data); ledB(data);ビットごとにLEDを接続したGPIOを制御する関数と、ビットを束ねたバイト単位でLEDを制御する関数をつくりました。
void onI2CReceiveCallback(unsigned char address, unsigned char data) { .. } void setI2CWriteCharCallback(unsigned char address) { unsigned char data; .. I2CWriteChar = data; }コールバックとして設定した関数がi2cのreadとwriteでそれぞれ呼ばれます。
writeのコールバックでは、writeしたいbyteをI2CWriteCharに書き込んでいます。
picマイコンのプログラムは、リポジトリの下記のディレクトリにまとめています。
arduino_and_pic_i2c/pic_i2c_slave/
ProMicro(Arduino)のプログラム
PICマイコンとi2c通信をおこない、LEDの光らせ方を変えつつ、PICマイコンが保持するLEDの状態を取得するプログラムです。
arduino_i2c_master.ino
#include <Wire.h> #define I2C_SLAVE_ADDRESS 0x10 #define RGB_ADDRESS 0x00 #define R_ADDRESS 0x01 #define G_ADDRESS 0x02 #define B_ADDRESS 0x03 void setup() { Wire.begin(); Serial.begin(115200); delay(500); } uint8_t writeByte(char deviceAddress, char bufferAddress, char data) { Wire.beginTransmission(deviceAddress); Wire.write(bufferAddress); Wire.write(data); return Wire.endTransmission(); } uint8_t read3bytesState() { Wire.beginTransmission(I2C_SLAVE_ADDRESS); Wire.write(R_ADDRESS); uint8_t result = Wire.endTransmission(); if (result != 0) return result; Wire.requestFrom(I2C_SLAVE_ADDRESS, 3); delay(10); Serial.print("read:"); while (Wire.available()) { Serial.print(" "); Serial.print(Wire.read(), HEX); } Serial.println(""); return 0; } void read1byteState() { Wire.beginTransmission(I2C_SLAVE_ADDRESS); Wire.write(RGB_ADDRESS); Wire.endTransmission(); Wire.requestFrom(I2C_SLAVE_ADDRESS, 1); delay(10); Serial.print("read: "); while (Wire.available()) { Serial.print(Wire.read(), BIN); } Serial.println(""); } void writeAndReadResults(char deviceAddress, char bufferAddress, char data) { if (writeByte(deviceAddress, bufferAddress, data) != 0) { Serial.println("Failed to write"); } else { read3bytesState(); read1byteState(); } } void loop() { Serial.println("turn R on"); writeAndReadResults(I2C_SLAVE_ADDRESS, R_ADDRESS, 1); delay(1000); Serial.println("turn G on"); writeAndReadResults(I2C_SLAVE_ADDRESS, G_ADDRESS, 1); delay(1000); Serial.println("turn B on"); writeAndReadResults(I2C_SLAVE_ADDRESS, B_ADDRESS, 1); delay(1000); Serial.println("turn R off"); writeAndReadResults(I2C_SLAVE_ADDRESS, R_ADDRESS, 0); delay(1000); Serial.println("turn G off"); writeAndReadResults(I2C_SLAVE_ADDRESS, G_ADDRESS, 0); delay(1000); Serial.println("turn B off"); writeAndReadResults(I2C_SLAVE_ADDRESS, B_ADDRESS, 0); delay(1000); Serial.println("R:off, G:off, B:on"); writeAndReadResults(I2C_SLAVE_ADDRESS, RGB_ADDRESS, 0b001); delay(1000); Serial.println("R:off, G:on, B:on"); writeAndReadResults(I2C_SLAVE_ADDRESS, RGB_ADDRESS, 0b011); delay(1000); Serial.println("R:on, G:on, B:on"); writeAndReadResults(I2C_SLAVE_ADDRESS, RGB_ADDRESS, 0b111); delay(1000); Serial.println("R:on, G:on, B:off"); writeAndReadResults(I2C_SLAVE_ADDRESS, RGB_ADDRESS, 0b110); delay(1000); Serial.println("R:on, G:off, B:off"); writeAndReadResults(I2C_SLAVE_ADDRESS, RGB_ADDRESS, 0b100); delay(1000); Serial.println("R:off, G:off, B:off"); writeAndReadResults(I2C_SLAVE_ADDRESS, RGB_ADDRESS, 0); delay(1000); }
このような流れでLEDの制御信号をPICマイコンに送信するプログラムです。
- 赤を点灯
- 緑を点灯
- 青を点灯
- 赤を消灯
- 緑を消灯
- 青を消灯
- 青を点灯
- 緑を点灯
- 赤を点灯
- 青を消灯
- 緑を消灯
- 赤を消灯
- 先頭の信号に戻る
PICマイコンのプログラムと同じように、リポジトリでも公開しています。
リポジトリで管理するプログラムはPlatformIOようにcppファイルになっていますが、内容をコピペしてもらえばArduinoIDEでも動きます。
arduino_and_pic_i2c/arduino_i2c_master/src/main.cpp
動作確認
PICマイコンとArduinoにそれぞれプログラムを書き込み、動かしてみました。期待通りに動きました。
まとめ
PICマイコンをスレーブとして動作させ、Arduinoからの信号によってLEDを変化させられました。この情報が何かの役に立てば嬉しいです。
参考
今回の記事で作成したプログラムです。asukiaaa/arduino_and_pic_i2c
PICマイコンのプログラムの参考にしました
PICをI2Cスレーブとする
PIC16LF1503 as I2C slave in XC8
記事には記述が出てきませんが、下記の動画を参考にしてMPLABのデバッガを利用しました。
プログラムがうまく動かず、何か情報が欲しい場合に役に立つと思います。
Microchip Debugging
今回利用したPICマイコンのデータシートです。
PIC16(L)F1825/9 Datasheet
今回利用したマイコンの製品ページです。
PIC16F1829
更新履歴
2019.07.06mplabxをv5.20、xc8をv2.05にすると下記の不具合があったので、それぞれ修正しました。
- interruptがコンパイルエラーになる -> __interrupt() に変更しました(変更内容、参考: MPLAB XC8 Compiler v2.00 と MPLAB Code Configurator v3.65 で割り込み処理にコンパイルエラー)
- コールバックが引数として渡せない -> 引数として渡さず、コールバック関数を直接定義するようにしました(変更内容)
microchipのサンプルプログラム(製品ページのDocumentsタブのCode Examples)に合わせて、マイコンのpragma configの追加と、PORTCではなくLATCを利用するように変更を行いました。
以前は「PORTCbits.RC5」でビット操作すると周辺のビットがおかしくなることがありましたが、PORTCではなくLATCを使うようになったためか、xc8をv2にしたためか、原因を調査していませんが、おかしくならなくなったため注意書きを削除しました。
gifアニメを期待通りに動く様子に変更しました。
参考にマイコンの製品ページを追加しました。
2021.11.08
PIC18F27J53でi2c_slave.cを利用する場合は「SP1CON3 = 0x00;」を削除する注意書きを追加しました。
0 件のコメント :
コメントを投稿