最弱のPICマイコンでカレンダー_ちょっと短縮2014年11月02日 04:03
先日の「最弱のPICマイコンでカレンダー」だが、ちょっと思いついてコードを短縮してみた。いい感じにキモくなったと思う。
変更箇所は2点。
【シリアル通信】
元のコードではRS232Cシリアルの送受信コードをそれぞれ書いていたが、これを1つにまとめることにより短縮した。
コア部分がこちら。1つのループの中で1bitの送信と受信を同時に行うようになっている。
・送信時
バッファに送信データを入れておき、スタートビットを送信してからこのループで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を足す形になった。
それでできた値がこれだ。
さらに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ワード縮む。
全コードは以下。
変更箇所は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
ファミコンディスクシステム用ソフトの作成
CDのピットの数
6段のカレンダーが好きだ
PIC16のDhrystone MIPSを測ろうとしてみた
Twitterの画像の扱いがやっとまともになって嬉しい
ファミコンで9×9ドット文字表示(ほか)
CDのピットの数
6段のカレンダーが好きだ
PIC16のDhrystone MIPSを測ろうとしてみた
Twitterの画像の扱いがやっとまともになって嬉しい
ファミコンで9×9ドット文字表示(ほか)
この記事へのコメント
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は使ったことがないのでトンチンカンなこと書いてたらゴメンナサイ。
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されても問題無いですね。
さぞかし名のあるゴルファーとお見受けします…。
> space
なるほど確かに。
思えばtx232:の上は空いていましたね。
> movlw '\n'
> goto tx232
なるほど確かに。
どうも関数という考え方に毒されていたようです。
> andlw 0x0F
> btfss STATUS,Z
> iorlw 0x30
> iorlw ' '
えーと…、あー、確かに!
数字しか来ない以上0x20がORされても問題無いですね。
Posted by いかづちSqueak
at 2014年11月06日 00:50
