0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Zephyrでmicro:bitの外部I2Cを使ってみた

Last updated at Posted at 2025-01-16

この文章は、ZephyrRTOS Advent Calendar 2024 1の7日目のエントリとして書かれました。

はじめに

これまで、何本かmicro:bitでZephyrを使う記事を書いてきましたが、全てサンプルを動かすというレベルのものでした。

そろそろ、なにか適当なアプリケーションを書きたいなぁと思ったところに、Zephyr and the BBC Microbit V2 Tutorial Part 3: I2C 2 というI2Cデバイスを使う記事があることに気が付きました。これは、内蔵I2Cに接続されている加速度センサーLSM303を使うという話になっています。

「micro:bitって外側端子にもI2Cが出ているから、ひょっとして外付けでI2Cデバイスが使えるんじゃね」と考えたので、使えないか試してみることにしました。

最終的にI2C接続のジョイスティックを動かすことができたので、報告したいと思います。

ハードウエア

利用したハードウエアは以下の通りです。

ハードウエア SKU 役割
micro:bit v2.0 SEDU-066006 メインボード
M5Stack用 I2Cジョイスティックユニット M5STACK-U024-C I2Cジョイスティック
M5:Bit micro:bit用変換ボード M5STACK-A051 micro:bitからI2Cを出す
GROVE - 4ピン-ジャンパメスケーブル SEEED-110990028 M5:Bitとジョイスティック接続用

接続イメージは、以下の図の通りです。

接続イメージ図

以下に各デバイスに関して説明します。

micro:bit

今回もmicro:bitを利用しました。
J-Link化したmicro:bit v2.0 3 を使っています。
初期設定などの詳細は、macOSでZephyrのmicro:bitデモを動かす 4 をご覧ください。

最新のmicro:bitはv2.2はJ-Link化できませんので、注意してください。

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

M5Stack用 I2Cジョイスティックユニット 5 は、Grove接続のI2Cジョイスティックです。

プロトコルは非常に簡単で、I2Cアドレス0x52のレジスタ0x00から3バイト読み込むと、それぞれx方向の値,y方向の値,btn状態が得られます。
初期化などは必要ありません。

接続用ハードウエア

以下のハードウエアをmicro:bitとジョイスティックの接続用に使いました。

M5:Bit micro:bit用変換ボード

M5:Bit micro:bit用変換ボード 6 は、micro:bitのコネクタをピンに出すためボードです。この種のボードとしては、非常に安価になっています。
I2Cが右下の端子で2系統出せるようになっています。I2CがGroveで出ていればもっと良かったのですが…
さらに、シリアルはGrove端子に出ているので、簡単に利用できます。

この製品に限らず、micro:bitからI2Cを出すボードは各種出ていますので、どれを使っても問題ありません。
売り切れが多いですが、"grove micro:bit"で検索する 7 とそのような商品が出てきます。
いくつかの商品は直接Grove接続できるので、次のケーブルを使わなくても、普通のGroveケーブルでジョイスティックと接続できるようになります。

GROVE - 4ピン-ジャンパメスケーブル

GROVE - 4ピン-ジャンパメスケーブル 8 は、グローブケーブルを2.54 mmピッチのジャンパメスコネクタに変換するケーブルです。
M5:Bit側が4ピンオスコネクタで、ジョイスティック側がGroveになるため、必要になります。

Seeed製品のGroveケーブルとM5Stack製品のGroveケーブルでは、黄色と白の信号線の色が反対になっているので注意してください。
私は、これで数時間溶かしました…

ソフトウエア:デモプログラムソースコード

ここでは、ソフトウエアについて解説します。

全てのコードは、 https://github.com/610t/zephyr-playground で公開しています。

ジョイスティックの値をシリアルに出力する:m5stack_joystick

今回作成したアプリケーションのソースコードは、以下の通りです。
githubでは、 https://github.com/610t/zephyr-playground/tree/main/samples/boards/bbc/microbit/m5stack_joystick にあります。
他のサンプルと同じように、zephyr/samples/boards/bbc/microbit/m5stack_joystick/に以下のように配置していることを仮定しています。

  • prj.conf
  • CMakeLists.txt
  • app.overlay
  • src/
    • main.c

プロジェクト設定ファイル:prj.conf

プロジェクトで利用する機能を指定します。
今回は、I2Cを有効にしています。

prj.conf
CONFIG_I2C=y

Cmakefile:CMakeLists.txt

アプリケーションbuild用のcmakeの設定ファイルです。
ソースは、main.cだけなので、それを指定しているだけです。

CMakeLists.txt
cmake_minimum_required(VERSION 3.13.1)

find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(m5stack_joystick)

target_sources(app PRIVATE src/main.c)

Device Tree Source Overlay:app.overlay

Device Tree Source(DTS)の上書きを行う場合に利用するファイルです。

外部I2C端子であるi2c1の定義を行っています。
外部I2Cでは、SDLが0x20、SCLが0x1aのため、これを指定しています。

app.overlay
&pinctrl {
        i2c1_default: i2c1_default {
                group1 {
                        psels = <NRF_PSEL(TWIM_SDA, 0, 0x20)>,
                                <NRF_PSEL(TWIM_SCL, 0, 0x1a)>;
                };
        };
};

&i2c1 {
        compatible = "nordic,nrf-twim";
        status = "okay";
        pinctrl-0 = <&i2c1_default>;
        pinctrl-names = "default";
};

アプリケーション本体:src/main.c

アプリケーション本体です。
ソースコードをシンプルにするため、返り値を確認してのエラー処理などは全くしてませんので、利用する時はちゃんとするようにしてください。

i2c = DEVICE_DT_GET(DT_NODELABEL(i2c1))でI2Cバスの初期化を行うだけで、このデバイスの場合は初期化処理の必要はありません。

データは、i2c_burst_read(i2c,0x52,0x00,val,3);で3バイトのデータval[]を受け取ります。

変数 意味
val[0] x方向の値
val[1] y方向の値
val[2] ボタンの状態

受け取った値は、printkを使ってシリアルコンソールに出力します。

src/main.c
#include <stdio.h>

#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <zephyr/drivers/i2c.h>

static const struct device *i2c;

int main(void)
{
        uint8_t val[3];

        // I2Cの初期化
        i2c = DEVICE_DT_GET(DT_NODELABEL(i2c1));
        k_msleep(1000);

        while (1) {
                // I2Cアドレス0x52のレジスタ0x00から3バイト読み込み
                i2c_burst_read(i2c,0x52,0x00,val,3);

                // 結果の出力
                printk("(%d,%d):%d\n",val[0],val[1],val[2]);

                k_msleep(100);
        }
}

ジョイスティックの状態を5x5 LED画面に表示する:m5stack_joystick_display

micro:bitには、5x5 LED画面があるので、ジョイスティックの状態をここに表示することを考えてみます。
githubでのコードは、 https://github.com/610t/zephyr-playground/tree/main/samples/boards/bbc/microbit/m5stack_joystick_display です。
更に、ジョイスティックを押し込んだ時には、Bを表示し、音をだすことにします。

ここでは、変更が必要なソースだけを紹介します。
CMakeLists.txtapp.overlayは、m5stack_joystickと同じものを利用します。

プロジェクト設定ファイル:prj.conf

今回は、I2C以外にもディスプレイ関係を有効にしています。
更に、音を出すために、PWMも有効にしています。

prj.conf
CONFIG_GPIO=y
CONFIG_DISPLAY=y
CONFIG_MICROBIT_DISPLAY=y
CONFIG_PWM=y
CONFIG_I2C=y

Device Tree Source Overlay:app.overlay

今回は、PWMに対する定義の追加が必要です。
他は、これまでと同じです。

app.overlay
&pinctrl {
        i2c1_default: i2c1_default {
                group1 {
                        psels = <NRF_PSEL(TWIM_SDA, 0, 0x20)>,
                                <NRF_PSEL(TWIM_SCL, 0, 0x1a)>;
                };
        };
};

&i2c1 {
        compatible = "nordic,nrf-twim";
        status = "okay";
        pinctrl-0 = <&i2c1_default>;
        pinctrl-names = "default";
};

/ {
        zephyr,user {
                /* period cell corresponds to initial period */
                pwms = <&pwm1 0 PWM_USEC(1500) PWM_POLARITY_NORMAL>;
        };
};

アプリケーション本体:src/main.c

アプリケーション本体です。
m5stack_joystickにジョイスティックの値に応じて5x5 LEDに表示を行う機能と、ジョイスティックが押し込まれた時にBを表示して音を出す機能を追加しています。

src/main.c
#include <stdio.h>

#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/pwm.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/device.h>

#include <zephyr/display/mb_display.h>

static const struct pwm_dt_spec pwm = PWM_DT_SPEC_GET(DT_PATH(zephyr_user));
static const struct device *i2c;

int main(void)
{
        uint8_t val[3];
        struct mb_display *disp = mb_display_get();
        int x,y;

        // PWMの初期化
        if (!pwm_is_ready_dt(&pwm)) {
                printk("%s: device not ready.\n", pwm.dev->name);
                return 0;
        }

        // I2Cの初期化
        i2c = DEVICE_DT_GET(DT_NODELABEL(i2c1));
        k_msleep(1000);

        while (1) {
                k_msleep(100);
                // I2Cアドレス0x52のレジスタ0x00から3バイト読み込み
                i2c_burst_read(i2c,0x52,0x00,val,3);

                // 結果の出力
                printk("(%d,%d):%d\n",val[0],val[1],val[2]);

                // ディスプレイに表示  
                x = 4-(int)(val[0]*5/255);
                y = (int)(val[1]*5/255);
                struct mb_image pixel = {};
                pixel.row[y] = BIT(x);
                mb_display_image(disp, MB_DISPLAY_MODE_SINGLE, 250, &pixel, 1);

                // ジョイスティックのボタンが押されているときの処理
                //// 画面に"B"を表示して、音を出す
                if(val[2]) {
                        mb_display_print(disp, MB_DISPLAY_MODE_SINGLE, 1 * MSEC_PER_SEC, "B");
                        pwm_set_dt(&pwm, pwm.period, pwm.period / 2U);
                        k_sleep(K_MSEC(60));
                        pwm_set_dt(&pwm, 0, 0);
                }
        }
}

ドットを食べるゲーム:m5stack_joystick_dot_eater

最後に、ジョイスティックで自機を操作して、現れるドットを食べていくゲームを作りました。
githubのコードは、 https://github.com/610t/zephyr-playground/tree/main/samples/boards/bbc/microbit/m5stack_joystick_dot_eater です。

はじめに、'A'が表示されている状態でボタンAを押すことでゲームが始まります。
ジョイスティックで自機を動かして、もうひとつのドットで表示される敵機と位置を合わせてください。それで、そのドットを食べることができます。
制限時間内にいくつ食べられるかを競います。
'GAME OVER!!'が表示された後で、スコアが表示されるようになっています。

src/main.c以外のファイルは、m5stack_joystick_displayと同じです。

アプリケーション本体:src/main.c

このプログラムでは、新たにボタンAを扱うために、GPIOのコールバックを用いています。

main.c:GPIOの処理
#include <zephyr/drivers/gpio.h>
  ...
static const struct gpio_dt_spec sw0_gpio = GPIO_DT_SPEC_GET(DT_ALIAS(sw0), gpios);
  ...
// ボタンコールバック: ゲームスタート用
static void button_pressed(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
{
  // 内容省略
}
  ...
	// GPIOの初期化
	static struct gpio_callback button_cb_data;
    /// ピンの設定
	gpio_pin_configure_dt(&sw0_gpio, GPIO_INPUT|GPIO_ACTIVE_HIGH);
    //// 割り込みの設定
	gpio_pin_interrupt_configure_dt(&sw0_gpio, GPIO_INT_EDGE_TO_ACTIVE);
    //// 割り込み用コールバック関数button_cb_dataの割り当て
	gpio_init_callback(&button_cb_data, button_pressed, BIT(sw0_gpio.pin));
    //// コールバック関数を登録
	gpio_add_callback(sw0_gpio.port, &button_cb_data);
  ...

敵機の処理をしている部分以外はこれまでのものと同じなので、詳細は省略します。

main.c
#include <stdio.h>
#include <stdlib.h>

#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/pwm.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/device.h>

#include <zephyr/display/mb_display.h>

bool game_flag = false;

static const struct gpio_dt_spec sw0_gpio = GPIO_DT_SPEC_GET(DT_ALIAS(sw0), gpios);
static const struct pwm_dt_spec pwm = PWM_DT_SPEC_GET(DT_PATH(zephyr_user));
static const struct device *i2c;

// ボタンコールバック: ゲームスタート用
static void button_pressed(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
{
	if (pins & BIT(sw0_gpio.pin)) {
		printk("A pressed\n");
		game_flag = true;
	}
}

int main(void)
{
	uint8_t val[3];
	struct mb_display *disp = mb_display_get();
        int x,y;

	// PWMの初期化
	if (!pwm_is_ready_dt(&pwm)) {
		printk("%s: device not ready.\n", pwm.dev->name);
		return 0;
	}

	// I2Cの初期化
	i2c = DEVICE_DT_GET(DT_NODELABEL(i2c1));
	k_msleep(1000);

	// GPIOの初期化
	static struct gpio_callback button_cb_data;
	gpio_pin_configure_dt(&sw0_gpio, GPIO_INPUT|GPIO_ACTIVE_HIGH);
	gpio_pin_interrupt_configure_dt(&sw0_gpio, GPIO_INT_EDGE_TO_ACTIVE);
	gpio_init_callback(&button_cb_data, button_pressed, BIT(sw0_gpio.pin));
	gpio_add_callback(sw0_gpio.port, &button_cb_data);

	int score=0;
	while (1) {
		int e_x,e_y;

		// Aボタンでゲーム開始
		while(!game_flag) {
			mb_display_print(disp, MB_DISPLAY_MODE_SINGLE, MSEC_PER_SEC, "A");
			k_msleep(100);
		}

		e_x=rand()%5;
		e_y=rand()%5;

		for(int timer=0;timer<100;timer++) {
			k_msleep(100);
			// I2Cアドレス0x52のレジスタ0x00から3バイト読み込み
			i2c_burst_read(i2c,0x52,0x00,val,3);

			// 結果の出力
			printk("(%d,%d):%d\n",val[0],val[1],val[2]);

			// ディスプレイに表示  
			x = 4-(int)(val[0]*5/255);
			y = (int)(val[1]*5/255);
			struct mb_image pixel = {};
			pixel.row[y] = BIT(x);
			pixel.row[e_y] = BIT(e_x);
			mb_display_image(disp, MB_DISPLAY_MODE_SINGLE, 250, &pixel, 1);
			printk("Pos:dot(%d,%d)==enemy(%d,%d)?\n",x,y,e_x,e_y);
			if(x==e_x && y==e_y) {
				score++;
				printk("score:%d\n",score);
				e_x=rand()%5;
				e_y=rand()%5;
				pwm_set_dt(&pwm, pwm.period, pwm.period / 2U);
				k_sleep(K_MSEC(60));
				pwm_set_dt(&pwm, 0, 0);
			}

			// ジョイスティックのボタンが押されているときの処理
			//// 画面に"B"を表示して、音を出す
			if(val[2]) {
				mb_display_print(disp, MB_DISPLAY_MODE_SINGLE, 1 * MSEC_PER_SEC, "B");
				pwm_set_dt(&pwm, pwm.period, pwm.period / 2U);
				k_sleep(K_MSEC(60));
				pwm_set_dt(&pwm, 0, 0);
			}
			k_msleep(25);
		}
		mb_display_print(disp, MB_DISPLAY_MODE_SINGLE, 0.25 * MSEC_PER_SEC, "GAME OVER!!");
		k_msleep(3000);

		mb_display_print(disp, MB_DISPLAY_MODE_SINGLE, 1 * MSEC_PER_SEC, "%d", score);
		k_msleep(3000);
		game_flag = false;
	}
}

動作手順

動作手順は、以下の通りです。

build

以下のコマンドでbuildします。
サンプルディレクトリ(samples/boards/bbc/microbit/m5stack_joystick)は適切に設定してください。ここでは、m5stack_joystickをbuildすることを仮定しています。

build
$ west build -p always -b bbc_microbit_v2 samples/boards/bbc/microbit/m5stack_joystick
   ...
[151/151] Linking C executable zephyr/zephyr.elf
Memory region         Used Size  Region Size  %age Used
           FLASH:       27556 B       512 KB      5.26%
             RAM:        5808 B       128 KB      4.43%
        IDT_LIST:          0 GB        32 KB      0.00%
Generating files from /Users/mutoh/work/zephyrproject/zephyr/build/zephyr/zephyr.elf for board: bbc_microbit_v2

flash

J-Link化したmicro:bitでは、以下のコマンドでflashできます。
--runner jlinkオプションは必要ない場合もあるようですが、J-Linkを強制的に利用するために指定しています。

flash
$ west flash --runner jlink
-- west flash: rebuilding
ninja: no work to do.
-- west flash: using runner jlink
-- runners.jlink: reset after flashing requested
-- runners.jlink: JLink version: 8.12
-- runners.jlink: Flashing file: /Users/mutoh/work/zephyrproject/zephyr/build/zephyr/zephyr.hex

動作確認

printkの出力は、シリアルコンソールに出されるため、これを確認する必要があります。
今回は、minicomを使いました。

minicomコマンドライン(macOS)
# シリアルデバイスの確認
$ ls -l /dev/cu.*
crw-rw-rw-  1 root  wheel  0x16000005  1  6 11:55 /dev/cu.Bluetooth-Incoming-Port
crw-rw-rw-  1 root  wheel  0x16000011  1 16 08:14 /dev/cu.usbmodem0007820596381
# minicomの起動
$ minicom -D /dev/cu.usbmodem0007820596381
(14,68):0
(33,39):0
(46,29):0
(58,19):1
(59,18):0
(59,39):0
(110,146):1
(104,162):0
(99,196):0
(75,239):1
  ...

m5stack_joystick

m5stack_joystickの動作の様子は、以下の動画 9 のようになります。

m5stack_joystick_display

m5stack_joystick_displayの動作の様子は、以下の動画 10 のようになります。

おわりに

サンプルを使うのではなく、はじめてZephyrでコードを書きました。
標準のDTSで定義されていないバスの情報などがアプリケーション側で上書きできたりするのには、ちょっと驚きました。
参考になりましたら、幸いです。

  1. https://qiita.com/advent-calendar/2024/zephyr

  2. https://zephyrproject.org/zephyr-and-the-bbc-microbit-v2-tutorial-part-3-i2c/

  3. https://www.switch-science.com/products/6600

  4. https://qiita.com/610t/items/65feb82e35db99568d25

  5. https://www.switch-science.com/products/8101

  6. https://www.switch-science.com/products/6469

  7. https://www.switch-science.com/search?q=grove+micro%3Abit

  8. https://www.switch-science.com/products/1048

  9. https://youtu.be/rk4jJjwS5Mw

  10. https://www.youtube.com/watch?v=ZFt2L00lDps

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?