PIC

pi calculator

2009年7月12日

IC16F690をつかった、pi calculatorの完成形。結果は、

3.141592653589793238462643383279502884

となる。

以下は、3.141592653589793238462643まで計算したところ。


本当は、小数点以下36桁まで計算し表示するのだが、その計算に8分強かかる。フルの長さの動画がアップできなかった。

この計算に当たり、40桁分のバッファをPIC内に確保したが、はじめその確保の仕方を見つけるのに、ずいぶん手間取った。PIC16F690は、256バイトのRAMがある。10進数2桁に一バイトを割り当ており、計算させるのに7つのバッファを使ったので、合計140バイトのメモリが必要だ。これをそのまま、unsigned char buff[140];としても、割り当てできない。ところが、70バイトずつ2つに分けると、割り当てできる。最初、これがなぜだか分からなかったのであるが、いろいろ調べてみて分かった。PIC16F690は、256 byte のRAMを、96, 80, 80 bytes と、3つのバンクに分けて確保しており、連続で80 bytesを超えるバッファの確保が難しく、96 bytesを超えることはできないためだ。

その他、乗除算の際にかなりのRAMを使うようで、140バイトのバッファを確保するためにコードの見直しにずいぶん時間がかかった。

PICでのプログラミングに関して、もうひとつ気をつけないといけないのは、スタックの使い方。マニュアルでは、スタックは8段までとある。どうやら、スタック用のメモリと、ユーザ用のメモリ(256バイトのRAM)は別々に確保してあるらしく、スタックはアセンブラのCALL命令(Cでは、関数呼び出し)のみに使われているらしい。通常、Cでは、関数内のローカル変数などもスタック内に確保されるので、この辺りが異なる。では、PICCの場合、関数内のローカル変数はどのように確保されているかというと、どうやらスタティック変数のようにユーザRAMを使っているらしい。もしそうだとすると、関数の再帰呼び出しはできない(やりにくい)ので、注意が必要である。ここは、あとでゆっくり調べなければならない。

こういった理由で、関数内のローカル変数の使用もなるべく避け、可能な場合はグローバル変数を使うようにし、なるべく新たな変数の定義を行わないようなコードにした。なので、あまりきれいなコードにできないのは、仕方のないところ。256バイトのRAMを最大限に利用しようとすると、どうしてもこうなるだろう。

pi2.h
#include <pic.h>

#define FIG 20

void show_all(unsigned char* set_num, unsigned char set_max);
void wait();
void init();


pi2.c
#include "pi2.h"
__CONFIG(INTIO & WDTDIS & PWRTDIS & MCLRDIS & UNPROTECT & BORDIS & IESODIS & FCMDIS);

unsigned char i;

unsigned char g_ledp=0;
unsigned short g_num=1000;
unsigned char g_dp=0;

unsigned char led7(char num){
    unsigned char ret=0xFF;
    if (num & 0x10) ret=0xF7;
    switch (num & 0x0F) {
        case 0: return ret & 0x28;
        case 1: return ret & 0x7B;
        case 2: return ret & 0x1C;
        case 3: return ret & 0x19;
        case 4: return ret & 0x4B;
        case 5: return ret & 0x89;
        case 6: return ret & 0x88;
        case 7: return ret & 0x2B;
        case 8: return ret & 0x08;
        case 9: return ret & 0x09;
        default: return 0xff;
    }
}

void show(unsigned char* num, unsigned char pos){
    // Set the dot, first.
    switch(pos){
        case 0:
            g_dp=0x02;
            break;
        case 1:
            g_dp=0x01;
            break;
        default:
            g_dp=0x00;
    }
    i=pos>>1;
    if (pos%2) g_num=(num[i]%10)*100 + num[i+1];
    else g_num=num[i]*10+num[i+1]/10;
}

unsigned char g_shown=0;
void show_all(unsigned char* set_num, unsigned char set_max){
    static unsigned char *num=0, max=0, state=0;
    if (set_max) {
        num=set_num;
        max=set_max;
        state=0;
        g_shown=0;
        return;
    }
    if (!max) {
        g_num=1000;
        return;
    }
    if (state<6) {
        if (state%2==0) g_num=1000;
        else show(num,1);
    } else if  (state < max+3) {
        show(num,state-5);
    } else if (state > max+5) {
        g_shown=1;
        state=3;
    }
    state++;
}
void wait(){
    while(!g_shown);
    g_num=1000;
    PORTA=0x00;
}


// 10000 cycles @ 8 MHz / 4 == 5 ms
#define WAIT_CYCLE (10000-23)
#define TMR1H_INIT ((unsigned char)(((unsigned short)(65536-WAIT_CYCLE))>>8))
#define TMR1L_INIT ((unsigned char)(((unsigned short)(65536-WAIT_CYCLE))&0x00ff))

void interrupt int_function (void) {
    if (!TMR1IF) return;
    TMR1H=TMR1H_INIT;
    TMR1L=TMR1L_INIT;
    TMR1IF=0;

    static unsigned char show_counter=0;
    if (99 < ++show_counter) {
        show_counter=0;
        show_all(0,0);
    }

    if (++g_ledp==3) g_ledp=0;
    PORTA=0x00;
    if (g_num>999) return;
    
    switch (g_ledp) {
        case 0:
            i=(g_dp&0x01)?0x10:0;
            i=i | g_num / 100;
            PORTC=led7( i );
            PORTA=0x04;
            break;
        case 1:
            i=(g_dp&0x02)?0x10:0;
            i=i | ( g_num / 10 ) % 10;
            PORTC=led7( i );
            PORTA=0x10;
            break;
        case 2:
        default:
            i=(g_dp&0x04)?0x10:0;
            i=i | (g_num % 10);
            PORTC=led7( i );
            PORTA=0x20;
            break;
    }
}

void init(){
    // CLK=8 MHz
    OSCCON=0x70;
    // All output
    TRISA=0;
    TRISB=0;
    TRISC=0;
    // Non-analog mode
    ANSEL=0x00;
    ANSELH=0x00;

    // Scaler 1:1
    T1CON=0x00;
    // Reset timer
    TMR1H=0x00;
    TMR1L=0x00;
    // Clear flag
    TMR1IF=0;
    // Enable interrupt
    TMR1IE=1;
    PEIE=1;
    GIE=1;
    // Stop timer, first
    TMR1ON=0;

}
main()以外のPIC機能は、ここに配置。

calc_pi.c
#include "pi2.h"

unsigned char cache[FIG*4];
unsigned char cache2[FIG*3];
unsigned char* buff(unsigned char page){
    if (page<4) return &cache[FIG*page];
    page-=4;
    return &cache2[FIG*page];
}

unsigned char i,c;
unsigned short t;

void copy(unsigned char* res, unsigned char* v1){
    // round the value
    t=v1[FIG-1]+5;
    for (c=0;9<t;c+=10) t-=10;
    v1[FIG-1]=c;
    // check the value and copy
    c=0;
    for (i=FIG-1;i<FIG;i--) {
        t=v1[i]+c;
        if (i) {
            for (c=0;99<t;c++) t-=100;
        }
        res[i]=t;
    }
}

void clear(unsigned char* res){
    for (i=0;i<FIG;i++) res[i]=0;    
}

void add(unsigned char* res, unsigned char* v1, unsigned char* v2){
    for (i=0;i<FIG;i++) res[i]=v1[i]+v2[i];
    copy(res,res);
}

void mul2(unsigned char* res, unsigned char* v1, unsigned char v2){
    c=0;
    for (i=FIG-1;i<FIG;i--) {
        t=v1[i]*v2+c;
        if (i) {
            for (c=0;99<t;c++) t-=100;
        }
        res[i]=t;
    }
    copy(res,res);
}
void mul(unsigned char* res, unsigned char* v1, unsigned char* v2){
    unsigned char i;
    clear(&cache2);
    clear(&cache2[FIG]);
    for (i=FIG-1;i<128;i--) {
        mul2(&cache2[FIG*2],v1,v2[i]);
        add(&cache2[i],&cache2[i],&cache2[FIG*2]);
    }
    copy(res,&cache2);
}
void div(unsigned char* res, unsigned char* v1, unsigned char v2){
    unsigned short c=0;
    for (i=0;i<FIG;i++) {
        c=c*100+v1[i];
        cache2[i]=c/v2;
        c=c-cache2[i]*v2;
    }
    copy(res,&cache2);
}

void main(){
    init();
    unsigned char* num=buff(0);
    unsigned char* pi=buff(1);
    unsigned char* y=buff(2);
    unsigned char* t=buff(3);
    unsigned char n,x2=0;
    unsigned char x;
    clear(num);
    clear(pi);

    for (n=0;n<127;n++) {
        // Begin pi calculation process
        clear(y);
        y[0]=3;
        div(y,y,2*n+1);
        for (x=1;x<=n;x++) {
            clear(t);
            t[0]=x+n;
            div(t,t,x);
            div(t,t,16);
            mul(y,y,t);
        }
        add(pi,pi,y);
        for(x=0;x<FIG;x++){
            if (num[x]!=pi[x]) break;
        }
        copy(num,pi);
        // Show the result on display
        if (1<x && x2<x) {
            x2=x;
            TMR1ON=1;
            show_all(num,x=x*2-2);
            wait();
            TMR1ON=0;
        }
        // Terminate if reaching maximum depth.
        if (FIG<=x2) break;
    }
    TMR1ON=1;
    while(1);
}
main()関数が、文字通りメインの仕事をする場所。

計算中は、タイマーをとめて、割り込み処理が行われないようにしている。こうしないと、計算が途中で止まったり、結果がおかしくなる。スタックの利用数の問題?割り込み処理中に、変な事をしてしまっているのか?

コメント

コメントはありません

コメント送信