PIC16F1938で4ch DMX受信からオフセットをかけた4ch PWM出力

はじめに

今回の記事では、
PIC16F1938を使って、
4ch分のDMX信号を受け、
その信号に下限のオフセットをかけ、
その信号を4ch分のPWMとして出力します。

下限のオフセットの有用性

この記事の読者の皆様もご存じの通り、
照明器具は、白熱球や蛍光灯などから
LEDへの置き換えが盛んです。

ホテルや宴会場の明るさを変化させる、調光が必要な
照明の現場でも、
省電力であるLEDが主力となってきています。

このLEDの照明器具には、
白熱球などの単にフィラメントに電流を流すだけ
の単純なものとは異なり、
複雑な電子回路が組み込まれています。

それを調光させる際、
同じ電圧を与えていても、
個体差で点灯するタイミングがバラバラになったり、
器具によっては
つきはじめのまだ薄明るいところで
チラついたりします。

この点灯のタイミングをそろえたり、
チラつきを抑えたりするために、
「調光時の5%以下は消灯」
の様な下限値をカット(下限オフセット)することがとても有効です。

今回は、
DMX → PWMにおける下限オフセットに
チャレンジしました。

今回の回路図

以前からのDMX → PWMの記事とほぼ同じですが、
オフセット位置を記憶させるための
スイッチ(SW1)を
RB4にプルアップで配置しています。

実際のコードと動作確認

今回の実現させたい動きとしては、以下の通りです。

  • 4ch DMXの受信
  • 受信した信号で4ch PWM出力
  • 下限位置をフェーダで決めSW1を押して、EEPROMに記録
  • EEPROMに記録された値から100%までの調光をさせる
    (DMX値 1 → PWM-duty 20% の様な動き)

結果、思い通りに動いたコードは、
以下のものでした。

/*******PINの初期設定***********/ 
void Pin_Init(void) {
    ANSELA = 0x00;
    ANSELB = 0x00;

    TRISAbits.TRISA2 = 0;
    TRISAbits.TRISA1 = 0;
    TRISBbits.TRISB4 = 1;       // offsetスイッチ用

    TRISCbits.TRISC3 = 0;

    TRISCbits.TRISC6 = 1;
    TRISCbits.TRISC7 = 1;
    
    /******割り込み用******/
    PIE1bits.RCIE = 1;
    
    IOCBPbits.IOCBP4 = 0;
    IOCBNbits.IOCBN4 = 1;
    IOCBFbits.IOCBF4 = 0;
    INTCONbits.IOCIF = 0;
    INTCONbits.IOCIE = 1;
    INTCONbits.PEIE = 1;
    INTCONbits.GIE = 1;
}

void PWM_Init(void) {
    OSCCON = 0b01110010; //8MHz

    TRISAbits.TRISA5 = 1;
    TRISCbits.TRISC2 = 0; //CCP1 OUT
    TRISCbits.TRISC1 = 0; //CCP2 OUT
    TRISBbits.TRISB5 = 0; //CCP3 OUT
    TRISBbits.TRISB0 = 0; //CCP4 OUT

    ANSELA = 0x00;
    ANSELB = 0x00; //RBxbits digital

    APFCONbits.CCP2SEL = 0; //0:RC1, 1:RB3
    APFCONbits.CCP3SEL = 1; //0:RC6, 1:RB5

    T2CONbits.T2CKPS = 0b10; //prescaler 1:16
    T2CONbits.TMR2ON = 1; //Timer2 ON
    PR2 = 124; // 1kHz

    CCP1CON = 0b00001100; //CCP1 PWM mode
    CCP2CON = 0b00001100; //CCP2 PWM mode
    CCP3CON = 0b00001100; //CCP3 PWM mode
    CCP4CON = 0b00001100; //CCP4 PWM mode
}

/***************EEPROM書き込みシーケンス***********************/
void EEPROM_Write(unsigned char addr, unsigned char data) {
    EEADRL = addr;
    EEDATL = data;
    EECON1bits.CFGS = 0;
    EECON1bits.EEPGD = 0;
    EECON1bits.WREN = 1;

    INTCONbits.GIE = 0;
    EECON2 = 0x55;
    EECON2 = 0xAA;
    EECON1bits.WR = 1;

    while (EECON1bits.WR);

    EECON1bits.WREN = 0;
    INTCONbits.GIE = 1;
}

/***************EEPROM読み込みシーケンス***********************/
unsigned char EEPROM_Read(unsigned char addr) {
    EEADRL = addr;
    EECON1bits.CFGS = 0;
    EECON1bits.EEPGD = 0;
    EECON1bits.RD = 1;

    return EEDATL;
}

/****************オフセットの計算*****************************/
unsigned char Apply_offset(unsigned char data_vol, unsigned char offset) {
    unsigned int span;
    unsigned int value;

    if (data_vol <= 2) return 0;
    if (data_vol >= 255) return 255;

    span = 255 - offset;

    value = offset + (((unsigned int) (data_vol - 2) * span) >> 8);

    if (value > 255) value = 255;

    return (unsigned char) value;

}

/***************PWM出力**************************************/
void pwm_apply(unsigned char i, unsigned char data) {
    unsigned int duty;

    duty = ((unsigned int) data << 1) - (data >> 5);

    if (duty >= 500) {
        duty = 499;
    }

    switch (i) {
        case 0:
            CCPR1L = duty >> 2;
            CCP1CONbits.DC1B = duty & 0x03;
            break;

        case 1:
            CCPR2L = duty >> 2;
            CCP2CONbits.DC2B = duty & 0x03;
            break;

        case 2:
            CCPR3L = duty >> 2;
            CCP3CONbits.DC3B = duty & 0x03;
            break;

        case 3:
            CCPR4L = duty >> 2;
            CCP4CONbits.DC4B = duty & 0x03;
            break;
    }
}

/****************USART初期設定****************/
void USART_Init(void) {
    TXSTAbits.SYNC = 0;
    TXSTAbits.BRGH = 1;
    BAUDCONbits.BRG16 = 1;

    SPBRG = 7;
    SPBRGH = 0;

    RCSTAbits.SPEN = 1;
    RCSTAbits.CREN = 1;
}


/****************割り込み処理****************/
void __interrupt() isr(void) {

    /********オフセット用PIN割り込み処理******/
    if (INTCONbits.IOCIF) {
        if (IOCBFbits.IOCBF4) {
            save_request = 1;
            IOCBFbits.IOCBF4 = 0;
        }
        INTCONbits.IOCIF = 0;
    }


    /*********DMX受信用割り込み処理***********/
    if (!PIR1bits.RCIF) {
        LATCbits.LATC3 = 0;
        return;
    }
    if (RCSTAbits.OERR) {
        RCSTAbits.CREN = 0;
        RCSTAbits.CREN = 1;
    }

    if (RCSTAbits.FERR) {
        dummy = RCREG;
        dmx_count = 0;
        LATCbits.LATC3 = !LATCbits.LATC3;
        return;
    }

    unsigned char data = RCREG;

    if (dmx_count == 0) {
        start_code = data;
    } else if ((dmx_count <= 512) && (start_code == 0x00)) {

        for (unsigned char i = 0; i < 4; i++) {
            if (dmx_count == my_address[i]) {
                dimmer[i] = data;
            }
        }
    }
    dmx_count++;


}

/**************main関数*****************/
void main(void) {
    Pin_Init();
    PWM_Init();
    USART_Init();

    unsigned char stored_offset[4];
    unsigned char write_val[4];

    for (char i = 0; i < 4; i++) {
        stored_offset[i] = EEPROM_Read(i);

    }

    while (1) {

        /*******EEPROMへ現在の明るさの記憶処理*******/
        if (save_request) {
            save_request = 0;

            for (unsigned char i = 0; i < 4; i++) {
                EEPROM_Write(i, dimmer[i]);
                stored_offset[i] = dimmer[i];
            }
        }


        /*******PWM出力処理*******/
        for (unsigned char i = 0; i < 4; i++) {
            write_val[i] = Apply_offset(dimmer[i], stored_offset[i]);
            pwm_apply(i, write_val[i]);
        }
    }
}

これを書き込んだ時の動きは、

しかし、今回は(も?)、かなりの苦労を強いられました。

「致命的なミス」と考察 & 次回予告

上の正解のコードのinterrupt()の中の
OERRの処理のところを

if (RCSTAbits.OERR) {
        RCSTAbits.CREN = 0;
        RCSTAbits.CREN = 1;
        return;
    }

にして実行すると、

1ch目のフェーダ上げても動かない → 戻す
2ch目のフェーダ上げても動かない → 戻す
3ch目のフェーダ上げても動かない → 戻す
4ch目のフェーダ上げても動かない → 戻す
全部上げると1ch, 2ch のLEDが調光 → 戻す

??

・・・もう1度

1ch目のフェーダ上げても動かない → 100%のまま
2ch目のフェーダ上げても動かない → 100%のまま
3ch目のフェーダ少し上げる → 1ch, 2ch LED 点灯
1ch目のフェーダ下げる → 1ch LED調光
2ch目のフェーダ下げる → 2ch LED調光

という、
3ch目のフェーダが少しでも上がっていないと、
1, 2ch目のLEDは調光しないという
謎の症状に襲われました。
逆に言えば、
3ch目が少しでも上がっていれば、
1, 2ch目のLEDはとてもきれいに調光しました。
しかし、いくら検証しても、
3, 4ch目のLEDはピクリとも動かず…

結局
『return;』
が不要と気づくのに1時間以上を費やしました。

offsetの計算式が重いのだろうか…
CCP3, CCP4が壊れてしまったのだろうか…
offsetを “20” で固定したら?

などなど、
いろんな式を入れたり、
文章をコメントアウトして、
足して、
と苦労しました。

以前からのDMX → PWMの記事には、
そんな『return;』
なんか入っていなかったのに、
余計な事するもんじゃないですね。
とても勉強になりました。

次回は、
ガンマ補正などを入れることによる
調光カーブに変化を入れようと思います。

シリーズ一覧

STEP1 UART
STEP2 RS485
STEP3 DMX送信
STEP4 DMX理解
STEP5 DMX受信
▶ STEP6 DMX→PWM(今回)
STEP7 現場仕様化

コメント

タイトルとURLをコピーしました