前回はソフトPWMとハードPWMの両方を
ボリュームで調光させるところまで行いました。
実際に様々な照明器具を調光していると、
0-100%の調光させると、
10%までの暗いところでは、チラつくことが多くあります。
今回はこのチラつき(フリッカー)の対策として、
下限オフセットを作りました。
つまり、設定したところが10%の場合、
ボリュームを回しても10%~100%範囲でしか調光せず、
0~10%の間は切り捨てられます。
この場合はボリュームが0でも10%の明かりが点いてしまうため、
ボリュームが0の時はOFF、少しでも回すと10%から始まり、
ボリュームを上げるとLEDは100%調光するという風にしました。
今回もなかなかうまく動いてくれなくて、
難儀しました。
そのあたりも紹介していきます。
今回の回路

前回と全く同じです。
ただし、今回ハードPWM、ソフトPWM共に
下限オフセットをかけるソフトにしていますが、
ソフトに関しては、全く確認してません。
ハードPWMの下限オフセットに全振りしています。
前回のソフトPWMは34段階でしか調光できていないので、
下限を設定してもあまり面白くないと思ったためです。
あと、ハードの方が実用性は高いと判断したためという
理由もあります。
下限オフセットの設定方法としては、
①ボリュームを回して明るさを作る
②SW1を押す
③下限値の決定
という具合にします。
今回のコード
前回と違う部分を紹介します。
変数の名前が少し変わっていたりはしますが
大きく違うのは以下の通りです。
PINの初期設定
c
void SOFT_Pin_Init(void){
TRISBbits.TRISB1 = 0;
LATBbits.LATB1 = 0;
TRISBbits.TRISB4 = 1; //RB4を入力モード(スイッチ入力)
ANSELBbits.ANSB4 = 0; //RB4をデジタルモード
OPTION_REGbits.TMR0CS = 0; //Timer0のクロック源を内部サイクルに設定
OPTION_REGbits.PSA = 0; //プリスケーラをTimer0に割り当てる
OPTION_REGbits.PS = 0b000; //プリスケーラを1:2に設定
TMR0 = 235; //Timer0カウントを235からスタート
INTCONbits.TMR0IF = 0; //Timer0オーバーフロー割り込みフラグ
INTCONbits.TMR0IE = 1; //Timer0オーバーフロー割り込み有効
IOCBPbits.IOCBP4 = 0; //RB4の立ち上がり割り込みの無効
IOCBNbits.IOCBN4 = 1; //RB4の立ち下がり割り込みの有効
IOCBFbits.IOCBF4 = 0; //RB4の状態変化割り込みフラグをクリア
INTCONbits.IOCIF = 0; //状態変化割り込みフラグをクリア
INTCONbits.IOCIE = 1; //状態変化割り込みをイネーブル
INTCONbits.GIE = 1; //グローバル割り込みをイネーブル
}
PIN割り込みの設定
c
volatile unsigned char save_request = 0;
void __interrupt() isr(void){
if(INTCONbits.IOCIF)
{
save_request = 1; //SW1が押されEEPROMへの書き込みのリクエストフラグ
IOCBFbits.IOCBF4 = 0; //RB4の状態変化フラグのクリア
INTCONbits.IOCIF = 0; //PIN割り込みの状態変化フラグのクリア
}
}
ここは前回とほぼ同じです。
後半のPORTBレジスタの状態変化の割り込みは、
初登場となりました。
このPICでは、PORTAやCでは使えず、
Bのみ使用できるとのことです。
たまたまスイッチ入力をBにしていたのが
功を奏した形になります。
EEPROMへの書き込み、読み込み
c
void EEPROM_Write(unsigned char addr, unsigned char data)
{
EEADRL = addr; //アドレスの設定
EEDATL = data; //データの設定
/*************以下EEPROMへの書き込みに必要なシーケンス***************/
EECON1bits.EEPGD = 0;
EECON1bits.CFGS = 0;
EECON1bits.WREN = 1;
INTCONbits.GIE = 0;
EECON2 = 0x55;
EECON2 = 0xAA;
EECON1bits.WR = 1;
INTCONbits.GIE = 1;
while(EECON1bits.WR);
EECON1bits.WREN = 0;
}
/*********************************************************************/
unsigned char EEPROM_Read(unsigned char addr)
{
EEADRL = addr;
/**************以下EEPROMからの読み込みに必要なシーケンス*************/
EECON1bits.EEPGD = 0;
EECON1bits.CFGS = 0;
EECON1bits.RD = 1;
/*********************************************************************/
return EEDATL;
}
こちらも初登場。
このシーケンスを書くだけでさまざまなデータを
保存できる訳ですね。
そこまでややこしくない!
下限オフセットの設定
c
unsigned int Apply_Offset(unsigned int adc_val, unsigned int offset)
{
//読み込んだADC値が2以下の時は0に設定
if(adc_val <= 2){
return 0;
}
//オフセット値が大きすぎる数値だった場合は、1023に設定
if(offset >= 1023){
return 1023;
}
//オフセットに設定するための仕掛け
return offset + ((unsigned long)(adc_val - 2) * (1023UL - offset)) / (1023UL - 2);
}
今回の記事のキモはここです!
ADCの値とEEPROMのオフセットの値を比較して、
ADCがEEPROMより大きくなると、
100%まで調光します。
0〜100%をそのまま使うのではなく、“使える範囲に再マッピングする”という考え方になります。
ここはかなり試行錯誤したので、次の章で解説します。
main関数
c
while(1)
{
if(save_request){
//EEPROMへの書き込みのリクエストフラグのクリア
save_request = 0;
for(int i = 0; i < 2; i++){
//VOL1とVOL2のADC値をEEPROMへ書き込む
EEPROM_Write(i, (unsigned char)(Read_ADC(ch[i]) >> 2);
}
}
//ハードオフセット値とソフトオフセット値のEEPROMからの読み込み
hard_offset = ((unsigned int)EEPROM_Read(0)) << 2;
soft_offset = ((unsigned int)EEPROM_Read(1)) << 2;
//現在のボリュームのADC値の読み込み
adc_hard_raw = Read_ADC(ch[0]);
adc_soft_raw = Read_ADC(ch[1]);
//上で読み込んだEEPROMのADC値と現在のADC値、2つの数値の比較
write_adc[0] = Apply_Offset(adc_hard_raw, hard_offset);
write_adc[1] = Apply_Offset(adc_soft_raw, soft_offset);
//比較結果の出力
HARD_PWM_APPLY(write_adc[0]);
SOFT_PWM_APPLY(write_adc[1]);
}
<h2>今日の学び</h2>
今回は、EEPROMへの書き込みに苦戦するのでは?と
少し期待(?)していました。
案の定間違えてました。
pull-upでのRB4の割り込みなのに、下のようにしていました。
c
void SOFT_Pin_Init(void){
…
IOCBPbits.IOCBP4 = 1; //RB4の立ち上がり割り込みの有効
IOCBNbits.IOCBN4 = 0; //RB4の立ち下がり
}
pull-downであればこれでよかったんですけど、
今回、SW1は、pull-upなので、
常時RB4pinには5Vが印加されていて、
スイッチが押されたときだけ、
0Vになるので、逆でした。
なんと言っても今回一番悩まされたのは、
上の下限オフセットの設定のところの式です。
Apply_Offset()関数の最後の行で、
何を"return"させるべきかかなり悩みました。
c
adc_val; //①
((unsigned long)(adc_val - offset) * 1023UL) / (1023UL - offset); //②
offset + ((unsigned long)(adc_val - 2) * (1023UL - offset)) / (1023UL - 2); //③
上のコード①~③の動きを図示したのが下の図です。
40%の位置で下限カットした場合の図となります。

①の式では、確かに下限カットはできています。
しかし、ボリュームの遊びの範囲が広すぎるためNG。
②の式では、0~40%が遊びの範囲になり、
ボリュームの40%位置から0-100%の調光になります。
0~40%の値で照明がチラつく場合は、意味がありません。
③の式では、0~40%がカットされています。
また、ボリュームの遊びの部分もなく、
少し回すと40%から調光が始まっています。
今回は③の動きを再現したかったので、
③を採用しました。
今回は、PWMの下限オフセットについて色々試してみました。
正直、ここまで悩むとは思っていなかったのですが、
実際にやってみると「0付近ってこんなに扱いにくいんだな」と実感しました。
ただ、その分かなり理解は深まった気がします。
まだ完全に思い通りという訳ではないですが、
一歩ずつ“使えるPWM”に近づいてきたかなと思います。
次は、この値をスイッチで操作できるようにしていきたいので、
短押し・長押しやチャタリング対策に挑戦していきます。


コメント