プログラミングと工作と

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

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

それでは、実際に動かしてみましょう。


まず回路ですが、PB0に470Ωくらいの抵抗とLEDアノード側の足を直列にして接続し、LEDカソード側の足をGNDに接続する。


これだけです。


ちなみに、この回路だとHigh出力でLEDが点灯します。
マイコン的には、Low出力で電流を引き込む状態で点灯させるのが正解なのですが、High(5V)で点灯したほうがわかりやすいだろ?ということでそうしました。
まあ、LED1個つけるだけですし。

 

で、プログラムです。

 

/*
*  LPika.asm
*
*  Created: 2015/11/03 18:13:49
*    Author: Takachiho
*/

.include "m168pdef.inc"

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

;-----------------------
; EQU定義
;-----------------------

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

.CSEG
         RJMP    MAIN

;*******************************
; サブルーチン
;*******************************

;*******************************
; メインルーチン
;*******************************

MAIN:

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

        CLI

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

        LDI      R16,        0b11111111    ;PortB全ピン出力設定
        OUT    DDRB,    R16
        LDI      R16,        0b00000000    ;PortB全て'0'出力
        OUT    PORTB,  R16

 

        LDI      R16,        0b11111111    ;PortC全ピン出力設定
        OUT    DDRC,    R16
        LDI      R16,        0b00000000    ;PortC全て'0'出力
        OUT    PORTC,  R16

 

        LDI      R16,        0b11111111    ;PortD全ピン出力設定
        OUT    DDRD,    R16
        LDI      R16,        0b00000000    ;PortD全て'0'出力
        OUT    PORTD,  R16

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

        SEI

;----- LED点灯 -----

        LDI      R16,         0b00000001    ;PortBのビット0をHighにする
        OUT    PORTB,   R16

END:
        RJMP  END

 

やたら長ったらしく見えますが、定義の部分やサブルーチンなど今回必要ないところがたくさん書いてあります。
これは、僕がアセンブラを書くときのひな形にしているもので、これにそっていつも書いています。
別にこの通り書く必要はないのですが、ひな形作っとくとなにかと楽ですので。

で、これをアセンブルして書き込んで・・・とやっていくのですが、今日はもう遅いので、また明日!

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

ずいぶん更新をサボっていました。
前回の続きで、実際にポートから出力をする実験をします。
コードばかり書いてても面白くないですからね。実際に動かしながらやるのが1番です。

では、ポートBのBit0へつないだLEDを光らせる、という簡単な例から始めます。
いわゆるLチカというやつですね。
いや、今回は点滅させないので、Lピカと言ったところでしょうか。

今回は、言語の勉強というよりは、環境の準備です。
書いたアセンブリ言語アセンブルして、マイコンに書き込み、動作を確認する、という一連の作業ができる環境をつくるということです。

まずは、パソコンに開発用ソフトをインストールします。
ソフトには、いくつかの選択肢があるのですが、1番メジャーなのは「Atmel Studio」ではないでしょうか。
Atmel社が出している純正ソフトだけあって、信頼性もありますし、なんといっても無料です。
今回はアセンブリを使っていますが、C言語も無料で使えるすぐれものです。
800MBくらいあるので、回線が遅いとダウンロードにちょっと時間がかかります。
ググってインストールして下さい。

 

さて、次にマイコンそのものが必要になります。
自作する、というのが正しいホビイストの姿かもしれませんが、今回は、みなさまおなじみの秋月電子通商で売っている「ATmega168/328マイコンボード」を買ってきました。

 

f:id:takashira:20151103184340j:plain

 

AVRマイコンのボード系では、調べた範囲では最安値の2100円、と言ったところが決め手です。これは、すべてのポートが引き出せるようになっているので、実験にはぴったりです。

最後に、書き込み器が必要です。
これまた秋月電子通商で買ってきたATMEL AVR ISPインシステム・プログラマー「AVRISPmkⅡ」です。

 

f:id:takashira:20151103184457j:plain

 

2015年11月現在、3800円で売っています。
実は、以前3000円くらいまで安くなっていたことがあって、僕はその時に買っていたのでした。
これまたATMEL社の純正品なので、安心して使えます。

あとは、実験に使うブレッドボードとかマイコンボード用のDC9Vくらいの電源とか必要になりますが、家の中を探せばだいたいありますよね?

 

さて、道具が揃えば実験開始です。
まず、Atmel Studioを立ち上げると、最初の画面の左側の方に
 ・New Project...
 ・New Example project...
 ・Open Project...
という項目があるので、新しく作り始めるときは、もちろんNew Projectをぽちっとします。

ちなみに、僕はバージョン6.2を使っています。

 

f:id:takashira:20151103184555p:plain

 

そうすると新しいポップアップが出てくるので、その左側にある「Assembler」をクリックして、下側にある「Name」の覧にプロジェクト名を記入します。
今回は「LPika」としました。

 

f:id:takashira:20151103184703p:plain

 

これでOKをクリックすると、またしても新しいポップアップが出てきます。
なかなか書き始められません。

 

f:id:takashira:20151103184746p:plain

 次に行うのはデバイスの選択です。

どのチップを使うか選ぶと、ATMEL Studioのほうで、いろいろと便宜をはかってくれるという便利な機能です。
今回は、秋月ボードに付属しているマイコンの表面にATmega168P-20PUと書いてあるので「ATmega168P」を選びます。
ATmega168にもPだのAだのPA,PBだの種類があるので注意が必要です。
これでOKを選ぶと、ついにプログラムを書き始められます!

 

f:id:takashira:20151103184826p:plain

 

ここにプログラムを書き始めるのですが、今日は長くなったので、また明日!

AVRマイコン アセンブリ言語入門 ― ポート設定

今日からしばらく、AVRマイコンアセンブリ言語を勉強していきます。
chipはATmega168P/328P をターゲットとしますが、基本は全部いっしょです。

で、まずは、基本中の基本、ポートの設定から。
ATmega168P/328Pは、PORTB, PORTC, PORTD の3系統のポートを持っています。
これらからデータを出力したり、外部からデータを入力したりするのですが、
ポートを利用するにあたり設定が必要になります。
なにを設定するの?

  • 入出力方向:ICの端子ピンを入力として使うか、出力としてか指定する
  • プルアップ有り/無し:入力時に、プルアップ有りにするか、無しか
  • 出力時の設定:Low/Highの値を指定する

これらの設定は、以下のレジスタに値を入れることで指定できます。

DDR* :入出力方向の指定(0:入力 1:出力)
PORT* :入力時は、0でプルアップ無し、1でプルアップ有り
    出力時は、0でlow出力、1でHigh出力

*の部分には、ポートBならBが、ポートCならCが入ります。

 

実際にアセンブリで書いてみましょう。
DDR*もPORT*レジスタも、値を直接代入出来ないため、汎用レジスタ経由で代入します。
汎用レジスタはR0からR31の32個もあるのですが、この内R0からR15までは、やはり値を直接代入出来ないのです。まったくなんでやねんと言った感じなのですが、そういう仕様なのでしょうがありません。
ですので、R16から使い始めます。
で、アセンブリです。

 

    LDI    R16,  0b11111111
    OUT  DDRB, R16

LDI命令は、「即値代入」命令で、上だと二進数表示(頭の'0b'が2進数表示の印)で11111111の値をレジスタR16に代入しています。
OUT命令で、レジスタR16の内容をDDRBレジスタに代入しています。
全部1にしているので、ポートBを全て出力端子に設定した、と言うことです。

いい忘れましたが、ポートBはPB0からPB7までの8ビット分の端子があるポートです。
上の11111111でいうと、一番右の'1’がPB0、一番左がPB7になります。
PB0,PB1,PB2を出力設定、それ以外を入力端子として使いたい場合は00000111をDDRBへ代入します。

さらにいうと、2進数表示ではなく、16進数表示(頭に0xを付ける)でもOKです。0b11111111は0xFFですので、LDI  R16,  0xFF と上の代入は同じです。

 

もういっちょ 

    LDI   R16,   0b00000001
    OUT    PORTB,  R16

PORTBへ00000001を代入したので、ビット0のみHigh出力、そのほかの端子は全部Low出力になりました。

たったこれだけです。簡単ですね。

ちなみに、

  • 全てのポートの初期状態は、プルアップ無し入力になっている
  • スイッチ入力などの端子は、プルアップ有り入力に指定する
  • 未接続(未使用)端子ピンは、'0’出力(GND接続状態)とする

らしいです。

まともなコンパイラにするために、必要な残された事など

さて、基本的な骨組みは出来上がってきました。
ここから、まともなコンパイラとして機能するためには、まずは

これが出来なければ話になりません。
中間言語の最適化から、アセンブリ・コード出力するための変換プログラムを作りこんでいく必要があります。

それから、今の状態は基礎部分に過ぎず、言語としてショボすぎるので、今後次の機能を組み込んでいきます。

 

1.型
いまは、整数型と論理型しか実装していません。
これに、実数型・文字列型・配列型・列挙スカラ型・列挙集合型を追加します。
さらに、レコードを組み合わせると、コンパイラとして実用的なレベルになりますね。

 

2.関数・手続き
これが使えないと話になりません。割り込み処理も書けませんよね。
これを実装すると、「値の渡し方」や「ブロック構造」の概念がよくわかると思います。

 

3.組み込み関数
これまた、使えないとコンパイラの魅力が半減してしまいます。
一般的な組み込み関数(√の計算等)と、AVRマイコンの機能を使いやすくする関数群の製作を考えています。
タイマーやA/D変換など、便利でパッと使える関数群を揃えたいですね!


・・・で、どこから手を付けるかですが、
まずはアセンブリ言語の出力を実装したいと思います。
これも、コンパイラと同様に一部実装、他の機能を実装して、また少し進化させて、と進めていきたいと思います。

ただ、僕自身AVRマイコンアセンブリに習熟しているわけではないので、明日から勉強がてらアセンブリでAVRマイコンを動かす実験をちびちび進めていきたいと思います。

制御構造を実装した(一部)

制御構造を実装してみました。
これで、プログラムらしいことが一応出来るようになりました。
アセンブリ言語にはまだ変換できませんが・・

制御構造は、まだ一部しか実装していません。
現在は

  •  if文、if-else文
  •  while文
  •  repeat文

だけに対応しています。

for文は、ブロック構造の概念が必要になるため後回しにしました。
case文はこれから実装予定です。


以上を実装して、実行させたものが下になります。
まず、ソースプログラム例が以下になります。

program Test1;
const
  x = 18;
  y = 12;
var
  a, b : integer;
begin
  if (x>0)and(y>0) then
  begin
    a := x;
    b := y;
    repeat
      while a > b do
        a := a - b;
      while b > a do
        b := b - a;
    until a = b;
  end;
end.

 

これをコンパイルすると、以下の内容のファイルを吐き出します。

   >   @T1 0   x
   >   @T2 0   y
   and  @T3 @T2 @T1
   comp  @T3
   goto  LABEL1
   store  a  x
   store  b  y
LABEL2
LABEL3
   >   @T4 b  a
   comp  @T4
   goto  LABEL4
   sub   @T5 b  a
   store  a  @T5
   goto  LABEL3
LABEL4
LABEL5
   >    @T6 a  b
   comp  @T6
   goto  LABEL6
   sub   @T7 a  b
   store  b @T7
   goto  LABEL5
LABEL6
   =    @T8 b  a
   comp   @T8
   goto  LABEL2
LABEL1

 

なんと言いますか、ひと目で無駄とわかるラベルがあったりしますが、とりあえず正常な計算は出来ているようです。
「最適化のしやすさ」で中間言語に四つ組を選んだわけでして、これから最適化の勉強もしていかないとです。

中間言語実装してみた

ソフトを作るのに没頭していてブログ更新をさぼっていた。

とりあえず、中間言語の実装、制御構造の実装(一部)というところまでたどり着いた。

今日は、中間言語をさらっと紹介します。
直接アセンブリ言語に変換せずに中間言語というワンクションを置くのは

  •  コードの最適化をやりやすい
  •  他のCPUに移植する時、全て書き換える必要がない

などの理由があるからなのです。

中間言語と言ってもいろいろな形があるのですが、コンパイラの本を何冊か読んでみて、今回は 四つ組と言われる形式を採用してみようと思います。

あとで変更や追加・削除などあるかもしれませんが、現時点で以下の内容を(勝手に)きめました。

 

中間言語案(その1)
add   reg1  reg2  reg3  :reg2にreg3を加え、結果をreg1に入れる。
sub   reg1  reg2  reg3  :reg2からreg3を引き、結果をreg1に入れる。
mult  reg1  reg2  reg3  :reg2にreg3を掛け、結果をreg1に入れる。
div    reg1  reg2  reg3  :reg2をreg3で割り、結果をreg1に入れる。
mod  reg1  reg2  reg3  :reg2をreg3で割り、あまりをreg1に入れる。
and   reg1  reg2  reg3  :reg2とreg3のandを取り、結果をreg1に入れる。
or      reg1  reg2  reg3  :reg2とreg3のorを取り、結果をreg1に入れる。
not    reg1  reg2            :reg2を反転し、結果をreg1に入れる。
=       reg1  reg2  reg3  :reg2とreg3が等しいか比較し、

                                          真偽(1・0)をreg1に入れる。
<>     reg1  reg2  reg3  :reg2とreg3が等しくないか比較し、

                                          真偽(1・0)をreg1に入れる。
<       reg1  reg2  reg3  :reg2がreg3より小さいか比較し、

                                          真偽(1・0)をreg1に入れる。
>       reg1  reg2  reg3  :reg2がreg3より大きいか比較し、

                                          真偽(1・0)をreg1に入れる。
<=     reg1  reg2  reg3  :reg2がreg3以下か比較し、

                                          真偽(1・0)をreg1に入れる。
>=     reg1  reg2  reg3  :reg2がreg3以上か比較し、

                                          真偽(1・0)をreg1に入れる。
store  reg1  reg2          :reg2の内容をdisp(reg1)番地に格納する。
load   reg1  reg2          :disp(reg2)番地の内容をreg1へ格納する。
comp reg1                   :reg1の真偽(1・0)を判定する。
goto   label                  :labelにとぶ。


これを実装して、実行させたものが下になります。
まず、ソースプログラムが以下になります。

program test1;
const
    zero = 0;
    five = 5;
var
    a,b,c : integer;
    w,z : int;
    d : bool;

begin
    a := five * (3 + 34);
    b := a + a;
    z := b + -5 mod a;
    c := (b + z) * (a - b);
    w := a + b div c;
    b := w + -5 mod a;
    d := z > zero;
end.

これをコンパイルすると、以下の内容のファイルを吐き出します。

    add     @T1     34         3
    mult    @T2     @T1     five
    store   a           @T2
    add     @T3      a          a
    store   b           @T3
    mod    @T4      a          5
    add     @T5     @T4     b
    store   z           @T5
    add     @T6      z          b
    sub     @T7      b          a
    mult    @T8      @T7   @T6
    store   c            @T8
    div      @T9       c          b
    add     @T10    @T9    a
    store   w           @T10
    mod    @T11     a         5
    add     @T12    @T11  w
    store   b            @T12
    >         @T13     zero    z
    store   d            @T13

 

なんとなくアセンブリっぽくなってきましたねw
ここからどうやって実際のアセンブリに変換していくか、課題は山積みですが亀の歩みで一歩づつ進んでいきたいです。
明日は、制御構造を実装した結果をUPします。

構文解析Ver.1の試運転

昨日までで、新しい構文規則を全部スクリプトへ落とし終えました。
ちょっと長くなりますが、構文解析クラスのスクリプト全文を示します。
その後に、実行結果を載せます。
とりあえず、問題なく動いているようです。

#!/usr/bin/python
# coding: utf-8

class psr:

    def __init__(self, token, e, sym):
        self.token = token
        self.e = e
        self.sym = sym
        

    def ids(self):
        temp = self.token.token
        if self.token.token in self.token.keyword:
            self.e.error(4)
        if self.token.next() not in [',', ':']:
            self.e.error(5)
        while self.token.token == ',':
            if self.token.next() in self.token.keyword:
                self.e.error(4)
            temp = temp + ',' + self.token.token
            self.token.next()
        return temp
       
       
    def part(self):
        if self.token.token not in ['not', '(', '+', '-']:
            if not self.token.token.isdigit:
                if self.token.token not in ['true', 'false']:
                    if self.token.token in self.token.keyword:
                        self.e.error(3)                       
        if self.token.token == 'not':
            self.token.next()
            self.part()        
        elif self.token.token == '(':
            self.token.next()
            self.express()
            if self.token.token != ')':
                self.e.error(28)
            self.token.next()            
        elif self.token.token == '+':
            self.token.next()
            if self.token.token == '(':
                self.express()
                if self.token.token != ')':
                    self.e.error(28)
            elif self.token.token.isdigit:
                self.token.next()
            elif self.token.token not in self.token.keyword:
                self.token.next()        
        elif self.token.token == '-':
            self.token.next()
            if self.token.token == '(':
                self.express()
                if self.token.token != ')':
                    self.e.error(28)
            elif self.token.token.isdigit:
                self.token.next()
            elif self.token.token not in self.token.keyword:
                self.token.next()        
        elif self.token.token.isdigit:
            self.token.next()                
        elif self.token.token in ['true', 'false']:
            self.token.next()            
        elif self.token.token not in self.token.keyword:
            self.token.next()
    
    
    def factors(self):
        if self.token.token in ['<>', '=', '>=', '<=', '<',
                                            '>', ')', ';', '+', '-', 'or']:
            return
        if self.token.token not in ['*', 'div', 'mod', 'and']:
            self.e.error(27)
        self.token.next()
        self.part()
        self.factors()
    
    
    def factor(self):
        if self.token.token not in ['not', 'true', 'false', '(', '+', '-']:
            if not self.token.token.isdigit:
                if self.token.token in self.token.keyword:
                    error(3)
        self.part()
        self.factors()
    
    
    def terms(self):
        if self.token.token in ['<>', '=', '>=', '<=', '<', '>', ')', ';']:
            return
        if self.token.token not in ['+', '-', 'or']:
            self.e.error(28)
        self.token.next()
        self.factor()
        self.terms()
    
    
    def term(self):
        if self.token.token not in ['not', 'true', 'false', '(', '+', '-']:
            if not self.token.token.isdigit:
                if self.token.token in self.token.keyword:
                    error(3)
        self.factor()
        self.terms()
        
        
    def expresses(self):
        if self.token.token in [')', ';']:
            return
        if self.token.token not in ['<>', '=', '>=', '<=', '<', '>']:
            self.e.error(3)
        self.token.next()
        self.term()
        
        
    def express(self):
        if self.token.token not in ['not', 'true', 'false', '(', '+', '-']:
            if not self.token.token.isdigit:
                if self.token.token in self.token.keyword:
                    error(3)
        self.term()
        self.expresses()
        
    
    def assign_stmt(self):
        if self.token.token in self.token.keyword:
            error(4)
        if self.token.next() != ':=':
            error(26)
        self.token.next()
        self.express()
        if self.token.token != ';':
            error(8)
    
    
    def exec_stmt(self):
        if self.token.token in self.token.keyword:
            error(4)
        self.assign_stmt()
    
    
    def exec_stmts(self):
        self.token.next()
        if self.token.token == 'end':
            return
        if self.token.token in self.token.keyword:
            error(4)
        self.exec_stmt()
        self.exec_stmts()
    
     
    def const_stmts(self):
        if self.token.token in self.token.keyword:
            self.e.error(10)
        x = self.token.token
        if self.token.next() != '=':
            self.e.error(11)
        y = self.token.next()
        if y in self.token.keyword:
            if not y.isdigit():
                if y not in ['+', '-', 'true', 'false']:
                    self.e.error(12)
        if y in ['+','-']:
            self.token.next()
            if not self.token.token.isdigit:
                self.e.error(13)
            y = y + self.token.token
        if self.token.next() != ';':
            self.e.error(8)
        self.sym.insert(x, self.sym.which_type(y), 'immutable',
                        self.sym.which_value(y), 'yes', 1)
        if self.token.next() in self.token.keyword:
            if self.token.token != 'var':
                if self.token.token != 'begin':
                    self.e.error(14)
        if self.token.token not in self.token.keyword:
            self.const_stmts()
    
    
    def var_stmts(self):
        if self.token.token in self.token.keyword:
            self.e.error(4)
        x = self.ids()
        if self.token.token != ':':
            self.e.error(6)
        if self.token.next() not in ['int', 'integer', 'bool', 'boolean']:
            self.e.error(7)
        y = self.token.token
        if self.token.next() != ';':
            self.e.error(8)
        self.sym.insert(x, self.sym.type_to_st(y), 'mutable', '', 'yes', 1)
        if self.token.next() != 'begin':
            if self.token.token in self.token.keyword:
                self.e.error(9)
        if self.token.token not in self.token.keyword:
            self.var_stmts()


    def begin_end_stmt(self):
        if self.token.token != 'begin':
            self.e.error(15)
        self.exec_stmts()
        if self.token.token != 'end':
            self.e.error(16)
        if self.token.next() != '.':
            self.e.error(17)
        self.token.next()
    
    
    def vars(self):
        if self.token.token != 'var':
            self.e.error(19)
        if self.token.next() in self.token.keyword:
            self.e.error(4)
        self.var_stmts()
    

    def consts(self):
        if self.token.token != 'const':
            self.e.error(18)
        if self.token.next() in self.token.keyword:
            self.e.error(10)
        self.const_stmts()
    

    def prog_stmt(self):
        if self.token.token != 'program':
            self.e.error(20)
        x = self.token.next()
        if self.token.token in self.token.keyword:
            self.e.error(21)
        if self.token.next() != ';':
            self.e.error(8)
        self.token.next()
        self.sym.insert(x, 'prog_name', 'immutable', x, 'no', 0)


    def prog(self):
        if self.token.token != 'program':
            self.e.error(20)
        self.prog_stmt()
        if self.token.token == 'const':
            self.consts()
        if self.token.token == 'var':
            self.vars()
        if self.token.token != 'begin':
            self.e.error(15)
        self.begin_end_stmt()
        if self.token.token != 'end_of_file':
            self.e.error(22)
        print(u'正常終了')


    def parser(self):
        self.token.next_char()
        if self.token.next() != 'program':
            self.e.error(20)
        self.prog()
        

長くなりましたが、以上が全文です。
で、実行させたのが下になります。

/PythonProject/PasAvr$ python main.py
program test1;
const
    zero = 0;
    five = 5;
var
    a     : integer;
    w,z   : int;
    d,e,f : bool;

begin
    a := five * (3 + 34);
    b := a + a;
    z := b + -5 mod a;
    c := (b + z) * (a - b);
    w := a + b div c;
    b := w + -5 mod a;
    d := z > zero;
end.
正常終了
{'test1': {'units': 0, 'alloc': 'no', 'type': 'prog_name', 'mode': 'immutable', 'value': 'test1'}, 'a': {'units': 1, 'alloc': 'yes', 'type': 'int_s', 'mode': 'mutable', 'value': ''}, 'e': {'units': 1, 'alloc': 'yes', 'type': 'bool_s', 'mode': 'mutable', 'value': ''}, 'd': {'units': 1, 'alloc': 'yes', 'type': 'bool_s', 'mode': 'mutable', 'value': ''}, 'f': {'units': 1, 'alloc': 'yes', 'type': 'bool_s', 'mode': 'mutable', 'value': ''}, 'zero': {'units': 1, 'alloc': 'yes', 'type': 'int_s', 'mode': 'immutable', 'value': '0'}, 'five': {'units': 1, 'alloc': 'yes', 'type': 'int_s', 'mode': 'immutable', 'value': '5'}, 'w': {'units': 1, 'alloc': 'yes', 'type': 'int_s', 'mode': 'mutable', 'value': ''}, 'z': {'units': 1, 'alloc': 'yes', 'type': 'int_s', 'mode': 'mutable', 'value': ''}}
~/PythonProject/PasAvr$

エラーもなく、問題なく動いているようです。
ただ、ただ受理するだけでは面白くないですよね。
次回から、いよいよ中間言語に変換するプログラムを追加していきたいと思います。
ただ、明日から二日間出張になったので、その間更新できなくなります。