IntelliServo is a project aiming to transform regular hobby servos into intelligent ones by replacing their original boards. By doing so the servo has upgraded capabilities, such as being able to read its position, temperature and current consumption. Multiple servos can now be daisy-chained and controlled over I2C from a microcontroller, or over USB from a computer. https://github.com/bqlabs/IntelliServo
Dependencies: IAP USBDevice mbed
All the files and info are available in Github (http://alvaroferran.github.io/IntelliServo), including the board design, arduino library, servo firmware and mechanical parts as well as instructions for use
IntelliServoMain.cpp
- Committer:
- alvaroferran
- Date:
- 2016-03-09
- Revision:
- 0:200017bf1454
File content as of revision 0:200017bf1454:
//--------------------------------------------------------------
//-- IntelliServo
//-- I2C servo with temperature and current sensors
//--------------------------------------------------------------
//-- BQ
//--------------------------------------------------------------
//-- Firmware created by
//-- Alvaro Ferran Cifuentes (alvaroferran)
//--------------------------------------------------------------
//-- Released on March 2016
//-- under the GPL v3
//--------------------------------------------------------------
//Select servo
//#define FUTABA_S3003
#define TURNIGY_1268HV
#include "mbed.h"
#include "USBSerial.h"
#include "IAP.h"
#include "IntelliServoConfig.h"
#define constrain(amt,low,high) ((amt)<(low)?(low):((amt)>(high)?(high):(amt)))
//EEPROM values
#define TARGET_ADDRESS 64 //First non-reserved address in EEPROM
#define BYTE_SIZE 1
//USBSerial pc;
IAP iap;
I2CSlave slave(P0_5, P0_4); //SDA, SCL
PwmOut mot1(P0_8);
PwmOut mot2(P0_9);
AnalogIn pot(P0_11);
AnalogIn current(P0_13);
AnalogIn temperature(P0_14);
void setAngle(int16_t);
int16_t getAngle();
int16_t getCurrent();
int16_t getTemp();
void changeAddress(uint8_t);
void setEepromByte(int, uint8_t);
uint8_t getEepromByte(int);
int eepromAddres=1; //After TARGET_ADDRESS
/********MAIN***************************************************************/
int main() {
char inI2C[10];
char outI2C[2];
int8_t slaveRegister;
int16_t angleIn, angleOut, currentOut, tempOut;
bool angleSet=false;
int8_t disable=1;
mot2.period_us(33.33f); // 30kHz
mot1.period_us(33.33f);
mot1.write(0.00f);
mot2.write(0.00f);
//Uncomment to reset default i2c address
//setEepromByte(PLACE,(uint8_t) 0x01);
uint8_t inValue, i2cAddress=0x02;
inValue=getEepromByte(eepromAddres);
if(inValue!= (uint8_t) 0 ) i2cAddress=inValue;
slave.address(i2cAddress<<1);
while(1){
//setAngle(90);
int i = slave.receive();
switch (i) {
case I2CSlave::WriteAddressed: //Receive
slave.read(inI2C, 10);
slaveRegister=inI2C[0];
switch(slaveRegister){
case 0x0A: //Change address
changeAddress(inI2C[1]);
break;
case 0x01: //Disable motor
disable=inI2C[1];
break;
case 0x02: //Set angle
angleIn=(int16_t) (inI2C[1] << 8) | inI2C[2];
disable=0;
angleSet=true;
break;
}
break;
case I2CSlave::ReadAddressed: //Reply
switch(slaveRegister){
case 0x04: //Get angle
outI2C[0]= (angleOut >> 8) & 0xFF;
outI2C[1]= angleOut & 0xFF;
break;
case 0x06: //Get current
outI2C[0]= (currentOut >> 8) & 0xFF;
outI2C[1]= currentOut & 0xFF;
break;
case 0x08: //Get temperature
outI2C[0]= (tempOut >> 8) & 0xFF;
outI2C[1]= tempOut & 0xFF;
break;
}
slave.write(outI2C, 2);
break;
}
for(int i = 0; i < 10; i++) inI2C[i] = 0;
//Keep setting the angle so the servo stays energized
if(angleSet==true){ //Make sure servo only starts moving when a value has been sent
if(disable==0)
setAngle(angleIn);
else {
mot1.write(0.00f);
mot2.write(0.00f);
}
}
angleOut=getAngle();
currentOut=getCurrent();
tempOut=getTemp();
}
}
/********SET ANGLE**********************************************************/
//PID setting based on Angel Espeso's example (http://roble.uno/control-pid-barra-y-bola-arduino/)
void setAngle(int16_t desiredAngle){
desiredAngle=constrain(desiredAngle,-1,maxAngle); //Limit legal angles
int16_t currentAngle=0, lastAngle, error=0, lastError;
int eCount=0;
float I=0;
while(1){
wait_ms(1);
//Read current angle
lastAngle=currentAngle;
currentAngle=getAngle();
//Error
lastError=error;
error=desiredAngle-currentAngle;
//Calculate average speed
float v[5]; //Speed vector
for (int i=0; i<5; i++) // Move all speed values one space to the left to make space for the newest one
v[i] =v [i+1];
v[4] = (error-lastError); // Add last speed
float vel=0;
for (int i=0; i<5; i++) // Average speed
vel += v[i];
vel /= 5;
vel/=100;
//I
if(abs(error)<10 && abs(error)>0.2)
I+=error*Ki;
else
I=0;
//Voltage calculation
float pwr=Kp*error+Kd*vel+I;
//Motor control
float speed=0;//0.06;
if(pwr>0){
mot1.write(1-(abs(pwr)+speed));
mot2.write(1);
}else {
mot2.write(1-(abs(pwr)+speed));
mot1.write(1);
}
//pc.printf("Pot Angle: %d Pwr: %.2f Current: %.2f\n" ,currentAngle ,abs(pwr)+speed ,current.read()*100/0.055);
//Angle stable
if(abs(currentAngle-lastAngle)<=1){ //Difference in angle instead of error to avoid blocking when angle is not recheable
eCount++;
if(eCount>5)
break;
}
}
}
/********GET ANGLE**********************************************************/
int16_t getAngle(){
float aF=0;
int16_t aI=0;
for(int i=0; i<5; i++)
aF+=(pot.read()*100-5)*2.439;
aI=(int16_t)constrain(aF/5,0,maxAngle);
return aI;
}
/********GET CURRENT*********************************************************/
int16_t getCurrent(){
float cF=0;
int16_t cI=0;
for(int i=0; i<5; i++)
cF+=current.read()*100/0.055;
cI=(int16_t)cF/5;
return cI;
}
/********GET TEMPERATURE****************************************************/
int16_t getTemp(){
return (int16_t) (temperature.read()*3300-500)/10;
}
/********CHANGE ADDRESS*****************************************************/
void changeAddress(uint8_t address){
setEepromByte(eepromAddres, address);
slave.address(address<<1);
}
/********EEPROM R&W*********************************************************/
//EEPROM acces code based on Dave Tech's example (https://developer.mbed.org/users/kstech/code/EepromTest/)
typedef union data {
uint8_t b;
char s[1];
} myData;
void setEepromByte(int place, uint8_t incomingByte){
myData setByte;
char someBytes[1];
setByte.b = incomingByte;
for(int i=0; i<sizeof(someBytes); i++)
someBytes[i]=setByte.s[i];
iap.write_eeprom( someBytes, (char*) (TARGET_ADDRESS+place), BYTE_SIZE );
}
uint8_t getEepromByte(int place){
myData getByte;
char someBytes[1];
iap.read_eeprom( (char*)(TARGET_ADDRESS+place), someBytes, BYTE_SIZE );
for(int i=0; i<sizeof(someBytes); i++)
getByte.s[i]=someBytes[i];
return getByte.b;
}
Alvaro Ferrán Cifuentes