Accelerated stepper motors on BeagleBone

pru_accel

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:

Latex 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:

Latex formula

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:

Latex formula

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.

5 thoughts on “Accelerated stepper motors on BeagleBone

  1. 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

  2. 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!

  3. Pingback: PyPRUSS – A simple PRU python binding for BeagleBone | Hipstercircuits

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>