Flora Liu Hardware Engineer

05 Visual servo control of a piezoelectric motor

Visual servo control of a piezoelectric motor

A camera watches a 9 × 6 mm motor, and a PID loop steers it.

A motor the size of a fingernail drifts off course. How do you steer it without adding a single sensor to it?

My master's thesis. The motor is a 9 × 6 mm plate that walks on traveling waves. Left alone it drifts, and any sensor mounted on it would add weight. So a camera watches it instead, and a PID loop corrects it 120 times a second.

github.com/floraliu-dev/piezo-motor-visual-servo ↗ · Python · OpenCV · SCPI · Presented at SPIE Smart Structures 2026

The motor

A PZT sheet on a stainless steel plate, both 9 × 6 mm, free on every edge. Exciting two vibration modes together, out of phase, turns standing waves into a traveling wave that pushes the plate along. One pair of modes moves it in a straight line; another pair turns it on the spot. The electrode layout decides how strongly each mode is driven.

Exploded view of the motor: a 9 by 6 mm stainless steel plate under a PZT sheet with split electrodes. Electrodes A1 and A2 are marked; Y linear motion uses modes Φ20 and Φ21, Z rotation uses Φ21 and Φ31.
Fig 1 Motor structure and electrode layout. Linear motion uses modes Φ20 + Φ21; rotation uses Φ21 + Φ31.

Closing the loop with a camera

A camera adds no weight and touches nothing, and it sees both position and heading at once. A global-shutter camera above the motor feeds a computer; the computer sets the function generator, whose signal a power amplifier raises to drive the motor.

Setup sketch: a computer connected to a camera module and a function generator; the function generator feeds a power amplifier wired to the motor on a grid.
Fig 2 Experimental setup.

Each frame, the software finds the motor, filters its position and heading, compares them with the target, and turns the error into a drive command. Capture, image processing and control run on separate threads so they overlap.

Block diagram: reference minus measured output gives the error; mode decision and PID give a command, sent through a compound SCPI write to the function generator and HV amplifier, which drive the actuator. A camera with Kalman and EMA filtering measures the motion at 120 fps and feeds it back.
Fig 3 The control loop. Camera, filtering and control run as three threads at 120 FPS.
Feedback rate
120.6 FPS
Camera to command
12.4 ms
Dropped frames
0 of 2,500
Calibration error
0.094 mm

Does the control work?

Driving straight, the uncontrolled motor wanders off line. With heading control it holds its course, and it is faster too: top speed rises from 67.7 to 122.4 mm/s.

Two trajectories from the same start. Without control the path wanders left, ending 10.2° off; with control it stays nearly straight, 2.6° off.
Fig 4 One straight run with and without heading control: 10.2° of drift down to 2.6°.

Turning on the spot to a target angle between 3° and 8°, it settles within about 1.2° of the target.

Switching drive modes in under a millisecond

Changing direction first meant re-uploading the waveform: about 268 ms, some 30 frames with no new command. Preloading the waveforms and sending every change as one compound SCPI command brought it under a millisecond, more than 600 times faster.

Log-scale bar chart: a full waveform upload takes 268 ms; compound commands take 0.3 to 0.4 ms, 670 to 850 times faster.
Fig 5 Mode-switch latency, full upload versus compound SCPI commands (log scale).

Try it

  • Run from source: pip install -r requirements.txt, then python main.py. Camera, mode and instrument address are set in config.py.
  • Every run saves a tracked video, a raw video, a CSV and plots.
  • Full latency and throughput benchmarks ↗