防犯ブザーを改造してサウンドロップを自作する Part2 - Raspberry PiのGPIOでAVRマイコンにプログラムを書き込む
AVRマイコン ATtiny85 と EEPROM 24FC512 を使ってオリジナルサウンドロップを作る連載のPart2です。
はじめに
Part1 では、Raspberry Pi で EEPROM 24FC512 に音声データを書き込みました。
今回は、AVRマイコン ATtiny85 でLチカ(LEDチカチカ)をするまでの方法をじっくり学びます。
「いいから早く音声を鳴らしたいぜ」という方はごめんなさい。今回はまだそこまで扱いません。
電子工作は入出力のかたまりのようなものなので、AVRの入出力に関する前提知識のない状態でコードだけ見てもさっぱりだと思います。なので今回はまずそこから説明させてくださいませ。
(と言っても今回入力の話はあまりしないんですが)
また、LチカをやるのでLEDと適当な抵抗器(100Ωから3kΩくらいまでならだいたいなんでもいい)を1つずつ用意しておいてください。
今回の目標
- Raspberry Pi上でATtiny85用のプログラムバイナリを作り、それをATtiny85に書き込めるようになること。
- ATtiny85を使った最低限の出力をするプログラムを作成できるようになること。
目次
# This code block gets replaced with the TOC
tight: true
ordered: true
from-heading: 2
to-heading: 4
AVRマイコンにプログラムを書き込むための方法
ふつうのC言語の実行方法の話
まず、ふつうのC言語プログラムの実行方法の話をします。
C言語のプログラムを実行するには、C言語のソースコードが書かれたファイルを用意し、それをコンパイラに与えて最終的な実行ファイルを生成する必要があります 。これをコンパイルとかビルドと呼びます。
正確には、オブジェクトファイルを作るところまでをコンパイルと呼んで、最終的な実行可能形式にすることをビルドと呼びますが、今回はビルドのことをコンパイルと呼ばせてください。
コンパイル(ビルド)は、一般的なC言語環境だと gcc
というコンパイラ環境を使って行います。
ただしこれは、そのマシン上で動かすソースコードをそのマシン上でコンパイルする時の話です。
AVR向けのC言語のコンパイルについて
今回はAVRマイコンで動くプログラムをRaspberry Pi上でコンパイルしたいです。
しかし、普通のgcc環境ではAVR用の実行ファイルを生成することができません。なぜなら、Raspberry Piに入っているCPUとAVRのCPUが異なっているからです。
このため、AVRに合わせた専用環境を構築する必要があります。このような専用の開発環境をクロス開発環境と呼びます。
今回はRaspberry Pi上にAVR向けのクロス開発環境を導入する必要があります。
この環境の構築方法については後で説明します。
クロスコンパイルから書き込みまでの流れ
クロス開発環境を整えたあとの話をします。
C言語をクロスコンパイルして、実行ファイルをATtiny85に書き込むまでの流れを次の図に示します。
コンパイルから書き込みまでに行う手順は次の3つです。
avr-gcc
というコマンドを使って、C言語ソースコード(xxx.c)をコンパイルして実行可能ファイル(xxx.elf)に変換する。avr-objcopy
というコマンドを使って、実行可能ファイル(xxx.elf)をhexファイル(xxx.hex)に変換する。avrdude
というコマンドを使って、hexファイル(xxx.hex)の機械語命令列をAVRマイコンに書き込む。
avr-gcc
- AVR用のクロスコンパイラです。ソースコードをコンパイルし、さらにそれをAVR向けの実行可能ファイル(xxx.elf)に変換します。
avr-objcopy
avr-objcopy
ファイル 形式を変換するユーティリティです。これを使ってelfファイルををIntel HEX形式(xxx.hex)に変換します。
avrdude
- AVRマイコンに対しデータの読み書きを行うコマンドです。hexファイルを渡すことでAVRマイコンにプログラムを書き込めます。ちなみに、Arduino IDEでスケッチを書き込むときにはこのコマンドが呼び出されています。
まとめ: AVRマイコンにプログラムを書き込むための方法
- クロス開発環境を導入する必要がある。
- 導入したコマンドを駆使して、ELF形式に変換→HEX形式に変換→書き込み の順番でプログラムをマイコンに書き込む。
クロスコンパイル環境の導入
前のセクションで紹介したツール一式をインストールしてクロスコンパイル環境を整えます。
といっても、やることは arduino-core
パッケージをインストールすることだけです。
arduino-core パッケージについて
arduino-core
パッケージをインストールすると、AVRマイコンにプログラムを書き込むための各種ツールをまとめてダウンロードすることができて便利です。
arduino-core
パッケージは、AVR開発に必要な複数のパッケージをまとめたパッケージです。その中身は以下のようになっています。
arduino-core
パッケージ:avr-libc
パッケージavrdude
パッケージavrdude
コマンド
binutils-avr
パッケージavr-objcopy
コマンド など
gcc-avr
パッケージavr-gcc
コマンドや、avr-cpp
,cc1
,avr-as
コマンドなどコンパイラ・ツールチェイン
雰囲気的には、コマンドやライブラリが /usr/bin/
や /usr/lib/gcc/avr/5.4.0/
内にインストールされる感じです(詳しいことは知らない)。
arduino-core パッケージをインストールする
次のコマンドを実行して arduino-core
パッケージをインストールします。
pi@raspberrypi:~ $ sudo apt install arduino-core
The following additional packages will be installed:
avr-libc avrdude binutils-avr gcc-avr
Suggested packages:
arduino-mk dfu-programmer avrdude-doc gcc-doc
The following NEW packages will be installed:
arduino-core avr-libc avrdude binutils-avr gcc-avr
0 upgraded, 5 newly installed, 0 to remove and 0 not upgraded.
Need to get 19.2 MB of archives.
After this operation, 126 MB of additional disk space will be used.
Do you want to continue? [Y/n] Y
...(省略)...
まとめ:クロスコンパイル環境の導入
apt install arduino-core
というコマンドを実行してクロスコンパイル環境を構築した。
ATtiny85の入出力を知る
環境が整ったのでいよいよプログラミング、と言いたいところですが、残念ながらマイコンの入出力周りの勝手が分かっていないとマイコンプログラミングはできません。Hello, World!
なら画面に出てきて「やったぜ。」となりますが、マイコンの場合は「まずどこに出力されるねん」という問題があるからです。
というわけで先にATtiny85の入出力とその制御方法について説明します。
先に結論
- ATtiny85にはPB5からPB0までのピンがある。
- ポートBを操作するとピンの電圧や入力受け付け状態をコントロールできる。
- ポートを操作するにはI/Oレジスタを操作する。
ATtiny85のピン
ATtiny85に生えている8本の足、ピン(端子)について見ていきます。
このページから tiny45.pdf を開いて、ATtiny85のデータシート(日本語版)を見てください。( /user/DS/PDF/tiny45.pdf
というパスに存在します。)
なぜ直リンクしないかというと、avr.jp に書いてある注意書きがなにやら香ばしくて、それを犯すと面倒な事になりそうだからです…。管理者は一人でAVRの技術情報を翻訳しまくっている熱心な方のようで、過去に翻訳物を巡って何やらやらトラブルがあったようですね。こわいな〜とづまりすとこ…
1. ピン配置
という章にATtiny85(25/45と共通)のピン(端子)の図と説明があります。
- VCC: 電源ピン(5.5Vまで)
- GND: 接地ピン(0V)
- PB5, PB4, PB3, PB2, PB1, PB0: ポートBを構成するピン
ポートという言葉が出てきました。
ATtiny85のポート
そもそもポートとは何か?これは説明するのが難しいんですが、ポートとは「入出力機能の単位」とでも言うんですかね。。うーん、ポートという言葉の定義はあまり気にしないでください。。
ATtiny85は入出力機能としてポートBを持ちます。
マニュアルによると、ポートBは「6ビットの内蔵プルアップ抵抗付きの双方向入出力ポート」です。
ポートBは PB5, PB4, PB3, PB2, PB1, PB0 の6ピンで構成されており、各ピンは、そのピンの電圧の高低に応じて、0か1の状態を持ちます。
ポートを操作するにはI/Oレジスタに読み書きする
「I/Oレジスタ」を読み書きすると、入出力ポートをコントロールすることができます。
つまりポートピンの電圧を上げ下げできます。(これによりLチカが実現できるという寸法です。)
I/Oレジスタの読み書きは、具体的には、次のようなC言語のコードで行えます:
DDRB = 0b00111000; // DDRBレジスタに書き込み。PB5-PB3を出力モードに、PB2-PB0を入力モードにする。
PORTB = 0b00010000; // PORTBレジスタに書き込み。PB5, PB4, PB3にそれぞれLOW, HIGH, LOWを出力する。
unsigned char r = PINB; // PINBレジスタの値を読み込んで変数rに保存
ここに出てきた DDRB
, PORTB
, PINB
というのがポートBのI/Oレジスタです。ポートBはこれら3つのI/Oレジスタを持ちます。
それぞれのレジスタの役割を以下に示します。
DDRB
- Port B Data Direction Register- 各ピンについて、入力・出力を切り替えるレジスタです。
DDRB
に書き込みを行うと、ポートBの6つのピンのモード(入力/出力)を切り替えることができます。
PORTB
- Port B Data Register- 各ピンについて、出力のHIGH/LOWを設定できるレジスタです。
PORTB
に書き込みを行うとポートBの6つのピンからHIGH/LOWを出力できます。
PINB
- Port B Input Pins Address- ピンへの入力値が格納されるレジスタです。
PINB
の値を読み込むと、ポートBの6つのピンの電圧の高低を取得することができます。
上のコードを実行したときの各レジスタの状態を表にまとめると次のようになります。
PB5 PB4 PB3 PB2 PB1 PB0
DDRB : OUT OUT OUT IN IN IN
PORTB : LO HI LO 0 0 0
PINB : 0 1 0 ? ? ?
このように、I/Oレジスタに対し読み書きを行うとポートに出力したりポートから入力を受け取ることができます。
ちなみに、ATtiny85にはポートBだけしかありませんが、Arduinoにも載っているATmega328PはポートB, ポートC, ポートDを持っています。 `/usr/lib/avr/include/avr/portpins.h` によると、ポートはAからLまで定義されているようです。ポートLを持つマイコンが実在するのかは分かりません。
I/Oレジスタの詳しい仕様
上で書いた各レジスタの説明は概略です。実際にレジスタを扱う際にはもう少し詳しくレジスタ仕様を理解する必要があるので、それを説明します。
詳しいレジスタの仕様はマニュアルの 10.4. I/Oポート用レジスタ
に載っています。
レジスタの各ビットは各ピンに対応しています:
- 0番目のビットの操作はピン
PB0
に作用する。 - 1番目のビットの操作はピン
PB1
に作用する。 - ...
- 5番目のビットの操作はピン
PB5
に作用する。
DDRB - Port B Data Direction Register
- 各ピンについて、入力・出力を切り替えるレジスタ。
1
: 出力,0
: 入力- I/O空間上でのアドレス: 0x17
PORTB - Port B Data Register
- 各ピンについて、HIGH/LOWの出力、またはプルアップ抵抗のオン・オフを設定するレジスタ。
- ピンが出力モードの場合
1
: HIGH出力,0
: LOW出力 (PINB
レジスタでこの出力を反転させることができるので注意)
- ピンが入力モードの場合
1
: プルアップ抵抗あり,0
: プルアップ抵抗なし
- I/O空間上でのアドレス: 0x18
PINB - Port B Input Pins Address
- ピンが入力モードのときに入力値が格納されるレジスタ。
- ふつうは読み込んで使うレジスタだが、ここに書き込むこともできる。
PINB
に1
を書き込んだポートピンは出力が反転し、1
のときLOW出力,0
のときHIGH出力になる(ローアクティブ)
- I/O空間上でのアドレス: 0x16
- (出力モードのときに読み込むと出力値がそのまま読み込まれるんだっけ…?)
まとめ: ATtiny85の入出力を知る
- ATtiny85にはPB5からPB0までのピンがある。
- ポートBを操作するとピンの電圧や入力受け付け状態をコントロールできる。
- ポートを操作するにはI/Oレジスタを操作する。
- (I/Oレジ スタを操作するにはI/Oレジスタがマッピングされたメモリに対し読み書きを行う。(おまけで説明))
- ポートを操作するにはI/Oレジスタを操作する。
詳しく知りたい人向けのおまけ:I/Oレジスタの正体とAVRマイコンのメモリ空間
上のI/Oレジスタ仕様の説明の中に「I/O空間上でのアドレス」という言葉が出てきたのに気づいたでしょうか。
これを理解するには、先にレジスタの正体とAVRのメモリ空間についてお話しする必要があります。
これについて詳しく知りたい方は読み進めてください。詳細に興味のない方は STEP1 - C言語でLチカのプログラムを書く に進んでいただいて結構です。
I/Oレジスタの実体は記憶回路
まずそもそもレジスタとは、マイコン内部の記憶回路です。プロセッサー自体の内部状態を保存しています。
レジスタ(register)はコンピュータのプロセッサなどが内蔵する記憶回路で、制御装置や演算装置や実行ユニットに直結した、操作に要する速度が最速の、比較的少量のものを指す。 (Wikipedia レジスタ(コンピュータ)より引用)
レジスタの中でも、ペリフェラル(周辺モジュール)の制御用に用意されたレジスタのことをI/Oレジスタと言います。
AVRマイコンのメモリ
AVRマイコンは3つの独立したメモリを持っています。
- プログラム用フラッシュメモリ(ROM)
- プログラム本体(機械語命令列)を保存する領域。ATtiny85では4K×16bit(4Kワード/4K語)の大きさを持つ。
- データメモリ(データ用SRAM)
- プログラム実行時にデータを操作する作業領域。
- メモリ空間の先頭に32本の汎用レジスタと64本のI/Oレジスタがマッピングされている。
- ATtiny85では512×8bit(512バイト)の大きさを持つ。
- EEPROM
- 不揮発性メモリ。電源を切っても内容が消えないので、設定情報を記録しておく用途などに使用可能。100,000回書き換え可能。
- ATtiny85では512バイトの大きさを持つ。
図のデータメモリ上に「I/Oレジスタ」とあることに注目してください。
ATtiny85ではデータメモリ上でのアドレス範囲 0x0020
- 0x005F
はI/Oレジスタにマッピングされています。
つまり、CPUが 0x0020
- 0x005F
の範囲のメモリアドレスにアクセスすると、SRAMの代わりにI/Oレジスタへのアクセスが実行されます。
ATtiny85のレジスタは1個あたり8ビットの幅を持っていて、CPUから見ると各レジスタは1バイトのメモリ領域に見えます。
I/Oアドレス空間
データメモリ上での 0x0020
- 0x005F
を、I/Oレジスタの世界から見て 0x00
- 0x3F
と見なしたものをI/Oアドレス空間と呼びます。
上でちょろっと書いたように、I/Oアドレス空間上で DDRB
は 0x17、PORTB
は 0x18、PINB
は 0x16 に割り当てられています。
(これを実際のデータメモリアドレスに直すとそれぞれ 0x0037, 0x0038, 0x0036 です。)
ちなみに、先ほどのC言語例では次のような記述をしましたが、
DDRB = 0b00111000; // DDRBレジスタに書き込み。PB5-PB3を出力モードに、PB2-PB0を入力モードにする。
PORTB = 0b00010000; // PORTBレジスタに書き込み。PB5, PB4, PB3にそれぞれLOW, HIGH, LOWを出力する。
unsigned char r = PINB; // PINBレジスタの値を読み込んで変数rに保存
実はこの PINB
, PORTB
, PINB
は、コンパイル中に、それぞれのI/Oレジスタに対応するメモリアドレスを指すポインタに展開されます。
たとえば PORTB
は最終的に (*(volatile uint8_t *)((0x18) + 0x20))
に、PINB
は最終的に (*(volatile uint8_t *)((0x16) + 0x20))
に、プリプロセッサにより展開されます。
STEP1 - C言語でLチカのプログラムを書く
ポートやレジスタについて分かったので、C言語のソースファイルを書き始めることができます。 記述量は僅かなので先に完成形を示します。
/*
* main.c
* blink an LED
* /
#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
DDRB = 0b00010000; // PB4を出力, それ以外を入力に設定。
PORTB = 0b00000000; // すべてのピンをリセット。PB4をLOW出力に、それ以外をプルアップ抵抗なしの入力に設定。
/* Toggle ON/OFF every 500ms */
while (1) {
PORTB = 0b00010000; // PB4をHIGH出力に設定。
_delay_ms(500); // 500ミリ秒待つ
PORTB = 0b00000000; // PB4をLOW出力に、それ以外はプルアップ抵抗なしの入力に設定。
_delay_ms(500); // 500ミリ秒待つ
}
return 0;
}
1行ずつ解説していきます。
まず先頭の ~~おまじない~~ インクルードヘッダから見ていきましょう。
avr/io.h
#include <avr/io.h>
これは /usr/lib/avr/include
の中の avr/io.h
をインクルードしています。
avr/io.h
をインクルードすることでAVRデバイス特有のI/O定義をC言語から使えるようになります。
具体的には、C言語で PORTB = 0x10
, DDRB = 0x10
, r = PINB
のような記述ができるようになります。なぜこのような書き方ができるようになるのかはこの記事の末尾のおまけコーナーで解説します。
util/delay.h
#include <util/delay.h>
これは /usr/lib/avr/include
の中の util/delay.h
をインクルードしています。
指定ミリ秒だけループして待つ(インライン)関数 _delay_ms()
と、そのマイクロ秒版 _delay_us()
が定義されています。
このヘッダファイルは内部で `F_CPU` というマクロを参照して待機時間の計算を行っているので、インクルードする箇所の前で予め `#define F_CPU (1000000UL)` と書く、あるいはコンパイルオプションに `-DF_CPU=1000000` を指定するなどして `F_CPU` マクロを定義しておく必要があります。 `F_CPU` の値はCPUのクロック周波数です(1000000 = 1MHz)。 `F_CPU` の定義をどこにも書かなかった場合は警告が出て、`F_CPU` の値として `1000000UL` が勝手に採用されます。
I/Oピンの初期化
int main(void)
{
DDRB = 0b00010000; // PB4を出力, それ以外を入力に設定。
PORTB = 0b00000000; // すべてのピンをリセット。PB4をLOW出力に、それ以外をプルアップ抵抗なしの入力に設定。
今回は PB4
のピンに0/1を出力したいので、まず PB4
ピンを出力モードに設定します。
I/Oレジスタ仕様のセクションで述べたように、入出力の方向は DDRB
レジスタで設定します。 PB4
のビットを立てた値を考えると、 0b00010000=(0x10)
が得られます。
(bit) --- --- PB5 PB4 PB3 PB2 PB1 PB0
DDRB = 0 0 0 1 0 0 0 0 (=0x10)
PORTB = 0 0 0 0 0 0 0 0 (=0x00)
PORTB
の初期値は、マニュアルを見ると 0x00
と決まっているのであんまり0で埋めておく理由はないです。
Lチカ部分
/* Toggle ON/OFF every 500ms */
while (1) {
PORTB = 0b00010000; // PB4をHIGH出力に設定。
_delay_ms(500); // 500ミリ秒待つ
PORTB = 0b00000000; // PB4をLOW出力に、それ以外はプルアップ抵抗なしの入力に設定。
_delay_ms(500); // 500ミリ秒待つ
}
return 0;
}
PB4をHIGH出力に設定し、500ミリ秒待ち、PB4をLOW出力にして、また500ミリ秒待つ、というのを永遠に繰り返します。
注意するべき点としては、 _delay_ms()
や _delay_us()
の引数は、定数(コンパイル時に具体的な数値リテラルに変換され得る値)でなければならないということです。変数を引数に取ることはできません。
まとめ: STEP1 - C言語でLチカのプログラムを書く
#include <avr/io.h>
のおかげでPORTB = 0x10
,DDRB = 0x10
,r = PINB
のような記述ができる。#include <util/delay.h>
のおかげで_delay_ms()
インライン関数が使える。- 引数は定数でなければならない。
- ポートに対応する位置のビットを立てた値をレジスタに書き込んでポートを操作する。
- PB4の出力をHIGHにする例:
PORTB = 0b00010000;
- PB4の出力をHIGHにする例:
STEP2 - ソースファイルのコンパイルとHEXファイルへの変換
C言語のプログラムを、AVRマイコンが実行可能な形式のファイルに変換する作業をします。
コンパイル
avr-gcc
コマンドを使って main.c
をビルドして、ファイル main.elf
を生成します:
pi@raspberrypi:~ $ avr-gcc main.c -o main.elf -DF_CPU=1000000 -mmcu=attiny85 -Os -Wall
-o
オプション- 出力先のファイル名を指定します。
-D
オプション-DXXX=YYY
と書くと、Cソースファイルで#define XXX YYY
と書いたのと同じことになります。- (
-D
とマクロ名との間にスペースを入れないのが古くからの習わしです(最近はス ペースを入れても構わないらしい))
-mmcu
オプション- 実行ターゲットとなるMCU(マイコン)の種類を指定します。
- 指定できる値は AVR Options に一覧になっています。
-Os
オプション- サイズ最適化を有効にします。
-O0
: 最適化なし,-O1
/-O2
/-O3
: ちょっと最適化/けっこう最適化/めっちゃ最適化(適当)- 最適化をなしにすると
util/delay.h
に怒られます。
-Wall
オプション- すべての警告(Warning)を表示します。
main.elf
ファイルができました。
ELFファイルをHEXファイルに変換
avr-objcopy
コマンドを使って main.elf
を ファイル main.hex
に変換します:
pi@raspberrypi:~ $ avr-objcopy -I elf32-avr -O ihex main.elf main.hex
-I
オプション- 入力ファイルのフォーマットを指定します。
- 今回のフォーマットはelf32-avrという形式らしいです。
-O
オプション- 出力ファイルのフォーマットを指定します。
- 今回はIntel HEX形式にしたいのでihexを指定します。
main.hex
ファイルができました。
STEP3 - HEXファイルをATtiny85に書き込む
avrdude
を使ってhexファイルをATtiny85に書き込むとついにプログラムが動き出します。
ATtiny85のプログラミング(書き込み)に使う信号線とピン
まずはATtiny85へのプログラムの書き込みに使う4つのピンとその役割を紹介します。
この4つのピンを適切にRaspberry PiのGPIOに接続することでATtiny85にプログラムを書き込むことができます。
ピン名 | 入出力 | 信号名 | 役割 |
---|---|---|---|
PB0 | 入力 | MOSI(Master Out Slave In) | シリアル(直列)データ入力 |
PB1 | 出力 | MISO(Master In Slave Out) | シリアルデータ出力 |
PB2 | 入力 | SCK(Serial CLock) | シリアルクロック入力 |
PB5 | 入力 | RESET | リセット入力(ローアクティブ)。RESETがGNDに引かれている間にプログラムを書き込める。 |
avrdudeのコンフィグファイルの準備
今回Raspberry PiのGPIOピンを使って書き込みを行おうとしているので、どのGPIOピンを使って書き込みを行うのかavrdudeに伝える必要があります。
そのためにまず /etc/avrdude.conf
を作業ディレクトリにコピーします。ファイル名は my-avrdude.conf
にしてみました。
pi@raspberrypi:~ $ cp /etc/avrdude.conf my-avrdude.conf
そして、 my-avrdude.conf
の末尾に以下の設定を追記します。
# Linux GPIO configuration for avrdude.
programmer
id = "pi_1";
desc = "Use the Linux sysfs interface to bitbang GPIO lines";
type = "linuxgpio";
reset = 21;
sck = 25;
mosi = 19;
miso = 16;
;
これは、信号とGPIOピンとのペアを記述したものを pi_1
という名前で登録しています。
今回はGPIOピンを以下のように割り当てました。ピンの番号は自由ですが、どのGPIOピンが何番のGPIOなのかを https://pinout.xyz を使ってよく調べてください。
- RESET: GPIO 21
- SCK: GPIO 25
- MOSI: GPIO 19
- MISO: GPIO 16
配線作業
以上のようにコンフィグファイルを設定したので、ATtiny85のピンとRaspberry PiのGPIOピンを次の対応で接続します。
- PB0 - GPIO 19 (MOSI)
- PB1 - GPIO 16 (MISO)
- PB2 - GPIO 25 (SCK)
- PB5 - GPIO 21 (RESET)
GPIOピンの割り当てを変えた場合は適宜接続の対応を変えてください。
イラストとはちょっと違いますが、実際の配線の写真がこちらです。
すみません、GPIO拡張アダプタとかいうちょっとズルいツールを使っています。
接続テスト
Raspberry PiからATtiny85が見えるかどうか接続テストをするために次のコマンドを実行します。
pi@raspberrypi:~ $ sudo avrdude -p t85 -C my-avrdude.conf -c pi_1 -v
-p t85
: AVRデバイスを指定するオプションです。ATtiny85の場合はt85となります。何が指定できるかは$ avrdude -p ?
で確認できます。-C my-avrdude.conf
: コンフィグファイルの場所を指定するオプションです。デフォルトでは/etc/avrdude.conf
が読み込まれます。-c pi_1
: プログラマー(書き込み器)の種類を指定します。今回はpi_1
という名前でGPIO書き込みを設定しているのでこの名前を指定します。-v
: verbose。出力を詳細にするオプションです。
avrdude: Version 6.3-20171130
Copyright (c) 2000-2005 Brian Dean, http://www.bdmicro.com/
Copyright (c) 2007-2014 Joerg Wunsch
System wide configuration file is "my-avrdude.conf"
User configuration file is "/root/.avrduderc"
User configuration file does not exist or is not a regular file, skipping
Using Port : unknown
Using Programmer : pi_1
AVR Part : ATtiny85
Chip Erase delay : 4500 us
PAGEL : P00
BS2 : P00
RESET disposition : possible i/o
RETRY pulse : SCK
serial program mode : yes
parallel program mode : yes
Timeout : 200
StabDelay : 100
CmdexeDelay : 25
SyncLoops : 32
ByteDelay : 0
PollIndex : 3
PollValue : 0x53
Memory Detail :
Block Poll Page Polled
Memory Type Mode Delay Size Indx Paged Size Size #Pages MinW MaxW ReadBack
----------- ---- ----- ----- ---- ------ ------ ---- ------ ----- ----- ---------
eeprom 65 6 4 0 no 512 4 0 4000 4500 0xff 0xff
flash 65 6 32 0 yes 8192 64 128 4500 4500 0xff 0xff
signature 0 0 0 0 no 3 0 0 0 0 0x00 0x00
lock 0 0 0 0 no 1 0 0 9000 9000 0x00 0x00
lfuse 0 0 0 0 no 1 0 0 9000 9000 0x00 0x00
hfuse 0 0 0 0 no 1 0 0 9000 9000 0x00 0x00
efuse 0 0 0 0 no 1 0 0 9000 9000 0x00 0x00
calibration 0 0 0 0 no 1 0 0 0 0 0x00 0x00
Programmer Type : linuxgpio
Description : Use the Linux sysfs interface to bitbang GPIO lines
Pin assignment : /sys/class/gpio/gpio{n}
RESET = 21
SCK = 25
MOSI = 19
MISO = 16
avrdude: AVR device initialized and ready to accept instructions
Reading | ################################################## | 100% 0.00s
avrdude: Device signature = 0x1e9108 (probably t25)
avrdude: Expected signature for ATtiny85 is 1E 93 0B
Double check chip, or use -F to override this check.
avrdude done. Thank you.
うまく接続できているとこのように Device signature が表示されます。(今回実はATtiny85ではなくATtiny25を接続したので、t25のシグネチャが表示されている)
うまくいかなかった場合は avrdude: AVR device not responding
と表示されます。配線を間違えていないか確認してください。それと、そもそもそのGPIOピンが壊れていないか確認してください(1敗)
PB4に抵抗とLEDを配線
接続確認ができたらPB4に抵抗とLEDをつないでください。
こんな感じで適当に220Ω抵抗と赤色LEDを繋ぎます。LEDのプラスマイナスの向きには注意してください(足が長いほうがプラス)。向きを間違えると光りません。
プログラムを書き込んで実行する
書き込みコマンドを実行します。
pi@raspberrypi:~ $ sudo avrdude -p t85 -C my-avrdude.conf -c pi_1 -v -U flash:w:main.hex:i
-U flash:w:main.hex:i
:flash
メモリにw(書き込み)
を行う ←main.hex
を ←intel hex
形式の-U メモリの種類:操作の種類:ファイル名:ファイル形式
のフォーマットでメモリ操作を実行する
〜(省略)〜
avrdude: AVR device initialized and ready to accept instructions
Reading | ################################################## | 100% 0.00s
avrdude: Device signature = 0x1e9108 (probably t25)
avrdude: safemode: lfuse reads as 62
avrdude: safemode: hfuse reads as DF
avrdude: safemode: efuse reads as FF
avrdude: NOTE: "flash" memory has been specified, an erase cycle will be performed
To disable this feature, specify the -D option.
avrdude: erasing chip
avrdude: reading input file "main.hex"
avrdude: writing flash (96 bytes):
Writing | ################################################## | 100% 0.07s
avrdude: 96 bytes of flash written
avrdude: verifying flash memory against main.hex:
avrdude: load data flash data from input file main.hex:
avrdude: input file main.hex contains 96 bytes
avrdude: reading on-chip flash data:
Reading | ################################################## | 100% 0.05s
avrdude: verifying ...
avrdude: 96 bytes of flash verified
avrdude: safemode: lfuse reads as 62
avrdude: safemode: hfuse reads as DF
avrdude: safemode: efuse reads as FF
avrdude: safemode: Fuses OK (E:FF, H:DF, L:62)
avrdude done. Thank you.
書き込みがうまくいくと、下のような感じでLEDが点滅します。(動画はずっと前のもの)
Raspberry PiのGPIOでATtiny85にプログラム書き込むのできた pic.twitter.com/aYhDYYrVS0
— いの (@iciclize) March 30, 2020
うまくいかなかった場合は、うまくいくように頑張ってください😇
うまくいった方は、これでATtiny85プログラミングができるようになりました!おめでとうございます!
まとめ
今回は以下のことを行いました。
- クロスコンパイル環境を導入した。
- ATtiny85のピン・ポート・レジスタ・メモリについて知った。
- Lチカのプログラムを書いた。
- ソースファイルをコンパイルしてHEXファイルへ変換して、ATtiny85にプログラムを書き込んだ。
- LEDを光らせた。
次回はマイコンとEEPROMを接続し、マイコンからEEPROMに読み出すのをやってみようと思います。
おまけ1: I/Oインクルードの細かい話
デバイス特有のI/O定義が読み込まれる仕組み
avr/io.h
の中では、マイコンの種類に応じて条件分岐が起こり、そのデバイス特有のI/O定義ヘッダが自動でインクルードされます。 たとえばマイコンの種類を指定するオプション -mmcu=attiny85
をコンパイラに与えた場合、その内部で __AVR_ATtiny85__
というマクロが定義され、avr/io.h
内の条件分岐
#elif defined (__AVR_ATtiny85__)
# include<avr/iotn85.h>
#elif ...
にマッチし、その結果ATtiny85特有のI/O定義ヘッダ avr/iotn85.h
が読み込まれます。(ちなみに avr/iotn85.h
を直接インクルードするのはNGと avr/io.h
内に書いてあります。)
PORTB, DDRB, PINBの実体を探る
PORTB
, DDRB
, PINB
は変数に見えますが、変数ではなく、レジスタのアドレスを指すポインタです。
その実体を探ってみましょう。
まずは次のような定義を avr/iotnx5.h
の中に見つけることができます。
...
#define PINB _SFR_IO8(0x16)
#define PINB5 5
#define PINB4 4
#define PINB3 3
#define PINB2 2
#define PINB1 1
#define PINB0 0
#define DDRB _SFR_IO8(0x17)
#define DDB5 5
#define DDB4 4
#define DDB3 3
#define DDB2 2
#define DDB1 1
#define DDB0 0
#define PORTB _SFR_IO8(0x18)
#define PB5 5
#define PB4 4
#define PB3 3
#define PB2 2
#define PB1 1
#define PB0 0
...
_SFR_IO8()
とは何か?これを探ると今度は avr/sfr/defs.h
に
#define _SFR_IO8(io_addr) _MMIO_BYTE((io_addr) + __SFR_OFFSET)
と書かれています。レジスタの正体まであともうちょっとです。
同じファイルにこのような定義も書かれています:
#define __SFR_OFFSET 0x20
#define _MMIO_BYTE(mem_addr) (*(volatile uint8_t *)(mem_addr))
これらの定義より、 _SFR_IO8(io_addr)
は (*(volatile uint8_t *)((io_addr) + 0x20))
というポインタに展開されます。
つまり、
PORTB
は最終的には(*(volatile uint8_t *)((0x18) + 0x20))
に展開されます。DDRB
は最終的には(*(volatile uint8_t *)((0x17) + 0x20))
に展開されます。PINB
は最終的には(*(volatile uint8_t *)((0x16) + 0x20))
に展開されます。
PORTB = 0x10
という変数への代入のような記述は、 (*(volatile uint8_t *)((0x18) + 0x20)) = 0x10
と書くのと等価であるというわけです。
この記述こそ、データメモリアドレス 0x38 (つまりレジスタPORTB) に直接アクセスする記述です。
おまけ2: レジスタ書き込みのスマートな記述方法
avr/iotnx5.h
で #define PB4 4
と定義されているおかげで、
DDRB = 0b00010000;
は、ビットシフトを使ってこんな書き方をすることができます:
DDRB = (1 << PB4);
複数のビットを立てた値を設定するときは、ビット演算を使って、こうです:
DDRB = (1 << PB4) | (1 << PB2) | (1 << PB0);
ちなみに avr/sfr_defs.h
に
#define _BV(bit) (1 << (bit))
という記述があるので、もっと短くしてこんな書き方もできます:
DDRB = _BV(PB4) | _BV(PB2) | _BV(PB0);
特定のビットPB4, PB2, PB0だけ立ててその他のビットはそのままの値にしておきたい場合は、このように書きます:
DDRB = DDRB | _BV(PB4) | _BV(PB2) | _BV(PB0);
または
DDRB |= _BV(PB4) | _BV(PB2) | _BV(PB0);
といっても、本当に「そのままの値」にしておけるわけではなく、最初にレジスタの値を読み込んでから、変更の必要のないビットについては同じ値で書き戻しています。
特定のビットPB4, PB2, PB0だけ落としてその他のビットはそのままの値にしておきたい場合は、このように書きます:
DDRB = DDRB & ~( _BV(PB4) | _BV(PB2) | _BV(PB0) );
または
DDRB &= ~( _BV(PB4) | _BV(PB2) | _BV(PB0) );
参考文献
- ArduinoISPを汎用AVRライタとして使う(2) - しなぷすのハード製作記 https://synapse.kyoto/tips/ArduinoISP_AVRWriter/page002.html
- AVRマイコンの紹介 http://www.ikushima.jp/avrpage/avrdoc/avrdoc.html
- AVR-GCC 導入編 https://www.clarestudio.org/elec/avr/gcc-1.html
- デバイスにアクセスするには | 学校では教えてくれないこと | [技術コラム集]組込みの門 | ユークエスト株式会社 https://www.uquest.co.jp/embedded/learning/lecture13.html
- マイコン入門 入出力ポートの使い方【初めてのレジスタ制御】 https://monozukuri-c.com/mbase-ioport/
- AVR avr/io.h インクルードファイルは一体何をしているのか??: マイコン漬け http://morokyuu.way-nifty.com/blog/2012/12/avr-avrioh-8475.html
- Atmel 8-bit AVR Microcontroller with 2/4/8K Bytes In-System Programmable Flash https://akizukidenshi.com/download/ds/atmel/attiny25_attiny45_attiny85.pdf
- gccコマンドは呼び出し屋さん gccコマンドの裏側 https://kaworu.jpn.org/kaworu/2011-11-10-1.php
- what is compiler doing http://nenya.cis.ibaraki.ac.jp/TIPS/compiler.html
- AVRDUDE: 4. Configuration File http://www.nongnu.org/avrdude/user-manual/avrdude_10.html#Configuration-File
- avr-objcopy http://ccrma.stanford.edu/planetccrma/man/man1/avr-objcopy.1.html
ほかの記事
- CH341A EEPROM programmer を使ってMacbook AirのUSB経由で 24LC512 に読み書きを行いたい記事です。EEPROMが認識されません。困りましたね。
- 防犯ブザーを改造してサウンドロップ風のオリジナルおもちゃを自作する方法を解説する連載のパート1。今回はRaspberry PiでEEPROMに音声を書き込みます。
- デフォルトゲートウェイには、同一セグメント内の/直接接続された/リンク上の ホストのIPアドレスを設定します。そのIPアドレスからそのホストのMACアドレスを調べられることが条件です。
- ふと、Let's Encryptじゃない認証局の証明書でhttps通信したいなと思ったので、Buypassという認証局を試してみました。でも珍しい証明書を使うということはつまり・・・