void loop()
{
  input = encoderRead();

  if(new_kP != old_kP)
  {
      myPID.SetTunings(new_kP,0,0);
      old_kP = new_kP;
  }

  myPID.Compute();
  double motorInput;
  if (output < -1)
  {
    motorInput = (112.0/127.0)*output -15;
  }
  else if (output > 1)
  {
    motorInput = (112.0/127.0)*output +15;
  }
  else
  {
    motorInput = output;
  }

  motorInput = motorInput+127.0;

  mySerial.write((int)motorInput);
  delay(120);
}
Exemplo n.º 2
0
int main()
{
    Network network;
    Accelerometer imu;
    imu.bypassDrift();
    motors.setToZero();

    signal(SIGABRT, sigHandler);
    signal(SIGINT, sigHandler);
    signal(SIGKILL, sigHandler);
    signal(SIGQUIT, sigHandler);
    signal(SIGTERM, sigHandler);

    float ypr[3];
    while(true) {
        if(imu.getFIFOCount() > 42) {
            imu.getYawPitchRoll(ypr);

            float p_computed = p_pid.compute(ypr[1], p_target), r_computed = r_pid.compute(ypr[2], r_target);
            motors.setSpeed(MOTOR_FL, throttle + r_computed - p_computed);
            motors.setSpeed(MOTOR_BL, throttle + r_computed + p_computed);
            motors.setSpeed(MOTOR_FR, throttle - r_computed - p_computed);
            motors.setSpeed(MOTOR_BR, throttle - r_computed + p_computed);

            network.send(SET_MEASURED_VALUES, ypr, sizeof(float)*3, false);
        }
    } 

    exit(EXIT_SUCCESS);
}
Exemplo n.º 3
0
void setup() {
  // put your setup code here, to run once:
  pinMode(pinHeater, OUTPUT);
  pinMode(pinThermistor, INPUT);
  digitalWrite(pinHeater, 0);

  Serial.begin(57600);
  Serial.setTimeout(250); // Set timeout to 250ms

  // Restore calibration if already set.
  thermCalibrated = true;
  if(EEPROM.read(thermStepSetAddr) == 0) {
    unsigned char setPoint[4];
    for(int i = 0; i < 4; i++)
      setPoint[i] = EEPROM.read(thermStepAddr+i);
    thermStep = *((float*)setPoint);
  } else {
    thermCalibrated = false;
  }
  
  if(EEPROM.read(thermYIntSetAddr) == 0) {
    unsigned char setPoint[4];
    for(int i = 0; i < 4; i++)
      setPoint[i] = EEPROM.read(thermYIntAddr+i);
    thermYInt = *((float*)setPoint);
  } else {
    thermCalibrated = false;
  }

  pid.SetOutputLimits(0.0f, 1.0f);
  pid.SetSampleTime(250); // 250ms compute period
  pid.SetMode(MANUAL);
}
Exemplo n.º 4
0
void computePID(void) {
	  if((pitch_set < PITCH_MIN) || (pitch_set > PITCH_MAX)) pitch_set = pitch_set_last;
	  if((roll_set < ROLL_MIN) || (roll_set > ROLL_MAX)) roll_set = roll_set_last;
	  if((yaw_set < YAW_MIN) || (yaw_set > YAW_MAX)) yaw_set = yaw_set_last;

	  pitch_set_last = pitch_set;
	  roll_set_last = roll_set;
	  pitch_set_last = pitch_set;

	  //////////////////////////////////////////////////////
	  //////////////////////////////////////////////////////
	  // FOR TEST ONLY /////////////////////////////////////
	  //////////////////////////////////////////////////////
	  //////////////////////////////////////////////////////
	  ypr[0] = 0;

	  if(abs(ypr[0]-yprLast[0])>30) ypr[0] = yprLast[0];
	  if(abs(ypr[1]-yprLast[1])>30) ypr[1] = yprLast[1];
	  if(abs(ypr[2]-yprLast[2])>30) ypr[2] = yprLast[2];

	  yprLast[0] = ypr[0];
	  yprLast[1] = ypr[1];
	  yprLast[2] = ypr[2];

	  pitchReg.Compute();
	  rollReg.Compute();
	  yawReg.Compute();

	  //trace_printf("yprPID: %f, %f, %f\n", bal_yaw, bal_pitch, bal_roll);
}
Exemplo n.º 5
0
bool GMEventManager::AddNewGMEvent (Client* client, csString eventName, csString eventDescription)
{
    int newEventID, zero=0;
    PID gmID = client->GetPID();
    int clientnum = client->GetClientNum();
    GMEvent* gmEvent;

    // if this GM already has an active event, he/she cant start another
    if ((gmEvent = GetGMEventByGM(gmID, RUNNING, zero)) != NULL)
    {
        psserver->SendSystemInfo(clientnum,
                                 "You are already running the \'%s\' event.",
                                 gmEvent->eventName.GetDataSafe());
        return false;
    }

    if (eventName.Length() > MAX_EVENT_NAME_LENGTH)
    {
        eventName.Truncate(MAX_EVENT_NAME_LENGTH);
        psserver->SendSystemInfo(client->GetClientNum(),
                                 "Event name truncated to \'%s\'.",
                                 eventName.GetDataSafe());
    }


    // GM can register his/her event
    newEventID = GetNextEventID();

    // remove undesirable characters from the name & description
    csString escEventName, escEventDescription;
    db->Escape(escEventName, eventName.GetDataSafe());
    db->Escape(escEventDescription, eventDescription.GetDataSafe());

    // first in the database
    db->Command("INSERT INTO gm_events(id, gm_id, name, description, status) VALUES (%i, %i, '%s', '%s', %i)",
        newEventID,
        gmID.Unbox(),
        escEventName.GetDataSafe(),
        escEventDescription.GetDataSafe(),
        RUNNING);

    // and in the local cache.
    gmEvent = new GMEvent;
    gmEvent->id = newEventID;
    gmEvent->gmID = gmID;
    gmEvent->EndTime = 0;
    gmEvent->eventName = eventName;
    gmEvent->eventDescription = eventDescription;
    gmEvent->status = RUNNING;
    gmEvents.Push(gmEvent);

    // keep psCharacter upto date
    client->GetActor()->GetCharacterData()->AssignGMEvent(gmEvent->id,true);

    psserver->SendSystemInfo(client->GetClientNum(),
                             "You have initiated a new event, \'%s\'.",
                             eventName.GetDataSafe());

    return true;
}
Exemplo n.º 6
0
int main(){

	/* Test water tank input output
	 * output should decrease */


	double level = 0;
	double vin = 0;
	double vout = 0;
	double ref = 500;
	int period = 100;

	PIDParameters pp(1,2,1,0,0.1,period,ref,0,255,false);
	PID pid;
	pid.updateParameters(pp);
	std::cout << pp << "\n";
	for (int i = 0; i < 7000; i++){
		vin = 0.2* pid.next(level);
		pid.updateStates();
		vout = 0.1 * sqrt(2*9.82*level);
		level = level + (vin - vout) * period/1000;
		if (level < 0) level = 0;
		std::cout << ref - level << "\n";
	}
}
Exemplo n.º 7
0
void PIDControl::optionsPID(int setTimePoint)
{
    //tell the PID to range between 0 and the full window size
    myPID.SetOutputLimits(0, setTimePoint);

    //turn the PID on
    myPID.SetMode(AUTOMATIC);
}
Exemplo n.º 8
0
//Sets up the internal state monitor (PID for heating and Watchdog)
void internalStateSetup() {
  //TMP102.h temperature ranges from -40 to 125 C
  controller.setInputLimits(-40.0, 125.0);
  controller.setOutputLimits(0.0, 1.0); 
  controller.setBias(0.3); // Try experimenting with the bias! 
  controller.setMode(AUTO_MODE);
  controller.setSetPoint(DESIRED_INTERNAL_TEMP);  
  W.Start(WATCH_DOG_RATE); 
}
Exemplo n.º 9
0
void CharCreationManager::HandleCharDelete(MsgEntry* me, Client* client)
{
    psCharDeleteMessage msg(me);
    csString charName = msg.charName;

    if(!charName.Length())
    {
        CPrintf(CON_WARNING,"Client %u sent malformed character name to deletion code!",client->GetClientNum());
        return; // No char
    }

    PID pid = psserver->CharacterLoader.FindCharacterID(client->GetAccountID(), charName);
    csString error;

    if(psserver->CharacterLoader.AccountOwner(charName, client->GetAccountID()))
    {
        // Found the char?
        if(!pid.IsValid())
        {
            psserver->SendSystemError(client->GetClientNum(),"Couldn't find character data!");
            return;
        }

        // Can we delete it?
        if(!psserver->CharacterLoader.DeleteCharacterData(pid, error))
        {
            psserver->SendSystemError(client->GetClientNum(),"Error: %s",error.GetData());
            return;
        }

        // Remove cached objects to make sure that the client gets a fresh character
        // list from the database.
        iCachedObject* obj = psserver->GetCacheManager()->RemoveFromCache(psserver->GetCacheManager()->MakeCacheName("list",client->GetAccountID().Unbox()));
        if(obj)
        {
            obj->ProcessCacheTimeout();
            obj->DeleteSelf();
        }
        obj = psserver->GetCacheManager()->RemoveFromCache(psserver->GetCacheManager()->MakeCacheName("auth", client->GetAccountID().Unbox()));
        if(obj)
        {
            obj->ProcessCacheTimeout();
            obj->DeleteSelf();
        }

        // Ok, we deleted it
        psCharDeleteMessage response(charName, me->clientnum);
        response.SendMessage();
    }
    else
    {
        CPrintf(CON_WARNING,"Character %s not deleted because of non-ownership\n", charName.GetData());
        psserver->SendSystemError(client->GetClientNum(),"You do not own that character!");
        return;
    }
}
Exemplo n.º 10
0
 void handle_enabled() {
   if (settings.enabled) {
     reset_window();
     pid.SetMode(AUTOMATIC);
   } else {
     cancel_autotune();
     pid.SetMode(MANUAL);
     set_heat(false);
   }
 }
Exemplo n.º 11
0
//---------- Functions -------------------------------
void init_pids ()
{
	right_mtr_pid.SetSampleTime(10);
	right_mtr_pid.SetMode(AUTOMATIC);
	//right_mtr_pid.SetOutputLimits(20,240);   //min, max
	left_mtr_pid.SetSampleTime(10);
	left_mtr_pid.SetMode(AUTOMATIC);
	//left_mtr_pid.SetOutputLimits(20,240);   //min, max

}
Exemplo n.º 12
0
 void Stop(PID& pid) {
     pid.SetMode(MANUAL);
     SetSpeed(0);
     
     m_fPower = 0.0;
     m_nTicksPID = 0;
     m_nLastCompute = 0;
     
     analogWrite(POWER, 0);
     pid.SetMode(AUTOMATIC);
 }
Exemplo n.º 13
0
msg_t velocity_node(void *arg) {
	r2p::Node node("velocity");
	r2p::Subscriber<r2p::Velocity3Msg, 5> vel_sub;
	r2p::Velocity3Msg *velp;
	r2p::Subscriber<r2p::QEIMsg, 5> qei1_sub(qei1_callback);
	r2p::Subscriber<r2p::QEIMsg, 5> qei2_sub(qei2_callback);
	r2p::Publisher<r2p::Velocity3Msg> odometry_pub;
	float v;
	float w;

	(void) arg;
	chRegSetThreadName("velocity");

	node.subscribe(qei1_sub, "qei1");
	node.subscribe(qei2_sub, "qei2");
	node.subscribe(vel_sub, "velocity");
	if (!node.advertise(odometry_pub, "odometry")) while(1);

	vel_pid.config(1.0, 3, 0, 0.05, -5.0, 5.0);
//	vel_pid.config(2.0, 2, -0.1, 0.05, -5.0, 5.0);
//	vel_pid.config(5.0, 1.0, 0.00, 0.05, -5.0, 5.0);
//	vel_pid.config(5.0, 1.0, 0.05, 0.05, -5.0, 5.0);
	vel_pid.set(0);

	for (;;) {
		if (!node.spin(r2p::Time::ms(500))) {
			angle_setpoint = 0;
			continue;
		}

		v = (left_speed + right_speed) / 2;
		w = (right_speed - left_speed) / 0.425;
		angle_setpoint = vel_pid.update(v); // 20hz

		r2p::Velocity3Msg *msgp;
		if (odometry_pub.alloc(msgp)) {
			msgp->x = v;
			msgp->y = 0;
			msgp->w = w;
			odometry_pub.publish(*msgp);
		}

		while (vel_sub.fetch(velp)) {
			palTogglePad(LED2_GPIO, LED2);
			vel_setpoint = velp->x;
			w_setpoint = velp->w;
			vel_sub.release(*velp);
		}
		vel_pid.set(vel_setpoint);

	}
	return CH_SUCCESS;
}
Exemplo n.º 14
0
/*Each time this is called, this pets the watchdog and computes how much the heater must be powered in order to maintain 
the desired temperature. If you every want to change the internal workings of the system, this is the function where you put that 
code*/
void internalStateLoop(const void *context) {
    //Pet the watchdog
    W.Pet();
    controller.setProcessValue(internal_temp); //We won't actually read from the TMP 102.h, we'll use the most recent internal temp variable (global).
    // Set the new output. 
    heater = controller.compute();
    printf("What should the output be? %f\n", controller.compute());
    // Now check for termination conditions
    // 1. If the GPS lat,lon exceed the permitted bounds, cut down.
    // 2. If you receive an iridum command telling you to end the flight, cut down.
    // 3. If you've not received an Iridium command in a while (5 hrs), cut down. 
}
Exemplo n.º 15
0
void motorsInit( int leftPin, int rightPin )
{
  servoLeft.attach(leftPin);
  servoRight.attach(rightPin);
  servoLeft.writeMicroseconds(servoSpeedLeft);
  servoRight.writeMicroseconds(servoSpeedRight);
#ifdef USE_PID
  Input = 0;
  Setpoint = 0;
  turningPID.SetMode(AUTOMATIC);
  turningPID.SetOutputLimits(-SERVO_DRIVE_TURN_SPEED, SERVO_DRIVE_TURN_SPEED);
#endif
}
Exemplo n.º 16
0
/*
 * Balance control node
 */
msg_t balance_node(void *arg) {
	r2p::Node node("balance");
	r2p::Subscriber<r2p::TiltMsg, 2> tilt_sub;
	r2p::TiltMsg *tiltp;

	r2p::Publisher<r2p::PWM2Msg> pwm2_pub;
	r2p::PWM2Msg *pwmp;

	int32_t pwm = 0;

	(void) arg;
	chRegSetThreadName("balance");

	node.advertise(pwm2_pub, "pwm2");
	node.subscribe(tilt_sub, "tilt");

	PID pid;
	//pid.config(800, 0.35, 0.09, 0.02, -4000, 4000);

	pid.config(800, 0.35, 0.01, 0.02, -4000, 4000);
	//pid.config(550, 0.6, 0, 0.02, -4000, 4000);
	//pid.config(500, 990.35, 0, 0.02, -4000, 4000);
	//	pid.config(600, 0.35, 0, 0.02, -4000, 4000);
	//
	//
	//
	pid.set(angle_setpoint);

	for (;;) {
		pid.set(angle_setpoint);

		while (!tilt_sub.fetch(tiltp)) {
			r2p::Thread::sleep(r2p::Time::ms(1));
		}

		pwm = pid.update(tiltp->angle); //rad2grad
		tilt_sub.release(*tiltp);

		if (pwm2_pub.alloc(pwmp)) {
			pwmp->value[0] = (pwm - w_setpoint * 100); //FIXME
			pwmp->value[1] = -(pwm + w_setpoint * 100); //FIXME
			pwm2_pub.publish(*pwmp);
			palTogglePad(LED3_GPIO, LED3);
			palSetPad(LED4_GPIO, LED4);
		} else {
			palClearPad(LED4_GPIO, LED4);
		}

	}
	return CH_SUCCESS;
}
Exemplo n.º 17
0
void loop() {

    // 22,0,0 is lowest value for sustaining oscillation
    // 1.48,0,0 is highest value for almost no reverse
    static float P = 1.48, I = 0, D = 0;

    static long setpoint = 0;

    if (Serial.available()) { // User started typing something
        if        (Serial.peek() == 'p') {
            Serial.read();
            Serial.println(F("\r\nGive a P value"));
            scanf_P(PSTR("%f"), &P);
            Serial.println(P);
        } else if (Serial.peek() == 'i') {
            Serial.read();
            Serial.println(F("\r\nGive a I value"));
            scanf_P(PSTR("%f"), &I);
            Serial.println(I);
        } else if (Serial.peek() == 'd') {
            Serial.read();
            Serial.println(F("\r\nGive a D value"));
            scanf_P(PSTR("%f"), &D);
            Serial.println(D);
        } else if (Serial.peek() == 's') {
            Serial.read();
            Serial.println(F("\r\nGive a Setpoint"));
            scanf_P(PSTR("%ld"), &setpoint);
            Serial.println(setpoint);
        } else {
            Serial.println(F("\r\nEnter new PID values"));
            scanf_P(PSTR("%f %f %f"), &P, &I, &D);
            printf_P(PSTR("%f %f %f"), P, I, D);
        }
    }
    botPID.SetPID(P, I, D);

    bot.UpdateEncoders();

    long encdists[4];
    bot.GetDists(encdists);

    long xdist, ydist;
    bot.GetPos(&xdist, &ydist);
    int wheelspd = botPID.GetOutput(xdist, setpoint, micros());
    bot.Run(0, wheelspd);
    
    //printf_P(PSTR(/*"P:%.2f I:%.2f D:%.2f "*/"curdist:%4ld setpoint:%4ld curspd:%4d\n"),
    //                      P,     I,     D,           xdist,     setpoint,  wheelspd);
    delay(10);
}
Exemplo n.º 18
0
void setup(void)
{
    board.init();

    // sleep for 100ms
    board.delayMilliseconds(100);

    // flash the LEDs to indicate startup
    board.ledRedOn();
    board.ledGreenOff();
    for (uint8_t i = 0; i < 10; i++) {
        board.ledRedToggle();
        board.ledGreenToggle();
        board.delayMilliseconds(50);
    }
    board.ledRedOff();
    board.ledGreenOff();

    // initialize our RC, IMU, mixer, and PID controller
    rc.init(&board);
    imu.init(&board);
    pid.init();
    mixer.init(&board, &rc, &pid); 
    msp.init(&board, &imu, &mixer, &rc);

    // set initial time
    previousTime = board.getMicros();

    // always do gyro calibration at startup
    calibratingG = CONFIG_CALIBRATING_GYRO_CYCLES;

    // trigger accelerometer calibration requirement
    haveSmallAngle = true;

} // setup
Exemplo n.º 19
0
PID EntityManager::CopyNPCFromDatabase(PID master_id, float x, float y, float z, float angle, const csString & sector, InstanceID instance, const char *firstName, const char *lastName)
{
    psCharacter * npc = NULL;
    PID new_id;

    npc = psServer::CharacterLoader.LoadCharacterData(master_id, false);
    if (npc == NULL)
    {
        return 0;
    }

    psSectorInfo* sectorInfo = CacheManager::GetSingleton().GetSectorInfoByName( sector );
    if (sectorInfo != NULL)
    {
        npc->SetLocationInWorld(instance,sectorInfo,x,y,z,angle);
    }

    if ( firstName && lastName )
    {
        npc->SetFullName( firstName, lastName );
    }

    if (psServer::CharacterLoader.NewNPCCharacterData(0, npc))
    {
        new_id = npc->GetPID();
        db->Command("UPDATE characters SET npc_master_id=%d WHERE id=%d", master_id.Unbox(), new_id.Unbox());
    }

    delete npc;

    return new_id;
}
Exemplo n.º 20
0
void loop(void) {
	SetTemp=cfg.DispTemp;
	SetHumi=cfg.DispHumi;
	float ndo = read_ds(&ds_stt);
	if(ndo!=9999) NowTemp=ndo;
	read_dht(&dht_temp,&dht_humi);
	double gap = abs(SetTemp-NowTemp); //distance away from setpoint
	if (gap < 10){  //we're close to setpoint, use conservative tuning parameters
		myPID.SetTunings(consKp, consKi, consKd);
	}else{	//we're far from setpoint, use aggressive tuning parameters
		myPID.SetTunings(aggKp, aggKi, aggKd);
	}
	myPID.Compute();
	analogWrite(SSR_PIN, (byte)OutPWM);
	if(dht_humi<SetHumi){	digitalWrite(HUMI_GENERATE, HIGH);}else{		digitalWrite(HUMI_GENERATE, LOW);}
	Display();
}
Exemplo n.º 21
0
void onSetAngleOutputLimits()
{
    Serial.println("Receiving Angle output limits command...");
    float lMin, lMax;
    readLimitsArgs(&lMin, &lMax);
    anglePID.SetOutputLimits(lMin, lMax);
    cmdMessenger.sendCmd(kStatus, "Angle output limits set");
}
Exemplo n.º 22
0
void onSetAnglePID()
{
    Serial.println("Receiving Angle tuning command...");
    float Kp, Ki, Kd;
    readTuningArgs(&Kp, &Ki, &Kd);
    anglePID.SetTunings(Kp, Ki, Kd);
    cmdMessenger.sendCmd(kStatus, "Angle PID tuned");
}
Exemplo n.º 23
0
bool  MotorControl::set_angular_rate(float angular_rate) {
    if (angular_rate < 0 || angular_rate > 1) return false;
    angular_rate_ = angular_rate;
    acceleration_rate_ = 1 - angular_rate;

    pid->setOutputLimits(-angular_rate_, angular_rate_);

    return true;
}
Exemplo n.º 24
0
void setup()
{
    //initialize the variables we're linked to
    Input = analogRead(0);
    Setpoint = 100;

    //turn the PID on
    myPID.SetMode(AUTO);
}
Exemplo n.º 25
0
void loop()
{
    hal.scheduler->delay(20);
    //rc.read_pwm();
    long error  = hal.rcin->read(0) - radio_trim;
    long control= pid.get_pid(error, 1);

    hal.console->print("control: ");
    hal.console->println(control,BASE_DEC);
}
float dutyCycleBasedOnProportionalControl(float currentTemperature)
{
    static PID pid;
    static float temperatureTarget = 0.0f;
    
    float previousTemperatureTarget = temperatureTarget;

    temperatureTarget = currentScript.currentTemperatureTarget();
    if (ISNAN(temperatureTarget)) {
        writeFailure("Temperature setpoint is NAN. Are thermocouples connected?");
        return 0.0f;
    }
    
    if (previousTemperatureTarget != temperatureTarget) {
        PID newPID(temperatureTarget, P_GAIN, 0, 0, PID_OUTPUT_LIMIT);
        pid = newPID;
    }

    return pid.nextControlOutput(currentTemperature, 0);
}
Exemplo n.º 27
0
void setup()
{
  Wire.begin(myAddress);
  Wire.onReceive(i2cReceive);
  Wire.onRequest(i2cRequest);

  pinMode(limit_switch_pin, INPUT); 

  mySerial.begin(9600); // Serial commuication to motor controller start.

  setpoint = 0.0;

  calibration(); // Running the calibration code on every start up

  input = encoderRead();

  myPID.SetMode(AUTOMATIC);
  myPID.SetOutputLimits(-127, 127);
  myPID.SetSampleTime(20);
}
Exemplo n.º 28
0
void control_loop() {
  pinMode( control_pin, OUTPUT );
  
  if(tuning && auto_tune.Runtime()) {
    finish_autotune();
  } else if(!tuning) {
    pid.SetMode(!config.paused);
    pid.SetTunings(profiles[config.driving].kp, profiles[config.driving].ki, profiles[config.driving].kd);
    pid.SetSampleTime(1000 * profiles[config.driving].sample_time);  // Update the control value once per second
    current_target_temp = profiles[config.driving].target_temp;
    pid.Compute();
  }
  
  if(last_power != power) {
    last_power = power;
    analogWrite(control_pin, power);
    Serial.print("Log\tpower\t");
    Serial.println((100.0 * ((float)power / 255.0)));
  }
}
Exemplo n.º 29
0
 bool ComputePID(PID& pid) {
     unsigned long nNow = millis();
     unsigned long nChange = nNow - m_nLastCompute;
     m_fTicksPerSecond = m_nTicksPID / (nChange / 1000.0);
     if(pid.Compute()) { // is the sample time low enough?
         m_nTicksPID = 0;
         m_nLastCompute = nNow;
         analogWrite(POWER, (int)m_fPower);
         return true;
     }
     return false;
 }
Exemplo n.º 30
0
msg_t pid_node(void * arg) {
	pid_conf * conf = reinterpret_cast<pid_conf *>(arg);
	r2p::Node node("pid");
	r2p::Subscriber<r2p::SpeedMsg, 5> speed_sub;
	r2p::Subscriber<r2p::EncoderMsg, 5> enc_sub(enc_callback);
	r2p::SpeedMsg * msgp;
	r2p::Time last_setpoint(0);

	(void) arg;
	chRegSetThreadName("pid");

	speed_pid.config(conf->k, conf->ti, conf->td, conf->ts, -4095.0, 4095.0);

	/* Enable the h-bridge. */
	palSetPad(GPIOB, GPIOB_MOTOR_ENABLE); palClearPad(GPIOA, GPIOA_MOTOR_D1);
	chThdSleepMilliseconds(500);
	pwmStart(&PWM_DRIVER, &pwmcfg);

	node.subscribe(speed_sub, "speed");
	node.subscribe(enc_sub, "encoder");

	for (;;) {
		if (node.spin(r2p::Time::ms(1000))) {
			if (speed_sub.fetch(msgp)) {
				speed_pid.set(msgp->value);
				last_setpoint = r2p::Time::now();
				speed_sub.release(*msgp);
			} else if (r2p::Time::now() - last_setpoint > r2p::Time::ms(1000)) {
				speed_pid.set(0);
			}
		} else {
			// Stop motor if no messages for 1000 ms
			pwm_lld_disable_channel(&PWM_DRIVER, 0);
			pwm_lld_disable_channel(&PWM_DRIVER, 1);
		}
	}

	return CH_SUCCESS;
}