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: PRU accessing main domain UART

Part Number: AM625

Tool/software:

Hi

I am looking for info on how to access the main domain UART in PRU. I was tracing the am625/PRU_Hardware_UART example and found out that the address of 'CT_UART' was the value of 'PRU_UART' which is defined in AM62x_PRU0.cmd

PRU_UART : org = 0x00028500 len = 0x00000038 CREGISTER=7
1. If I want to access the UART5 (0x02850000), should I change the 0x00028500 to 0x02850000? How about the len?
2. I've configured the uart5 in dts file, should I enable it or disable it?
   &main_uart5 {
       pinctrl-names = "default";
       pinctrl-0 = <&main_uart5_pins_default>;
       status = "okay";
    };
3. Using the 'PRU_Hardware_UART.c' I would like to see the 'Hello!' message in UART5.
Could you give me some guidance?.
Regards,
John
  • Hello John,

    Constant table entries

    CT in the linker.cmd file stands for "Constant Table" (or "Constants table", you'll see both phrases used in the technical reference manual or TRM). The constant table entries are fixed. For more information about the Constant Table entries, refer to the TRM, PRU chapter, table "PRU0/1 Constant Table".

    The Constant table basically allows us to spend one fewer clock cycle for each memory access, which is why it is used in the PRU_Hardware_UART example. For your work, you would want to use the actual hardware address for the UART5 registers.

    Basic multicore concepts: peripheral ownership 

    The PRU is initialized by the Linux remoteproc driver, but the PRU is still a separate core that has separate resources. In a multicore processor, you only want one software instance (i.e., either Linux, OR a PRU core) to control a specific peripheral. That means that if you want the PRU to control a peripheral, that peripheral needs to be disabled in the Linux devicetree file so that Linux doesn't mess with it.

    For more information about that basic concept, refer to the AM62x academy, multicore section, Peripherals:
    https://dev.ti.com/tirex/explore/node?node=A__AZAVEddCL5eFK1WrnKz45Q__AM62-ACADEMY__uiYMDcq__LATEST

    (at this point in time I wrote that documentation to be specific to the M4F & DM R5F cores, NOT the PRU cores, so the exact implementation will be different, even though the basic concepts remain the same)

    How does the PRU request ownership of a peripheral from the Device Management task? 

    This is the bit that I have not experimented with yet on AM62x/AM64x/AM65x. I just got back from a 2 week vacation and I'm still catching up on everything, so if I can get you to ping me again in a couple of days, I'll ask around to see if any of our PRU firmware developers have guidance on how to do this from the PRU.

    Regards,

    Nick

  • Update:

    It sounds like all cores need to request ownership of the peripherals from the Device manager core - except the PRUs. It sounds like the PRUs should work similar to on previous devices, where you just have to make sure the peripheral is not requested by any other processor cores (that means it needs to be explicitly DISABLED in Linux devicetree file), and then the PRUs would be in charge of directly modifying the config registers to enable the clock signal to the peripheral, configure the peripheral, etc.

    Regards,

    Nick

  • Hi Nick,

    I tried to follow the am335x/PRU_Hardware_UART example. So, I did this patches for am62x.

    diff --git a/examples/am62x/PRU_Hardware_UART/AM62x_PRU0.cmd b/examples/am62x/PRU_Hardware_UART/AM62x_PRU0.cmd
    index 55a4ecd..ff26410 100644
    --- a/examples/am62x/PRU_Hardware_UART/AM62x_PRU0.cmd
    +++ b/examples/am62x/PRU_Hardware_UART/AM62x_PRU0.cmd
    @@ -71,7 +71,7 @@ MEMORY
          * split up the pruIntc structure and CT_INTC variable in
          * include/PROCESSOR/pru_intc.h */
         /*PRU_INTC_0x200: org = 0x00020200 len = 0x00001304 CREGISTER=6*/
    -    PRU_UART    : org = 0x00028000 len = 0x00000038 CREGISTER=7
    +    PRU_UART    : org = 0x02850000 len = 0x00000100 CREGISTER=7
         PRU_IEP0_0x100  : org = 0x0002E100 len = 0x0000021C CREGISTER=8
         PRU0_CTRL   : org = 0x00022000 len = 0x00000030 CREGISTER=11
         PRU_RAT0    : org = 0x00008000 len = 0x00000854 CREGISTER=22
    diff --git a/examples/am62x/PRU_Hardware_UART/PRU_Hardware_UART.c b/examples/am62x/PRU_Hardware_UART/PRU_Hardware_UART.c
    index 30a9b20..ea330e3 100644
    --- a/examples/am62x/PRU_Hardware_UART/PRU_Hardware_UART.c
    +++ b/examples/am62x/PRU_Hardware_UART/PRU_Hardware_UART.c
    @@ -132,23 +132,26 @@ void main(void)
     
         /*** SEND SOME DATA ***/
     
    -    /* Let's send/receive some dummy data */
    -    for (cnt = 0; cnt < MAX_CHARS; cnt++) {
    -        /* Load character, ensure it is not string termination */
    -        if ((tx = hostBuffer.data[cnt]) == '\0')
    -            break;
    -        /* Write to the THR data bitfield, as defined in the AM335x TRM */
    -        CT_UART.THR_bit.DATA = tx;
    -
    -        /* Because we are doing loopback, wait until LSR.DR == 1
    -         * indicating there is data in the RX FIFO */
    -        while ((CT_UART.LSR1_bit.DR == 0x0));
    -
    -        /* Read the value from RBR */
    -        buffer[cnt] = CT_UART.RBR_bit.DATA;
    -
    -        /* Wait for TX FIFO to be empty */
    -        while (!((CT_UART.IIR_bit.INTID) == 0x1));
    +    while (1)
    +    {
    +        /* Let's send/receive some dummy data */
    +        for (cnt = 0; cnt < MAX_CHARS; cnt++) {
    +            /* Load character, ensure it is not string termination */
    +            if ((tx = hostBuffer.data[cnt]) == '\0')
    +                break;
    +            /* Write to the THR data bitfield, as defined in the AM335x TRM */
    +            CT_UART.THR_bit.DATA = tx;
    +
    +            /* Because we are doing loopback, wait until LSR.DR == 1
    +            * indicating there is data in the RX FIFO */
    +            while ((CT_UART.LSR1_bit.DR == 0x0));
    +
    +            /* Read the value from RBR */
    +            buffer[cnt] = CT_UART.RBR_bit.DATA;
    +
    +            /* Wait for TX FIFO to be empty */
    +            while (!((CT_UART.IIR_bit.INTID) == 0x1));
    +        }

    I also tried to disabled the peripheral (uart5) in device tree. When I load the pru, I did not see the data.

    I am guessing:
    1. My patches is not enough and it needs to do more modifications in the code.

         If my patches needs more modifications, could you provide a detail info?.


    2. The PRU still has no support to access the main domain peripherals.

         Could you confirm if the existing software stack support it?.

    Regards,

    John

  • Hello John,

    You cannot use the constant table to access UART5

    The Linker.cmd file changes are incorrect. CREGISTER=7 indicates that this is the 7th entry in the PRU constant table. Let's check the Technical reference manual (TRM), section "PRU constant table":

    The Constant table allows the PRU compiler to save 1 clock cycle per instruction by using the LBCO / SBCO assembly instructions instead of the LBBO/SBBO instructions.

    Since UART5 is not an entry in the constant table, you won't be able to use a constant table entry to access it.

    How to modify the header file so you can use it? 

    Please refer to the AM335x PRU_ADC_onChip example:

    https://git.ti.com/cgit/pru-software-support-package/pru-software-support-package/tree/examples/am335x/PRU_ADC_onChip/pru_adc_firmware.c

    You can see that the ADC header file is included here:
    #include <sys_tscAdcSs.h>

    And then you can find the file here:
    https://git.ti.com/cgit/pru-software-support-package/pru-software-support-package/tree/include/am335x/sys_tscAdcSs.h

    Pay special attention to the line at the bottom of that file:
    /* Definition of Touchscreen/ADC register structures. */
    #define ADC_TSC (*((volatile sysTscAdcSs*)0x44E0D000))

    I would suggest copying file am62x/pru_uart.h to a new file, sys_uart.h. Then at the bottom of your new file, add lines like this:
    #define UART4 (*((volatile uart*)0x2840000));
    #define UART5 (*((volatile uart*)0x2850000));

    etc.

    Then after you include the file in your PRU code, you can reference it like this:
    UART5.DIVLSB = 104;

    Don't forget that the PRU needs to enable the clocks to the UART peripheral 

    If the UART is not clocked, then you will not be able to read & write to it.

    Once your code is working, please share it with me - several customers have asked about this, so I could turn it into an FAQ to help future people.

    Regards,

    Nick

  • Hi Nick,

    In order to use the UART5, it needs to configure the RX/TX pins (pinmux). If I recall it correctly the M4 or R5 firmware was doing a pinmux before it use the pins. In PRU, how to configure those pins?

    Regards,

    John

  • Hello John,

    There are a couple of ways to do it. One option is to simply "sneak" the UART pinmuxing into an existing set of pinmux settings in the Linux devicetree. Another option is to manually update the pinmux registers with the PRU cores. Adding to the Linux devicetree file is probably easier to implement, but it is more of a hack and requires you to remember in the future where your pinmux settings are hiding.

    Setting Pinmux within the Linux devicetree 

    Linux only applies pinmux settings that are linked to an enabled devicetree node, so it is not sufficient to simply add the pinmux settings that you want. You want to make sure those settings are getting placed within a probed devicetree node's pinmux settings.

    For example, let's say you know you are using main UART0 in your design. Then you could update file k3-am62x-sk-common.dtsi

    from this:

            main_uart0_pins_default: main-uart0-pins-default {
                    pinctrl-single,pins = <
                            AM62X_IOPAD(0x1c8, PIN_INPUT, 0) /* (D14/A13) UART0_RXD */
                            AM62X_IOPAD(0x1cc, PIN_OUTPUT, 0) /* (E14/E11) UART0_TXD */
                    >;
            };
    

    to this:

            main_uart0_pins_default: main-uart0-pins-default {
                    pinctrl-single,pins = <
                            AM62X_IOPAD(0x1c8, PIN_INPUT, 0) /* (D14/A13) UART0_RXD */
                            AM62X_IOPAD(0x1cc, PIN_OUTPUT, 0) /* (E14/E11) UART0_TXD */
                            ...
                            <your custom pru pinmuxing here>
                            ...
                    >;
            };
    

    Setting Pinmux from the PRU 

    Note that you'll need to set the kick registers appropriately before modifying the pinmux settings. For more information on the basic concept, refer to
    https://e2e.ti.com/support/processors-group/processors/f/processors-forum/1348153/re-am625-how-to-set-pinmuxing-for-pru-subsystem/5137357#5137357

    Regards,

    Nick

  • Hi Nick,

    I followed your instructions, I used the devicetree to pinmux the pins. Then, in the PRU example (PRU_Hardware_UART.c), I tried to set 'MCTR' to 0, 0x4, 0x8 and 0xC

    I ran it and it show the first character 'H' in the serial and the rest are missing. Any idea how to fix it?.

    Regards,

    John

  • Hello John,

    I bet the code is pausing here:

    	/*** SEND SOME DATA ***/
    
    	/* Let's send/receive some dummy data */
    	for (cnt = 0; cnt < MAX_CHARS; cnt++) {
    	...
    		/* Because we are doing loopback, wait until LSR.DR == 1
    		 * indicating there is data in the RX FIFO */
    		while ((CT_UART.LSR1_bit.DR == 0x0));

    I'm not sure if just typing something into the terminal from your computer side would be enough to get the next character to show up? Or you could just comment out that line and see what happens. There may be other code that also needs to be updated for the non-internal-loopback usecase.

    Regards,

    Nick

  • Hi Nick,

    I was able to print of the whole message in the serial. I had to commented out the following:

    //CT_UART.FCR_bit.DMAMODE1 = 1;
    //CT_UART.FCR_bit.TXCLR = 1;
    //CT_UART.FCR_bit.RXCLR = 1;

    and set:

    CT_UART.MCTR = 0;

    The current issue right now is the RX. I added this code:

    while (1)
    {
    
    	if (CT_UART.LSR1_bit.DR)
    		PrintMessageOut("DR!\r\n");
    
    	if (CT_UART.LSR1_bit.RXFIFOE)
    		PrintMessageOut("RXFIFOE!\r\n");
    
    	if (CT_UART.LSR1_bit.TEMT)
    		PrintMessageOut("TEMT!\r\n");
    
    	if (CT_UART.LSR1_bit.THRE)
    		PrintMessageOut("THRE!\r\n");
    
    	if (CT_UART.LSR1_bit.BI)
    		PrintMessageOut("BI!\r\n");
    
    	if (CT_UART.LSR1_bit.FE)
    		PrintMessageOut("FE!\r\n");
    
    	if (CT_UART.LSR1_bit.PE)
    		PrintMessageOut("PE!\r\n");
    
    	if (CT_UART.LSR1_bit.OE)
    		PrintMessageOut("OE!\r\n");
    
    	__delay_cycles(100000000);
    }

    If I let the firmware run without typing in the serial terminal it continuously print the 'TEMT!' but, if I press any key it seems the firmware locked out somewhere (I don't know where). I was expecting the 'DR!' message in the serial console but I didn't see any data anymore in the serial.

    The issue is very easy to replicate. 

    Regards,

    John

  • Hello John,

    Have you tried connecting CCS to the PRU core while running in order to debug where the code is when it gets locked up?

    Here's an example debug flow I used to troubleshoot an R5F core on AM64x (the details are different, but basic concepts should be the same):
    https://dev.ti.com/tirex/explore/node?node=A__AfR2ju5RKoCJDj50kzWghg__AM62-ACADEMY__uiYMDcq__LATEST

    And here's the debug resources from the PRU Getting Started Labs:
    https://software-dl.ti.com/processor-sdk-linux/esd/AM62X/10_00_07_04/exports/docs/common/PRU-ICSS/PRU-Getting-Started-Labs_Lab5.html

    Regards,

    Nick

  • Hi Nick,

    got it working. I had to commented this out too.

    while (!((CT_UART.IIR_bit.INTID) == 0x1));

    Regards,

    John

  • Hello John,

    Glad to hear!

    I would like to get your feedback. I have only tested this example myself with internal loopback, so I have not really considered the modifications that would be required to get it working in a more "normal" usecase.

    Are the code changes you have to make fairly obvious? Or is this something where we should consider also documenting a simple example that reads and writes from the UART terminal?

    Regards,

    Nick