LED_BUILTIN先が逆極性なヤツは偶に困る
Arduino互換機系の設計だと、LEDは普通"正極性"で光らせるようになっている。しかし世の中そんな設計ばかりとは限らない。
pinMode(LED_BUILTIN, OUTPUT); /* これだけでLEDが光るのが負極性 */
digitalWrite(LED_BUILTIN, HIGH); /* これでようやく光るのが正極性 */
Lチカするのならどっちでも良いんですよ。どうせ時系列で切り替わるから。
しかしHIGHとLOWの使い分けを常に一定に保ちたいなら、適宜 GPIOの入出力自体を"反転"させる。
/* 負極性と判明している場合のみ、次行を実行 */
pinControlRegister(LED_BUILTIN) = PORT_INVEN_bm; /* MacroAPI での記法 */
digitalWrite(LED_BUILTIN, HIGH); /* HIGHで必ず点灯できる */
ちなみに "Curiosity" や "XPlained" はだいたい”負極性”だ。正直どっちかに統一する決まりがある訳でもない。まあ回路設計する側面から見ると"負極性"のほうが GPIOに 数十mA 以上の大電流が流れても、マイコン自身の VCC(VDD) へ及ぼす電圧変動が少ないので、安定性向上に少しは寄与できる。GNDプレーン設計がガッシリしてる前提だけれど。
AVR-Dx系の電力設計は、VCC で +350mA、GPIO 1本あたり ±50mA だ。なので "吐き出し" だと 6本までしか MAX で吐けないが、"吸い込み" はこれを上回ることができる。
それで。ちょっと悩むのは作り捨ての応用記述で、とりあえず MPUが起動した(暴走も brickもしてない)ことを示すため、単に LED_BUILTIN を光らせたいケース。かつ同じ応用記述を複数機種で使い回す場合。公開サンプルスケッチなぞ特にそうですな。ちゃんと対応するならタイマー割込をコピペすれば良いのだが、いろいろやっててタイマーの空きがない場合や、割込が邪魔になる場合は、悩ましい。
それで最近使ってるコードは、まあある程度 LED_BUILTIN の実体物理ポートが限定されているという前提はあるものの、こんなやつ。
/* LED が PF2 の場合 */
EVSYS_CHANNEL0 = EVSYS_CHANNEL_USART0_XCK_gc;
EVSYS_USEREVSYSEVOUTF = EVSYS_USER_CHANNEL0_gc; /* EVOUTF: PF2 */
/* LED が PA7 の場合 (DxCoreやMegaCore使ってるならこれ一択) */
EVSYS_CHANNEL0 = EVSYS_CHANNEL_USART0_XCK_gc;
PORTMUX_EVSYSROUTEA = PORTMUX_EVOUTA_ALT1_gc; /* EVOUTA: PA7 */
EVSYS_USEREVSYSEVOUTA = EVSYS_USER_CHANNEL0_gc;
EVSYS_CHANNEL_USARTn_XCK_gcには、対応する USART 周辺機能が生きているなら、その設定ボーレートに応じた周波数の信号(HIGH/LOW)が常時出力されている。なので LED_BUILTIN 実体が EVOUTx と合致するなら、EVSYS(と必要ならPORTMUX)経由で信号分配してしまえば、Duty 比 50 % で明るさ 1/2 の LED点灯ができる。しかも特別なタイマーを別途用意する必要もない。Serial活動中なら USART が動いていないなんてこともありえない。これが目に見えて点滅してるなら WDT が効いてるとかの無限リセットループしてるのだとわかる。
しかしデータシートを読むとEVSYS_CHANNEL_USARTn_XCK_gcは、USARTが"同期モード"か"SPI主装置モード"の時のみ有効と書かれているが、なんで信号が出てるんだコレ?
また裏技見つけちまったか・・・懲りないな。
書いてないということは、あらゆる姉妹品種で同様に動作するかは不明ということ。
USARTボーレート周波数をセルフ観測する
一応これだけでも終わる話だが、USART を吐いている MPU自身で、その周波数をタイマー/カウンター測定によってセルフ検証することができる。USARTn_BAUDレジスタを読んで逆算すれば済むことでもあるのだが、計算式がF_CPU値に依存するのが面白くないし、LIN通信のような自動ボーレート設定されるモードを使ってると余計に現在値がわからなくもある。
それで。主クロックと独立で 32.768 kHz基準の周期イベントを吐ける PIT機能と、16-bit TCB0 カウンターを使って、こんなコードで可視化してみた。
void setup (void) {
/* 外部 Xtal 32.768 kHz 発振器が回路にあるなら、この2行を有効にする */
_PROTECTED_WRITE(CLKCTRL_XOSC32KCTRLA, CLKCTRL_ENABLE_bm | CLKCTRL_RUNSTDBY_bm);
loop_until_bit_is_set(CLKCTRL_MCLKSTATUS, CLKCTRL_XOSC32KS_bp);
/* 32.768 kHz 発振器で、高周波主発振器を自動校正する : AVR-DU の場合 */
uint8_t _reg = (CLKCTRL_OSCHFCTRLA & ~CLKCTRL_AUTOTUNE_gm) | CLKCTRL_AUTOTUNE_32K_gc;
_PROTECTED_WRITE(CLKCTRL_OSCHFCTRLA, _reg);
loop_until_bit_is_set(CLKCTRL_MCLKSTATUS, CLKCTRL_OSCHFS_bp);
/* Serial == USART1 として、PF2(LED)に USART1_XCLKを送る */
EVSYS_CHANNEL0 = EVSYS_CHANNEL_USART1_XCK_gc;
EVSYS_USEREVSYSEVOUTF = EVSYS_USER_CHANNEL0_gc;
/* 同時に TCB0 で、それをカウントする */
EVSYS_USERTCB0COUNT = EVSYS_USER_CHANNEL0_gc;
/* CH1で、イベントを捕獲できるようにする */
EVSYS_CHANNEL1 = EVSYS_CHANNEL_RTC_EVGEN0_gc;
EVSYS_USERTCB0CAPT = EVSYS_USER_CHANNEL1_gc;
/* TCB0 で、USART1_XCLKをカウントする */
TCB0_EVCTRL = TCB_CAPTEI_bm;
TCB0_CTRLB = TCB_CNTMODE_FRQ_gc;
TCB0_CTRLA = TCB_ENABLE_bm | TCB_CLKSEL_EVENT_gc;
/* PIT周期イベント 32Hz (32768/1024) を CH1 に送る */
/* これは AVR-DU/EB 以降の書き方(EVSYS世代 2.2)*/
RTC_PITEVGENCTRLA = RTC_EVGEN0SEL_DIV1024_gc;
RTC_PITCTRLA = RTC_PITEN_bm;
delay_millis(1000); /* Curiosity の VCP-CDC の起動が遅いので… */
Serial.begin(CONSOLE_BAUD);
while (Serial.available()) Serial.read();
Serial.println(F("\n<startup>"));
Serial.print(F("F_CPU=")).println(F_CPU, DEC);
Serial.print(F("_AVR_IOXXX_H_=")).println(_AVR_IOXXX_H_);
Serial.print(F("CONSOLE_BAUD=")).println(CONSOLE_BAUD, DEC);
Serial.flush();
}
void loop (void) {
/* 32Hz でキャプチャしてるので、1Hz精度に直すには 32倍して 8(倍速モードなら4)で割る */
const static uint8_t _dbl = ((USART0_CTRLB & USART_RXMODE_gm) == USART_RXMODE_CLK2X_gc) ? 4 : 8;
delay_millis(1000);
uint32_t _ccmp = TCB0_CCMP;
Serial.print(F("BAUD=")).print(_ccmp, DEC, 6).write(',');
Serial.print(_ccmp * _dbl, DEC, 7).write(',');
Serial.print((float)_ccmp * _dbl / CONSOLE_BAUD * 100, 2, 7);
Serial.println(" %");
Serial.flush();
}
(コードをきっちり読んだ諸兄なら)ん?その係数は何?(と思うだろうが、出力結果は以下)
<startup>
F_CPU=20000000
_AVR_IOXXX_H_=ioavr64du32.h
CONSOLE_BAUD=500000
BAUD= 61861, 494888, 98.98 %
BAUD= 61858, 494864, 98.97 %
BAUD= 61856, 494848, 98.97 %
BAUD= 61848, 494784, 98.96 %
BAUD= 61857, 494856, 98.97 %
BAUD= 61846, 494768, 98.95 %
BAUD= 61851, 494808, 98.96 %
BAUD= 61852, 494816, 98.96 %
非同期設定の USARTn がEVSYS_CHANNEL_USARTn_XCK_gcに通じて吐いてる周波数は、なんと設定ボーレートの 8倍なのだった。倍速モード(USART1_BAUD <<= 1; USART1_CTRLB |= USART_RXMODE_CLK2X_gc)であるなら 4倍に一致する。それでここでは 16-bitカウンターが ボーレート 500 kbpsまでは溢れないよう、キャプチャ周期を調整し 32 Hz で観測している。
32.768 kHz 発振器で、内蔵高周波主発振器を校正しても/しなくても、1 % 以上の誤差が観測されるとは予想外だったけど…んん?
データシートの USARTn_BAUD の説明を読むと、非同期 USARTへは RxD 多数決サンプリングのために ボーレートの 8ないし16倍の内部クロックを要しているとある。ここで観測された周波数はその説明と矛盾しない。そしてまた F_CPU/8 が設定可能な最速ボーレートであることも、ここから導くことができる。
ちょっと待てよ?
サンプリング周波数を 32 Hz としている時点で多少の精度落ちはありうる。だが 32-bit カウンターで 1 Hz キャプチャとしても有意差はなかった。しかし、自動校正を有効にしていてなお、1 % 以上のズレはどうも納得いかない。
modernAVR 系の USARTn_BAUD の設定値は、どの型番のデーターシートでも、次のような計算式で書かれている。二進固定小数部 6 bit、整数部 10 bit なので、64が下限値とされる。
/* 非同期標準速 */
USARTn_BAUD = (64 * F_CPU) / (16 * CONSOLE_BAUD); /* min limit 64 */
/* 非同期倍速 */
USARTn_BAUD = (64 * F_CPU) / (8 * CONSOLE_BAUD); /* min limit 64 */
/* 同期主装置 */
USARTn_BAUD = ((F_CPU) / (2 * CONSOLE_BAUD)) << 6; /* min limit 64 */
一方、classicAVR / reduceAVR 系では、整数部 12 bitだ。これらでは最後に1を減じる。そしてゼロ設定も許される。それゆえ式の途中で四捨五入する必要はない。
/* 非同期標準速 */
UBBR0 = (F_CPU) / (16 * CONSOLE_BAUD) - 1;
/* 非同期倍速 */
UBBR0 = (F_CPU) / (8 * CONSOLE_BAUD) - 1;
/* 同期主装置 */
UBBR0 = (F_CPU) / (2 * CONSOLE_BAUD) - 1;
観測された XCLK周波数からすると、modernAVR であっても、USARTn_BAUD レジスタの実体は "分周器設定" ではなく "タイマーカウンターの溢れ設定値" と見做したほうが自然だ。倍速設定ではそのタイマー周期の全体で、標準速では 1回飛ばしの半数回で RxDサンプリングを行っていると考えれば、classicAVR / reduceAVR 世代と同じメカニズムになる推測できる。だったら、USARTn_BAUD 設定値は、BOTTOM=0 から MAX までが 1周期の筈なので;
1を減じるのが正解なんじゃないか?
・・・やってみた。
<startup>
F_CPU=20000000
_AVR_IOXXX_H_=ioavr64du32.h
CONSOLE_BAUD=500000
BAUD= 62267, 498136, 99.63 %
BAUD= 62276, 498208, 99.64 %
BAUD= 62273, 498184, 99.64 %
BAUD= 62268, 498144, 99.63 %
BAUD= 62265, 498120, 99.62 %
BAUD= 62253, 498024, 99.60 %
BAUD= 62256, 498048, 99.61 %
BAUD= 62270, 498160, 99.63 %
念の為、2を減じたパターンも観測してみる。
<startup>
F_CPU=20000000
_AVR_IOXXX_H_=ioavr64du32.h
CONSOLE_BAUD=500000
BAUD= 62654, 501232, 100.25 %
BAUD= 62643, 501144, 100.23 %
BAUD= 62645, 501160, 100.23 %
BAUD= 62645, 501160, 100.23 %
BAUD= 62652, 501216, 100.24 %
BAUD= 62626, 501008, 100.20 %
BAUD= 62641, 501128, 100.23 %
BAUD= 62635, 501080, 100.22 %
うわあ・・・
-2のほうが理想値に近いのも意外だが、32.768kHz 計時発信器での測定誤差(F_CPUとCONSOLE_BAUDの両方を割り切れるか否かと割込発生間隔のブレ)を見越しても -1で 0.4 % 程度の誤差に収まってくる。
データシートの計算式は誤記載じゃないのか、コレ。
USART 分数ボーレート発生器とは
つまり、推測するとこういう構造なのだと思う
- USART 内蔵タイマーは
32 * F_CPU / (USARTn_BAUD + 1)Hz の PWM(Duty1:1)周波数を発生させ、XCLKへ送っている。これは8 * CONSOLE_BAUDに一致する。倍速ならUSARTn_BAUDには 2倍の数値をセットする。なので発生周波数は半分になり、4 * CONSOLE_BAUDに一致する。- これで 8-bit 相当のリングカウンタを回し、途中のビットタップをフリップフロップに繋いで、RxD 信号線の論理値を調べる回路なのだろう。
これを確認するためのコードは次のようになる。今度はEVSYS_CHANNEL_USARTn_XCK_gc をキャプチャ信号として、直接 CLK_PER(F_CPU)の所要カウント数を得てみる。
F_CPU=24000000L で CONSOLE_BAUD=500000L とした場合、1周期あたり 標準速では 6 CLK、倍速では 12 CLK になる筈だ。
void setup (void) {
/* Serial == USART0 として、XCLKを CH0 に送る */
/* 等速動作の TCB0 で、それを捕獲する */
EVSYS_CHANNEL0 = EVSYS_CHANNEL_USART0_XCK_gc;
EVSYS_USERTCB0CAPT = EVSYS_USER_CHANNEL0_gc;
TCB0_EVCTRL = TCB_CAPTEI_bm;
TCB0_CTRLB = TCB_CNTMODE_FRQ_gc;
TCB0_CTRLA = TCB_ENABLE_bm | TCB_CLKSEL_DIV1_gc;
delay_millis(1000);
Serial.begin(CONSOLE_BAUD);
#if 0 /* 標準速から倍速モードに再設定するなら 1 */
USART0_BAUD = ((USART0_BAUD + 1) << 1) - 1;
USART0_CTRLB |= USART_RXMODE_CLK2X_gc;
#endif
Serial.println(F("\n<startup>"));
Serial.print(F("F_CPU=")).println(F_CPU, DEC);
Serial.print(F("_AVR_IOXXX_H_=")).println(_AVR_IOXXX_H_);
Serial.print(F("USART_BAUD=")).println(USART0_BAUD, DEC);
Serial.print(F("RXMODE_CLK2X=")).println((bool)(USART0_CTRLB & USART_RXMODE_gm), DEC);
Serial.print(F("CONSOLE_BAUD=")).println(CONSOLE_BAUD, DEC);
Serial.flush();
}
void loop (void) {
delay_millis(1000);
uint32_t _ccmp = TCB0_CCMP + 1;
uint16_t _baud = USART0_BAUD + 1;
Serial.print(F("CAPT=")).print(_ccmp, DEC, 6).write(',');
Serial.print(_ccmp * 32, DEC, 6).write(',');
Serial.print(((double)_ccmp * 32 * 100) / _baud, 2, 7);
Serial.println(" %");
Serial.flush();
}
レジスタ設定値はいずれも普通のタイマーと同じであるとするなら、レジスタ値を読んで周波数に変換する前には+1で補正し、レジスタへ再設定する際には-1とするルールに従う。
標準速の測定結果;
<startup>
F_CPU=24000000
_AVR_IOXXX_H_=ioavr64dd32.h
USART_BAUD=191
RXMODE_CLK2X=0
CONSOLE_BAUD=500000
CAPT= 6, 192, 100.00 %
CAPT= 6, 192, 100.00 %
CAPT= 6, 192, 100.00 %
CAPT= 6, 192, 100.00 %
CAPT= 6, 192, 100.00 %
CAPT= 6, 192, 100.00 %
CAPT= 5, 160, 83.33 %
CAPT= 6, 192, 100.00 %
倍速の測定結果;
<startup>
F_CPU=24000000
_AVR_IOXXX_H_=ioavr64dd32.h
USART_BAUD=383
RXMODE_CLK2X=1
CONSOLE_BAUD=500000
CAPT= 12, 384, 100.00 %
CAPT= 12, 384, 100.00 %
CAPT= 12, 384, 100.00 %
CAPT= 12, 384, 100.00 %
CAPT= 11, 352, 91.67 %
CAPT= 12, 384, 100.00 %
CAPT= 12, 384, 100.00 %
CAPT= 12, 384, 100.00 %
CAPT= 12, 384, 100.00 %
ぴったり。たまに 1 CLK 少ない値が捕獲されて、そこで流れが乱れるが・・・32.768 Hz 基準観測値が下振れ気味だったのはこれが理由かな。
まとめ
なんだか最後は、予想外の方向へ深掘りしてくことになってしまったな。ほんとに「Lチカ」話かこれ?
EVOUT+LED 設定ができる回路設計の場合、LED_BUILTIN の物理ポートにオシロなりデジマルなりを繋いで周波数を測定し、8 で割れば、稼働中の USARTボーレートが一発でわかる。これはこれで嬉しい。しばらく放置していてモノの設定値がわからなくなって、ターミナルのボーレート値を闇雲に設定変更するような煩わしさからも解放される。特に意味もなく LED を点灯させとくだけよりも、よほど便利で役に立つだろう。消費電力だって点けっぱなしの半分だ。かわりに余計な ECM ノイズ発生源にもなりうるのだが。
LEDに繋ぐのではなく、UART対抗先の別のMPUで、この出力の周波数測定を行えると正確な UART同期速度を得ることができる。MPUが modernAVR 系統なら、単に 32パルスの所用経過数を自身の CLK_PER でカウントすれば、それがそのまま USARTn_BAUDレジスタへの設定値だ。それがもし 32以上 64未満なら、2倍して、USART_RXMODE_CLK2X_gcフラグも立てればよい。classicAVR でなら分数発生器ではないので、4パルスをカウントして 16で割れば UBBRn レジスタへ書く値にできる。
関連リンク
投稿者の人気記事
![[modernAVR] USARTボーレート周波数をセルフ観測する](https://res.cloudinary.com/elchika/image/upload/t_elchika_article_cover/v1/user/060cd109-19e8-40f6-8046-7d4f29a6d7a6/article/d08c7122-5d83-42ef-98fd-c6340f5b1093/pon4hhcagz8bm3hxbsjq.jpg)
![[modernAVR] AVR32LA32を使ってみる](https://res.cloudinary.com/elchika/image/upload/t_elchika_article_cover/v1/user/060cd109-19e8-40f6-8046-7d4f29a6d7a6/article/458fafd5-a755-4916-8eef-ff0c762fcfb0/fju7iiukzdlk3ujscgrl.jpg)
![[modernAVR] ひどいタクトスイッチのチャタリング除去と闘う](https://res.cloudinary.com/elchika/image/upload/t_elchika_article_cover/v1/user/060cd109-19e8-40f6-8046-7d4f29a6d7a6/article/221f78cf-4e6a-49c6-b6fe-ff14bd3bcaa8/mrbmeasnlgjs9fgtehgj.jpg)
![[modernAVR] 簡易ロジックアナライザーを雑に作る](https://res.cloudinary.com/elchika/image/upload/t_elchika_article_cover/v1/user/060cd109-19e8-40f6-8046-7d4f29a6d7a6/article/22c04885-8c5e-4ce2-b26d-1c1677608e44/vemgbt2ldp1wvxxhrrjb.jpg)

-
askn
さんが
前の日曜日の20:05
に
編集
をしました。
(メッセージ: 初版)
ログインしてコメントを投稿する