BMP280温度センサー・その4(I2C通信スレッド)
簡易目次
実際のプログラムに入る前にプロジェクトフォルダーをGitHubに作成しました。
ブログにまとめていないだけでプログラム自体はそれなりに進んでいます。その都度参照していこうと思います。
まずはI2Cとの通信の実装から行います。I2Cとの通信はI2Cマスター毎に動作する専用のスレッドとスレッドに関連付けられたクラス内で行っていきます。
I2Cとの通信を受け持つクラスとして「SPI2C::CSPI2CManager」と言うクラスを実装します。クラスのインスタンスは別スレッド内で実行され、クラス内にはミューテックスで保護されたリストがあり、そのリストに並んでいるI2Cデバイスアクセス用のクラスをFIFOでリストからチェックアウトして、I2Cとの通信を行うことにより様々なデバイスとのアクセスを一元的に行っていくことにします。説明だけでは分かりにくいのでソースを提示します。
CSPI2CManagerクラス(抜粋)
namespace SPI2C { class CSPI2CManager { public: static void * ThreadProc( void * pVoidParameter ); CSPI2CManager(const char * pcszI2CName); virtual ~CSPI2CManager(); void AddDeviceAccess( class CSPI2CDeviceAccessBase * pDevice ); inline void StopThread() { this->m_bStopLoop = true; } protected: void ReleaseAllDevice(); class CSPI2CDeviceAccessBase * CheckoutAccess(); class CSPI2CDeviceAccessBase * m_pFirstAccess; class CSPI2CDeviceAccessBase * m_pLastAccess; bool CommunicationLoop(); bool WriteI2C( uint8_t ui8DeviceAddress, CSPI2CDataElement * pDataElement ); bool ReadI2C( uint8_t ui8DeviceAddress, CSPI2CDataElement * pDataElement ); }; }
このクラスのインスタンスの作成及びスレッドの実行は以下のようにします。
class CSPI2CManager int main() { SPI2C::CSPI2CManager i2cManager("/dev/i2c-1"); pthread_t threadId; pthread_create( &threadId, NULL, SPI2C::CSPI2CManager::ThreadProc, (void*)&i2cManager );SPI2C::CSPI2CManagerのインスタンスをI2Cマスターのデバイス名付きで作成します。その後pthreadのスレッドプロシージャー(pthread_createの第3引数)に対してSPI2C::CSPI2CManager::ThreadProc(クラス内でstaticで宣言されている関数)を指定し、パラメータ(pthread_createの第3引数)として作成したインスタンスのポインタを指定します。作成されたスレッドはSPI2C::CSPI2CManager::ThreadProc内で以下のように実行されます。
using namespace SPI2C; void * CSPI2CManager::ThreadProc( void * pVoidParameter ) { CSPI2CManager * pI2CManager = (CSPI2CManager*)pVoidParameter; pI2CManager->CommunicationLoop(); return NULL; }
引数として渡されたインスタンスへのポインタをクラスにキャストし、クラスのCommunicationLoop関数を実行します。関数が終了したらNULLを返して終了します。
CommunicationLoopは以下のような内容になっています。
bool CSPI2CManager::CommunicationLoop() { this->m_iFD = open( this->m_pszI2CName, O_RDWR ); if( this->m_iFD < 0 ) { return false; } while( 1 ) { CSPI2CDeviceAccessBase * pAccess = this->CheckoutAccess(); if( pAccess ) { //pAccess内容の処理 } else if( this->m_bStopLoop ) { break; } } close( this->m_iFD ); this->m_iFD = -1; return true; }
CommunicationLoopの大体のフローチャート
今回はCSPI2CManagerクラスの動作を説明したいので、「pAccess内容の処理」の部分は省略します(次回で説明します)。CommunicationLoopではまずインスタンス作成時に渡されたI2Cマスターデバイス名(先ほどのmain関数の例では"/dev/I2c-1"をopenし、ループに入ります。ループ内では「CheckoutAccess()」関数を利用して、クラス内部のミューテックスで保護されたリストからI2Cにアクセスするための情報をチェックアウトし、処理します。
もしも処理するものがすべてなくなり、かつクラス変数の「m_bStopLoop」がtrueになっていれば、ループを終了します。ループ終了後、I2Cマスターデバイスのファイルディスクリプターを閉じます。
この関数はm_bStopLoopがfalseにならない限りは無限ループします。m_bStopLoopはいつfalseになるのでしょうか?最初に提示したリスト「CSPI2CManagerクラス(抜粋)」の中に「StopThread」という関数があり、ここでm_bStopLoop変数をtrueにしています。
このCSPI2CManagerクラスのインスタンスはmain関数内で定義されており、このI2Cのスレッドのみならず他のスレッドからアクセスすることが出来ます。それでこのインスタンスのStopThread関数を呼び出すことにより、このスレッドを終了させることが出来ます。
例えば以下のようにすると、起動後10秒後に終了するプログラムを作成できます。
class CSPI2CManager int main() { SPI2C::CSPI2CManager i2cManager("/dev/i2c-1"); pthread_t threadId; pthread_create( &threadId, NULL, SPI2C::CSPI2CManager::ThreadProc, (void*)&i2cManager ); sleep(10); i2cManager.StopThread(); pthread_join( threadId, NULL ); return 9; }
コミュニケーションループ内では内部リストからCheckoutAccess関数でCSPI2CDeviceAccessBaseのインスタンスをチェックアウトすることにより処理を実行していました。ではこのチェックアウトするリストはいつ整備するのでしょうか?これはケースバイケースになります。スレッドが開始される前にSPI2C::CSPI2CManagerのインスタンスに対してAddDeviceAccessを実行して追加することもできますし、スレッドが実行された後に随時追加していくこともできます。この辺の仕組みなどは今後様々な機器を実装していく時にこのような仕組みにしたメリットも含めて一緒に説明していきたいと思います。
コメント
コメントを投稿