pwm from pru on beaglebone black

Hello,

I am building a winding machine using the BBB. I would appreciate commercial support, or help on this forum.

I set out implementing pwm in pru by software until I realized that I could program the three pwm units to do the tedious pwm, and adjust the pwm parameters in realtime using the pru.

My understanding is that there is a conflict between the kernel driver and the pru.
Also my understanding is that the pru can not enable the pwm.

My machine is supposed to load the pru firmware, after which it can receive commands to start a winding a coil with speciffic parameters using rpmsg.

I have an oscilloscope.
I use config-pin P9_21 pwm and config-pin P9_22 pwm to pinmux.
Pwm works if I configure period, duty_cycle and enable in linux.
PS: /sys/class/pwm/pwmchip3/ is pwm0 (P9_21, P9_22)

debian@BeagleBone:~/wgwind$ uname -r
5.10.168-ti-r71

However I have never been able to adjust anything using the pru0.
I can generate software pwm using the pru, but no hw pwm.

Strategies I have attempted and failed:

  1. Enable pwm using the pru:
void enable_pwm0_1khz_2khz(void) {
    // Step 1: Reset PWMSS0 to a known state
    PWMSS0.SYSCONFIG = 0x1;  // SOFTRESET = 1
    delay_ms(1);             // Wait 1 ms for reset to settle
    while (PWMSS0.SYSCONFIG & 0x1) {  // Ensure reset completes
    }

    // Step 2: Enable PWMSS0 clock for EHRPWM and set free-run
    PWMSS0.SYSCONFIG = 0x2;  // FREEEMU = 1 (free-run even in debug)
    PWMSS0.CLKCONFIG = 0x100;  // EPWMCLK_EN = 1 << 8
    while (!(PWMSS0.CLKSTATUS & 0x100)) {  // Wait for EPWMCLK_EN_ACK
    }

    // Step 3: Configure Timebase for 2 kHz
    PWMSS0.EPWM_TBCTL = 0x6008;  // Add FREE_SOFT=2 (free-run), up-count, CLKDIV=/1, HSPCLKDIV=/1, shadow
    PWMSS0.EPWM_TBPRD = 49999;   // 100 MHz / 2 kHz - 1 = 49999
    PWMSS0.EPWM_TBCNT = 0;       // Reset counter to 0

    // Step 4: Configure Output A for 1 kHz
    PWMSS0.EPWM_CMPA = 12499;    // 25% of 50000
    PWMSS0.EPWM_AQCTLA = 0x0021; // Set on zero, clear on CMPA up

    // Step 5: Configure Output B for 2 kHz
    PWMSS0.EPWM_CMPB = 24999;    // 50% of 50000
    PWMSS0.EPWM_AQCTLB = 0x0201; // Set on zero, clear on CMPB up

    // Step 6: Force counter to start and ensure clock stability
    PWMSS0.EPWM_TBCTL |= 0x8000;  // Set CTRRST (bit 15) to force counter reset, then clear it
    PWMSS0.EPWM_TBCTL &= ~0x8000; // Clear CTRRST to start counting
}

int main(void) {
    PRU0_CTRL.CTRL_bit.CTR_EN = 0;  // Stop PRU
    enable_pwm0_1khz_2khz();        // Configure PWM0
    PRU0_CTRL.CTRL_bit.CTR_EN = 1;  // Start PRU

    while (1) {
        // Toggle a GPIO or memory location for debugging (optional)
        delay_ms(500);              // Blink every 500 ms to confirm PRU is running
        // Example: PWMSS0.EPWM_TBCNT visible in shared memory for ARM to read
    }

    return 0;
}

This fails.

  1. Enable pwm in linux, and adjust it in pru. Also fail. echo blacklist pwm-ti-ehrpwm > /etc/modprobe.d/blacklist-pwm.conf. and reboot.
  2. Device tree overlays and cape managers confuse me.

If you have a kernel driver loaded to control the PWMs, that’s inherent.

When going through the OCP, the PRUs can reach virtually any resource, so this sounds wrong.

Again, take a close look at some of the OCP access examples,
they should be able to enlighten you.

We love that you have chosen Beaglebone and I’m sorry this is feeling like
an uphill battle for you, but at the same time I always try to utilize the right tool for the job,
so I can’t help thinking, just perhaps, an ESP32 or a Pi Pico would have been a better pick?

Don’t get me wrong, Beaglebones are awesome devices,
but with all this awesomeness come lots and lots of complexity.

I hope I haven’t discouraged you too much, because making a project work is very rewarding.

What works is use your BBB as the main control. Off load via uart the deterministic code. The RPI2040 is good and a short time till you are up. The Ti c2000 has much tighter timing between the channels and less jitter than the rpi2040. RPI you will be up and running in very quickly, the Ti, not so quickly. The 2040 can handle the uart at 921kbs data with a good solid cable.

Controlling PWM subsystems from PRU is realized in libpruio. It’s possible to set up the A and B channels of all three modules, as well as synchronize the output between the modules (and start all by one command). Check out qep example for using thePwm->AqCtl array.

So from my point of view your project seems easy, no additional hardware.

I cannot check your appraoch, since I neither use C language nor rpmsg. Using ASM and uio-pruss instead gives more control over timing, which is essential for PRU projects (FMPOV).

Regards

Thank you all for the encouraging info, knowing that what I want to do is possible with the hardware I have gives me renewed confidence to continue the project.

It is nice to hear from the author of libpruio. I was perfectly happy using rpmsg for communication but I understand that uio is much lighter and does not require the data to go through a kernel driver.

@DTJF Thank you for creating and also mentioning libpruio. If you don’t mind I would like to ask you a few questions:

Q1. From the below, it seems like I am missing uio_pruss. The update_kernel.sh is no longer in /opt. Do I need to go back to the previous debian release with the 4.19 kernel in order to use libpruio?

root@BeagleBone:~# uname -a
Linux BeagleBone 5.10.168-ti-r71 #1bullseye SMP PREEMPT Fri Sep 1 04:05:07 UTC 2023 armv7l GNU/Linux
root@BeagleBone:~# lsmod|grep uio
uio_pdrv_genirq        20480  0
uio                    20480  1 uio_pdrv_genirq

The machine that I am building is a parallel winding machine will be used in a production environment. I am a little unsure if I should set up pwm and then adjust using the pru, or do the synchronized pulses (winding motor + traverse motor) in software. Software seems safer as they will stop if the software hangs, while the pwm will just keep going, possibly will need to have a trip-condition that if not updated by the pru will stop the pwm.

I am a little worried about latency (thus wanting deterministic code in the pru) around the edges of the coil when the traverse motor has to stop and reverse direction. I will start with max 200 imp/rev and 8 rps = 1600 impulses / s and increase the pulses / rev if performance permits.

My understanding is that there is a firmware on the pru which is loaded by the linux program,
however in the documentation it is quite unclear which are the pru firmware, everything looks like programs that run on the host.

I was thinking that my application needs a custom firmware for the pru, that is capable of communicating with the linux host, but should be able to the job of winding with the set parameters without arm–pru communication. Communication should be used to see status, adjust speed, start/stop, etc.

Q2. How do I go about understanding where the pru program is and where the linux program is from libpruio documentation/sources?

Q3. For me commands (ex. adjust speed, adjust stop length do not have to be fast, I mean a human will barely notice 100 ms delay, while for the machine this is a very long time. I am more worried about the cycles necessary on the pru, for example if the winding is at the edge, it needs to be pulse-perfect in order to turn around at the right time. Which communication method is right for me?
From io, mm and rb I would guess IO.

Sorry for the long text and thank you!

Hi @dotbit!

Q1: It’s hard to get all system components installed and running for libpruio. Perhaps you want to test on a proven system first, and do all the config hassle when you’re sure it meeds your need.

You may want to check out the pre-installed SD image at libpruio - pre-installed on 5.10 kernel image. After booting from the burned SD card, the library and all examples are pre-installed, ready to test.

Q2: The main programm is running on ARM and loads (and starts) the PRU firmware. In libpruio, calling the constructor loads and starts the firmware on the configured PRU. Afterwards on the other PRU, a second firmware can get loaded by function load_firmware and started by function prussdrv_pru_enable.

See examples pruss_add or pruss_toggle.

Q3: Modes mm and rb are related to ADC. Yes, io mode seems to be fine.

It seems to be a good idea to watch rotation/position. The prefered solution should be a QEP sensor.

When using hardware PWM, you may consider to generate an interrupt on each PWM cycle, counting impulses. This is a small callback in the PRU firmware, but needs some configuration for the interupt controller.

In contrast, you can generate the PWM pulses in the PRU firmware. Check direct GPIO for output (fixed three cycle latency). Anyway, this also needs some loopback for pulse counting/checking.

Regards

PS: Regarding timing, 200 PRU cycles (enough for your main loop) is 1 μs (0.001 ms). You should be fine.