PIC16F1938で4ch DMX受信から4ch PWM出力のチャレンジ1

PWM

はじめに

前回の記事でDMX受信自体には成功しました。
今回は、実際に受信したDMXの値をPWMにして
アウトプットすることにチャレンジしました。

なお4ch分のDMXを読み取り、
それをPWMに変換して出力させることにチャレンジしてみました。

今回の回路

DMX512信号をUSARTで受信し、
CCP1~CCP4を使いPWMを出力させる回路となります。

実際のコード

PINの初期設定は、
過去のPWM出力の記事(PIC16F1938 3ch ADC → 3ch PWMの作り方~AN8でハマった話とPWM100%にならない話~)などを参考にしています。
なお、今回の周波数は1kHzとしています。

またDMX受信に関しての初期設定などは、
前回の記事(PIC16F1938でDMX512を受信してLEDを制御してみる【DMX受信編】)
を参考に仕上げました。

①DMX値をそのままCCPR1Lに代入してみる

/*********DMXデータをPWMに反映させる関数********/

void pwm_apply(unsigned char i, unsigned char data) {

    switch (i) {
        case 0:
            CCPR1L = data;
            break;

        case 1:
            CCPR2L = data;
            break;

        case 2:
            CCPR3L = data;
            break;

        case 3:
            CCPR4L = data;
            break;
    }
}

/********DMXを受信しデータを保存する関数*******/
void dmx_receive(void) {
    if (!PIR1bits.RCIF) {
        return;
    }

    if (RCSTAbits.FERR) {
        dummy = RCREG;
        dmx_count = 0;
        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();
    USART_Init();
    PWM_Init();

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

        dmx_receive();

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

このコードを書き込み動かしてみました。

フェーダを徐々に上げて行き、
それに沿ってLEDもきれいに調光しました。
しかし、フェーダが50%ぐらいでLEDはMAXになってしまいました。

CCPRxLは、8bitであり
DMXデータも8bitであるので、
そのまま代入で問題ないと思っていました。

しかし実際に試してみると、
フェーダが50%程度の位置でLEDがMAXになってしまいました。
データシートを確認すると、PWMのデューティ比は

(CCPRxL : DCxB) ÷ {4(PR2+1)}

で決まります。

今回の設定では、

PR2 = 124

であるため、PWMの最大デューティ値は

4 × (124 + 1) = 500

となります。
一方、CCPRxLに127を書き込むと内部的には

CCPRxL = 127, DCxB = 0の時、

10bit duty値は 127 << 2 = 508

となり、PWMの最大値500を超えてしまいます。
そのためDMX値が約50%の時点でPWM出力が飽和し、
LEDがMAXになっていたのでした。

②DMX値をスケーリングしてみる

①方法では、フェーダが半分でMAXだったので、
次は、計算してスケーリングさせてみました。

void pwm_apply(unsigned char i, unsigned char data) {
    
    unsigned int duty;

    duty = ((unsigned int) data * 500) / 255;

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

上の “duty=” の式で、0~255を0~500にスケーリングできます!
これを書き込み動かしてみました。

波形はこんな感じです。
かなり広範囲のエリアを撮影しています。

図は high になっているエリアだけが、
ちゃんとしたDutyを出しています。

つまり、LEDはチラチラして見ていられませんでした。

なぜ、これがうまく動かないのか、
全く理解できません。

いろいろと数値を変えてみたり、試してみましたが、
全く症状は治りません。

③高周波数で試す

ここでChatGPTに相談すると、

”PIC16で32bit計算と割り算が重いからだ”

と言われ、言われるがまま、

T2CONbits.T2CKPS = 0b01; // prescaler 1:4
PR2 = 255;

とし、

void pwm_apply(unsigned char i, unsigned char data) {

    switch (i) {
        case 0:
            CCPR1L = data;
            CCP1CONbits.DC1B = 0;
            break;

//省略
    }
}

①と同様にdataをそのまま入れました。

今度はとてもきれいな調光

PRx = 255であるので、

調光はとてもスムーズでした。

PWMのデューティ値は、
CCPRxL の 8bit と DCxB の 2bit を合わせた
10bit値として扱われます。

つまり、

PWM Duty値 = (CCPRxL << 2) + DCxB

となります。

そのため、
フェーダをフルに使っている!
余計な計算がない!
となります。

やはり、計算処理やループ処理に時間がかかり、
DMX受信を取りこぼしている可能性が高いことが分かりました。

でも、この場合、

PWM = 8000000 / (4 × 4 × (255 + 1)) = 約1950Hz

となってしまいます。

照明用のPWM制御では、
1kHz前後のPWM信号が使われることも多いので、
今回もまずは1kHzで動かす方針にしました。

そのため、

T2CONbits.T2CKPS = 0b10; // prescaler 1:16
PR2 = 124;

に戻し、1kHzで勝負がしたいと思いました。

④計算を軽くする

上の反省を生かして次は、
ビットシフト指示で演算をさせてみます。

void pwm_apply(unsigned char i, unsigned char data) {
    unsigned int duty;

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

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

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

//省略


    }
}

これでばっちり動きました。
周波数も1kHzです!

この計算の意味は、

DMX : 0 ~ 255

PWM: 0 ~ 500 (PR2=124 ∵4 × (PR2 +1) = 4 × 125 = 500)

この最大値を合わせに行きたい。
もし、DMX値を 2倍すると

0 ~ 510

少しだけど、大き過ぎる。

500 ÷ 255 = 1.961

であるので、

2倍して0.04マイナスしたい

2倍にするのは、

×2 : 左に 1bit シフト

これは簡単。問題は-0.04。

ここで、右のbitシフトを見てみます

1bit >> : 1/2 = 0.5
2bit >> : 1/4 = 0.25
3bit >> : 1/8 = 0.125
4bit >> : 1/16 = 0.0625
5bit >> : 1/32 = 0.03125

0.04に最も近いのは5bit 右シフトの1/32であることが分かりました。

理想値 : 500 / 255 = 1.96078…
近似値 : 2 – 0.03125 = 1.96875

めっちゃ近くなります!

フェーダMAX時は、

255 × 1.96875 = 502.03…

PWMは500までなので、
式の下で500以上は500になるようにしています。

今回使った近似式は以下です。

duty = data × 2 – data ÷ 32

C言語では、これをビットシフトで次のように書きました。

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

これで動かした様子は次の動画です。

1ch目のフェーダを上げたときはスムーズな調光をしています。
しかし2ch目のフェーダはちらつきが酷く、
3ch, 4ch目のフェーダに関しては、
2番目のLEDが反応してしまっていて、
完全におかしな挙動をしてしまっています。

DMXの1ch目はしっかりと拾えているが、
2ch目は怪しく、
3ch、4ch目は完全にアウトといった感じだと思います。

PICの仕事をもう少し減らしてあげる

少しPICの仕事を減らすために、
for文を消してみました。

/********DMXを受信しデータを保存する関数*******/
void dmx_receive(void) {
    if (!PIR1bits.RCIF) {
        return;
    }

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

    unsigned char data = RCREG;

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

        if (dmx_count == 1)  dimmer[0] = data;
        if (dmx_count == 2)  dimmer[1] = data;
        if (dmx_count == 3)  dimmer[2] = data;
        if (dmx_count == 4)  dimmer[3] = data;
            
        
    }
    dmx_count++;
}


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

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

        dmx_receive();

        pwm_apply(0, dimmer[0]);
        pwm_apply(1, dimmer[1]);
        pwm_apply(2, dimmer[2]);
        pwm_apply(3, dimmer[3]);
        }     
    }
}

これで動かしてみましたが、
結果は上の動画とほぼ変わらず。。。

次に1chずつ受信させてみることにしました。

1ch目は動いていることは確認済みなので、
2ch目、3ch目、4ch目をそれぞれ順に試してみました。

結果は、2ch目、3ch目はうまく調光しましたが、
4ch目だけは無反応。。。

そこで、CCP4が死んでいる可能性も考えられるので、

  pwm_apply(3, dimmer[0])

このコードを書き込んでみると、
ちゃんとLEDは反応しました。

つまりPWMをつくるCCP4はちゃんと働いているが、
4ch目のデータを取りこぼしていることが確認できました。

考察と次回予告

今回は動作確認を優先し、
ポーリングでDMX受信してみました。

32bit計算や割り算をさせることが
PICにとってかなり重い動作であることが確認できました。

DMXは250kbpsで連続してデータが届くため、
ポーリング受信ではメインループ内の処理時間によって
受信を取りこぼす可能性があります。

そのため、次回はUSART受信割り込みを使い、
より安定したDMX受信を目指します。

シリーズ一覧

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

コメント

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