KOZOS上で超音波センサのデータを周期的にPCへ送信してみた (実装編)

# 1. 今回のやりたいこと
「周期的に超音波センサのデータを取得し、bluetoothを使ってPCにそのデータを送信する」ということをしたいです。

今回のしたいことにおいて、設計編と実装編に分けて記載します。今回は、実装編になります。
設計編については、下記を参照ください。
[KOZOS上で超音波センサのデータを周期的にPCへ送信してみた (設計編)](https://qiita.com/hcuymitoh/items/5bea7f9bed80ff842ea2)

環境と前提は設計編と変わりませんので割愛します。

# 2. 距離測定システムの実装
距離測定システムの機能を改めて下記に示します。
各機能の詳細については、設計編を参照ください。

* システム制御機能
* 超音波センサ制御機能
* BlueTooth送信機能
    * 接続状態管理機能
    * データ送信機能
* コンソール機能

全機能の実装を記載すると長くなりますので、超音波センサ制御機能とBlueTooth送信機能の実装のみ記載します。

### 2.1 超音波センサ制御機能の実装

```c: 超音波センサ制御機能
// マクロ定義
#define US_SENSOR_NUM  (1U)
#define BUF_SIZE       (1U)
#define SPEED_OF_SOUND (34U)   // 34cm/ms
#define US_TSK_PERIOD  (1000U) // 1000ms

// 状態
enum State_Type {
	ST_UNINITIALIZED = 0U,
	ST_INITIALIZED,
	ST_RUN,
	ST_STOP,
	ST_UNDEIFNED,
	ST_MAX,
};
// イベント
enum EVENT_Type {
	EVENT_INIT = 0U,
	EVENT_RUN,
	EVENT_STOP,
	EVENT_MAX,
};
// 関数ポインタ
typedef int (*FUNC)(void);

// 状態遷移用定義
typedef struct {
	FUNC func;
	uint8_t nxt_state;
}FSM;

// 制御用ブロック
typedef struct {
	kz_thread_id_t tsk_id;      // メインタスクのID
	kz_msgbox_id_t msg_id;      // メッセージID
	uint8_t state;              // 状態
	US_CALLBACK callback;       // コールバック
	uint8_t data[BUF_SIZE];     // データ格納用バッファ(未使用)
} US_SENSOR_CTL;
US_SENSOR_CTL us_sensor_ctl[US_SENSOR_NUM];

// プロトタイプ宣言
static void us_init(void);
static void us_get_data_start(void);
static void us_get_data_stop(void);
static void us_get_data(void);
static uint8_t read_distance(void);

// 状態遷移テーブル
static const FSM fsm[ST_MAX][EVENT_MAX] = {
		// EVENT_INIT               EVENT_RUN                    EVENT_STOP                   EVENT_MAX
		{{us_init, ST_INITIALIZED}, {NULL, ST_UNDEIFNED},        {NULL, ST_UNDEIFNED},        {NULL, ST_UNDEIFNED},}, // ST_UNINITIALIZED
		{{NULL, ST_UNDEIFNED},      {us_get_data_start, ST_RUN}, {NULL, ST_UNDEIFNED},        {NULL, ST_UNDEIFNED},}, // ST_INITIALIZED
		{{NULL, ST_UNDEIFNED},      {us_get_data, ST_RUN},       {us_get_data_stop, ST_STOP}, {NULL, ST_UNDEIFNED},}, // ST_RUN
		{{NULL, ST_UNDEIFNED},      {us_get_data_start, ST_RUN}, {NULL, ST_UNDEIFNED},        {NULL, ST_UNDEIFNED},}, // ST_STOP
};

// メインのタスク
// 状態遷移テーブルに従い、処理を実行し、状態を更新する
int US_main(int argc, char *argv[])
{
	US_SENSOR_CTL *this = &us_sensor_ctl[0];
	uint8_t cur_state = ST_UNINITIALIZED;
	uint8_t nxt_state = cur_state;
	US_MSG *msg;
	uint32_t size;
	FUNC func;

	// 自タスクのIDを制御ブロックに設定
	this->tsk_id = kz_getid();
	// メッセージIDを制御ブロックに設定
	this->msg_id = MSGBOX_ID_US_MAIN;

	while(1){
		// メッセージ受信
		kz_recv(this->msg_id, &size, &msg);

		// 処理/次状態を取得
		func = fsm[cur_state][msg->msg_type].func;
		nxt_state = fsm[cur_state][msg->msg_type].nxt_state;

		// msgを解放
		kz_kmfree(msg);

		// 現状態を更新
		this->state = nxt_state;

		// 処理を実行
		if (func != NULL) {
			func();
		}

		// 状態を更新
		if (nxt_state != ST_UNDEIFNED) {
			cur_state = nxt_state;
		}
	}

	return 0;
}

// 外部公開関数
// 初期化要求メッセージを送信する関数
void US_MSG_init(void)
{
	US_SENSOR_CTL *this = &us_sensor_ctl[0];
	US_MSG *msg;

	msg = kz_kmalloc(sizeof(US_MSG));

	msg->msg_type = EVENT_INIT;
	msg->msg_data = NULL;

	kz_send(this->msg_id, sizeof(US_MSG), msg);

	return;
}

// データ取得開始メッセージを送信する関数
void US_MSG_measure_start(void)
{
	US_SENSOR_CTL *this = &us_sensor_ctl[0];
	US_MSG *msg;

	msg = kz_kmalloc(sizeof(US_MSG));

	msg->msg_type = EVENT_RUN;
	msg->msg_data = NULL;

	kz_send(this->msg_id, sizeof(US_MSG), msg);

	return;
}

/* データ取得メッセージを送信する関数 */
void US_MSG_get_data(void)
{
	US_SENSOR_CTL *this = &us_sensor_ctl[0];
	US_MSG *msg;

	msg = kz_kmalloc(sizeof(US_MSG));

	msg->msg_type = EVENT_RUN;
	msg->msg_data = NULL;

	kz_send(this->msg_id, sizeof(US_MSG), msg);

	return;
}

/* データ取得停止メッセージを送信する関数 */
void US_MSG_measure_stop(void)
{
	US_SENSOR_CTL *this = &us_sensor_ctl[0];
	US_MSG *msg;

	msg = kz_kmalloc(sizeof(US_MSG));

	msg->msg_type = EVENT_STOP;
	msg->msg_data = NULL;

	kz_send(this->msg_id, sizeof(US_MSG), msg);

	return;
}

/* 超音波センサのデータ取得時にコールするコールバックを設定する関数 */
uint32_t US_set_callback(US_CALLBACK callback)
{
	US_SENSOR_CTL *this = &us_sensor_ctl[0];
	
	// パラメータチェック
	// callbackがNULLの場合、-1を返して終了
	if (callback == NULL) {
		return -1;
	}

	// コールバックがまだ登録されていない場合
	if (this->callback == NULL) {
		// コールバックを登録
		this->callback = callback;
	}
	
	return 0;
}

// 内部関数
// 初期化要求メッセージ受信時に実行する関数
static void us_init(void)
{
	// 詳細については、下記参照ください
    // https://qiita.com/hcuymitoh/items/e905dff59f34466c5f5a
}

// データ取得開始メッセージ受信時に実行する関数
static void us_get_data_start(void)
{
	US_SENSOR_CTL *this = &us_sensor_ctl[0];
	uint32_t ret;
	
	// システム制御機能に、1s周期でデータ取得メッセージを送信してもらうように要求
	ret = set_cyclic_message(US_MSG_get_data, US_TSK_PERIOD);
	
	return;
}

// データ取得メッセージ受信時に実行する関数
static void us_get_data(void)
{
	US_SENSOR_CTL *this = &us_sensor_ctl[0];
	uint8_t data = 0;
	
	// 距離を取得
	data = read_distance();
	
	// コールバックを呼ぶ
	if (this->callback != NULL) {
		this->callback(data);
	}
	
	return;
}

// 超音波センサからデータを取得する関数
static uint8_t read_distance(void)
{
	// 詳細については、下記参照ください
    // https://qiita.com/hcuymitoh/items/e905dff59f34466c5f5a
}

// データ取得停止メッセージ受信時に実行する関数
static void us_get_data_stop(void)
{
	US_SENSOR_CTL *this = &us_sensor_ctl[0];
	uint32_t ret;

	// システム制御機能に、1s周期のデータ取得メッセージの送信を停止してもらうように要求
	ret = del_cyclic_message(US_MSG_get_data);

	return;
}
```
メインのタスクであるUS_main()は、kz_recv()でメッセージが送信されるまで待機します。
このタスクにメッセージを送信するAPIをUS_MSG_*という名前で作成しています。これらのAPIは外部へ公開されているため、他アプリはこれらのAPIをコールすることでメッセージを送信することができます。US_main()はメッセージを受信すると、状態遷移テーブルに従い、状態を更新し、処理を実行します。
例として、現状態が初期化済み(ST_INITIALIZED)の時にデータ取得メッセージ(EVENT_RUN)を受信したときの動作を説明します。

データ取得メッセージを送信するAPIは下記になります。
```c: データ取得メッセージを送信する関数
/* データ取得メッセージを送信する関数 */
void US_MSG_get_data(void)
{
	US_SENSOR_CTL *this = &us_sensor_ctl[0];
	US_MSG *msg;

	msg = kz_kmalloc(sizeof(US_MSG));

	msg->msg_type = EVENT_RUN;
	msg->msg_data = NULL;

	kz_send(this->msg_id, sizeof(US_MSG), msg);

	return;
}
```
kz_kmalloc()でメッセージ用のメモリを確保し、kz_send()でUS_main()に対して、データ取得メッセージ(EVENT_RUN)を送信します。
状態遷移テーブルについては、一つの要素を{[実行する処理], [次状態]}とし、これがメッセージ*状態の数だけあります。今回の例のように、現状態が初期化済み（ST_INITIALIZED）で、データ取得メッセージ(EVENT_RUN)を受信したときはus_get_data_start()を実行します。

```c:状態遷移テーブル
// 状態遷移テーブル
static const FSM fsm[ST_MAX][EVENT_MAX] = {
		// EVENT_INIT               EVENT_RUN                    EVENT_STOP                   EVENT_MAX
		{{us_init, ST_INITIALIZED}, {NULL, ST_UNDEIFNED},        {NULL, ST_UNDEIFNED},        {NULL, ST_UNDEIFNED},}, // ST_UNINITIALIZED
		{{NULL, ST_UNDEIFNED},      {us_get_data_start, ST_RUN}, {NULL, ST_UNDEIFNED},        {NULL, ST_UNDEIFNED},}, // ST_INITIALIZED
		{{NULL, ST_UNDEIFNED},      {us_get_data, ST_RUN},       {us_get_data_stop, ST_STOP}, {NULL, ST_UNDEIFNED},}, // ST_RUN
		{{NULL, ST_UNDEIFNED},      {us_get_data_start, ST_RUN}, {NULL, ST_UNDEIFNED},        {NULL, ST_UNDEIFNED},}, // ST_STOP
};
```
### 2.2 BlueTooth送信機能の実装
基本的なつくりは、超音波センサ制御機能と同様です。
状態遷移テーブルに従い、処理を実行します。

```c: BlueTooth送信機能
// マクロ定義
#define BT_NUM   (1U)
#define BUF_SIZE (32U)

// 状態
enum State_Type {
	ST_UNINITIALIZED = 0U,
	ST_INITIALIZED,
	ST_CONNECTED,
	ST_DISCONNECTED,
	ST_UNDEIFNED,
	ST_MAX,
};

// イベント
enum EVENT_Type {
	EVENT_INIT = 0U,
	EVENT_CONNECT,
	EVENT_RUN,
	EVENT_DISCONNECT,
	EVENT_GET_STATUS,
	EVENT_MAX,
};

// 関数ポインタ
typedef int (*FUNC)(void);

// 状態遷移用定義
typedef struct {
	FUNC func;
	uint8_t nxt_state;
}FSM;

// バッファ定義
typedef struct {
	uint8_t data[BUF_SIZE];
	uint8_t rd_idx;
	uint8_t wr_idx;
}BUF;

// 制御用ブロック
typedef struct {
	BUF primary;   // データ格納用バッファ (基本このバッファを使用)
	BUF secondary; // データ格納用バッファ (未使用)
}TRANSFER_CTL;
typedef struct {
	kz_thread_id_t tsk_id;      // メインタスクのID
	kz_msgbox_id_t msg_id;      // メッセージID
	uint8_t state;              // 状態
	TRANSFER_CTL trans_ctl;     // 送信データの情報
} BT_CTL;
BT_CTL bt_ctl[BT_NUM];

// プロトタイプ宣言
static void bt_init();
static void bt_send_data();
static void bt_get_status();

// 状態遷移テーブル
static const FSM fsm[ST_MAX][EVENT_MAX] = {
		// EVENT_INIT               EVENT_CONNECT         EVENT_RUN                      EVENT_DISCONNECT         EVENT_GET_STATUS
		{{bt_init, ST_INITIALIZED}, {NULL, ST_UNDEIFNED}, {NULL, ST_UNDEIFNED},          {NULL, ST_UNDEIFNED},    {bt_get_status, ST_UNINITIALIZED}, }, // ST_UNINITIALIZED
		{{NULL, ST_UNDEIFNED},      {NULL, ST_CONNECTED}, {bt_send_data, ST_UNDEIFNED},  {NULL, ST_DISCONNECTED}, {bt_get_status, ST_INITIALIZED}, },   // ST_INITIALIZED
		{{NULL, ST_UNDEIFNED},      {NULL, ST_CONNECTED}, {bt_send_data, ST_CONNECTED},  {NULL, ST_DISCONNECTED}, {bt_get_status, ST_CONNECTED}, },     // ST_CONNECTED
		{{NULL, ST_UNDEIFNED},      {NULL, ST_CONNECTED}, {NULL, ST_UNDEIFNED},          {NULL, ST_DISCONNECTED}, {bt_get_status, ST_DISCONNECTED}, },  // ST_DISCONNECTED
};

int BT_main(int argc, char *argv[])
{
	BT_CTL *this = &bt_ctl[0];
	uint8_t cur_state = ST_UNINITIALIZED;
	uint8_t nxt_state = cur_state;
	BT_MSG *msg;
	int32_t size;
	FUNC func;

	// 制御ブロックの初期化
	memset(bt_ctl, 0, sizeof(bt_ctl));

　　// 自タスクのIDを制御ブロックに設定
	this->tsk_id = kz_getid();
　　// メッセージIDを制御ブロックに設定
	this->msg_id = MSGBOX_ID_BTMAIN;

	while(1){
		// メッセージ受信
		kz_recv(this->msg_id, &size, &msg);

		// 処理/次状態を取得
		func = fsm[cur_state][msg->msg_type].func;
		nxt_state = fsm[cur_state][msg->msg_type].nxt_state;

		// メッセージを解放
		kz_kmfree(msg);

		this->state = nxt_state;

		// 処理を実行
		if (func != NULL) {
			func();
		}

		// 状態遷移
		if (nxt_state != ST_UNDEIFNED) {
			cur_state = nxt_state;
		}
	}

	return 0;
}

// 外部公開関数
/* 初期化メッセージを送信する関数 */
void BT_MSG_init(void)
{
	BT_CTL *this = &bt_ctl[0];
	BT_MSG *msg;

	msg = kz_kmalloc(sizeof(BT_MSG));

	msg->msg_type = EVENT_INIT;
	msg->msg_data = NULL;

	kz_send(this->msg_id, sizeof(BT_MSG), msg);

	return;
}

// 接続済みメッセージを送信する関数
void BT_MSG_connect(void)
{
	BT_CTL *this = &bt_ctl[0];
	BT_MSG *msg;

	// 現状態がCONNECTEDの場合、イベントを発行しない
	if (this->state == ST_CONNECTED) {
		return;
	}

	msg = kz_kmalloc(sizeof(BT_MSG));

	msg->msg_type = EVENT_CONNECT;
	msg->msg_data = NULL;

	kz_send(this->msg_id, sizeof(BT_MSG), msg);

	return;
}

// データ送信要求メッセージを送信する関数
void BT_MSG_send_data(uint8_t *data, uint8_t size)
{
	BT_CTL *this = &bt_ctl[0];
	BT_MSG *msg;
	uint8_t i;

	msg = kz_kmalloc(sizeof(BT_MSG));

	// 内部バッファにコピー
	for (i = 0; i<size; i++) {
		this->trans_ctl.primary.data[this->trans_ctl.primary.wr_idx++] = data[i];
		this->trans_ctl.primary.wr_idx = this->trans_ctl.primary.wr_idx & (BUF_SIZE - 1);
	}

	msg->msg_type = EVENT_RUN;
	msg->msg_data = this->trans_ctl.primary.data;

	kz_send(this->msg_id, sizeof(BT_MSG), msg);

	return;
}

// 非接続メッセージを送信する関数
void BT_MSG_disconnect(void)
{
	BT_CTL *this = &bt_ctl[0];
	BT_MSG *msg;

	// 現状態がDISCONNECTEDの場合、イベントを発行しない
	if (this->state == ST_DISCONNECTED) {
		return;
	}

	msg = kz_kmalloc(sizeof(BT_MSG));

	msg->msg_type = EVENT_DISCONNECT;
	msg->msg_data = NULL;

	kz_send(this->msg_id, sizeof(BT_MSG), msg);

	return;
}

// 状態取得メッセージを送信する関数
void BT_MSG_get_status(void)
{
	BT_CTL *this = &bt_ctl[0];
	BT_MSG *msg;

	msg = kz_kmalloc(sizeof(BT_MSG));

	msg->msg_type = EVENT_GET_STATUS;
	msg->msg_data = NULL;

	kz_send(this->msg_id, sizeof(BT_MSG), msg);

	return;
}

// 内部関数
// 初期化要求メッセージ受信時に実行する関数
static void bt_init(void)
{
	// BlueToothの初期化
	bt_dev_send_use(SERIAL_BLUETOOTH_DEVICE);

	return;
}

//　データ送信要求メッセージ受信時に実行する関数
static void bt_send_data(void)
{
	BT_CTL *this = &bt_ctl[0];
	uint8_t val[3];
	char data[3];

	// 3桁目の数字を取得
	val[2] = this->trans_ctl.primary.data[this->trans_ctl.primary.rd_idx] / 100;
	// 2桁目の数字を取得
	val[1] = (this->trans_ctl.primary.data[this->trans_ctl.primary.rd_idx] - val[2]*100) / 10 ;
	// 1桁目の数字を取得
	val[0] = (this->trans_ctl.primary.data[this->trans_ctl.primary.rd_idx] - val[2]*100 - val[1]*10);

	// バッファのリードインデックスを更新
	this->trans_ctl.primary.rd_idx++;
	this->trans_ctl.primary.rd_idx = this->trans_ctl.primary.rd_idx & (BUF_SIZE - 1);

	// アスキーコードに変換
	data[0] = val[2] + 0x30;
	data[1] = val[1] + 0x30;
	data[2] = val[0] + 0x30;

	// BlueTooth経由でPCへ送信
	bt_dev_send_write(data);

	return;
}

//　状態取得メッセージ受信時に実行する関数
void bt_get_status(void)
{
	BT_CTL *this = &bt_ctl[0];

	switch(this->state){
	case ST_UNINITIALIZED:
		consdrv_send_write("ST_UNINITIALIZED\n");
		break;
	case ST_INITIALIZED:
		consdrv_send_write("ST_INITIALIZED\n");
		break;
	case ST_CONNECTED:
		consdrv_send_write("ST_CONNECTED\n");
		break;
	case ST_DISCONNECTED:
		consdrv_send_write("ST_DISCONNECTED\n");
		break;
	case ST_UNDEIFNED:
		consdrv_send_write("ST_UNDEIFNED\n");
		break;
	}

	return;
}
```

# 3. 動作の様子
BlueTooth経由でデータを受信することを確認するために、PCにBlueTooth用のターミナルをインストールする必要があります。
今回は下記をインストールしました。
https://apps.microsoft.com/store/detail/bluetooth-serial-terminal/9WZDNCRDFST8?hl=ja-jp&gl=jp

実際のボード、超音波センサ、BlueTooth通信モジュールの接続図を下記に示します。
私は、あまり配線を気にせずやってしまうため、汚くなってしまいます。大変申し訳ありません。
![実際の接続図.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2782242/af968bfd-0418-c451-ecbb-778597aa5a95.png)

* 動作時のPC画面
距離測定システム動作時のPC画面を下記に示します。
左がBlueTooth経由で受信したデータを表示するためのアプリで、右がコンソールになります。
コンソールに"command>"と表示されていますが、これはnucleo-l4r5ziからの出力で、nucleo-l4r5ziがコマンドを受け付ける状態になっていることを表しています。
　![起動時.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2782242/d06c2d04-479f-0c63-a9ee-29855a6d4301.png)
コンソールに"measure start"と打ち込みます。
blueToothアプリのほうに超音波センサのデータが表示されるようになります。
![measure start.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2782242/d446203f-6f9c-abea-d67f-9c3b5210f8eb.png)

# 4. 最後に
今回、設計編と実装編の記事を記載してみました。
本件について、何か不明点・アドバイスがあればコメントしていただけると嬉しいです。

次回は、また別の記事を記載する予定です。