プログラミングと工作と

PythonとかPascalとかAVRマイコンとか、コンパイラつくったり電子工作なんかを楽しんでいるおっさんの記録

AVRマイコン アセンブリ言語入門 ― 外部割込み

前回、割込み動作の説明をやったついでに、外部割込みの説明をします。

  • 外部割込みってなんだよ?

たいていのマイコンは、外部割込み端子ピンを持っています。
AVRmega48/88/168/328シリーズだと、INT0, INT1 の2本の端子があり、ピン4,5番端子がそれにあたります。
このピンに、外部から変化をあたえると、割込み動作をさせることが出来ます。
例えば、外部割込み端子にスイッチをつないでおくと、スイッチ入力に使えます。

 

  • 外部割込みってどうやって使うの?

AVRマイコンの各機能を使うには、それぞれの設定レジスタに値を設定するんでしたね。
外部割込みには、2つの設定内容(レジスタ)があります。

  1. 外部割込み条件の設定
  2. 外部割込みの許可を設定

1の外部割込み条件とは、外部割込み端子をどういう状態にすれば割込みを発生させるかを設定します。
「どういう状態」には4種類あり、以下になります。


 1.端子をLowにする
 2.端子に変化を与えた時(Low→High,またはHigh→Low)
 3.端子をHigh→Lowにする下降エッジで発生
 4.端子をLow→Highにする上昇エッジで発生

 

2の許可の設定は、そのままの意味で、外部割込みの使用を許可するための設定です。

設定レジスタの一覧を下に示します。

f:id:takashira:20151212214325p:plain

はい、外部割込みの設定はたったこれだけです。
簡単ですね。


では、ちょっと簡単なプログラムをつくってみます。
つくるのは、外部割込み端子0をHigh→Lowにすると、PortBのビット0端子に接続したLEDが反転する、というものにします。

 

上の表から、
EICRAレジスタのISC01,ISC00ビットを '10' へ、
EIMSKレジスタのINT0ビットを '1' へ
セットすればいいことがわかりますね。

で、プログラムが以下になります。

/*
 * EINT1.asm
 *
 *  Created: 2015/12/12 20:24:42
 *   Author: Takachiho
 */ 

.include "m168pdef.inc"

;************************************************
; 各種定義
;************************************************
;--------------------
; 汎用レジスタ
;--------------------

.def	STACK = R16     ;汎用レジスタR16に'STACK'という名前をつける

;--------------------
; 割り込みベクタ定義
;--------------------

.CSEG
		RJMP	MAIN
.ORG	0x0002          ;外部割込み0発生で
		RJMP	EINT0   ;ラベルEINT0へジャンプする。

;************************************************
;  割り込みルーチン 外部割込み0
;************************************************

EINT0:
;------- ステータスレジスタの内容を退避  --------

		IN	STACK,	SREG

;------- LED ON/OFF を切り替え ------------------

		IN  	R17,	PORTB       ;PortBの内容をR17へコピーする
		LDI 	R18,	0x01        ;R18へ値0x01 = 0b00000001を代入
		EOR 	R17,	R18         ;R17とR18の排他的ORの結果をR17へ
		OUT 	PORTB,	R17         ;R17の結果をPortBへ代入する
		
		;8bitタイマの時と同じ処理

;------- ステータスレジスタの内容を復帰  --------

		OUT		SREG,	STACK

		RETI

;************************************************
;  メイン・ルーチン (MAIN)
;************************************************
MAIN:

;------- 全割り込み禁止 -------------------------

		CLI

;------- PORT 設定  -----------------------------

		LDI		R17,	0b11111111  ;ポートBは全て出力に設定
		OUT		DDRB,	R17
		LDI		R17,	0b00000001  ;bit0以外すべて'0'出力
		OUT		PORTB,	R17

		LDI		R17,	0b11111111  ;ポートCは全て出力に設定
		OUT		DDRC,	R17
		LDI		R17,	0b00000000  ;すべて'0'出力
		OUT		PORTC,	R17

		LDI		R17,	0b11111011  ;PD2(INT0 pin)を入力に設定
		OUT		DDRD,	R17
		LDI		R17,	0b00000100  ;PD2はプルアップ設定
		OUT		PORTD,	R17

;------- 外部割込み0レジスタ設定 ---------------

    ;-------  外部割込み条件 設定  -------

		LDI		R17,	0b00000010	;下降エッジで割込み指定
		STS		EICRA,	R17

    ;-------  割込み許可 設定  -------

		SBI		EIMSK,	INT0		;INT0ビットを'1'へ

;-------  全割り込み許可  -----------------------

		SEI


MAIN01:
		RJMP	MAIN01

こまかいことを言うと、スイッチのチャタリング処理をちゃんとしないときれいに反転しないのですが、割込みが働いているのは良くわかります。

AVRマイコン アセンブリ言語入門 ― 割込み動作

  • 割込み動作ってなに?

ある人曰く、マイクロプロセッサの最大のイノベーションは割込み動作、だそうです。
基本的に、ひとつのプロセッサは一度にひとつの処理しか出来ません。
割込み動作とは、プロセッサが一生懸命ある処理をしている途中で、「あ、俺の仕事を優先で、」と横から割り込んでプロセッサの処理動作を奪い取ることです。

 

  • 割込みってどんな仕組みなの?

マイクロプロセッサの動作をちょっとでもわかっていれば、割込み動作の仕組みは簡単です。
AVRマイコンでいうと、我々が書いたプログラムはフラッシュメモリエリアへ書き込まれます。
マイコンは、電源を入れたりリセットボタンを押されたりすると、このフラッシュメモリの0番地から命令を読み込んで動作を開始します。
その後も番地を一つ進めて命令を読み込む、また一つ番地を進めて読み込む・・・をひたすら続けていきます。
ちなみに、このメモリ番地から命令を読み込む動作のことを「フェッチ」というみたいです(豆)。


この、読み込む番地を入れている場所をPC(プログラムカウンタ)と言います。
CPUは、このPCに入っている番地から命令を読み込もうとするので、処理の途中でPCの値を書き換えると、その番地へ飛んで行って命令を読み込もうとします。


そう、割込みが発生すると、PCの値を割込み処理のプログラムが書いてあるメモリ番地に書き換えます。
もうちょっと正確に言うと、割込みが発生すると、それぞれの割込み内容で決められているプログラムアドレスをPCに設定します。

例えば、8bitタイマ0 COMPAレジスタ一致割込みが発生すると0x001C番地をPCに設定する、と言った具合です。


この決められた番地それぞれに、実際の処理内容が書かれた番地へのジャンプ命令を書き込んで割込み処理をさせるという2段階右折状態で割込み処理へ飛んでいきます。


ここでいう「割込み内容に対応して決められた番地」の一覧を「割込みベクタテーブル」といいます。

ATmega48〜328シリーズの割込みベクタテーブルを書いておきます。

 

f:id:takashira:20151129184924p:plain

ややこしい話は置いておいて、割込みを使うには、

  1. 上の表から使いたい割込みのアドレスを調べる。
  2. そのアドレスに割込み処理へのジャンプ命令を書く。

まあ、基本はこれだけです。


.CSEG                           ←プログラムの先頭(0番地)を意味する
             RJMP    MAIN   ←MAINというラベルの処理へジャンプする
.ORG    0x001C     ←0x001Cへ飛んできたら
             RJMP    ICTC0 ←ICTC0ラベルの処理へジャンプする
.ORG    0x002A
             RJMP    ADCOK

 

上の例は定形表現みたいなものなので、ベクタアドレスの部分と、自分のプログラムのラベル部分に合わせれば、それだけで割込み処理の完成です。

 

  • 使用上の注意

「それだけで割込み処理の完成です」といった舌の根も乾かないうちになんですが、割込み処理を書く上でいくつかの注意点があります。


まずは「許可」が必要です。
各機能がそれぞれ勝手に割込みしまくったら、処理がめちゃくちゃになってしまいます。
割込みには「今なら割込みOKだよ」といった許可が必要になります。
これには2種類あって、

  1. 全割り込み許可
  2. 機能別の割込み許可

の2つを同時に満たしてはじめて割りこみが実行されます。

 

割込みを使うには、このような割込み許可を含めた初期設定が必要になりますが、その初期設定のやりかたは

  1. メインルーチンの最初に全割り込み禁止(CLI命令)を行う
  2. ポートの入出力や各種機能別の初期設定を行う
  3. 全割り込み許可(SEI命令)を行う

の順番で行います。

 

また、割込み初理中にほかの割込みを行う(多重割込みという)のは基本的に禁止されています。
割込み処理が発生すると、ハードウェアは自動的に全割り込みを禁止にします。
割込み処理が終了すると、自動的に再び許可に戻ります。


多重割込みを行いたい場合は、この辺をうまくコントロールしなければなりませんが、処理が複雑になるのでもっと先でやりましょう。

 

では、今回はこのへんで。

AVRマイコン アセンブリ言語入門 ― 8bitタイマその4

さて、今回はいよいよ8bitタイマを使ったLED点滅プログラムを作ってみます。

まずは、レジスタの設定値を決めます。
前回の復習 ー 設定する項目は

  1. 動作モード:ノーマルモードかCTCモードか等
  2. プリスケーラの値
  3. カウント比較値
  4. 割込み要求の設定

になります。

今回は、200msごとにLEDを点滅させてみます。
この200msを8bitタイマを使って作り出します。

 

CPUクロックはデフォルトでは1MHzなので、クロック1パルスが1μs、
プリスケーラを1024にしてカウント比較値を196にすると
1μs×1024×196 = 200.704ms
これで行きます。

 

使用するのはタイマ0、動作モードは以前話したようにCTCモード一択なので、これでレジスタ値が全部決まります。

  1. 動作モード :CTCモード  ->  WGM02〜WGM00 = 010
  2. プリスケーラの値 :1024  ->  CS02〜CS00 = 101
  3. カウント比較値 :196  ->  OCR0A = 0b11000100 = 0xC4

前回の表にこれらの値を入れると、それぞれのレジスタ値は以下になります。

TCCR0A = 0b00000010
TCCR0B = 0b00000101

OCR0A = 0b11000100 = 0xC4

さあ準備が出来ました。
プログラムを書いていきます。

 

/*
 * LED_Timer.asm
 *
 *  Created: 2015/09/16 20:56:21
 *   Author: Takachiho
 */ 
;  PortBのBit0に接続したLEDを、
; Timer0のCTCモードを使って点滅させる
;  約200ms間隔で点滅させる

.include "m168pdef.inc"

;************************************************
; 各種定義
;************************************************
;--------------------
; 汎用レジスタ
;--------------------

.def	STACK = R16     ;汎用レジスタR16に'STACK'という名前をつける

;--------------------
; 割り込みベクタ定義
;--------------------

.CSEG
		RJMP	MAIN
.ORG	0x001C          ;8bitタイマ0からの割込みが発生すると
                        ;マイコンはこのアドレスを実行する。
		RJMP	ICTC0   ;ラベルICTC0へジャンプする。

;************************************************
;  割り込みルーチン タイマ0
;************************************************

ICTC0:
;------- ステータスレジスタの内容を退避  --------

		IN	STACK,	SREG

;------- LED ON/OFF を切り替え ------------------

		IN  	R17,	PORTB       ;PortBの内容をR17へコピーする
		LDI 	R18,	0x01        ;R18へ値0x01 = 0b00000001を代入
		EOR 	R17,	R18         ;R17とR18の排他的ORの結果をR17へ
		OUT 	PORTB,	R17         ;R17の結果をPortBへ代入する
		
		;排他的論理和(エクスクルーシブオア)は、ビット反転に使われる。
		;'1'とのEORをとると、相手が'1'なら'0'へ、'0'なら'1'へ
		;反転する。
		;この性質を利用して、LEDが点灯'1’しているなら消灯'0'
		;消灯しているなら点灯させる動作を行わせる。

;------- ステータスレジスタの内容を復帰  --------

		OUT		SREG,	STACK

		RETI

;************************************************
;  メイン・ルーチン (MAIN)
;************************************************
MAIN:

;------- 全割り込み禁止 -------------------------

		CLI

;------- PORT 設定  -----------------------------

		LDI		R17,	0b11111111  ;ポートBは全て出力に設定
		OUT		DDRB,	R17
		LDI		R17,	0b00000001  ;bit0以外すべて'0'出力
		OUT		PORTB,	R17

		LDI		R17,	0b11111111  ;ポートCは全て出力に設定
		OUT		DDRC,	R17
		LDI		R17,	0b00000000  ;すべて'0'出力
		OUT		PORTC,	R17

		LDI		R17,	0b11111111  ;ポートDは全て出力に設定
		OUT		DDRD,	R17
		LDI		R17,	0b00000000  ;すべて'0'出力
		OUT		PORTD,	R17

;------- 8bitタイマレジスタ設定 ---------------

    ;-------  動作モード 設定  -------

		LDI		R17,	0b00000010	;CTCモードを指定
		OUT		TCCR0A,	R17

    ;-------  プリスケーラ 設定  -------

		LDI		R17,	0b00000101	;プリスケーラ 1024
		OUT		TCCR0B,	R17

    ;-------  比較値 セット  -------

		LDI		R17,	0xC4        ;比較値196 = 0xC4に設定
		OUT		OCR0A,	R17

    ;-------  タイマ割り込み許可  -------

		LDS		R17,	TIMSK0          ;TIMSK0レジスタの値をR17へ
		SBR		R17,	(1 << OCIE0A)   ;ビット1(OCIE0A)を'1'へセット
		STS		TIMSK0,	R17             ;TIMSK0レジスタに値を代入
		
		;TIMSK0レジスタのビット1(OCIE0A)を'1'へセットすることにより
		;カウント値がOCR0Aレジスタ(タイマ/カウンタ0比較レジスタA)の値と
		;一致した時、割込みを発生させる。

;-------  全割り込み許可  -----------------------

		SEI


MAIN01:
		RJMP	MAIN01

 

で、実行させました。

 

f:id:takashira:20151123215602j:plain

大成功!
・・・って、写真じゃ点滅しているところがわからないですね。

 

あと、プログラムの割込みのあたりが良くわからないかもしれません。
次回は割込み動作の説明をしたいと思います。

AVRマイコン アセンブリ言語入門 ― 8bitタイマその3

今回は、8bitタイマを使う時に設定する「レジスタ」の説明をします。
8bitタイマに限らず、マイコンの各機能を使うときは、その機能の設定をするレジスタが用意されています。
このレジスタに値を書き込んで、「こういう動作をしてください」とマイコンにお願いするのです。

 

さて、8bitタイマの設定は、以下の4項目が必要になります。

 

  1. 動作モード:ノーマルモードかCTCモードか等
  2. プリスケーラの値
  3. カウント比較値
  4. 割込み要求の設定

 

これら4項目を以下のレジスタに設定します。 

f:id:takashira:20151123111910j:plain

たとえば、動作モードをCTCモードにする場合、WGM02〜WGM01を’010’に、つまり

と言った感じで設定していきます。

ここで、レジスタ名はタイマ0の名前を使っていますが、タイマ2を使う場合は数字の0を2にすればOKです。

TCCR0A→TCCR2Aといった具合です。

 

さて、4項目目の割込み要求の設定に行きます。

 

f:id:takashira:20151123120726p:plain

割込みの話はまだしていないので、なんのこっちゃ感があるかもしれませんが、プログラムは習うより慣れろの部分もありますので、後でLチカプログラムを書く時にわかってくると思います。

 

これだけのレジスタがわかれば、8bitタイマは動作可能です。

次回から、いよいよプログラムに入りたいと思います。

AVRマイコン アセンブリ言語入門 ― 8bitタイマその2

前回、タイマ動作はクロックのパルスの数を数えて「一定の時間」を生み出すと説明しました。
今回、ここの部分を具体的に説明します。

タイマ動作の「一定の時間」は、次の項目により決定します。

  1. クロック
  2. プリスケーラ
  3. 比較レジスタ(OCRnA)

1のクロックは、言うまでもなくマイコンの動作周波数ですね。
ATmega168Pは、何も設定しない場合、内部発振の1MHzで動作します。
外部にX'tal発振子を付けたりして利用する場合は、その周波数が基準になります。
今回は、デフォルトの1MHzで動作させます。

 

2のプリスケーラとはなんでしょうか?
日本語で言うと分周器ってことでいいのかな?クロックの周波数を半分に、そのまた半分に・・・と、遅くしていくことが出来ます。
フリップフロップというデジタル回路をググれば仕組みもわかると思います。
なんで遅くするかというと、長い時間を作り出すためです。


具体的に考えてみます。
今回の1MHzのパルスひとつの時間は、1/1MHz = 1μs です。
これを100個数えたら100μs = 0.1ms = 0.0001秒です。
で、プリスケーラを使って分周すると、
1/2ならクロックのパルスが2μs、
1/1024なら1024μs = 1.024msになったのと等しくなります。
これらを100個数えたら、1/2の場合200μs、1/1024の場合102.4ms = 0.1024秒になります。
同じ100個のパルスを数えても、0.1msから102.4msまで増えました。


ちなみに、ATmega168Pは2つの8bitタイマを持っていて、それぞれタイマ0とタイマ2と名前がついています。
タイマ1はどこにいったと思われるかもですが、16bitタイマとして君臨しています。
使い方は8bitタイマとほぼ同じなので、8bitがわかれば怖くありません。


で、このタイマ0とタイマ2でプリスケーラの設定値が違います。
タイマ0は1・8・64・256・1024分の一が
タイマ2は1・8・32・64・128・256・1024分の一が選べます

 

3の比較レジスタは、プリスケーラからのパルスを何個数えるのかという値を入れるレジスタです。
8bitタイマと言う名称は、このレジスタが8bitになっているから付けられた名前なのです。
8bitということは、十進数でいうと256まで数えることができます。
ちなみにタイマ1の16bitタイマとは、このレジスタが16bitになっただけです。

 

 

さて、プリスケーラと比較レジスタを使った最大カウント数を考えてみます。
クロックを1MHzとすると、1μs × 1024 × 256 = 256ms となります。
クロックを最大の20MHzとするとクロックひとつは1/20MHz = 0.05μs なので
0.05μs × 1024 × 256 = 約13.1ms になります。

このように、プリスケーラと比較レジスタの値を組み合わせて「一定の時間」を作り出します。

 

うすうす感づいている方もいると思いますが、例えばクロック1MHzでピッタリ100msの間隔がほしいと思ってもできません。
プリスケーラを1024、比較レジスタを97にして99.328ms、98で100.352msです。
こういう場合、近い値で妥協するか、「ぴったりじゃなきゃダメなんだよ!」と言うのであればクロック動作周波数を変更する必要があります。

 

次回は、8bitタイマを使う時に使用する各種レジスタについて説明したいと思います。

AVRマイコン アセンブリ言語入門 ― 8bitタイマ

前回までで、環境の構築からアセンブル、書き込み、動作確認までをやりました。

今回から、AVRマイコンが持っている沢山の機能を、一つづつ攻略していきたいと思います。

初回の今回は、「8bit タイマ」です。

 

  • タイマって何するの?

なにかつくろうとする時、一定の時間ごとに処理をしたいってことがあります。
一秒ごとに秒針を動かす時計とか、一定時間内のパルス数を数える周波数カウンタとか・・・
この「一定の時間」を生み出す機能がタイマになります。

 

  • どうやって「一定の時間」をつくるの?

実は、そもそもマイコンは「一定の時間」をもとに動いていますよね。
そう、基準動作クロックってやつです。
今使っているATmega168Pだと、最高20MHzまで使えるあの「クロック」です。
このクロックのパルス一個づつを決まった数だけ数えて「一定の時間」を生み出すのです。

 

  • 動作モード

AVRマイコンのタイマには、「ノーマルモード」と「CTCモード」の2種類の動作モードがあります。
ノーマルモードというくらいなので、こちらが標準的な使い方だろう・・・と思ったのですが、はっきり言ってこのモード、使い道がよくわかりません。
いや、僕の知識と経験が浅いからなのでしょうが、どういう場面でノーマルモードを使えばよいのか、今の僕にはよくわからないのです。
この先、使っていくうちに「あっ!こういうことかっ!!」と言う時が来るかもしれないので、それまでこのモードは無視することにしました。

 

というわけで「CTCモード」です。
おそらく、タイマ動作が必要になればCTCモードで全てカバー出来ると思いますし、はっきり言ってノーマルモードよりも使い方が簡単です。


次回から、具体的な使い方と、実際に動作させてみて、あの「Lチカ」をやってみたいと思います。

AVRマイコン アセンブリ言語入門 ― ポート出力その3

今回は、前回書いたプログラムをアセンブルして、マイコンに書き込み、動作確認する、と言うところまでを行います。

前々回のAtmel Studioのプログラム書き込み部分にソースを書き込みます。

f:id:takashira:20151108230458p:plain

プログラムを入力し終わったら、Build → Build Solution をクリックするか、F7を押します。

するとアセンブルが始まります。

下の「Output」の覧にBuild succeeded と出れば成功です。なにか間違いがあればエラーが出るので、ソースファイルを修正します。

f:id:takashira:20151108230624p:plain

アセンブルが正常に終われば、いよいよ書き込みです。
マイコンボードと書き込み器、パソコンを接続します。

f:id:takashira:20151108230818j:plain

f:id:takashira:20151108231522j:plain

 

Atmel Studio の Tool → Device Programming をクリックします。
すると、Device Programming 画面が出てきます。

f:id:takashira:20151108230958p:plain

この上の部分のToolを「AVRISP mkⅡ」、Deviceを「ATmega168P」、InterfaceをISPにして、横のApplyボタンを押します。
そうすると、書き込みやチップの設定などが行える状態になります。
今回は設定はなにも変えません。左横のMemoriesの項目をクリックすると書き込みの画面になります。

f:id:takashira:20151108231154p:plain

真ん中の「Program」ボタンを押すと書き込みが始まります。
そうすると、ポートBのPB0へつないだLEDが点灯すると思います。
大成功ですね!

f:id:takashira:20151108231328j:plain