目次
前
次
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に保存します。
ファームウエアで実現するシーケンス処理は
以下としました。
- カウンタ値に対応し、D/Aコンバータから電圧出力
- A/DコンバータでD/A値とELの電圧値を取得し、保存
- カウンタ値を+1する
- カウンタ値が100になったら終了
- 10ms遅延
- 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ビットに
数値を格納している仕様なので、シフトしています。
ここで、コマンドを定義しておきます。
- '?' ヘルプ(1文字コマンドの内容表示)
- 'A' 保存しているA/D値をすべて表示
- 'D' D/Aコンバータへの設定値とその位置を指定
- 'Z' A/D値、D/A値、カウンタ値を初期化
- 'R' カウンタ値を0に設定
コマンドを定義したので、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()
- 'A' show_adv()
- 'D' set_dac()
- 'Z' clear_values()
各処理は単純なので、以下のように定義しました。
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)
目次
前
次