「#9-7 内蔵RC発振器を校正する」でテーマにしたATmega328の内蔵RC発振器と同様、先日購入したATtiny2313にも内蔵RC発振器があります。今回のテーマは、ATtiny2313の内蔵RC発振器の校正です。
ATmega328と同様に、ATtiny2313もOSCCALレジスタに設定する値によって、発振周波数が変化するようになっています。ATmega328ではOSCCALレジスタは8ビットで、以下図の様にOSCCAL=128を境として低周波数側と高周波数側で不連続な特性を持っていました。
ATtiny2313ではOSCCALレジスタは7ビットで、4MHz用内蔵RC発振器と、8MHz用内蔵RC発振器が別になっているみたいです。4MHz発振器と8MHz発振器は、ヒューズのCKSEL3〜0ビットで切り替えて使うことができます。
このため、後ほど確認しますが、校正値も4MHz用と8MHz用の2個を持っています。ただし、リセット時、自動的にOSCCALレジスタにロードされる値は、CKSELビットにかかわらず常に8MHz版なので、4MHz発振器を選択して動作させる場合には、ユーザプログラムの先頭で、OSCCALに4MHz発振器用の校正値を書き込む必要があります。
4MHz発振器の校正値を読み出す方法(読み出し関数)については後ほど解説します。
「#9-4 ATmega328のヒューズ書き込み」で作成したプログラム(ser_prog.ino)を使って、ATtiny2313のヒューズ値等を確認しておきます。以下、UNOをホストとして使う場合の確認手順です。
実験サンプル:【 】内はシリアルモニタからの送信データ
OK: serial programming mode
OK: ATtiny2313(A) detected
RDY
【dmpb】
OK+ fuse: 0xff 0xdf 0x64
OK+ lock: 0xff
OK+ cali: 0x66 0x66
OK: sign: 0x1e 0x91 0x0a 0xff
これ(↑)が工場出荷時のデフォルト設定値でした。
前節で解説した様に、ATtiny2313には4MHzと8MHz、2個のRC発振器が内蔵されています。各々、4MHzと8MHzで動作させるための校正値が書き込まれており、それが「cali:」欄に記された2つの16進数(8MHz用と4MHz用)です。手元のATtiny2313では両方とも数値としては同じ0x66だった様ですが、一般的には4MHz用と8MHz用の校正値は異なるはずです。
ATTinyCoreボードを導入したArduino IDEで、ATtiny2313のヒューズを設定した後に同じことを実行すると、以下の様になります。
実験サンプル:【 】内はシリアルモニタからの送信データ
【dmpb】
OK+ fuse: 0xff 0x9f 0xe4
OK+ lock: 0xff
OK+ cali: 0x66 0x66
OK: sign: 0x1e 0x91 0x0a 0xff
上のヒューズ値の解釈を下表に示します。
ATtiny2313のヒューズ値(ATTinyCoreで設定後) | |||||
Fuse | data | bit | name | value | memo |
E | 0xff | 7 | − | 1 | 未定義 |
6 | − | 1 | |||
5 | − | 1 | |||
4 | − | 1 | |||
3 | − | 1 | |||
2 | − | 1 | |||
1 | − | 1 | |||
0 | SELFPGEN | 1 | セルフプログラム無効 | ||
H | 0x9f | 7 | DWEN | 1 | デバッグワイヤ無効 |
6 | EESAVE | 0 | EEPROM非消去 | ||
5 | SPIEN | 0 | シリアルプログラム有効 | ||
4 | WDTON | 1 | WDTCSRで制御 | ||
3 | BODLEVEL2 | 1 | BODレベル無効 | ||
2 | BODLEVEL1 | 1 | |||
1 | BODLEVEL0 | 1 | |||
0 | RSTDISBL | 1 | リセット端子有効 | ||
L | 0xe4 | 7 | CKDIV8 | 1 | システムクロックを分周しない |
6 | CKOUT | 1 | クロック出力禁止 | ||
5 | SUT1 | 1 | スタートアップ時間 | ||
4 | SUT0 | 0 | |||
3 | CKSEL3 | 0 | 内蔵RC発振器8MHz | ||
2 | CKSEL2 | 1 | |||
1 | CKSEL1 | 0 | |||
0 | CKSEL0 | 0 |
※水色に着色したビットは、ATTinyCoreの設定+ブートローダ書き込みにて設定可能です。
「#9-7 内蔵RC発振器を校正する」にて、ATmega328P-PUの内蔵RC発振器を校正するのに作成したプログラム(cali_rcosc.ino)をATtiny2313用に改造する必要があります。
ATtiny2313はFlash 2Kバイトしか搭載していないのですが、cali_rcosc.inoはホストArduinoとI2Cで通信しながら校正を実行します。ATtiny2313でWireライブラリを使った場合のメモリ消費量はこちらで調査済みです。Wireライブラリを使った時点で、最低でも1Kバイト以上のFlashメモリを使用してしまうので、なるべく機能を絞って、省メモリでプログラミングしてください。
オリジナルのcali_rcosc.inoは、指定したI/Oポートの状態(LOW/HIGH)でホスト側/ターゲット(校正される)側を切り替えて使えるようになっていましたが、ATtiny2313はホストになる必要は無いし、兎に角省メモリでプログラムする必要があるので、#ifdef等による場合分けではなく、ATtiny2313用にプログラム(tiny_rcosc.ino)を独立して作ってしまった方が楽です。
オリジナルのcali_rcosc.inoでは、16ビットタイマ(TC1)を使ってターゲットが出力したPWMの周波数を計測、8ビットタイマ(TC2)でターゲット側がPWM波形を出力していました。
ATtiny2313には、UNOのTC1互換16ビットタイマは搭載されているのですが、8ビットタイマ(TC2)に相当するタイマが空いていませんので、ATtiny2313.TC1を使ってUNO.TC2と同じPWM波形を出力するようにプログラムします(以下、コード抜粋)。
// TC1 setting
// OC1A toggle, fast pwm OCR1A mode 15, fcpu/8,
TCCR1A=0x40 + 0x03;
TCCR1B=0x18 + 0x02;
noInterrupts ();
OCR1A=0x00ff;
interrupts ();
OCR1Aは、こちらのATtiny2313端子表で20DIP端子番号の15ピンに割り当たっています。
ATtiny2313には、4MHz用と8MHz用2つの校正値があるのですが、リセット中に自動的にOSCCALレジスタへ設定されるのは常に8MHz用の校正値であるため、ヒューズのCKSELビットで4MHz発振器を選択した場合、ユーザプログラムの先頭で、4MHz用校正値をOSCCALレジスタに設定しなければなりません。
この校正値を読み出すため、DSIT(Device Signature Imprint Table)というテーブルがあります。
このテーブルの読み出し手順は少し特殊なので、DSITの読み出し関数を作っておきます。
DSITを読み出すには、SPMCSRレジスタのRSIGビットとSPMENビットに1を立てて、プログラムメモリ空間のアドレス0x00〜0x04のデータを読み出すことで所定のデータを読み出すことが可能となっています。プログラムメモリ空間のアドレスと読み出しデータの関係を下表に示します。
アドレス | データ | 内容 |
0x00 | 0x1e | シグネチャ0 |
0x01 | 校正値 | 8MHz用校正値 |
0x02 | 0x91 | シグネチャ1 |
0x03 | 校正値 | 4MHz用校正値 |
0x04 | 0x0a | シグネチャ2 |
AVRコアはハーバードアーキテクチャなので、プログラムメモリ空間にアクセスするには専用のlpm(Load Program Memory)命令を使う必要があります。また、先程説明したSPMCSRレジスタのRSIGビットとSPMENビットですが、フラグをセットすると、3×CPUサイクル後に自動クリアされる仕様になっています。確実に3×CPUサイクル内でアクセスするためにインラインアセンブラを使って記述しなければなりません(↓)。
int read_dsit (
uint8 adr)
{
// read DSIT(Device Signature Imprint Table)
// adr: range=0x01~0x04 for ATtiny2313
// return: dsit@adr
uint8 dat;
noInterrupts ();
__asm__ __volatile__ (
// set address to z register
"ldi zh,0x00\n\t"
"mov zl,%[adr]\n\t"
// SPMCSR=RSIG | SPMEN, read data in 3 cpu cycle
"ldi %[dat],%[spmcsr_rsig_spmen]\n\t"
"out %[spmcsr],%[dat]\n\t"
"lpm %[dat],z\n\t"
: [dat]"=&r"(dat)
: [adr]"r"(adr),
[spmcsr]"I"(0x37),
[spmcsr_rsig_spmen]"I"(0x21)
: "r31", "r30"
);
interrupts ();
return (dat);
}
SPMCSRレジスタを設定するout命令と、プログラムメモリ空間から読み出すlpm命令の間で割り込み処理が入ってしまうと、3×CPUサイクルなんてあっという間に経過してしまって読み出しを失敗するので、アセンブラの処理部分は割り込みを禁止してあります。
インラインアセンブラについては、過去記事で何度か使っていますので、逆引き索引などから当たってみてください。今回新出のテクニックとしては、クロバ(clobber)リストが上げられます。
クロバリストと言うのは、__asm__のカッコ内で、3つ目のコロン(:)に記述したレジスタリスト("r31","r30")の事です。クロバリストは、インラインアセンブラの中で、データを破壊してしまうレジスタをCコンパイラ(GCC)に教えてあげるためのリストになっています。
インラインアセンブラの中で、zレジスタ(r31:r30レジスタペアの別名)にアドレスを設定し、lpm命令でデータを読み出すため、zレジスタの値を勝手に変更してしまっています。AVRのGCCでは、zレジスタを汎用のアドレスポインタとして使っているので、勝手に変更してしまうとGCCが生成したコードとの間で不整合が起きて、期待通りに動かなくなってしまいます。
そこで、クロバリストです。「レジスタ"r31","r30"を使って、データを破壊しちゃうよ」と、GCCに教えてあげることで、GCCは__asm__の前後でzレジスタを退避・回復するコードを生成したり、あるいはzレジスタを使わずにコードを生成しようとします(諦めてエラーを吐いてくることもありますが……(1^^;))。
コンパイル後のアセンブラを眺めてみたところ、今回のプログラムでは、後者の方法(zレジスタを使わない)で回避していました。
ヒューズ(CKSEL)を設定して4MHz発振器を使う場合には、この(↑)方法で4MHz発振器の校正値(アドレス0x03)を読み出し、自分でOSCCALに書き込む必要があります。
※ちなみに筆者手元のATtiny2313では、4MHz発振器でも8MHz発振器でも同じ校正値0x66なので、4MHz発振器を使う場合、上の方法で校正値を読み出そうと出すまいと結果は同じです……(1^^;)。
校正を実施するためには、ホストArduinoとターゲットArduinoの2機が必要です。今回の実験では、ホストにはUNOを使い、ターゲットをATtiny2313とします。
UNOとATtiny2313を以下の通りに結線してください。ホスト⇔ターゲット間通信はI2Cです。
Arduino UNO | 接続 | ATtiny2313 | |
5V | → | 20 | VCC |
GND | ⇔ | 10 | GND |
D8(ICP1) | ← | 15 | D12(OC1A) |
A4(SDA) | ⇔ | 17 | D14(SDA) |
A5(SCL) | ⇔ | 19 | D16(SCL) |
D4 | GND |
※ATtiny2313の数値は20DIPの端子番号です。また、ATtiny2313のVCC⇔GND間にパスコンを接続しておいてください。
以下、校正(実験)中の様子です。
ホスト側UNOで動作させるプログラムを以下にリンクします。
ホスト側プログラム:cali_rcosc.ino
ターゲット側ATtiny2313で動作させるプログラムは以下です。
ターゲット側プログラム:tiny_rcosc.ino
各々、UNOとATtiny2313へコンパイル&書き込んでおいてください。Arduino as ISPを使ったATtiny2313へのプログラム書き込みについてはこちらを参照してください。
以降、ホスト側UNOのシリアルモニタを使って実験を進めます。
ホスト側シリアルモニタで受け付けるコマンドはこちらをご覧ください。今回、ATtiny2313に合わせて、以下の2点で拡張・追加してあります。
実験サンプル:【 】内はシリアルモニタからの送信データ
【dsit】
OK: dsit 1e 91 0a 66 66
↑ATtiny2313のプログラムで読み出したシグネチャ(0x1e,0x91,0x0a)と、内蔵RC発振器の工場出荷時校正値(0x66,0x66)が読み出せました。
【eval,127】
0,2907
1,2931
: (中略)
126,11827
127,12112
OK: eval
まず、8MHz発振器の校正データを取得しました。
カンマ区切りで、「OSCCAL設定値,その時の周波数[kHz]」が表示されますので、Excel等に貼り付けてデータ処理(グラフ作成など)してください。
続いて4MHz発振器の校正データを取りたいのですが、その為には、ヒューズのクロックモードCKSELを書き換えなくてはなりません。こちらに従って接続とUNOのプログラムを変更した後、以下の様にしてCKSELを変更します。
実験サンプル:【 】内はser_prog.inoシリアルモニタからの送信データ
OK: serial programming mode
OK: ATtiny2313(A) detected
RDY
【wrfl,e2】
OK? to write 0xE2 to fuse.l? send 'y' or 'n' > Yes, go
【y】 …… 確認してOKの場合。
OK: write fuse.l=0xe2
面倒ですが(1^^;)、再び、結線とUNOのプログラムをこちらに変更して、実験を続けます(↓)。
実験サンプル:【 】内はシリアルモニタからの送信データ
【eval,127】
:(省略)
OK: eval
※またまた面倒ですが、取得が済んだら忘れずにヒューズを8MHz発振器に戻しておきましょう【wrfl,e4】。
2回に分けて取得したデータを1つのグラフにまとめたのが下図です。
横軸にOSCCALの設定値、縦軸がその時のクロック周波数[kHz]です。
OSCCALレジスタは7ビットなので、0〜127までしか設定できませんが、横軸0〜127の範囲に4MHz発振器の校正データ、128〜255の範囲に8MHz発振器の校正データをプロットしています。よって、8MHz発振器の校正値OSCCAL=横軸−128となります。
雑感:
ATmega328と同様、内蔵RC発振器はEEPROMおよび、Flashの書き込み/消去時に時間を計測するために使われています。従って、OSCCALレジスタを使ってクロックアップした状態で、EEPROMやFlashの書き込み/消去を実施しないように注意してください。
ここまではATtiny2313の内蔵RC発振器の校正について書いてきたのですが、ついでなので、ATmega328の方も、整理しておきたいと思います。
今、手元には5個のATmega328P-PUが存在しています。単体で購入したATmega328P-PUが3個、UNO互換機が2機です。UNO互換機は2機ともATmega328P-PU搭載機で、ICソケット実装なので、その気になれば取り外すことが出来ます。
この内、内蔵RC発振器の校正データを取得したのは、最初にAmazonで購入した単体のATmega328P-PU、1個だけですので、残りの4個も全て測定してみました。以下表に校正結果をまとめます。
工場出荷 時校正値 |
最低周波 数[kHz] |
最高周波 数[kHz] |
8MHz 校正値 |
備考 | 対応(換装状況) |
0x96 | 4,887 | 15,598 | 0x5a | Iduino UNO互換機搭載 | →328duino用に使う |
0x9b | 4,749 | 15,248 | 0x9a | ELEGOO UNO互換機搭載 | |
0x9c | 4,679 | 15,000 | 0x9d | Amazonで購入 | |
0xaa | 4,481 | 14,157 | 0x67 | aitendoで追加購入 | →ELEGOO UNO互換機で使う |
0xaf | 4,300 | 13,663 | 0xaf | →Iduino UNO互換機で使う |
以降、それぞれOSCCALの工場出荷時校正値で同定しようと思います(ピンク着色セル、たまたまユニークな校正値だったので)。
雑感と対応:
UNO互換機のMCU換装手順については、敢えて詳細説明を省略します。改造は各自の自己責任で行うべきものですので、自力で確実に換装できる自信💪のある方のみ、実施してください。
ATtiny2313でも、OSCCALレジスタの値を書き換えて、12MHz程度まではクロックアップできることが判りました。ただ、12MHzって言う周波数も微妙(あまり速くない)だし、2Kバイトしかないメモリを圧迫してまでクロック切り替えコードを追加するモチベーションは無いかな?と、思いました。