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
Top Comments