QMK FirmwareでのI2C通信

さて、また随分と間が開いてしまってすみません。引き続き仕事が忙しかった&年を取るとなかなか集中力も続かず、なかなか面倒な作業は進みが遅くなりがちです。

さて、今回は、ADNS-5050の使い方( https://edoball.hatenablog.com/entry/2021/01/24/154514 )の続き、I2C通信を使ってQMK Firmware(の動いているProMicro)にProMicro + ADNS-5050を追加する方法について見ていきたい。

I2Cは、QMK Firmwareでの左右分離型のキーボードの接続によく使われているので、概要をご存じの方も多いと思う。ざっくり言うと、バス型のデバイス間通信の規格で、各デバイスはアドレスを持ち、マスタデバイスからのクロックに同期して、マスタとスレイブ間でデータのやり取りをする、というものだ。詳しいことは良い解説ページが幾つもある(例えば、kurobekoblog.com)ので、そちらを参照してほしい。

QMK Firmwareで何かカスタマイズする時は、だいたいkeymap.cを修正すればいいようになっているけど、I2Cで他のデバイスと通信する際もkeymap.cを書き換えるだけで良いようだ。今回はポインティングデバイスの追加なので、追加する内容としては、I2Cとポインティングデバイス関係で、I2Cとポインティングデバイスを初期化し,あとは適宜I2Cを使ってデバイスからのデータを読み込み,ホストにポインティングデバイス関係のイベントを送る、というものだ。

以前書いたADNS-5050コントロール側のProMiciroでは

Wire.onRequest(requestEvent);

という関数で、リクエストイベント関数を登録し、

requestEvent()

関数内で

Wire.write(-mouse_dx);
Wire.write(-mouse_dy);

などとI2Cで送信するデータを登録している。

一方、読み出し側のQMK Firmware側では、メインループから読み出される関数であるpointing_device_task()関数内で、
status = i2c_readReg(I2C_SLAVE_2_ADDR, 0x1, buff, sizeof(buff), TIMEOUT);
といった風に読み出してやればいい。


もう少し、具体的に見てみよう。

#include QMK_KEYBOARD_H
#include <stdint.h>
#include "report.h"
#include "host.h"
#include "timer.h"
#include "print.h"
#include "debug.h"
#include "pointing_device.h"
#include "i2c_master.h"
#include "i2c_slave.h"

// I2C_SALVE_2(8bit address)
#define I2C_SLAVE_2_ADDR 0x20
#define TIMEOUT 100
static report_mouse_t mouseReport = {};

まずは、こののように,pointing_device.h, i2c_master.h, i2c_slave.h などのポインティングデバイスや,I2C関係のヘッダをインクルードしておく。前の記事でADNS-5050コントローラ側のPro Microのアドレスを 7ビットで 0x10と定義したが,QMK Firmware側では8bitで表すので、上の例では
#define I2C_SLAVE_2_ADDR 0x20
と定義している。

次に、ポインティングデバイスの初期化等:

void pointing_device_init(void){
  mouseReport.x = 0;
  mouseReport.y = 0;
  mouseReport.v = 0;
  mouseReport.h = 0;
  mouseReport.buttons = 0;
}

void pointing_device_send(void){
  host_mouse_send(&mouseReport);
  mouseReport.x = 0;
  mouseReport.y = 0;
  mouseReport.v = 0;
  mouseReport.h = 0;
}

pointing_device_task()関数の実装:

void pointing_device_task(void){
  uint8_t buff[4];
  i2c_status_t status;
  int8_t dX, dY;
  static uint8_t prev_buff_state = 0;
  static uint8_t prev_buff_state_touch = 0; 
  static int8_t v_movement = 0;
  static float v_movement_mag = 0.05;
  mouseReport.x = 0; // x方向移動量
  mouseReport.y = 0; // y方向移動量
  mouseReport.v = 0; // 縦スクロール量
  mouseReport.h = 0; // 横スクロール量

// I2Cからの読み出し。
  status = i2c_readReg(I2C_SLAVE_2_ADDR, 0x1, buff, sizeof(buff), TIMEOUT);

  if (status == I2C_STATUS_SUCCESS) { //   正常に読み出せ場合
    dX = (int8_t) buff[0];
    dY = (int8_t) buff[1];
    // ハードウェアスイッチ(3バイト目のデータのLSB)についての処理。
    // オンの時RAISEレイヤをオンにする。
    if (prev_buff_state != buff[2]) {
      if (buff[2] & 1) {
        layer_on(_RAISE);
        update_tri_layer(_LOWER, _RAISE, _ADJUST);
      } else {
        layer_off(_RAISE);
        update_tri_layer(_LOWER, _RAISE, _ADJUST);
     }
    }
    prev_buff_state = buff[2];
    // タッチスイッチ1(4バイト目のデータのLSB)についての処理。
    // オンの時MOUSEレイヤをオンにする。
    if (prev_buff_state_touch != buff[3]) {
      if (buff[3] & 1) {
        layer_on(_MOUSE);
      } else {
        layer_off(_MOUSE);
      }
    }
    prev_buff_state_touch = buff[3];
    if ((buff[3] & 1) && ((buff[3] & 1<<1) == 0)) { // タッチスイッチ1がオンでタッチスイッチ2がオフの時、x,y 座標の移動量をマウスの移動量にする。
      // mouse x y movement
      mouseReport.x = dX;
      mouseReport.y = dY;
    } else {
      // それ以外の時(タッチスイッチ1がオフ、又は、タッチスイッチ2がオン)のときは、y座標の移動量を縦スクロールの移動量にする。
      v_movement += dY;
      if (abs(v_movement * v_movement_mag) >= 1) {
mouseReport.v = -(v_movement * v_movement_mag);
v_movement = v_movement + mouseReport.v / v_movement_mag;
      }
    }
    pointing_device_send();
  } else {
    // I2C 失敗。
  }
}

前の記事では、ADNS-5050 コントローラ側でのタッチスイッチ関係のコードを省略していたので、上のコードと対応していない点があるが、ざっくりこんな感じで、割と簡単にProMicroとI2C通信ができることがお分かりいただけたかと思う