みなさんこんにちわ、こんばんわ

SAIです。

 

Arduinoで遊ぶネタが・・

というか、需要がなさそうで気力が続かないというのもありますが、

 

ゲームブログとYoutube作って遊んでるせいというのがでかい!

 

 

The ダメ人間

 

 

で、話は仕事。

 

 

仕事で実機評価を行うことがあるわけですが、

テストが面倒くさいから、テストをプログラムとハードにやらせたいSAIです。

 

つまり、ユーザの操作とか、外的割り込みをパソコン経由で自動でやりたい。

 

そうすれば、ミリ秒オーダの操作もできるわけです。

計測器のとり漏らしも減りますよね。

 

ずいぶん前からブツブツ言っていたのですが、

お堅い社風なんでなかなか進まない訳です。

 

ま、所詮、平社員の言い分なんて。。というのもあります。


 

ここ数年、別部署(仕様設計する側)に異動していたわけですが、

無駄作業をエクセルとか自前ソフトで楽にしつつ、

(一応、ざっくり裏付け再計算と過去比較だけしてサインして)

他部署の工数の話がでるたびに時々ぶつくさ言ってたわけです。

 

これを聞かれてたかどうかは判りませんが、


今年に入って、いきなり呼び出されて、

「SAI君 は評価するのが好きだろう。」

とか何とか言って、4月に異動命令が来たそうです。

 

(誰からからの指示か知らないけど、

 3月ころに SAIの上司の上司が拒否したものの、拒否できなかったぽいことを言ってた・・・ 

 そんな偉い人がSAIに目を止めるわけないと思うので、上司の作り話だと思うけど。)

 

 

評価の自動化。

裏では、別部署にいた間に自動化が徐々に進んでいました。

が、あまり効率よいところまで出来ている感じではなかったみたいですね。

 

まだ道半ばっぽいですが、

SAIの思っている方向と少しずれている感じが。。

 

 

やっぱりどうにか構築したいなぁ・・・と思うことが多いわけです。

 

ハードもなければ作ればいいじゃん。

Arduinoにしろ、ラズパイにしろそんな高くないよね。

 

なにより、うちの会社

 

製品製造してんだから、

もう製品EOLで不要になったマイコンが

山ほどあるじゃん。

(評価に使う数個に対して、不要なデバボに乗ってるのが山ほどという意味。)

 

と思うわけです。

 

が、

会社の言い分としてはもう一つ理由があって、

 

評価者はプログラマーじゃない

 

そうですよね。。。

 

 

もっと柔軟に考えてほしいなぁ・・・

 

 

今、画面を撮影して目視判定してるところも、

2回目以降の評価なら画像解析すりゃいいじゃんとか思うわけです。

 

 

python とか OpenCV とか勉強しようかしら。

 

 

 

 

3月にやりたかった勉強会ネタ。

これをArduinoでやろうと重い腰を上げた直後、の移動。

 

中途半端でやる気を失ったSAIです。

 

 

Legoロボットにしろ、Arduinoにしろ

SAIが重い腰を上げると頓挫する。

よくあるパターン。

 

ふぅ。

 

みなさんこんにちわ、こんばんわ

SAIです。

 

さて、前回はC++言語で純粋仮想関数を利用した定期呼び出しを実現しました。

 

 

さてさて、今回は非同期コールバックを実装しようと思います。

(クラス自体は4月に作ったやつです。。放置しすぎました)

 

 

何をやりたいかというと、

C言語の時もやりましたね。

 

 

 

これをC++で実装です。

 

内容の思い出しですが、

やりたかったことはこれです!

A君とB君、二人以上のタイマーを管理させる

 

 

 

 

感のいい人はお気づきでしょう。

 

タイマークラスを作成しますよ!

 

まずは、コールバックをするためには、

C++では純粋仮想関数を決めないといけませんね。

 

時間経過を示す名称になりますから、

class MTimerObserver
{
public:
  // コールバック用の純粋仮想関数 /
  virtual void OnTimerFire() = 0;
};

こんな感じにしてみたよ!

 

タイマが完了したら、

OnTimerFire() ;

が呼び出されるということですね。

 

次、管理するクラスを作るよ!

C言語だと、inoファイルにセット関数やキャンセル関数を用意していたよね。

 

今回はC++言語なので、クラスにしちゃいますよ!

 

 

 

さて、クラスを作るにあたり、一つ課題があります。

 

タイマーってどのクラスからも、すぐに使えたほうが便利ですよね?

 

 

C言語って、すべての関数はstaticな領域に格納されています。

 

では、C++言語はどうかというと、わざとstaticと書かなければ、

クラスのアドレス(ポインター)を知らないと使えないよね。


ということは、クラスのポインターを知らないクラスからは、

タイマークラスのSet関数を呼び出すことができないということになってしまいます。

 

それでは困りますね。

 

 

そんな時の対策として

 

シングルトンオブジェクト

 

という考え方があります。

 

シングルトン・・・・独身・単独でも生きていける人、自立といったような意味がありますね。

 

一人きりでも生きていける、世の中に一人しか存在しない。

といったような意味合いなのだと思います。

 

 

どういった技法かというと、

一度作ったクラスを一般公開するようなイメージです。

 

通常クラスはnewでヒープにクラスを展開します。

ところが、ヒープ上のアドレスを知っているのは、クラスを生成した人だけ。

 

シングルトンはというと、

自身で自信を作り、staticな領域にアドレスを置いておくことで、

Get関数で、アドレスを入手してどこのクラスからもアクセス用にできます。

 

 

シングルトンはどんな機能に適しているでしょうか?

 

どこからでもアクセスしたい機能

例えば、

・デバックログ関連

・タスク操作系

・タイマー管理系

・セッション管理系

・セマフォ関連

のような、どこからでもアクセスしたいようなクラスに適しています。

 

 

逆に適さない機能は、

クラスを複製が必要となるような歯車のような機能には適していません。

例えば、

状態を管理するオセロの石のようなクラス

ですね。

 

 

気を付ける点としては、

シングルトンは、

誰からもアクセスできる=誰も管理していない

ととらえることができます。

 

つまり、気を付けるのは解放漏れです。

 

必ず、一番最後に使う人にdeleteしてもらう必要があります。

が、組み込みの場合は電池がなくなるまで作りっぱなしでも

問題になることはないでしょうね。

 

 

それでは、実際に作ってみましょう。

 

/*
 * @file    CTimerOneManager
 * @brief   Lesson B_3 タイマーマネージメントクラス。
 * @author  SAI
 * @date    2021/04/04
 * @note    タイマー登録してコールバック制御するマネージメントクラス
 */
class CTimerOneManager
{
public:
  /* タイマー登録配列用クラス */
  class TObserverStr
  {
  public:
    MTimerObserver* iTimerObserver;
    long iFierTime;
  };
  
public:
 
static CTimerOneManager* GetTimer();  // アドレスゲット関数 兼シングルトン生成 //
  ~CTimerOneManager();
  /* setter */
  void SetTimer(MTimerObserver & aObserver,long aDelay);
  void RemoveTimer(MTimerObserver & aObserver);

  // Arduino TimerCallBask呼び出し関数 /
  void TimerOneFire();
private:
  CTimerOneManager();
  
private:
  /* コールバック呼び出し用アドレス */
 
static CTimerOneManager* gTimerPointer;
  
  /* 10個までタイマーの応答を登録できる */
  TObserverStr Array[CArrayNum];
  int iSetClassNum;
  long iTimerCounter;
};

 

なんと!

コンストラクタがプライベートになっているのが衝撃的ですよね。

 

処理の中身や考え方は、

 

 

 

この時のC言語と一緒なので割愛します。

 

 

あとは、

X秒後にコールバックをしてほしい、Mクラスを継承したクラスから、

以下の関数を呼び出すだけで、

  (CTimerOneManager::GetTimer())->SetTimer(*this,delay);

 

delay秒後にOnTimerFire()が呼び出されるようになります。


また、

OnTimerFire()関数の中で、再度SetTimer(*this,delay)を呼び出せば、

定期呼び出しとしても使えるようになりますね!

 

 

それではクラスを実装してみましょう。

 

コードは最後。

 

 

 

晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ

雑談

SAIがシングルトンオブジェクトを知った2004年~2005年?ころは、

インターネットでシングルトンと打ち込んでも、

オブジェクト指向言語の関連のサイトは見当たりませんでした。

 

出てくるのは、独身という翻訳サイトばっかり。

 

今、15年以上ぶりに検索したら、オブジェクト指向系のサイトが山のように出て気ました。

 

いい時代になったものです。

 

晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ

 

◆ヘッダ(Mクラス)

/*
 * @file    MTimerObserver
 * @brief   Lesson A_5 タイマーを制御するクラスを作成してみよう。
 * @author  SAI
 * @date    2021/04/04
 * @note    タイマーの純粋仮想コールバック関数を宣言する為のコールバック用基底クラス
 */
class MTimerObserver
{
public:
  // コールバック用の純粋仮想関数 /
  virtual void OnTimerFire() = 0;
};
 

◆ヘッダ(タイマクラス)

/* クラス定義 */
class MTimerObserver;

const int CArrayNum = 10;

/*
 * @file    CTimerOneManager
 * @brief   Lesson A_5 タイマーマネージメントクラス。
 * @author  SAI
 * @date    2021/04/04
 * @note    タイマー登録してコールバック制御するマネージメントクラス
 */
class CTimerOneManager
{
public:
  /* タイマー登録配列用クラス */
  class TObserverStr
  {
  public:
    MTimerObserver* iTimerObserver;
    long iFierTime;
  };
  
public:
  static CTimerOneManager* GetTimer();
  ~CTimerOneManager();
  /* setter */
  void SetTimer(MTimerObserver & aObserver,long aDelay);
  void RemoveTimer(MTimerObserver & aObserver);

  // Arduino TimerCallBask呼び出し関数 /
  void TimerOneFire();
private:
  CTimerOneManager();
  
private:
  /* コールバック呼び出し用アドレス */
  static CTimerOneManager* gTimerPointer;
  
  /* 10個までタイマーの応答を登録できる */
  TObserverStr Array[CArrayNum];
  int iSetClassNum;
  long iTimerCounter;
};
 

◆ソースコード(タイマクラス)

CTimerOneManager.cpp

/*
 * @file    CTimerOneManager
 * @brief   Lesson A_5 タイマーマネージメントクラス。
 * @author  SAI
 * @date    2021/04/04
 * @note    タイマー登録してコールバック制御するマネージメントクラス
 */

/* *** Include *** */
#include <TimerOne.h>
/* 注意
   TimerOne.h を使用するためには、
   スケッチ -> ライブラリのインクルード -> ライブラリの管理
   から、TimerOne をインストールしておく必要があります。
*/
#include <Arduino.h>

#ifndef __MTimerObserver_
#include "MTimerObserver.h"
#endif

#ifndef __CTimerOneManager__
#include "CTimerOneManager.h"
#endif


/* オーバーフロー防止用のクリア時間 us */
const long COverFlowTime = 1000*1000*1000;

const CTimerOneManager::TObserverStr TObserverStrInit = {nullptr , 0};
//タイマーの応答時間をマイクロ秒単位で設定 /
const int CTIMER_DELAY = 1000;

/* コールバック呼び出し用アドレス */
static CTimerOneManager* CTimerOneManager::gTimerPointer = nullptr;

/* TimerOneに呼び出してもらう関数 */
static void TimerOneFire()
{
  if(CTimerOneManager::GetTimer() != nullptr)
  {
    CTimerOneManager::GetTimer()->TimerOneFire();
  }
}


/* シングルトンオブジェクト取得 */
static CTimerOneManager* CTimerOneManager::GetTimer()
{
  if(CTimerOneManager::gTimerPointer ==nullptr)
  {
    /* なければ生成 */
    CTimerOneManager::gTimerPointer = new CTimerOneManager();
  }
  return CTimerOneManager::gTimerPointer;
}
  

 /*
 * @fn    初期設定
 * @brief コンストラクタ
 * @param -
 */
CTimerOneManager::CTimerOneManager()
:iSetClassNum(0),iTimerCounter(0)
{
  gTimerPointer = this;
  for(int i=0; i<CArrayNum;i++)
  {
    Array[i] = TObserverStrInit;
  }

  /* タイマー用のカウンターを初期化 */
  // Timer setting  タイマー設定
  Timer1.initialize(CTIMER_DELAY); //マイクロ秒単位で設定 /
  Timer1.attachInterrupt(::TimerOneFire); // コールバック関数設定 /
  
}

/*
 * @fn    終了設定
 * @brief デストラクタ
 * @note  終了時には必ずリソースを終了状態にする。
 *        生成したヒープ、つないだセッションなど開放する
 */
CTimerOneManager::~CTimerOneManager()
{
  
}

void CTimerOneManager::SetTimer(MTimerObserver & aObserver,long aDelay)
{
  Array[iSetClassNum].iTimerObserver  = &aObserver;
  Array[iSetClassNum].iFierTime       = iTimerCounter + aDelay;
  // over flow 対策 /
  if(COverFlowTime > Array[iSetClassNum].iFierTime)
  {
    Array[iSetClassNum].iFierTime -= COverFlowTime;
  }
  // 保存数を更新 /
  iSetClassNum++;
}

void CTimerOneManager::RemoveTimer(MTimerObserver & aObserver)
{
  int lClassNum = 0;
  for( ; lClassNum < iSetClassNum ; lClassNum++)
  {
    if(Array[lClassNum].iTimerObserver == &aObserver)
    {
      break;
    }
  }

  /* 見つかったアドレス以降はスライドする */
  for( ; lClassNum < iSetClassNum-1 ; lClassNum++)
  {
    Array[lClassNum].iTimerObserver = Array[lClassNum+1].iTimerObserver;
    Array[lClassNum].iFierTime      = Array[lClassNum+1].iFierTime;
  }
  Array[iSetClassNum].iTimerObserver = nullptr;
  Array[iSetClassNum].iFierTime      = 0;
  
  // 保存数を更新 /
  iSetClassNum--;
}
  

// Arduino TimerCallBask呼び出し関数 /
void CTimerOneManager::TimerOneFire()
{
  iTimerCounter++;
  long lTime = (iTimerCounter*CTIMER_DELAY);
  
  for( int lClassNum = 0 ; lClassNum < iSetClassNum ; lClassNum++)
  {
    /* 発動時間を超えていたら、コールバック実施 */
    if(Array[lClassNum].iFierTime < lTime)
    {
     Array[lClassNum].iTimerObserver->OnTimerFire();
     RemoveTimer(*(Array[lClassNum].iTimerObserver));
    }
  }

};
  

使い方

コールバックしてほしいクラスで、MTimerObserverクラスを継承する。

  ⇒ class CSegControlClass : public MTimerObserver

コールバックしてほしいクラスに、  void OnTimerFire()override;を実装する。

  

だけですね。


 

 

 

 

 

 

 

 

みなさんこんにちわ、こんばんわ

SAIです。

 

さて、前回はC++言語で関数ポインター呼び出しを

コールバック関数に利用しようとしても、うまくいきませんでしたね。

 

 

では、C++言語ではどのようにコールバックを実装したらよいのでしょうか?

 

その方法は純粋仮想関数(pure virtual function)で実装する方法です。

 

これを理解するには、もっと基本的なところから勉強する必要があります。

 

少し話がそれたところからスタートしますが、

頑張ってついてきてくださいね!

 

 

C++言語はオブジェクト指向言語ということで、

継承(inheritance) という機能があります。

 

難しそうな単語が出てきましたね。

 

 

◆継承とは

 

継承は、親から子供に受け継がれるというイメージです。

絵にすると判りやすいと思います。

 

    (Fig:1継承)

 

親クラスHogehogeBaseというクラスを、

子クラスHogehogeChildというクラスが継承したとします。

(左図の2つで、継承方法は★後述★

 

すると、

親クラスの関数や変数(priveteを除く)が

子供のクラスでも自身の持っている機能として扱えるようになるというものです。

 

また、継承をする際には、関数や変数に色々付加することができます。

 

継承する際に親クラス側にvirtual(仮想)と書いている関数に関しては、

子供がoverrideとして、処理を進化することもできるのです。

仮想とある通り、子が独自進化する可能性があるかもしれないことを示唆しているのです。

(この場合、親の能力は消えて子供の能力だけになります※下図赤字

 

   (Fig:2仮想関数)

面白いでしょ!

 

 

クラスは”構造体みたいなもの”といいましたが、
子供のクラス扱えば、子供のクラスと親クラスのすべてを使うことができます。

 

オブジェクト指向言語の面白いところは、

この子供の実体を親クラスのポインターとして扱うこともできるのです。

ただし、その場合使用できる関数は、親クラスに定義しているものだけになります。

 

が、

Fig:2仮想関数に記載のFunc1は、実体である子クラスの能力になりますよね。

 

そうです。

親クラスの型で呼び出したとしても、

virtualがついている関数に関しては、子クラスが呼び出されるのです。

 

 

 

さらに、仮想関数にはさらに面白い機能があり、

 

残念ながら親は持てなかった機能関数を、

子供は絶対にこの機能関数を持たせるんだ!!

 

強い意志の元、約束の機能を定義することもできるのです。

 

この約束の機能のことを純粋仮想関数(pure virtual function)といいます。

 

現実だったら、なんて傲慢な親なんでしょうね。(笑)

 

純粋仮想関数は、以下図のFunc1()のようなイメージです。

 


  (Fig:3 純粋仮想関数)

 

この純粋仮想関数は

親クラスには実体はないけど、子供が絶対に実装します!と約束したものです。

なので、純粋仮想関数は、派生した子は必ず処理を実装する必要があります。

 

親は持っていないんだけど、

親クラスの型であるHogehogeBaseとしてFunc1()を呼び出せば、

必ず子クラスが処理を持っていることになるので、

親クラスの型でも、Func1()はあるものとして使うことができるのです。

 

 

さて本題です。

コールバック関数をC++で実現するには・・・・

 

感の良い方はもう気づいたでしょうかね?

コールバックを作る側は、

純粋仮想関数を定義した親クラスを作ってやればよいのです。

 

純粋仮想関数なので、クラス定義だけで十分です。

 

つまり、こんな感じ。

 

class MTimerCallBack
{
public:
   virtual void CallBack(int aParam) =0;
 };

 

実体の関数は、書かなくていいわけです。

 

これを提供して、利用する側はこれを継承した子クラスを実装し、

呼び出し側には以下のように、

子クラスのポインタを親クラスの型で渡してやればいいわけです。

 

SetCallBack(MTimerCallBack*  aCallBackClass)

 

 

周期呼び出しだったら、コールバック関数はいらないね。

 

 

とりあえず、

今日は親クラスの型で配列に入れられるところのみを実装してみるよ!

 

 

それでは、後述するといったコードの実装の記載方法です。

 

まずは、継承です。

今まで作っていたクラスの記載に、以下の文章を付けるだけです。

 

class CSegControlClass

{

・・・

・・・

};

class CSegControlClass :public MPeriodicCall

{

・・・

・・・

};

 

これだけです。

:public はおまじないだと思ってください。

 

たったこれだけで、継承ができます。

 

次に仮想関数

派生側には何も処理は必要ありません。

あえて言うなら関数の後ろにoverrideと書いた方がより明確ですね。

  void SetOutput(int aOutput);
 ↓

  void SetOutput(int aOutput)override;
 

親クラス側は、関数定義の前に virtual と書いて、仮想関数だよと定義する必要があります。

 

  virtual void SetOutput(int aOutput);
 

 

そして、純粋仮想関数にしたいときは、プロトタイプ宣言の定義の最後に =0 と書くだけです。

 

  virtual void SetOutput(int aOutput) = 0;
 

なんと簡単!

 

覚えてしまえば使い勝手が良いので、是非覚えましょう!

 

晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ

作ったコードは以下の通り。

 

◆継承用のクラス ”MPeriodicCall.h

 

#ifndef _MPERIODIC_CALL__
#define _MPERIODIC_CALL__

/*
 * @file    MPeriodicCall
 * @brief   Lesson B_2 
 * @author  SAI
 * @date    2021/06/27
 * @note    定期呼び出し用の呼び出し関数継承用のクラス
 */
class MPeriodicCall
{
public:
   virtual void SetOutput(int aOutput) =0;
 };

#endif //_MPERIODIC_CALL__

 

◆継承 SegControlClass.hの改造

 ↓で使ったコードの改変になります。

 

 

53行目のクラス定義を、継承する形に変更

 

class CSegControlClass

class CSegControlClass :public MPeriodicCall
 

60行目を

void SetOutput(int aOutput);

void SetOutput(int aOutput)override;

 

ちなみに、ソース側は修正不要です!

 

◆メインinoファイル

 

/*
   @file    CPP_L_B_2_TestCLASS
   @brief   Lesson B_2 Classをcppファイルに分離みよう。
   @author  SAI
   @date    2021/06/27
   @note    Classをcppファイルに分離みよう。
*/


/* *** Include List *** */
#ifndef __SegControlClass_
#include "SegControlClass.h"
#endif // __SegControlClass_

/* *** Define 定義 *** */
const int ASeg = 13;
const int BSeg = 12;
const int CSeg = 11;
const int DSeg = 10;
const int ESeg = 9;
const int FSeg = 8;
const int GSeg = 7;
const int DotSeg = 6;

const int LED_Port = 2;


const T7SegSetter C_7SegSetter =
{
  // ON/OFF情報を設定
  LOW
  , HIGH

  // 各ポートの番号を設定する/
  , ASeg
  , BSeg
  , CSeg
  , DSeg
  , ESeg
  , FSeg
  , GSeg
  , DotSeg
};


// 関数配列 //
// typedef void (*pFunc)(int aNum);
// pFunc FunctionList[3] ;


 MPeriodicCall *FunctionList[3];  //定期呼び出しクラスの配列 //

/* LED制御クラス */ 
class CLedCtrl :public MPeriodicCall
{
public:
  CLedCtrl(int aPort);
  void SetOutput(int aOutput)override;
  int iPort;
};

CLedCtrl::CLedCtrl(int aPort)
:iPort(aPort)
{
  pinMode(iPort, OUTPUT);
}


void CLedCtrl::SetOutput(int aOutput)
{
  if(aOutput%2 == 0)
  {
    digitalWrite(iPort, LOW);
  }else
  {
    digitalWrite(iPort, HIGH);
  }
}






/*
   @note  Arduinoで最初に呼ばれる関数
*/
void setup()
{
  //今回はなし
  pinMode(Keta1Seg, OUTPUT);
  pinMode(Keta2Seg, OUTPUT);
  pinMode(Keta3Seg, OUTPUT);
  pinMode(Keta4Seg, OUTPUT);
  digitalWrite(Keta1Seg, HIGH);
  digitalWrite(Keta2Seg, HIGH);
  digitalWrite(Keta3Seg, HIGH);
  digitalWrite(Keta4Seg, HIGH);
}


/*
   @note  ぐるぐるループ
*/
void loop()
{

  // ---------------  こんな方法で設定してもいいけど --------- //
  /* スタック上にクラスを生成 */
  //  T7SegSetter l7SegSetter;
  //
  // ON/OFF情報を設定
  //  l7SegSetter.iLED_ON = LOW;
  //  l7SegSetter.iLED_OFF = HIGH;
  // 各ポートの番号を設定する/
  //  l7SegSetter.iASeg = ASeg;
  //  l7SegSetter.iBSeg = BSeg;
  //  l7SegSetter.iCSeg = CSeg;
  //  l7SegSetter.iDSeg = DSeg;
  //  l7SegSetter.iESeg = ESeg;
  //  l7SegSetter.iFSeg = FSeg;
  //  l7SegSetter.iGSeg = GSeg;
  //  l7SegSetter.iDotSeg = DotSeg;
  //  CSegControlClass  lSegCtrlClass(l7SegSetter);

  // --------  ↓のようにconst定数を作って設定したほうが楽だよね --------- //

  /* 制御クラスを生成 */
  CSegControlClass  lSegCtrlClass(C_7SegSetter);
  CLedCtrl lCedCtrl(LED_Port); 

  /* 配列に並べる */
  FunctionList[0] = &lSegCtrlClass;
  FunctionList[1] = &lCedCtrl;



  /* 3つを同時に点灯 */
  for (int i = 0 ; i < 100; i++)
  {
    //moto    lSegCtrlClass.SetOutput(i);
    // NG    (void)(*FunctionList[0])(i);   //  1秒ごとにカウントアップ //
    FunctionList[0]->SetOutput(i);   //  1秒ごとにカウントアップ //
    FunctionList[1]->SetOutput(i);   //  1秒ごとに点滅 //


    delay(1000);
  }

  /* 関数終了時にスタックが解放されて、デストラクタが走る
    同時に、デストラクタでLEDを消灯する*/
}