最近のPCBは表面実装が主になっていてスルーホール部品の入手性が日増しに悪くなっていることを感じることがある。特に新製品についてはほぼ全てが表面実装と言ってもいいくらいだ。なのでいつかは表面実装に移行しないといけないと以前より思ってはいたもののリフローをどうするかを決めかねている。専用機は高すぎるし、ググッてみると、トースター、オーブン、ホットプレート、ヒートガンなど様々な方法があるようだ。どの方法を使うかに関わらず温度制御が一番重要なのとゼロクロスSSRの手持ちがあったのでまずはゼロクロスSSRでヒーター温度を制御するための電力制御ライブラリを考えてみた。
ゼロクロスSSRは0V付近でスイッチングするデバイスなので最短の制御間隔は電源周波数の半周期。50Hzの地域なら10ms間隔となる。しかし、そもそも半周期だけ使うのって問題ないのか少し疑問に思ったので調べてみたが一般家庭内のコンセントで使える電力程度なら特に問題はなさそうだ。
電力制御サイクルは「電源周波数の半周期x分解能」となるため、分解能を256、周波数50Hzとしたとき「10msx256=2560ms」なので電力制御1サイクルに2.56秒かかることになる。なのでより高い分解能は現実的ではなさそうだ。
制御方法については0~分解能の範囲で電力レベルを指定するものとしたとき、分解能が256の場合、電力制御1サイクルあたり
電力レベル0=半周期オンx0回(完全オフ)
電力レベル1=半周期オンx1回
電力レベル2=半周期オンx2回
,
電力レベル254=半周期オンx254回
電力レベル255=半周期オンx255回(完全オン)
のように電力レベル回数分だけ半周期オンする仕様で考えてみた。また、オン~オンまでの間隔は出来るだけ均等にする必要もある。
次は分解能16のときのオンオフ・パターンの例。参考まで。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | [ 0] 000000000000000 [ 1] 100000000000000 [ 2] 100000001000000 [ 3] 100001000010000 [ 4] 100010001000100 [ 5] 100100100100100 [ 6] 100101001010010 [ 7] 100101010101010 [ 8] 101010101010101 [ 9] 101011010110101 [10] 101101101101101 [11] 101101110111011 [12] 101111011110111 [13] 101111110111111 [14] 101111111111111 [15] 111111111111111 | 
実装については、オンオフ・パターンをテーブル化する方法、計算により求める方法などが考えられるが、テーブル化はコード/データ領域とも浪費してしまうため非力なMPUにも対応できるよう計算により求める方法を選択。整数演算では精度が足りずうまく計算できないが浮動小数点は計算負荷が高いので必要な精度を確保でき計算負荷も低い256倍した計算値を使うことで対処している。
このライブラリは電源周波数とは非同期で動作する。そのためタイミングズレにより半周期分の制御をミスるときがあることに注意しよう。ヒーター制御としては問題ないとは思うが、正確な制御が必要ならゼロクロス検出回路を追加しゼロクロスに同期したタイマー割り込みによりオンオフ制御すべし。かな。
同様な制御方法は、連続比例型ゼロクロス、最適サイクル制御などとメーカーにより異なる呼び方がされているようだ。
【実際の制御波形】

※↑左から2番目のLOWパルス部分に注目。タイミングズレにより制御に失敗している。
【ライブラリ概要】
| 1 2 | template<uint8_t FREQUENCY, int ZCPIN = -1, uint8_t RESOLUTION = 255> class ZCSSR | 
電源周波数(FREQUENCY)と解像度(RESOLUTION)を指定しクラス・インスタンスを生成する。電源周波数は、50 or 60。解像度は、0-255までを指定する。ZCPINにはゼロクロス検出ピンを指定する。ZCPIN未指定時(-1)は非同期モードで制御する。
| 1 | uint16_t interval(void) | 
電源周波数の半周期をマイクロ秒で取得する。
| 1 | void power(uint8_t value) | 
電力レベルを0-解像度(RESOLUTION)の範囲で指定する。
| 1 | bool next(void) | 
次の半周期のオンオフを取得する。
| 1 | bool handle(void) | 
半周期経過後に次の半周期のオンオフを取得する。
【サンプル・スケッチ】
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #include "zcssr.h" #define SSR_PIN 1 ZCSSR<50> zcssr; void setup() {   digitalWrite(SSR_PIN, LOW);   pinMode(SSR_PIN, OUTPUT);   zcssr.power(128); } void loop() {   digitalWrite(SSR_PIN, zcssr.handle()); } | 
【更新履歴】
2020-12-13
ゼロクロス点に同期し正確な制御が行えるようゼロクロス検出回路からの入力ピン(ZCPIN)を指定できるように改良してみた。
ゼロクロス検出回路は半波毎に単純にオン・オフを繰り返す次のようなシンプルな回路を想定している。ゼロクロス点から500-600usほどズレてしまっているが制御的には全く問題なし。
【ライブラリ】
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | /*   zcssr.h - Power Control Library for Zero Cross SSR   Copyright (c) 2020 Sasapea's Lab. All right reserved.   This library is free software; you can redistribute it and/or   modify it under the terms of the GNU Lesser General Public   License as published by the Free Software Foundation; either   version 2.1 of the License, or (at your option) any later version.   This library is distributed in the hope that it will be useful,   but WITHOUT ANY WARRANTY; without even the implied warranty of   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU   Lesser General Public License for more details.   You should have received a copy of the GNU Lesser General   Public License along with this library; if not, write to the   Free Software Foundation, Inc., 59 Temple Place, Suite 330,   Boston, MA  02111-1307  USA */ #ifndef __ZCSSR_H #define __ZCSSR_H #if defined(ARDUINO)   #include "Arduino.h" #elif defined(__linux__) || defined(__MINGW32__) || defined(__MINGW64__)   #include <stdint.h>   #include <stdbool.h>   #include <time.h> #else   #error "not supported environment." #endif template<uint8_t FREQUENCY, int ZCPIN = -1, uint8_t RESOLUTION = 255> class ZCSSR {   private:     uint16_t _start;     uint16_t _power;     uint8_t  _index;     uint8_t  _zcval;     bool     _state;     bool     _first;   protected: #if !defined(ARDUINO)     unsigned long micros()     { #if defined(__linux__) || defined(__MINGW32__) || defined(__MINGW64__)       struct timespec ts;       clock_gettime(CLOCK_MONOTONIC, &ts);       return (unsigned long)((unsigned long long)ts.tv_sec * 1000000) + (ts.tv_nsec / 1000); #endif     } #endif     uint8_t readZCPIN(void)     { #if defined(ARDUINO)       return digitalRead(ZCPIN); #else       return 0; #endif     }   public:     ZCSSR(void)     : _start(0)     , _power(0)     , _index(0)     , _zcval(0)     , _state(false)     , _first(true)     {     }     uint16_t interval(void)     {       return 1000000 / FREQUENCY / 2;     }     void power(uint8_t value)     {       _power = value ? (uint16_t)RESOLUTION * 256 / (value < RESOLUTION ? value : RESOLUTION) : 0;     }     bool next(void)     {       if (_power == 0)         return false;       if (_power == 256)         return true;       _index = (_index < RESOLUTION ? _index + 1 : 1);       return (((_index - 1) * 256) % _power) & ~0xFF ? false : true;     }     bool handle(void)     {       uint16_t n = (uint16_t)micros();       uint16_t t = interval();       if (ZCPIN >= 0)       {         uint8_t val = readZCPIN();         if (_zcval != val)         {           _zcval = val;           if (val != 0) // 1=rising edge, 0=falling edge             _first = true;         }       }       if (_first)       {         _first = false;         _start = n - (ZCPIN >= 0 ? t >> 1 : t);       }       if ((uint16_t)(n - _start) >= t)       {         _start += t;         _state = next();       }       return _state;     } }; #endif | 



