目次

Z80でPLC

 PLCは、ワンチップマイコンがあれば実現できます。  手元には、Z80ボードが多数あるので、これらを  PLCにしてみます。これらのボードは、Z84C015  を利用しているので、PIO、CTC、SIOが含まれて  おり、SRAMも8kBか32kBあるので、ROMの中に  PLC用インタプリタを入れて、実現します。  入力、出力は、PIOがあるので、ポートA、Bを  利用します。8ビット入力、8ビット出力として  使います。  動作は、タイマー割込みで入力ポートから入力メモリ  に内容を転送します。スキャン処理が終了したなら  出力メモリから出力ポートにデータを出力します。  全体をC言語で記述した後、スキャン処理を高速  に実行するため、アセンブリ言語コードで書換え  ます。  最初にAVR用C言語で記述した内容を、Z80に  置き換えます。AVR用C言語のソースコードは  以下。 #include <avr/io.h> #include <avr/io8535.h> #include <avr/interrupt.h> #define TSK_ID_MAX 6 #define TSK_ID0 0 #define TSK_ID1 1 #define TSK_ID2 2 #define TSK_ID3 3 #define TSK_ID4 4 #define TSK_ID5 5 typedef unsigned char UBYTE ; typedef unsigned short UWORD ; typedef signed char SBYTE ; typedef signed short SWORD ; typedef struct { void (*tsk)(void); UWORD wcount ; } TCBP ; typedef union { struct { unsigned char B0:1; unsigned char B1:1; unsigned char B2:1; unsigned char B3:1; unsigned char B4:1; unsigned char B5:1; unsigned char B6:1; unsigned char B7:1; } BIT ; unsigned char DR ; } FLAGSP ; volatile FLAGSP x_flags ; #define SFLAG x_flags.BIT.B0 #define IFLAG x_flags.BIT.B1 #define PFLAG x_flags.BIT.B2 #define NFLAG x_flags.BIT.B3 #define OFLAG x_flags.BIT.B4 #define MFLAG x_flags.BIT.B4 #define BUFSIZE 10 volatile UBYTE rbuf[BUFSIZE] ; volatile UBYTE rindex; UBYTE asc_cha[16] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'} ; #define OFF 0 #define ON 1 #define IDLE 0 #define RUN IDLE+1 #define LED_OFF (PORTD |= 0x10) #define LED_ON (PORTD ^= 0xef) #define LOWER_LATCH 1 #define UPPER_LATCH 2 #define MASKFF 0xff #define MASK0F 0x0f volatile UBYTE msft ; volatile UBYTE memory[32] ; volatile UBYTE mshift ; volatile UWORD cadr ; volatile UWORD cdat ; volatile UBYTE stksft ; volatile UBYTE opcode ; volatile UBYTE operand; volatile UBYTE q ; volatile UBYTE r ; volatile UBYTE cmd ; volatile UWORD rom_address ; volatile UBYTE reg ; volatile UBYTE regx ; volatile UWORD pcnt ; #define TTS_SUSPEND 0 #define TTS_WAIT TTS_SUSPEND+1 #define TTS_READY TTS_SUSPEND+2 #define NO 0 #define YES 1 UBYTE ready ; UBYTE suspend; UBYTE vldtsk ; UBYTE run_tsk; TCBP tcb[TSK_ID_MAX]; /*------------------------*/ /* task function protoype */ /*------------------------*/ void tsk0_proc(void); void tsk1_proc(void); void tsk2_proc(void); void tsk3_proc(void); void tsk4_proc(void); void tsk5_proc(void); /*-----------------------*/ /* system call prototype */ /*-----------------------*/ void init_os(void); void cre_tsk(UBYTE tid,void (*tsk)(void)); void sta_tsk(UBYTE tid,UBYTE sta); void rsm_tsk(UBYTE tid); void sus_tsk(UBYTE tid); void slp_tsk(void); void wai_tsk(UWORD x); UBYTE is_tsk_ready(UBYTE tid); /*--------------------------------*/ /* Insert user functions protoype */ /*--------------------------------*/ void user_initialize(void); #define ITRG 0 #define OEA 1 #define OEB 2 #define OEO 3 #define OTRGA 4 #define OTRGB 5 #define OP_END 0 #define OP_OUT 1 #define OP_LD 2 #define OP_LDN 3 #define OP_AND 4 #define OP_ANDN 5 #define OP_OR 6 #define OP_ORN 7 #define OP_ANDB 8 #define OP_ORB 9 void rs_putchar(UBYTE x); void rs_puts(UBYTE *x); void write_eeprom(UWORD adr,UBYTE dat); UBYTE read_eeprom(UWORD adr); void push(UBYTE x); UBYTE pull(void); UWORD get_circuit_infomation(UWORD x); void doStep(UWORD radr); void doScan(UWORD radr); UBYTE get_hex(UBYTE x); UWORD get_ad(UBYTE onoff); void put_dat_eeprom(UWORD adr,UWORD dat); /*------*/ /* main */ /*------*/ int main(void) { TCBP pcur_tsk ; /* disable interrupt */ cli(); /* initialize monitor */ init_os(); cre_tsk(TSK_ID0,tsk0_proc); cre_tsk(TSK_ID1,tsk1_proc); cre_tsk(TSK_ID2,tsk2_proc); cre_tsk(TSK_ID3,tsk3_proc); cre_tsk(TSK_ID4,tsk4_proc); cre_tsk(TSK_ID5,tsk5_proc); sta_tsk(TSK_ID0,TTS_READY); sta_tsk(TSK_ID1,TTS_SUSPEND); sta_tsk(TSK_ID2,TTS_SUSPEND); sta_tsk(TSK_ID3,TTS_READY); sta_tsk(TSK_ID4,TTS_READY); sta_tsk(TSK_ID5,TTS_READY); user_initialize(); /* enable interrupt */ sei(); /* loop */ run_tsk = TSK_ID0 ; while ( 1 ) { pcur_tsk = tcb[run_tsk] ; if ( is_tsk_ready( run_tsk ) == YES ) { (*(pcur_tsk.tsk))(); } run_tsk++; if ( run_tsk == TSK_ID_MAX ) { run_tsk = TSK_ID0 ; } } /* dummy */ return 0 ; } /*----------------*/ /* task functions */ /*----------------*/ void tsk0_proc(void) { /* mode switch handling */ LED_OFF ; if ( MFLAG == ON ) { LED_ON ; } /* check flag */ if ( SFLAG == OFF ) return ; /* clear flag */ SFLAG = OFF ; /* get command */ cmd = *(rbuf+0) ; /* judge */ if ( cmd == 'S' ) { /* get address */ cadr = get_ad(OFF) ; /* get data */ cdat = get_ad(ON) ; /* wake up */ rsm_tsk( TSK_ID1 ) ; } if ( cmd == 'L' ) { rsm_tsk( TSK_ID2 ) ; } if ( cmd == 'I' ) { IFLAG = OFF ; if ( *(rbuf+1) == '1' ) { IFLAG = ON ; } } if ( cmd == 'P' ) { PFLAG = OFF ; if ( *(rbuf+1) == '1' ) { PFLAG = ON ; pcnt = 0 ; } } if ( cmd == 'N' ) { pcnt += 2 ; } if ( cmd == 'O' ) { OFLAG = OFF ; if ( *(rbuf+1) == '1' ) { OFLAG = ON ; } } } void tsk1_proc(void) { /* save */ put_dat_eeprom( cadr , cdat ); /* sleep */ slp_tsk(); } void tsk2_proc(void) { UBYTE tmp ; UBYTE xdat[3]; /* set delimiter */ *(xdat+2) = 0 ; for ( rom_address = 0 ; rom_address < 512 ; rom_address++ ) { tmp = read_eeprom( rom_address ) ; *(xdat+0) = asc_cha[ (tmp >> 4) & MASK0F ]; *(xdat+1) = asc_cha[ tmp & MASK0F ]; rs_puts( xdat ); } /* sleep */ slp_tsk(); } void tsk3_proc(void) { UBYTE xdat[3]; UBYTE tmp ; /* send latch trigger */ PORTB |= (1 << ITRG); PORTB &= ~(1 << ITRG); /* enable nOEA */ PORTB &= ~(1 << OEA); /* get lower data */ *(memory+0) = PINA ; /* disable nOEA */ PORTB |= (1 << OEA); /* enable nOEB */ PORTB &= ~(1 << OEB); /* get upper data */ *(memory+1) = PINA ; /* disable nOEB */ PORTB |= (1 << OEB); /* show input memory */ if ( IFLAG == ON ) { *(xdat+2) = 0 ; tmp = *(memory+0) ; *(xdat+0) = asc_cha[ (tmp >> 4) & MASK0F ]; *(xdat+1) = asc_cha[ tmp & MASK0F ]; rs_puts( xdat ); tmp = *(memory+1) ; *(xdat+0) = asc_cha[ (tmp >> 4) & MASK0F ]; *(xdat+1) = asc_cha[ tmp & MASK0F ]; rs_puts( xdat ); } } void tsk4_proc(void) { UWORD i; if ( PFLAG == ON ) { doStep(pcnt); } else { for ( i = 0 ; i < 512 ; i += 2 ) { doStep(i); } } } void tsk5_proc(void) { UBYTE xdat[3]; UBYTE tmp ; /* output lower data */ PORTC = *(memory+2) ; /* send latch pulse */ PORTB |= (1 << OTRGA) ; PORTB &= ~(1 << OTRGA) ; /* output upper data */ PORTC = *(memory+3) ; /* latch upper data */ PORTB |= (1 << OTRGB) ; PORTB &= ~(1 << OTRGB) ; /* show output memory */ if ( OFLAG == ON ) { *(xdat+2) = 0 ; tmp = *(memory+2) ; *(xdat+0) = asc_cha[ (tmp >> 4) & MASK0F ]; *(xdat+1) = asc_cha[ tmp & MASK0F ]; rs_puts( xdat ); tmp = *(memory+3) ; *(xdat+0) = asc_cha[ (tmp >> 4) & MASK0F ]; *(xdat+1) = asc_cha[ tmp & MASK0F ]; rs_puts( xdat ); } } /*------------------*/ /* system call body */ /*------------------*/ void init_os(void) { ready = vldtsk = 0 ; } void cre_tsk(UBYTE tid,void (*tsk)(void)) { if ( tid >= TSK_ID_MAX ) return ; vldtsk |= (1 << tid) ; tcb[tid].tsk = tsk; tcb[tid].wcount = 0; } void sta_tsk(UBYTE tid,UBYTE sta) { if ( tid >= TSK_ID_MAX ) return ; if ( sta == TTS_READY ) { ready |= (1 << tid); suspend &= ~(1 << tid); } if ( sta == TTS_WAIT ) { ready &= ~(1 << tid); suspend &= ~(1 << tid); } if ( sta == TTS_SUSPEND ) { ready &= ~(1 << tid); suspend |= (1 << tid); } } void rsm_tsk(UBYTE tid) { if ( tid >= TSK_ID_MAX ) return ; ready |= (1 << tid); suspend &= ~(1 << tid); } void sus_tsk(UBYTE tid) { if ( tid >= TSK_ID_MAX ) return ; ready &= ~(1 << tid); suspend |= (1 << tid); } void slp_tsk(void) { sus_tsk(run_tsk); } void wai_tsk(UWORD x) { ready &= ~(1 << run_tsk); suspend &= ~(1 << run_tsk); tcb[run_tsk].wcount = x ; } UBYTE is_tsk_ready(UBYTE tid) { return( (ready >> tid) & 1 ) ; } /*-----------------------------*/ /* timer handler */ /* call from timer interrupt */ /*-----------------------------*/ void timer_handler(void) { UBYTE tmp; UBYTE i ; tmp = (ready ^ vldtsk) ^ suspend ; for ( i = 0 ; i <= TSK_ID_MAX ; i++ ) { if ( tmp & 1 ) { tcb[i].wcount-- ; if ( tcb[i].wcount == 0 ) { rsm_tsk(i); } } tmp >>= 1 ; } } /*-----------------------*/ /* Insert user functions */ /*-----------------------*/ #define FOSC 8000000 #define BAUD 38400 #define MYUBRR (FOSC/16/BAUD)-1 void user_initialize(void) { volatile UBYTE i ; /* PORT A */ DDRA = 0b00000000 ; /* iiiiiiii */ PORTA = 0b00000000 ; /* 00000000 */ /* PORT B */ DDRB = 0b11111111 ; /* oooooooo */ PORTB = 0b00000000 ; /* 00000000 */ /* PORT C */ DDRC = 0b11111111 ; /* oooooooo */ PORTC = 0b00000000 ; /* 00000000 */ /* PORT D */ DDRD = 0b11110010 ; /* ooooiioi */ PORTD = 0b00010000 ; /* 00010000 */ /* initialize registers */ x_flags.DR = 0 ; for ( i = 0 ; i < 32 ; i++ ) { *(memory+i) = 0 ; } msft = 0 ; /* initialize serial */ { /* clear index */ rindex = 0 ; /* clear buffer */ *(rbuf+0) = 0 ; /* set Baud Rate Registers */ UBRR = MYUBRR ; /* Enable receive interrupt , receive module and transmit module */ UCR = (1 << TXEN) | (1 << RXEN) | (1 << RXCIE); } /* initialize timer1 */ { /* clear timer/counter */ TCNT1 = 0 ; /* set counter */ OCR1A = 9999 ; /* Control Register 1 B */ TCCR1B = (1 << CS10) ; } /* Enable interrupt */ TIMSK |= (1 << OCIE1A) | (1 << TOIE0); } /* UART receive interrupt */ ISR(SIG_UART_RECV) { UBYTE ch ; /* get 1 charactoer */ ch = UDR ; /* store */ *(rbuf+rindex) = ch ; rindex++ ; /* judge */ if ( ch == '\r' ) { SFLAG = ON ; rindex = 0 ; } } /* timer1 interrupt */ ISR(SIG_OUTPUT_COMPARE1A) { /* clear timer/counter */ TCNT1 = 0 ; /* get switch data */ msft <<= 1 ; msft &= 0xfe ; if ( PIND & 0x20 ) { msft |= 1 ; } if ( msft == MASKFF ) { MFLAG = ON ; } else { MFLAG = OFF ; } /* handling */ timer_handler(); } void rs_putchar(UBYTE x) { while ( !(USR & (1 <<<< UDRE)) ) {} UDR = x ; } void rs_puts(UBYTE *x) { while ( *x != '\0' ) { rs_putchar( *x ) ; x++ ; } rs_putchar('\r'); rs_putchar('\n'); } void write_eeprom(UWORD adr,UBYTE dat) { /* wait */ while ( EECR & (1 << EEWE) ) ; /* set address */ EEARH = (UBYTE)((adr >> 8) & MASKFF); EEARL = (UBYTE)(adr & MASKFF); /* set data */ EEDR = dat ; /* enable Master Write Enable */ EECR |= (1 << EEMWE) ; /* enable Write Enable */ EECR |= (1 << EEWE); } UBYTE read_eeprom(UWORD adr) { /* wait */ while ( EECR & (1 << EEWE) ) ; /* set address */ EEARH = (UBYTE)((adr >> 8) & MASKFF); EEARL = (UBYTE)(adr & MASKFF); /* enable Read Enable */ EECR |= (1 << EERE) ; return EEDR ; } void push(UBYTE x) { stksft <<= 1 ; stksft &= 0xfe ; stksft |= x ; } UBYTE pull(void) { volatile UBYTE tmp ; tmp = stksft ; stksft >>= 1 ; return(tmp & 1); } UWORD get_circuit_infomation(UWORD x) { volatile UBYTE dh; volatile UBYTE dl; volatile UWORD result ; /* get upper byte */ dh = read_eeprom( x ) ; /* get lower byte */ dl = read_eeprom( x+1 ) ; /* concatenate */ result = dh ; result <<= 8 ; result |= dl ; return result ; } void doStep(UWORD radr) { /* get circuit infomation from EEPROM */ operand = get_circuit_infomation( radr ); /* get operational code */ opcode = (UBYTE)(operand >> 12); operand &= 0x0fff ; q = ((operand & 0xff) >> 3); r = ((operand & 0xff) & 0x07); /* END instruction */ if ( opcode == OP_END ) return ; /* LD LDN instruction */ if ( opcode == OP_LD || opcode == OP_LDN ) { push( reg ); reg = *(memory+q); reg >>= r; reg &= 1 ; /* inverse */ if ( opcode == OP_LDN ) { reg ^= 1 ; } } /* OP_AND OP_ANDN OP_OR OP_ORN instruction */ if ( opcode == OP_AND || opcode == OP_ANDN || opcode == OP_OR || opcode == OP_ORN ) { regx = *(memory+q); regx >>= r; regx &= 1; /* inverse */ if ( opcode == OP_ANDN || opcode == OP_ORN ) { regx ^= 1 ; } /* AND ANDN instruction */ if ( opcode == OP_AND || opcode == OP_ANDN ) { reg &= regx ; } /* OR ORN instruction */ if ( opcode == OP_OR || opcode == OP_ORN ) { reg |= regx ; } } /* OP_ANDB OP_ORB instruction */ if ( opcode == OP_ANDB || opcode == OP_ORB ) { regx = pull(); /* block AND */ if ( opcode == OP_ANDB ) { reg &= regx ; } /* block OR */ if ( opcode == OP_ORB ) { reg |= regx ; } } /* OP_OUT instruction */ if ( opcode == OP_OUT ) { /* shift bit data */ reg <<= r ; /* clear target bit */ *(memory+q) ^= ~(1 << r); /* store target bit */ *(memory+q) |= reg ; } } UBYTE get_hex(UBYTE x) { volatile UBYTE result ; /* default */ result = 0 ; /* select */ if ( '0' <= x && x <= '9' ) { result = x - '0' ; } if ( 'A' <= x && x <= 'F' ) { result = x - 'A' + 10 ; } if ( 'a' <= x && x <= 'f' ) { result = x - 'a' + 10 ; } /* */ return result ; } UWORD get_ad(UBYTE onoff) { volatile UWORD result ; /* get first byte */ result = get_hex( *(rbuf+1) ); /* get second byte */ result = (result << 4) | get_hex( *(rbuf+2) ); /* get third byte */ result = (result << 4) | get_hex( *(rbuf+3) ); /* get fourth byte */ result = (result << 4) | get_hex( *(rbuf+4) ); /* compute data */ if ( onoff == ON ) { /* get first byte */ result = get_hex( *(rbuf+5) ); /* get second byte */ result = (result << 4) | get_hex( *(rbuf+6) ); /* get third byte */ result = (result << 4) | get_hex( *(rbuf+7) ); /* get fourth byte */ result = (result << 4) | get_hex( *(rbuf+8) ); } /* */ return result ; } void put_dat_eeprom(UWORD adr,UWORD dat) { volatile UBYTE dhl ; /* get upper data */ dhl = (UBYTE)((dat >> 8) & MASKFF) ; /* store */ write_eeprom( adr , dhl ); /* get lower data */ dhl = (UBYTE)(dat & MASKFF) ; /* store */ write_eeprom( adr+1 , dhl ); }  ソースコードを眺めると、シリアルインタフェース、EEPROMを  利用しています。シリアルインタフェースはZ80SIOを利用する  だけでよいのですが、EEPROMは外付けとなります。  RTOS(Real Time Operating System)を利用しているので、タイマー  割込みでを10msを生成しなければなりません。  パラレルI/Oは、Z80PIOがあり、8ビットx2を使えますが  EEPROMを外付けするには、どちらかのチャネルをEEPROMに  割当てしないとうまくいかないでしょう。  手元には8255が2個あるので、I/Oメモリに8255を2個配置し  入力24ビット、出力24ビットとして使います。  Z84C015では、内蔵I/Oは10h〜1Fhにアサインされているので  2個の8255を、00h〜07hに配置します。  I/Oアドレスを、以下とします。  00h 8255#A_PA (input)  01h 8255#A_PB (input)  02h 8255#A_PC (input)  03h 8255#A_CTRL  04h 8255#B_PA (output)  05h 8255#B_PB (output)  06h 8255#B_PC (output)  07h 8255#B_CTRL  I/Oアドレスを、上のようにするため、74LS125、74LS139を  使います。配線は、次のように接続します。  この他に、リード、ライト信号が必要なので74LS32を利用し  負論理のAND回路を作ります。  デジタル回路がわかった時点で、入力、出力を担当する  最下位層のプログラムコードを記述します。  8255の#Aは、全ポートを入力にするので  制御レジスタに設定する値は、0x9b。  8255の#Bは、全ポートを出力にするので  制御レジスタに設定する値は、0x80。  Cのコードにすると、次のようになります。 #define I8255A 0x00 #define I8255B 0x01 #define I8255C 0x02 #define I8255W 0x03 #define O8255A 0x04 #define O8255B 0x05 #define O8255C 0x06 #define O8255W 0x07 #define I8255DDRV 0x9b #define O8255DDRV 0x80 void set_dds(void) { outportb(I8255W,I8255DDRV); outportb(O8255W,O8255DDRV); }  入力24ビット、出力24ビットは、構造体変数の中に  配列を定義していれておけば充分でしょう。 typedef unsigned char UBYTE ; typedef struct { UBYTE xin[3]; UBYTE xout[3]; } IOPARAMP ;  構造体変数の内容を決めれば、入力と出力は  専用関数の中に定義するだけ。 IOPARAMP ioparam ; void get_io(IOPARAMP iox) { iox.xin[0] = inportb(I8255A) ; iox.xin[1] = inportb(I8255B) ; iox.xin[2] = inportb(I8255C) ; } void put_io(IOPARAMP iox) { outportb(O8255A,iox.xout[0]); outportb(O8255B,iox.xout[1]); outportb(O8255C,iox.xout[2]); }  コーリングシーケンスは、単純です。 入力   単純に関数get_ioを呼び出すだけです。    get_io(ioparam);   これだけで構造体変数の中に、その時点の   入力値(3バイト)を記憶できます。 出力   構造体変数の中に、値を入れて関数put_ioを   呼出すだけになります。    ioparam.xin[0] = 0x12 ;    ioparam.xin[1] = 0x34 ;    ioparam.xin[2] = 0x56 ;    put_io(ioparam);  PLCの場合、ビット単位での処理になるので  バイトアクセス処理を用意して、コードを  作成しやすくします。  1バイト中の指定ビットをセット、クリアする  関数を定義します。 UBYTE bit_set(UBYTE xdat,UBYTE xbit) { UBYTE result ; /* copy */ result = xdat ; /* judge */ if ( xbit < 8 ) { result |= (1 << xbit); } return result ; } UBYTE bit_clr(UBYTE xdat,UBYTE xbit) { UBYTE result ; /* copy */ result = xdat ; /* judge */ if ( xbit < 8 ) { result &= ~(1 << xbit); } return result ; }  さらに指定ビットの論理反転をできるように  しておけば、使いやすくなります。 UBYTE bit_invers(UBYTE xdat,UBYTE xbit) { UBYTE result ; /* copy */ result = xdat ; /* judge */ if ( xbit < 8 ) { result ^= (1 << xbit); } return result ; }  これらの関数は、PLCのニモニックで次の命令を  実行する場合に使います。  LDN ANDN ORN  1ビットの値をスタックに保存、復帰することができると  スキャン処理を記述しやすくなるので、16ビットのシフト  レジスタを用意し、1ビットの値を出し入れする関数を  定義しておきます。  typedef unsigned short UWORD ; UWORD stksft ; void push_bit(UBYTE xdat) { /* shift */ stksft <<= 1 ; /* update LSB */ stksft |= xdat ; } UBYTE pop_bit(void) { /* shift */ stksft >>= 1 ; /* get LSB */ return(stksft & 1) ; }  最下位層の処理を定義したので、入力、スキャン、出力の  各機能を担当タスクに割り振っていきます。 入力   入力は、タスク3に記述されているので   関数get_ioを利用して、定義します。 void tsk3_proc(void) { get_io(ioparam); } 出力   出力は、タスク5に記述されているので   関数put_ioを利用して、定義します。   スキャン結果で生成された出力情報がメモリの中に   あるとして、次のように記述します。 UBYTE omemory[3] ; void tsk5_proc(void) { /* copy */ ioparam.xout[0] = omemory[0] ; ioparam.xout[1] = omemory[1] ; ioparam.xout[2] = omemory[2] ; /* send */ put_io(ioparam); } スキャン   スキャンは、タイマー割込みで10msに   呼び出される仕様とします。   タイマー割込みをトリガーにして、入力と記憶に   含まれている内容をすべて読み込み、指定されて   いる処理後、記憶と出力の内容を更新します。 (under construction)
目次

inserted by FC2 system