2020年9月14日月曜日

電子負荷装置を自作する

 さて、今回は(も?)ブログのタイトルに反してプログラミングは全く関係ない記事です。すみません。

1年ほど前、自作で電源装置を作りました。電源装置を作ったら当然負荷テストをしたくなりますよね。ということで、当時はセメント抵抗を何種類か買ってきてテストしていたのですが、抵抗値を変えようと思ったら繋ぎ変えが必要ですので、徐々に負荷を変化させていくということができません。思えば、この時から電子負荷装置が欲しかったのですが、ひとまずは電源装置も安定して動作しておりましたので特にその熱は一旦引いていました。

ですが、最近、日本橋の共立電子に行ったところ、 特設コーナーでスイッチング電源が安売りしていました。5V6Aが550円、12V9Aが1200円など、比較的高出力なものもかなり手ごろな値段で売っております(8月末~9月頭にかけての情報です。特売ですので、いつ行っても買えるとは限りません)。

普段は弱電ばっかり扱っている私ですので、たった数十Wでもパワみを感じて衝動買いしてしまいました。ですが、これだけ高出力な電源の性能を十分に発揮できるだけの負荷が手元にありません。

ここから、1年ほど前の「電子負荷装置」の熱が自分の中で再燃し、せっかくなので作ることを決意しました。

回路設計

電子負荷装置もいろいろなものがあります。商用レベルだと、数百Wとか数kWとかを消費でき、その上、その電力を電力系統に回生するようなものまで売っています。そういうものを自作するのは流石にレベルが高すぎますので、ここは簡単に数十W程度を消費できる単純な負荷を用意することとしました。

モードとしては、定電流モードと定抵抗モードを想定します。定電流モードは入力電圧にかかわらず電流を一定に保つモード、定抵抗モードは入力電圧に比例した電流を流すモードです。名前の通りですね。

この回路は、かなり簡単に作ることができます。オペアンプの基礎が分かっていれば、下記回路には何も説明はいらないでしょう。

たいてい回路図には現れませんが、オペアンプのLM358の電源は12V側から取っています。LM358は2回路入りなので、使わない側は周辺回路と容量結合とかして発振しないよう、適切に接地等してあげましょう。

SW1が定抵抗モード/定電流モードを切り替えるスイッチ、SW2がレンジのスイッチですね。

定電流モードで最大約10Aを流すことを想定しているので、オペアンプの+入力が最大約1Vになるよう抵抗値を調整しました。電源側ににさらに条件を付ければ外部電源無しでも作れますが、それは嫌だったのに加え、電源が必要な電圧/電流計を使用することにしたので外部電源を使うようにしました。

熱設計

電圧、電流、許容損失

さて、上記の通り回路は超シンプルで簡単です。電子負荷装置を作る上で最も難しく、コストもかさむのは熱処理です。

例えばサンケン電子製の2SK3711のデータシートを見ると、定格電圧が60V、定格電流が70Aです。

2SK3711のデータシートより引用)

これだけ見ると、12V10Aくらい余裕のよっちゃんに見えます。

ただ、電子負荷装置は、この12V10AすべてをこのFETで受け止めることになります。たいていのスイッチング用途は何か別の負荷がドレインにつながっていて、それをFETでON/OFFするだけなので、FET自体にかかる電圧は低く、FETそのもので消費する電力もそこまで大きくはなりません。しかし、今回の用途ではすべての電力をFETで受け止めることになります(大事なことなので2度言いました)。

ですが、許容損失は130Wと、12V10A=120Wの損失を発生させても大丈夫そうです。ただし、チャネル温度を150℃以下に抑えられるならね。

そう、一番厳しいのはここ、チャネル温度なのです。定格電流にも定格電圧にも達していなくても、チャネル温度が150℃を超えたらFETが焼け切れてしまうのです。

熱抵抗

熱抵抗というパラメーターがあります。単位はK/Wです。熱伝導率の逆数ですね。「何Wの熱を流すのに何K温度の差が付くか」という物理量です。電気抵抗も「何Aの電流を流すのに何Vの電位差が付くか」という物理量ですね。そのアナロジーで考えると簡単です。

例によって2SK3711のデータシートを見ると、過渡熱抵抗θj-cが定常状態で0.95℃/Wくらいです。過渡熱抵抗と言うとわかりにくいですが、下付き文字の「j-c」は「ジャンクション-ケース間」の意味と思われます。すなわち、チャネルで発生した熱をケース(=FETのパッケージ)に伝えるのにどれくらい温度差が付くか、ということですね。

0.95℃/Wということは、たとえば120Wの損失を作るとチャネルとケースに120×0.95=114℃の温度差が付きます。 超強い放熱器を付けて、ケースを30℃に抑えたとしても、チャネル温度は143℃となり絶対最大定格のぎりぎりとなります。

一方で、例えば東芝製TK70J20Dならば、チャネル-ケース間熱抵抗が最大0.305℃/Wです。先ほどと同じく120Wを消費したとしても、チャネルとケースは36.6℃しか差が付かないということになりますね。これならば少し弱い放熱器を付けて、ケースが110℃くらいまで上昇してしまっても絶対最大定格を超えることはありません。さすが、値段が高いだけありますね。放熱特性が高性能です。

このように、大きな電力を消費させるFETの場合、電流や電圧といった特性よりも何よりも、このようにチャネル温度が最も設計上のボトルネックとなります。

ケース選定

ヒートシンク付きケース

さて、放熱がどれほど重要か、という話をしましたが、そこで重要になってくるのが放熱器です。例えば共立電子ではジャンク扱いでクソデカ放熱器が売っていたりしますが、ちょっとこれはネタすぎてカッコよくないですよね。

今回はタカチ製のヒートシンクアルミケースのEXHシリーズを採用しました。パネルの大きさ、熱抵抗などからEXH14-5-19BBを選択しました。熱抵抗2.27℃/Wです。ちなみに黒アルマイト品のほうが値段が少々高くつきますが、放熱器は黒のほうが黒体放射で冷却性能がやや高いらしいです。ほんとかよ。

このケースだけで値段が5000円程度してしまいます。回路だけだと1000円も出せば十分に組み立てられてしまうのに、ほんと、「電子負荷装置の自作は放熱機構の自作である 」という感じですね。

なお、このケース、放熱器側にナット用の溝があり基板を取り付けることができます。表面にネジ穴を出さないスマート設計ですね。

カタログPDFより引用)

アルミ板を用意し、そこにFET、シャント抵抗、基板を固定しケースに取り付けました。良い感じです。

アルミ板と放熱器の間の空間

さて、この取り付け方、カタログPDFに書いてある通りなのですが、いかんせん、放熱器への熱が流れるルートが少なすぎます。アルミ板とケースが接しているのはナットのレール部分のみ、放熱器本体とアルミ板の間は空気です。空気はかなり熱抵抗が大きな素材です。これでは、効率よく熱を逃がせません。

そこで、間を熱伝導率の高いもので埋めることを考えました。調べていたところ、ワイドワーク製の放熱ゴムというものを見つけました。図面上は隙間が6mm程度ですので、12mm厚のものを購入しました。潰して半分くらいの厚さにできるかなと思いましたが、硬く無理だったので、ハサミでカットして使いました。

熱伝導率2.4W/m・Kなので、面積100mm×100mm(=0.01㎡)、厚さ6mm(=0.006m)とすると2.4×0.01/0.006=4W/Kになります。逆数を取れば熱抵抗で、0.25K/Wとなります。ケースの熱抵抗は2.27K/Wなので、足して2.5K/W程度となります。これで、そこそこの放熱環境を整えられたと言えるでしょう。

耐久テスト

自然冷却

さて、調子に乗って電流を流すと次々とFETが焼けていくんですねえ。さらに、チャネル温度が限界突破して焼けた場合、ドレイン―ソース間がショートモードで故障するらしく、あまり良くない状況になります。

最終的に、TK70J20Dを使って連続30W運転程度が落としどころとなりました。

チャネル―外気間の熱抵抗を3K/Wとすると、30Wで約90℃の上昇となります。気温25℃なら115℃ですね。理論値はこうですが、いろいろと挟んでいるものもあるはずですので、まあこの辺が現実的な解かと思います。

強制空冷

もっと冷やしたくなったら強制空冷ですね。ケースの上にサーキュレーターを置いて、ガンガンヒートシンクを冷やしてみました。

80Wまでなんとかもちました。チャネル温度と気温の温度差を125℃とすると熱抵抗1.56K/Wです。もうすでにこの時点でケースの自然冷却時の熱抵抗を下回っています。さすが空冷ですね。一説によると、熱抵抗が1桁以上変わるんだとか。

ちなみに、90Wにしたら焼けました(◞‸◟)

こうやってみると、TDPが100Wを超えるようなCPUも普通にありますが、その性能をフルに発揮するヒートシンクって実はなかなかに大変なものなんでしょうね。

まとめ

というわけで、電子負荷装置を自作しましたが、話の中心は電気的なところではなく熱的なところとなりました。まあそれも予定調和ですね。

前半のほうに少し触れた電力系統への回生機能を持った電子負荷装置は、単にエコだけでなく、このような強烈な放熱機構の必要性という面からも非常に重要なもの、ということですね。数百Wも放熱するような電子負荷装置を作るとしたら、一体どれだけ大きな放熱機構が必要になることやら。

 

おまけ

デカール貼り

今回、レンジとモードのスイッチを持っていて表記を作らないとわかりにくいことから、デカールを用意して貼り付けました。

小さいころから鉄道模型はやっていたのでインスタントレタリングこと「インレタ」は何度かやったことがあったのですが、デカールはたぶん初めてでした。

難しいですね。小さい文字なんか、器用に貼ろうとしてもどうしても傾いてブサイクになってしまいます。

殉職したFETの方々

今回、実際どの程度FETが持つのかということを知りたくて、FET焼く覚悟で高負荷をかけて実験しました。これらのFETの死は無駄ではなかった…と信じたいです。どうか、来世ではスイッチング電源にでもなって活躍されますように。

FET交換の工夫

最初はFETをはんだ付けしていたのですが、だんだんとそれ自体が面倒になってきます。だってすぐに焼けるんだもん。

ということで、端子台を用意し、はんだ付け不要でFETを交換できるようにしました。

2020年6月28日日曜日

System.Interactiveで快適LINQ生活

久しぶりにプログラム書いてたらライブラリの名前をすっかり忘れて探すのにだいぶ苦労したのでメモしておきます。

C#3.0で導入されたLINQも、少し痒いところに手が届かないことがあります。
例えばDistinctメソッドは基本的に要素本体でしか重複判定をすることができません。LINQならば、例えば以下のようなことしたいですよね。
var uniqueName = data.Distinct(p => p.Name);
何かのデータで同じ名前の重複を削除したいときはこう書きたいです。
実際には他のプロパティが違うデータでも同じ名前だったら1つを残して削除されちゃいますし、どれが削除されるのかは直感的にはわかりません。そういった不都合があってこのようなメソッドが作られなかったのかもしれませんが、でも実際問題使いたいことが結構あります。

他にも、例えばSelectManyメソッドは「配列の配列」をそのまま縦につなげて1つの配列にするのに便利ですが、その逆、すなわち配列をいくつかごとに分割して「配列の配列」にするメソッドはありません。

そんなもろもろの痒いところに手を届かせたライブラリは実はMicrosoft公式で存在します。


Nugetから入れれば、一発でDistinctメソッドにキー選択機能があるオーバーロードが追加されます。めでたしめでたし。ちなみに後者の「SelectManyの逆」はBufferメソッドです。

このライブラリはReactive Extensionsの一部です。まあ、LINQを配列のように「位置的に並んでいるもの」だけじゃなく「時系列的に並んでいるもの」にまで拡張させたのがReactive Extensionsですから、その流れでできた新しい便利な拡張メソッドをIEnumerable<T>用の拡張メソッドに逆輸入した形なのでしょう(たぶん)。

備忘録でブログに残しておいただけなので、今日はこれくらいで。

2020年6月24日水曜日

WPFでChromium埋め込みブラウザを使用する [CefSharp]

自分のソフトの中でブラウザを埋め込みたいことってありますよね。
WPFではWebBrowserコントロールが用意されていますが、とにかく使いにくい上に、IE7ベースで動作するという非常に残念なものです。IE11/Edgeに切り替えることもできなくはないようですが…レジストリをいじることになるのでアプリケーションとして使うというよりか、システムとして設定を変更することになってしまいます。
どうせならGoogle Chromeなどが代表的なChromiumブラウザ系統の組み込みブラウザを使いたいですよね。最近、IE系統に対応せずにChrome対応を謳うサイトも増えてきたことですし。まあ私はFirefox派なんですが。

そこで今回紹介するのがCefSharpというライブラリです。
Chromium Embedded Framework (CEF)というChromiumブラウザの表示部分のみを抜き出したのフレームワークがあり、これをC#からアクセスできるようになったものがCefSharpです。

導入

導入は至って簡単です。Nugetから入れることができます。
今回はWPFで使用するので、CefSharp.WPFを入れましょう。そうするとついでにCefSharp.Commonも入ります。
注意点ですが、Targetをx86かx64に指定してやる必要があります。Any CPUだと動きません。

使用

これも簡単です。CefSharp.Wpf.ChromiumWebBrowserをXAML上で定義してあげるだけです。
<Window x:Class="WebBrowserTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WebBrowserTest"
        xmlns:cef="clr-namespace:CefSharp.Wpf;assembly=CefSharp.Wpf"
        mc:Ignorable="d"
        Title="MainWindow" Height="1080" Width="1920">
    <Grid>
        <cef:ChromiumWebBrowser Address="https://www.google.co.jp" />        
    </Grid>
</Window>
めでたくブラウザが表示されます。

Navigate

任意のURLのWebページに遷移したいことがありますよね。
標準のWebBrowserコントロールは厄介で、Sourceプロパティが依存関係プロパティではありません。そのため、バインディングをすることができず、ビヘイビアを使うなどして操作しないといけません。超面倒でした。
CefSharpはそこのところはちゃんと考えてあり、Addressプロパティにバインディングすることができます。
<Window x:Class="WebBrowserTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WebBrowserTest"
        xmlns:cef="clr-namespace:CefSharp.Wpf;assembly=CefSharp.Wpf"
        mc:Ignorable="d"
        Title="MainWindow" Height="1080" Width="1920">
    <DockPanel>
        <TextBox DockPanel.Dock="Top" x:Name="Address" Text="https://www.google.co.jp"/>
        <cef:ChromiumWebBrowser Address="{Binding ElementName=Address, Path=Text, Mode=TwoWay}" />        
    </DockPanel>
</Window>
TextBoxのTextをTwoWayでバインディングすることで、いわゆるブラウザのアドレスバーのような形で使用することができます。

スクリプトを実行

せっかく自分のプログラム上でブラウザを動かしているのですから、何かしらブラウザ上でアクションを起こしたいですよね。
そんな人の為にExecuteScriptAsyncという拡張メソッドが用意されています。実行するにはChromiumWebBrowserのインスタンスが必要ですので、MVVMを維持しながら作るならばビヘイビア等を活用して作る必要がありますが、今回は簡単のために、ボタンを押したらコードビハインドでスクリプトを実行するプログラムを作ってみます。
<Window x:Class="WebBrowserTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WebBrowserTest"
        xmlns:cef="clr-namespace:CefSharp.Wpf;assembly=CefSharp.Wpf"
        mc:Ignorable="d"
        Title="MainWindow" Height="1080" Width="1920">
    <DockPanel>
        <TextBox DockPanel.Dock="Top" x:Name="Address" Text="http://orteil.dashnet.org/cookieclicker/"/>
        <Button DockPanel.Dock="Bottom" Content="Earn a million cookies" Click="Button_Click" />
        <cef:ChromiumWebBrowser x:Name="browser" Address="{Binding ElementName=Address, Path=Text, Mode=TwoWay}" />        
    </DockPanel>
</Window>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        browser.ExecuteScriptAsync("Game.Earn(1000000)");
    }
}
こんなコードを用意すると、ボタンを押すだけでクッキーが100万個生産されます。
True NeverclickなんていうAchievementを解除してしまいました。

スクリプトを実行してその値をC#側で受け取りたいこともあると思います。
そういうときは、EvaluateScriptAsync拡張メソッドを使います。
private async void Button_Click(object sender, RoutedEventArgs e)
{
    var response = await browser.EvaluateScriptAsync("document.getElementById('cookies').innerText");

    if(response.Success && response.Result != null)
        MessageBox.Show(response.Result.ToString());
}
非同期メソッドであることに注意してください。
上のメソッドを実行すると、このように'cookies'というIDを持った要素のinnerTextプロパティの値を返してきます。
注意点として、返却値は配列と文字列はOKみたいですが、それ以外のオブジェクトはダメなようです。微妙に使いにくいですが…。


てなところです。
私自身も使い始めたばかりなので全然触れていない部分もありますが、まあ、少なくとも標準のWebBrowserコントロールよりかは使いやすそうです。
こういうの使って何とかC#のステージに引きずり下ろすより、とっととJavascriptをしっかり勉強してブラウザのアドオンとか作ったほうがいいのかなあ。

2020年5月4日月曜日

PICマイコンでBCD変換 その2

以前PICマイコンでBCD変換という記事を書きました。
久しぶりにまたBCD変換を実装しなければならない場面が出てきたので、そういえば昔そんな記事書いていたよなと思って自分のブログを読み返したのですが、そこで参照していた外部サイトがリンク切れを起こしていましたので、訪れた人にとっては理解しにくい記事になってしまっていました。

ですので、そのBCD変換 のアルゴリズムをこの記事にて詳しく説明します。
もしもBCD変換の意義とかそのあたりについてを知りたい人、もしくは具体的な実装を見たい人がいれば上のリンクの記事を見てください。

準備

このようなメモリ空間を用意します。
下の位に2進数、上の位にBCD変換された数が入るようにして、一続きの値になるようにします。以前の記事では、24bit整数型と共用体でビットフィールドを作成し、このようなメモリを実現しました。

さて、実際に計算をこの箱を使いながら試していきます。
今回は例として、「123」をBCD変換してみましょう。123は2進数で表現すると0111 1011です。





n進数

n進数のおさらいです。
普段我々が日常的に使っているのは10進数で、1桁に0~9の10個の数を使います。そしてその桁の数が10を超えたら次の位に1が加算されます。つまり、各桁の値が表しているのは$10^n$ずつの単位になります。
ですので、 $10^n$の位の数字を$d_n$と置くと、各桁の値を使って以下のように表せます。\[10^0d_0+10^1d_1+^2d_2+...\]なんら疑問のない表現ですね。多くの人にとってはくどすぎる解説にすら感じるでしょう。
この考え方は2進数でも同じです。2進数で各桁の値を$b_n$と置くと、2進数の数値は以下のように表せます。\[2^0b_0+2^1b_1+2^2b_2+...\]つまり、上の2進数表現をした123は、すなわち以下のような表現だということです。\[2^7\times0+2^6\times1+2^5\times1+2^4\times1+2^3\times1+2^2\times0+2^1\times1+2^0\times1\\=64+32+16+8+2+1\]さて、話を戻して、この2進数の各桁の数から値を得る式を少し変形してみましょう。\[2^0b_0+2^1b_1+2^2b_2+2^3b_3+2^4b_4+2^5b_5+2^6b_6+2^7b_7\\=b_0+2(b_1+2(b_2+2(b_3+2(b_4+2(b_5+2(b_6+2b_7))))))\]そんなに難しい変形ではないですね。ここで重要なのは、$b_7$を2倍してから$b_6$を足して、それを丸ごと2倍してから$b_5$を足して…という計算で2進数の各桁を数値に変換できるということです。このBCD変換はこの考え方が大きなヒントになります。

左シフト

さて、先ほどの話に戻って、準備で用意したメモリを使って処理をしていきます。
これに左シフト演算を8回行います。ただ単にシフトするだけでは「2進数」エリアにある数字が「BCD10の位」「BCD1の位」の8ビット分に移動するだけですが、ここでは、この「移動するだけ」のシフト演算に意味を見出していきます。
左シフトには「値を2倍にする」という意味合いがあります。10進数で表された数の各桁を一つ左の位に移動させたら値は10倍になりますよね。同じように、2進数でも2倍になります。

BCD1の位を中心に物事を考えると、1回目の左シフトでBCD1の位の最小桁に現れた「0」(下の画像で赤色のもの)は、残り7回のシフトで最終的に$2^7$がかかります。2回目のシフトで現れた「1」(下の画像で緑色のもの)は、残り6回のシフトで最終的に$2^6$がかかります。

このように、左シフトを1回ずつしながら値を動かしていくことで「2進数の各桁の値を倍々にしていって、各桁にふさわしい係数($2^n$)がかかるようにする」という操作をしているとみなすことができます。これは、先ほどの各桁の数から値を導く式とやっていることは同じです。

さて、この操作、メモリ空間上でやっているので2進数でしか物事が見えていませんが、別に入れ物の数の表し方がどうであれ、「2倍して2進数の次の桁を足す」という操作さえ8回繰り返してできれば、示す数値としては元の2進数と同じ意味合いになるはずです。
ですので、左側のBCD3桁分で、BCDに沿ったルールで「2倍して2進数の次の桁を足す」という処理を行っていけば、「BCDで表される元の2進数と同じ数値」が得られるわけです。

BCDとしての「2倍する」

さて、いよいよBCD変換の肝に入っていきます。今度は4bitずつ、「BCDの位」 単位で物事を見ていきます。
前章の最後にも少し触れましたが、BCDの位単位で物事を見ようとも、「2倍してから2進数の次の桁を足す」という計算さえできれば、元の2進数と同じ意味合いの数値が得られます。

ここでBCDで2倍するとはどういうことか、考えてみます。
それは、まぎれもなく左シフトで実現できます。ただし、2進数とは違い、左シフトだけがすべてではありません。BCDの桁は4bitありますので、物理的には0000~1111(2進数)、すなわち0~15(10進数)の16個の数字が入ります。ですがBCDの桁が表すのは10進数の1桁、あくまでも入る数字は0~9です。ですので、左シフトをした後に10以上の数字が入っていたら繰り上げ処理をしてあげれば、問題なく「2倍」の数が得られるはずです。

というわけで実際にやってみましょう。
先ほどの流れで順調に左シフトをしていくと、5回目の左シフトで異変が起こります。
BCD1の位の値が「1111(2進数)」すなわち「15(10進数)」になってしまうのです。ここで桁あふれ処理として、1つ上の位に1を足して、自身は1の位の値だけを保持するという処理をしてあげます。そのシフト繰り上げ処理をしたのが以の図の下2行になります。

「左シフトを8回する過程でこんな変な処理を入れちゃっておかしくならないの?」と思う方もいるかもしれません。
しかし思い出してください。やっていることは「2進数の桁を1桁ずつ加算しては2倍するの処理を繰り返す」ということだけです。その過程で、物事の見方が2進数からBCDに切り替えているのならば、ちゃんとBCDのルールに沿って2倍する処理をしてあげればいいだけです。BCDのルールでは、「各桁の値は10以上になってはいけない」がありますので、超えてしまったらちゃんと次の桁に送ってあげればいい、それだけです。「2進数の桁を1桁ずつ加算しては2倍するの処理を繰り返す」以上のことは何もしていません。ですので、BCDの表現ルールに従ったうえでの結果にはなりますが、数値の意味としては何ら変わりません。

この調子で続けていきます。毎回シフトするたびに各桁が桁あふれしていないか確認し、桁あふれしていれば繰り上げ処理をしてあげます。
 合計8回シフトし終わった時点で、2進数の部分の数値はすべて出払い、BCDの部分には左から順に「1」「2」「3」が格納されました。このようにして、除算を一切使わずにBCD変換ができてしまうのです。

落とし穴

さて、ここまで説明してきた方法には落とし穴というか、考慮されていないパターンがありました。気づいた方はいますでしょうか。これで気づけていたらすごいと思います。

例えば「152(10進数)」=「10011000(2進数)」のBCD変換を考えてみましょう。
4回シフトした時点でBCD1の位が1001(2進数)=9(10進数)となるので、桁あふれはしていません。ですので、このまま進めて左シフトをします。
やっていることは「値を2倍して次の桁を足す」の処理なので、BCD1の位は9を2倍して18、その1はBCD10の位へ繰り上がり、残りの8に2進数のエリアからやってきた次の桁「1」が足し合わされ、9になることが期待されます。 しかし実際に入っていたのは3でした。
このまま続けていくと、最終的にBCDエリアには0001 0000 0100の値が入り、結果は「104」となってしまいます。157とは違う、間違った計算結果になってしまいました。

何が間違っていたかというと、9をそのまま左シフトしてしまったことです。
9を2倍すると桁あふれすることはわかり切っていますが、その後の結果は 「3」となっており、桁あふれを検知できません。すなわち、「BCDの桁あふれ」の章で赤字で書いた「左シフトをした後に10以上の数字が入っていたら」というのは実は間違い(条件不十分)だったわけです。桁あふれを起こしても10以上の数字が入らないことがあるんですね。

先ほども書いたように、9を2倍するということは、1つ上の桁に1が入り、自身の桁には8が入ることを意味します。しかし、単純に左シフトをしてしまうと、1つ上の位には1が入りますが、自身の桁には2が入ります。
それもそのはず、左シフトでは単なる16進数としての扱いなので、9の2倍である12(16進数)をそのまま10進数とみなしてBCD1の位に2が入ってしまっていたのです。

すなわち、従来の「左シフト(=2倍)して10以上ならば繰り上げ処理をする」という方法では、値が8, 9のときに10以上になったことを検知できなくなってしまいます。かなり厄介です。

対策

これを防ぐにはどうしたら良いでしょう。値が8, 9のとき、次回繰り上げ処理をするようなプログラムを書く?いやいや、前回の演算が影響してくるループとかややこしくてやってられません。バグの元です。

こんな時はまるっきり見方を変えてみましょう。

「桁の値が5以上ならば次に左シフトすると桁あふれを起こすから、あらかじめ繰り上げ処理をしておこう」

これならどうでしょうか。
繰り上げ処理とは、10~15の6つの数字をスキップすることですから、10以上だった場合は6を足してあげれば良いのです。6を足せば自身の位の最上位bitが自然とあふれて1つ上の桁の最下位bitに1が足されます。
これを左シフトする前に行うのならば、6の代わりに3を足しておけば、シフト後に6を足したのと同じ計算結果になるはずです。

というわけで、方針がここまでで以下のように推移してきました。

×「桁の値が10以上ならば桁あふれを起こしているから、1つ上の桁に1を足して自身から10を引こう」←桁あふれを起こしても10以上にならないことがあるからダメ
△「桁の値が10以上の場合の処理に加えて、左シフト前の桁の値が8,9のときは左シフト後に桁の値が10以上にならない桁あふれを起こすから、その場合の処理を記述しよう」←正しいが実装上の条件が複雑でよくない
○「桁の値が5以上ならば次に左シフトすると桁あふれを起こすから、シフト完了後に6を足そう」←だいぶ条件がスッキリしたが、ループの前回の条件が影響するのはまだ改善の余地あり
◎「桁の値が 5以上ならば次に左シフトすると桁あふれを起こすから、あらかじめ3を足しておこう」


これに従って先ほどのBCD変換をしてみます。
 先ほどは「1001だから桁あふれしていない」、シフト後は「0011だから桁あふれしていない」という判定となり、本当は桁あふれしていることに気づかずに通り過ぎてしまっていましたが、今回は「1001は5以上だから次のシフトで桁あふれする」という判断になり3を足すことができましたので、次のシフトでBCD1の位に正しく「1001(2進数)」=「9(10進数)」が入りました。

続きを見ていきましょう。
左シフトの前には必ず各桁に対して「5以上か」という判定が入り、5以上ならば3を足すことで繰り上げ処理としています。
これで、最終的に8回シフトが終わった時点で0001 0101 0010で152が得られました。めでたしめでたし。

まとめ

かなり丁寧に書きましたが、まとめるとアルゴリズムの考え方としては以下の通りです。
  1. 2進数の各桁(=bit)を2倍しては次の桁を足して…という処理をBCD上で行うことで2進数→BCD変換を行う。
  2. 2倍する際にはBCDだと桁あふれが起こるため、分岐処理が必要。
  3. 桁あふれしてから繰り上げ処理をしようとすると条件が複雑になるので、2倍する前に桁の値が5以上だったら3を足して繰り上げ処理とする。
アルゴリズムとしてはここまでで、これをいかに効率よく処理させるかということに関しては以前の記事にしっかり載っていますので、必要な方は参照してください。

2020年5月1日金曜日

MPLAB X 5.35 + XC8 2.20でオートコンプリートが正常に動作しない問題を対処する

最近、久しぶりにPICマイコンでのプログラミングをしました。
久しぶりにするということで、ひとまず最新の環境に揃えた上でプログラミングを始めたところ、オートコンプリート機能が全く動かないというトラブルに見舞われました。
こんな感じです。ありとあらゆるレジスタの名前が未解決になってしまうため、IDEというよりかただのテキストエディタになってしまいます。この状態でプログラミングするだけでもストレスフルです。
なお、IDEのオートコンプリート機能がトラブっているだけで、コンパイル自体は正常に通ります。

環境

  • MPLAB X 5.35
  • XC8 2.20
  • PIC16F1579(確認はしていないがおそらく他の8bitファミリでも同じと思われる)

原因

結論から言うと、原因はMPLAB XがPIC16F1579.hを見失っていたからでした。そのため、各SFRの名前やアドレスが分からなくなっていたのです。

黄色い波線が引いてあるxc.hにCtrlキーを押しながらマウスオーバーするとこんなポップアップが出てきました。
xc.h内ではどうもプロジェクトの設定を元にデバイスのヘッダファイルを読み込む条件分岐があるようで、その中で「pic16f1579.h」見つけられずにレジスタ名をすべて解決できなくなっていたようです。
すでに上の画像にも書いていますが、試しに#include <pic16f1579.h>を追加してみたところ、以下のようなエラーが出てきました。

この中で赤文字の"C:\Program Files\Microchip\xc8\v2.20\pic\include\plib"を参照してみたところ、このようなフォルダはありませんでした。おそらく過去はこのようなパスにそれぞれのPICのヘッダファイルを入れていたのでしょうが、最近パスが変更された一方でMPLABのほうではその変更が漏れておりヘッダファイルを見失ってしまったのでしょう。
現在のフォルダは"C:\Program Files\Microchip\xc8\v2.20\pic\include\proc"でした。

対策

system include pathsが間違っているので、これを修正してやれば直るはずです。

…と思ったのですが、system include pathsの編集のしかたが分かりませんでした。設定にもそのような項目は見当たらず、かと言ってMPLAB Xの膨大な構成ファイルからそれが記録されている設定ファイルを探す気にもなれず、結局お手上げでした(もし知っている方がいたら教えてください)。

system include pathsの編集がダメだったので、付け焼き刃ではありますが、プロジェクト単位で"C:\Program Files\Microchip\xc8\v2.20\pic\include\proc"のパスを通してあげればpic16f1579.hが見えるようになりました。
プロジェクトのプロパティ画面のツリーの「XC8 Compiler」を選択し、「Include directories」に先ほどのパスを追加してあげます。
見事にSFRの下の波線もなくなり、オートコンプリートが正常に動作するようになりました。めでたしめでたし。


にしてもあれですね、MPLAB Xって必ず「こんなん絶対誰か気づくやろ」みたいなお粗末なバグが入ってますよね。訓練されたユーザーじゃないと使いこなせないソフトです…。

2020年4月1日水曜日

久しぶりにプログラミングをした話

別にエイプリルフールネタじゃないよ。

最近仕事が忙しかったうえ、あまりこれを作りたいというインスピレーションも無く、全然プログラミングに触れていませんでした。
前回の記事は全然プログラミングに関係ないめちゃめちゃアナログな回路のお話でした。その前はリフルシャッフルをテーマにした算数のお話で、辛うじてこじつけ程度にプログラミングに触れた程度でした。

ですが、世の中はコロナパニック、二言目には「不要不急の外出は控え」ということで、先週末久しぶりに少しだけ文字列をいじくるC#プログラムを書きました。そのところ、未来にでもワープしてきたんじゃないかというくらいめちゃめちゃ環境が変わっていてびっくりしたので、ひとまずかるーく記事にしておこうと思います。

Range構文

まあ「未来にでもワープしてきた」と大げさに書きましたが、気づいたらC#8.0がリリースされていたというだけです。
C#8.0ではRange構文というものが導入されました。これまでは配列や文字列の一部分を切り取る際に、よくこんな書き方をしていました。

text.Substring(now, m.Index - now);

now~m.Indexの範囲の文字列を抽出するだけのプログラムですね。
余談ですが、mはMatchクラスの変数です。すなわち、now以降正規表現でマッチした部位より手前の文字列を抽出するだけのプログラムです。

おやおや、VisualStudioさんに煽られてしまったではありませんか。Substringを使うのはもう時代遅れと。

実質的にこのRange構文を使ったコードはSubstringに展開されるだけのようなので、バイナリとしては変わりませんが、冗長な引き算やメソッド名が省略できるうえ、構文として意味が明確に定義される(メソッドだとメソッドを実装した人の流儀で決まってしまう)のでとても良い構文だと思います。

今回は使いませんでしたが、これ以外にも「末尾から何文字」とかいう表現も容易にできます。かゆいところに手が届く、非常に強力な構文ですね。はい。

null許容参照型

さて、Range構文を採用したとなると、セットで出てくるのがSpan<T>です。
C#が言語としての安全性を求めるあまり、C言語ではゴリゴリ書けていたポインタをいじくりまわすようなプログラムは書けないようになっています(Unsafeは除く)。ですが、その代償として場合によってはあまり効率の良いプログラムは書けないもどかしい状況がありました。

そこで、安全にポインタらしい機能を実装しようということで導入されたのがSpan<T>です。これは「範囲」を指定するRange構文と非常に相性が良く、Range構文で示した範囲をSpan<T>で参照として返す、というのが非常に基本的な使い方となります。

というわけで、text.AsSpan()してRange構文を適用しようとしたら…

ん…?「string?」 …?

型名の後ろに着く「?」は、null許容型を意味しています。
そもそもnullが無い値型をNullable<T>構造体でラッピングし、nullという意味を追加しようというものです。
ですが、stringは参照型ですので、わざわざNull許容型にしなくてもnullを取ることができます。なので、後ろに「?」を付けると昔ならばコンパイルエラーになっていました。

はい、これもnull許容参照型という C#8.0から導入された機能、「null許容参照型」です。

「null」は参照型がどこも参照していない、という意味を表す定数です。参照型には、参照である以上「どこも参照していない」という状況が付いて回るのですが、それを使うかどうかはまた別問題です。実際問題、null状態の参照型を受け付けたくない状況は非常にたくさんあります。
その場合、今までならばメソッドを実装するときに最初にパラメーターのnull性を確認して、場合によってArgumentNullExceptionを投げるようなプログラムを書くことになりますが、めんどくさいですよね。そして使う側もその例外が発生する可能性を考慮しなければならない。本来の大切なロジックを書く以前のところで雑務が発生してしまうし、コードとしてもノイズが入るので見通しが悪くなってしまうわけです。

そこで、C#8.0では、ただ単に参照型の型名を書くと「null非許容」としてみなすようになりました(破壊的仕様変更になるので、オプションでONしないと有効にはならないようです)。その場合、コンパイラが非nullであることをしっかり確認し、どこかでnullになるような可能性があればエラーを出してくれるようになりました。
逆に、nullを使いたいという時は、敢えて型名に「?」を付けなければならなくなりました。これがnull許容参照型です。

気づいたのがプログラムを書いている途中だったので、今回は先延ばしです。オプションは有効にしないことにします。
ですが、厳格に運用できれば、逆にもっと効率よくすっきりとしたプログラムを書けるようになりますね。


ほかにもC#8.0には多くの機能が実装されています。
例えばswitch式とかインターフェースのデフォルト実装機能とか。

そもそも半年前にC#8.0が正式リリースされているにもかかわらず、そのことを知ったのが今って、本当に久しぶりにC#に触ったんだなあ。また余裕ができたら何か作りたいもの考えてプログラム書かないとな。

2019年7月30日火曜日

フルアナログの自励式フライバック・コンバータを自作する

前回の記事から遅くなってしまいましたが、今度は自励式フライバック・コンバータを自作してみました。そりゃあね、原理がわかったら自分で試したくなるのがサガってもんでしょう。

注意:スイッチング電源はそのサージにより数百Vの電圧が発生する部位がありますので感電したら重大な事故になることがあります。また、状況により上手く動作せず素子が焼損したり、最悪爆発して重大な事故になる可能性もあります。危険性を認識し安全対策を行ったうえ、各自の責任で製作するようお願いします。万が一の事態が発生しても当方では一切責任を負いかねます

基本設計

まずは基本設計をします。どれくらいのスペックの電源が欲しいかを設定します。

入力電圧 AC100V
出力電圧 2~15V
出力電流 最大3A
スイッチング周波数 75kHz

こんな感じでしょうか。日本でコンセントに接続して使う感じです。出力は可変とし、3Aくらい流せるように作ります。フライバックコンバーターだと数十W程度の出力が限度と言われておりますので、まあこんなもんでしょう。
スイッチング周波数は少なくとも可聴域(~20kHz)は外すようにします。そうしないとコイル鳴きが聞こえて不快です。たいていフライバックコンバータは70~80kHz程度を狙うそうです。

トランス製作

さて、まずはトランスを自作することとします。というよりか、スイッチング電源はトランスが最も鍵となるパーツの上、使用環境によってパラメーターがごっそり変わってきます。市販品をそのまま使うことはまずできません。
となると、トランスのボビンとコアを買ってきて自分で巻かなければなりません。ですが、割とニッチな趣味なのかあまり小売りしている電子部品店は無さそうでした。比較的入手性の良さそうなのはaitendoかと思います。aitendoでは2種類のサイズを売っておりますので、大きいほうのEE25コアを採用すること前提で設計をしていきます(なんか販売終了予定になっているようですので必要な方はお急ぎを)

手順としては、基本設計パラメーターをもとにトランスを設計していきます。それをもとに実際にトランスを巻き、出来上がったトランスのパラメーターを測定します。どれくらい狙ったところにパラメーターを合わせ込めるかはわかりませんが、出来上がったトランスのパラメーターを使って回路全体を設計していくこととします。

トランス設計

aitendoにはトランスコアのデータシートはありませんが、一般的なEE25のパラメーターを使用します。

Le 48.7mm
Ae 40mm2
Bs 420mT@100℃

$L_e$は実効磁路長、$A_e$は実効断面積です。$B_s$は飽和磁束密度ですので、常にこの磁束密度は超えないように注意しなければなりません。

トランスはフライバック型ですので1次側をONしてエネルギーを溜め、その後2次側から放出するという流れになります。そのほか、スイッチング制御用に補助巻線を用意します。
電流はこの図のように流します。$D_{on}$はトランジスタをONしているデューティー比、$D_{off}$はトランジスタをOFFしているタイミング、すなわち2次側にエネルギーを放出しているときのデューティー比です。$D_{delay}$はスナバコンデンサとトランスが共振してスナバコンデンサの電圧が最小になるまで待つ時間です。これを挿入することによりスナバコンデンサの電荷を回生できるので効率が上がります。

1次側

1次電圧は最小で102Vとしました。深い意味はありません。理想は$100{\rm V}×\sqrt 2 =141{\rm V}$となりますが、実際はダイオードブリッジのドロップだったり、平滑リプルによる実効値の低下だったりいろいろあります。ですので、$85{\rm V}\times0.85\times \sqrt 2$にしてみました。

すると、出力は最大で15V3Aに設定しているので出力は45W、効率を仮に82%と置いてみると入力側の電力は55W必要となります。ですので、入力の電流は0.54Aとなります。
その入力電流は平均値です。フライバックコンバータは、実際はトランジスタがONしている間に電流が時間に比例して増えていき、OFFしている間はゼロとなります。ですので、電流の最大値$I_{peak}$は以下の式で表せます。\[I_{peak}=I_{ave} \times \frac{1}{2D_{on}}\]分母に2が付いているのはノコギリ波だからですね。
$D_{on}=0.5$としていますので、$I_{peak}=2.15{\rm A}$となります。

さて、電圧と電流が定まると1次側の自己インダクタンスを求めることができます。
スイッチング周波数を75kHz、デューティー比を0.5からON時間は6.67usとなります。この時間で電流が0から2.15Aまで増えるときにトランス両端に102Vが生まれるわけですから、インダクタの基本式\[V=L \frac{{\rm d}I}{\rm dt}\]を用いてLは317.1uHとなります。

一方で、巻き線のターン数はファラデーの法則スタートで計算できます。\[V=-N\frac{{\rm d}\Phi}{\rm dt}\]コアの最大磁束密度420mTより、安全率0.72を取って最大の磁束密度を302mTとします。そうすると、ON時間(=6.67us)間にこれだけの磁束の変化があればいいわけですから、代入して1次側巻き数を$N_p=56$と求めることができます。

最後にギャップ長を求めておきます。トランスにギャップを付けるとギャップが磁気抵抗のほぼすべてと近似できるため、以下の式が成り立ちます。\[\mathcal R_m=\frac{l_g}{\mu_0 A_e}\]これと磁気抵抗の公式$\mathcal R_m=N^2/L$からギャップを求めると0.497mmとなります。これは実効磁路長におけるトータルのギャップ長ということに注意してください。EE25コアを浮かせると3か所にギャップができますので、1つ当たり1/3でだいた0.165mmとなります。ギャップ長はだいたい0.5mm以下程度を目安とするといいでしょう。あまり大きくなりすぎると漏れ磁束が大きくなり、結合度が下がるだけでなく、サージも大きくなってしまいます。

2次側

トランス2次側は平均値として3Aを出力しなければなりません。トランスから2次側にエネルギーを放出している時間のデューティー比を0.4とし、トランスの出力波形がノコギリ波であることを踏まえると$I_{peak}=3\times2/0.4=15{\rm A}$となります。
2次側自己インダクタンス$L_s$は1次側と同様に求め、5.55uHとなります。ただし、電源装置として出力15Vを確保するために、トランスの出力としては整流ダイオードのドロップ分を考慮して15.6Vとして計算しています。

トランスは1次側と2次側の巻線比の2乗がインダクタンス比ですから、それをもとに2次側の巻き数を求めると7ターンとなります。

補助巻線

補助巻線は1次巻線と巻線の向きが同じですから、1次側との電圧比が巻線比となります。10V出力とすると6ターンとなります。自己インダクタンスは4.08uHとなります。

文章と数式だけで長々と書いてしまいましたが、まとめると以下のようなパラメーターとなります。

1次 自己インダクタンス 317.1uH
巻数 56
2次 自己インダクタンス 5.547uH
巻数 7
補助 自己インダクタンス 4.075uH
巻数 6

トランス巻き

つづいてトランスを巻いていきます。
巻き終わりました(((
結合度を上げるために、1次巻線を半分巻いたら2次巻線を巻き、その上に1次巻線を巻くといった巻き方をしています。
最後は自己インダクタンスを測定しながら上記の値になるようにギャップを調整して終了です。

パラメーター測定

最後に、後ほどシミュレーションできるようにしっかりパラメーターを確認しておきます。各端子の自己インダクタンスのほか、他の端子を短絡させたうえでのインダクタンスも測定しておきます。それにより、結合部分の自己インダクタンスがゼロとなるので、漏れ磁束による自己インダクタンスを求めることができます。そこから巻線間の結合度も導くことができますので、のちのシミュレーションに非常に役に立ちます。
今回私が巻いたものの測定結果は以下の通りになりました。

自己インダクタンス 1次 329.9uH
2次 5.231uH
補助 4.022uH
結合度 1次—2次 0.9865
2次—補助 0.9208
補助—1次 0.9383

ちゃんとした商品の電源トランスは結合度0.9999とか平気で超えるそうですね。ただ、今回はギャップがあったり自分の工作精度だったりでここまで値が下がっています。1次—2次は交互に巻いたので結合度が高いですが、その上に補助巻線を巻いたので、補助巻線と他の巻線の結合度は1次―2次間に比べて悪くなってしまっております。

絶縁抵抗計などを持っていれば1次側と2次側の絶縁性を確認しておくのも良いでしょう。私はあの2万円くらいする計測器は買えませんでした…。

回路設計(シミュレーション)

回路は上記のトランスをもとに作っていきます。

上の画像はシミュレーション用に作ったLTSpiceの回路です。良い部品がなかった場合は類似品としているので、完璧にシミュレーションできているわけではない点に注意してください。
メインのスイッチング用FETは東芝製のTK20A60Uとしました。耐圧600Vと比較的高く、ON抵抗も0.165Ωとかなり抑えられています。

C6とC3は部品選定がなかなか重要です。整流平滑用コンデンサはスイッチング周波数で充放電が繰り返されますので、かなり激しく電流が行き来し、コンデンサの内部抵抗で発熱をします。そのため、それに耐えられるかどうかの指標として「許容リプル電流」というものが定められており、これが使用する回路に適合するように選んでいく必要があります。
とはいえ、リプル電流の実効値を計算していくのはなかなか難しいです。それだけで記事が一つできてしまいそうですので、皆さん各自勉強してください(私ももうちょっとちゃんと勉強したいです)。
今回はC6はニチコンLSシリーズの250V220uF(リプル電流1260mA)、C3はニチコンVRシリーズの25V2200uF(リプル電流1550mA)の2並列としました。回路の中でも大型部品となりますので、箱にちゃんと納められるのかという意味での制約も出てきて選定になかなか苦労します。

他は敢えて説明するほどのものでもないかもしれませんが、少しだけ説明しておきます。C1はM1のゲートを駆動するためのコンデンサです。前回の記事ではバイポーラでしたが、今回はMOSFETですので電荷の供給元が必要です。Q2はフォトカプラだけだとゲインが足りなかったので付けました。なかなかこの辺の回路構成は試行錯誤でした。

あとは、自分の求めたい出力電圧や出力電流を設定しながら、ひたすらトライアンドエラーで回路パラメーターを調整していきます。上手くしないと変に共振してトランス1次側のサージ電圧が高くなってしまったり、2次側の出力電流が大きくなりすぎてトランスが磁気飽和するレベルになってしまったりしますので、実際に作ったときに事故を起こさないようにあらかじめシミュレーションを追い込んでおきましょう。


シミュレーション結果はこんな感じになりました。15V3A出力で安定して出力できています。トランジスタのドレイン電圧も定格はオーバーせず、電流も飽和しないレベルです。

ある程度回路シミュレーションが追い込めたら、次は試作を行いましょう。今回の記事では省略しますが、実際シミュレーション通りに動くとは限りませんからね。
私はそこでトランジスタをいくつか焼いています。

基板設計

さて、試作も終わりある程度作れる目星がついてきたら次は基板を起こします。
回路は基本的にシミュレーションと同じですが、部品単位で起こしたので少し見た目が変わっているのと、ソフトが違うので部品番号が変わってしまっています。また、シャントレギュレーターはシミュレーションではTL431を使いましたが、試作したところおそらく位相余裕が無く負荷条件によっては発散してしまうようで、ここでは製品として高周波ゲインが抑えられた新日本無線のNJM2825を使いました。これだと最小1.2Vまで下げられますし、今回の可変用途にもうってつけです。

さて、今回はKiCadで基板を起こしてみました。


J2はパイロットランプ用の出力、J4は電圧調整用ボリュームのコネクタです。
余談ですが、NJM2825はカソード側が基準となっているオペアンプのため、Bカーブのボリュームの角度に比例した電圧を出力させようとするとこのような位置にボリュームを設置せざるをえなくなってしまいました。この場合、J4を差し忘れたり接触不良が起こった場合、出力電圧が最大値まで跳ね上がってしまうフェイルセーフではない設計となってしまっています。あまりそういうのは良くないでしょうが、かと言ってR15の位置にボリュームを付けると電源装置として使いにくくなってしまいますので、悩んだ末に安全を捨てました。安全を捨てたといっても、コネクタを差しさえすれば問題は無いですので、ある日突然…ということはまあまず無いでしょう。

また、コンセントからアース端子を基板に取り込めるようにしており、出力側はC9を介してアースに接続しております。この回路図には出てきていませんが、筐体に取り付けたACインレットはノイズフィルタ付きの製品を使っておりますので、厳密には1次側と2次側が絶縁された回路と言うわけではなくなってしまいます。
ただ、2次側の出力が完全に浮いているような設計にしてしまっても、回路である以上何かしらの容量で結合しています。そのようなよくわからない電位を設定するくらいなら、ちゃんとマイナス側をアースにコンデンサ結合させておいたほうが安心ですし安定もするはずです。



基板はこのような感じになりました。
基本的には1次側と2次側は分離しております。中央少し左のトランスとフォトカプラを境に1次側と2次側が分かれております。唯一茶色のレイヤーでそこをまたいでいるベタパターンがアースです。
スイッチング回路はノイズを出しますし、他から受けても困りますので、できるだけ機能ごとにまとめます。スイッチング制御回路は中央下側、フィードバック回路は左下側にまとめています。
スイッチング用トランジスタと出力の整流ダイオードは熱を発しますので、放熱板を設置できるような位置に配置します。また、電解コンデンサは熱でどんどん寿命が縮んでいくため発熱部品からは離して配置し…たいところなのですが、そうはいきませんでした…。

それにしても、KiCadの3D表示機能は圧巻ですね。発注した基板が到着するのがいつもより待ち遠しくなってきます。

製作

基板が届いたら基板への部品の実装と箱の加工を進めていきます。
今回、箱はタカチのTS-1というアルミケースを使ってみました。結構ギリギリでしたが、まあ何とか納めることができました。
それにしても、なかなか金属加工というものは骨が折れるものです。できれば工作機械でパパッと穴を開けたいところでしたね。そんないいもの私は持っていないので、ドリルと金ノコ、テーパーリーマとやすりでひたすら頑張りました。

先ほど少し言いましたが、ACの入力側にはノイズフィルタ付きのインレットを使っております。スイッチング電源は高周波で動作しますので、家庭のコンセント側に高周波ノイズをばらまいてしまわないようにとの配慮です。
基板からインレットまでの配線もできるだけツイストし、その配線から出る輻射ノイズもできるだけ抑えるようにしてあげましょう。
動作確認というほどたいそうなことでもありませんが、つまみを動かしたり適当な負荷をぶら下げてみて動作確認をします。
シミュレーションでは15V3A流れましたが、5Ω負荷をぶら下げると5V程度で出力がサチってしまいました。なかなか計算通りにはいかないものです。こういう時に電子負荷装置でも持っていると、負荷を変えながら特性を詳細に探ることができるのでしょうが。

調整

さて、電流が思ったほど出なかったので現物合わせでパラメーター調整をしていきます。
R5でC3を充電し、Q1のベース電圧が上がるとMOSFETをOFFする、という流れになりますので、充電時間を長くすればQ1がなかなかMOSFETをOFFしに行ってくれないのでたくさんの電力を取り出せるようになるはずです。
というわけで、R5を増やしていきました。増やしていったところ、7.5kΩにて短絡電流が3A程度になったので、ここで調整終了としました。 このように複雑なアナログ回路は計算通りに動いてくれないので、最後は現物合わせという形を取る必要が出てきます。

5Ω負荷で1.7A出力ですので、当初予定ほどの性能は出ませんでした、でも、短絡時にメーターが振り切れても困るのでまあこんなところで良いでしょう。これくらいの出力が取れれば、まあ趣味の電子工作程度ではそうそう困ることは無いかと思いますし。

おまけ


消費電力計を買ってきて作ったスイッチング電源の効率を測ってみました。

もちろん負荷によって変わりますが、高出力状態の時でおおよそ75%程度です。もうちょい、実用的には8割以上は行きたいところですが、まあ適当設計なのでこんなもんでしょう。
力率は0.57程度と低めです。というのも、スイッチング電源は入力段にダイオードブリッジとコンデンサでの整流回路を備えていますが、そのような整流回路は電圧がピークになった周辺でしか電流が流れないため電流波形が大きく崩れます。そのため力率は低く、一般的にこれくらいになるそうです。
フライバックコンバータ程度だと問題ありませんが、比較的大きな負荷だと電力系統にダメージを与えてしまいますので、整流回路を制御して力率を改善する回路を入れたりするそうです。


また、出力が2V前後では結構出力リプルが大きくなってしまいました。これはまあフィードバック回路の性能ですので、シャントレギュレーター周りのフィルタを調整することで改善できるのかもしれませんが、基板パターンを改造しなければならないので今回は現状とします。なかなか広い範囲で出力電圧を安定させるというのは難しいのですね。

まとめ

さて、フルアナログのフライバックコンバータ型スイッチング電源を自作してみました。トランスの製作、回路の設計から実際の電源装置の製作までやり、計画していた性能には届きませんでしたが、なんとか実用レベルのものを作りあげることができました。
1個数百円するトランジスタを何個も焼きながら試行錯誤して、結構いろいろな勉強になりました。普段パソコンとかマイコンとかでプログラミングばっかりやっているときとは違う頭の使い方をしますし、アナログ回路って本当に生身で物理の世界に挑んでいる感じがありますのでまた別の面白さがあると思います。

さて、次は何作ろうかな。