マイコン制御基礎の基礎

マイコン制御基礎の基礎(6)

コンピュータに引き算をさせるには?

みわよしこ(執筆協力:山本栄一、小椋秀一、宇野雄騎)  2008/6/11

マイコン制御プログラムを自由自在に書けるようになろう! 前シリーズ「マイコン制御基礎以前」に続く本シリーズでは、アセンブリ言語の解説を中心に基礎力固めを目指す。(編集部)

- PR -

 われわれは日常、結果の取り扱いについて特に意識することなく、引き算を行っている。電卓や表計算ソフト、高級言語などを使用して10進数の引き算を行う場合、結果として表示される数値をただ単に読み取ればよい。しかし、そうした日常の常識は、アセンブラの世界においては、しばしば通用しない。今回は、「コンピュータが引き算を行う」ということが意味するものを理解しよう。

足し算と引き算の違い

 足し算(加算)と引き算(減算)には、さまざまな違いがある。

 加算では、「足される数」と「足す数」の順序を入れ替えても結果は変わらない(交換則が成立する)。「3+5」と「5+3」の結果は、同様に「8」である。また、正の整数同士の足し算の結果は、必ず正の整数になる(正の整数の集合は、足し算に関して閉じている)。

 これらはどちらも、減算に関しては当てはまらない。「引かれる数」と「引く数」を入れ替えれば、結果は変わってしまう。「3−5」の結果は「−2」で、「5−3」の結果は「2」である。また、正の整数同士の減算の結果は、正の整数であるとは限らない。「“引かれる数” < “引く数”」の場合には、結果は負の整数になる。

 コンピュータに減算を行わせるためには、足し算を行わせる場合以上に、工夫と注意が必要である。算数の宿題を電卓で片付ける小学生のように、「表示された数字をそのまま書き写す」というわけにはいかないのだ。10進数の演算命令を持たないアセンブリ言語でプログラミングを行う場合、減算は実行するにも結果を読み取るにも「大人の知恵」が必要である。電卓ならば小学生でも扱える。では、大人であるわれわれは“電卓のプロセッサの内部で行われている計算”を理解しようではないか。

整数の減算を行うプログラム

 以下に、AVRアセンブリ言語で整数の減算を行うプログラム(リスト1)とその処理の流れを示す(図1)。

; Copyright 2008 Denno Neko Yashiki
; written by Yoshiko Miwa
;
; AからBを引く引き算を行い
; 結果が正だったら、PB0を結果と同じ回数だけ点滅させる
; 結果が負だったら、最初にPB1を点灯させる

.include "m16def.inc"

.equ LED0_ON   = 0b00000000
.equ LED0_OFF  = 0b00000001
.equ LED1_ON   = 0b00000011
.equ LED1_OFF  = 0b00000001
.equ ALL_PORT_OUTPUT = 0xFF
.equ PUD_SET   = 0b00000100 ;2ビット目のPUDビットをセットする
.equ LOOP_LENGTH1 = 0xFF
.equ LOOP_LENGTH2 = 0xFF

.def Temp      = R16
.def COUNT1    = R17
.def COUNT2    = R18
.def A         = R19
.def B         = R20
.def RESULT    = R21

;---------------------------------------------
; おまじない。ここに、割り込みベクタが入る
;---------------------------------------------
.org 0x0000    
    rjmp RESET


;---------------------------------------------
; サブルーチン群
;---------------------------------------------

;-----------------------------
; Carry_ON:
; キャリー発生時の処理
;-----------------------------
Carry_ON:
        ;Aの2の補数を取る(Aがもとの値でなくなることに注意)
        neg A
        
        ;LED1 ON
        ldi    Temp,LED1_ON
        out    PORTB,Temp

        ;1単位ディレイ
        rcall  Delay1

        ;LED0 OFF
        ldi    Temp,LED1_OFF
        out    PORTB,Temp

        ;1単位ディレイ
        rcall  Delay1

        ret

;-----------------------------
;Delay1:
;1倍ループ
;-----------------------------
Delay1:

        ldi    COUNT2, LOOP_LENGTH2
Loop2:

        ldi    COUNT1, LOOP_LENGTH1 
Loop1:    
        dec    COUNT1            ; カウンタ値をマイナス1、結果が0になるとSREG:Z=1
        brbc   1, Loop1    ; SREGのビット1(Z)がクリヤされていればLoop2へブランチ

        dec    COUNT2
        brbc   1, Loop2

        ret


;-----------------------------
;ここから、プログラムスタート
;-----------------------------

RESET:
;-----------------------------
;初期化部
;-----------------------------


;スタックの初期化
;RAMの最終番地をスタックポインタにセットする
; (RAMの最終番地は、インクルード(.def)ファイルに"RAMEND"として定義されている
;  AVRは一回に8Bitしか扱えないので、LowとHighに分けてセットする
        ldi    Temp, low(RAMEND)
        out    SPL, Temp
        ldi    Temp, high(RAMEND)    
        out    SPH, Temp

;PORT B の初期化(全ビットを出力にセット)
        ldi    Temp, ALL_PORT_OUTPUT
        out    DDRB, Temp

;PUD(プルアップを禁止)ビットをセットし、プルアップを禁止する
        ldi    Temp, PUD_SET
        out    SFIOR, Temp

;最初に、LEDはすべてOFFしておく
        ;LED OFF
        ldi    Temp,0b00000010
        out    PORTB,Temp


;-------------------------------------------------------------------
;メインルーチン
;-------------------------------------------------------------------

;-------------------------------------------------------------------
;減算を行う
;-------------------------------------------------------------------

        ldi A,7
        ldi B,5
        sub A,B

        brcc   SETUP
        rcall  Carry_ON

SETUP:
        mov    RESULT,A

MAIN:
        ;LED0 ON
        ldi    Temp,LED0_ON
        out    PORTB,Temp

        ;1単位ディレイ
        rcall  Delay1

        ;LED0 OFF
        ldi    Temp,LED0_OFF
        out    PORTB,Temp

        ;1単位ディレイ
        rcall  Delay1

        dec    RESULT
        brbc   1, MAIN


; 最後に無限ループを書いておく
ENDLESS:
        rjmp ENDLESS


;END
;処理系によってバグることがあるので、この後には何行か空行を入れておくとよい


リスト1 整数の減算を行うプログラム

図1 リスト1「整数の減算を行うプログラム」のフロー図

 前回解説した加算プログラムとの違いは、「加算ではなく減算を行う」と「結果が負になった場合には適切な処理を行う」の2点である。

 「AVR Studio」でこのプログラムをアセンブルし、シミュレータで実行していくと(AVR Studioとシミュレータの使用法は、前シリーズ「マイコン制御基礎以前 第8回」を参照していただきたい)、

;-------------------------------------------------------------------
;減算を行う
;-------------------------------------------------------------------

        ldi A,7
        ldi B,5
        sub A,B

の直後、レジスタAこと「R19」へ「2」が格納されることを確認できる(図2)。

図2 レジスタAこと「R19」へ「2」が格納される

 「sub A,B」では、レジスタAの内容からレジスタBの内容を引く減算が行われ、計算結果はAに格納される。このプログラムの実行形式を書き込んだATmega16を「STK500」に装着すると、動画1のように、「PORT B」の0番ピンに接続したLEDが2回点滅する。 同じATmega16を自作LEDフラッシャボードに装着すると、同じく「PORT B」の0番ピンに接続したLEDが2回点滅する(動画2)。なお、マイコンにプログラムを書き込む方法については前シリーズ「マイコン制御基礎以前 第8回」、LEDフラッシャボードの制作については前シリーズの第46回を参照いただきたい。

動画1 STK500での動作確認
画像をクリックすると動画が再生されます(aviファイル)

動画2 自作LEDフラッシャボードでの動作確認
画像をクリックすると動画が再生されます(aviファイル)

 ではここで、プログラムの同じ個所を

;-------------------------------------------------------------------
;減算を行う
;-------------------------------------------------------------------

        ldi A,5
        ldi B,7
        sub A,B

と変更し、同じようにアセンブルを行い、シミュレータを動かして結果を見てみよう。「sub A,B」の直後、レジスタAことR19の内容は図3ようになる。

図3 レジスタAことR19の内容

 「5−7=−2」のはずが、「5−7=0xFE(10進数の254)」となってしまう。8ビットの2進数が表現できる数の範囲は、10進数で0〜255である。整数の正負を表現するには「+」または「−」の符号が必要である。マイコンは誤って「5−7=254」と表示しているわけではなく、正しく計算を行い、結果である「−2」を「254」と表現してしまうのだ。問題は、扱っている数の中に「符号」という概念が存在しないことにある。

  • 連載バックナンバー
  • 全記事インデックス
  • 組み込み開発トップ
  • MONOistトップ

メールマガジン

アイティメディアの提供サービス

ホワイトペーパー(TechTargetジャパン/閲覧には会員登録が必要です)

スキルアップ/キャリアアップ(JOB@IT)

スポンサーからのお知らせ

イベント特集ページ開設

DMS2008

Windows Embeddedコーナー

Windows Embedded
Windows Embedded専門コーナー誕生。Windows CEやXP Embeddedに関する技術情報を提供します。

@IT MONOist 求人情報