This is a picture of my own PID-like regulator of the form:
out = feed + MaxMin(DiffMax, DiffMin, // limit the difference, add feed (can be removed)
+ P*(E=setp-feed) // proportional factor, error calculation
+ (A = MaxMin(AccuMax, AccuMin, A // limit for accumulator
+ I*E - D*(feed-prev))) // joined integrator and derivator
The big difference from regular PID regulators is in the way of using the accumulator for both I and D - which is a bit similar to that leaking integrator mentioned by ryan0270 (I was experimenting with similar thing as well). The derivator will decrease the accumulator (which can be limited as well to prevent windup and big value when set-point is greatly changed).

blue = output / control signal (force, input to boiler)
white = feedback (regulated value, actual temperature)
green/yellow = set-point (desired value)
red = state of accumulator (centered like being +50%)
navy/dark blue = proportional part
I have designed it for temperature regulation, but it seems to be equally good for your problem. My fade factor of the system is simulating the temperature dissipation / error of equitherm regulator. Your gravity and variable small weights seems similar to me. The integrator can compensate for this, but would normally overshoot and cause oscillation. Derivator applied on the shared accumulator prevents that - it reduces the accumulated error by the change made.
Proportional is strongest at the beginning (startup acceleration), Integrator comes next (error-correcting acceleration) and Derivator will slow it last (like a brake - preventing the overshoot by reducing the accumulator).
Regulator step:
// regulator step
float diff = setp - feed
float accu = 0
if pA && !isnan(diff)
// integrator
accu = *pA + I*diff
// derivator
if pV
accu += D*(*pV - feed)
*pV = feed
// accu limits
if accu > aMax; accu = aMax
if accu < aMin; accu = aMin
// store back
*pA = accu
// proportional
diff = P*diff + accu
// diff limits
if diff > dMax; diff = dMax
if diff < dMin; diff = dMin
Full code:
#include <math.h>
#include "mem/config.h"
#include "device/analog.h"
__packed struct PID
byte type
char name[9]
word period
Timer tmr
byte feed, setp
word flags
ANALOG output
float vals[9]
enum
fP = 1<<0
fI = 1<<1
fD = 1<<2
fDiffMax = 1<<4
fDiffMin = 1<<5
fAccuMax = 1<<6
fAccuMin = 1<<7
void devPid()
PID *pid = (PID *)xpointer(DevAddr)
if !pid->tmr.reached(pid->period*10u)
return
pid->tmr.start()
// inputs
float feed = 0.0f
float setp = 0.0f
if pid->feed < ainCount
feed = getxf(ains[pid->feed] + AIN_value)
if pid->setp < ainCount
setp = getxf(ains[pid->setp] + AIN_value)
// PID factors (multiplicators)
float P = 0.0f
float I = 0.0f
float D = 0.0f
// difference limits (output-feedback)
float dMax = INFINITY
float dMin = -INFINITY
// accumulator limits (anti-windup)
float aMax = INFINITY
float aMin = -INFINITY
// flags and pointers
word flags = pid->flags
float __packed* pf = pid->vals
float __packed* pA = null // accumulator
float __packed* pV = null // previous feed
// parse variable object part
if flags & fP
P = *pf++
if flags & fI
I = *pf++
pA = pf++
if flags & fD
D = *pf++
pV = pf++
if flags & fDiffMax
dMin = -(dMax = *pf++)
if flags & fDiffMin
dMin = *pf++
if flags & fAccuMax
aMin = -(aMax = *pf++)
if flags &fAccuMin
aMin = *pf++
// regulator step
float diff = setp - feed
float accu = 0
if pA && !isnan(diff)
// integrator
accu = *pA + I*diff
// derivator
if pV
accu += D*(*pV - feed)
*pV = feed
// accu limits
if accu > aMax; accu = aMax
if accu < aMin; accu = aMin
// store back
*pA = accu
// proportional
diff = P*diff + accu
// diff limits
if diff > dMax; diff = dMax
if diff < dMin; diff = dMin
// final output
ainSet(DevAddr + offsetof(PID,output),
feed + diff)
You can tune it a bit and use the error, accumulator and feedback-change to detect the steady state to power it off.
Add-on
I have been experimenting with it a bit more - used the regulator with erroneous non-linear 7bit DAC (custom-made of few resistors and opto-transistors) with erroneous main regulation (basic conversion peformed very bad in high values) and observed the behaviour (good ADC was used as feedback). The regulator performed really well and quickly ended with 1-bit oscillation (which is inevitable). I have further enhanced my simulation with this gained knowledge and here is the picture:
