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.

AM625: Checking PRU interrupts through R31

Part Number: AM625

Tool/software:

Hi all,

From the AM62 TRM it seems that there is a possible to check for some interrupts of the PRU directly on bits 30 or 31 of __R31.

In particular, I'd like to be able to check for pending the ECAP APCM CMPEQ_FLG (COmpare Equal Status Flag) as fast as possible, which would be viable if I could at least get an indication of this interrupt flag on R31[30] or R31[31].

* Is it, at all, possible to configure the PRU interrupt controller so that one of the R31 bits is able to give an indication of a pending interrupt?

* If so, what would be procedure to configure the interrupt controller in such a way? I'm happy to follow any documentation or pointers.

Thank you!

António

  • After quite a bit of reading I think I found how to do this but, except how to tell the event ID that matches each interrupt of the PRUSS.

    From 7.4.6.1.1:

    All system events are interrupt inputs, with events 0 through 31 being internal for PRUSS sources. Does this mean that an interrupt flag can be used as an event that can be mapped to a host interrupt and ultimately be exposed through R31[31,30]?

    • Does the ECINT_EN_FLG.CMPEQ_FLG interrupt (14.8.5.2.8.1) have a matching event id that allows it to be routed to R31?
    • If yes, where can I find the mapping between peripheral interrupts and system event IDs?

    Assuming that this interrupt has a matching event ID that we can map to a host interrupt, using the AM62 TRM (rev. B) I think this is the workflow to route it, using the information from 7.4.6.1.2.3:

    1. Find the matching CH_MAP_REGi (14.3.5.1.61) and set the CH_MAP bits to 0b000, so that this interrupt is mapped to channel 0
    2. In HINT_MAP_REG0 (14.3.5.1.77.1) set HINT_MAP0 bits to 0b0000, so that channel 0 maps to host channel 0
    3. In ENABLE_SET_INDEX_REG (14.3.5.1.48.1), write 0x1, to enable interrupt channel 0
    4. In HINT_ENABLE_SET_INDEX_REG (14.3.5.1.50.1), write 0x1, to enable the host channel 0
    5. In GLOBAL_ENABLE_HINT_REG (14.3.5.1.44.1), write 0x1, to enable any host interrupt.

    Would this be the correct procedure to expose the interrupt on R31?

    A Little Background on this Question:

    I am writing a firmware that generates a stable CLOCK signal while driving other signals simultaneously. I want this clock to be as jitter free as possible, therefore I configured the ECAP as PWM and set an interval that yields the desired output frequency. The most naïve firmware implementation, loops waiting for ECEINT_EN_FLG.CMPEQ_FLG, sends the clock pulse, clears the interrupt and returns to waiting for the interrupt.

    The issue with this approach is that checking the interrupt register (which is memory mapped), takes a few assembly instructions. I don't know PRU assembly, but I'd imagine something like:

    • load the register's address to register A
    • load the memory pointed to by register A into register B
    • branch if one bit of B is not set (back to the beginning)

    Depending on how soon or late the firmware arrives at this wait, it may need to iterate and there will be a varying mismatch between the actual trigger of the interrupt and it being detected and processed by firmware.

    Therefore this question,  because I know that quick branch instructions operate directly on PRU core registers and take a single cycle to execute. In this case, as long as the firmware reaches the waiting loop before the interrupt is triggered (very easy to ensure), the clock pulse will always have the same offset to the interrupt event, making the clock virtually jitter free.

    While the scope of my question is primarily about the interrupt routing mechanics and how to use them, if you have any suggestion to perfectly synchronize a software generated clock signal to the ECAP PWM counter, I'll be most happy to hear them.

    NB: I believe the PWM output of the PRU ECAP could be taken from the SOC and used directly as the clock signal. We are trying to avoid it because of the potential for loss of synchronization in case something very nasty happens that we couldn't predict in firmware. Any suggestion about this alternative is obviously also welcome.

    Thanks!

    António

  • Hi António,

    I have assigned your thread to our expert, please allow them some time to review your approach and provide their inputs. 

    Regards,

    Nitika

  • Hello António,

    Basic overview: for INTC theory
    the PRU Interrupt controller (INTC) has 3 parts: Host interrupts, interrupt channels, and system events (or interrupt sources). Please refer to the Technical Reference Manual (TRM) section "PRUSS Local INTC" for more details.

    All interrupts you want to go to the PRU must be routed to the Host interrupts 0 or 1, which show up as bits 30 and 31 in R31. Multiple system events can be routed to a single channel, and multiple channels can be routed to a single host interrupt.

    I'm going to link some resources:

    How to do PRU INTC configuration from Linux:
    https://software-dl.ti.com/processor-sdk-linux/esd/AM62X/10_01_10_04/exports/docs/common/PRU-ICSS/INTC_Configuration.html

    https://git.ti.com/cgit/pru-software-support-package/pru-software-support-package/tree/examples/am62x/PRU_Direct_Connect0/intc_map_0.h

    Directly setting the INTC registers from the PRU firmware:

    Refer to Lab 2 of the AM335x PRU Hands-on Labs. Concepts apply, details may be slightly different:
    https://software-dl.ti.com/processor-sdk-linux/esd/AM62X/10_01_10_04/exports/docs/common/PRU-ICSS/PRU-Hands-on-Labs.html#id25
    https://git.ti.com/cgit/pru-software-support-package/pru-software-support-package/tree/labs/Hands_on_Labs/lab_2/solution

    By the way: getting started with assembly code 

    I figured you already know about the current PRU Getting Started Labs, but just in case, we do have some guidance on writing mixed C and assembly code here:
    https://software-dl.ti.com/processor-sdk-linux/esd/AM62X/10_01_10_04/exports/docs/common/PRU-ICSS/PRU-Getting-Started-Labs_Lab2.html

    Design for generating a PWM signal? 

    I am not sure I understand your design (seems like the PWM isn't actually generating a waveform on its own, and instead you want to do a write from the PRU cores for every clock toggle?), so I will not comment much right now.

    If that was your plan, keep in mind that you can also just generate the waveform by directly writing to the PRU GPO signals (assuming you have the PRU clock cycles to do all your other computing within the needed pulse width window). You can see an example that we programmed that does this on AM335x here: https://git.ti.com/cgit/apps/tida01555/tree/PRU_ADS8688_Interface/PRU_ADS8688_Interface.asm
    (where the overall system context is here: https://www.ti.com/tool/TIDA-01555 > design guide)

    Regards,

    Nick

  • Hi Nick, thanks for the pointers.

    The labs link was very useful to cook my own equivalent solution for the AM62. Sadly it is not working yet.

    First things first: what is the ID of the event for ECAP interrupts and where to find it? I searched everywhere for this and found nothing. I suspect this might be event number 15, from the TRM of the AM335x but I couldn't just assume it and be happy, so here's one test I ran:

    int main()
    {
        CT_ECAP.ECCTL2_ECCTL1_bit.TSCNTSTP = 0;
        CT_ECAP.CAP1 = 61; // 333MHz/62 = 5.37MHz
        CT_ECAP.CAP2 = 31; // 50% Duty Cycle
        CT_ECAP.CNTPHS = 0;
        CT_ECAP.ECCTL2_ECCTL1_bit.CAP_APWM = 1;
        CT_ECAP.ECCTL2_ECCTL1_bit.APWMPOL  = 1;
        CT_ECAP.ECCTL2_ECCTL1_bit.SYNCI_EN = 0;
        CT_ECAP.ECCTL2_ECCTL1_bit.SYNCO_SEL = 0x3;
        CT_ECAP.ECCTL2_ECCTL1_bit.FREE    = 1; // run always, regardless of suspend
        CT_ECAP.ECFLG_ECEINT_bit.EN_PRDEQ = 1;
        CT_ECAP.ECFLG_ECEINT_bit.EN_CMPEQ = 0;
    
        __R31 = 0x00000000;
        CT_INTC.CH_MAP_REG3_bit.CH_MAP_15 = 0;
        CT_INTC.HINT_MAP_REG0_bit.HINT_MAP_0 = 0;
        CT_INTC.STATUS_CLR_INDEX_REG_bit.STATUS_CLR_INDEX = 15;
        CT_INTC.ENABLE_SET_INDEX_REG_bit.ENABLE_SET_INDEX = 15;
        CT_INTC.HINT_ENABLE_SET_INDEX_REG_bit.HINT_ENABLE_SET_INDEX = 0x1;
        CT_INTC.GLOBAL_ENABLE_HINT_REG_bit.ENABLE_HINT_ANY = 1;
    
    
        CT_ECAP.ECCTL2_ECCTL1_bit.TSCNTSTP = 1;
    
    
        while (1) *((uint32_t* volatile) 0x9c600000) = __R31;
    }

    This firmware was executed first thing after boot. Nothing else ran previously on the PRU unless otherwise noted.

    • If instead of setting TSCNTSTP to 1 I have it set to 0, RAW_STATUS_REG0 (0x30060200) contains the value 0x00000000
    • If I run the firmware with TSCNTSTP set to 1, RAW_STATUS_REG0 contains the value 0x00008000. This seems a solid indication that the ECAP interrupt is event 15. Can you confirm?

    Now for the parts:

    The code above seems like a direct adaptation of https://git.ti.com/cgit/pru-software-support-package/pru-software-support-package/tree/labs/Hands_on_Labs/lab_2/solution to the AM62, with the exception that I am routing the event to host channel 0. Yet, the value copied from R31 into a memory location reads 0 all the time.

    I also noticed that if I run the firmware above a second time after boot, the RAW_STATUS_REG0 remains at 0x00000000 on the second and all subsequent runs. In other words, only on the very first execution of the test do I see the RAW_STATUS_REG0 indicating an active event 15.  What am I missing?

    I have to admit that I have a lot of trouble following the PRU documentation on the AM62 TRM. For instance:

    * there is no list of event IDS supported by the INTC, with a clear description of their scope (which interrupt(s) cause said events to be triggered)

    * there is no clear, objective, procedural, if you'd like, explanation of how to map the interrupts. I know that the INTC section does mention how to perform this mapping, but for someone with no background on the PRU's internals, the meaning of those instructions is open for interpretation. When something doesn't work  as intended it becomes super frustrating to troubleshoot the issue.

    The documentation seems pretty explicit on the TRM regarding peripherals being event sources  and events being able to be routed into R31 bits 30 and 31. So, please help me understand how to:

    * find out which event corresponds to the PRU's ECAP PWM PERIOD interrupt flag;

    * find out how to map this event to Host Channel 0 of the INTC

    * find out how to clear this event after it was set

    If there's anything I can do to make this easier to understand, please let me know.

    How does generating a square wave fit in all this? Simply put, the firmware is supposed to wait for the ECAP interrupt. When the interrupt hits, the firmware generates a clock pulse (set H, set L shortly after) and returns to the waiting state. If the wait is executed with single cycle branch instructions (which is possible with register based checks), we are able to generate an output clock synchronous with the ECAP interval, with virtually zero jitter.

    Kind regards,

    António

  • Finally!!!

    I figured out how to accomplish this and why my first attempts failed.

    The main problem:

    • The core issue I was facing was clearing the interrupts in the incorrect sequence. It is absolutely crucial that the ECAP interrupt is cleared before clearing the event on INTC. If this sequence is not followed, it looks like either the PRU hangs or the INTC interrupt doesn't trigger again, (I didn't go far enough testing to find out what really happens).

    Roadblocks:

    • It may be just my opinion, but I find the PRU section of the AM62 TRM virtually impossible to follow. It seems to be machine generated and not undergone a single proof-reading pass, which would easily spot wording issues that could be addressed in the source materials:
    • The list of system events is missing on the AM62 TRM. I was lucky to find one on the AM33 PRU-ICSS, which turned out to also apply to the AM62. I also used the RAW flags to try and guess the indeces of the events....
    • It is not clear in the AM62 TRM if some registers operate on bitfields or integer indeces. The AM33 TRM is much clearer in this regard, and, still, the lab2 example in the CT_INTC.HIEISR |= (1 << 0); /*TODO: Enable proper event */ statement seems to imply a bit field, when, all my testing (and my interpretation of the TRM) indicates that this is an integer value that must be written.
    • The register descriptions on the AM62 TRM lack any description about their purpose. Only the fields are (machine?) documented in a way that is very hard to follow.

    Sorry for sounding negative, you guys probably have tons of stuff to juggle and support and it's not easy to keep up with so extensive and complex documentation, let alone fancy use cases that customers decide to try based on it... This feedback is my attempt to bring up some potential places for improvement, should the documentation be reviewed and updated at any point.

    That being said, the Hand-On-Labs examples were precious to navigate through these doubts and start to understand how things really work. Thanks for those pointers, again!

    After we start playing with it, the PRU is an actual fun peripheral to work with. It's realtime capabilities make it a really interesting option to consider for some tasks and it might even see more usage if getting used to it's features and documentation quirks wasn't such a steep hill.

    The Solution:

    For anyone potentially looking for clarification on INTC routing and handling on the AM62, here's the final solution:

    #include <stdint.h>
    #include <pru_cfg.h>
    #include <pru_ctrl.h>
    #include <pru_intc.h>
    #include <pru_ecap.h>
    
    volatile register uint32_t __R30;
    volatile register uint32_t __R31;
    
    #define BIT(n) (1u<<n)
    
    static inline void set_clk_l()
    {
        __R30 &= ~BIT(6); // modify as needed
    }
    
    static inline void set_clk_h()
    {
        __R30 |= BIT(6); // modify as needed
    }
    
    static inline int has_ecap_irq()
    {
        return __R31&BIT(30);
    }
    
    static inline void clear_ecap_irq()
    {
        /* The order **MATTERS!!!**
         * FIRST Clear IRQ on ECAP so that INTC doesn't see it active
         * when we go about clearing event 15 on the INTC.
         *
         * Not following this sequence somehow prevents R31.t30
         * interrupt (host 0) from triggering again
         */
        CT_ECAP.ECCLR = BIT(6) | BIT(0);
        CT_INTC.STATUS_CLR_INDEX_REG_bit.STATUS_CLR_INDEX = 15;
    }
    
    int main()
    {
        // Setup ECAP as PWM. Do not start it...
        CT_ECAP.ECCTL2_ECCTL1 = 0x02c0c000;
        CT_ECAP.CAP1 = 61; // 333MHz/62 = 5.37MHz
        CT_ECAP.CAP2 = 0; // Duty cycle is ignored.
        CT_ECAP.CNTPHS = 0;
        // Make sure IRQs
        // Make sure all ECAP interrupts are cleared, otherwise
        // clearing INTC event will **not** work.
        CT_ECAP.ECCLR = 0xff;
        CT_ECAP.ECFLG_ECEINT = BIT(6); // Enable Period interrupt
    
        // Setup INTC:
        // ECAP_PWM_PERIOD --> INTC --> R31.t30
        // PWM_PERIOD_INT (15) --> channel_0 --> host_channel_0 --> R31.t30
        __R31 = 0x00000000;
        // all registers below operate on indeces.
        // THEY ARE NOT BIT FIELDS!!!
        CT_INTC.CH_MAP_REG3_bit.CH_MAP_15 = 0;
        CT_INTC.HINT_MAP_REG0_bit.HINT_MAP_0 = 0;
        CT_INTC.STATUS_CLR_INDEX_REG_bit.STATUS_CLR_INDEX = 15;
        __delay_cycles(5);
        CT_INTC.ENABLE_SET_INDEX_REG_bit.ENABLE_SET_INDEX = 15;
        CT_INTC.HINT_ENABLE_SET_INDEX_REG_bit.HINT_ENABLE_SET_INDEX = 0;
        CT_INTC.GLOBAL_ENABLE_HINT_REG_bit.ENABLE_HINT_ANY = 1;
    
        // Start the ECAP counter, APWM node
        CT_ECAP.ECCTL2_ECCTL1 = 0x02d0c000;
    
        for (;;) {
    
            // single cycle-based wait for IRQ (asm: QBBC)
            while ( ! has_ecap_irq() )
                ;
    
            set_clk_h();
            clear_ecap_irq();
            __delay_cycles(5);
            set_clk_l();
            // Placeholder for custom code...
        }
    }

    In a nutshell, this code runs a single-cycle based wait for the ECAP interrupt (made possible by routing the interrupt to R31) and generates the CLK rising edge immediately after the interrupt is detected. The remaining time then becomes available to run custom application code.

    Potential Death Trap:

    If the loop takes too long to iterate, and reaches the check for R31 too late, it seems to become stuck on the check for the Host 0 interrupt. I couldn't find a reasonable explanation for this. If someone knows it, I'd be happy to know. In the meantime, it is absolutely crucial that the loop returns to checking R31 exactly at or before the Host 0 interrupt is set.

    Cheers,

    António

  • Hello António,

    Glad you were able to get it working!

    Thanks for the documentation feedback. We are absolutely still working on improving the PRU's TRM documentation. Unfortunately it is a pretty slow process. I have already filed bugs quite a while back against missing PRU_CTRL registers (these ones) and no register summaries at the top of each register section to make it easier to navigate to the specific register you want.

    System events are listed here (though we are using a different term for it in different places):
    TRM table "PRUSS IP Internal Interrupts"
    where ECAP is entry 15

    I have limited control over the TRM. So I can file bugs against it, but that's all at this point. That's part of the reason my team is starting work on a separate PRU academy that is more focused on teaching, so that we can have an easier introduction into this stuff.

    If you can remember which general register sets were better documented in AM335x than AM62x, I can take that info and file some additional bug reports to make sure those descriptions get touched when the team finally circles back to release a new TRM revision for AM62x.

    Regards,

    Nick

  • Thanks Nick!

    In general, I'd say the AM335x manual does a better job at describing the purpose and usage of registers in the summary.

    It would be nice for these things to have a common nomenclature. For instance, figure 7-42 on the AM62x TRM shows "Channel mapping of system events" and "Sys_event *". Admittedly, it also mentions "internal interrupts" in section 7.4.6.1 but 7.4.6.1.1 and 7.4.6.1.2 describe things in terms of "system events". This makes it hard to guess what to search for and increases the changes of us missing it, and then whining on TI E2E looking for answers that are actually in the TRM Slight smile

    My suggestions to address these points would be:

    • use common nomenclature for system interrupts (anything is fine, as long  as it is always the same name for the same thing)
    • link to table 7-64 from section  7.4.6.1
    • In section 7.4.6.2 use register and fields names that match the register and field names in the registers section, making searching easier (possible?)
    • Provide a summary for each register, to clarify its role or principle of operation

    I really appreciate you taking the time to read and acknowledge these documentation issues. Thanks for looking into it and for the pointers that ultimately allowed me to reach a solution.

    Cheers,

    António