エンジニアはなぜか効率を求めるロマンに走る時が時々ある。太陽電池で遊ぶに当たっても、最大限に太陽電池の性能を引き出そうと思うのはやむを得ない。週末を何回もつぶして取り組む趣味としては十分に楽しめた。
ただ、太陽電池のような発電を伴う場合、エネルギーの受け入れ先を十分に検討しておく必要がある。私は車orバイクの鉛電池をエネルギーの受け入れ先として採用した。最近バイクの始動性が如実に悪くなってきてバッテリーが劣化してるかなと思うことがしばしばあったので。
太陽電池は、使い方次第で効率が変わってしまう。最大限に効率の良い所で運転するためには制御回路が必要で、これはMPPT(Max Peak Power Tracking)回路などと呼ばれている。
本当は真のMPPTやりたいんだけど、電流の測定がどうにもいろいろ問題を含んでいて最終的には疑似的なMPPTになりました。でも、一応最大に近い電力を取り出すような回路にはなってます。
本節で紹介する制御方法は太陽電池の最大電力点が開放電圧の80%程度の電圧の場所にある、という制御方法である。適応対象は結晶シリコン太陽電池であり、アモルファス太陽電池、GaAs太陽電池は最適値が異なるのでI-Vを実際に測定してから実装されたし。
太陽電池の電力はまさに御天道様次第、不安定である。よって、一旦蓄電池に電力を蓄えておくほうが後々便利である。
太陽電池から蓄電池に充電するにあたって簡単な回路はこんな感じ。ダイオードで逆流防止してバッテリー直結。太陽電池のでんあつが電池電圧よりも高くなればバッテリーに充電され、太陽電池電圧がバッテリー電圧より低くなれば充電されないといった簡単な方法。
しかしながら、これでは効率の良い運転が出来ない場合もある。効率が良い場合もあるが、大体その組合わせを実現することは難しい。
太陽電池は光に照らされると発電する便利な発電装置である。
太陽電池の効率が仮に20%という場合は、太陽電池から電力を取り出せる時に光→電力変換効率が20%になるという話で、とりあえずバッテリーに接続すればその効率が得られるってものではない。
太陽電池のパラメータには、短絡電流ISC、開放電圧VOCと、I-V特性がある。次のグラフは実測ではなく、手持ちの太陽電池のパラメータから適当に見積もって計算した太陽電池の特性です。
ISCというパラメータは短絡電流といって太陽電池が発生出来る最大の電流。これは光(フォトン)がPN接合の所に入ってきて電子とホールを生成するときに得られる電流分。SC=Short Circuit。
VOCは負荷を繋がないで太陽電池を発電させた場合の電圧。OC=Open Circuit。太陽電池は単セルで使うことはあまりなく、内部で直列になっているので直列セル数やらの影響を受ける。単セルなら大体0.6V~0.7Vくらい。
VMPPはMax Power Pointの電圧。電力を最大限に取り出せる電圧のこと。この最大電力点で太陽電池を動かす時、太陽電池はその性能を最大限に発揮するが、そこからずれた場所で運用すると、効率が悪くなる。
たとえば、鉛電池の場合3直列の6Vのタイプがあるが(最近見ないけど)、この鉛蓄電池に充電するならこの太陽電池はとても良い特性である。この辺で運転する分にはたぶん逆流防止ダイオード要らない。
1.2VのNi-MH蓄電池を4つ直列にした電池に充電する場合は4.8Vになるので、取り出せる電力が1.6W程度まで低下する。
極端な例では、1.2VのNi-MH電池1本に充電する場合、太陽電池を1.2Vで運用するので取り出せる電力は0.4W程度と、元々2W近く発電出来る太陽電池の20%程度のエネルギーしか取り出せていない。残りの80%はすべて熱になる。
車やバイクの鉛電池は12Vの電圧だが、この電圧に対してはそもそも充電できない。というように、太陽電池から蓄電池に直接充電する際には効率が良いときも悪いときもある。
太陽電池が生み出せるエネルギーの20%しか使わないというのはさすがにもったいないので、DC-DCコンバータの回路を応用して太陽電池は最大電力の電圧で運転し、出力電圧を広い範囲に分布させるのがMPPT(Max Power Point Tracking)回路である。MPPTもDC-DCコンバータの一種である。
充電対象が12V鉛電池なので、以下はブーストコンバータ(昇圧回路)をベースに考え方を解説。
よくあるDC-DCコンバータは出力電圧が一定になるようにフィードバックをかける。負荷が重い(低抵抗の負荷を接続)したときは多く電流を流すように働き、電流が要らないときは電流を流さないように調節する。負荷が主役です。
しかし、太陽電池からエネルギーを取り出す場合、太陽電池の電圧をVMPPに維持する、つまり、入力電圧が一定になるようなフィードバックが必要になる。なのでフィードバックさせる箇所がいつもと逆になる。太陽電池が主役なのだ。
発電電力が大きい場合、過充電しないためにも、電池電圧を監視するべきだが、今回は受け皿が大きい(12V 64Ahの鉛電池)のでやらない。
最大電力が得られる電圧は、入射光の強度や温度によって変動する。この変動に対して制御を追従しないといけない。入射量を変化させてみたが・・・あまり違いがわからないな。
最大電力点は発電電力が小さい時は電圧が小さい方向にちょっと動く。開放電圧も小さくなる。
さて、変動するVMPPを追いかけるにはいろいろな制御方法がある。詳細説明はWikipedia英語版に譲る。Wikipediaにも詳細な説明ないけど。
正攻法はやはり太陽電池電圧、電流を計測して、電力が最大値になるように制御する方法。電圧を変動させて、そのときに取り出せる電力が大きくなったか、小さくなったかで電圧を変動させる方向を変化させる。
しかし、電流計測というちょっとやっかいな方法があるので、今回は手をださずに終わった。ハイサイド電流測定専用のICを使えば楽なんだけどね。そのうちやろうと思う。
電流測定をせずとも、疑似的に最大電力点を追いかける方法がある。それが今回採用する制御方法でConstant Voltageの方法。英語版WikipediaのConstant Voltageを要約すると、最大電力になる電圧は開放電圧の0.76倍だよ、と言っている。解法電圧を測定し、それの76%程度になるように太陽電池電圧を制御してやればよい。
■発電を停める
■太陽電池のVOC測定
■太陽電池電圧が0.76VOCになるようにDC-DCコンバータを制御この三つで済む。
ループは1000回とは限らない。
早速回路。アルゴリズムは簡単。開放電圧を測定→太陽電池電圧が開放電圧の80%になるようにスイッチング制御。これの無限ループ。
適切な負荷がなかったのでエネループ8本、約10Vの電池をターゲットに設定。
マイコンを使って制御を行います。使うマイコンはAVR ATTIny13A。
《着手に当たっての仕様》
・太陽電池は最大6Vで動く可能性がある。
・受電の鉛電池は12V程度から充電する、かもね。
・マイコンは内蔵4.8MHzのクロックを用い、PWMは8ビットカウンタを使う(0-255カウント)のでスイッチング周波数は約20kHz。1サイクル50us。
・マイコン電源は3.3V(太陽電池電圧が5Vを下回る可能性があるので)
・スイッチングはNPNトランジスタを使う(3.3Vで駆動できるMOSFETが手元になかった)
・開放電圧の76%は計算が面倒なので、80%の電圧で運転する。0.76倍は整数演算でやるなら19倍して25で割る。0.80倍は4倍して5で割る。ここの4%で大きくは違うまい!
昇圧比が2なら、Duty比は1/2程度、50usサイクルの半分、25usでコイルの電流がきちんと上昇するだけのインダクタンス設定が必要。
コイルに流れる電流は断続になるとして、電流のイメージはこんな感じ。昇圧回路なので、tonやtoffはスイッチングトランジスタ(上の回路図では2SC2655)のon/off時間です。
tonの間にコイルにエネルギーをため込み、toffの間にコイルに貯めたエネルギーを吐き出す感じ。
平均電流の2倍の電流でコイルが飽和しないといけない。でも、念のため次のようなことも考えて置く。24Vバッテリーなどもうちょっと高電圧に対応させるにはピーク電流を大きくする必要がある、かも。
一応昇圧比が大きいことを想定し(こんな感じ)
コイルに流れる電流が平均電流の4倍程度を見こみます。太陽電池は最大電力点で330mAと書いてあるので、ピーク電流1.3A程度の見込み。たぶん1A容量のコイルでも良いだろうが、2Aサイズのコイルがいいかと。※後に分かるが1Aクラスのコイルで十分。
電流の立ち上がりは、dI/dt=電源電圧E/コイルのL[H]なので、tonと乗じて、ピーク電流がEton/Lになります。25usで上記の1.3Aまで立ち上がらないとそもそもエネルギー輸送ができないので、大きすぎるコイルはNG。
L=100uH, E=6V ton=25usで計算すると、Ipeak=6*25/100=1.5Aと100uHのコイルで十分です。
220uHのコイルになると25usのスイッチオンでは0.68Aまでしか電流が増大しませんので、ダメです。
電流連続モードになればいいのかもしれませんが、そのへんよく分かりません。コイルに蓄えられるエネルギーは0.5LI^2なので、Iが2倍でエネルギー4倍になるので、0~0.5A振幅なら0.25L、0.5A~1.0A振幅なら0.75Lのエネルギーが一回のスイッチングで輸送できるので、電流振幅が高いところに来ればエネルギーはもうちょっと輸送しやすくなるのかも。まあ、この辺はヒマになったら考えます。
さて、これでコイルの選定は完了。回路をちゃちゃっと作って実際に運用してみましょう。
この写真は最終形態だけど、こんなサイズに収まります。いろいろ後から回路を追加したので、背面はちょっとアレな配線引き回しになってるので、内緒。
とりあえず、部屋の窓越しに太陽光を受光し、テストしてみたときの太陽電池電圧、スイッチングトランジスタの制御端子電圧はこんな感じ。黄色が太陽電池電圧。赤がトランジスタのベースを駆動するマイコンピンの電圧。
太陽電池電圧(黄色)が跳ね上がっている箇所で、太陽電池の開放電圧を測定しています。初期のアルゴリズムは10ミリ秒だけスイッチングを停止して開放電圧の計測するようにしていました。開放電圧6.40Vで電力を取り出しているときの電圧が5.04Vなので、VOCの79%程度の電圧で運転出来ている。大体設計通り。太陽さんさん当たっているのに10ms待つのはムダだから、10ms待ちは廃止になって、電圧測定して、電圧が上昇しなくなったら開放電圧測定完了、といったコントロールに変更。低照度の時はコンデンサの充電に時間がかかる分を考えるとこっちのほうが良さそう。実際に曇ったりすると10msでは電圧が上がりきらず、運転電圧がどんどん低下していくモードに突入してしまいます。
動作が確認出来ればあとは実際の使用に当たって想定されることを追加していく・低電圧検出(マイコンリセット):太陽電池電圧が下がりすぎたとき、マイコンが暴走しないようにリセットをかける。マイコン内蔵のBODを使用。・出力開放検出:出力に電池など負荷がないと出力端子の電圧がどんどん増加してしまうので、出力開放の検出を行う。・LEDの点灯:外から見てちゃんと動作しているのか、電圧低くてリセットかかりまくっているのか判断したい。(外がどの程度暗くなってもエネルギー取り出せるのか知りたい)
出力開放はツェナーダイオードを使って一定電圧を超えたときにマイコンリセットをかけるような回路にした。扱う電力が小さいが、念のためツェナーには2Wくらいの大きさのものを使った。
実際の回路。適当に部品を配置して回路を組んでしまったので背面は内緒です。ツェナーは発熱するかとおもって浮かせましたが、実際にはほとんど発熱しない。発熱しないようにしたつもりですから。
LEDは開放電圧測定の時に光るようにしてあります。リセットかかった後は0.25秒間隔で点滅するようにしてあります。正常に動作しているときは約1秒ごとにピカっと光る。明るさが足りなくて発電できないときはマイコンのリセット頻発でLEDが点滅する。出力開放のときもリセットかかりまくりで点滅。正常なときと異常なときとが見分けられる。
太陽電池からの電流を計測しない疑似的なMPPT動作なので、実際にはめいっぱいのMPPTではないが、バッテリー直結などに比べればかなり効率のよい運転になっている。太陽電池はカタログスペックで2Wのものだったが、冬に1.4W近い電力を取り出せていた。真夏のように日射が強いシーズンならもう少しいけるだろう。
後日分かったが、車のガラスは紫外線カットやらいろいろ機能があって、車のガラス越しの太陽電池発電量は屋外での物に比べ約半分程度にしかなりません。
車の外に太陽電池を置けばいいんだが、そうなると雨風に耐えるものにしないといけないが、密封が面倒なので、そこまではしない。都合の悪いことには一切手をださなくていいのも趣味の醍醐味である。仕事は大体都合の悪いこと片付けないとOKが出ないんだけどね。
市販品にも車内においた太陽電池でバッテリーに充電ってものがあるが、それよりは幾分かいいものが出来たとおもう。
最後に、電池への過充電があるかざっくり計算してみる。
充電対象の電池:12V 64Ah 鉛電池(車のバッテリー)自己放電を0.5%/dayとすると、13mAほどが自己放電を補う電流。
車のセキュリティーやらで30mA程食うとのことなので、合計約40mAほどを流せれば良さそうである。40mA×24h=960mAhが一日の発電の目標値。
車内での発電電力はせいぜい0.8W程度だった、出力電流にして70mAにならない程度。一日8時間充電できたとしても560mAhであり、上記の放っておいても減る電力を超えることはないだろう。なので、過充電にはならないんじゃないかな。
最後に総合効率だが、ざっくり75%程だった。入力1.24Wの時、出力0.93Wだった。差分はインバータ内部で消費されている。
いつもソースを載せようとするのだが、上手くいかない。以下コンパイル後のHEXファイル。
:1000000009C00EC00DC00CC00BC00AC009C008C09A
:1000100007C006C011241FBECFE9CDBF73D0DDC01D
:10002000EFCF84E084BB81E087B985EE86B9349B4D
:10003000FECF089583E88FBD81E083BF089583E0FC
:100040008FBD13BE08958FE599E00197F1F700C0C9
:10005000000084B195B1EFE5F9E03197F1F700C008
:10006000000044B155B19A01281B390B23303105EA
:100070005CF581E042E050E090E00DC024B135B184
:10008000B901641B750B6330710594F4482F50E07F
:100090004F5F5F4FF1CFE7E5F2E03197F1F700C036
:1000A00000009F5F292F30E024173507A4F308953F
:1000B000EFE5F9E03197F1F700C000008F5FA9018B
:1000C0008A3FE1F680E008958FE599E00197F1F726
:1000D00000C0000082E0D2CF8AE092E098BBEFE659
:1000E000F7E13197F1F700C0000018BAFFE324E60A
:1000F00031E0F15020403040E1F700C00000815075
:1001000069F7089583E087BBE7DF81E086BF92DF70
:1001100088DF0F2EF5E0EF2EF12CF02D6894CC2423
:10012000C1F8B12C08E813E10F2EF8ECDF2EF02D0A
:1001300086DF89DFA82E24B135B1C901880F991F48
:10014000880F991FB70135D0EB0174DFC8BA8FEB68
:100150009DE50197F1F700C0000018BAAA2051F000
:100160009B2DEFE6F7E13197F1F700C000009F5FAC
:100170009A11F7CF18BAC801E0EEF1E03197F1F724
:1001800024B135B12C173D0748F426B7223020F0B2
:1001900026B7215026BF0AC016BE08C026B7283C85
:1001A00020F426B72F5F26BF01C0D6BE019721F7E6
:1001B000BFCFAA1BBB1B51E107C0AA1FBB1FA617BD
:1001C000B70710F0A61BB70B881F991F5A95A9F700
:0E01D00080959095BC01CD010895F894FFCF65
:00000001FF
次はソース
/* * _20140105_SolarCell_PseudoMPPT.c * * Created: 2014/1/5 11:59:25 * Author: Makoto * 電圧制御タイプのMPPTを行う。 * 結晶系の太陽電池では開放電圧の80%電圧にておおよそ最大電力点になる。 * 電流を測定しなくていいので、回路が非常に簡単に済むメリットがある。 * * マイコンへの電源供給は3.3V(低ドロップ電圧のレギュレータを使う) * 太陽電圧はマイコン電源電圧を超えるので、抵抗で分圧する(4.7kと3.3kで3.3/8にする) * →開放電圧8Vまでの太陽電池に対応できる。 * * 使ったピン * * ~RST-|^^U^^|- Vcc <-- 3.3V from LDO Reg * -|TINY |- ADC1 <-- 0-3.3V * -| 13A |- * GND-|_____|- PB0(OC0A, PWM out) --> Switching Transistor * * * 太陽電池電圧は抵抗で3.3/(3.3+4.7)=33/80に分圧されている。 * 解放電圧が6.4Vくらいなので、分圧値は2.64Vほど→ADCが818くらい。 * * */ #define F_CPU 4800000UL //PWMを20kHzくらいにしたかったので4.8MHzで動作させる #include <avr/io.h> #include <stdint.h> #include <util/delay.h> #define Duty_Upper_Limit 200 //DUTY比の上限決定 #define LoopNumber 5000 //電圧制御を何回繰り返すかの指標。5000で1秒程度になる。 void Start_ADC(void) /*ADC変換を開始する。自動トリガでADC1の値を読み込み続ける。プログラムからはADCを読めばよい*/ { //10bit分解能用ルーチン ADCは連続で動作させる //デジタル入出力からの切り離し DIDR0 = (0<<ADC0D) //ADC0ピンの切り離し。しかし、ADC0はリセット端子になってるから設定しちゃダメ | (1<<ADC1D) //同ADC1, PB2 | (0<<ADC2D) //同ADC2, PB4 | (0<<ADC3D); //同ADC3, PB3 ADMUX = (0<<REFS0) //Reference: 0でVCC参照, 1で1.1V内部電圧源 | (0<<ADLAR) //ADC Left Adjust Result: 1にセットすると左詰で結果を出してくる | (1); //ADMUX1, ADMUX0で入力チャンネルを選べる 00から11までADC0からADC3まで対応 今はADC1を使う ADCSRA = (1<<ADEN) //ADC enable | (1<<ADSC) //ADC Start Conversion 1にすると変換開始 | (1<<ADATE) //ADC Auto Trigger Enable (for Continuous Conversion) | (0<<ADIF) //ADC Interrupt Flag 変換完了すると1になるらしい | (0<<ADIE) //ADC Interrupt Enable 変換完了したときに割り込み許可 | (0b101); //Clock Division: ADコンバータはクロック50-200kHzで性能がよい //分周は0のとき2、それ以上の時2^n分周。クロック4.8MHzのとき32分周(101)で150kHz。 //ADCは起動時にアナログ回路リセットが入るので変換に25クロック(167us)必要 //以降連続変換で回せば13クロックサイクル(87us)で変換完了→87us以内にADC読みに行くと前の値と同じってことになる loop_until_bit_is_set(ADCSRA,ADIF); //ADIFビットが1になるまでムダループ // ADC_result = ADC; //ピン切り離しの解除 /* DIDR0 = (0<<ADC0D) //ADC0ピンの切り離し。しかし、ADC0はリセット端子になってるから設定しちゃダメ | (0<<ADC1D) //同ADC1を接続(1設定で切り離し実行、0設定で接続) | (0<<ADC2D) //同ADC2 | (0<<ADC3D); //同ADC3 */ } void Start_PWM(void) /*PWM動作を開始する。CPUクロックと同じ速度で動く*/ { //PWM出力の設定 カウンタが0からOCR0Aまでの間HIGH、OCR0A+1から255までの間LOWになるように設定 TCCR0A = (1<<COM0A1) /*OC0A端子をターゲットに、COM0A1:A0は高速PWM設定で以下の動作(高速PWMで使うモードのみセット)*/ | (0<<COM0A0) /*10;BottomでHigh, 比較一致でLow、11;BottomでLow、比較一致でHigh、00でPWMから切断!*/ | (0<<COM0B1) /*COM0B1:B0は出力がOC0Bになるだけで、同じ設定*/ | (0<<COM0B0) | (1<<WGM01) /*WGMレジスタはTCCR0BのWGM02とあわせて設定*/ | (1<<WGM00); /*WGM2:0が 011で8bit高速PWM動作*/ //PWM種類とプリスケーラ設定 TCCR0B = (0<<FOC0A) /*非PWM動作のときのみ有効。今回は0設定*/ | (0<<FOC0B) /*同上*/ | (0<<WGM02) /*TCCR0AのWGMビット参照*/ | (0<<CS02) /*CS0xでプリスケーラ設定*/ | (0<<CS01) /*000停止、001=1分周、010=8分周、011=64分周、100=256分周、101=1024分周 、110=T0ピン下降端(外部クロック)、111=T0ピン上昇端(外部クロック)*/ | (1<<CS00); } void Pause_PWM(void) /*PWM動作の停止 */ { //TCCR0Aレジスタ設定して、PWM出力を停止する TCCR0A = (0<<COM0A1) //OC0A端子をターゲットに、COM0A1:A0は高速PWM設定で以下の動作(高速PWMで使うモードのみセット) | (0<<COM0A0) //10;BottomでHigh, 比較一致でLow、11;BottomでLow、比較一致でHigh、00でPWMから切断! | (0<<COM0B1) //COM0B1:B0は出力がOC0Bになるだけで、同じ設定 | (0<<COM0B0) // | (1<<WGM01) //WGMレジスタはTCCR0BのWGM02とあわせて設定 | (1<<WGM00); //WGM2:0が 011で8bit高速PWM動作 //クロック供給も停止する(ほんのちょっと節電) TCCR0B = (0<<FOC0A) | (0<<FOC0B) | (0<<WGM02) | (0<<CS02) /*CS0xでプリスケーラ設定*/ | (0<<CS01) /*000停止、001=1分周、010=8分周、011=64分周、100=256分周、101=1024分周*/ | (0<<CS00); } uint8_t wait_until_ADCStable(void) { /* 直射日光が当たらないような条件では極端に発電電流が小さくなるのでその対策 1msごとサンプリングしてADC値の差が2、電圧にして0.06V以下になればゴーサイン。 待ち時間は8bitカウンタの都合で250msを上限にする。 */ int16_t ValBefore; int16_t ValNow; _delay_ms(2); ValBefore = ADC; _delay_ms(2); for(uint8_t i=1;i<250;i++) { ValNow = ADC; if(ValNow-ValBefore < 3 ) { for(uint8_t j=0;j<i+1;j++) { _delay_us(500); } return i; break; } else { ValBefore = ValNow; _delay_ms(2); } } return 0; } void initial_motion(void) { //起動時にLEDをチカチカ点滅させる。リセット時にも動くので出力解放or電力不足の判断できる。 for(uint8_t i=0;i<10;i++) { PORTB = 0b00000010; //PB1をHIGH _delay_ms(5); //wait a while PORTB = 0b00000000; //PB1をLOW _delay_ms(95); //wait a while } } int main(void) { uint16_t V_aim=0; //電圧目標値を入れる。ADCが10bit整数、変数そのものは16bitなので、ADCの値を64倍するとオーバーフロー uint8_t Delay_Cycle; DDRB = 0b00000011; /*PORTB0 for PWMの出力になる*/ initial_motion(); OCR0A = 1; /*とりあえずPWMの初期値設定*/ Start_PWM(); Start_ADC(); while(1) { /*■開放電圧の測定■*/ Pause_PWM(); //PWM出力の停止 Delay_Cycle = wait_until_ADCStable(); //電圧が安定するまで待機。安定するまでの時間に応じた整数を返してくる。 V_aim=ADC*4/5; //電圧を0.8倍する。65536/1024=64なので、ADCの値を64倍しても16ビット変数はあふれない。→もう少し細かく Start_PWM(); //PWMの開始(PWMは開放電圧測定前のDuty比で回る) PORTB = 2; //PB1のON(LEDを点灯) _delay_ms(20); PORTB = 0; //PB1のOFF for(uint8_t i=0;i<Delay_Cycle;i++) { _delay_ms(5); } PORTB = 0; //PB1のOFF /*■PWM制御■*/ for(uint16_t i=0;i<LoopNumber;i++) { _delay_us(400); /*delay 400us×5000=1second*/ if (ADC < V_aim) /*ADCが値目標電圧より小さい場合は、Dutyを一つ小さく(負荷を軽く)。*/ { (OCR0A > 1) ? (OCR0A--) : (OCR0A=0); /*0を下回らないように*/ } else { (OCR0A < Duty_Upper_Limit) ? (OCR0A++) : (OCR0A=Duty_Upper_Limit); /*DUTY比が上限を超えないようにする*/ } } } }