迫真の氷結晶

初投稿です。

  • HOME
  • 作品集
  • 生活系
防犯ブザーを改造してサウンドロップを自作する Part2 - Raspberry PiのGPIOでAVRマイコンにプログラムを書き込む

防犯ブザーを改造してサウンドロップを自作する Part2 - Raspberry PiのGPIOでAVRマイコンにプログラムを書き込む

2021-06-20(更新: 2022-04-23)
  • #技術系
  • #電子工作

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に書き込むまでの流れを次の図に示します。

C言語がコンパイルされてATtinyX5に書き込まれるまでの手順
C言語ソースコードをavr-gccに渡し実行ファイルを生成し、その実行ファイルをavr-objcopyに渡しhexファイルを生成し、そのhexファイルをavrdudeに渡してGPIO経由でATtinyX5にプログラムを書き込む。

コンパイルから書き込みまでに行う手順は次の3つです。

  1. avr-gcc というコマンドを使って、C言語ソースコード(xxx.c)をコンパイルして実行可能ファイル(xxx.elf)に変換する。
  2. avr-objcopy というコマンドを使って、実行可能ファイル(xxx.elf)をhexファイル(xxx.hex)に変換する。
  3. 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本の足、ピン(端子)について見ていきます。

https://avr.jp/user/ds.htm

このページから tiny45.pdf を開いて、ATtiny85のデータシート(日本語版)を見てください。( /user/DS/PDF/tiny45.pdf というパスに存在します。)

なぜ直リンクしないかというと、avr.jp に書いてある注意書きがなにやら香ばしくて、それを犯すと面倒な事になりそうだからです…。管理者は一人でAVRの技術情報を翻訳しまくっている熱心な方のようで、過去に翻訳物を巡って何やらやらトラブルがあったようですね。こわいな〜とづまりすとこ…

1. ピン配置 という章にATtiny85(25/45と共通)のピン(端子)の図と説明があります。

ATtiny25/45/85のピン配置
ATtiny25/45/85のピン配置(画像は秋月電子通商のサイトに置かれているデータシートから)
  • 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ポート用レジスタ に載っています。

ポートBに対応するレジスタとそのI/O空間上での場所
ポートBに対応するレジスタとその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

  • ピンが入力モードのときに入力値が格納されるレジスタ。
  • ふつうは読み込んで使うレジスタだが、ここに書き込むこともできる。
    • PINB1を書き込んだポートピンは出力が反転し、1のときLOW出力, 0のときHIGH出力になる(ローアクティブ)
  • I/O空間上でのアドレス: 0x16
  • (出力モードのときに読み込むと出力値がそのまま読み込まれるんだっけ…?)

まとめ: ATtiny85の入出力を知る

  • ATtiny85にはPB5からPB0までのピンがある。
  • ポートBを操作するとピンの電圧や入力受け付け状態をコントロールできる。
    • ポートを操作するには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バイトの大きさを持つ。
avr-memory
AVRマイコンにはデータメモリとプログラム用フラッシュメモリとEEPROMがある。データメモリには汎用レジスタとI/Oレジスタがマッピングされている。

図のデータメモリ上に「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;

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に引かれている間にプログラムを書き込める。
avr-memory
RESETピンがGNDに引かれている間ATtiny85にはSPIバスを使ってプログラムを書き込める。

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)
ATtiny85とRaspberry Pi GPIOの配線図

GPIOピンの割り当てを変えた場合は適宜接続の対応を変えてください。

イラストとはちょっと違いますが、実際の配線の写真がこちらです。

ATtiny85とRaspberry Pi 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をつないでください。

抵抗とLEDの配線イラスト
配線のイラスト
抵抗と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が点滅します。(動画はずっと前のもの)

うまくいかなかった場合は、うまくいくように頑張ってください😇

うまくいった方は、これで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) );

参考文献


ほかの記事

迫真の氷結晶 © 2024