Force-Controlled Gripper

A DIY gripper that controls how hard it squeezes, for under $40

2026

Once our robot arm was working, it needed a hand. Most DIY grippers just open and close, but we wanted one that could control how hard it squeezes. That way the same gripper can hold a 3D-printed cube firmly and still pick up a potato chip without crushing it. The whole thing cost $36.48.

My brother Steve designed the controller and the PCB. I did all of the mechanical design and the assembly, and I built the test rig we used to measure the motor's torque. Steve has a write-up that covers the controller and the electrical side in detail. On this page I'm going to cover the mechanical design and the current loop, because together they're what turn motor current into grip force.

The gripper on the robot arm, picking and placing a cube and a grape

Why Force Control?

Most DIY grippers run on a hobby servo, the kind that comes in an Arduino kit. The problem is that you can only tell a servo where to go, not how hard to push. It just pushes as hard as it can until it gets there. That's fine for a block of wood, but not so great for a potato chip.

Grippers that can control force usually use a brushless motor and an FOC driver. That works really well, but good FOC drivers are expensive. Source Robotics' SSG-48 gripper, for example, sells for €599.

Steve came up with a cheaper idea in spring 2026, while he was taking an electric drives course. During a lab on controlling DC motors, he realized a plain brushed DC motor could do the same job for a fraction of the price.

Mechanical Design

The motor spins, but the jaws need to slide in a straight line. The simplest way to turn rotational motion into linear motion is a rack and pinion. The pinion is a gear on the motor's output shaft, and the rack is a straight strip of teeth on the jaw. As the pinion turns, it pushes the rack along, and the jaw slides with it.

The jaws opening and closing, then a look inside at the rack and pinion

Both jaws move, so each one has its own rack. The two racks sit on opposite sides of the pinion, so when the pinion turns, it drives them in opposite directions. The jaws always move the same distance toward or away from each other, which keeps the gripper centered on whatever it's holding. Each jaw is mounted on a carriage that rides along a 100 mm linear rail. The rail takes the side loads and twisting, so the rack and pinion only has to push the jaws along it.

Front view of the gripper CAD with the jaw fingers hidden, showing a 16-tooth pinion with one rack above it and one rack below it
Looking in from the front. The pinion sits between the two racks, one for each jaw

Grip force

The pinion is a 16-tooth spur gear with a module of 1.889 mm. The pitch diameter is the module times the number of teeth, so its pitch radius \(r\) is:

\[r = \frac{m\,z}{2} = \frac{1.889 \times 16}{2} = 15.1\ \text{mm}\]

A rack and pinion works like a wheel rolling along the ground. When the pinion turns through an angle \(\theta\), each rack moves by the arc length at the pitch circle. Since the two jaws move in opposite directions, the gap between them closes twice as fast:

\[x_{\text{jaw}} = r\,\theta, \qquad \Delta_{\text{gap}} = 2\,r\,\theta\]

Torque works the same way in reverse. When the jaws close on an object, the object pushes back on each jaw with a force \(F\), and that force acts on the pinion at the pitch radius. There are two racks, so the torque from the motor gets shared between them:

\[\tau = 2\,F\,r \quad\Longrightarrow\quad F = \frac{\tau}{2r}\]

This is the key equation for the whole gripper. The grip force is just the motor's torque scaled by a fixed number set by the gear, so if we can control the torque, we can control the force. And as the next section shows, a DC motor's torque is set by its current. On the test rig we measured 0.310 N·m/A at the gearbox output, once the current gets past the gearbox friction. The fitted line crosses zero torque at about 0.19 A, which we'll call \(i_0\). Putting it all together gives the grip force straight from the current:

\[\begin{aligned} F &= \frac{k_T\,(i - i_0)}{2r} = \frac{0.310\ \text{N}\cdot\text{m/A}}{2 \times 0.0151\ \text{m}}\,(i - i_0) \\ &\approx 10.3\ \text{N/A} \times (i - i_0) \end{aligned}\]

So every extra amp of current adds about 10 N of squeeze on each jaw. At 0.6 A, we measured 130 mN·m of torque, which works out to about 4.3 N per jaw. This ignores any losses in the rack and pinion itself.

The jaws don't hold things by squeezing alone. It's the friction from that squeeze that stops an object from sliding out. Each jaw gives a friction force of up to \(\mu F\), and there are two jaws, so the heaviest object the gripper can hold is:

\[m_{\max} = \frac{2\,\mu\,F}{g}\]

Here \(\mu\) is the friction coefficient between the jaws and the object, so how much the gripper can hold depends on what it's picking up. A heavier or more slippery object needs more current, and a fragile one like a chip needs just enough current to hold it without breaking it.

Isometric view of the gripper CAD: a cream housing with the motor mount at the back, a black cover on top with a connector, and two black jaws at the front
The full gripper assembly in CAD
Top view of the gripper CAD looking down at the cover and connector, with the two jaws pointing up
Top view
Side view of the gripper CAD, with the mounting flange on the left and a jaw on its carriage on the right
Side view

Control Design

The reason a DC motor works here is that its torque is proportional to the current going through it:

\[\tau = k_T\, i\]

Here \(\tau\) is the torque, \(i\) is the current, and \(k_T\) is a constant for the motor. If you can control the current, you can control the torque, and that means you can control the grip force. So the whole gripper really comes down to one job: holding the motor current at whatever value we ask for. There are also speed and position loops wrapped around it for opening and closing the jaws, but the current loop is what does the gripping, so that's what I'll walk through.

Motor model

Electrically, a DC motor behaves like a resistor, an inductor, and a voltage source all in series. If you add up the voltages around that loop, you get:

\[V_a = R_a\, i_a + L_a \frac{di_a}{dt} + e_b, \qquad e_b = k_E\, \omega\]

\(V_a\) is the voltage we apply, \(R_a\) and \(L_a\) are the resistance and inductance of the windings, and \(e_b\) is the back EMF. Back EMF is a voltage the motor makes as it spins, and it grows with speed (\(\omega\)). It's the reason you can't just set a voltage and call it a day. Put 3 V across the motor and you'll get one current when it's stalled and a much smaller current once it starts spinning.

Left: the motor drawn as a voltage source, resistor, inductor and back-EMF source in series. Right: the current loop block diagram, where the current setpoint minus the measured current feeds a PI controller, back EMF is subtracted from its voltage, and the result drives the motor windings 1 over (R_a + s L_a)
Left: the motor's electrical model. Right: the current loop, with back EMF entering as a disturbance

The good news is that back EMF changes a lot more slowly than the current does. So instead of modeling it, we treat it as an outside disturbance that the controller has to push against. Once you drop it and take the Laplace transform, you're left with a simple first-order transfer function from voltage to current:

\[P(s) = \frac{I_a(s)}{V_a(s)} = \frac{1/R_a}{1 + s\,\tau_e}, \qquad \tau_e = \frac{L_a}{R_a}\]

We measured our motor ourselves: \(R_a\) came out to 3.0 Ω at stall, and \(L_a\) was 3.5 mH on an LCR meter. That gives an electrical time constant \(\tau_e\) of 1.17 ms, which puts the motor's only pole at 857 rad/s (about 136 Hz).

PI controller

There's no integrator in this transfer function, which makes it what's called a type 0 system. In practice, that means a plain proportional controller would always leave the current sitting a little below the target. Adding an integral term fixes that, since the integral keeps building up until the error is zero. So we went with a PI controller:

\[C(s) = k_p + \frac{k_i}{s} = k_p\, \frac{s + k_i/k_p}{s}\]

Writing it the second way shows that the PI controller adds a zero at \(s = -k_i/k_p\). That zero ends up being what makes tuning so easy.

Gain selection

If we put the controller's zero right on top of the motor's pole, by setting \(k_i/k_p = 1/\tau_e\), the two cancel each other out. What's left of the open-loop transfer function is just a pure integrator:

\[L(s) = C(s)\, P(s) = \frac{k_i}{R_a\, s}\]

An integrator is about the nicest loop you can ask for. Its gain drops at a steady 20 dB per decade, and its phase sits at −90° at every frequency. That means the phase margin is 90° wherever the crossover lands. Once you close the loop, the current follows the setpoint smoothly, with no overshoot.

Cancelling the pole only sets the ratio between the two gains, though, so we need one more equation to pin them down. If we pick a crossover frequency \(\omega_c\) and set the loop gain to 1 there, we get both gains:

\[k_i = \omega_c\, R_a, \qquad k_p = \omega_c\, L_a\]

Crossover frequency

So why not just make \(\omega_c\) as high as possible? Because the real loop isn't a perfect integrator. Every cycle, the microcontroller measures the current and calculates a new voltage, but that voltage doesn't reach the motor until the next PWM update. The filter on the current sensor adds a bit more lag on top of that. Both of these eat into the phase margin, and they get worse the higher the frequency goes. A common rule of thumb is to keep the crossover 1 to 2 decades below the switching frequency.

Our PWM runs at 25 kHz, so we set the current loop crossover to 1 kHz. Plugging in our measured motor values gives \(k_p\) = 22 V/A and \(k_i\) = 18,850 V/(A·s). One last detail: the controller outputs a voltage, and the firmware turns that into a PWM duty cycle by dividing by the supply voltage. That way the PWM stage just looks like a gain of 1 to the controller.

Hardware

For the motor, we used a JGB37-520 geared DC motor from AliExpress ($12.18). A small DC motor doesn't make much torque on its own, so the built-in gearbox helps a lot, and it comes with an encoder on the back that we use for position.

The JGB37-520 geared DC motor held in a hand
The JGB37-520 geared DC motor
Front view of the assembled gripper
The assembled gripper

Steve designed a custom PCB to drive it. An STM32G431 runs the controller, and a DRV8876 drives the motor. He picked the DRV8876 because it measures the motor current on its own, so we didn't need a separate current-sense resistor. The catch is that it can't measure current flowing backwards, which happens when the motor acts like a generator. That doesn't really matter for us, since the motor is stalled against the object while it's gripping.

Top side of the gripper controller PCB with the STM32G431
Top of the controller PCB, with the STM32G431
Bottom side of the gripper controller PCB with the DRV8876
Bottom of the controller PCB, with the DRV8876

Testing

To make sure the current actually turned into a predictable torque, I built a test rig. A 3D-printed lever arm on the motor shaft presses down on a 5 kg load cell, and an ESP32 reads the load cell. We stepped through a range of current setpoints and recorded the torque at each one.

The torque test rig: a lever arm on the motor shaft pressing on a load cell
The torque test rig. The lever arm on the motor shaft presses on the load cell

Up to about 150 mA, the torque barely changed. All of that current was going into overcoming friction in the gearbox. Past that point, torque rose in a straight line with current, at 0.310 N·m/A with an R² of 0.994. At 0.6 A we measured about 130 mN·m. To make up for the friction, Steve added a dead zone to the controller.

Plot of output torque against motor current with a linear fit of 0.310 N·m/A
Output torque against motor current, measured on the test rig

And of course, if you turn the current up, it'll crush the chip too:

A gentle grip at a low current, then the same chip at a higher current setting (turn the sound on)

Cost

ItemCost
JGB37-520 DC motor$12.18
PCB components$9.30
100 mm linear rail (comes with one carriage)$6.00
Second carriage$4.00
3D-printed parts$3.00
PCB$1.00
Flange coupling$1.00
Total$36.48

There's still room to bring the cost down. The STM32G431 ($5.81) is way more chip than this job needs, and ST's new STM32C5 line costs about $2 and would do just fine. Steve stuck with the G4 because it's the same chip he used on our robot arm's joint drivers, so he could reuse a lot of that code.

Conclusion

This project came down to one simple idea: control the current and you control the force. With a cheap geared DC motor, a board that cost about $10, and a properly tuned current loop, we ended up with force control that usually costs $100 or more. Honestly, it worked better than we expected. If you want to see the full controller and electrical design, check out Steve's write-up.

Thanks for reading!