目次

PLCスケッチ

  PLC(Programmable Logic Controller)は、リレーで実現していた
  ロジック回路を、マイクロコンピュータを利用して代行する装置
  の一種です。

 リレーを利用して、次のようなラダー回路を構成し、ランプの点灯
 モータ回転などの制御します。



 マイクロコンピュータのプログラムを書くよりも簡単に
 ラダー図を作成し、各種装置を制御できるので、機械系
 の処理に利用されていることが多いです。

 Arduinoは、マイクロコンピュータAVRを利用した処理系
 なので、PLCを実現できます。

 ラダー図をニモニック形式で入力し、暗くなったなら
 ランプを点灯する処理を、Arduino上に実現したPLCで
 制御してみます。

 ラダー図で描くと、次のようになります。



 ランプの点灯、消灯という状態を維持するので
 自己保持と呼ぶ回路を構成します。

 これをニモニック形式で表現すると、以下。

  LD   X000
  LD   Y000
  ANDN X002
  ORB
  ANDN X001
  OUT  Y000

 ラダー図をそのままでは入力できないので
 一度ニモニック形式に変換後、PLCへ入力
 となります。

 Arduinoで、PLCを実現するため、内蔵EEPROMに
 ラダー図に相当する情報を格納します。動作は
 EEPROM内のラダー図情報を逐次取出し、解釈後
 実行します。



 PLCを除いたデバイスは、入力にポートD、出力に
 ポートBを利用します。ともに6ビットを使えば
 低機能のPLCを実現できます。

 EEPROMには、1ワード=16ビットで、ラダー図に
 相当する情報を格納します。



 ラダー図をそのまま入力できればよいのですが
 それにはOSが入ったコンピュータが必要なので
 Arduinoでは荷が重過ぎです。

 ラダー図を描いて、ニモニック形式で動作を
 記述する処理は、人間が担当することに。



 シリアルインタフェースを利用して、Arduinoには
 ステップ番号とニモニックを与えます。

 Arduinoは、受取ったステップ番号とニモニックを
 利用してワードに変換し、EEPROMに格納します。

 1ワード=16ビットで、ラダー図上のステップが
 いくつ入るのかを計算します。

 AVRにATmega168を利用すると、内蔵EEPROMが512バイト
 なので、1ワード=2バイトで256ステップ格納できます。
 ATmega328では、内蔵EEPROMが1024バイトなので、512
 ステップ格納できます。

 256ステップ格納できるなら、簡単な処理なら充分と思います。

 PLC動作は、次のシーケンスで実行します。
  1. EEPROMから1ステップの動作を取り出し
  2. 動作を解釈
  3. 入力メモリから情報取得
  4. 入力メモリの情報を使い、指定動作を実行
  5. 出力メモリへ情報転送
  6. 1にもどる
 このシーケンスを実現するため、Arduinoのもつ  ビルトイン関数で使えるものをリストします。 EEPROM.read EEPROM.write Serial.read Serial.write  EEPROMへのアクセスは、1バイト単位なので  2バイト処理には、リード、ライト用の関数  を定義します。  EEPROMへのワード格納は、シリアルインタフェース  を利用します。  ワードに対応するニモニックを定義します。 OUT 指定ビットに'H'出力 LD A接点データ(ビットデータ)入力 LDN B接点データ(ビットデータ)入力 AND A接点データ(ビットデータ)入力との論理積を求める ANDN B接点データ(ビットデータ)入力との論理積を求める OR A接点データ(ビットデータ)入力との論理和を求める ORN B接点データ(ビットデータ)入力との論理和を求める ANDB ブロック間の論理積を求める ORB ブロック間の論理和を求める JUMP 指定ステップに分岐 END 停止  ニモニックには、命令の他に対象が必要です。  今回は、入力、出力ともに6ビットなので各々  X000〜X005、Y000〜Y005とします。  ワードの構成を決めます。  JUMPで指定ステップに分岐します。1ワードの  ビット数を確定するため、この命令を実現可能  な構成を考えました。  最大256ステップなので、このステップ番号を  扱えるサイズでは、12ビット=1ワードとする  ので充分です。極力、扱いを簡単にすることを  考え、16=1ワードとします。  ここまで仕様を決めれば、スケッチの構成を考え  実現するコードを記述できます。  スケッチの構成を、以下としました。  コマンドインタプリタは、PLC動作を実現する  perfomerに指示を出すことと、ニモニックを  ワードに変換しEEPROMに格納します。  コマンドインタプリタは、PersonalComputerから  与えられる指令で動作します。その指令が、いつ  来るのか予測できないので、受信割込みで受取り  ます。  perfomerは、EEPROMに格納されているラダー図  情報を1ステップごとに引出します。情報解釈  後に、入力から情報を取得し、対応する命令を  実行、パラメータを出力します。
スケッチ作成  スケッチの構成が決まれば、ブロックごとに  コードを記述していきます。  performer   このブロックは、コマンドインタプリタから   与えられるトリガーで動作を開始します。   トリガーを受取ってからは、動作シーケンスは   以下とすれば充分でしょう。
  1. EEPROMから情報取得
  2. 取得情報を動作命令として演算
  3. 演算結果を出力
  4. 1に戻る
  シーケンス実現で使う道具の中で、最も簡単なもの   と言えば、ステートマシン。   ステートマシンは、switch caseを利用するのが   最も簡単です。 switch ( state ) { /* wait trigger */ case 0 : state = 0 ; if ( trigger == ON ) { state = 1 ; iadr = 0 ; } break ; /* get ladder information */ case 1 : state = 2 ; lcmd = get_ladder_code( iadr ) ; iadr++ ; dport = PIND ; dport >>= 2 ; break ; /* decode instrctuction */ case 2 : state = 3 ; instruction = get_cmd( lcmd ) ; if ( instruction == OUT ) { execute = PUTBIT ; } if ( instruction == LD ) { execute = GETBIT ; } if ( instruction == LDN ) { execute = GETBIT ; } if ( instruction == AND ) { execute = ANDOPERATION ; } if ( instruction == ANDN ) { execute = ANDOPERATION ; } if ( instruction == OR ) { execute = OROPERATION ; } if ( instruction == ORN ) { execute = OROPERATION ; } if ( instruction == ANDB ) { execute = BLOCKOPERATIONAND ; } if ( instruction == ORB ) { execute = BLOCKOPERATIONOR ; } if ( instruction == JUMP ) { execute = ADDRESSUPDATE ; } if ( instruction == END ) { state = 5 ; } break ; /* execute */ case 3 : state = 4 ; if ( execute == PUTBIT ) { } if ( execute == GETBIT ) { } if ( execute == ANDOPERATION ) { } if ( execute == OROPERATION ) { } if ( execute == BLOCKOPERATIONAND ) { } if ( execute == BLOCKOPERATIONOR ) { } if ( execute == ADDRESSUPDATE ) { } break ; /* judge continue or break */ case 4 : state = 5 ; if ( trigger == ON ) { state = 1 ; } break ; /* exit */ case 5 : state = 0 ; iadr = 0 ; break ; /* */ default : state = 0 ; break ; }   ステートごとの実行内容がわかれば、変数、定数   および関数を定義していきます。   定数定義は、以下。 #define OFF 0 #define ON 1 #define END 0 #define OUT 1 #define LD 2 #define LDN 3 #define AND 4 #define ANDN 5 #define OR 6 #define ORN 7 #define ANDB 8 #define ORB 9 #define JUMP 15 #define PUTBIT 0 #define GETBIT PUTBIT+1 #define ANDOPERATION PUTBIT+2 #define OROPERATION PUTBIT+3 #define BLOCKOPERATIONAND PUTBIT+4 #define BLOCKOPERATIONOR PUTBIT+5 #define ADDRESSUPDATE PUTBIT+6   変数は名前とサイズを考えます。 byte state ; byte trigger ; byte lcmd ; word iadr ; byte instruction ; byte execute ; byte bport ; byte dport ;   ラダー図で記述された動作命令をEEPROMから   取得する関数を考えます。   アドレスを与えて16ビットコードを取得する処理   を定義し、16ビットコードの上位4ビットを返す   ことができれば、よいでしょう。   アドレスを与えて16ビットコードを取得する関数   get_ladder_codeとして、定義します。 word get_ladder_code(word x) { word result ; word xadr ; /* calcuate address */ xadr = (x << 1) ; /* first byte */ result = EEPROM.read( xadr ) ; result <<= 8 ; /* address increment */ xadr++ ; /* second byte */ result |= EEPROM.read( xadr ) ; return result ; }   16ビットコードが取得できていれば、get_cmdは簡単。 byte get_cmd(word x) { word tmp ; /* mask and substitue */ tmp = x & 0xf000 ; /* shift */ tmp >>= 12 ; return (byte)tmp ; }   EEPROMから16ビットコードを取得する関数   get_ladder_codeを作ったので、逆の操作を   関数put_ladder_codeで定義します。 void put_ladder_code(word xptr,word xcode) { byte dd ; word xadr ; /* calcuate address */ xadr = (xptr << 1) ; /* store first byte */ dd = (xcode >> 8) & 0xff ; EEPROM.write(xadr,dd) ; /* address increment */ xadr++ ; /* second byte */ dd = xcode & 0xff ; EEPROM.write(xadr,dd) ; }   PLCの命令は、ビット処理になるので対応処理を   丁寧に記述していきます。   LD、LDN処理    X000〜X005の値を取り出し、スタックに保存します。    スタックに論理値を保存するため、専用関数を定義    します。 void push_bit(byte x) { /* shift */ plcsft <<= 1 ; /* store */ if ( x ) { plcsft |= ON ; } }    スタックから論理値を取得する専用関数を定義します。 byte pop_bit() { /* shift */ plcsft >>= 1 ; /* */ return(plcsft & ON) ; }    スタック処理は1ビット単位になるので、Arduinoのような    SRAMが少ないマイコンでは、バイト単位で扱うとメモリが    不足します。    16ビットのシフトレジスタで、PLC用のスタックを実現します。    X000〜X005の値は、dportの0ビットから5ビットのどこか    のビットに含まれている論理値なので、それをスタックに    入れる処理を作れば充分。また、Y000〜Y005の値は、bportの    0ビットから5ビットのどこかのビットに含まれる論理値に    なるので、それを判定すればよいでしょう。 /* LD code */ if ( instruction == LD ) { execute = GETBIT ; tmp = ((dport >> bitloc) & 1) ; if ( bitloc > 7 ) { tmp = ((bport >> (bitloc-8)) & 1) ; } bitval = tmp ; } /* LDN code */ if ( instruction == LD ) { execute = GETBIT ; tmp = ((dport >> bitloc) & 1) ; if ( bitloc > 7 ) { tmp = ((bport >> (bitloc-8)) & 1) ; } bitval = tmp ; bitval ^= 1 ; }    ビット位置は、bitlocの中に保存されているので    入力状態を格納しているdportの該当ビットを引き    出す操作に利用します。    LDNの場合、負論理から正論理に変換し対応します。    LD、LDNの命令実行は、スタックに値を保存する    と考えて、スタック操作します。 /* LD or LDN */ if ( execute == GETBIT ) { push_bit(bitval); }   AND、ANDN処理    X000〜X005の値あるいはY000〜Y005の値を取り出し    スタックに保存してある値との論理積値を求めます。    その論理積値をスタックに入れ直せばよいでしょう。    デコード処理ステートで、X000〜X005の値あるいは    Y000〜Y005の値を取り出し演算種別を設定します。 /* AND code */ if ( instruction == AND ) { execute = ANDOPERATION ; tmp = ((dport >> bitloc) & 1) ; if ( bitloc > 7 ) { tmp = ((bport >> (bitloc-8)) & 1) ; } bitval = tmp ; } /* ANDN code */ if ( instruction == ANDN ) { execute = ANDOPERATION ; tmp = ((dport >> bitloc) & 1) ; if ( bitloc > 7 ) { tmp = ((bport >> (bitloc-8)) & 1) ; } bitval = tmp ; /* inverse */ bitval ^= 1 ; }    命令実行ステートで、スタックから1ビット情報を取り出し    論理積を求め、再度スタックに保存します。 /* AND or ANDN */ if ( execute == ANDOPERATION ) { /* get value and operation */ bitval &= pop_bit(); /* store */ push_bit(bitval); }   OR、ORN処理    X000〜X005の値あるいはY000〜Y005の値を取り出し    スタックに保存してある値との論理和値を求めます。    その論理和値をスタックに入れ直せばよいでしょう。    デコード処理ステートで、X000〜X005の値あるいは    Y000〜Y005の値を取り出し演算種別を設定します。 /* OR code */ if ( instruction == OR ) { execute = OROPERATION ; tmp = ((dport >> bitloc) & 1) ; if ( bitloc > 7 ) { tmp = ((bport >> (bitloc-8)) & 1) ; } bitval = tmp ; } /* ORN code */ if ( instruction == ORN ) { execute = OROPERATION ; tmp = ((dport >> bitloc) & 1) ; if ( bitloc > 7 ) { tmp = ((bport >> (bitloc-8)) & 1) ; } bitval = tmp ; /* inverse */ bitval ^= 1 ; }    命令実行ステートで、スタックから1ビット情報を取り出し    論理和を求め、再度スタックに保存します。 /* OR or ORN */ if ( execute == OROPERATION ) { /* get value and operation */ bitval |= pop_bit(); /* store */ push_bit(bitval); }   OUT処理    スタックの最も上にある値を取り出し、Y000〜Y005の    どこかに出力すれば処理は終了です。    デコード処理ステートで、演算種別を設定します。 /* OUT code */ if ( instruction == OUT ) { execute = PUTBIT ; }    命令実行ステートで、スタックから1ビット情報を    取り出し、指定ビットに設定します。 /* OUT */ if ( execute == PUTBIT ) { /* get value */ bitval = pop_bit(); /* clear */ bport &= ~(1 << bitloc); /* update */ bport |= (bitval << bitloc); /* out */ PORTB = bport ; }   ANDB、ORB処理    ブロック処理は、スタックに保存されている    他の命令の処理結果と論理積あるいは論理和    を求めます。    デコード処理ステートで、スタックの最も上にある値を    取り出し、演算種別を設定します。 /* ANDB code */ if ( instruction == ANDB ) { execute = BLOCKOPERATIONAND ; bitval = pop_bit() ; } /* ORB code */ if ( instruction == ORB ) { execute = BLOCKOPERATIONOR ; bitval = pop_bit() ; }    命令実行ステートで、スタックから1ビット情報を    取り出し、演算し結果をスタックに保存します。 /* ANDB */ if ( execute == BLOCKOPERATIONAND ) { /* get value and operation */ bitval &= pop_bit(); /* store */ push_bit(bitval); } /* ORB */ if ( execute == BLOCKOPERATIONOR ) { /* get value and operation */ bitval |= pop_bit(); /* store */ push_bit(bitval); }   JUMP処理    JUMPは、EEPROMのアドレスを書き換えるだけで    よいので、命令コードの下位8ビットを取得後    アドレスを入れ替えます。    デコード処理ステートで、アドレスの取得と    演算種別を設定します。 /* JUMP code */ if ( instruction == JUMP ) { execute = ADDRESSUPDATE ; ixadr = lcmd & 0x00ff ; }    命令実行ステートで、アドレスを変更します。 /* JUMP */ if ( execute == ADDRESSUPDATE ) { iadr = ixadr ; }   END処理    PLCの動作停止なので、ステートマシンの実行ループ    から抜出します。   まとめると、次の関数に。 void perform_plc() { switch ( state ) { /* wait trigger */ case 0 : state = 0 ; if ( trigger == ON ) { state = 1 ; iadr = 0 ; } break ; /* get ladder information */ case 1 : state = 2 ; lcmd = get_ladder_code( iadr ) ; iadr++ ; dport = PIND ; dport >>= 2 ; break ; /* decode instrctuction */ case 2 : state = 3 ; instruction = get_cmd( lcmd ) ; bitloc = (byte)(lcmd & 0x000f); /* OUT code */ if ( instruction == OUT ) { execute = PUTBIT ; } /* LD , LDN code */ if ( instruction == LD || instruction == LDN ) { execute = GETBIT ; tmp = ((dport >> bitloc) & 1) ; if ( bitloc > 7 ) { tmp = ((bport >> (bitloc-8)) & 1) ; } bitval = tmp ; /* LDN inverse */ if ( instruction == LDN ) { bitval ^= 1 ; } } /* AND code */ if ( instruction == AND || instruction == ANDN ) { execute = ANDOPERATION ; tmp = ((dport >> bitloc) & 1) ; if ( bitloc > 7 ) { tmp = ((bport >> (bitloc-8)) & 1) ; } bitval = tmp ; /* ANDN inverse */ if ( instruction == ANDN ) { bitval ^= 1 ; } } /* OR code */ if ( instruction == OR || instruction == ORN ) { execute = OROPERATION ; tmp = ((dport >> bitloc) & 1) ; if ( bitloc > 7 ) { tmp = ((bport >> (bitloc-8)) & 1) ; } bitval = tmp ; /* ORN inverse */ if ( instruction == ORN ) { bitval ^= 1 ; } } /* ANDB code */ if ( instruction == ANDB ) { execute = BLOCKOPERATIONAND ; bitval = pop_bit() ; } /* ORB code */ if ( instruction == ORB ) { execute = BLOCKOPERATIONOR ; bitval = pop_bit() ; } /* JUMP code */ if ( instruction == JUMP ) { execute = ADDRESSUPDATE ; ixadr = lcmd & 0x00ff ; } /* END code */ if ( instruction == END ) { state = 5 ; } /* execute */ case 3 : state = 4 ; /* OUT */ if ( execute == PUTBIT ) { /* get value */ bitval = pop_bit(); /* clear */ bport &= ~(1 << bitloc); /* update */ bport |= (bitval << bitloc); /* out */ PORTB = bport ; } /* LD or LDN */ if ( execute == GETBIT ) { push_bit(bitval); } /* AND or ANDN */ if ( execute == ANDOPERATION ) { /* get value and operation */ bitval &= pop_bit(); /* store */ push_bit(bitval); } /* OR or ORN */ if ( execute == OROPERATION ) { /* get value and operation */ bitval |= pop_bit(); /* store */ push_bit(bitval); } /* ANDB */ if ( execute == BLOCKOPERATIONAND ) { /* get value and operation */ bitval &= pop_bit(); /* store */ push_bit(bitval); } /* ORB */ if ( execute == BLOCKOPERATIONOR ) { /* get value and operation */ bitval |= pop_bit(); /* store */ push_bit(bitval); } /* JUMP */ if ( execute == ADDRESSUPDATE ) { iadr = ixadr ; } break ; /* judge continue or break */ case 4 : state = 5 ; if ( trigger == ON ) { state = 1 ; } break ; /* exit */ case 5 : state = 0 ; iadr = 0 ; break ; /* */ default : state = 0 ; break ; } }   最終イメージは、次の図となります。  command interpreter   このブロックは、Personal Computerから   与えられる指令で動作します。   コマンドとパラメータを決めていきます。   コマンドは、英文字、記号とします。   パラメータが必要なのは、コマンド's'のみとします。   コマンド's'は、次のようにステップとニモニックを   入力するために使います。   Personal Computerから送られるコマンドとパラメータを   入れる領域が必要になります。受信バッファを用意して   そのサイズは32バイトとします。   受信バッファは配列で確保し、配列のどこまで文字を   入力したのかを示すための変数を用意します。 #define BSIZE 32 byte sbuf[BSIZE] ; byte sindex ;   受信バッファにコマンドとパラメータを保存するには   受信割込みを使います。関数serialEventを定義すれば   充分でしょう。 byte uflag ; void serialEvent() { if ( Serial.available() > 0 ) { /* get 1 charactor */ ch = Serial.read(); /* store */ sbuf[sindex] = ch ; /* increment */ sindex++ ; /* judge */ if ( ch == '\r' ) { sindex = 0 ; uflag = ON ; } } }   受信割込みで、コマンドとパラメータがバッファに   入ったことを通知するため、イベントフラグuflagを   使います。   loop関数の中に、コマンドインタプリタに相当   するコードを入れるため、フラグを使います。   コマンドインタプリタは、次のように単純です。 /* command interpreter */ if ( uflag == ON ) { /* clear flag */ uflag = OFF ; /* get command */ cmd = sbuf[0] ; /* help */ if ( cmd == '?' ) { } /* assemble */ if ( cmd == 's' ) { } /* disassemble */ if ( cmd == 'l' ) { } /* show PLC code */ if ( cmd == 'p' ) { } /* enable PLC handling */ if ( cmd == 'e' ) { } /* disable PLC handling */ if ( cmd == 'd' ) { } }   1文字だけで完結する処理から定義していきます。   PLCとして動くか否かは、トリガーtriggerをONかOFF   にするだけでよいので、変数操作で記述します。 /* enable PLC handling */ if ( cmd == 'e' ) { trigger = ON ; } /* disable PLC handling */ if ( cmd == 'd' ) { trigger = OFF ; }   ヘルプは、単に説明を表示すればよいので   専用関数を用意し、呼出すことに。 void rs_putchar(char x) { Serial.write(x); } void rs_puts(char *x) { while ( *x ) { rs_putchar(*x); x++ ; } } void crlf() { rs_putchar('\r'); rs_putchar('\n'); } void show_help() { rs_puts("? help"); crlf(); rs_puts("s assemble"); crlf(); rs_puts("l disassemble"); crlf(); rs_puts("p show PLC code with hexadecimal format"); crlf(); rs_puts("e enable PLC handling"); crlf(); rs_puts("d disable PLC handling"); crlf(); }   コマンドインタプリタでは、次の記述で呼出します。 if ( cmd == '?' ) { show_help() ; }   16ビットコード表示には、EEPROMから1ワードずつ   読出し、16進数4けたでシリアル転送します。 if ( cmd == 'p' ) { for ( ixadr = 0 ; ixadr < 256 ; ixadr++ ) { /* get 1 word */ plcdat = get_ladder_code(ixadr); /* show */ rs_putchar(asc_hex[(plcdat >> 12) & 0xf]); rs_putchar(asc_hex[(plcdat >> 8) & 0xf]); rs_putchar(asc_hex[(plcdat >> 4) & 0xf]); rs_putchar(asc_hex[plcdat & 0xf]); rs_putchar(' '); /* new line */ if ( (ixadr & 7) == 7 ) { crlf(); } } }   配列asc_hexには、setup関数の中で、文字定数を設定します。 asc_hex[0] = '0' ; asc_hex[1] = '1' ; asc_hex[2] = '2' ; asc_hex[3] = '3' ; asc_hex[4] = '4' ; asc_hex[5] = '5' ; asc_hex[6] = '6' ; asc_hex[7] = '7' ; asc_hex[8] = '8' ; asc_hex[9] = '9' ; asc_hex[10] = 'A' ; asc_hex[11] = 'B' ; asc_hex[12] = 'C' ; asc_hex[13] = 'D' ; asc_hex[14] = 'E' ; asc_hex[15] = 'F' ;   逆アセンブル処理は、16ビットコードから、文字列を   生成するだけになります。 if ( cmd == 'l' ) { for ( ixadr = 0 ; ixadr < 256 ; ixadr++ ) { /* get 1 word */ plcdat = get_ladder_code(ixadr); /* show */ show_code( plcdat ) ; } }   文字列表示は、関数show_codeに任せます。 void show_code(word x) { byte xcmd ; byte xopa ; /* get instruction */ xcmd = (x >> 12) & 0x0f ; /* get operand */ xopa = (byte)(x & 0xff) ; /* END */ if ( xcmd == END ) { rs_puts("END"); } /* OUT */ if ( xcmd == OUT ) { rs_puts("OUT "); show_io( xopa ); } /* LD */ if ( xcmd == LD ) { rs_puts("LD "); show_io( xopa ); } /* LDN */ if ( xcmd == LDN ) { rs_puts("LDN "); show_io( xopa ); } /* AND */ if ( xcmd == AND ) { rs_puts("AND "); show_io( xopa ); } /* ANDN */ if ( xcmd == ANDN ) { rs_puts("ANDN "); show_io( xopa ); } /* OR */ if ( xcmd == OR ) { rs_puts("OR "); show_io( xopa ); } /* ORN */ if ( xcmd == ORN ) { rs_puts("ORN "); show_io( xopa ); } /* ANDB */ if ( xcmd == ANDB ) { rs_puts("ANDB"); } /* ORB */ if ( xcmd == ORB ) { rs_puts("ORB"); } /* JUMP */ if ( xcmd == JUMP ) { rs_puts("JUMP "); show_line( xopa ); } crlf(); }   関数show_codeから、入力ピン、出力ピンの名称表示する   関数とステップ番号を表示する関数に処理を切出します。 void show_io(byte x) { switch (x) { case 0 : rs_puts("X000"); break; case 1 : rs_puts("X001"); break; case 2 : rs_puts("X002"); break; case 3 : rs_puts("X003"); break; case 4 : rs_puts("X004"); break; case 5 : rs_puts("X005"); break; case 8 : rs_puts("Y000"); break; case 9 : rs_puts("Y001"); break; case 10 : rs_puts("Y002"); break; case 11 : rs_puts("Y003"); break; case 12 : rs_puts("Y004"); break; case 13 : rs_puts("Y005"); break; default : break; } } void show_line(byte x) { byte line ; byte msg[3] ; byte i ; /* copy */ line = x ; /* convert */ for ( i = 0 ; i < 3 ; i++ ) { *(msg+2-i) = (line % 10) + '0' ; line /= 10 ; } /* show */ for ( i = 0 ; i < 3 ; i++ ) { rs_putchar( *(msg+i) ); } }   アセンブルには、ステップ番号、命令、パラメータがあるので   これらを数値に変換し、16ビットコードを構成後、EEPROMに   保存します。 if ( cmd == 's' ) { /* convert capital letter */ convert_cap(); /* get step number */ line = get_step_number() ; /* get instruction code */ lcmd = get_instruction() ; /* clear */ xplc = 0 ; /* generate code */ if ( lcmd == END || lcmd == ANDB || lcmd == ORB ) { xplc = (lcmd << 12) ; } else { /* get operand */ xplc |= get_operand() ; /* add opcode */ xplc |= (lcmd << 12) ; } /* store */ put_ladder_code(line,xplc); }   命令、パラメータが大文字、小文字の混在だと   扱いにくいので、大文字に変換します。   ステップ番号、命令、パラメータを取得するには   別途専用関数を定義します。 void convert_cap() { byte i ; byte msg ; for ( i = 1 ; i < 32 ; i++ ) { /* get 1 byte */ msg = sbuf[i] ; /* ? delimiter */ if ( msg == '\r' ) break ; /* ? not capital letter */ if ( 'a' <= msg && msg <= 'z' ) { msg = msg - 'a' + 'A' ; } /* store */ sbuf[i] = msg ; } } word get_step_number() { byte i ; byte msg ; word result ; /* clear */ result = 0 ; /* convert */ for ( i = 1 ; i < 6 ; i++ ) { /* get 1 byte */ msg = sbuf[i] ; /* ? white space */ if ( msg == '\t' ) break ; if ( msg == ' ' ) break ; /* calculate */ result = result * 10 + (msg - '0'); } return result ; } byte get_instruction() { byte i ; byte msg ; byte result ; /* skip white space */ for ( i = 1 ; i < 6 ; i++ ) { /* ? white space */ if ( sbuf[i] == '\t' ) break ; if ( sbuf[i] == ' ' ) break ; } msg = sbuf[i+1] ; /* judge */ if ( msg == 'E' ) { result = END ; } if ( msg == 'J' ) { result = JUMP ; } if ( msg == 'L' && sbuf[i+3] != 'N' ) { result = LD ; } if ( msg == 'L' && sbuf[i+3] == 'N' ) { result = LDN ; } if ( msg == 'O' && sbuf[i+3] == 'T' ) { result = OUT ; } if ( msg == 'O' && sbuf[i+3] != 'N' ) { result = OR ; } if ( msg == 'O' && sbuf[i+3] == 'N' ) { result = ORN ; } if ( msg == 'O' && sbuf[i+3] == 'B' ) { result = ORB ; } if ( msg == 'A' && sbuf[i+4] == 'B' ) { result = ANDB ; } if ( msg == 'A' && sbuf[i+4] == 'N' ) { result = ANDN ; } if ( msg == 'A' && sbuf[i+3] == 'D' && sbuf[i+4] != 'N' ) { result = ANDN ; } if ( msg == 'A' && sbuf[i+3] == 'D' && sbuf[i+4] != 'B' ) { result = ANDN ; } return result ; } byte get_operand() { byte i ; byte ptr ; byte msg ; byte result ; /* search delimiter */ result = 0 ; for ( i = 1 ; i < 32 ; i++ ) { if ( sbuf[i] == '\r' ) break ; } msg = i ; /* serch white space */ for ( i = msg ; i > 1 ; i-- ) { if ( sbuf[i] == '\t' ) break ; if ( sbuf[i] == ' ' ) break ; } ptr = i+1 ; msg = sbuf[ptr] ; /* input */ if ( msg == 'X' ) { result = sbuf[ptr+3] - '0' ; } /* output */ if ( msg == 'Y' ) { result = sbuf[ptr+3] - '0' + 8 ; } /* step number */ if ( msg != 'X' && msg != 'Y' ) { result = 0 ; for ( i = ptr ; i < 32 ; i++ ) { /* judge '\r' */ if ( sbuf[i] == '\r' ) break ; /* calculate */ result = result * 10 + (sbuf[ptr]-'0') ; } } return result ; }  最終スケッチは、以下。 #include <EEPROM.h> #define LINELAST 16 #define OFF 0 #define ON 1 #define BSIZE 32 #define END 0 #define OUT 1 #define LD 2 #define LDN 3 #define AND 4 #define ANDN 5 #define OR 6 #define ORN 7 #define ANDB 8 #define ORB 9 #define JUMP 15 #define PUTBIT 0 #define GETBIT PUTBIT+1 #define ANDOPERATION PUTBIT+2 #define OROPERATION PUTBIT+3 #define BLOCKOPERATIONAND PUTBIT+4 #define BLOCKOPERATIONOR PUTBIT+5 #define ADDRESSUPDATE PUTBIT+6 byte uflag ; byte sbuf[BSIZE] ; byte sindex ; byte cmd ; char ch; byte state ; byte trigger ; byte lcmd ; word iadr ; byte instruction ; byte execute ; byte bport ; byte dport ; word plcsft ; byte bitloc ; byte bitval ; word ixadr ; byte tmp ; word plcdat ; byte asc_hex[16] ; word line ; word xplc ; void perform_plc(); word get_ladder_code(word x); byte get_cmd(word x); void put_ladder_code(word xptr,word xcode); void push_bit(byte x); byte pop_bit(); byte get_bit_location(word x); void show_code(word x); void show_io(byte x); void show_line(byte x); void convert_cap(); word get_step_number(); byte get_instruction(); byte get_operand(); void rs_putchar(char x) { Serial.write( x ); } void rs_puts(char *x) { while ( *x ) { rs_putchar( *x ) ; x++ ; } } void crlf() { rs_putchar('\r'); rs_putchar('\n'); } void show_help() { rs_puts("? help"); crlf(); rs_puts("s assemble"); crlf(); rs_puts("l disassemble"); crlf(); rs_puts("p show PLC code with hexadecimal format"); crlf(); rs_puts("e enable PLC handling"); crlf(); rs_puts("d disable PLC handling"); crlf(); } void setup() { /* set communication speed */ Serial.begin(9600); /* */ DDRD = 0x02 ; DDRB = 0xff ; PORTD = 0xfe ; PORTB = 0x00 ; /* */ uflag = OFF ; sindex = 0 ; state = 0 ; trigger = OFF ; lcmd = 0 ; iadr = 0 ; /* EEPROM word address */ ixadr = 0 ; /* EEPROM word address */ instruction = END ; execute = PUTBIT ; plcsft = 0 ; plcdat = 0 ; tmp = 0 ; /* set charactor values */ asc_hex[0] = '0' ; asc_hex[1] = '1' ; asc_hex[2] = '2' ; asc_hex[3] = '3' ; asc_hex[4] = '4' ; asc_hex[5] = '5' ; asc_hex[6] = '6' ; asc_hex[7] = '7' ; asc_hex[8] = '8' ; asc_hex[9] = '9' ; asc_hex[10] = 'A' ; asc_hex[11] = 'B' ; asc_hex[12] = 'C' ; asc_hex[13] = 'D' ; asc_hex[14] = 'E' ; asc_hex[15] = 'F' ; } void loop() { /* command interpreter */ if ( uflag == ON ) { /* clear flag */ uflag = OFF ; /* new line */ Serial.println(""); /* get command */ cmd = sbuf[0] ; /* help */ if ( cmd == '?' ) { show_help() ; } /* assemble */ if ( cmd == 's' ) { /* convert capital letter */ convert_cap(); /* get step number */ line = get_step_number() ; /* get instruction code */ lcmd = get_instruction() ; /* clear */ xplc = 0 ; /* generate code */ if ( lcmd == END || lcmd == ANDB || lcmd == ORB ) { xplc = (lcmd << 12) ; } else { /* get operand */ xplc |= get_operand() ; /* add opcode */ xplc |= (lcmd << 12) ; } /* store */ put_ladder_code(line,xplc); } /* disassemble */ if ( cmd == 'l' ) { for ( ixadr = 0 ; ixadr < 256 ; ixadr++ ) { /* get 1 word */ plcdat = get_ladder_code(ixadr); /* judge */ if ( plcdat == 0xffff ) break ; /* show */ show_code( plcdat ) ; } } /* show PLC code */ if ( cmd == 'p' ) { for ( ixadr = 0 ; ixadr < 256 ; ixadr++ ) { /* get 1 word */ plcdat = get_ladder_code(ixadr); /* show */ rs_putchar(asc_hex[(plcdat >> 12) & 0xf]); rs_putchar(asc_hex[(plcdat >> 8) & 0xf]); rs_putchar(asc_hex[(plcdat >> 4) & 0xf]); rs_putchar(asc_hex[plcdat & 0xf]); rs_putchar(' '); /* new line */ if ( (ixadr & 15) == 15 ) { crlf(); } } } /* enable PLC handling */ if ( cmd == 'e' ) { trigger = ON ; } /* disable PLC handling */ if ( cmd == 'd' ) { trigger = OFF ; } } /* PLC */ perform_plc(); } void serialEvent() { if ( Serial.available() > 0 ) { /* get 1 charactor */ ch = Serial.read(); /* store */ sbuf[sindex] = ch ; /* increment */ sindex++ ; /* judge */ if ( ch == '\r' ) { sindex = 0 ; uflag = ON ; } } } void perform_plc() { switch ( state ) { /* wait trigger */ case 0 : state = 0 ; if ( trigger == ON ) { state = 1 ; iadr = 0 ; } break ; /* get ladder information */ case 1 : state = 2 ; lcmd = get_ladder_code( iadr ) ; iadr++ ; dport = PIND ; dport >>= 2 ; break ; /* decode instrctuction */ case 2 : state = 3 ; instruction = get_cmd( lcmd ) ; bitloc = (byte)(lcmd & 0x000f); /* OUT code */ if ( instruction == OUT ) { execute = PUTBIT ; } /* LD code */ if ( instruction == LD || instruction == LDN ) { execute = GETBIT ; tmp = ((dport >> bitloc) & 1) ; if ( bitloc > 7 ) { tmp = ((bport >> (bitloc-8)) & 1) ; } bitval = tmp ; /* LDN inverse */ if ( instruction == LDN ) { bitval ^= 1 ; } } /* AND code */ if ( instruction == AND || instruction == ANDN ) { execute = ANDOPERATION ; tmp = ((dport >> bitloc) & 1) ; if ( bitloc > 7 ) { tmp = ((bport >> (bitloc-8)) & 1) ; } bitval = tmp ; /* ANDN inverse */ if ( instruction == ANDN ) { bitval ^= 1 ; } } /* OR code */ if ( instruction == OR || instruction == ORN ) { execute = OROPERATION ; tmp = ((dport >> bitloc) & 1) ; if ( bitloc > 7 ) { tmp = ((bport >> (bitloc-8)) & 1) ; } bitval = tmp ; /* ORN inverse */ if ( instruction == ORN ) { bitval ^= 1 ; } } /* ANDB code */ if ( instruction == ANDB ) { execute = BLOCKOPERATIONAND ; bitval = pop_bit() ; } /* ORB code */ if ( instruction == ORB ) { execute = BLOCKOPERATIONOR ; bitval = pop_bit() ; } /* JUMP code */ if ( instruction == JUMP ) { execute = ADDRESSUPDATE ; ixadr = lcmd & 0x00ff ; } /* END code */ if ( instruction == END ) { state = 5 ; } /* execute */ case 3 : state = 4 ; /* OUT */ if ( execute == PUTBIT ) { /* get value */ bitval = pop_bit(); /* clear */ bport &= ~(1 << bitloc); /* update */ bport |= (bitval << bitloc); /* out */ PORTB = bport ; } /* LD or LDN */ if ( execute == GETBIT ) { push_bit(bitval); } /* AND or ANDN */ if ( execute == ANDOPERATION ) { /* get value and operation */ bitval &= pop_bit(); /* store */ push_bit(bitval); } /* OR or ORN */ if ( execute == OROPERATION ) { /* get value and operation */ bitval |= pop_bit(); /* store */ push_bit(bitval); } /* ANDB */ if ( execute == BLOCKOPERATIONAND ) { /* get value and operation */ bitval &= pop_bit(); /* store */ push_bit(bitval); } /* ORB */ if ( execute == BLOCKOPERATIONOR ) { /* get value and operation */ bitval |= pop_bit(); /* store */ push_bit(bitval); } /* JUMP */ if ( execute == ADDRESSUPDATE ) { iadr = ixadr ; } break ; /* judge continue or break */ case 4 : state = 5 ; if ( trigger == ON ) { state = 1 ; } break ; /* exit */ case 5 : state = 0 ; iadr = 0 ; break ; /* */ default : state = 0 ; break ; } } word get_ladder_code(word x) { word xadr ; /* calcualte address */ xadr = (x << 1) ; /* first byte */ plcdat = EEPROM.read( xadr ) ; plcdat <<= 8 ; /* address increment */ xadr++ ; /* second byte */ plcdat |= EEPROM.read( xadr ) ; return plcdat ; } void put_ladder_code(word xptr,word xcode) { byte dd ; word xadr ; /* calcualte address */ xadr = (xptr << 1) ; /* store first byte */ dd = (xcode >> 8) & 0xff ; EEPROM.write(xadr,dd) ; /* address increment */ xadr++ ; /* second byte */ dd = xcode & 0xff ; EEPROM.write(xadr,dd) ; } byte get_cmd(word x) { word tmp ; /* mask and substitute */ tmp = (x & 0xf000); /* shift */ tmp >>= 12 ; return (byte)tmp ; } void push_bit(byte x) { /* shift */ plcsft <<= 1 ; /* store */ if ( x ) { plcsft |= ON ; } } byte pop_bit() { /* shift */ plcsft >>= 1 ; /* */ return(plcsft & ON) ; } void show_code(word x) { byte xcmd ; byte xopa ; /* get instruction */ xcmd = (x >> 12) & 0x0f ; /* get operand */ xopa = (byte)(x & 0xff) ; /* END */ if ( xcmd == END ) { rs_puts("END"); } /* OUT */ if ( xcmd == OUT ) { rs_puts("OUT "); show_io( xopa ); } /* LD */ if ( xcmd == LD ) { rs_puts("LD "); show_io( xopa ); } /* LDN */ if ( xcmd == LDN ) { rs_puts("LDN "); show_io( xopa ); } /* AND */ if ( xcmd == AND ) { rs_puts("AND "); show_io( xopa ); } /* ANDN */ if ( xcmd == ANDN ) { rs_puts("ANDN "); show_io( xopa ); } /* OR */ if ( xcmd == OR ) { rs_puts("OR "); show_io( xopa ); } /* ORN */ if ( xcmd == ORN ) { rs_puts("ORN "); show_io( xopa ); } /* ANDB */ if ( xcmd == ANDB ) { rs_puts("ANDB"); } /* ORB */ if ( xcmd == ORB ) { rs_puts("ORB"); } /* JUMP */ if ( xcmd == JUMP ) { rs_puts("JUMP "); show_line( xopa ); } crlf(); } void show_io(byte x) { switch (x) { case 0 : rs_puts("X000"); break; case 1 : rs_puts("X001"); break; case 2 : rs_puts("X002"); break; case 3 : rs_puts("X003"); break; case 4 : rs_puts("X004"); break; case 5 : rs_puts("X005"); break; case 8 : rs_puts("Y000"); break; case 9 : rs_puts("Y001"); break; case 10 : rs_puts("Y002"); break; case 11 : rs_puts("Y003"); break; case 12 : rs_puts("Y004"); break; case 13 : rs_puts("Y005"); break; default : break; } } void show_line(byte x) { byte i ; byte line byte msg[3] ; /* copy */ line = x ; /* convert */ for ( i = 0 ; i < 3 ; i++ ) { *(msg+2-i) = (line % 10) + '0' ; line /= 10 ; } /* show */ for ( i = 0 ; i < 3 ; i++ ) { rs_putchar( *(msg+i) ) ; } } void convert_cap() { byte i ; byte msg ; for ( i = 1 ; i < 32 ; i++ ) { /* get 1 byte */ msg = sbuf[i] ; /* ? delimiter */ if ( msg == '\r' ) break ; /* ? not capital letter */ if ( 'a' <= msg && msg <= 'z' ) { msg -= ('a' - 'A') ; } /* store */ sbuf[i] = msg ; } } word get_step_number() { byte i ; byte msg ; word result ; /* clear */ result = 0 ; /* convert */ for ( i = 1 ; i < 6 ; i++ ) { /* get 1 byte */ msg = sbuf[i] ; /* ? white space */ if ( msg == '\t' ) break ; if ( msg == ' ' ) break ; /* calculate */ result = result * 10 + (msg - '0'); } return result ; } byte get_instruction() { byte i ; byte msg ; byte result ; /* skip white space */ for ( i = 1 ; i < 6 ; i++ ) { /* ? white space */ if ( sbuf[i] == '\t' ) break ; if ( sbuf[i] == ' ' ) break ; } msg = sbuf[i+1] ; /* judge */ if ( msg == 'E' ) { result = END ; } if ( msg == 'J' ) { result = JUMP ; } if ( msg == 'L' && sbuf[i+3] != 'N' ) { result = LD ; } if ( msg == 'L' && sbuf[i+3] == 'N' ) { result = LDN ; } if ( msg == 'O' && sbuf[i+3] == 'T' ) { result = OUT ; } if ( msg == 'O' && sbuf[i+3] != 'N' ) { result = OR ; } if ( msg == 'O' && sbuf[i+3] == 'N' ) { result = ORN ; } if ( msg == 'O' && sbuf[i+3] == 'B' ) { result = ORB ; } if ( msg == 'A' && sbuf[i+4] == 'B' ) { result = ANDB ; } if ( msg == 'A' && sbuf[i+4] == 'N' ) { result = ANDN ; } if ( msg == 'A' && sbuf[i+3] == 'D' && sbuf[i+4] != 'N' ) { result = ANDN ; } if ( msg == 'A' && sbuf[i+3] == 'D' && sbuf[i+4] != 'B' ) { result = ANDN ; } return result ; } byte get_operand() { byte i ; byte ptr ; byte msg ; byte result ; /* search delimiter */ result = 0 ; for ( i = 31 ; i > 1 ; i-- ) { if ( sbuf[i] == '\r' ) break ; } msg = i ; /* serch white space */ for ( i = msg ; i > 1 ; i-- ) { if ( sbuf[i] == '\t' ) break ; if ( sbuf[i] == ' ' ) break ; } ptr = i+1 ; msg = sbuf[ptr] ; /* input */ if ( msg == 'X' ) { result = sbuf[ptr+3] - '0' ; } /* output */ if ( msg == 'Y' ) { result = sbuf[ptr+3] - '0' + 8 ; } /* step number */ if ( msg != 'X' && msg != 'Y' ) { result = 0 ; for ( i = ptr ; i < 32 ; i++ ) { /* judge '\r' */ if ( sbuf[i] == '\r' ) break ; /* calculate */ result = result * 10 + (sbuf[ptr]-'0') ; } } return result ; }  TeraTermを利用して、コマンド一覧を表示できるかを  テストしてみると、以下となりました。  逆アセンブルして、16進コードで表示できるか  テストすると以下となり、正しく動作している  ことがわかります。  ニモニックコード入力し、16進で1ワードの  コードがどうなっているかを表示してみると  正しく変換されていることを確認できます。  EEPROMに保存するので、タイプミスしても  再度入力し直せば、修正できます。  EEPROMに保存されているニモニックコードを  確認したいときには、コマンド'l'を使います。  簡単なラダー図を、回路に展開して動作するかを  確認します。  ラダー図から、ニモニック形式の回路情報を作成します。 0 LD X000 1 OR Y000 2 ANDN X001 3 OUT Y000 4 JUMP 0  PLCのニモニック形式情報を入力し、逆アセンブルで  正しく入力されているのかを確認します。  EEPROMにも該当するワードが保存されたかを確認。

目次

inserted by FC2 system