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

IchigoJam

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しているのが原因だ。

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


2015年11月17日 (火)

IchigoJam(10) <8x8LEDを光センサーにする>

 飽きもしないでIchigoJamと8x8マトリクスLEDで遊んでいる。

 一度試してみようと思いながら先送りしていた、LEDを光センサーとして使うアイディアを試してみた。このアイディアをは古くからあって、初めて読んだのはエレクトロニクスライフと記憶している。

 その他にも

に実装例がある。

 IchigoJamはVer1.1b5から、ポートの出力、入力を切り替えられるようになったので、IchigoJamと8X8マトリクスLEDでやってみた。 IchigoJam(8) <8x8LED>IchigoJam(9) <8x8LEDで8Qeen問題>で使用したハードウェア使う。

 表示しながら、LEDをスイッチ代わりに使える。(はずだ。)

Ichigo8x8sensor

基本動作は

OUT 1,-1 : '入力ポートに切り替える
A=ANA(5): '電圧を読む
OUT 1,0   : '出力ポートに切り替える

で、光が当たっているときと指で光を遮ったときで読み取った電圧に差が生じるはずである。

↓のようなプログラムを作って試してみると、ちゃんと変化が読み取れた。

1 'Nov.2015/Yoshi
1 'LED Senser Test
10 GOSUB 1000

100 FOR P=1 TO 2
110  M=#7FFF: X=0
120  A=0: OUT P,-1: FOR I=0 TO 19: [I]=ANA(P+4): NEXT
130  FOR I=0 TO 19
140   IF [I]<M M=[I]
150   IF X<[I] X=[I]
160   A=A+[I]
170  NEXT
180  ?"ANA(";P+4;") MIN:";M,"MAX:";X,"AVE:";A/20
190 NEXT
199 END

1000 'Initialize
1000 VIDEO 0
1020 OUT 8,0: OUT 9,0: OUT 10,0: OUT 11,0
1100 RETURN
list

run
ANA(5) MIN:57   MAX:78  AVE:65   
ANA(6) MIN:48   MAX:84  AVE:63

run
ANA(5) MIN:0    MAX:18  AVE:3
ANA(6) MIN:0    MAX:35  AVE:10

run
ANA(5) MIN:2    MAX:34  AVE:18
ANA(6) MIN:22   MAX:77  AVE:44

上から

  • 隠していない状態(8個のLEDに光が当たっている)
  • 全て隠した状態(8個のLEDに光が当たっていない)
  • 半分隠した状態(4個のLEDに光が当たっている)

 マトリクスLEDは1列のLEDのアノード側が繋がっているので、隠したLEDの数によって電圧に差が出ている。 IchigoJamの速度では隠した列の判定はできるが、座標まで特定するのは難しそうだ。

 ということで、指で触れると表示がカウントアップするプログラムを書いてみた。

1 'Nov.2015/Yoshi
1 'LED Senser Demo
10 GOSUB 500

100 'Main loop
100 I=(I+1)&7: OUT (I<<8)|[I+C]: GOSUB P: GOTO 100

200 '
200 OUT 1,-1: L=L+ANA(5): OUT 1,0: IF J=7 GOTO 280
201 GOTO 290
210 OUT 2,-1: R=R+ANA(6): OUT 2,0: IF J=15 GOTO 280
211 GOTO 290
220 L=L>>3: R=R>>3: IF D=1 GSB 700
221 GOTO 280
230 IF L<T AND U<R C=(C+8)%80: GOTO 270
231 GOTO 280
240 IF T<L AND R<U C=(C+72)%80: GOTO 270
241 GOTO 280
250 J=J+1: IF J<100 RTN
260 J=0: L=0: R=0: P=200: RTN
270 P=250: BEEP: RTN
280 P=P+10
290 J=J+1: RTN

500 'Initialize
500 VIDEO 0: FOR I=8 TO 11: OUT I,0: NEXT: CLV: P=200
510 D=1: T=55: U=40
510 T=55: U=40

600 'bit pattern
600 LET[0],#00,#3C,#E4,#EC,#F4,#E4,#E4,#3C
610 LET[8],#00,#18,#18,#38,#18,#18,#18,#FC
620 LET[16],#00,#3C,#E4,#84,#0C,#30,#60,#FC
630 LET[24],#00,#3C,#E4,#84,#1C,#84,#E4,#3C
640 LET[32],#00,#0C,#1C,#3C,#4C,#FC,#0C,#0C
650 LET[40],#00,#FC,#60,#7C,#84,#84,#E4,#3C
660 LET[48],#00,#3C,#E4,#60,#7C,#E4,#E4,#3C
670 LET[56],#00,#FC,#E4,#0C,#18,#18,#18,#18
680 LET[64],#00,#3C,#E4,#E4,#3C,#E4,#E4,#3C
690 LET[72],#00,#3C,#E4,#E4,#BC,#84,#E4,#3C
699 RETURN

700 'debug
700 ?"L:";L,"R:";R: RTN
list

 8x8LEDの両端の列を表示用兼光センサとして使っている。片方の端はOUT 1に繋がっているのでOUT 1、ANA(5)で使える。もう片方の列は OUT 8に繋がっているので ANA(1)で使えるのかと思ったが使えないようなので、OUT 2とOUT 8を入れ替えている。

 数字を表示する合間にLEDの電圧を読んでいる。ばらつきがあるので8回読んで平均を取るようにした。

 510行目のTとUが閾値だ。D=1を指定すると、電圧を表示するので、ちゃんと動くようにTとUの値を指定しなければならない。(VIDEO 0なのでシリアルで確認する)

Ichigo8x8sensor_2
ダウンロード Ichigo8x8Sensor1M.avi (981.6K)

 LEDを光センサとして使っているので周囲の明るさで読み取る電圧が変化する。ちゃんと使えるようにするには周囲の明るさに合わせて閾値を自動的に変化させる工夫が必要だ。


2015年11月10日 (火)

IchigoJam(9) <8x8LEDで8Qeen問題>

IchigoJamだけで8x8マトリクスLEDが表示できるようになった(IchigoJam,(8) <8x8LED>(2015/10/))
BASICではダイナミック表示に必要な処理速度が足りないようで、スクロールさせるだけでもチラつきがある。

 ダイナミック表示+何かの処理ができると簡単なゲームならIchigoJamだけでできるのではないかと考え、8Queen問題を解きながら8x8LEDのダイナミック表示ができるかやってみた。

 8Qeen問題はチェス盤(8x8)の上にQueen(将棋の飛車と角を合わせた動きができる駒)を、互いに進路を邪魔しないように配置する問題だ。これを解くプログラムをFORループで書くとこんな感じだ。→(IchigoJam BASICのFOR/NEXT(2)(2015/04/07))

 FORループの中でLEDの表示(OUT xx)を行うと、処理によってOUT命令の間隔が変わるのでチラつきが激しくなるだろう。

 そこで、考え方を変えて、8Queenを解くルーチンを分割して、メインループ(100行)でLEDの表示を行い、合間に分割したルーチンを順次呼び出す方式にした。

1 '8Queen for IchigoJam 
2 'Oct. 2015/Yoshi
3 GOSUB 1000

100 'Main loop
100 N=(N+1)&7: OUT (N<<8)|[N]: GOSUB P: GOTO 100

200 IF [I]=X GOTO 420 ELSE GOTO 410
210 IF [I]=X>>(Y-I) GOTO 420 ELSE GOTO 410
220 IF [I]=X<<(Y-I) GOTO 420 ELSE GOTO 410
230 'CHK OK
230 I=I+1: IF I<Y GOTO 400 ELSE GOTO 410
240 X=1: I=0: GOTO 410
250 Y=Y+1: [Y]=X: IF Y=8 GOTO 430 ELSE GOTO 400
260 'CHK NG
260 X=X<<1: IF X=#100 [Y]=0: Y=Y-1: X=[Y]: RETURN ELSE GOTO 410
270 [Y]=X: I=0: GOTO 400

400 P=200: RETURN
410 P=P+10: RETURN
420 P=260: RETURN
430 P=430: IF BTN()=1 Y=7: GOTO 420 ELSE RETURN

900 'Print table
900 PRINT
910 FOR J=0 TO 7
920  FOR K=0 TO 7
930   IF [J]&(1<<K)=0 ?" "; ELSE ?"Q";
940   ?"|";
950  NEXT: ?
960 NEXT
970 RETURN

1000 'Initialize
1000 OUT 8,0: OUT 9,0: OUT 10,0: OUT 11,0
1010 VIDEO 0: CLV
1020 [0]=1
1030 Y=0: X=1: I=0: N=0: R=0: P=250
1100 RETURN
list

 8Queenを解くルーチン(200行~270行)は、駒を置こうとする座標(X,Y) 既に置いた駒が進路にあるかを調べている行(I)、置けるか置けないかの判定(R)によって処理が決まるので、X,Y,I,Rを保存してあれば処理を分割することが可能だ。

 メインループ(100行目)の(GOSUB P)で分割している処理を順次呼び出す。分割された処理からRETURNする前に、次に呼び出す行番号を更新している。

Ichigo8x88q千石の袋はフィルタ代わり(^^;
リンク先に(Ichigo8x88Q.avi (2,031KB)) サイト外に接続します


2015年11月 4日 (水)

IchigoJam(8) <8x8LED>

IchigoJamだけでダイナミック表示ができたので8x8マトリクスLEDを点けてみた。

Ichigo8x8led

 IchigoJamの出力ポートはOUT1~OUT11の11本しかないので、OUT1~OUT8をマトリクスLEDのR1~R8に接続して、カラム側は74HC138(3-8Decoder)を使ってOUT9~OUT11でC1~C8をドライブする。R1~R8は0Ωに(直結)した。LEDが点灯する数によって明るさが違う。

 C1~C7を駆動する74HC138の出力は負論理なので、例えば入力が"101"ならば出力Y4に"L"が出力される。したがってR1~R7は正論理で駆動すればよい。

 例えば、2列目に「○●○●●○●○」(C1→C8、○ON、●OFF)のパターンを表示する場合、OUT #2A5 とする。

 とりあえずテストプログラムを書いてみた。4つのパターンで点灯する。

  1. 順に点灯
  2. 渦巻き
  3. ランダム
  4. 反射

1 '8x8LED TEST
10 GOSUB 1000
20 GOSUB 100: GOSUB 200: GOSUB 300: GOSUB 400
30 FOR Z=0 TO 4
40  R=RND(4): R=(R+1)*100: GOSUB R
50 NEXT
90 END

100 'ALL LED ZigZag
100 FOR Y=0 TO 7
110  IF Y%2=0 T=0: E=7: S=1 ELSE T=7: E=0: S=-1
120  FOR X=T TO E STEP S
130   GOSUB 900
140  NEXT
150 NEXT
160 RETURN

200 'Spilal
200 X=0:
210 FOR A=0 TO 3: B=7-A
220  FOR Y=A TO B: GOSUB 900: NEXT
230  FOR X=X+1 TO B : GOSUB 900: NEXT
240  FOR Y=Y-1 TO A STEP -1: GOSUB 900: NEXT
250  IF A<3 FOR X=X-1 TO A+1 STEP -1: GOSUB 900: NEXT
260 NEXT 
270 RETURN

300 'Random
300 FOR P=0 TO 63
310  R=RND(63): Y=R/8: X=R%8: GOSUB 900
320 NEXT
330 RETURN

400 'Reflect
400 A=1: B=2: X=3: Y=4
410 FOR P=0 TO 63
420  GOSUB 900
430  X=X+A: Y=Y+B
440  IF X<0 X=0: A=RND(2): A=A+1: GOTO 460
450  IF 7<X X=7: A=-A
460  IF Y<0 Y=0: B=RND(2): B=B+1: GOTO 480
470  IF 7<Y Y=7: B=-B
480 NEXT
490 RETURN

900 'LED(X,Y) ON
900 OUT (X<<8)|(1<<Y): WAIT 5: RETURN

1000 'Initialize
1000 FOR I=8 TO 11: OUT I,0: NEXT
1100 RETURN
LIST

Ichigo8x8ledtest
↑リンク先に Ichigo8x8LEDtest.avi (999.2K)

↑はLEDの同時点灯数1個だから簡単だ。結構簡単にできたので 気をよくして、パターンを表示させてみた。

1 '8x8LED display
2 'NOV. 2015/ Yoshi
10 GOSUB 1000: GOSUB 1200

100 'Main loop
100 I=I+1: J=(I&7): D=(I&T)>>5: OUT (J<<8)|[D+J]: IF I&#1FF=0 GOSUB 500
110 GOTO 100

500 'button
500 IF BTN()=0 B=0: RETURN
510 'ELSE
510  P=P+1: IF P=2 P=0
520  IF P=0 T=#3E0: GOTO 540
530  IF P=1 T=#700: GOTO 540
540  IF B=0 B=1: BEEP: ELSE END
550 RETURN

1000 'Initialize
1000 CLV: VIDEO 0
1010 V=60: N=V: P=1: S=0: T=#700
1020 OUT 8,0: OUT 9,0: OUT 10,0: OUT 11,0
1030 CLT
1100 RETURN

1200 'Bit pattern
1200 LET [0],#00,#03,#0F,#1C,#F8,#F0,#0C,#03
1210 LET [8],#00,#3C,#7E,#FF,#81,#C3,#7E,#3C
1220 LET [16],#00,#46,#8F,#9F,#B9,#F9,#F1,#62
1230 LET [24],#00,#FF,#FF,#FF,#18,#18,#FF,#FF
1240 LET [32],#00,#00,#81,#FF,#FF,#FF,#81,#00
1250 LET [40],#00,#88,#58,#7C,#3F,#7C,#58,#88
1300 RETURN
LIST

1200行から1250行までが表示パターン。

例えば、1200行は、配列[0]~[7]に"Y"のパターンを格納している。

[0][1][2][3][4][5][6][7]
・ ○ ○ ・ ・ ・ ・ ○ 01h
・ ○ ○ ・ ・ ・ ・ ○ 02h
・ ・ ○ ○ ・ ・ ○ ・ 04h
・ ・ ○ ○ ○ ・ ○ ・ 08h
・ ・ ・ ○ ○ ○ ・ ・ 10h
・ ・ ・ ・ ○ ○ ・ ・ 20h
・ ・ ・ ・ ○ ○ ・ ・ 30h
・ ・ ・ ・ ○ ○ ・ ・ 40h
00 03 0F 1C F8 F0 0C 03

のように格納してある。

 メインループ(100行110行)を、どれだけ速く書くかでチラつきかたが変わってくる。(もう少し速くなるかも) 効果があるのは行数を減らすことだった。

Ichigo8x8leddisp
↑リンク先に Ichigo8x8LEDdisp.avi (900.5K)

 さすがにチラつくなあ。ビデオではわからないけどLEDの点灯数で明るさが違うのが、はっきり分かる。

メインループを早くするアイディア

  • 74HC138(3-8Decoder)でなく74HC4017(JohnsonCounter)を使う
  • 配列アクセスをPOKEに変える

くらいかなあ。

 タイマー割り込みが欲しい。


2015年11月 2日 (月)

勉強会

ゆるい勉強会に参加した。

 今回の発表は、

  • API Hook
  • Drone
  • Maker Fire&IchigoJam
  • Windows10 IoTCore
  • いいろなSecurity Conferenceにいってみた
  • FPGA入門

など、難しい話から軽い話まで。

 展示もあった。
chigoJamを使ってキーボートを押すと前進・後退、回転できる。
Photo

 息子さんの夏休みの自由研究らしい。(「大きいお友達」の影が見えるのは気のせい?)
今時の自由研究はIchigoJamなんだ。将来が楽しみだね。

 ちなみにこの台車(タミヤ、ロボクラフトシリーズNo7)はミニヘボコンで優勝したときに使ったものだって。

2015年10月19日 (月)

IchigoJam(7) 

IchigoJam Ver1.1 beta6 で IN1~IN4をOUT8~OUT11で使えるようになったらしい。

IchigoJam(6) <4桁ダイナミック表示>(2015/10/13) で作った4桁LED時計はVer1.1 beta5で作った。出力ポートがOUT1~OUT7の7本しかなかったので、桁ドライブ用に74HC4017を使った。
 OUT8~OUT11が使えるなら、新たに使えるようななった出力ポートを桁ドライブ用に使用するとちょうどよい。

Ichigojamclockd8

シンプルになった。

 プログラムの変更が少なくなるように、OUT8~OUT11を桁ドライブに使用すると3桁目(OUT10)が点灯しない。?_?)  出力電圧を測ってみると1.7vしか出ていない。配線を間違えたかと確認したが、間違えていない。

 IchigoJamの回路図を眺めたら気がついた。OUT10(IN3)はI2CのSDAだ。I2CバスはOpenDrainでドライブするので、IN3をOUT10で使った場合は当然OD出力となる。(データシートにも書いてある)

 ODは"L"を出力(電流吸込み)できるが、"H"は出力(電流吐出し)できないないので、プルアップ抵抗で"H"にする。
ところが、桁ドライブは電流を吐出さなければならないので、LEDを点灯させることができない。

 カソードコモンの7SegLEDを使おうかとも考えたが、安易に、OUT1~OUT4を桁ドライブに使用して、OUT5~OUT11をセグメントドライブに使用するように変えた。(赤色が変更した行)

1 'IchigoClock / dynamic lighting use OUT8-11
2 'Oct. 2015 / Yoshi
3 GOSUB 700

100 'Main loop
100 T=TICK(): IF BTN()=1 GOSUB 600 ELSE B=0
110  IF R=1 AND V<T GOTO 180
120  IF T<N GOTO 180
130   N=(N+D)&#7FFF: IF N<T THEN R=1 ELSE R=0
140   M=M+1: IF M<60 GOTO 160
150    M=0: H=H+1: IF 24<=H THEN H=0
160   [0]=[10+H/10]|8: [1]=[10+H%10]|4:
165   [2]=[10+M/10]|2: [3]=[10+M%10]|1

170   IF X=1 GOSUB 1000
180  I=I+1: OUT [I&3]
200 GOTO 100

600 'Button push
600 IF B=0 THEN B=T
610 IF E+B<T THEN END
620 IF B+K<T THEN D=J: N=TICK(): GOTO 650
630 IF D=J THEN D=V: N=TICK()+D: GOTO 650
640 IF T=B+J THEN D=V: N=TICK()
650 RETURN

700 'Initialize
700 [10]=#400: [11]=#790: [12]=#240: [13]=#300: [14]=#190
710 [15]=#120: [16]=#020: [17]=#780: [18]=#000: [19]=#100

730 V=3610: X=0
740 J=40: K=J*5: E=J*25
750 Z=0: F=#FF: R=0: D=V: N=D
760 OUT 8,0: OUT 9,0: OUT 10,0 OUT 11,0
770 H=23: M=50: [0]=[12]|8: [1]=[13]|4: [2]=[15]|2: [3]=[10]|1

780 VIDEO 0: CLT
880 RETURN

1000 'Send serial
1000 ? CHR$(13); H/10; H%10; ":"; M/10; M%10;
1010 RETURN
list

↓こんな感じ
 IchigoJam(6) <4桁ダイナミック表示>(2015/10/13)にあった74HC4017が無い。Ichigoclockdynamic2
↑リンク先にAVI(IchigoClockDynamic2.avi (1020.9K))

 動画は明るく見えるけど、蛍光灯の真下では見えないくらい暗い。

 R1~R8を0Ωにする(IchigoDotのように直結する)、つまりLPC1114のOUT5~OUT11を直接LEDに繋ぐと蛍光灯の下でも見えるくらい明るくなる。

 教科書にはLEDは必ず電流制限用の抵抗が必要と書いてあるが、マイコンのI/OでLEDを駆動する場合には直結することも可能だ。

 電流制限用の抵抗を省略しているわけではなく、I/Oポートには内部抵抗があるので電流が制限される。データーシートで出力電流と出力電圧の特性を確認すると、

Voliol Vohioh

 Fig38はI/Oピンに"L"を出力(OUT4,0 etc.)したときの電圧と電流の関係だ。I/Oピンに電流を吸い込むとI/Oピンの電圧が上がる。つまり内部に抵抗がある。

 一方Fig39はI/Oピンに"H"を出力(OUT1,1)したときの電圧と電流の関係だ。I/Oポートから出力する電流が増えると、出力電圧が下がる。つまり内部に抵抗がある。

 そして、LEDに流す電流とLEDの電圧降下の特性もデータシートで確認すると
Ifvf

 LEDは流れる電流で順方向の降下電圧が異なる。教科書で見かけるLEDの順方向降下電圧2.0vは20mA流したときの値だ。

 マイコンのI/Oポート直結でLEDを点灯するときは↓のような回路になっている。

Leddrive1

つまり、VoH=VoL+Vfになるように電流が流れる。

 "8"を表示(LEDが7個点灯)したときは、LEDに2.5mAくらい電流が流れる。また、"1"を表示(LEDが2個点灯)したときは、LEDに3mAくらい電流が流れる。

 OUT1は点灯しているLEDの電流を全て吐き出しているので、点灯しているLEDの個数で降下する電圧が異なり、点灯しているLEDを流れる電流が変わる。2.5mAと3mAの変化は見た目にはわからない。

 "H"を出力したときの内部抵抗 Rohより、"L"を出力したときの内部抵抗 Rolのほうが値が小さいので、カソードコモンの7セグLEDを使うとより電流が流せることになる。

 カソードコモンの7セグLEDを使うと5mAくらい流せるので明るくなる。


2015年10月13日 (火)

IchigoJam(6) <4桁時計ダイナミック表示>

IchigoJamで4桁時計を作った。( IchigoJam(5) (2015/9/24))

 スタティック表示だから、大した苦労もなく作れた。使った表示器はHDSP0760(Latch+Decoder+Driver+LED)という表示器で一般的ではない。

 普通の7セグLEDを使って、ダイナミック表示でもできるのではと思い立ったので早速作ってみた。

 4511(BCD-to-Seven Segment Latch/Decoder/Driver)と74HC139(2-4Decoder)の組み合わせはわかりやすい。

Ichigoclockd

 IchigoJamのOUT1~OUT7(LED)でLEDのa~gを駆動すると 4511(7SegDecoder)は 省略できそうだ。

Ichigoclockd2

 桁ドライブには74HC4017(JohnsonCounter)を使う。

 74HC4017(JohnsonCounter)はカウントパルスが入力されると、順番にQ0~Qnに"H"が出力される。上の回路では、Q4をRESETに繋いであるので、Q0→Q1→Q2→Q3→Q0→Q1・・・のように循環して出力される。 "H"が出力された桁に、OUT1~OUT7に数字のパターンを出力すると4桁表示できる。

 74HC4017のデータシートによると出力電流は25mAなので、コモン側のドライバを省略してみる。CD4017は出力電流が取れないのでドライブ用のトランジスタが必要だ。(点線の中の図)

 a~gまでの全てのセグメントが点灯した場合は、それぞれのセグメントに25mA/7≒3.6mA流れることになる。最近のLEDは3.6mAも流すと室内では十分な明るさで光る。 しかし、それはスタティック表示の場合だ。

 4桁のダイナミック表示なので、点灯している時間は全体の1/4だから、平均電流は3.6mA/4=0.9mAだ。さすがに0.9mAは少ないなあ。でも、トランジスタのドラバを入れると部品が増えるしなあ...まあやってみよう。

 IchigoJamの出力はOUT1~OUT7で、これをLEDのa~gに使うと、74HC4017のカウントパルスに使える出力がないので、D1,D2,R8でORを構成してカウントパルスを作る。

 OUT2,OUT3は7セグLEDの"b","c"に繋がっている。この7セグLEDはアノードコモンだからOUT1~OUT7(LED)に出力するパターンは

: gfedcba
0 : 1 0 0 0 0 0 0 : #40
1 : 1 1 1 1 0 0 1 : #79
2 : 0 1 0 0 1 0 0 : #24
3 : 0 1 1 0 0 0 1 : #30
4 : 0 0 1 1 0 0 1 : #19
5 : 0 0 1 0 0 1 0 : #12
6 : 0 0 0 0 0 1 0 : #02
7 : 1 1 1 1 0 0 0 : #78
8 : 0 0 0 0 0 0 0 : #00
9 : 0 0 1 0 0 0 0 : #10

"b","c"をよく見ると、"0"~"9"のどのパターンを表示したときでも、"b","c"どちらかが"0"である。そして、"b"はD1、"c"はD2を経由してHC4017の13ピンに繋がっているので、表示中は、HC4017の13ピン CKE は"L"になる。

 隣の桁を表示する前に、一旦、OUT1~OUT7を全て"H"(OUT #FF )にすると、HC4017の CKE は"H"になる。

 そして、次の桁の数字を表示したときに再び"L"に戻るので、カウンターが進みLEDの次の桁に"H"が供給され、同時にOUT1~OUT7(a~g)に数字のパターンが出力されているので、無事次の桁に数字が表示される。

 つまり、次の桁の数字を表示する前に、OUT #FF を実行すればよい。(170行)

プログラムは↓こんな感じ。

1 'IchigoJam 4Digit clock / dynamic lighting
2 'Aug. 2015 / Yoshi
3 GOSUB 700

100 'Main loop
100 T=TICK(): IF BTN()=1 GOSUB 600 ELSE B=0
110 IF R=1 AND V<T GOTO 170
120 IF T<N GOTO 170
130  N=(N+D)&#7FFF: IF N<T THEN R=1 ELSE R=0
140  GOSUB 800: M=M+1: IF M<60 GOTO 160
150  M=0: H=H+1: IF 24<=H THEN H=0
160  [0]=[10+H/10]: [1]=[10+H%10]: [2]=[10+M/10]: [3]=[10+M%10]
170 I=I+1: OUT F: OUT [I&3]
190 IF X=1 GOSUB 1000
200 GOTO 100

600 'Button push
600 IF B=0 THEN B=T
610 IF E+B<T THEN END
620 IF T<B+K GOTO 640
630  D=J: N=TICK(): GOTO 680
640 IF D<>J GOTO 660
650  D=V: N=TICK()+D: GOTO 680
660 IF T<>B+J GOTO 680
670  D=V: N=TICK():
680 RETURN

700 'Initialize
700 [10]=#40: [11]=#79: [12]=#24: [13]=#30: [14]=#19
710 [15]=#12: [16]=#02: [17]=#78: [18]=#00: [19]=#10
730 V=3610: ' X=0
740 J=40: K=J*5: E=J*25
750 Z=0: F=#FF: R=0: D=V: N=D
760 H=23: M=50: [0]=[12]: [1]=[13]: [2]=[15]: [3]=[10]
770 VIDEO 0: CLT

800 'Align Digit
800 FOR I=0 TO 3
810  OUT F: OUT 0: IF IN(1)=1 THEN I=3
820 NEXT
830 RETURN

1000 'Send serial
1000 ? CHR$(13); H/10; H%10; ":"; M/10; M%10;
1010 RETURN

時計をダイナミック表示するプログラムは、メインルーチンで、

  • 時間の計測
  • 時刻のカウントアップ(分は60進数、時は24/12進数)
  • ボタンの取得
  • 時刻あわせ処理

を行い。表示には割り込みを使うと、メインルーチンの処理が重くなってもチラつかない。

 IchigoJam BASICは割り込みが使えないので、全てメインルーチンで処理しなくてはならない。時刻のカウントアップは処理が増えるのでチラつくが、60秒に1回しか実行しないので気にならないだろう。また、ボタンを押すと、時刻あわせ処理が実行されるのでループ1回の処理時間が長くなりチラつく。 常時ボタンを押しているわけではないので良しとしよう。

 こんな感じ↓
Ichigoclockdynamic

↑リンク先にAVI (http://yoshi-s.cocolog-nifty.com/cpu/files/IchigoClockDynamic.avi)

 BASICは遅いという先入観があって、本当にダイナミック表示できるのだろうかと思っていたが、それほどチラつきもなくできてしまった。CortexM0 48MHzだから当然か?

 8x8マトリクスLEDもIchigoJamだけで表示できそうな気がしてきたが、出力ポートが足りない。

 OUT 8、OUT 9で入力ポートが出力ポートになればいいのになあ。

と書いたら、Ver1.1 beta 6でIN 1~IN 4がOUT 8~OUT 11に割り当て可能になったようだ。


2015年9月24日 (木)

IchigoJam(5)

 ヤフオクでLEDディスプレイ(HDSP0670)を手に入れた。 HDSP0760(2) (2015/09/24)HDSP0760 (2015/09/22)   16進数表示と勘違いしていたので、何かに使わないと死蔵してしまいそうだ。

 このディスプレイがあると、IchigoJamでも簡単に時計が作れると思い立ち、さっそく作ってみた。

 IchigoJamの出力は、OUT1~OUT6、LEDの7つしかない。HDSP0760は、Latch+Decoder+Driver+LEDなので、表示データを保持するためにLE(LatchEnable)入力が必要だ。LEの制御にLEDを使い、OUT1~OUT4を表示する数値に使うと残りは、OUT5、OUT6だ。
OUT5、OUT6で4桁を制御しようとすると、2to4のデコーダー(74139)が必要だ。

 HDSP0760は5V-TTLでIchigoJamに使用されているLPC1114FN28は3.3V-CMOSなので、IchigoJam→HDSP0760方向は直結できる。2-4Decoder(74139)は、入力レベルがTTLの74HCT139を使用する。

Ichigoclock4

 ブレッドボードで作ってみた。↓
4digitclock
リンク先にAVI (4DigitClock.avi  1008.8K) (1秒でカウントするモードで撮影)

 IchigoJamのTICK()は1/60sなので0~#7FFFは9分でカウントアップする。ちょっと工夫が必要だ。(100、130、135行)
 時刻設定はIchigoJamに乗っているSW(BTN)を使った。1個のSWで時刻を合わせるのは大変だ。(600~710行)

  • 1秒押す         →1分増
  • 3秒以上押す →早送りその後自動的に増加
  • 17秒以上押す→プログラム終了

のようにした。(600~710行)

1 '4Digit Clock
2 'Aug.2015/Yoshi / Ver2.0
3 VIDEO 0: CLV: LED 1
4 V=3600: F=0
5 H=23: M=50
6 N=V: D=V
7 GOSUB 500: GOSUB 520: CLT

95 'Main loop
100 IF W=1 AND N<TICK() GOTO 100
110  IF BTN()=1 THEN GOSUB 600 ELSE G=0
120  T=TICK(): IF T<N GOTO 110
130  N=N+D: IF N<0 N=N&#7FFF
140  IF N<T THEN W=1 ELSE W=0
150  M=M+B: IF M<60 GOTO 180
160   M=0: H=H+1: IF 24<=H H=0
170   GOSUB 520
180   GOSUB 500
190 IF F=1 GOSUB 900
200 GOTO 100

495 'Disp LED
500 OUT M%10: LED 0: LED 1
510 OUT #10+M/10: LED 0: LED 1: RETURN
520 OUT #20+H%10: LED 0: LED 1
530 OUT #30+H/10: LED 0: LED 1: RETURN

595 'Push Button
600 IF G=0 G=T
610 IF 1000+G<T END
620 IF T<240+G GOTO 640
630  D=40: N=TICK(): GOTO 710
640 IF T<>30+G GOTO 710
650 IF D=40 GOTO 700
660  M=M+1: IF M<60 GOTO 690
670   M=0: H=H+1: IF 24<=H H=0
680   GOSUB 520
690  GOSUB 500
700 D=V: N=TICK()+D
710 RETURN

895 'Send Serial
900 [0]=M%10: [1]=M/10:
910 [2]=H%10: [3]=H/10:
920 ? CHR$(13); [3]; [2]; ":"; [1]; [0];
930 RETURN

6時間で1分くらい進む。このIchigoJamそっくりさんはX'tal付きなんだけどなあ。

1日で4分くらい進んでいたので、4/60*24=1/360余分にカウントするようにした(D=3600→D=3610)日差+20秒になった。(2015/09/27追記)

 


 

娘に見せると、毎度のことだけど、
  娘  :「なに作ってるの?」
σ^^):「時計」
  娘  :「時計作ってどうするの?」
σ^^):「時間を見る」
  娘  :「時計って1個あればいいじゃん」
σ^^):「うん~...」


2015年6月 1日 (月)

IchigoJam(4) <メモリダンプ・プログラム>

 IchigoJam BASICはPEEK()が使えるようなのでメモリダンププログラムを書いてみた。 HEX$()CHR$()があるので楽だ。

1 'Memory dump
2 ' May. 2015 / Yoshi
10 INPUT "Dump address:?", A
20 INPUT "Length:?", L
30 IF L=0 THEN L=256 
40 IF (L%16)<>0 THEN L=(L/16+1)*16

100 FOR I=A TO A+L-1
110   IF (I%256)=0 THEN PRINT "     +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F"
120   IF (I%16)=0 THEN PRINT HEX$(I,4);" ";
130   C=PEEK(I)
140   IF ASC(" ")<=C AND C<=ASC("~") THEN [I%16]=C ELSE [I%16]=ASC(".")
150   PRINT HEX$(C,2);" ";
160   IF (I%16)<>15 THEN GOTO 210
170     FOR J=0 TO 15
180       PRINT CHR$([J]);
190     NEXT
200     PRINT
210   REM ENDIF
220 NEXT
230 END

ちょっと丁寧に書いてみた。^^)
メモリダンプ見てると萌えるねえ...

###

 IchigoJamの作者のページ「福野泰介の一日一創」に「IchigoJamのメモリマップと演算子優先順位」というエントリがあってIchigoJamのメモリマップが公開されている。これを見るとPEEK()で見えるアドレスは仮想的なアドレスのようだ。

 やっぱりRAM(実アドレス、0x10000000~)の中を見たい!
 USR()で機械語ルーチンをコールできるようなので、PEEK()の代わりにUSR()でRAMの実アドレスを読むようにすればよさそうだ。

 しかし!、ARMのアセンブラなど書きたくない!!、しかもthumb命令だし!!

 IchigoJamのUSR()は引数をR0レジスタで渡して戻り値をR0で受け取るらしいので、機械語への翻訳はgccにお願いすることにしよう。

uint16_t peek_ex(uint16_t addr)
{
    return *(uint8_t*)(0x10000000+addr);
}

のような関数を書いてgccでコンパイルしてobjdumpで逆汗とすると

$ arm-none-eabi-gcc -c -Os -mthumb -mlittle-endian -mno-unaligned-access
$ arm-none-eabi-objdump -d dump.o

00000000 <peek_ex>:
   0:   4b01            ldr     r3, [pc, #4]    ; (8 <peek_ex+0x8>)
   2:   5cc0            ldrb    r0, [r0, r3]
   4:   4770            bx      lr
   6:   46c0            nop                     ; (mov r8, r8)
   8:   10000000        .word   0x10000000

が得られる。

 RAMの実アドレスのデータを読む関数は

POKE #700, #01, #4B, #C0, #5C, #70, #47, #C6, #46, #00, #00, #00, #10
D=USR(#700,A)

とすればよさそうだ。
実メモリをダンプするプログラムは

1 'Memory dump
2 ' May. 2015 / Yoshi
10 POKE #700, #01, #4B, #C0, #5C, #70, #47, #46, #C0, #00, #00, #00, #10
20 INPUT "Internal:0 RAM:1 Flash:2 APB:3 AHB:4 System:5 ?", T
30 IF T<0 OR 5<T GOTO 10
40 IF T=2 THEN POKE #70B, 0
50 IF T=3 THEN POKE #70B, #40
60 IF T=4 THEN POKE #70B, #50
70 IF T=5 THEN POKE #70B, #E0
80 INPUT "Address? ", A
90 IF 3<=T AND #1000<=A THEN POKE #70A,A>>12 : A=A>>4
100 INPUT "Length? ", L
110 IF L=0 THEN L=256
120 IF (L%16)<>0 THEN L=(L/16+1)*16

200 L=A+L-1
210 FORI=ATOL
220   IF!(I&#FF)?"    ";:FORK=0TO15:?" +";K;:NEXT:?
230   IF!(I&#F)?HEX$(I,4);" ";
240   C=PEEK(I):IFTC=USR(#700,I)
250   [I&#F]=C:IFC<32OR128<c[i&#f]=46 
260   ?HEX$(C,2);" "
270   IFI&#F=15FORJ=0TO15:?CHR$([J]);:NEXT:?
280 NEXT
290 END

ダンプ・ルーチンを最適化(分かり難く)してみた。
ソースはわかり難くなったけど、速度は目に見えて早くなったようには感じないなあ


     

より以前の記事一覧

2018年9月
            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            

最近のトラックバック