BeagleBone BlackのPRUでADCを制御する

Beagle Bone Black

前回まででPRUの有益なチュートリアルを学んだ。
BeagleBone Black: Introduction to PRU-ICSS

今回は、PRUの応用としてBBBのADCをPRUから制御するデモを紹介する。
BBBではLinuxドライバが用意されており簡便にADCを用いることができる。
しかし、実際に試してみると判る通り、連続でAD変換を行うためには周期的にドライバを叩く必要があるので数msecのサンプリング周期が限界であり、しかも比較的大きなジッタが発生する。
秒単位で電池電圧をサンプリングする等の用途には十分であるし、そのような目的の為に設計されているドライバであると思われる。
BBBは搭載SoCのAM335xのペリフェラルにあるADCを用いて、恐らくは800K Sample/sec程度のAD変換能力を持つ。
このような比較的高速なADCを備えていることが、ライバルであるraspberry pi等に対するメリットであるはずだが、あまり有効に使われている応用例を見たことがない。
その能力を引き出すLinuxドライバが公開されていないことが原因であろう。

今回紹介するのはLinuxドライバとしてADCの逐次変換を実現させるのではなく、PRUをADCとの同期手段としてLinuxのUser-LandでADCの逐次変換結果を刈り取る実装例である。
PRUの処理速度はADCの変換速度より十分高速であるため、割り込みやDMAを用いずともポーリングだけでADCサンプルデータを刈り取ることができる。
PRUにはLinuxとの間のI/Fとして用いることのできるシェアードメモリがあり、これを同期用のリングバッファとして用いることにより簡便なI/Fを用意できる。
Linux ADCドライバのオルタナティブとしての提案である。
いくつかの問題点があり、過度な期待をされると困るが音声レベルのデジタル信号処理の実験に使うことはできると思う。

一応ターゲットとしては以下。
・50K sample/sec
・2ch input

今回のデモの信号の流れを以下の図に示す。

BBB上のLinuxで動作するUser-Land Applicationで異なる二つのサインウエーブを生成し、USB-DACを用いて左右のチャネルそれぞれから音として再生を行う。
その音を0.9V基準になるようにレベルシフトするとともに0V~1.8Vの範囲を超えないように制限をかける。
左右の音データをそれぞれBBBのアナログ入力ポートAIN0/AIN1で受け、ADCのContinuous modeで逐次変換を行う。
AD変換したサンプリングデータはFIFOに入るので、PRU0のプログラムにより逐次刈り取り、PRUのシェアードメモリ上に用意するリングバッファに格納する。
リングバッファの半分が埋まったタイミングでホストに割り込みが入るようにし、BBBのLinuxで動作するUser-Land Applicationでデータを刈り取りファイルに書き込む。
つまりトーンのループバックテストデモとなっている。

前回までの環境が構築されている前提とする。

Beagle Bone BlackのPRUを学ぶ(その4)
Beagle Bone BlackのPRUを学ぶ(その3)
Beagle Bone BlackのPRUを学ぶ(その2)
Beagle Bone BlackのPRUを学ぶ(その1)

PRU0プログラムのソースコードは以下。

// adc.p
 
.origin 0
.entrypoint ADC_SAMPLE
 
#include "adc.hp"
 
// Our plan to use Registers
//     R0: temporary use
//     R1/R2: temporary use
//     R3/R4: temporary use
//     R8: The pointer of RING_BUF. 
//     R9: NumberOfADCSamples
//     R15: Number to indication for Host 
//     R16: Indication of current RING_BUF
 
// I/F with Host 
//   CONST_PRUSHAREDRAM + 0x00: size 4Byte: Number to indication for Host.
//   CONST_PRUSHAREDRAM + 0x04: size 4Byte: Indication of current RING_BUF pointer 0x00000000 or 0x00002000 for Host
//   CONST_PRUSHAREDRAM + 0x08: size 4Byte: Command from Host. If it is not zero, ADC will stop.
//   CONST_PRUSHAREDRAM + 0x0C: size 4Byte: Status notification of ADC to Host. If it is not zero, ADC has been stoped.
 
ADC_SAMPLE:
// Make Constant values for registers
    MOV       r10.w0, 0x0fff            // Mask pattern for ADCDATA in FIFODATA
    MOV       r10.w2, 0x000f            // Mask pattern for ADCCHNLID in FIFODATA
    MOV       r11.w0, 0x2000            // Harf size of RING_BUF but it may not be used
    MOV       r11.w2, 0x4000            // Size of RING_BUF
    MOV       r12.w0, 0x1fff            // Mask pattern for Harf of RING_BUF 
    MOV       r12.w2, 0x3fff            // Mask Pattern for all of RING_BUF
    MOV       r13, TSC_ADC_SS_BASE      // r13 is Address of TSC_ADC_SS_BASE
    MOV       r14.w0, OFFSET_FIFO0DATA  // r14.w0 is OFFSET_FIFO0DATA
    MOV       r14.w2, OFFSET_FIFO1DATA  // r14.w2 is OFFSET_FIFO1DATA
    MOV       r17, 0x00000000           // r17 is always all ZERO
    MOV       r18, r17                  // r18-r20 is always all ZERO
    MOV       r19, r17                  // 
    MOV       r20, r17                  // 
 
// Enable OCP Master port
    LBCO      r0, CONST_PRUCFG, OFFSET_CFG_SYSCFG_REG, 4  // r0 = *(CONST_PRUCFG + OFFSET_CFG_SYSCFG_REG)
    CLR       r0, r0, 4                                   // Clear bit4 of r0 (Clear SYSCFG:STANDBY_INIT 
                                                          // to enable OCP master port)
    SBCO      r0, CONST_PRUCFG, OFFSET_CFG_SYSCFG_REG, 4  // *(CONST_PRUCFG + OFFSET_CFG_SYSCFG_REG) = r0
 
// Init Indexs and Pointers of Constant tables 
    MOV       r0, PRU0_CTRL_REG_BASE             // 
    SBBO      r17, r0, OFFSET_PRU_CTBIR0_REG, 4  // *(PRU0_CTRL_REG_BASE + OFFSET_PRU_CTBIR0_REG) = r17:0x00000000
                                                 //   Set Fields C25_BLK_INDEX and C24_BLK_INDEX to 0.
    MOV       r1, 0x00000100                     // 
    SBBO      r1, r0, OFFSET_PRU_CTPPR0_REG, 4   // *(PRU0_CTRL_REG_BASE + OFFSET_PRU_CTPPR0_REG) = r1:0x00000100
                                                 //   Set Fields C29_POINTER to 0x0000 and C28_POINTER to 0x0100.
                                                 //   Unexpectedly, C28 is NOT dedicated for Shared PRU RAM,
                                                 //   we can use C28 for PRU0/1 Local Data RAM if C28_POINTER
                                                 //   in CTPPRx register is set to 0x0000/0x0020. So to use 
                                                 //   for Shared PRU RAM C28_POINTER must be set to 0x0100.
// Init Indication params for/from Host
    SBCO      r17, CONST_PRUSHAREDRAM, 0x00, 4   // *(CONST_PRUSHAREDRAM + 0) = r17:0x00000000
    SBCO      r17, CONST_PRUSHAREDRAM, 0x04, 4   // *(CONST_PRUSHAREDRAM + 4) = r17:0x00000000
    SBCO      r17, CONST_PRUSHAREDRAM, 0x08, 4   // *(CONST_PRUSHAREDRAM + 8) = r17:0x00000000
    SBCO      r17, CONST_PRUSHAREDRAM, 0x0c, 4   // *(CONST_PRUSHAREDRAM + 12) = r17:0x00000000
 
// Init RING_BUF
    MOV       r8.w0, r17.w0                      // r8.w0:LoopCounter = 0x0000
 
CLEAR_RING_BUF:
    SBCO      r17, CONST_PRUD0RAM, r8.w0, 4      // *(CONST_PRUD0RAM + r8.w0:LoopCounter) = r17:0x00000000
    ADD       r8.w0, r8.w0, 4                    // r8.w0:LoopCounter += 4
    QBNE      CLEAR_RING_BUF, r8.w0, r11.w2      // if r8.w0:LoopCounter != r11.w2:0x4000 then 
                                                 // goto CLEAR_RING_BUF
 
// Init TSC_ADC_SS
    LBBO      r0, r13, OFFSET_REVISION, 4        // r0 = *(TSC_ADC_SS_BASE + OFFSET_REVISION)
                                                 //   REVISION register is read-only and fixed value as 0x47300001.
    MOV       r0, 0x06                           //
    SBBO      r0, r13, OFFSET_ADC_CTRL, 4        // *(TSC_ADC_SS_BASE + OFFSET_ADC_CTRL) = r0:0x00000006
                                                 // CTRL Register
                                                 //   0x44E0D040 : 0x6
                                                 //     9 HW_preempt: 0 = SW steps are not pre-empted 
                                                 //                   by HW events.
                                                 //     8 HW_event_mapping: 0 = Map HW event to Pen touch 
                                                 //                         irq (from AFE).
                                                 //     7 Touch_Screen_Enable: 0 = Touchscreen transistors 
                                                 //                            disabled.
                                                 //     6-5 AFE_Pen_Ctrl: 
                                                 //     4 Power_Down: 0 = AFE is powered up (default).
                                                 //     3 ADC_Bias_Select: 0 = Internal.
                                                 //     2 StepConfig_WriteProtect_n_active_low: 
                                                 //         1 = Step configuration registers are not 
                                                 //             protected (writable). 
                                                 //             To config configuration registers, 
                                                 //             it must be set.
                                                 //     1 Step_ID_tag: 
                                                 //         1 = Store the channel ID tag.
                                                 //     0 Enable: 
                                                 //         After programming all STEPs and 
                                                 //         configuration registers, we write 1 to this 
                                                 //         bit to turn on TSC_ADC_SS. To config 
                                                 //         configuration registers it must be disabled.
    MOV       r0, 0x0000000c                     //
    SBBO      r0, r13, OFFSET_SYSCONFIG, 4       // *(TSC_ADC_SS_BASE + OFFSET_SYSCONFIG) = r0:0x0000000c
                                                 // SYSCONFIG Register
                                                 //   0x44E0D010 : 0xC
                                                 //     IdleMode : 11 = Smart Idle with Wakeup.
    MOV       r0, 0x00000001                     //
    SBBO      r0, r13, OFFSET_ADC_CLKDIV, 4      // *(TSC_ADC_SS_BASE + OFFSET_ADC_CLKDIV) = r0:0x00000001
                                                 // ADC_CLKDIV Register
                                                 //   0x44E0D04C : 0x1
                                                 //       The input ADC clock will be divided by this value 
                                                 //       and sent to the AFE.
                                                 //       Program to the value minus 1, so 0x01 is 2x.
                                                 //       Max Freq of adc_clk is 24MHz, however in BBB Rev.C 
                                                 //       clock source (CLK_M_OSC) is 25MHz, so ADC clock 
                                                 //       must be divided by 2x or more.
//  MOV       r0, 0x00000002                     // we use STEP1 
    MOV       r0, 0x00000006                     // we use STEP1 and STEP2
    SBBO      r0, r13, OFFSET_STEPENABLE, 4      // *(TSC_ADC_SS_BASE + OFFSET_STEPENABLE) = r0:0x00000002
                                                 // STEPENABLE Register
                                                 //   0x44E0D054 : 0x2
                                                 //       Enable/Disabe STEPS
                                                 //         16 STEP16 R/W 0h Enable step 16
                                                 //         15 STEP15 R/W 0h Enable step 15
                                                 //         14 STEP14 R/W 0h Enable step 14
                                                 //         13 STEP13 R/W 0h Enable step 13
                                                 //         12 STEP12 R/W 0h Enable step 12
                                                 //         11 STEP11 R/W 0h Enable step 11
                                                 //         10 STEP10 R/W 0h Enable step 10
                                                 //         9  STEP9 R/W 0h Enable step 9
                                                 //         8  STEP8 R/W 0h Enable step 8
                                                 //         7  STEP7 R/W 0h Enable step 7
                                                 //         6  STEP6 R/W 0h Enable step 6
                                                 //         5  STEP5 R/W 0h Enable step 5
                                                 //         4  STEP4 R/W 0h Enable step 4
                                                 //         3  STEP3 R/W 0h Enable step 3
                                                 //       * 2  STEP2 R/W 0h Enable step 2
                                                 //       * 1  STEP1 R/W 0h Enable step 1
                                                 //         0  TS_Charge R/W 0h Enable TS Charge step
                                                 //           In this sample application, STEP1-8 will 
                                                 //           be configured as below.
                                                 //             STEP1: Channel 1 AIN0 (P9_39)
                                                 //             STEP2: Channel 2 AIN1 (P9_40)
                                                 //             STEP3: Channel 3 AIN2 (P9_37)
                                                 //             STEP4: Channel 4 AIN3 (P9_38)
                                                 //             STEP5: Channel 5 AIN4 (P9_33)
                                                 //             STEP6: Channel 6 AIN5 (P9_36)
                                                 //             STEP7: Channel 7 AIN6 (P9_35)
                                                 //             STEP8: Channel 8 AIN7 (NC w/ pin header)
                                                 //           So to enable STEP1-8 using Enable/Disabe STEPS
                                                 //           we may be able to use corresponding AIN. 
 
    SBBO      r17, r13, OFFSET_IDLECONFIG, 4     // *(TSC_ADC_SS_BASE + OFFSET_IDLECONFIG) = r17:0x00000000
                                                 // IDLECONFIG Register
                                                 //   0x44E0D058 : 0x0
                                                 //         25 Diff_CNTRL: Differential Control Pin
                                                 //         24-23 SEL_RFM_SWC_1_0: 00 = VSSA.
                                                 //         22-19 SEL_INP_SWC_3_0: 0000 = Channel 1.
                                                 //         18-15 SEL_INM_SWC_3_0: 0000 = Channel 1.
                                                 //         14-12 SEL_RFP_SWC_2_0: 000 = VDDA_ADC.
                                                 //         11 WPNSW_SWC: WPNSW pin SW configuration
                                                 //         10 YPNSW_SWC: YPNSW pin SW configuration
                                                 //         9  XNPSW_SWC: XNPSW pin SW configuration
                                                 //         8  YNNSW_SWC: YNNSW pin SW configuration
                                                 //         7  YPPSW_SWC: YPPSW pin SW configuration
                                                 //         6  XNNSW_SWC: XNNSW pin SW configuration
                                                 //         5  XPPSW_SWC: XPPSW pin SW configuration
    // Config STEPCONFIG Registers and STEPDELAY Registers
                                                 // STEPCONFIGx Register
                                                 //   0x44E0D064+(x*8) : 0x0
                                                 //         27 Range_check: 0 = Disable out-of-range check.
                                                 //         26 FIFO_select: 0 = FIFO 0 
                                                 //         25 Diff_CNTRL: Differential Control Pin
                                                 //         24-23 SEL_RFM_SWC_1_0: 00 = VSSA.
                                                 //         22-19 SEL_INP_SWC_3_0: 0000 = Channel 1.
                                                 //         18-15 SEL_INM_SWC_3_0: 0000 = Channel 1.
                                                 //         14-12 SEL_RFP_SWC_2_0: 000 = VDDA_ADC.
                                                 //         11 WPNSW_SWC: WPNSW pin SW configuration
                                                 //         10 YPNSW_SWC: YPNSW pin SW configuration
                                                 //         9  XNPSW_SWC: XNPSW pin SW configuration
                                                 //         8  YNNSW_SWC: YNNSW pin SW configuration
                                                 //         7  YPPSW_SWC: YPPSW pin SW configuration
                                                 //         6  XNNSW_SWC: XNNSW pin SW configuration
                                                 //         5  XPPSW_SWC: XPPSW pin SW configuration
                                                 //         4-2 Averaging: 000 = No average.
                                                 //         1-0 Mode: 00 = SW enabled, one-shot.
                                                 //                   01 = SW enabled, continuous.
                                                 //                   10 = HW synchronized, one-shot.
                                                 //                   11 = HW synchronized, continuous.
                                                 // STEPDELAYx Register
                                                 //   0x44E0D068+(x*8) : 0x0
                                                 //          31-24 SampleDelay
                                                 //              This register will control the number 
                                                 //              of ADC clock cycles to sample (hold SOC high).
                                                 //              Any value programmed here will be added 
                                                 //              to the minimum requirement of 1 clock cycle.
                                                 //          17-0 OpenDelay 
                                                 //              Program the number of ADC clock cycles to wait 
                                                 //              after applying the step configuration registers 
                                                 //              and before sending the start of ADC conversion.
    MOV       r0, 0x00000001                     //
    SBBO      r0, r13, OFFSET_STEPCONFIG1, 4     // *(TSC_ADC_SS_BASE + OFFSET_STEPCONFIG1) = r0:0x00000001
                                                 // STEPCONFIG1 Register
                                                 //   0x44E0D064 : 0x00000001
                                                 //          27 Range_check: 0 = Disable out-of-range check.
                                                 //          26 FIFO_select: 0 = FIFO 0
                                                 //          22-19 SEL_INP_SWC_3_0:
                                                 //                  0000 = Channel 1 AIN0 (P9_39)
                                                 //          4-2 Averaging: 000 = No average.
                                                 //          1-0 Mode: 01 = SW enabled, continuous.. 
    MOV       r0, 0x00080001                     //
    SBBO      r0 , r13, OFFSET_STEPCONFIG2, 4    // *(TSC_ADC_SS_BASE + OFFSET_STEPCONFIG2) = r17:0x00000000
                                                 //          22-19 SEL_INP_SWC_3_0:
                                                 //                  0001 = Channel 2 AIN1 (P9_40)
                                                 //          Other fields are same as STEPCONFIG1 Register.
    MOV       r0, 0x00100001                     //
    SBBO      r0, r13, OFFSET_STEPCONFIG3, 4     // *(TSC_ADC_SS_BASE + OFFSET_STEPCONFIG3) = r17:0x00000000
                                                 //          22-19 SEL_INP_SWC_3_0:
                                                 //                  0010 = Channel 3 AIN2 (P9_37)
                                                 //          Other fields are same as STEPCONFIG1 Register.
    MOV       r0, 0x00180001                     //
    SBBO      r0, r13, OFFSET_STEPCONFIG4, 4     // *(TSC_ADC_SS_BASE + OFFSET_STEPCONFIG4) = r17:0x00000000
                                                 //          22-19 SEL_INP_SWC_3_0:
                                                 //                  0011 = Channel 4 AIN3 (P9_38)
                                                 //          Other fields are same as STEPCONFIG1 Register.
    MOV       r0, 0x00200001                     //
    SBBO      r0, r13, OFFSET_STEPCONFIG5, 4     // *(TSC_ADC_SS_BASE + OFFSET_STEPCONFIG5) = r17:0x00000000
                                                 //          22-19 SEL_INP_SWC_3_0:
                                                 //                  0100 = Channel 5 AIN4 (P9_33)
                                                 //          Other fields are same as STEPCONFIG1 Register.
    MOV       r0, 0x00280001                     //
    SBBO      r0, r13, OFFSET_STEPCONFIG6, 4     // *(TSC_ADC_SS_BASE + OFFSET_STEPCONFIG6) = r17:0x00000000
                                                 //          22-19 SEL_INP_SWC_3_0:
                                                 //                  0101 = Channel 6 AIN5 (P9_36)
                                                 //          Other fields are same as STEPCONFIG1 Register.
    MOV       r0, 0x00300001                     //
    SBBO      r0, r13, OFFSET_STEPCONFIG7, 4     // *(TSC_ADC_SS_BASE + OFFSET_STEPCONFIG7) = r17:0x00000000
                                                 //          22-19 SEL_INP_SWC_3_0:
                                                 //                  0110 = Channel 7 AIN6 (P9_35)
                                                 //          Other fields are same as STEPCONFIG1 Register.
    MOV       r0, 0x00380001                     //
    SBBO      r0, r13, OFFSET_STEPCONFIG8, 4     // *(TSC_ADC_SS_BASE + OFFSET_STEPCONFIG8) = r17:0x00000000
                                                 //          22-19 SEL_INP_SWC_3_0:
                                                 //                  0111 = Channel 8 AIN7 (NC w/ pin header)
                                                 //          Other fields are same as STEPCONFIG1 Register.
    SBBO      r17, r13, OFFSET_STEPCONFIG9, 4    // *(TSC_ADC_SS_BASE + OFFSET_STEPCONFIG9) = r17:0x00000000
    SBBO      r17, r13, OFFSET_STEPCONFIG10, 4   // *(TSC_ADC_SS_BASE + OFFSET_STEPCONFIG10) = r17:0x00000000
    SBBO      r17, r13, OFFSET_STEPCONFIG11, 4   // *(TSC_ADC_SS_BASE + OFFSET_STEPCONFIG11) = r17:0x00000000
    SBBO      r17, r13, OFFSET_STEPCONFIG12, 4   // *(TSC_ADC_SS_BASE + OFFSET_STEPCONFIG12) = r17:0x00000000
    SBBO      r17, r13, OFFSET_STEPCONFIG13, 4   // *(TSC_ADC_SS_BASE + OFFSET_STEPCONFIG13) = r17:0x00000000
    SBBO      r17, r13, OFFSET_STEPCONFIG14, 4   // *(TSC_ADC_SS_BASE + OFFSET_STEPCONFIG14) = r17:0x00000000
    SBBO      r17, r13, OFFSET_STEPCONFIG15, 4   // *(TSC_ADC_SS_BASE + OFFSET_STEPCONFIG15) = r17:0x00000000
    SBBO      r17, r13, OFFSET_STEPCONFIG16, 4   // *(TSC_ADC_SS_BASE + OFFSET_STEPCONFIG16) = r17:0x00000000
 
    MOV       r0, 0x00000000                     //
    SBBO      r0, r13, OFFSET_STEPDELAY1, 4      // *(TSC_ADC_SS_BASE + OFFSET_STEPDELAY1) = 0x00000000
                                                 // STEPDELAY1 Register
                                                 //   0x44E0D068+(x*8) : 0x0
                                                 //          31-24 SampleDelay
                                                 //              This register will control the number 
                                                 //              of ADC clock cycles to sample (hold SOC high).
                                                 //              Any value programmed here will be added 
                                                 //              to the minimum requirement of 1 clock cycle.
                                                 //          17-0 OpenDelay 
                                                 //              Program the number of ADC clock cycles to wait 
                                                 //              after applying the step configuration registers 
                                                 //              and before sending the start of ADC conversion.
    MOV       r0, 214                            //
    SBBO      r0, r13, OFFSET_STEPDELAY2, 4      // *(TSC_ADC_SS_BASE + OFFSET_STEPDELAY2) = 214
    SBBO      r17, r13, OFFSET_STEPDELAY3, 4     // *(TSC_ADC_SS_BASE + OFFSET_STEPDELAY3) = r17:0x00000000
    SBBO      r17, r13, OFFSET_STEPDELAY4, 4     // *(TSC_ADC_SS_BASE + OFFSET_STEPDELAY4) = r17:0x00000000
    SBBO      r17, r13, OFFSET_STEPDELAY5, 4     // *(TSC_ADC_SS_BASE + OFFSET_STEPDELAY5) = r17:0x00000000
    SBBO      r17, r13, OFFSET_STEPDELAY6, 4     // *(TSC_ADC_SS_BASE + OFFSET_STEPDELAY6) = r17:0x00000000
    SBBO      r17, r13, OFFSET_STEPDELAY7, 4     // *(TSC_ADC_SS_BASE + OFFSET_STEPDELAY7) = r17:0x00000000
    SBBO      r17, r13, OFFSET_STEPDELAY8, 4     // *(TSC_ADC_SS_BASE + OFFSET_STEPDELAY8) = r17:0x00000000
    SBBO      r17, r13, OFFSET_STEPDELAY9, 4     // *(TSC_ADC_SS_BASE + OFFSET_STEPDELAY9) = r17:0x00000000
    SBBO      r17, r13, OFFSET_STEPDELAY10, 4    // *(TSC_ADC_SS_BASE + OFFSET_STEPDELAY10) = r17:0x00000000
    SBBO      r17, r13, OFFSET_STEPDELAY11, 4    // *(TSC_ADC_SS_BASE + OFFSET_STEPDELAY11) = r17:0x00000000
    SBBO      r17, r13, OFFSET_STEPDELAY12, 4    // *(TSC_ADC_SS_BASE + OFFSET_STEPDELAY12) = r17:0x00000000
    SBBO      r17, r13, OFFSET_STEPDELAY13, 4    // *(TSC_ADC_SS_BASE + OFFSET_STEPDELAY13) = r17:0x00000000
    SBBO      r17, r13, OFFSET_STEPDELAY14, 4    // *(TSC_ADC_SS_BASE + OFFSET_STEPDELAY14) = r17:0x00000000
    SBBO      r17, r13, OFFSET_STEPDELAY15, 4    // *(TSC_ADC_SS_BASE + OFFSET_STEPDELAY15) = r17:0x00000000
    SBBO      r17, r13, OFFSET_STEPDELAY16, 4    // *(TSC_ADC_SS_BASE + OFFSET_STEPDELAY16) = r17:0x00000000
 
    SBBO      r17, r13, OFFSET_FIFO0THRESHOLD, 4 // *(TSC_ADC_SS_BASE + OFFSET_FIFO0THRESHOLD) = r17:0x00000000
                                                 // FIFO0THRESHOLD Register
                                                 //   0x44E0D0E8 : 0x0
                                                 //       Program the desired FIFO0 data sample level to reach before
                                                 //       generating interrupt to CPU (program to value minus 1)
    SBBO      r17, r13, OFFSET_FIFO1THRESHOLD, 4 // *(TSC_ADC_SS_BASE + OFFSET_FIFO1THRESHOLD) = r17:0x00000000
                                                 // FIFO1THRESHOLD Register
                                                 //   0x44E0D0F4 : 0x0
                                                 //       Program the desired FIFO1 data sample level to reach before
                                                 //       generating interrupt to CPU (program to value minus 1)
    MOV       r0, 0x07                           //
    SBBO      r0, r13, OFFSET_ADC_CTRL, 4        // *(TSC_ADC_SS_BASE + OFFSET_ADC_CTRL) = r0:0x00000007
                                                 // CTRL Register
                                                 //   0x44E0D040 : 0x7
                                                 //     2 StepConfig_WriteProtect_n_active_low: 
                                                 //        1 = Step configuration registers are not 
                                                 //            protected (writable).
                                                 //     1 Step_ID_tag: 1 = Store the channel ID tag.
                                                 //     0 Enable: 
                                                 //        1 = Turn on TSC_ADC_SS. By this ADC is sterted.
 
// Read and discard data in FIFO if exist
DISCARD_DATA:
    LBBO      r0, r13, OFFSET_FIFO0COUNT, 4      // r0 = *(TSC_ADC_SS_BASE + OFFSET_FIFO0COUNT)
    QBEQ      SET_LOOP_COUNTER, r0.w0, r17.w0    // if r0.w0:FIFO1COUNT == r17:0x0000 then goto SET_LOOP_COUNTER
    LBBO      r0, r13, r14.w0, 4                 // r0 = *(TSC_ADC_SS_BASE + r14.w0:OFFSET_FIFO0DATA)
    JMP       DISCARD_DATA                       // goto DISCARD_DATA
 
SET_LOOP_COUNTER:
    MOV       r8, r17                            // r8:LoopCounter = r17:0x00000000
    MOV       r9, r17                            // r9:NumberOfADCSamples = r17:0x00000000
    MOV       r16, r17                           // r16:IndicationOfCurrentBUF = r17:0x00000000
    MOV       r15, r17                           // r15:IndexNumberOfIndication = r17:0x00000000
 
//endless main loop 
MAIN_LOOP:
    // check command from host with shared mem
    LBCO      r0, CONST_PRUSHAREDRAM, 0x08, 4    // r0 = *(CONST_PRUSHAREDRAM + 8)
    QBEQ      POLL_FIFO, r0, r17                 // if r0:Command_from_host == r17:0x00000000 then goto POLL_FIFO
 
    // Turn off the ADC if exist
    MOV       r0, 0x06                           //
    SBBO      r0, r13, OFFSET_ADC_CTRL, 4        // *(TSC_ADC_SS_BASE + OFFSET_ADC_CTRL) = r0:0x00000006
    // then notify Host with shared mem that command has been executed
    MOV       r0, 0x00000001                     //
    SBCO      r0, CONST_PRUSHAREDRAM, 0x0c, 4    // *(CONST_PRUSHAREDRAM + 12) = r0:0x00000001
     
POLL_FIFO:
    // poll the FIFO1COUNT register
    LBBO      r0, r13, OFFSET_FIFO0COUNT, 4      // r0 = *(r13:TSC_ADC_SS_BASE + OFFSET_FIFO0COUNT)
    QBEQ      MAIN_LOOP, r0.w0, r17.w0           // if r0.w0:Words_in_FIFO0 == r17.w0:0x0000 then goto MAIN_LOOP
 
    // Read ADC sampling DATA in FIFO
    LBBO      r0, r13, r14.w0, 4                 // r0 = *(r13:TSC_ADC_SS_BASE + r14.w0:OFFSET_FIFO0DATA)
    AND       r0.w0, r0.w0, r10.w0               // r0.w0 = r0.w0:ADCDATA & r10.w0:0x0fff
    AND       r0.w2, r0.w2, r10.w2               // r0.w2 = r0.w2:ADCCHNLID & r10.w2:0x000f
    LSL       r0.w2, r0.w2, 12                   // r0.w2 = r0.w2:ADCCHNLID << 12
    OR        r0.w0, r0.w0, r0.w2                // r0.w0 = r0.w0 | r0.w2
    MOV       r0.w2, r9.w0                       // r0.w2 = r9.w0:NumberOfADCSamples
    SBCO      r0, CONST_PRUD0RAM, r8.w0, 4       // *(CONST_PRUD0RAM + r8.w0:LoopCounter) = r0
    ADD       r9.w0, r9.w0, 1                    // r9.w0:NumberOfADCSamples += 1
 
    ADD       r8.w0, r8.w0, 4                    // r8.w0:LoopCounter += 4
    AND       r8.w0, r8.w0, r12.w2               // r8.w0:LoopCounter &= r12.w2:0x3fff
    AND       r0.w0, r8.w0, r12.w0               // r0.w0 = r8.w0:LoopCounter & r12.w2:0x1fff
    QBNE      MAIN_LOOP, r0.w0, r17.w0           // if r0.w0 != r17.w0:0x0000 then goto MAIN_LOOP
 
    // Store ADC sampling DATA into RING_BUF
    MOV       r16.w0, r8.w0                      // r16:IndicationOfCurrentBUF = r8.w0:LoopCounter
                                                 //   At this r8.w0:LoopCounter may be 0x0000 or 0x2000.
                                                 //   So r16:IndicationOfCurrentBUF means 0x0000-0x1fff 
                                                 //   if r8.w0:LoopCounter is 0x2000,
                                                 //   and 0x2000-0x3fff if r8.w0:LoopCounter is 0x0000.
    ADD       r15, r15, 1                        // r15:IndexNumberOfIndication += 1
    SBCO      r15, CONST_PRUSHAREDRAM, 0x00, 4   // *(CONST_PRUSHAREDRAM + 0) = r15:IndexNumberOfIndication
    SBCO      r16, CONST_PRUSHAREDRAM, 0x04, 4   // *(CONST_PRUSHAREDRAM + 4) = r16:IndicationOfCurrentBUF
 
    MOV       r31.b0, PRU0_ARM_INTERRUPT+16      // Send notification to Host
                                                 //   It means that half of RING_BUF has been filled 
                                                 //   with ADC sampling data.
    JMP       MAIN_LOOP                          // goto MAIN_LOOP

同じくPRU0プログラムのヘッダファイルは以下。

// adc.hp
 
#ifndef _PRU_ADC_
#define _PRU_ADC_
 
// ***************************************
// *      Global Macro definitions       *
// ***************************************
 
#define PRU0_PRU1_INTERRUPT     17
#define PRU1_PRU0_INTERRUPT     18
#define PRU0_ARM_INTERRUPT      19
#define PRU1_ARM_INTERRUPT      20
#define ARM_PRU0_INTERRUPT      21
#define ARM_PRU1_INTERRUPT      22
 
#define CONST_PRUCFG            C4
#define CONST_PRUD0RAM          C24
#define CONST_PRUD1RAM          C25
#define CONST_PRUSHAREDRAM      C28
#define CONST_DDR               C31
 
#define PRU0_CTRL_REG_BASE    0x00022000
 
#define OFFSET_PRU_CONTROL_REG        0x00
#define OFFSET_PRU_STATUS_REG         0x04
                                              // 5 SUB_MWAIT
                                              //     Status bit for wait state.
                                              //       0 = Ready for Transaction
                                              //       1 = Wait until 0
                                              // 4   STANDBY_INIT 
                                              //       1 = Initiate standby sequence.
                                              //       0 = Ready for Transaction
                                              // 3-2 STANDBY_MODE
                                              //       0h = Force standby mode: Initiator unconditionally 
                                              //            in standby (standby = 1).
                                              //       1h = No standby mode: Initiator unconditionally out 
                                              //            of standby (standby = 0).
                                              //       2h = Smart standby mode: Standby requested by initiator 
                                              //            depending on internal conditions.
                                              //       3h = Reserved.
                                              // 1-0 IDLE_MODE
                                              //       0h = Force-idle mode.
                                              //       1h = No-idle mode.
                                              //       2h = Smart-idle mode.
                                              //       3h = Reserved.
#define OFFSET_PRU_WAKEUP_EN_REG      0x08
#define OFFSET_PRU_CYCLE_REG          0x0C
#define OFFSET_PRU_STALL_REG          0x10
#define OFFSET_PRU_CTBIR0_REG         0x20
                                               // 23-16 C25_BLK_INDEX (PRU1/0 Local Data)
                                               // 7-0   C24_BLK_INDEX (PRU0/1 Local Data)
#define OFFSET_PRU_CTBIR1_REG         0x24
                                               // 23-16 C27_BLK_INDEX (MII_RT (local))
                                               // 7-0   C26_BLK_INDEX (IEP (local))
#define OFFSET_PRU_CTPPR0_REG         0x28
                                               // 31-16 C29_POINTER (TPCC)
                                               // 15-0  C28_POINTER (Shared PRU RAM (local))
#define OFFSET_PRU_CTPPR1_REG         0x2C
                                               // 31-16 C31_POINTER (EMIF0 DDR Base)
                                               // 15-0  C30_POINTER (L3 OCMC0)
 
#define PRU_ICSS_CFG_REG_BASE 0x00026000
#define OFFSET_CFG_REVID_REG          0x00
#define OFFSET_CFG_SYSCFG_REG         0x04
                                               // 5 SUB_MWAIT
                                               //     Status bit for wait state.(Read only)
                                               //       0 = Ready for Transaction
                                               //       1 = Wait until 0
                                               // 4 STANDBY_INIT
                                               //       1 = Initiate standby sequence.
                                               //       0 = Enable OCP master ports.
                                               // 3-2 STANDBY_MODE
                                               //       0h = Force standby mode: Initiator unconditionally 
                                               //            in standby (standby = 1).
                                               //       1h = No standby mode: Initiator unconditionally out 
                                               //            of standby (standby = 0).
                                               //       2h = Smart standby mode: Standby requested by initiator 
                                               //            depending on internal conditions.
                                               //       3h = Reserved.
                                               // 1-0 IDLE_MODE
                                               //       0h = Force-idle mode.
                                               //       1h = No-idle mode.
                                               //       2h = Smart-idle mode.
                                               //       3h = Reserved.
#define OFFSET_CFG_GPCFG0_REG         0x08
                                               // 25 PRU0_GPO_SH_SEL
                                               //     Defines which shadow register is currently getting used 
                                               //     for GPO shifting.(Read only)
                                               //       0 = gpo_sh0 is selected
                                               //       1 = gpo_sh1 is selected
                                               // 24-20 PRU0_GPO_DIV1
                                               //     Divisor value (divide by PRU0_GPO_DIV1 + 1).
                                               //       0h = div 1.0.
                                               //       1h = div 1.5.
                                               //       2h = div 2.0.
                                               //         ..
                                               //       1eh = div 16.0.
                                               //       1fh = reserved.
                                               // 19-15 PRU0_GPO_DIV0
                                               //     Same as PRU0_GPO_DIV1
                                               // 14 PRU0_GPO_MODE
                                               //       0 = Direct output mode
                                               //       1 = Serial output mode
                                               // 13 PRU0_GPI_SB
                                               //     Start Bit event for 28-bit shift mode.
                                               //     PRU0_GPI_SB (pru0_r31_status[29]) is set when first capture 
                                               //     of a 1 on pru0_r31_status[0].
                                               //       Read 1: Start Bit event occurred.
                                               //       Read 0: Start Bit event has not occurred.
                                               //       Write 1: Will clear PRU0_GPI_SB and clear the whole 
                                               //                shift register.
                                               //       Write 0: No Effect.
                                               // 12-8 PRU0_GPI_DIV1
                                               //     Divisor value (divide by PRU0_GPI_DIV1 + 1).
                                               //       0h = div 1.0.
                                               //       1h = div 1.5.
                                               //       2h = div 2.0.
                                               //         ..
                                               //       1eh = div 16.0.
                                               //       1fh = reserved.
                                               // 7-3  PRU0_GPI_DIV0
                                               //     Same as PRU0_GPI_DIV1
                                               // 2 PRU0_GPI_CLK_MODE
                                               //     Parallel 16-bit capture mode clock edge.
                                               //       0 = Use the positive edge of pru0_r31_status[16]
                                               //       1 = Use the negative edge of pru0_r31_status[16]
                                               // 1-0 PRU0_GPI_MODE
                                               //       0h = Direct connection mode.
                                               //       1h = 16-bit parallel capture mode.
                                               //       2h = 28-bit shift mode.
                                               //       3h = Mii_rt mode.
#define OFFSET_CFG_GPCFG1_REG         0x0C
                                               // Fields are Same as GPCFG0_REG, buf GPCFG1_REG is for PRU1.
 
#define OFFSET_CFG_CGR_REG            0x10
#define OFFSET_CFG_ISRP_REG           0x14
#define OFFSET_CFG_ISP_REG            0x18
#define OFFSET_CFG_IESP_REG           0x1C
#define OFFSET_CFG_IECP_REG           0x20
#define OFFSET_CFG_PMAO_REG           0x28
#define OFFSET_CFG_MII_RT_REG         0x2C
#define OFFSET_CFG_IEPCLK_REG         0x30
#define OFFSET_CFG_SPP_REG            0x34
#define OFFSET_CFG_PIN_MX_REG         0x40
 
// Address for the Constant table Block Index Register (CTBIR)
#define CTBIR          0x22020
 
// Address for the Constant table Programmable Pointer Register 0(CTPPR_0)
#define CTPPR_0         0x22028
 
// Address for the Constant table Programmable Pointer Register 1(CTPPR_1)
#define CTPPR_1         0x2202C
 
#define TSC_ADC_SS_BASE           0x44E0D000
#define OFFSET_REVISION                0x000
#define OFFSET_SYSCONFIG               0x010
#define OFFSET_IRQENABLE_CLR           0x030
#define OFFSET_IRQWAKEUP               0x034
#define OFFSET_ADC_CTRL                0x040
#define OFFSET_ADCSTAT                 0x044
#define OFFSET_ADC_CLKDIV              0x04C
#define OFFSET_STEPENABLE              0x054
#define OFFSET_IDLECONFIG              0x058
#define OFFSET_TS_CHARGE_STEPCONFIG    0x05C
#define OFFSET_TS_CHARGE_DELAY         0x060
#define OFFSET_STEPCONFIG1             0x064
#define OFFSET_STEPDELAY1              0x068
#define OFFSET_STEPCONFIG2             0x06C
#define OFFSET_STEPDELAY2              0x070
#define OFFSET_STEPCONFIG3             0x074
#define OFFSET_STEPDELAY3              0x078
#define OFFSET_STEPCONFIG4             0x07C
#define OFFSET_STEPDELAY4              0x080
#define OFFSET_STEPCONFIG5             0x084
#define OFFSET_STEPDELAY5              0x088
#define OFFSET_STEPCONFIG6             0x08C
#define OFFSET_STEPDELAY6              0x090
#define OFFSET_STEPCONFIG7             0x094
#define OFFSET_STEPDELAY7              0x098
#define OFFSET_STEPCONFIG8             0x09C
#define OFFSET_STEPDELAY8              0x0A0
#define OFFSET_STEPCONFIG9             0x0A4
#define OFFSET_STEPDELAY9              0x0A8
#define OFFSET_STEPCONFIG10            0x0AC
#define OFFSET_STEPDELAY10             0x0B0
#define OFFSET_STEPCONFIG11            0x0B4
#define OFFSET_STEPDELAY11             0x0B8
#define OFFSET_STEPCONFIG12            0x0BC
#define OFFSET_STEPDELAY12             0x0C0
#define OFFSET_STEPCONFIG13            0x0C4
#define OFFSET_STEPDELAY13             0x0C8
#define OFFSET_STEPCONFIG14            0x0CC
#define OFFSET_STEPDELAY14             0x0D0
#define OFFSET_STEPCONFIG15            0x0D4
#define OFFSET_STEPDELAY15             0x0D8
#define OFFSET_STEPCONFIG16            0x0DC
#define OFFSET_STEPDELAY16             0x0E0
#define OFFSET_FIFO0COUNT              0x0E4
#define OFFSET_FIFO0THRESHOLD          0x0E8
#define OFFSET_FIFO1COUNT              0x0F0
#define OFFSET_FIFO1THRESHOLD          0x0F4
#define OFFSET_FIFO0DATA               0x100
#define OFFSET_FIFO1DATA               0x200
 
#endif //_PRU_ADC_

LinuxのUser-Land上で動作するADC Applicationのソースコードは以下。

/*
 * adc.c
 */
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <unistd.h>
#include <sched.h>
 
#include "prussdrv.h"
#include <pruss_intc_mapping.h>
 
#define PRU_NUM      0
 
#define OFFSET_MEM0      0x00000000
#define OFFSET_MEM1      0x00002000
#define OFFSET_SHAREDRAM 0x00010000
#define SIZE_OF_BUFFER   0x2000
 
/******************************************************************************
* Global Variable Definitions                                                 *
******************************************************************************/
 
static void *pruDataMem;
static unsigned int *pruDataMem_int0; /*AM33XX_DATA 8KB RAM0*/
static unsigned int *pruDataMem_int1; /*AM33XX_DATA 8KB RAM1*/
static unsigned int *sharedMem_int;
static unsigned int *currentBuffer_int;
 
/******************************************************************************
* Global Function Definitions                                                 *
******************************************************************************/
 
int main(int argc, char *argv[])
{
    unsigned int ret;
    struct sched_param sp;
    int policy;
    int result;
 
    int i = 0;
    FILE* pfile;
    tpruss_intc_initdata pruss_intc_initdata = PRUSS_INTC_INITDATA;
    unsigned short sampling_data[2048 * 16];
 
    if(argc < 2)/*2nd argc is file name for output sampling data.*/
    {
        printf("There is no output file name.\n");
        return(0);
    }
    pfile = fopen(argv[1], "w");
    if (pfile == NULL)
    {
        printf("Unable to open output file.\n");
        return(0);
    }
 
    /* change schduling policy to real-time */
    policy = SCHED_FIFO; /* we can select either SCHED_RR or SCHED_FIFO as policy. */
    sp.sched_priority = 1; /* we can select priority value within 1 to 99. */
    result = sched_setscheduler(0, policy, &sp);
    if (result)
    {
        printf("Unable to change schduling policy: sched_setscheduler = %d\n", result);
        return(0);
    }
 
    /* Initialize the PRU */
    prussdrv_init ();
 
    /* Open PRU Interrupt */
    ret = prussdrv_open(PRU_EVTOUT_0);
    if (ret)
    {
        printf("prussdrv_open open failed\n");
        return (ret);
    }
 
    /* Get the interrupt initialized */
    prussdrv_pruintc_init(&pruss_intc_initdata);
 
    /* Execute example on PRU */
    printf("\tINFO: Executing example.\r\n");
    prussdrv_exec_program (PRU_NUM, "./adc.bin");
 
    prussdrv_map_prumem(PRUSS0_PRU0_DATARAM, &pruDataMem);
    /*assign data RAM addresses to two pointers*/
    pruDataMem_int0 = (unsigned int*)(pruDataMem + OFFSET_MEM0      ); /*AM33XX_DATA 8KB RAM1,Glocal Memory Address*/
    pruDataMem_int1 = (unsigned int*)(pruDataMem + OFFSET_MEM1      ); /*AM33XX_DATA 8KB RAM0,Glocal Memory Address*/
    sharedMem_int   = (unsigned int*)(pruDataMem + OFFSET_SHAREDRAM );
     
    /* endless loop*/
    while (1)
    {
        int j;
        unsigned int sample;
        unsigned int startstep = 0x10;
 
        /* Wait PRU0 interrupt */
        prussdrv_pru_wait_event (PRU_EVTOUT_0);
        /* clear event */
        prussdrv_pru_clear_event (PRU_EVTOUT_0, PRU0_ARM_INTERRUPT);
        printf("sharedMem_int[0]=%8x , sharedMem_int[1]=%8x , ", sharedMem_int[0],sharedMem_int[1]);
 
    currentBuffer_int = (sharedMem_int[1])?pruDataMem_int0:pruDataMem_int1;
    printf ("%8x , %8x\n", currentBuffer_int[0], currentBuffer_int[1]);
        if (startstep == 0x10)startstep = (currentBuffer_int[0] & 0xf000) >> 12;
        for (j = 0; j < SIZE_OF_BUFFER/sizeof(unsigned int) ; j++)
        {
            sample = currentBuffer_int[j];
            if <sup id="footnote_plugin_tooltip_1" class="footnote_plugin_tooltip_text" onclick="footnote_moveToAnchor('footnote_plugin_reference_1');">1)</sup><span class="footnote_tooltip" id="footnote_plugin_tooltip_text_1">(sample & 0xf000) >> 12) == startstep)
            {
                if (i == 2048) break;
                fprintf(pfile, "\n%d", (sample >> 16) >> 1);
                i++;
            }
            fprintf(pfile, "\t%d", sample & 0xfff);
        }
        if (i == 2048){
            break;
        }
    }
    fprintf(pfile, "\n");
    fclose(pfile);/*close the output file*/
    printf ("sharedMem_int[3]=%8x\n", sharedMem_int[3]);
    sharedMem_int[2] = 0x00000001;/*turn off the ADC*/
    while (sharedMem_int[3] == 0x00)/*wait untile Command has been executed by PRU0.*/
    {
        usleep(1000);
    }
    printf ("sharedMem_int[3]=%8x\n", sharedMem_int[3]);
 
    /* Disable PRU and close memory mapping. */
    prussdrv_pru_disable(PRU_NUM);
    prussdrv_exit ();
    return(0);
}
 
 
</span>

ビルド済のソースコード一式は以下を格納しておく。
adc_demo.tar.gz

am335x_pru_packageを~/pru配下に展開してあると仮定し、~/pru/am335x_pru_package/pru_sw/にadc_demoを展開する。

adc_demo側のコンパイル手順は以下。

debian@beaglebone:~$ cd ./pru/am335x_pru_package/pru_sw/adc_demo debian@beaglebone:~/pru/am335x_pru_package/pru_sw/adc_demo$ export CROSS_COMPILE= debian@beaglebone:~/pru/am335x_pru_package/pru_sw/adc_demo$ make clean debian@beaglebone:~/pru/am335x_pru_package/pru_sw/adc_demo$ make debian@beaglebone:~/pru/am335x_pru_package/pru_sw/adc_demo$ cp ./bin/adc.bin ./adc/obj/ debian@beaglebone:~/pru/am335x_pru_package/pru_sw/adc_demo$ cd ./adc/obj/ debian@beaglebone:~/pru/am335x_pru_package/pru_sw/adc_demo/adc/obj$ gcc adc.o -L../../../app_loader/lib/ -lprussdrv -lpthread -o mytest.out

本デモではレジスタ設定を簡略化するため、BBBのADCの設定を流用する。

debian@beaglebone:~$ sudo -s root@beaglebone:/home/debian# export SLOTS=/sys/devices/bone_capemgr.*/slots root@beaglebone:/home/debian# echo cape-bone-iio > $SLOTS root@beaglebone:/home/debian# echo BB-BONE-PRU > $SLOTS root@beaglebone:/home/debian# cat $SLOTS 0: 54:PF--- 1: 55:PF--- 2: 56:PF--- 3: 57:PF--- 4: ff:P-O-L Bone-LT-eMMC-2G,00A0,Texas Instrument,BB-BONE-EMMC-2G 5: ff:P-O-- Bone-Black-HDMI,00A0,Texas Instrument,BB-BONELT-HDMI 6: ff:P-O-- Bone-Black-HDMIN,00A0,Texas Instrument,BB-BONELT-HDMIN 7: ff:P-O-L Override Board Name,00A0,Override Manuf,cape-bone-iio 8: ff:P-O-L Override Board Name,00A0,Override Manuf,BB-BONE-PRU root@beaglebone:/home/debian#exit debian@beaglebone:~$

adc_demoではBBBのADC関連レジスタをPRU側から書き換えてしまうので、以下等のADC関連のインターフェースを用いると衝突がおきるので操作してはならない。

/sys/bus/iio/devices/iio\:device*/in_voltage*_raw
/sys/devices/ocp.*/helper.*/AIN*

以下のように実行すると実行ファイルmytest.outと同じディレクトリにout.txtというサンプリング結果出力ファイルが生成される。

debian@beaglebone:~/pru/am335x_pru_package/pru_sw/adc_demo/adc/obj$ sudo ./mytest.out out.txt INFO: Executing example. sharedMem_int[0]= 1 , sharedMem_int[1]= 2000 , 7ac , 11838 sharedMem_int[0]= 2 , sharedMem_int[1]= 0 , 80007af , 8011830 sharedMem_int[3]= 0 sharedMem_int[3]= 1 debian@beaglebone:~/pru/am335x_pru_package/pru_sw/adc_demo/adc/obj$

root権限が必要であることに注意。
適当な入力信号があるのならば、AIN0及びAIN1に入力しておけば、約50KHzサンプリングで2ch 2048ポイントがキャプチャされる。
約50KHzと書いているのはイマイチサンプリング周波数計算に自信がないからだ。
暇なときに検証しようと思うが今は放置する。

サンプリング結果出力ファイルの書式は、gnuplotで取扱いやすいように以下のようになっている。
サンプル番号\tAIN0 ADC結果\tAIN1 ADC結果\n
…以後2046行続く…

以下のようにgnuplotを用いてサンプリング結果をpng画像ファイルに変換することができる。

debian@beaglebone:~/pru/am335x_pru_package/pru_sw/adc_demo/adc/obj$ cat ../plot.cmd
set terminal png
set output 'out.png'
plot 'out.txt' using 1:3 with points, 'out.txt' using 1:2 with points

debian@beaglebone:~/pru/am335x_pru_package/pru_sw/adc_demo/adc/obj$ gnuplot ../plot.cmd
debian@beaglebone:~/pru/am335x_pru_package/pru_sw/adc_demo/adc/obj$

gnuplotのインストールは以下の通り。

debian@beaglebone:~$ sudo apt-get install gnuplot_nox

以上で一応BBBのADCをPRUから制御できる。
ただし適当な入力信号が無いと動作しているかが判らないので、SIN-waveを生成しUSB-DACから出力を行いADCに対してループバックを行う。
LinuxのUser-Land上で動作するSIN-wave生成 Applicationのソースコードは以下。

/*
tonegen.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h> 
#include <time.h>
#include <alsa/asoundlib.h>
 
#define BUF_SAMPLES   4096
#define SAMPLE_RATE   44100/*samples per second*/
#define NUM_CHNNELS   2/*1: mono , 2: stereo */
#define DEFAULT_FREQ1 100.0/*Hz for left*/
#define DEFAULT_FREQ2 200.0/*Hz for right*/
#define PCM_LATENCY   50000/*usec*/
#define OUTPUT_GAIN   4096.0
 
/* this function makes the look-up table to generate sin-wave */
void makeLUT(int16_t * sin_table)
{
    double deg, rad;
    int i;
 
    if (sin_table == NULL) return;
    for (i=0; i < SAMPLE_RATE; i++)
    {
        deg = (double)i / (double)SAMPLE_RATE * 360.0;
        rad = deg / 180.0 * M_PI;
        sin_table[i] = (short)(sin(rad) * OUTPUT_GAIN);
    }
    return;
}
 
/* this function generates the PCM data of sin-wave with look-up table and given frequency value*/
int16_t sinWaveGen(int16_t *sin_table, double freq, double *p)
{
    int16_t ret;
 
    if (sin_table == NULL || p == NULL) return 0;
    ret = sin_table[(int)*p];
    *p += freq;
    if (*p > (double)SAMPLE_RATE) *p -= (double)SAMPLE_RATE;
    return ret;
}
 
int main(int argc, char *argv[])
{
    unsigned int soft_resample = 1;
    const static unsigned int latency = PCM_LATENCY;
    static snd_pcm_format_t format = SND_PCM_FORMAT_S16;
 
    int16_t *pbuf = NULL;
    int16_t *pLUT = NULL;
    int bufSize = BUF_SAMPLES;
    int result, n;
    snd_pcm_sframes_t frames;
 
    snd_pcm_t *hndl = NULL;
    double freq1 = DEFAULT_FREQ1;
    double freq2 = DEFAULT_FREQ2;
    double p1 = 0.0;
    double p2 = 0.0;
 
    do
    {
        if(argc < 2)/*2nd argc is device name then it is mandatory.*/
        {
            printf("no input device name (ex. hw:1)\n");
            break;
        }
        if(argc >= 3)/*3rd argc is frequency value for left tone. It is option.*/
        {
            freq1 = atof(argv[2]);
        }
        if(argc >= 4)/*4th argc is frequency value for right tone. It is option.*/
        {
            freq2 = atof(argv[3]);
        }
        pbuf = malloc(BUF_SAMPLES * NUM_CHNNELS * sizeof(int16_t<script type="text/javascript"> jQuery("#footnote_plugin_tooltip_1").tooltip({      tip: "#footnote_plugin_tooltip_text_1",     tipClass: "footnote_tooltip",       effect: "fade",     fadeOutSpeed: 100,      predelay: 400,      position: "top right",      relative: true,     offset: [10, 10]    });</script>;
        if(pbuf == NULL)
        {
            printf("Unable to allocate pbuf\n");
            break;
        }
        /* we allocate memory used for the look-up table to generate sin-wave. */
        /* this table is very large, so it is not good manner. */
        /* please note that this program is just sample. */
        pLUT = malloc(SAMPLE_RATE * sizeof(int16_t));
        if(pLUT == NULL)
        {
            printf("Unable to allocate pLUT\n");
            break;
        }
        /*We make the look-up table to generate sin-wave.*/
        makeLUT(pLUT);
        /*we open the pcm stream on indicated device.*/
        result = snd_pcm_open(&hndl, argv[1], SND_PCM_STREAM_PLAYBACK, 0);
        if(result != 0)
        {
            printf( "Unable to open PCM device\n" );
            break;
        }
        /*we set PCM params.*/
        result = snd_pcm_set_params( hndl, format, SND_PCM_ACCESS_RW_INTERLEAVED,
                              NUM_CHNNELS, SAMPLE_RATE, soft_resample, latency);
        if(result != 0)
        {
            printf( "Unable to set PCM format\n" );
            break;
        }
#if NUM_CHNNELS == 2
        printf("Left freq = %f , Right freq = %f\n", freq1 , freq2); 
#else
        printf("freq = %f\n", freq1); 
#endif
        while (1)/*endless loop*/
        {
            /*we get the avail value that means empty spaces of alsa internal buffer.*/
            frames = snd_pcm_avail_update(hndl);
            if (frames > 0){
                bufSize = (frames > BUF_SAMPLES)? BUF_SAMPLES : frames;
                /* we make PCM sampling data that forms sin-wave. */
                for (n = 0; n < bufSize * NUM_CHNNELS; n += NUM_CHNNELS)
                {
                    pbuf[n]   = sinWaveGen(pLUT, freq1, &p1);
#if NUM_CHNNELS == 2
                    pbuf[n+1] = sinWaveGen(pLUT, freq2, &p2);
#endif
                }
                /*we write PCM sampling data to PCM stream to reproduce sin-wave.*/
                frames = snd_pcm_writei(hndl, (const void*)pbuf, bufSize);
                if (frames >= 0)
                {
                    if (frames < bufSize)
                    {
                        /* since number of bufSize are less or equal than number of avail value, */
                        /* so i think that all of witten data shall be success.*/
                        printf("There are GAP data. bufSize=%d , frames=%d\n", bufSize ,frames);
                    }
                }
                else if (frames < 0)
                {
                    if( snd_pcm_recover(hndl, result, 0 ) < 0 )
                    {
                        printf( "snd_pcm_recover err. frames=%d\n" , frames);
                        break;
                    }
                }
            }
            else if (frames < 0)
            {
                printf( "snd_pcm_avail_update err. frames=%d\n.", frames );
                break;
            }
            /* we have done to write some data to PCM stream. */
            /* then we need to sleep to make idle time till PCM data has been consumed.*/
            usleep(10000);
        }
        snd_pcm_drain(hndl);
    } 
    while(0);
    if (hndl != NULL)
    {
        snd_pcm_close(hndl);
    }
    if (pLUT != NULL)
    {
        free(pLUT);
    }
    if (pbuf != NULL)
    {
        free(pbuf);
    }
    return 0;
}

このアプリケーション tonegenはlibasound2が必要となるのでインストールを行う。

debian@beaglebone:~$ sudo apt-get install libasound2-dev libesd0

コンパイル手順は以下。

debian@beaglebone:~$ cd ~/pru/am335x_pru_package/pru_sw/tonegen debian@beaglebone:~/pru/am335x_pru_package/pru_sw/adc_demo/tonegen$ gcc -lasound -lm tonegen.c -o tonegen debian@beaglebone:~/pru/am335x_pru_package/pru_sw/adc_demo/tonegen$

以下のようにデバイスを引数に実行する。
実行中はシェルに戻って来ないのでsshの別窓から実行する。
当然USB-DACが認識されていることが前提となる。

debian@beaglebone:~/pru/am335x_pru_package/pru_sw/adc_demo/tonegen$ ./tonegen hw:1 Left freq = 100.000000 , Right freq = 200.000000 snd_pcm_avail_update err. frames=-32

USB-DACのデバイス番号の取得方法はこの記事を参照のこと。
AKI.DAC-U2704をBeagleBone Blackで鳴らす

このサンプルアプリの実行によりUSB-DACの左チャネルから100Hz、右チャネルから200Hzのサインウエーブが出力される。

tonegenはALSAを用いてUSB-DAC等から信号波形を生成するサンプルプログラムで、makeLUT()関数を適当に弄ると色々な波形を生成することができ重宝している。

tonegenの出力をADCに折り返す為には0V~1.8Vの範囲でのスイングに収まることを保証する必要がある。
これを実現するために、USB-DAC出力とADCの入力の間に以下の簡単な外付け回路を挿入する。
回路図は以下。


部品リストは以下。

記号型番仕様/諸元等定数等数量入手先
R1, R2金属皮膜抵抗(1/4W)470Ω 1%2秋月電子
R3~R6アキシャルリードカーボン抵抗(1/4W)2.2KΩ4秋月電子
R7, R8アキシャルリードカーボン抵抗(1/4W)1KΩ2秋月電子
C1導電性高分子アルミ固体電解コンデンサ330μF1秋月電子
C2ラジアルリードセラミックコンデンサ0.1μF1秋月電子
C3, C4両極性ケミカルコンデンサ1μF2秋月電子
U1TI OPA2314単電源、Rail-to-Rail汎用オペアンプ1RSオンライン
J1, J23.5mmステレオミニジャックDIP化キット2秋月電子
SOP8(1.27mm)DIP変換基板 金フラッシュ1秋月電子
細ピンヘッダ1X20 (DIP変換基板用に1X4を二つ)1秋月電子
      

今回この回路をブレッドボード上に実装した。
意味的にはオペアンプを用いたゲイン等倍の反転増幅回路であり、HPFによりDCカットした入力信号に対し抵抗分圧で作成した0.9Vでオフセットを与る。
この結果0V基準の入力信号を0.9V基準にレベルシフトさせる。
使用しているオペアンプは外付け部品を少なくできるのでTI OPA2314としている。
OPA2314はSOP 8ピンパッケージなのでブレッドボード上で使用するためにはDIP変換基板を用いる。
このオペアンプには1番ピンを示すマークがなく、パッケージの断面の角度の違いから1~4番ピン側か5~8番ピン側かを見分ける必要がある。
C2はラジアルリードセラミックコンデンサを変換基板上に実装するとブレッドボード上の配線取り回しを簡略化できる。

このオペアンプは1.8Vの単電源でrail-to-railで動作し、電源をBBBのVDD_ADCとすることで出力が0V~1.8Vであることを保証する。
ただし余裕が無い為、簡単に発振してしまうようだ。
C1は電源を切るときVDD_ADCへ電圧が残ってしまうことを嫌うため、できるだけ小さくしたいが小さくしすぎると発振してしまうので330μFとしている。
VDD_ADCの許容供給電流の上限は判らなかった。
しかし、VDD_ADCの元であるBBBの1.8V railはHDMIデバイス等の電源になっているので、HDMIが動作していない状態ならば少なくとも数十mA程度は許容されるはずと仮定している。
R3,R4は主にオペアンプへの電流制限を目的として定数決定をしている。
C3,R3及びC4,R4はHPFのカットオフ周波数を決めるが、DCカットのみが目的なので数Hz以下になれば問題ない。
C3,C4はケミコンを用いるならば、両極性である必要がある。

BBBとの結線は以下の通り。

VDD_ADC: P9.32 
GND_ADC: P9.34 
AIN0: P9.39 
AIN1: P9.40

この回路では3.5φステレオミニジャックの入力側(J1)から出力側(J2)に位相反転した信号が出力される。
先に述べたサインウエーブの生成を行うtonegenのUSB-DAC出力をこの回路に入力すると出力側に音として出力されるのでヘッドホンで簡単に動作確認ができる。

この回路をtonegenのUSB-DAC出力とBBBのADC入力端子間に挿入しループバックパスとする。
次のグラフは実行例。

縦軸はADCの変換結果値、横軸は時間順のサンプル番号、緑はAIN0からの入力を表し赤はAIN1を表す。
100Hzと200Hzの波形を見る事ができる。
ノイズが多く全体的にドリフトしていて波形も変形しているが一応意図した結果が得られているので、デモレベルとしてはまぁこんなものかと考えている。

尚、50KHzサンプリング 2ch inputはBBBの限界ではない。
限界は2ch inputの場合300KHz台, 1ch inputの場合は700KHz台と思われる。
2ch inputでの最高速度の設定はadc.pの以下の部分を変えると試す事ができる。

…
// MOV r0, 214 //
MOV r0, 0 //
// SBBO r0, r13, OFFSET_STEPDELAY2, 4 // *(TSC_ADC_SS_BASE + OFFSET_STEPDELAY2) = 214
SBBO r0, r13, OFFSET_STEPDELAY2, 4 // *(TSC_ADC_SS_BASE + OFFSET_STEPDELAY2) = 0
…

残念ながらこの変更だけでは期待通りに動作しない。
サンプリングレートを上げてゆくとLinuxのUser-Landでのサンプリングデータの刈り取りが間に合わなくなってゆくからだ。
PRUとLinuxは非同期であるので、検討すべき事項が増えてゆき難易度があがってゆく。
また、BBBはADCを一つしか持たないので、複数のポートを同じタイミングでサンプリングできない問題もある。
つまり複数ポートをサポートした場合、サンプリングタイミングはポート毎に少しズレるわけだ。
サンプリングレートを早くした場合、このタイミング差が無視できなくなってくると思われる。
ポート間のサンプリングタイミングの差をデータ処理の前提とする必要があることに注意が必要。

このデモにより、BBBで外付け部品を追加することなく12bit 50KHzサンプリング 2ch inputのADC変換が可能であることを示すことができた。
色々問題はあるが、簡単なサンプルプログラムでBBBのADCのContinuous modeでの逐次変換を行うことができるわけだ。
12bit 50KHzでのサンプリングは音のキャプチャとしては十分であるので、音関連のおもちゃや実験の材料として有用なのではないだろうか。
この記事が誰かの役にたつ事を祈りつつ。

タイトルとURLをコピーしました