BL652(nRF) Max30102



Max30102、姉妹品のMax30105の情報としてはSparkFunからArduinoのライブラリーが公開されていており、利用されている事も多いのか情報量が最も多い。

このライブラリー何をしているか?というのをマニュアルを参照しながら、nRF、Embedded studio開発下で動くように移植してやってnRFのライブラリーを作る事をめざす。nRFをターゲットとするが、同じ事を他の例えば、STM32系とかで実施するればライブラリーを作っていく事はできるはず。要するに、”理解”さえしていれば。ここではBL652だが、純正のDKやXAIOも同じ事をすれば動くはず。

【下準備】

スタートとして、前回作ったテンプレートにさらにApp_timerを使えるようにしてあるテンプレートから開始する。(DLはここ)ssdのドライバーを所定のフォルダに入れてやる必要があるが詳細は、前回を参照。

【I2C】

基本テンプレートに入っているのでTWIが使えるように設定をする。
ヘッダーファイル類はそのままでもよいし使うものだけ以外はコメントアウトする。

/* ****************/
/* TWI
/* ****************/
/* TWI instance ID. */
#if TWI0_ENABLED
#define TWI_INSTANCE_ID 0
#elif TWI1_ENABLED
#define TWI_INSTANCE_ID 1
#endif

/* TWI instance. */
const nrf_drv_twi_t m_twi = NRF_DRV_TWI_INSTANCE(0); //do not change instance name "m_twi" which is used ssd1306.c

void twi_init(void)
{
const nrf_drv_twi_config_t twi_config = {
.scl = ARDUINO_SCL_PIN,
.sda = ARDUINO_SDA_PIN,
.frequency = NRF_DRV_TWI_FREQ_400K,
.interrupt_priority = APP_IRQ_PRIORITY_HIGH,
.clear_bus_init = false
};

nrf_drv_twi_init(&m_twi, &twi_config, NULL, NULL);
nrf_drv_twi_enable(&m_twi);
}

それ以外はコメントアウトするか、削除でOK。

メインの中も思い切って削除し、

int main(void)
{
//Initialization of TWI
twi_init();
  while (true)
    {
        // Do nothing.
    }
}
と一旦簡素化する。

【レジスタへの基本的な読み書き】

前回すでにやっているレジスタへの読み書きの関数を用意してやる。

mainの直上に記述する

uint8_t MAX30102_read_register (uint8_t reg_address)
{
uint8_t data;
ret_code_t err_code;

err_code = nrf_drv_twi_tx(&m_twi, 0x57, &reg_address, sizeof(reg_address), true);
APP_ERROR_CHECK(err_code);

err_code = nrf_drv_twi_rx(&m_twi, 0x57, &data, sizeof(data));
APP_ERROR_CHECK(err_code);
return data;
}

uint8_t MAX30102_write_register (uint8_t reg_address, uint8_t data)
{
ret_code_t err_code;
uint8_t buffer[2] = {reg_address, data};
err_code = nrf_drv_twi_tx(&m_twi, 0x57, buffer, sizeof(buffer), true);
APP_ERROR_CHECK(err_code);
}

【基本的初期設定】

mainの中でwhileの外に

//Reset
MAX30102_write_register(0x09,0x47);
//FIFO Configuration
MAX30102_write_register(0x08,0x5F);
//Mode Configuration
MAX30102_write_register(0x09,0x03);

//SpO2 Configuration
MAX30102_write_register(0x0A,0x00);

//LED current
MAX30102_write_register(0x0C,0x1F);
MAX30102_write_register(0x0D,0x1F);

//Multi-LED Mode Control Registers 
MAX30102_write_register(0x11,0x21);
MAX30102_write_register(0x12,0x03);
//FIFO clear
MAX30102_write_register(0x04,0x00);
MAX30102_write_register(0x05,0x00);
MAX30102_write_register(0x06,0x00);

と詳細なセッティング内容はデータシートを見てもらうとして設定をレジスタに書き込む。最低限として上記だけでも指定する。

【FIFOの理解】


続いて、同じくmainの中でwhileの外に
    uint8_t readPointer;
    uint8_t writePointer;
と変数を二つ用意してやる。

whileの中に、
readPointer=MAX30102_read_register (0x06);
writePointer=MAX30102_read_register (0x04);

NRF_LOG_INFO("readPointer=%d",readPointer);
NRF_LOG_FLUSH();
NRF_LOG_INFO("writePointer=%d",writePointer);
NRF_LOG_FLUSH();
nrf_delay_ms(200);

 と記載する。
レジスタ
0x06と0x04の値を読みにいき、これは、センサーの値を次々に書き込みをしている何番目に書き込みしたか、何番目が読まれたか?というのを表示し、200msec間隔でそれらを繰り返し実施する事になる。

これを実行し、シリアルモニタで見てやると、


writePointerの値は勝手におおな数字になっていき、一方で、readPointerはずっとゼロのままになる。
これは、センサが読みっとった値を次々とFIFOの箱に順番に書き込む場所を変えながら格納していっている事を意味している。32サンプル分のデータを格納できるが、一杯になるとゼロ番目からに戻る。
一方で、命令を送って読み込みをしていないのでreadPointerがゼロのままとなっているのである。

ではデータを一つだけ読み込んでみる。


whileの外側で
    uint8_t temp_array[6];
    uint8_t MAX30105_FIFODATA =0x07;
を宣言しておく。読み取るのはRとIRの2つのデータ、1つあたり3バイトかかるので2つでバイト分を格納できるように配列の箱を6つ用意する。
また、データが格納されているFIFO Data Register0x07を定義しておく。

データを読み込むには、読み込み先のレジスタ0x07を指定して、その後読み取る個数を指定して読み込む。

nrf_drv_twi_tx(&m_twi, 0x57, &MAX30105_FIFODATA, sizeof(MAX30105_FIFODATA), true);
nrf_drv_twi_rx(&m_twi, 0x57, temp_array, sizeof(temp_array));
となる。0x57の部分はI2Cのアドレス。
これをwhileの中でdelayの前に追記してやる。

readPointer=MAX30102_read_register (0x06);
writePointer=MAX30102_read_register (0x04);

NRF_LOG_INFO("readPointer=%d",readPointer);
NRF_LOG_FLUSH();
NRF_LOG_INFO("writePointer=%d",writePointer);
NRF_LOG_FLUSH();

nrf_drv_twi_tx(&m_twi, 0x57, &MAX30105_FIFODATA, sizeof(MAX30105_FIFODATA), true);
nrf_drv_twi_rx(&m_twi, 0x57, temp_array, sizeof(temp_array));

nrf_delay_ms(200);



whileが一周回るたびに読み込みを一度する事になるので、readPointerの数字が1つ大きくなっていくことが確認できる。(データシートのP14に記載している事の確認ができたという事)

実際は、データを最後に格納した位置と、読み込みが済んだ位置の差がそのタイミングで準備できていて読み込みが終わっていない個数なので、読み込むべき個数を

numberOfSamples = writePointer - readPointer;

として定義する。この時、宣言を int numberOfSamples;でしておく必要がある。uintはunsignで負の値を取り扱いできない。writePointer が一巡して小さい数字の場合が起こり得るので、その場合を加味して負の値を表現できるint型。読み込む個数が負というのはあり得ないので循環を加味して、

      numberOfSamples = writePointer - readPointer;
      if (numberOfSamples < 0) numberOfSamples += 32
負なら32を足す処理をする。
このnumberOfSamplesがゼロでない場合は読むべきデータがあるという事を意味するのでその分読み込みをする処理をすると

 if (numberOfSamples>0){
        for (int i=0;i<numberOfSamples;i++){
          nrf_drv_twi_tx(&m_twi, 0x57, &MAX30105_FIFODATA, sizeof(MAX30105_FIFODATA), true);
          nrf_drv_twi_rx(&m_twi, 0x57, temp_array, sizeof(temp_array));
        }//for
      }//if

となる。

ただこれは、BYTEを格納しているだけで18ビットで1つの数字になるので処理をしてやる必要がある。

whileの上の部分で使う変数を3つ定義する。

 if (numberOfSamples>0){
        for (int i=0;i<numberOfSamples;i++){
          nrf_drv_twi_tx(&m_twi, 0x57, &MAX30105_FIFODATA, sizeof(MAX30105_FIFODATA), true);
          nrf_drv_twi_rx(&m_twi, 0x57, temp_array, sizeof(temp_array));

         temp_32=0;
          temp_32|=temp_array[0];
          temp_32<<=8;
          temp_32|=temp_array[1];
          temp_32<<=8;
          temp_32|=temp_array[2];
          temp_32_Red=temp_32;
          temp_32_Red&=0x0003FFFF;

          temp_32=0;
          temp_32|=temp_array[3];
          temp_32<<=8;
          temp_32|=temp_array[4];
          temp_32<<=8;
          temp_32|=temp_array[5];
          temp_32_IR=temp_32;

          temp_32_IR&=0x0003FFFF;
      
          NRF_LOG_INFO("Red:%d,IR:%d",temp_32_Red,temp_32_IR);
          NRF_LOG_FLUSH();
        }//for
      }//if

やっている事は、論理和をとって下位ビットから順に格納していき、ビットシフトする。
上位ビット                                                                            下位ビット
上位ビットにあたるtemp_array[0]をtemp_32と論理和をとる。単にコピーされる。
その後左に8ビットずらし、下位ビット8個があらたに0が埋まっている状態になる。
そこにtemp_array[1]を下位ビットと論理和、そしてシフト、最後も同じでtemp_array[2]を下位ビット8ビットに論理和をとる。
すると32ビットで表現された一つの数字のできあがり。

NRF_LOG_INFOでこれまで表示していたログをコメントアウトしてあげてNRF_LOG_INFO("Red:%d,IR:%d",temp_32_Red,temp_32_IR);
だけにしておく、ArduinoIDEのシリアルプロッタで表示すると心拍らしい波形がえられるはず。
わかりにくい場合は、IRだけ表示するなど片方にすると図を拡大してくれるのでわかりやすい。

したの図のように最初数百ぐらいの信号だったのが指をおいていくと上に這いついてしまう場合は、LEDの電流の量を調整し飽和しないような値にする。


//LED current
MAX30102_write_register(0x0C,0x1F);
MAX30102_write_register(0x0D,0x1F);
としているのを
//LED current
MAX30102_write_register(0x0C,0x1A);
MAX30102_write_register(0x0D,0x1A);
とかに変更してやると測定できる。

ここまで理解していれば、あとは初期設定をデータシートを読んで適当に設定してやればよいだけ。
この記事を読んでから、SparkFunからArduinoのライブラリーを読めばやっている事は理解できるかと。nRFなどのマイコンと違ってI2Cで読める個数に制限があったりとちょっとテクニックを使っている部分はあるけど。

以下ソースコード

/**
 * Copyright (c) 2009 - 2021, Nordic Semiconductor ASA
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form, except as embedded into a Nordic
 *    Semiconductor ASA integrated circuit in a product or a software update for
 *    such product, must reproduce the above copyright notice, this list of
 *    conditions and the following disclaimer in the documentation and/or other
 *    materials provided with the distribution.
 *
 * 3. Neither the name of Nordic Semiconductor ASA nor the names of its
 *    contributors may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 *
 * 4. This software, with or without modification, must only be used with a
 *    Nordic Semiconductor ASA integrated circuit.
 *
 * 5. Any software provided in binary form under this license must not be reverse
 *    engineered, decompiled, modified and/or disassembled.
 *
 * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */
/** @file
* @brief Example template project.
* @defgroup nrf_templates_example Example Template
*
*/

#include <stdbool.h>
#include <stdint.h>

#include "nrf.h"
#include "nordic_common.h"
#include "boards.h"

// for using nrf_log
#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"

// for using nrf_delay
#include "nrf_delay.h"

// for using I2C
#include "nrf_drv_twi.h"


/* ****************/
/*  TWI
/* ****************/
/* TWI instance ID. */
#if TWI0_ENABLED
#define TWI_INSTANCE_ID     0
#elif TWI1_ENABLED
#define TWI_INSTANCE_ID     1
#endif
/* TWI instance. */
const nrf_drv_twi_t m_twi = NRF_DRV_TWI_INSTANCE(0); //do not change instance name "m_twi" which is used ssd1306.c

void twi_init(void)
{
    const nrf_drv_twi_config_t twi_config = {
       .scl                = ARDUINO_SCL_PIN,
       .sda                = ARDUINO_SDA_PIN,
       .frequency          = NRF_DRV_TWI_FREQ_400K,
       .interrupt_priority = APP_IRQ_PRIORITY_HIGH,
       .clear_bus_init     = false
    };
   nrf_drv_twi_init(&m_twi, &twi_config, NULL, NULL);
   nrf_drv_twi_enable(&m_twi);
}



uint8_t MAX30102_read_register (uint8_t reg_address)
{
uint8_t data;
ret_code_t err_code;

err_code = nrf_drv_twi_tx(&m_twi, 0x57, &reg_address, sizeof(reg_address), true);
APP_ERROR_CHECK(err_code);

err_code = nrf_drv_twi_rx(&m_twi, 0x57, &data, sizeof(data));
APP_ERROR_CHECK(err_code);
return data;
}

uint8_t MAX30102_write_register (uint8_t reg_address, uint8_t data)
{
ret_code_t err_code;
uint8_t buffer[2] = {reg_address, data};
err_code = nrf_drv_twi_tx(&m_twi, 0x57, buffer, sizeof(buffer), true);
APP_ERROR_CHECK(err_code);
}

/**
 * @brief Function for application main entry.
 */
int main(void)
{
//Initialization of TWI
twi_init();

//Reset
MAX30102_write_register(0x09,0x47);
//FIFO Configuration
MAX30102_write_register(0x08,0x5F);
//Mode Configuration
MAX30102_write_register(0x09,0x03);

//SpO2 Configuration
MAX30102_write_register(0x0A,0x00);

//LED current
MAX30102_write_register(0x0C,0x1F);
MAX30102_write_register(0x0D,0x1F);

//Multi-LED Mode Control Registers 
MAX30102_write_register(0x11,0x21);
MAX30102_write_register(0x12,0x03);
//FIFO clear
MAX30102_write_register(0x04,0x00);
MAX30102_write_register(0x05,0x00);
MAX30102_write_register(0x06,0x00);


    //Initialization of NRF_LOG
    APP_ERROR_CHECK(NRF_LOG_INIT(NULL));
    NRF_LOG_DEFAULT_BACKENDS_INIT();

    uint8_t readPointer;
    uint8_t writePointer;
    int numberOfSamples;
    uint8_t temp_array[6];
    uint8_t MAX30105_FIFODATA =0x07;

    uint32_t temp_32=0;
    uint32_t temp_32_Red=0;
    uint32_t temp_32_IR=0;

    
    while (true)
    {
        readPointer=MAX30102_read_register (0x06);
        writePointer=MAX30102_read_register (0x04);

      //NRF_LOG_INFO("readPointer=%d",readPointer);
      //NRF_LOG_FLUSH();
      //NRF_LOG_INFO("writePointer=%d",writePointer);
      //NRF_LOG_FLUSH();

      numberOfSamples = writePointer - readPointer;
      if (numberOfSamples < 0) numberOfSamples += 32; 

      //NRF_LOG_INFO("numberOfSamples=%d",numberOfSamples);
      //NRF_LOG_FLUSH();

      if (numberOfSamples>0){
        for (int i=0;i<numberOfSamples;i++){
          nrf_drv_twi_tx(&m_twi, 0x57, &MAX30105_FIFODATA, sizeof(MAX30105_FIFODATA), true);
          nrf_drv_twi_rx(&m_twi, 0x57, temp_array, sizeof(temp_array));

          temp_32=0;
          temp_32|=temp_array[0];
          temp_32<<=8;
          temp_32|=temp_array[1];
          temp_32<<=8;
          temp_32|=temp_array[2];
          temp_32_Red=temp_32;
          temp_32_Red&=0x0003FFFF;

          temp_32=0;
          temp_32|=temp_array[3];
          temp_32<<=8;
          temp_32|=temp_array[4];
          temp_32<<=8;
          temp_32|=temp_array[5];
          temp_32_IR=temp_32;

          temp_32_IR&=0x0003FFFF;
      NRF_LOG_INFO("Red:%d",temp_32_Red);
     // NRF_LOG_INFO("Red:%d,IR:%d",temp_32_Red,temp_32_IR);
      NRF_LOG_FLUSH();
        }//for
      }//if


      nrf_delay_ms(250);

    }
}


/** @} */








コメント

このブログの人気の投稿

Attiny85とAQM0802A(LCD)のI2C接続

Attiny85 FuseRest

HS101 STM32の自作お手軽オシロスコープ