I'm trying here is to let the real-time units of the BeagleBone generate the signals for a stepper motor.

In this post: control two output pins from PRU and Linux

action photo beaglebone pru cape


The PRU will have to drive two pins when driving the stepper motor.

One for the direction of rotation. The other for the step pulses.

I'll try here to send the PRU a number of steps, and a flag to give the direction.

The PRU should then drive the DIR pin (high or low based on the parameter) and pulse the STEP pin the requested number of times.


I'm selecting PRU GPIO pins 29 and 31 - because I have a cape that has LEDs on these pins that make it easy to see the effect.

But any PRU accessible GPIO pins can be used.


MUX Outputs for PRU


There are several ways you can define the pin mux. I'm using the Linux driver to do that.

These are the two pins I'm using:

  • PR1_PRU0_GPO0: P9_31 (blue LED, symbolically represents the DIR signal)
  • PR1_PRU0_GPO1: P9_29 (green LED, represents the STEP signal)


image source: modified from seeed wiki for the BB green


These Linux commands reserve them as PRU controlled output pins:

config-pin P9_31 pruout
config-pin P9_29 pruout


The PRU can now address them and drive them high and low.


When finished at the end of the program, best assign them back to their default muxing. Else the PRU driver complains after reboot.

config-pin P9_31 default
config-pin P9_29 default


Very likely there's a better way to do this (overlay?). If you know how, put it in the comments please.


Simple PRU Program to Drive the Pins


My code is based on TI's example program LAB 6: Blinking LEDs with RPMsg from Linux User Space.

The TI copyright is taken over. I don't claim any additional rights for my changes. They are free for any purpose.


// Based on LAB 6: Blinking LEDs with RPMsg from Linux User Space.
// http://processors.wiki.ti.com/index.php/PRU_Training:_Hands-on_Labs#LAB_6:_Blinking_LEDs_with_RPMsg_from_Linux_User_Space

 * Copyright (C) 2016-2018 Texas Instruments Incorporated - http://www.ti.com/

#include <stdint.h>
#include <stdio.h>
#include <ctype.h>
#include <pru_cfg.h>
#include <pru_intc.h>
#include <rsc_types.h>
#include <pru_rpmsg.h>
#include "resource_table_0.h"


Two PRU registers are used for the two pins and interrupts from + to Linux


volatile register uint32_t __R30;
volatile register uint32_t __R31;

/* Host-0 Interrupt sets bit 30 in register R31 */
#define HOST_INT ((uint32_t) 1 << 30)

/* The PRU-ICSS system events used for RPMsg are defined in the Linux device tree
 * PRU0 uses system event 16 (To ARM) and 17 (From ARM)
 * PRU1 uses system event 18 (To ARM) and 19 (From ARM)
#define TO_ARM_HOST 16
#define FROM_ARM_HOST 17

#define DIR     0x0
#define STEP    0x1


Common parts to exchange info with Linux. Literally from TI's example:


* Using the name 'rpmsg-pru' will probe the rpmsg_pru driver found
* at linux-x.y.z/drivers/rpmsg/rpmsg_pru.c
#define CHAN_NAME "rpmsg-pru"
#define CHAN_DESC "Channel 30"
#define CHAN_PORT 30

 * Used to make sure the Linux drivers are ready for RPMsg communication
 * Found at linux-x.y.z/include/uapi/linux/virtio_config.h

uint8_t payload[RPMSG_MESSAGE_SIZE];

 * main.c
void main(void)
struct pru_rpmsg_transport transport;
uint16_t src, dst, len;
volatile uint8_t *status;

__R30 = 0x0;

/* Allow OCP master port access by the PRU so the PRU can read external memories */

/* Clear the status of the PRU-ICSS system event that the ARM will use to 'kick' us */

/* Make sure the Linux drivers are ready for RPMsg communication */
status = &resourceTable.rpmsg_vdev.status;
while (!(*status & VIRTIO_CONFIG_S_DRIVER_OK));

/* Initialize the RPMsg transport structure */
pru_rpmsg_init(&transport, &resourceTable.rpmsg_vring0, &resourceTable.rpmsg_vring1, TO_ARM_HOST, FROM_ARM_HOST);

/* Create the RPMsg channel between the PRU and ARM user space using the transport structure. */
while (pru_rpmsg_channel(RPMSG_NS_CREATE, &transport, CHAN_NAME, CHAN_DESC, CHAN_PORT) != PRU_RPMSG_SUCCESS);
while (1) {
/* Check bit 30 of register R31 to see if the ARM has kicked us */
if (__R31 & HOST_INT) {
/* Clear the event status */


Now my part. I expect a string of characters from Linux.

Position 1 is the direction ('0' or '1').

All others are a number of steps, and should all be digits.



1458: Set the DIR pin high, pulse 458 times.

06: Set the DIR pin low, pulse 6 times.


First we check the direction and drive the pin accordingly:


/* Receive all available messages, multiple messages can be sent per kick */
while (pru_rpmsg_receive(&transport, &src, &dst, payload, &len) == PRU_RPMSG_SUCCESS) {
    if (len < 2) { // at least directory and 1 bit of counts

    // fist position is direction. If '0', then clear DIR bit, else set dir bit
    if (payload[0] == '0') {
                    __R30 &= ~(1UL << DIR);
    } else {
                    __R30 |= 1UL << DIR;


Then calculate the number of pulses.

I parse each character in the argument and give it the right weight by using this formula (the first, most siginificant digit is at index 1):

digit * 10 pow (total length - 2 - position)


   // get the number of pulses
    int i;
    uint32_t pulses = 0U;
    for (i = 1; i < len; i++) {
        if (isdigit(payload[i])) {
            pulses += ((payload[i] - '0') * opt_int_pow(len - 2 - i));


And then toggle the step line twice for each pulse, because a pulse has two edges)

    // toggle STEP twice the pulse count (a pulse is two toggles)
    for (i = 0; i < pulses * 2; i++) {
        __R30 ^= 1UL << STEP;  // can be optimised if I constant the right operator
                    __delay_cycles(600000); // Intrinsic method delay


Because the standard pow() function does not fit in the PRU's memory, I borrowed one from stackowerflow:

static int opt_int_pow(int n)
    int r = 1;
    const int x = 10;
    while (n)
        if (n & 1) 
           r *= x;
            r *= x * x;
            n -= 2;
    return r; 


Compile, then copy the executable to the BB. I moved it to /home/debian/bin


In Linux, execute these commands to drive the DIR high and run 20 pulses:


sudo -i

config-pin P9_31 pruout
config-pin P9_29 pruout

cp /home/debian/bin/bb_PRU_STEPPER.out /lib/firmware/bb_PRU_STEPPER.out

echo 'bb_PRU_STEPPER.out' > /sys/class/remoteproc/remoteproc1/firmware

echo 'start' > /sys/class/remoteproc/remoteproc1/state

echo "120" > /dev/rpmsg_pru30

echo 'stop' > /sys/class/remoteproc/remoteproc1/state

config-pin P9_31 default
config-pin P9_29 default



It works!


Code Composer Studio project attached.

You need to download TI's LABs, they contain some dependencies we need.

In the project's properties, adapt the variable PRU_SOFTWARE_SUPPORT_PACKAGE to the home location where you saved the LABs.




Related blog:
BeagleBone Control Stepper Motors with PRU - Part 1: Intentions
BeagleBone Control Stepper Motors with PRU - Part 2: Test Driving Outputs
BeagleBone: Enable SPI with Overlay and from Command Line
BeagleBone Control Stepper Motors with PRU - Part 3: Hardware Provisioning and Wiring
BeagleBone Control Stepper Motors with PRU - Part 4: SPI Setup
BeagleBone Control Stepper Motors with PRU - Part 5: It Works