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.

Linux/AM3358: PWM initialization from PRU

Part Number: AM3358
Other Parts Discussed in Thread: SYSCONFIG

Tool/software: Linux

We are trying to use the PRU on a BeagleBone Black to generate a PWM using ePWM1.  I've modified the dtb to include the pins.

Initially, the PWMSS1.EPWM_TBCTL is 131 and after initialization is it 128. I think that's correct.  The CTRMODE changes from frozen to up count and the other bits remain the same.

Also, If I set PWMSS1.EPWM_TBCNT to something like 100 it never changes on subsequent reads.

I've verified that

PWMSS1.EPQM_TBPRD, PWMSS1.EPWM_CMPA and PWMSS1.EPWM_CMPB are all set to values I set.

It's as though the clock isn't going. Here are the details:

           brake_bone_pins: brake_bone_pins {

                       pinctrl-single,pins = <

                       0x0A0 (MUX_MODE5) /* pr1_pru1_pru_r30_0,  P8->45, debug pin a */

                       0x0A4 (MUX_MODE5) /* pr1_pru1_pru_r30_1,  P8->46, debug pin b*/

 

                       0x0AC (MUX_MODE5) /* pr1_pru1_pru_r30_3,  P8->44, QEI enable*/

 

                       0x0E0 (PIN_INPUT_PULLDOWN | MUX_MODE6) /* pr1_pru1_pru_r31_8, P8->27, fault */

 

                       0x0D0 (PIN_INPUT_PULLDOWN | MUX_MODE2)  /* eQEP1A_in P8->35 */

                       0x0D4 (PIN_INPUT_PULLDOWN | MUX_MODE2)  /* eQEP1B_in P8->33 */

                       0x0D8 (PIN_INPUT_PULLDOWN | MUX_MODE2)  /* eQEP1_index          P8->31 */

                       0x0DC (PIN_INPUT_PULLDOWN | MUX_MODE2)  /* eQEP1_strobe        P8->32 */

 

                       0x0E8 (MUX_MODE5) /* pr1_pru1_pru_r30_10, P8->28, motor trigger 1 */

                       0x0E4 (MUX_MODE5) /* pr1_pru1_pru_r30_9,  P8->29, PWM enable A */

                       0x0EC (MUX_MODE5) /* pr1_pru1_pru_r30_11, P8->30, PWM enable B */

 

                        0x048 (PIN_OUTPUT_PULLDOWN | MUX_MODE6) /* ePWM1A, P9.14, brake current A */

                        0x04C (PIN_OUTPUT_PULLDOWN | MUX_MODE6) /* ePWM1B, P9.16, brake current B */

                       >;

           };

I based my PRU code on this GitHub project:

https://github.com/mhaberler/BBBIOlib/blob/master/BBBio_lib/BBBiolib_PWMSS.c

In the PRU code, I have the following routines PWM_Init & PWM_Enable. I call PWM_Init 1st followed by PWM_Enable(1). I'm sure the period and duty cycle aren't right, but for now I just want to see something on the output pins. I've captured a few of the registers so that you can see what they look like. As you can see I explicitly set TBCNT, but it always returns 0 (values below are in decimal). I read the following registers every 10ms -- always with the same results.

CM_PER_EPWMSS1 = 2

PWMSS1.EPWM_TBCTL = 128

PWMSS1.CLKSTATUS = 273

PWMSS1.EPWM_TBSTS = 1

PWMSS1.EPWM_TBCNT = 0

 

void PWM_Init(void)

{

   /* Enable PWMSS1 clock signal generation */

   while (!(CM_PER_EPWMSS1 & 0x2))

       CM_PER_EPWMSS1 |= 0x2;

 

   PWMSS1.SYSCONFIG =

             (2 << 4)           // smart standby mode

           | (2 << 2);          // smart idle mode

 

   PWMSS1.EPWM_TBCTL |= 0x3;   // freeze

 

   PWMSS1.EPWM_TBPRD = 50000;

   PWMSS1.EPWM_CMPA  = 30000;

   PWMSS1.EPWM_CMPB  = 20000;

 

   PWMSS1.EPWM_TBCNT = 100;

}

 

void PWM_Enable(bool enable)

{

            if(enable)

            {

           PWMSS1.EPWM_AQCTLA =

                       (0x3 << 4)       // cmpa = set

                       | (0x2 << 0);     // zero = high

 

           PWMSS1.EPWM_AQCTLB =

                       (0x3 << 8)       // cmpa = set

                       | (0x2 << 0);     // zero = high

 

           PWMSS1.EPWM_TBCNT = 0;

 

           PWMSS1.EPWM_TBCTL &= ~0x3;   // up-mode

            }

            else

            {

           PWMSS1.EPWM_TBCTL |= 0x3;      // freeze

           PWMSS1.EPWM_AQCTLA =

                       (0x3 << 4)       // cmpa = set

                       | (0x1 << 0);     // zero = force low

 

           PWMSS1.EPWM_AQCTLB =

                       (0x3 << 8)       // cmpa = set

                       | (0x1 << 0);     // zero = force low

 

           PWMSS1.EPWM_TBCNT = 0;

            }

}

  • The software team have been notified. They will respond here.
  • Hello Daniel,

    Thank you for the detailed information. I will start working on your PWM issue tomorrow.

    Regards,
    Nick
  • Nick:

    Any feedback yet?  We are looking forward to getting this resolved - this has been haunting us for a long while and now the project is behind. 

    - Dan

  • Hello Daniel,

    It looks like your PWM register settings are ok. I will review the flow of your register settings tomorrow to see if something is out of order or missing.

    Did you verify TBCNT cannot be changed (e.g. if you set TBCNT to 100 in your PWM_Enable function rather than 0, do you still get 0?)

    Could you check your EPWM_CMPCTL register value for me?

    Have you verified that the PWM clock is running? I think the serial console of your BeagleBone Black has the tool "omapconf export ctt" (see omapconf --help) to use with the Sitara Clock Tree Tool (www.ti.com/.../CLOCKTREETOOL)

    We could debug if standby mode or idle mode is causing issues by using debug values of 0x0 or 0x1 for SYSCONFIG STANDBYMODE and IDLEMODE (currently set to 0x2 in your code).

    Regards,
    Nick
  • Hello Daniel,

    It looks like the PWM clock needs to be enabled in two places: the PWMSS_CTRL register in the control module (AM335x TRM Chapter 9.3.1.31), and PWMSS CLKCONFIG. I'm guessing CM_PER_EPWMSS1 is the PWMSS_CTRL register - if so, your value is correct. Are you setting PWMSS CLKCONFIG?

    Regards,
    Nick
  • Some answers:

    I downloaded the latest clock tree tool: CTT-Sitara-v1.0.0.3 and installed it. I ran "omapconf export ctt" on the BeagleBone Black, and copied the dump lines into a file of type .rd1. When I try to import the registers into the clock tree tool, I get an error: INCOMPATIBLE DEVIC ETYPE AM335x1.0 OF FILE

    Whatever I set TBCNT to it remains. If it's set to 100, it remains at 100 if I read it back. The code I originally posted has it being set back to zero in PWM_Enable(). If I remove that line, then TBCNT remains whatever I set in PWM_Init()

    I am not explicitly setting up PWMSS1.CLKCONFIG. But, I've read back the CLKSTATUS and it's decimal 273 = 0x111 = ePWMCLK_EN, EQEPCLK_EN, and eCAPCLK_EN which is the default.

    Here is a dump of what I believe are the relevant registers (values are in decimal):

    CM_PER_EPWMSS1  = 2

    PWMSS1.EPWM_TBCTL = 176

    PWMSS1.CLKCONFIG = 273

    PWMSS1.CLKSTATUS = 273

    PWMSS1.EPWM_TBSTS = 1

    PWMSS1.TBCNT = 0

    PWMSS1.TBPRD = 600

    PWMSS1.CMP_A = 350

    PWMSS1.CMP_B = 200

    PWMSS1.TBPHS = 0

    PWMSS1.CMPCTL = 768

    PWMSS1.AQCTLA = 50

    PWMSS1.AQCTLB = 770

  • 
    

    Hello Doug,

    Looks like a syntax error on the .dr1 file. Try opening your .dr1 file and changing the first line so it looks like this:
    DeviceName AM335x_SR2.x_SR1.0

    I got that from the CTT by going Save/Load > save registers and then checking the first line of the file the tool generated.

    Let me know if that allows you to check the clock tree. Looking at your other information now.

    Nick

  • It allowed me load it, but I'm not sure I know what to look for. It does show a green arrow going into the EPWMSS1 block which I assume is a good thing ;-) The value of that register (0x44E000CC) when I run is 2 which according to the datasheet is enabled.

    MODULEMODE 0x2 = ENABLE : Module is explicitly enabled. Interface clock (if not used for functions) may be gated according to the clock domain state. Functional clocks are guarantied to stay present. As long as in this configuration, power domain sleep transition cannot happen.

  • Have you ungated the pwmss timebase clocks via the pwmss_ctrl register in the control module?

    Note that since writes to the control module have to be privileged, you cannot write this register from a PRU core.

  • Matthijs,

    Thanks. No, I have not. According to the manual, that would be 0x44E10664. I printed that out and it's all zeros (timebases not configured). Despite your warning, I tried to set it via the PRU to no avail :-(

    So, how do I go about setting that register? I'm far from a Linux expert on the kernel. We do have our own device tree. Is there a way to define it in there? We're using the TI SDK.

  • Hello Doug,

    Does your application use low power modes? If so, setting the pwmss_ctrl register in the device tree may interfere with entering a lower power mode (I'm still consulting with the power team to understand it). If you do not use low power modes, you should be able to set the register in the device tree.

    Regards,
    Nick
  • No, It does not use low power modes. It's your stock kernel.
  • pwmss_ctrl is not related to power management. It exists to allow simultaneous ungating of the timebase clock for multiple eHRPWM instances (after having configured these) to let them run in sync.

    If you don't care about this functionality, you can just set them once at boot and pay no further attention to them. I'm not sure if there's an elegant way to do this, I generally just use /dev/mem and trick the kernel into performing the privileged write for me by using process_vm_readv().

  • Matthijs, thanks again. I'm familiar with /dev/mem and can write a short program to update the register, but I'm not familiar with process_vm_readv(). Can you provide or point me to a snippet of code showing out to do a privileged write?
  • Doug Griswold said:
    I'm familiar with /dev/mem and can write a short program to update the register, but I'm not familiar with process_vm_readv(). Can you provide or point me to a snippet of code showing out to do a privileged write?

    #include <unistd.h>
    #include <sys/uio.h>
    
    template< typename T >
    void privileged_write( T volatile *target, T const &value )
    {
    	struct iovec dstv = { (void *) target, sizeof(T) };
    	struct iovec srcv = { (void *) &value, sizeof(T) };
    	if( process_vm_readv( getpid(), &dstv, 1, &srcv, 1, 0 ) < 0 )
    		die( "process_vm_readv: %m\n" );
    }