5 Dof Wearable Robotic Arm

Created by @jaybejaybenot

$${\color{white}06/15/2025 \space project \space statement \space and \space planning}$$

I have an idea!

Problem statement: Two arms can get everything done. But a third one can be nifty, huh? Even if it’s for simple applications, it can still be vastly useful. What if you’re holding everything in your two arms, and you need to open a door? What if you are typing and want to get a glass of water to your lips without breaking momentum? What if, possibly, you are performing a surgery and need some assistance? What if you’re doing some sort of combat sport, and want to get a bit creative? What if you just wanna show off at parties?

Objectives: wearable Robotic arm that follows the following specifications:

5 degrees of freedom: wrist, elbow, shoulder roll, shoulder pitch, gripper.

Graphical User interface that controls arm movements.

minimum payload 100-300 g

Angular accuracy: +/- 2-3 degrees with high-resolution servos or steppers+encoders

Position repeatability: 1-2 mm at full reach. Depends on backlash and compliance

Backlash: <0.5mm. Use zero backlash gearboxes or direct drive.

Delay must be <=500 ms from user input to motor response.

Should stay relatively still when “attached” to the user’s back

Should have a safety mechanism so the user doesn’t get shocked by the 24v battery

I may use the local college’s lab to get some cheap hardware. I doubt they have the exact parts, but they may potentially have potentiometers, resistors, capacitors, Microcontrollers, etc.

PLANNING:

VNH5019 modules for motor driving have already been designed and are available for $15-20 a pop. You see, we’re kinda budget-tight, so we’re going to design a PCB with 4 of the bare VNH5019A-E ICs–$5 each– to attach to our Maxon EC-i 40 + GP 10 A gearbox. Also note that the VNH5019 only drives brushed motors.

$${\color{white}5/16/2025}$$

Stepper motor possibility (but a few hours later)

The brushed DC motor mentioned earlier is gonna set us back 120 dollars each. We can use a NEMA 23 stepper motor with a large gearbox ratio for the desired torque output instead. Currently thinking 20:1, but I’ll have to see a more experienced engineer later to see if that is possible. Because stepper motors kinda can’t be run using the VNH5019, we’ll instead look into the possibilities of the TMC2226 motor driver.

Update: We’re going with the NEMA 23 stepper motor with TMC 2226 motor driver

Possibilities of using in revision: 1. harmonic/planatery reducer for motors to extract highest torque instead 2. Using encoders (AS5600, AS5048A) instead of potentiometer for position feedback. Can be a bit more expensive, and we’ll have to learn some things.

$${\color{white}6/18/2025}$$

Break: Heart project All that planning for the Robotic arm project is stressing me out. So we’re gonna take a break and make a PCB with LED lights in the shape of a heart, lighting up in sequence. This is the schematics I made. Utilizes 2 IC’s: 555 timer to send clock signals in astable mode, and DC4017 to “chase” LEDs in a sequence. Powered by 3 coin batteries, of course. Screenshot 2025-07-01 at 11 36 05 AM

I’ll either add this to the robotic arm, or give it as a trinket to my friend. They gave me a heart keychain a few months ago. Regardless, I’ll make the PCB layout for this in my next break.

I can also possibly modify the design to make it act as an error tester later on the road.

1 hour

$${\color{white}6/22/2026}$$

We’re on vacation, so progress is slow. A wrist-mounted control panel for the arm would be cool as hell, no? We’ll probably have to take on even more disciplines though… Like Embedded UI, power management, and probably ergonomics. Bluetooth for communication, maybe a charge controller for safety, a TFT LCD for affordability and durability, and a MCU for the spi communication; Perhaps an ESP 32 would suit the application. I’ll add this as an idea for next iterations of the arm.

The design for the PCB with the TMC2226-SA motor driver has been commenced, and will probably finish by the end of the day. Moving on next, we’ll figure out the programming and software, and where we can get the mechanical design for the arm. We’ll also tinker around with ROS2, and use Gazebo for simulation (let’s also copy paste the URDF files for the actual arm). My old computer has Ubuntu installed, so let’s do that.

We can potentially use the 24V battery volt source to power the Nano by using a LM2596 voltage regulator.

Today’s progress (Elbow joint driver and MCU): Screenshot 2025-06-27 at 9 21 26 PM

3 hours

$${\color{white}7/1/2025}$$

Just came back after climbing Mount HuangShan, it was peak (ahahaha).

Anyways, gonna crank out the basic PID code for the arduino nano and for driving the NEMA 17. Honestly, I am quite inexperienced on implementing PID, even though I know alot about the theory.

I work in bursts anyways, so hopefully I can get most of it done today. Today’s code:

Screenshot 2025-07-04 at 8 53 39 AM 3.5 hours

$${\color{white}7/2/2025}$$

Further project planning + parts selecting

Ok, so, we’re gonna keep the pcb Designs, but there will no longer be any centralized PCB (like as if it’s a critical hit area in a video game). Essentially, the drivers will be placed right next to the motors in the form of small PCB’s, with terminals connecting from the base, which houses the MCU, voltage regulator, and whatnot. Wait, so the 24v power supply to the motors will have a long wire too? I wonder if that’ll introduce even more problems.

To offset this, we’ll make the power supply slightly larger than 24V to compensate for the higher resistance (26v, 28v). We’ll also add a bulk cap (100-470 uF) decoupling capacitor as well as a ceramic cap (0.1-1uF). right next to Vin of Drivers, as well as ferret beads on the power supply line right before the driver. This is a pretty crude, unelegant method but it’ll probably be enough.

Main reason we’re still using our own design for the motor drivers is because they’re lowk expensive ($30+) and too big to fit in the joints.

Final decision:

Main PCB will sit at base of robotic arm, containing the STM32 Bluepill (STM32F103C8Tx); 2 motor drivers for shoulder yaw and shoulder pitch; and a voltage regulator connected to 24v power source. Elbow joint will have their own motor driver for the TMC2209 NEMA 17

Tmc 2209-LA info: Footprints and symbol Datasheet Finally decided on the specific models: Nema 17 Nema23 (There are links to these in the original doc)

Reminder, Driver compatibility is about matching current, voltage, and inductance to the driver’s chopping frequency and thermal rating! Thus, the TCM2226-SA will be used to drive the NEMA 17, while the dual TCM2209-LA will be used to drive the NEMA 23. Today's schematic progress (so far): Screenshot 2025-07-04 at 8 57 01 AM

Iteration for Mark II: Just watched 稚晖君’s video on his dummy robotic arm, and felt kinda envious when compared to my own design. Thus, I have a wishlist for Mark II in case I build a second robotic arm: CAN bus Per-joint MCUs Force sensors (Encoders instead of potentiometers) Sleeker driver IC layouts Smarter GUI Power optimization AI vision (optional) glove to control robotic arm movement

For now, we just need to worry about making A robotic arm.

Taking notes on TMC2209 driver features:

Stealthchop: Silent operation, good for lower velocities

Significantly improved microstepping with low-cost motors - Motor runs smooth and quiet - Absolutely no standby noise - Reduced mechanical resonance yields improved torque

Spreadcycle: Good for higher velocities.

After a few hours, I have finished the schematic FULLY, and have reviewed it to have no errors (so far, I will review it once more when I prepare for PCB layout). Here is the full schematic:Screenshot 2025-07-04 at 2 53 10 PM

I should probably add redundancies for overheating, since running all these on the same PCB will fry it easily. But I feel like moving on to software for now, so that’ll have to wait until I prepare for the PCB layout.

Btw ignore PB5. I've already removed that label; it's just a small error.

8 hours total

$${\color{white}7/6/2025}$$

Ok, platformIO is acting up. Not only did I lose the Main.cpp file for the main PCB board, but I literally can’t find the folder itself. The folder is also nowhere to be seen on the project tab of VScode.

So, we’re gonna ditch platformIO for STM32CubeIDE.

Hip hip hooray! We get to rewrite the ENTIRE code for the main PCB, but this time in HAL =D! I can’t WAIT to basically learn a whole different language =DDDDDDDD.

Just played around with STM32cubeIDE for a while, and learned from guides/documentations on youtube. Phil’s Lab my GOAT fr

Screenshot 2025-07-06 at 4 02 45 AM

Ran into some problems with the clock configurations, seems like the APB1 peripheral clock frequency is too low.

Screenshot 2025-07-06 at 4 02 58 AM

After some tweaking and painfully reading the STM32F1 reference manual, this minor speedbump has been remedied.

Screenshot 2025-07-06 at 4 03 08 AM

Pins are configured, and so are the clocks. Now all we have to do is figure out how to set the STM32 up to receive serial from the Mac, what to do with certain info, and create the GUI. Finally, we’ll create the 3D physical model and we’ll be done.

3 hours

Note: Kinda been dodging a core problem right now, but we gotta confront the fact that carrying around a 24v battery can kinda cause some lowkey agonizing pain if something goes wrong. So we'll have to overload on safety mechanisms when we get to mechanical design. We'll also have to make additions on our PCB to further prevent overheating

$${\color{white}7/17/2025}$$

Just came back from camp, back to the grind. This also means we’ll have to ramp up productivity. A review of the arm: Shoulder 1. will have 3 D.O.F. 2. 2 NEMA motors will be driven by main PCB, 3. 3rd one will be driven by Arduino Nano interfaced via I2C STM32F1 will be master MCU. We have 48 MHz of processing power and we will use the full 48 MHz of it! 4.Power source: 24 volts, will be placed in a heavily insulated box to prevent shocks Elbow: A singular Arduino nano and TMC driver for Nema 17. Further iterations might utilize belts to lower torque on elbow Wrist: Simple MG90 servo. Just use PWM vro GUI: will be built in tkinter. Interfaces with the Uart serial to STM32. Alright, today we’ll get serial done, and begin on the mechanical design.

This means we'll gonna have to LEARN UART programming, including HAL and serial parsing. Here's the code so far: Screenshot 2025-07-18 at 9 26 53 PM

5 Hours

$${\color{white}7/23/2025-7/25/2025}$$

I have chipped down the DRC more and more on clearance violations, and have connected the pins together finally. We have decided to use GND and power planes for noise and emi reduction, though it will be a bit more expensive.

We have also finally added PCBway constraints into the settings. Thus, we’ll have to switch from 0402 components to larger ones, because lowk they’re unable to tolerate the close proximity of the components.

Screenshot 2025-07-26 at 5 03 54 AM

Screenshot 2025-07-26 at 5 04 09 AM

We WILL add heat pours in the future—after finishing the DRC.

I have also met with a longtime friend, a Umich alumni in robotics about this design. he says it’s good so far, so that’s something to think about =].

12 hours

May seem like a long time, but trust me, I haven’t done something this complex before so I MUST make sure everything runs as good as possible.

$${\color{white}7/26/2025}$$

Yayyyy, mechanical design! Woohoo, Yippee.

Constraints:

  1. 0.5-1 meters

  2. 2 kg payload minimum.

  3. Fully covering circuitry

  4. Openable via screws to access circuitry and wires

  5. Separate compartment for wires and circuitry

  6. Hexagonal comb design. Will have one side honeycomb, the other three sides will be solid

  7. 25:1 gearboxes, compromise for torque and speed

  8. 7075 or 6061 aluminum for linkages. More Carbon fiber towards end of arm. Steel for joints and base.

  9. Planetary gearboxes for zero backlash if we can afford it (probably not for the first design. May be employed for future iterations )

  10. Modular end factor for different attachable functions. Module will have plugs that attach to end factor to communicate to a microcontroller on what to do. Will also have latches to maintain connection.

Alright, let's get started.

The specs are as follows: shoulder (base) will have 3 degrees of freedom to mimic actual human arm. Will contain the main PCB, alongside slave MCU PCB to drive the additional motor. The motors will all be NEMA 23s.

The elbow joint will be 1 D.O.F. and uses arduino nano + TMC2226SA for the Nema 17. Will also interface with main PCB. Only comment lightly on the fact that we're using I2C protocol for the ~.33m distance between the main mcu and slave mcu.

The wrist will use SG90S microservo. Optional, actually.

Link length from** shoulder joints to elbow joint will be .33 m. From elbow to wrist is .46 m.**

All 3 shoulder motors will be placed closely together at the base to reduce torque

Joints decisions:

  1. Shoulders:

  2. Options:

  3. Welded Pivot Bracket joint.

  4. Pros:

  5. extremely strong and rigid,

  6. zero backlash.

  7. Cons:

  8. Permanent. Make a mistake? Womp womp, you can’t fix it. You also can’t take them out, so they’re not modular

  9. You kinda need to know how to weld, and get welding equipment (DEALBREAKER)

  10. Bolt on bracket system (WE’RE USING THIS ONE)

  11. Pros:

  12. Still pretty strong

  13. Bolted, not welded (NOT DEALBREAKER)

  14. Looks pretty clean

  15. Elbow:

  16. Clevis + Pin Joint.

  17. Pro:

  18. Can be laser cutted

  19. Pretty compact

  20. Strong enough

  21. VEX robotics people use it

  22. Con:

  23. Need retainer clips or shoulder bolts

  24. Needs TIGHT tolerances

Let’s not talk about end effector for now.

Of course, we’ll also need:

  1. Bearings

  2. 10 mm bore, 26mm OD Flanged bearing

  3. Shaft link hub / coupler

  4. Shaft collar

  5. Mounting brackets or housing to hold bearings in place and constrain all movements except rotation

  6. One side must hold rigidity, (bearing pocket or press-fit)

  7. Spacer(s) and Shoulder Bolt / Axle Bolt

  8. Prevents axial shrifting, keeps rotations smooth

  9. Space prevents the bearing from rubbing against joint arm or frame. Shoulder bolt has a section that fits bearing ID perfectly.

We’ll mount the Nema 17 at elbow. But if we NEED to optimize torque, then we can just place them at the back with a belt loop.

rectangular holes are probably for ventilation and weight \; we’ll employ that.

1 Hour

7/27/2025

Mechanical design marathon.

Aluminum 6061 is very expensive, datasheet . So here’s the plan:

  1. At the base, we’ll use some sort of steel alloy. Cheaper, but 3 times the density

  2. Aluminum 5052 could be a substitute.

I feel the plan right now is model a rough diagram of the arm on paper, create a multidimensional function of stress, strain, load, and torque in terms of density of the material selected, and the width/girth of the arm. THEN we'll choose the components based on our application.

Alright, we’ve finished the analysis of the arm at it’s worst scenario: Perfectly horizontal.

Screenshot 2025-07-30 at 5 43 59 PM

30 minutes

Not gonna do other angles, because we’ve already accounted for worst case scenario. Also, it’s probably not likely that it’ll just collapse on itself if i raise it perfectly vertical even if the axial stress is at its strongest.

Time for parts selection!

Code for calculations:

import math

==== Inputs ==== Geometry (in meters) L1 = 0.33 # Length of Link 1

L2 = 0.46 # Length of Link 2

w = 0.1 # Width of rectangular cross-section

h = 0.1 # Height (thickness) of rectangular cross-section

Material (Aluminum 6061-T6) E = 68.9e9 # Modulus of Elasticity (Pa)

sigma_y = 276e6 # Yield Strength (Pa)

tau_y = 207e6 # Shear Strength (Pa)

fatigue_strength = 96.5e6 # Fatigue Strength (Pa)

Density = 2700 #kg/m3

Masses (kg) m1 = L1 * Density * w * h # Mass of Link 1

m2 = L2 * Density * w * h # Mass of Link 2

payload = 4200 # force from punch or weight (Newtons)

Angle of payload force relative to the link axis (in degrees) theta_deg = 0

theta = math.radians(theta_deg)

Safety Factor FoS = 1.5

Gravity g = 9.81 # m/s2

==== Derived geometric properties ==== A = w * h # Cross-sectional area

I = (w * h**3) / 12 # Second moment of area

c = h / 2 # Distance from neutral axis

==== Forces ==== P_parallel = payload * math.cos(theta) # Axial component

P_perp = payload * math.sin(theta) # Transverse (bending) component

==== Torques ==== tauelbow = m2 * g * (L2 / 2) + Pperp * L2

taushoulder = (m1 * g * (L1 / 2) +m2 * g * (L1 + L2 / 2) +Pperp * (L1 + L2))

==== Stresses ==== sigmaaxial = Pparallel / A

sigmabending = ((Pperp * (L1 + L2) + (m1 * g * L1/2) + (m2g(L1 + L2/2))) * c) / I

sigmatotal = sigmaaxial + sigma_bending

AFoS = 276e6 / sigma_total

==== Strain ==== strain = sigma_total / E

==== Deflection ==== Tip deflection of link 2 under end load only delta = (P_perp * L2**3) / (3 * E * I)

==== Checks ==== print(=== ROBOTIC ARM STRESS REPORT ===)

print(fAxial Stress: {sigma_axial/1e6:.2f} MPa)

print(fBending Stress: {sigma_bending/1e6:.2f} MPa)

print(fTotal Stress: {sigma_total/1e6:.2f} MPa)

print(fStrain: {strain:.5e})

print(fDeflection (L2): {delta*1000:.2f} mm)

print(fElbow Torque: {tau_elbow:.2f} Nm)

print(fShoulder Torque: {tau_shoulder:.2f} Nm)

print(ffactor of safety: {AFoS})

print(\n=== SAFETY CHECK ===)

print(fYield Limit ({sigma_y/FoS/1e6:.2f} MPa) : {'Yessir!' if sigma_total < sigma_y / FoS else 'No =['})

print(fFatigue Limit ({fatigue_strength/FoS/1e6:.2f} MPa): {'Yessir!' if sigma_total < fatigue_strength / FoS else 'No =['})

print(fDeflection Check (< {L2/250:.2e} m): {'Yessir!' if delta < L2/250 else 'No =['})

We’ll play around with the variables until we find something desirable.

*15 minutes*

Ok so, it turns out that if we were to go with a 0.1 m x 0.1 m approximate cross sectional area of the arm using aluminum 6061, we’ll need 28 Nm of torque… Nema 17 can only produce 0.14-0.75 nm max. We’ll have to redo the PCB to change the Nema 17 to a Nema 23. On top of that, aluminum 6061 is frick-ton expensive even if hollow(~$200 for one link), so we’ll have to find other materials and compromise.

Material alternatives:

  1. Steel 1018

  2. 370 MPa yield

  3. A36 steel

  4. 248.2 MPa yield

Material selection later. Let’s just design the arm now.

We’ll begin with the joint, constraint definitions:
1. Houses Nema 23 motor

  1. Houses the gearbox along the Nema 23 motor

  2. Rotate well

  3. Holes for wires to connect with motor

There will be 2 type of joints; the shoulder joint, and elbow joint.

That’s basically it honestly

We originally considered HTD5 timing pulleys, but that’s too mechanically advanced for me. So we’re going to use NEMA23 **worm gearbox (50:1)** as simple add ons to the motor. Cheaper than planetory, and we don’t need industrial level precision so we may get away with $150 total.

Nema 23 sizes

Just learned the method is called a *Euler angle joint** chain mimicking the glenohumeral (shoulder) joint

The first base will be larger than the second base to compensate for having to carry extra weight. We might have to “overengineer: the coupling between the bases to prevent backlash and slippage. Of course the final rotational joint will just be bolt on bracket.

Enter image alt description example image

Enter image alt description inexpensive planetory gearboxes

7/29/2025

(TORQUE POTENTIALLY TOO LOW)

Ugh, it seems we’ll have to go searching for gearboxes again.

We’ll use Aliexpress if we need to.

Screenshot 2025-07-30 at 5 45 40 PM

We’ll add bearings to this sliding point later to both reduce stress on the main motor and to reduce friction. Oh, and the lid for this part will be honeycomb pattern for aesthetics and saving on material.

I’m back with the design, and I just realized.

LOOK AT THE SIZE OF THAT THING HOLY C- BANANA FOR SCALE BY THE WAY.

Screenshot 2025-07-30 at 5 45 59 PM

Realistically, we have 2 options:

  1. Redesign the shoulder from scratch

  2. Severely cut down on the container size for each motors.

If we go with number 2, which is the most alluring option by far, we might just have the motors exposed. Cuts back on cost and torque too, I guess.

Main issue comes from the motor + gearbox, which is 0.15 m by itself. Holy-

Direct motor mount, we’ll be using this

Screenshot 2025-07-30 at 5 46 28 PM

Alright, much more manageable now. (Banana for scale)

5 hours

7/29/2025

Continued with the mechanical design. We had to dig through various sources to get semi fluent. In short, we’ve decided to use bracket mounts to hold the motors in place and flanged couplers to get it attached to stuff, ball bearings and honeycomb pattern pending. Of course, there are holes for the I2C wires to go through.

Finally, we’re finished.

Screenshot 2025-07-30 at 5 46 51 PM

Now all we have to do is provide the wiring diagram, and BOM. Screenshot 2025-07-30 at 4 51 13 PM Screenshot 2025-07-30 at 4 46 00 PM Screenshot 2025-07-30 at 4 37 42 PM Screenshot 2025-07-30 at 4 36 12 PM Screenshot 2025-07-30 at 4 31 14 PM

This is the wiring for now. Of course, *We will add clips during assembly to hold the wires down. *

**8 hours** (I didn’t know much about mechanical design)

Even if there are many flaws in the design; Ngl, this has been a wild month, and a wild ride. I’ll never forget the 15 hour PCB marathon on the plane. Oh, and I hope my journals entertained somebody.

8/1/2025

A healthier person would take this moment for rest. Hang out with friends, go to the gym, touch grass, go outside. 

Which is why today we’ll revise our project a little for feasibility. 

To do list:

  1. Add heat sinks to PCB for thermal control

  2. Refine firmware code for serial parsing

  3. Refine GUI for serial interfacing

  4. Add aesthetic designs to 3d model, honeycomb patterns is a must. 

Actually done list:

  1. Rewired PCB for more feasibility, including complete connection of USBC footprint, heat pours. Made ABSOLUTELY sure the PCB is wired correctly and SHOULD function correctly (Nothing is ever guaranteed, though). Reduced price by 2x-5x by replacing blind Vias with thru Vias. 

  2. Firmware code completely reworked to utilize 2 CLK signals for separate operation of motors, rather than having 1 move after another

  3. Absolutely refined 3d model. Now uses ergonomic shapes, hollowed out parts and honeycomb surfaces for weight optimization, compacted the shoulder joints, and made it look much cooler =)

8/9/2025

Worked on firmware. 

Because we need motors to move at the same time, we can’t just duplicate the move motor functions and call it a day. 

We need a non-blocking, timer-driven approach, a motion planner (or motion blocks), and a simple sync protocol between the two MCUs.

Understanding all this is going to be painful, though. 

#define Vcp2_Pin GPIO_PIN_0

#define Vcp2_GPIO_Port GPIOA

#define MS1B_Pin GPIO_PIN_1

#define MS1B_GPIO_Port GPIOA

#define MS2B_Pin GPIO_PIN_2

#define MS2B_GPIO_Port GPIOA

#define STNDBY2_Pin GPIO_PIN_3

#define STNDBY2_GPIO_Port GPIOA

#define DIR2_Pin GPIO_PIN_4

#define DIR2_GPIO_Port GPIOA

#define STEP2_Pin GPIO_PIN_5

#define STEP2_GPIO_Port GPIOA

#define VCC_IO2_Pin GPIO_PIN_6

#define VCC_IO2_GPIO_Port GPIOA

#define SPREAD2_Pin GPIO_PIN_7

#define SPREAD2_GPIO_Port GPIOA

#define D2_Pin GPIO_PIN_0

#define D2_GPIO_Port GPIOB

#define STEP_Pin GPIO_PIN_1

#define STEP_GPIO_Port GPIOB

#define DIR_Pin GPIO_PIN_2

#define DIR_GPIO_Port GPIOB

#define MS1A_Pin GPIO_PIN_12

#define MS1A_GPIO_Port GPIOB

#define MS2A_Pin GPIO_PIN_13

#define MS2A_GPIO_Port GPIOB

#define VCC_IO_Pin GPIO_PIN_14

#define VCC_IO_GPIO_Port GPIOB

#define ISR1_Pin GPIO_PIN_15

#define ISR1_GPIO_Port GPIOB

#define ISR2_Pin GPIO_PIN_8

#define ISR2_GPIO_Port GPIOA

#define USB_D__Pin GPIO_PIN_11

#define USB_D__GPIO_Port GPIOA

#define USB_D_A12_Pin GPIO_PIN_12

#define USB_D_A12_GPIO_Port GPIOA

#define Vref_Pin GPIO_PIN_3

#define Vref_GPIO_Port GPIOB

#define SPREAD1_Pin GPIO_PIN_4

#define SPREAD1_GPIO_Port GPIOB

#define STNDBY_Pin GPIO_PIN_9

#define STNDBY_GPIO_Port GPIOB

#include stm32f1xx\_hal.h

#include <string.h>

#include <stdio.h>

#include <stdlib.h>

#include <math.h>

#define TIMER_CLK_HZ 72000000UL

#define CONTROL_FREQ_HZ 500 // 2ms control tick

#define MICROSTEPS 8

#define STEPS_PER_REV 200

#define EFFECTIVE_STEPS_PER_REV (STEPS_PER_REV * MICROSTEPS)

UART_HandleTypeDef huart1;

typedef struct {

    TIM_HandleTypeDef *htim;

    uint32_t tim_channel;

    GPIO_TypeDef* dir_port;

    uint16_t dir_pin;

    int32_t total_steps;  // total steps to move (signed for dir)

    int32_t steps_done;

    float target_rpm;

    float current_freq;   // in Hz (step pulses)

    float target_freq;    // in Hz

    float accel;          // in steps/second2

    uint8_t running;

} motor_t;

motor_t motor1, motor2;

uint8_t rx_buf[64];

uint8_t rx_idx = 0;

// Forward declarations

void motor_start(motor_t *motor);

void motor_stop(motor_t *motor);

void control_tick(void);

void process_command(char *cmd);

void uart_printf(const char *fmt, ...);

void SystemClock_Config(void);

void MX_GPIO_Init(void);

void MX_USART1_UART_Init(void);

void MX_TIM2_Init(void);

void MX_TIM3_Init(void);

void MX_TIM6_Init(void);

// --- Motor timer IRQ handlers ---

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {

    motor_t *motor = NULL; 

    if(htim->Instance == TIM2) motor = &motor1;

    else if(htim->Instance == TIM3) motor = &motor2;

    if(motor && motor->running) { 

        motor->steps_done++;

        if(motor->steps_done >= abs(motor->total_steps)) {

            motor_stop(motor);

            char msg[32];

            snprintf(msg, sizeof(msg), DONE %s\n, (motor == &motor1) ? 1 : 2); 

            uart_printf(msg);

        }

    }

}

// --- Motor control functions ---

void motor_start(motor_t *motor) {

    motor->steps_done = 0;

    motor->running = 1;

    // Set direction pin

    if(motor->total_steps >= 0)

        HAL_GPIO_WritePin(motor->dir_port, motor->dir_pin, GPIO_PIN_SET);

    else

        HAL_GPIO_WritePin(motor->dir_port, motor->dir_pin, GPIO_PIN_RESET);

    // Start timer PWM output (step pulses)

    HAL_TIM_PWM_Start_IT(motor->htim, motor->tim_channel);

}

void motor_stop(motor_t *motor) {

    motor->running = 0;

    motor->current_freq = 0;

    motor->target_freq = 0;

    HAL_TIM_PWM_Stop_IT(motor->htim, motor->tim_channel);

}

// Convert frequency (Hz) to timer ARR and PSC for toggling at correct freq

// Timer toggles output pin on compare match -> output frequency = timer_clk / (PSC+1)/2/(ARR+1)

void freq_to_timer_params(float freq, TIM_HandleTypeDef *htim, uint32_t *psc, uint32_t *arr) {

    if(freq <= 0) {

        *psc = 0;

        *arr = 0xFFFF;

        return;

    }

    // Find PSC and ARR to fit freq within 16-bit ARR range

    // Try PSC from 0 upwards

    for(uint32_t prescaler = 0; prescaler < 0xFFFF; prescaler++) {

        uint32_t arr_candidate = (uint32_t)((TIMER_CLK_HZ / ((prescaler + 1) * 2 * freq)) - 1);

        if(arr_candidate <= 0xFFFF) {

            *psc = prescaler;

            *arr = arr_candidate;

            return;

        }

    }

    // If no valid pair found, clamp to max ARR

    *psc = 0xFFFF;

    *arr = 0xFFFF;

}

void motor_update_timer(motor_t *motor) {

    if(!motor->running)

        return;

    uint32_t psc, arr;

    freq_to_timer_params(motor->current_freq, motor->htim, &psc, &arr);

    __HAL_TIM_DISABLE(motor->htim);

    motor->htim->Instance->PSC = psc;

    motor->htim->Instance->ARR = arr;

    motor->htim->Instance->EGR = TIM_EGR_UG; // Update registers

    __HAL_TIM_ENABLE(motor->htim);

}

// --- Control tick, called at 500Hz ---

void control_tick(void) {

    motor_t *motors[2] = {&motor1, &motor2};

    for(int i=0; i<2; i++) {

        motor_t *m = motors[i];

        if(!m->running)

            continue;

        // Update freq toward target with accel

        float freq_diff = m->target_freq - m->current_freq;

        float max_delta = m->accel / CONTROL_FREQ_HZ;

        if(fabs(freq_diff) <= max_delta)

            m->current_freq = m->target_freq;

        else

            m->current_freq += (freq_diff > 0 ? max_delta : -max_delta);

        motor_update_timer(m);

    }

}

// --- UART receive and command parser ---

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {

    if(huart->Instance == USART1) {

        uint8_t b = rx_buf[rx_idx++];

        if(b == '\n' || rx_idx >= sizeof(rx_buf)-1) {

            rx_buf[rx_idx] = 0;

            process_command((char*)rx_buf);

            rx_idx = 0;

        }

        HAL_UART_Receive_IT(&huart1, &rx_buf[rx_idx], 1);

    }

}

void process_command(char *cmd) {

    int motor_id = -1, steps = 0, rpm = 0;

    float accel = 1000.0f; // default accel steps/s2

    int num_args = sscanf(cmd, MOVE %d %d %d %f, &motor_id, &steps, &rpm, &accel);

    if(num_args < 3) {

        uart_printf(ERR Invalid command\n);

        return;

    }

    motor_t *motor = NULL;

    if(motor_id == 1) motor = &motor1;

    else if(motor_id == 2) motor = &motor2;

    else {

        uart_printf(ERR Invalid motor ID\n);

        return;

    }

    if(motor->running) {

        uart_printf(ERR Motor busy\n);

        return;

    }

    if(rpm <= 0 || rpm > 3000) {

        uart_printf(ERR RPM out of range\n);

        return;

    }

    motor->total_steps = steps * MICROSTEPS;

    motor->target_rpm = (float)rpm;

    motor->accel = accel;

    motor->current_freq = 0;

    motor->target_freq = ((rpm / 60.0f) * EFFECTIVE_STEPS_PER_REV);

    motor_start(motor);

    uart_printf(OK MOVE %d %d %d\n, motor_id, steps, rpm);

}

// --- UART printf helper ---

void uart_printf(const char *fmt, ...) {

    char buf[128];

    va_list args;

    va_start(args, fmt);

    int len = vsnprintf(buf, sizeof(buf), fmt, args);

    va_end(args);

    if(len > 0) {

        HAL_UART_Transmit(&huart1, (uint8_t*)buf, len, HAL_MAX_DELAY);

    }

}

// --- Main and peripheral init ---

int main(void) {

    HAL_Init();

    SystemClock_Config();

    MX_GPIO_Init();

    MX_USART1_UART_Init();

    MX_TIM2_Init();

    MX_TIM3_Init();

    MX_TIM6_Init(); // control timer

    // Setup motors

    motor1.htim = &htim2;

    motor1.tim_channel = TIM_CHANNEL_1;

    motor1.dir_port = GPIOA; // example

    motor1.dir_pin = GPIO_PIN_0; // example

    motor2.htim = &htim3;

    motor2.tim_channel = TIM_CHANNEL_1;

    motor2.dir_port = GPIOA; // example

    motor2.dir_pin = GPIO_PIN_1; // example

    rx_idx = 0;

    HAL_UART_Receive_IT(&huart1, &rx_buf[rx_idx], 1);

    // Start control timer for 500 Hz tick

    HAL_TIM_Base_Start_IT(&htim6);

    while(1) {

        // Main loop idle. All motor control done in timer IRQs.

    }

}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim6) {

    if(htim6->Instance == TIM6) {

        control_tick();

    }

}

**9 hours**

I still have barely any clue how PSC and ARR work  😭

8/14/2025

So firmware and PCB are absolutely done (if not a few debugging necessary). That leaves mechanical design.

Our current design is bulky, and not very marketable. So an overhaul must be made.

Firstly, the shoulder for 3 degrees of freedom is too “inelegant”, merely a cylindrical container to install brackets and motors on. Thus, we’ll utilize a design inspired from human biology, the transformation of spherical CSKPs, a topic in this research paper

Which means we’ll consider the use of ball and socket joints.

In terms of linkage, we attempted to replicate something like this:

The image to the right is our shape for the bicep. It’s not finished, more refinements necessary in the future. 

Ok, let’s just stick with the basic joint and cylinder design now.

We’ll take inspiration from this research paper  for the shoulder joint

This is our result. This base will use either steel or aluminum 6061 (expensive!) since it has the most load.

**5 hours**

8/16/2025

Worked on the arm even more. Designed the wearable base with honeycomb lid for access and structural integrity. Gap is inside the base to fit the PCB and wiring. The flat part of the base will press against the wearer’s back while being strapped via the backpack strappers. Also added detachable/modular shoulder pads (depending on how you feel like calling them) so that it can withstand blunt force in certain scenarios. Linkage design pending.

8/17/2025:

Final Design:

In the name of ergonomics and marketability, We will base the forearm design on the human forearm, backed by fillet and ribs for structural support.