公開日:2020/04/29
title.png

10th Season


#10-6 328duinoをクロックアップ(完結編)

 前回、25MHzの水晶発振子を使って、328duino(第一形態)の動作を確認することができました。今回はArduino IDEからプログラムを書き込める様に、開発環境を整えます。

ブートローダのコンパイル

 まず、ブートローダに手を付けようと思います。Arduino IDEをインストールすると以下のディレクトリに各ボード用のブートローダが格納されています。

  <Arduino_IDE>\hardware\arduino\avr\bootloaders\

 <Arduino_IDE>はArduino IDEのインストールディレクトリです。手元のArduino IDEはバージョン1.8.11ですが、ATmega328用のブートローダは16MHz版と8MHz版しか見当たりません。クロックアップした25MHzで使用可能なブートローダを自分でコンパイルする必要があります。

コンパイル方法(正道)

 まず、コンパイルするブートローダはUNO等に使われているoptibootとします。

  <Arduino_IDE>\hardware\arduino\avr\bootloaders\optiboot\

 上のディレクトリ内に、「omake.bat」と言うファイルがあるので、これをWindowsのコマンドラインシェルから実行するとブートローダをコンパイルすることができます。これが正道。

 でも、手持ちの環境(1.8.11)ではコンパイルできませんでした。以下のファイルが見つからなかった様です。

  <Arduino_IDE>\hardware\tools\avr\utils\bin\make.exe

 何か、Windowsのシェルで動くmake.exeを探してきてインストールする必要があるみたいです。

コンパイル方法(邪道?)

 手元のPCには、Cygwinと言う環境がインストールされています。よって、Cygwinを使ってコンパイルしようと思います。Cygwinのインストールについてはこちらを参照してください。

 では、Cygwinを使ってoptibootをコンパイルする手順を示します。

 Cygwinの環境が立ち上がっていれば、そんなに難しいことはありません。

 なお、この時点でコンパイルしたブートローダは使えないので、注意して下さい。なぜか?↓↓↓
 どうやらGCC等のバージョンが、元々のoptibootをコンパイルした環境よりも新しくなっているせいで、コンパイルしたブートローダは512バイトに収まりませんでした。つまり、ブートローダ用のFlashメモリサイズ(512バイト)をオーバしているので、使えません。

 止むを得ないので、ブートサイズを一段階大きい1,024バイトに拡張してコンパイルしようと思います。テキストエディタでCygwin HOMEディレクトリにコピーしたoptiboot\Makefileを開き、適切な場所に以下の行を追加後、セーブしてください。

  atmega328_25MHz: TARGET = atmega328_25MHz
  atmega328_25MHz: MCU_TARGET = atmega328p
  atmega328_25MHz: CFLAGS += '-DLED_START_FLASHES=3' '-DBAUD_RATE=115200'
  atmega328_25MHz: AVR_FREQ = 25000000L
  atmega328_25MHz: LDSECTIONS = -Wl,--section-start=.text=0x7c00 -Wl,--section-start=.version=0x7ffe
  atmega328_25MHz: $(PROGRAM)_atmega328_25MHz.hex
  atmega328_25MHz: $(PROGRAM)_atmega328_25MHz.lst


※上のテキストデータは、最新版の328duinoライブラリをインストールすると、<My Documents>\Arduino\libraries\328duino\328duino_optiboot_mk.txtファイルに格納されています。

 Cygwinで【make atmega328_25MHz】を実行します。エラー無くコンパイルが終了すれば、「optiboot_atmega328_25MHz.hex」と言うファイルができたはずです。これが、25MHz版のブートローダファイルとなります。

 必要に応じて、「25」の所を20,24などに変更した行を追加してコンパイルしてください。

Cygwinのインストール

 と言っても、Cygwinのインストールとセットアップについて詳しく説明するつもりはありません。必要な方は、各自の力量でインストールお願いします。以下、手順を簡単に説明しておきます。

 Cygwinは、Windows上にUNIXライクな処理系を提供する環境です。デフォルトで起動するシェルはbashだったと思います。csh使いの筆者はtcshを追加インストールしてあるし、確か、makeコマンドも追加インストールしてあったような気がします。昔過ぎて忘れました。

 はい。正確に宣言しておきましょう。「詳しく説明するつもりはありません」 → 「詳しく説明できるほど覚えていません」が、正確なのです(1^^;)。ごめんなさい。

 setup.exeは、最初のインストールだけでなく、その後、パッケージを最新バージョンに更新したり、個別にツールを指定してインストールしたりと言ったメンテナンスにも使います。setup.exeを再度起動してパッケージの選択画面まで到達したら、その画面でtcshやmake等のツールを検索し、必要に応じてインストールしてください(下図)。

a106_cyginst.png

 本ページでは、Windowsのコマンドシェルを使うより、Cygwinでtcshを使った方が後々使い易いのでCygwinを選択しました。これからCygwinをインストールする方にとってはハードルが高いかもしれません。optiboot\omake.batを使った方が楽かも知れないですね。各自の力量と相談してコンパイル方法を選択してください。

 ただし本HPでは以降、Cygwinをインストールした前提で話が進みます。

328duinoライブラリの対応

 328duino(第二形態)で内蔵RC発振器をクロックアップして動作させるために作った328duinoライブラリ(「#9-9 328duinoをArduino IDEに組み込む」参照)を、第一形態のクロックアップ用に拡張します。
 ただし第一形態は水晶発振子で発振しますので、内蔵RC発振器の校正やクロック切り替え(A328duinoクラス、setSysClock()メソッド等)には対応せず、誤差を補正した_micros()関数のみ使用可能とします。
 プログラムを組む時に第一形態と第二形態を切り分ける必要が生じるかもしれないので、それぞれ別な#defineマクロを定義することにしました。

 この他、_micros()関数の、理想microsからの誤差を補正するfloat型の定数として、A328DUINO_ERRを定義しました。補正値の計算式は以下の通りです。

  A328DUINO_ERR=ideal.micros/_micros();

 この定数を使うと、_micros()関数で取得した時間から誤差を補正した正確な時間を求めることができます。

  uint32 usec_start;
  usec_start=_micros ();
   :
   :
  uint32 usec;
  usec=_micros () - usec_start;
  sprintf (lbuf,"elapsed time=%ld",uint32 (usec*A328DUINO_ERR));
  Serial.println (lbuf);


 float型の演算が必要ですので多用するのはお勧めしませんが、正確な時間を知りたい用途では力を発揮するはずです。

※細かいところに突っ込むと、float型は32ビットの単精度浮動小数点数で仮数部が24ビット程度なのに対して、補正対象の_micros()は32ビット符号無整数のため、float演算してしまうと_micros()の情報が一部欠落する場合があります。

 整数演算できる様に、以下の整数定数も用意しておきました。

  ideal.micros/_micros()=1+A328dMicrosErrInum/A328dMicrosErrIden
    …… A328dMicrosErrInumは、−1,0,1の、いずれか。
    …… A328dMicrosErrIden≧1

 相手が符号無整数なので、使い方がちょっとまどろっこしいのですが……、以下の様になります。

  uint32 usec_start;
  usec_start=_micros ();
   :
   :
  uint32 usec;
  usec=_micros () - usec_start;
  sprintf (lbuf,"elapsed time=%ld",
    usec + ((A328dMicrosErrInum<0)?
      -(usec/A328dMicrosErrIden): usec/A328dMicrosErrIden));
  Serial.println (lbuf);


 整数定数なので、A328DUINO_ERRに比べると少し誤差は大きくなりますが、_micros()の情報が欠落することはありません。

※整数演算だから速いか?と、言うと、32ビット符号無整数の割り算なので速くはありません。使い所を誤ると、時間の正確さ以前に時間軸方向の解像度を失って本末転倒になる恐れもあります。誤差を許容できる場面では、許容する(誤差を補正しないでそのまま使う)ことを推奨します。


 第一形態対応版の328duinoライブラリをリンクしておきます。

  328duinoライブラリ:328duino.zip

 オリジナルを差し替えてあるので、どこからダウンロードしても最新版です。インストール方法についてはこちらをご参照ください。

※micros()関数補正方法や、クロックアップ時のプログラムサンプルとして、328duinoライブラリに対応したLogiscopeを更新しておきました。logiscope.inoと、logiscope.olvの両方セットで更新する必要があります。

コアライブラリを改修する

 fcpu=25MHzで使う場合、millis()関数の誤差が1%を超えており、ちょっと嫌な感じです。しかし、millis()関数を修正するためには、コアライブラリのソースをいじるしか手がありません。さすがに、コアライブラリのソースに手を加えるのは避けてきたのだけど、そうとも言って居られなくなってしまいました。
 本ページではコアライブラリを修正することにしました。前節の328duinoライブラリも、コアライブラリの変更を前提として作られていますので、もし、コアライブラリの修正が嫌な方は、あきらめて20MHzまたは24MHzでお使いください。25MHz以外では、コアライブラリの修正は不要です。

 コアライブラリの修正手順を以下に示します。

 ちょっとハードルが高いかもしれませんが、これを実行すると25MHzにおけるmillis()関数とmicros()関数の誤差が、すっごく小さくなります(下表)。

F_CPU 理想millis millis()関数 誤差[%] 理想micros micros() 誤差[%] _micros() 誤差[%] 備考
16MHz 1.024 1.024 0.000 4.000 4.000 0.000 4.000 0.000  
20MHz 0.819 0.816 −0.391 3.200 3.000 −6.250 3.188 −0.391  
24MHz 0.682 0.680 −0.391 2.667 2.000 −25.000 2.656 −0.391  
25MHz 0.655 0.656 0.098 2.560 2.563 0.098 2.563 0.098

※25MHzの数値はコアライブラリ改修後の値です。オリジナルmicros()関数の値も改修されます。

 16MHz(2冪数MHz)と違って誤差0%という訳には行きませんでしたが、誤差0.098%ですので、まず、気になることは無いのではないでしょうか? → 1s=1,000msのカウントで約1msの誤差。

boards.txtへの組み込み

 328duino(第二形態)の時と同様にして、Arduino IDEのboards.txtに328duino(第一形態)のクロックアップ版を追加します。以下、手順です。

 Arduino IDEへの組み込みが終了したら、忘れずにブートローダを書き込んでください。書き込み手順は、「#9-4 ATmega328のヒューズ書き込み」を参照してください。

使ってみよう、328duino(第一形態)クロックアップ版

 これで、Arduino IDEを使った328duino(第一形態)の開発環境が整いましたので、動作確認用セルフテストプログラムとして使った円周率πの計算プログラムで試してみます。

 実際に使ってみる前に、脱線して円周率πの計算方法をご紹介しておきます。

円周率πの計算公式

 メモリも処理速度も限られたArduinoで、敢えてπを計算させる意味はないのですが、クロックアップした時にATmega328P-PUの正常動作を確かめるセルフテストプログラムとして開発しました。
 使用したπの公式は以下の通りです。

a106_calcpi1.png

 πの公式はマチンの公式、atan()の展開はGregory級数と呼ばれるものです。atan()の展開でk→大と、することで、より下の桁まで正しい値を計算できるようになります。必要なπの桁数に従って、それに見合ったkまで計算に算入します。

多倍長固定小数点数

 小数点以下数100桁まで計算しようとしても、float型なり、double型なりと言った既存の浮動小数点数では、有効数字(桁数)が足りず計算不能です。そこで、整数型配列変数を使った多倍長固定小数点数を使います。
 配列データint16 A[N]を多倍長固定小数点数とみなして、A[0]が整数部、A[1]×BASE-1、A[2]×BASE-2、……、A[i]×BASE-i、i=0〜N−1とします。即ち、各桁の数値をBASE進数と見て処理します。以下の様に表現することができます。

a106_calcpi2.png

 計算終了後、数値化する時にBASEを10のべき乗にしておくと処理が容易になります。多倍長固定小数点数用の配列として16ビット符号付整数型(int16)を使う場合、BASE=10,000が良いと思います。

 atan(1/x)を級数展開して、m×atan(1/x)を計算するには、C=m/((2k+1)×x2k+1)の多倍長固定小数値÷整数値の割り算が必要となります。なお、マチンの公式では、m=16または−4であり、x=5または239です。これ以外の値は取りません。
 m×atan(1/x)の計算手順は以下の通りです。

円周率πの計算

 マチンの公式より、atan()の級数C<k>を多倍長固定小数点数Aに足し込んでいく作業となります。

  A=A+(-1)k×C<k>

 この加算はA[i]+=(-1)k×C[i]で良い(carry save addition)のだけど、配列をint16にしてBASE=10,000とした場合、何度か加算を繰り返すうちにint16をオーバフロウしてしまうので、以下の様に部分的に桁上げ(キャリ)しながら計算します。

  加算結果A[i]≧BASEとなったら:A[i]−=BASE,A[i−1]++
  加算結果A[i]≦−BASEとなったら:A[i]+=BASE,A[i−1]−−

 本来は上の桁までひたすらキャリを伝搬すべきですが、処理時間がもったいないので、上の様に直上位桁だけ(部分的)にキャリを上げます(partial carry save addition)。

 部分的にしかキャリを伝搬していないので、2回のatan()演算終了後に下位桁から順次キャリ(ボロウ)を伝搬して行く必要があります(carry propagation)。

  A[i]<0なら:A[i]+=BASE,A[i−1]−−
  A[i]≧BASEなら:A[i]−=BASE,A[i−1]++
    ただし、i=N−1,N−2,……,2,1

 メモリは、πを計算するA[N]、係数m/x2k+1を保持するB[N]を確保する必要があります。m×atan(1/x)の級数項C[N]については、商(変数名quo)と剰余(変数名rem)を使って伝搬しながらA[N]に足し込んで行けば良いので、配列の確保は不要です。
 int16配列をN=LFP_MAX個確保するなら、必要なメモリは2バイト(int16)×LFP_MAX×2(AとB用)=4×LFP_MAXバイト必要です。ArduinoのRAM容量(2Kバイト)で賄う必要があり、1〜1.5[Kバイト]が限界ですので、LFP_MAX値で256〜384が限界でしょう。現在のプログラム(calc_pi.ino)では、LFP_MAX=256にしてあります。メモリ残量と相談して決めてください。
 この時、算出できるπの桁数は、小数点以下LFP_MAX×log10(BASE)−α桁となります。αは、数桁です。より下位桁からのキャリ伝搬を無視している分、最後の数桁は正しくありません。

  // start calculation
  int16 A[LFP_MAX]={ 0 };
  m_atan_1_x (A, 16, 5);
  m_atan_1_x (A, -4, 239);
  carry_prop (A);


 πの計算(上のコード)が終了すると、A[0]=3、A[1]=1,415、A[2]=9,265、A[3]=3,589、……のように、4桁づつ数値が格納されています(BASE=10,000の場合)。

 fmax等の測定用なので、演算後A[N]の32ビットLFSRを取って期待値と照合しています。LFSRについては「#6-10 真の乱数を生成する」を参照してください。
 下位桁の、πとしては間違っている桁のデータも含めて期待値を計算します。MCUの動作確認が目的なので、「正しく間違っている」ことの確認にも意味があるためです。

 PCで計算させた、π(小数点以下10,000桁)の期待値データをリンクしておきます。

  π(小数点以下10,000桁)期待値:calc_pi_10kdigits.txt

 PCで計算させた後、WEBで検索したπの値とも見比べてチェックしたので正しいと思います。プログラム(calc_pi.ino)が出力するπのフォーマットに合わせてあるので、シリアルモニタからコピー&ペーストしたテキストデータと、何らかのテキスト比較ツール(Cygwinのdiffなど)を使って文字列比較することが可能です。

忘れそうになった

 そうそう、328duinoクロックアップ版の動作確認でしたね。

  円周率計算プログラム:calc_pi.ino

 前回使ったのと同じプログラムです。Arduino IDEで開き、「ツールメニュー ⇒ ボード ⇒ 328duino」、「ツールメニュー ⇒ プロセッサ ⇒ ATmega328P (25MHz,5V)」を選択してコンパイル&書き込みしてください(下図)。

a106_328menu.png

 もし、ブートローダのコンパイルとか、ブートローダの書き込みとか、328duinoのクロック周波数とかに間違いがあれば、書き込みできずにエラーになるはずです。 → その場合は最初から手順を見直してやり直し。

 書き込みが成功したらシリアルモニタを開いて、文字化けしないことを確認して下さい。文字化けしなければ、指定した周波数にF_CPUマクロが設定されて、かつ、その周波数で328duino(第一形態)が動作しているということです。

  実験サンプル:【 】内はシリアルモニタからの送信データ
  【pi,1020】
  OK+ start calculation
  OK+ PI=3.
    1415926535 8979323846 2643383279 5028841971 6939937510
     :
     : (中略)
     :
    3809525720 1065485847
  OK+ digits=1020 (size=256)
  OK+ time=16119068[us]
  OK+ check=0x9c6bf0fa
  OK: pi F_CPU=25000000

 現在のプログラム(calc_pi.ino)で計算可能な最大桁数を計算させてみました。計算時間は、約16秒でした。Arduino UNO @16MHzだと、約25秒かかりますので、クロック周波数の比で高速化されています。バッチリ👌、OKでした。


 もし、ATmega328の定格内で使いたくて、UNO @16MHzより高速な動作が必要なら、fcpu=20MHzがお勧めです。UNOより25%程高速に動作することができます。「後ちょっと」届かない時に救世主となってくれることでしょう。


btn_arduino.png

btn_prev.png btn_next.png

home