Vibration actuators primer

Before designing haptic effects on an Android device, it helps to get an overview of how vibration actuators work.

Illustration of the components of a Haptic LRA

The most common vibration actuators are Linear Resonant Actuators (LRAs). Each LRA consists of a voice coil pressed against a magnetic moving mass that is attached to a spring. An AC voltage applied to the voice coil creates an electromagnetic force that causes the mass to move. The spring provides the restoring force that causes the mass to return to its starting position. The back-and-forth movement of the mass causes the LRA to vibrate. They have a resonance frequency at which the output is maximum.

Given the same input voltage at two different frequencies, the vibration output amplitudes can be different. The further away the frequency is from the LRA’s resonant frequency, the lower its vibration amplitude is.

One common function of LRAs in a device is to simulate the feeling of a button click on an unresponsive glass surface. It serves to make user interaction feel more natural. When applied to typing on a virtual keyboard, click feedback can increase typing speed and reduce errors. A clear and crisp click feedback signal is typically less than 10 to 20 milliseconds in duration. Achieving a good click requires some knowledge of the LRA used in a device. This is why relying on pre-fabricated waveforms provides the best feedback for a click. You can use them with the constants provided by the platform whenever a click feedback is needed.

The haptic effects achievable in a device is determined by both the vibration actuator and its driver. Haptic drivers that include overdrive and active braking features can reduce the rise time and ringing of LRAs, leading to a more responsive and clear vibration. For illustration, let’s see how a custom waveform pattern behaves on a generic device.

Kotlin

val timings: LongArray = longArrayOf(50, 50, 50, 50, 50, 100, 350, 250)
val amplitudes: IntArray = intArrayOf(77, 79, 84, 99, 143, 255, 0, 255)
val repeatIndex = -1 // Do not repeat.

vibrator.vibrate(VibrationEffect.createWaveform(timings, amplitudes, repeatIndex))

Java

long[] timings = new long[] { 50, 50, 50, 50, 50, 100, 350, 250 };
int[] amplitudes = new int[] { 77, 79, 84, 99, 143, 255, 0, 255 };
int repeatIndex = -1 // Do not repeat.

vibrator.vibrate(VibrationEffect.createWaveform(timings, amplitudes, repeatIndex));

The plot below shows the waveform corresponding to the code snippets shown above.

Plot of step function input waveform

The corresponding acceleration is shown below:

Plot of actual measured waveform, showing more organic transitions between levels

Note that the acceleration increases gradually, not suddenly, whenever there is a step change of amplitude in the pattern (e.g., at 0ms, 150ms, 200ms, 250ms, 700ms). There is also an overshoot at each step change of amplitude, and there is visible ‘ringing’ that lasts at least 50ms when the input amplitude suddenly drops to 0.

This haptic pattern can be improved by increasing and decreasing the amplitudes gradually to avoid overshoot and reduce ringing time. The following shows the waveform and acceleration plots of the revised version.

Kotlin

val timings: LongArray = longArrayOf(
    25, 25, 50, 25, 25, 25, 25, 25, 25, 25, 75, 25, 25,
    300, 25, 25, 150, 25, 25, 25
)
val amplitudes: IntArray = intArrayOf(
    38, 77, 79, 84, 92, 99, 121, 143, 180, 217, 255, 170, 85,
    0, 85, 170, 255, 170, 85, 0
)
val repeatIndex = -1 // Do not repeat.

vibrator.vibrate(VibrationEffect.createWaveform(timings, amplitudes, repeatIndex))

Java

long[] timings = new long[] {
        25, 25, 50, 25, 25, 25, 25, 25, 25, 25, 75, 25, 25,
        300, 25, 25, 150, 25, 25, 25
    };
int[] amplitudes = new int[] {
        38, 77, 79, 84, 92, 99, 121, 143, 180, 217, 255, 170, 85,
        0, 85, 170, 255, 170, 85, 0
    };
int repeatIndex = -1; // Do not repeat.

vibrator.vibrate(VibrationEffect.createWaveform(timings, amplitudes, repeatIndex));

Plot of input waveform with additional steps

Plot of measured waveform, showing smoother transitions

It follows that creating a haptic effect on an Android device requires more than providing a frequency and amplitude value. It is not a trivial task to design a haptic effect from scratch without full access to the engineering specifications of the vibration actuator and the driver. Android APIs provide constants that let you do the following:

  • Perform clear effects and primitives.

  • Concatenate them to compose new haptic effects.

These pre-defined haptic constants and primitives can greatly speed up your work while ensuring high-quality haptic effects.