- はじめに
- I2Cスレーブアドレス
- コマンド構造
- レジスタマップ
- キーボードステータスの読み取り
- レジスタへの書き込み
- レジスタからの読み取り
- 16ビットレジスタアクセス
- マスターコードの例
- 注意事項とベストプラクティス
- 付録: キーボードステータスバイト
このドキュメントは、I2Cマスター側の開発者向けに、LCDディスプレイやトラックパッドを搭載したカスタムキーボードデバイスとの通信方法を詳細に説明します。このキーボードはI2Cスレーブデバイスとして動作し、マスター(LCDコントローラーやトラックパッドコントローラーなど)はキーボードのステータスを読み取ったり、マウスやスクロールデータを送信したりすることができます。
キーボードデバイスのI2Cスレーブアドレスは以下の通りです:
- スレーブアドレス:
0x32
注記: 他のI2Cデバイスとアドレスが競合しないことを確認してください。必要に応じて、キーボードのファームウェアでアドレスを変更してください。
マスターからスレーブ(キーボード)に対してコマンドを送信し、レジスタに対して読み書き操作を行います。コマンドは、まずコマンドバイトを送信し、その後に必要なデータバイトを送ります。コマンドには絶対値書き込みと相対値書き込みの2種類があります。
-
0x00 - 0x1F: 絶対値書き込みコマンド
- 用途: レジスタに1バイトの即値(
uint8_t
)を直接書き込みます。
- 用途: レジスタに1バイトの即値(
-
0x20 - 0x3F: 相対値書き込みコマンド(
uint8_t
メモリ)- 用途: レジスタに対して、符号付き1バイト(
int8_t
)の増減値を加えます。レジスタはuint8_t
として扱います。
- 用途: レジスタに対して、符号付き1バイト(
-
0x40 - 0x5F: 相対値書き込みコマンド(
int8_t
メモリ)- 用途: レジスタに対して、符号付き1バイト(
int8_t
)の増減値を加えます。レジスタはint8_t
として扱います。
- 用途: レジスタに対して、符号付き1バイト(
-
0x60 - 0x7F: 相対値書き込みコマンド(
uint16_t
メモリ)- 用途: レジスタペアに対して、符号付き2バイト(
int16_t
)の増減値を加えます。レジスタペアはuint16_t
として扱います(リトルエンディアン)。
- 用途: レジスタペアに対して、符号付き2バイト(
-
0x80 - 0x9F: 相対値書き込みコマンド(
int16_t
メモリ)- 用途: レジスタペアに対して、符号付き2バイト(
int16_t
)の増減値を加えます。レジスタペアはint16_t
として扱います(リトルエンディアン)。
- 用途: レジスタペアに対して、符号付き2バイト(
書き込み操作を行うには、マスターはまずコマンドバイトを送信し、その後に必要なデータバイトを送信します。コマンドカテゴリに応じて、絶対値または相対値を書き込みます。
相対値コマンドは、現在のレジスタの値に指定した増減値を加算します。これにより、現在の値を事前に知ることなく、値をインクリメントやデクリメントすることが可能です。
キーボードのメモリは32個のレジスタ(0x00
から 0x1F
)に分かれています。それぞれのレジスタには特定の機能が割り当てられています。
- アドレス:
0x00
- サイズ: 1バイト (
uint8_t
) - R/W: 読み取り専用
- 説明: キーボードの修飾キーとレイヤーの状態を保持しています。
ビット割り当て:
ビット | 機能 | 説明 |
---|---|---|
7 | Shift | 1 = Shiftキーが押されています |
6 | Ctrl | 1 = Ctrlキーが押されています |
5 | Cmd/GUI | 1 = Cmd/GUIキーが押されています |
4 | Opt/Alt | 1 = Option/Altキーが押されています |
3-2 | 予約 | 現在は使用されていません |
1-0 | レイヤー | 現在のアクティブレイヤー(0〜3)を示します |
レジスタ | 説明 | 型 | サイズ | R/W |
---|---|---|---|---|
0x01 |
ボタン状態 | uint8 |
1 | 書き込み |
0x02 |
X座標 | int8 |
1 | 書き込み |
0x03 |
Y座標 | int8 |
1 | 書き込み |
0x04 |
水平スクロール | int8 |
1 | 書き込み |
0x05 |
垂直スクロール | int8 |
1 | 書き込み |
ボタンレジスタ(0x01
)ビット割り当て:
ビット | 機能 | 説明 |
---|---|---|
0 | 左ボタン | 1 = 左ボタンが押されています |
1 | 右ボタン | 1 = 右ボタンが押されています |
2 | 中ボタン | 1 = 中ボタンが押されています |
3-7 | 予約 | 現在は使用されていません |
マウス・スクロールデータの説明:
- X座標 (
0x02
): 水平方向のマウス移動量を示します。符号付き8ビット整数(int8
)で表現されます。 - Y座標 (
0x03
): 垂直方向のマウス移動量を示します。符号付き8ビット整数(int8
)で表現されます。 - 水平スクロール (
0x04
): 水平方向のスクロール量を示します。符号付き8ビット整数(int8
)で表現されます。 - 垂直スクロール (
0x05
): 垂直方向のスクロール量を示します。符号付き8ビット整数(int8
)で表現されます。
これらのレジスタは将来の拡張(追加の周辺機器や機能の拡張)用に予約されています。現在は未使用であり、アクセスしないでください。
キーボードのステータスは、レジスタ0x00
に格納されています。このレジスタを読み取ることで、修飾キーの状態やアクティブなレイヤーを取得できます。
読み取り手順:
- 読み取り要求の送信: マスターはスレーブのレジスタ
0x00
から1バイトを要求します。 - データの解釈: 受信したバイトを解析し、各修飾キーの状態とレイヤー番号を取得します。
例:
#include <Wire.h>
#define SLAVE_ADDRESS 0x32
void setup() {
Wire.begin(); // I2Cマスターとしてバスに参加
Serial.begin(9600);
}
void loop() {
Wire.beginTransmission(SLAVE_ADDRESS);
Wire.write(0x00); // レジスタ0x00: キーボードステータス
Wire.endTransmission();
Wire.requestFrom(SLAVE_ADDRESS, 1); // 1バイト要求
if (Wire.available()) {
uint8_t data = Wire.read();
bool shift = data & 0x80;
bool ctrl = data & 0x40;
bool cmd = data & 0x20;
bool opt = data & 0x10;
uint8_t layer = data & 0x03;
Serial.print("Shift: "); Serial.println(shift);
Serial.print("Ctrl: "); Serial.println(ctrl);
Serial.print("Cmd: "); Serial.println(cmd);
Serial.print("Opt: "); Serial.println(opt);
Serial.print("Layer: "); Serial.println(layer);
}
delay(1000); // 1秒待機
}
レジスタへの書き込みは、コマンドバイトを送信し、その後に必要なデータバイトを送信することで行います。コマンドの種類に応じて、絶対値または相対値の書き込みを行います。
目的: レジスタに対して特定の値を直接設定します。
コマンド範囲: 0x00 - 0x1F
手順:
- コマンドバイトの送信: 書き込み先のレジスタを示すコマンドバイトを送信します。
- データバイトの送信: 即値のデータバイトを送信します。
例: レジスタ0x02
(X座標)に値10
を設定する。
uint8_t command = 0x02; // 0x00-0x1F: 絶対値書き込みコマンド
uint8_t value = 10;
Wire.beginTransmission(SLAVE_ADDRESS);
Wire.write(command);
Wire.write(value);
Wire.endTransmission();
相対値書き込みは、現在のレジスタの値に対して指定した増減値を加算します。これにより、現在の値を事前に知る必要なく、値を変更できます。
- 0x20 - 0x3F:
uint8_t
メモリ用相対値書き込み - 0x40 - 0x5F:
int8_t
メモリ用相対値書き込み - 0x60 - 0x7F:
uint16_t
メモリ用相対値書き込み - 0x80 - 0x9F:
int16_t
メモリ用相対値書き込み
手順:
- コマンドバイトの送信: 操作対象のレジスタを示すコマンドバイトを送信します。
- データバイトの送信: 増減値のデータバイトを送信します。
例: レジスタ0x03
(Y座標)を-5
だけ減少させる。
int8_t delta = -5;
uint8_t command = 0x40 | (0x03 & 0x1F); // 0x40-0x5F: int8_t メモリ用相対値書き込み
Wire.beginTransmission(SLAVE_ADDRESS);
Wire.write(command);
Wire.write((uint8_t)delta); // 符号付き値をuint8_tにキャスト
Wire.endTransmission();
注記: 相対値書き込みでは、適切なキャストを行い、データが正しく送信されるようにしてください。
現時点では、レジスタ1-5は書き込み専用であり、読み取りはサポートされていません。主にレジスタ0x00(キーボードステータス)からの読み取りが行われます。
レジスタ0x00からキーボードのステータスを読み取る方法は以下の通りです。
手順:
- 読み取り要求の送信: マスターはスレーブのレジスタ
0x00
から1バイトを要求します。 - データの受信と解釈: 受信したバイトを解析し、修飾キーの状態とレイヤー番号を取得します。
例:
#include <Wire.h>
#define SLAVE_ADDRESS 0x32
void setup() {
Wire.begin(); // I2Cマスターとしてバスに参加
Serial.begin(9600);
}
void loop() {
Wire.beginTransmission(SLAVE_ADDRESS);
Wire.write(0x00); // レジスタ0x00: キーボードステータス
Wire.endTransmission();
Wire.requestFrom(SLAVE_ADDRESS, 1); // 1バイト要求
if (Wire.available()) {
uint8_t data = Wire.read();
bool shift = data & 0x80;
bool ctrl = data & 0x40;
bool cmd = data & 0x20;
bool opt = data & 0x10;
uint8_t layer = data & 0x03;
Serial.print("Shift: "); Serial.println(shift);
Serial.print("Ctrl: "); Serial.println(ctrl);
Serial.print("Cmd: "); Serial.println(cmd);
Serial.print("Opt: "); Serial.println(opt);
Serial.print("Layer: "); Serial.println(layer);
}
delay(1000); // 1秒待機
}
現在の実装では、16ビットレジスタアクセスは使用していませんが、将来の拡張のためにサポートされています。
- レジスタ0x00 - 0x1F: 各レジスタは1バイト(
uint8_t
)ですが、2つの連続したレジスタを組み合わせて16ビット値(uint16_t
またはint16_t
)として扱うことができます。リトルエンディアン形式です。
- アドレスペアリング: 16ビット値を扱うには、2つの連続した8ビットレジスタを使用します。例えば、レジスタ
0x06
と0x07
を組み合わせて16ビット値とします。 - エンディアンネス: 下位バイトは低アドレスに配置されます。
例: レジスタ0x06
と0x07
を組み合わせて16ビット符号付き整数として扱う。
uint8_t low_byte = i2c_slave_read_uint8(0x06);
uint8_t high_byte = i2c_slave_read_uint8(0x07);
int16_t combined = (int16_t)(high_byte << 8) | low_byte;
注記: 現在のファームウェアでは16ビットアクセスは実装されていませんが、将来のアップデートで使用される可能性があります。
以下に、I2Cマスター側のコード例を示します。これらの例は、ArduinoおよびPython(smbus)を使用して、キーボードデバイスと通信する方法を示しています。
#include <Wire.h>
#define SLAVE_ADDRESS 0x32
void setup() {
Wire.begin(); // I2Cマスターとしてバスに参加
Serial.begin(9600);
}
void loop() {
// キーボードステータスの読み取り
Wire.beginTransmission(SLAVE_ADDRESS);
Wire.write(0x00); // レジスタ0x00: キーボードステータス
Wire.endTransmission();
Wire.requestFrom(SLAVE_ADDRESS, 1); // 1バイト要求
if (Wire.available()) {
uint8_t status = Wire.read();
bool shift = status & 0x80;
bool ctrl = status & 0x40;
bool cmd = status & 0x20;
bool opt = status & 0x10;
uint8_t layer = status & 0x03;
Serial.print("Shift: "); Serial.println(shift);
Serial.print("Ctrl: "); Serial.println(ctrl);
Serial.print("Cmd: "); Serial.println(cmd);
Serial.print("Opt: "); Serial.println(opt);
Serial.print("Layer: "); Serial.println(layer);
}
// マウスとスクロールデータの書き込み
uint8_t buttons = 0b00000101; // 例: 左ボタンと中ボタンが押されている
int8_t x = 10; // X方向に10移動
int8_t y = -5; // Y方向に-5移動
int8_t h = 2; // 水平スクロールに2移動
int8_t v = -3; // 垂直スクロールに-3移動
// ボタン状態の書き込み
Wire.beginTransmission(SLAVE_ADDRESS);
Wire.write(0x01); // レジスタ0x01: ボタン状態
Wire.write(buttons);
Wire.endTransmission();
// X座標の書き込み
Wire.beginTransmission(SLAVE_ADDRESS);
Wire.write(0x02); // レジスタ0x02: X座標
Wire.write((uint8_t)x);
Wire.endTransmission();
// Y座標の書き込み
Wire.beginTransmission(SLAVE_ADDRESS);
Wire.write(0x03); // レジスタ0x03: Y座標
Wire.write((uint8_t)y);
Wire.endTransmission();
// 水平スクロールの書き込み
Wire.beginTransmission(SLAVE_ADDRESS);
Wire.write(0x04); // レジスタ0x04: 水平スクロール
Wire.write((uint8_t)h);
Wire.endTransmission();
// 垂直スクロールの書き込み
Wire.beginTransmission(SLAVE_ADDRESS);
Wire.write(0x05); // レジスタ0x05: 垂直スクロール
Wire.write((uint8_t)v);
Wire.endTransmission();
delay(1000); // 1秒待機
}
import smbus
import time
# I2Cの初期化
bus = smbus.SMBus(1) # Raspberry Piでは通常1を使用
SLAVE_ADDRESS = 0x32
def read_keyboard_status():
# レジスタ0x00の読み取り
bus.write_byte(SLAVE_ADDRESS, 0x00)
status = bus.read_byte(SLAVE_ADDRESS)
shift = bool(status & 0x80)
ctrl = bool(status & 0x40)
cmd = bool(status & 0x20)
opt = bool(status & 0x10)
layer = status & 0x03
return {
'shift': shift,
'ctrl': ctrl,
'cmd': cmd,
'opt': opt,
'layer': layer
}
def write_mouse_scroll(buttons, x, y, h, v):
# ボタン状態の書き込み
bus.write_byte_data(SLAVE_ADDRESS, 0x01, buttons)
# X座標の書き込み
bus.write_byte_data(SLAVE_ADDRESS, 0x02, x & 0xFF)
# Y座標の書き込み
bus.write_byte_data(SLAVE_ADDRESS, 0x03, y & 0xFF)
# 水平スクロールの書き込み
bus.write_byte_data(SLAVE_ADDRESS, 0x04, h & 0xFF)
# 垂直スクロールの書き込み
bus.write_byte_data(SLAVE_ADDRESS, 0x05, v & 0xFF)
def main():
while True:
# キーボードステータスの読み取り
status = read_keyboard_status()
print(f"Shift: {status['shift']}, Ctrl: {status['ctrl']}, Cmd: {status['cmd']}, Opt: {status['opt']}, Layer: {status['layer']}")
# マウスとスクロールデータの書き込み
buttons = 0b00000101 # 例: 左ボタンと中ボタンが押されている
x = 10 # X方向に10移動
y = -5 # Y方向に-5移動
h = 2 # 水平スクロールに2移動
v = -3 # 垂直スクロールに-3移動
write_mouse_scroll(buttons, x, y, h, v)
print("マウスとスクロールデータを送信しました。")
time.sleep(1) # 1秒待機
if __name__ == "__main__":
main()
-
I2Cプルアップ抵抗: I2Cバスには適切なプルアップ抵抗(通常4.7kΩ)がSDAおよびSCLラインに接続されていることを確認してください。
-
アドレス競合の回避: 同一I2Cバス上に同じスレーブアドレスを持つデバイスが存在しないことを確認してください。必要に応じて、キーボードのスレーブアドレスを変更してください。
-
バス速度の設定: 標準のI2C速度(100kHz)を使用してください。マスターとスレーブの両方が選択した速度をサポートしていることを確認してください。
-
エラーハンドリング: バスエラー、NACK応答、タイムアウトなどのシナリオに対するエラーハンドリングを実装してください。
-
同期の確保: マスターとスレーブがコマンド構造を正しく理解し、データの誤解釈を防ぐために同期を取ってください。
-
データの整合性: 重要なアプリケーションでは、データの整合性を確認するためにチェックサムやCRCメカニズムを実装してください。
-
電力管理: マスター側のデバイスがバッテリー駆動の場合、消費電力に注意してください。
-
予約レジスタの扱い: 予約レジスタ(
0x06
-0x1F
)にはアクセスしないでください。将来の拡張のために予約されています。
キーボードのステータスは、レジスタ0x00
に格納された1バイト(uint8_t
)で表現されます。このバイトは、修飾キーの状態とアクティブなレイヤーをリアルタイムで提供します。
ビット | 機能 | 説明 |
---|---|---|
7 | Shift | 1 = Shiftキーが押されています |
6 | Ctrl | 1 = Ctrlキーが押されています |
5 | Cmd/GUI | 1 = Cmd/GUIキーが押されています |
4 | Opt/Alt | 1 = Option/Altキーが押されています |
3 | 予約 | 将来の使用のために予約されています |
2 | 予約 | 将来の使用のために予約されています |
1 | レイヤービット1 | レイヤー番号の一部(0〜3) |
0 | レイヤービット0 | レイヤー番号の一部(0〜3) |
レイヤー番号: ビット1とビット0を組み合わせて、アクティブなレイヤーを表します(00
= レイヤー0、01
= レイヤー1、10
= レイヤー2、11
= レイヤー3)。
例の解釈:
Wire.requestFrom(SLAVE_ADDRESS, 1); // 1バイトを要求
if (Wire.available()) {
uint8_t data = Wire.read();
bool shift = data & 0x80; // Shiftキーが押されているか
bool ctrl = data & 0x40; // Ctrlキーが押されているか
bool cmd = data & 0x20; // Cmd/GUIキーが押されているか
bool opt = data & 0x10; // Opt/Altキーが押されているか
uint8_t layer = data & 0x03; // レイヤー番号(0~3)
}
このガイドに従うことで、開発者はI2Cマスター側のファームウェアを効果的に実装し、カスタムキーボードデバイスとの通信を円滑に行うことができます。コマンド構造やレジスタマップを正しく理解し、適切な読み書き操作を実装してください。