2014年02月12日 00:29
PICの周波数調整用コードを書いた。
軽く説明しておくと、PICには(大抵)内蔵発振器があり、その周波数は(物によって)OSCCALレジスタの設定値で128段階などに微調整出来る(する必要があると言うべきか)。
周波数調整で問題なのが何を基準に合わせるかだが、今回はシリアル通信を使うことにした。
シリアル通信のボーレートを自動調整する事がよくあるが、それを逆に利用することで、クロック周波数が分かる。
動作のアルゴリズムは単純で、"?"の文字を受信するとそのパルス幅を数え、その数値を送信するだけである。それにボーレートを掛ければクロック周波数が分かるという仕組みだ。
なぜ"?"なのかだが、これは世のボーレート自動設定機能も大抵"?"を使っているのだが、実は作ってみるまで理由が分かっていなかった。
"?"のASCIIコードは0x3Fで、シリアルではスタート・ストップビットが付き、最下位bitから送信するので、0111111001となる。
この1が6連続と長く並んでいるので、この部分の長さを計って6で割るのだろうと最初は思っていたのだが、6は切りが悪いし、"@"なら0000000101と0が7つ並ぶのでこちらのほうが長い。また、0→1と1→0の遷移で時間が同じ保証がない。
ではどこを見るかというと、0→1の遷移が最初と最後にあるが、この間がちょうど8ビット時間と切りが良い。さらにその間に余計な0→1の遷移がない。
この条件を満たすには下位bitが1・上位bitが0でその間に切り替わりが1回のみである必要があるので、0x7F、0x3F、0x1F、0x0F、0x07、0x03、0x01のいずれかとなり、そのうち印字可能文字は"?"のみである。
世のボーレート自動設定のコードを見たことはないが、おそらくこれが"?"を使っている理由なのではないかと思っている。
概要を説明する。
まず使うPICは10F200である。これは最も機能の少ない石なので、プログラムを直して他の石で動くようにするのは簡単だろう。
ボーレートは300とした。これは10F200のタイマーで計れる最長時間がプリスケーラーを使って65536サイクルなので、8ビット時間がこれ以下になる値ということで決めた気がするが、実際の計り方を考えるとこれはあまり意味がなかった。まあいいや。
65536サイクルを計るわけだが、計数精度が1バイトでは心もとない。しかし2バイトで数えようとすると繰り上げ処理に時間を食ったりで途端に大変になるのでどうしようかと考えた結果、面白い方法を思いついた(のでブログに書きたくなった)。
アイディアの骨子はこうだ。
・まずオーバーフローしない程度の低分解能で大体の時間を計る
・もう一度今度は高分解能でオーバーフローを気にせず計る
・それを統合する
ここで上位バイトと下位バイトは1bit分重ねておく必要がある。そうでないと2回の誤差でちょうど繰り上がった時に判断ができない。
実際のコードでは、8ビット時間分の計測対象の前にちょうど1ビット時間分のスタートビットがあるので、これを低分解能で計っている。
あとは1回毎にOSCCALの設定値を2づつ(最小単位が2)変えて、計測結果とその時のOSCCALの値をシリアルで送信…しようと思ったが面倒なので1つ前のOSCCALの値とその時の結果を送信している。
シリアル送信のボーレートをきちんと合わせるのも面倒なので、1つ前の、しかも低分解能の計測結果を使っている。
なので正常に通信できないことを考えて、クロックが最大11%ずれていても送信できるモードを考えた。
・16進数を送信したいだけなので、下位4bitが正常に送れれば良い。
・しかし、ストップビットが正しくないと受け付けられない。
・であれば、上位bitをストップビットと同じ"1"にしておけば良い。
・16進数の0-Fを0xC0-0xCFとして送ることにしよう。
・0xC0は「タ」、0xCFは「マ」である。
・これをタマモードと名付けよう。
しかしなんか普通に送れたので使う機会がなかった。一応コードには残してあるが未チェックである。使うならストップビットの時間も長くしたほうがいい。
さてそれで計った生データがこちら。
グラフにしたのがこちらである。

これを見ると面白いのが、32個ごと(値は64ごと)に不連続な点が見える。折角なので差分もグラフにしてみると、16個ごとや8個ごとにも値が飛んでいるのが分かる。
たぶん、周波数の微調整のためにはCR発振器のCやRを変化させるために抵抗ラダーのような構成にしているのだろうが、その特性のずれが見えているのだろう。
コードは以下のとおり。無保証あずいず。
シリアルポートとの結線はこちらを参考にしました。
RS-232C - TTLレベルの簡易変換方法 (ELM by ChaN)
軽く説明しておくと、PICには(大抵)内蔵発振器があり、その周波数は(物によって)OSCCALレジスタの設定値で128段階などに微調整出来る(する必要があると言うべきか)。
周波数調整で問題なのが何を基準に合わせるかだが、今回はシリアル通信を使うことにした。
シリアル通信のボーレートを自動調整する事がよくあるが、それを逆に利用することで、クロック周波数が分かる。
動作のアルゴリズムは単純で、"?"の文字を受信するとそのパルス幅を数え、その数値を送信するだけである。それにボーレートを掛ければクロック周波数が分かるという仕組みだ。
なぜ"?"なのかだが、これは世のボーレート自動設定機能も大抵"?"を使っているのだが、実は作ってみるまで理由が分かっていなかった。
"?"のASCIIコードは0x3Fで、シリアルではスタート・ストップビットが付き、最下位bitから送信するので、0111111001となる。
この1が6連続と長く並んでいるので、この部分の長さを計って6で割るのだろうと最初は思っていたのだが、6は切りが悪いし、"@"なら0000000101と0が7つ並ぶのでこちらのほうが長い。また、0→1と1→0の遷移で時間が同じ保証がない。
ではどこを見るかというと、0→1の遷移が最初と最後にあるが、この間がちょうど8ビット時間と切りが良い。さらにその間に余計な0→1の遷移がない。
この条件を満たすには下位bitが1・上位bitが0でその間に切り替わりが1回のみである必要があるので、0x7F、0x3F、0x1F、0x0F、0x07、0x03、0x01のいずれかとなり、そのうち印字可能文字は"?"のみである。
世のボーレート自動設定のコードを見たことはないが、おそらくこれが"?"を使っている理由なのではないかと思っている。
概要を説明する。
まず使うPICは10F200である。これは最も機能の少ない石なので、プログラムを直して他の石で動くようにするのは簡単だろう。
ボーレートは300とした。これは10F200のタイマーで計れる最長時間がプリスケーラーを使って65536サイクルなので、8ビット時間がこれ以下になる値ということで決めた気がするが、実際の計り方を考えるとこれはあまり意味がなかった。まあいいや。
65536サイクルを計るわけだが、計数精度が1バイトでは心もとない。しかし2バイトで数えようとすると繰り上げ処理に時間を食ったりで途端に大変になるのでどうしようかと考えた結果、面白い方法を思いついた(のでブログに書きたくなった)。
アイディアの骨子はこうだ。
・まずオーバーフローしない程度の低分解能で大体の時間を計る
・もう一度今度は高分解能でオーバーフローを気にせず計る
・それを統合する
ここで上位バイトと下位バイトは1bit分重ねておく必要がある。そうでないと2回の誤差でちょうど繰り上がった時に判断ができない。
実際のコードでは、8ビット時間分の計測対象の前にちょうど1ビット時間分のスタートビットがあるので、これを低分解能で計っている。
あとは1回毎にOSCCALの設定値を2づつ(最小単位が2)変えて、計測結果とその時のOSCCALの値をシリアルで送信…しようと思ったが面倒なので1つ前のOSCCALの値とその時の結果を送信している。
シリアル送信のボーレートをきちんと合わせるのも面倒なので、1つ前の、しかも低分解能の計測結果を使っている。
なので正常に通信できないことを考えて、クロックが最大11%ずれていても送信できるモードを考えた。
・16進数を送信したいだけなので、下位4bitが正常に送れれば良い。
・しかし、ストップビットが正しくないと受け付けられない。
・であれば、上位bitをストップビットと同じ"1"にしておけば良い。
・16進数の0-Fを0xC0-0xCFとして送ることにしよう。
・0xC0は「タ」、0xCFは「マ」である。
・これをタマモードと名付けよう。
しかしなんか普通に送れたので使う機会がなかった。一応コードには残してあるが未チェックである。使うならストップビットの時間も長くしたほうがいい。
さてそれで計った生データがこちら。
1667D014678612663E1065F90E65B30C647B0A64350863F00663A904625E0262190061D4FE618EFC6079FA6035F85FF0F65FAAF45E62F25E1BF05DD6EE5D90EC5C5AEA5C13E85BD0E65B8BE45A40E259FBE059B7DE5872DC5823DA57DFD8579CD65658D4560FD255CAD05586CE5441CC540ACA53C6C85383C6523FC451F6C251B2C0506FBE502BBC4FB9BA4E76B84E32B64DEFB44DA8B24C63B04C23AE4BDEAC4BA8AA4A64A84A24A649E0A4499AA24858A048169E47D29C47849A46449846029645C09444799244399043F58E43B58C427D8A423D8841FB8641BA844072824030803FF0823FAF843FEE8640308840728A41BA8C41FB8E423D90427D9243B59443F79644379844799A45C09C46029E4644A04786A247D4A44814A64858A8499AAA49E2AC4A24AE4A66B04BA8B24BDFB44C21B64C65B84DA7BA4DEFBC4E32BE4E76C04FB8C2502BC4506FC651B2C851F6CA523FCC5383CE53C6D0540AD25441D45586D655CAD8560FDA5658DC579CDE57E1E05824E25871E459B7E659FCE85A41EA5B89EC5BCEEE5C15F05C5AF25D90F45DD6F65E1BF85E60FA5FAAFC5FEFFE603400607A02618F0461D30662190862600A63A90C63F00E643610647B1265B41465F916663E1867861A67D01C68161E685D2069A32269F1246A39266A7E286BC52A6C112C6C582E6D9E306DE5326E1E346E66366FAB386FF33A703C3C71843E71CB4072134273884473D046741648745E4A75AB4C75F34E76385077805277BA5478015678495879915A79DD5C7A255E7A6D607BB4627C03647C4B667D93687DDB6A7E286C7E706E7FB6707FFD72803674807E7681C478820B7A82577C839E7E83E47C842B7A83E478839E76825774820B7281C470807E6E80366C7FFD6A7FB5687E6E667E28647DDB627D93607C4B5E7C055C7BB45A7A6D587A255679DC5479915278495078024E77BA4C77814A763B4875F34675AB44745E4274164073D03E73883C72133A71CC38718436703C346FF1326FAB306E642E6E1E2C6DE62A6D9E286C58266C11246BC6226A7E206A381E69F11C69A31A685B1868161667D01467861266401065F90E65B30C647A0A64350863F00663A904625E0262190061D4FE618EFC6079FA6035F85FEFF65FAAF45E60F25E1BF05DD6EE5D91EC5C5AEA5C15E85BD0E65B8BE45A41E259FCE059B7DE5872DC5824DA57DFD8579CD65658D4560FD255CAD05586CE5441CC540ACA53C6C85383C6523FC451F7C251B2C0506FBE502BBC4FB9BA4E76B84E32B64DEFB44DA8B24C66B04C23AE4BDFAC4BA9AA4A67A84A24A649E2A4499AA24858A048169E47D59C47879A46459846029645C1
グラフにしたのがこちらである。

これを見ると面白いのが、32個ごと(値は64ごと)に不連続な点が見える。折角なので差分もグラフにしてみると、16個ごとや8個ごとにも値が飛んでいるのが分かる。
たぶん、周波数の微調整のためにはCR発振器のCやRを変化させるために抵抗ラダーのような構成にしているのだろうが、その特性のずれが見えているのだろう。
コードは以下のとおり。無保証あずいず。
シリアルポートとの結線はこちらを参考にしました。
RS-232C - TTLレベルの簡易変換方法 (ELM by ChaN)
list p=10F200
#include p10f200.inc
radix dec
; N/C+-v-+(GP3)
; Vdd| |Vss
; GP2| |N/C
; GP1+---+GP0
__CONFIG _MCLRE_OFF & _CP_OFF & _WDT_OFF
cblock 0x10
tmr32
tmr2
flag
cnt
waitcnt
waitcnt2
txdat
endc
;レジスタ使いまわし
#define temp waitcnt
#define temp2 waitcnt2
;プリスケーラー設定用
#define OPTVAL2 b'10010000' ;2:1
#define OPTVAL32 b'10010100' ;32:1
#define OPTVAL64 b'10010101' ;64:1
; ^/GPWU: DontCare
; ^/GPPU DontCare
; ^T0CS=0: Fosc/4,GP2=I/O
; ^T0SE Don'tCare
; ^PSA TMR0
; ^^^PS
#define FCLK .4000000 ;クロック周波数
#define BAUD .300 ;ボーレート
#define ASCEND 0
#define TAMA 0 ;タマモード(未使用)
#define SERIAL_WIDTH (FCLK/4 / BAUD / 32) ;シリアルの1ビット時間(プリスケーラー32)
init:
org 0x00
movwf OSCCAL
movlw SERIAL_WIDTH
movwf tmr32
clrf cnt
clrw
movwf GPIO
tris GPIO
bcf flag,ASCEND
mainloop:
movlw OPTVAL64
option ;ps=64
wait2bhigh: ;wait 2bit high pulse
btfss GPIO,3 ; wait ^
goto $-1
clrf TMR0
bcf STATUS,C
rrf tmr32,W
movwf temp
bcf STATUS,C
rrf temp,W
subwf tmr32,W; W=tmr32*0.75
btfsc GPIO,3 ; wait v
goto $-1
subwf TMR0,W
btfss STATUS,C
goto wait2bhigh
movlw OPTVAL32
option ;ps=32
count1bit:
btfss GPIO,3 ;wait ^ [0,3)
goto $-1
clrf TMR0 ;[2,5)+2
btfsc GPIO,3 ;wait v [0,3)
goto $-1
goto $+1
movf TMR0,W ;[4,7)
movwf tmr32
movlw OPTVAL2 ;ps=2
option
clrf TMR0 ;[8,11)
count8bit:
btfss GPIO,3 ; wait ^
goto $-1
btfsc GPIO,3 ; wait v [0,3)
goto $-1
nop
movlw 2 ;(8-4)/2
addwf TMR0,W ;[4,7)
movwf tmr2
movlw 2
btfss flag,ASCEND
movlw -2
addwf OSCCAL,F
;方向転換
movlw 0x80
subwf OSCCAL,W
btfsc STATUS,Z
bsf flag,ASCEND
movlw 0x7E
subwf OSCCAL,W
btfsc STATUS,Z
bcf flag,ASCEND
;カウント上位を補正
rlf tmr2,W ;tmr2,7 -> C
rlf 0x0F,W ;Cが欲しいだけ
xorwf tmr32,W
movwf temp
btfss temp,0 ;if tmr2,7!=tmr32,0 then do
goto skipadj
;補正実行
btfsc tmr2,6
decf tmr32
btfss tmr2,6
incf tmr32
skipadj:
;結果送信
swapf OSCCAL,W
call tx232
movf OSCCAL,W
call tx232
swapf tmr32,W
call tx232
movf tmr32,W
call tx232
swapf tmr2,W
call tx232
movf tmr2,W
call tx232
goto mainloop
;シリアル送信
tx232:
andlw 0x0F
movwf txdat
#if TAMA
movlw 0xC0
#else
movlw 6
addwf txdat,W
movlw '0'
btfsc STATUS,DC
movlw 'A'-10
#endif
bsf GPIO,2 ;#
addwf txdat,F
nop
call wait32n
bsf cnt,3
txloop:
movf GPIO,W
andlw 0x0B
rrf txdat,F
btfss STATUS,C ;極性反転
iorlw 0x04
movwf GPIO ;# >9, >9>
call wait32n
decfsz cnt,F
goto txloop
call wait6
bcf GPIO,2 ;# >3
call wait32n
retlw 0
wait32n: ;wait 3+32n
movf tmr32,W
movwf waitcnt
waitloop:
nop
movlw .9
movwf waitcnt2
waitloop2:
decfsz waitcnt2,F
goto waitloop2
decfsz waitcnt,F
goto waitloop
retlw 0
wait6:
goto $+1
retlw 0
end
2014年02月02日 07:06
前回から1週間。時間が空いた時に復習のためにちまちま本を読み返してます。
中断前は大体1記事1日ペースだったんですが、こうちまちま進めていると作業日数が分からなくなってちょっと残念。
現在6日目まで読み返してみて、ブートの流れと、あとメモリのセグメントあたりがより深く理解できた気がします。
ちょっと自分の理解を深める意味も込めてPCの起動時の流れをまとめてみます。
さてつまり、第1セクタの512バイトは何もプログラムを書かなくてもBIOSが頑張って読んでくれます。
フロッピーでもHDDでもSDカードでも、対応しているならBIOSでブートドライブに設定するだけで同様に使えるのです。
ということは、

こういうことで。
これはMBRしか使っていない1日目のコードを文字列だけ変えてSDカードに書いたものです。イメージ書き込みに使ったソフトはなんだか有名っぽいWin32DiskImager.exeというもの。
本のプログラムはフロッピーを使うことを想定しているので、実機動作確認のためにWindows98時代の3DNow!なノートPCを使っていましたが、うまくSDで動けば手軽なEeePCでも動作確認できて楽になりそうです。
そう思ってもうちょっと調べるといい情報が見つかりました。
http://www2192ue.sakura.ne.jp/~uaa/gomitext/2011/20111203/
BIOSにフロッピーを読ませるときはCHS(シリンダ・ヘッド・セクタ)の3値で指定するんですが、フラッシュメモリを読むときはどうすればいいのかと思っていたらどうも同じように読めるようです。
ただし同じメモリデバイスに対してもBIOSによって数値が変わってくるようで、そこの対処は必要そうです。
今のところ本のプログラムではフロッピーのシリンダ・ヘッド・セクタ数は固定のものとして扱っています。
しかし、データの最初の部分、0シリンダ目・0ヘッド目のフロッピーとSDでセクタ番号が同じ部分はそのまま読めそうです。
試しに作業フォルダにあった一番新しいものでやってみると、見事に動きました。(しかしこの画面記憶に無い。次にやる所だったかも)

ただ実はこれが動くのは不思議な話で、このイメージは実質30kBほどあるのに対し、フロッピーのシリンダあたりのセクタ数は18なので、9kBまでしか正常に読めないはずなのです。
これを試したときは上記資料を見て0x3Fセクタや0x20セクタは読めるはずと思っていたんですが、よく考えたらフロッピーの方が少ないからそこまでしか読まないはずで。
謎が残りましたが、まあその方がモチベーションが湧くということで、今日はここまでとします。
◆ここ1週間くらいの成果◆
ブートの流れの理解が深まった
◆今日の成果◆
SDからブートできた
中断前は大体1記事1日ペースだったんですが、こうちまちま進めていると作業日数が分からなくなってちょっと残念。
現在6日目まで読み返してみて、ブートの流れと、あとメモリのセグメントあたりがより深く理解できた気がします。
ちょっと自分の理解を深める意味も込めてPCの起動時の流れをまとめてみます。
- PCを起動すると、CPUは仕様で決まったアドレスを読みに行く。
- そこにはBIOSのROMがつながっており、CPUはそこに書かれた機械語を実行する。
- BIOSのプログラムの指示によりCPUはI/Oを制御する。
- それによりマザーボード上のチップセットか何かが働き、BIOSで設定されたブートドライブの最初のセクタ(マスターブートレコード: MBR)がメモリに読み込まれる。
- メモリ上のMBRの内容の頭に処理が飛び、CPUはそこに書かれた機械語を実行する。
- MBRのプログラムはBIOSにシステムコール(と呼んでいいの?)を発行することで他のセクタを読んだりできるので、そこのプログラムを実行したりする。
- そこのプログラムがOS。
さてつまり、第1セクタの512バイトは何もプログラムを書かなくてもBIOSが頑張って読んでくれます。
フロッピーでもHDDでもSDカードでも、対応しているならBIOSでブートドライブに設定するだけで同様に使えるのです。
ということは、

こういうことで。
これはMBRしか使っていない1日目のコードを文字列だけ変えてSDカードに書いたものです。イメージ書き込みに使ったソフトはなんだか有名っぽいWin32DiskImager.exeというもの。
本のプログラムはフロッピーを使うことを想定しているので、実機動作確認のためにWindows98時代の3DNow!なノートPCを使っていましたが、うまくSDで動けば手軽なEeePCでも動作確認できて楽になりそうです。
そう思ってもうちょっと調べるといい情報が見つかりました。
http://www2192ue.sakura.ne.jp/~uaa/gomitext/2011/20111203/
BIOSにフロッピーを読ませるときはCHS(シリンダ・ヘッド・セクタ)の3値で指定するんですが、フラッシュメモリを読むときはどうすればいいのかと思っていたらどうも同じように読めるようです。
ただし同じメモリデバイスに対してもBIOSによって数値が変わってくるようで、そこの対処は必要そうです。
今のところ本のプログラムではフロッピーのシリンダ・ヘッド・セクタ数は固定のものとして扱っています。
しかし、データの最初の部分、0シリンダ目・0ヘッド目のフロッピーとSDでセクタ番号が同じ部分はそのまま読めそうです。
試しに作業フォルダにあった一番新しいものでやってみると、見事に動きました。(しかしこの画面記憶に無い。次にやる所だったかも)

ただ実はこれが動くのは不思議な話で、このイメージは実質30kBほどあるのに対し、フロッピーのシリンダあたりのセクタ数は18なので、9kBまでしか正常に読めないはずなのです。
これを試したときは上記資料を見て0x3Fセクタや0x20セクタは読めるはずと思っていたんですが、よく考えたらフロッピーの方が少ないからそこまでしか読まないはずで。
謎が残りましたが、まあその方がモチベーションが湧くということで、今日はここまでとします。
◆ここ1週間くらいの成果◆
ブートの流れの理解が深まった
◆今日の成果◆
SDからブートできた