温度制御1 (PWM、オンオフ動作)


PWMとは、Pulse Width Modulation の略で、直訳するとパルス幅変調となります。これは、周期 におけるONとOFFの比率を変えて制御する方法で、モータ制御や調光 (光の明るさを可変する) など に使用されます。たとえば、LEDでは、電流をパルス状にして点灯時間を100%から50%にした場合、 見かけ上電流値を半分にしたのと同じように見えます。

このPWMで湯沸しポットの電源を操作し、お湯の温度を変えてみます。

@操作の方法は「回路図2 (CPU部分)」よりのPWM出力を「回路図1 (SSR部分)」のPWM入力につなぎ、 「回路図1 (SSR部分)」のACソケットよりポットの電源を供給すると、ポットはPWMの出力で操作されます。
PWMの1周期は210ミリ秒で、オンで始まり、指定された位置 (%) でオフします。

A温度監視はIC化温度センサーLM35DZ (「写真」のポットの側面、「回路図2」) で取った温度データを RS-232C (115200 8-N-1) で出力します。
測定温度範囲は 0.0〜110.0℃です。
温度測定、PWMのオンオフ時間の設定 (%) 、RS-232Cの出力は1秒毎です。

実験してみます。
目標値 (50℃) >= 測定値 ならPWM オン時間100%
目標値 (50℃) < 測定値 ならPWM オン時間0%
上記の条件はオンオフ動作です。

実験時の「RS-232C (115200 8-N-1) での転送データ画面」、 実験結果の「RS-232C (115200 8-N-1) での転送データより作成されたグラフ」、 実験に使用した「ソフト」を下に記載しておきます。


NC-525.JPG

写真
ナショナル電気ポット NC-525
1.2リットル 500W


SSR_kairo.gif

回路図1 (SSR部分)


ondo_ctrl_kairo.gif

回路図2 (CPU部分)


ondo_ctrl_Hyper.gif

RS-232C (115200 8-N-1) での転送データ画面
50.5, mv = -50 ..... 50.5℃ 電源スイッチをオフ
50.4, mv = -50 ..... 50.4℃ 電源スイッチをオフ
50.3, mv = -50 ..... 50.3℃ 電源スイッチをオフ
50.2, mv = -50 ..... 50.2℃ 電源スイッチをオフ
50.1, mv = -50 ..... 50.1℃ 電源スイッチをオフ
50.0, mv = 50 ..... 50.0℃ 電源スイッチをオン
49.9, mv = 50 ..... 49.9℃ 電源スイッチをオン
49.8, mv = 50 ..... 49.8℃ 電源スイッチをオン
49.7, mv = 50 ..... 49.7℃ 電源スイッチをオン
49.6, mv = 50 ..... 49.6℃ 電源スイッチをオン
49.5, mv = 50 ..... 49.5℃ 電源スイッチをオン


ON_OFF_50.gif

RS-232C (115200 8-N-1) での転送データより作成されたグラフ


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

プログラム
/*
 * temp_ctrl.c
 *
 * Created: 2017/03/14 19:53:56
 * Author : User
 */ 

/*
 *  オンオフ動作
 *
 *  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 _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 ON_OFF(float SV, float PV);
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;               /* 偏差による操作量 (%) */
    short pwm_on_time;      /* PWMオン時間 (0、100%) */
    long tmp;
    char buff[32], buff2[8];
    int len;

    /* 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 = ON_OFF(_SV_, ondo);

            /* 電力制御部 */
            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);
        }
    }
}

/* SV       目標値
 * PV       測定値
 * 戻り値   偏差による操作量 (-50, 50)
 */
short ON_OFF(float SV, float PV)
{
    float e;                /* 偏差 (%) */
    short mv;               /* 偏差による操作量 (%) */

    /* 偏差(%)を取得 */
    e = 100.0 * (SV - PV) / _SPAN_;

    if(e >= 0.0)
        mv = 50;
    else
        mv = -50;

    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++;
    }
}


真空管
戻る