2020年4月25日土曜日

ATtiny85のパルスジェネレータで迷走

この前、PICで簡易周波数カウンタ作りましたが
その際に確認用のパルスジェネレータを作りました。
中央下のやつです。

その途中、ほぼ見えてる地雷を踏んでしまったので(-_-;)
記録しておきます。
何せArduino初心者なもので...

作ろうと思ってた所に奇遇にも、YYさんからスケッチをいただきました!
10KHzのパルスでDUTYが5秒毎に25%⇒50%⇒75%と変化するものです。
やっぱ、ArduinoはPICに比べてとても簡単ですね~
*********
void setup() {
  pinMode(1, OUTPUT);
  
  // timer1 の設定 PWM出力for PB1(Digital 1 Pin)
  TCCR1 = (1 << CTC1) | (1 << PWM1A) | (1 << COM1A1);  
  //timer1 Prescaler 1/4 
  TCCR1 |= (1 << CS11) | (1 << CS10);  
  OCR1C = 200; //PWM周波数 CPUクロック8000KHz / 200 / 4(Prescaler)= 10KHz 
}

void loop() {
  OCR1A=50;  //Duty比 OCR1A/OCR1C=50/200 25%
  delay(5000);
  OCR1A=100; //Duty比 OCR1A/OCR1C=100/200 50%
  delay(5000);
  OCR1A=150; //Duty比 OCR1A/OCR1C=150/200 75%
  delay(5000);
}
*********

早速、マーティーのリビングでやると、
あれっ???
何度見ても、1周期 800μsec・・・1250Hzなのです(T_T)
delay(5000)も5秒ではなく、30秒以上なのです。
YYさんのオシロの動画では、9.96KHz(左下の方に表示)で、
DUTYは、5秒間隔で変化してたのです。
・・・RIGOL欲しいな~^^;
Clock 8MHz(internal)にしても、16MHz(PLL)にしても
1250Hzのまま変化しません(T_T)
なんで~?
PWMのClockが1/8になってるとしか考えられないのです。
ちなみにAliExpressから届いたばかりの新品の生ATtiny85なのです。
Timer1のClock 8MHzのハズなのですが
逆算すると
1250 x 4 x 200 = 1000000Hz = 1MHz
ここを変更してみるも変化なし(T_T)
そうだ!Clock 64MHzにする時は、Fuse bitを書き換えると聞いた記憶が!
64MHzに限らず、Clock変更は、Fuse bitを書き換える必要があるんだ!
今頃、気づくのであります(-_-;)
スケッチを書き込む前に
「ブートローダを書き込む」してFuse bitの書込を行なうと
あっさり、
1周期 100μsec・・・10KHzになりました(^O^)/
Timer1 Clockがやっと8MHzになったのです。
気づくのにえらく時間を費やしてしまいました(-_-;)
まあ、時間かかった分、記憶からは簡単に消えないでしょう^^;
新品のATtiny85は、
Clock:8MHz、Prescale Resister:1/8になってるんですね。
なので、Timer1 clock:1MHzだったのです。
US$ 2.24/2個(11% OFF、4個まで送料無料)
その後、日に日に送料が上がってま~す(T_T)

ちょっとFuse bitの勉強しようかと、
ATtiny85 DatasheetでFuse bitを探すと、P32に
6.5.2 CLKPR – Clock Prescale Register
がありますね~

よく読むと、Runtimeで書き換えできるようです。
気になるのが、
*********
2. Within four cycles, write the desired value to CLKPS while writing a zero to CLKPCE.
*********
4サイクル以内でどうのこうのとある。

*********
・Bit 7 – CLKPCE: Clock Prescaler Change Enable 
The CLKPCE bit must be written to logic one to enable change of the CLKPS bits. 
The CLKPCE bit is only updated when the other bits in CLKPR are simultaniosly written to zero. 
CLKPCE is cleared by hardware four cycles after it is written or when the CLKPS bits are written. 
Rewriting the CLKPCE bit within this time-out period does neither extend the time-out period, nor clear the CLKPCE bit.
*********

Google先生に翻訳してもらうと
*********
・bit 7 – CLKPCE:クロック・プリスケーラ変更の有効化
 ・CLKPSE bitの変更を有効にするには、CLKPCE bitに「1」を書込せねばなりません。
 ・CLKPCE bitは、CLKPRの他のbitに同時に「0」が書込された場合のみ更新されます。
 ・CLKPCEは、CLKCE又はCLKPSのbit書込後、Hardwareの4-cycleでクリアされます。
 ・その4-cycle期間内にCLKPCE bitを再書込しても、time-out期間は延長されず、
  CLKPCE bitはクリアされません。
*********

ここにも
• Bits 3:0 – CLKPS[3:0]: Clock Prescaler Select Bits 3 - 0
*********
These bits define the division factor between the selected clock source and the internal system clock. 
These bits can be written run-time to vary the clock frequency to suit the application requirements. 
As the divider divides the master clock input to the MCU, the speed of all synchronous peripherals is reduced when a division factor is used.
The division factors are given in Table 6-15.
To avoid unintentional changes of clock frequency, a special write procedure must be followed to change the CLKPS bits:
1. Write the Clock Prescaler Change Enable (CLKPCE) bit to one and all other bits in CLKPR to zero.
2. Within four cycles, write the desired value to CLKPS while writing a zero to CLKPCE.
Interrupts must be disabled when changing prescaler setting to make sure the write procedure is not interrupted.
The CKDIV8 Fuse determines the initial value of the CLKPS bits. 
If CKDIV8 is unprogrammed, the CLKPS bits will be reset to “0000”. 
If CKDIV8 is programmed, CLKPS bits are reset to “0011”, giving a division factor of eight at start up. 
This feature should be used if the selected clock source has a higher frequency than the maximum frequency of the device at the present operating conditions. 
Note that any value can be written to the CLKPS bits regardless of the CKDIV8 Fuse setting. 
The Application software must ensure that a sufficient division factor is chosen if the selcted clock source has a higher frequency than the maximum frequency of the device at the present operating conditions. 
The device is shipped with the CKDIV8 Fuse programmed.
*********
Google先生に翻訳してもらうと
*********
これらのbitは、選択したクロックソースと内部システムクロックの間のprescaleを定義します。
これらのbitは、Run-Timeに書込して、アプリケーション要件に合わせてクロック周波数を変更できます。
分周器がMCUへのマスタークロック入力を分周するため、分周係数を使用すると、

すべての同期ペリフェラルの速度が低下します。
分周係数を表6-15に示します。
クロック周波数の意図しない変更を回避するには、特別な書込手順でCLKPS bitを変更する必要があります。
1. CLKPCE bitを「1」に、CLKPRの他の全てのbitを「0」に書込します。
2. 4-cycle以内に、CLKPCEに「0」及び、CLKPSに必要な値を書込します。

Prescale設定を変更する時は、割込無効にして、書込手順が中断されないようにする必要があります。
CKDIV8 Fuseにより、CLKPS bitの初期値が決定されます。
CKDIV8 Fuseがプログラムされてない場合、CLKPS bitは「0000」にリセットされます。
CKDIV8 Fuseがプログラムされている場合、CLKPS bitは「0011」にリセットされ、

起動時に1/8のPrescaleになります。
この機能は、選択したクロックソースの周波数が

現在の動作条件でのデバイスの最大周波数よりも高い場合に使用する必要があります。
CKDIV8 Fuseの設定に関係なく、任意の値をCLKPS bitに書込できることに注意してください。
選択したクロックソースの周波数が現在の動作条件でのデバイスの最大周波数よりも高い場合、

アプリケーションソフトウェアは十分なPrescaleが選択されていることを確認する必要があります。
デバイスは、CKDIV8 Fuseがプログラムされた状態で出荷されます。

*********

「1.」と「2.」に
CLKPR:Clock Prescale Register や CLKPS: Clock Prescaler Select Bitsの
書き込み手順が、あります。
特にCLKPSは、4-cycle以内に書き込まねばならないようです。
ん~ん、なんか厄介そうですね~
まさかアセンブリ言語じゃないといけないのだろうか?

それと、Fuse bitがプログラムされた状態で出荷されてるので
起動時にFuse bitからCLKPSにコピーされ、「0011」になるので
Prescale 1/8になるのです。

このブロック図で、System Clockは、CKSELで制御されてます。

Table 6-6の(1)に
出荷時は、8MHzとありますね!
ここにも、default 8.0MHz clockとあります。
*********
6.2.3 Calibrated Internal Oscillator
By default, the Internal RC Oscillator provides an approximate 8.0 MHz clock.
*********
これで新品のATtiny85の挙動と辻褄が合ってスッキリです(^O^)/

さて、CLKPR:Clock Prescale Registerや
CLKPS: Clock Prescaler Select Bits は、
「Runtimeに書き込みできる」とあるのですが、
「CLKPSは、4-cycle以内に書き込まねばならない」ってのが引っかかります。

探してみると~~
ここに「Programming CLKPR」というのがありました!
スケッチのmain()の中で設定できることはわかったのですが
マーティーには「~_BV」や「_BV」は謎のコードであります(-_-;)
どうも「1 << CLKS0」や「0b00011000」じゃダメそうな気配ですね~
***CLKPR部だけ抜粋***
~略~
int main(void)
{
CLKPR=_BV(CLKPCE);
CLKPR=(CLKPR & (~_BV(CLKPS0) | ~_BV(CLKPS1) | ~_BV(CLKPS2) | _BV(CLKPS3)));
~略~
    while(1)
    {
~略~
     }
}
*********

CLKPRレジスタの中身は、これなので
上のは、CLKPS:0b0001 でPrescale:1/2だと思われます。

ここの「Arduinoの高速化」に理由が載っていました。

上の
CLKPR = _BV(CLKPCE);
は、
CLKPR = ( 1 << CLKPCE );

CLKPR = 0b10000000;
の意味で、PORTを制御するC言語のマクロらしいのです

「PB1をHIGH」を例にすると
digitalWrite(1, HIGH);
は、44-cycle clock
PORTB = _BV(1);
だと、3-cycle clockで実行されるということなのです。

つまり、先の
*********
CLKPR=_BV(CLKPCE);
CLKPR=(CLKPR & (~_BV(CLKPS0) | ~_BV(CLKPS1) | ~_BV(CLKPS2) | _BV(CLKPS3)));
*********
は、
Datasheet P32の6.5.2にあった
1. CLKPCE bitを「1」に、CLKPRの他の全てのbitを「0」に書込します。
2. 4-cycle以内に、CLKPCEに「0」及び、CLKPSに必要な値を書込します。
の手順において
「CLKPR=_BV(CLKPCE);」は、3-cycleで実行されるので
「2.」の条件「4-cycle以内」にCLKPSの設定を実行できるのです。

ちなみに、参考サイトの「Arduinoの高速化」には、(日本語で~す!)
「digitalWrite」と「_BV と ~_BV」を比較した波形写真もあり
とても参考になりましたm(_ _)m

では、まずは、新品の生チップに
このCLKPRの設定をしていないスケッチで書き込んでみます。
到着後、初めての書込です。
****スケッチ①*****
void setup() {
  pinMode(1, OUTPUT);       // PWM OUT PB1(pin6)
  
  // Timer1 の設定 PWM出力 for PB1
  TCCR1 = (1 << CTC1) | (1 << PWM1A) | (1 << COM1A1);
  //timer1 Prescale 1/4
  // "|="は、左辺 = 左辺 OR 右辺、上のTCCR1で設定したBITを消さないため 
  TCCR1 |= (1 << CS11) | (1 << CS10);  
  OCR1C = 199 ; //周波数固定、CPU Clock 8000KHz / (199+1) / 4(Prescale)= 10KHz

  OCR1A = 67; // Duty比 約30%
}

void loop() {
  // Freq. Duty固定なので、なにもなし
}
*********

Arduino IDE Ver.1.8.3の設定はこれです。
Timer1 Clock:CPU
Clock:8MHz(internal)
Arduino UNOの書込機を使ってるので[Arduino as ISP]です。
[ブートローダー書き込む]はしません。
AliExpressから届いた生チップで、これが1回目の書込です。
[書込装置を使って書き込む]だけします。
電源ON!
約800μsecなので1250Hzです。
この前、PIC 16G1827で作った周波数&DUTY計測器では、
1246Hzとでました。
Timer1 Clock = 1246 x 200 x 4 = 996800Hz ≒ 1MHz
になっているということです。
工場出荷時のSystem Clock:8MHzのハズなので
CLKPRのPrescale設定は、1/8になってるということになります。

では、void setup() { の最初に次のスケッチを追加してみます。
( 1 << CLKPCE ) も4cycle以内に実行されれば、
Prescaleが1/1に設定されるはずです。
*********
void setup() {
  // System Clock Prescale Initialize
  CLKPR = ( 1 << CLKPCE ); // CLKPR書込フラグセット
  CLKPR &= 0b00000000; // CLKPS:0000・・・Prescale 1/1

  pinMode(1, OUTPUT);       // PWM OUT PB1(pin6)


以下同じ
*********
これも[ブートローダー書き込む]はしません。
このチップさん2回目の書込です
[書込装置を使って書き込む]だけします。
電源ON!
お~っ!周波数が高くなってる!
ちと予想外であります。
100μsecなので10KHzです!
あっさりできてしまいました!
こっちも、10050Hzになっています。
「CLKPR = ( 1 << CLKPCE ); 」でも4-cycle以内に実行されるってことですね。
「CLKPR = _BV(CLKPCE);」と同じで3cycleで実行されてるんでしょう。
ただし、これは、Fuse bitが書き換えられたわけではないので
先のCLKPRの設定がないスケッチ①を再度書き込むと
1250Hzに戻ります。
Prescale 1/8に戻って、Timer1 Clock 1MHzになるのです。
これは、予想通りの挙動です^^;
では、
先のCLKPRの設定がないスケッチ①を書き込む前に
「ブートローダーへ書き込む」します。
設定項目は最初と同じです。
その後に、CLKPRの設定がないスケッチ①を
[書込装置を使って書き込む]します。
目出度く10KHzになりました。
スケッチでPrescaleを設定して
ダイナミックに変更することができることがわかったのですが
Prescaler前段のMaster Clockを8MHzや16MHzに変更したり
Timer1 Clockを32MHzや64MHzに変更するには、
結局「ブートローダーを書き込む」でFuse bitを書き換えしないと
反映されないのです。
ん~ん、今の所、使い道は、なさそうですね~

今の所、マーティーの用途では、
Arduino IDEの「ブートローダーを書き込む」で事足りると思います^^;

また、AVR StudioってのがFuse bitをGUIで読み書きできるようですが、
これもInstallしてません。

まあ、「_BV();」や「~_BV();」では
僅か3cycleで実行できて高速にポート制御できるのってのが
分かったのは収穫であります^^;
digitalWrite(); の代わりに
非常に高速にPORT制御したい時には、忘れずに使いたいものです。

それと「~」文字はフォントによって、位置が変わるんですね~

ATtiny85は、Timer Clockに64MHzが使えて、8ピンだし重宝しそうです。
16bit Timerがあれば更にいいのですがね~
まだまだ遊んでもらえそうなので(-_-;)
ちょっと在庫増やしておこうかな~^^;
10個だとここが安そうです。
US$ 12.31/10個(送料無料)
まあ、秋月電子の130円/個とほぼ同じだけど^^;

忘れた頃にまた見ないといけないので
後でよ~く眺めようと思ってる参考サイトを記録しておきます。

1.これ
「James Lewis」さん「Benchmarking Arduino’s digitalWrite() with a Logic Analyzer」
 Just how fast is digitalWrite()?
コードの違いによる、ポート制御の時間差の波形が面白い。

2.これ
「AVRWiki」の「I/O入出力ポート」ってところ。
I/Oレジスタ、入出力ポートの使い方。
一部のAVRには、PINB命令でbit-nに1を書込と、
PORTBのbit-nが反転するというおいしい機能があるそうです。
ATtiny85は使えそうです。

3.これ
Fuse bit(byte)の書き込みは、間違えると大変ですが
「しなぷすのハード製作記」さん
「「ArduinoISPを汎用AVRライタとして使う(2)」
Fuse bitの詳しい解説と
avrdudeをコマンドラインでのFuse bit(byte)の読/書があります。

4.これ
Qiitaの@autumn-positionさんの
「Arduinoプログラムの高速化」
基本的なArduino命令の書き換えが参考になります。

5.これ
「プログラミングテクニック集キヤミー 」さんの
「Arduino処理速度とメモリー使用改善の策」
ハンドブック的に使えそうです。

2 件のコメント:

JR3BGT さんのコメント...

Arduinoはフューズビットが壁の一つですね。(一からの開発時)
PICで最近遊んでますが、開発環境が激変していますね。
設定を正確にするのに、一苦労です。(5vや3.3vの指定やなにやかや)
今時アセンブラはおよびでない(-_-;)

マーティーの工房日誌 さんのコメント...

見ていただきありがとうございます。
やはりPICの開発環境は激変してますか~!
怖くてバージョンアップできませんね~(T_T)
アセンブラはZ80でしかやったことないです(-_-;)