Previous Post | Top | Next Post |
TOC
Arduino Uno/Nano に使われている基本のシリアルAVRの ATmega328P を中心としてAVRチップのプログラムの練習の続きをしました。
確かにハードとして少々古い感はありますが、5V仕様で安価で入手簡単な開発 ボードがあるのが練習用にいい感じです。
開発環境
クロスコンパイル開発環境は以下でOK(2020/03, Debian stable buster)
$ sudo apt install gcc-avr avr-libc binutils-avr
コードスタイル
以下色々なところからコードを引っ張ってくると、コードスタイルが混じって 見づらくなるし、手動で直すのもばかばかしいのでちょっとコードスタイル関係を 調べました。もともとK&R系のTAB無し2-4スペースインデントの 1TBS なので、色々のスタイルガイドを見て、結局 GOOGLEスタイル に最も共感しました。
今後はPython以外は全言語基本2スペースインデントにします。
VIMのモードライン(C)
// vim: set sts=2 sw=2 ft=c ai si et tw=80:
VIMのモードライン(SH)
# vim: set sts=2 sw=2 ft=sh ai si et tw=80:
VIMのモードライン(PYTHON)
# vim: set sts=4 sw=4 ft=python ai si et:
Cの自動リフォーマットは
$ clang-format -style=Google -i filename.c
SHELLの全文リフォーマットはVIMで(modelineが無い場合は:set syntax=sh
の後)gg=G
とする。
PYTHONの全文リフォーマットは「black」を使います。
レファレンス文書
MICROCHIP社(AVRを作っていた旧Atmel社はPICを作っていた旧MICROCHIP社と合併 して、現在MICROCHIP社をして両方を供給中)の AVR関連英文サイト が基本の開発関連レファレンス文書を広くカバーしています。
日本語資料は、AVR日本語情報サイトに素晴らしい 公式文書の翻訳資料群があります。
特にソフトに関しては AVR Libc Reference Manual 英文 MICROCHIPサイト・ AVR Libc Reference Manual 英文 NONGNUサイト。AVR Libc 日本語版もあります。 は重要です。
もちろん対象ハードであるATmega328Pのデーターシート も重要です。ATmega328/328Pのデーターシート 日本語版
例: LED点滅 (Lチカ)
まず、単純なLEDの点滅(通称Lチカ)を例にプログラム開発を示します。
Cのソースコード
以下にLチカのCのソースコード blink.c
を示します。
これでNanoのPB5ピンにぶら下がっているLEDを0.5秒間隔でON/OFFします。
// Arduino nano board is 16 MHz
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
int main(void) {
// For Data Direction Register of port B, set the 5th bit (OR)
DDRB |= _BV(DDB5);
while (1) {
// For Port B, toggle the 5th bit (XOR)
PORTB ^= _BV(PORTB5);
_delay_ms(500); // 500 ms delay
}
}
ここで普通のCのプログラムでは見かけないマクロの_BV()
や、
よく分からない変数DDRB
等が気になります。
AVRクロスコンパイル開発環境では、普通を違い/usr/lib/avr/include/
下に
inclideファイルがあります。これさえ分かれば後はコードを見ればほぼ自明です。
例えば_BV()
は、基本のヘッダーファイルavr/io.h
が読み込む
avr/sfr_defs.h
中に定義されています。
#define _BV(bit) (1 << (bit))
ところで、見慣れない変数名ですがその答えは、avr/io.h
やavr/sfr_defs.h
を読むと見えてきます。ちなみに、avr/io.h
はチップのIO(入出力)の定義の
ヘッダーファイルで、avr/sfr_defs.h
は「AVR special function registers」
の定義のヘッダーファイルです。
これらのヘッダーファイルは、xmega等もサポートするために少々複雑になって いますが、ザックリは対象chipのdatasheetを読んでIOポートの物理アドレスを 手動で調べてそのアドレス値を数値でプログラムを書かなくとも、データーシー トの「ポートの名前」等をCの変数名として直接使えるようにするCマクロの 仕組みです。
io.h
がコンパイル時に-mmcu=atmega328p
で選ばれたチップ毎に合わせて
iom328p.h
などの対応する定義ファイルをinclideすることで異なるハード
ウエアーへの対応を実現しています。
ポートの名前は、 ATmega328Pのデーターシート の「Register Description」の章を見ましょう。例えば:
DDRB
は、Port B の Data Direction Register(データー入出力方向レジスター)- 「0」WRITE、入力ポートに設定 (デフォルト)
- 「1」WRITE、出力ポートに設定
- READ: 入出力方向設定値の読み出し
PORTB
は、Port B の Data Register (出力レジスター)- 「0」WRITE、出力ポート: LOW (0V)設定 / 入力ポート: Pull-up OFF 設定 (デフォルト)
- 「1」WRITE、出力ポート: HIGH (VCC)設定 / 入力ポート: Pull-up ON 設定
- READ: 出力設定値の読み出し
PINB
は、Port B の Input Pins Address (入力レジスター) 入力・出力ポートとも有効- 「0」WRITE: 出力ビット設定値の無変更
- 「1」WRITE: 出力ビット設定値の変更(トグル)
- READ: 入力値の読み出し
一方_BV(bit)
のbit
に書かれるマクロの値は文字列の最後の数字その物
のことが多いです。データーシート中のビットの名前に合わせてあるので
チップ間で一見命名が不規則な面があります。ただコードのポータビリティー
のための変数名エリアスがio*.h
にある程度定義されています。
この他のマニュアル、特に AVR42787: AVR Software User Guide や AVR035: Efficient C Coding for AVR 等にも目を通しましょう。
コンパイル・リンク・プログラム
AVRISP mkII をISP接続した上で コンパイル・リンク・プログラムは次の様に進めます。
$ avr-gcc -mmcu=atmega328p -Wall -Os -o blink.elf blink.c
$ avr-size --mcu=atmega328p --format=avr blink.elf
Program: 162 bytes (0.5% Full)
(.text + .data + .bootloader)
Data: 0 bytes (0.0% Full)
(.data + .bss + .noinit
$ avr-objcopy -j .text -j .data -O ihex blink.elf blink.hex
$ sudo avrdude -p m328p -c avrisp2 -U flash:w:blink.hex
どうですか、Lチカしていますね。
小さなプログラムで、フラッシュ使用は162バイトで、SRAM使用は0バイトです。
例: LED点灯
ビット値が1が、出力がHIGHであることを確認しましょう。上記ソースコードを 以下の様に変更します。
// Arduino nano board is 16 MHz
#define F_CPU 16000000UL
#include <avr/io.h>
int main(void) {
// For Data Direction Register of port B, set the 5th bit (OR)
DDRB |= _BV(DDB5);
// For Port B, turn on the 5th bit (OR)
PORTB |= _BV(PORTB5);
while (1) {
}
}
同様のコンパイル・リンク・プログラムをします。
どうですか、今度はLEDが付きっぱなしですね。
さらに小さなプログラムで、フラッシュ使用は138バイトで、SRAM使用は0バイトです。
例: LED点滅 (Lチカ)の別の方法
PINへの書き込みによる、トグルを使うようにオリジナルのLED点滅ソースコードを 以下の様に変更します。
// Arduino nano board is 16 MHz
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
int main(void) {
// For Data Direction Register of port B, set the 5th bit
DDRB = _BV(DDB5);
while (1) {
// For PIN B, toggle the 5th bit
PINB = _BV(PINB5);
_delay_ms(500); // 500 ms delay
}
}
同様のコンパイル・リンク・プログラムをします。
どうですか、Lチカしていますね。
これも小さなプログラムで、フラッシュ使用は158バイトで、SRAM使用は0バイトです。 同一機能を4バイト少なくして実現しました。
例: 端子入力にあわせてLED点滅
端子入力にあわせてLED点滅させるようにオリジナルのLED点滅ソースコードを 以下の様に変更します。
// Arduino nano board is 16 MHz
#define F_CPU 16000000UL
#include <avr/io.h>
int main(void) {
// For Data Direction Register of port B, set 5 pin as output for LED
DDRB = 0b00100000;
// pull-up for all port B non-5 pins (0b11011111)
PORTB = ~0b00100000;
while (1) {
// Check if any one of low 5 bits 01234 of PORTB is grounded
if ((PINB & 0x1f) == 0x1f) {
// not grounded -> LED ON
PORTB |= 0b00100000;
} else {
// grounded -> LED OFF
PORTB &= ~0b00100000;
}
}
}
今回はビット値の設定をマクロではなく直接2進表記の数値でしました。
同様のコンパイル・リンク・プログラムをします。
どうですか、LEDが付いていますね。
ここでPORTBの01234の5つのPIN(USBコネクターを右に置いた際に、手前 USBコネクター側の端子)のどれかを接地(ground)するとLEDが消えますね。
これも小さなプログラムで、フラッシュ使用は156バイトで、SRAM使用は0バイトです。
例: 端子入力にあわせてLEDタイマー点灯
端子入力にあわせてLEDを3秒間タイマー点灯させるようにオリジナルの LED点滅ソースコードを以下の様に変更します。
// Arduino nano board is 16 MHz
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#define SURE_LO 0
#define UNSURE 1
#define SURE_HI 2
int check(void) {
// Check if any one of low 5 bits 01234 of PORTB is grounded
if ((PINB & 0x1f) != 0x1f) {
_delay_ms(50); // 50 ms delay
if ((PINB & 0x1f) != 0x1f) {
return SURE_LO;
}
}
// Check if all low 5 bits 01234 of PORTB are not grounded
else if ((PINB & 0x1f) == 0x1f) {
_delay_ms(50); // 50 ms delay
if ((PINB & 0x1f) == 0x1f) {
return SURE_HI;
}
}
return UNSURE;
}
#define BIT5HI 0b00100000
int main(void) {
int previous = SURE_HI; // pull-uped
// For Data Direction Register of port B, set 5 pin as output for LED
DDRB = BIT5HI;
// pull-up for all port B non-5 pins (0b11011111)
PORTB = ~BIT5HI;
while (1) {
if ((previous == SURE_HI) & (check() == SURE_LO)) {
// LED ON
PORTB |= BIT5HI;
_delay_ms(3000); // 3000 ms delay
// LED OFF
PORTB &= ~BIT5HI;
previous = SURE_LO;
} else if ((previous == SURE_LO) & (check() == SURE_HI)) {
previous = SURE_HI;
}
}
}
ここでPORTBの01234の5つのPIN(USBコネクターを右に置いた際に、手前 USBコネクター側の端子)のどれかを接地(ground)するとLEDが3秒間点灯 します。
ちょっと大きくはなったとは言えまだ小さなプログラムで、 フラッシュ使用は286バイトで、SRAM使用は0バイトです。
シリアル経由のプログラム
Optibootが入っていればシリアル経由のプログラムができます。
入れ方は、ATmega328P (1) の最後の「Optiboot プログラムのインストール」を参照下さい
avrdude
を-e
付きで実行するような乱暴なことをせず、
上記の様にISPからの書き込みが下位アドレスに留まれば、高位
アドレスのOptibootは温存されます。
Optibootが入っていることは、電源供給時にRESETを押すと、ピカピカ とLEDが2回光るので確認できます。
ホストPCから開発ボードのUSBポートにUSBケーブルをつなぎ、 USB-Serial変換器(Arduino UnoはFT232Rが、Arduino Nano互換機では CH340がその役割)経由でATmega328PのD0/D1ピンにシリアルにデーター を流し込みプログラムするには、上記のISP経由の書き込み後、以下の手順でホストPCから操作します。
まず、USBを差し込む前に以下を実行(これは、何ちゃってArduinoのCH340タイプ)
$ journalctl -f
そして、USBを差し込む。すると以下が表示される。
...
... : usb 4-2: new full-speed USB device number 30 using xhci_hcd
... : usb 4-2: New USB device found, idVendor=2341, idProduct=0043, bcdDevice= 0.01
... : usb 4-2: New USB device strings: Mfr=1, Product=2, SerialNumber=xxx
... : usb 4-2: Manufacturer: Arduino (www.arduino.cc)
... : usb 4-2: SerialNumber: xxxxxxxxxxxxxxxxxxxx
... : cdc_acm 4-2:1.0: ttyACM0: USB ACM device
...
- 本物のArduino Uno (ATmega16u4でUSB接続)は、
ttyACM0
が作成されるデバイス名です。 - 何ちゃってArduino Nano (CH341でUSB接続)は、
ttyUSB0
が作成されるデバイス名です。
デバイス名を合わせてシリアル接続でのプログラム注入を実行します。
$ sudo avrdude -p m328p -c arduino -P /dev/ttyACM0 -U flash:w:blink.hex
この方がプログラム更新にはISPよりはるかに手軽です。
もちろんFUSE変更操作などにはISPは不可欠です。
Makefile
毎回シェルにコマンドを打ち込むのも面倒です。
こういった基本的な開発環境では、Makefile
を使いたいですよね。
Makefile
の基本テンプレートは以下がよく使われます。
$ sudo apt install subversion
$ svn checkout https://svn.code.sf.net/p/winavr/code/trunk winavr-code
Linux系の開発環境だとGNU Make を使うので、この中の
winavr-code/Mfile/data/makefile_template.winavr
が基本テンプレートとなります。これをソース内に
Makefile
にコピーして、周波数・チップ種・プログラマーなどに合わせ
変数を書き換えて使います。
make関連参考情報
- Wimavr Makefileの経緯情報
- arduino-mk – Makefile for Arduino sketches – Debian arduino-mk パッケージの元ネタ
Previous Post | Top | Next Post |