石原 博の覚書

電子工作に関する日々の覚書を記載します

Forthのエディタ(スクリーンエディタ)

2021-05-23 18:17:58 | 日記
現在流のスクリーンエディタと比較すると貧弱だが、Forthにもスクリーンエディタがある。
画面のコントロールは以下の4つのワードを使用する

AT (S col row -- )  カーソル位置の指定
BLOT (S col -- ) 行内カーソルより右クリア
DARK (S -- ) 画面クリア
-LINE (S -- ) 1行削除(下の行はスクロールアップ)

デフォールトではdumb端末を想定しており、
ATは (AT) (S col row -- ) 2DROP CR ;
BLOTは (BLOT) (S col -- ) C/L SWAP - SPACES ;
DARKは (DARK) (S -- ) 24 0 DO CR LOOP ;
-LINEは NOOP
となっている。

端末がANSIのエスケープシーケンスを受け付ける場合
例えばputtyとか、xtermとかなら以下の設定を受け付けることが出来る。
(avrcpmではputtyでシリアル、RunCPMではxtermなのでどちらもOK)

ANSIのエスケープシーケンスの場合
-----------------------------------
ANSI−AT カーソル位置の移動(n行、m列)  (esc)n;mH
ANSI-BLOT 行内カーソルより右をクリア  (esc)[K
ANSI-DARK 画面全体クリア  (esc)[2J
ANSI--LINE 1行削除(下の行はスクロールアップ) (esc)[1M


使用例
------
EDITOR ANSI (ボキャブラリEDITORを選び、ANSIによりエスケープシーケンスを使うように設定)
1 EDIT (スクリーン1を編集する)
ここで Enter your ID: ........... と表示されるので、適当にIDをいれて Enter

上部18行に現在のスクリーンデータ、その下1行が現在の編集行、
それ以下5行はForthへの入力コマンド等が表示される。(画面は24行想定 .ALLにハードコーディング)

1 NEW
とすると1行目から順に入力することが出来る。
(ただし上下左右のカーソルコントロール等は出来ない)

Forthのエディタ(コマンド)

2021-05-16 23:11:09 | 日記

エディタの使い方

[1]新規ファイルを作る(0〜N-1  のN個のスクリーン)

 N create-file ファイル名

[1']ファイルオープンする

 open ファイル名

[2]スクリーン1を編集

 1 editor

 

エディタコマンド

定義 内容 使用例 説明
: TOP (S -- ) R# OFF ; スクリーン内のカーソル変数(R#)にFALSE(0)を書き込む(OFF) TOP カーソルを1行目の左端に移動
: C (S n -- ) R# @ + C/SCR MOD R# ! ; スクリーン内のカーソル変数(R#)を読み出して(@)、nを足す(+)
C/SCR(スクリーン内の文字数1024)で割ったあまり(MOD)をカーソル変数(R#)に書き込む(!)
2 C
-3 C
カーソルを右に2文字移動
カーソルを左に3文字移動
: T (S n -- ) TOP C/L * C ; カーソルをスクリーンの最初に移動(TOP)してから、
C/L(1行内の文字数64)をかけて(*)移動数を求め、移動する(C)
2 T カーソルを2行目左端へ移動
: +T (S n -- ) LINE# + T ; 現在行(LINE#)を足して(+)、その行に移動する(T) 1 +T カーソルを1行下左端へ移動
: KEEP (S -- ) ‘LINE C/L ‘INSERT PLACE ; 現在行の先頭(‘LINE)からC/L(行内文字数64)を’INSERT(insert-buffer)へ、文字列として保存(PLACE)する    
: K (S -- ) ‘FIND PAD C/PAD CMOVE ‘INSERT ‘FIND C/PAD CMOVE PAD ‘INSERT C/PAD CMOVE ; find-bufferからPAD(作業領域)へC/PAD(84個)移動(CMOVE)
insert-bufferからfind-bufferへC/PAD(84個)移動(CMOVE)
PAD(作業領域)からinsert-bufferへC/PAD(84個)移動(CMOVE)
⇒結局find-bufferとinsert-bufferの交換
   
: W (S -- ) SAVE-BUFFERS ; バッファーをディスクへ保存する    
: I (S -- ) (I) INSERT C ; insert-bufferに文字列を入れ((I)) (S -- len‘insert len ‘cursor #after)
insert-bufferからcursor位置に挿入(INSERT) (S string length buffer size -- )
insert-bufferの文字列長だけカーソルを進める(C) (S n -- )
I xyz カーソル位置からxyzを挿入
: O (S -- ) (I) REPLACE C ; insert-bufferに文字列を入れ、
(I) (S -- len ‘insert len ‘cursor #after) insert-bufferに文字列を入れ
REPLACE (S string length buffer size -- ) insert-bufferからcursor位置に上書き
C (S n -- ) insert-bufferの文字列長だけカーソルを進める
O xyz カーソル位置からxyzを上書き
: P (S -- ) ‘INSERT ?TEXT DROP ‘LINE C/L CMOVE MODIFIED ; insert-bufferに文字列を入れ、行先頭(‘LINE)からC/L(64文字)移動 P xyz 現在行に xyzを上書き
: U (S -- ) C/L C ‘LINE C/L OVER #END INSERT P ; C/L(64文字)カーソルを進め、
(‘LINE C/L OVER #END) 行頭 C/L 行頭 行頭からスクリーン末までの文字数(#END)を INSERT  現在行を次行に複製する
Pでinsert-bufferに文字列を入れ、行先頭(‘LINE)からC/L(64文字)移動
U xyz 次行にxyzを挿入
: X (S -- ) KEEP ‘LINE #END C/L DELETE MODIFIED ; 現在行をinsert-bufferに入れ(KEEP)、
行頭アドレス(‘LINE) 行頭からスクリーン末までの文字数(#END) C/L(64文字) を引数にして削除(DELETE)
X 現在行を削除
(削除した行はinsert-bufferへ)
: SPLIT (S -- ) PAD C/L 2DUP BLANK ‘CUSOR #REMAINING INSERT MODIFIED ; PAD(作業領域)をC/L(64文字)用意し、空白化(2DUP BLANK)
PAD(作業領域)からC/L(64文字)をカーソル位置(‘CURSOR)から挿入(INSERT)
⇒ブランクを64文字分挿入
   
: JOIN (S -- ) ‘LINE C/L + C/L ‘C#A INSERT ; ‘LINE(行頭アドレス)にC/L(64文字)を足して(+)次の行頭を求め、V/L(64)文字分のデータを
現在のカーソルアドレスと行末までの文字数(’C#A)、以降に挿入(INSERT)
(‘LINE C/L + C/L ‘C#A INSERT) 'LINE+C/L C/L ‘CURSOR #AFTER INSERT
   
: WIPE (S -- ) ‘START B/BUF BLANK MODIFIED ; バッファーの先頭アドレス(‘START)からB/BUF(1024)ブランクを書き込む WIPE 現在のスクリーンを消去
: G (S screen line -- ) C/L * SWAP IN-BLOCK + C/L ‘INSERT PLACE C/L NEGATE C U C/L C ; screenのline行目をinsert-bufferに入れ、1行上にあがり挿入(U)(Uは次行挿入なので先に1行上がる)
カーソルを1行下げる。
1 2 G スクリーン1の2行目を現在位置に挿入
: F (S -- ) FIND? ?MISSING ‘F+ C ; find-bufferに文字列を入れ、現在のカーソルからスクリーン末まで検索
なければエラー あればカーソルをfind-bufferの文字列長(‘F+)進める
f xyz xyz をfind-burrerに入れ検索
: S (S n -- ) 1 ?ENOUGH FIND? IF ‘F+ C EXIT THEN DROP FALSE OVER SCR @ DO N TOP ‘FIND COUNT ‘CURSOR #REMAINING SEARCH IF ‘F+ C DROP TRUE LEAVE ELSE DROP THEN KEY? ABORT” Break!” LOOP ?MISSING ;

find-bufferに文字列を入れ、現在のカーソルからスクリーン末まで検索

この後n個のスクリーンも対象とする。

   
: E (S -- ) ‘FIND C@ DUP NEGATE C ‘C#A ROT DELETE ; find-bufferの文字列長(‘FIND C@)だけカーソルを戻し(NEGATE C)、
find-bufferの文字列長 cursor位置(‘cursor) 行末までの文字列長(#after)
(ROT DELETE) cursor位置(‘cursor) 行末までの文字列長(#after) find-bufferの文字長 DELETE
⇒カーソル前の、find-buffer文字列長分の文字を削除(文字列の内容は関係ない)
   
: D (S -- ) F E ; find-bufferに文字列を入れ、検索。そこから文字列長分を前に向かって削除
⇒一致する文字列を削除
D xyz xyzを削除
: R (S -- ) E I ; カーソル前のfind-bufferに文字列長分の文字を削除し、insert-bufferに文字列を入れ、カーソル位置に挿入する
⇒削除する文字列は指定出来ない。事前にFなどで検索する必要あり
F abc
R xyz
abcを検索し、xyzに置き換える
: TILL (S -- ) ‘C#A (TILL) ‘F+ DELETE ; ‘C#A (S -- cursor after )
(TILL) (S -- offset ) find-bufferに文字列を入れ検索 一致位置はoffset
'F+ (S n1 -- n2 )
⇒カーソル位置から、find-bufferに文字列を入れて検索し一致した検索文字列を含め削除
TILL xyz xyzを含め削除
: J (S -- ) ‘C#A (TILL) DELETE ; ⇒カーソル位置から、find-bufferに文字列を入れて検索し一致した検索文字列を含めず削除 J xyz xyzまでの文字列を削除
: KT (S -- ) ‘CURSOR (TILL) ‘F+ ‘INSERT PLACE ; ‘CURSOR (S -- cursor )
(TILL) (S -- offset ) find-bufferに文字列を入れ検索 一致位置はoffset
'F+ (S n1 -- n2 )
'INSERT (S -- insert-buffer )
⇒カーソル位置から、find-bufferに文字列を入れて検索し一致した検索文字列を含めinsert-bufferにコピー
   
: NEW (S n -- ) L/SCR SWAP DO [ FORTH ] I [ EDITOR ] T EDIT-AT >IN OFF QUERY SPAN @ IF P ELSE [ FORTH ] I REDISPLAY LEAVE THEN .SCREEN LOOP .SCREEN ; (n L/SCR SWAP DO [ FORTH ] I [ EDITOR ] T) n行目から最終行まで入力する
(EDIT-AT >IN OFF QUERY SPAN @ IF P) 画面上のカーソル位置にカーソルを移動し、入力がればその行に上書き
([ FORTH ] I REDISPLAY LEAVE THEN) 入力がなければ行番号(I)とその行を印字(REDISPLAY)し、抜ける(LEAVE)
⇒n行目から複数行入力する。2回改行で抜ける
   
: DONE (S -- ) [ EDITOR ] EDITING? @ IF PREVIOUS EDITING? OFF CR SCR ? >UPDATE @ 0< NOT IF .” Un” THEN .” modified” ?STAMP W THEN DISK-ERROR OFF AUTO 2@ ! ;     編集終了
: ED (S -- ) [ EDITOR ] GET-ID INSTALL EDITOR ‘VIDEO B/BUF ERASE DARK .ALL ;      
: EDIT (S scr -- ) 1 ?ENOUGH SCR ! [ EDITOR ] TOP ED ; 引数が1個であることを確認(1 ?ENOUGH)し、スクリーン変数に代入(SCR !)
カーソルをTOP位置に移動させて、編集(ED)
1 EDIT スクリーン1を編集する

AVR(attiny202)でのLチカ

2021-05-05 11:56:58 | 日記
トラ技 2021/4 のAVR記事で attiny202 が使用されていた。
これまで atmega328ばかり使っていたが、8pinのAVRも面白そうだっだので、ちょっと工作。

前に用事で東京に行ったついでに
秋月でattiny202を購入(パッケージがsop8なのでピッチ変換基板も購入)していた。

これまでAVRには USPASP と avrdude で対応していたが、pickitでないと書けない。
そこまでは知っていたが、持っているpickit3ではUPDIで書き込む必要があるattiny202には
書けないことが帰ってから判明。(pickit3も高かったのになあ)
やむなく、後から通信販売でpickit4を購入。

連休中時間があるので、使い慣れないMPLAB X IDE と pickit4 を使って、まずLチカでもと
思ったが結構落とし穴があった。

注意点1
 pickit4はpicに対してはProject Properties-Powerで電力供給が可能だが
avrに対しては、電力供給出来ない(外部から電源供給が必要)

注意点2
 Fuseの設定は avrdude でも面倒だったが、MPLAB X IDEは独特
 Window-TargetMemoryViews-Configuration Bits で 画面を開き、左上部のアイコン
(Read Configuration Bits)をクリックすると、接続している avr から現在の configが読み取れる
 ここで適当に変更(変更しなくても良いけど)して、下部の Generate Source Code to Outputをクリック
 すると、Config Bits Sourceというタブが出来て内部にFuse設定が書き込まれているという段取り。
 これをソースに貼ることになる。

注意点3
 ポートに対する入出力が変わっている。
従来 DDRA = 4 とか PORTA = 4 とかだったが、これだとエラーになる。
 インストール時に出来たファイル
 /opt/microchip/mplabx/v5.45/packs/Microchip/ATtiny_DFP/2.5.116/include/avr/iotn202.h
 を確認すると、どうも違うらしい

 PORTA.DIR = 4 とか PORTA.OUT = 4 とか。(実は IDEが候補を出してくれてたのだが気づかなかった)

注意点4
 1秒の間隔の積りだったが、やけに遅い。(クロックは20MHzのはず)
 https://avr.jp/user/DS/PDF/tiny402.pdf で確認したところ、
デフォールトでは、MCLKCTRLBで、PDIV3=1 PEN=1 結局4分周している。

===最終結果===
/*
* File: main.c
* Author: ishi
*
* Created on 2021/05/04, 22:00
*/
#define F_CPU 20000000UL / 4

#include
#include
#include
#include

FUSES = {
.WDTCFG = 0x00, // WDTCFG {PERIOD=OFF, WINDOW=OFF}
.BODCFG = 0x00, // BODCFG {SLEEP=DIS, ACTIVE=DIS, SAMPFREQ=1KHz, LVL=BODLEVEL0}
.OSCCFG = 0x02, // OSCCFG {FREQSEL=20MHZ, OSCLOCK=CLEAR}
.TCD0CFG = 0x00, // TCD0CFG {CMPA=CLEAR, CMPB=CLEAR, CMPC=CLEAR, CMPD=CLEAR, CMPAEN=CLEAR, CMPBEN=CLEAR, CMPCEN=CLEAR, CMPDEN=CLEAR}
.SYSCFG0 = 0xF6, // SYSCFG0 {EESAVE=CLEAR, RSTPINCFG=UPDI, CRCSRC=NOCRC}
.SYSCFG1 = 0x07, // SYSCFG1 {SUT=64MS}
.APPEND = 0x00, // APPEND {APPEND=User range: 0x0 - 0xFF}
.BOOTEND = 0x00, // BOOTEND {BOOTEND=User range: 0x0 - 0xFF}
};

LOCKBITS = 0xC5; // {LB=NOLOCK}

int main(int argc, char** argv) {
PORTA.DIR = 4; //PA2を出力に設定
while (1) {
PORTA.OUT = 4; //PA2をON
_delay_ms(1000); //1s待つ
PORTA.OUT = 0; //PA2をOFF
_delay_ms(1000); //1s待つ
}
}

Forthの制御構造(CASEの場合)

2021-05-03 21:23:19 | 日記
今度は逆に CASEを作ってみよう

FORTH DIMENSIONS II/3 の CASE Contest (Dr. Charles E. Eaker) を参考に

このような定義の場合、
: TEST
CASE
1 OF ." ONE" ENDOF
2 OF ." TWO" ENDOF
." OTHER"
ENDCASE ;

以下のようになってほしい
: TEST 1 OVER = ?BRANCH x1 DROP (.") ONE BRANCH x3
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^
CASE OF ENDOF

2 OVER = ?BRANCH x2 DROP (.") TWO BRANCH x3 (.") OTHER DROP ;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^
OF ENDOF ENDCASE

こうするためには、
CASE =>
OF => COMPILE OVER COMPILE = COMPILE ?BRANCH HERE 0 , COMPILE DROP
ENDOF => COMPILE BRANCH HERE 0 , SWAP HERE !
ENDCASE => HERE SWAP ! をすべてのOFに対して繰り返す
すべてのOFに対して繰り返すのは、回数が未定のためCASEで
フラグ(TRUE)を積んで置くことにする。

これらを考慮すると
: CASE TRUE ; IMMEDIATE
: OF COMPILE OVER COMPILE = COMPILE ?BRANCH HERE 0 , COMPILE DROP ; IMMEDIATE
: ENDOF COMPILE BRANCH HERE 0 , SWAP HERE SWAP ! ; IMMEDIATE
: ENDCASE COMPILE DROP BEGIN DUP TRUE <> WHILE
HERE SWAP ! REPEAT DROP ; IMMEDIATE

: TEST CASE 1 OF ." ONE" ENDOF 2 OF ." TWO" ENDOF ." OTHER" ENDCASE ;

確認すると、
SEE TEST
: TEST 1 OVER = ?BRANCH 14 DROP (.") ONE BRANCH 34 2 OVER = ?BRANCH 14
DROP (.") TWO BRANCH 12 (.") OTHER DROP ;

このままでも使えるが、F83らしく
HERE 0 , => >MARK
HERE SWAP ! => >RESOLVE
とすると、
: CASE TRUE ; IMMEDIATE
: OF COMPILE OVER COMPILE = COMPILE ?BRANCH >MARK COMPILE DROP ; IMMEDIATE
: ENDOF COMPILE BRANCH >MARK SWAP >RESOLVE ; IMMEDIATE
: ENDCASE COMPILE DROP BEGIN DUP TRUE <> WHILE
>RESOLVE REPEAT DROP ; IMMEDIATE

さらにチェック用のフラグを入れると
: CASE TRUE ; IMMEDIATE
: OF COMPILE OVER COMPILE = COMPILE ?BRANCH ?>MARK COMPILE DROP ; IMMEDIATE
: ENDOF COMPILE BRANCH ?>MARK 2SWAP ?>RESOLVE ; IMMEDIATE
: ENDCASE COMPILE DROP BEGIN DUP TRUE <> WHILE
?>RESOLVE REPEAT DROP ; IMMEDIATE


https://forth-standard.org/standard/core/CASE にある
テストで動くことが確認出来た。

: cs1 CASE 1 OF 111 ENDOF
2 OF 222 ENDOF
3 OF 333 ENDOF
>R 999 R>
ENDCASE
;

1 cs1 . 111 ok
2 cs1 . 222 ok
3 cs1 . 333 ok
4 cs1 . 999 ok

: cs2 >R CASE
-1 OF CASE R@ 1 OF 100 ENDOF
2 OF 200 ENDOF
>R -300 R>
ENDCASE
ENDOF
-2 OF CASE R@ 1 OF -99 ENDOF
>R -199 R>
ENDCASE
ENDOF
>R 299 R>
ENDCASE R> DROP ;

-1 1 cs2 . 100 ok
-1 2 cs2 . 200 ok
-1 3 cs2 . -300 ok
-2 1 cs2 . -99 ok
-2 2 cs2 . -199 ok
0 2 cs2 . 299 ok

注意
 CASEワードを自分で書くまで気づかなかったが、ForthのCASEには微妙な点がある。
 CASE 条件1 OF ワード1 ENDOF
条件2 OF ワード2 ENDOF
ワード3
ENDCASE
条件1、条件2に合致しない場合は、ワード3が実行される。ところがスタックトップの値は残ったままになり、
 ENDCASE で DROP される。このためワード3でスタックに値を積んで残すには工夫が必要になる。

cs1の定義の以下の部分がそれに相当する。
>R 999 R>

Forthの制御構造(IF ELSE THENの場合)

2021-05-02 20:44:40 | 日記
IF THEN の例
: TEST 1 0 > IF TRUE . THEN ;

SEE TEST
: TEST 1 0 > ?BRANCH 6 TRUE . ;

BEGIN AGAINと違い、前方への分岐になる

VIEWで確認すると

12 : IF COMPILE ?BRANCH ?>MARK ; IMMEDIATE
2 : THEN ?>RESOLVE ; IMMEDIATE

BEGINは後方参照なので <MARK, <RESOLVEだったが、今回は前方参照なので >MARK, >RESOLVE になる。

8 : ?>MARK (S -- f addr ) TRUE >MARK ;
9 : ?>RESOLVE (S f addr -- ) SWAP ?CONDITION >RESOLVE ;
3 : >MARK (S -- addr ) HERE 0 , ;
4 : >RESOLVE (S addr -- ) HERE SWAP ! ;

>MARKは、現在のアドレスをスタックに積み、ダミーのアドレス0を書き込んでおく
>RESOLVEは、現在のアドレスを、積んでいたアドレスの場所に書き込む


IF ELSE THEN の例
: TEST 0= IF ." ZERO" ELSE ." NOT ZERO" THEN ;

SEE TEST
: TEST 0= ?BRANCH 13 (.") ZERO BRANCH 13 (.") NOT ZERO ;

VIEWで確認すると

13 : ELSE COMPILE BRANCH ?>MARK 2SWAP ?>RESOLVE ; IMMEDIATE