2021年3月20日土曜日

crtbegin fail freePascal

 http://www.deepla.net/developmemo/lazarus/lazarus.html 参照


トラブルシューティング

コンパイル中にWarning: "crtbegin.o" not found,...メッセージが表示される

表示する

原因

ライブラリパスにcrtbegin.oがないため

対策

  1. 端末で、以下のコマンドを実行してcrtbegin.oファイルを検索します
    1. $ find /usr/lib -name crtbegin.o
    2. /usr/lib/gcc/i686-linux-gnu/7/crtbegin.o
    3. $
  2. crtbegin.oファイルがあるディレクトリをライブラリパスに設定します
    • 端末で、以下のコマンドを実行してfpc.cfgファイルを編集します(テキストエディタLeafpadを使用した場合)
      1. $ sudo leafpad /etc/fpc.cfg
    • 178行付近の-Flパラメータにcrtbegin.oファイルのあるディレクトリを追加し、保存します
      1. -Fl/usr/lib/fpc/$fpcversion/lib/$FPCTARGET;/usr/lib/gcc/i686-linux-gnu/7
参考: https://forum.lazarus.freepascal.org/index.php?topic=34288.0

2021年3月18日木曜日

 Linux real time 

https://team-ebi.com/arc/290 参照

GPIO入力を割り込み信号として最速応答速度(最小遅延)でアプリ利用

問題発覚:GPIO入力の応答時間は遅延する

GPIO入力の変化に応じて何らかのアクションを起こすプログラムを作る場合、定期的に(適当な時間sleepして)Readするか、poll等のイベントドリブン(この関数をコールすると、変化が発生するまで返ってこない)を用いると思います。

GPIO入力が変化してから、それを認識してアプリの動作に反映させるまでの時間にこだわらなければ、これがベストでしょう。

しかし、即 反応しなければならないアプリを作る場合、少々厳しいことになります。
ちょっと複雑なLinux環境でpollを使うと、数ms~数100msも遅れる場合があります。

そして、困ったことに、この遅延時間が安定しません。
数msで反応したと思ったら、次の変化では数100msも遅れた、という状態になるのです。

この現象はLinux等のOSでより顕著であるため、こういった遅延や応答速度のバラツキが問題になる基板や製品では採用できず、RTOSを採用せざるを得ない、というケースが多くあります。

以前携わったプロジェクトでも、同様な条件のためRTOSを採用する予定でしたが、「Linuxでも当社のプラットフォームなら1msで応答できます」「ほら見て!」というベンダーの言葉とデモを鵜呑みにしてLinuxを採用し、後から問題になったことがありました。

おそらく、ベンダーの言やデモは、他に何も邪魔するアプリケーションやドライバが稼働していない状態での話だったのでしょう。
あるいは、1msというのは平均値の話であり、最悪値の話ではなかったのでしょう。

最悪なのは、その問題が発覚したのがリリース目前で今更OS変えるなど考えられない時期だったことです。

そして、その問題を明らかにしたのが私でした。
なぜかというと、その応答部分のプログラムの担当にされたから、というのもありますが、前述の話を聞いた時から薄々気づいていたからです。

しかし、状況は前述のとおり最悪で、担当である私には逃げ場がありませんでした。

 

実証と現実:実際の応答速度は?

薄々感づいていた私は、できるだけ早くこの問題を明らかにする様に取り組みました。
プログラムが完成し、デバッグ段階に突入してからでは、さすがにどうにもならないだろうと考えたからです。
(もちろん最初に警告は発しましたが、のれんに腕押しだったので、証拠を突きつけるしかないのです)

方法は特記するほどのモノではなく、古典的にLEDに出す、というものです。

結果は、平均値は3ms~5msくらい、最速は約1ms、最悪値は100msを超えました。
実際、その基板のパフォーマンスはかなり高いので(少なくとも私が触れたことのある基板では最速)、ラズパイ等の汎用基板ではもっと悪い数値になるでしょう。

それに対する、要求仕様はというと…

「1msくらい?」

話にならない。

 

試行錯誤:応答速度を改善するには?

文句を言っても始まらないので、あの手この手で改善を試みました。

そもそも、この遅延の原因は何だろうか?

基板のパフォーマンスもLinuxのパフォーマンスもそこまで悪くはない。

そう、だから私も証拠無く反対できなかったのですが、その結果は私の予想よりも更に悪いものでした。

各プログラム(プロセスやスレッド)は、OS(Linuxカーネル)のスケジューラによって、順番に、時には交互に、実行されるようになっています。

GPIO入力ドライバが入力の変化をイベントとして発行したとき、何らかのプログラムが実行中の場合、そのブログラムが一旦止まるまで目的のプログラムが動けないので、イベントを受け取るタイミングが遅れてしまうのです。

しかし、優先順位という仕組みがあります。

あるプログラム(プロセスやスレッド)が動作中でも、より優先度の高いプログラムが条件を満たすと(イベント受け取りが発生すると)動作中のプログラムを止めて、優先プログラムが動作するわけです。

通常は優先順位はOSが適切な形になるようにコントロールしていますが、これを操作できます。

とりあえず、目的のプログラム(スレッド)の優先順位だけ上げてみたところ一定の効果を得ることができました。

数値にするのは難しいのですが、頻繁に10msを超えていた状況が改善され、概ね10ms以内に収まる様になった、という感じです。

しかし、これでは不十分です。

「時々、10msを超える」
「頻繁に数ms~10msくらいの遅延が出る」

これではまだ、だめなのです。

 

割込み応答速度(遅延)改善の切り札

追い詰められた私を救ったのは、あるエンジニアにもらったヒントでした。

シグナルというソフト割り込みの方が早いかも、と。

そして、人のよい彼はそのドライバをわざわざ公開してくれたのです。


gpiisig.c  make.zip  by ジン(team-ebiメンバー)


 

結果は劇的でした。

ほとんどの遅延が1ms以内に収まったのです。

 

 

そうして山を越え、無事にリリースにこぎつけることができました。

シグナルについては本にも載っていましたが、アプリ側(プロセスやスレッド)にばかり目が行っていました。

1つに集中して周りが見えなくなる悪い例だと思います。

やはり、一人で根を詰めるのはよくありませんね。

助け合いましょう!

 

というわけで、ドライバを動かすアプリも公開!

 


gpiiapp.c  by ふじ


お試しあれ!

TTimerでGPIO監視

 TTimerでGPIO監視


http://izawa-web.com/lazarus/lazarus.html

■ Raspberry Pi (ラズベリー・パイ) で Lazarus (FreePascal)

・2020/12/17 追記
 こちら↓の情報によると、下記の方法でインスト―ルされるのは、現時点では Ver.2.00 で、パッケージを追加できないのだそうです。
 つまり、PythonForLazarus をインストールできない。・・・ということのようです。
 「天晴の小部屋」様:Lazarus 2.0.8 for Raspberry Pi

 とりあえず Lazarus を使ってみたい時は、以下のインストール方法で大丈夫です。


ほぼ Delphi ですね。

インストールは、コマンドラインに、次のコマンドを入力するだけでいけました。(結構時間がかかります。)

sudo apt-get update
sudo apt-get upgrade
sudo apt-get install fpc
sudo apt-get install lazarus
sudo apt-get install libfbclient2

プログラム(例えば、projetc1)の起動は、
sudo ./project1
なんですね。



■ rpi_hal ユニットで、i2c を使う

 ADS1015(12ビット4チャンネルAD変換)を使ってみました。

 rpi_hal.pas ユニットを使っています。
 そのままでは、ランタイムエラーで起動できないので、
 メニュー-プロジェクト-プロジェクトオプション-コンパイラオプション-その他 のカスタムオプションに、
-dUseCThreads を追加します。
 終了時にエラー(例外クラス)が出る場合は、Uses 節の cmem をコメントアウトします。



unit ads1015Unit;
 
{$mode objfpc}{$H+}
 
interface
 
uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
  ExtCtrls,
  // 追加ユニット
  rpi_hal, cthreads, Unix;
 
type
 
  { TForm1 }
 
  TForm1 = class(TForm)
    Edit1: TEdit;
    Timer1: TTimer;
    procedure Timer1Timer(Sender: TObject);
  private
    { private declarations }
  public
    { public declarations }
  end;
 
var
  Form1: TForm1;
 
implementation
 
{$R *.lfm}
 
{ TForm1 }
 
procedure TForm1.Timer1Timer(Sender: TObject);
var
  s : string;
  ret : integer;
  v : double;
begin
  
  // $48 : ADS1015_I2C_ADDRESS
  // $01 : ADS1015_REG_POINTER_CONFIG
  // $00 : ADS1015_REG_POINTER_CONVERT
  // $F183 : 3CHを±6.144V (Gain 2/3) で変換 (0CH = $C183, 1CH = $D183, 2CH = $E183)
  // ±4.096V (Gain 1) の時は、 $F183 or $0200
  // 実際には、最大 VDD + 0.3V までしか測定できない
  
  i2c_string_write($48$01, #$F1 + #$83 , NO_TEST);
  sleep(1); // 変換待ち
  s := i2c_string_read($48$002, NO_TEST);
  if Length(s) >= 2 then begin
     ret := StrToInt('$' + IntToHex(Ord(s[1]), 2) + IntToHex(Ord(s[2]), 2));
     // 下位 4 ビットは無関係
     ret := ret shr 4;
     v := ret * ((6.144 2) / 4096);
     Edit1.Text := Format('%.3f', [v]);
  end;
end;
 
end.

■ PiGpio ユニットを使ってステッピングモーターを制御

 ステッピングモーター: ST-42BYG0506H (1回転ステップ数:200 基本ステップ角:1.8度)
 ドライバIC: TB6674PG
 ※TB6674PG(TA7774P互換)と、TA7774PGの違い
  
TB6674PG : モータ電源 Vs1A、Vs1B > 6.5V (標準12V)必要
  TA7774PG : モータ電源 Vs1A、Vs1B >= Vcc でOK

unit Unit4;
 
{$mode objfpc}{$H+}
 
interface
 
uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
  // 追加ユニット
  PiGpio;
type
 
  { TForm1 }
 
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormActivate(Sender: TObject);
    procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
  private
    { private declarations }
  public
    { public declarations }
  end;
 
var
  Form1: TForm1;
  GPIO_Driver : TIoDriver;
  Gpf : TIoPort;
 
implementation
 
{$R *.lfm}
 
{ TForm1 }
 
procedure TForm1.FormActivate(Sender: TObject);
begin
  if GPIO_Driver.MapIo then begin
    Gpf := GpIo_Driver.CreatePort(GPIO_BASE, CLOCK_BASE, GPIO_PWM);
 
    // GPIO20, 21, 16 を OUTPUT に
    // ICの 3 番ピンに接続(A)
    Gpf.SetPinMode(20, OUTPUT);
    // ICの 6 番ピンに接続(B)
    Gpf.SetPinMode(21, OUTPUT);
 
    // ICの 8 番ピンに接続(L:スタンバイ/ H:オペレーション)
    Gpf.SetPinMode(16, OUTPUT);
  end;
end;
 
procedure TForm1.Button1Click(Sender: TObject);
var
  i: integer;
  msec : integer;
begin
  if Gpf <> nil then begin
    msec := 10;
    Gpf.SetBit(16); // オペレーション
     // 200 ステップのモーターで、1回転
    for i := 0 to 49 do begin
      Gpf.ClearBit(20);
      Gpf.ClearBit(21);
      sleep(msec);
      Gpf.SetBit(20);
      Gpf.ClearBit(21);
      sleep(msec);
      Gpf.SetBit(20);
      Gpf.SetBit(21);
      sleep(msec);
      Gpf.ClearBit(20);
      Gpf.SetBit(21);
      sleep(msec);
    end;
    Gpf.ClearBit(20);
    Gpf.ClearBit(21);
    Gpf.ClearBit(16); // スタンバイ
   end;
end;
 
procedure TForm1.Button2Click(Sender: TObject);
var
  i:integer;
  msec : integer;
begin
  if Gpf <> nil then begin
    msec := 10;
    Gpf.SetBit(16); // オペレーション
     // 200 ステップのモーターで、1回転
    for i := 0 to 49 do begin
      Gpf.ClearBit(20);
      Gpf.ClearBit(21);
      sleep(msec);
      Gpf.ClearBit(20);
      Gpf.SetBit(21);
      sleep(msec);
      Gpf.SetBit(20);
      Gpf.SetBit(21);
      sleep(msec);
      Gpf.SetBit(20);
      Gpf.ClearBit(21);
      sleep(msec);
    end;
    Gpf.ClearBit(20);
    Gpf.ClearBit(21);
    Gpf.ClearBit(16); // スタンバイ
  end;
end;
 
procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction);
begin
  if Gpf <> nil then begin
    Gpf.ClearBit(20);
    Gpf.ClearBit(21);
    Gpf.ClearBit(16);
    GpIo_Driver.UnmapIoRegisrty(Gpf);
  end;
end;
 
end.

 TTimer Free Pascal Lazarus


Lazarusメニューの[ファイル]->[新規...]->[プロジェクト]->[アプリケーション]を選択して

新しいアプリケーションプロジェクトを開始します。

※この記事内のリンクでダウンロードしたサンプルアプリをロードするには[ファイル]->[開く]を選んで、ダウンロードして解凍したディレクトリ内にあるproject1.lprを選択します。

コンパイルして起動するには、lazarus左上の▶︎を押すだけです。

 

 

フォームの画面はこんな感じです。

 

細かいForm Propertyやその他については、下の方で、このプログラムのソースをダウンロード

できる様にしておきますので、そちらを開いて確認してください。

 

いろいろ解説を書いていますが、最初は何を言っているのか、

この行が何をしているのか、何のために必要なのかわからないと思います。

 

それは、極々普通ですので、諦めないでください。

しばらくこのソースの意味が解らなくても全然問題ないです。

 

フォームのデザインを変えたり、時刻のフォントを変更したり

はじめは意味が分からなくとも自分の好みに改造してみてください。

勿論、機能もです。

そうしているうちに、そんな簡単なことかと思うほど理解できるようになります。

前回でも述べましたが理解するより、とにかくがむしゃらに沢山の色々なプログラムにチャレンジしてください。

いつか必ず理解して、プログラムそのものを掌握する事ができるようになります。

集中力が全てです。本当にこれがやりたいのなら、何時間でも集中してやりましょう。

努力など全く必要ありません。

楽しんでやりましょうね。ww

 

以下はプログラム内容の説明です。

10分くらいで、適当に作成したので、間違っていたらごめんなさい。

 

 

unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, ExtCtrls;

type

  { TForm1 }

  TForm1 = class(TForm)
    BtnStart: TButton;
    BtnStop: TButton;
    BtnReset: TButton;
    LabelStopWMilSec: TLabel;
    LabelClock: TLabel;
    Label2: TLabel;
    LabalStopW: TLabel;
    Label4: TLabel;
    ClockTimer: TTimer;
    StopWTimer: TTimer;
    procedure BtnResetClick(Sender: TObject);
    procedure BtnStartClick(Sender: TObject);
    procedure BtnStopClick(Sender: TObject);
    procedure ClockTimerTimer(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure StopWTimerTimer(Sender: TObject);
  private

  public
    gTimerStartTime:TDateTime;

   //↑public〜endまでのエリアに変数を書くと、この変数がこのプログラム全体で参照できる様になります。
  end;

var
  Form1: TForm1;

implementation
   uses DateUtils;

   //↑時間等を扱うので、dateUtilsをインクルードします。
{$R *.lfm}

{ TForm1 }

procedure TForm1.FormShow(Sender: TObject);
begin
  ClockTimer.Enabled:=True;

  //↑フォームが表示された時のイベントで、時刻を表示するタイマーを起動します。
end;



procedure TForm1.ClockTimerTimer(Sender: TObject);
var
  clocktimerstr:string[8];

  //↑現在の時刻を保存する為の変数をローカルで宣言します。
begin
  clocktimerstr:=TimeToStr(Now);

  //↑処理がここに来た時点で、現在時刻を保存します。

  //なぜ保存するかと言うと、Nowを何度も発行してPCに負荷をかけない為です。
  if(clocktimerstr<>LabelClock.Caption) then //表示されている時刻と現在時刻が違った時のみ書き直します。
  begin                                                          //こうしないと、TimerのInterval毎に書き直される事になり、CPUに負荷がかかります。
    LabelClock.Caption:=clocktimerstr;
  end;
end;

procedure TForm1.BtnStartClick(Sender: TObject);
begin
  gTimerStartTime:=Now;        //タイマー[Start]ボタンが押された時刻を記録します。
  StopWTimer.Enabled:=True;  //タイマーを起動します。
end;



procedure TForm1.BtnStopClick(Sender: TObject);
begin
  if(StopWTimer.Enabled) then  //[Timerが動いている時のみ、以下の命令を実行させます
  begin
    StopWTimer.Enabled:=False; //Timerを停止させる
    StopWTimerTimer(Nil);         //時刻表示を更新
  end;

end;

procedure TForm1.BtnResetClick(Sender: TObject);
begin
  gTimerStartTime:=Now;  //リセットするので、スタートした時刻を現在に更新
  StopWTimerTimer(Nil);   //時刻表示を更新
end;

procedure TForm1.StopWTimerTimer(Sender: TObject);
var
  nowtime:TDateTime;  //TimerなのになぜTDateTime型を利用しているかというと、例えば23:59:59〜次の日の0:00:01を計測時に
  pasttime:TDateTime; //なったときに-23:59:58ではなく00:00:02と計算させるために日付も必要なのです。
  timerstr:string[8];     //単純にstringだけでも良いのですが、メモリーの資源を節約するのと処理速度を高めるために[8]というサイズを与えます。

                                //なぜ[8]かというと、"00:00:00"は8文字だからです。
begin
  nowtime:=Now;       //何度もNow関数を呼ばなくてもいいように一旦変数に記録します。
  pasttime:= gTimerStartTime-nowtime;  //pasttime(経過時間)はStartボタンが押されてから、現在まで。
  LabelStopWMilSec.Caption:='.'+IntToStr(MilliSecondOf(pasttime) div 100); //ミリセカンド用のlabelのみ他と分けている理由は

                //Timerのインターバル(100ms)毎にいちいちTimer時刻全体を書き直すような事がないようにするためです。
  timerstr:=TimeToStr(pasttime);
  if(timerstr<>LabalStopW.Caption) then  //いちいち書き直さないように、秒数が違った時のみ書き直します。
  begin
    LabalStopW.Caption:=timerstr;
  end;
end;
end.

 

 

 

このソースプログラムのダウンロード

ttps://drive.google.com/file/d/16N-XPxBPBxX8iDSX1UpfiaMKmh55pig6/view?usp=sharing