旧スタッフブログ記事からの転記となります。
今回は、ATtinyマイコン向けのOSCCAL値のキャリブレーション治具を紹介します。
おもちゃの修理やモノづくりの制御マイコンに8bit RISCアキーテクチャマイコンのATtiny13aやATtiny85を利用しています。
8ピンとIOは少ないのですが、GP-IOでのスイッチやドライバの制御やPWMも使えるので、重宝しています。SPIやUARTもソフトで実現すると、今回のような治具の制作もできます。
さて、通信販売ページの赤外線リモコン(ハピエスト リモコン)などを製作する際やUART通信を行いたい場合など、ライブラリで使用する内部タイマーの精度が必要です。
特に製造段階でキャリブレーション済みであろうOSCの値であっても、デフォルト値のままだとズレていたります。
実際の使用環境(内部発振か外部発振、周波数、電圧、周辺温度)に合わせてOSC値を計測しインプリします。
今回は、その最適値の計測治具とプログラムを紹介します。
開発環境:Arduino IDE(統合開発環境なので、コンパイラやエディタなど一式含む)
※先人様のおかげではあるのですが、統合開発環境なのでmakeファイルなども一式使わせていただけ、便利な世の中になりました。コンパイラ一つとっても、gccのバージョンなどいろいろありますからね。
ATtiny13aデータシート P23参照 ブロック図でいうところの青枠なのですが、精度を求めるなら、外部に水晶発振器を付けるということになるのですが、部品点数の増加や基板のスペース的なところもあって、内部発振器の精度でよければ、外付け回路無しで構成できます。
マイコン開発業務に携わった経験があればご存じかもしれませんが、低価格帯マイコンの出荷選別にコストは掛けれませんので、この数値は設計保証もしくは抜き取り検査だろうと思います。
インターネットなどでATtinyやその他マイコンの紹介記事を参照すると、内部RC発振を校正(最適なOSCCAL値のチェック)する記事をいろいろ拝見します。
当医院でもブレッドボードで最適なOSCCALを確認しているのですが、ちゃんと基板に起こしておこうという紹介になります。前振り長くてすんません。^^;
要件は、以下のような感じです。
- ユニバーサル基板でサクッと作れる。
- 電源は、使用環境での最適値を探索するため外部安定化電源から供給。
- OSCCALの調整は、プッシュスイッチで。
- OSCCALの値は、UARTからシリアルでターミナルソフトで確認。
- オシロスコープでクロックサイクルの波形を目視する。
- ターゲットマイコンは、ATtiny13aとATtiny85。ATtiny2313もそのうち。
当初、OSCCALの変更は、UART経由でキーボードで打ち込もうかと思っていたのですが、そもそも最適値でない場合、通信が文字化けして分からないことや、OSCCALの変更は、周波数の2%を超えるなということでプッシュスイッチの押下で±1づつを調整することにしました。 コンセプトとしては、ネット上でよく拝見する内容とほぼ同じとなります。
UARTでの通信とVCC電源ですが、Arduino Pro Mini用のシリアルI/F基板があり、ちょうど3.3V/5Vの2種の供給とUSB越しにUART通信ができるので、そのまま挿して接続できるようにしました。
3.3V/5V以外は、外部から給電できりょうにVCC/GNDピンを設け切り替えスイッチで切り替えるようにしています。
さて、キャリブレーションについてですが、ATtiny85のようにクロックアウト機能を有している場合は、FUSEビットを書き換えるなりで外部ピンにシステムクロックを出力させOSCCALを増減させジッタなりを評価してやればいいのかもしれませんが、ATtiny13aのようにシステムクロックを直に評価できない場合の手段が必要になります。
自分の場合は、インラインアセンブラにて、1CPUサイクルで実行できる命令にてポートにパルスを出力させるようにしました。
仕様書のISAの当たりにある表(Instruction Set Summary)で確認し、手っ取り早く、OUT命令が、1サイクルで実行されるので、適当なレジスタをスタッキングし退避させ、”HI” → “LOW” → “HI”とトグルさせてています。そのパルス信号をオシロスコープで周期を確認するといった具合にしています。
例えば、ATtiny13aを内部RC発振の1.2MHzで使用するとすると、デフォルト内部RC発振で起動します。
デフォルト値でCKSEL[1:0] = 10で9.6MHzで発振しますが、Fuse bitのCKDIVが立っているので、9.6MHz設定を8逓倍で回した場合、9.6MHz / 8 = 1.2MHzとなります。
因みに、SUT[1:0] = 10なので、longest start-up timeだそうです。
まぁ、ここら辺の基本的なお作法は、マイコンを使用上での基本中の基本なのでね。
1.2MHzの周期は、1/1.2M = 833nsをカーソルなりで計測し目視します。 同時にTeraTermに表示されているOSCCALの値が、最適値になります。
ATtiny85用コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
#include <SoftwareSerial> #include <avr/io> // PB0 キャリブレーション用パルス出力 // PB1 OSCCAL値をインクリメント // PB2 UART出力 9600bps // PB3 UART入力 // PB4 OSCCAL値をデクリメント // // RST .--+ VCC // UART in PB3 + + PB2 UART out // decrement PB4 + + PB1 increment // GND +--+ PB0 Output pulse // ATtiny85の場合 // ポートBのLOW期間パルス幅が、1000nsになるようにOSCCALを調整 // 8M / 8 = 1MHz = 1000ns = 1us #define SERIAL_TX 2 #define SERIAL_RX 3 SoftwareSerial Serial(SERIAL_RX, SERIAL_TX); // Serial pin RX:Pin#3, TX:Pin#2 void setup() { // @ No prescale // CLKPR = 0b10000000; // CLKPR = 0b00000000; // PORTB setting // Output PB0, PB2 Input PB1, PB3, PB4 // PB1, PB4 Pull up pinMode(0, OUTPUT); // Pulse output pinMode(1, INPUT_PULLUP); // OSCCAL increment pinMode(2, OUTPUT); // UART output pinMode(3, INPUT); // UART input pinMode(4, INPUT_PULLUP); // OSCCAL decrement Serial.begin(9600); } void loop() { uint8_t i; if(digitalRead(1) == LOW){ // PB1=L OSCCAL++; // OSCCAL++ Serial.println("++"); delayMicroseconds(16383); // delay insert while(digitalRead(1) == LOW) { // wait for until PB1=H Serial.println("wait..."); } delayMicroseconds(16383); // delay insert } else if(digitalRead(4) == LOW){ // PB4=L OSCCAL--;; // OSCCAL-- Serial.println("--"); delayMicroseconds(16383); // delay insert while(digitalRead(4) == LOW) { // wait for until PB4=H Serial.println("wait..."); } delayMicroseconds(16383); // delay insert } else { Serial.print("OSCCAL = "); Serial.println(OSCCAL); // Port B data register address = 0x18 // PORTB &= ~_BV(PB0); // PORTB |= _BV(PB0); asm volatile ("push r16" "\n\t" // PUSH r16 "push r17" "\n\t" // PUSH r17 "ldi r16, 0b00000000" "\n\t" // r16 = 0x00000000 "ldi r17, 0b11111111" "\n\t" // r17 = 0x11111111 "out 0x18, r16" "\n\t" // CPU 1 cycle "out 0x18, r17" "\n\t" // CPU 1 cycle "pop r17" "\n\t" // PUSH r16 "pop r16"); // PUSH r17 } } |
ATtiny13a用コード ATtiny13a用は、Software Serialが使用できないため、ATtiny13a用のライブラリを利用します。ライブラリの一部を更新しております。2022.3.3 追記
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
#include <picoUART> #include <pu_print> #include <avr/io> // PB0 キャリブレーション用パルス出力 // PB1 OSCCAL値をインクリメント // PB2 UART出力 9600bps // PB3 UART入力 // PB4 OSCCAL値をデクリメント // // RST .--+ VCC // UART in PB3 + + PB2 UART out // decrement PB4 + + PB1 increment // GND +--+ PB0 Output pulse // ATtiny13aの場合 // ポートBのLOW期間パルス幅が、833nsになるようにOSCCALを調整 // 9.6M / 8 = 1.2MHz = 833ns // コンパイルオプション Clock : 1.2MHz // 注意:ライブラリと整合が必要 // The follow info define in \libraries\picoUART-1.2.0\src\pu_config.h // #define PU_BAUD_RATE 9600L // #define PU_TX B,2 // #define PU_RX B,3 void setup() { // @ No prescale // CLKPR = 0b10000000; // CLKPR = 0b00000000; // PORTB setting // Output PB0, PB2 Input PB1, PB3, PB4 // PB1, PB4 Pull up pinMode(0, OUTPUT); // Pulse output pinMode(1, INPUT_PULLUP); // OSCCAL increment pinMode(2, OUTPUT); // UART output pinMode(3, INPUT); // UART input pinMode(4, INPUT_PULLUP); // OSCCAL decrement } void loop() { char buf[3]; // UART output char if(digitalRead(1) == LOW){ // PB1=L OSCCAL++; // OSCCAL++ prints("++\r\n"); delayMicroseconds(16383); // delay insert while(digitalRead(1) == LOW) { // wait for until PB1=H prints("wait...\r\n"); } delayMicroseconds(16383); // delay insert } else if(digitalRead(4) == LOW){ // PB4=L OSCCAL--; // OSCCAL-- prints("--\r\n"); delayMicroseconds(16383); // delay insert while(digitalRead(4) == LOW) { // wait for until PB4=H prints("wait...\r\n"); } delayMicroseconds(16383); // delay insert } else { itoa(OSCCAL, buf, 10); prints("OSCCAL = "); prints(buf); prints("\r\n"); // Port B data register address = 0x18 // PORTB &= ~_BV(PB0); // PORTB |= _BV(PB0); asm volatile ("push r16" "\n\t" // PUSH r16 "push r17" "\n\t" // PUSH r17 "ldi r16, 0b00000000" "\n\t" // r16 = 0x00000000 "ldi r17, 0b11111111" "\n\t" // r17 = 0x11111111 "out 0x18, r16" "\n\t" // CPU 1 cycle "out 0x18, r17" "\n\t" // CPU 1 cycle "pop r17" "\n\t" // PUSH r16 "pop r16"); // PUSH r17 } } /* void serOut(const char* str) { while(*str) TxByte(*str++); } */ // The end of file. |