2025年12月2日火曜日

超音波距離計(その4)

 

 RGB-LED(PL-9823)は次のような特殊なPWMパルスを作って駆動する必要がある。このデータは信号が0→1になる所からスタートし、全てのビットを送り終える最後のビットの最後は0→1で終了する。であるなら全てのデータを送った後のアイドルの状態は0なのか1なのか? スタートの条件を考えれば0であるが最後は1で終わるように見える。たぶん最後は0を延長したRETコードをアイドル状態として使うのだろうと推測する。

このパルスの厳密なタイミングには幾つかの情報があり曖昧であるが次の情報が確からしい。実際には動かして確かめる必要があるがHとLが概ね1:3あるいは3:1の比率であれば成立しそうだ。

またビットの並びはMSBファーストで、次の様にRGBの順だという資料もあるがGRBという資料もあり、これも実機で確かめる必要がある。

RGB-LEDはカスケード接続で複数を制御できる優れものである。今回はこれを5個を使う。

データの転送時間は上のタイミングから1ビット当たり1.25μsかかり(転送レート800Kbps相当)、1つのLEDに30us、5個のLEDを合わせると150μsかかる。

この信号をどうやって生成するかであるが、NOP命令を挿入してタイミングを取るソフト的な方法と、ハードロジックで信号を作成する方法が考えられる。MicrochipのアプリケーションノートAN1606にはPIC16F1509のCCPを使ってWS2811を駆動する例が示されており、これが参考になりそうである。しかし何れも時間的にかなりシビアで先人の例でも涙ぐましい苦労の跡が見える。特に回路の遅延などに伴うヒゲの発生には十分注意する必要がある。開発にはロジックアナライザを活用する必要があろう。

 ソフト的にやるのは後回しとして、とりあえずハードロジックを検討してみる。PL-9823はAN1606で示されたWS2811とは駆動する波形が違うので、それに合わせる必要がある。

信号の基本はSPIの出力である。PL-9823ではSPIの1つの出力ビットを4つに分けて1:3(0 code)又は3:1(1 code)の比率のPWMパルスを生成すれば仕様に合う。SPI出力が800Kbpsなら800KHzと1.6MHzの方形波があれば合成できる。SPIを駆動するクロックはFosc/4を分周したものであるため、3.2MHzの2^n倍のFoscが必要であるが一方PIC単体で発生できるクロックは限られており最も近い値として12MHzを4分周してFosc=3MHzとして使うのが適当だろう。ということで転送レート 750Kbps(=3MHz/4)で検討したい。

 AN1606に準じて考えると、SPIの出力(SDO)、SPIのクロック(SCK)、それにSCKの元となるクロックFosc/2から合成できそうだ。Fosc/2はCLKRを使ってFoscを2分周して生成した。

0 code = /SDO & SCK & Fosc/2

1 code = /(/SDO + /SCK + /Fosc/2)  

 PWMパルス = 0 code + 1 code

 この式で & は論理積、+ は論理和、語頭の/ は否定を表す。

この式ならCLKRとCLC各1個で実現できそうである。ということでこれを実際やってみたところ、SPIで送信するバイトの最後のビット(lsb)がSDOでアイドル状態まで引きの伸ばされ0に戻らないためlsbが1だとアイドル状態でもFosc/2が出力されAN1606のようには行かない。強制的にlsbを0として7ビットのデータとするなら上手く行きそうだが。そこで悩んだ末に何とか窓信号を作り論理積をとる事とした。

具体的には2個目のCLCを使ってSCKをFosc/2の立下りでサンプリングすることで25%遅らせたSCK2を作り、SCKとSCK2の論理和を窓信号とする。つまり、 

     1 code = SDO & ( SCK + SCK2 ) 

 とする。これで上手く行きそうだ。そして、次の通り上手く光ってくれた。

実際に出来た波形を観測すると、 SPIの出力は1バイト(8ビット)送出した後次のバイトを送出までに隙間が出来る。つまりSPIではバイトを跨ぐ連続送信は出来ずバイト間では T0L や T1L が長く引き延ばされている状態になる。この隙間の大きさの規程は無いがRETコード、つまり(確かめた訳ではないが)50usを超えなければ問題ないようである。

 ここまでで基本的な機能は実現できたので、あとは受信した測距データをもとに表示内容を決めていきたい。

参考までにPWMパルスを作成するためのCLKR、SPI1、CLC1、CLC2の初期設定部分は次の様にした。

    //
    // CLKR Fosc(3MHz) to 1.5MHz
    //
    CLKRCONbits.CLKRDC  = 2;        // Duty is 50%
    CLKRCONbits.CLKRDIV = 1;        // divide by 2
    CLKRCONbits.CLKREN  = 1;
    //
    // SPI1
    //
    SSP1CON1            = 0x0;      // clear SSPEN
    SSP1CON1bits.CKP    = 0;        // IDLE Clock is Low
    SSP1CON1bits.SSPM   = 0x0;      // SPI with Clock = Fosc/4
    SSP1STATbits.CKE    = 0;        // trans. at clk idl->actv 
    PIR1bits.SSP1IF     = 0;        //        
    SSP1CON1bits.SSPEN  = 1;
    //
    // CLC1
    //
    CLC1CONbits.LC1MODE = 0;        // AND-OR
    CLC1SEL0            = 0x13;     //1 SPI1 SDO
    CLC1SEL1            = 0x12;     //2 SPI1 SCK
    CLC1SEL2            = 0x0B;     //3 CLKR = Fosc/2
    CLC1SEL3            = 0x05;     //4 CLC2 = SCK2
    CLC1GLS0            = 0x16;     // 0 code = /SDO & Fosc/2
    CLC1GLS1            = 0;        // T
    CLC1GLS2            = 0x02;     // 1 0cde = SDO
    CLC1GLS3            = 0x88;     //          &  /(/CSK & /SCK2 )  
    CLC1POL             = 0x03;
    CLC1CONbits.EN      = 1;
    //
    // CLC2 - MAKE DELAYED SCK
    //
    CLC2CONbits.LC2MODE = 4;        // 1 input D-ff with S & R
    CLC2SEL0            = 0x12;     //1 SPI1 SCK
    CLC2SEL1            = 0x0B;     //2 CLKR = Fosc/2
    CLC2SEL2            = 0;
    CLC2SEL3            = 0;
    CLC2GLS0            = 0x4;      // CK: /SEL1 = /Fosc/2
    CLC2GLS1            = 0x2;      // D:  SEL0 = SCK
    CLC2GLS2            = 0;        // R: F
    CLC2GLS3            = 0;        // S: F
    CLC2POL             = 0x00;
    CLC2CONbits.EN      = 1;
 

またSPIでデータを送信するには、通常のシリアルインターフェイスだと送信バッファーが空の状態を監視してバッファーにデータを書き込むが、SPIでは送受のバッファーが共通で、しかも受信データがバッファーに格納された事を表すフラグしかない。今回は受信は行わないので先ずバッファーに送信データを書き込み、受信完了のフラグを待つというプログラミングをする必要がある。具体的には5個のLEDにデータを送るのに次の様にした。なお割込みが起きる事でタイミングが狂わないようこの部分は割り込み禁止としている。

void rgbSetLeds(byte *clr_r, byte *clr_g, byte *clr_b){
    byte GIEBitValue = INTCONbits.GIE;
    INTCONbits.GIE   = OFF;          // di()-----------------              for(byte i=0; i<5; i++){
        PIR1bits.SSP1IF = 0;
        SSP1BUF     = *clr_r++;     
        while(PIR1bits.SSP1IF==0){} PIR1bits.SSP1IF = 0;
        SSP1BUF     = *clr_g++;     
        while(PIR1bits.SSP1IF==0){} PIR1bits.SSP1IF = 0;
        SSP1BUF     = *clr_b++;     
        while(PIR1bits.SSP1IF==0){}
    }
    INTCONbits.GIE = GIEBitValue;    // ei()-----------------

}

これを誤って最初を、UARTの送信のように

        while(PIR1bits.SSP1IF==0){} 
        SSP1BUF     = *clr_r++;     
 

とするとSPIでは永久の待ちとなりデータは送信できない。

(続く) 

 

0 件のコメント:

コメントを投稿