「咳カウンター」システムを作ってみた（機能紹介と記録モジュール編）

## １．はじめに
喘息持ちで主治医から咳や咳払いの状況を毎日記録するように言われているので、
趣味と実益を兼ねて「咳カウンター」とでも言うシステムを作ってみました。
ここでは自分の製作記録として２つの記事に分けて紹介します。
![咳カウンター 装着イメージ2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1773386/06d3c3ef-7bcd-9856-986d-1a71014d91ff.jpeg)



## ２．システム全体の構成と機能概要

システム全体は喉元に貼り付ける加速度センサ部、携帯型の記録モジュール部、そして
ＰＣ側の処理とで構成されています。

記録モジュールは身に着けて使うのでできるだけ薄く小さくする為にミンティアケースに
入れる事を目標に作りました。それを100円ショップのIDカード入れに入れて首から
ぶら下げて、喉元に貼り付けた加速度センサ部からのZ軸加速度データ（喉に垂直方向）
をSDカードへ記録します。

ＰＣ側では測定した加速度データをグラフ化するツールと、咳を認識して時間帯毎の
ヒストグラムを作成するツールをPythonにて作成しました。咳や咳払いと歩行などの
外乱振動との識別に少々苦労しましたが、自分ひとりのデータですが、咳や咳払いを
正しく認識する確率はおよそ93％、外乱振動を咳や咳払いとして誤認識する確率は
およそ2％程度の精度にチューニングすることができました。咳や咳払いを見逃すより、
外乱を誤認識することを出来るだけ抑えたかったのでこのぐらいで良いかなと思っています。
![咳カウンター システム構成.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1773386/0e5da8d8-fe97-8bca-e52a-5677e8bb4561.jpeg)



## ３．記録モジュール

### ３－１．ハード構成

加速度センサ部はLIS3DH搭載の３軸加速度センサモジュールを使用し、記録モジュール部
との接続にはUSBケーブル（シールド付き）を使用しました。センサ部は喉元に貼り付けて
使うので防湿のために全体をレジンで固めました。

記録モジュールは、Seeeduino XIAO、microSDカードモジュール、リチウムイオン電池、
充電モジュールをミンティアケースに詰め込みました。

今回の製作で使用した部品の情報は以下の通りです。

１）３軸加速度センサモジュール
　　[LIS3DH搭載 三軸加速度センサモジュール（スイッチサイエンス）](https://www.switch-science.com/catalog/3115/)
　　[LIS3DH データシート](https://cdn.sparkfun.com/assets/learn_tutorials/5/9/6/LIS3DH_Datasheet_DocID_17530rev1.pdf)
　　＊喉元に貼り付けて使うので防湿のためにセンサ部全体をレジンで固めました。

２）Seeeduino XIAO（Arduino互換マイクロコントローラ）
　　[Seeeduino XIAO（スイッチサイエンス）](https://www.switch-science.com/catalog/6335/)
　　[Seeeduino XIAO データシート](https://wiki.seeedstudio.com/jp/Seeeduino-XIAO/)

３）SDカードリーダーライタ―
　　[Aideepen マイクロSDストレージ拡張ボードマイクロSDカードメモリシールドモジュール](https://www.amazon.co.jp/gp/product/B07MB9TS13/ref=ppx_yo_dt_b_asin_title_o03_s00?ie=UTF8&psc=1)
　　＊PSI接続できるものであれば他の物でも代用できます。

４）充電モジュール
　　[小型リチウムイオン電池充電器 USB Type -Cコネクタ搭載（スイッチサイエンス）](https://www.switch-science.com/catalog/6006/)
　　＊ミンティアケースに入れるために電池接続側コネクタを外して使いました。

５）リチウムポリマー電池
　　[502030-250mAhリチウムポリマー電池](https://www.amazon.co.jp/gp/product/B07XPM9Q26/ref=ppx_yo_dt_b_asin_title_o06_s00?ie=UTF8&psc=1)
　　＊ケースに収まるものであれば他の物でも代用できます。
　　　ただし、20時間以上計測するには250mAh程度以上は必要です。

回路図（といっても各モジュールを繋いだだけですが、、、）
![記録モジュール 回路図.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1773386/6be0fc49-8ce4-cb33-83f7-8370ae14e192.jpeg)

記録モジュール部実配線例（ミンティアケースに入れた例です）
![実配線図例.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1773386/b3393445-02eb-6a4b-d432-ed153da1ffb6.jpeg)

### ３－２．ソフトウェア
* 開発環境：Arduino IDE
　（Arduino IDEのインストールとSeeeduinoのボードマネージャの追加方法は → [**ここ**](https://wiki.seeedstudio.com/jp/Seeeduino-XIAO/#seeeduino-xiao) ）

* ソフトウェア処理の概要
・電源SWがON後、LEDが10秒間「明」→３回「暗・明」→「暗」で計測が開始されます
　 → 計測開始の日時（例えば2021年7月15日の10時12分14秒）を別途メモしておきます
　　 （後で咳の認識やグラフ表示をするデータのファイル名の一部として使用します）
・計測開始後は加速度センサ(LIS3DH)のデータを読込んでSDカードへ保存し続けます
・加速度センサのサンプリング周波数は50Hz、12bit精度、LIS3DHのFIFOに16サンプル
　以上溜まったらデータをバッファメモリへ読込みます
・バッファのセンサデータが128サンプルを超えたらバイナリデータ（2byte/サンプル）
　としてACL_ZXXX.SDAファイルへ追記保存します
・センサデータ保存の32回毎に累積サンプル数とプログラム起動時からの経過時間を
　ACL_ZXXX.TXTファイルへ追記保存します（後の処理でサンプリング周波数のばらつき
　を補正する為に使用します・・・不要かもしれませんが、、、）
　また、この時のファイルへのデータ書き込み中だけLEDを「明」としており、この
　LEDの点灯（約82秒毎）で正常に動作していることの確認ができます。
・上記のACL_ZXXXの「XXX」の部分はSDカード内のNEXT_SDA.TXTファイル中に記されて
　いる値となります。（詳細はソフトウェアスケッチ中のコメントに記載）
・加速度センサやSDカードへのアクセスエラーが発生した場合は、正しく計測されて
　いないので、その場合はLEDが連続点滅（3回の「明・暗」と1回の「暗・暗」）して
　知らせます
・電源SWのOFFまたはSDカードを抜くことで計測を終了します

* ソフトウェアスケッチ
できるだけコメントを入れて解り易くしたつもりですが、まだ経験が浅いのでプログラムの
記述作法やアルゴリズムに不充分なところがあると思います。ご容赦ください。


```cpp
/*
 * Cough_data_logger.ino
 * 
 *  概要
 *  ・加速度センサのデータを読み込んでSDカードへ保存する
 *  ・使用する加速度センサはLIS3DH
 *      サンプリング周波数：50Hz、センサ値：12bit精度、
 *      LIS3DHのFIFOに16サンプル以上溜まったらデータを読み込む
 *  ・センサデータが128サンプルを超えたらバイナリデータ（2byte/サンプル）として.SDAファイルへ保存する
 *  ・センサデータ保存の32回毎に累積サンプル数とプログラム起動時からの経過時間を.TXTファイルへ保存する
 *    （後の処理でサンプリング周波数のばらつきを補正する為に使用する）
 *  ・加速度センサやSDカードへのアクセスでエラーが発生した場合は、正しく計測されていません
 *  　その場合は、LEDが連続点滅（3回の「明・暗」と1回の「暗・暗」）して知らせます
 *  ・電源SWのON後、LED点滅（10秒間「明」→ 3回の「暗・明」→ 「暗」）のタイミングが計測開始となる
 *    → 計測開始の日時（2021年7月15日の10時12分14秒ならば「2021-07-15 10-12-14」）を別途メモしておく
 *    → 後で咳の認識やグラフ表示の為に使用します
 *  ・電源SWのOFFまたはSDカードを抜くことで計測が終了となる
 * 
 *  ・使用ファイル
 *      NEXT_SDA.TXT：次に計測するときの.SDAデータと.TXTデータの保存ファイル名を保持
 *                    計測する際には事前にSDカードにこのファイルを作成しておく必要あり
 *          データ形式　ACL_Z000.XXX   ← 初期状態では最低限この行は必須
 *                    ACL_Z001.XXX   ← 計測する毎に行が増えていく
 *                          :
 *                          :
 *                    
 *      ACL_Z***.SDA（センサデータ本体、SDA = Sensor Data Aruduinoの頭文字）
 *          データ形式　2byteの整数データ列（[下位byte][上位byte]の連続）
 *          
 *      ACL_Z***.TXT（サンプルNo.と経過時間（msec）の対応リスト、およそ4096サンプル毎に記録）
 *          データ形式　S_No = 0 Timer(msec) = 13058      ← 最初の行は測定開始時の時間を表す
 *                    S_No = 4096 Timer(msec) = 92695   ← 4096番目のサンプルの時間は92695msec
 *                      :     :       :           :
 *                      :     :       :           :
 * 
 */


#include <Wire.h>
#include <SPI.h> 
#include <SD.h>


// 定数の定義（ここから）**************************************************
#define DEBUG 0  // デバッグ用シリアルプリント出力のオン/オフ 0:OFF 1:ON

#define LED_ON_TIME 100  // ダイアグ表示時のLEDの「明」時間（msec）
#define LED_OFF_TIME 900  // ダイアグ表示時のLEDの「暗」時間（msec）

#define ACC_Z_BUF_SIZE 128  // Acc_z用バッファサイズの最小値（int型128個 = 256bytes）

// LIS3DH の設定（ここから）**********
#define ACL_SEN_ADRS          0x18  // 加速度センサのI2Cアドレスの設定
#define CTRL_REG1             0x20  // CTRL_REG1のレジスタアドレス設定
#define CTRL_REG1_DEF_VALUE   0x07  // CTRL_REG1のデフォルト値
#define CTRL_REG1_VALUE       0x47  // CTRL_REG1の設定値（50Hz、XYZ軸全て有効）
#define CTRL_REG4             0x23  // CTRL_REG4のレジスタアドレス設定
#define CTRL_REG4_VALUE       0x08  // CTRL_REG4の設定値（+/- 2g、高解像度有効）
#define CTRL_REG5             0x24  // CTRL_REG5のレジスタアドレス設定
#define CTRL_REG5_FIFO_EN     0x40  // CTRL_REG5の設定値（FIFOを有効にする）
#define CTRL_REG5_FIFO_DEN    0x00  // CTRL_REG5の設定値（FIFOを無効にする）
#define FIFO_CTRL_REG         0x2E  // FIFO_CTRL_REGのレジスタアドレス設定
#define FIFO_CTRL_REG_BYPASS  0x0F  // FIFO_CTRL_REGの設定値（Bypass mode、ウォーターマークレベル ＝ 15）
#define FIFO_CTRL_REG_FIFO    0x4F  // FIFO_CTRL_REGの設定値（FIFO mode、ウォーターマークレベル ＝ 15）
#define FIFO_SRC_REG          0x2F  // FIFO_SRC_REGのレジスタアドレス設定
#define WTM                   0x80  // FIFO_SRC_REG内のWTM bit の位置（bit[7]）
#define FSS                   0x1F  // FIFO_SRC_REG内のFSS bit の位置（bit[4:0]）
#define OUT_X_L               0x28  // X軸加速度データ下位8bit読込みレジスタアドレス
#define OUT_X_L_B_READ        0xA8  // X軸加速度データBURST読込みレジスタアドレス
#define OUT_Y_L               0x2A  // Y軸加速度データ下位8bit読込みレジスタアドレス
#define OUT_Y_L_B_READ        0xAA  // Y軸加速度データBURST読込みレジスタアドレス
#define OUT_Z_L               0x2C  // Z軸加速度データ下位8bit読込みレジスタアドレス
#define OUT_Z_L_B_READ        0xAC  // Z軸加速度データBURST読込みレジスタアドレス
#define OUT_Z_H               0x2D  // Z軸加速度データ上位8bit読込みレジスタアドレス
// ********** LIS3DH の設定（ここまで）
// ************************************************** 定数の定義（ここまで）


// データ収集用外部変数定義（ここから） ****************************************
const int Psi_chipSelect = 7; // PSI(SDリーダーライターとのインターフェース)のチップセレクト。Seeduino XIAO の場合
byte Status_reg, Acl_sen_zl, Acl_sen_zh;
char Next_f_name[15] = "ACL_Z000.XXX";  // 次に作成する計測値データ保存ファイル名
char F_no[5] = "000";  // 計測値データ保存ファイル名の番号部の生成用バッファ
char F_name[15] = "XXXXXXXX.XXX";  // 計測値データを保存するファイル名
int16_t Acl_z[ACC_Z_BUF_SIZE + 32];  // Z軸加速度データ読込み用バッファ
                                     // +32は他の処理で読込み遅れが生じた時に備え、FIFOバッファ分の余裕を見込むため
// **************************************** データ収集用外部変数定義（ここまで）

// ダイアグ用外部変数（ここから） ****************************************
byte Err_code = 0; // ダイアグ用エラーコード格納メモリ確保と0で初期化、0：エラー無し、1：エラー有り
byte Err_code_pattern[4][2] = {{1,0},{1,0},{1,0},{0,0}};  // ダイアグのエラー発生時のLED出力パターン
byte Tflag;  // LED出力区間切替りタイミングイベント発生フラグ（1:発生、0:途中）
byte Led_out_period = 0x00;  // 現在の出力区間　bit3:区間の有無(0:無区間 1:区間有無区間とはプログラム起動直後を意味する)、
                             // bit0:区間(1:100msec区間 0:900msec区間）
int16_t Led_out_count;  // エラーコードパターンの現在の出力位置
uint32_t Led_on_off_timing;  // LED出力区間切換えタイマー用時間情報
// **************************************** ダイアグ用外部変数（ここまで）


/* diag_led_out()：ダイアグLED出力処理
 *  処理概要
 *    エラー発生時のLED出力は(100msec+900msec)を周期とし、
 *      100msec区間に"明"を900msec区間に"暗"を設定したパターンを3回、
 *      100msecと900msecの両方の区間に"暗"を設定したパターンを1回出力する。
 *    Tflag=1の場合（LED出力区間切換えタイミングイベントが発生）に以下の処理をする。
 *      何らかのエラーが発生している場合　
 *        次が100msec区間の時　→　Err_code_pattern[Led_out_count][0]が示す位置の値に応じてLED出力開始する
 *        次が900msec区間の時　→　Err_code_pattern[Led_out_count][1]が示す位置の値に応じてLED出力開始する
 *        Led_out_countは0～3の間でカウントアップ
 *    最後にTflagをクリアする
 *  
 *  使用する外部変数
 *    Tflag (R/W)          ：LED出力区間切換えタイミングイベント発生フラグ（1:発生、0:途中）　
 *                           diag_timing()で 1 にセットし、ここで 0 にする
 *    Err_code (R)         ：ダイアグ用エラーコード格納メモリ確保と0で初期化、0：エラー無し、1：エラー有り
 *    Err_code_pattern (R) ：ダイアグのエラー発生時のLED出力パターン 1:明 0:暗
 *    Led_out_count (R/W)  ：エラーコードパターンの現在の出力位置
 *    Led_out_period (R)   ：現在の出力区間　
 *                            bit3:区間の有無(0:無区間 1:区間有、無区間とはプログラム起動直後を意味する)、
 *                            bit0:区間(0:900msec区間 1:100msec区間）
 *  
 *  使用する内部変数
 *    無し
 */
void diag_led_out() {
  
  if (Err_code == 0) {  // 出力すべきエラーが無い時
    Tflag = 0;
    return;
  }
  
  // 出力すべきエラーがある時
  if (Led_out_period == 0x11) { // 次が100msec区間の時
    digitalWrite(LED_BUILTIN,Err_code_pattern[Led_out_count][0] ^ 0x01);  // LED出力が 0:明 1:暗 なので符号を反転する
  }
  else {  // 次が900msec区間の時
    digitalWrite(LED_BUILTIN,Err_code_pattern[Led_out_count][1] ^ 0x01);  // LED出力が 0:明 1:暗 なので符号を反転する
    Led_out_count++;
    if ( Led_out_count == 4 ){
      Led_out_count = 0;
    }
  }
  Tflag = 0;  // Tflagクリア
}


/* diag_timing()：ダイアグLED出力区間切換えタイミングイベント発生の判定と、次に出す区間を指定する
 *  処理概要
 *    エラー発生時のLED出力パターンの切換えタイミングを監視し、切換えタイミングになったら以下の処理をする。
 *      ・次のLED出力区間の設定（100msec OR 900msec）
 *      ・次の切換えタイミングの時刻の設定
 *      ・LED出力区間切換えタイミングイベント発生のフラグの設定
 *  
 *  使用する外部変数
 *    Led_on_off_timing(R/W)  ：次にLEDの出力を切換える時刻
 *    Tflag(R/W)         ：LED出力区間切換えタイミングイベント発生フラグ（1:発生、0:途中）　
 *                        ここで 1 にセットし、diag_led_out()で 0 にする
 *    Led_out_period(R/W)：現在の出力区間
 *                          bit3:区間の有無(0:無区間 1:区間有、無区間とはプログラム起動直後を意味する) 
 *                          bit0:区間(1:100msec区間 0:900msec区間）
 *  使用する内部変数
 *    無し
 *  
 *  戻り値
 *    Tflag
 *      0：切替りタイミングではない
 *      1：切替りタイミング発生
 */
byte diag_timing() {
  
  if (Led_out_period == 0x00) {  // プログラム起動直後（現在の出力が無区間）の時　→　スタート処理
    Tflag = 0;
    Led_on_off_timing = millis() + LED_OFF_TIME;
    Led_out_period = 0x10;
  }
  else if (Led_out_period == 0x10) {  // 現在が900msec区間の時 → 100msec区間への切替え判定と処理
    if (Led_on_off_timing <= millis()) {
      Tflag = 1;
      Led_on_off_timing += LED_ON_TIME;
      Led_out_period = 0x11;  // 100msec区間
    }
  }
  else if (Led_out_period == 0x11) {  // 現在が100msec区間の時 → 900msec区間への切替え判定と処理
    if (Led_on_off_timing <= millis()) {
      Tflag = 1;
      Led_on_off_timing += LED_OFF_TIME;
      Led_out_period = 0x10;  // 900msec区間
    }
  }
  return(Tflag);
}


/* i2c_write_byte()：LIS3DHのレジスタへ１Byteデータを書き込む
 *  処理概要
 *    以下の処理を順次実行する
 *      ・Wire.beginTransmission(i2c_adrs)：指定アドレスのスレーブへI2C通信の送信処理開始
 *      ・Wire.write(s_reg)：レジスタアドレスのキューイング
 *      ・Wire.write(s_data)：レジスタへセットするデータのキューイング
 *      ・Wire.endTransmission(false)：キューイングしたデータの送信を実行する
 *    各処理でエラーが発生したら、そのコードを戻り値として返す
 *  
 *  使用する引数
 *    i2c_adrs(R)   ：LIS3DHのスレーブアドレス
 *    s_reg(R)      ：出力先のレジスタアドレス　
 *    s_data(R)     ：出力データ
 *  
 *  使用する内部変数
 *    i2c_ret(R/W)  ：Wireライブラリ実行時の戻り値
 *    err_code(R/W) ：エラー発生個所を示すコード、この値がこの関数の戻り値となる
 *  
 *  戻り値
 *    err_code
 *      0      ：エラー無し
 *      0以外   ：エラー有り
 */
byte i2c_write_byte(uint8_t i2c_adrs, uint8_t s_reg, uint8_t s_data){
  byte i2c_ret, err_code;
  
  err_code = 0;
  Wire.beginTransmission(i2c_adrs);  // 指定アドレスのスレーブへI2C通信の送信処理開始。
  while( Wire.write(s_reg) != 1 ){  // レジスタアドレスのキューイング。
    err_code = err_code | 0x01;
  }
  while( Wire.write(s_data) != 1 ){  // レジスタへセットするデータのキューイング。
    err_code = err_code | 0x02;
  }
  i2c_ret = 5;
  while ( i2c_ret != 0){
    i2c_ret = Wire.endTransmission(false);  // キューイングしたデータの送信を実行する。
    if(i2c_ret != 0 ){
      err_code = err_code | 0x04;
    }
  }
  return (err_code);
}


/* i2c_read_bytes()：LIS3DHのレジスタから指定Byteのデータを読み込む
 *  処理概要
 *    以下の処理を順次実行する
 *      ・Wire.beginTransmission(i2c_adrs)：指定アドレスのスレーブへI2C通信の送信処理開始
 *      ・Wire.write(s_reg)：レジスタアドレスのキューイング
 *      ・Wire.endTransmission(false)：キューイングしたデータの送信を実行する
 *      ・Wire.requestFrom(i2c_adrs, s_bytes, false)：i2cのレジスタからs_bytesバイトのデータ読み出しを宣言
 *      ・Wire.read()：指定Byte数のデータを読み込む
 *    各処理でエラーが発生したら、そのコードを戻り値として返す
 *  
 *  使用する引数
 *    i2c_adrs(R)   ：LIS3DHのスレーブアドレス
 *    s_reg(R)      ：読込み先のレジスタアドレス　
 *    *s_data(W)    ：読み込んだデータを格納する配列へのポインタ
 *    s_bytes(R)    ：読み込むデータのByte数
 *    
 *  使用する主な内部変数
 *    i2c_ret(R/W)  ：Wireライブラリ実行時の戻り値
 *    err_code(R/W) ：エラー発生個所を示すコード、この値がこの関数の戻り値となる
 *  
 *  戻り値
 *    err_code
 *      0      ：エラー無し
 *      0以外   ：エラー有り
 */
byte i2c_read_bytes(uint8_t i2c_adrs, uint8_t s_reg, uint8_t *s_data, uint8_t s_bytes){
  byte i2c_ret, err_code;
  int i, j;
  
  err_code = 0;
  Wire.beginTransmission(i2c_adrs);  // I2C通信の開始。スレーブ側のアドレスの定義する。
  while( Wire.write(s_reg) != 1 ){  // レジスタアドレスのキューイング。
    err_code = err_code | 0x01;
  }
  i2c_ret = 5;
  while ( i2c_ret != 0){
    i2c_ret = Wire.endTransmission(false);  // キューイングしたデータの送信を実行する。
    if(i2c_ret != 0 ){
      err_code = err_code | 0x02;
    }
  }
  i2c_ret = 0xff;
  while ( i2c_ret != s_bytes){
    i2c_ret = Wire.requestFrom(i2c_adrs, s_bytes, false);  // 加速度センサからs_bytesバイトのデータ読み出しを宣言。
    if(i2c_ret != s_bytes ){
      err_code = err_code | 0x04;
    }
  }
  i = 0;
  j = s_bytes;
  while ( j != 0 ){
    while (Wire.available() != j );
    s_data[i]  = Wire.read();  // 加速度データの読み出し。
    i++;
    j--;
  }
  return (err_code);
}


/* read_fifo_status()：LIS3DHのFIFO STATUSレジスタのデータを読み込む
 *  処理概要
 *    以下の処理を順次実行する
 *      ・i2c_read_bytes()関数を実行する
 *      ・エラーが発生したらErr_codeに１をセット
 *    fifo_statusを戻り値として返す
 *  
 *  使用する引数
 *    無し
 *  
 *  使用する外部変数
 *    Err_code (R)         ：ダイアグ用エラーコード格納メモリ確保と0で初期化、0：エラー無し、1：エラー有り
 *  
 *  使用する内部変数
 *    i2c_ret(R/W)      ：Wireライブラリ実行時の戻り値
 *    fifo_status(R/W)  ：FIFO STATUSレジスタから読み込んだ値、この値がこの関数の戻り値となる
 *  
 *  戻り値
 *    fifo_status
 */
byte read_fifo_status() {
  byte fifo_status, i2c_ret;
  
  // FIFO_SRC_REGを読み込む
  i2c_ret = i2c_read_bytes(ACL_SEN_ADRS, FIFO_SRC_REG, &fifo_status, 1);
  if (i2c_ret != 0) {
    Err_code = 1; // エラー発生
  }
  return ( fifo_status );
}


/* read_fifo_data()：LIS3DHのFIFOからデータを読み込む
 *  処理概要
 *    以下の処理を順次実行する
 *      ・i2c_read_bytes()関数を実行する
 *      ・エラーが発生したらErr_codeに１をセット
 *    fifo_statusを戻り値として返す
 *  
 *  使用する引数
 *    *fifo_data(W) ：FIFOからの読込みデータを格納するメモリへのポインタ
 *    s_bytes(R)    ：FIFOから読み込むデータ数（Byte数）
 *  
 *  使用する外部変数
 *    Err_code (R)         ：ダイアグ用エラーコード格納メモリ確保と0で初期化、0：エラー無し、1：エラー有り
 *  
 *  使用する内部変数
 *    i2c_ret(R/W)      ：Wireライブラリ実行時の戻り値
 *  
 *  戻り値
 *    無し
 */
void read_fifo_data(uint8_t *fifo_data, uint8_t s_bytes) {
  byte i2c_ret;
  
  // FIFOから指定バイト数のデータを読み込む
  i2c_ret = i2c_read_bytes(ACL_SEN_ADRS, OUT_X_L_B_READ, fifo_data, s_bytes);
  if (i2c_ret != 0) {
    Err_code = 1; // エラー発生
  }
}


/* setup()：セットアップ処理
 *  処理概要
 *    ビルトインLEDのアクティブ化と首都力を「暗」にセット → 加速度計測処理の開始タイミング表示とエラー発生時のダイアグ表示用
 *    PSI通信のCSピン位置の設定
 *    I2C通信のバスマスターとして初期化
 *    LIS3DHの各種レジスタの設定
 *      サンプリング周波数：50Hｚ、XYZ軸全て有効、測定レンジ：±2G、高解像度有効
 *    SD card 有りの判定とライブラリの初期化
 *    NEXT_SDA.TXTから測定データを保存するファイル名を読み込み、次に保存するファイル名も一番下の行に追加する
 *      (ファイル名はACL_Z000.XXXからACL_Z999.XXXまで１ずつ増加し、ACL_Z999.XXXの次はACL_Z000.XXXに戻る)
 *    計測開始時刻確認のタイミングの為のLED点滅（10秒「明」の後、3回の「暗・明」）
 *    加速度センサデータ読み込みとファイルへの保存関数（acl_z_record()）の起動
 *  
 *  使用する外部変数
 *    Psi_chipSelect (R) ：PSI(SDリーダーライターとのインターフェース)のチップセレクト
 *    Err_code (R)       ：ダイアグ用エラーコード格納メモリ確保と0で初期化、0：エラー無し、1：エラー有り
 *    F_name[] (R/W)     ：計測値データを保存するファイル名
 *    F_no[] (R/W)       ：計測値データ保存ファイル名の番号部の生成用バッファ
 *    Next_f_name[] (R/W)：次に作成する計測値データ保存ファイル名
 *    
 *  使用する主な内部変数
 *    i2c_ret (R/W)     ：Wireライブラリ実行時の戻り値
 *    tmp_char (R/W)    ：NEXT_SDA.TXTからデータを読み出す為のバッファ
 *  
 *  戻り値
 *    無し
 */
void setup() {
  byte i2c_ret;  // Wireライブラリ実行時の戻り値
  char tmp_char;  // NEXT_SDA.TXTからデータを読み出す為のバッファ
  int i, f_no_i;
  
  pinMode(LED_BUILTIN, OUTPUT);  // ビルトインLEDをアクティブ化
  digitalWrite(LED_BUILTIN,1);  // ビルトインLEDを「暗」にセット
  
  pinMode(Psi_chipSelect,OUTPUT);  // PSI通信のCSピンの位置を設定する
  Wire.begin();  // I2C通信のバスマスターとして初期化
  
  // LIS3DHのCTRL_REG1の設定。（50Hz、XYZ軸全て有効）
  i2c_ret = i2c_write_byte(ACL_SEN_ADRS, CTRL_REG1, CTRL_REG1_VALUE);
  if (i2c_ret != 0) {
    Err_code = 1; // エラー発生
  }
    
  // LIS3DHのCTRL_REG4の設定。（+/- 2g、高解像度有効）
  i2c_ret = i2c_write_byte(ACL_SEN_ADRS, CTRL_REG4, CTRL_REG4_VALUE);
  if (i2c_ret != 0) {
    Err_code = 1; // エラー発生
  }
  
  /* ----- SD card 有りの判定とライブラリの初期化------ */
  #if DEBUG == 1
    Serial.print("Initializing SD card...");
  #endif
   //see if the card is present and can be initialized:
  if (!SD.begin(Psi_chipSelect)) {
    Err_code = 1; // エラー発生
  }
  
  // NEXT_SDA.TXTから測定データを保存するファイル名（一番下の行）を読み込む
  File dataFile = SD.open("NEXT_SDA.TXT", FILE_WRITE);
  if( dataFile != false ) {
    while(!dataFile.seek(0));  // WRITEモードでオープンしているので、読み込み位置を先頭に戻す
    i = 0;
    while( true ){  // NEXT_SDA.TXT内の最後の行を読み込む（そこまでの行は読み飛ばす）
      tmp_char = char(dataFile.read());
      if( tmp_char == 0xFF ){
        break;
      }
      else {
        F_name[i] = tmp_char;
        i++;
        if( i == 14 ){
          i = 0;
        }
      }
    }
  }
  else {
    Err_code = 1; // エラー発生
  }
  if( F_name[12] != 0x0d ){ // NEXT_SDA.TXTの最終行にリターンが入っていない時は追加する
    dataFile.println();
  }
  
  // 次回の計測の時に使うデータファイルの名前を生成して一番下の行へ追加する
  F_no[0] = F_name[5];
  F_no[1] = F_name[6];
  F_no[2] = F_name[7];
  f_no_i = atoi(F_no);  // 文字列を数字に変換
  f_no_i++;
  if( 1000 <= f_no_i ){
    f_no_i = 0;
  }
  sprintf(F_no, "%03d", f_no_i);  // 数字を3桁の文字列に変換（'0'で埋める）
  Next_f_name[5] = F_no[0];
  Next_f_name[6] = F_no[1];
  Next_f_name[7] = F_no[2];
  dataFile.println(Next_f_name);
  dataFile.close();
  
  // 計測開始時刻確認のタイミングの為のLED点滅（10秒「明」の後、3回の「暗・明」）
  digitalWrite(LED_BUILTIN,0);  // ビルトインLEDを「明」にセット
  delay(10000); // 10秒間「明」
  for( i = 0; i < 3; i++ ){
    digitalWrite(LED_BUILTIN,1);  // ビルトインLEDを「暗」にセット
    delay(300); // 0.3秒間「暗」
    digitalWrite(LED_BUILTIN,0);  // ビルトインLEDを「明」にセット
    delay(700); // 0.7秒間「明」    
  }
  digitalWrite(LED_BUILTIN,1);  // ビルトインLEDを「暗」にセット
  
  acl_z_record();
  
}


/* acl_z_record()：Z軸加速度データの読込みとSDカードへの保存
 *  処理概要
 *    加速度データ用ファイル名(.SDA)、時間計測用ファイル名(.TXT)を作成する
 *    LIS3DHの各種レジスタの設定
 *      FIFOの有効化、BYPASS modeの起動（FIFOバッファのクリアする）、FIFO modeの起動
 *    FIFOからZ軸加速度データを読み込み、Z軸加速度データバッファ（Acl_z[]）へ格納する
 *    Acl_z[]がACC_Z_BUF_SIZEで指定するサイズを超えたら.SDAファイルへバイナリモードで追記する
 *      （書込みサイズは書込み平均時間が小さい256Bytesを狙う。約2.56秒毎に書込み）
 *    Acl_z[]がACC_Z_BUF_SIZEで指定するサイズを超えていない間は、ダイアグ表示処理を行う
 *    .SDAファイルへの書込みの32回毎に累積サンプル数とプログラム起動時からの経過時間を.TXTファイルへ保存する
 *    .TXTファイルに保存している間のみLEDを「明」とする
 *      （約82秒毎に光ります。これにより、プログラムが正常動作している事を確認できます。）
 *  
 *  使用する外部変数
 *    F_name[] (R/W)    ：計測値データを保存するファイル名
 *    Err_code (R)      ：ダイアグ用エラーコード格納メモリ確保と0で初期化、0：エラー無し、1：エラー有り
 *    Acl_z[] (R/W)     ：Z軸加速度データ読込み用バッファ
 *    
 *  使用する主な内部変数
 *    f_name_sda[] (R/W)    ：Z軸加速度データを保存するファイル名
 *    f_name_time (R/W)     ：累積サンプル数とその時の時刻のデータを保存するファイル名
 *    fifo_count (R/W)      ：FIFOに溜まっているデータ数
 *    fifo_status (R/W)     ：FIFO_STATUS_REGの状態
 *    fifo_data[] (R/W)     ：FIFOから読み出したデータのバッファ
 *    i2c_retWire (R/W)     ：Wireライブラリ実行時の戻り値
 *    tmp_acl_z (R/W)       ：Z軸加速度データ読込み用一時バッファ
 *    s_read_count (R/W)    ：Acl_z[]バッファへ読込んだデータ数
 *    s_read_count_sum (R/W)：Acl_z[]バッファへ読込んだデータ数の累積値
 *    timer_millis (R/W)    ：プログラム起動時からの経過時間（msec）の一時保管
 *    f_write_count (R/W)   ：.SDAファイルへの書込み回数
 *  
 *  戻り値
 *    無し
 */
void acl_z_record() {
  char f_name_sda[] = "XXXXXXXX.SDA";  // .SDAファイルのオープン用のファイル名を保持する
  char f_name_time[] = "XXXXXXXX.TXT";  // 時間計測用データファイル（.TXT）のオープン用のファイル名を保持する
  //boolean tflag = false;
  byte fifo_count, fifo_status, fifo_data[192], i2c_ret;
  int16_t i, j, s_read_count, tmp_acl_z;
  uint32_t s_read_count_sum = 0, timer_millis, f_write_count = 0;
  
  /* F_nameの内、8bytes（.拡張子より前の部分）をf_name_sda、f_name_timeへコピー。*/
  for ( i=0; i < 8; i++ ){
    f_name_sda[i] = F_name[i];  // 加速度データ用ファイル名(.SDA)
    f_name_time[i] = F_name[i];  // 時間計測用ファイル名(.TXT)
  }
  
  // LIS3DHのCTRL_REG5の設定。（FIFOの有効化）
  i2c_ret = i2c_write_byte(ACL_SEN_ADRS, CTRL_REG5, CTRL_REG5_FIFO_EN);
  if (i2c_ret != 0) {
    Err_code = 1; // エラー発生
  }
  // LIS3DHのFIFO_CTRL_REGの設定。（BYPASS modeの起動、これによりFIFOバッファをクリアする）
  i2c_ret = i2c_write_byte(ACL_SEN_ADRS, FIFO_CTRL_REG, FIFO_CTRL_REG_BYPASS);
  if (i2c_ret != 0) {
    Err_code = 1; // エラー発生
  }
  // LIS3DHのFIFO_CTRL_REGの設定。（FIFO modeの起動） 
  i2c_ret = i2c_write_byte(ACL_SEN_ADRS, FIFO_CTRL_REG, FIFO_CTRL_REG_FIFO);
  if (i2c_ret != 0) {
    Err_code = 1; // エラー発生
  }
  
  timer_millis = millis();
  
  // ACC_Z_BUF_SIZE(=128Bytes)サンプルの加速度データの読み込み（ここから）********************
  while( true ) {
    i = 0;
    while( i < ACC_Z_BUF_SIZE ) {// ACC_Z_BUF_SIZE(=128Bytes)サンプルの加速度データの読み込み
      fifo_status = read_fifo_status();  // FIFO_SRC_REGの読込み
      fifo_count = fifo_status & FSS;
      if( (fifo_status & WTM) != 0 ) {  // FIFO_WTMビットが「1」の時（WTMオーバーフローあり）
        read_fifo_data(fifo_data, (uint8_t)(fifo_count * 6));
        for(j = 0; j < fifo_count; j++ ){
          tmp_acl_z = (int16_t)fifo_data[j*6+5];    // Z軸加速度の上位バイトの読込み（1Byte変数→2Byte変数へ変換）
          tmp_acl_z = tmp_acl_z << 8;    // 8Bit左シフト
          tmp_acl_z = tmp_acl_z | (int16_t)fifo_data[j*6+4];    // Z軸加速度の下位バイトを読込み、上位バイトと結合
          Acl_z[i] = tmp_acl_z >> 4;    // 4Bit右シフト（センサー値を12Bitで扱います）
          i++;
        }
      }
      else {  // FIFOからの読込みタイミング以外の時
        if (diag_timing() == 1) {  // ダイアグ出力切換えタイミングの場合
          diag_led_out();  // ダイアグ出力処理
        }
      }
    }
    // ******************** ACC_Z_BUF_SIZE(=128Bytes)サンプルの加速度データの読み込み（ここまで）
    
    s_read_count = i;
    // s_read_count分の加速度データをSDカードに書き込む
    File dataFile = SD.open(f_name_sda, FILE_WRITE);  // .SDAファイルをオープン
    if( dataFile == false ){
      Err_code = 1;
    }
    dataFile.write((byte*)Acl_z, (s_read_count * 2));
    dataFile.close();
    
    // (s_read_count x 32)回分のセンサ読込み時の時刻データをSDに書き込む（ここから）**********
    // （サンプリング周波数が50Hzならば設計上は約81.92秒毎）
    f_write_count++;
    s_read_count_sum += s_read_count;
    if( f_write_count == 1 ){  // 計測開始時の時刻を書き込む
      dataFile = SD.open(f_name_time, FILE_WRITE);  // .TXTファイルをオープン
      if( dataFile == false ){
        Err_code = 1;
      }
      dataFile.print("S_No = 0");
      dataFile.print(" Timer(msec) = ");
      dataFile.println(timer_millis);
      dataFile.close();
    }
    if( f_write_count % 32 == 0 ){  // (s_read_count x 32)回毎に時刻を書き込む
      timer_millis = millis();
      digitalWrite(LED_BUILTIN,0);  // 書込み開始タイミングLEDを「明」にセット
      dataFile = SD.open(f_name_time, FILE_WRITE);
      if( dataFile == false ){
        Err_code = 1;
      }
      dataFile.print("S_No = ");
      dataFile.print(s_read_count_sum);
      dataFile.print(" Timer(msec) = ");
      dataFile.println(timer_millis);
      dataFile.close();
      digitalWrite(LED_BUILTIN,1);  // 書込み開始タイミングLEDを「暗」にセット
      // ********** (s_read_count x 32)回分のセンサ読込み時の時刻データをSDに書き込む（ここまで）

      // SDカードへのアクセス可否確認（ここから）**********
      //（FILE_WRITEでSD.open実行時にエラーが検出できない場合があるため、ここで確認を追加する）
      dataFile = SD.open(f_name_time, FILE_READ);
      if( dataFile == false ){
        Err_code = 1;
      }
      else{
        if( char(dataFile.read()) == 0xFF ){
          Err_code = 1;
        }
      }
      dataFile.close();
      // ********** SDカードへのアクセス可否確認（ここまで）
    }
    
  }
  
}
  

void loop() {
  // 空ループ
}
```


---

以上が「咳カウンター」システムの「機能紹介と記録モジュール編」です。
次回の「[**ＰＣ側ソフトウェア編**](https://qiita.com/sho2-ykym/items/e2c61b80c8db73264562)」では加速度データをグラフ化するツールと、
咳を認識して時間帯毎のヒストグラムを作成するツールをPythonにて作成した
記事を紹介します。

---