PR

M5Stack スクロールユニット & ジョイスティック2ユニットをRaspberry Pi Pico/RP2040ボード(Arduino-Pico)、MicroPythonで使おう

Raspberry Pi/電子工作
スポンサーリンク

こんにちは、あろっちです。

M5Stack スクロールユニットはマウスのスクロールホイール風ロータリーエンコーダ拡張ユニットです。

m5-docs
The reference docs for M5Stack products. Quick start, get the detailed information or instructions such as IDE,UIFLOW,Ar...

Arduinoライブラリが用意されており、I2C経由でロータリーエンコーダの状態を取得できるので、手軽に実装できて様々な活用方法がありそうなイケてるデバイスと感じました。

共立電子産業株式会社 KYOHRITSU ELECTRONIC INDUSTRY CO.,LTD.
\商品券4%還元!/
Yahooショッピング

M5Stack ジョイスティック2ユニットはI2C経由でジョイスティックの状態が取得できるように作られており、こちらも使い勝手がよさそうです。

m5-docs
The reference docs for M5Stack products. Quick start, get the detailed information or instructions such as IDE,UIFLOW,Ar...

Arduinoライブラリ

共立電子産業株式会社 KYOHRITSU ELECTRONIC INDUSTRY CO.,LTD.
\商品券4%還元!/
Yahooショッピング

ただし、いずれの製品もライブラリがM5Stack製品 (ESP32) での使用が前提で作られていて他のプラットフォームではそのまま使えない点が惜しいところ。

ライブラリのソースコードを確認したところ、Wireの初期化部分を適切に変更することでRaspberry Pi Pico/RP2040ボード(Arduino-Pico)でも動作可能になることが分かりました。そこで、今回はその修正方法について紹介します。

また、スクロールユニット用のMicroPython向けライブラリを作成したので、こちらも紹介します。

スポンサーリンク

ライブラリの修正内容

Arduinoライブラリ(M5UnitScroll、M5UnitJoystick2)はインストール済みの前提とします。

インストールしたArduinoライブラリの格納場所は以下の記事を参照してください。

M5Stack スクロールユニット Arduinoライブラリ (M5UnitScroll)

ライブラリ M5UnitScrollの修正対象ファイル
  • M5UnitScroll.cpp

修正前

/*! @brief Initialize the Scroll. */
bool M5UnitScroll::begin(TwoWire *wire, uint8_t addr, uint8_t sda, uint8_t scl, uint32_t speed) {
    _wire  = wire;
    _addr  = addr;
    _sda   = sda;
    _scl   = scl;
    _speed = speed;
    _wire->begin(_sda, _scl);
    _wire->setClock(_speed);
    delay(10);
    _wire->beginTransmission(_addr);
    uint8_t error = _wire->endTransmission();
    if (error == 0) {
        return true;
    } else {
        return false;
    }
}

_wire->begin(_sda, _scl);の箇所を以下のように修正します。

/*! @brief Initialize the Scroll. */
bool M5UnitScroll::begin(TwoWire *wire, uint8_t addr, uint8_t sda, uint8_t scl, uint32_t speed) {
    _wire  = wire;
    _addr  = addr;
    _sda   = sda;
    _scl   = scl;
    _speed = speed;
#if defined(ARDUINO_ARCH_RP2040) && !defined(ARDUINO_ARCH_MBED)
    _wire->setSDA(_sda);
    _wire->setSCL(_scl);
    _wire->begin();
#else
    _wire->begin(_sda, _scl);
#endif
    _wire->setClock(_speed);
    delay(10);
    _wire->beginTransmission(_addr);
    uint8_t error = _wire->endTransmission();
    if (error == 0) {
        return true;
    } else {
        return false;
    }
}

M5Stack ジョイスティック2ユニット Arduinoライブラリ (M5UnitJoystick2)

ライブラリ M5UnitJoystick2の修正対象ファイル
  • m5_unit_joystick2.cpp

修正前

bool M5UnitJoystick2::begin(TwoWire *wire, uint8_t addr, uint8_t sda, uint8_t scl, uint32_t speed)
{
    _wire  = wire;
    _addr  = addr;
    _sda   = sda;
    _scl   = scl;
    _speed = speed;
    _wire->begin(_sda, _scl);
    _wire->setClock(speed);
    delay(10);
    _wire->beginTransmission(_addr);
    uint8_t error = _wire->endTransmission();
    if (error == 0) {
        return true;
    } else {
        return false;
    }
}

_wire->begin(_sda, _scl);の箇所を以下のように修正します。

bool M5UnitJoystick2::begin(TwoWire *wire, uint8_t addr, uint8_t sda, uint8_t scl, uint32_t speed)
{
    _wire  = wire;
    _addr  = addr;
    _sda   = sda;
    _scl   = scl;
    _speed = speed;
#if defined(ARDUINO_ARCH_RP2040) && !defined(ARDUINO_ARCH_MBED)
    _wire->setSDA(_sda);
    _wire->setSCL(_scl);
    _wire->begin();
#else
    _wire->begin(_sda, _scl);
#endif
    _wire->setClock(speed);
    delay(10);
    _wire->beginTransmission(_addr);
    uint8_t error = _wire->endTransmission();
    if (error == 0) {
        return true;
    } else {
        return false;
    }
}

いずれのライブラリも#ifなどのプリプロセッサを使用することで既存コードへの影響をなくし、Raspberry Pi Pico/RP2040ボード(Arduino-Pico)に対応させています。

Raspberry Pi Pico/RP2040ボード(Arduino-Pico)のプリプロセッサを使った判定については以下の記事を参照してください。

MicroPython向けライブラリ

M5Stack スクロールユニット

ライブラリのコードです。マイコンに保存します。

from machine import I2C, Pin
import time

# 定数の定義
SCROLL_ADDR = 0x40
ENCODER_REG = 0x10
BUTTON_REG = 0x20
RGB_LED_REG = 0x30
INC_ENCODER_REG = 0x50
BOOTLOADER_VERSION_REG = 0xFC
JUMP_TO_BOOTLOADER_REG = 0xFD
FIRMWARE_VERSION_REG = 0xFE
I2C_ADDRESS_REG = 0xFF


class M5UnitScroll:
    def __init__(self, i2c, addr=SCROLL_ADDR):
        self.i2c = i2c
        self.addr = addr
        self.initialized = False
        time.sleep(0.01)
        try:
            self.i2c.writeto(self.addr, b"")
            self.initialized = True
        except OSError:
            self.initialized = False

    def _write_bytes(self, addr, reg, buffer):
        data = bytearray([reg]) + buffer
        self.i2c.writeto(addr, data)

    def _read_bytes(self, addr, reg, length):
        self.i2c.writeto(addr, bytearray([reg]), False)
        return self.i2c.readfrom(addr, length)

    def get_encoder_value(self):
        data = self._read_bytes(self.addr, ENCODER_REG, 2)
        value = int.from_bytes(data, "little")
        if value >= 0x8000:
            value -= 0x10000
        return value

    def get_inc_encoder_value(self):
        data = self._read_bytes(self.addr, INC_ENCODER_REG, 2)
        value = int.from_bytes(data, "little")
        if value >= 0x8000:
            value -= 0x10000
        return value

    def get_button_status(self):
        data = self._read_bytes(self.addr, BUTTON_REG, 1)
        return data[0] == 0x00

    def set_led_color(self, color, index=0):
        data = bytearray(
            [index, (color >> 16) & 0xFF, (color >> 8) & 0xFF, color & 0xFF]
        )
        self._write_bytes(self.addr, RGB_LED_REG, data)

    def get_led_color(self):
        data = self._read_bytes(self.addr, RGB_LED_REG, 4)
        return (data[2] << 16) | (data[1] << 8) | data[0]

    def set_encoder_value(self, encoder):
        data = encoder.to_bytes(2, "little")
        self._write_bytes(self.addr, ENCODER_REG, data)

    def reset_encoder(self):
        self._write_bytes(self.addr, 0x40, bytearray([1]))

    def get_dev_status(self):
        try:
            self.i2c.writeto(self.addr, b"")
            return True
        except OSError:
            return False

    def get_bootloader_version(self):
        self.i2c.writeto(self.addr, bytearray([BOOTLOADER_VERSION_REG]), False)
        return self.i2c.readfrom(self.addr, 1)[0]

    def get_firmware_version(self):
        self.i2c.writeto(self.addr, bytearray([FIRMWARE_VERSION_REG]), False)
        return self.i2c.readfrom(self.addr, 1)[0]

以下はテスト用のコードです。I2CのGPIOは実際に接続されているGPIOのピン番号に変更してください。デフォルトはSCL=1、SDA=0

from machine import I2C, Pin
import time
from M5UnitScroll import M5UnitScroll


def main():
    # I2Cの初期化
    i2c = I2C(0, scl=Pin(1), sda=Pin(0), freq=400000)
    scroll = M5UnitScroll(i2c)

    # デバイスの初期化確認
    if not scroll.initialized:
        print("Failed to initialize M5UnitScroll")
        return

    # LEDの色を緑に設定
    scroll.set_led_color(0x00FF00)
    # エンコーダの値をリセット
    scroll.reset_encoder()

    boot_version = scroll.get_bootloader_version()
    firmware_version = scroll.get_firmware_version()
    device_status = scroll.get_dev_status()
    print(
        "Bootloader Version:",
        boot_version,
        "Firmware Version:",
        firmware_version,
        "Device Status:",
        device_status,
    )
    # エンコーダがクリックされるまで待機
    while not scroll.get_button_status():
        time.sleep_ms(100)

    # エンコーダの値をリセット
    scroll.set_encoder_value(0)

    # エンコーダの値を20msごとに表示
    while True:
        encoder_value = scroll.get_encoder_value()
        inc_encoder_value = scroll.get_inc_encoder_value()
        btn_status = scroll.get_button_status()
        print(
            "Encoder Value:",
            encoder_value,
            "Inc Encoder Value:",
            inc_encoder_value,
            "Button Status:",
            btn_status,
        )
        time.sleep_ms(20)


# プログラムのエントリーポイント
if __name__ == "__main__":
    main()

M5Stack ジョイスティック2ユニット

ライブラリのコードです。マイコンに保存します。

from machine import I2C, Pin
import time

# 定数の定義
JOYSTICK2_ADDR = 0x63
JOYSTICK2_ADC_VALUE_12BITS_REG = 0x00
JOYSTICK2_ADC_VALUE_8BITS_REG = 0x10
JOYSTICK2_BUTTON_REG = 0x20
JOYSTICK2_RGB_REG = 0x30
JOYSTICK2_ADC_VALUE_CAL_REG = 0x40
JOYSTICK2_OFFSET_ADC_VALUE_12BITS_REG = 0x50
JOYSTICK2_OFFSET_ADC_VALUE_8BITS_REG = 0x60
JOYSTICK2_FIRMWARE_VERSION_REG = 0xFE
JOYSTICK2_BOOTLOADER_VERSION_REG = 0xFC

ADC_8BIT_RESULT = 0
ADC_16BIT_RESULT = 1


class M5UnitJoystick2:
    def __init__(self, i2c, addr=JOYSTICK2_ADDR):
        self.i2c = i2c
        self.addr = addr
        self.initialized = False
        time.sleep(0.01)
        try:
            self.i2c.writeto(self.addr, b"")
            self.initialized = True
        except OSError:
            self.initialized = False

    def _write_bytes(self, addr, reg, buffer):
        data = bytearray([reg]) + buffer
        self.i2c.writeto(addr, data)

    def _read_bytes(self, addr, reg, length):
        self.i2c.writeto(addr, bytearray([reg]), False)
        return self.i2c.readfrom(addr, length)

    def get_joy_adc_value_x(self, adc_bits):
        if adc_bits == ADC_16BIT_RESULT:
            reg = JOYSTICK2_ADC_VALUE_12BITS_REG
            data = self._read_bytes(self.addr, reg, 2)
            value = int.from_bytes(data, "little")
        elif adc_bits == ADC_8BIT_RESULT:
            reg = JOYSTICK2_ADC_VALUE_8BITS_REG
            data = self._read_bytes(self.addr, reg, 1)
            value = data[0]
        return value

    def get_joy_adc_value_y(self, adc_bits):
        if adc_bits == ADC_16BIT_RESULT:
            reg = JOYSTICK2_ADC_VALUE_12BITS_REG + 2
            data = self._read_bytes(self.addr, reg, 2)
            value = int.from_bytes(data, "little")
        elif adc_bits == ADC_8BIT_RESULT:
            reg = JOYSTICK2_ADC_VALUE_8BITS_REG + 1
            data = self._read_bytes(self.addr, reg, 1)
            value = data[0]
        return value

    def get_button_value(self):
        reg = JOYSTICK2_BUTTON_REG
        data = self._read_bytes(self.addr, reg, 1)
        return data[0]

    def set_rgb_color(self, color):
        self._write_bytes(self.addr, JOYSTICK2_RGB_REG, color.to_bytes(4, "little"))

    def get_rgb_color(self):
        data = self._read_bytes(self.addr, JOYSTICK2_RGB_REG, 4)
        return int.from_bytes(data, "little")

    def get_bootloader_version(self):
        reg = JOYSTICK2_BOOTLOADER_VERSION_REG
        data = self._read_bytes(self.addr, reg, 1)
        return data[0]

    def get_firmware_version(self):
        reg = JOYSTICK2_FIRMWARE_VERSION_REG
        data = self._read_bytes(self.addr, reg, 1)
        return data[0]

    def get_joy_adc_16bits_value_xy(self, adc_x, adc_y):
        reg = JOYSTICK2_ADC_VALUE_12BITS_REG
        data = self._read_bytes(self.addr, reg, 4)
        adc_x[0] = int.from_bytes(data[0:2], "little")
        adc_y[0] = int.from_bytes(data[2:4], "little")

    def get_joy_adc_8bits_value_xy(self, adc_x, adc_y):
        reg = JOYSTICK2_ADC_VALUE_8BITS_REG
        data = self._read_bytes(self.addr, reg, 2)
        adc_x[0] = data[0]
        adc_y[0] = data[1]

    def get_joy_adc_12bits_offset_value_x(self):
        reg = JOYSTICK2_OFFSET_ADC_VALUE_12BITS_REG
        data = self._read_bytes(self.addr, reg, 2)
        value = int.from_bytes(data, "little")
        if value >= 0x8000:
            value -= 0x10000
        return value

    def get_joy_adc_12bits_offset_value_y(self):
        reg = JOYSTICK2_OFFSET_ADC_VALUE_12BITS_REG + 2
        data = self._read_bytes(self.addr, reg, 2)
        value = int.from_bytes(data, "little")
        if value >= 0x8000:
            value -= 0x10000
        return value

    def get_joy_adc_8bits_offset_value_x(self):
        reg = JOYSTICK2_OFFSET_ADC_VALUE_8BITS_REG
        data = self._read_bytes(self.addr, reg, 1)
        value = data[0]
        if value >= 0x80:
            value -= 0x100
        return value

    def get_joy_adc_8bits_offset_value_y(self):
        reg = JOYSTICK2_OFFSET_ADC_VALUE_8BITS_REG + 1
        data = self._read_bytes(self.addr, reg, 1)
        value = data[0]
        if value >= 0x80:
            value -= 0x100
        return value

    def set_joy_adc_value_cal(
        self,
        x_neg_min,
        x_neg_max,
        x_pos_min,
        x_pos_max,
        y_neg_min,
        y_neg_max,
        y_pos_min,
        y_pos_max,
    ):
        data = bytearray(16)
        data[0:2] = x_neg_min.to_bytes(2, "little")
        data[2:4] = x_neg_max.to_bytes(2, "little")
        data[4:6] = x_pos_min.to_bytes(2, "little")
        data[6:8] = x_pos_max.to_bytes(2, "little")
        data[8:10] = y_neg_min.to_bytes(2, "little")
        data[10:12] = y_neg_max.to_bytes(2, "little")
        data[12:14] = y_pos_min.to_bytes(2, "little")
        data[14:16] = y_pos_max.to_bytes(2, "little")
        self._write_bytes(self.addr, JOYSTICK2_ADC_VALUE_CAL_REG, data)

    def get_joy_adc_value_cal(
        self,
        x_neg_min,
        x_neg_max,
        x_pos_min,
        x_pos_max,
        y_neg_min,
        y_neg_max,
        y_pos_min,
        y_pos_max,
    ):
        data = self._read_bytes(self.addr, JOYSTICK2_ADC_VALUE_CAL_REG, 16)
        x_neg_min[0] = int.from_bytes(data[0:2], "little")
        x_neg_max[0] = int.from_bytes(data[2:4], "little")
        x_pos_min[0] = int.from_bytes(data[4:6], "little")
        x_pos_max[0] = int.from_bytes(data[6:8], "little")
        y_neg_min[0] = int.from_bytes(data[8:10], "little")
        y_neg_max[0] = int.from_bytes(data[10:12], "little")
        y_pos_min[0] = int.from_bytes(data[12:14], "little")
        y_pos_max[0] = int.from_bytes(data[14:16], "little")

メインコードからは次のように初期化します。

from machine import I2C, Pin
from M5UnitJoystick2 import M5UnitJoystick2

i2c = I2C(0, scl=Pin(1), sda=Pin(0), freq=400000)
joystick = M5UnitJoystick2(i2c)
if not joystick.initialized:
    print("Failed to initialize M5UnitJoystick2")

関連記事

当ブログのマイコン記事です。ぜひご覧ください。

コメント

タイトルとURLをコピーしました