目次

PID制御

 手元にあるDesignWaveMagazineの記事に
 ARMを利用したPID制御の事例がありました。

 以前、PID制御学習用のシミュレーション基板
 を作成したので、このシミュレーション基板を
 利用して、PID制御の基本実験をします。



 本シミュレーション基板は、D/Aコンバータより
 電圧でパラメータを与え、A/Dコンバータで制御
 対象の出力電圧を取得する仕様です。

 制御対象は、EL(ELement block)と呼ぶOPアンプで
 実現する積分器、微分器、倍数器、反転器で構成
 します。これらをパッチコードで接続します。



 EL(ELement block)には、ピンソケットを使い
 抵抗、キャパシタを着脱できます。



 シミュレーション基板とD/A、A/Dコンバータの
 接続点には、電圧バッファを入れ、EL動作に
 影響がないようになっています。

 ARMから見て、D/Aコンバータから電圧を出力し
 同時にA/Dコンバータで電圧を入力するだけで
 PID制御の観測ができる環境を用意できます。

 最初は、ARMを観測装置として利用し、入力、出力
 の関係をグラフで表示するシステムを構築しました。



 グラフ表示は、PersonalComputerの仕事として
 ELへ印加する電圧値とタイミングを決め、ELが
 出力する電圧値を内部SRAMに保存します。

 ファームウエアで実現するシーケンス処理は
 以下としました。
  1. カウンタ値に対応し、D/Aコンバータから電圧出力
  2. A/DコンバータでD/A値とELの電圧値を取得し、保存
  3. カウンタ値を+1する
  4. カウンタ値が100になったら終了
  5. 10ms遅延
  6. 1に戻る
 カウンタ値は0〜99とし、100サンプル分の電圧値を  保存する仕様とします。10ms遅延は、利用している  ELにOPアンプを使っているので、usオーダーでは  反応しきれないと判断し、この数値にしています。  シーケンスを、関数にまとめてみます。 void send_catch_value(void) { volatile UWORD ptr ; volatile UWORD tmp ; volatile UBYTE i ; /* judge */ if ( gcnt < 0 ) return ; /* set pointer */ ptr = (gcnt << 2) ; /* 4ch handling */ for ( i = 0 ; i < 4 ; i++ ) { /* get data */ tmp = *(dac+ptr+i) ; /* put DAC value */ if ( gcnt == 0 ) { put_dac(i,tmp); } if ( (gcnt > 0) && (*(pre_dac+i) != tmp) ) { put_dac(i,tmp); } /* get ADC value */ *(adv+ptr+i) = get_adv(i+4) ; /* store DAC value */ *(pre_dac+i) = tmp ; } /* update */ gcnt++ ; if ( gcnt == GLAST ) { gcnt = -1 ; } /* delay */ delay_ms(10); }  PCからのコマンドで、上記関数を動かします。  A/D変換、D/A変換の利用チャネルを決めます。  A/D変換器は、チャネル0〜11までのうち  チャネル4〜7の4チャネルを利用します。  D/A変換器は、チャネル0〜3すべて使います。  操作を簡単にするために、2関数を定義します。  D/A変換器は、チャネルを指定して、数値を与えます。 void put_dac(UBYTE chx,UWORD x) { /* judge */ if ( chx > 3 ) return ; /* store */ switch ( chx ) { case 1 : DAC1DAT = (x << 16); break ; case 2 : DAC2DAT = (x << 16); break ; case 3 : DAC3DAT = (x << 16); break ; default : DAC0DAT = (x << 16); break ; } }  D/A変換のデータレジスタは32ビットの上位16ビットに  数値を設定する仕様なので、シフトしています。  A/D変換器は、チャネルを指定して、数値を取得します。 UWORD get_adv(UBYTE chx) { volatile UWORD result ; /* default */ result = 0 ; /* judge */ if ( chx > 11 ) { return result ; } /* select channel */ ADCCP = chx ; /* config: fADC/2, acq. time = 16 clocks => ADC Speed = 1MSPS */ ADCCON = 0x7E3; /* wait conversion */ while (!ADCSTA) {} /* data */ result = (ADCDAT >> 16) & 0x3fff ; return result ; }  A/D変換のデータレジスタは32ビットの上位16ビットに  数値を格納している仕様なので、シフトしています。  ここで、コマンドを定義しておきます。  コマンドを定義したので、main関数を定義します。 int main(void) { /* initialize user */ init_usr(); /* show message */ rs_puts("Hello"); crlf(); /* endless loop */ while(ON) { /* command interrpreter */ if ( uflag == ON ) { /* newline */ crlf(); /* clear flag */ uflag = OFF ; /* judge */ cmd = *(sbuf+0) ; if ( cmd == '?' ) { show_help(); } /* ADC */ if ( cmd == 'A' ) { show_adv(); } /* DAC */ if ( cmd == 'D' ) { set_dac(); } /* clear */ if ( cmd == 'Z' ) { clear_values(); } /* get data */ if ( cmd == 'R' ) { gcnt = 0 ; } } /* PID handling */ send_catch_value(); } /* dummy return */ return (0); }  PCからのコマンドは、受信割込みで取得します。  コマンド+パラメータが受信バッファに格納された  なら、フラグでmain関数の無限ループ内に通知する  仕様です。  コマンドごとの動作を、4つの関数に振分けます。  各処理は単純なので、以下のように定義しました。  show_help   1文字コマンドと簡単な説明を文字列として   シリアルインタフェースに出力します。 void show_help(void) { rs_puts("? help") ; crlf(); rs_puts("A show A/D values") ; crlf(); rs_puts("D set DAC values") ; crlf(); rs_puts("R execute") ; crlf(); rs_puts("Z initialize") ; crlf(); }  show_adv   配列に保存してあるA/D値を、シリアルインタフェースに   出力します。1行に4チャネル分のデータを10進4けた   で出力します。 void show_adv(void) { volatile UWORD ptr ; volatile UWORD tmp ; volatile UBYTE i ; volatile UBYTE j ; /* loop */ for ( i = 0 ; i < 100 ; i++ ) { /* calculate address */ ptr = (i << 2) ; /* show 4 word */ for ( j = 0 ; j < 4 ; j++ ) { tmp = *(adv+ptr+j) ; show_value(tmp); } } }  set_dac   D/A値、カウンタ値、チャネル番号の順に   受信バッファにデータが入っているので   D/A値用の配列に値を格納します。 void set_dac(void) { volatile UBYTE idx ; volatile UBYTE chx ; volatile UWORD ptrx ; volatile UWORD tmp ; /* get counter */ idx = get_hex( *(sbuf+1) ); idx *= 10 ; idx += get_hex( *(sbuf+2) ); /* get channel number */ chx = get_hex( *(sbuf+3) ); /* calculate pointer */ ptrx = (idx << 2) + chx ; /* get value */ tmp = get_hex( *(sbuf+4) ) ; tmp *= 10 ; tmp += get_hex( *(sbuf+5) ) ; tmp *= 10 ; tmp += get_hex( *(sbuf+6) ) ; tmp *= 10 ; tmp += get_hex( *(sbuf+7) ) ; /* store */ *(dac+ptrx) = tmp ; }  clear_values   A/D、D/A値用の配列に0を格納し、カウンタを-1に   設定します。 void clear_values(void) { volatile UWORD i ; for ( i = 0 ; i < ASIZE ; i++ ) { *(adv+i) = 0 ; *(dac+i) = 0 ; } gcnt = -1 ; }  他に必要な関数を定義し、全体としては  次のソースコードになります。 #include <ADuC7026.h> #define OFF 0 #define ON OFF+1 /* data definitions */ typedef unsigned char UBYTE ; typedef signed char SBYTE ; typedef unsigned short UWORD ; typedef signed short SWORD ; typedef unsigned long ULONG ; typedef signed long SLONG ; void IRQ_Handler(void) __irq; void init_usr(void); #define MASKFF 0xFF #define MASK0F 0x0F #define MASK80 0x80 #define MASK40 0x40 #define MASK20 0x20 #define MASK10 0x10 #define MASK08 0x08 #define MASK04 0x04 #define MASK02 0x02 #define MASK01 0x01 #define MASKF0 0xF0 #define GLAST 100 ULONG timcnt ; void rs_putchar(UBYTE x); void crlf(void); void rs_puts(UBYTE *x); UBYTE get_hex(UBYTE x); void ADCpoweron(UWORD x); void show_help(void); void delay_ms(UWORD x); void show_adv(void); void set_dac(void); void send_catch_value(void); void clear_values(void); void show_value(UWORD x); UWORD get_adv(UBYTE chx) ; void put_dac(UBYTE chx,UWORD x) ; /* global variables */ volatile UBYTE uflag ; volatile UBYTE sbuf[16] ; volatile UBYTE sindex ; volatile UBYTE cmd ; #define SCNT_MAX 20 #define MASK07 0x07 #define ASIZE 400 #define DSIZE 400 volatile UWORD adv[ASIZE] ; volatile UWORD dac[DSIZE] ; volatile SBYTE gcnt ; int main(void) { /* initialize user */ init_usr(); /* show message */ rs_puts("Hello"); crlf(); /* endless loop */ while(ON) { /* command interrpreter */ if ( uflag == ON ) { /* newline */ crlf(); /* clear flag */ uflag = OFF ; /* judge */ cmd = *(sbuf+0) ; if ( cmd == '?' ) { show_help(); } /* ADC */ if ( cmd == 'A' ) { show_adv(); } /* DAC */ if ( cmd == 'D' ) { set_dac(); } /* clear */ if ( cmd == 'Z' ) { clear_values(); } /* get data */ if ( cmd == 'R' ) { gcnt = 0 ; } } /* PID handling */ send_catch_value(); } /* dummy return */ return (0); } void IRQ_Handler(void) __irq { volatile UBYTE ch ; /* judge UART receive interruption */ if ( (IRQSTA & UART_BIT) == UART_BIT ) { /* judge */ if ( COMSTA0 & 1 ) { /* clear flag */ ch = COMRX ; *(sbuf+sindex) = ch ; sindex++ ; if ( ch == '\r' ) { sindex = 0 ; uflag = ON ; } } } /* judge timer3 interruption (1ms) */ if ( (IRQSTA & WATCHDOG_TIMER_BIT) == WATCHDOG_TIMER_BIT ) { /* clear timer3 interrupt flag */ T3CLRI = 0xff ; /* increment */ timcnt++ ; /* blink */ if ( (timcnt & 0x3ff) == 1000 ) { GP4DAT ^= (1 << 23); } } } void init_usr(void) { /* select clock 10.44MHz initialized in start up routine */ PLLKEY1 = 0xaa ; PLLCON = 0x01 ; PLLKEY2 = 0x55 ; /* power control initialized in start up routine */ /* clear flag */ uflag = OFF ; gcnt = -1 ; /* clear counter */ timcnt = 0 ; /* initialize UART */ { /* set baud rate 19200 bps CD = 2 */ COMCON0 = 0x80 ; /* select COMDIV1 and COMDIV0 */ COMDIV0 = 0x11 ; COMDIV1 = 0x00 ; /* set conditions */ COMCON0 = 0x03 ; /* select COMRX and COMTX , 8bit data , 1 stop bit , no parity */ /* enable interrupt */ COMIEN0 = 0x01 ; /* ERBFI */ } /* P0 */ { /* */ GP0DAT = 0xDF000000 ; } /* P1 */ { /* use UART */ GP1CON = 0x00000011 ; /* */ GP1DAT = 0xfe000000 ; } /* P2 */ { /* all bits outputs */ GP2DAT = 0xff000000 ; } /* P3 */ { /* all bits outputs */ GP3DAT = 0xff000000 ; } /* P4 */ { GP4DAT = 0xff000000 ; } /* initialize timer 3 (1ms) */ { T3LD = 33 ; /* (32.768kHz / 33) = about 1kHz */ T3CON = 0xc0 ; /* enable , cyclic , 1/1 */ } /* A/D converter */ { /* enable A/D converter */ ADCpoweron(20000); /* select channel */ ADCCP = 0x04; /* connect internal 2.5V reference to Vref pin */ REFCON = 0x01; } /* D/A converter */ { /* channel 0 (referance AVcc) */ DAC0CON = 0x03 ; /* channel 1 (referance AVcc) */ DAC1CON = 0x03 ; /* channel 2 (referance AVcc) */ DAC2CON = 0x03 ; /* channel 3 (referance AVcc) */ DAC3CON = 0x03 ; } /* */ clear_values(); /* enable timer 3 interrupt and UART interrupt */ IRQEN = WATCHDOG_TIMER_BIT | UART_BIT ; } /* UART putchar */ void rs_putchar(UBYTE x) { /* ? transmmit buffer empty */ while( (COMSTA0 & 0x40) == 0 ) ; /* set value */ COMTX = x ; } /* carriage return and line feed */ void crlf(void) { rs_putchar('\r'); rs_putchar('\n'); } /* UART puts */ void rs_puts(UBYTE *x) { while ( *x != '\0' ) { rs_putchar( *x ) ; x++ ; } } /* convert ASCII to number */ UBYTE get_hex(UBYTE x) { UBYTE result ; /* default */ result = 0 ; /* judge */ 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 ; } #define ADCPWR 5 void ADCpoweron(UWORD x) { /* power-on the ADC */ ADCCON |= (1 << ADCPWR) ; /* wait for ADC to be fully powered on */ while ( x) { x-- ; } } /* show help */ void show_help(void) { rs_puts("? help") ; crlf(); rs_puts("A show A/D values") ; crlf(); rs_puts("D set DAC values") ; crlf(); rs_puts("R execute") ; crlf(); rs_puts("Z initialize") ; crlf(); } void delay_ms(UWORD x) { ULONG last ; /* calculate */ last = timcnt + 10 * x ; /* wait */ while ( timcnt < last ) ; } void show_adv(void) { volatile UWORD ptr ; volatile UWORD tmp ; volatile UBYTE i ; volatile UBYTE j ; /* loop */ for ( i = 0 ; i < 100 ; i++ ) { /* calculate address */ ptr = (i << 2) ; /* show 4 word */ for ( j = 0 ; j < 4 ; j++ ) { tmp = *(adv+ptr+j) ; show_value(tmp); } } } void set_dac(void) { volatile UBYTE idx ; volatile UBYTE chx ; volatile UWORD ptrx ; volatile UWORD tmp ; /* get counter */ idx = get_hex( *(sbuf+1) ); idx *= 10 ; idx += get_hex( *(sbuf+2) ); /* get channel number */ chx = get_hex( *(sbuf+3) ); /* calculate pointer */ ptrx = (idx << 2) + chx ; /* get value */ tmp = get_hex( *(sbuf+4) ) ; tmp *= 10 ; tmp += get_hex( *(sbuf+5) ) ; tmp *= 10 ; tmp += get_hex( *(sbuf+6) ) ; tmp *= 10 ; tmp += get_hex( *(sbuf+7) ) ; /* store */ *(dac+ptrx) = tmp ; } void send_catch_value(void) { volatile UWORD ptr ; volatile UWORD tmp ; volatile UBYTE i ; /* judge */ if ( gcnt < 0 ) return ; /* set pointer */ ptr = (gcnt << 2) ; /* 4ch handling */ for ( i = 0 ; i < 4 ; i++ ) { /* get data */ tmp = *(dac+ptr+i) ; /* put DAC value */ if ( gcnt == 0 ) { put_dac(i,tmp); } if ( (gcnt > 0) && (*(pre_dac+i) != tmp) ) { put_dac(i,tmp); } /* get ADC value */ *(adv+ptr+i) = get_adv(i+4) ; /* store DAC value */ *(pre_dac+i) = tmp ; } /* update */ gcnt++ ; if ( gcnt == GLAST ) { gcnt = -1 ; } /* delay */ delay_ms(10); } void clear_values(void) { volatile UWORD i ; for ( i = 0 ; i < ASIZE ; i++ ) { *(adv+i) = 0 ; *(dac+i) = 0 ; } gcnt = -1 ; } void show_value(UWORD x) { volatile UBYTE i ; volatile UBYTE msg[5] ; volatile UWORD tmp ; /* store */ tmp = x ; /* conversion */ *(msg+0) = tmp / 1000 + '0' ; tmp %= 1000 ; *(msg+1) = tmp / 100 + '0' ; tmp %= 100 ; *(msg+2) = tmp / 10 + '0' ; *(msg+3) = tmp % 10 + '0' ; *(msg+4) = ' ' ; /* judge */ if ( x < 1000 ) { *(msg+0) = ' ' ; } if ( x < 100 ) { *(msg+1) = ' ' ; } if ( x < 10 ) { *(msg+2) = ' ' ; } /* display */ for ( i = 0 ; i < 5 ; i++ ) { rs_putchar( *(msg+i) ) ; } /* newline */ crlf(); } UWORD get_adv(UBYTE chx) { volatile UWORD result ; /* default */ result = 0 ; /* judge */ if ( chx > 11 ) { return result ; } /* select channel */ ADCCP = chx ; /* config: fADC/2, acq. time = 16 clocks => ADC Speed = 1MSPS */ ADCCON = 0x7E3; /* wait conversion */ while (!ADCSTA) {} /* data */ result = (ADCDAT >> 16) & 0x3fff ; return result ; } void put_dac(UBYTE chx,UWORD x) { /* judge */ if ( chx > 3 ) return ; /* store */ switch ( chx ) { case 1 : DAC1DAT = (x << 16); break ; case 2 : DAC2DAT = (x << 16); break ; case 3 : DAC3DAT = (x << 16); break ; default : DAC0DAT = (x << 16); break ; } } (under construction)
目次

inserted by FC2 system