This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

AM5728: Need precise gpio timing (within 1 microsecond) on dedicated code running on the ipu

Part Number: AM5728
Other Parts Discussed in Thread: SYSBIOS

Hello,

We have a requirement of toggling a gpio line with precise timing. For example, set a gpio high, wait for at least 9 microseconds but less than 11 microsecond, then set the gpio low.

We're running the code on ipu1 and there's nothing else running on that ipu. (There is code running on both dsp's and the main cpu)

I'm watching the output using a oscilloscope with 10 nanosecond resolution.

I see the output is low for as short as 6.12 microseconds and as long as 8.72 microseconds. This is outside the requirements.


We're using code based on ipc_3_47_02_00/examples/DRA7XX_linux_elf/ex02_messageq/ipu1/Server.c

I've stripped the gpio toggling code down to the basics (see below)

I'm using eCap free running timer (PWMSS_ECAP_TSCNT) for delay loops. Nothing other code on the system is using or accessing that register.


The performance of this code depends on the load on the main cpu, even with interrupts disabled. I would expect the timing to be basically independent of the rest of the system.

Is there something wrong with this code?
What can I check / change to achive reliable waveform timing? (The particular protocol isn't standard and isn't available in the chip)


Thank you,
Scott





Note: the register addresses start with "0x6" instead of the usual "0x4" because the code is running on the ipu.

    volatile U32 * const PWMSS_ECAP_TSCNT  = (U32 *) 0x68442100;
    volatile U16 * const PWMSS_ECAP_ECCTL2 = (U16 *) 0x6844212A;

    volatile U32 * const CTRL_CORE_PAD_GPMC_A5 = (U32 *) 0x6A003454;
    volatile U32 * const CTRL_CORE_PAD_GPMC_A6 = (U32 *) 0x6A003458;

    volatile U32 * const gpio1OE  = (U32 *) 0x6AE10134;
    volatile U32 * const gpio1Set = (U32 *) 0x6AE10194;
    volatile U32 * const gpio1Clr = (U32 *) 0x6AE10190;

    const U32 countusec = 128;
    const int delay1 = 3 * countusec;
    const int delay2 = 6 * countusec;


    *PWMSS_ECAP_ECCTL2 = (1<<4);    // set TSCNTSTP free running TSCNT


    asm(" CPSID FI\n");

    unsigned loop;
    for ( loop = 0; loop < 16; ++loop )
    {
        *gpio1Clr = (1<<27);    // clear gpio1_27

        *PWMSS_ECAP_TSCNT = 0;
        while ( *PWMSS_ECAP_TSCNT < delay1 )
        {}


        *gpio1Set = (1<<27);    // set gpio1_27

        *PWMSS_ECAP_TSCNT = 0;
        while ( *PWMSS_ECAP_TSCNT < delay2 )
        {}
    }

    asm(" CPSIE FI\n");

  • M4 doesn't provide precise timing when accessing registers and fetching instruction/data (from DDR).

    If you can use interrupt to M4 every 9 microseconds, instead of CPU reading register, the latency may be more consistent.

    GP timer is running on 20Mhz clock so you can use GP timer to generate interrupt in 9 us intervals and M4 will toggle gpio line in the ISR. 

    However, if this still doesn't meet the required precision, you will have to look into the usage of PRU.

    You can refer to the below application notes for how to program PRU.

    https://www.ti.com/lit/pdf/sprach5

    https://www.ti.com/lit/pdf/sprace9

    Regards,
    Stanley

  • Hello Stanley,

    We need pulses with different widths, including 8, 9, 17, 28, 30, and 43 microseconds.

    Which GP timer are you referring to? Does it have the resolution needed for a 1 shot timer with the above durations?

    Can the PRU access any gpio pin?

    Thanks,

    Scott

  • Please refer to Chapter 22 Timers and Chapter 30 PRU-ICSS in the TRM.

    https://www.ti.com/lit/ug/spruhz6l/spruhz6l.pdf

    You can reprogram GP Timer to trigger interrupt with different durations. PRU-ICSS has dedicated GPIO pins.

  • Hello Stanley,

    What is the expected interrupt latency using a GP timer? If code running on an ipu is sometimes delayed by 5+ microseconds, I expect the interrupt latency could have the same delay.

    Thanks,

    Scott

  • Hi Scott,

    I don't have the benchmark number for the interrupt latency.

    However, the point is about consistency.

    If you put the ISR code in the IPU internal memory, the interrupt latency + ISR execution should be consistent.

    The only variant would be the time for CPU to update GPIO register to toggle the line.

    You can then measure the time variant with this approach and see if it is within the precision you need.

    Regards,
    Stanley

  • Hello Stanley,

    I was trying to use the Timer_* APIs to provided. (The code I’m using is below. Documentation in docs/cdoc/ti/sysbios/timers/gptimer/Timer.html.) I expect the APIs end up using the exact same registers as direct register access code, but maybe the APIs have other constraints that cause problems.

     On my oscilloscope, I’ve captured two different runs. The only thing running on the system is basic / minimal code. (Kernel, ssh on the console, basic networking, etc.) Our main application is not running.

     With this code, I would expect 8 cycles with basically 16 microseconds each and basically 50% duty cycle. I saw that occasionally, but usually the result had obvious problems just looking at the oscilloscope.

     The timing in the first capture is bad enough, but the second capture is much worse. Most of the intervals are 6-10 microseconds. Some are 10-23 microseconds. The 2 long intervals are 877 and 918 milliseconds (yes, each almost a full second)

    Thanks,

    Scott

    ================================
    
    #define GPIO_BASE 0x68050000 // ipu 0x68050000 is mpu 0x48050000
    #define BIT7    (1<<7)
    #define BIT24 (1<<24)
    #define BIT26 (1<<26)
    #define BIT29 (1<<29)
    #define GPIO2 0x5000 #define GPIO6 0xD000 #define GPIO_CLEARDATAOUT 0x0190 #define GPIO_SETDATAOUT 0x0194 static Timer_Handle gTimerHandle = NULL; static volatile unsigned gTestTimerCount = 0; void TimerTestHandler( UArg unused ) { volatile U32 * const GPIO2SET = (U32 *) ( GPIO_BASE + GPIO2 + GPIO_SETDATAOUT ); // ipu 0x68055194 mpu 0x48055194 volatile U32 * const GPIO2CLR = (U32 *) ( GPIO_BASE + GPIO2 + GPIO_CLEARDATAOUT ); // 0x68055190 0x48055190 volatile U32 * const GPIO6SET = (U32 *) ( GPIO_BASE + GPIO6 + GPIO_SETDATAOUT ); // 0x6805D194 0x4805D194 volatile U32 * const GPIO6CLR = (U32 *) ( GPIO_BASE + GPIO6 + GPIO_CLEARDATAOUT ); // 0x6805D190 0x4805D190 if ( ++gTestTimerCount >= 32 ) Timer_stop( gTimerHandle ); if ( gTestTimerCount & 1 ) { *GPIO6SET = BIT7; // gpio6_7 *GPIO2SET = BIT29 | BIT26 | BIT24; // gpio2_29, gpio2_26, and gpio2_24 } else { *GPIO6CLR = BIT7; *GPIO2CLR = BIT29 | BIT26 | BIT24; } } void TimerTest( void ) { Timer_Params timerParams; Timer_Params_init( &timerParams ); timerParams.arg = 1; timerParams.period = 250; timerParams.periodType = Timer_PeriodType_MICROSECS; timerParams.runMode = Timer_RunMode_CONTINUOUS; timerParams.startMode = Timer_StartMode_USER; if ( !gTimerHandle ) { gTimerHandle = Timer_create( Timer_ANY, &TimerTestHandler, &timerParams, NULL ); } if ( gTimerHandle ) { Timer_stop( gTimerHandle ); Timer_setPeriodMicroSecs( gTimerHandle, 8 ); gTestTimerCount = 0; Timer_start( gTimerHandle ); } } ================================

  • Hello Stanley,

    I finally got the raw interrupt handler working. (Based on the code example packages\ti\csl\example\timer\timer_app\main_m4.c)

    The global variable:
       static volatile uint32_t gCntValue;
    is initialized to 16 before the timer is started, so there should be 8 full cycles.

    I know the interrupt handler is working because gCntValue is only decremented in the handler and the gpio lines are only manipulated in the handler. The interrupt vector points to this routine directly - no intermediate code. The routine is small enough there should be no cache issues. Also, there are no routine calls. All the register accesses are inline.

    I tried setting the period to 100 microseconds (TLDR 0xFFFFF82F or -2001) and 10 microseconds (TLDR 0xFFFFFF37 or -201). Neither is close to the requested interval time. However, even if it was the correct time the actual period and duty cycle are still wildly erratic

    The timer is initialized only for an interrupt on overflow. I tried using timer4 (like the example) and also timer10. The result was the same. For both timers, CM_L4PER_TIMER4_CLKCTRL and CM_L4PER_TIMER10_CLKCTRL were 0x00030000, which indicates the timer wasn't enabled and nothing else is accessing the timer.

    See the interrupt handler code (below) and the oscilloscope captures.

    Do you have any suggestions?

    Are the general purpose timers supposed to have reliable periodic interrupts? (Even at 1 millisecond intervals the actual period is erratic.)

    Thanks,
    Scott

    ====================

    void TimerIsr( void *handle )
    {
       volatile U32 * const irqStatus = (U32 *) ( MY_TIMER_BASE + TIMER_IRQSTATUS );
       *irqStatus = TIMER_INT_OVF_IT_FLAG;
    
       if ( gCntValue )
          --gCntValue;
    
       if ( gCntValue & 1 )
       {
          volatile U32 * const GPIO2SET = (U32 *) ( GPIO_BASE + GPIO2 + GPIO_SETDATAOUT );
    
          *GPIO2SET = BIT29 | BIT26 | BIT24;  // gpio2_29, gpio2_26, and gpio2_24
       }
       else
       {
          volatile U32 * const GPIO2CLR = (U32 *) ( GPIO_BASE + GPIO2 + GPIO_CLEARDATAOUT );
    
          *GPIO2CLR = BIT29 | BIT26 | BIT24;
       }
    }

    ====================

    Timer 4, requested 100 microsecond interval

    Timer 4, requested 10 microsecond interval

    Timer 10, 100 microsecond

    Timer 10, 10 microsecond

  • What is the timer clock source? It set by CM_L4PER_TIMER4_CLKCTRL.CLKSEL.

    By default, it is set to SYS_CLK1. If that's the case, what is the clock frequency of SYS_CLK1 on your design?

    Are you sure there is nothing else in your system which may idle the timer functional clock, for example, Linux power management running on A15?

    Another possibility is IPU power management... If M4 goes to Idle, you have to enable wakeup for interrupt to be serviced.

    Did you program IPU WUGEN_MEVTx register to enable wakeup for the IRQnum which is tied to timer interrupt?

    Regards,

    Stanley

  • Hi Stanley,

     

    I checked the timer clock source. CM_L4PER_TIMER4_CLKCTRL is 0x00030000, so it is SYS_CLK1 which is 20 MHz. (xi_osc0 is connected to a 20 MHz clock input) The issue isn't the timing. The problem is the interrupt from a general purpose timer is erratic and unreliable.

     

    There shouldn't be anything on the system to idle the timer functional clock. The A15 itself is busy. I tried the same IPU code with our main application running. No difference in the gpio output.

     

    The M4 isn't idle. The task started the timer and is in a CPU loop waiting for a global variable to reach zero. (i.e. “while ( variable );” )

     

    The code isn't currently changing IPU WUGEN_MEVTx. Accessing 0x5508100C or 0x55081010 from the IPU code crashes the IPU. This appears intentional. The file \ipc_3_47_02_00\examples\DRA7XX_linux_elf\ex02_messageq\ipu1\IpuAmmu.cfg uses the MMU to map 16K starting at 0x55080000 to 0x40000000.

    This is confirmed from the command line:

    devmem2 0x58880924 returns 0x40000000

    devmem2 0x588809A4 returns 0x55080000

    devmem2 0x58880A24 returns 0x00000003

     

    I tried accessing the IPU_WUGEN registers. (Normally 0x5508???? but mapped to 0x4000???) Reading 0x40001000, 0x40001004, 0x40001008, 0x4000100C, and 0x40001010 give the expected values. However, writing to those locations have no effect. They are apparently read-only.

     

     

    Is there any example IPU code for initializing the interrupts for a general timer? (Code from packages\ti\csl\example\timer\timer_app\main_m4.c crashes on the IPU)

    What about example code for using what appears to be IPU specific timers? (IPUx_UNICACHE_SCTM)

     

     

    Thanks,

    Scott

  • If IPU clock never goes into idle, then you don't have to worry about WUGEN registers to wake up IPU clock.

    WUGEN registers are accessed thru 0x5508xxxx address since it is internal IPU bus.

    We usually don't remap this in AMMU.

  • Hello,

    Quick update from customer on this issue.

    Customer was able to get consistent timing using interrupts from the IPU timers IPUx_UNICACHE_SCTM in a non-BIOS context.

    The application was written and compiled with SYSBIOS, but for timing critical code, Task_disable() is called to halt the scheduler to enable deterministic opperation of the IPU.

    BIOS is enabled afterwards for standard IPC with the main A15 core. Since this seems to be a workable solution for the customer OK to close out this thread.

    Thanks!

    Munan