An accessible powered prosthetic -- open-source and affordable. Simple, effective, assistive tech developed to accommodate continuous development. Brought to you by a bunch of hardworking undergraduates enamored with a vision of a future where robotics is widely used to improve the quality of life for all. While new technology endeavors are often profligate, our leg costs about as much as a mid-tier IKEA couch ($550).
Our project and price point rely strongly on the utilization of new tech, such as continuous 3D printed carbon fiber, that has only recently become available. Moreover, our project is completely open-source so that it can be freely manufactured and refined by students, tech hobbyists, and amputees alike. We hope that this project will immediately improve the quality of function for interested amputees and that it might ultimately serve as a platform for proliferating development and future research in the field of assistive technology.
This project was conceptualized by Dr. Tommaso Lenzi at the University of Utah. The materials were all sponsored by his lab, the Bionic Engineering Lab. Go Utes.
The first thing to do is order all of the components. They will take a few days to arrive. We have tried to use as few suppliers as possible to avoid having to make dozens of purchases.
Disassembling the DrillThis project uses a drill because it is a cost-effective way of acquiring a powerful brushless DC motor, motor controller, planetary gear set, and rechargeable lithium-ion batteries.
3D PrintingThe preferred 3D printers are the Markforged Mark Two for its ability to incorporate continuous carbon fiber strands and the Ultimaker S5 for Tough PLA components.
We have printed the shaft-socket connector from continuous strand carbon fiber for its incredible 3D printed weight-to-strength ratio. The front housing, rear housing, and motor case are printed with tough PLA for initial prototyping to reduce costs and set a baseline for these components. As we continue developing, experimentation with the 3D composite printer will be explored to decrease weight and improve the durability of these components.
There are a total of six print files that will need to be made:
- Shaft-socket connector (carbon fiber/Onyx composite)
- Front housing (Tough PLA)
- Rear housing (Tough PLA)
- Motor casing (Tough PLA)
- Spacers (Tough PLA)
- Emergency Stop (Tough PLA)
Shaft-Socket Connector
Use the Markforged Mark Two or other Composite Printer with the following settings
- Nozzle size: Standard
- Print layer height: 0.125 [mm]
- Support: Onyx
- Infill percent: 37%
- Number of carbon concentric layers: 15 concentric rings maximum, all walls reinforced, 37 percent infill, roof and floor layers 5, wall layers 3, triangular infill
- Layers: Reinforce on layers 139 - 278, concentric in the dimension shown
Front Housing, Rear Housing, and Motor Casing
Using the Ultimaker S5 or other Tough PLA Printer with the following settings
- Nozzle size: AA 0.4
- Print layer height: 0.2 [mm]
- Support: PVA
- Infill percent: 10%
Spacers
Using the Ultimaker S5 or other Tough PLA Printer with the following settings
- Nozzle size: AA 0.4
- Print layer height: 0.1 [mm]
- Support: PVA
- Infill percent: 100%
Emergency Stop
Using the Ultimaker S5 or other Tough PLA Printer with the following settings
- Nozzle size: AA 0.4
- Print layer height: 0.06 [mm]
- Support: PVA
- Infill percent: 100%
Assemble the boards:
While waiting for the parts to come in, you need to order two printed circuit boards (PCB). One for the motherboard and another for the ground reaction force (GRF) sensor. You need to send the Gerber files to your favorite fabrication house. The zipped Gerber files can be found on the GitHub repo with the file paths “PCB -> Motherboard” and “PCB -> GRF”. Submit the.zip files with your order. Our boards used the following details:
- Number of Layers: 2
- Material: FR4
- Material Thickness: 0.062 [in]
- Unit Dimensions: 0.972 x 2.350 [in] (Motherboard) and 0.835 x 0.600 [in] (GRF)
- Copper Weight: 1 [oz]
When the boards arrive, solder on the proper components in the reference designators found in the BOM (these can be found in the same file path as the Gerber files).
If you don’t know how to mount surface mount devices (SMD) to a PCB, watch this tutorial.
Assemble the ground reaction force (GRF) sensor:
Cut the 3 [mm] rubber mat into a 52.0 x 52.0 [mm] square and then cut out squares in each corner at 10.0 x 10.0 [mm]. It should look roughly like this.
Its surprisingly hard to cut the rubber in a clean fashion, but try your best to get the lines straight and the cuts proportional. This will make sensor calibration easier and the readings more reliable.
Next, we need to mount the magnet inside of the bottom pyramid (or whatever you are using for pylon attachment). The optimal distance from the bottom-dead-center of the GRF to the top dead center of the magnet is 11.55 [mm], which is also the minimum distance. The maximal distance is 15.15 [mm]
Mount the electronics to the main structure:
Feed the emergency stop (e-stop) wires through the side of the top pyramid flat, then gather the inertial measurement unit (IMU) wires and route both the e-stop and IMU wires down through the pyramid flat through the hole indicated and out the shaft near the magnet (non-sprocket) side.
Route these and the encoder wires cleanly along the housing to the channel of the I-beam, securing them out of the way of the chain and sprocket transmission system.
Feed the wires of the GRF up through the bottom pyramid flat on the I-beam.
Solder the power wires from the motherboard to the battery pack connector terminals (the spatula connectors).
In this image, the soldered wires are the green to the black terminal (negative) and grey to the red terminal (positive).
If you plan on running the leg from a custom battery (without a battery monitoring system ) or a power supply, you will also need to solder in a 250 [Ohm] resistor between the red (positive) and yellow (reference) terminals to replicate the resistance of a thermistor in the Makita battery packs.
NOTE: working with lithium batteries is dangerous so follow proper procedures and make sure that exposed wires are unable to short.
Once all the wires have been routed, we need to connect them to the motherboard. Wire the 20 position and the 5 position Molex connectors as described in the pinouts found on the GitHub in the file paths “PCB->Motherboard”
Before assembling the leg, be sure to have all the parts displayed and listed below.
1. The first step required for assembly is to modify the shaft-socket connector bushings (part 3). The bushings, as shown in the picture below, need to be cut to 0.5 [in] in length using a band saw, and then deburred. The bushings then need to be fit to the shaft-socket connector (part 15).
2. The 0.5 [in] shaft (part 13) then needs to be cut to 103 [mm], threaded and tapped to attach the sprockets. Drill a 5 [mm] hole in the center of both ends of the shaft and tap them for an M6 bolt. Then using 1/2 [in]-13 die, create threads on both external ends of the shaft.
3. On the shaft-socket connector 20 tooth sprocket (part 5b), 4 holes will need to be spaced out equally and drilled around the perimeter of the 0.5 [in] hole. The holes need to be 3.4 [mm] in diameter and 8.89 [mm] (0.35 [in]) from the center of the sprocket (center-to-center measurement).
4. Next take all the 4 [mm] heat set inserts and install on the rear housing (part 10) in the respective 4 [mm] clearance holes. Do the same for the 3 [mm] heat inserts on the top shaft-socket connector (part 15).
5. Fit both shafts between the two halves of the frame along with the 0.5 [in] ID bushings and respective spacers. Place the encoder (part 12b) in the slot next to the shaft-socket connector. Secure all bolts to fit the two parts of the housing together. The left 2 bolts are M4-40 [mm] (part 6c), the 3 bolts in between the shafts are M4-55 [mm] (part 6a), the right three bolts are M4-50 [mm] (part 6b), and the 2 bolts along the edge of the bottom shaft are M4-25 [mm] (part 6d).
6. Secure the top sprocket (part 5b) to shaft-socket connector with the M3-70 [mm] bolts (part 4a).
7. Install motor (part 1) and screw in M4-12 [mm] fasteners and M4 nuts (part 6e) for motor housing (part 11).
8. Install chains (part 7) and master links on both sets of sprockets.
9. Mount the IMU (part 12a) to the shaft-socket connector. Place the GRF (part 12d) in the bottom of the connected frames, followed by the polyurethane rubber, and finally, the pyramid (part 8) to hold the GRF in place.
10. Cut the Makit drill battery holder off of the drill handle so it is flush across the top. Secure the battery terminal (part 1) and motor controller in the cut-down Makita drill housing. Use an M4-12 [mm] screw to fasten the drill housing to the back housing.
You will need both Arduino and Teensyduino to (easily) upload the following code on the Teensy 4.0 to the motherboard (part 12c).
Sensor Verification:
To ensure all sensors and signal wires are in working order, individually upload the code on GitHub in the file path “Verification -> verifying[sensorName]”. Each section of code tests a single sensor and its connections.
Now that everything is working properly, let’s get this leg moving. It is critical to follow these steps precisely on the first startup because improper sensor readings can cause the system to go unstable. If you ran all the previous code and it returned no errors, the sensor readings should be correct, but safety first, follow these steps:
NOTE: Do not connect the USB cable to the motherboard while the leg is powered, it will destroy the Teensy. If you want to connect to the motherboard while the leg is powered, the USB power and board power must be separated.
- Make sure the leg is secured by either the top or bottom pyramid
- Close the e-stop (push the red cap down)
- Connect a computer to the motherboard with USB to micro USB cable
- Upload the code on GitHub in the file path “Integration -> testingPcontroller” to the motherboard
- Disconnect the computer from the motherboard
- Open the e-stop (twist the red cap)
- Power on the leg at 18 [V]
Here is the code to make the leg swing back and forth:
/*
* Auth: Justin Francis
* Date: 11/27/19
* Ver: 1.0
* Sum: this is a position P-controller for the Open source powered prosthetic //leg,
* it oscillates the leg back and forth between 5 deg and 85 deg from vert//ical,
* or it can be controlled with a potentiometer remotely.
* written for teensy 3.2
*
* Mod:
*
*/
#include <Arduino.h>
#include <Encoder.h>
//empirically determined speed curve (x = thetaDotDes [rad/s], y = command vol//tage[rad/s])
#define MOT_CONTROL_CURVE (0.67 - 0.148*log(abs(thetaDotDes)))
//teensy 3.2 limits
#define DAC_RES 1023 //10 bit
#define DAC_MAX 3.3 //[V]
//incremental encoder interrupt pins
#define IncEncA 2
#define IncEncB 3
//use this for potentiometer control
// #define POT_CTRL
//use this for plotting
// #define PLOTTING
//use this for use with battery pack, comment out if using a current limited p//ower supply
//the reason being, the battery pack can supply enough power to tear the leg a//part if a signal
//is being processed incorrectly, this keeps that from happening.
#define TRIAL
//control vars
float P = 30; //proportional gain K_u ~= 35
float k; //conversion factor for theta dot to voltage for the controller output
float motCmd; //value to output to motor controller
float err; //controller error
float thetaDes = 0; //desire joint position [rad]
float theta, thetaDotDes; //joint position [rad], desire joint velocity [rad/s]
#ifdef POT_CTRL
float inputV; //potentiometer voltage [V]
const int POT_PIN = 14; //potentiometer pin
#endif
//motor vars
const int DAC_Pin = A14; //motor speed pin
const int directionPin = 5;
const int enablePin = 4;
//incremental Encoder
Encoder inc(IncEncA,IncEncB); //interrupt attach to digital pins 2 and 3
int counts, countsOld = 0;
const float INC_RES = 4096.0; //resolution of encoder [cpr]
void setup()
{
Serial.begin(115200); //turn on serial port (display) at 115200 baud
//set ports
pinMode(DAC_Pin, OUTPUT);
pinMode(directionPin, OUTPUT);
pinMode(enablePin, OUTPUT);
#ifdef POT_CTRL
pinMode(POT_PIN, INPUT);
#endif
//this is needed for the motor startup sequence
digitalWrite(enablePin, LOW);
delay(200);
#ifdef PLOTTING
Serial.println("theta thetaDes");
#endif
}
/*
* function to control the motor, takes in a desired velocity and commands the motor
* no return
*/
void MotorDrive(float thetaDotDes)
{
if(thetaDotDes >= 0.0)
{
digitalWrite(directionPin, HIGH);
}
else
{
digitalWrite(directionPin, LOW);
}
digitalWrite(enablePin, HIGH);
motCmd = MOT_CONTROL_CURVE; //[V]
motCmd = constrain(motCmd, 0.0, .75); //anything outside of this voltage r //ange cause the controller to pause
analogWrite(DAC_Pin, (int)(motCmd*DAC_RES/DAC_MAX)); //convert command int //o voltage
}
float i = 0; //used for oscillations
void loop()
{
//read sensor
counts = inc.read();
//potentiometer control
#ifdef POT_CTRL
inputV = analogRead(POT_PIN) * (3.3/1024); //[V]
#endif
//standard oscillations
#ifndef POT_CTRL
thetaDes = (PI/2.2*sin(i));
i += 0.0001; //this needs to decrease if the teensy 4.0 is used because of //faster clock
#endif
//convert to radians
theta = (((float)counts/INC_RES))*2*PI; //[rad]
#ifdef POT_CTRL
thetaDes = inputV * (PI/(3.3*2)) - .5; //[rad]
#endif
#ifdef TRIAL
thetaDes = constrain(thetaDes, -.5, 1.5);
#endif
#ifdef PLOTTING
Serial.print(theta*.25);Serial.print("\t");Serial.println(thetaDes*.25);
#endif
//find err
err = thetaDes - theta; //rad
//calc correction
thetaDotDes = P * err; //[rad/s]
//command system
MotorDrive(thetaDotDes);
//feedback (not needed for P controller)
}
Now the leg should be swinging back and forth, stopping for a few seconds at either end of the swing.
After the first trial run, the following steps can be used to power up the leg.
- Open the e-stop (twist red cap)
- Power the leg at 18 [V]
Wireless communication can be most easily carried out through the use of a second Raspberry Pi Zero W for the controller. Communication between the devices can then be achieved through a connection to an existing network or by setting up one of the Pis as an access point.
Upon connection to the same network, Python's socket library can be used to create a server (on the leg) client (the remote) relationship between the two Pis using the code on GitHub in the file path “wireless_communication”.
Server setup (Python):
import socket
# establish connection
HOST = "INSERT SERVER IP ADDRESS"
PORT = "INSERT OPEN PORT" # e.g. port above 5000
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print "socket created"
# check for valid host and port
try:
s.bind((HOST,PORT))
except socket.error:
print "bind Failed"
# establish connection
s.listen(1)
print "Socket awaiting messages"
(conn, addr) = s.accept()
print "connected"
# await and receive messages
while True:
data = conn.recv(1024)
print data
# close connection when finished
conn.close()
Client Setup (Python):
import socket
import Adafruit_ADS1x15
import time
# Configure wireless connection with server (board on leg)
HOST = "INSERT SERVER IP ADDRESS"
PORT = "INSERT OPEN PORT" # e.g. port above 5000
# Connect to server host/port
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
# ADC Converter: Create an ADS1115 ADC (16-bit) instance.
adc = Adafruit_ADS1x15.ADS1115()
# Choose Gain
GAIN = 1
while True:
# Read all the ADC channel values in a list.
# Our joystick has two channels (direction and button)
values = [0]*2
for i in range(2):
# Read the specified ADC channel using the previously set gain value.
values[i] = adc.read_adc(i, gain=GAIN)
# we are currently only interested in the direction value
command = str(values[0])
# send command to the server
s.send(command)
# delay
time.sleep(0.5)
Through an analog-to-digital converter (ADC), the controller's Pi can read and then send the joystick's ADC channel values to the leg's Pi. Finally, through the pySerial library, the leg's Pi can transfer these analog values to the Teensy 4.0, to control the motor's speed and direction.
Add code to thetopof Server code (Python):
import serial
import time
# connect with teensy, choose baud rate
ser = serial.Serial('/dev/tty/ACM0', 9600)
time.sleep(.5)
Add code to the bottom of theserver while loop to communicate with Teensy (Python):
# send command to teensy
ser.write(str(data))
time.sleep(.1)
Teensy Setup (C++):
void setup() {
Serial1.begin(9600);
Serial.begin(9600);
}
void loop() {
String readString = "";
// read in message
while (Serial.available())
{
delay(30);
if (Serial.available() > 0)
{
char c = Serial.read();
readString += c;
}
}
// print message to serial monitor or use value to control motor
if (readString.length() > 0)
{
Serial.println(readString);
}
}
Our use of wireless communication allows control, data collection, code refactoring, and debugging to be more efficient and convenient.
Comments