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

In chapters 1 to 5, I developed PRU firmware that works for 1 stepper motor

 

In part 6, I switch to a timer controlled version, that should allow for more motors and smoother start and end. Here in post 6a: refactoring the code a first time to extract motor dependent logic from the main loop:

 

Prepare for More Motors and Motor State

 

I created the shell for a structure to hold the state per motor.

 

typedef struct {
  // pin assignments
  uint32_t pin_step;
  uint32_t pin_dir;
  // status
  uint32_t dir;
  uint32_t steps;
  uint32_t clockDivider;
} motor_t;

 

And an array to keep all motors.

 

motor_t motors[MOTOR_COUNT];

 

This doesn't fundamentally change the program at this point. I defined the array size 1, so that's still one motor.

And the code doesn't loop over the array yet, it just uses that first motor.

But it's a starting point. One that can be used to validate if the program is still working after making the change.

 

Make Main Loop Simpler

 

The initialisation of the motor(s) and getting data from Linux/ARM are refactored into a separate function now.

 

 void motor_init() {
   // todo this should become parameterised.
   // initialise motor array
   motors[0].pin_dir = PIN_DIR_MOTOR1;
   motors[0].pin_step = PIN_STEP_MOTOR1;
   motors[0].dir = 0U;
   // set STEP low
   __R30 &= ~motors[0].pin_step;
   // set dir to inital dir
   __R30 &= ~motors[0].dir << motors[0].pin_dir;
 }

 bool parseArmCmd (void *data, uint16_t len) {
   if (len < 2) { // at least directory and 1 bit of counts
     return false;
   }

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

   if (motors[0].dir) {
     __R30 |= 1UL << motors[0].pin_dir;
   } else {
     __R30 &= ~(1UL <<  motors[0].pin_dir);
   }

   // 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));
     }
   }
   motors[0].steps = pulses;
   return true;
 }

 

This makes the loop simpler to read, and simpler to work on in the next exercise.

 

  motor_init();

  /* 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 */
      CT_INTC.SICR_bit.STS_CLR_IDX = FROM_ARM_HOST;
      /* Receive all available messages, multiple messages can be sent per kick */
      while (pru_rpmsg_receive(&transport, &src, &dst, payload, &len) == PRU_RPMSG_SUCCESS) {
        if (parseArmCmd(payload, len)) {
          int delay = 60000;
          int i;
          // toggle STEP twice the pulse count (a pulse is two toggles)
          for (i = 0; i < motors[0].steps * 2; i++) {
            __R30 ^= 1UL << motors[0].pin_step;  // can be optimised if I constant the right operator
            //                    __delay_cycles(300000); // Intrinsic method delay
            int j;
            for (j = 0; j < delay; j++) {
            }
          }
        }
      }
    }
  }

 

The combination of these two activities help, while not making so many modifications in the program flow that debugging becomes difficult.

 

This version of the PRU firmware: https://gist.githubusercontent.com/jancumps/ee9482dd27856c823ddcf505830dece4/raw/9a251b9f81f5576499db23d7570b0564bdcd639…

 

Make Testing Simpler

 

I used to do a number of manual steps each time I changed the PRU source. I've reduced that to 3 steps by using a shell script.

 

What I used to do

  • compile in CCS
  • move to the BB in CCS by dropping it on a remote systems window
  • 1 time set all pins, enable SPI, send SPI commands to stepper controller
  • stop running PRU firmware on BB
  • register new firmware
  • start running new firmware, get stepper out of sleep
  • run test
  • put stepper driver back to sleep (or it would heat up, 0.6A constant through the coils when not running.

 

With the shell script:

  • compile in CCS
  • move to the BB in CCS by dropping it on a remote systems window
  • run shell script on the BB. It does everything mentioned above

 

When I know that I have to do a series of repetitive steps to test code modifications, I get lazy and hold off.

An efficient round trip rocess motivates me to do small incremental improvements and test them.

I can optimise this later and add the upload to BB and launching the shell script to the CCS build process. I'll do that when the 3-step process starts to bother me.

 

Here is the script. I named it PRU_STEPPER.sh.

 

#!/bin/bash
PRU_STEPPER_link="$(readlink -f /home/debian/bin/bb_PRU_STEPPER.out)"
fw0_link=${PRU_STEPPER_link}
if [ ${fw0_link} ];
     then
     echo "stop the program if running"
     echo 'stop' > /sys/class/remoteproc/remoteproc1/state 2>/dev/null
     rm /lib/firmware/am335x-pru0-fw &> /dev/null
     ln -s ${fw0_link} /lib/firmware/am335x-pru0-fw

     echo "load the firmware to PRU"
     echo 'am335x-pru0-fw' > /sys/class/remoteproc/remoteproc1/firmware

     echo "start the program"
     echo 'start' > /sys/class/remoteproc/remoteproc1/state

     echo ""
     echo "Firmware is running"
     echo ""
else
     echo "Firmware .out files does not seem to exist. Did you place " ${PRU_STEPPER_link} "?"
     echo ""
fi

echo "sleep on"
echo out > /sys/class/gpio/gpio30/direction
echo 0 > /sys/class/gpio/gpio30/value
  
echo "reset off"
echo out > /sys/class/gpio/gpio31/direction
echo 0 > /sys/class/gpio/gpio31/value

echo "set pin 29 and 31 as PRU GPIO out"
config-pin P9_31 pruout    
config-pin P9_29 pruout    

echo "initialise SPI"
/home/debian/bin/bb_LINUX_STEPPER_SPI 
echo "stepper motor control ready from PRU0"

while test $# -gt 0
do
    case "$1" in
        --test) 
     echo "test requested"
     echo "sleep off"  
     echo 1 > /sys/class/gpio/gpio30/value
     echo "motor move start"
     echo '120' > /dev/rpmsg_pru30
     echo '020' > /dev/rpmsg_pru30
     echo "motor move stop"
     echo "short pause before activating motor sleep..."
     sleep 0.5s
     echo "sleep on"
     echo 0 > /sys/class/gpio/gpio30/value
        
     ;;
    esac
    shift
done

 

 

To do a full test cycle:

sudo /home/debian/bin/PRU_STEPPER.sh --test