Skip to content

Commit

Permalink
Very smooth perimeter tracking
Browse files Browse the repository at this point in the history
  • Loading branch information
Holoratte committed Jun 4, 2017
1 parent ce6ac47 commit c6b0eb4
Show file tree
Hide file tree
Showing 6 changed files with 311 additions and 22 deletions.
182 changes: 182 additions & 0 deletions code/ardumower/RunningMedian.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
//
// FILE: RunningMedian.cpp
// AUTHOR: Rob dot Tillaart at gmail dot com
// VERSION: 0.1.13
// PURPOSE: RunningMedian library for Arduino
//
// HISTORY:
// 0.1.00 - 2011-02-16 initial version
// 0.1.01 - 2011-02-22 added remarks from CodingBadly
// 0.1.02 - 2012-03-15 added
// 0.1.03 - 2013-09-30 added _sorted flag, minor refactor
// 0.1.04 - 2013-10-17 added getAverage(uint8_t) - kudo's to Sembazuru
// 0.1.05 - 2013-10-18 fixed bug in sort; removes default constructor; dynamic memory
// 0.1.06 - 2013-10-19 faster sort, dynamic arrays, replaced sorted float array with indirection array
// 0.1.07 - 2013-10-19 add correct median if _cnt is even.
// 0.1.08 - 2013-10-20 add getElement(), add getSottedElement() add predict()
// 0.1.09 - 2014-11-25 float to double (support ARM)
// 0.1.10 - 2015-03-07 fix clear
// 0.1.11 - 2015-03-29 undo 0.1.10 fix clear
// 0.1.12 - 2015-07-12 refactor constructor + const
// 0.1.13 - 2015-10-30 fix getElement(n) - kudos to Gdunge
//
// Released to the public domain
//

#include "RunningMedian.h"

RunningMedian::RunningMedian(const uint8_t size)
{
_size = constrain(size, MEDIAN_MIN_SIZE, MEDIAN_MAX_SIZE);

#ifdef RUNNING_MEDIAN_USE_MALLOC
_ar = (double *) malloc(_size * sizeof(double));
_p = (uint8_t *) malloc(_size * sizeof(uint8_t));
#endif

clear();
}

RunningMedian::~RunningMedian()
{
#ifdef RUNNING_MEDIAN_USE_MALLOC
free(_ar);
free(_p);
#endif
}

// resets all counters
void RunningMedian::clear()
{
_cnt = 0;
_idx = 0;
_sorted = false;
for (uint8_t i = 0; i< _size; i++) _p[i] = i;
}

// adds a new value to the data-set
// or overwrites the oldest if full.
void RunningMedian::add(double value)
{
_ar[_idx++] = value;
if (_idx >= _size) _idx = 0; // wrap around
if (_cnt < _size) _cnt++;
_sorted = false;
}

double RunningMedian::getMedian()
{
if (_cnt > 0)
{
if (_sorted == false) sort();
if (_cnt & 0x01) return _ar[_p[_cnt/2]];
else return (_ar[_p[_cnt/2]] + _ar[_p[_cnt/2 - 1]]) / 2;
}
return NAN;
}

#ifdef RUNNING_MEDIAN_ALL
double RunningMedian::getHighest()
{
return getSortedElement(_cnt - 1);
}

double RunningMedian::getLowest()
{
return getSortedElement(0);
}

double RunningMedian::getAverage()
{
if (_cnt > 0)
{
double sum = 0;
for (uint8_t i=0; i< _cnt; i++) sum += _ar[i];
return sum / _cnt;
}
return NAN;
}

double RunningMedian::getAverage(uint8_t nMedians)
{
if ((_cnt > 0) && (nMedians > 0))
{
if (_cnt < nMedians) nMedians = _cnt; // when filling the array for first time
uint8_t start = ((_cnt - nMedians)/2);
uint8_t stop = start + nMedians;

if (_sorted == false) sort();
double sum = 0;
for (uint8_t i = start; i < stop; i++) sum += _ar[_p[i]];
return sum / nMedians;
}
return NAN;
}

double RunningMedian::getElement(const uint8_t n)
{
if ((_cnt > 0) && (n < _cnt))
{
uint8_t pos = _idx + n;
if (pos >= _cnt) // faster than %
{
pos -= _cnt;
}
return _ar[pos];
}
return NAN;
}

double RunningMedian::getSortedElement(const uint8_t n)
{
if ((_cnt > 0) && (n < _cnt))
{
if (_sorted == false) sort();
return _ar[_p[n]];
}
return NAN;
}

// n can be max <= half the (filled) size
double RunningMedian::predict(const uint8_t n)
{
if ((_cnt > 0) && (n < _cnt/2))
{
double med = getMedian(); // takes care of sorting !
if (_cnt & 0x01)
{
return max(med - _ar[_p[_cnt/2-n]], _ar[_p[_cnt/2+n]] - med);
}
else
{
double f1 = (_ar[_p[_cnt/2 - n]] + _ar[_p[_cnt/2 - n - 1]])/2;
double f2 = (_ar[_p[_cnt/2 + n]] + _ar[_p[_cnt/2 + n - 1]])/2;
return max(med - f1, f2 - med)/2;
}
}
return NAN;
}
#endif

void RunningMedian::sort()
{
// bubble sort with flag
for (uint8_t i = 0; i < _cnt-1; i++)
{
bool flag = true;
for (uint8_t j = 1; j < _cnt-i; j++)
{
if (_ar[_p[j-1]] > _ar[_p[j]])
{
uint8_t t = _p[j-1];
_p[j-1] = _p[j];
_p[j] = t;
flag = false;
}
}
if (flag) break;
}
_sorted = true;
}

// END OF FILE
82 changes: 82 additions & 0 deletions code/ardumower/RunningMedian.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//
// FILE: RunningMedian.h
// AUTHOR: Rob dot Tillaart at gmail dot com
// PURPOSE: RunningMedian library for Arduino
// VERSION: 0.1.13
// URL: http://arduino.cc/playground/Main/RunningMedian
// HISTORY: See RunningMedian.cpp
//
// Released to the public domain
//

#ifndef RunningMedian_h
#define RunningMedian_h

#if defined(ARDUINO) && ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif

#include <inttypes.h>

#define RUNNING_MEDIAN_VERSION "0.1.13"

// prepare for dynamic version
// not tested use at own risk :)
// #define RUNNING_MEDIAN_USE_MALLOC

// conditional compile to minimize lib
// by removeing a lot of functions.
#define RUNNING_MEDIAN_ALL


// should at least be 5 to be practical
// odd size results in a 'real' middle element.
// even size takes the lower of the two middle elements
#define MEDIAN_MIN_SIZE 1
#define MEDIAN_MAX_SIZE 19 // adjust if needed


class RunningMedian
{
public:
explicit RunningMedian(const uint8_t size); // # elements in the internal buffer
~RunningMedian(); // destructor

void clear(); // resets internal buffer and var
void add(const double value); // adds a new value to internal buffer, optionally replacing the oldest element.
double getMedian(); // returns the median == middle element

#ifdef RUNNING_MEDIAN_ALL
double getAverage(); // returns average of the values in the internal buffer
double getAverage(uint8_t nMedian); // returns average of the middle nMedian values, removes noise from outliers
double getHighest(); // returns highest element
double getLowest(); // return lowest element

double getElement(const uint8_t n); // get n'th element from the values in time order
double getSortedElement(const uint8_t n); // get n'th element from the values in size order
double predict(const uint8_t n); // predict the max change of median after n additions

uint8_t getSize() { return _size; }; // returns size of internal buffer
uint8_t getCount() { return _cnt; }; // returns current used elements, getCount() <= getSize()
#endif

protected:
boolean _sorted;
uint8_t _size;
uint8_t _cnt;
uint8_t _idx;

#ifdef RUNNING_MEDIAN_USE_MALLOC
double * _ar;
uint8_t * _p;
#else
double _ar[MEDIAN_MAX_SIZE];
uint8_t _p[MEDIAN_MAX_SIZE];
#endif
void sort();
};

#endif
// END OF FILE
44 changes: 28 additions & 16 deletions code/ardumower/motor.h
Original file line number Diff line number Diff line change
Expand Up @@ -223,17 +223,17 @@ void Robot::motorControlImuRoll(){
// PID controller: track perimeter
void Robot::motorControlPerimeter(){
if (millis() < nextTimeMotorPerimeterControl) return;
nextTimeMotorPerimeterControl = millis() + 100;
nextTimeMotorPerimeterControl = millis() + 30;

if ((millis() > stateStartTime + 5000) && (millis() > perimeterLastTransitionTime + trackingPerimeterTransitionTimeOut)){
// robot is wheel-spinning while tracking => roll to get ground again
if (trackingBlockInnerWheelWhilePerimeterStruggling == 0){
if (perimeterInside) setMotorPWM( -motorSpeedMaxPwm/1.5, motorSpeedMaxPwm/1.5, false);
else setMotorPWM( motorSpeedMaxPwm/1.5, -motorSpeedMaxPwm/1.5, false);}
if (perimeterInside) setMotorPWM( -motorSpeedMaxPwm/2, motorSpeedMaxPwm/2, false);
else setMotorPWM( motorSpeedMaxPwm/2, -motorSpeedMaxPwm/2, false);}

else if (trackingBlockInnerWheelWhilePerimeterStruggling == 1){
if (perimeterInside) setMotorPWM( 0, motorSpeedMaxPwm/1.5, false);
else setMotorPWM( motorSpeedMaxPwm/1.5, 0, false);
if (perimeterInside) setMotorPWM( motorSpeedMaxPwm/6, motorSpeedMaxPwm/2, false);
else setMotorPWM( motorSpeedMaxPwm/2, motorSpeedMaxPwm/6, false);
}

if (millis() > perimeterLastTransitionTime + trackingErrorTimeOut){
Expand All @@ -244,20 +244,32 @@ void Robot::motorControlPerimeter(){
}
return;
}
if (perimeterInside)
perimeterPID.x = -1;
else
perimeterPID.x = 1;

perimeterPID.w = 0;
perimeterPID.y_min = -motorSpeedMaxPwm;
perimeterPID.y_max = motorSpeedMaxPwm;
perimeterPID.max_output = motorSpeedMaxPwm;
perimeterPID.x = 5*((double(perimeterMag)/double(perimeterMagMedian.getHighest())));
if (perimeterInside){
perimeterPID.w = -1;
if (!lastPerimeterTrackInside) perimeterPID.reset();
lastPerimeterTrackInside = 1;
}

else{
perimeterPID.w = 1;
if (lastPerimeterTrackInside) perimeterPID.reset();
lastPerimeterTrackInside = 0;
}
//if (perimeterPID.x > 1) perimeterPID.x = 1;
//else if (perimeterPID.x < -1) perimeterPID.x = -1;




perimeterPID.y_min = -motorSpeedMaxPwm/1.5;
perimeterPID.y_max = motorSpeedMaxPwm/1.5;
perimeterPID.max_output = motorSpeedMaxPwm/1.5;
perimeterPID.compute();
//setMotorPWM( motorLeftPWMCurr +perimeterPID.y,
// motorRightPWMCurr -perimeterPID.y, false);
setMotorPWM( max(-motorSpeedMaxPwm, min(motorSpeedMaxPwm, motorSpeedMaxPwm/2 - perimeterPID.y)),
max(-motorSpeedMaxPwm, min(motorSpeedMaxPwm, motorSpeedMaxPwm/2 + perimeterPID.y)), false);
setMotorPWM( max(-motorSpeedMaxPwm/1.5, min(motorSpeedMaxPwm/1.5, motorSpeedMaxPwm/2.3 - perimeterPID.y)),
max(-motorSpeedMaxPwm/1.5, min(motorSpeedMaxPwm/1.5, motorSpeedMaxPwm/2.3 + perimeterPID.y)), false);
/*Console.print(perimeterPID.x);
Console.print("\t");
Console.println(perimeterPID.y); */
Expand Down
2 changes: 1 addition & 1 deletion code/ardumower/pfod.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,7 @@ void RemoteControl::sendPerimeterMenu(boolean update){
sendSlider("e15", F("Perimeter out reverse time"), robot->perimeterOutRevTime, "", 1, 8000);
sendSlider("e16", F("Perimeter tracking roll time"), robot->perimeterTrackRollTime, "", 1, 8000);
sendSlider("e17", F("Perimeter tracking reverse time"), robot->perimeterTrackRevTime, "", 1, 8000);
sendSlider("e11", F("Transition timeout"), robot->trackingPerimeterTransitionTimeOut, "", 1, 5000);
sendSlider("e11", F("Transition timeout"), robot->trackingPerimeterTransitionTimeOut, "", 1, 10000);
sendSlider("e12", F("Track error timeout"), robot->trackingErrorTimeOut, "", 1, 10000);
sendPIDSlider("e07", F("Track"), robot->perimeterPID, 0.1, 100);
//serialPort->print(F("|e09~Use differential signal "));
Expand Down
20 changes: 15 additions & 5 deletions code/ardumower/robot.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,9 @@ Robot::Robot(){
imuRollHeading = 0;
imuRollDir = LEFT;

perimeterMag = 0;
perimeterMag = 1;
perimeterMagMedian.add(perimeterMag);
lastPerimeterTrackInside = 1;
perimeterInside = true;
perimeterCounter = 0;
perimeterLastTransitionTime = 0;
Expand Down Expand Up @@ -385,15 +387,23 @@ void Robot::readSensors(){


if ((perimeterUse) && (millis() >= nextTimePerimeter)){
nextTimePerimeter = millis() + 50; // 50
nextTimePerimeter = millis() + 30; // 50
perimeterMag = readSensor(SEN_PERIM_LEFT);
perimeterMagMedian.add(abs(perimeterMag));
if ((perimeter.isInside(0) != perimeterInside)){
perimeterCounter++;
perimeterLastTransitionTime = millis();
perimeterInside = perimeter.isInside(0);
}
if (perimeterInside < 0) setActuator(ACT_LED, HIGH);
else setActuator(ACT_LED, LOW);
static boolean LEDstate = false;
if (perimeterInside && !LEDstate) {
setActuator(ACT_LED, HIGH);
LEDstate = true;
}
if (!perimeterInside && LEDstate) {
setActuator(ACT_LED, LOW);
LEDstate = false;
}
if ((!perimeterInside) && (perimeterTriggerTime == 0)){
// set perimeter trigger time
if (millis() > stateStartTime + 2000){ // far away from perimeter?
Expand All @@ -407,7 +417,7 @@ void Robot::readSensors(){
&& (stateCurr != STATE_STATION_CHARGING) && (stateCurr != STATE_STATION_CHECK)
&& (stateCurr != STATE_STATION_REV) && (stateCurr != STATE_STATION_ROLL)
&& (stateCurr != STATE_STATION_FORW) && (stateCurr != STATE_REMOTE) && (stateCurr != STATE_PERI_OUT_FORW)
&& (stateCurr != STATE_PERI_OUT_REV) && (stateCurr != STATE_PERI_OUT_ROLL)) {
&& (stateCurr != STATE_PERI_OUT_REV) && (stateCurr != STATE_PERI_OUT_ROLL) && (stateCurr != STATE_PERI_TRACK)) {
Console.println("Error: perimeter too far away");
addErrorCounter(ERR_PERIMETER_TIMEOUT);
setNextState(STATE_ERROR,0);
Expand Down

0 comments on commit c6b0eb4

Please sign in to comment.