Programmable Realtime Unit! The guy that thought of that name must be a poet. The above picture is what constant acceleration looks like on an oscilloscope. Boxysean has a great post on getting started with PRU on BeagleBone. I’m pretty sure that guy is also a contestant in the BeagleBone cape contest, so best of luck to him! Anyways, here is a video that pretty much demonstrates all I have to say in this post:
Accelerated stepper using pru on beaglebone from Elias Bakken on Vimeo.
Constant speed
Well, constant speed in one dimention is not very impressive, but this one uses the PRU for stepping. That is actually pretty cool. The following script calculates the number of instructions to wait to get the right speed. That is placed in a list and fed in to the data ram of PRU 0.
import pypruss as pru # The Programmable Realtime Unit Library''' speed_test.py - test script for testing fixed speed in PyPRUSS library''' import pypruss as pru # The Programmable Realtime Unit Library import numpy as np # Needed for braiding the pins with the delays speed = 80 # The travelling speed in mm/s distance = 50 # Distance in mm microstepping = 2.0 # With microstepping, every step need four ticks. steps_pr_mm = 6.1*microstepping # Number of ticks the stepper needs to go one mm pru_hz = 200*1000*1000 # The PRU has a speed of 200 MHz s_pr_inst = 1.0/pru_hz # I take it every instruction is a single cycle instruction inst_pr_loop = 16 # This is the minimum number of instructions needed to step. inst_pr_delay = 2 # Every loop adds two instructions: i-- and i != 0 s_pr_mm = 1.0/speed # Seconds pr mm is the inverse of the mm/s s_pr_step = s_pr_mm/steps_pr_mm # To get the time to wait for each step, divide by mm/step inst_pr_step = s_pr_step/s_pr_inst # Calculate the number of instructions of delay pr step. inst_pr_step /= 2.0 # To get a full period, we must divide by two. inst_pr_step -= inst_pr_loop # Remove the "must include" number of steps inst_pr_step /= inst_pr_delay # Yes, this must be right.. inst_pr_step = int(inst_pr_step) # Make it an int num_steps = int(distance*steps_pr_mm) # Number of ticks in total steps = [(1<<12), 0]*num_steps # Make the table of ticks for the stepper. delays = [inst_pr_step]*2*num_steps # Make the table of delays data = np.array([steps, delays]) # Make a 2D matrix combining the ticks and delays data = data.transpose().flatten() # Braid the data so every other item is a data = [num_steps*2+1]+list(data) # Make the data into a list and add the number of ticks total pru_num = 0 # PRU0 pru.init(pru_num, "./firmware.bin") # Load PRU 0 with the firmware. pru.set_data(pru_num, data) # Load the data in the PRU ram pru.wait_for_event(pru_num) # Wait a while for it to finish. pru.disable(pru_num) # Clean shit up, we don't want to be piggies. ''' Put your thing down, flip it and reverse it ''' steps = list(np.array(steps) + (1<<13)) # Make the table of ticks for the stepper. data = np.array([steps, delays]) # Make a 2D matrix combining the ticks and delays data = data.transpose().flatten() # Braid the data so every other item is a data = [num_steps*2+1]+list(data) # Make the data into a list and add the number of ticks total pru_num = 0 # PRU0 pru.init(pru_num, "./firmware.bin") # Load PRU 0 with the firmware. pru.set_data(pru_num, data) # Load the data in the PRU ram pru.wait_for_event(pru_num) # Wait a while for it to finish. pru.disable(pru_num) # Clean shit up, we don't want to be piggies.
General purpose firmware
To accompany these constant speed and constant acceleration scripts is a very minimalistic firmware script that I have written. It basically just sets pin states, delays, sets more pin states and delays and so on. It is good for testing ideas and maybe get started quickly with something. The number of toggles is limited to 1023 since every pin state takes 4 bytes and every delay takes 4 bytes as well and the total available data memory is 8KB. Anyways here is the assembly code for the GPIO firmware:
.origin 0
.entrypoint START
#define PRU0_ARM_INTERRUPT 19
#define GPIO1 0x4804c000 // The adress of the GPIO1
#define GPIO_DATAOUT 0x13c // This is the register for settign data
#define LEN_ADDR 0x00000000 // Adress of the abort command
#define PIN_OFFSET 0x00000004 // Offset for the pins (reserve the first adress for abort)
START:
LBCO r0, C4, 4, 4 // clear that bit
CLR r0, r0, 4 // No, really, clear it!
SBCO r0, C4, 4, 4 // Actually i have no idea what this does
MOV r0, 0 // The first register contains the loop count
LBBO r1, r0, LEN_ADDR, 4 // Load the number of steps to perform into r1
MOV r4, PIN_OFFSET // r4 is the pin and delay counter
SET_PINS:
LBBO r2, r4, 0, 4 // Load pin data into r2
MOV r3, GPIO1 | GPIO_DATAOUT // Load the address of GPIO | DATAOUT in r3
SBBO r2, r3, 0, 4 // Set the pins
ADD r4, r4, 4 // r4 += 4
LBBO r0, r4, 0, 4 // Load Delay into r0
DELAY:
SUB r0, r0, 1 // Delay the required ticks
QBNE DELAY, r0, 0
ADD r4, r4, 4 // r4 += 4
SUB r1, r1, 1 // Decrement r1
QBNE SET_PINS, r1, 0 // Branch back to SET_PINS if r0 != 0, abort!
MOV R31.b0, PRU0_ARM_INTERRUPT+16 // Send notification to Host for program completion
HALT
Adding acceleration
Given a constant acceleration, the speed at each step must be calculated. We do not know the time intervals in advance. All we know is the distances, so the time steps must be calculated.
At what time does the stepper reach maximum speed? Well, it there is constant acceleration, it can be calculated by using the formula:
Where v is velocity at time t, u is the initial velocity and a is acceleration.
So for a constant acceleration of 200mm/s^2, a max speed of 80mm/s and an initial velocity of 0 we hit maximum speed after 0.4 seconds.
Ok, so knowing the time at which the max speed is hit, we can calculate the position at that point. Using the formula for constant acceleration:
where s is the distance travelled at time t, a is acceleration and u is initial velocity. Solving this for t = 0.4 seconds yields 16mm travel distance. Now all the time stamps in the range from 0 to 16mm must be calculated. Let’s say the stepper needs 6.1 steps to travel one mm. In that case we have a step size of 0.16mm and that becomes the increment size. So again using the formula for constant acceleration, but this time solving for t we have:
Once the timestamps have been calculated its is trivial to extract the differences to calculate the actual delays and convert it to instructions. Finally the table must be reversed and used for deceleration. In between the ramp up and the ramp down, we fill in with the delays for max speed. Here is the script used in the video.
''' accel_test.py - test script for testing fixed speed in PyPRUSS library''' import pypruss as pru # The Programmable Realtime Unit Library import numpy as np # Needed for braiding the pins with the delays max_speed = 80 # Top speed in mm/s min_speed = 0 # Minimum speed (V0). distance = 50 # The distance to travel in mm acceleration = 200 # Acceleration in mm/s^2 microstepping = 2.0 # With microstepping, every step need four ticks. steps_pr_mm = 6.1*microstepping # Number of ticks the stepper needs to go one mm pru_hz = 200*1000*1000 # The PRU has a speed of 200 MHz s_pr_inst = 1.0/pru_hz # I take it every instruction is a single cycle instruction inst_pr_loop = 16 # This is the minimum number of instructions needed to step. inst_pr_delay = 2 # Every loop adds two instructions: i-- and i != 0 Vm = max_speed/1000.0 # The travelling speed in m/s a = acceleration/1000.0 # Accelleration in m/s/s s = distance/1000.0 # Distance in m ds = (1.0/(steps_pr_mm*1000.0)) # Delta S, distance in meters travelled pr step. u = min_speed/1000.0 # Minimum speed in m/s tm = (Vm-u)/a # Calculate the time for when max speed is met. sm = u*tm+0.5*a*tm*tm # Calculate the distace travelled when max speed is met def t_by_s(s): # Get the timestamp given a certain distance. return (-u+np.sqrt(2*a*s+u*u))/a # This is the s = ut+1/2at^2 solved with reference to t def sec_to_inst(s): # Shit, I'm missing MGP for this?? inst_pr_step = s/s_pr_inst # Calculate the number of instructions of delay pr step. inst_pr_step /= 2.0 # To get a full period, we must divide by two. inst_pr_step -= inst_pr_loop # Remove the "must include" number of steps inst_pr_step /= inst_pr_delay # Yes, this must be right.. return int(inst_pr_step) # Make it an int distances = np.arange(0, sm, ds) # Table of distances t_in_s = map(t_by_s, distances) # Make a table of times, the time at which a tick occurs d_in_s = np.diff(t_in_s)/2.0 # We are more interested in the delays pr second. Half it, cos we will double it later dd_in_s = np.array([d_in_s, d_in_s]) # Double the array dd_in_s = dd_in_s.transpose().flatten() # Transposing and flattening braids the data. num_steps = int(distance*steps_pr_mm) # Number of ticks in total steps = [(1<<12), 0]*num_steps # Make the table of ticks for the stepper. delays = map(sec_to_inst, dd_in_s) # Number of instructions pr. step is now calculated i_steps = num_steps-len(delays) # Find out how many delays are missing i_dlys = delays[-1::]*i_steps*2 # Make the intermediate steps delays = delays+i_dlys+delays[::-1] # Add the missing delays. These are max_speed data = np.array([steps, delays]) # Make a 2D matrix combining the ticks and delays data = data.transpose().flatten() # Braid the data so every other item is a data = [num_steps*2+1]+list(data) # Make the data into a list and add the number of ticks total pru_num = 0 # PRU0 pru.init(pru_num, "./firmware.bin") # Load PRU 0 with the firmware. pru.set_data(pru_num, data) # Load the data in the PRU ram pru.wait_for_event(pru_num) # Wait a while for it to finish. pru.disable(pru_num) # Clean shit up, we don't want to be piggies.

Your PyPruss code is awesome, thanks for making that available!
Hi again,
What distro are you running on your BeagleBone? I have an A6 here running fully patched A6A Angstrom and I am getting the following error when trying to compile your PRU code:
cc -Wall -Iapp_loader/include -I/usr/include/python2.7 -D__DEBUG -O2 -mtune=cortex-a8 -march=armv7-a -shared -fPIC -c -o pypruss.o pypruss.c
pypruss.c:1:0: error: bad value (armv7-a) for -march= switch
pypruss.c:1:0: error: bad value (cortex-a8) for -mtune= switch
make: *** [pypruss.o] Error 1
Never mind, I am an idiot. In the wrong window trying to compile this on my desktop machine instead of on the BeagleBone. Compiles with no errors perfectly!
GP, I admire your courage for trying to get this to work already! I have post waiting about PyPRUSS, how to install and so on so your experiences will be highly valued!
Pingback: PyPRUSS – A simple PRU python binding for BeagleBone | Hipstercircuits