/*
Zeus Commander Controller Series
I2C controlled motorfader octet
Full PID motor control supporting two operating modes:
TARGET for fast preset recall
FOLLOW for slow to fast following of values
This code expands on the examples provided with the i2c_t3 library for the I2Ccommunication
This code is in the public domain.
*/
#include <i2c_t3.h>
#include <ResponsiveAnalogRead.h>
// PID arrays
float dState[8];
float iState[8];
float iMin[8];
float iMax[8];
float pGain[8];
float iGain[8];
float dGain[8];
float plantDrive[8];
float maxD=0.00; // maximum derivative value for debugging purposes
void UpdatePID(int faderIndex, float faderError, float faderPosition)
{
float pTerm, dTerm, iTerm, plantDriveInput;
// calculate the proportional term
pTerm = pGain[faderIndex] * faderError;
// calculate the integral state with appropriate limiting
iState[faderIndex] += faderError;
if (iState[faderIndex] > iMax[faderIndex]){
iState[faderIndex] = iMax[faderIndex];
}
else if (iState[faderIndex] < iMin[faderIndex]){
iState[faderIndex] = iMin[faderIndex];
}
iTerm = iGain[faderIndex] * iState[faderIndex]; // calculate the integral term
dTerm = dGain[faderIndex] * (faderPosition - dState[faderIndex]);
if (abs(faderPosition - dState[faderIndex]) > maxD){
maxD=abs(faderPosition - dState[faderIndex]);
}
dState[faderIndex] = faderPosition;
plantDriveInput = pTerm + iTerm - dTerm;
if (plantDriveInput > 4096) {
plantDriveInput=4096;
}
if (plantDriveInput < -4096) {
plantDriveInput=-4096;
}
plantDrive[faderIndex]=plantDriveInput;
}
elapsedMicros sinceFaderRead; // timer for fader check
unsigned int faderReadInterval=1000; // interval in microseconds for checking fader position(update)
elapsedMicros sinceTouchRead; // timer for touch read
unsigned int touchReadInterval=5000; // interval in microseconds for reading fader touch
elapsedMillis sinceLastCommand; // timer for time-out of PIF after last command
unsigned int commandTimeOutInterval=500; // interval for PID time-out in milliseconds
int faderWiperPin[8]={32,31,39,20,21,15,65,64}; // analog in pin for fader wipers
int faderTouchPin[8]={1,0,16,17,18,19,22,23}; // touchRead pins for fader touch
int faderMotorUpPin[8]={29,9,7,5,3,14,36,38}; // motor up PWM pin
int faderMotorDownPin[8]={30,10,8,6,4,2,35,37}; // motor down PWM pin
int faderValue[8]; // fader value ranging 0 to 1023
int faderTarget[8]; // fader target value ranging 0 to 1023
int faderTouch[8]; // fader touch reading
boolean faderPidOn[8]; // status of PID
int faderTouchThreshold=3000; // threshold value for touchRead
boolean faderTouched[8]; // boolean indicating if fader is touched
ResponsiveAnalogRead analog[8]{ResponsiveAnalogRead(32, true),ResponsiveAnalogRead(31, true),
ResponsiveAnalogRead(39, true),ResponsiveAnalogRead(20, true),
ResponsiveAnalogRead(21, true),ResponsiveAnalogRead(15, true),
ResponsiveAnalogRead(65, true),ResponsiveAnalogRead(64, true)};
// I2C communication
// Command definitions
#define TARGET 0x10 // All faders to target value as fast as possible. PID state is cleared with each command.
#define FOLLOW 0x20 // Faders follow value, last byte in message is used to determine which motors should move.
// PIDs will time-out after last command.
// Function prototypes
void receiveEvent(size_t count);
void requestEvent(void);
#define PANEL_ADDR 0x66 // I2C address, should be selectable by jumper using leftover pins.
#define MESSAGE_LEN 17
byte faderValueData[MESSAGE_LEN]; // 16 bytes for values, 1 byte for touch
byte faderTargetData[MESSAGE_LEN]; // 16 for target values, 1 byte for indicating which faders should be moved.
volatile uint8_t received;
volatile uint8_t cmd;
byte operatingMode = TARGET;
void setup()
{
//initialize faders and analog reference
analogReference(EXTERNAL);
analogWriteResolution(12);
for (int i=0; i<8; i++){
pinMode(faderMotorUpPin[i], OUTPUT);
pinMode(faderMotorDownPin[i], OUTPUT);
analogWriteFrequency(faderMotorUpPin[i],14648.437); // 14648.437 ideal frequency for 180MHz 12bit PWM
analogWriteFrequency(faderMotorDownPin[i],14648.437); //14648.437 ideal frequency for 180MHz 12bit PWM
faderTouched[i]=false;
faderValueData[i]=0;
faderTarget[i]=500;
faderPidOn[i]=false;
}
readFaders();
// set PID tuning parameters
for (int i=0;i<8;i++){
pGain[i]=10;
iGain[i]=1;
dGain[i]=50.00;
iMin[i]=-1500;
iMax[i]=1500;
}
// Setup for Slave mode pins 33/34, external pullups, 400kHz
Wire.begin(I2C_SLAVE, PANEL_ADDR, I2C_PINS_33_34, I2C_PULLUP_EXT, 400000);
// register events
Wire.onReceive(receiveEvent);
Wire.onRequest(requestEvent);
cmd = 0; // incoming command operating mode
}
void loop()
{
// check elapsedMicros and millis timers
// faders
if (sinceFaderRead >=faderReadInterval) {
sinceFaderRead = sinceFaderRead-faderReadInterval;
readFaders();
// always update PID for timing consistency
for (int i=0;i<8;i++){
UpdatePID(i,faderTarget[i]-faderValue[i],faderValue[i]);
}
// loop through faders and move where necessary
for (int i=0;i<8;i++){
if (!faderTouched[i] && faderPidOn[i]){
if(plantDrive[i]>0 && faderValue[i] <=1022){
analogWrite(faderMotorDownPin[i],0);
analogWrite(faderMotorUpPin[i],plantDrive[i]);
} else if (plantDrive[i] <=0 && faderValue[i] >=1){
analogWrite(faderMotorUpPin[i],0);
analogWrite(faderMotorDownPin[i],-plantDrive[i]);
}
}
else if(!faderPidOn[i]){
analogWrite(faderMotorDownPin[i],0);
analogWrite(faderMotorUpPin[i],0);
}
}
}
// separate touchRead timer
if (sinceTouchRead >= touchReadInterval){
sinceTouchRead = sinceTouchRead - touchReadInterval;
readTouch();
}
// command time-out
if (sinceLastCommand >= commandTimeOutInterval){
for (int i=0;i<8;i++){
faderPidOn[i]=false;
}
sinceLastCommand = 0;
}
}
// read fader analog values and update faderValue array
void readFaders() {
for (int i=0; i<8; i++) {
analog[i].update();
faderValue[i] = analog[i].getValue();
}
}
// read fader touch
void readTouch() {
for (int i=0; i<8; i++) {
faderTouch[i]= touchRead(faderTouchPin[i]);
if (faderTouch[i]>faderTouchThreshold){
faderTouched[i]=true;
} else if (faderTouch[i]<=faderTouchThreshold){
faderTouched[i]=false;
}
}
}
// handle Rx Event (incoming I2C data)
void receiveEvent(size_t count) {
size_t idx;
int incomingTargetValue[8];
boolean incomingFaderMove[8];
if(count)
{
// grab command
cmd = Wire.readByte();
switch(cmd)
{
case TARGET:
if (operatingMode==FOLLOW){
// set PID tuning parameters
// for (int i=0;i<8;i++){
// pGain[i]=20.00;
// iGain[i]=0.5;
// dGain[i]=40.00;
// }
operatingMode=TARGET;
}
break;
case FOLLOW:
if (operatingMode==TARGET){
// set PID tuning parameters
// for (int i=0;i<8;i++){
// pGain[i]=20.00;
// iGain[i]=0.5;
// dGain[i]=40.00;
// }
operatingMode=FOLLOW;
}
break;
}
idx = 0;
while(Wire.available()) faderTargetData[idx++] = Wire.readByte();
// Process data
for (int i=0;i<8;i++){
// combine bytes back to integers and put in temporary array
incomingTargetValue[i]= faderTargetData[(i*2)];
incomingTargetValue[i]= incomingTargetValue[i] << 8 | faderTargetData[(i*2)+1];
// fill faderPidOn boolean array with last byte
incomingFaderMove[i] = 1 & faderTargetData[16] >> i;
// clear PID states if PID is switched on or if mode is TARGET
if ((incomingFaderMove[i] && faderPidOn[i]!=incomingFaderMove[i]) || operatingMode==TARGET){
dState[i]=faderValue[i];
iState[i]=0;
}
faderPidOn[i]= incomingFaderMove[i];
// update fader target
faderTarget[i]=incomingTargetValue[i];
}
// reset timer
sinceLastCommand=0;
}
}
// handle Tx Event (outgoing I2C data)
void requestEvent(void)
{
faderValueData[16]=0;
for (int i=0;i<8;i++){
// split value integers over two bytes in array
faderValueData[(i*2)]=(faderValue[i] >> 8) & 0xFF;
faderValueData[(i*2)+1]=faderValue[i] & 0xFF;
// fill last byte in array with touch boolean array
faderValueData[16] |= faderTouched[i] << i;
}
Wire.write(faderValueData, MESSAGE_LEN); // fill Tx buffer (send full mem)
}