コンテンツにスキップ

センサーの統合と読み出し

この記事では、SiFliのセンサー統合ガイドを、実用的なドライバー開発手順としてまとめています。同じガイドはSF32LB52x、SF32LB55x、SF32LB56x、SF32LB58x向けに公開されています。ファイルパスやシンボルを転用する前に、対象SDKブランチを確認してください。センサーのデータシートと基板配線の確認から、Kconfigの設定、バスの読み書きコールバック、割り込み処理、RT-Threadセンサー登録、コマンドラインでの検証までを扱います。

例では、6軸センサーのLSM6DSL(加速度センサーとジャイロスコープ)および温度・気圧センサーのBMP280を使用します。SDKガイドのAPI名とサンプル識別子をそのまま示しています。製品に転用する前に、対象SDKブランチを確認してください。

対象ファミリー

この記事では、SF32LB52x、SF32LB55x、SF32LB56x、SF32LB58x向けに共通するセンサードライバー開発の流れをまとめています。LSM6DSL/BMP280のコードは実装例です。実際のバスインスタンス、GPIO番号、割り込み経路、Kconfigのパス、利用できる周辺機能は、対象ファミリーやボードによって異なります。

ハードウェアとセンサーインターフェースを確認する

まずセンサーのデータシートを読み、センサーの機能とホスト側インターフェースを確認します。SDKガイドでは、6軸(加速度・ジャイロ)、温度、気圧、地磁気、照度、GPS、モーターの各センサーを例に挙げています。このガイドで扱うホスト側インターフェースはI2C、SPI、UARTです。

ドライバーを実装する前に、次を確認してください。

  • ハードウェア設計を確認し、選択したバスが有効になっていて、パッケージの端子まで配線されていること。
  • センサーとSF32デバイスの電圧範囲および周波数範囲に互換性があること。
  • バス名、デバイスアドレス、チップセレクト(該当する場合)、電源制御、リセット、割り込みピンを特定すること。
  • ドライバーがアプリケーションに提供するデータ操作と制御操作を、アプリケーション担当者と合意すること。

ドライバーとビルド設定を準備する

SiFli-SDKの周辺機器ドライバーはrtos/rthread/bsp/sifli/peripherals以下にあります。バスと実装方式を決めたら、新しいデバイス用のディレクトリを追加します。rtos/rthread/bsp/sifli/peripherals/Kconfigにmenuconfigエントリーを追加し、次の項目を含めます。

  • センサーの有効化スイッチ。
  • 対応するバスの選択と名称(I2C、SPI、UARTなど)。
  • 電源、リセット、チップセレクト、割り込み用GPIOの設定。
  • 活動検出、歩数計、FIFOなどの任意のセンサー機能。

新しいディレクトリにドライバーを実装します。ボード固有の値をハードコードせず、バス名、GPIO番号、割り込み番号はKconfigシンボルから読み取ります。最後に、そのディレクトリのSConscriptファイルでドライバーとKconfigシンボルを関連付けます。

プロジェクトディレクトリからmenuconfigを実行し、Select board peripherals を選択して新しいモジュールを有効化します。その配下の設定でボード固有の値を設定します。

Kconfigでインターフェースを設定する

次のKconfig例はSF32LB52xガイドから抜粋したもので、6軸センサーグループの下でLSM6DSLを有効にします。SENSOR_USING_6Dはグループ全体のスイッチで、その子スイッチがLSM6DSLの実装を選択します。

menuconfig SENSOR_USING_6D
    bool "Enable 6D Sensor for Accelerator and Gyro"
    default n
    if SENSOR_USING_6D
        menuconfig ACC_USING_LSM6DSL
            bool "Enable Accelerator and Gyro LSM6DSL"
            select RT_USING_SENSOR
            default n
            if ACC_USING_LSM6DSL
                config LSM6DSL_USING_I2C
                int "LSM6DSL BUS type: 1 = I2C, 0 = SPI"
                default 0
                config LSM6DSL_BUS_NAME
                string "Sensor LSM6DSL BUS name"
                default "spi1"

                config LSM6DSL_INT_GPIO_BIT
                int "LSM6DSL Interrupt 1 PIN"
                default 97
                config LSM6DSL_INT2_GPIO_BIT
                int "LSM6DSL Interrupt 2 PIN"
                default 94

                config LSM_USING_AWT
                bool "Enable AWT fucntion"
                default y
                config LSM_USING_PEDO
                bool "Enable Pedometer fucntion"
                default y
                config LSM6DSL_USE_FIFO
                bool "LSM6DSL use fifo"
                default y
            endif
    endif

グループスイッチには、LSM6DSLやSC7A20など複数の対応モデルを含めることができます。外部インターフェースが競合しないよう、1つのバス構成では対象モデルだけを有効にしてください。この例の各設定は次のとおりです。

  • ACC_USING_LSM6DSLはLSM6DSLドライバーを有効にします。
  • LSM6DSL_USING_I2CはI2C(1)またはSPI(0)を選択します。センサーが1種類のインターフェースにしか対応しない場合、このシンボルは省略します。
  • LSM6DSL_BUS_NAMEは、RT-Threadセンサードライバーが検索するバスデバイス名です。
  • ボードで割り当てている場合、LSM6DSL_INT_GPIO_BITとLSM6DSL_INT2_GPIO_BITに割り込みピンを指定します。
  • LSM_USING_AWT、LSM_USING_PEDO、LSM6DSL_USE_FIFOはデバイス固有の機能です。選択したドライバーが対応する場合にのみ定義します。

この例はSF32LB52xガイドに準拠しています。他のファミリーのガイドやSDKブランチでは、オプションやメニューのパスが異なる場合があります。例を適用する前に、対象ブランチで実際のシンボルを確認してください。

バスを取得してレジスターアクセスを実装する

rt_i2c_bus_device_findまたはrt_device_findでバスデバイスを取得し、選択したインターフェースに対応するバス読み書きAPIを呼び出します。デバイスアドレス、レジスターマップ、コマンドシーケンス、レジスター幅については、センサーのデータシートを一次情報として扱ってください。

int LSM6DSL_I2C_Init()
{
    /* get i2c bus device */
    lsm6dsl_content.bus_handle = rt_i2c_bus_device_find(LSM6DSL_BUS_NAME);
    if (lsm6dsl_content.bus_handle)
    {
        LOG_D("Find i2c bus device %s\n", LSM6DSL_BUS_NAME);
    }
    else
    {
        LOG_E("Can not found i2c bus %s, init fail\n", LSM6DSL_BUS_NAME);
        return -1;
    }

    return 0;
}

int32_t LSM_I2C_Write(void *ctx, uint8_t reg, uint8_t *data, uint16_t len)
{
    rt_size_t res;

    struct LSM6DSL_CONT_T *handle = (struct LSM6DSL_CONT_T *)ctx;

    if (handle && handle->bus_handle && data)
    {
        uint16_t addr16 = (uint16_t)reg;
        res = rt_i2c_mem_write(handle->bus_handle, handle->dev_addr, addr16, 8, data, len);
        if (res > 0)
            return 0;
        else
            return -2;
    }

    return -3;
}

int32_t LSM_I2C_Read(void *ctx, uint8_t reg, uint8_t *data, uint16_t len)
{
    rt_size_t res;
    struct LSM6DSL_CONT_T *handle = (struct LSM6DSL_CONT_T *)ctx;

    if (handle && handle->bus_handle && data)
    {
        uint16_t addr16 = (uint16_t)reg;
        res = rt_i2c_mem_read(handle->bus_handle, handle->dev_addr, addr16, 8, data, len);
        if (res > 0)
            return 0;
        else
            return -2;
    }

    return -3;
}

このサンプルは、バスが見つからない場合に-1、バス転送が失敗した場合に-2、コンテキスト、バスハンドル、データポインターが無効な場合に-3を返します。エラー処理方針は、周辺のドライバー実装に合わせてください。

センサー割り込みを設定する

rt_pin_modeで割り込みピンを入力に設定し、rt_pin_attach_irqでハンドラーとトリガー条件を登録します。割り込みの有効/無効はrt_pin_irq_enableで切り替えます。SDKでは、立ち上がりエッジ(PIN_IRQ_MODE_RISING)、立ち下がりエッジ(PIN_IRQ_MODE_FALLING)、両エッジ(PIN_IRQ_MODE_RISING_FALLING)、High/Lowレベル(PIN_IRQ_MODE_HIGH_LEVEL/PIN_IRQ_MODE_LOW_LEVEL)の各トリガーに対応しています。ハンドラーの内容はセンサーによって異なります。

int lsm6dsl_gpio_int_enable(void)
{
    struct rt_device_pin_mode m;

    // get pin device
    rt_device_t device = rt_device_find("pin");
    if (!device)
    {
        LOG_E("GPIO pin device not found at LSM6DSL\n");
        return -1;
    }

    rt_device_open(device, RT_DEVICE_OFLAG_RDWR);

    // int pin cfg
    m.pin = LSM6DSL_INT_GPIO_BIT;
    m.mode = PIN_MODE_INPUT;
    rt_device_control(device, 0, &m);

    // enable LSM int
    rt_pin_mode(LSM6DSL_INT_GPIO_BIT, PIN_MODE_INPUT);
    rt_pin_attach_irq(m.pin, PIN_IRQ_MODE_RISING, lsm6dsl_int1_handle, (void *)(rt_uint32_t)m.pin);
    rt_pin_irq_enable(m.pin, 1);

    return 0;
}

RT-Threadセンサーデバイスを登録する

新しいデバイス用のディレクトリにsensor_xxx.cを追加します。このファイルでは、RT-Threadのセンサーフレームワーク経由でデバイスを公開し、アプリケーションコードがバス固有のレジスター関数を直接呼び出さずにセンサーを使えるようにします。

登録処理は次の2つで構成されます。

  1. 1つ以上のrt_sensor_deviceインスタンスを割り当て、rt_hw_sensor_registerで登録する。
  2. データ読み出し、動作モード制御、スリープ/復帰処理のsensor_opsコールバックを用意する。

SDKガイドのBMP280の例では、温度と気圧のチャンネルを別々に登録し、それぞれの単位と範囲を設定したうえで、共通設定をコピーし、コールバックテーブルを共有しています。

int rt_hw_bmp280_init(const char *name, struct rt_sensor_config *cfg)
{
    rt_int8_t result;
    rt_sensor_t sensor_temp = RT_NULL, sensor_baro = RT_NULL;

    result = _bmp280_init();
    if (result != RT_EOK)
    {
        LOG_E("bmp280 init err code: %d", result);
        goto __exit;
    }

    /* temperature sensor register */
    {
        sensor_temp = rt_calloc(1, sizeof(struct rt_sensor_device));
        if (sensor_temp == RT_NULL)
            goto __exit;

        sensor_temp->info.type       = RT_SENSOR_CLASS_TEMP;
        sensor_temp->info.vendor     = RT_SENSOR_VENDOR_BOSCH;
        sensor_temp->info.model      = "bmp280_temp";
        sensor_temp->info.unit       = RT_SENSOR_UNIT_DCELSIUS;
        sensor_temp->info.intf_type  = RT_SENSOR_INTF_I2C;
        sensor_temp->info.range_max  = 85;
        sensor_temp->info.range_min  = -40;
        sensor_temp->info.period_min = 5;

        rt_memcpy(&sensor_temp->config, cfg, sizeof(struct rt_sensor_config));
        sensor_temp->ops = &sensor_ops;

        result = rt_hw_sensor_register(sensor_temp, name, RT_DEVICE_FLAG_RDWR, RT_NULL);
        if (result != RT_EOK)
        {
            LOG_E("device register err code: %d", result);
            goto __exit;
        }
    }

    /* barometer sensor register */
    {
        sensor_baro = rt_calloc(1, sizeof(struct rt_sensor_device));
        if (sensor_baro == RT_NULL)
            goto __exit;

        sensor_baro->info.type       = RT_SENSOR_CLASS_BARO;
        sensor_baro->info.vendor     = RT_SENSOR_VENDOR_BOSCH;
        sensor_baro->info.model      = "bmp280_bora";
        sensor_baro->info.unit       = RT_SENSOR_UNIT_PA;
        sensor_baro->info.intf_type  = RT_SENSOR_INTF_I2C;
        sensor_baro->info.range_max  = 110000;
        sensor_baro->info.range_min  = 30000;
        sensor_baro->info.period_min = 5;

        rt_memcpy(&sensor_baro->config, cfg, sizeof(struct rt_sensor_config));
        sensor_baro->ops = &sensor_ops;

        result = rt_hw_sensor_register(sensor_baro, name, RT_DEVICE_FLAG_RDWR, RT_NULL);
        if (result != RT_EOK)
        {
            LOG_E("device register err code: %d", result);
            goto __exit;
        }
    }

    LOG_I("sensor init success");
    return RT_EOK;

__exit:
    if (sensor_temp)
        rt_free(sensor_temp);
    if (sensor_baro)
        rt_free(sensor_baro);
    if (bmp_dev)
        rt_free(bmp_dev);
    return -RT_ERROR;
}

type、vendor、model、unit、intf_type、有効範囲、最小周期には、デバイスのデータシートに記載された値を設定します。メモリー確保またはいずれかの登録が失敗した場合も、必要なリソースをすべて解放できるようにしてください。

**ソース例のクリーンアップに関する注意:**温度デバイスの登録に成功した後、気圧デバイスのメモリー確保または登録に失敗すると、例のクリーンアップ処理は温度デバイスを登録解除せずに解放します。製品コードでは、登録に成功したデバイスを記録し、対象RT-Threadバージョンがサポートする削除APIで登録解除してから、その領域を解放してください。この失敗時の処理をそのまま流用しないでください。

FinSHからドライバーを検証する

ドライバーをイメージに組み込んだら、コマンドライン関数を追加し、FINSH_FUNCTION_EXPORT_ALIASで登録します。検証コマンドには、次の確認項目を含めると有効です。

  • センサーの初期化とデバイスアドレスの検出。
  • I2CまたはSPI経由でのレジスター読み書き。
  • 気圧、温度、加速度など、センサー固有の機能の測定結果。

次のBMP280テストコマンドは、デバイスのオープン/クローズ、レジスター読み出し、温度・気圧・高度の取得、I2Cバス速度の設定例です。

#define DRV_BMP280_TEST

#ifdef DRV_BMP280_TEST
#include <string.h>

int cmd_bmpt(int argc, char *argv[])
{
    int32_t temp, pres, alti;
    if (argc < 2)
    {
        LOG_I("Invalid parameter!\n");
        return 1;
    }
    if (strcmp(argv[1], "-open") == 0)
    {
        uint8_t res = BMP280_Init();
        if (BMP280_RET_OK == res)
        {
            BMP280_open();
            LOG_I("Open bmp280 success\n");
        }
        else
            LOG_I("open bmp280 fail\n");
    }
    if (strcmp(argv[1], "-close") == 0)
    {
        BMP280_close();
        LOG_I("BMP280 closed\n");
    }
    if (strcmp(argv[1], "-r") == 0)
    {
        uint8_t rega = atoi(argv[2]) & 0xff;
        uint8_t value;
        BMP280_ReadReg(rega, 1, &value);
        LOG_I("Reg 0x%x value 0x%x\n", rega, value);
    }
    if (strcmp(argv[1], "-tpa") == 0)
    {
        temp = 0;
        pres = 0;
        alti = 0;
        BMP280_CalTemperatureAndPressureAndAltitude(&temp, &pres, &alti);
        LOG_I("Get temperature = %.1f\n", (float)temp / 10);
        LOG_I("Get pressure= %.2f\n", (float)pres / 100);
        LOG_I("Get altitude= %.2f\n", (float)alti / 100);
    }
    if (strcmp(argv[1], "-bps") == 0)
    {
        struct rt_i2c_configuration cfg;
        int bps = atoi(argv[2]);
        cfg.addr = 0;
        cfg.max_hz = bps;
        cfg.mode = 0;
        cfg.timeout = 5000;
        rt_i2c_configure(i2cbus, &cfg);
        LOG_I("Config BMP I2C speed to %d\n", bps);
    }

    return 0;
}
FINSH_FUNCTION_EXPORT_ALIAS(cmd_bmpt, __cmd_bmpt, Test driver bmp280);

#endif //DRV_BMP280_TEST

読み出し値を有効と判断する前に、デバイスアドレスを確認し、既知のIDレジスターまたはキャリブレーションレジスターを読み出して、実測値が想定範囲にあることを確かめます。このコマンド例は基本的な立ち上げ確認用であり、そのまま製品用CLIとして使えるものではありません。製品に組み込む前に、各サブコマンドの引数の個数と値の範囲を検証し、デバイス/API呼び出しの戻り値を確認したうえで、境界チェックを追加してください。

公式ソース

最終更新: