はじめに
今回の記事では、
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 現場仕様化


コメント