nRF Connect SDK:SPIM ASYNC(非同期)

# SPIMには非同期もある
そういえばすっかり存在を忘れていましたが、SPIMには非同期処理も用意されています。nRF SDK時代はメインプログラムがシングルタスクだったので非同期でファームウェアを組むと割とメンドクサイことになってしまって使われることはあまりなかったのではないかと思いますが、nRF Connect SDK(以下NCS)はRTOSなのでむしろ非同期のほうが都合がいいんじゃないかなと勝手に思っています(笑)
# 非同期処理を実装する
SPIMのサンプルプロジェクトをベースに非同期処理を追加しようと思います。まず設定は非同期処理をするという設定が増えます。

``` prj.conf
CONFIG_SPI=y
CONFIG_SPI_ASYNC=y
CONFIG_DEBUG_THREAD_INFO=y
CONFIG_DEBUG_OPTIMIZATIONS=y
```

続いてメインのプログラムですが、spi_transferではなくspi_transfer_cbを使います。このサンプルプログラムでは単一処理にして、かつcallbackをセマフォで待つように作っているため事実上の同期処理になっています。例えば初期化関数をスレッド化すれば、初期化関数自体が非同期で処理される形にできます。

``` main.c
/*
 * Copyright (c) 2012-2014 Wind River Systems, Inc.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/spi.h>
#include "defines_ICM2064x.h"

const uint8_t INIT_CMD[] = {
    UB0_PWR_MGMT_1,
    WAIT_MSEC,
    UB0_USER_CTRL,
    UB0_INT_PIN_CFG,
    UB0_PWR_MGMT_1,
    UB0_LP_CONFIG,
    UB0_REG_BANK_SEL,
    UB2_GYRO_SMPLRT_DIV,
    UB2_GYRO_CONFIG_1,
    UB2_GYRO_CONFIG_2,
    UB2_ACCEL_SMPLRT_DIV_1,
    UB2_ACCEL_SMPLRT_DIV_2,
    UB2_ACCEL_CONFIG,
    UB2_REG_BANK_SEL,
    UB0_PWR_MGMT_1,
};
const uint8_t INIT_PARAM[] = {
    PWR_MGMT_1_DEVICE_RESET,
    0x64,
    0x00,
    INT_PIN_CFG_BYPASS_EN,
    PWR_MGMT_1_CLKSEL_AUTO,
    LP_CONFIG_ACCEL_CYCLE | LP_CONFIG_GYRO_CYCLE,
    REG_BANK_SEL_USER_BANK_2,
    0x04,
    GYRO_CONFIG_1_GYRO_DLPFCFG_0 | GYRO_CONFIG_1_GYRO_FS_SEL_FULL | GYRO_CONFIG_1_GYRO_FS_SEL_GYRO_FCHOICE,
    0x00,
    0x00,
    0x04,
    ACCEL_CONFIG_ACCEL_FS_SEL_FULL,
    REG_BANK_SEL_USER_BANK_0,
    PWR_MGMT_1_LP_EN | PWR_MGMT_1_CLKSEL_AUTO,
};

/* Callback */
K_SEM_DEFINE(k_sem, 0, 1);
static void spim_callback(const struct device *dev, int result, void *data)
{
	printk("Callback\n");
	k_sem_give(&k_sem);
}

/* write */
static int write_bytes(const struct device *spi, struct spi_config *spi_cfg, uint8_t *data, uint32_t num_bytes)
{
	int err;
	struct spi_buf bufs[] = {
		{
			.buf = data,
			.len = num_bytes
		}
	};
	struct spi_buf_set tx = {
		.buffers = bufs,
		.count = 1
	};
	err = spi_transceive_cb(spi, spi_cfg, &tx, NULL, spim_callback, NULL);

	if (err) {
		printk("Error during SPI write\n");
		return -EIO;
	}

	return 0;
}

/* read */
static int read_bytes(const struct device *spi, struct spi_config *spi_cfg, uint8_t *tx_data, uint32_t tx_num_bytes, uint8_t *rx_data, uint32_t rx_num_bytes)
{
	int err;
	struct spi_buf bufs_tx[] = {
		{
			.buf = tx_data,
			.len = tx_num_bytes
		}
	};
	struct spi_buf bufs_rx[] = {
		{
			.buf = rx_data,
			.len = rx_num_bytes
		}
	};
	struct spi_buf_set tx = {
		.buffers = bufs_tx,
        .count = 1
	};
    struct spi_buf_set rx = {
        .buffers = bufs_rx,
        .count = 1
    };

    err = spi_transceive_cb(spi, spi_cfg, &tx, &rx, spim_callback, NULL);
	if (err) {
		printk("Error during SPI read\n");
		return -EIO;
	}

	return 0;
}

void main(void)
{
	const struct device *spi;
	static const struct spi_cs_control spi_dev_cs_ctrl = {
		.gpio = GPIO_DT_SPEC_GET(DT_NODELABEL(spi0), cs_gpios),
	};
	struct spi_config spi_cfg = {
		.operation = SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | SPI_WORD_SET(8),
		.frequency = 4000000U,
    	.cs = &spi_dev_cs_ctrl,
    	.slave = 0,
	};
	int err;
	uint8_t tx_buf[16] = {0};
	uint8_t rx_buf[16] = {0};
	int16_t accel[3], gyro[3];

	spi = DEVICE_DT_GET(DT_NODELABEL(spi0));
	if (!device_is_ready(spi)) {
		printk("SPI device %s is not ready\n", spi->name);
		return;
	}

	for (int i = 0; i < sizeof(INIT_CMD); i++) {
		if (INIT_CMD[i] == WAIT_MSEC) {
			// Wait
			k_sleep(K_MSEC(INIT_PARAM[i]));
		} else {
			// Transfer
			tx_buf[0] = INIT_CMD[i];
			tx_buf[1] = INIT_PARAM[i];
			// write
			err = write_bytes(spi, &spi_cfg, tx_buf, 2);
			k_sem_take(&k_sem, K_FOREVER);
			if (err) {
				printk("Error writing to device! error code (%d)\n", err);
				return;
			}
		}
	}

	for (;;) {
		tx_buf[0] = (UB0_ACCEL_XOUT_H | 0x80) & 0xfe;
		tx_buf[1] = 0x00;
		err = read_bytes(spi, &spi_cfg, tx_buf, 2, rx_buf, 14);
		k_sem_take(&k_sem, K_FOREVER);
		if (err) {
			printk("Error reading from device! error code (%d)\n", err);
			return;
		}

		accel[0] = (rx_buf[2] << 8) | rx_buf[3];
		accel[1] = (rx_buf[4] << 8) | rx_buf[5];
		accel[2] = (rx_buf[6] << 8) | rx_buf[7];
		gyro[0] = (rx_buf[8] << 8) | rx_buf[9];
		gyro[1] = (rx_buf[10] << 8) | rx_buf[11];
		gyro[2] = (rx_buf[12] << 8) | rx_buf[13];
		printk("ax = %d, ay = %d, az = %d, gx = %d, gy = %d, gz = %d\n", accel[0], accel[1], accel[2], gyro[0], gyro[1], gyro[2]);
	}
}
```

# 非同期と同期を同居できるっぽい
実はこれは試してはいないのですが、コールする関数が分かれているのでおそらく非同期と同期で同居できるような気がします。えっ？何の役に立つんだって？
少なくとも初期化している時はコマンドを1個ずつ送らないといけないので同期処理のほうが楽です。ただし、センサーデータのリード時はそれなりに時間がかかる処理(読み出すバイト数が多いと特に)なので、その間は非同期処理で違うことをやっていたほうがCPU効率はよくなります。
# 余談(時事ネタ)
AIでノーコードプログラマーか・・・。そうだな～、ここに書いたことをAIが全部やってくれるといいなぁ(笑)