Just to exit this somewhat contentious loop, I'll post the code for the motor I mentioned.
I'm unsure about its suitability to drive a turntable or if the OP is interested in a DC alternative. My sense is that it does a great job compensating for torque and voltage but there are small changes in speed around the setpoint. Perhaps the PID tuning can be improved.
However, this motor has made it easy to experiment with PID in the real world.
/* PID Motor Control Example 7834 (25%) / 362 (17%) bHogan 7/27/16
* Using eBay "DC 12V Brushless Motor CW/CCW PWM Speed Control Urgent Stop With Drive Circuit"
* #172168676210 with Pro-Mini. 12V to motor, all signals work directly with 5V logic, (nice!)
* Recommended PWM freq - 20-30kHz (native PWM freq = 490Hz - changed to 31.2kHz))
* Stop = YELLOW->GND, CCW = BLUE->GND. PWM=WHITE, Speed=BROWN (open collector - pull high)
*
*DEVELOPMENT NOTES:
* - PWM freq of 31kHz is important - motor runs smother and closer to set speed
* - some gap is due to fluctuations in the pot setting
* - PID works best in DIRECT mode - PWMval is inverted so it can run in this mode.
*
* WHAT IS NEW since last:
* - (v1e) release with auto-tune and attempt at strobe
* - removed auto-tune - no luck with it
*
* TODO
* - use Timer1 for roll your own PWM (like Mini) ?
* - more experiments with tuning parameters?
*
------------------------------------------------------------------------------------------------*/
#include <PID_v1.h> // http://playground.arduino.cc/Code/PIDLibrary
//#include <PID_AutoTune_v0.h> // http://playground.arduino.cc/Code/PIDAutotuneLibrary
#define DEBUG true // allow serial print
#define SPEED_PIN 2 // speed signal from motor creates INT - BROWN wire
#define MOTOR_ON 3 // LOW to stop motor - YELLOW wire
#define MOTOR_DIR 4 // LOW to turn CCW - GREEN wire
#define PWM_PIN 9 // best PWM for motor speed - WHITE wire
#define POT_PIN A1 // 10k pot across gnd and 5V to set speed
#define MIN_RPM 100 // minimum motor speed
#define MAX_RPM 4800 // maximum motor speed
#define MOTOR_CCW false // true to run motor CCW
byte PWMval; // PWM setting for motor speed
volatile unsigned long duration, freq, RPM; // from speed signal output from motor
volatile unsigned long prevMicroSec = 0; // to calc RPM in ISR
//Define PID Variables
double Setpoint, Input, Output;
//double Kp = 0.01, Ki = 0.15, Kd = 0.0; // tuning bnenchmark
double Kp = 0.01, Ki = 0.4, Kd = 0.0; // TESTING - higher I (.4) = faster start
//Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT); // PWM inverted so use DIRECT
void setup() {
Serial.begin(115200); // for DEBUG and speed read
pinMode(MOTOR_ON, OUTPUT); // setup motor on pin
pinMode(MOTOR_DIR, OUTPUT); // setup motor direction pin
pinMode(SPEED_PIN, INPUT); // setup speed pin
digitalWrite(MOTOR_ON, LOW); // start with motor off
digitalWrite(MOTOR_DIR, HIGH); // start with CW rotation
digitalWrite(SPEED_PIN, HIGH); // set 20K pullup on it
Serial.println("Begin");
setPwmFrequency(9, 1); // increase default PWM freq from 490Hz to 31.2kHz
//initialize the PID variables
Setpoint = checkPot(); // PID setpoint comes from pot
Input = RPM; // PID input is the calculated speed of the motor
//turn the PID on
myPID.SetMode(AUTOMATIC); // PID is always on
myPID.SetSampleTime (100); // how often the PID evaluates - default is 200mS.
#if (MOTOR_CCW)
digitalWrite(MOTOR_DIR, LOW); // run with CCW rotation
#endif
digitalWrite(MOTOR_ON, HIGH); // start motor
attachInterrupt(0, GetSpeed, FALLING); // count the time between pulses to calc RPM
}
void loop() {
Setpoint = checkPot(); // get the setpoint from the pot
Input = RPM; // get the current current RPM
myPID.Compute(); // compute the output
setSpeed (Output); // adjust PWM duty to set speed
#if (DEBUG)
int gap = abs(Setpoint - Input); // calc distance away from setpoint
Serial.print("Setpoint: ");
Serial.print(Setpoint, 0);
Serial.print("\tRPM: ");
Serial.print(RPM);
Serial.print(" Gap: ");
Serial.print(gap);
Serial.print("\t\tOutput: ");
Serial.print(Output);
Serial.print(" PWMval: ");
Serial.println(PWMval);
#endif
}
unsigned long checkPot() { // 4.6 RPM / division
unsigned int potVal;
potVal = analogRead(POT_PIN);
// return 2500; // for DEBUG - constant setpoint bypassing the pot
return map(potVal, 0, 1023, MIN_RPM, MAX_RPM);
}
void setSpeed (int setPWM) {
PWMval = map(setPWM, 0, 255, 255, 0); // invert output so we can use DIRECT PID (not REVERSE)
analogWrite(PWM_PIN, PWMval); // set the PWM for the motor speed
}
/*----------------------------------------------------------------------------------------------+
* setPwmFrequency - Divides a given PWM pin frequency by a divisor.
* ** Pins 9 & 10 safest to use - 31.250 kHz if divide by 1 **
* The resulting frequency is equal to the base frequency divided by the given divisor:
* - Base frequencies:
* o The base frequency for pins 3, 9, 10, and 11 is 31250 Hz.
* o The base frequency for pins 5 and 6 is 62500 Hz.
* - Divisors:
* o The divisors available on pins 5, 6, 9 and 10 are: 1, 8, 64,
* 256, and 1024.
* o The divisors available on pins 3 and 11 are: 1, 8, 32, 64,
* 128, 256, and 1024.
*
* PWM frequencies are tied together in pairs of pins. If one in a pair is changed,
* the other is also changed to match:
* - Pins 5 and 6 are paired on timer0
* - Pins 9 and 10 are paired on timer1
* - Pins 3 and 11 are paired on timer2
*
* Note that this function will have side effects on anything else that uses timers:
* - Changes on pins 3, 5, 6, or 11 may cause the delay() and
* millis() functions to stop working. Other timing-related
* functions may also be affected.
* - Changes on pins 9 or 10 will cause the Servo library to function
* incorrectly.
*
/-----------------------------------------------------------------------------------------------*/
void setPwmFrequency(int pin, int divisor) {
byte mode;
if (pin == 5 || pin == 6 || pin == 9 || pin == 10) {
switch (divisor) {
case 1: mode = 0x01; break;
case 8: mode = 0x02; break;
case 64: mode = 0x03; break;
case 256: mode = 0x04; break;
case 1024: mode = 0x05; break;
default: return;
}
if (pin == 5 || pin == 6) {
TCCR0B = TCCR0B & 0b11111000 | mode;
} else {
TCCR1B = TCCR1B & 0b11111000 | mode;
}
} else if (pin == 3 || pin == 11) {
switch (divisor) {
case 1: mode = 0x01; break;
case 8: mode = 0x02; break;
case 32: mode = 0x03; break;
case 64: mode = 0x04; break;
case 128: mode = 0x05; break;
case 256: mode = 0x06; break;
case 1024: mode = 0x7; break;
default: return;
}
TCCR2B = TCCR2B & 0b11111000 | mode;
}
}
//----------------------------------------------------------------------------------------------+
// ISR
//----------------------------------------------------------------------------------------------+
void GetSpeed() { // ISR triggered for each pulse (6 pulses /revolution)
duration = micros() - prevMicroSec; // using micros() in an ISR despite the warning
freq = (1000000L / duration); // convert uS duration to Hz
RPM = freq * 10; // 6 pulses / revolution and convert to minutes
prevMicroSec = micros(); // reset the period time
}
BTW, "bHogan" is a nickname I got in college after having way too much Crown Royal. So it sounds funny to me to be preceded by "Mr."! But thanks!