【Arduino工作〜発展編】AVR-RustでAtmega328pのUSARTを使ってみる
※ 当ページには【広告/PR】を含む場合があります。
2021/11/06

RustでAVRマイコン機能を作り込んでいく技術ネタ紹介シリーズの第4段です。
前回はAtmega328pのPWM波形を発生させる方法を詳しく説明しました。
今回はAVRマイコン開発の際に、デバッグ機能としても重宝するUSARTを用いたシリアル通信を使いこなす方法を解説していきます。
USART接続例
まずはAtmega328pを使ってUSARTテスト用環境を以下のように構築します。

ここではシリアル通信用のモニタリングにArduino Unoとそれに接続したPCを使います。
Atmaga328pからのシリアル通信を確認出来るのが目的ですので、Arduino Unoで無くとも、他のRS232C接続可能なUSBコンバータがあればそれで代用してください。
外部発振器の設定
AVRマイコンのUSART機能を使う前に良く理解しておく必要があるのが、システムで利用する実質的なクロック周波数のことです。
USARTでのシリアル通信のボーレート値は、マイコンのクロック周波数ベースで決定されますので、このクロック周波数の考え方と設定方法を復習しておきます。
下位Fuseビットとその書き換え方法
AtmegaシリーズのマイコンにはFuseビットと呼ばれる特別なレジスタが3つあります。
詳しくは
下位Fuseビット
ビットの名称:
CKDIV8-CKOUT-SUT1-SUT0-CKSEL3-CKSEL2-CKSEL1-CKSEL0
デフォルト: 01100010(0x62)
ここではあまり重要ではないので深くは触れませんが、最初の7ビット目のCKDIV8は
0
6ビット目のCKOUTは、CLKOピン(PB0)からシステムクロック波形を出力すること許可する設定です。 デフォルトは
1
5-4ビット目のSUT1-SUT0はリセットからの起動時間を選択します。
00...14クロック起動。低電圧検出リセット(BOD)許可に使用
01...高速起動(14クロック+4.1ms)。高速上昇電源に使用
10...低速起動(14クロック+65ms)。デフォルト。低速上昇電源に使用
どのオプションが適切かはデバイスの電源を考慮して設定します。
デフォルトのSUT1-SUT0の値(
10
3-0ビット目がクロック周波数を外部から取る上でもっとも重要なビット列になります。
クリスタルやセラミック発振器の設定は詳しく後述しますが、例えばそれ以外の設定ならば、
0010...校正付き内蔵RC発振器7.3~8.1MHz。デフォルト
0011...128kHz内部発振器128kHz
0000...外部クロック信号を直接利用
という感じに利用します。
128kHz内部発振器は非常に低い電力で動作するように利用されるクロック源です。 少電力で動作する反面、高精度用途では利用できません。
またここでの外部クロック信号とは、独立した外部デバイス(例えば他のマイコン)からクロック波形を供給する場合に利用します。 供給されるクロックが安定的に維持されるように、極力外部からのノイズの影響を受けない設計が必要になります。
また以上のことから、下位Fuseビットを出荷値ままデフォルト使うと、内部RC発振器の約8MHzを8分周するので、おおよそ1MHzがシステムの周波数ということになります。
Avrdudeコマンドによる下位Fuseビットの書き込み
Fuseビットの書き込みは実行ファイルからはできませんが、Avrdudeコマンドから直接マイコンを叩くことで変更できます。
書き込み装置は色々と選択できますが、例えばここではAtmel-ICEを利用しています。
以下のコマンドは下位FuseビットにAtmega328pの工場出荷値
0x62
$ sudo avrdude -p m328p -c atmelice_isp -P usb -U lfuse:w:0x62:m -v
見てのように`-U lfuse:w:0x
の
の部分に下位Fuseビットの設定値を16進数で与えて書き換えることが可能です。
本記事の下位Fuseビットの話とは直接関係有りませんが、Avrdudeコマンドでの
書き込み装置によっては、不可逆な書き換えが起こってしまい、困った事態を招くかも知れませんので、Fuseビットの書き換えは慎重に行いましょう。
クリスタル/セラミック発振器のCKSELレジスタ
Arduinoや他のベアメタル製品などの設計で良く目にするように、クリスタル振動子やセラミック振動子などのクロック周波数を安定供給するためのディスクリート部品を使うための設定を説明しましょう。
大体のメーカーデータシートにも構成図が載っていると思いますが、どのマイコンにも発信素子の取り付け用のペアになっているピンが2つ設けられています。

市販の発振素子も様々な種類があり、これらをマイコンへ接続設定するとは言っても、取り付ける振動素子の適性を考えて選択する必要があります。
AVRマイコンでは主に
低電力発振器
全振幅発振器
低周波数発振器
全振幅発振器
この設定では電気的にノイズが多い環境での利用や他の複数のデバイスへクロック入力を供給する場合に利用します。
対して、消費電力は常時高めになるので、低電圧にならないような電源もそれなりにパワーのあるもので設計を考慮する必要があります。
このため、この全振幅振動モードでは
Vcc = 2.7~5.5[V]
また推奨される駆動周波数は
0.4~20[MHz]
12~22[pF]
またこの全振幅振動モードの設定では、CKSELビットだけでなくSUTビットも併せて細かい設定ができます。
下位Fuseビットの設定をまとめると以下の表のようになります。
※1) SUT1-SUT0-CKSEL3-CKSEL2-CKSEL1-CKSEL0の順序で6ビット分 なおこのモードは
CKSEL3-CKSEL2-CKSEL1
011
※2)
Vcc = 5[V]
この表でいうと、上から順に初回の起動時間等が遅くなっていきます。
実装する発信素子や電源のスペックに併せて下位Fuseビットを選択しましょう。
低電力発振器
据え置き型バッテリーなどで電源を供給する場合、出来るだけ少電力に済ませたい設計を求めるならば低電力モードも検討できます。
この低電力モードは先程の全振幅振動モードより更に細かく下位Fuseビットを設定することができます。
先程の表とほぼ内容は一緒ですが、起動に必要なVccの値はケースバイケースです。
※1) SUT1-SUT0-CKSEL3-CKSEL2-CKSEL1-CKSEL0の順序で6ビット分
このモードでは
CKSEL3-CKSEL2-CKSEL1
100 ... 0.4~0.9MHz(セラミック振動子向け) / コンデンサ無し
101 ... 0.9~3.0MHz / 12~22pF
110 ... 3.0~8.0MHz / 12~22pF
111 ... 8.0~16MHz / 12~22pF
※2)
Vcc = 5[V]
低周波数発振器
このモードは時計用32.768kHz水晶振動子に最適化するような特別なモードです。
特にマイコンによって正確な時計機能を実装する必要がある場合に利用されますが、ここでは関係ないので省略します。
CLKPRレジスタの設定
CLKPR(クロック前置分周)レジスタは、システムの基本クロック周波数を分周させるためのバイトを記述します。
CLKPCE-無し-無し-無し-CLKPS3-CLKPS2-CLKPS1-CLKPS0
まず7ビット目はCLKPCEと呼ばれ、クロック分周の値を変更することを許可・不許可を設定できます。
分周数の変更を許可する場合、CLKPCEに
1
ただし注意が必要なのが、CLKPCEビットへの書き込みはCLKPS3~0の全ビットが
0
さらに、CLKPCEに
1
なぜかというと、CLKPCEは
1
0
3~0ビット目のCLKPSレジスタでクロック分周値を定義します。
分周値が変更されると、直ちに分周器がMCUへのメインクロック周波数を分周し、それに伴って全ての周辺機能のクロック速度がそれに従います。
CLKPS3-CLKPS2-CLKPS1-CLKPS0
0000...分周数1
0001...分周数2
0010...分周数4
0011...分周数8(デフォルト)
0100...分周数16
0101...分周数32
0110...分周数64
0111...分周数128
1000...分周数256
ということでここでの要点をまとめると、CLKPRの書き換えにおいて、
1. CLKPS3~0を全て0にする
2. CLKPCEビットを1にして、書き換えを許可する
3. 4クロック以内に新しいCLKPS3~0で書き換える
ということを覚えておきましょう。
USARTについて
ここからはようやく首題のUSARTに触れていきます。
UBRRレジスタとボーレート
前置きで、システムのクロック周波数の話を詳しく取り上げたのも、USARTシリアル通信の速度であるボーレート(単位;bps)を正しく換算するためでした。
この値を設定するレジスタが、
UBRR
このボーレート設定値$$R
標準速非同期モード
Eq. (1)
なお、ボーレートの設定は標準速非同期モードの他に、より高速な通信を行うための
倍速モード
例えばクロック周波数が
8[MHz]
9600[bps]
Eq. (2)
小数点以下切り捨てで、UBRRレジスタには
51
なお、UBRRは10ビットの非負の整数をとりますので、2バイト分の大きさのレジスタとなります。 上位のレジスタは
UBRRH
UBRRL
この2バイト分の書き込みには少し工夫が必要になるかも知れません。 Rustでの利用方法は後ほど実装で解説しましょう。
UCSRレジスタとUDRレジスタ
USARTの制御方法や状態を保持する一般設定を行うレジスタは、
UCSRA
UCSRB
UCSRC
AVRマイコンのUSARTで送受信されたデータはUDRレジスタに一時的に緩衝され、ユーザーはこのUDRレジスタをアクセスすることでデータをやり取りすることできます。
よってUSARTからデータ送信したい場合にはUDRレジスタに書き込むことを意味します。
まずはUCSRAから説明すると、例えばUCSR0Aレジスタが以下にようなビット列になります。
RXC0-TXC0-UDRE0-FE0-DOR0-UPE0-U2X0-MPCM0
デフォルト値:
0-0-1-0-0-0-0-0
重要なビットだけ掻い摘むと、ここでは5ビット目のUDRE0(USART Data Register Empty)が特に重要です。
UDRE0はUSARTから送信待ちになっているデータレジスタが空いているかどうかを教えてくれるフラグ用のビットです。 よって読み取り専用のビットで書き込みは不可です。
UDRE0フラグを読み取ることで、送信緩衝レジスタのUDR0が新規のデータを受け取ることが出来るかどうかを判別することができます。
シリアル通信で送信されるデータを配置するキュー配列のようなレジスタ
UDR
また
UDR
0
送信が完了してUDRレジスタが空になると、UDREが
1
つまり、
UDRE0が1
逆に
UDRE0が0
次にUCSRBレジスタですが、UCSR0Bを例に取ると、以下のようなレジスタ構成になってます。
RXCIE0-TXCIE0-UDRIE0-RXEN0-TXEN0-UCSZ02-RXB80-TXB80
デフォルト値:
0-0-0-0-0-0-0-0
このレジスタで重要なのは、4−3ビット目の
RXEN
TXEN
その名の通り、ビットに
1
2ビット目の
UCSZ2
最後のUCSRCレジスタは、USARTの動作状態を設定するレジスタです。
例えばUCSR0Cは以下のようなレジスタ構成です。
UMSEL01-UMSEL00-UPM01-UPM00-USBS0-UCSZ01-UCSZ00-UCPOL0
デフォルト値:
0-0-0-0-0-1-1-0
始めの7−6ビット目のUMSEL1とUMSEL0が
00
5−4ビット目のUPM1とUPM0でシリアル通信のパリティモードを選択できます。
パリティビットはエラー検出などに利用できますが、今回は使わないので、
00
3ビット目のUSBSは停止ビットの数を選択します。 必要な停止ビット1つなら
0
1
2−1ビット目のUCSZ1とUCSZ0は、先程のUCSRBの2ビット目のUCSZ2を加味して、送信するデータのビット長を設定します。
シリアル通信は大体8ビットで1フレームで送信する形式が多いと思います。
デフォルトもUCSZ2-0は
011
プログラムの実装
えらくUSART関連のレジスタの話が長くなってしまいましたが、ここからようやく具体的なプログラミングの話に移ります。
なおAVRマイコンのデバッカーからプログラムを書き込むまでの手順は以前のブログ記事で紹介していますので、Rustプログラミングによる実行ファイルのビルドやプログラム書き込み手順を知りたい場合にはそちらを参照してください。
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};
//👇USART関連の設定レジスタ
use avrd::atmega328p::{
UCSR0A,
UCSR0B,
UCSR0C,
UDR0,
UBRR0
};
#[no_mangle]
#[start]
pub extern "C" fn main() {
clock_init(); //クロックの初期化
usart_init(); //USARTの初期化
unsafe { write_volatile(DDRB, 0b00000010); }
let mut out = 0b00000000;
loop {
//👇実行クロック周波数8MHzの場合の1秒
delay(2000000);
//👇動作確認用のLED点滅
unsafe { write_volatile(PORTB, out); }
out ^= 1 << 1;
//👇1ループに付き1文字を送信
transmit('d' as u8);
}
}
#[no_mangle]
fn clock_init() {
unsafe {
//👇CLKPRレジスタを一旦クリア
write_volatile(CLKPR, 0b10000000);
//👇4クロック以内に分周数2を書き込む
write_volatile(CLKPR, 0b00000001);
}
}
#[no_mangle]
fn usart_init() {
unsafe {
//👇ボーレート9600bps設定
write_volatile(UBRR0, 51);
//👇RxとTxを起動
write_volatile(UCSR0B, 0b00011000);
//👇非同期・パリティなし・データ長8ビット
write_volatile(UCSR0C, 0b00000110);
}
}
#[no_mangle]
fn transmit(byte: u8) {
//👇データ送信完了まで待つ
while !ready_to_transmit() {}
//👇UDRに新しいデータを書き込む
unsafe { write_volatile(UDR0, byte); }
}
#[no_mangle]
fn ready_to_transmit() -> bool {
unsafe {
//👇UDRE0(UCSR0Aの5ビット目)を読み込んで
// 1(送信完了)かどうか調べる
(read_volatile(UCSR0A) & (1 << 5)) > 0
}
}
これをビルドして出来た実行ファイルをAVRマイコンに書き込んでおきます。
下位Fuseビットの書き換え
書き換える下位Fuseビット値はお手元の環境状態によっても異なるので、ここではあくまで参考としてください。
現在の著者の手持ちの部品と5V電源だと、低電力モードと低速起動を狙って、
CKDIV8-CKOUT-SUT1-SUT0-CKSEL3-CKSEL2-CKSEL1-CKSEL0
0-1-11(低速起動)-111(低電力モード)-1(低速起動)
と設定したい場合、
01111111
0x7F
$ sudo avrdude -p m328p -c atmelice_isp -P usb -U lfuse:w:0x7F:m -v
これは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に書き込む必要があります。
#include <SoftwareSerial.h>
//Rx:2ピン, Tx:3ピン
SoftwareSerial mySerial = SoftwareSerial(2, 3);
char data = 'a';
void setup() {
Serial.begin(9600);
mySerial.begin(9600);
pinMode(2, INPUT);
pinMode(3, OUTPUT);
delay(200);
}
void loop() {
if (mySerial.available() > 0) {
//ソフトウェアシリアルでAtmegaから受け取ったデータを読込
data = mySerial.read();
//ハードウェアシリアルでPCモニタに表示
Serial.println(data);
}
}
一度Arduino書き込んでおけば、以降はArudinoIDEのシリアルモニタを開くだけで通信状況を確認できるようになります。
通信テスト
さて、書き込みを終えたAtmega328pを以下の写真のようにブレッドボード上でテスト回路を組んで、シリアル通信出来ているかを試してみましょう。

これをPC側のAduinoIDEのシリアルモニターで確認すると、

となり、確かにAtmega328pのSUARTから送信された文字がArduino側で受信されていることが確認できます。
しかし、なんだか読み取り精度が甘いのか、同期するタイミングが悪いのか、
d
もともとソフトウェアシリアルは精度面での用途ではあまり宜しくないので、ボーレートも9600bpsで利用するのがせいぜいなのかも知れません。
またブレッドボード上でしかもコテコテのジャンパー配線しているのがかなり致命的で、あちらこちらでノイズが発生している気がします。
...とりあえず今回はUSARTの信号送信ができていることが確認できた、ということでお茶を濁しておきます。
まとめ
今回はAVRマイコンのUSART機能をRustで使う方法を紹介していきました。
とはいえAtmaga328pからの信号送信でしか実装していませんでしたが、内容が長くなりそうなので次回以降でADC入力と併せてAtmega328p側で信号受信の方もやってみたいと思います。