#include "rtapi.h"
#include "rtapi_app.h"
#include "rtapi_math.h"
#include "hal.h"
MODULE_AUTHOR("Pete Vavaroutsos");
MODULE_DESCRIPTION("Auto-Tune PID Loop Component for EMC HAL");
MODULE_LICENSE("GPL");
static int num_chan; static int default_num_chan = 3;
static int howmany;
RTAPI_MP_INT(num_chan, "number of channels");
static int debug = 0; RTAPI_MP_INT(debug, "enables optional params");
#define MAX_CHAN 16
static char *names[MAX_CHAN] = {0,};
RTAPI_MP_ARRAY_STRING(names, MAX_CHAN, "names of at_pid components");
#define PI 3.141592653589
typedef enum {
STATE_PID,
STATE_TUNE_IDLE,
STATE_TUNE_START,
STATE_TUNE_POS,
STATE_TUNE_NEG,
STATE_TUNE_ABORT,
} State;
typedef enum {
TYPE_PID,
TYPE_PI_FF1,
} Mode;
typedef struct {
hal_float_t *deadband;
hal_float_t *maxError; hal_float_t *maxErrorI; hal_float_t *maxErrorD; hal_float_t *maxCmdD; hal_float_t *maxCmdDd;
hal_float_t *bias; hal_float_t *pGain; hal_float_t *iGain; hal_float_t *dGain; hal_float_t *ff0Gain; hal_float_t *ff1Gain; hal_float_t *ff2Gain; hal_float_t *maxOutput; hal_float_t *tuneEffort; hal_u32_t *tuneCycles;
hal_u32_t *tuneType;
hal_float_t *errorI; hal_float_t *errorD; hal_float_t *cmdD; hal_float_t *cmdDd; hal_float_t *ultimateGain; hal_float_t *ultimatePeriod;
hal_bit_t *pEnable;
hal_float_t *pCommand; hal_float_t *pFeedback; hal_float_t *pError; hal_float_t *pOutput; hal_bit_t *pTuneMode; hal_bit_t *pTuneStart;
hal_float_t prevError; hal_float_t prevCmd; hal_float_t limitState; State state;
hal_u32_t cycleCount;
hal_u32_t cyclePeriod;
hal_float_t cycleAmplitude;
hal_float_t totalTime;
hal_float_t avgAmplitude;
} Pid;
static int Pid_Init(Pid *this);
static int Pid_Export(Pid *this, int compId, char* prefix);
static void Pid_AutoTune(Pid *this, long period);
static void Pid_CycleEnd(Pid *this);
static void Pid_Refresh(void *this, long period);
typedef struct {
int id; Pid *pidTable;
} Component;
static Component component;
int
rtapi_app_main(void)
{
int retval,n,i;
Pid *pComp;
if(num_chan && names[0]) {
rtapi_print_msg(RTAPI_MSG_ERR,"num_chan= and names= are mutually exclusive\n");
return -EINVAL;
}
if(!num_chan && !names[0]) num_chan = default_num_chan;
if(num_chan) {
howmany = num_chan;
} else {
howmany = 0;
for (i = 0; i < MAX_CHAN; i++) {
if ( (names[i] == NULL) || (*names[i] == 0) ){
break;
}
howmany = i + 1;
}
}
if((howmany <= 0) || (howmany > MAX_CHAN)){
rtapi_print_msg(RTAPI_MSG_ERR,
"AT_PID: ERROR: invalid number of channels: %d\n", howmany);
return(-1);
}
component.id = hal_init("at_pid");
if(component.id < 0){
rtapi_print_msg(RTAPI_MSG_ERR, "PID: ERROR: hal_init() failed\n");
return(-1);
}
component.pidTable = hal_malloc(howmany * sizeof(*pComp));
if(component.pidTable == NULL){
rtapi_print_msg(RTAPI_MSG_ERR, "PID: ERROR: hal_malloc() failed\n");
hal_exit(component.id);
return(-1);
}
pComp = component.pidTable;
i = 0; for(n = 0; n < howmany; n++, pComp++){
if(num_chan) {
char buf[HAL_NAME_LEN + 1];
rtapi_snprintf(buf, sizeof(buf), "pid.%d", n);
retval = Pid_Export(pComp, component.id, buf);
} else {
retval = Pid_Export(pComp, component.id, names[i++]);
}
if (retval != 0) {
rtapi_print_msg(RTAPI_MSG_ERR,
"AT_PID: ERROR: at_pid %d var export failed\n", n);
hal_exit(component.id);
return -1;
}
if(Pid_Init(pComp)){
hal_exit(component.id);
return(-1);
}
}
hal_ready(component.id);
return(0);
}
void
rtapi_app_exit(void)
{
Pid *pComp;
hal_exit(component.id);
if((pComp = component.pidTable) != NULL){
}
}
static int
Pid_Init(Pid *this)
{
*(this->deadband) = 0;
*(this->maxError) = 0;
*(this->maxErrorI) = 0;
*(this->maxErrorD) = 0;
*(this->maxCmdD) = 0;
*(this->maxCmdDd) = 0;
*(this->errorI) = 0;
this->prevError = 0;
*(this->errorD) = 0;
this->prevCmd = 0;
this->limitState = 0;
*(this->cmdD) = 0;
*(this->cmdDd) = 0;
*(this->bias) = 0;
*(this->pGain) = 1;
*(this->iGain) = 0;
*(this->dGain) = 0;
*(this->ff0Gain) = 0;
*(this->ff1Gain) = 0;
*(this->ff2Gain) = 0;
*(this->maxOutput) = 0;
this->state = STATE_PID;
*(this->tuneCycles) = 50;
*(this->tuneEffort) = 0.5;
*(this->tuneType) = TYPE_PID;
return(0);
}
static int
Pid_Export(Pid *this, int compId,char* prefix)
{
int error, msg;
char buf[HAL_NAME_LEN + 1];
msg = rtapi_get_msg_level();
rtapi_set_msg_level(RTAPI_MSG_WARN);
error = hal_pin_bit_newf(HAL_IN, &(this->pEnable), compId, "%s.enable", prefix);
if(!error){
error = hal_pin_float_newf(HAL_IN, &(this->pCommand), compId, "%s.command", prefix);
}
if(!error){
error = hal_pin_float_newf(HAL_IN, &(this->pFeedback), compId, "%s.feedback", prefix);
}
if(!error){
error = hal_pin_float_newf(HAL_OUT, &(this->pError), compId, "%s.error", prefix);
}
if(!error){
error = hal_pin_float_newf(HAL_OUT, &(this->pOutput), compId, "%s.output", prefix);
}
if(!error){
error = hal_pin_bit_newf(HAL_IN, &(this->pTuneMode), compId, "%s.tune-mode", prefix);
}
if(!error){
error = hal_pin_bit_newf(HAL_IO, &(this->pTuneStart), compId, "%s.tune-start", prefix);
}
if(!error){
error = hal_pin_float_newf(HAL_IO, &(this->deadband), compId, "%s.deadband", prefix);
}
if(!error){
error = hal_pin_float_newf(HAL_IO, &(this->maxError), compId, "%s.maxerror", prefix);
}
if(!error){
error = hal_pin_float_newf(HAL_IO, &(this->maxErrorI), compId, "%s.maxerrorI", prefix);
}
if(!error){
error = hal_pin_float_newf(HAL_IO, &(this->maxErrorD), compId, "%s.maxerrorD", prefix);
}
if(!error){
error = hal_pin_float_newf(HAL_IO, &(this->maxCmdD), compId, "%s.maxcmdD", prefix);
}
if(!error){
error = hal_pin_float_newf(HAL_IO, &(this->maxCmdDd), compId, "%s.maxcmdDD", prefix);
}
if(!error){
error = hal_pin_float_newf(HAL_IO, &(this->bias), compId, "%s.bias", prefix);
}
if(!error){
error = hal_pin_float_newf(HAL_IO, &(this->pGain), compId, "%s.Pgain", prefix);
}
if(!error){
error = hal_pin_float_newf(HAL_IO, &(this->iGain), compId, "%s.Igain", prefix);
}
if(!error){
error = hal_pin_float_newf(HAL_IO, &(this->dGain), compId, "%s.Dgain", prefix);
}
if(!error){
error = hal_pin_float_newf(HAL_IO, &(this->ff0Gain), compId, "%s.FF0", prefix);
}
if(!error){
error = hal_pin_float_newf(HAL_IO, &(this->ff1Gain), compId, "%s.FF1", prefix);
}
if(!error){
error = hal_pin_float_newf(HAL_IO, &(this->ff2Gain), compId, "%s.FF2", prefix);
}
if(!error){
error = hal_pin_float_newf(HAL_IO, &(this->maxOutput), compId, "%s.maxoutput", prefix);
}
if(!error){
error = hal_pin_float_newf(HAL_IO, &(this->tuneEffort), compId, "%s.tune-effort", prefix);
}
if(!error){
error = hal_pin_u32_newf(HAL_IO, &(this->tuneCycles), compId, "%s.tune-cycles", prefix);
}
if(!error){
error = hal_pin_u32_newf(HAL_IO, &(this->tuneType), compId, "%s.tune-type", prefix);
}
if(debug > 0){
if(!error){
error = hal_pin_float_newf(HAL_OUT, &(this->errorI), compId, "%s.errorI", prefix);
}
if(!error){
error = hal_pin_float_newf(HAL_OUT, &(this->errorD), compId, "%s.errorD", prefix);
}
if(!error){
error = hal_pin_float_newf(HAL_OUT, &(this->cmdD), compId, "%s.commandD", prefix);
}
if(!error){
error = hal_pin_float_newf(HAL_OUT, &(this->cmdDd), compId, "%s.commandDD", prefix);
}
if(!error){
error = hal_pin_float_newf(HAL_OUT, &(this->ultimateGain), compId, "%s.ultimate-gain", prefix);
}
if(!error){
error = hal_pin_float_newf(HAL_OUT, &(this->ultimatePeriod), compId, "%s.ultimate-period", prefix);
}
} else {
this->errorI = (hal_float_t *) hal_malloc(sizeof(hal_float_t));
this->errorD = (hal_float_t *) hal_malloc(sizeof(hal_float_t));
this->cmdD = (hal_float_t *) hal_malloc(sizeof(hal_float_t));
this->cmdDd = (hal_float_t *) hal_malloc(sizeof(hal_float_t));
this->ultimateGain = (hal_float_t *) hal_malloc(sizeof(hal_float_t));
this->ultimatePeriod = (hal_float_t *) hal_malloc(sizeof(hal_float_t));
}
if(!error){
rtapi_snprintf(buf, sizeof(buf), "%s.do-pid-calcs", prefix);
error = hal_export_funct(buf, Pid_Refresh, this, 1, 0, compId);
}
if(!error){
*this->pEnable = 0;
*this->pCommand = 0;
*this->pFeedback = 0;
*this->pError = 0;
*this->pOutput = 0;
*this->pTuneMode = 0;
*this->pTuneStart = 0;
}
rtapi_set_msg_level(msg);
return(error);
}
static void
Pid_AutoTune(Pid *this, long period)
{
hal_float_t error;
error = *this->pCommand - *this->pFeedback;
*this->pError = error;
if(!*this->pEnable || !*this->pTuneMode){
this->state = STATE_TUNE_ABORT;
}
switch(this->state){
case STATE_TUNE_IDLE:
if(*this->pTuneStart)
this->state = STATE_TUNE_START;
break;
case STATE_TUNE_START:
this->state = STATE_TUNE_POS;
this->cycleCount = 0;
this->cyclePeriod = 0;
this->cycleAmplitude = 0;
this->totalTime = 0;
this->avgAmplitude = 0;
*(this->ultimateGain) = 0;
*(this->ultimatePeriod) = 0;
*this->pOutput = *(this->bias) + fabs(*(this->tuneEffort));
break;
case STATE_TUNE_POS:
case STATE_TUNE_NEG:
this->cyclePeriod += period;
if(error < 0.0){
if(-error > this->cycleAmplitude)
this->cycleAmplitude = -error;
if(this->state == STATE_TUNE_POS){
this->state = STATE_TUNE_NEG;
Pid_CycleEnd(this);
}
*this->pOutput = *(this->bias) - fabs(*(this->tuneEffort));
}else{
if(error > this->cycleAmplitude)
this->cycleAmplitude = error;
if(this->state == STATE_TUNE_NEG){
this->state = STATE_TUNE_POS;
Pid_CycleEnd(this);
}
*this->pOutput = *(this->bias) + fabs(*(this->tuneEffort));
}
if(this->cycleCount < *(this->tuneCycles))
break;
*(this->ultimateGain) = (4.0 * fabs(*(this->tuneEffort)))/(PI * this->avgAmplitude);
*(this->ultimatePeriod) = 2.0 * this->totalTime / *(this->tuneCycles);
*(this->ff0Gain) = 0;
*(this->ff2Gain) = 0;
if(*(this->tuneType) == TYPE_PID){
*(this->pGain) = 0.6 * *(this->ultimateGain);
*(this->iGain) = *(this->pGain) / (*(this->ultimatePeriod) / 2.0);
*(this->dGain) = *(this->pGain) * (*(this->ultimatePeriod) / 8.0);
*(this->ff1Gain) = 0;
}else{
*(this->pGain) = 0.45 * *(this->ultimateGain);
*(this->iGain) = *(this->pGain) / (*(this->ultimatePeriod) / 1.2);
*(this->dGain) = 0;
*(this->ff1Gain) = 1;
}
case STATE_TUNE_ABORT:
default:
*this->pOutput = 0;
*this->pTuneStart = 0;
this->state = (*this->pTuneMode)? STATE_TUNE_IDLE: STATE_PID;
}
}
static void
Pid_CycleEnd(Pid *this)
{
this->cycleCount++;
this->avgAmplitude += this->cycleAmplitude / *(this->tuneCycles);
this->cycleAmplitude = 0;
this->totalTime += this->cyclePeriod * 0.000000001;
this->cyclePeriod = 0;
}
static void
Pid_Refresh(void *arg, long periodNs)
{
Pid *this = (Pid *)arg;
hal_float_t period, periodRecip;
hal_float_t prevCmdD, error, output;
if(this->state != STATE_PID){
Pid_AutoTune(this, periodNs);
return;
}
error = *this->pCommand - *this->pFeedback;
*this->pError = error;
if(*this->pTuneMode){
*(this->errorI) = 0;
this->prevError = 0;
*(this->errorD) = 0;
this->prevCmd = 0;
this->limitState = 0;
*(this->cmdD) = 0;
*(this->cmdDd) = 0;
*(this->pOutput) = 0;
this->state = STATE_TUNE_IDLE;
return;
}
period = periodNs * 0.000000001;
periodRecip = 1.0 / period;
if(*(this->maxError) != 0.0){
if(error > *(this->maxError)){
error = *(this->maxError);
}else if(error < -*(this->maxError)){
error = -*(this->maxError);
}
}
if(error > *(this->deadband)){
error -= *(this->deadband);
}else if(error < -*(this->deadband)){
error += *(this->deadband);
}else{
error = 0;
}
*(this->errorD) = (error - this->prevError) * periodRecip;
this->prevError = error;
if(*(this->maxErrorD) != 0.0){
if(*(this->errorD) > *(this->maxErrorD)){
*(this->errorD) = *(this->maxErrorD);
}else if(*(this->errorD) < -*(this->maxErrorD)){
*(this->errorD) = -*(this->maxErrorD);
}
}
prevCmdD = *(this->cmdD);
*(this->cmdD) = (*this->pCommand - this->prevCmd) * periodRecip;
this->prevCmd = *this->pCommand;
if(*(this->maxCmdD) != 0.0){
if(*(this->cmdD) > *(this->maxCmdD)){
*(this->cmdD) = *(this->maxCmdD);
}else if(*(this->cmdD) < -*(this->maxCmdD)){
*(this->cmdD) = -*(this->maxCmdD);
}
}
*(this->cmdDd) = (*(this->cmdD) - prevCmdD) * periodRecip;
if(*(this->maxCmdDd) != 0.0){
if(*(this->cmdDd) > *(this->maxCmdDd)){
*(this->cmdDd) = *(this->maxCmdDd);
}else if(*(this->cmdDd) < -*(this->maxCmdDd)){
*(this->cmdDd) = -*(this->maxCmdDd);
}
}
if(!*this->pEnable){
*(this->errorI) = 0;
*this->pOutput = 0;
this->limitState = 0;
return;
}
if(error * this->limitState <= 0.0){
*(this->errorI) += error * period;
}
if(*(this->maxErrorI) != 0.0){
if(*(this->errorI) > *(this->maxErrorI)){
*(this->errorI) = *(this->maxErrorI);
}else if(*(this->errorI) < -*(this->maxErrorI)){
*(this->errorI) = -*(this->maxErrorI);
}
}
output =
*(this->bias) + *(this->pGain) * error + *(this->iGain) * *(this->errorI) +
*(this->dGain) * *(this->errorD);
output += *this->pCommand * *(this->ff0Gain) + *(this->cmdD) * *(this->ff1Gain) +
*(this->cmdDd) * *(this->ff2Gain);
if(*(this->maxOutput) != 0.0){
if(output > *(this->maxOutput)){
output = *(this->maxOutput);
this->limitState = 1;
}else if(output < -*(this->maxOutput)){
output = -*(this->maxOutput);
this->limitState = -1;
}else{
this->limitState = 0;
}
}
*this->pOutput = output;
}