第 9 回 C言語

本日の内容


このドキュメントは http://edu.net.c.dendai.ac.jp/ 上で公開されています。

9-1. プログラムの待機

近年の組み込み系のプログラミングは生産効率を上げるためや、習熟期間の短 縮などのために C 言語での開発が主流になってきています。 但し、これから行うマイコンのプログラミングにおける C 言語のプログラミ ングは、プログラミングの初心者が習う C 言語のプログラミングと異なり、 かなりハードウェアの構造を意識したプログラミングを行うことになります。

通常の C 言語とマイコンの C 言語の扱いの違いとして次があります。

  1. 基本的なデータが32bitである int 型ではない。特に、マイコンでは int 型が 16bit になることがある。さらに、8bit マイコンでは char や unsigned char が主となるデータ単位となる。
  2. 入出力はすべてプログラムにより制御しなくてはいけない。 つまり、 printf や scanf などの標準入出力などは使えない。 I/Oポートを見たり、値を入れたりすることで入出力を行う。
  3. ヘッダファイルで、I/Oレジスタがグローバルに定義される。 グローバルな変数を管理する手法が重要となる。
  4. プログラム中で監視する変数が、割り込みや、外部からの操作によりプログラ ムの制御以外で変化しうる。 この時、その変数に対して volatile 宣言をする必要がある。
  5. main 関数は return で終了させない

なお、 公式に出されている ATtiny2313 などに対する C 言語の手引きにはつ ぎのものがあります。

  1. AVR035: Efficient C Coding for 8-bit AVR microcontrollers
  2. AVR1000: Getting Started Writing C-code for XMEGA
  3. Atmel AVR4027: Tips and Tricks to Optimize Your C Code for 8-bit AVR Microcontrollers

9-2. C 言語の基礎

LEDを光らせる

C言語を扱う上で、通常のプログラミングテクニックはそのまま使うことがで きます。 しかし、通常の計算して結果を出力して終了するプログラムと違う点は、前 処理の後に、無限ループを作る点です。

また、入出力は I/O ポートを使用することになりますが、これはグローバル 変数が用意されています。 この変数を使うのに、 arv/io.h というヘッダファイルを読み込みます。 なお、このヘッダファイルは C:\Program Files (x86)\Atmel\Atmel Toolchain\AVR8 GCC\Native\3.4.1056\avr8-gnu-toolchain\avr\include\avrにあり ます。

例9-1

例5-1と同じ問題を C言語でやってみましょう。

あらかじめ与えられた特定のパターン(例えば 2 進数で与える)の値を PORTB に出力し、 LED を光らせたままにするプログラムを作りなさい。 (ヒント: プログラムを止めるには sleep ではなく、単なる無限ループを作ります。 )

これを行うには、次を行う必要がありました。

  1. PORTB を出力にするために DDRB のビットをすべて ON にする
  2. PORTB の各ビットを実際に ON にする
  3. 無限ループ

C言語で唯一アセンブラと違って不便なのは二進数を記述することができない ことです。 そのため、通常は16進数を使用します。 つまり、8bit すべてを ON にするには 0b11111111 の代わりに 0xff を使用 します。 avr/io.h ファイルをインクルードすると、 DDRB, PORTB ともアセンブラの時 と同様に、今度はラベルの代わりに変数として使用することができます。

作成したプログラムは下記のようになります。


/************
*  例9-1 *
************/
#include <avr/io.h>

int main(void){
	DDRB = 0xff;
	PORTB = 0x55;
    while(1){
    }
}

SWに対応する

次に、特定のスイッチ操作に反応するプログラムを作ります。

例9-2

switch プログラムの移植を考えます。 つまり、次のような処理を考えます。

  1. PORTBを出力ポートにする
  2. PORTDを入力ポートにする
  3. 以下を繰り返す

    PORTD の 4bit 目が ON だったら PORTB に 0 を入れ、そうでなければ PORTB に0xff を入れる

このようにして組んだプログラムを次に示します。


/************
*  例9-2 *
*  switch  *
************/
#include <avr/io.h>

int main(void){
    DDRB = 0xff;
    DDRD = 0;
    while(1){
        if(PIND & (1<<4)){
		PORTB = 0;
	}else{
		PORTB = 0xff;
	}
    }
}

変数宣言

次に、ボタンを押す度に表示の On, Off を繰り返すプログラムの演習をしま した。 これを実現するには、前回のボタンの値を覚えておく必要がありました。 そのため、 C 言語では変数を宣言して、値を記憶させることを考えます。 但し、AVR用のC言語はデフォルトで変数宣言をすると 16bit の領域を確保することに なりますので、8bitマイコンを扱う上で、ビット長を考える必要があります。

文献[3]の表3-1を下記に引用します。

Data typeSize
signed char / unsigned char int8_t / uint8_t 8-bit
signed int / unsigned int int16_t / uint16_t 16-bit
signed long / unsigned long int32_t / uint32_t 32-bit
signed long long / unsigned long long int64_t / uint64_t 64-bit

例9-3

ボタンを押す度に表示を反転させるため、まず次の 8bit の変数を宣言します。

PORTD の 4bit 目が ON だったら PORTB に 0 を入れ、そうでなければ PORTB に0xff を入れる

このようにして組んだプログラムを次に示します。


/************
*  例9-3 *
************/
#include <avr/io.h>

int main(void){
	int8_t current = 0;
	int8_t last = 0;
	int8_t pattern = 0xff;
	DDRB = 0xff;
	DDRD = 0;
	while(1){
		current = PIND;
		if((!(last & (1<<4)) && (current & (1<<4)))){
			pattern = ~ pattern;
		}
		PORTB = pattern;
		last = current;
	}
}

例9-4

次に、 flash の移植を考えましょう。 flash はビジーウェイトを用いて、ほぼ1秒毎に表示を点滅させてました。 ここでは int32_t を使って、 一重ループで反転するようにしましょう。

C言語の変数において、プログラムの流れ的には意味が無かったり、値が変わ らないようでも、実際は必ず定義されて無いといけないような変数を使うとき、 volatile 宣言をします。 これをすると、最適化の際に変数その物を消されたりすることがなくなります。 今回はビジーウェイトという、プログラム上はなくしても計算の結果には影響 せず、計算時間を遅くするだけの変数に対して、volatile 宣言をしています。


/************
*  例9-4 *
*  flash *
************/
#include <avr/io.h>
void wait(void){
    volatile int32_t i;
    for(i=0;i<35000;i++){
    }
}
int main(void){
    DDRB = 0xff;
    while(1){
	PORTB = 0xff;
	wait();
	PORTB = 0;
	wait();
    }
}

volatile 宣言を取ってコンパイルすると、最適化によりビジーウェイトその 物が取り除かれることが分かると思います。

配列変数

次に、特定のパターンを順番に表示させてみましょう。 パターンを作成、保持するのに、配列変数を使います。

なお、配列変数に入っている要素数は、(配列変数の全バイト数)/(配列変数の 要素のバイト数)ですので sizeof(配列変数)/sizeof(配列変数[0]) で計算できます(ポインタではできないので要注意)。

例9-5


/************
*  例9-5 *
************/
#include <avr/io.h>
void wait(void){
    volatile int32_t i;
    for(i=0;i<35000;i++){
    }
}
int main(void){
    const uint8_t pattern[]={0,0x01,0x03,0x07,0x0f,0x1f,0x3f,0x7f,0xff};
    int8_t i;
    DDRB = 0xff;
    while(1){
	for(i=0;i<sizeof(pattern)/sizeof(pattern[0]);i++){
            PORTB = pattern[i];
	    wait();
        }
    }
}

演習問題

演習9-1

PORTB につながっている LED が 0,1,2,3,4,5,6,7,8,9,A,b,C,d,E,F を順番に表示するプログラムを作りなさい。

演習9-2

PD4 に接続した sw を押す度に PORT B に つながっている LED が 0,1,2,3,4,5,6,7,8,9,A,b,C,d,E,F を順番に表示するプログラムを作りなさい。

9-3. 割り込み

C言語で割り込みを行う場合、 avr/interrupt.h をインクルードします。 そして、次のような手順で割り込みのプログラムを記述します。

  1. ISR (Interrupt Service Routine)マクロを使い、ISR(割り込みベ クタ名)という名前で関数を定義すると、その割り込みでその関数 が呼ばれるようになる。
  2. main の冒頭で割り込み用の各種レジスタを設定する。
  3. sei() 関数呼び出しで、割り込みが許可される
  4. while(1) により無限ループを作り、割り込みを待つ

9-4. 待機

CPU を sleep させるには avr/sleep.h をインクルードします。 そして、次のように使用します。

  1. set_sleep_mode(スリープモード) マクロで、スリープのモードを決定す る。
  2. sleep_cpu() 関数でスリープする
参考文献
  1. AVR1010: Minimizing the power consumption of AtmelAVR XMEGA dvices

例9-6

例9-5と同様に一定時間毎に別のパターンを表示するプログラムを、割り込み と sleep を使用して書いたのが以下のプログラムです。


/************
*  例9-6 *
************/
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>

const unsigned char pattern[]={0x00,0x01,0x03,0x07,0x0f,0x1f,0x3f,0x7f,0xff};
volatile int8_t counter;
void inccounter(){
	counter++;
	if(counter>=sizeof(pattern)/sizeof(pattern[0])){
		counter=0;
	}
}
void dispcounter(){
	PORTB = pattern[counter];	
}
ISR( TIMER0_OVF_vect){
	inccounter();
	dispcounter();
}
int main(void){
	DDRB = 0xff;	
	set_sleep_mode(SLEEP_MODE_IDLE);
	TCCR0A = 0x00;
	TCCR0B = 0x05;
	TIMSK = 0x02;
	
	counter =0;
	dispcounter();
	
	sei();                          // 全体の割込を許可
	while(1){
		sleep_cpu();
	}
}

演習9-3

演習9-1同様に PORTB につながっている LED が 0,1,2,3,4,5,6,7,8,9,A,b,C,d,E,F を順番に表示するプログラムを割り込みと、sleepを使って作りなさい。

演習9-4

演習9-2同様に PD4 に接続した sw を押す度に PORT B に つながっている LED が 0,1,2,3,4,5,6,7,8,9,A,b,C,d,E,F を順番に表示するプログラムを作りなさい。 但し、割り込みを使用して、小数点が 0.5 秒毎に点滅をするようにしなさい。

演習9-5

スイッチを押す度に、小数点が暗くなっていくプログラムを作りなさい。

演習9-6

スイッチを押す度に、LED の数字が増えていき、小数点が暗くなっていくプロ グラムを作りなさい。


坂本直志 <sakamoto@c.dendai.ac.jp>
東京電機大学工学部情報通信工学科