※ 当ページには【広告/PR】を含む場合があります。
2021/11/06
【Arduino工作〜発展編】AVR-RustでAtmega328pからPWM波形を出力したい!
【Arduino工作〜発展編】AVR-RustでAtmega328pの割り込み処理を試そう!
RustでAVRマイコン機能を作り込んでいく技術ネタ紹介シリーズの第4段です。
前回はAtmega328pのPWM波形を発生させる方法を詳しく説明しました。
今回はAVRマイコン開発の際に、デバッグ機能としても重宝するUSARTを用いたシリアル通信を使いこなす方法を解説していきます。
Arduino Uno Rev3 ATmega328 マイコンボード A000066
組込みエンジニアの教科書
Atmel-ICE MCU AVR SAM Xmega プログラマー デバッガー
Atmel AT AVR ISP mk2 MKII ATMEL インシステムプログラマー USB AVRISP XPII デバッガー
実践Rustプログラミング入門
Outtag 5V 3A ACDCアダプター充電器 8種変換プラグ付き
USART接続例
まずはAtmega328pを使ってUSARTテスト用環境を以下のように構築します。
ここではシリアル通信用のモニタリングにArduino Unoとそれに接続したPCを使います。
Atmaga328pからのシリアル通信を確認出来るのが目的ですので、Arduino Unoで無くとも、他のRS232C接続可能なUSBコンバータがあればそれで代用してください。
Arduino Uno Rev3 ATmega328 マイコンボード A000066
組込みエンジニアの教科書
Atmel-ICE MCU AVR SAM Xmega プログラマー デバッガー
Atmel AT AVR ISP mk2 MKII ATMEL インシステムプログラマー USB AVRISP XPII デバッガー
実践Rustプログラミング入門
Outtag 5V 3A ACDCアダプター充電器 8種変換プラグ付き
外部発振器の設定
AVRマイコンのUSART機能を使う前に良く理解しておく必要があるのが、システムで利用する実質的なクロック周波数のことです。
USARTでのシリアル通信のボーレート値は、マイコンのクロック周波数ベースで決定されますので、このクロック周波数の考え方と設定方法を復習しておきます。
下位Fuseビットとその書き換え方法
AtmegaシリーズのマイコンにはFuseビットと呼ばれる特別なレジスタが3つあります。
詳しくは Atmegaデータシート などで確認してもらうとして、外部発振器の設定に直接関連する 下位Fuseビット
というレジスタを使い方を理解する必要があります。
ここではあまり重要ではないので深くは触れませんが、最初の7ビット目のCKDIV8は 0
の時に起動時にシステムクロックを8分周に初期化します(デフォルトで8分周)。
6ビット目のCKOUTは、CLKOピン(PB0)からシステムクロック波形を出力すること許可する設定です。 デフォルトは 1
で、これは出力不許可を意味しています。
5-4ビット目のSUT1-SUT0はリセットからの起動時間を選択します。
どのオプションが適切かはデバイスの電源を考慮して設定します。
デフォルトのSUT1-SUT0の値( 10
)となり、とりあえず余裕をもった最大の起動時間に設定されています。
3-0ビット目がクロック周波数を外部から取る上でもっとも重要なビット列になります。
クリスタルやセラミック発振器の設定は詳しく後述しますが、例えばそれ以外の設定ならば、
という感じに利用します。
128kHz内部発振器は非常に低い電力で動作するように利用されるクロック源です。 少電力で動作する反面、高精度用途では利用できません。
またここでの外部クロック信号とは、独立した外部デバイス(例えば他のマイコン)からクロック波形を供給する場合に利用します。 供給されるクロックが安定的に維持されるように、極力外部からのノイズの影響を受けない設計が必要になります。
また以上のことから、下位Fuseビットを出荷値ままデフォルト使うと、内部RC発振器の約8MHzを8分周するので、おおよそ1MHzがシステムの周波数ということになります。
Avrdudeコマンドによる下位Fuseビットの書き込み
Fuseビットの書き込みは実行ファイルからはできませんが、Avrdudeコマンドから直接マイコンを叩くことで変更できます。
書き込み装置は色々と選択できますが、例えばここではAtmel-ICEを利用しています。
以下のコマンドは下位FuseビットにAtmega328pの工場出荷値 0x62
を書き込んでいるコマンドです。
見てのように -U lfuse:w:0x**:m
の 0x**
の部分に下位Fuseビットの設定値を16進数で与えて書き換えることが可能です。
本記事の下位Fuseビットの話とは直接関係有りませんが、Avrdudeコマンドでの 上位Fuseビット
の値を書き換える際の注意点も紹介していました。
[Atmel-ICE on Linux] Debianからavrdudeを使ってブレッドボード上でAtmega328pのプログラムを書き込む 新品のAtmega328pにDebian Linux & Atmel-ICEでavrdudeを使ったプログラムをブレッドボード上で書き込みする手順を特集します。
書き込み装置によっては、不可逆な書き換えが起こってしまい、困った事態を招くかも知れませんので、Fuseビットの書き換えは慎重に行いましょう。
クリスタル/セラミック発振器のCKSELレジスタ
Arduinoや他のベアメタル製品などの設計で良く目にするように、クリスタル振動子やセラミック振動子などのクロック周波数を安定供給するためのディスクリート部品を使うための設定を説明しましょう。
大体のメーカーデータシートにも構成図が載っていると思いますが、どのマイコンにも発信素子の取り付け用のペアになっているピンが2つ設けられています。
市販の発振素子も様々な種類があり、これらをマイコンへ接続設定するとは言っても、取り付ける振動素子の適性を考えて選択する必要があります。
AVRマイコンでは主に 低電力発振器
・ 全振幅発振器
・ 低周波数発振器
の3つの設定に分かれます。
全振幅発振器
この設定では電気的にノイズが多い環境での利用や他の複数のデバイスへクロック入力を供給する場合に利用します。
対して、消費電力は常時高めになるので、低電圧にならないような電源もそれなりにパワーのあるもので設計を考慮する必要があります。
このため、この全振幅振動モードでは Vcc = 2.7~5.5[V]
の間でなければ動作しないことに気をつけなければいけません。
また推奨される駆動周波数は 0.4~20[MHz]
、グラウンド間のコンデンサC1(C2)の推奨容量は 12~22[pF]
となります。
またこの全振幅振動モードの設定では、CKSELビットだけでなくSUTビットも併せて細かい設定ができます。
下位Fuseビットの設定をまとめると以下の表のようになります。
設定ビット※1
リセットからの遅延時間※2
電圧低下からの復旧遅延時間
推奨条件
000110
14クロック + 4.1 [ms]
258クロック
外部セラミック振動子・高速上昇電源
010110
14クロック + 65 [ms]
258クロック
外部セラミック振動子・低速上昇電源
100110
14クロック
1kクロック
外部セラミック振動子・低電圧検出(BOD)リセットON
110110
14クロック + 4.1 [ms]
1kクロック
外部セラミック振動子・高速上昇電源
000111
14クロック + 65 [ms]
1kクロック
外部セラミック振動子・低速上昇電源
010111
14クロック
16kクロック
外部水晶振動子・低電圧検出(BOD)リセットON
100111
14クロック + 4.1 [ms]
16kクロック
外部水晶振動子・高速上昇電源
110111
14クロック + 65 [ms]
16kクロック
外部水晶振動子・低速上昇電源
※1) SUT1-SUT0-CKSEL3-CKSEL2-CKSEL1-CKSEL0の順序で6ビット分 なおこのモードは CKSEL3-CKSEL2-CKSEL1
までのビット部分は 011
で共通です。
※2) Vcc = 5[V]
の時の目安です。
この表でいうと、上から順に初回の起動時間等が遅くなっていきます。
実装する発信素子や電源のスペックに併せて下位Fuseビットを選択しましょう。
低電力発振器
据え置き型バッテリーなどで電源を供給する場合、出来るだけ少電力に済ませたい設計を求めるならば低電力モードも検討できます。
この低電力モードは先程の全振幅振動モードより更に細かく下位Fuseビットを設定することができます。
先程の表とほぼ内容は一緒ですが、起動に必要なVccの値はケースバイケースです。
設定ビット※1
リセットからの遅延時間※2
電圧低下からの復旧遅延時間
推奨条件
00---0
14クロック + 4.1 [ms]
258クロック
外部セラミック振動子・高速上昇電源
01---0
14クロック + 65 [ms]
258クロック
外部セラミック振動子・低速上昇電源
10---0
14クロック
1kクロック
外部セラミック振動子・低電圧検出(BOD)リセットON
11---0
14クロック + 4.1 [ms]
1kクロック
外部セラミック振動子・高速上昇電源
00---1
14クロック + 65 [ms]
1kクロック
外部セラミック振動子・低速上昇電源
01---1
14クロック
16kクロック
外部水晶振動子・低電圧検出(BOD)リセットON
10---1
14クロック + 4.1 [ms]
16kクロック
外部水晶振動子・高速上昇電源
11---1
14クロック + 65 [ms]
16kクロック
外部水晶振動子・低速上昇電源
※1) SUT1-SUT0-CKSEL3-CKSEL2-CKSEL1-CKSEL0の順序で6ビット分
このモードでは CKSEL3-CKSEL2-CKSEL1
までのビット部分は以下の4つから選択できます。
※2) Vcc = 5[V]
の時の目安です。
低周波数発振器
このモードは時計用32.768kHz水晶振動子に最適化するような特別なモードです。
特にマイコンによって正確な時計機能を実装する必要がある場合に利用されますが、ここでは関係ないので省略します。
CLKPRレジスタの設定
CLKPR(クロック前置分周)レジスタは、システムの基本クロック周波数を分周させるためのバイトを記述します。
まず7ビット目はCLKPCEと呼ばれ、クロック分周の値を変更することを許可・不許可を設定できます。
分周数の変更を許可する場合、CLKPCEに 1
を書き込みます。
ただし注意が必要なのが、CLKPCEビットへの書き込みはCLKPS3~0の全ビットが 0
である時だけ更新することができます。
さらに、CLKPCEに 1
を書き込んでからクロックの変更を許可したら、4クロック後以内にCLKPS3~0ビットに書き込まないといけません。
なぜかというと、CLKPCEは 1
を書き込んで4クロックでハードウェア側から許可解除され、再び 0
に戻る仕様だからです。
3~0ビット目のCLKPSレジスタでクロック分周値を定義します。
分周値が変更されると、直ちに分周器がMCUへのメインクロック周波数を分周し、それに伴って全ての周辺機能のクロック速度がそれに従います。
CLKPS3-CLKPS2-CLKPS1-CLKPS0
の順番で以下のようなビット列で分周数を設定することが可能です。
ということでここでの要点をまとめると、CLKPRの書き換えにおいて、
ということを覚えておきましょう。
Arduino Uno Rev3 ATmega328 マイコンボード A000066
組込みエンジニアの教科書
Atmel-ICE MCU AVR SAM Xmega プログラマー デバッガー
Atmel AT AVR ISP mk2 MKII ATMEL インシステムプログラマー USB AVRISP XPII デバッガー
実践Rustプログラミング入門
Outtag 5V 3A ACDCアダプター充電器 8種変換プラグ付き
USARTについて
ここからはようやく首題のUSARTに触れていきます。
UBRRレジスタとボーレート
前置きで、システムのクロック周波数の話を詳しく取り上げたのも、USARTシリアル通信の速度であるボーレート(単位;bps)を正しく換算するためでした。
この値を設定するレジスタが、 UBRR
です。
このボーレート設定値 R B A U D R_\mathrm{BAUD} R BAUD とすると、実行クロック周波数を f o s c f_\mathrm{osc} f osc 、ターゲットボーレート k B A U D k_\mathrm{BAUD} k BAUD とおく時、 標準速非同期モード
の場合に以下の式で与えられます。
R B A U D = f o s c 16 k B A U D − 1 \displaystyle{
\begin{aligned}
R_\mathrm{BAUD} &= \frac{f_\mathrm{osc}}{16 k_\mathrm{BAUD}} - 1
\end{aligned}
} R BAUD = 16 k BAUD f osc − 1 Eq. (1)
なお、ボーレートの設定は標準速非同期モードの他に、より高速な通信を行うための 倍速モード
があります。 この記事では倍速モードは利用しませんが、利用する場合には後述のUCSRAレジスタのU2Xビットに1を書き込んで設定を許可します。
例えばクロック周波数が 8[MHz]
である場合、 9600[bps]
狙いとしてボーレートの設定値を計算してみましょう。
R B A U D = f o s c 16 k B A U D − 1 = 8 [ M H z ] 16 × 9600 [ b p s ] − 1 ≃ 51.083 \displaystyle{
\begin{aligned}
R_\mathrm{BAUD}
&= \frac{f_\mathrm{osc}}{16 k_\mathrm{BAUD}} - 1 \\
&= \frac{8\ \mathrm{[MHz]}}{16 \times 9600\ \mathrm{[bps]}} - 1 \\
&\simeq 51.083
\end{aligned}
} R BAUD = 16 k BAUD f osc − 1 = 16 × 9600 [ bps ] 8 [ MHz ] − 1 ≃ 51.083 Eq. (2)
小数点以下切り捨てで、UBRRレジスタには 51
を2進法で書き入れることになります。
なお、UBRRは10ビットの非負の整数をとりますので、2バイト分の大きさのレジスタとなります。 上位のレジスタは UBRRH
、下位のレジスタは UBRRL
で区別します。
この2バイト分の書き込みには少し工夫が必要になるかも知れません。 Rustでの利用方法は後ほど実装で解説しましょう。
UCSRレジスタとUDRレジスタ
USARTの制御方法や状態を保持する一般設定を行うレジスタは、 UCSRA
・ UCSRB
・ UCSRC
の3つのレジスタ内のビットに書き込みます。
AVRマイコンのUSARTで送受信されたデータはUDRレジスタに一時的に緩衝され、ユーザーはこのUDRレジスタをアクセスすることでデータをやり取りすることできます。
よってUSARTからデータ送信したい場合にはUDRレジスタに書き込むことを意味します。
まずはUCSRAから説明すると、例えばUCSR0Aレジスタが以下にようなビット列になります。
重要なビットだけ掻い摘むと、ここでは5ビット目のUDRE0(USART Data Register Empty)が特に重要です。
UDRE0はUSARTから送信待ちになっているデータレジスタが空いているかどうかを教えてくれるフラグ用のビットです。 よって読み取り専用のビットで書き込みは不可です。
UDRE0フラグを読み取ることで、送信緩衝レジスタのUDR0が新規のデータを受け取ることが出来るかどうかを判別することができます。
シリアル通信で送信されるデータを配置するキュー配列のようなレジスタ UDR
に書き込むことで、1バイトずつ送信していきます。
また UDR
にデータを書き込んだ時点で、UDREが 0
の状態になります。
送信が完了してUDRレジスタが空になると、UDREが 1
の状態に戻り、再びUDRにデータを書き込むことが可能です。
つまり、 UDRE0が1
ならばUDR0は空で新規の送信用データが書き込みできます。
逆に UDRE0が0
ならば未送信のデータがレジスタ内にまだ残っているので、新しい送信データを書き込む準備ができていません。
次にUCSRBレジスタですが、UCSR0Bを例に取ると、以下のようなレジスタ構成になってます。
このレジスタで重要なのは、4−3ビット目の RXEN
(Receiver Enable)と TXEN
(Transmitter Enable)です。
その名の通り、ビットに 1
を書き込んでRxピンとTxピンを起動します。
2ビット目の UCSZ2
は飛び地的にUCSRBレジスタに入ってますが、後述するUCSRCレジスタの設定値の一部ですのでそちらで説明します。
最後のUCSRCレジスタは、USARTの動作状態を設定するレジスタです。
例えばUCSR0Cは以下のようなレジスタ構成です。
始めの7−6ビット目のUMSEL1とUMSEL0が 00
の場合、USARTは非同期処理で動作します。
5−4ビット目のUPM1とUPM0でシリアル通信のパリティモードを選択できます。
パリティビットはエラー検出などに利用できますが、今回は使わないので、 00
で無しの設定にしておきます。
3ビット目のUSBSは停止ビットの数を選択します。 必要な停止ビット1つなら 0
、2つなら 1
を書き込みます。
2−1ビット目のUCSZ1とUCSZ0は、先程のUCSRBの2ビット目のUCSZ2を加味して、送信するデータのビット長を設定します。
シリアル通信は大体8ビットで1フレームで送信する形式が多いと思います。
デフォルトもUCSZ2-0は 011
であり、8ビットデータ長の通信ベースになっています。
Arduino Uno Rev3 ATmega328 マイコンボード A000066
組込みエンジニアの教科書
Atmel-ICE MCU AVR SAM Xmega プログラマー デバッガー
Atmel AT AVR ISP mk2 MKII ATMEL インシステムプログラマー USB AVRISP XPII デバッガー
実践Rustプログラミング入門
Outtag 5V 3A ACDCアダプター充電器 8種変換プラグ付き
プログラムの実装
えらくUSART関連のレジスタの話が長くなってしまいましたが、ここからようやく具体的なプログラミングの話に移ります。
なおAVRマイコンのデバッカーからプログラムを書き込むまでの手順は以前のブログ記事で紹介していますので、Rustプログラミングによる実行ファイルのビルドやプログラム書き込み手順を知りたい場合にはそちらを参照してください。
[Arduino工作〜発展編] RustでATmega328pのプログラムをビルドしてみる RustからどのようにAVRマイコン用のプログラムを使っていくのか考えていきます。
RustでAtmega328pへプログラミング
以下が今回利用するRustのソースコードです。
開く/閉じる #![feature(llvm_asm, lang_items, unwind_attributes)]
#![feature(start)]
#![no_std]
#![no_main]
extern crate avr_delay;
extern crate avr_std_stub;
extern crate avrd;
use avrd::atmega328p::{
DDRB, PORTB
};
use avr_delay::{delay};
use core::ptr::{write_volatile, read_volatile};
use avrd::atmega328p::{CLKPR};
use avrd::atmega328p::{
UCSR0A,
UCSR0B,
UCSR0C,
UDR0,
UBRR0
};
#[no_mangle]
#[start]
pub extern "C" fn main () {
clock_init ();
usart_init ();
unsafe { write_volatile (DDRB, 0b00000010 ); }
let mut out = 0b00000000 ;
loop {
delay (2000000 );
unsafe { write_volatile (PORTB, out); }
out ^= 1 << 1 ;
transmit ('d' as u8 );
}
}
#[no_mangle]
fn clock_init () {
unsafe {
write_volatile (CLKPR, 0b10000000 );
write_volatile (CLKPR, 0b00000001 );
}
}
#[no_mangle]
fn usart_init () {
unsafe {
write_volatile (UBRR0, 51 );
write_volatile (UCSR0B, 0b00011000 );
write_volatile (UCSR0C, 0b00000110 );
}
}
#[no_mangle]
fn transmit (byte: u8 ) {
while !ready_to_transmit () {}
unsafe { write_volatile (UDR0, byte); }
}
#[no_mangle]
fn ready_to_transmit () -> bool {
unsafe {
(read_volatile (UCSR0A) & (1 << 5 )) > 0
}
}
これをビルドして出来た実行ファイルをAVRマイコンに書き込んでおきます。
下位Fuseビットの書き換え
前述したように 、外部振動素子からクロック周波数を供給する場合には、下位Fuseビットも併せて書き換えする必要があります。
書き換える下位Fuseビット値はお手元の環境状態によっても異なるので、ここではあくまで参考としてください。
現在の著者の手持ちの部品と5V電源だと、低電力モードと低速起動を狙って、
と設定したい場合、 01111111
は16進法で 0x7F
ですので、Avrdudeコマンドで以下のように打ち込みます。
これはAvrdudeコマンドで単独で下位Fuseビットを書き込んでもいいですし、先程の実行ファイルと同時に書き込んでも結構です。
信号受信用スケッチをArduinoへ書き込む
今回はArduinoを挟んでPCとAVRマイコン間でシリアル通信をやり取りさせてみます。
シリアル通信は基本的にモジュール1対1型の技術ですので、一つのシリアル通信で複数台のデバイスは扱えません。
しかしArduinoとPCとのシリアル通信のやり取りに1つ、ArduinoとAtmega328pとのシリアル通信で1つの2つが必要になります。
ArudinoをPCで繋ぐ場合、既にハードウェアシリアル(物理ピンはRX:0ピン/TX:1ピン)が自動で使用されるため、Atmega328p側にはソフトウェアシリアルで通信する必要があります。
ということで、以下のようにAtmega328p側から信号を受信して、それをPC側に転送するプログラムスケッチをArduinoに書き込む必要があります。
一度Arduino書き込んでおけば、以降はArudinoIDEのシリアルモニタを開くだけで通信状況を確認できるようになります。
Arduino Uno Rev3 ATmega328 マイコンボード A000066
組込みエンジニアの教科書
Atmel-ICE MCU AVR SAM Xmega プログラマー デバッガー
Atmel AT AVR ISP mk2 MKII ATMEL インシステムプログラマー USB AVRISP XPII デバッガー
実践Rustプログラミング入門
Outtag 5V 3A ACDCアダプター充電器 8種変換プラグ付き
通信テスト
さて、書き込みを終えたAtmega328pを以下の写真のようにブレッドボード上でテスト回路を組んで、シリアル通信出来ているかを試してみましょう。
これをPC側のAduinoIDEのシリアルモニターで確認すると、
となり、確かにAtmega328pのSUARTから送信された文字がArduino側で受信されていることが確認できます。
しかし、なんだか読み取り精度が甘いのか、同期するタイミングが悪いのか、 d
の文字を送っているのに、結構な頻度で間違った文字が表示されています。
もともとソフトウェアシリアルは精度面での用途ではあまり宜しくないので、ボーレートも9600bpsで利用するのがせいぜいなのかも知れません。
またブレッドボード上でしかもコテコテのジャンパー配線しているのがかなり致命的で、あちらこちらでノイズが発生している気がします。
...とりあえず今回はUSARTの信号送信ができていることが確認できた、ということでお茶を濁しておきます。
Arduino Uno Rev3 ATmega328 マイコンボード A000066
組込みエンジニアの教科書
Atmel-ICE MCU AVR SAM Xmega プログラマー デバッガー
Atmel AT AVR ISP mk2 MKII ATMEL インシステムプログラマー USB AVRISP XPII デバッガー
実践Rustプログラミング入門
Outtag 5V 3A ACDCアダプター充電器 8種変換プラグ付き
まとめ
今回はAVRマイコンのUSART機能をRustで使う方法を紹介していきました。
とはいえAtmaga328pからの信号送信でしか実装していませんでしたが、内容が長くなりそうなので次回以降でADC入力と併せてAtmega328p側で信号受信の方もやってみたいと思います。
参考
ATmega48A/PA/88A/PA/168A/PA/328/P データシート