フォトカプラでスイッチング 第2話

スラマッパギ!! N.C.です。最近、甘食を見ると食べたくなりますね。

 予告から、だいぶ時間がたってしまいましたが、フォトカプラでスイッチング第1話の続きです。今回はさらなるスピードアップを目指しました。

 お詫び:前回の記事で、フォトカプラの型番が間違ってました。TLP251ではなく、TLP521でした。

 前回の回路では、TLP521というフォトカプラで10kHzの信号を伝達することができました。しかし、波形のゆがみを見る限り、デューティ比50%の信号が限度だろうという印象を受けました。PWMを送るなら数kHz程度でしょう。今回は、同じTLP251を使いながら、さらに高速で信号を伝達する回路を試しました。まずは、回路図を見てもらいましょう。(図1)

図1:テスト回路

 この回路では、フォトカプラにフルスイングさせることは諦め、その代り、後ろにコンパレータを配置し、最終的な信号を得ようという考えに基づいて考えました。この回路で、約25kHzのPWMを伝達してみました(図2、図3)。遅延があるものの、鋭い立ち上がりを保ったまま信号を伝達できました。しかし、duty比5%では、元の信号とduty比が変わってしまいました。でも、プログラムの方で補正をかければ使えるかもしれません。といっても、今回の回路は、半分ネタですけどね。ネタに全力を注ぐ・・・それがT-semiです。
図2:duty比=50% 赤:Vin, 黄:Vout

図3:duty比=5%

因みに、今回使ったコンパレータ(LM339)は、4回路入りで、一個94円(マルツ価格)、TLP521は4回路入りが一個168円(梅沢価格)で、5Vから動作するので、TLP250がどうしても使えない時に、ネタとしてではなく、実際に使う機会が来るかもしれません。そのときには、出力段を強化する必要がありますが・・・

これから「制御」の話をしよう(予告&追記)

N.Cさんが指摘している通りあまりにもロボットの話がないので、たまにはロボットの話をしましょう。でも私はガワ屋ではないのでメカメカしたお話はできません。そこで制御の話でもしようと思います。

何回かに分けてPIDの基礎的なお話をできればいいなと思っています。あとは直感的な理解のためにJavaScriptを使ってアニメーションを交えながら紹介したいと思いますので、未だに古いブラウザを使っている人は最新のブラウザにアップデートしておいてください。(HTML5のCanvasを使うつもりです。)

最近忙しくなりつつあるので次に更新できるのがいつになるか分かりませんが、とりあえず自分にプレッシャーをかけるべくここに予告をしておきます。 by Y.O

以下にゆれる振り子が表示されない場合にはブラウザを更新してください。とりあえずChrome 11, Safari 5, Firefox 4, Opera 11で確認してあります。

var offset = 5
var angle = 0
var running = false
function draw() {
var cv = document.getElementById(“canvas”);
var ctx = cv.getContext(‘2d’)
ctx.clearRect(0,0,400,400);
ctx.save()
ctx.translate(200,20)
ctx.rotate(Math.sin(angle))
strokeHalf = function(){
ctx.beginPath()
ctx.moveTo(0,-10)
ctx.lineTo(5,-10)
ctx.lineTo(5,100)
ctx.lineTo(25,115)
ctx.lineTo(25,135)
ctx.lineTo(15,145)
ctx.lineTo(10,140)
ctx.lineTo(20,130)
ctx.lineTo(20,120)
ctx.lineTo(0,105)
ctx.stroke()
ctx.closePath()
}
strokeHalf()
ctx.scale(-1,1)
strokeHalf()
ctx.restore()
}
function first() {
offset += 5
var cv = document.getElementById(“canvas”);
var ctx = cv.getContext(‘2d’)
if(!running){
running = true
setInterval(function(){angle+=0.05;draw();},30)
}
}
first()

FETの特性のかけらから、FETの特性を再構成してやろう

スラマッマラム N.C.です。

 パワーMOSFETに限らず、部品のデータシートには、特定の条件での特性しか書かれていません。少なくともこのサークルでは、多くの場合それで事足りるでしょう。しかし、安全に動作するように余裕を確保しつつも必要以上の余裕を持たせたくない(つまり金銭や大きさなどの制約があって、ギリギリを攻めたい)場合には、自分が使用する条件での特性をある程度正確に知る必要があります。どうすればいいでしょうか。
 FETを使うときに非常に重要な特性がQg特性です。これを知ることはスイッチングに要する時間やその際発生する損失を知ることに直結します。以前にもQg特性が条件によって変化することは述べましたが、どのように依存するのかは述べませんでした。今回は、その辺を詳しく考えてみることにしましょう。
 Qg特性を変化させる要因には次のものがあります。
・電源電圧
・ドレイン電流
・温度
電源電圧による変化は書かれているデータシートもあれば、ないデータシートもあります。
ドレイン電流による変化はたいてい書かれていません。
温度は、閾値電圧に影響を与え、これはデータシートに書かれています。
それぞれが、特性にどのような影響を与えるのかを考えてみましょう。

<電源電圧>
 ゲート・ドレイン電圧がスイッチング時に何ボルト変化するのかが主に電源電圧で決まります。そのため、ミラー領域の長さに影響を与えます。スイッチング時に注入される電荷はCgd-V特性を積分すると知ることができそうです。この特性はC-V特性から知ることができます。
<ドレイン電流>
 ミラー領域でのゲート電圧(ミラー電圧)を変化させます。ミラー電圧が変化すれば、それまでに注入されるゲート電荷は当然、増加します。ちなみに、ミラー領域では、ドレイン・ソース電圧と閾値の大きさの関係から、定電流領域で動作していることが分かります。
<温度>
 ゲート閾値電圧は温度に対して負の温度係数を持っています。そのため、温度が変化すれば閾値電圧も変化するため、注入されるゲート電荷も変化します。

かけらは揃った。次回、再構成しよう。

ボリュームの値を読む 第2話(最終話)

スラマッマラム N.C.です。

 今日は、前回の続きです。
・十分な電圧
 n-bitのAD変換を行うならVref/2^nの分解能を得ることができます。読み取りたい電圧をVreadとおくと、

Vread-Vref/2^(n+1)

までコンデンサが充電されれば確実に読み取れます。つまり、この電圧が十分な電圧です。例えば前回の例のように、読み取るべき電圧が4.5Vだとします。また、8bitのAD変換を行いたいとします。すると、目標電圧は4.5-5.0/2^9=4.490Vです。
・十分な電圧まで充電される条件
 先ほどは、具体例を挙げましたが、その具体例を基準に考えて設計するわけではありません。回路方程式を解いて、条件を計算し、もっとも時間がかかる場合を割り出してあげる必要があります。ちょっとした過渡現象の問題です。目標電圧に達するまでの時間t0は、可変抵抗の全体の抵抗をRv、可変抵抗のうち、電源側の抵抗をR1、AD変換器のなかの抵抗をRs/h、コンデンサをCs/hとおけば(s/hはsample holdのことです)、R1<Rv/2の場合は

で計算できます。しかし、この式の最大値を解析的に計算するのは、私には厳しかったので、文明の利器に頼ることにしました。とりあえず、次のように変形します。

あとは、これの最大値をExcelの力を借りながら計算するだけです。但し、n=8としました。この過程は省略します。で、できたグラフが図1です。AVRのデータシートによればRs/hは1kから100kΩです(AD変換の入力抵抗とは別)。なので、Rs/hにたいしてt0が単調増加するのか確認のためいくつかのRs/hに対する曲線を描いてあります。100kΩのときが最大だということが分かるので、設計するときは100kΩの曲線を見ればいいことになります。このグラフを使えば必要なサンプリング時間で正しい結果を得るにはRvをいくつに選べばよいのかを読み取れます。

図1:サンプリングに必要な最大時間 8bitの場合

ちなみに、x>1/2の領域でも全く同じになるのでx<1/2の領域だけ考えれば十分です。

ボリュームの値を読む 第1話

スラマッマラム N.C.です。

 最近のブログの更新速度は半端ないですね。しかし、ロボット関連の記事がありません。しかも、何のサークルなのかよくわからない記事が増えてきてしまいました。新入生のみなさん、うちのサークルは一応、ロボットがメインの活動となってます。(もちろん、それ以外のことをやっても構いませんが。)というわけで、きょうはロボットという単語を出したいと思います。
 ロボットで、関節などの角度を読み取るとき、もっとも手軽なのは、可変抵抗(ボリューム)の抵抗値を電圧に変換して、AD変換で読み取ってやる方法です。では、そのボリュームの値はどれくらいにすればよいでしょうか。「AD変換の入力抵抗がいくつだからその抵抗によるズレを無視できればいいら・・・」と考えた人、そうではありませんよ。(私も最近までそう考えていましたが・・・)
 AD変換器の内部の、電圧をサンプリングする部分の回路は、図1中の「AVRの中」のようになっています。今、端子の電圧は4.5Vになっていて、これを読み取りたいとします。

図1:AD変換

 AD変換は、内部のコンデンサに充電してから行われます。なぜそのような回りくどいことを行うか簡単に説明します。AD変換には時間がかかります。しかし、その間は、電圧が一定である必要があります。そこで、一瞬だけコンデンサと入力端子の間の回路をつなぎ、コンデンサに充電し、回路を切って、コンデンサが放電しないようにして(これを”Sample & Hold”と呼びます)、その電圧をAD変換します。
 AVRのデータシートのAD変換の部分を読むと、AD変換が始まってから1.5クロック(注:AD変換のクロック)がサンプリングとなっています。つまり、1.5クロック間だけ、図1中のスイッチが閉じます。この間にもしも、コンデンサが4Vまでしか充電されなかったとすると、AD変換で得られる電圧は4Vということになってしまいます。4.5Vを読み取るためには、1.5クロックでコンデンサが十分な電圧まで充電されなければなりません。
 「ボリュームの値を読む」第2話では、「十分な電圧とは何ボルトなのか」、「その電圧まで時間内に充電される条件とは何か」を説明していきたいと思います。

AVRはAVRはmain関数を抜け出してみる

main関数を抜け出したらどこに行くんだろう?、ってミサカはミサカはAVRを心配してみたり。

前回の記事に書きましたがmain関数の最後にはret命令があります。パソコン上のプログラムであればこの後に少し処理を行ってから(stdin, stdout, stderrのクローズなど)、OSに処理を返します。AVRではこのret命令の後にどこに処理が移るのでしょうか。

ret命令のジャンプ先はスタックに積まれています。スタックの先頭が変なアドレスを指していた場合はプログラムは暴走します。main関数が別の場所からcallされていれば、スタックの先頭が正当なアドレスであることが保証されるので問題は起きません。その点に注意しながら見ていきましょう。

今回は
avr-gcc -c -o main1.o -mmcu=atmega328p main1.c
avr-gcc -o main1.elf -mmcu=atmega328p main.o
avr-objdump -h -S -z main1.elf > main1.lss
という順でコマンドを実行して、一度elfファイルを作ってからそれを逆アセンブルしてみました。(main1.cは前回の記事を参照)

逆アセンブル結果 main1.lss
[plain]

main1.elf: file format elf32-avr

Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000042 00000000 00000000 00000054 2**1
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .stab 000006b4 00000000 00000000 00000098 2**2
CONTENTS, READONLY, DEBUGGING
2 .stabstr 00000054 00000000 00000000 0000074c 2**0
CONTENTS, READONLY, DEBUGGING

Disassembly of section .text:

00000000 <__vectors>:
0: 0c c0 rjmp .+24 ; 0x1a <__ctors_end>
2: 13 c0 rjmp .+38 ; 0x2a <__bad_interrupt>
4: 12 c0 rjmp .+36 ; 0x2a <__bad_interrupt>
6: 11 c0 rjmp .+34 ; 0x2a <__bad_interrupt>
8: 10 c0 rjmp .+32 ; 0x2a <__bad_interrupt>
a: 0f c0 rjmp .+30 ; 0x2a <__bad_interrupt>
c: 0e c0 rjmp .+28 ; 0x2a <__bad_interrupt>
e: 0d c0 rjmp .+26 ; 0x2a <__bad_interrupt>
10: 0c c0 rjmp .+24 ; 0x2a <__bad_interrupt>
12: 0b c0 rjmp .+22 ; 0x2a <__bad_interrupt>
14: 0a c0 rjmp .+20 ; 0x2a <__bad_interrupt>
16: 09 c0 rjmp .+18 ; 0x2a <__bad_interrupt>
18: 08 c0 rjmp .+16 ; 0x2a <__bad_interrupt>

0000001a <__ctors_end>:
1a: 11 24 eor r1, r1
1c: 1f be out 0x3f, r1 ; 63
1e: cf e5 ldi r28, 0x5F ; 95
20: d2 e0 ldi r29, 0x02 ; 2
22: de bf out 0x3e, r29 ; 62
24: cd bf out 0x3d, r28 ; 61
26: 02 d0 rcall .+4 ; 0x2c <main>
28: 0a c0 rjmp .+20 ; 0x3e <_exit>

0000002a <__bad_interrupt>:
2a: ea cf rjmp .-44 ; 0x0 <__vectors>

0000002c <main>:
2c: df 93 push r29
2e: cf 93 push r28
30: cd b7 in r28, 0x3d ; 61
32: de b7 in r29, 0x3e ; 62
34: 80 e0 ldi r24, 0x00 ; 0
36: 90 e0 ldi r25, 0x00 ; 0
38: cf 91 pop r28
3a: df 91 pop r29
3c: 08 95 ret

0000003e <_exit>:
3e: f8 94 cli

00000040 <__stop_program>:
40: ff cf rjmp .-2 ; 0x40 <__stop_program>
[/plain]

36行目でスタートアップルーチンからmain関数が呼ばれています。なので、51行目でmain関数から抜け出した後は37行目に処理が移ります。その後は54行目で割り込みを禁止して、最終的に57行目の無限ループに入ります。

main関数を抜け出した後はしっかりと無限ループでトラップされることが確認されたので、安心できました。実質的に上記コードはmain関数の最後にfor(;;)があるようなものです。これで、main関数の形式はvoid main(void)形式がベスト(ただし警告が出る)ということが言えました。

と言いたいところですが、まだ見落としている点があります。それはmain関数中にfor(;;)などの無限ループがありmain関数が終了しない時です。マイコンプログラムではむしろこちらの方が多いです。

次回はこの場合のコードを検証してみます。

by Shiozaki

こんなかたちで終わることしかできない私を許して下さい

今 万感の思いを込めて 命令が実行される
今 万感の思いを込めて PC(プログラムカウンタ)が進む
ひとつのプログラムは終わり また新しいプログラムが始まる
さらばAVR
さらばmain関数
さらば少年の日よ
(十分な間を置く)
(GODIGOのあの曲が流れる)

一般的にmain()関数は
int main(void)
もしくは
int main(int argc, char **argv)
と書きます。
しかし、仕様上はOSのサポートが無い環境(フリースタンディング環境)では
void main(void)
などの処理系独自の表記も許されています。

ではAVRのプログラムでint main(void)とvoid main(void)で何か違いがあるでしょうか?さらにはmain関数の最後のreturn文での返り値に何か意味があるのでしょうか?

とりあえず、3つのパターンを試してしました。
パターン1 int main(void)形式でreturn 0
パターン2 int main(void)形式でreturn1
パターン3 void main(void)形式

それぞれのパターンのソースコードをmain1.c, main2.c, main3.cとして挙げます。

[c]
/* main1.c */
int main(void)
{
return 0;
}
[/c]

[c]
/* main2.c */
int main(void)
{
return 1;
}
[/c]

[c]
/* main3.c */
void main(void)
{
return;
}
[/c]

それぞれのコードをavr-gcc -S -mmcu=atmega328p main*.c でアセンブルしました。

結果は次のようになりました。
[plain]
.file "main1.c"
__SREG__ = 0x3f
__SP_H__ = 0x3e
__SP_L__ = 0x3d
__CCP__ = 0x34
__tmp_reg__ = 0
__zero_reg__ = 1
.text
.global main
.type main, @function
main:
push r29
push r28
in r28,__SP_L__
in r29,__SP_H__
/* prologue: function */
/* frame size = 0 */
ldi r24,lo8(0)
ldi r25,hi8(0)
/* epilogue start */
pop r28
pop r29
ret
.size main, .-main
[/plain]

[plain]
.file "main2.c"
__SREG__ = 0x3f
__SP_H__ = 0x3e
__SP_L__ = 0x3d
__CCP__ = 0x34
__tmp_reg__ = 0
__zero_reg__ = 1
.text
.global main
.type main, @function
main:
push r29
push r28
in r28,__SP_L__
in r29,__SP_H__
/* prologue: function */
/* frame size = 0 */
ldi r24,lo8(1)
ldi r25,hi8(1)
/* epilogue start */
pop r28
pop r29
ret
.size main, .-main
[/plain]

[plain]
.file "main3.c"
__SREG__ = 0x3f
__SP_H__ = 0x3e
__SP_L__ = 0x3d
__CCP__ = 0x34
__tmp_reg__ = 0
__zero_reg__ = 1
.text
.global main
.type main, @function
main:
push r29
push r28
in r28,__SP_L__
in r29,__SP_H__
/* prologue: function */
/* frame size = 0 */
/* epilogue start */
pop r28
pop r29
ret
.size main, .-main
[/plain]

この結果を見る限りでは、int main(void)形式の場合はmain関数の返り値はmain関数の呼び出し元にしっかり返っています。void main(void)形式の場合は不定値が返っています。

「どうせmain関数の戻り値なんて使わない」という考えの下ではvoid main(void)形式がメモリ使用量が少なくベストです。しかし、main関数の戻り値についての警告(main3.c:2: warning: return type of ‘main’ is not ‘int’)が出ます。

さて、どのコードも最後にret命令があります。このret命令でmain関数から抜けていますがどこに処理が移るのでしょうか?
そろそろ、記事が長くなってきたので続きは次回に。

by Shiozaki

フォトカプラでスイッチング 第1話

スラマッマラム N.C.です。

 フォトカプラとは、LEDとフォトトランジスタが中に入っている素子で、一つの基盤上で、電気的に絶縁された回路間での信号伝達に使われます。なぜそのような部品が必要か説明します。モータドライバでは、ロジック回路と、パワー回路が混在してしまいます。パワー回路からまき散らされるノイズは非常に大きく、ロジック回路を簡単に誤動作させます。そのため、両者の接点はできるだけ少なくする必要があります。そこで、信号を光で伝達できるフォトカプラという素子が威力を発揮するのです。ただし、ここで扱う信号はディジタル信号のみとします。
 さて、そのフォトカプラですが、使い方に注意しないと信号の形を大きく変えてしまいます。フォトカプラを使うときの典型的な回路は図1のような回路でしょう。

図1:テスト回路

 これを御覧のあなたなら、図1中のRはいくらにしますか?もしくはどのように決定しますか?Rの値によって結果には天と地との差ができてしまいます。
まずは、R=1kΩにしてみました。TLP521のVce-Ic特性から、このぐらいの抵抗にすればVoutをほぼ0から5Vまでいっぱいに振ることができます。これで問題ないでしょうか?VinとVoutをオシロスコープで観測した結果を見てみましょう。図3の緑の線を見てください。信号の立下りがVin(赤い線)に比べてかなり遅れてしまいました。これは、少数キャリアの蓄積が起こってしまったためで、普通のバイポーラトランジスタをスイッチとして使うとき、飽和領域でどうさせたときに起きる現象と同様のものと考えられます。実際、フォトカプラでも蓄積遅延時間と呼ばれています。これでは、信号のデューティ比が(周波数によりますが)変化してしまいます。
 どうすればこの蓄積遅延時間を解消できるでしょうか。普通のバイポーラトランジスタなら、スピードアップコンデンサをつけることで蓄積遅延時間を解消することができます。しかし、フォトカプラでは、ベース電極がないため、そもそもその方法は取れません。そうなると、動作点が飽和領域に被らないようにする必要があります。そこで、R=470Ωにしました。こうしたときの負荷線をデータシートのVCE-IC特性に引けば、図2から分かるように、飽和領域を外れてくれます。この時の波形が、図3中の黄色の線です。蓄積遅延時間は解消されました。それでも、立ち上がりと立下りはなまっていますが、どちらも同じくらい遅れるので信号の形への影響はあまりありません。しかし、High状態の出力電圧が低下してしまうことには注意が必要です。
[caption id=”attachment_1458″ align=”alignnone” width=”478″ caption=”図2:R=470Ωでの動作点(TLP521のデータシートに負荷線を引いたもの)
IF=5mAのとき赤丸の点が動作点になります。IF=5mAの曲線のうちVCE

怪談:初期化されない.dataセクション

これは本当にあった話ですが…
その日、私はいつも通りにプログラムを書いていたんですよ。すると、.dataセクションに置く必要のあるデータがあったので、
.section .data
FOO: .byte 0x01, 0x02, 0x03, 0x04
という具合に書いたんですよ。
でもプログラムを動かしてみるとどうも動作がおかしい。不審に思った私は逆アセンブル結果を見てみました。
そうしたら、なんと.dataセクションが初期化されてなかったんです!!

How to OHARAY(御祓い)

この手の問題は般若心境を唱えても解決しません。もちろん法華経もお清めの塩も聖書も十字架も効きません。

本来ならば.dataセクションの初期化コードはリンカが自動生成してくれます。しかし、組み込みなどのメモリが限られている環境ではわずかな初期化コードですらメモリを圧迫します。そのため、.bssセクションをゼロクリアしないという話は聞いたことがありますが、まさか.dataセクションも初期化されないとは…

ソースコードに
.global __do_copy_data
と書くことでこの問題は解決します。

同様に.bssセクションをゼロクリアするためには
.global __do_clear_bss
と書くといいです。

__do_copy_dataで検索してもまともな記事が無いので正式な方法ではないかもしれません。正式な方法を知っている人がいたら教えてください。

複数のソースファイルから成っている場合はどれか1つのソースコードに書けばいいです。

by Shiozaki

ギャラリーを追加しました

最近はShiozaki、N.C、Y.Oの三人でニッチな記事ばかりを書いていますが、あまりにも新入生向けの内容がないのでギャラリーのページを追加しました。基本的には過去に載せた動画や写真を中心に充実していくはずです。手始めに過去の動画を三つほど載せましたのでごらんください。

ロボコンの動画には、どの学年が作ったのかを載せていますので参考にしてください。我々のサークルでは入った当初は全く経験がない人が8割くらいを占めていますが、やる気次第でこの動画のようなロボットを作ることができるようになりますので興味のある方はどしどし新サークル棟105号室へお越しください。たいていの時間は誰かしらいるはずですが、心配な人はゼミ長へメールをください。待っています。

by M1のY.O