たまりば

パソコン・インターネット パソコン・インターネット三鷹市 三鷹市

GBAはじめました。
2015年03月23日 02:04

GBA_HelloWorld

ファミコンに続き、ゲームボーイアドバンス(以下GBA)の開発を始めてみた。3年前に。
Twitterのログやファイルの更新日時を見るに、2012年5/30~6/6くらいにやっていたようである。
なぜ今頃書くのかというと、そろそろネタを書きためているテキストファイルの見通しが悪くなってきたので整理するためだ。前回のCボタンユニットもそれである。

開発環境はdevkitProというもの。これが環境を整えるのがすごく楽だった。インストーラの指示に従ってあとは環境変数を書くだけ。
言語はとりあえずC言語。まあARMくらいの性能になると敢えてアセンブラを使う理由も薄いだろう。
ARMの命令セットは条件実行機能が面白そうだから使ってみたいんだけどね。

GBAではフルスクリーンの1枚絵を何の制限もなく表示できる。GBカラーやファミコンではできなかった芸当だ。
GBA_15bitカラー1枚絵
GBAの画面モードは色々あるが、これはGBAの最大色数である15bitカラー表示。背景数は1枚のみである。
使用した画像データはこちら。
GBA用画像_青肌
GBAの色データはxBBBBBGGGGGRRRRRであるが、GIMPで出力する15bitカラーはxRRRRRGGGGGBBBBBしか出せず、RとBを入れ替えて書き出しているため、不気味な色になってしまった。上下反転はBMP画像の特徴。

もう1枚、こちらは256色インデックスカラー。このモードでは背景面が2枚持てる。
GBA_インデックスカラー1枚絵

次はGIF画像を表示したり簡単な落ち物ゲームを作ろうかと思っていたようだが力尽きたようだ。
その後最近になって(14年8月~11月あたり)、1カートリッジプレイで無改造で実機で動作させることを目指してPICで通信実験などしていた。
そのうち再開したい。  

  • ファミコンの縦解像度224px説の考察
    2015年02月22日 04:49

    ファミコンの解像度は横256px×縦240pxだが、縦を224pxとする説が根強い。
    中には「内部的には240px」の但し書きをつけたり、「約」224pxとするものもあるが、224という値を書いている時点で五十歩百歩だ。

    「内部的」の解釈も曖昧だが、たぶん次のような認識なのだろう。
    「メモリ上には240px分の領域が用意されているが、出力されるのはそのうちの224px分のみ」
    そのようなことがないのは、Google画像検索で適当なファミコンソフト名を「640×480」や「720×480」の解像度指定で検索すれば分かるだろう。

    ただ似たような現象はあって、240px分が出力されても、普通のアナログTVでは実際に画面に表示されるのはその内の85%や90%程度である。
    (詳しくは「タイトルセーフ・アクションセーフ」「オーバースキャン」あたりで検索するとよい)
    ではそのことを言っているのかというと、2つの点からおかしい。
    第1に、どの程度の範囲が表示されるかはTVによって異なるので、224pxという値は出てこない。「約」であっても(10進法では)切りの悪い値にするのは不自然だ。
    第2に、TVに映らないという理由で解像度を224pxなどの値にするなら、同様にアナログTVに出力するスーパーファミコンやプレイステーションも240pxや480pxを出力できないことになろうが、これらの解像度には239pxや480pxという値が平気で書かれている。

    ではこの224という数字の出処はどこかだが、スーパーファミコンにはどうやら224pxモードがある。
    これこれを見ると、$2133のbit2で縦224pxと239px(448pxと478px)の切り換えができるようだ。
    またプレイステーションについても、興味が無いのであまり調べていないが、224pxや448pxのモードがあるような記述が見られる。

    もう1つの可能性として、エミュレータのオプションから来ているのかもしれない。
    エミュレータには縦240pxのうち224pxのみを表示するモードが付いている物がある。
    エミュレータ240pxモード1エミュレータ240pxモード2
    (というよりどちらもデフォルトが224pxで、240pxモードがある)

    まとめると、
    ・そもそもアナログTVの仕様として映像信号のうち画面には表示されない部分がある
    ・スーパーファミコンには縦224px出力のモードがある
    ・ファミコンのエミュレータには出力のうち縦224pxのみしか表示しないモードがあるものがある
    このあたりを混同した結果、ファミコンの縦224px説が生まれたのではないだろうか。


    さて本題の考察は以上だが、なぜエミュレータには224px設定が付いているのかの説明をしよう。
    まずこちらの画像をご覧頂きたい。
    ファミコン縦スクロール回り込み
    ドラゴンクエスト3で縦スクロール中に撮った画像だ。これは互換機だが、ファミコン実機でも同様の表示になるはずである。
    一番下の数ドットが、本来下に続くマップではなく、最上段からはみ出した分が下に回りこんでいるのが見て取れるだろう。

    これはファミコンの仕様で、内部的に2画面分を縦または横につなげた仮想画面をスクロールしているため、縦か横どちらか、この場合縦は1画面分の幅しかなく、スクロール時に上下にまたがるキャラクタは表示が乱れるのである。
    この乱れは、上記の通り通常のアナログTVでは画面外で表示されない部分であり、ユーザーの目にはいることはまずない。
    しかしPCのキャプチャカードで全ての出力を取り込んだり、エミュレータで全てを表示すると見えてしまうわけだ。
    そこでエミュレータでは、この本来表示されない領域を隠す設定が付けられた。
    どれだけ隠せばいいかといえば、背景面を構成するキャラクタは16×16単位で設定できる(正確には色が16×16、キャラが8×8)ので、うまく作られたプログラムなら半分の8pxづつだけ隠せばよいはずである。
    (うまく作られたプログラムばかりでもないと思うが)

    また、横についても同様の乱れが起こる。さらに縦と違う点として、ファミコンは横方向はもともと映像表示期間の90%強しか使っていないため、少し広めのTVではこの乱れが画面内に見えてしまう場合がありうる。
    おそらくそのために、ファミコンには左のみ8pxを表示しない設定がある(両側でないのはなぜだろう)。上のドラクエ3の画像もそうなっている。左右の黒帯の幅が違うことから分かるだろう。
    これを考えると、ファミコンの解像度は248×240と256×240の2モードあるとすべきだろう。

    追記:
    左8pxを表示しないモードについて、スクロール時の乱れを見せないためだろうと推測したが、おそらく違った。
    ファミコンのスプライトは横8px幅であり、そのX座標は0-255の値をとり、0で左端、248で右端、249-255では右端からはみ出す位置になる。つまり画面の右端からスプライトの一部分が見える表現はできるが、左端で同じことはできない非対称性がある。
    ここで左端8pxを背景・スプライトともに隠すことで、左端からスプライトの一部分が見える表現が可能となる。
    両側でない理由もつくのでこれがこの機能を付けた理由で間違いないだろう。乱れを見せないためにも使えたかもしれないが、少なくともメインの目的ではないだろう。
    なおこちらによればMSXやSG-1000ではスプライトの表示位置を32pxずらす機能があったらしい。
      

  • EEPROM書換え上限テスト
    2014年11月17日 08:06

    PICマイコンに内蔵されていたりするEEPROMには書換え回数に限界がある。
    PIC16F628A内蔵のものでは、書換え耐性は最小10万回、標準100万回となっている。
    これをテストしてみよう。

    なおこれは既にやっている方がいる。
    参考: 自己満足系 「PIC耐久試験」

    同じことをやっても芸が無いので、書込みデータを変えてみた。
    EEPROMの書き込み手順はまず0xFFにクリアしたのち、必要なbitのみ0にするという手順を踏むそうだ。
    であれば「0」のみを書き込み続けた場合と「0」「1」を交互に書き込んだ場合では「0」のみの方が早く限界に達するはずだ。
    これはMicrochip社のアプリケーションノート「AN537」を見てもそう書いてある。(figure4)
    これを確かめるため、書込みデータは「0x5C」と「0xAC」の交互にした。上位(7bit目~4bit目)が0/1交互、3,2bit目が1固定、下位(1,0bit目)が0固定だ。
    これが意外な結果になるのだが…。

    プログラムは最後に載せるが、内容を説明すると以下のとおり。
    ・電源ONでまずEEPROMの内容を全て出力し、その後待機状態に入る
    ・スイッチを押すとLEDを点灯させて開始を示し、1秒後に消灯して処理を開始
    ・書込み後に読み出してベリファイし、値が異なっていればエラー表示をして終了
    ・0x5Cと0xACの2回の書き込みを1セットとし、256セットごとにEEPROMに書込み回数を保存
    ・256セットごとにスイッチを監視して、一時中断が可能

    装置はこんな感じ。
    EEPROM破壊装置
    下の3つは置き場が無くて挿してあるPICで、回路に無関係である。上のコンデンサについては後述する。
    橙の線はスイッチ代わり。LEDは開始・終了のインジケーター。もう1個エラー表示もつけようと思ってプログラムもそう書いたのだが、よく考えてみれば終了LEDだけでことが済んだのでつけなかった。
    余談だが、LEDが青なのはこの試験中に青色LED開発者がノーベル賞を取ったのでなんとなく元は赤だったのを変えてみたためだ。

    試験を開始する。
    電源は仕様上3V以上必要なのだが、いつもEneloop2本で動かしているので今回もそれでやってしまった。どうせ厳密な試験をするつもりもなし、動いてるので問題ないだろう。
    まず少々処理が進んだところで一時中断し、書き込み速度をチェックする。
    どうやら1回あたり5msほど掛かっている。時間からして書き込みは行われているようだ。
    (プログラムの製作中は書き込み操作をコメントアウトしていたが、この時はマイクロ秒単位でループが回っていた)
    ここから破壊予定を計算してみよう。
    100000*5/1000/60=8.33
    1000000*5/1000/60/60=1.39
    より、最小の10万回ならわずか8分強、標準の100万回でも1時間半ももたない計算になる。
    上記の先人の使った16F84は標準1000万回のところ2300万回で破壊に至ったということなので、同じ割合なら3時間ほど。
    何日も点けっぱなしにするのは面倒なのでこれは楽でいい。
    と思ったのだが…。

    試験はTwitterにつぶやきながら行っていたので、ログを見ながら進行状況を書いていく。
    10/05 16:55 試験開始
    10/05 17:13 開始20分で2*0x1A400回 (※書き込み2回ごとに1カウントしている)
    10/05 18:43 標準値を突破、1.3M回超
    10/05 20:04 標準寿命の倍を超えたがまだ動く
    10/05 21:12 3.1M回を超える
    10/05 22:15 2*1D6600回
    10/05 23:56 5M回
    10/06 01:51 6.5M回
     標準寿命の2,3倍程度だろうと思っていたため、寝る前に終わる予定でいたので困る。
    10/06 06:04 結局点けっぱなしで朝、9.5M回
     標準寿命の10倍近くでまだ正常と主張しているのでそろそろプログラムのミスが疑われ始める。
     とりあえず中断し、電池を充電する。
    (10/06 21:38 再開時刻記録忘れ。遅くともこの時刻)
    10/07 00:22 11,661,312回
     本格的にプログラムのミスが心配になる。
    10/07 01:44 ついに終了

    以上、延べ17.25時間掛けて、12,663,170回目でエラー発生。なんと標準値の12倍ももつという記録が出た。
    書込み回数の記録をグラフにするとこんな感じ。
    EEPROM書込み回数グラフ
    記録がいい加減(「標準の倍を超えた」とか)なわりには意外と綺麗なグラフになっている。温度で書込み時間に違いが出たりするかと期待していたが、少なくともこのレベルの記録には残らないようだ。

    エラーの内容だが、8Cが読めているので、ACを書き込んで8Cが読めた、つまり5bit目が異常になったエラーのようだ。これは不思議なことで、前述のとおり常に0を書いている下位2bitが真っ先にやられるものと予想していた。
    それはそうとここから先は未知の領域なのでとりあえず試験を続ける。
    まず意外だったのが、一度8Cが読めた箇所を20回ほど読んでみても正しくACが読めることだ。どうやらEEPROMのエラーというものは、一度エラーが出たらそれっきりというものではなく、「書いた値が稀に正常に読めない」という症状を示すようだ。
    その後再度同じプログラムで走らせても、しばらくの間は正常にベリファイが走る。やはりエラーは確率的なもののようだ。
    これを考えると、テストのやり方は正しくなかったと言える。1千万回で初めてエラーが出たとして、それ以前から確率的に読み取れない状態になっていたのだろうから、ある程度の回数書き込むたびに読み出しが正しくできるかのテストを数百回なり数千回なり読み出してみて調べるべきだろう。まあこれは今後の研究課題ということで。

    さてその後も同じプログラムで何度も走らせてみた。結果がこれだ。
    EEPROM書込みエラー記録
    プログラムを走らせ、エラーが発生して停止するまでに書込みが行われた回数を毎回記録している(最後除く)。
    3色の◆の部分が最初と同じプログラムで走らせたものである。
    傾向として最初の1266万回から回を追うごとに減っていき、15回目あたりからエラーまでの書き込み回数がほぼ一定になっているのが見て取れる。これは512回である。
    最初に書いたとおり、このプログラムでは2回×256セットごと、すなわち512回ごとにEEPROMに書込み回数を保存している。つまりこの書込み直後の読み込みが失敗しやすいようである。
    この原因として書込みによって電圧が下がっている可能性を考え、コンデンサを付けてみた(赤部分)。するとしばらくの間書き込みエラーが起こりにくくなったような気もするが、またコンデンサを外しても14万回とかなりの回数が出たのでよく分からない。
    もう少し確かめてみたかったが、そうこうしている間に別のエラーが発生した。
    部分が、読めた値が2Cであった部分である。AC→2Cになったものであろうから、7bit目が異常になったようだ。またも上位bitからエラーが発生した。このとき累計書込み回数1663万回。
    その後数回2Cが続き、再び8Cでのエラーが発生。
    ここで、プログラムを変更する。異常になった2つのbitをマスクして、他のbitのみを見るようにした。これが紫部分。
    変更後最初のエラーまでに64万回、累計1765万回。1Cが読めたので、5C→1Cで6bit目が異常。
    その後2回ほど試したのち、またマスクを変更。3つのbitをマスクする。これが水色部分。(なぜか凡例だけマークが「*」になっているが、Excelが異常で凡例のマークをどうやっても変更できなかった)
    変更後最初のエラーまでに209万回、累計1974万回。4Cが読めたので、5C→4Cで4bit目が異常。
    これで上位4bitが全て異常になり、下位4bitには異常が見られないという状況になった。
    不可解だがとりあえず続けよう。マスクを変更。上位4bitをマスクするようにした。
    すると、いつまで走らせても一向にエラーが起こらない。累計1億回、すなわち標準寿命の100倍に達してなおエラーが出なかったので、あきらめて試験を終了することにした。この時点が橙である。

    以上まとめると、0/1を交互に書き込んだ場合は標準寿命の10~20倍で最初のエラーが発生し、0のみまたは1のみを書き込んだ場合は標準寿命の100倍を超えてもエラーが発生しないという結果になった。
    資料から予想した結果と違っており不可解だが、この結果から考えられることとして、PICのEEPROMは前回と同じ値を書き込むときは消去・書込み動作を行わないのだろうか。
    EEPROMはバイトごとに消去が行われるものと思っているのだが、bitごとの消去ができるものもあるのだろうか。この辺も今後の研究課題である。

    また、エラー発生時の累計回数の生データを見ると1つ不思議なことが分かる。以下がそのデータだ。
    609CC0, 6545D4, 675110, 67C8C0, 6A3690, 6A3700, 6AB598, 6AB600, 6AB710, 6AB800, 6AC59C, 6B08C0, 6B0D58, 6B14D0, 6B1500, 6B1600, 6B1700, 6B1800, 6B1900, 6B1A00, 6B1B00, 6B1C00, 6B1D00, 6B1E00, 6B1F84, 6B2048, 6B2100, 6B2200, 6B2304, 6B2400, 6B2500, 6B2600, 6B2700, 6B3380, 6CE7D8, 6CE800, 6D0904, 6D2590, 6D2654, 6D2710, 6D2800, 6D4184, 6D4200, 6EB540, 702D10, 702E10, 702F00, 703000, 745114, 7A5C14, 7A5D10, 7A5E04, 7D1404, 7D1504, 7E2B0C, 7E2C00, 7E2D00, 7EE104, 7EE200, 7EE30C, 7EED00, 81C300, 86A514, 86A600, 86A70C, 969F00
    1の位に0,4,8,Cと4の倍数しか現れていない。00になるのはEEPROM書込み直後なので分かるとして、4の倍数、すなわち8回ごとにエラーが起こる理由が分からない。内部構造に原因があるのだろうか。今後の研究課題だ。研究課題多いなあ。

    最後にコード。
        list p=16f628a
        #include p16f628a.inc
        radix dec
        __CONFIG _INTOSC_OSC_NOCLKOUT & _WDT_OFF & _PWRTE_ON & _CP_OFF & _LVP_OFF & _MCLRE_ON & _BODEN_OFF

        radix dec


    ;     RA2 +-v-+ RA1
    ;     RA3 |   | RA0
    ;     RA4 |   | RA7
    ; Vpp RA5 |   | RA6
    ;     GND |   | Vdd
    ;     RB0 |   | RB7 PGD
    ;     RB1 |   | RB6 PGC
    ;     RB2 |   | RB5
    ;     RB3 +---+ RB4

    ; EEPROM制御がしやすいよう、変数はbank1にとる
        cblock 0xA0
            targetaddr
            dataaddr
            cnt0,cnt1,cnt2
            cnt232
            dat,datbuf
            waitcnt0,waitcnt1
        endc
    ; 結局使わなかった割り込み退避用変数
        cblock 0x70
            wbuf
            sbuf
        endc


    #define TESTPTR 0x00
    #define MASK 0xFF ;結果をマスクするときここを変える

    ;pin
    #define TXPIN232 0
    #define BUTTON 1
    #define ERRLED 2
    #define STOPLED 3

        org 0
        goto init

        org 4
        retfie

    init:

        bsf STATUS,RP0 ;BANK1
        movlw b'00000011'
        ;       ^/pull up enable
        ;        ^int edge
        ;         ^tmr0 clock source 0:internal
        ;          ^tmr0 source edge
        ;           ^prescaler assignment 0:tmr
        ;            ^^^prescaler rate
        movwf OPTION_REG
        movlw 1<<BUTTON
    ; 間違ってPORTBの値を設定する前にTRISBを設定しているのでたまにシリアル出力の先頭が化けることに後で気づいたが直し忘れた
        movwf TRISB
        bcf STATUS,RP0
        movlw 0x07
        movwf CMCON
    ; EEPROMの制御のためコードはすべて全てbank1で走らせる
    ; そのため変数はすべてbank1にとった
    ; bank0のレジスタでPORTBだけ必要なのでINDFをPORTBにしておく
        banksel 0x80 ;bank1
        movlw PORTB
        movwf FSR ;INDF=PORTB
        clrf INDF
        clrf cnt232
    ; EEPROMに保存してある書込み回数カウントを取得
        movlw (~TESTPTR)&0x7F
        movwf targetaddr
        movlw TESTPTR*4
        movwf dataaddr
        
        movf dataaddr,W
        movwf EEADR
        bsf EECON1,RD
        movf EEDATA,W
        movwf cnt2
        incf dataaddr,W
        movwf EEADR
        bsf EECON1,RD
        movf EEDATA,W
        movwf cnt1
        movf dataaddr,W
        addlw .2
        movwf EEADR
        bsf EECON1,RD
        movf EEDATA,W
        movwf cnt0
    ; 書込み回数カウントを…と思ったが面倒なのでEEPROM全体をシリアルに出力
        movlw '/'
        call tx232
    showeep:
        clrf EEADR
    showeeploop:
        bsf EECON1,RD
        movf EEDATA,W
        call tx232hex
        incf EEADR,F
        btfss EEADR,7
        goto showeeploop
    ; スタート待ち
        btfsc INDF,BUTTON
        goto $-1
    ; LEDを1秒点灯させスタート表示 (停止表示用だった名残が名前に見える)
        bsf INDF,STOPLED
        
        call wait1s
        
        bcf INDF,STOPLED

    main:
        movf targetaddr,W
        movwf EEADR

    mainloop:
    ; データAを書込み、読み取り、比較
        movlw 0x5C ;データA
        call writeeep
        clrf EEDATA
        bsf EECON1,RD
        movlw 0x5C
        xorwf EEDATA,W
        andlw MASK
        btfss STATUS,Z
        goto err

        movlw 0xAC ;データB
        call writeeep
        clrf EEDATA
        bsf EECON1,RD
        movlw 0xAC
        xorwf EEDATA,W
        andlw MASK
        btfss STATUS,Z
        goto err

        incfsz cnt0
        goto f
    ; 256カウントごとの操作
    ; 上位カウント
        incf cnt1,F
        btfsc STATUS,Z
        incf cnt2,F
        bcf STATUS,C

    ; カウント値を保存
        movf dataaddr,W
        movwf EEADR
        movf cnt2,W
        call writeeep
        
        incf dataaddr,W
        movwf EEADR
        movf cnt1,W
        call writeeep
        
        movlw 0x02
        addwf dataaddr,W
        movwf EEADR
        movf cnt0,W
        call writeeep

    ; ボタンを確認し、押されていれば中断
        btfss INDF,BUTTON
        goto stop

    f:
        goto mainloop

    ; 書込みエラーが発生したら、カウント下位および読み取った値を書き込んで、LEDを点灯させ停止
    err:
        bsf dataaddr,1
        incf dataaddr,W
        movwf EEADR
        movf EEDATA,W
        call writeeep
        movf dataaddr,W
        movwf EEADR
        movf cnt0,W
        call writeeep
        
        bsf INDF,ERRLED

    stop:
        bsf INDF,STOPLED
        goto $

    ; EEPROM書込みサブルーチン
    ; WをEEADRで示すアドレスに書き込む
    writeeep:
        movwf EEDATA
        bsf EECON1,WREN
        movlw 0x55
        movwf EECON2
        movlw 0xAA
        movwf EECON2
        bsf EECON1,WR
        btfsc EECON1,WR
        goto $-1
        return

    ; Wを16進表記でシリアル出力 (IOピン直結)
    tx232hex:
        movwf datbuf
        swapf datbuf,W
        call sendnibble
        movf datbuf,W
        call sendnibble
        return

    sendnibble:
        andlw 0x0F
        movwf dat
        movlw .6
        addwf dat,F
        movlw '0'-.6
        btfsc STATUS,DC
        movlw 'A'-.10-.6
        addwf dat,W
    ; Wをシリアル出力
    tx232:
        movwf dat
        bsf INDF,TXPIN232
        bsf cnt232,3
    tx232loop:
        rrf dat,F
        movf INDF,W
        andlw ~(1<<TXPIN232)
        btfss STATUS,C
        iorlw 1<<TXPIN232
        movwf INDF
        decfsz cnt232,F
        goto tx232loop
        goto $+1
        goto $+1
        nop
        bcf INDF,TXPIN232
        
        retlw 0

    ; 1秒くらい待つ
    wait1s:
        clrf waitcnt0
        clrf waitcnt1
    waitloop0:
    waitloop1:
        goto $+1
        goto $+1
        goto $+1
        goto $+1
        goto $+1
        goto $+1
        decfsz waitcnt1
        goto waitloop1
        decfsz waitcnt0
        goto waitloop0
        
        return

    ; 初回のみここのコメントを外してビルドしEEPROMのデータ保存領域を0で初期化する
    ;    org 0x2100
    ;    dt 0,0,0,0,0,0
        
        end
      

  • 最弱のPICマイコンでカレンダー_ちょっと短縮
    2014年11月02日 04:03

    先日の「最弱のPICマイコンでカレンダー」だが、ちょっと思いついてコードを短縮してみた。いい感じにキモくなったと思う。

    変更箇所は2点。
    【シリアル通信】
    元のコードではRS232Cシリアルの送受信コードをそれぞれ書いていたが、これを1つにまとめることにより短縮した。
    コア部分がこちら。1つのループの中で1bitの送信と受信を同時に行うようになっている。
    loop232:
        movlw 0xF8
        addwf GPIO,W  ;0xF8を足すことでGPIO[3]をCに入れる
        movlw 1<<TXPIN
        rrf dat232,F  ;ローテートでCをバッファに、バッファの反対端をCに
        btfsc STATUS,C
        xorwf GPIO    ;送信データに応じてGPIOの送信ピンをトグル
        decfsz cnt232
        goto loop232
    具体的な挙動は次のようになる。
    ・送信時
    バッファに送信データを入れておき、スタートビットを送信してからこのループで1bitづつ送信する。
    (なお時間の都合でXORでトグルしているので、事前にバッファに入れる値を調整しておく必要がある。)
    同時に受信操作も行われるが、特にスタートビットに合わせているわけではないので、無意味な値が取れる。
    ・受信時
    バッファに全0を入れておき、スタートビットの受信を待ってからこのループで1bitづつ受信する。
    同時に送信操作も行われるが、スタートビットも出さずデータが全0なので何も起こらない。

    これで2つが1つになったので半分くらいに縮んでほしかったのだが、前処理が増えたせいで計30ワード→25ワードとわずかな短縮に留まった。まあ縮んだだけよしとしよう。

    【月データ】
    元のコードでは月ごとに「当該月の日数」と「基準の年の当該月1日の曜日」の2つのテーブルを持っていた。
    しかし月の日数は28~31、曜日は0~6しか値を取らないので、それぞれ1バイトとるのは無駄が多い。
    そこで2つのデータを1バイトにまとめてみたのだが、最初は単純に上下4bitづつに分け、月の下位4bitと曜日を表そうとしていた。
    しかし途中でもっといい方法を思いついた。
    ・下位bitで月を表す。
    ・残りのビットを適に設定して、全体でmod7で曜日を表す数にする。
    というものだ。なおmodの計算の都合上全体の数は132を超えられないので、最上位bitは実質0固定。そして曜日に3bit使うので、月には4bitしか残らず、あとで0x10を足す形になった。
    それでできた値がこれだ。
    getmonthattr:
        addwf PCL,F
        dt .95, .28, .63, .94, .47, .78,
        dt .31,.111, .30, .95,.126, .79,
        dt .31,.125

    さらにmodの計算ルーチンを変えて1命令短縮した。
    元は「7を引き続け、0を下回ったらループを抜け、答えを0~6にするため7を足して完了」というものだったが、最後の7を足す部分が無駄である。
    そこで「7を足し続け、オーバーフローしたら完了」に変えた。このため256%7=4だけ値を補正しておく必要がある。

    これにより、テーブルが1つ減って15ワード減、読み取り部分はなんだかんだで同命令数、mod7の1命令減があって計16ワードとかなりの短縮になった。


    この2点の変更で188ワード→167ワードとなった。
    PICのコードをバイトで数えることはあまりしないのだが、計算してみると167*1.5=250.5バイト。おお、256バイトを切っている。

    なお、感覚としてはもう数命令は縮められそうな気はするが、縮めたところで別に他に入れる機能もなし、面白いことは無いかなと思うので今のところやる気はしない。
    とりあえず「Y?>」「M?>」のメッセージを無くすだけで13ワード縮む。

    全コードは以下。
        list p=10F200
        #include p10f200.inc
        radix dec

        __CONFIG _MCLRE_OFF & _CP_OFF & _WDT_OFF

        cblock 0x10
            dat232
            yearh, yearl, month
            cnt, dowcnt, daycnt, cnt232
            day
            temp
        endc

    ;ピン
    #define TXPIN 0
    ;RXPINは3固定

        org 0
    init:
        movwf OSCCAL ;クロック補正値セット
        movlw b'11001000'
        ;設定データ。プルアップOFF、他Don'tCare
        ;ちょうど下位3bitがDon'tCareなのでI/O設定と共用
        clrf GPIO ;出力ピンを全Lowに
        tris GPIO ;I/Oモード設定
        option ;設定

    main:
        clrf cnt
        clrf cnt232

    ;"Y?>"
        movlw 'Y'
        call tx232
        movlw '?'
        call tx232
        movlw '>'
        call tx232
       
    ;年4桁受信: abcd
    ;年上位 = 10a+b ≡ 2a+b
        call rx232
        movf dat232,W
        movwf yearh ;*1
        addwf yearh,F ;*2
        call rx232
        movf dat232,W
        addwf yearh,F
    ;年下位 = 10c+d
        call rx232
        movf dat232,W
        movwf yearl ;*1
        bcf STATUS,C
        rlf yearl,F ;*2
        rlf yearl,F ;*4
        addwf yearl,F ;*5
        rlf yearl,F ;*10
        call rx232
        movf dat232,W
        addwf yearl,F

    ;"M?>"
        call newline
        movlw 'M'
        call tx232
        movlw '?'
        call tx232
        movlw '>'
        call tx232
       
    ;月2桁受信: ef
        call rx232
        movf dat232,W
        andlw 0x01 ;1っぽければ10をセット
        btfss STATUS,Z
        movlw .10
        movwf month
        call rx232
        movf dat232,W
        andlw 0x0F
        addwf month,F ;C0

    ;閏年判定
        movf yearl,W ;年下位を取り、
        btfsc STATUS,Z ;0なら
        movf yearh,W ;年上位を取り、
        andlw 0x03 ;下位2bitが
        btfss STATUS,Z ;0でなければ
        goto notleap ;平年。

    ;閏年1,2月を13,14月扱い
        movlw .3
        subwf month,W
        movlw .12
        btfss STATUS,C
        addwf month,F
    notleap:

    ;年ごとの曜日の基準を算出
    ;年下位 + 年下位/4 + (年上位%4)*2
        rrf yearl,W
        movwf temp
        rrf temp,W
        andlw 0x3F
        addwf yearl,F
        rlf yearh,W
        andlw 0x06
        subwf yearl,F
    #define doworg yearl ;名前変更: DoW origin

    ;当月の曜日の差分に相当する数を基準に足す
    ;および、当月日数を取得
        decf month,W
        call getmonthattr
        addwf doworg,F
        andlw 0x0F
        iorlw 0x10
        movwf daycnt

    ;mod7を求めるが、簡略化のため、
    ;7を加算していきキャリーが出た時点で終了する。
    ;256%7=4だけずれるのでデータ側で補正済
        movlw .7
    mod7loop:
        addwf doworg,F
        btfss STATUS,C
        goto mod7loop

    ;ループ用にNと(6-N)を生成、コードの都合上+1
        movlw .8
        movwf dowcnt
        incf doworg,W
        subwf dowcnt
        movwf cnt
       
        call newline

    ;月初めの日まで空白で埋める
        goto dayadjstart
    dayadjloop:
        movlw ' '
        call tx232
        movlw ' '
        call tx232
        movlw ' '
        call tx232
    dayadjstart:
        decfsz cnt,F
        goto dayadjloop

        clrf day
    dayoutloop:
    ;表示用日(BCD)を++
        movlw .7
        addwf day,F
        movlw .6
        btfss STATUS,DC
        subwf day,F
       
    ;1日分の表示
        movlw ' '
        call tx232
        swapf day,W
        andlw 0x0F
        btfsc STATUS,Z
        movlw ' '
        btfss STATUS,Z
        iorlw 0x30
        call tx232
        movf day,W
        andlw 0x0F
        iorlw 0x30
        call tx232

        decf daycnt,F
        btfsc STATUS,Z
        goto break ;1月分出力完了で抜ける

        decfsz dowcnt,F
        goto dayoutloop ;日ループ

        movlw .7
        movwf dowcnt
        call newline
        goto dayoutloop ;週ループ
    break:
        call newline

        goto main

    ;### subroutine ###

    ;改行を出力
    newline:
        movlw '\r'
        call tx232
        movlw '\n'
        call tx232
        retlw 0

    ;下位4bitが「当該月の日数」であり、
    ;全体が「基準の年の当該月1日の曜日+4」とmod7で等しいような数を返す
    getmonthattr:
        addwf PCL,F
        dt .95, .28, .63, .94, .47, .78,
        dt .31,.111, .30, .95,.126, .79,
        dt .31,.125

    ;RS232Cで1バイト受信し、下位ニブルのみ返す
    rx232:
        btfss GPIO,3 ;rx start [0,3)
        goto $-1
        clrf dat232
        goto $+1
        goto f
    ;RS232Cで1バイト送信する
    tx232:
        movwf dat232
        bcf STATUS,C
        rlf dat232,W
        xorwf dat232,F
        bsf GPIO,TXPIN ;tx start 0
    f:
        bsf cnt232,3
    loop232:
        movlw 0xF8
        addwf GPIO,W ;read [9,12)+9n
        movlw 1<     rrf dat232,F
        btfsc STATUS,C
        xorwf GPIO ;write 7+9n
        decfsz cnt232
        goto loop232

        movlw 0x0F
        andwf dat232,F
        xorwf dat232,F
        goto $+1
        bcf GPIO,TXPIN ;stop
        retlw 0

        end
      

  • 最弱のPICマイコンでカレンダー
    2014年10月30日 01:12

    「プロ生ちゃんカレンダー プログラミング プチコンテスト 2014」という面白そうなものを知った。
    カレンダーに載せる、"カレンダーを表示するソースコード"を募集するコンテストである。
    思えばカレンダーのプログラムを書いたことが無かったので自分も何か書いてみたくなり、さて何で書くかと考えたところ、やはり最近マイブームなPIC10F200でいくことにした。
    (その後よく考えてみたらJavaScriptでカレンダーを書いたことがあったが、まあ自力で閏年判定とかが初めてということで)

    コードは末尾に載せたが、中でも今回自分で書いてて気に入っているポイントをいくつか挙げると、
    ・数値を受け取るそばから要らない部分を捨てて保存する(年の1000の位は2倍して100の位に足す)
    ・不正な値は完全無視(月の10の位は最下位bitしか見てないとか)
    ・6命令で閏年判定
    movf yearl,W
    btfsc STATUS,Z
    movf yearh,W
    andlw 0x03
    btfss STATUS,Z
    goto notleap
    といったあたり。
    あとはいつも使ってるコードだけどシリアル送受信は自信作。

    使い方は、シリアルポートに抵抗だけ挟んで写真のように直結し、電源ON。
    PIC10F200カレンダー_回路

    「Y?>」に対して年を4桁で、「M?>」に対して月を2桁で入力すると、当該月のカレンダーを出力してまた「Y?>」から入力待ちとなる。
    表示はこんな感じ。(ローカルエコーON)
    PIC10F200カレンダー_コンソール
    プログラム容量は余裕があるのでもうちょっとまともなメッセージにしてもよかったかもしれない。

    以下プログラム。
    プログラム容量は188ワード。PIC10F200の容量256ワードのうち使用率75%ほど。
    (なお最初書き上げたときは201ワードだったのだが、コードにコメントなどつけて整理していたらいつの間にかここまで縮んでしまった。)
    使用メモリは11バイト。16バイトの7割。
    どちらも余裕であった。
    ;### PIC10F200 Calendar###
    ;by Ikadzuchi (@Pleist)
        list p=10F200
        #include p10f200.inc
        radix dec

        __CONFIG _MCLRE_OFF & _CP_OFF & _WDT_OFF

        cblock 0x10
            txdat, rxdat
            yearh, yearl, month
            cnt, dowcnt, daycnt, cnt232
            day
            temp
        endc

    ;ピン
    #define TXPIN 0
    #define RXPIN 3

        org 0
    init:
        movwf OSCCAL ;クロック補正値セット
        movlw b'11001000'
        ;設定データ。プルアップOFF、他Don'tCare
        ;ちょうど下位3bitがDon'tCareなのでI/O設定と共用
        clrf GPIO ;出力ピンを全Lowに
        tris GPIO ;I/Oモード設定
        option ;設定

    main:
        clrf cnt
        clrf cnt232

    ;"Y?>"
        movlw 'Y'
        call tx232
        movlw '?'
        call tx232
        movlw '>'
        call tx232

    ;年4桁受信: abcd
    ;年上位 = 10a+b ≡ 2a+b
        call rx232
        movf rxdat,W
        movwf yearh ;*1
        addwf yearh,F ;*2
        call rx232
        movf rxdat,W
        addwf yearh,F
    ;年下位 = 10c+d
        call rx232
        movf rxdat,W
        movwf yearl ;*1
        bcf STATUS,C
        rlf yearl,F ;*2
        rlf yearl,F ;*4
        addwf yearl,F ;*5
        rlf yearl,F ;*10
        call rx232
        movf rxdat,W
        addwf yearl,F

    ;"M?>"
        call newline
        movlw 'M'
        call tx232
        movlw '?'
        call tx232
        movlw '>'
        call tx232

    ;月2桁受信: ef
        call rx232
        movf rxdat,W
        andlw 0x01 ;1っぽければ10をセット
        btfss STATUS,Z
        movlw .10
        movwf month
        call rx232
        movf rxdat,W
        andlw 0x0F
        addwf month,F ;C0

    ;閏年判定
        movf yearl,W ;年下位を取り、
        btfsc STATUS,Z ;0なら
        movf yearh,W ;年上位を取り、
        andlw 0x03 ;下位2bitが
        btfss STATUS,Z ;0でなければ
        goto notleap ;平年。

    ;閏年1,2月を13,14月扱い
        movlw .3
        subwf month,W
        movlw .12
        btfss STATUS,C
        addwf month,F
    notleap:

    ;年ごとの曜日の基準を算出
    ;年下位 + 年下位/4 + (年上位%4)*2
        rrf yearl,W
        movwf temp
        rrf temp,W
        andlw 0x3F
        addwf yearl,F
        rlf yearh,W
        andlw 0x06
        subwf yearl,F
    #define doworg yearl ;名前変更: DoW origin

    ;当月日数を取得
        decf month,W
        call getdaysinmonth
        movwf daycnt
    ;当月の曜日の差分を取得し基準に足す
        decf month,W
        call getfirstdayinmonth
        addwf doworg,F

    ;%=7
        movlw .7
    mod7loop:
        subwf doworg,F
        btfsc STATUS,C
        goto mod7loop
        addwf doworg,F

    ;ループ用にNと(6-N)を生成、コードの都合上+1
        movlw .8
        movwf dowcnt
        incf doworg,W
        subwf dowcnt
        movwf cnt

        call newline

    ;月初めの日まで空白で埋める
        goto dayadjstart
    dayadjloop:
        movlw ' '
        call tx232
        movlw ' '
        call tx232
        movlw ' '
        call tx232
    dayadjstart:
        decfsz cnt,F
        goto dayadjloop

        clrf day
    dayoutloop:
    ;表示用日(BCD)を++
        movlw .7
        addwf day,F
        movlw .6
        btfss STATUS,DC
        subwf day,F

    ;1日分の表示
        movlw ' '
        call tx232
        swapf day,W
        andlw 0x0F
        btfsc STATUS,Z
        movlw ' '
        btfss STATUS,Z
        iorlw 0x30
        call tx232
        movf day,W
        andlw 0x0F
        iorlw 0x30
        call tx232

        decf daycnt,F
        btfsc STATUS,Z
        goto break ;1月分出力完了で抜ける

        decfsz dowcnt,F
        goto dayoutloop ;日ループ

        movlw .7
        movwf dowcnt
        call newline
        goto dayoutloop ;週ループ
    break:
        call newline

        goto main

    ;### subroutine ###

    ;改行を出力
    newline:
        movlw '\r'
        call tx232
        movlw '\n'
        call tx232
        retlw 0

    ;当該月の日数を返す
    getdaysinmonth:
        addwf PCL,F
        dt .31, .28, .31, .30, .31, .30 ;1~12月
        dt .31, .31, .30, .31, .30, .31
        dt .31, .29 ;閏年1,2月

    #define ADJ .7 ;負にならないよう補正するため
    ;基準の年の当該月1日の曜日を返す
    getfirstdayinmonth:
        addwf PCL,F
        dt 0+ADJ, 3+ADJ, 3+ADJ, 6+ADJ, 1+ADJ, 4+ADJ ;1~12月
        dt 6+ADJ, 2+ADJ, 5+ADJ, 0+ADJ, 3+ADJ, 5+ADJ
        dt 6+ADJ, 2+ADJ ;閏年1,2月

    ;RS232Cで1バイト送信する
    tx232:
        movwf txdat
        bsf GPIO,TXPIN ;start 0
        bsf cnt232,3
    tx232loop:
        rrf txdat,F
        movf GPIO,W
        andlw ~(1<     btfss STATUS,C
        iorlw 1<     movwf GPIO ;write 7+9n
        decfsz cnt232,F
        goto tx232loop
        goto $+1
        goto $+1
        nop
        bcf GPIO,TXPIN ;stop
        retlw 0

    ;RS232Cで1バイト受信し、下位ニブルのみ返す
    rx232:
        btfss GPIO,3 ;start [0,3)
        goto $-1
        movlw 0x80
        movwf rxdat
        bcf STATUS,C
    rxloop:
        goto $+1
        movlw 0x0F
        rrf rxdat,F
        btfss GPIO,RXPIN ;read [9,12)+9n
        bsf rxdat,7
        btfss STATUS,C
        goto rxloop
        andwf rxdat,F
        retlw 0

        end

    おまけで機械語。1ワード(=1命令)が12bitで、16進数にすると3文字という独特の見た目が気に入っている。
        0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
    0x 025 CC8 066 006 002 075 078 C59 99E C3F 99E C3E 99E 9AE 211 032
    1x 1F2 9AE 211 1F2 9AE 211 033 403 373 373 1F3 373 9AE 211 1F3 97B
    2x C4D 99E C3F 99E C3E 99E 9AE 211 E01 743 C0A 034 9AE 211 E0F 1F4
    3x 213 643 212 E03 743 A3B C03 094 C0C 703 1F4 313 03A 31A E3F 1F3
    4x 352 E06 0B3 0D4 980 037 0D4 98F 1F3 C07 0B3 603 A4A 1F3 C08 036
    5x 293 0B6 035 97B A5B C20 99E C20 99E C20 99E 2F5 A55 079 C07 1F9
    6x C06 723 0B9 C20 99E 399 E0F 643 C20 743 D30 99E 219 E0F D30 99E
    7x 0F7 643 A79 2F6 A5E C07 036 97B A5E 97B A05 C0D 99E C0A 99E 800
    8x 1E2 81F 81C 81F 81E 81F 81E 81F 81F 81E 81F 81E 81F 81F 81D 1E2
    9x 807 80A 80A 80D 808 80B 80D 809 80C 807 80A 80C 80D 809 030 506
    Ax 578 330 206 EFE 703 D01 026 2F8 AA1 AAA AAB 000 406 800 766 AAE
    Bx C80 031 403 AB4 C0F 331 766 5F1 703 AB3 171 800 --- --- --- ---
    Cx --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
    Dx --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
    Ex --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
    Fx --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- Cxx
      

  • XPはいつまで安全か
    2014年04月09日 01:29

    この文章をWindowsXPマシンで書いているのだが、そのXPのサポートは今日で終了する。
    では今日以降XPを使うのは危険なのだろうか。そうでないならいつまで安全なのか。
    考えてみよう。
    厳密にいえば、「現在および過去のXPと比べ危険になる/なったのはいつか」を考える。
    そうでないと、そもそもXPはサポート期限内でも(7などに比べて)危険である。

    まず、なぜ危険になるのか。
    世の中にはOSのセキュリティーホールを見つけてそれを突く攻撃をする人がいる。
    サポートされているWindowsはセキュリティーホールが見つかればそれを塞ぐパッチが作られWindowsUpdateを通して適用されるが、サポートが切れればそれが無くなるため、危険になるのである。

    では危険になるのはいつか。
    今日サポートが終了するというのは、今日WindowsXPに対する最後のパッチが公開されるという意味である。
    そして、WindowsUpdateは通常1ヶ月に1回行われる。これは言い換えれば、通常は1ヶ月に1回のパッチ適用でWindowsの安全性は十分に保たれるとMicrosoftは考えているということである。
    であれば、今日公開される最後のパッチを当ててから1ヶ月間、あるいはこれは余裕を持った値と考えればそれ以上、WindowsXPは十分に安全であると考えることができる。

    ただし、この考えの大前提は、「今後も今まで同様の攻撃がなされる」ということである。
    今後の攻撃が増える要因が2点ある。

    まず1つに、XP以外のWindowsに対するパッチで修正されるセキュリティーホールがXPにもあった場合、そのパッチを解析することでXPへの攻撃が容易になってしまうという点。
    これを考えると、安心できるのは次に重大なセキュリティーパッチが出るまでということになる。
    来月のWindowsUpdateか、もしかするとその前に臨時に出るかもしれないし、来月のWindowsUpdateが小物ばかりならもう1ヶ月猶予ができるかもしれない。

    次に、既にセキュリティーホールが見つかっている場合、攻撃者は、サポートが切れて修正される危険が無くなってからそれを使った攻撃をするであろうという点。
    これにより、サポート切れを機に攻撃が増えると考えられる。
    さらに言えば、攻撃があってからパッチを作るのには時間がかかるだろうから、実際にはサポート切れの少し前にはもうセキュリティーホールが塞がれる危険性は無くなっているだろう。
    つまり、もうだいぶ前から既に危険であると考えられる。

    しかしゼロデイ攻撃は今までもあったわけで、その程度の危険性なら以前と変わらないとも言える。

    そんなわけでまとめると、悲観的に考えればもう既に遅く、楽観的に考えれば来月のWindowsUpdateまではわりと安全ではないかと思う。
    また、XPをサポートし続けるセキュリティーソフト会社は多いので、XPに何か脆弱性が見つかれば彼らが教えてくれる。何か言われるまではわりと安全であろう。  

  • PIC10F200 OSCCAL調整用コード
    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は「マ」である。
    ・これをタマモードと名付けよう。
    しかしなんか普通に送れたので使う機会がなかった。一応コードには残してあるが未チェックである。使うならストップビットの時間も長くしたほうがいい。

    さてそれで計った生データがこちら。
    1667D014678612663E1065F90E65B30C647B0A64350863F00663A904625E0262190061D4FE618EFC6079FA6035F85FF0F65FAAF45E62F25E1BF05DD6EE5D90EC5C5AEA5C13E85BD0E65B8BE45A40E259FBE059B7DE5872DC5823DA57DFD8579CD65658D4560FD255CAD05586CE5441CC540ACA53C6C85383C6523FC451F6C251B2C0506FBE502BBC4FB9BA4E76B84E32B64DEFB44DA8B24C63B04C23AE4BDEAC4BA8AA4A64A84A24A649E0A4499AA24858A048169E47D29C47849A46449846029645C09444799244399043F58E43B58C427D8A423D8841FB8641BA844072824030803FF0823FAF843FEE8640308840728A41BA8C41FB8E423D90427D9243B59443F79644379844799A45C09C46029E4644A04786A247D4A44814A64858A8499AAA49E2AC4A24AE4A66B04BA8B24BDFB44C21B64C65B84DA7BA4DEFBC4E32BE4E76C04FB8C2502BC4506FC651B2C851F6CA523FCC5383CE53C6D0540AD25441D45586D655CAD8560FDA5658DC579CDE57E1E05824E25871E459B7E659FCE85A41EA5B89EC5BCEEE5C15F05C5AF25D90F45DD6F65E1BF85E60FA5FAAFC5FEFFE603400607A02618F0461D30662190862600A63A90C63F00E643610647B1265B41465F916663E1867861A67D01C68161E685D2069A32269F1246A39266A7E286BC52A6C112C6C582E6D9E306DE5326E1E346E66366FAB386FF33A703C3C71843E71CB4072134273884473D046741648745E4A75AB4C75F34E76385077805277BA5478015678495879915A79DD5C7A255E7A6D607BB4627C03647C4B667D93687DDB6A7E286C7E706E7FB6707FFD72803674807E7681C478820B7A82577C839E7E83E47C842B7A83E478839E76825774820B7281C470807E6E80366C7FFD6A7FB5687E6E667E28647DDB627D93607C4B5E7C055C7BB45A7A6D587A255679DC5479915278495078024E77BA4C77814A763B4875F34675AB44745E4274164073D03E73883C72133A71CC38718436703C346FF1326FAB306E642E6E1E2C6DE62A6D9E286C58266C11246BC6226A7E206A381E69F11C69A31A685B1868161667D01467861266401065F90E65B30C647A0A64350863F00663A904625E0262190061D4FE618EFC6079FA6035F85FEFF65FAAF45E60F25E1BF05DD6EE5D91EC5C5AEA5C15E85BD0E65B8BE45A41E259FCE059B7DE5872DC5824DA57DFD8579CD65658D4560FD255CAD05586CE5441CC540ACA53C6C85383C6523FC451F7C251B2C0506FBE502BBC4FB9BA4E76B84E32B64DEFB44DA8B24C66B04C23AE4BDFAC4BA9AA4A67A84A24A649E2A4499AA24858A048169E47D59C47879A46459846029645C1

    グラフにしたのがこちらである。
    PIC10F200 OSCCAL設定値と周波数
    これを見ると面白いのが、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
      

  • OS自作[9]_SDからのブート
    2014年02月02日 07:06

    前回から1週間。時間が空いた時に復習のためにちまちま本を読み返してます。
    中断前は大体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でブートドライブに設定するだけで同様に使えるのです。
    ということは、
    SDからブート
    こういうことで。
    これは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でセクタ番号が同じ部分はそのまま読めそうです。
    試しに作業フォルダにあった一番新しいものでやってみると、見事に動きました。(しかしこの画面記憶に無い。次にやる所だったかも)
    SDブートその2
    ただ実はこれが動くのは不思議な話で、このイメージは実質30kBほどあるのに対し、フロッピーのシリンダあたりのセクタ数は18なので、9kBまでしか正常に読めないはずなのです。
    これを試したときは上記資料を見て0x3Fセクタや0x20セクタは読めるはずと思っていたんですが、よく考えたらフロッピーの方が少ないからそこまでしか読まないはずで。
    謎が残りましたが、まあその方がモチベーションが湧くということで、今日はここまでとします。

    ◆ここ1週間くらいの成果◆
    ブートの流れの理解が深まった
    ◆今日の成果◆
    SDからブートできた  

  • OS自作[8]_環境復元
    2014年01月27日 01:44

    どうもブログを書く時間がとれません。1週間前のことを今ごろ書いてます。
    再開するにあたって、まずは今までの進捗を思い出すところからです。
    とりあえず過去の記事を全部読み直してみて、続いて作ったものを実行してみようとしたのですが、どうやって実行するのかを完全に忘れている。
    本を読みなおしてみて、どうやら用意されたバッチファイルから「make run」すれば良いとわかったのですが、これが動かない。
    エラーを見ると、
    process_begin: CreateProcess((null), copy /B asmhead.bin+bootpack.hrb haribote.sys, ...) failed.
    make (e=2): 指定されたファイルが見つかりません。
    make.exe[2]: *** [haribote.sys] Error 2
    指定されたファイルが見つからないといってもasmhead.binもbootpack.hrbもちゃんとある。
    しばらく悩んだのちググってみると、原因判明。
    ここここを見て分かったことは、ファイルが見つからないと言っていたのは「copy」自体のことで、どうやらパスの通った場所に別の「copy」という実行可能ファイルがあると実行対象が1つに決まらずこのエラーを出すようです。
    しかし探してみても他にcopyという名の実行ファイルは見つからず。ファイルでない何かを見ているのだろうか…。
    よく分からないので結局公式のサポートページを参考にmakefileを書き換えて動くようになりました。
    原因が分からないのは癪ですが、まあとりあえずこれで作業に戻れそうです。
    本の方も今までの分を読み返しているところで、ブートセクターのところが分かりそうな気がしています。

    -----

    ◆今日の成果◆
    実行できるようになった。
    ブートセクターが分かりそうな気がしてきた。  

  • OS自作[7]_復活
    2014年01月18日 18:47

    あれから早5年。
    ふと思い立って続きをやってみようかなと。
    あれからマイコンいじりにハマったりプログラミングが職業になったりしたので、たぶん当時より楽に進めるんじゃないかなと思う。

    -----

    ◆今日の成果◆
    5年超の眠りから目覚めた
    本のホコリをはらった  

  • 重畳漢字ROM
    2013年10月25日 01:41

    ちょっとした機器で漢字を表示したい時、文字のデータをROMに持つことになる。
    ROMの容量を抑えたいなら、漢字のレパートリーをJIS第1水準のみに絞ったりするだろう。
    その際、使う文字コードが(Shift_)JISならば問題ないのだが、何かの都合でUnicodeを使わざるを得ない場合もある…と思う。
    (無いと話が終わってしまうのであることにしていただきたい)
    ここで発生する問題が、変換テーブルが巨大になるということである。
    UnicodeはJISコードとは無関係に文字が並べられているので、UnicodeでJIS第1水準の文字はバラバラに存在している。
    例えばこんなだ。
    UnicodeでのJIS第1水準漢字
    空白部分を漢字ROMに持つのは無駄なのでROMには詰めて入れるが、そうすると文字コードとの対応を別にテーブルでもっておかなければならない。
    これがどれほどのデータ量になるか。
    まず漢字ROM本体だが、例えば12×12ドットのフォントを使うとして、JIS第1水準漢字2965文字は2965*12*12/8 = 53370Byte ≒ 53KBとなる。
    一方変換テーブルだが、JIS第1水準漢字はUnicodeにU+4E00「一」からU+9F8D「龍」までのおよそ21000文字にわたって分布している。
    2965文字を指定するには12bit必要なので、素直に考えれば21000*12/8 = 31500Byte、すなわち32KBと、本体の半分ものデータ量になってしまう。

    と、ここまでが前置きで、今回これを解決するアルゴリズムを考案した。タイトルにもあるように、このアルゴリズムは「重畳漢字ROM」と名付けた。
    この方式では漢字ROMの容量は少々増えるが(今回のサンプルでは14%増)、変換テーブルの容量を劇的に縮小することができる(32KB→1.3KB)。
    仕組みは次のようになっている。
    ・ROMの番地は12bitある。
    ・その下位4bitは文字コードの下位4bitと同じである。
    ・上位8bitは文字コードの上位12bitからテーブルを引いて求める。
    つまり文字コードの上位12bitが異なる文字を、番地の上位8bitが同じ箇所に押し込めている事になる。
    先に述べたとおりUnicode中のJIS第1水準漢字は飛び飛びに存在しているのでこういうことができるのだ。
    図で示すと分かりやすいかもしれない。
    漢字ROM重畳例

    ただしこの方式には1つ重大な欠点がある。第1水準外の文字が入力された時でも区別できずに何らかの文字を表示してしまうのだ。
    きちんと表示させるためにはデータを事前に調整しなければならず、それならJISに変換すればいいじゃないかということになりかねない。

    最後にサンプルを用意した。
    テキストボックスに任意の第1水準漢字を入れボタンかEnterを押せば下にその漢字の画像が表示される。わざと第2水準漢字を入れて変な文字が出るのを試すのもよいだろう。
    変換テーブルはソースを参照。
    コードは単純で、漢字ROM相当の画像ファイルの表示位置をずらして当該文字を出している。
    なおこのROMの重畳のパターンは文字の多い順に並べてからナイーブな貪欲法で1つづつ重ねて作ったものである。やりようによってはもっと縮められると思う。