たまりば

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

最弱の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

  • 同じカテゴリー(プログラム・アルゴリズム)の記事画像
    6段のカレンダーが好きだ
    ファミコンで9×9ドット文字表示(ほか)
    JPEG圧縮を繰り返しても際限なく劣化するわけではない
    ゲームボーイの吸い出し機を作った (後編)
    ファミコンで全画面に任意の画像(ただしモノクロ)を表示
    最近のWindowsのビットマップフォントの太字
    同じカテゴリー(プログラム・アルゴリズム)の記事
     CDのピットの数 (2021-03-05 23:32)
     6段のカレンダーが好きだ (2020-11-15 04:06)
     PIC16のDhrystone MIPSを測ろうとしてみた (2020-04-29 02:23)
     Twitterの画像の扱いがやっとまともになって嬉しい (2019-06-23 00:23)
     ファミコンで9×9ドット文字表示(ほか) (2019-04-08 02:02)
     謎の色名「honeydewtab」とlegacy color valueパース手順 (2018-03-24 12:45)
    この記事へのコメント
    4箇所ある

    movlw ' '
    call tx232



    call space

    とかにしてtx232:のすぐ上に

    goto $+2
    goto f
    ;空白を出力
    space:
    movlw ' '
    ;RS232Cで1バイト送信する
    tx232:

    とかするとちょっと小さくなると思います。

    movlw '\n'
    call tx232
    retlw 0



    movlw '\n'
    goto tx232

    でいいのでは?

    andlw 0x0F
    btfsc STATUS,Z
    movlw ' '
    btfss STATUS,Z
    iorlw 0x30



    andlw 0x0F
    btfss STATUS,Z
    iorlw 0x30
    iorlw ' '

    でいいと思います。PICは使ったことがないのでトンチンカンなこと書いてたらゴメンナサイ。
    Posted by 名無し at 2014年11月02日 18:25
    コメントありがとうございます。
    さぞかし名のあるゴルファーとお見受けします…。

    > space
    なるほど確かに。
    思えばtx232:の上は空いていましたね。

    > movlw '\n'
    > goto tx232
    なるほど確かに。
    どうも関数という考え方に毒されていたようです。

    > andlw 0x0F
    > btfss STATUS,Z
    > iorlw 0x30
    > iorlw ' '
    えーと…、あー、確かに!
    数字しか来ない以上0x20がORされても問題無いですね。
    Posted by いかづちSqueakいかづちSqueak at 2014年11月06日 00:50
    URL欄を実験的に消してる間に廃止されてしまいました。まあいいか。
     
    <ご注意>
    書き込まれた内容は公開され、ブログの持ち主だけが削除できます。
    削除
    最弱のPICマイコンでカレンダー_ちょっと短縮
      コメント(2)