第 5 回 アセンブラ演習

本日の内容


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

5-1. 演習問題

例5-1

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

解答例

;************
;*  例5-1 *
;************
.device attiny2313
.include <tn2313def.inc>
.cseg
.org	0x0000
	rjmp reset
.org	INT_VECTORS_SIZE
reset:
	ldi	r16,low(RAMEND)
	out SPL,r16
	ldi	r16,0xff
	out	ddrb, r16
	ldi	r16,0
	out	ddrd,r16
.def	pattern = r16
main:
	ldi   pattern,0b01010101 ; 点灯させるビットパターン
	out   portb,pattern
end:
	rjmp	end
	.exit

例5-2

あらかじめ与えられたふたつの特定のパターン(例えば 2 進数で与える)の値 のうちのどちらかが on になっている部分の LED を光らせるプログラムを作 りなさい。 (ヒント: or, ori 命令)

解答例

どちらのビットが on の時、on にするには両方のビットパターンに対して OR を行います。 x or 0 = x, x or 1 = 1 より、ビットパターンが 0 の部分はそのままの値に なり、ビットパターンが 1 の部分は 1 になります。


;************
;*  例5-2 *
;************
.device attiny2313
.include <tn2313def.inc>
.cseg
.org	0x0000
	rjmp reset
.org	INT_VECTORS_SIZE
reset:
	ldi	r16,low(RAMEND)
	out	SPL,r16
	ldi	r16,0xff
	out	ddrb, r16
	ldi	r16,0
	out	ddrd,r16
.def	pattern = r16
main:
	ldi	pattern,0b01010101 ; 点灯させるビットパターン
	ori	pattern,0b00001111
	out	portb,pattern
end:
        rjmp	end
	.exit

演習5-1

あらかじめ与えられたふたつの特定のパターン(例えば 2 進数で与える)の値 のうちの両方が on になっている部分の LED を光らせるプログラムを作 りなさい。 (ヒント: and 命令)

演習5-2

あらかじめ与えられた特定のパターン(例えば 2 進数で与える)の値に対して、 2 進数で指定される特定の部分だけ反転させて LED を光らせるプログラムを作 りなさい。 (ヒント: eor 命令はレジスタ同士しか無いので、レジスタを二つ使う必要が あります)

演習5-3

PORTB に接続した LED になにかパターンを表示し、 PD4 に接続した sw を押 した時に On , Off が反転するプログラムを作りなさい。

演習5-4

PORTB に接続した LED が PD4 に接続した sw を押す度に On , Off が反転す るプログラムを作りなさい。

例5-3

LED のつながっている PORT B に対して、 PB0 のみ、 PB0 と PB1、 PB0 から PB2 まで、と順番に一つずつビットを 1 にしていき、全て 1 になったら全て消灯するプログラムを作りなさい。

解答例

第 2 回の flash プログラムを流用 します。

各パターンを書き込んで、wait を呼び出します。


;************
;*  例5-3 *
;************
.device attiny2313
.include <tn2313def.inc>
.cseg
.org	0x0000
	rjmp reset
.org	INT_VECTORS_SIZE
reset:
	ldi	r16,low(RAMEND)
	out	SPL,r16
	ldi	r16,0xff
	out	ddrb, r16
.def	pattern = r16
main:
	ldi	pattern,0b00000000
	out portb,pattern
	rcall	wait
	ldi	pattern,0b00000001
	out portb,pattern
	rcall	wait
	ldi	pattern,0b00000011
	out portb,pattern
	rcall	wait
	ldi	pattern,0b00000111
	out portb,pattern
	rcall	wait
	ldi	pattern,0b00001111
	out portb,pattern
	rcall	wait
	ldi	pattern,0b00011111
	out portb,pattern
	rcall	wait
	ldi	pattern,0b00111111
	out portb,pattern
	rcall	wait
	ldi	pattern,0b01111111
	out portb,pattern
	rcall	wait
	ldi	pattern,0b11111111
	out portb,pattern
	rcall	wait
	rjmp	main


.def	wreg0 = r20
.def	wreg1 = r21
.def	wreg2 = r22
wait:
	ldi	wreg0,time
wait0:
	ldi	wreg1,0
wait1:
	ldi	wreg2,0
wait2:
	nop
	dec	wreg2
	brne	wait2
	dec	wreg1
	brne	wait1
	dec	wreg0
	brne	wait0
	ret
.exit
解答例 2: マクロを使う

これで美しくないと思った場合の別解を示します。 まず、考えられるのは似たようなパターンが出てきますので、これを一つのマ クロ命令で置き換えることです。


;**************
;*  例5-3 2 *
;**************
.device attiny2313
.include <tn2313def.inc>
.cseg
.org	0x0000
	rjmp reset
.org	INT_VECTORS_SIZE
reset:
	ldi	r16,low(RAMEND)
	out	SPL,r16
	ldi	r16,0xff
	out	ddrb, r16
.equ	time=3
.def	pattern = r16

.macro	display
	ldi	pattern,@0
	out portb,pattern
	rcall	wait
.endmacro

main:
	display	0b00000000
	display	0b00000001
	display	0b00000011
	display	0b00000111
	display	0b00001111
	display	0b00011111
	display	0b00111111
	display	0b01111111
	display	0b11111111
	rjmp	main


.def	wreg0 = r20
.def	wreg1 = r21
.def	wreg2 = r22
wait:
	ldi	wreg0,time
wait0:
	ldi	wreg1,0
wait1:
	ldi	wreg2,0
wait2:
	nop
	dec	wreg2
	brne	wait2
	dec	wreg1
	brne	wait1
	dec	wreg0
	brne	wait0
	ret
.exit
解答例 3: ループを使い、表示部分を最小にする

プログラミングのコツとして、入出力部分を一箇所にまとめて、最小限にする ことが考えられます。 ループ変数を用意して、出力パターンを計算して出力することを考えます。 C 言語で書くと次のようになります。


for(;;){
  pattern=0;
  for(counter=1; counter<256; counter*=2){
    pattern = counter | i;
    pattern の出力;
  }
}

counter *= 2 を実現するには rotateあるいは shift と呼ばれる命令を使います。 これはレジスタの内容をビット毎に右又は左に移動する命令です。 単なる移動は shift で、溢れた bit を反対側の bit に回すのが rotate です。 PIC では STATUS の C bit を含めた rotate 命令のみがあります。 bit 毎に移動するということは、二進数で桁をずらすことになるので、 2 倍、 あるいは 1/2 にするということになります。 つまり、値を 2 倍するには左に rotate すれば良いことになります。 また、rotate した値が256 を越えると STATUS レジスタの C が 1 になりま す。 なお、C 言語にも shift 演算 << と >> があります。 例えば、 count*=2(2倍する) は count<<=1(左に 1 bit シフトする) と書けます。

但し、 counter に 1 が入ってから、 rotate で C=1 となる まで 8 回の繰り返しになるので、このプログラムのままでは全消灯を含めた 9 パターンは表示できません。


;**************
;*  例5-3 3 *
;**************
.device attiny2313
.include <tn2313def.inc>
.cseg
.org	0x0000
	rjmp reset
.org	INT_VECTORS_SIZE
reset:
	ldi	r16,low(RAMEND)
	out	SPL,r16
	ldi	r16,0xff
	out	ddrb, r16

.def	pattern = r16
main:
	clr	pattern
loop:
	out	portb,pattern
	rcall	wait
	sec
	rol	pattern
	brcs	main
	rjmp	loop
	
.def	wreg0 = r20
.def	wreg1 = r21
.def	wreg2 = r22
wait:
	ldi	wreg0,time
wait0:
	ldi	wreg1,0
wait1:
	ldi	wreg2,0
wait2:
	nop
	dec	wreg2
	brne	wait2
	dec	wreg1
	brne	wait1
	dec	wreg0
	brne	wait0
	ret
.exit
解答例 4: パターンをパラメータで呼び出す

前の解法はエレガントですが、パターンを計算して出力する点で応用が効きま せん。 そこで、パターンはデータとして列挙し、パラメータで呼び出すことにします。 そして、出力はやはり一箇所で行うようにします。

データを列挙する場所はプログラム領域になります。 データを列挙するディレクティヴは .db.dw になります。 .db は 8bit ずつ、 .dw は 16bit ずつデータを列挙できます。 一方、プログラム領域の値をレジスタに読み込む命令は lpm です。 これは定数の値のほか、インデックスレジスタ Z だけは間接読み出し(レジス タの指す番地の値を読む)が可能です。 特に、アドレッシングとして事前に Z を一つ減算したり、読み出した後で一 つ加算することもできます。

但し、 AVR はハーバードアーキテクチャなので、ここで単純に話は終わりま せん。 AVR ではレジスタ、データメモリは 8bit ですが、プログラムは 16bit です。 そのため、次のような注意点があります。

  1. .db ディレクティヴでは、二つで16bit のデータ一組として、一つのア ドレスに入る。 奇数個のデータを指定すると、アセンブル時に警告が出て、最後に8bit の 0 が埋められる。
  2. Z レジスタの最下位ビットは 0 で 16bit のデータの上位 8bit を読み、 1 で下位 8bit を読む。そのため、Zレジスタへのアドレスの指定方法はプ ログラム領域の アドレスを左に一つシフトして、最下位 bit には上位か 下位のどちらを読むかを指定する。

このよう自明でないな事情があることから、プログラム中で Z レジスタの値 と実アドレスを関連付ける(代入、比較など)をする場合に表現が複雑になりま す。 これを避けるために、あらかじめデータの入っている領域のそばでアドレス計 算をしておくようにします。 パターンの最大数はパターンの終了アドレスからパターンの開始アドレスを引 いて求めると、あとでパターンの変更をしてもパターンを出力するプログラム を変更する必要が無くなります。

インデックスレジスタは16bitなので、16bit の処理ができる命令がいろいろ あると便利ですが、AVR にはあまりありません。 つぎのようなものだけです

movw
レジスタ同士の値の移動(r1:r0からr31:r30まで)
adiw
レジスタ(r25:r24からr31:30まで)と定数(0から63まで)の加算
sbiw
レジスタ(r25:r24からr31:30まで)と定数(0から63まで)の減算

したがって、比較をする際も 8bit ごとに分ける必要があります。 大小比較するしようとするとかなり複雑です。 但し、等しいかどうかを判定するのは cp 命令二つでできます。

また、 16bit のデータを分けて処理する場合、読み込みは下位から、書き込 みは上位から行うようにします。


;**************
;*  例5-3 4 *
;**************
  .include <tn2313def.inc>
 .cseg
 
 .org 0x0000
	rjmp reset
 .org INT_VECTORS_SIZE
reset:
	ldi	r16,low(RAMEND)
	out	SPL,r16
	ldi	r16,0xff
	out	ddrb, r16
	.def	pattern = r16

main:
	ldi	ZH,high(startdataadr)
	ldi	ZL,low(startdataadr)
loop:
	lpm	pattern,Z+
	out	portb,pattern
	rcall	wait
	cpi	ZL,low(enddataadr)
	brne	loop
	cpi	ZH,high(enddataadr)
	brne	loop
	rjmp	main

	.equ	padding = 0
startdata:
	.db	0b00000001,	0b00000011
	.db	0b00000111,	0b00001111
	.db	0b00011111,	0b00111111
	.db	0b01111111,	0b11111111
	.db	0b00000000, padding
enddata:
.equ	startdataadr = startdata<<1
.equ	enddataadr = (enddata<<1)-1



.equ	time=3
.def	wreg0 = r20
.def	wreg1 = r21
.def	wreg2 = r22
wait:
	ldi	wreg0,time
wait0:
	ldi	wreg1,0
wait1:
	ldi	wreg2,0
wait2:
	nop
	dec	wreg2
	brne	wait2
	dec	wreg1
	brne	wait1
	dec	wreg0
	brne	wait0
	ret
.exit

演習5-5

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

演習5-6

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

5-2. 解答と解説

演習5-1

ビットを off にするには off にしたいビットパターンを作り、AND を行います。 x and 0 = 0, x and 1 = x より、ビットパターンが 0 の部分は 0 になり、 ビットパターンが 1 の部分はそのままの値になります。


;************
;*  演習5-1 *
;************

 .device attiny2313
 .include <tn2313def.inc>
 .cseg
 .org 0x0000
	rjmp	reset
 .org INT_VECTORS_SIZE
reset:
	ldi	r16,low(RAMEND)
	out SPL,r16
	ldi	r16,0xff
	out	ddrb, r16
	ldi	r16,0
	out	ddrd,r16
.def	pattern = r16
main:
	ldi	pattern,0b01010101 ; 点灯させるビットパターン
	andi	pattern,0b00001111
	out	portb,pattern
end:
        rjmp	end   
	.exit

演習5-2

ビットを反転させるには反転させたいビットを指定するビットパターンを作り、 XOR を行います。 x xor 0 = x, x xor 1 = x より、ビットパ ターンが 0 の部分はそのままになり、 ビットパターンが 1 の部分は値が反転します。


;************
;*  演習5-2 *
;************

 .device attiny2313
 .include <tn2313def.inc>
 .cseg
 .org 0x0000
	rjmp	reset
 .org INT_VECTORS_SIZE
reset:
	ldi	r16,low(RAMEND)
	out	SPL,r16
	ldi	r16,0xff
	out	ddrb, r16
	ldi	r16,0
	out	ddrd,r16
.def	pattern = r16
.def	work = r17
main:
	ldi	pattern,0b01010101 ; 点灯させるビットパターン
	ldi	work,0b00001111
	eor	pattern,work
	out	portb,pattern
end:
        rjmp	end
.exit

演習5-3

押してない時と押した時で PORTB の値を変化させます。 あらかじめ ON の時のパターンを pattern レジスタに入れておき、反転した パターンを invpattern にいれておきます。 その後、sbic pind, 4 で PD4 のビットを検査します。 もし、OFF だったら invpattern を portb に出力し、 ON だったら pattern を portb に出力します。

そのため、 ON の時のパターンから OFF の時のパターンをあらかじめ作ってお きます。 com 命令でレジスタのビット反転ができます。

また、あらかじめ PORTD の 4 bit 目を入力ポートになるように初期化します。


;************
;*  演習5-3 *
;************
 .device attiny2313
 .include <tn2313def.inc>
 .cseg
 .org 0x0000
	rjmp	reset
 .org INT_VECTORS_SIZE
reset:
	ldi	r16,low(RAMEND)
	out SPL,r16
	ldi	r16,0xff
	out	ddrb, r16
	ldi	r16,0
	out	ddrd,r16
	ldi	r16,1<<4
	out	ddrd,r16

.def	pattern = r16
.def	invpattern = r17
	ldi	pattern,0b01010101 ; 点灯させるビットパターン
	mov	invpattern,pattern
	com	invpattern
main:
	sbic	pind, 4
	rjmp	swon
swoff:
	out	portb,invpattern
	rjmp	main
swon:
	out	portb,pattern
	rjmp	main
	.exit

演習5-4

演習5-3 と同様の準備が必要です。

sw が押される度というのはどうやって考えれば良いでしょうか? sw は押している間 1 で、離すと 0 になります。 「1 なら反転」とすると、押している間反転を繰り返してしまうことになりま す。

押す度に切替えるには、押した瞬間に切替え、その後、幾ら押し続けても反転 しないようにする必要があります。 つまり、sw が押されてない状態から押された状態への変化を捉え、そこで反 転させます。 そのためには、直前の動作をファイルレジスタに取っておき、直前が 0 でス イッチが 1 の時だけ反転するようにします。


;************
;*  演習5-4 *
;************
 .device attiny2313
 .include <tn2313def.inc>
 .cseg
 .org 0x0000
	rjmp	reset
 .org INT_VECTORS_SIZE
reset:
	ldi	r16,low(RAMEND)
	out SPL,r16
	ldi	r16,0xff
	out	ddrb, r16
	ldi	r16,0
	out	ddrd,r16
	ldi	r16,1<<PIND4
	out	ddrd,r16
.def	pattern = r16
	ldi	pattern,0b01010101 ; 点灯させるビットパターン
.def	last = r17
.def	current = r18
main:
	in	current,PIND
	sbrc	last,PIND4
	rjmp	pressed
	sbrs	current,PIND4
	rjmp	notpressed
pressednow:
	com	pattern
pressed:
notpressed:
	out	portb,pattern
	mov	last,current
	rjmp	main
	.exit

なお、このように入力に対して動作が変化するようなプログラムをデバッグす る時は、シミュレータを使用します。 Atmel Studio の上部にある play や pause ボタンはシミュレータの実行や 一時停止を意味します。 止まっている時は、右側のペインでI/Oや CPU の状態などを見ることができま す。 さらに、特定の命令で右クリックしてブレークポイントを設定す ると、 playボタンを押しても、ブレークポイントの設定された命令を実行す ると自動的に pause 状態になります。 さらに、外部ピンを ON にするには、当該ポート(例えば PORTD の 4 bit目) を指す四角をクリックして色を反転させると、次のタイミングで PIN のビッ トが追従します。

演習5-5

例5-3 4 のプログラムにおいて出力パターンを変更したものを作ります。 出力パターンは例5-3 のプログラムを動かして、どの LED がどのビットに 対応しているのかを調べると簡単です。


;************
;*  演習5-5 *
;************
  .include <tn2313def.inc>
 .cseg
 
 .org 0x0000
	rjmp reset
 .org INT_VECTORS_SIZE
reset:
	ldi	r16,low(RAMEND)
	out	SPL,r16
	ldi	r16,0xff
	out	ddrb, r16
.def	pattern = r16
.equ	time=3

main:
	ldi	ZH,high(startdata<<1)
	ldi	ZL,low(startdata<<1)
loop:
	lpm	pattern,Z+
	out	portb,pattern
	rcall	wait
	cpi	ZL,low(enddataadr)
	brne	loop
	cpi	ZH,high(enddataadr)
	brne	loop
	rjmp	main

startdata:
	.db	0b01110111,	0b00010100	;0,1
	.db	0b10110011,	0b10110110	;2,3
	.db	0b11010100,	0b11100110	;4,5
	.db	0b11100111,	0b00110100	;6,7
	.db	0b11110111,	0b11110110	;8,9
	.db	0b11110101,	0b11000111	;A,b
	.db	0b01100011,	0b10010111	;C,d
	.db	0b11100011,	0b11100001	;E,F

enddata:
	.equ	startdataadr=startdata<<1
	.equ	enddataadr=enddata<<1

.def	wreg0 = r20
.def	wreg1 = r21
.def	wreg2 = r22
wait:
	ldi	wreg0,time
wait0:
	ldi	wreg1,0
wait1:
	ldi	wreg2,0
wait2:
	nop
	dec	wreg2
	brne	wait2
	dec	wreg1
	brne	wait1
	dec	wreg0
	brne	wait0
	ret
	.exit

演習5-5の改良

前述の演習5-5のプログラムには欠点があります。 それは、まず、表示している文字と対応するデータをプログラム内で保持して いないこと。 つまり、例えば、 1 を表示している時に、どのレジスタも 1 を持っていない ということです。 これは、このプログラムを再利用して他のプログラムから特定の数字を表示 しようとする場合へ応用が効かなくなります。

さらに、もう一つ欠点があります。 前述のプログラムでは Z レジスタをループカウンタとして利用して、プログ ラム領域のデータをうまく取り出して表示していました。 しかし、このような使い方のできる便利なレジスタは Z レジスタ一本しかあ りません。 そのため、このように利用度の高いレジスタを占有してしまうようなプログラ ムは、他のプログラムと結合し辛くなります。

以上のことより、ループカウンタとして counter という汎用レジスタを用意 し、次のサブルーチンを用意します。

dispcounter
counter の値を表示するサブルーチン
incounter
counter を 1 増やす。範囲外になったら 0 にする

このような準備をした後、次のプログラムを作ります。

  1. counter を 0 にします
  2. dispcounter を呼び、counter の値を表示します
  3. 一定時間待ちます
  4. inccounter を呼び、counter の値を増やします
  5. 2 に戻ります。

このように改良したのが以下のプログラムです。

dispcounter ではデータの先頭番地を Z レジスタに入れた後、 8bit の counter を足します。 そのため、下位 8bit である ZL に counter を足し込んだ後、オーバーフロー した時だけ ZH を 1 増やします。


;*************
;*  演習5-52 *
;*************

  .include <tn2313def.inc>
 .cseg
 
 .org 0x0000
	rjmp reset
 .org INT_VECTORS_SIZE
reset:
	ldi	r16,low(RAMEND)
	out	SPL,r16
	ldi	r16,0xff
	out	ddrb, r16
	.def	pattern = r16
	.def	counter = r17
	.equ	time=3
	clr	counter
	
main:
	rcall	dispcount
	rcall	wait
	rcall	inccounter
	rjmp	main

dispcount:	;counter	の値を表示
	ldi	ZH,high(startdata<<1)
	ldi	ZL,low(startdata<<1)
	add	ZL,counter
	brcc	dispcount1
	inc	ZH
dispcount1:
	lpm	pattern,Z
	out	portb,pattern
	ret

inccounter:
	inc	counter
	cpi	counter,countmax
	breq	inccounter1
	ret
inccounter1:
	clr	counter
	ret

	.equ	padding = 0
startdata:
	.db	0b01110111,	0b00010100	;0,1
	.db	0b10110011,	0b10110110	;2,3
	.db	0b11010100,	0b11100110	;4,5
	.db	0b11100111,	0b00110100	;6,7
	.db	0b11110111,	0b11110110	;8,9
	.db	0b11110101,	0b11000111	;A,b
	.db	0b01100011,	0b10010111	;C,d
	.db	0b11100011,	0b11100001	;E,F
enddata:
.equ	num_start_adr = startdata <<1
.equ	num_end_adr = enddata << 1
.equ	countmax = low(num_end_adr - num_start_adr)


.def	wreg0 = r20
.def	wreg1 = r21
.def	wreg2 = r22
wait:
	ldi	wreg0,time
wait0:
	ldi	wreg1,0
wait1:
	ldi	wreg2,0
wait2:
	nop
	dec	wreg2
	brne	wait2
	dec	wreg1
	brne	wait1
	dec	wreg0
	brne	wait0
	ret
.exit

演習5-6

演習5-6 のプログラムと 演習5-4 のプログラムを組み合わせます。 但し、表示する dispcount と、値を増やす inccount を分けて呼び出すこと にして、メインからは常に dispcount を呼び出し、ボタンが押されたこと 検知すると inccount を呼ぶようにします。


;************
;*  演習5-6 *
;************
  .include <tn2313def.inc>
 .cseg
 
 .org 0x0000
	rjmp reset
 .org INT_VECTORS_SIZE
reset:
	ldi	r16,low(RAMEND)
	out	SPL,r16
	ldi	r16,0xff
	out	ddrb, r16
	ldi	r16,1<<PIND4
	out	ddrd,r16
	.def	pattern = r16
	.def	last = r17
	.def	current = r18
	.def	counter = r19

	clr	counter
main:
	rcall	dispcounter

	in	current,PIND
	sbrc	last,PIND4
	rjmp	pressed
	sbrs	current,PIND4
	rjmp	notpressed
pressednow:
	rcall	inccounter
pressed:
notpressed:
	out	portb,pattern
	mov	last,current
	rjmp	main

dispcount:	;counter	の値を表示
	ldi	ZH,high(startdata<<1)
	ldi	ZL,low(startdata<<1)
	add	ZL,counter
	brcc	dispcount1
	inc	ZH
dispcount1:
	lpm	pattern,Z
	out	portb,pattern
	ret

inccounter:
	inc	counter
	cpi	counter,countmax
	breq	inccounter1
	ret
inccounter1:
	clr	counter
	ret

startdata:
	.db	0b01110111,	0b00010100	;0,1
	.db	0b10110011,	0b10110110	;2,3
	.db	0b11010100,	0b11100110	;4,5
	.db	0b11100111,	0b00110100	;6,7
	.db	0b11110111,	0b11110110	;8,9
	.db	0b11110101,	0b11000111	;A,b
	.db	0b01100011,	0b10010111	;C,d
	.db	0b11100011,	0b11100001	;E,F
enddata:
.equ	num_start_adr = startdata <<1
.equ	num_end_adr = enddata << 1
.equ	countmax = low(num_end_adr - num_start_adr)
.exit

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