I²C通信仕様
FP01の中では、RS485で受けたコマンドをArduino(I²Cマスター)が解釈し、I²CでPSoC 4(出力制御)・EEPROM(設定の保存)・DMXドライバへ指示を出します(FP01 Rev1.6ではOLEDもI²C接続)。このページは、FP01ファームウェア Rev13 と PSoC Creator プロジェクトのソースを解析して、通信設定、PSoC 4のレジスタ、各コマンドのバイト列をまとめたものです。
I²Cバス構成
FP01のI²Cは2系統あります。Arduino側のI²Cは、FP01基板上のPSoC 4・EEPROMをつなぎ(FP01 Rev1.6ではOLEDも)、FFCの21・22番ピンからメイン基板にも出ています。PSoC側のI²CはFFCの19・20番ピンに出ていて、DIMX4では温度センサー用(TEMP/SDA・SCL)に割り当てられています。
ピン番号は FFCコネクタ仕様(FFC_Rev2) によります。DMXドライバのアドレスはファームウェア内の定義で、ドライバ1がリボンケーブルにいちばん近い基板です。
通信設定
マスターはArduinoだけです。Wire.begin() を引数なしで呼んでいるので、Arduinoはマスターとしてバスに参加します。速度は指定せず、Arduinoの標準の100 kHzで動きます。OLEDのライブラリ(U8x8)は何も指定しないと400 kHzに上げてしまうため、setBusClock(100000) で100 kHzにそろえています。
PSoC 4
調光レベル、フェードタイム、モジュールIDを書き込むレジスタ型のスレーブ。書込みだけで、Arduinoからの読出しはしていません。
i2cDIMMER.cpp · PSOC_SLAVE_ADDRESS
EEPROM(AT24C256C)
モジュールアドレス、回路ごとの設定、グループ、シーンを保存。2バイトのメモリアドレス(上位→下位)で指定します。
i2cEEPROM.cpp · SLAVE_ADDRESS_EEPROM
OLED(SH1106)
U8x8ライブラリの U8X8_SH1106_128X64_NONAME_HW_I2C。アドレスはライブラリの標準値です。約5分操作がないと省電力モードに入ります。
FP01 Rev2.1ではOLEDはSPI接続(SCK・MOSI・CS+DC・RST)で、このI²Cアドレスは使いません。
screen.cpp
DMXドライバ 1〜4
回路ごとに設定したドライバ番号(1〜4)のアドレスへ、4バイト固定のパケットを送ります。
Rev13.ino · I2C_SLAVEADDR_1〜4
設定値の一覧
| 項目 | 値 | 定義場所 | 補足 |
|---|---|---|---|
| 役割 | Arduino = マスター | Wire.begin() | 引数なし=マスター。setup() 内で2回呼んでいますが動作は同じです。 |
| クロック | 100 kHz | Arduino標準 | DMX対応版のコメントに「i2cの速度 100kbps(Arduinoデフォルトなので指定しない)」とあります。 |
| OLEDのクロック | 100 kHz | u8x8.setBusClock(100000) | FP01 Rev1.6でOLEDが同じバスにつながっていたため、速度をそろえています。 |
| アドレス形式 | 7ビット | — | 表の値はすべて7ビット表記です(書込み時の8ビット値は2倍:0x08 → 0x10)。 |
| バイト順 | ビッグエンディアン | — | 2バイトの値(調光レベル、EEPROMアドレス、DMXチャンネル)は上位バイトから送ります。 |
| PSoCへの送信待ち | delay(5) | i2cDIMMER.cpp | 各書込みで endTransmission() の前に5 ms待ちます。 |
| EEPROM書込み待ち | 10〜20 ms | i2cEEPROM.cpp | 1バイト:5 ms+10 ms、2バイト:5 ms+20 ms、複数バイト:バイト数×5 ms。 |
PSoC 4レジスタマップ
PSoC 4は先頭1バイトをレジスタアドレスとして受け取り、その後ろのデータを書き込むレジスタ型スレーブです。PSoC 4に送るのはローカルアドレス0〜7の回路だけで、8以降は送りません。
| レジスタ | 名前 | サイズ | 値 | 送信関数 |
|---|---|---|---|---|
0x00 | モジュールID | 1 byte | 1:IGBTX4 2:DIMX4 3:PWMX4 4:RELAYX8 5:RRX8 6:CTRL1 7:RS485X | i2c_psoc_write_reg_module_id() |
0x01–0x09 | 未使用 | — | Rev13では書き込みません | — |
0x0A + LA0x0A–0x11 | フェードタイム | 1 byte × 8 | 0〜255(0.1秒単位。2 = 0.2秒) | i2c_psoc_write_reg_fade_time() |
0x12 + LA×20x12–0x21 | 調光レベル(DIRECT) | 2 byte × 8 | 0〜1000(実% ×0.1%。0 = 消灯、1000 = 100%)、上位バイトから | i2c_psoc_write_reg_light() |
LA = ローカルアドレス(モジュール内の回路番号 0〜7)。以前のバージョンにはMIN・MAXレベルのレジスタ(0x12・0x22)がありましたが、Rev13ではコメントアウトされていて、MIN・MAXの換算はArduino側の換算テーブルで行います。
送信フレーム
i2c_psoc_write_reg_light(LA, level)i2c_psoc_write_reg_fade_time(LA, time)i2c_psoc_write_reg_module_id(id)バイト列を計算する
論理レベル(RS485コマンドで指定する 0〜1000)は、回路ごとのMIN・MAXから作る換算テーブルで実%に直してから送ります。論理0は0、論理1はMIN、論理1000はMAX、その間は直線で割り付けます(MIN = MAX のときは 1〜1000 として扱います)。
EEPROMの読み書き
設定はすべて外付けEEPROM(AT24C256C-SSHL-T、256Kbit = 32KB、ページ64バイト)に保存し、起動時に読み込みます。メモリアドレスは2バイト(0x0000〜0x7FFF)で、ローカルアドレス × 0x100 + オフセットで回路ごとのブロックを分けています。LA0〜63の64回路分(0x0000〜0x3FFF、16KB)が容量の中に収まり、ファームウェアのページ長 EEPROM_PAGE_LENGTH = 64 もこのEEPROMのページサイズと一致します。
i2c_eeprom_write_byte / _2byte / _nbyte書いたあとはEEPROMの内部書込みが終わるまで待ちます(1バイト 10 ms、2バイト 20 ms、Nバイト N×5 ms)。
i2c_eeprom_read_byte / _2byteアドレスを書いていったんSTOPし、requestFrom() で読み出します(リピートスタートは使っていません)。
主な保存項目
| オフセット | 項目 | サイズ | 値 |
|---|---|---|---|
0x0000 | モジュールアドレス | 2 | 0〜255(LA0ブロックのみ) |
0x000E | レスポンスモード | 1 | 返信の種類(LA0ブロックのみ) |
0x000F | OLED電源 | 1 | 0:OFF 1:ON(LA0ブロックのみ) |
0x0011 | モジュール機種ID | 1 | 1〜7(PSoCのレジスタ0x00にも書く) |
0x18 | パワーオンレベル | 2 | 0〜1000(論理) |
0x1C / 0x1E | MIN / MAXレベル | 2 + 2 | 0〜1000(実%) |
0x22 | ストアドレベル | 2 | OFF前のレベル(on__で復帰) |
0x24 / 0x25 | UP/DOWNステップ 1 / 2 | 1 + 1 | 壁スイッチ用 / HA用(×0.1%) |
0x27 | フェードタイム | 1 | 0〜255(PSoC・DMXへそのまま転送) |
0x29 | オンディレイ | 1 | 0〜255(×0.1秒) |
0x2B | 回路説明 | 16 | ASCII |
0x3B | 回路アドレス | 2 | RS485で指定するアドレス |
0x40 | 所属グループ ×16 | 32 | グループn = 0x40 + n×2 |
0x7A / 0x7B | DMXドライバ番号 / チャンネル | 1 + 2 | 1〜4 / 1〜512 |
0x80 | シーンテーブル ×32 | 64 | 0〜1000、-1(0xFFFF)= 変化なし |
0x18以降のオフセットは回路ごとの値で、実際のアドレスは LA × 0x100 + オフセットです。
DMXドライバへのパケット
DMXチャンネルが設定された回路は、PSoC 4への送信と同時に、DMXドライバにも4バイトのパケットを送ります。送り先はEEPROMの「DMXドライバ番号」で決まり、1→0x0A、2→0x0B、3→0x0C、4→0x0Dです。
i2c_DMX(LA, level) → i2c_send_4byte_n()明るさは実%(0〜1000)を4で割った値です。最大は250で、DMXの255までは届きません。UP/DOWN操作のときはフェードタイムを2(0.2秒)に固定して送ります。
ファームウェアには、ドライバから12バイトを読み出してRS485へ中継するポーリング処理(Polling_Slave_1〜4)も残っていますが、loop() からの呼出しはコメントアウトされていて、Rev13では動きません。
RS485コマンドとI²C送信
RS485のコマンドは12文字(vコマンドは8文字)のASCIIで、[モード1文字][アドレス3桁][命令4文字][引数4桁] の形です。どのコマンドを受けたときにI²Cで何が送られるかをまとめました。
| コマンド | 内容 | PSoC 4(0x08) | DMX | EEPROM |
|---|---|---|---|---|
m dire / a sccc / g sccc | 直接レベル指定・シーン呼出し | レベル | 送信 | 読出しのみ |
v on__ / v off_ / v togg | グループのON・OFF・トグル | レベル | 送信 | OFF時にストアドレベルを書込み |
v up__ / down / up_2 / dn_2 | グループのUP・DOWN | フェード=2 レベル | 送信 | ステップ値を読出し |
a fade / g fade | 一時的なフェードタイム | フェード | — | 書き込まない |
s wftt / x wftt | フェードタイムの設定 | フェード | — | 0x27 |
a idon | 識別点滅の開始 | フェード=15 → 0 / 1000 を交互に | — | — |
a idof | 識別点滅の停止 | レベル=0 | — | — |
| OLED 機種選択 | モジュール機種の変更 | モジュールID | — | 0x0011 |
s wmin / wmax / wpol など | 設定値の書込み | — | — | 各項目 |
レベルを上げるとき(0より大きい値を送るとき)は、送信前にオンディレイ(×0.1秒)だけ待ちます。
起動時のシーケンス
設定を読む
EEPROMからモジュールアドレス、機種ID、レスポンスモード、OLED電源を読み込みます。
フェードタイム
LA0〜7のフェードタイムをEEPROMから読み、PSoCの 0x0A+LA に書きます。
パワーオンレベル
パワーオンレベルを読み、30 ms間隔でPSoCの 0x12+LA×2 に書いて点灯させます。
モジュールID
MIN・MAXを読んで換算テーブルを作り、最後に機種IDをPSoCの 0x00 に書きます。
PSoCプロジェクトのI²C設定
スイッチ(ROTARY03・ROTARY02N)のPSoC 5LPプロジェクトにもI²C部品の設定が残っています。ただし、現在のビルドでは配置配線レポートでI²Cの使用が0%で、FP01との通信はRS485(UART)で行っています。以前の設計でFP01と同じアドレス 0x08・100 kbps をそろえていた名残です。
| プロジェクト | 部品 | モード | 速度 | アドレス | 状態 |
|---|---|---|---|---|---|
| ROTARY03 Rev1.1.5 CY8C5888LTQ-LP097 | I2C | マスター(UDB) | 100 kbps | (0x08) | 未使用 呼出しはコメントアウト |
I2C_1 | マルチマスター | 100 kbps | 0x08 | 未使用 | |
| ROTARY02N Rev1.3 | EZI2C_Slave / EZI2C_1 | EZI2Cスレーブ(サブアドレス8ビット) | 100 kbps | 0x08 | 未使用 |
FP01に載っているPSoC 4(CY8C4245PVI-482)側のファームウェアはこの解析の対象に含まれていません。PSoC 4のレジスタマップは、マスター(Arduino)が送っている内容から読み取ったものです。
実装上の注意
Rev13のソースを読んでわかった、使うときに気をつけたい点です。
- PSoC 4に送るのはLA0〜7まで。RS485Xのように回路数が多い機種でも、PSoC 4のレジスタと換算テーブルは8回路分しかありません。LA8以降は換算テーブルを通さず、DMXにだけ送られます。
- グループ読出しのアドレス指定。
a joi1とa gclrの処理で、i2c_eeprom_read_2byte()の1つ目の引数に0x50ではなくEEPROM_ADDRESS_FOR_GROUP(0x40)が渡されています。 - 送信待ちの位置。Wireは
endTransmission()を呼んだ時点でまとめて送るため、その前に入っているdelay(5)は送信の間隔を空ける役には立っていません。連続して送るときの間隔は、各関数の後ろの待ちで決まります。