SSブログ
English Version

独自言語 picle のコンパイラ化(その11) [PIC]

 久々にpicleコンパイラをアップデートします。
 今回は軽微な修正2件と言語仕様の追加1件について書きます。

  1. 軽微な修正
     修正は次の2件です。
    • InpKey_() のリターン値の上位バイトクリア
       キー入力情報をリターン値としている InpKey_() で上位バイトがゼロになっていなかったので上位バイトをクリアするようにした。
    • ローカル変数の重複チェック
       ローカル変数宣言で「 var a,b,a; 」のように重複がある場合、エラー検出するようにした。
       エラーにしなくてもプログラムの実行上は問題ないが、スタックエリアが余分に消費されてしまうので修正した。


  2. 他ソースの読込み機能追加
     今回の本題です。
     PIC24FJ64GA002 はフラッシュメモリのサイズはそれなりに大きいのですが、SRAM サイズが小さい(8K)ので picle のソースが大きくなるとメモリ(SRAM)に収まらなくなります。
     ソースサイズを削減するためにコメントやスペースを削ると可読性が悪化してしまいますし、それでも限度があります。
     フラッシュメモリは沢山あるのに picle言語としての利便性がメモリサイズの制限のために著しく限定されてしまうことになります。

     そこで perl の「use」のような(C言語の「#include」のような(Pythonの「import」のような))機能を追加しました。
    • 記述方法
       読込むソース(以降ライブラリと記載)を次の形式で指定します。
      use LibraryName;
    • use の記述場所
       use ステートメントはソースの先頭に置く必要があります。
       ソースの構成は、最初に use ステートメント(複数可、無くても可)、次にグローバル変数宣言(なくても可)、次に関数や処理の記述になります(ソースの可読性を考慮し記述位置を固定にした)
    • ライブラリソースの記述方法
       フラッシュメモリにソースを保存し ¥¥ コマンドで保存ソース一覧を表示した場合、ソースの先頭行が表示されますが、ライブラリ名の記載は先頭行を
      #LibraryName
      とします('#'とライブラリ名の間にはスペースを入れない)。ライブラリ名の後にスペースを入れコメントを書くことも可能。
       ソースの1行目にライブラリ名を入れた状態でフラッシュメモリにセーブする。
    • ライブラリ内の main() 処理
       use で読込むソース内に main() 処理がある場合、main()処理はコンパイル対象外(構文チェックは実施される)
    • 多段読込可能
       use で読込むライブラリソース内でも useステートメントを使用可能。

     上記の対応をした picle コンパイラは「独自言語 picle compiler on PIC24FJ」からダウンロードできます。
     末尾に use 機能試験用ライブラリソースを付けました。use ステートメント試験の実行結果は次のようになります。

    use ステートメントテスト実行結果
    :\\
                                             
    +B400-B4CB #lib1 test library1
    +B800-B87B #lib2 test library2
    
    :l
    
       1:# use statement test
       2:#  2017/03/27 by skyriver
       3:
       4:use lib1;
       5:
       6:proc main() {
       7:   PrnStr_( "\nmain start" );
       8:   VarLib1 = 1;
       9:   VarLib2 = 2;
      10:
      11:   proc1();
      12:
      13:   PrnStr_( "\nVarLib1 = " );
      14:   PrnDec_( VarLib1 );
      15:   PrnStr_( "\nVarLib2 = " );
      16:   PrnDec_( VarLib2 );
      17:}
    :run
    
    main start
    proc1 start
    proc2 start
    VarLib1 = 10
    VarLib2 = 200
    :
    

     library sample source(picle)
    #lib1 test library1                                 
    # 2017/03/27 by skyriver
    
    use lib2;
    
    var VarLib1;
    
    proc proc1() {
        PrnStr_( "\nproc1 start" );
        VarLib1 = 10;
        VarLib2 = 20;
        proc2();
    }
    
    proc main() {
        PrnStr_( "\nlib1 main" );
    }
    
    #lib2 test library2
    # 2017/03/27 by skyriver
    
    var VarLib2;
    
    proc proc2() {
        PrnStr_( "\nproc2 start" );
        VarLib2 = 200;
    }
    


[TOP] [ 前へ ] 連載記事 [ 次へ ]

PIC24FJの出力コンペアモジュールとLCD3V駆動の実験 [PIC]

 PIC24FJには出力コンペアモジュールが内蔵されていて Timer2 または Timer3 のカウント値と比較レジスタ(1個または2個)を使って外部に信号出力したり割り込みを発生させる機能があります。

 動作モードとしては
  • シングル比較モード
  • デュアル比較モード
    • 単一出力パルスモード
    • 連続出力パルスモード
  • 単純パルス幅変調モード
    • フォルト保護入力付き
    • フォルト保護入力なし
があります。
 今回はTimer2を使ったデュアル比較の連続パルスモードを使い、5V用LCDモジュールを3.3V電源で動作させる実験を行いました。

  1. Timer2の設定
     今回は消費電力を削減するためにCLKDIVレジスタ内のRCDIVを2に設定し、内部クロックを2MHz(PLLで4倍なので動作クロックは8MHz)にしています。
     Timer2関連のレジスタの設定は次のとおりで1周期を 10KHz にしました。
    • T2CON : 0x8000 (TON,Prescale 1:1)
    • PR2 : 399(4MHz / (399 + 1) = 10KHz)

     因みに config の設定は次のとおり
    // CONFIG2
    #pragma config POSCMOD = NONE   // Primary Oscillator Select
    #pragma config I2C1SEL = PRI    // I2C1 Pin Location Select
    #pragma config IOL1WAY = OFF    // IOLOCK Protection
    #pragma config OSCIOFNC = ON    // Primary Oscillator Output Function
    #pragma config FCKSM = CSDCMD   // Clock Switching and Monitor
    #pragma config FNOSC = FRCPLL   // Oscillator Select (Fast RC
    #pragma config SOSCSEL = SOSC   // Sec Oscillator Select
    #pragma config WUTSEL = LEG     // Wake-up timer Select
    #pragma config IESO = OFF       // Internal External Switch Over Mode
    
    // CONFIG1
    #pragma config WDTPS = PS1      // Watchdog Timer Postscaler (1:1)
    #pragma config FWPSA = PR128    // WDT Prescaler (Prescaler ratio of 1:128)
    #pragma config WINDIS = OFF     // Watchdog Timer Window
    #pragma config FWDTEN = OFF     // Watchdog Timer Enable
    #pragma config ICS = PGx1       // Comm Channel Select
    #pragma config GWRP = OFF       // General Code Segment Write Protect
    #pragma config GCP = OFF        // General Code Segment Code Protect
    #pragma config JTAGEN = OFF     // JTAG Port Enable
    

  2. OC1(出力コンペア1)の設定
     連続出力パルスモードでの OC1 の出力は 使用するタイマー(今回はTIMER2)のカウント値が OC1RS と一致したとき low になり OC1R と一致した時に high になります。
     OC1R はtimer2のカウント最大値にしているので OC1RS の値を変更することで出力される矩形波の duty を変更できます。
    • OC1CON : 5(use Timer2,generates continuous output)
    • OC1R : 399
    • OC1RS : 199(duty 50%)

  3. PPS(ペリフェラルピンセレクト)の設定
     PIC24FJ64GA002 は28ピンのDIPパッケージでピン数が少ないため、PPS機能により、内蔵の機能モジュールの入出力をICの物理的なピンにアサイン可能になっています。
     今回は上記で設定した出力コンペアの出力を RP5(14ピン)に出力するようにしました。
     レジスタの設定としては RPOR2 レジスタ内の RP5R フィールドに OC1 の機能番号である 18 を設定するだけです。

  4. 5V用LCDの3V動作実験
     5V電源で動作する LCD を 3.3V で動かすためにLCDのコントラスト調整用電源として下に示したチャージポンプ回路でマイナス電圧を発生させている制作例が見受けられます。
     人間の目の時間的な周波数特性はあまり高くないので今回は後段の平滑部分(赤い丸で囲んだ部分)を省略して回路を簡略化し、矩形波でコントラスト調整します。
     ダイオードが1個減ることにより、負電圧値がVf分高くなるという効果も期待できます。

    チャージポンプ回路

     また、必要な電圧は -2V 程度あればいいので使用するダイオードはショットキーバリアではなくノーマルのもの(ショットキーより安価、逆電圧時のリーク電流も少ない)にしました。
     ショットキーとノーマルの場合の波形を比較したものが次の画像です。黄色がPIC24FJ の出力コンペアモジュールからの出力信号(10KHzの矩形波)で紫色がチャージポンプ回路の出力です。
     LCDのコントラスト端子からの流出電流が大きいため逆電圧時のリーク電流が小さいという効果は見られませんがノーマルダイオードでも行けそうです。素子の定数は後述の実験回路を参照してください。

    ショットキーバリアダイオード ノーマルダイオード

     今回の実験の回路図が下記になります。
     回路図では2行表示の LCD ですが、かなり前に秋月電子さんから購入した1行表示の LCD(300円)を使いました。
      また、回路図には書いていませんがシリアル接続は RP8:RX(受信)17番ピン、 RP9:TX(送信)18番ピン になります。
     シリアルモード : 19200bps、8bit、パリティ無し、Stopビット:1

    実験回路(KiCAD)

     ロジアナを使うまでもないのですが、LCD 初期化部分の信号を確認した結果が次のキャプチャです。使用しているソフトは pulseview です。

    LCD制御信号 (pulseview)

     実際に LCD に表示してみると次の写真のように表示できました。写真では少し薄く映っていますがコントラストとしては特に問題ないレベルです(見る角度にも依存しますが・・)

    LCD表示例

     LCD のコントラスト調整用のマイナス電源を矩形波にしたことで矩形波の duty をソフト制御しコントラスト調整することが可能になります。
     Duty を変更した場合のチャージポンプ出力波形が下記になります。紫色がチャージポンプの出力です。

    Duty16% Duty70%

     それぞれの Duty での LCD 表示は次のようになります。

    LCD表示(Duty16%) LCD表示(Duty70%)

     いかがでしょうか?5V用LCDを3.3 Vで使用するための部品を半減できた上にコントラスト調整もソフト制御で簡単にできる(コントラスト調整用ボリュームも不要)ようになりました^^

     今回の実験で作成したプログラムを末尾に示します(picle言語だけを使っています)。消費電力を抑えるために init() 処理内で PIC24FJ の動作クロックを1/4(32MHz⇒8MHz)に変更しています。
     LCD 表示部分は以前 PIC16F88 を使ったツール制作の時に作成したC言語のものを picle に書き換えて使用しました。言語仕様が近いのでコンバージョンは楽です。
     実験プログラムの操作としては'1'、'2'のキー入力でそれぞれ duty が減少/増加し、スペースキーでプログラム終了となっています。

    PIC24FJ OutputCompare module test(picle言語)
    # PIC24FJ OutputCompare module test
    #  to use LCD with 3V
    #  written with picle language by skyriver
    #  Ver 0.01 2017/03/18
    
    var _REG,REG,TRISA,LATA,TRISB,LATB;
    var AD1PCFG;
    var T2CON,PR2;
    
    var bitLCD_E,bitLCD_RS,bitLCD_POW;
    var LCDPORT;
    
    
    # wait tim x 50us
    proc LcdWait( tim ) {
        var i;
        while ( tim ) {
            for ( i = 20; i; i=i-1 ) {}
            tim = tim-1;
        }
    }
    
    proc LcdWrite( nible ) {
        LATA[0] = LATA[0] & $fff0 | (nible & $000f);
    }
    
    proc LcdWrCmd( nible ) {
        LcdWrite( nible );
        LCDPORT[0] = LCDPORT[0] & ~bitLCD_RS;
        LCDPORT[0] = LCDPORT[0] | bitLCD_E;
        LCDPORT[0] = LCDPORT[0] & ~bitLCD_E;
        LCDPORT[0] = LCDPORT[0] | bitLCD_RS;
        LcdWrite( $ff );    # decrease pull up current
    }
    
    proc LcdWrDat( nible ) {
        LcdWrite( nible );
        LCDPORT[0] = LCDPORT[0] |bitLCD_RS;
        LCDPORT[0] = LCDPORT[0] | bitLCD_E;
        LCDPORT[0] = LCDPORT[0] & ~bitLCD_E;
        LcdWrite( $ff );    # decrease pull up current
    }
    
    proc LcdWrCmdWait( nible ) {
        LcdWait( 1 );
        LcdWrCmd( nible );
    }
    
    proc LcdWrCmdWait8( dat ) {
        LcdWrCmdWait( dat / 16 );
        LcdWrCmd( dat );
    }
    
    proc LcdWrDatWait8( dat ) {
        LcdWait( 1 );
        LcdWrDat( dat / 16 );
        LcdWrDat( dat );
    }
    
    proc LcdClear() {
        LcdWrCmdWait8( $01 );
        LcdWait( 31 );
    }
    
    proc LcdSetPos( pos ) {
        pos = pos | $80;
        LcdWrCmdWait8( pos );
    }
    
    proc LcdDspStr( _str ) {
        while ( _str[0] <> '"' ) {
            LcdWrDatWait8( _str[0] );
            _str = _str + 1;
        }
    }
    
    
    proc LcdInit() {
        LCDPORT[0] = LCDPORT[0] | bitLCD_RS | bitLCD_POW;
        LcdWait( 200 );
        LcdWrCmd( $03 );
        LcdWait( 82 );
        LcdWrCmd( $03 );
        LcdWait( 2 );
        LcdWrCmd( $03 );
        LcdWrCmdWait( $02 );
        LcdWrCmdWait8( $28 );   # set 4bit mode
        LcdClear();
        LcdWrCmdWait8( $06 );   # set entry mode,inc,non-shift
        LcdWrCmdWait8( $0c );   # display on
    }
    
    
    proc init() {
        TRISA = $02c0;
        LATA = $02c4;
        TRISB = $02c8;
        LATB = $02cc;
        LCDPORT = LATB;
        AD1PCFG = $032c;
        T2CON = $0110;
        PR2 = $010c;
        bitLCD_E = $0002;
        bitLCD_RS = $0001;
        bitLCD_POW = $0080;
        _REG[$0745] = _REG[$0745] & $f8 | 2;    # FRC:2MHz(CLKDIV)
        REG[$0228/2] = 12;  # 19200bps(U1BRG)
        REG[$0102/2] = 155; # 10ms(PR1)
    
        TRISA[0] = $fff0;
        TRISB[0] = ~(bitLCD_E | bitLCD_RS | bitLCD_POW);
        AD1PCFG[0] = $ffff; # set digital mode
        T2CON[0] = $8000;   # 1:1,16bit.internal
        PR2[0] = 399;
        _REG[$06c5] = 18;   # set OC1 to RP5(RPOR2H)
        REG[$0184/2] = 5;       # timer2,dual compare(OC1CON)
        REG[$0182/2] = PR2[0];  # OC1R
        REG[$0180/2] = PR2[0]/2;# OC1RS
    }
    
    
    proc main() {
        var duty,c;
        init();
        LcdInit();
        LcdDspStr( "hello to" );
        LcdSetPos( $40 );
        LcdDspStr( " picle" );
        duty = 50;
        while ( 1 ) {
            if ( InpChk_() ) {
                c = InpChar_() & $ff;
                if ( c = '1' ) {
                    duty = duty - 2;
                }
                else if ( c = '2' ) {
                    duty = duty + 2;
                }
                else if ( c = ' ' ) {
                    break;
                }
                REG[$0180/2] = REG[$0182/2] * duty / 100;
                PrnStr_("\nduty : ");
                PrnDec_( duty );
            }
        }
    }
    
    ※ブラウザ表示用にTABを4個のスペースに置換しています。


[ 前へ ] 連載記事 [ 次へ ]

PIC24FJ64GAのRTCCモジュール実験 [PIC]

 PIC24FJ64GAにはRTCC(リアルタイムクロックカレンダ)モジュールが内蔵されていて 32.768kHzのクリスタルを外付けすることで時刻や年月日(うるう年対応)をハードウェアで管理できます。また、一定時間間隔(1分、10分、1時間、1日等)や指定の時刻に割り込みを発生させることができます。
 アラームに関しては繰り返し数設定やxx秒の時(1分間隔)等、通常の使用で必要になると思われる殆どのパターンに対応できそうです。
 ロギング装置等で消費電力を下げるために通常はsleep状態にしておき、それなりに正確な一定時間間隔で測定処理を行い、データとともに時刻も保存したいような場合に便利です。

 今回は既存のRTCC用のライブラリは使わず、picle言語とアセンブラでRTCCを実験的に動かしてみました。
 回路はごく一般的なもので回路図を以下に示します。
 インターネット上では水晶発振が不安定だった事例等があるようですが、今回はブレッドボード上での実験でしたが安定して発信しました。プローブをつないで波形を見ても問題ない状態でした。
 また、回路図描画に使用したCADについてですが、DesignSpark PCB はUIは非常に使い易いのですが回路図の見た目がイマイチなので、今回は KiCAD で回路図を描いてみました。
★2017/03/18 追記
 回路図には書いていませんがシリアル接続は RP8:RX(受信)17番ピン、 RP9:TX(送信)18番ピン になります。
 シリアルモード : 19200bps、8bit、パリティ無し、Stopビット:1

RTCCモジュール実験回路(KiCAD)


 RTCC 用のクロックを有効にするためには RTCC 関連のレジスタの設定だけでは駄目で、OSCCON レジスタ内の SOSCEN(Secondary Oscillator Enable)のビットを1にする必要があります。
 かつ、RCFGCAL レジスタの RTCEN(RTCC Enable bit)を1にする必要がありますが、RCFGCAL レジスタを変更するためには このレジスタ内の RTCWREN を1にする必要があります。
 これらの変更を行うためにはそれぞれのレジスタに対してアンロックシーケンスを行う必要があり、この部分と sleep 命令実行の部分はアセンブラで記述し、picle言語からコールすることにしました。
 アセンブラのソースが下記で「PIC24FJ64GAでのアセンブラ環境覚書」の記事で書いた方法で ソース部分のみのhexファイルを作成し、OneBitLoader で書き込みました。
 ヘキサファイルは ここ からダウンロードできます。

assembler source to enable RTCC
/*************************************
  RealTimeClock setting
    made by skyriver at 2017/03/11
**************************************/
    .title  "Real Time Clock"
    .include "p24fj64ga002.inc"

    .section    .text_Ap,code,address( 0x7c00 )

    bra     EnableRtc
    bra     RtLockFree

;
; set device into sleep mode
;
Sleep:
    pwrsav  #SLEEP_MODE
    return

;
; unlock RealTimeRegister(RCFGCAL)
;
RtLockFree:
    mov     #NVMKEY,w1  ; unlock to write RCFGCAL
    mov.b   #0x55,w0
    disi    #3
    mov.b   w0,[w1]
    mov.b   #0xaa,w0
    mov.b   w0,[w1]
    bset    RCFGCAL,#13 ; set RTCWREB bit
    return

;
; enable real time clock(activate secondary OSC)
;
EnableRtc:
    mov     #OSCCONL,w0 ; open lock OSCCONL
    mov.b   #0x46,w1
    mov.b   #0x57,w2
    disi    #2
    mov.b   w1,[w0]
    mov.b   w2,[w0]
    bset.b  OSCCONL,#1  ; set SOSCEN bit
    return

    .end


 注意点としては RCFGCAL レジスタの RTCWREN ビットを1にした後、RCFGCAL レジスタの書き換え時に RTCWREN を1にしたままにしておけば、その後もアンロックシーケンスを行わなくても RCFGCAL レジスタを変更できます。
 RTCWREN ビットを0の状態で書き換えた後は、ロック状態となりアンロックシーケンスを行わないと書き換えができない状態になります。
 また、時刻情報とアラーム時刻の設定と読込みは設定データ種別毎にレジスタアドレスがあるのではなく、それぞれ RCFGCAL と ALCFGRPT レジスタ内にインデックスを設定し、RTCVAL、ALRMVALレジスタを使ってアクセスします(レジスタ用のメモリ空間の肥大防止のためと思います)

 RTCCモジュールの試験プログラムは下記のとおりで、アセンブラ側の処理を外部宣言してコールしています。
 RTCC から10秒毎(秒の1桁目が5)に割り込み要求が掛かることで、sleep 状態から抜け、時刻を表示後、sleep するようにしています。
 RTCC の割り込みプライオリティを1に設定し、割り込みレベルも1にしているので実際は割り込み処理は実行されず、sleep から抜けるだけになっています。
 時刻表示処置(DspTime)の最後で Timer_ を設定しウエイトしていますが、時刻表示出力のシリアルデータが送信完了するまで待つためです。
 sleep 状態では CPUが停止しますが、シリアルモジュールへのクロックも止めているのでウエイトを入れないと表示が尻切れになってしまいます。
 また、sleep 中はエスケープキーでの中断もできない状態になります。実際の装置に使う場合はスイッチ入力でも sleep から抜けるようにできます。

PIC24FJ RTCC module test(picle言語)
# PIC24FJ64GA002 RTCC test
# written with picle language by skyriver
#   2017/03/10 ver 0.01

var _REG,RCFGCAL,PADCFG1,RTCVAL,ALCFGRPT,ALRMVAL;
var RDBUF;

proc EnableRtc() = $7c00;
proc RtLockFree() = $7c02;
proc SleepMode() = $7c04;


proc DspTime() {
    var i,sec;
    do {
        sec = RTCVAL[0];    # get second data
        RCFGCAL[0] = $a300; # set index
        for ( i = 0; i < 4; i=i+1 ) {
            RDBUF[i] = RTCVAL[0];
        }
    } while ( sec <> RDBUF[3] );
    PrnStr_("\n20");
    PrnHexB_( RDBUF[0] );
    for ( i = 1; i < 4; i=i+1 ) {
        PrnStr_(" ");
        PrnHex_( RDBUF[i] );
    }
    Timer_ = 1;
    while ( Timer_ ) {}
}

proc init() {
    RDBUF = Array_(0);
    RCFGCAL = $0626;
    PADCFG1 = $02fc;
    RTCVAL = $0624;
    ALRMVAL = $0620;
    ALCFGRPT = $0622;
    _REG[$9b] = $40;    # enable RTC int(set IEC3)
    _REG[$42] = $20;    # set interrupt level to 1(set SR)
    _REG[$c3] = 1;      # set RTC priority to 1(set IPC15)
    EnableRtc();
}

proc main() {
    init();
    RtLockFree();

    RCFGCAL[0] = $a300;

    RTCVAL[0] = $17;    # set year
    RTCVAL[0] = $0311;  # set day
    RTCVAL[0] = $0623;  # set week & hour
    RTCVAL[0] = $1000;  # set min & sec

    ALCFGRPT[0] = $c800;	# set 10sec alarm
    ALRMVAL[0] = $0005;

    while (1) {
        SleepMode();
        _REG[$8b] = 0;  # clear RTCIF
        DspTime();
    }
}



[ 前へ ] 連載記事 [ 次へ ]