温度制御3 (PID動作)


温度制御1 (PWM、オンオフ動作)のオンオフ動作の次はPID動作です。

位置型PID動作の微分方程式による表現を下記に示します。{ }内の各項は、おのおの比例 動作・積分動作・微分動作を表している。

mv = (100/PB)・{ e + (1/TI)・∫edt + TD・(de/dt) }

ただし、
mv:操作量[%]
e:偏差[%]
PB:比例帯[%]
TI:積分時間[s]
TD:微分時間[s]
を表す。

まず、比例 (P) 動作を実験してみると、教科書どうりオフセットが生じてしまい、 積分 (I) 動作でオフセットを埋めなくてはなりません。 温度は 47.5℃より上がらず、操作量 mv = 3 以下に下がりません。 PWM オン時間が 6%では47.5℃より温度を上げることができないと言う事です。 (グラフ2)

次に、比例・積分 (PI) 動作の実験です。オーバーシュートが大きいです。
これはスタートから目標値までに蓄えられた積分値が大きく、 目標値を超えてもなかなか減らないためです。 (グラフ3、4、5、6)

オーバーシュート対策
●TIを大きくするとオーバーシュートは小さくなります。(グラフ3、4、5、6)
温度制御4 (比例帯 (PB))の 比例帯(PB)を小さくして、積分開始からから目標値までを短くする方法もあります。
●AWR機能を装備して、スタート後の積分開始位置を目標値側に寄せて積分値をあまり大きくしない方法もあります。

使用したソフトを下に記載しておきます。


本装置の比例 (P) 動作のオフセットの実測値 (PB=100%)
目標値 [℃]到達温度 [℃]オフセット [℃]到達温度の操作量 mv到達温度のPWMオン時間 [%]
3028.41.624
4037.72.424
5047.52.524
6055.74.348
7064.06.0612


ON_OFF_50.gif

グラフ1 温度制御1のオンオフ動作


P_50_100.gif

グラフ2 比例 (P) 動作


PI_50_100_300.gif

グラフ3 比例・積分 (PI) 動作


PI_50_100_600.gif

グラフ4 比例・積分 (PI) 動作


PI_50_100_1000.gif

グラフ5 比例・積分 (PI) 動作


PI_50_100_1300.gif

グラフ6 比例・積分 (PI) 動作


How to Compile (コンパイルのしかた)

プログラム
/*
 * temp_ctrl3.c
 *
 * Created: 2017/03/14 20:08:39
 * Author : User
 */ 

/*
 *  PID動作
 *
 *  ATmega328P
 *
 *
 *  Fuses(システムクロック:20MHz)
 *
 *
 *              ディホルト  Int. RC Osc.    水晶        外部クロック
 *              DISABLED    2.7V            2.7V        2.7V
 *              8/8MHz      8MHz
 *  EXTENDED    0xFF        0xFD            0xFD        0xFD
 *  HIGH        0xD9        0xD9            0xD9        0xD9
 *  LOW         0x62        0xE2            0xF7        0xE0
 */

/* 実験は下記の定数を変更してコンパイルして実行する */
#define _SV_        50.0    /* 目標値 (℃)*/
#define _PB_        100     /* オンオフ動作 (0%) */
                            /* 比例帯 (1〜100%) */
#define _TI_        300     /* 積分時間 (s、32767:積分動作なし) */
#define _TD_        0       /* 微分時間 (s、0:微分動作なし) */
#define _SAMPLE_    1.0     /* 1s間のサンプル回数 */
#define _SPAN_      100     /* 測定スパン (測定範囲の最大値と最小値の差) */

#include <stdio.h>          /* sprintf() */
#include <string.h>         /* strlen() */
#include <avr/io.h>
#include <avr/interrupt.h>
///#define F_CPU 20.0E6     /* 20 MHz */
///#include <util/delay.h>

#define USEC 1024
#define round(x)     ((x)>=0?(long)((x)+0.5):(long)((x)-0.5))

typedef struct t_systim
{
    short utime;            /* 上位16bit */
    unsigned long ltime;    /* 下位32bit */
} SYSTIM;

///void delay_us(unsigned short t){while(t--) _delay_us(1);}
///void delay_ms(unsigned short t){while(t--) _delay_ms(1);}
short PID(float SV, float PV, short PB, short TI, short TD, float sample, char *reset_flag);
void initADC(void);
void initTIMER(void);
void get_tim(SYSTIM *p_tim);
void initUSART0(void);
void Send0(char *str);

short ad_data;
SYSTIM SYSCK;

int main(void)
{
    SYSTIM tim;
    static unsigned long ot[2];
    float ondo;             /* 温度 (0.0 〜 110.0℃) */
    short mv;               /* 偏差による操作量 (%) */
    char reset_flag;
    short pwm_on_time;      /* PWM オン時間 (0 〜 100%) */
    long tmp;
    char buff[32], buff2[8];
    int len;

    reset_flag = 0;

    /* ATmega328P */
    DDRB = 0b00000101;      /* XTAL2 XTAL1 SCK   MISO  MOSI  OC1B  ----- LED   */
    DDRC = 0b00000000;      /* xxxxx RESET ----- ----- ----- ----- ----- ----- */
    DDRD = 0b00000000;      /* ----- ----- ----- ----- ----- ----- TXD   RXD   */
    PORTB = 0b00111010;
    PORTC = 0b00111110;
    PORTD = 0b11111100;

    initADC();
    initTIMER();
    initUSART0();

    /*
     *  WGM13 WGM12 WGM11 WGM10 動作種別     TOP値
     *    1     1     1     1   高速PWM動作 OCR1A
     *
     *  COM1A1 COM1A0 COM1B0 COM1B0
     *  0      0      1      0      比較一致でLow、BOTTOMでHighをOC1Bピンへ出力(非反転動作)
     */
    TCCR1A = 0b00100011;    /* 一致時(OCR1B)Low, TOP->BOTTOM(OCR1A)でHighを */
                            /* OC1Bピンへ出力(非反転動作), 高速PWM動作 */
/// TCCR1B = 0b00011001;    /* (1 / 20MHz) * 65535 = 3.3mS */
/// TCCR1B = 0b00011010;    /* (8 / 20MHz) * 65535 = 26mS */
    TCCR1B = 0b00011011;    /* (64 / 20MHz) * 65535 = 210mS */
/// TCCR1B = 0b00011100;    /* (256 / 20MHz) * 65535 = 839mS */
/// TCCR1B = 0b00011101;    /* (1024 / 20MHz) * 65535 = 3355mS */
    OCR1A = 0xffff;         /* TOP->BOTTOMでHigh, 固定 */
    OCR1B = 0;              /* 一致時Low、可変(0はPWM出力on時間0%) */
    sei();

    Send0("START\r");

    get_tim(&tim);
    ot[0] = tim.ltime;
    ot[1] = tim.ltime;
    while(1){
        get_tim(&tim);
        if((unsigned long)(tim.ltime - ot[0]) >= 500000/USEC){  /* 500mS */
            ot[0] = tim.ltime;
            PORTB ^= 0b00000001;
        }

        get_tim(&tim);
        if((unsigned long)(tim.ltime - ot[1]) >= 1000000/USEC){ /* 1S */
            ot[1] = tim.ltime;

            /* 情報伝達部 */
            /* 温度を得る 例 12.3℃ */
            ondo = 1100.0 * ad_data / 10230;    /* 0.0〜110.0℃ */

            /* 情報伝達部 */
            /* 偏差による操作量を得る (-50 〜 50) */
            mv = PID(_SV_, ondo, _PB_, _TI_, _TD_, _SAMPLE_, &reset_flag);

            /* 電力制御部 */
            if(mv >= 0)                         /* mv = 0 〜 50% */
                pwm_on_time = mv * 2;           /* pwm_on_time = 0 〜 100% */
            else
                pwm_on_time = 0;                /* pwm_on_time = 0% */
            tmp = round(65535.0 * pwm_on_time / 100.0);
            if(tmp > 65535)
                tmp = 65535;
            OCR1B = (short)tmp;
            
            /* 表示例 49.9, mv = 50  */
            sprintf(buff2, "%ld", round(ondo * 10));
            len = strlen(buff2);
            buff2[len + 1] = buff2[len];
            buff2[len] = buff2[len - 1];
            buff2[len - 1] = '.';
            sprintf(buff, "%s, mv = %d\r", buff2, mv);
            Send0(buff);
        }
    }
}

/*
 *  位置型PID制御
 *
 *  SV      目標値
 *  PV      測定値
 *  PB      比例帯 (1~100%)、 オンオフ動作 (0%)
 *  TI      積分時間 (S、1~32766 32767:積分動作なし)
 *  TD      微分時間 (S、1~32767 0:微分動作なし)
 *  sample  1秒間のサンプル回数
 *  戻り値  偏差による操作量 (-50~-1%, 0%~50%)
 */
short PID(float SV, float PV, short PB, short TI, short TD, float sample, char *reset_flag)
{
    static float e[2];      /* 偏差 (%) */
    static float sum;       /* 偏差の合計 */
    short mv;               /* 偏差による操作量 (%) */

    /* 偏差(%)を取得 */
    /* 比例帯 PB = 100% の比例動作 */
    e[1] = 100.0 * (SV - PV) / _SPAN_;

    /* 偏差(比例帯 PB = 100% の比例動作)に比例帯(PB)の考えを導入 */
    /* 結果(操作量)を -50 〜 0 〜 50% に制限する */

    /* 比例帯 PB = 0% だったら、オンオフ動作 */
    if(PB == 0){
        if(e[1] >= 0.0)
            mv = 50;
        else
            mv = -50;

        return mv;
    }

    /* 初回 */
    /* 比例帯 PB = 1 〜 100% */
    /* PB = 60%:偏差(比例帯 PB = 100% の比例動作)の  1.7 倍の速度 */
    /* PB = 50%:偏差(比例帯 PB = 100% の比例動作)の  2.0 倍の速度 */
    /* PB = 40%:偏差(比例帯 PB = 100% の比例動作)の  2.5 倍の速度 */
    /* PB = 30%:偏差(比例帯 PB = 100% の比例動作)の  3.3 倍の速度 */
    /* PB = 20%:偏差(比例帯 PB = 100% の比例動作)の  5.0 倍の速度 */
    /* PB = 10%:偏差(比例帯 PB = 100% の比例動作)の 10.0 倍の速度 */
    if(*reset_flag == 0){
        mv = (short)round(100.0 * e[1] / PB);
        if(mv <= -50)
            mv = -50;
        if(mv >= 50)
            mv = 50;

        sum = 0.0;
        e[0] = e[1];
        *reset_flag = 1;

        return mv;
    }
    /* 二回目以降 */
    /* 比例動作(P)に積分動作(I)、微分動作(D)を加える */
    else{
        if(TI != 32767)
            sum += e[1];
        mv = (short)round(100.0 * (e[1] + (sum / sample) / TI + TD * sample * (e[1] - e[0])) / PB);
        if(mv < -50){
            mv = -50;
            sum = 0.0;
            e[0] = e[1];
            return mv;
        }
        else if(mv > 50){
            mv = 50;
            sum = 0.0;
            e[0] = e[1];
            return mv;
        }
        else{
            e[0] = e[1];
            return mv;
        }
    }
}

void initADC(void)
{
    ADCSRA = 0b10101111;    /* AD変換イネーブル, AD自動変換イネーブル,AD変換割り込みイネーブル */
                            /* 1/128クロック(最大変換時間=200uS @16MHz, 156uS @20MHz) */
    ADCSRB = 0b00000011;    /* タイマ/カウンタ0比較一致A */
    ADMUX = 0b11000000;     /* 基準電圧 = 1.1V(@atmega328p), 右詰, アナログ入力 = ADC0 */
}

ISR(ADC_vect)
{
    static long result;
    static short counter;

    result += ADC;
    counter++;
    if(counter >= 100){
        counter = 0;
        ad_data = (short)(result / 100);
        result = 0;
    }
}

void initTIMER(void)
{
    TCCR0A = 0b00000010;    /* CTC動作 */
    TCCR0B = 0b00000100;    /* clk / 256 */
    OCR0A = 80 - 1;         /* (256 / 20MHz) * 80 = 1024uS */
    TIMSK0 = 0b00000010;    /* 比較A割り込み要求有効 */
}

ISR(TIMER0_COMPA_vect)
{
    if(++SYSCK.ltime == 0)
        ++SYSCK.utime;
}

void get_tim(SYSTIM *p_tim)
{
    cli();
    *p_tim = SYSCK;
    sei();
}

void initUSART0(void)
{
    UBRR0 = 21;             /* U2X0 = 1, 115200ボー/20MHzクロック */
    UCSR0A = 0b00000010;    /* U2X0 = 1 */
    UCSR0B = 0b00001000;    /* 送信イネーブル */
    UCSR0C = 0b00000110;    /* 8ビット, パリティなし, ストップ・ビット1 */
}

/*
 *  1データ送信
 */
void Tx0(char data)
{
    while((UCSR0A & _BV(5)) == 0)
        ;
    UDR0 = (unsigned char)data;
}

/*
 *  文字列を送信
 */
void Send0(char *str)
{
    while(*str != '\0'){
        Tx0(*str);
        str++;
    }
}


真空管
戻る