フォト
無料ブログはココログ

プログラミング

2019年2月 8日 (金)

プログラミング教育

 60年足らずの人生だけど、最も獲得してよかったと思う能力はプログラミング能力だと思う。

 技術系の仕事ではプログラミング能力は必須だ。 しかし、職場の研修やOJTで、教育やトレーニングしようとするとなかなか難しい。

 職場でのプログラミング教育の問題は

  • プログラミング能力獲得の目的があいまい
    誰もが職業プログラマになるわけではないから、手段としてのプログラミングを教えるべき。
  • 講師がプログラミング能力獲得方法を形式知化していない
    プログラミング能力を持っていても、その能力の獲得方法を形式知化していない者は他人に教えられない。

ことだと書いた。(プログラミングの講義(2019/02/01))

 この問題は、これから始まる小学校で始まるプログラミング教育でも言えることかもしれない。

 唐突だけど、

 プログラミングの技能を車の運転に例えるなら、多くの人は問題を解決するための手段として運転技能を獲得している。 問題解決とは、目的地に移動するためや荷物を運ぶためとか趣味としてのドライブとか。

 もちろん、バス・タクシーの運転手、トラックの運転手、F1ドライバーなどの職業ドライバーになろうとすると一定レベル以上の運転技能が必要なことは言うまでもない。 職業ドライバーには、安全に運転する、操縦しにくい車を運転する、速く運転する技能が求められる。

 それらと比べて、普通の人は最低限の運転技能があればことが足りということ。

 普通の人にとって重要なのは、どこに行くのか、なぜ行くのかだ。 そして、車で移動することが合理的であれば運転する。 当然、徒歩や列車で移動することが合理的なら車は運転しない。

 プログラミングに置き換えると、必要のないコーディングはやらない。アプリケーションソフトで問題が解決できるならアプリケーションソフトを使う。 Excelのような汎用ソフトを使うことが合理的ならば汎用ソフトを使えばよい。

 重要なことは、

 どの問題をプログラムで解決するのかを分析すること。 解決手段(既成アプリ、アプリ作成)を考えること。 そして、プログラミングが必要であればアルゴリズムを考えることだ。

 注意しなければならないことは、

 趣味で車を運転する人がいるように、趣味でプログラミングしている人がいる。
プログラミングすること自体が目的の人だ。 このような人に、教えてもらうときには注意した方が良い。 プログラミングは手段ではなく目的だからなぜプログラミングが必要かは教えてくれない。

 さらに重要なことは

 それと、何か聞いてみて、ちゃんと言葉で説明できない人は、人に教えることができないと思って間違いない。 プログラミング言語は曖昧さを排除した言語だ。 母語で明確に説明できない人は抜けのないアルゴリズムを考えられないし、正確なコーディングができないと思って間違いない。


最近の投稿】【最近のプログラミング】【2018の投稿】【2017の投稿】【2016の投稿】【2015の投稿

2019年2月 1日 (金)

プログラミングの講義 <言語仕様とIDEの使い方ではない>

 ★ 言語仕様の解説と開発環境(IDE)の使い方と例題を体験するだけで良いのか?

 プログラミングの講義を頼まれた若い人が集まって実習をどうするか話していた。
聞くとはなしに聞いているとなかなかまとまらないようだ。割り当てられた短い時間で、プログラミングができない人をログラミングができるようにすることはかなり難しい。

 話している人たちは職業プログラマーではない。
もっぱら作業を自動化、効率化するためにプログラムを作っている人たちだ。

 講義する相手も職業プログラマーではない。
作業を自動化効率化する手段として、プログラミング能力を獲得したい人だ。

〇教えることを言葉や図表にして説明できるのか?

 「君達はどうやってプログラミングができるようになったの?」と聞いてみた。
Hello wordを教えてもらったあと、課題を与えらえてクラスメイトと一緒に試行錯誤して覚えたらしい。

 なるほどスパルタだ。

 おそらく、体系だって教えてもらったのではなく、試行錯誤の中から学んだのだろう。
試行錯誤して学ぶことは重要だ。しかし、試行錯誤の結果は暗黙知になることが多い。
そして、暗黙知を伝えることは難しい。

 技能を伝えようとすると、言葉や図表で表すことができる形式知にする必要がある。
彼らが教えてもらった教師も、プログラミング能力の獲得方法が形式知化できていないから、言語仕様だけ教えて、後は課題を与えることしかできなかったのだろう。

 教えてもらっていないことは、教えることができないのだ。

 自分がプログラミングできるようになった過程を、言葉にして説明するか文書にしてみると良いとアドバイスした。

★ 暗黙知は形式知化しなければ伝えることはできない

〇 問題解決手段としてのプログラミング

 ソフトウェア開発が主業務ではない人がプログラミングするときの手順は

  1. 問題を分析
  2. プログラムで解決する問題の明確化
  3. アルゴリズムの決定
  4. コーディング
  5. テスト(不合格なら前に)
  6. 評価

だ。 業務でプログラムを作っていない人や、1人でプログラミングしている人は、上記のステップを意識していないことが多い。

 分業しようとするとステップ間で、決定事項を誤りなく伝えなければならないから、それぞれのステップでアウトプットを明文化することが必要で、そのために、共通のフォーマットや図表が必要になる。

 要件定義書とかプログラム仕様書は分業する上では重要で必ずアウトプットする必要がある。 しかし、1人でプログラミングする場合には途中のステップでアウトプットするのは面倒だし、このステップを意識していないことも多い。

 良く言えばアジャイル型開発だが、そもそも、それぞれのステップが渾然一体となっているので分離することができないだけとも言える。

 プログラムは作れるけど、特定の言語や特定の開発環境にこだわる人はたいていこのタイプだ。 このタイプの人は、プログラミングの過程を形式知化できないので、人に教えることができない。

 プログラミングの講義と言いながら教えていることが、開発環境の使い方だったりする。 さすがにこれだけでは役に立たないので例題を与える。 具体的な質問をすると具体的に教えてくれるが、抽象化していないので応用が効かない。

 つまり、教えられる人はいつまでたってもプログラミングできるようにならない。

 そういえば、彼の案件も言語が指定(C#されている時点で怪しい。C#とVisualStudioの使い方を教えてお茶を濁そうとしているのか?
 言語仕様と開発環境は誰かが形式知化しているから人に教えることができる。 そのほかの部分が形式知化できていないので他所に振ったのか?

★ プログラミングを教える人は暗黙知を形式知化すること

〇 教えることは、言語仕様や開発環境の使い方なのか

 言語仕様を教えたらプログラミングができるようになると思っている人が残念ながらまだいる。コーディングとプログラミングを混同しているのだ。だから、プログラミングの講義の内容が言語仕様と開発環境の説明になったりする。

 職業プログラマーでもなく仕様をプログラミング言語で記述するコーダでもなく、欲しい能力は、問題を解決する手法としてのプログラミング技能という人は多いだろう。

 つまり、すべての人に職業プログラマが必要とする知識・能力が必要ではなく、与えられたり発見した問題を分析して、問題を解決するアルゴリズムを考えて、場合によってはプログラミング言語を使用してソフトウェアを作ることが求められている。

 だから、多くの人にとって、言語仕様やIDEの使い方はプログラミングの極々一部の些細なことで大したことではない。

★ 「プログラミング教育=言語仕様、IDEの使用方法の説明」ではない!

 もちろん講師が用意した例題を解いてみることでもない


最近の投稿】【最近のプログラミング】【2018の投稿】【2017の投稿】【2016の投稿】【2015の投稿

2019年1月12日 (土)

awk <古くて新しいLL>

 awkを使い始めて30年。jgawk.exeをDOSで使ったのが最初だ。

 その後UNIXを使うようになった。 UNIXは設定ファイルもログもテキストファイルだからgrepやcat,cut,sort,head,wcなんかで作業するのだが、ちょっと複雑な作業はawkやperlなどのスクリプト言語(LL)を使うことになる。

 今時は何でもExcelに読み込んで処理できる。 昔は数Gbyteのログはオンメモリで処理できないから逐次処理するしかなかった。逐次処理はOneLinerが使えるかどうかで作業効率がずいぶん違う。 例えば、「httpdのaccesslogから特定の日の05:10:00~06:10:50の間のステータスコード500のアクセス元を取り出す」作業はgrep,cut,wcを使うとできるけどawkを使うと100倍簡単だ。

 当時いつでも使えるようにしていたスクリプトは

  • radiusの認証ログから特定ユーザのlogin/logoutを抜き出すスクリプト
  • UNIXタイムで記録されたsquidのログをローカルタイムに変換するスクリプト

 radiusのlogは1レコードが複数行なのでgrepでは難しい。 UNIXタイムで記録されたsquidのlogは見ただけでは日時が分からないので、サーバーのログを見るたためには必須だった。

 当時perlやgawkは全てのUNIXにインストールされているわけではなかったし、manも満足にインストールされていないことが多かったのでawkを使っていた。 awkはどのUNIXにも必ず入っているしmanも使えた。

 gawkはあるかどうか分からないから拡張機能は使わないようにしていた。 というか「プログラミング言語 awk」で勉強したので拡張機能を知らなかったりする。だから、空白でもなく記号でもない文字を表す正規表現は"\w"でもなく"[[:alnum:]_]"でもなく"[_0-9a-zA-Z]"を使っている。

Awk

 最近はUNIX=LINUXになったのawkはgawk(gnu awk)だawkもあるけどgawkへのlinkになってる。 だから互換性はあまり気にしなくても良いのかもしれない。

 プログラミングしているとアスキーコード表が欲しいときがある。そんな時に使える1liner。これまで一番多く使っている。

↓DOS/Windows

>gawk "{for(i=32;i<128;i++)printf(\"%c %02x\",i,i)}" 

↓UNIX

$ awk '{for(i=32;i<128;i++)printf("%c %02x",i,i)}' 

↓Enterを押すと表示される。

  20 ! 21 " 22 # 23 $ 24 % 25 & 26 ' 27 ( 28 ) 29 * 2a + 2b , 2c - 2d . 2e / 2f
0 30 1 31 2 32 3 33 4 34 5 35 6 36 7 37 8 38 9 39 : 3a ; 3b < 3c = 3d > 3e ? 3f
@ 40 A 41 B 42 C 43 D 44 E 45 F 46 G 47 H 48 I 49 J 4a K 4b L 4c M 4d N 4e O 4f
P 50 Q 51 R 52 S 53 T 54 U 55 V 56 W 57 X 58 Y 59 Z 5a [ 5b \ 5c ] 5d ^ 5e _ 5f
` 60 a 61 b 62 c 63 d 64 e 65 f 66 g 67 h 68 i 69 j 6a k 6b l 6c m 6d n 6e o 6f
p 70 q 71 r 72 s 73 t 74 u 75 v 76 w 77 x 78 y 79 z 7a { 7b | 7c } 7d ~ 7e  7f

 ところで、いまさらだけど、gawkは3.10からscketが使えるようになっていたらしい。

BEGIN {
  host = $1;
  path = "/" $2;
  http = "/inet/tcp/0/" host "/80"
  print "GET " path " HTTP/1.0\nHost: " host "\n" |& http;  
  while(0<(http |& getline)) print;
  close(http);
}

↑こんな感じで HTTPクライアントが書ける。 (当然だけどレンダリングはできない。)


最近の投稿】【最近のプログラミング】【2018の投稿】【2017の投稿】【2016の投稿】【2015の投稿

2019年1月 6日 (日)

cocologタイトル一覧作成awkスクリプト

 cocologにはバックナンバーのタイトル一覧機能が無い。
バックナンバーは日別、週別、月別、カテゴリー別に表示することができるけど、記事のタイトル一覧を表示させる機能が無い。

 しかたないので、毎年1年分のタイトル一覧を作成して投稿している。
さすがにタイトル一覧を手作業で作る気にはならないのでawkを使って自動的に作成している。

 ところが、毎年書いているような気がする。
スクリプト言語(LL)は気合を入れて書くもんじゃないといつも適当に書いているので、1年後に使おうとするとどうやって使うのか思い出せなかったりする。

 ということで、忘れないように今年使ったawkのスクリプトを書いておく。

 post2index.awkは1年分の記事から記事のタイトルとリンク先とカテゴリを取り出して、一覧にするスクリプトだ。

 使うのは gawkとnkf32/iconvなど。
gawkのインストール方法はくぐるとたくさん見つかる。
nkf32/iconvはあれば便利なければ UTF8toUTF16.batを使う。

〇手順

  1. バックナンバー保存
    まず、bknumディレクトリを作って、そこにcocologからバクナンバーを1月づつhtmlファイルに保存する。
    bknum \
             +bn1.html
           :
             +bn12.html
     
  2. 文字コードを変換する
    cocologの文字コードはutf-8なのでSJISに変換する。
    • cmd.exeを使うなら
      こんなバッチファイルを作って
      UTF8toUTF16.bat bknum\*.html bk_sjis.html
    • nkf32.exeを使うなら
      nkf32 -W16L bknum\*.html > bk_sjis.html
    • iconvを使うなら
      iconv -f UTF-8 -t SJIS bknum\*.html > bk_sjis.html
    • awkでもできるけどね
       
  3. 一覧に表示するカテゴリを指定する
    日付とタイトルの間にカテゴリが入るようになっている。
    ここに表示させるカテゴリはpost2index.awkを開いて

    LCATEGORY="日記・コラム・つぶやき 日 書籍・雑誌 書 映画・テレビ TV 趣味 趣 ペット ぺ"

    の行を編集する。
    表示させたいcocolog上カテゴリと表示させる文字をスペースで区切って並べる。
     
  4. 一覧を作る
        gawk -f post2index.awk bk_sjis.html > index.html
  5. coclogの記事作成の[htmlの編集]タブにindex.htmlを張り付ける。
〇スクリプト

post2index.awk
#
# make index from cocolog  entry
# Dec. 2018 / Yoshi
#
# usage: gawk -f post2index.awk bknum\*.html > index.html
#
BEGIN {
    #------ ここを書き換える -----------
    YEAR        = 2018;
    LCATEGORY   = "日記・コラム・つぶやき 日 書籍・雑誌 本 映画・テレビ 映 趣味 趣 ペット ぺ"; 
    NCATEGORY   = 5;
    #-----------------------------------
    FALSE       = 0;
    TRUE        = !FALSE;
    FCATEGORY   = FALSE;
    nf = split(LCATEGORY, a);
    for (i=1; i<=nf; i+=2) {
        CATEGORY[++j,"category"]    = a[i];
        CATEGORY[j  ,"mark"]        = a[i+1];
    }
    CATEGORY[0] = j;
    year        = "";
    month       = "";
    day         = "";
    url         = "";
    title       = "";
    category    = "";
}

#
# get category url
FCATEGORY {
    FCATEGORY = FALSE;
    for (i=1; i<=CATEGORY[0]; i++) {
        if ($0 ~ CATEGORY[i, "category"]) {
            sub(/^[ \t]+/, "");
            CATEGORY[i, "url"] = $0;
        }
    }
    next;
}

/<li class=\"module-list-item\">/ { #"
    FCATEGORY = FALSE;
    next;
}

#
# stor itme list
function stor_link() {
    link[date, "url"]       = url;
    link[date, "title"]     = title;
    link[date, "category"]  = category;
    url         = "";
    title       = "";
    date        = "";
    category    = "";
}

#
# Date
/<h2 class=\"date-header\">[0-9]+年[0-9]+月 ?[0-9]+日/ { #"
    if (match($0, /[0-9]+年[0-9]+月 ?[0-9]+日/)) {
        s = substr($0, RSTART, RLENGTH);
        nf = split(s, a, /[年月日]/);
        year    = a[1];
        month   = a[2];
        day     = a[3];
    }
    date = sprintf("%04d/%02d/%02d", year, month, day);
    if (DEBUG) printf("date=%s\n", date);
}

#
# Category
/<span class=\"post-footers\">/ {    #"
    j = 0;
    for (i=1; i<=CATEGORY[0]; i++) {
        if ($0 ~ CATEGORY[i, "category"]) {
            category = category (j?",":"") CATEGORY[i, "mark"];
            j++;
        }
    }
    if (!FTWITTER) stor_link();
    if (DEBUG) printf("category=|%s|\n", category);
}

#
# make title list
#
END {
    printf("<p>%dの投稿一覧\n", YEAR);
    printf("</p>\n<p>凡例:");   
    for (i=1; i<=CATEGORY[0]; i++) {
        printf("&nbsp;%s:%s&nbsp;", CATEGORY[i, "mark"], CATEGORY[i, "url"])
    }
    for (m=1; m<=12; m++) {
        printf("</p>\n");
        printf("<p>--%04d/%02d--<br>\n", YEAR, m);
        for (d=1; d<=31; d++) {
            date = sprintf("%04d/%02d/%02d", YEAR, m, d)
            if (link[date, "url"]) {
                printf("<a target=\"_blank\" ");
                printf("href=\"%s\">",          link[date, "url"]);
                printf("(%s)",                  date);
                s = sprintf("%-" NCATEGORY "s", link[date, "category"]);
                gsub(/ /, "_", s);
                sub(/_+/, "<span style=\"color: #FFFFFF;\">&</span>",s);
                printf("[<tt>%s</tt>]&nbsp;",   s);
                printf("%s</a><br>\n",          link[date, "title"]);
            }
        }
    }
}

〇UTF-8→SJIS変換バッチファイル
  (http://scripting.cocolog-nifty.com/blog/2017/09/utf-8jis-2668.htmlを参考にしました)

UTF8toUTF16.bat
start /min /wait cmd /c chcp.com 65001 ^& ^( set /p x=""^<nul ^& cmd /u /c type %1 ^) ^>%2
start /min /wait cmd /c chcp.com 932 ^& cmd /c type %2 ^>%3

 



最近の投稿】【最近のプログラミング】【2018の投稿】【2017の投稿】【2016の投稿】【2015の投稿

2018年9月21日 (金)

Bus pirate(4) <スクリプトを動かしてみる>

相も変わらず Bus Pirateで遊んでいる。

 BusPirateには組み込みscript(BASICのサブセット)がある。
terminalインタフェースではデバイスからのレスポンスを待つのは難しいが、scriptを使うとできるようになる。

 以前に買ったLED&KEYを制御してみた。

 このモジュールのLEDコントローラにはTM1638が使われている。インタフェースは、クロック(CLK)に同期したシリアル通信で、データ(DIO)は双方向だ。デバイの選択はSTBをLにする。

 英語版のデータシートは(https://www.mikrocontroller.net/attachment/332035/TM1638_V1.3_EN.pdf)にある。

 BusPirateの raw 2-wireで制御してみる。

 接続はこんな感じ↓

BusPirateLED&KEY
MOSI --- DIO
CLK --- CLK
AUX --- STB
5V -+- VCC
VPU -+ 
GND --- GND

 ビットオーダーには注意が必要だ。SPIやI2CはMSBから送るMSB-firstだがTM1368はLSBから送るLSB-firstだから、"L"コマンドでビットオーダーを切り替えなくてはならない。

 ↓実行結果

HiZ>m 6 1 1		← raw 2-wire
R2W (spd hiz)=( 0 1 )
Ready
2WIRE>WPLc		← PowerON,PullUP,LSBfirst,ctrlAUX
Power supplies ON
Pull-up resistors ON
LSB set: LEAST sig bit first	← "L"command
a/A/@ controls AUX pin		← "c"command
2WIRE>v
Pinstates:
1.(BR)  2.(RD)  3.(OR)  4.(YW)  5.(GN)  6.(BL)  7.(PU)  8.(GR)  9.(WT)  0.(Blk) 
GND     3.3V    5.0V    ADC     VPU     AUX     SCL     SDA     -       -
P       P       P       I       I       I       O       I       I       I
GND     3.29V   5.02V   0.00V   5.02V   H       L       H       H       H
2WIRE>a 0x40 A		← DataCommand: WriteRegister,AutoIncremet
AUX LOW
WRITE: 0x40
AUX HIGH
2WIRE>a 0xC0		← AUX←L、AddressCommand: 00h
AUX LOW
WRITE: 0xC0
2WIRE>  0x76,0x00, 0x79,0x00, 0x38,0x00, 0x38,0x00	← "HELL" 
WRITE: 0x76
WRITE: 0x00
WRITE: 0x79
WRITE: 0x00
WRITE: 0x38
WRITE: 0x00
WRITE: 0x38
WRITE: 0x00
2WIRE>  0x3F,0x00, 0x40,0x01, 0x40,0x01, 0x40,0x01	← "O---"  
WRITE: 0x3F
WRITE: 0x00
WRITE: 0x40
WRITE: 0x01
WRITE: 0x40
WRITE: 0x01
WRITE: 0x40
WRITE: 0x01
2WIRE>A 		← AUX←H
AUX HIGH
2WIRE>a 0x8A A		← DisplayControl: DispON
AUX LOW
WRITE: 0x8A
AUX HIGH
2WIRE>

BP-HELLO

 表示を動かそうとするとコマンドを何回も入力しなければならないので、scriptで動かしてみる。

 BusPireteのscriptはBASICのサブセットなので制限がある。

  • 数値は10進数だけ。16進数は使えない
     "="コマンドで10進、16進、2進の変換ができるので、10進数にする。
  • 変数への代入はLETを使う
     ×A=10、〇LET A=10
  • "DATA"、"READ"コマンドはあるけど"RESTOR"コマンドが無い
  • AUXピンが制御できない
     "AUXPIN 0"でAUXピンが制御できるはずだが、CSしか制御できない(BUG?)
  • "SEND"コマンドのビットオーダーは常にMSB-First
     ターミナルコマンド"L"を引き継がない。

 AUXピンが制御できないのは致命的だ。 仕方がないので、ターミナルコマンドでDataCommand(40h)、DispContorl(88h)を送っておいて、表示データをscriptで送ってみる。

 ↓ターミナルで送るコマンド

m 6 1 1		← raw 2-wire
WPLc		← PowerON,PullUP,LSBfirst,ctrlAUX
a 0x40 A	← DataCommand: WriteRegister,AutoIncremet
a 0xC0		← AUX←L、 AddressCommand: 00h 

 scriptを例えば"hello.bas"というファイル名で保存しておいて、ターミナル・ソフトのファイル送信機能でBusPirateに送ると簡単。 BusPirateが処理する時間を確保するために行当たり500msの時間待ちしている。

 scriptはこんな感じ↓

hello.bas
10 FOR I=1 TO 100
20   GOSUB 1000
30   DELAY 100
40 NEXT I
50 END
999  REM Disp HELLO
1000 IF I<50 THEN SEND 0
1010 IF I<50 THEN SEND 128   
1020 SEND 110
1030 SEND 0
1040 SEND 158
1050 SEND 0
1060 SEND 28
1070 SEND 0
1080 SEND 28
1090 SEND 0
1100 SEND 252
1110 SEND 0
1120 SEND 0
1130 SEND 128
1140 SEND 0
1150 SEND 128
1160 IF I<50 THEN SEND 0
1170 IF I<50 THEN SEND 128
1180 RETURN

↑SENDコマンドで送る数値はビットオーダーを変えてある。 例えば1020行の SEND 110は、表示したいパターンは01110110(76h)なので、01110110b→01101110b→6Eh→110を送る。

 scriptでLSBから1bitづつ送るようにすれば、いちいち変えなくてよいのだがどちらも面倒。

↓動いているところ。

Bphello_1ダウンロード BP-HELLO.mp4 (1110.6K)

↑途中でメッセージが移動する方向が変わる。

 待てよ、AUXではなくCSを使えばいいじゃないかと気が付いた。

###

この記事に書いた制限(BUG?)は↓のFirmware固有のものかもしれない。

HiZ>i
Bus Pirate v3b
Firmware v5.10 (r559)  Bootloader v4.4
DEVID:0x0447 REVID:0x3046 (24FJ64GA002 B8)
http://dangerousprototypes.com
CFG1:0xF9DF CFG2:0x3F7F

最新のFirmwareにアップデートしてみるか



最近の投稿】【最近のCPUボード】【2017の投稿】 【2016の投稿】 【2015の投稿

2018年4月24日 (火)

lcc1802

 以前書いた記事(CDP1802 (15/10/11))のコメントにCDP1802用のCコンパイラの質問があったので調べてみた。 

 結論は、出力されるバイナリが大きいので、RAMを十分載せておけば使える。

lcc1802

 CDP1802は入手したがまだ動かしていない。 (^^;
SBCを作ったとしても、おそらくtinyBASICでも動かしたら満足するのだろう。というわけで、CDP1802用のCコンパイラは調べていなかった。

 調べてみるとlcc1802が見つかった。
lccはOpen SourceのコンパイラでターゲットCPUはAlpha,SPARC,MIPS,x86に対応している。 lcc1802はlccをCDP1802に対応させたもので、COSMAC ELF Membersip Cardのアドオンシールドにarduinoを持たせた 1802 olduino 用に作られたもののようだ。 
https://sites.google.com/site/lcc1802/downloads
からダウンロードできる。

インストール

 lcc42judy.zipをインストールしてみた。
zipを解凍して現れるlcc42ディレクトリをCドライブ直下(C:\)にコピーしろと書いてある。
開発系は C:\Devzにインストールしているので、ここにインストールした。 コンパイル・ドライバ(lcc)が呼び出すコマンドは同じディレクトリを探すので問題はないだろうと考えたのだが、cppが見付からないと怒られた。(^^;
どうやら、プリプロセッサ(cpp.exe)、コンパイラ(rcc.exe)、アセンブラ(asw.exe)のパスは"C:\lcc42\bin"がハードコードしてあるようだ。 仕方がないので、C:\からinkを張っておいた。

エミュレータ

 まだSBCを作っていないのでCOSMACエミュレータ Emma 02で試してみた。
Emma 02はCDP1802が使われている数々のボードやシステムのエミュレーションができる。 その中でシンプルな VELFで試してみた。

Emma02_velf

Emma02_velf_frontpanel

 COSMAC ELFはシリアル・インタフェースを持っていない。Q(#4)をTXD、EF2(#23)をRXDに使ってソフトウェアで実装してある。

 シンプルな COSMAC ELFで試しておけばSBCを作るときにシリアルインタフェースを載せなくて良い。

サンプル

 lcc1802にはサンプルが用意されている。 これらのサンプルはolduino用なので、これを変更してVELFで動くようにする。

 まずはお約束の hello worldから。 examples/helloworldディレクトリにある。

↓helloworld.c

/*
   print the string "hello World!"
*/
#include <nstdlib.h>
void main()
{
printstr("hello World!\n");
}
#include <nstdlib.c>

ライブラリ

 4行目の #include <nstdlib.h>は良いが、最終行の #include <nstdlib.c>は何?である。

 lcc1802はライブラリはなくて、最低限の関数がinclude/nstdlib.cに書いてある。printf()もあるが、最低限の機能だけで出力幅指定はできない。

 文字列出力関数のprintstr()は1文字出力にout(5,d)を呼んでいるが、VELFでは動作しないのでputc()を使うように変更しておく。

↓nstdlib.c内のprintstr()

void printstr(char *ptr){
//    while(*ptr) out(5,*ptr++);
    while(*ptr) putc(*ptr++);
}

 nstdlib.cにはfloatを扱う関数がある。 sdccは賢いので必要な関数だけリンクしてくれるがlcc1802ではnstdlib.cはいつでもコンパイルされるので使わない関数でもオブジェクトに含まれる。float関係の部分だけは "nofloats"が宣言されているとコンパイルしないようになっている。 コマンドラインオプションで -Dnofloatsとすれば良い。

ランタイムライブラリ

 ランタイムライブラリは、include/lcc1802Prolo??.incとinclude/lcc1802Epiloxx.inc。

 これらはのライブラリは、コンパイル済のライブラリをlinkerがリンクするのではなく、ソースに展開している。ソース中にnsddlib.cをインクルードし、アセンブラソース中でlcc1802Prolo??.incとlcc1802Epiloxx.incをインクルードしている。

入出力ルーチン

 IOルーチンは、include/IO1802.incに書いてある。 printf()は1文字出力にputc()を呼んでいる。  lcc1802が想定しているolduinoの1文字出力はout(5,d)を使っているので、VELFでは出力できない。

 IO1802.incに1文字出力(putc())と1文字入力(getc())を書けば良さそうだ。
ここでハタと困った。CDP1802のアセンブラは書いたことが無い。
とりあえず、Emma02にVELFのモニタソース(Emma02\data\Velf\VELFbios-v3.1.LST)があったので、この中の1文字入出力ルーチンを参考に(ほぼコピペ)、Cから引数を渡す部分と戻り値を返す部分を付け加えて、テンポラリ変数をreからrt1に変えた。

↓IO1802.inc

;**********************************************************************
;Transmit Byte via Q connected to RS232 driver
;call with R3 being the previous PC
;Byte to send in D
;Returns with D unaffected
;re.1 = D
;Destroys rt1.0
;----------------------------------------------------------------------
_putc: glo regArg1
txchar: phi rt1
ldi 9 ;9 bits to transmit (1 start + 8 data)
plo rt1
ghi rt1
shl ;set start bit
rshr ;DF=0

txcloop:
bdf $+5 ;10.5   jump to seq to send a 1 bit
req ;11.5   send a 0 bit
br $+5 ;1      jump +5 to next shift
seq ;11.5   send a 1 bit
br $+2 ;1      jump +2 to next shift (NOP for timing)
rshr ;2      shift next bit to DF flag
phi rt1 ;3      save D in rt1.1
DEC rt1 ;4      dec bit count
glo rt1 ;5      get bit count
bz txcret ;6      if 0 then all 9 bits (start and data) sent
ghi rt1 ;7      restore D
NOP ;8.5    pause 1/2 time
br txcloop ;9.5    loop back to send next bit
txcret: ghi rt1 ;7
ghi rt1 ;8
ghi rt1 ;9
NOP ;10.5
seq ;11.5 stop bit
NOP ;1
NOP ;2.5
NOP ;4
NOP ;5.5
NOP ;7
NOP ;8.5
NOP ;9
NOP ;10.5
Cretn

;**********************************************************************
;rx_char
;Receive Byte via EF2 connected to RS232 rt1ceiver
;Recieves 8 bits
;call with R3 being the previous PC
;Returns with Byte received in D and rt1.1
;Destroys rt1.0
;----------------------------------------------------------------------
_getc:
rxchar: ldi 8 ;start bit +7 bits from loop, last bit on returning
plo rt1
ldi 0
rxcw: ;wait for start bit
bn2 rxcw ;each instr takes 9us, we need 104us = 11.5
;delay 1/2 bit time to center samples
NOP ;     Don't test for correct start bit
NOP ;     it will work, if there's too much
NOP ;     noise on the line, shorten the cable!
rxcloop:
NOP ;10.5
b2 $+6 ;11.5 sample rx input bit
ori 80h ;1
br $+4 ;2
phi rt1 ;1
phi rt1 ;2
shr ;3
phi rt1 ;4
DEC rt1 ;5
glo rt1 ;6
bz rxcret ;7
ghi rt1 ;8
br rxcloop ;9
rxcret: ghi rt1 ;8
ghi rt1 ;9
NOP ;10.5
b2 $+4 ;11.5 sample last rx input bit
ori 80h ;1
plo retVal
Cretn

 

コンパイル

 この変更でIntelHexフォーマットの a.hexが作られるようになる。

C:\Users\user> lcc -Dnofloats helloworld.c
And St. Judy's Compiler FLOATS Across your code...
P2HEX/C V1.42 Beta [Bld 87]
(C) 1992,2013 Alfred Arnold
C:\Users\user\AppData\Local\Temp/lcc124682.p==>>a.hex  (3609 Bytes)

しかし、hellowroldだけで3,609byteとは。 

8Queen

 8Queen問題を解くプログラムをコンパイルしてみる。 再帰を使っているのでちゃんと動くだろうか?

/*
* 8Queen.c
* Yoshi / Apr.2018
*/

#include <nstdlib.h>

#define FALSE 0
#define TRUE (!FALSE)
#define u_char unsigned char
#define u_short unsigned short
#define NL "\r\n"
#define getchar getc
int DEBUG = 0;

char table[8];
void clrtbl(void) { int i; for(i=0;i<8;i++) table[i]=0xFF; }

void printtb(char n) {
    int x, y;
    printf(NL "-- %d --" NL, n);
    for (y=0;y<8;y++) {
        putchar('|');
        for (x=0;x<8;x++) {
            if (table[y]==x)
                 putchar('Q');
            else putchar(' ');
            putchar('|');
        }
        puts(NL);
    }
}

char check(int x, int y) {
    int i;

    if (y==0) return TRUE;
    for (i=y-1; 0<=i; i--) {
        if (2<DEBUG) printf("tb[%d]=%d" NL, i, table[i]);
        if (table[i]==x || table[i]==(x-(y-i)) || table[i]==(x+(y-i)))
            return FALSE;
    }
    return TRUE;
}

char place(int x, int y) {
    static int n = 1;

    if (DEBUG) printf("place(%d,%d):" NL, x, y);

    if (7<y) {  /* 解 */
        printtb(n++);
        return FALSE;
    }
    if (7<x) {  /* 見つからない */
        return FALSE;
    }

    if (check(x,y)) {
        table[y] = x;
        if (n==1) printtb(n);
        place(0, y+1);
        table[y] = (u_char)-1;
    }
    return place(x+1, y);
}

void main(void) {
    printf("8 Queen" NL);
    clrtbl();
    place(0,0);
    exit(0);
}

#include <nstdlib.c>

コンパイルすると、4,542byteだった。

↓実行結果

Emma02_velf_terminal

ちゃんと実行されるようだ。

まとめ

 SBCを作るときはRAMをケチらないで載せておけばlcc1802は使える。今時、32kのRAMを積んでも大した出費ではない。

 残念ながら、オリジナルのCOSMAC ELFではRAMが少ないので使えないだろう。もっともオリジナルのCOSMAC ELFを忠実に作るなら、ハンドアセンブルして、8個のスイッチでプログラムを入力する覚悟が必要だ。

 rcc.exeがASxxxxアセンブラを吐くようにするとSDCCのライブラリが使えるようになる。そして、aslinkを使うと出力がもう少しコンパクトになると思う。 興味はあるが、そこまでやるか... 



最近の投稿】【2017の投稿】【2016の投稿】【2015の投稿

最近のCOSMAC/CDP1802

2018年4月15日 (日)

IchigoJam(11) 

LED&KEY(TM1638)をIchigojamで点けてみた。BASICで入出力ポートを制御するとさすがに遅い。あまりに遅いので、マシン語で高速化してみた。

Tm1638_yoshi1
ダウンロード TM1638_Yoshi1.mp4 (1075.3K)

YOSHIがスクロールしているのだけれど、残像が残って認識できないくらい速くなっている。

 福野さんがこのボードをマシン語で制御しておられる。(2018/4/14)
(格安7セグディスプレイ「LED&KEY」を使ってカチカチカウンターづくり、TM1638 x IchigoJam用マシン語SPIドライバの作り方)

どの部分をマシン語で書くか

  このボードに載っているLED&KEY制御用デバイス(TM1638)はSPIもどきでデータを送り受けする。で8個のLEDを制御しようとすると、 8(LED)x16bit/LED+3(command)×8bit/cmd=152bit のデータを送る必要があるから、クロックも152個必要だ。
このクロックをBASICのOUT命令でON/OFFしているから、データI/Oが152回、クロックが304回で合計456回IN/OUT命令を実行しなければならない。

 高速化するならば、データを送り受けする部分をマシン語にすれば効果があるだろう。

 メモリダンプ・プログラムを書いたときの手法でやってみる。IchigoJam(4) (2015/06/01)
Cで書いてgccでコンパイルして、逆汗して、BASICのPOKE文を作る。
逆アセンブラはgccのツールチェーンに入っているobjdumpを使った。
逆汗リストからPOKE文の作成にはawkを使った。今どきならpythonオヤジ世代ならperlだがおじいちゃん世代はawkだ。
 (IJUtils (http://ijutilities.micutil.com/#Software)のIJBin2Pokeを使うと簡単にできるらしい)

Cソース

 Cのソースはこんな感じ。

/*
* access.c
* Apr.2018/Yoshi
*/
#include <stdio.h>
#include <stdlib.h>

#define    GPIO1_MASKED_ACCESS    ((volatile uint32_t *)0x50010000)
#define    GPIO0_MASKED_ACCESS    ((volatile uint32_t *)0x50000000)

#define    BIT_LED    5    //GPIO1_5
#define    BIT_STB    0    //GPIO1_0
#define    BIT_CLK    1    //GPIO1_1
#define    BIT_DIO    5    //GPIO0_5

#define    STB_L    0
#define    STB_H    (1<<BIT_STB)
#define    CLK_L    0
#define    CLK_H    (1<<BIT_CLK)

#define    STB(d)    GPIO1_MASKED_ACCESS[1<<BIT_STB]=(d)
#define    CLK(d)    GPIO1_MASKED_ACCESS[1<<BIT_CLK]=(d)
#define    CLKIN()   GPIO1_MASKED_ACCESS[1<<BIT_CLK]
#define    DI()      GPIO0_MASKED_ACCESS[1<<BIT_DIO]
#define    DO(d)     GPIO0_MASKED_ACCESS[1<<BIT_DIO]=(d)

#define    GPIO0_DIR    *((volatile uint32_t *)0x50008000)
#define    GPIO0_10_IN    0
#define    GPIO0_10_OUT    (1<<BIT_DIO)

#pragma GCC optimize("O1")
void wait(void) {
    int w=5;
    while (0<w) {
        w--;
        CLKIN();
    }
}
#pragma GCC optimize("Os")

/*
* receive 16bit
* uint32_t recv(uint32_t data)
* arg : uint32_t data : receive data
* ret : uint32_t : receive data
*/
uint32_t recv(uint32_t data) {
    int i;
    GPIO0_DIR &= ~(1<<BIT_DIO);    //DIO -> input
    for (i=0; i<16; i++) {
        CLK(CLK_L);
        wait();
        data <<= 1;
        data |= DI();
        CLK(CLK_H);
        wait();
    }
    data >>= BIT_DIO;
    return data;
}

/*
* send 8/16bit data
* uint32_t send(uint32_t data)
* arg : uint32_t send data
* ret : uint32_t :
* rem : send data : 1xxx xxxx CCCC CCCC : command (8bit)
*                   0xxx xxDD DDDD DDDD : data(16bit)
*/
uint32_t send(uint32_t data) {
    int i = (data & 0x8000)? 8: 0;    //check command flag
    data <<= BIT_DIO;
    GPIO0_DIR |= (1<<BIT_DIO);        //DIO -> output
    STB(STB_L);
    for (; i<16; i++) {
        CLK(CLK_L);
        DO(data);
        data >>= 1;
        wait();
        CLK(CLK_H);
        wait();
    }
}

void main(void) {
    wait();
    recv(0);
    send(0);
}

  TM1638にデータを送る関数はuint32_t send(uint32_t data)でデータを受ける関数はuint32_t recv(uint32_t dummy)だ。

 IchigojamのUSR関数の解説では、hint16_t foo(uint16_t arg , uint8_t* mem, uint8_t* font)となっているが気にしない。

 第1引数にはUSR(address , num)で呼んだときのnumがR0レジスタ経由で渡される、第2引数には仮想メモリの先頭アドレスが、第3引数にはキャラクタ・パターン・テーブルの先頭アドレスが渡されるが今回は使わないので宣言しない。

 ARMの関数呼び出し規約では、4個までの引数はR0~R3レジスタを使い、5個目以上の引数はスタック経由で渡すらしい。 第2引数(uint8_t* mem)、第3引数(uint8_t* font)を宣言すると、R1とR2レジスタをコンパイラが使わなくなるので第2、第3引数は宣言しなかった。

マスクアクセス(masked access)

 Ichihojamに使われているCPU LPC1114FN28には、マスク・アクセスという機能がある。福野さんのサイトに解説がある。
(マシン語でLEDを光らせよう! - IchigoJamではじめるArmマシン語その4)

 一般に出力ポートの特定のビットを制御するときには、他のビットに影響しないようにする必要がある。
C言語では、出力ポートPxに'1'や'0'を出力するには、Px |= (1<<BIT)、Px &= ~(1<<BIT)
と書くことが多い。

もっと詳しく書くと
 W <- Px
 W <- W & (1<<BIT)
 W <-  DATA << BIT
 Px <- W

という処理が必要だ。つまり、変数Wに一旦ポートのデータを読んできて書き換えたいビットだけ書き換えてもう一度ポートに出力する処理が必要になる。

 簡単に言うと、この面倒な処理を一度のアクセスで実現するのがマスク・アクセス機能で、GPIOのベース・アドレス付近にこの機能が割り当てられている。

 C言語的には、ベースアドレスに配列 uint32_t MSKED_ACCESS[4096] があって。配列の要素がそれぞれビット・パターン(マスクパターン)に対応している。

 例えば、IchigojamのGPIO1のbit(5)に接続されているLEDを制御する場合。

 GPIO1のベースアドレスは 0x50010000で、データ・レジスタ GPIO1_DATA は 0x500103FFCにマッピングされている。

 LEDを点灯させるには GPIO1_DATA(5)に'1'を出力すればよいから

 GPIO1_DATA |= (1<<5);

となる。これをマスク・アクセスを使うと

 GPIO1_MASKED_ACCESS[1<<5] = (1<<5);

でよい。出力する値は I<<5= 0b00010000でなくても、5ビットが1であれば良く、0b11111111でも良い。

 Cで書くと手間はあまり変わらないが、マシン語に翻訳すると

      GPIO1_DATA |= (1<<5);

    movs    r3, #32     ; r3 <- 1<<BIT_LED
    ldr     r2, .L2     ; r2 <- &PIO1_DATA
    ldr     r1, [r2]    ; r1 <- *PIO1_DATA
    orrs    r3 r1       ; r1 <- *PIO1_DATA | (1<<BIT_LED)
    str     r3, [r2]    ;(PIO1_DATA <- r1
    bx      lr          ; return_
    .align  2
.L2:
    .word    0x50013FFC

   GPIO1_MASKED_ACCESS[1<<5] = (1<<5);

    movs    r2, #32     ; r2 <- 1<<BIT_LED
    ldr     r3, .L5     ; r3 <- &PIO1_MASKED_ACCESS[1<<BIT_LED]
    str     r2, [r3]    ; PIO1_MASKED_ACCESS[1<<BIT_LED] <- 1<<BIT_LED
    bx      lr          ; return    ;
    .align   2
.L5:
    .word    0x50010008 ; &PIO1_MASCED_ACCESS[1<<BIT_LED]

コードは短くなっている。

 コードが減るより重要なことは、ポートアクセスは一旦ポートの状態を読んでいるので、処理中にポートの状態が変わってしまうと誤動作してしまう。 ところが、マスクアクセスはポートの状態を読み出さないので、誤動作する心配がないということである。

ウエイト(時間待ち)

 Cのソースはごく普通に16/8bitのデータを受けて1ビットづつ出力する処理だ。

 TM1638のデータシートを見るとクロックの最小パルス幅(PWCLK)は400ns、データセットアップタイム(tSETUP) データホールドタイム(tHOLD)はそれぞれ100nsとなっている。

 BASICは遅いからクロック出力はOUT 2,0/OUT 2,1で良いが、
マシン語に置き換えると速すぎる。'1'又は'0'を出力して400nsは出力データが変わらないように時間待ちする必要がある。

最適化

 gccは-Oオプションで最適化レベルを指定できる。実行速度よりコード領域を最小化するオプション -Os は強力だ。 Ichigojamのようにマシン語で使える領域が少ない場合には有難い。

 ところが、-Osオプションで最適化すると、ループを回って時間待ちするルーチンは最適化されて無くなってしまう。

 void wait(void { int i; for (i=0;i<100;i++) ; }

の結果はどこにも影響を与えないので無くても良いということだけど、ちょっと困る。

 最適化されないようにするには、wati()関数内で volataile属性の変数にアクセスすれば良い。 volatile属性は、例えばIO関係のレジスタや割り込みで使用される変数のようにアクセスするたびに内容が変わるという意味だ。

 つまり、コンパイラに「あんたが知らないうちに値が変わってる変数だヨ」と教えておくと、最適化しないでくれる。

 これをコンパイルして、wati(),recv(),send()をBASICのPOKE文にすると

REM wait()
POKE #700,#03,#4B,#1A,#68,#1A,#68,#1A,#68,#1A,#68,#1B,#68,#70,#47,#C0,#46,#08,#00,#01,#50
REM recv()
POKE #714,#20,#21,#F8,#B5,#04,#00,#10,#25,#02,#27,#0B,#4A,#13,#68,#8B,#43,#13,#60
POKE #726,#00,#23,#09,#4E,#33,#60,#FF,#F7,#E8,#FF,#08,#4B,#1B,#68,#64,#00,#37,#60
POKE #738,#01,#3D,#1C,#43,#FF,#F7,#E0,#FF,#00,#2D,#F0,#D1,#60,#09,#F8,#BC,#02,#BC
POKE #74A,#08,#47,#00,#80,#00,#50,#08,#00,#01,#50,#80,#00,#00,#50
REM send()
POKE #758,#20,#21,#0F,#4A,#F8,#B5,#13,#68,#0B,#43,#13,#60,#00,#22,#02,#27,#0C,#4B
POKE #76A,#04,#04,#E4,#0F,#1A,#60,#E4,#00,#45,#01,#00,#23,#0A,#4E,#33,#60,#0A,#4B
POKE #77C,#1D,#60,#FF,#F7,#BF,#FF,#01,#34,#37,#60,#6D,#08,#FF,#F7,#BA,#FF,#10,#2C
POKE #78E,#F1,#D1,#F8,#BC,#02,#BC,#08,#47,#C0,#46,#00,#80,#00,#50,#04,#00,#01,#50
POKE #7A0,#08,#00,#01,#50,#80,#00,#00,#50

BASICソース

BASICのプログラムはこんな感じ

1 REM TM1638 demo2
2 CLV: VIDEO 0
3 REM === ===
3 REM [0]-[7]    :7SEG data:
3 REM [8]        :guard
3 REM [9]        :LED data
3 REM [20]-[22]  :key
3 REM [23]-[26]  :key scan data
3 REM
3 REM === FONT ===
3 REM  -- a       dpgfe dcba   dpgfe dcba   dpgfe dcba   dpgfe dcba
3 REM |f |b     0: 0011 1111 1: 0000 0110 2: 0101 1011 3: 0100 1111
3 REM  -- g       dpgfe dcba   dpgfe dcba   dpgfe dcba   dpgfe dcba
3 REM |e |c     4: 0110 0110 5: 0110 1101 6: 0111 1101 7: 0000 0111
3 REM  -- d .dp   dpgfe dcba   dpgfe dcba   dpgfe dcba   dpgfe dcba
3 REM           8: 0111 1111 9: 0110 1111 A: 0111 0111 B: 0111 1100
3 REM             dpgfe dcba   dpgfe dcba   dpgfe dcba   dpgfe dcba
3 REM           C: 0011 1001 D: 0101 1110 E: 0111 1001 F: 0111 0001
3 LET [80],#3F,#06,#5B,#4F, #66,#6D,#7D,#07, #7F,#6F,#77,#7C, #39,#5E,#79,#71
4 REM             dpgfe dcba   dpgfe dcba   dpgfe dcba   dpgfe dcba
4 REM           H: 0111 0110 L: 0011 1000 G: 0011 1101 Y: 0110 1110
4 REM             dpgfe dcba   dpgfe dcba   dpgfe dcba   dpgfe dcba
4 LET [96],#76,#38,#3D,#6E
5 D=3        :'Duty:1/16
6 'P=0        :'Position 0
7 'L=8        :'Length 8

100 REM === MAIN ===
120 REM I/O STB:1 CLK:2 DI:IN3/OUT10
120 OUT 1,1: OUT 2,1
130 GOSUB @DSPOFF
135 @SCRL
140 REM SCROOL
140 LET [0],0,0,0,[99],[80],[85],[96],[81],0    :'"YOSHI"
150 @LOOP1
160 GOSUB @GETKEY: [9]=[20]
170 'PRINT HEX$([23],4);" ";HEX$([24],4);" ";BIN$([20],8)
170 PRINT BIN$([20],8);:FOR I=0 TO 7:?CHR$(8);:NEXT
180 N=N+1:
190 IF [20]=#81 THEN GOSUB @DSPNUM: GOTO @SCRL
200 'ELSE
210  P=0: L=8: GOSUB @DSP7SEG
220  [8]=[0]: COPY #800, #802, 16
280 GOTO @LOOP1

500 REM Disp number
500 REM N:number
500 REM W:work Y:counter
500 @DSPNUM
510 W=N: LET[0],0,0,0
520 FOR Y=7 TO 3 STEP -1
530   IF W=0 THEN [Y]=0
540   [Y]=[80+W%10]: W=W/10
550 NEXT
560 GOSUB @DSP7SEG
570 RETURN

600 REM    KeyScan
600 REM    Command=#42:Rread key-scan data
600 REM    [20]:K1 [21]:K2 [22]:K3 [23]-[26] scan data
600 REM S:SendData R:RecvData Y:counter
600 @GETKEY
610 S=#42: GOSUB @SENDC
620 FOR Y=23 TO 24
630   GOSUB @RECV
640   [Y] = R
650 NEXT
660 OUT 1,1    :'STB off
670 X=#8888
680 FOR Y=0 TO 2: U=Y+20
690   [U]=    ([23]&X)>>(1-Y)        :'-1---5---2---6--
690   [U]=[U]|([24]&X)>>(3-Y)        :'-1-3-5-7-2-4-6-8
700   [U]=([U]&#FF)|([U]&#FF00)>>7   :'--------12345678
710   X=X>>1
720 NEXT
730 RETURN
                   
800 REM DISP OFF
800 REM S:Send data #80=Command:Display Off
800 @DSPOFF
810 S=#80+D: GOSUB @SENDC: OUT 1,1
820 RETURN

900 REM DISP 7SEG (address add mode)
900 REM D: Dimmer 0-7 #80:Disp OFF
900 REM P: Position
900 REM L: Length
900 REM [0]-[7]: 7SEGx8, [9]:LEDx8
900 @DSP7SEG
910 S=#40:GOSUB @SENDC: OUT 1,1        :'#40: write register | auto increment
920 S=#C0+P*2: GOSUB @SENDC            :'address
930 FOR I=0 TO (L-1)
940   S=[P+I]: GOSUB @SENDD            :'7SEG
960 NEXT
970 OUT 1,1                            :'STB off
980 @DSPON: S=#88 | D: GOSUB @SENDC: OUT 1,1    :'88H:disp on
990 RETURN

1000 REM R:Recv data(16bit)
1000 @RECV
1000 R=USR(#714,0)
1010 'OUT 10,-1            :'OUT10->IN3
1020 'FOR X=1 TO 16
1030 '  OUT 2,0            :'OUT2:CLK
1040 '  R = R<<1: R=R+IN(3)    :'IN3, DI
1050 '  OUT 2,1            :'OUT2:CLK
1060 'NEXT
1070 RETURN

1100 REM S: Send data
1100 REM 1xxxxxxx CCCCCCCC : command(8bit)
1100 REM 0xxxxxDD DDDDDDDD : data(10bit)
1100 @SENDC        :'send command
1110 S=S|#8000
1120 @SENDD        :'send data
1130 X=USR(#758,S)
1130 'OUT 1,0        :'STB on
1140 'M=16: IF S&#8000 THEN M=8
1150 'FOR X=1 TO M
1160 '  OUT 2,0        :'OUT2:CLK
1170 '  OUT 10, S&1    :'OUT3:DIO
1180 '  OUT 2,1        :'OUT2:CLK
1190 '  S = S>>1
1200 'NEXT
1210 RETURN

コマンドは8ビット、データは16ビットで送受信するように変えた。

 メモリ容量が足りないので、ソースの中にマシン語が書けない。
マシン語をメモリに書き込む処理は最初に実行すれば良い。そしてプログラム実行中に書き換える必要性はないので、プログラム実行前にダイレクトモードで実行すればよい。メモリにセーブしたい場合には、別プログラムにしてLRUNで呼び出すことになる。

 1024バイトに収まるようにパックすると、

10 'TM1638 demo2
20 CLV:VIDEO 0
30 LET[80],#3F,#06,#5B,#4F,#66,#6D,#7D,#07,#7F,#6F,#77,#7C,#39,#5E,#79,#71
40 LET[96],#76,#38,#3D,#6E:D=3:OUT 1,1:OUT 2,1:GSB @DSPOFF
50 @SCRL:LET[0],0,0,0,[99],[80],[85],[96],[81],0
60 @LOOP1:GSB @GETKEY:[9]=[20]:? BIN$([20],8):FOR I=0 TO 7:?CHR$(8);:NEXT:N=N+1
70 IF[20]=#81 GSB @DSPNUM:GOTO @SCRL
80 P=0:L=8:GSB @DSP7SEG:[8]=[0]:COPY #800,#802,16:GOTO @LOOP1
90 @DSPNUM:W=N:LET[0],0,0,0:FOR Y=7 TO 3 STEP-1:IF W=0 [Y]=0
100 [Y]=[80+W%10]:W=W/10:NEXT:GSB @DSP7SEG:RTN
110 @GETKEY:S=#42:GSB @SENDC:FOR Y=23 TO 24:GSB @RECV:[Y]=R:NEXT:OUT 1,1:X=#8888
120 FOR Y=0 TO 2:U=Y+20:[U]=([23]&X)>>(1-Y):[U]=[U]|([24]&X)>>(3-Y)
130 [U]=([U]&#FF)|([U]&#FF00)>>7:X=X>>1:NEXT:RTN
140 @DSPOFF:S=#80+D:GSB @SENDC:OUT 1,1:RTN
150 @DSP7SEG:S=#40:GSB @SENDC:OUT 1,1:S=#C0+P*2:GSB @SENDC:FOR I=0 TO(L-1):S=[P+I]
160 GSB @SENDD:NEXT:OUT 1,1
170 @DSPON:S=#88|D:GSB @SENDC:OUT 1,1:RTN
180 @RECV:R=USR(#714,0):RTN
190 @SENDC:S=S|#8000
200 @SENDD:X=USR(#758,S):RTN
'974bytes

マシン語

 わざわざ窮屈なメモリ環境でBASICからマシン語を使わなくて良いと思う。 Cで書いていると「全部Cで書いたらいいじゃないか」と思ったりして...

 趣味やパズルだと考えるとやる気がわいてくるのだけれど。^^)



最近の投稿】【最近のCPUボード】 【最近のIchigoJam】 【2017の投稿】 【2016の投稿】 【2015の投稿

2018年4月 8日 (日)

IchigoJam(10)

 何気なくaitendoを見ていたら、マイコンボードにちょうど良さげな表示器を見つけた。

aitendoで売ってるボードはたいていamazonでも売っているので調べたら、270円で売っていたのでAmazonで買った。今は2個で850円に値上がりしている。

Ledkey

 TM1638はTitan Micro ElectronicsのLED表示用ICで、英語版のデータシートは(https://www.mikrocontroller.net/attachment/332035/TM1638_V1.3_EN.pdf)にある。

 このICは7セグLED10桁の表示と、8x3のキー・マトリクスのスキャンができる。8279に似てると言ってわかるのはオジサンだろう。

 インタフェースはSPIモドキ。 TM1638はSIとSOが分離していない(オープンドレイン)ので、SPIで接続するならMaster側のMOSIとMISOを接続して使う。出力が衝突する可能性があるのでMOSIに1kΩの抵抗を入れておく。

Connect_spi

 サンプルはたくさんある。特にarduino用のサンプルは検索するとたくさん見つかる。

Ichgojamでやってみた。 IchigojamはI2C用にオープンドレイン出力のOUT10(IN3)がある。これをDI/DOUTに使うと簡単だ。 STBとCLKはOUT1,OUT2でON/OFFする。

↓こんな感じ

1 REM TM1638 
2 CLV: VIDEO 0
3 REM === ===
3 REM [0]-[7]	:7SEG data: 
3 REM [8]	:LED data
3 REM [20]-[22]	:key
3 REM [23]-[26]	:key scan data
3 REM
3 REM === FONT ===
3 REM  -- a       dpgfe dcba   dpgfe dcba   dpgfe dcba   dpgfe dcba 
3 REM |f |b     0: 0011 1111 1: 0000 0110 2: 0101 1011 3: 0100 1111 
3 REM  -- g       dpgfe dcba   dpgfe dcba   dpgfe dcba   dpgfe dcba
3 REM |e |c     4: 0110 0110 5: 0110 1101 6: 0111 1101 7: 0000 0111
3 REM  -- d .dp   dpgfe dcba   dpgfe dcba   dpgfe dcba   dpgfe dcba
3 REM           8: 0111 1111 9: 0110 1111 A: 0111 0111 B: 0111 1100
3 REM             dpgfe dcba   dpgfe dcba   dpgfe dcba   dpgfe dcba
3 REM           C: 0011 1001 D: 0101 1110 E: 0111 1001 F: 0111 0001
3 LET [80],#3F,#06,#5B,#4F, #66,#6D,#7D,#07, #7F,#6F,#77,#7C, #C9,#5E,#79,#71
4 REM             dpgfe dcba   dpgfe dcba   dpgfe dcba   dpgfe dcba
4 REM           H: 0111 0110 L: 0011 1000 G: 0011 1101 -: 0100 0000
4 LET [96],#76,#38,#3D,#40
5 D=3		:'Duty:1/16
6 'P=0		:'Position 0
7 'L=8		:'Length 8

100 REM === MAIN ===
120 REM I/O STB:1 CLK:2 DI:IN3/OUT10
120 OUT 1,1: OUT 2,1 
130 GOSUB @DSPOFF
135 @SCRL
140 REM SCROOL
140 LET [0],0,0,0,[96],[94],[97],[97],[80]	:'"HELLO"
150 @LOOP1
160 GOSUB @GETKEY: [8]=[20]
170 'PRINT BIN$([20],8);:FOR I=0 TO 7:?CHR$(8);:NEXT
180 N=N+1: 
190 IF [20]=#81 THEN GOSUB @DSPNUM: GOTO @SCRL
200 'ELSE
210  P=0: L=8: GOSUB @DSP7SEG
220  W=[0]
230  FOR I=0 TO 6
240    [I]=[I+1]
250  NEXT
260  [7]=W
270 GOTO @LOOP1

500 REM Disp number
500 REM N:number
500 REM W:work Y:counter
500 @DSPNUM
510 W=N: LET[0],0,0,0
520 FOR Y=7 TO 3 STEP -1
530   IF W=0 THEN [Y]=0
540   [Y]=[80+W%10]: W=W/10
550 NEXT
560 P=3: L=5: GOSUB @DSP7SEG
570 RETURN

600 REM	KeyScan
600 REM	Command=#42:Rread key-scan data
600 REM	[20]:K1 [21]:K2 [22]:K3 [23]-[26] scan data
600 REM S:SendData R:RecvData Y:counter
600 @GETKEY
610 S=#42: GOSUB @SENDC
620 FOR Y=23 TO 26
630   GOSUB @RECV
640   [Y] = R
650 NEXT
660 OUT 1,1	:'STB off
670 LET [20],0,0,0:
680 FOR Y=0 TO 3
690   [20]=[20]|([Y+23])>>Y
700 NEXT
720 RETURN

800 REM DISP OFF
800 REM S:Send data #80=Command:Display Off
800 @DSPOFF
810 S=#80+D: GOSUB @SENDC: OUT 1,1
820 RETURN

900 REM DISP 7SEG (address add mode)
900 REM D: Dimmer 0-7 #80:Disp OFF 
900 REM P: Position
900 REM L: Length
900 REM [0]-[7]: 7SEGx8, [8]:LEDx8
900 @DSP7SEG
910 S=#40:GOSUB @SENDC :OUT 1,1	:'#40: write register | auto increment
920 S=#C0+P*2: GOSUB @SENDC 	:'address
930 FOR I=0 TO (L-1)
940   S=[P+I]: GOSUB @SENDD	:'7SEG
950   S=([8]>>I)&1: GOSUB @SENDD:'LED
960 NEXT
970 OUT 1,1			:'STB off
980 @DSPON: S=#88 | D: GOSUB @SENDC: OUT 1,1	:'88H:disp on
990 RETURN

1000 REM R:Recv data
1000 @RECV
1010 OUT 10,-1			:'OUT10->IN3
1020 FOR X=1 TO 8
1030   OUT 2,0			:'OUT2:CLK
1040   R = R<<1: R=R+IN(3)	:'IN3, DI
1050   OUT 2,1			:'OUT2:CLK
1060 NEXT
1070 RETURN

1100 REM S: Send data
1100 REM need STB ON
1100 @SENDC		:'send command
1110 OUT 1,0		:'STB on
1120 @SENDD		:'send data
1130 FOR X=1 TO 8
1140   OUT 2,0		:'OUT2:CLK
1150   OUT 10, S&1	:'OUT3:DIO
1160   OUT 2,1		:'OUT2:CLK
1170   S = S>>1
1180 NEXT
1190 RETURN

↑間違っていたので修正(2018/4/11) >(690   [20]=[20]|([Y+23]X)>>Y)

↑これでは1024バイトに収まらないので、省略形の命令に変えて、スペースを取って、マルチステートメントに変える。↓

10 ' TM1638
20 CLV:VIDEO 0
30 LET[80],#3F,#06,#5B,#4F,#66,#6D,#7D,#07,#7F,#6F,#77,#7C,#C9,#5E,#79,#71
40 LET[96],#76,#38,#3D,#40:D=3:OUT 1,1:OUT 2,1:GSB @DSPOFF
50 @SCRL:LET[0],0,0,0,[96],[94],[97],[97],[80]
60 @LOOP1:GSB @GETKEY:[8]=[20]:N=N+1:IF[20]=#81 GSB @DSPNUM:GOTO @SCRL
70 P=0:L=8:GSB @DSP7SEG:W=[0]:FOR I=0 TO 6:[I]=[I+1]:NEXT:[7]=W:GOTO @LOOP1
80 @DSPNUM:W=N:LET[0],0,0,0:FOR Y=7 TO 3 STEP-1:IF W=0[Y]=0
90 [Y]=[80+W%10]:W=W/10:NEXT:P=3:L=5:GSB @DSP7SEG:RTN
100 @GETKEY:S=#42:GSB @SENDC:FOR Y=23 TO 26:GSB @RECV:[Y]=R:NEXT:OUT 1,1
110 LET[20],0,0,0:FOR Y=0 TO 3:[20]=[20]|([Y+23]X)>>Y:NEXT:RTN
120 @DSPOFF:S=#80+D:GSB @SENDC:OUT 1,1:RTN
130 @DSP7SEG:S=#40:GSB @SENDC:OUT 1,1:S=#C0+P*2:GSB @SENDC:FOR I=0 TO(L-1):S=[P+I]
140 GSB @SENDD:S=([8]>>I)&1:GSB @SENDD:NEXT:OUT 1,1
150 @DSPON:S=#88|D:GSB @SENDC:OUT 1,1:RTN
160 @RECV:OUT 10,-1:FOR X=1 TO 8:OUT 2,0:R=R<<1:R=R+IN(3):OUT 2,1:NEXT:RTN
170 @SENDC:OUT 1,0
180 @SENDD:FOR X=1 TO 8:OUT 2,0:OUT 10,S&1:OUT 2,1:S=S>>1:NEXT:RTN

↑間違っていたので修正(2018/4/11) (110 LET[20],0,0,0:FOR Y=0 TO 3:[20]=[20]|([Y+23]X)>>Y:NEXT:RTN)

↓動かしたところ

Tm1638

ダウンロード TM1638.mp4 (1058.8K)

 さすがに遅い。 ソフトでCLKをON/OFFしているのが原因だ。

 もう少し早くなりそうだが。



最近の投稿】【最近のCPUボード】 【最近のIchigoJam】 【2017の投稿】 【2016の投稿】 【2015の投稿

2017年10月 8日 (日)

STC12C2052(2) <8Queen問題>

aitendoで買ったSTCmicroのSTC12C2052でプログラミングできるようになったので、8Queen問題を解くプログラムを走らせてみた。

 以前作った8031SBC用に書いたソースを変更した。 (8Queen2011/09/15)
 8031(8051のROM無し)は内臓RAMが128byteしかないので、スタックを消費する再帰プログラムは厳しい。
 ↓オプション --idata_loc 0x0000 --idata_size 128(8031用)でコンパイルするとスタックは98byteとれた。

-- 8031; ---
Internal RAM layout:
      0 1 2 3 4 5 6 7 8 9 A B C D E F
0x00:|0|0|0|0|0|0|0|0|a|b|b|c|d|e|e| |
0x10:| | | | | | | | | | | | | | | | |
0x20:|T|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|
0x30:|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|
0x40:|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|
0x50:|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|
0x60:|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|
0x70:|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|
0x80:| | | | | | | | | | | | | | | | |
0x90:| | | | | | | | | | | | | | | | |
0xa0:| | | | | | | | | | | | | | | | |
0xb0:| | | | | | | | | | | | | | | | |
0xc0:| | | | | | | | | | | | | | | | |
0xd0:| | | | | | | | | | | | | | | | |
0xe0:| | | | | | | | | | | | | | | | |
0xf0:| | | | | | | | | | | | | | | | |
0-3:Reg Banks, T:Bit regs, a-z:Data, B:Bits, Q:Overlay, I:iData, S:Stack, A:Absolute

STC12C2052は256byteの内臓RAMが載っているので、オプション --idata_size 240でコンパイルするとスタックは192byte確保できている。(なぜ--idata_size 256ではないかは後で)

-- STC12C2052 ---
Internal RAM layout:       0 1 2 3 4 5 6 7 8 9 A B C D E F 0x00:|0|0|0|0|0|0|0|0|a|a|a|a|a|a|a|a| 0x10:|a|b|b|b|b|b|b|b|b|b|b|b|b|b|b|b| 0x20:|b|b|b|b|b|b|b|b|b|c|d|d|e|Q|Q|Q| 0x30:|Q|Q|Q|Q|Q|S|S|S|S|S|S|S|S|S|S|S| 0x40:|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S| 0x50:|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S| 0x60:|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S| 0x70:|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S| 0x80:|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S| 0x90:|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S| 0xa0:|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S| 0xb0:|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S| 0xc0:|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S| 0xd0:|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S| 0xe0:|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S| 0xf0:| | | | | | | | | | | | | | | | | 0-3:Reg Banks, T:Bit regs, a-z:Data, B:Bits, Q:Overlay, I:iData, S:Stack, A:Absolute

 プログラム用のメモリが2kbyteしかないので、printf()は使えない。
↓実行結果

Stc12c2052_8q

 ところで、内蔵のR/C発振器でUARTを使おうとしたら、一発では動かない。ソースはそのままで動くと思っていた。

 通信速度の設定はTH1、TL1に設定する値で決まる。

   TH1 = TL1 = 256 - (クロック周波数 / 12 / 32 / 通信速度)

 SDCCのライブラリに autobaud()という関数がある。この関数を呼んでRxDにCR(0Dh)を送ると1byte(8bit)分の時間を測ってTH1、TL1レジスタを自動的に設定してくれるという便利な関数だ。 

 この関数を使ってみると、4,800bpsでは文字化けが多い、4,800bpsで動いていも次の日には文字化けが多くて使い物にならなくなる。確実に動かすには1,200bpsまで下げなければならない。

 TH1、TL1レジスタにセットされている値を表示させてみると大きい。 (TH1,TL1=FDh @4,800bps)
この値が大きいとズレが大きくなる。または正確なクロック周波数の発振器が必要だ。

 データーシートではクロック周波数は18.234MHzと書いてあることが多いので、R/C発振器の周波数も18.234MHzかと思っていた。 よく読むとそんなことは書いてない。

 データーシートに「R/C発振器の周波数を知る方法」が載っている。
電源ON直後、内蔵RAMのF8h~FBh、FCh~FFhを読むことで分かるのだそうだ。F8h~FBhに直前の設定値、FCh~FFhに工場出荷時の設定値が書かれている。この領域はRAMとして普通に使えるので当然データを書いたり読んだりできる。

 確認してみようと、この領域を読みだすと全て 00hだ。 (?_?

 SDCCはstart_up時にIDATA領域をクリアする。 (SDCC/lib/src/mcs51/crtclean.asm) つまり、IDATA領域の末尾の部分(F8h~FFh)も00hでクリアされているようだ。

 SDCCは --idata_loc と --idata_size オプションでIDATAのサイズと位置を指定できる。そこで、内蔵RAM(IDATA)は256byteあるけど --idata_size 240 とすると、F8h~FFhがクリアされなくなる。

 読んでみた。
Stc12c2052_freq  

 工場出荷時の設定値は 5,619776MHz、電源投入時の設定値は5,669,485MHzのようだ。

 よく見たら、stcgal.pyのメッセージで表示される値だ。



最近の投稿】【最近のCPUボード】【8031/8052 SBC】【2016の投稿】【2015の投稿

2016年9月19日 (月)

Digispark(3)

 DigisparkはATtiny85を使っていて、bootloaderが2k使っているので、スケッチは6k使える。はずだが、3k以上書けなかった。(Digispark(2) 2016/9/5)

 GitHubにDigispark用のArduinoIDEがある。(https://github.com/digistump/DigistumpArduino/releases/)
これは Digispark用ライブラリインストール済のArduinoIDE ver1.6.7だ。

 このIDEを使っていたのだが、ArduinoIDEの最新版(1.6.11)に変えて、Digispark用ライブラリを手動でインストールしてみた。

 コンパイル終了時のメッセージを見ていて気が付いた。ArduinoIDE 1.6.11はRAMの使用量をレポートしてくれる。

最大6,012バイトのフラッシュメモリのうち、スケッチが4,360バイト(72%)を使っています。
グローバル変数は980バイトのRAMを使用しています。

 ATtiny85のRAMは512byteしかないはず。

 DigisparkはArduinoIDEを使っているので最後にavr-gccでコンパイルされる。

DigiKeyboard.println("hello, world");

と書くと文字列 "hello, world" は、CODE領域つまりフラッシュメモリに配置されると思うが、初期化済データとしてRAMに配置される。って「avr-gcc constをプログラム・メモリに配置(2013/02/08)」で書いてる。(^^ゞ

 文字定数をフラッシュメモリに配置するにはF()マクロを使うとよい。

DigiKeyboard.println(F("hello, world"));

文字定数をフラッシュ・メモリに配置するように書き換えてコンパイルすると、

最大6,012バイトのフラッシュメモリのうち、スケッチが4,330バイト(72%)を使っています。
グローバル変数は96バイトのRAMを使用しています。

RAMの使用量は512byte以下に収まった。

 ATtiny85はRAMが512byteしかないので、RAMの使用量を増やさないようなプログラミングが必要だ。簡単にUSBデバイスがつくれるのだけれど、初心者向きではないのかも。


 F()マクロは  include/avr/pgmspace.hではなく、hardware/arduino/avr/cores/arduino/WString.hで定義されている。


最近の投稿】【最近のAVR

2019年4月
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30        

最近のトラックバック