はじめに
Arduino Nano Everyでマイコン工作に入門して、他の機種を買ってみたりもしつつ、次のステップとしてマイコンボードではなく素のマイコンを使ってみたかった。
https://www.youtube.com/watch?v=w5pkcX_LIHg
を見て、このちっちゃいやつを買うことにした。
続くテキストで触れているとおり、使い方や使い道を色々考えてみたい品なので、アイデアブックとして書き溜めていければ、というチラシ裏みたいな記事である。
前置き
ちっちゃ…(物理)
実際に実物を見るとSOP8って小さいよなあ…ってなる。写真は手近にあった箱コンとサイズ比較したもの。
開発環境
既存のArduino UNO R3互換機をライターにするjtag2updiとmegaTinyCoreを使って、Arduino IDEから利用する環境を構築した。
(すでに解説記事は多いので詳細省略)
Arduino UNO R4を使おうとしたらコンパイルが通らなくて、jtag2updiがATMega系のボード専用だったことに遅れて気づいたのは内緒だ。
ちっちゃ…(論理)
適当な既存のスケッチをコンパイルしたらフラッシュメモリサイズ超過で通らねえ!
とりあえずLチカで様子を見ると…
int pin_led = PIN_PA6;
void setup() {
// put your setup code here, to run once:
pinMode(pin_led, OUTPUT);
}
void loop() {
// put your main code here, to run repeatedly:
digitalWrite(pin_led, HIGH);
delay(500);
digitalWrite(pin_led, LOW);
delay(500);
}
最大2048バイトのフラッシュメモリのうち、スケッチが550バイト(26%)を使っています。
最大128バイトのRAMのうち、グローバル変数が10バイト(7%)を使っていて、ローカル変数で118バイト使うことができます。
Lチカしただけで1/4が埋まってしまったぞ。
ちっちゃ…(論理2)
#include <Wire.h>
void setup() {
// put your setup code here, to run once:
Wire.begin();
}
void loop() {
// put your main code here, to run repeatedly:
}
最大2048バイトのフラッシュメモリのうち、スケッチが982バイト(47%)を使っています。
最大128バイトのRAMのうち、グローバル変数が64バイト(50%)を使っていて、ローカル変数で64バイト使うことができます。
Wireライブラリをmasterとして読み込んだだけで半分が埋まった。
※megaTinyCoreでmaster/slave片方のみ読み込みのオプションを有効にした状態。
最大2048バイトのフラッシュメモリのうち、スケッチが952バイト(46%)を使っています。
最大128バイトのRAMのうち、グローバル変数が64バイト(50%)を使っていて、ローカル変数で64バイト使うことができます。
slaveとした場合でもやっぱり半分埋まる。
まじめな話をするならば
Arduino環境でコードを書こうとすると、ある程度リッチなフラッシュとメモリが求められるということがよくわかる。
小さいマイコンをガチで使おうとすると、Adruino環境じゃないコンパイル環境かアセンブラ開発になるんだろうと思っている。
でもホビーユースでそこまでガチりたくないな…とATTiny1614を買ってごまかすことで、まじめな話を回避する。
ホビーユースだったらESP32とかもっとリッチなものでいいじゃない、という反論からは目を背ける。
でもせっかくなので
ATTiny202で何か遊びたい。
「手段と目的」において、ATTiny202を使うことを目的に置く。
Plan 1 とりあえずPWM
なぜかLチカの次の定番練習みたいな扱いをしてしまう。多分最初のマイコン工作のマイルストーンに「余ったPCファンの利用」を置いたからだと思う。
標準のAnalogWriteで
ATTiny202を20MHzで稼働させる場合、約1200Hzで出てくる(誤差あり)。
PWM出力対応のピンは、普通のDIP8のピンの数え方でいうと7番ピン(GNDの隣)。コード上の数値では4。マクロ指定ではPIN_PA2。詳細は下記を参照。
https://github.com/SpenceKonde/megaTinyCore/blob/master/megaavr/variants/txy2/pins_arduino.h
レジスタ操作
XMEGAアーキテクチャということで、前回メモしたArduino Nano Eneryで使われているATmega4809とだいたい同じだった。
https://qiita.com/yukeyuke/items/8f9f55c778075d3bdf6c
Pin | TCA WOx (CMPx) | TCB WOx | Timer |
---|---|---|---|
PIN_PA1 / 4 | 1 | - | A0 |
PIN_PA2 / 5 | 2 | - | A0 |
PIN_PA3 / 7 | 0 / 3 | - | A0 |
PIN_PA6 / 2 | - | 0 | B0 |
PIN_PA7 / 3 | 0alt | - | A0 |
TimerAを使うとmillis()等に影響があることも同様。ただしmegaTinyCoreのほうでmillis()等の依存先Timerを変更するオプションが用意されているので、影響を出さずにTimerAをいじり倒すこともできるようになっていた。
さすがに駆動力の問題もあるようで、あまりにも周波数を高くすると波形の立ち上がりが苦しそうな感じになる。末尾のおまけ欄を参照。
ポテンショメーターでPWMの出力調整(約25kHz)。
const unsigned int SET_PER = 199;
unsigned int input = 0;
void setup() {
////fPWM_SS [Hz] = (fCLK_PER / DIV) / (PER + 1)
//fCLK_PER = 20MHz
//DIV = TCAプリスケーラー = 4
//PER = 分解能(0~255) = 199
//fPWM_SS = (20MHz / 4) / (199 + 1)
//fPWM_SS = (20000000 / 4) / 200)
//fPWM_SS = 5000000 / 200
//fPWM_SS = 25000 Hz
////TCAプリスケーラーを64(0b1011)から4(0b101)に ⇒ fPWM_SS (20000000 / 4) / 256 = 19531 Hz
TCA0.SINGLE.CTRLA = 0b101;
TCA0.SINGLE.PER = SET_PER;
TCA0.SINGLE.CTRLB = 0x23; //CMP1 = pin PA1
pinMode(PIN_PA1, OUTPUT);
pinMode(PIN_PA2, INPUT);
}
void loop() {
input = analogRead(PIN_PA2);
TCA0.SINGLE.CMP1 = map(input, 0, 1023, 0, SET_PER);
delay(100);
}
最大2048バイトのフラッシュメモリのうち、スケッチが610バイト(29%)を使っています。
最大128バイトのRAMのうち、グローバル変数が10バイト(7%)を使っていて、ローカル変数で118バイト使うことができます。
PWM信号をMOSFETのゲートとかに繋げてスイッチングする。
PCファンのPWM信号部への入力を想定して25kHzとしてみた。
本末転倒?ターゲット周波数を指定したら良い感じにしてくれるやつ
マイコンの動作周波数で計算が変わるので、とにかくだいたい指定の周波数を出すことにこだわりたい時のコードを試作してみた。duty解像度がまれによく結構犠牲になる。
一部雑なところもあるけど、そもそもユースケースが行方不明なやつなので、細かいことを気にしてはいけない。
⇒おまけ欄の波形チェックに使ったのでよしとしよう。
const long TARGET_HZ = 25000;
const bool UP_TO_TARGET = true; //指定をちょい下回る方にする
const long MAX_CPU_HZ = 20000000;
const long SUB_CPU_HZ = 16000000;
// 20MHzのときのプリスケーラー3種における周波数
const long SCALE1_MIN_HZ = 78125;
const long SCALE2_MIN_HZ = 39062;
const long SCALE3_MIN_HZ = 1200;
const int MAX_PER = 255;
int PER;
int input;
/*
累乗計算関数
普通のpowよりもサイズが小さくなる
コード引用元
https://blog.logicky.com/2020/03/10/032523
*/
unsigned long powpow(unsigned long x, uint8_t n) {
if(n == 0){
return 1;
}
unsigned long val = powpow(x, n / 2);
val *= val;
if(n % 2 == 1){
val *= x;
}
return val;
}
void setup() {
// 20MHz or 16MHz
long freqsel;
if((FUSE.OSCCFG & 0b00000011) == 0b01){
freqsel = SUB_CPU_HZ;
} else {
freqsel = MAX_CPU_HZ;
}
long cpu_hz;
long pdiv;
//div_enable ?
if((CLKCTRL.MCLKCTRLB & 0b00000001) == 0){
//disable = no div
pdiv = 1;
} else {
//enable = div
pdiv = powpow(2, (((CLKCTRL.MCLKCTRLB & 0b00011110) >> 1) + 1));
}
cpu_hz = freqsel / pdiv;
double div_cpu_hz = MAX_CPU_HZ / cpu_hz;
long div_prescaler;
if(TARGET_HZ >= SCALE1_MIN_HZ / div_cpu_hz){
div_prescaler = 1;
TCB0.CTRLA = 0b001;
} else if(TARGET_HZ >= SCALE2_MIN_HZ / div_cpu_hz){
div_prescaler = 2;
TCB0.CTRLA = 0b011;
} else {
div_prescaler = 64;
TCB0.CTRLA = 0b101;
}
for(PER = MAX_PER; PER > 0; PER--){
if(TARGET_HZ < ((cpu_hz / div_prescaler) / (PER + 1)) ){
if(UP_TO_TARGET){
PER++;
}
break;
}
}
// set PER
TCB0.CCMPL = PER;
// start output
TCB0.CTRLB = 0b10111; //pin PA7
//pinMode(PIN_PA7, OUTPUT); TCBはいらない
pinMode(PIN_PA2, INPUT);
}
void loop() {
input = analogRead(PIN_PA2);
// set duty
TCB0.CCMPH = map(input, 0, 1023, 0, PER);
delay(100);
}
最大2048バイトのフラッシュメモリのうち、スケッチが1592バイト(77%)を使っています。
最大128バイトのRAMのうち、グローバル変数が12バイト(9%)を使っていて、ローカル変数で116バイト使うことができます。
おまけ
PWMの周波数ごとの電圧立ち上がり
安物の小型オシロで見たやつを参考に。duty約50%で設定。
DIP変換…を基板ではなく
こんな面白そうなモノを見つけた。スプリング式で固定するDIP変換器。日本のショップにもあるのかな?未確認。
https://ja.aliexpress.com/item/1005007245799044.html?spm=a2g0o.order_list.order_list_main.29.6795585axyJQ7h&gatewayAdapt=glo2jpn
…ので、変換基板への半田付けじゃなくてチップのまま扱えるようにしてみた。配線そのものは、jtag2updiのリファレンスほぼそのまま。
ハーフピッチのユニバーサル基板
通常サイズのユニバーサル基板に実装するならDIP変換基板がいるけど、ハーフピッチならそのままちょうど半田付けできる。あちこちヘタなのは見逃して。
写真で拡大されたのを見ると、ちょこちょこ本当に怪しい箇所あるな…