果報は寝て待て

2021年11月29日月曜日

pythonでのスレッド間通信

 またまたpythonねたです。次のような仕様のプログラムを作成中、スレッド間のデータの受け渡しが必要になりましたので、使い方の覚えとして記録しておきます。

1 webカメラで30フレーム/秒の画像を取り込み画面に表示する。

2 sirial通信でaruduinoのI/Oの値(オン、オフ)を取り込む(1回/秒)

3 それをトリガーに録画を開始、停止する。

4 累積された録画データが30分たまればハードディスクに落としていく。

 

画像処理は 1秒に30回まわります。aruduinoからの処理は1秒に1回回ります。ということでスレッド間通信が必要となりました。しかしオンオフだけでいいので今回はイベントを使います。

スレッド間のデータの受け渡しのサンプル、基本の使い方です。 

 

import threading
import time

def proc1():
    event.set()
    time.sleep(4)
    event.set()

def proc2():
    time.sleep(2)
    event.clear()
    time.sleep(5)

if __name__ == "__main__":
    event = threading.Event()
    th1 = threading.Thread(target=proc1)
    th2 = threading.Thread(target=proc2)
    th1.start()
    th2.start()

 これに1行ごとのevent内容をプリントさせたのが以下のプログラムです。

 

import threading

import time

def proc1():
    print("process1: start")
    print("proc1 event is ",event.is_set())
    event.set()
    print("proc1 event is set as ",event.is_set())

    
    time.sleep(4)
    print("proc1 event is ",event.is_set())
    event.set()
    print("proc1 event is set as ",event.is_set())

    print("process1: end")

def proc2():

    print("process2: start")

    time.sleep(2)
    print("proc2 event is ",event.is_set())
    event.clear()
    print("proc2 event is set as ",event.is_set())
 
    time.sleep(5)
    print("process2: end")

if __name__ == "__main__":
    event = threading.Event()
    th1 = threading.Thread(target=proc1)
    th2 = threading.Thread(target=proc2)
    th1.start()
    th2.start()


実行の結果

process1: start
process2: start
proc1 event is  False              # 最初は False です。        

proc1 event is set as  True      # proc1で True に変更されました
proc2 event is  True               # proc2で呼び出した時に True でした。
proc2 event is set as  False     # proc2で False に変更されました
proc1 event is  False              # proc1で再度呼び出した時に False でした。
proc1 event is set as  True     # proc1で True に変更されました
process1: end
process2: end

 

proc1とproc2の間でeventの値をきちんと引き継いでいます。このeventを録画スタート、ストップに使ってプログラムを作ります。

 

 


2021年10月17日日曜日

インタープランの無線モジュールを使ってみた。その2

 パトライトの無線化に気を良くして、次々と無線化していきます。

次は切削水のタンクです。ライン担当者はライン内の旋盤に切削水を補給する場合、遠いラインで50mくらい歩いてきて、集中タンクについているポンプスイッチを押します。1度スイッチを押すと5分くらいポンプが作動するように設定してます。その間にラインに戻ってコックを操作して切削水を補給します。

それをライン近くのリモコンのスイッチを押せば 、遠く離れた集中タンクのポンプが回りだすという、リモコンの見本のような使い方をします。リモコンは2台配置します。

上がリモコンのケースです。前回はアルミケースを使用したので尻尾をケースから出す必要がありました。今回は収納して大丈夫と思います。
上の左がリモコン側、右が受信側です。

左がリモコン側、右が受信機で集中タンクに取り付けます

簡単に無線化できる。またその時の状況に応じて増やせるというのはほんと、便利ですね。


2021年10月15日金曜日

旋盤のチャックの爪の芯ズレを調べる方法

 旋盤で、ワークを加工する時、爪で掴みます。爪は金属製、鬼爪(インサート)と言って繰り返しの使用時に摩耗しにくい材料がワークに直接当たるように作ってあります。

旋盤をぐるぐると回したときに中心とワークを掴む爪の中心が一致していればよいのですが、そのためには爪を取り付けするたびに削らないといけません。まして鬼爪を装着してる爪は削れません。

ではどうするのかと言いますと、旋盤に常時固定の親爪と、鬼爪が装着されてる子爪との間にシム(薄っぺらい金属片)をはさんで爪の中心をずらして、旋盤の軸芯(中心)に近づけてやります。

今回、どの爪にどれだけのシムをかますべきなのかの計算方法を説明します。

1, 芯出しリングを使用する方法

爪1、爪2、爪3の三本の爪が旋盤についていてこれらの中心にワークを置いて、爪はそれぞれ中心向きの力でワークを支えます。(外径クランプ)


 

上図のように芯出しリングをクランプさせます。芯出しリングとは、外径の中心と内径の中心が一致している真円状のリングです。厳密に言えば存在しませんが、ほぼそんなイメージです。


芯出しリングの内径にピックインジケータやダイヤルゲージをあてて、主軸(チャック)をぐるりんと手で回します。

その時、旋盤の軸芯と爪の中心が一致していればダイヤルゲージの振れは0のままです。

では上図のように爪の中心が旋盤の軸芯から距離aだけ爪1の方向へずれていたらどうでしょうか?

その場合ダイヤルゲージの最も遠い位置が爪1の方向になり、ズレの距離は

a = (最も近い位置のゲージ読み -最も近い位置のゲージ読み)÷2

となります。例えば最も近い位置のゲージ読みが +0.3mmで遠い方が-0.1mmだと a=0.2mmとなります。ここまではイメージしやすいと思います。

 

2、 芯出しリングを使わない方法

 芯出しリングの使用はある程度正確にでるのですが、使えない場合が結構あります。

 1 適切なサイズのリングを持ち合わせていない

 2 あるけど、爪の跡やさびでがたがた

 3 ロケーション(リングを旋盤方向へ押し当てるための台)が爪より外にある

こういう時の方法の一つにワークの内径を切削させる方法があります。

 1 4つ爪使用時の方法

   比較的簡単です。下図のようなワークとします。


下図のようにクランプします。
内径を切削した所下図のようになりました。

距離bだけ爪1のほうへワークが寄っているのです。
この状態で(ワークを一度もアンクランプしない)ダイヤルゲージを当てて主軸を手で回すと振れは理論上0ですね。


次にワークをアンクランプし、180度回転させて再度クランプします。黒の点は旋盤の軸芯、緑の点は爪の中心、赤い点はワークの内径の中心です。

上図のようにダイヤルゲージを当てて回すと、最小値(最も遠い所)の方向に爪の中心が寄っていることがわかります。また寄っている距離bは

b = (min - max)÷4

例えばダイヤルゲージの最大値が0.5mmで最小値がー0.1mmだとすると  b = (0.5 + 0.1)/4 = 0.15 となり、爪の中心は爪1の方向へ0.15mm寄っていることがわかります。

その場合爪1側に0.15x2 = 0.3mmのシムをかませます。 なぜ2倍の厚みのシムなのかは別の機会があれば説明します。

ここまではイメージしやすいと思います。

  

  2  3つ爪使用時の方法

ここからが今回の本番です。下図のようなワークと爪があります。


じ実際に内径を切削すると下図のようになりました。
爪の中心は距離cだけ爪1の方向へ寄っています。このままダイヤルゲージは0のまま触れません。180度回転させることはできません。

そこで、ワークをアンクランプさせ、時計方向に120度回転させて再度クランプさせますと下図になります。


拡大してみると下図になる。

Oc : 爪の中心(チャックの中心)

Os :  旋盤の軸芯(中心)

On :  ワークの内径の中心

Onは、回転させる前はOsと同じ場所でしたが、Ocを中心に120度時計方向に移動しました。


ダイヤルゲージをあて主軸を手でまわすと(Osを中心に回転)、最大値と最小値の差は 2 x (On,Os間の距離)となります。三角形(On,Oc,Os)は120度の角度を持つ2等辺三角形になるので、cを求めると

c = (最大値と最小値の差)÷2÷√3

となります。また最も遠い点は、点Onと点Osを通る線になるので、爪1との方向のズレは ー30度となります。

 

   最後に3つ爪方法をまとめると、

材料をクランプさせ内径切削する。

アンクランプさせ、120度時計方向にかいてんさせ、再度クランプさせる

③ダイヤルゲージで振れの最大値と最小値の差から距離cを求める

       c = (最大値と最小値の差)÷2÷√3

④最小値(最も遠い点) から時計方向に30度回転させた方向に、距離cだけ爪の中心はずれている

例えばダイヤルゲージの読みの最大値が 0.1で、最小値が ー0.4だとすると c = (0.1+0.4)/2/√3 =  0.14  がズレの距離となります。


理論通りにはなかなかいきませんが目安としての使用には有効で10年以上使ってます。




2021年10月2日土曜日

インタープランの無線モジュールを使ってみた



職場(工場)の改善でインタープランの無線モジュールを使う機会がありました。


多種類ある無線モジュールの中でも、本体のみで接点モードが使えるということでこれを選びました。 

導入の背景です。

作業されてる方は設備1と設備2の複数のラインを担当しています。その方が設備2の検査場所にいると、設備1の停止の音をしらせるパトライト1は設備の向こう側にあります。

 

そのため設備停止がすぐにわからず、停止時間が長引くという不具合が起こってます。

 そこで次のように変更します。 


パトライト1の信号を有線でIM920sLへ送り、無線で親機に飛ばし、有線でブザーユニットにつないで鳴らします。

これで作業されてる方が設備2の検査場所にいても設備1の停止がすぐにわかります




 

 

無線モジュールの設定です。

 ① 親機の設定

  1.  REG SW を押しながら電源を入れます。

    2.  STATUS LED 2 回点灯した時点でREG SW を離します。

     この時点で早い点滅を繰り返してる場合は、何度か 1. をやり直します。

  3. REG SW を再度押し、LED が連続点滅になるまで押し続けます。(約3 秒)

  4. REG SW を再度押し、LED 0.5 秒に1 回の点滅となるまで(約3 秒間)押し続けます。

    電源を切らずにこのままの状態で子機のそばに置いときます。

② 子機の設定

  1.  REG SW を押しながら電源を入れます。

    2.  STATUS LED 2 回点灯した時点でREG SW を離します。

     この時点で早い点滅を繰り返してる場合は、何度か 1. をやり直します。

    STATUS LED は連続点灯に変化すると設定完了です。

 

将来的に送信機は増える可能性があるので、子機に。受信機を親機にします。

2.5㎜ピッチの変換アダプタも購入してユニバーサル基盤に組みます。

子機(送信機)側です。

IM920c用変換アダプタ(IM920c-ADP)のピン配置がどこにも載ってなかったみたいですが、テスターで当たって上の図のようになってることがわかりました。

パトライトの信号線(LOWにしたときにオンします。  )をフォトカプラで電圧変換してIM920sLのP1に入れます。

P9がハイ、P10がローで 接点モードの送信機となります。

次に親機(受信機)側です。


 3端子レギュレータは発熱を考え、ロームのDC/DCコンバータを使いました。

有効性が確認できれば他のパトライトの信号も受信させる予定です。受信機の場所も簡単に変更できます。

中華製WIFIのモジュールがワンコインで買える時代、インタープランの無線モジュールは1台5000円く近くしますが、日本製の無線モジュールという事でとても安心感がありますね。遊びで使うのでなく、仕事でならなおさらです。

 


2021年5月10日月曜日

工場のライン内の監視カメラを作ってみた

 ライン内で加工不良が出ます。

 不良の出る原因がわかればその原因を取り除けば対策完了です。

しかし、原因がわからない場合があります。今回はその中でも、怪しい箇所が4箇所あるラインに監視カメラを つけて調査しました。


 ①カメ;ワークストッカーから搬送用ロボットがワークを取り出す様子を写します

②カメ;取り出した時の角度の、マスターとの微細なズレを測定します。その様子を写します。

③カメ;微細な角度のズレを補正して、旋盤1へ取り付ける様子を写します。

④カメ;微細な角度のズレを補正して、旋盤2へ取り付ける様子を写します。

 

監視カメラの条件

 1、 長時間録画できること (上書き保存できて最新5時間が常に録画)

 2、 4台のカメラで監視できる

 3、 1画面を4分割して録画できる(時間のズレ防止のため)

 4、 時刻もどっかに録画できる

 5、 イベントスイッチを押したら、その以前の録画は上書きされずに残っている

 6、 安い事

カメラはロジクール。これを4台使います。コロナ以前は1000円以下で購入できたのですが今は2500円です。在宅勤務で需要が多いから?

ビデオソフトですが、かゆいところに手が届くソフトはないのでpythonで作ります。コードは最後に添付します。

録画中、以下のような画面になります。 

1秒間に30フレームの仕様ですが実際は20フレームくらいにコマ落ちしてますので再生時は1.5倍速くらいのスピードで再生されます。パソコンの性能によるものと思います。また20分(1200フレーム)で1.2Gbくらいのサイズになります。

画面左上に現在時刻が表示されます。左上がワークをロボットで取り出す所、右上が取り出したワークの位相(角度)を調べる所、左下が旋盤1にワークを取り付ける所 、右下が旋盤2にワークを取り付ける所を録画してます。

 pythonを利用したら1万円くらいで、4カメの監視装置が簡単に構築できる時代です。 

 私の拙いpythonプログラムでも実用になりました。工場での改善活動に挑戦されてる方、pythonにも挑戦してみませんか?

 

 

 

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Thu Mar  4 21:32:24 2021
監視カメラ 4台用
 約20分ごとにビデオをmp4で記録していく
 12ファイルたまれば古いファイルから削除していく
 小文字の z を長押しすれば「event hh:mm:ss 」と表示し、その時点で溜まっているファイルは削除しない
 小文字の q を押せば終了する。
@author: toru
"""

import cv2
import datetime
import os
import time

d = datetime . datetime . now ( )
time_stump = str(d . hour)+":"+str(d . minute)+":"+str(d . second)
print(time_stump)

"""
VideoCapture(*) は0〜4を指定する。
どのカメラが何番になるのかはusbポートに差し込む順番にもより、不明
合成画面を見て変更すること 2134
"""
camera1 = cv2.VideoCapture(2)                
camera2 = cv2.VideoCapture(1)                
camera3 = cv2.VideoCapture(3)                
camera4 = cv2.VideoCapture(4)


# 動画ファイル保存用の設定
fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')        # 動画保存時のfourcc設定(mp4用)
fps1 = int(camera1.get(cv2.CAP_PROP_FPS))                    # カメラ1のFPSを取得
w1 = int(camera1.get(cv2.CAP_PROP_FRAME_WIDTH))              # カメラ1の横幅を取得
h1 = int(camera1.get(cv2.CAP_PROP_FRAME_HEIGHT))             # カメラ1の縦幅を取得
print("camera1",fps1, w1, h1)


fps2 = int(camera2.get(cv2.CAP_PROP_FPS))                    # カメラ2のFPSを取得
w2 = int(camera2.get(cv2.CAP_PROP_FRAME_WIDTH))              # カメラ2の横幅を取得
h2 = int(camera2.get(cv2.CAP_PROP_FRAME_HEIGHT))             # カメラ2の縦幅を取得
print("camera2",fps2, w2, h2)


fps3 = int(camera3.get(cv2.CAP_PROP_FPS))                    # カメラ3のFPSを取得
w3 = int(camera3.get(cv2.CAP_PROP_FRAME_WIDTH))              # カメラ3の横幅を取得
h3 = int(camera3.get(cv2.CAP_PROP_FRAME_HEIGHT))             # カメラ3の縦幅を取得
print("camera3",fps3, w3, h3)

fps4 = int(camera4.get(cv2.CAP_PROP_FPS))                    # カメラ4のFPSを取得
w4 = int(camera4.get(cv2.CAP_PROP_FRAME_WIDTH))              # カメラ4の横幅を取得
h4 = int(camera4.get(cv2.CAP_PROP_FRAME_HEIGHT))             # カメラ4の縦幅を取得
print("camera4",fps4, w4, h4)



video_count = 1
video_list = [1]
video_mix = cv2.VideoWriter('video_mix'+str(video_count)+'.mp4', fourcc, fps1, (w1+w2, h1+h2))  # 動画の仕様(ファイル名、fourcc, FPS, サイズ)
print("camera_mix",fps1, w1+w2, h1+h2)  # フレーム数、サイズを合わせないとエラーが出る
start_time = time.time()
print("start",start_time)             # スタート時間             

# 撮影=ループ中にフレームを1枚ずつ取得(qキーで撮影終了)
while True:
    ret, frame1 = camera1.read()              # カメラ1のフレームを取得
#       time_stump in frame1
    d = datetime . datetime . now ( )         #現在時刻を取得
    time_stump = str(d . hour)+":"+str(d . minute)+":"+str(d . second)    #時刻をタイムスタンプに変更
    cv2.putText(frame1, time_stump, (0, 50), cv2.FONT_HERSHEY_PLAIN, 3, (0, 0, 255), 3, cv2.LINE_AA)      #タイムスタンプをフレーム1に書き込み

    ret, frame2 = camera2.read()              # カメラ2のフレームを取得
    ret, frame3 = camera3.read()              # カメラ3のフレームを取得
    ret, frame4 = camera4.read()              # カメラ4のフレームを取得


    frame_hmix1 = cv2.hconcat([frame1, frame2])  # conect horizonal frame1とframe2を水平方向に結合しframe_hmix1とする
    frame_hmix2 = cv2.hconcat([frame3, frame4])  # conect horizonal frame3とframe4を水平方向に結合しframe_hmix2とする

    frame_mix = cv2.vconcat([frame_hmix1, frame_hmix2])  # conect vurtical frame_hmix1とframe_hmix2を垂直方向に結合しframe_mixとする
    cv2.namedWindow('camera_mix', cv2.WINDOW_NORMAL)
    cv2.imshow('camera_mix', frame_mix)       # frame_mixを画面に表示
    video_mix.write(frame_mix)                # frame_mixをvideo_mixに録画する
    now_time = time.time()
    #    print("now_time",now_time)

    """
    20分(1200)経ったら video_mixを終了し、1繰り上げた番号をつけて保存する
    """
    if now_time - start_time > 1200 : #20min = 1200
        start_time = now_time
        video_mix.release()
        video_count = video_count + 1
        video_mix = cv2.VideoWriter('video_mix'+str(video_count)+'.mp4', fourcc, fps1, (w1+w2, h1+h2))  # 動画の仕様(ファイル名、fourcc, FPS, サイズ)
        
        video_list.append(video_count)    #  video_listに video_countを追加する
        if len(video_list) > 12 : #ファイルが指定数(12)を超えたら、削除する
            path = 'video_mix'+str(video_list.pop(0))+'.mp4'
            if os.path.exists(path) :
                os.remove(path)

    # 'q'を押したらwhileループを抜ける
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
    # 'z'を押したら「event」と時間をプリントして削除リストを空にする
    if cv2.waitKey(1) & 0xFF == ord('z'):
        print("event",time_stump)
        video_mix.release()
        start_time = now_time
        video_count = video_count + 1
        video_mix = cv2.VideoWriter('video_mix'+str(video_count)+'.mp4', fourcc, fps1, (w1+w1, h1+h1))  # 動画の仕様(ファイル名、fourcc, FPS, サイズ)
        video_list = []
        video_list.append(video_count)
        


# 撮影用オブジェクトとウィンドウの解放
camera1.release()
camera2.release()
camera3.release()
camera4.release()

video_mix.release()
cv2.destroyAllWindows()

"""
"""



2020年8月11日火曜日

60の手習い 〜python勉強中〜

pythonを勉強中、再帰法の題材にでてくる、ハノイの塔 のプログラムがどうしても理解できません。

演習問題で、「ハノイの塔をタートルで動かせ」とありましたのでなんとか動かしてみました。

 

 コードを入力

import turtle       
wn = turtle.Screen()
t1 = turtle.Turtle()
t1.resizemode("user")
t1.shapesize(1,1,1)
t1.shape("circle")
t1.color("red")
t1.speed(3)
t1.up()
t1.setpos(0,180)

t2 = turtle.Turtle()
t2.resizemode("user")
t2.shapesize(1,2,1)
t2.shape("circle")
t2.color("green")
t2.speed(3)
t2.up()
t2.setpos(0,150)

t3 = turtle.Turtle()
t3.resizemode("user")
t3.shapesize(1,3,1)
t3.shape("circle")
t3.color("brown")
t3.speed(3)
t3.up()
t3.setpos(0,120)

t4 = turtle.Turtle()
t4.resizemode("user")
t4.shapesize(1,4,1)
t4.shape("circle")
t4.color("black")
t4.speed(3)
t4.up()
t4.setpos(0,90)

t5 = turtle.Turtle()
t5.resizemode("user")
t5.shapesize(1,5,1)
t5.shape("circle")
t5.color("yellow")
t5.speed(3)
t5.up()
t5.setpos(0,60)

t6 = turtle.Turtle()
t6.resizemode("user")
t6.shapesize(1,6,1)
t6.shape("circle")
t6.color("pink")
t6.speed(3)
t6.up()
t6.setpos(0,30)

t7 = turtle.Turtle()
t7.resizemode("user")
t7.shapesize(1,7,1)
t7.shape("circle")
t7.color("cyan")
t7.speed(3)
t7.up()
t7.setpos(0,0)

t8 = turtle.Turtle()
t8.resizemode("user")
t8.shapesize(1,8,1)
t8.shape("circle")
t8.color("violet")
t8.speed(3)
t8.up()
t8.setpos(0,-30)


def move_turtle(t_num,to_pole):
if t_num ==1 :
t_num = t1
if t_num ==2 :
t_num = t2
if t_num ==3 :
t_num = t3
if t_num ==4 :
t_num = t4
if t_num ==5 :
t_num = t5
if t_num ==6 :
t_num = t6
if t_num ==7 :
t_num = t7
if t_num ==8 :
t_num = t8

count_a = 0
count_b = 0
count_c = 0
for t in (t1,t2,t3,t4,t5,t6,t7,t8):
if t.xcor() == 0:
count_a = count_a + 1
if t.xcor() == 150:
count_b = count_b + 1
if t.xcor() == 300:
count_c = count_c + 1
to_a_y = count_a * 30 - 30
to_b_y = count_b * 30 - 30
to_c_y = count_c * 30 - 30
if to_pole =="A":
t_num.sety(200)
t_num.setx( 0)
t_num.setpos(0 ,to_a_y)
if to_pole =="B":
t_num.sety(200)
t_num.setx(150)
t_num.setpos(150 ,to_b_y)
if to_pole =="C":
t_num.sety(200)
t_num.setx(300)
t_num.setpos(300 ,to_c_y)

def moveTower(height,fromPole, toPole, withPole):
if height >= 1:
moveTower(height-1,fromPole,withPole,toPole)
moveDisk(height,fromPole,toPole)
moveTower(height-1,withPole,toPole,fromPole)

def moveDisk(height,fp,tp):
print("moving disk",height," from",fp,"to",tp)
move_turtle(height,tp)

moveTower(8, "A", "C", "B")


wn.exitonclick()  

8段まで動かしてイメージを作ってます。

じっくり眺めてても古い頭ではなかなか理解できません。

2020年6月2日火曜日

コロナに負けるな!パルスオキシメータを作ろう

コロナに負けるな!自分に何ができるかを考えよう。
ということでパルスオキシメータの制作を思いつきました。
 理由  
1 急に重症化するときがあり、その目安の一つになるらしい。  
2 どうも安い部品でできそう。  
3 Aruduino Unoに標準のライブラリがある  
4 おうちでできる。  
5 年配の家族がいるので一台あれば安心。
6  Aruduino Unoがあるのでそれを使える。もし大量生産が必要なら、ATMEGA  だけ購入して増産できる。

使用するものはAruduino Unoと16桁2行のlcdとパルスオキシメータのモジュール。

ebayで385円でポチりました。
香港からの発送で、届くのが5月末の予定。
コロナが収束して必要なくなってたら、それはそれで良い事だね。

 いつも思うのですが、返品、交換の送料は購入者の負担だと言うこと。
 一体送料はいくらかかるのでしょうか?すくなくとも385円以上は間違いないと思うのでこういう安いものを返品交換した人は実際にいるのでしょうか。
 使えなくてもあきらめのつく金額に押さえておくというのは販売者にとって大切なことかもしれませんね。


回路は以下の通りです。
このモジュールは1.8vでの出力なので出力電圧の変換が必要です。
2N7000のFETで変換します。


Aruduino Unoのコードですが、AruduinoIDEのスケッチ →ライブラリをインクルード→ライブラリを管理の画面でmax30102を検索します。

 これをインストールします。
そのライブラリのなかの SparkFun_MAX3010x_Pulse_and_Proximity_Sensor_Library→exsamples→Example8_SPO2→Exsample8_SPO".inoを開きます。

コードです。
/*
  Optical SP02 Detection (SPK Algorithm) using the MAX30105 Breakout
  By: Nathan Seidle @ SparkFun Electronics
  Date: October 19th, 2016
  https://github.com/sparkfun/MAX30105_Breakout

  This demo shows heart rate and SPO2 levels.

  It is best to attach the sensor to your finger using a rubber band or other tightening 
  device. Humans are generally bad at applying constant pressure to a thing. When you 
  press your finger against the sensor it varies enough to cause the blood in your 
  finger to flow differently which causes the sensor readings to go wonky.

  This example is based on MAXREFDES117 and RD117_LILYPAD.ino from Maxim. Their example
  was modified to work with the SparkFun MAX30105 library and to compile under Arduino 1.6.11
  Please see license file for more info.

  Hardware Connections (Breakoutboard to Arduino):
  -5V = 5V (3.3V is allowed)
  -GND = GND
  -SDA = A4 (or SDA)
  -SCL = A5 (or SCL)
  -INT = Not connected
 
  The MAX30105 Breakout can handle 5V or 3.3V I2C logic. We recommend powering the board with 5V
  but it will also run at 3.3V.
*/

#include 
#include "MAX30105.h"
#include "spo2_algorithm.h"

MAX30105 particleSensor;

#define MAX_BRIGHTNESS 255

#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__)
//Arduino Uno doesn't have enough SRAM to store 100 samples of IR led data and red led data in 32-bit format
//To solve this problem, 16-bit MSB of the sampled data will be truncated. Samples become 16-bit data.
uint16_t irBuffer[100]; //infrared LED sensor data
uint16_t redBuffer[100];  //red LED sensor data
#else
uint32_t irBuffer[100]; //infrared LED sensor data
uint32_t redBuffer[100];  //red LED sensor data
#endif

int32_t bufferLength; //data length
int32_t spo2; //SPO2 value
int8_t validSPO2; //indicator to show if the SPO2 calculation is valid
int32_t heartRate; //heart rate value
int8_t validHeartRate; //indicator to show if the heart rate calculation is valid

byte pulseLED = 11; //Must be on PWM pin
byte readLED = 13; //Blinks with each data read

void setup()
{
  Serial.begin(115200); // initialize serial communication at 115200 bits per second:

  pinMode(pulseLED, OUTPUT);
  pinMode(readLED, OUTPUT);

  // Initialize sensor
  if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) //Use default I2C port, 400kHz speed
  {
    Serial.println(F("MAX30105 was not found. Please check wiring/power."));
    while (1);
  }

  Serial.println(F("Attach sensor to finger with rubber band. Press any key to start conversion"));
  while (Serial.available() == 0) ; //wait until user presses a key
  Serial.read();

  byte ledBrightness = 60; //Options: 0=Off to 255=50mA
  byte sampleAverage = 4; //Options: 1, 2, 4, 8, 16, 32
  byte ledMode = 2; //Options: 1 = Red only, 2 = Red + IR, 3 = Red + IR + Green
  byte sampleRate = 100; //Options: 50, 100, 200, 400, 800, 1000, 1600, 3200
  int pulseWidth = 411; //Options: 69, 118, 215, 411
  int adcRange = 4096; //Options: 2048, 4096, 8192, 16384

  particleSensor.setup(ledBrightness, sampleAverage, ledMode, sampleRate, pulseWidth, adcRange); //Configure sensor with these settings
}

void loop()
{
  bufferLength = 100; //buffer length of 100 stores 4 seconds of samples running at 25sps

  //read the first 100 samples, and determine the signal range
  for (byte i = 0 ; i < bufferLength ; i++)
  {
    while (particleSensor.available() == false) //do we have new data?
      particleSensor.check(); //Check the sensor for new data

    redBuffer[i] = particleSensor.getRed();
    irBuffer[i] = particleSensor.getIR();
    particleSensor.nextSample(); //We're finished with this sample so move to next sample

    Serial.print(F("red="));
    Serial.print(redBuffer[i], DEC);
    Serial.print(F(", ir="));
    Serial.println(irBuffer[i], DEC);
  }

  //calculate heart rate and SpO2 after first 100 samples (first 4 seconds of samples)
  maxim_heart_rate_and_oxygen_saturation(irBuffer, bufferLength, redBuffer, &spo2, &validSPO2, &heartRate, &validHeartRate);

  //Continuously taking samples from MAX30102.  Heart rate and SpO2 are calculated every 1 second
  while (1)
  {
    //dumping the first 25 sets of samples in the memory and shift the last 75 sets of samples to the top
    for (byte i = 25; i < 100; i++)
    {
      redBuffer[i - 25] = redBuffer[i];
      irBuffer[i - 25] = irBuffer[i];
    }

    //take 25 sets of samples before calculating the heart rate.
    for (byte i = 75; i < 100; i++)
    {
      while (particleSensor.available() == false) //do we have new data?
        particleSensor.check(); //Check the sensor for new data

      digitalWrite(readLED, !digitalRead(readLED)); //Blink onboard LED with every data read

      redBuffer[i] = particleSensor.getRed();
      irBuffer[i] = particleSensor.getIR();
      particleSensor.nextSample(); //We're finished with this sample so move to next sample

      //send samples and calculation result to terminal program through UART
      Serial.print(F("red="));
      Serial.print(redBuffer[i], DEC);
      Serial.print(F(", ir="));
      Serial.print(irBuffer[i], DEC);

      Serial.print(F(", HR="));
      Serial.print(heartRate, DEC);

      Serial.print(F(", HRvalid="));
      Serial.print(validHeartRate, DEC);

      Serial.print(F(", SPO2="));
      Serial.print(spo2, DEC);

      Serial.print(F(", SPO2Valid="));
      Serial.println(validSPO2, DEC);
    }

    //After gathering 25 new samples recalculate HR and SP02
    maxim_heart_rate_and_oxygen_saturation(irBuffer, bufferLength, redBuffer, &spo2, &validSPO2, &heartRate, &validHeartRate);
  }
}

「メモリが不足しています」とのメッセージが出ます。



4月30日にebayで注文したのが6月1日にやっと到着しました。
製作意欲は20%くらいに低下しました。
それでも動作確認してますと、うまく動きません。
どうやら I2Cのレベル変換がうまくいってないようです。

試しに次の回路でスイッチをオンオフして出力側がオンオフするのか確認してみました。


そうすると入力側の電圧が2.5vを下回ると出力側でオンオフしないことがわかりました。
つまり最初の回路では、5vと3.3vの変換はできても5vと1.8vの変換はできないようです。
専用のレベル変換ICではなく、ありあわせの部品でやりたいのでしばらく検討します。