Using a mobile device to control a motor via PID.

I have designed and been using a motor controller via PID with feedback via encoder, and a nrf24 radio linked hand piece which allowed setting of rpm, on/off and a couple of other variables while mobile around the machine.

I have had issues with the PID due to all the other stuff the nano had to do and found myself going to direct writing to pin to control the motor and avoid PID issues.
ie

void computeOutput()
{

  if (Input != Setpoint)
  {
    if (Input < Setpoint)
    {
      output = output + 1;
    }
    if (Input > Setpoint)
    {
      output = output - 1;
    }

    if (output <= 0)
    {
      output = 0;
    }

    if (output >= 255)
    {
      output = 255;
    }

  }
  analogWrite(PWMpin, output);
}

I need to take this to the next level.

This is what I now propose:
Motor is a brushed 12v 200w wheel chair type motor. 40 rpm max after reduction.
Uno controls a BTS7960 based speed controller via PID
Uno and Wemos D1 mini are linked via SPI
Wemos is in AP mode and serves a page/pages to mobile device to select on/off, required rpm, auto on/off via position sensor, variable rate on/off, etc.
Wemos sends actual rpm and on/off status to mobile device.
Wemos receives data from gps module via software serial for variable rate according to ground speed.
Wemos reads induction sensor for auto motor on/off.
Wemos read current to motor via current sensor for esc protection and warn operator of overload.
Wemos sends PID Setpoint and esc enable to Uno

Reasons for doing this?

  • The custom made hand controller became redundant with the addition of new features.

  • [Using a Uno to just handle the PID and no more reduces the problems with PID loop interference from interrupt and serial functions.
    /li]

  • Using a Wemos to direct connect a mobile device via wifi allows almost infinite development of new functions.

So far I have got the Uno and Wemos talking to each other via SPI, the GPS is sending data to the Wemos via ss, the Wemos is able to be accessed via mobile device and serves basic html.

I am trying to decide if the Wemos or the Uno should have the task of reading the rpm of the motor. I am leaning towrds the Wemos due to problems I have experienced before with PID and interrupt functions.

I have no experience with the Wemos as a server and face a steep learning curve to write a sensible html or java interface for the mobile device.

I also struggle with sending data via SPI. I am currently sending strings as per example but of course I need to send integers or an array.

Here is the Wemos code in it's current rough form.

/*
   Pin allocation table
   D0
   D1 (GPio5) Linkage sensor
   D2
   D3 (GPio0) Rx1 Gps
   D4 (GPio2) Tx1 Gps
   D5 (GPio14) Uno13
   D6 (GPio12) Uno12
   D7 (GPio13) Uno11
   D8 (GPio15) Uno10
   A0
*/
//-----------Serial Comms stuff--------------
#include <SoftwareSerial.h>
SoftwareSerial GPSserial(0, 2); //GPS Rx D3, Tx D4

//-------------SPI-----------------------
#include "SPISlave.h"
int spiTxArray[3];//0=rpm, 1=Setpoint, 2=isStarted.
byte spiTxArrayLen = 6;
int spiRxArray[3];
byte spiRxArrayLen = 6;

//------------Wifi Stuff------------------
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include "FS.h"
const char *ssid = "Seeder";
//const char *password = "";

ESP8266WebServer server(80);

void handleRoot() {
  server.send(200, "text/html", "<h1>Seabrook Seeders
<a href='html_test.html'> test</a></h1>");
}
//---------------GPS Stuff------------------
#include <TinyGPS++.h>
static const uint32_t GPSBaud = 9600;

// The TinyGPS++ object
TinyGPSPlus gps;
unsigned long last = 0UL;
float groundSpeed;
bool gpsStatus = false;

//---------------Linkage sensor------
const int linkage = 3;
bool linkageSensor = false;

//--------------other stuff------------------
int Setpoint = 0;
bool isStarted = false;
bool cal = false;
bool variableRate = false;

void setup() {
  GPSserial.begin(9600);
   delay(1000);
   Serial.setDebugOutput(true);
  SPIFFS.begin();
  Serial.begin(115200);
  Serial.println();
  Serial.print("Configuring access point...");
  WiFi.softAP(ssid);// password parameter removed.

  IPAddress myIP = WiFi.softAPIP();
  Serial.print("AP IP address: ");
  Serial.println(myIP);
  server.on("/", handleRoot);
  // server.on("/html_test.html", html_test);
  server.begin();
  Serial.println("HTTP server started");

SPISlave.onData([](uint8_t * data, size_t len) {
        String message = String((char *)data);
        if(message.equals("Hello Slave!")) {
            SPISlave.setData("Hello Master!");
        } else if(message.equals("What speed are we doing?")) {
            char answer[33];
            //sprintf(answer,"Alive for %u seconds!", millis() / 1000);
            sprintf(answer, "Ground speed is %u kph", int(groundSpeed));
            SPISlave.setData(answer);
        } else {
            SPISlave.setData("Say what?");
        }
        Serial.printf("Question: %s\n", (char *)data);
    });

    // The master has read out outgoing data buffer
    // that buffer can be set with SPISlave.setData
    SPISlave.onDataSent([]() {
        Serial.println("Answer Sent");
    });

    // status has been received from the master.
    // The status register is a special register that bot the slave and the master can write to and read from.
    // Can be used to exchange small data or status information
    SPISlave.onStatus([](uint32_t data) {
        Serial.printf("Status: %u\n", data);
        SPISlave.setStatus(millis()); //set next status
    });

    // The master has read the status register
    SPISlave.onStatusSent([]() {
        Serial.println("Status Sent");
    });

    // Setup SPI Slave registers and pins
    SPISlave.begin();

    // Set the status register (if the master reads it, it will read this value)
    SPISlave.setStatus(millis());

    // Sets the data registers. Limited to 32 bytes at a time.
    // SPISlave.setData(uint8_t * data, size_t len); is also available with the same limitation
    SPISlave.setData("Ask me a question!");

pinMode(linkage, INPUT);
}

void loop() {
  server.handleClient();
  readGps();
  debug();
getSetPoint();
readLinkageSensor();
getStartStop();

}
void getSetPoint()
{
 // Setpoint = 20;
  
  if (variableRate == true && cal == false)
  {
    if (gpsStatus == false)
    {
      Setpoint = spiRxArray[1];//ToDo
    }
    else Setpoint = round(((float)spiRxArray[1] / 10) * groundSpeed);
  }

  else  Setpoint = spiRxArray[1];
  
}

void readLinkageSensor()
{
  digitalRead (linkage);
}

void getStartStop()
{
  if (spiRxArray[0] == 1) //start/stop button on.
  {
    if (spiRxArray[3] == 0) //height switch off.
    {
      isStarted = true;
      
    }
    if (linkageSensor == HIGH && spiRxArray[3] == 1) //if ping dist is less than dist set point and height switch is on.
    {
      isStarted = true;
       
    }
    else if (linkageSensor == LOW && spiRxArray[3] == 1)
    {
      isStarted = false;
     
    }

  }

  else
  {
    isStarted = false;

  }

}

void readGps()
{
  // Dispatch incoming characters
  while (GPSserial.available() > 0)
    gps.encode(GPSserial.read());

  if (gps.speed.isValid())
  {
    gpsStatus = true;
    groundSpeed = (gps.speed.kmph());

  }

  else if (millis() - last > 5000)
  {
    if (gps.charsProcessed() < 10)
    {
      gpsStatus = false;
      //      spiTxArray[2] = 0;
    }

    //    else spiTxArray[2] = 1;

    last = millis();
  }
}

void debug()
{
 // Serial.print(" ground speed ");
 // Serial.println(groundSpeed);
}

Hi

Just read your post and was wondering how you used pid to control a motor. I'm a beginner and have a need to control a motor speed with a encoder.

Ben

ullisees:
Hi

Just read your post and was wondering how you used pid to control a motor. I'm a beginner and have a need to control a motor speed with a encoder.

Ben

There are plenty of examples to use in encoder output, (rpm or interval) as the Input for the PID. See PID library examples.

moose4621:
I have had issues with the PID due to all the other stuff the nano had to do

You need to post the complete program if we are to see where the problem might be.

What is a "Wemos" ?

...R

Robin2:
You need to post the complete program if we are to see where the problem might be.

Thanks for responding Robin.
The PID problems were with the previous controller which had ping sensor and opto encoder attached plus other stuff. Zhomeslice has given me a great deal of help with that project, (cannot thank him enough), and we, (he), decided that I should try this latest approach. I haven't got the PID configured in the new controller yet.

I do not wish to return to the old controller since using a mobile device instead of a custom made handpiece seems like a much better option. At least for now.

Robin2:
What is a "Wemos" ?

...R

Wemos D1 mini has ESP8266 +ftdi+voltage reg on board.
https://www.wemos.cc/product/d1-mini.html

Does anyone know if the Wemos D1 mini has an interrupt pin. This might force my decision on which board gets the rpm encoder task.

Hello moose4621
I haven't attempted to put interrupts on the ESP8266 (Wemos D1 mini development board)
but this post is promising:

Note that the ESP8266 has to deal with many other things interrupting them may have adverse affects but keeping the interrupts as short as possible your plan may work well. the ESP8266 is quite powerful! I think its 17 times faster than the UNO.

Z

Thanks Zhomeslice, as always you are so helpful.

Robin2 and I have been working on some tachometer code similar to what you are working on The Latest PID code is working great and robin is working on a smaller fast version that has great promise. so you can update your copy I will attach my PID code and The latest version of the Tachometer code you are familiar with. I found a way to do auto start that is working well. It also works well with a large variety of tachometers from 1 pulse per revolution to 400 pulses per revolution.

Tachometer_Final_Auto_Duration_and_Stall_Recovery.ino (5.12 KB)

PID_v2.h (2.94 KB)

PID_v2.cpp (8.08 KB)

Great code Zhomeslice.
I'll be trying it later today on the motor I intend to use for this machine.
I like what I see.
If this runs well, I will leave the tacho on the Uno with the PID and do everything else on the Wemos.

Looking at your code, zhomeslice, I was in a quandry over what to set SampleDuration at. I will be operating at 1 to 40 rpm with 40 PulsesPerRevolution.
At 1rpm thats 1.5 seconds per pulse. So setting the SampleDuration to anything less than that would be futile, correct? But a 1.5 sec SampleDuration at 40 rpm is probably too slow.

So how do you think this will work?

// Adjust these variables:
double Setpoint = 10;// RPM could be 1 to 40
int PulsesPerRevolution = 40;// From Tachometer How Many Pulses Pur Revolution
int SampleDuration = map(Setpoint, 1, 40, 1500, 50); // <<<===in Milliseconds. Variable according to Setpoint.
double consKp = 1, consKi = 0.01, consKd = 1;

Of course, theSampleDuration definition will have to be moved to loop to allow for Setpoint adjustments.

Looking at the code I could spend some time cleaning up the notes lol. It should be good and work well for you I like the concept of moving most of the code to the esp8266 on my project I am doing the same you should consider using the ISP connections and freeing up the serial communication for troubleshooting and your GPS

The limits on ISP:
Status 1 byte both send and receive
data 32 bytes both send and receive
The benefit is that this is hardware based and so it responds well, The esp and uno code is easy to work with.

in the IDE set the board to the ESP8266 development board of your choice
under the examples scroll down till you find the SPISlave
The Master designated files are for the UNO
The slave file is for the ESP8266
SPISlave_SafeMaster is what I am using/ modifying for my uno and the SPISlave_Test is for the ESP8266

GPIO Pins on the ESP8266/WeMos

    GPIO  WeMos   Name   |   UNO
  ================================
     15     D8     SS    |   D10
     13     D7     MOSI  |   D11
     12     D6     MISO  |   D12
     14     D5     SCK   |   D13


Attached File Notes (UNO / atmega328p side)
I created a class for the Uno side to make life easier ESPSafeMaster.h
the functions I am currently working on readAnyData and writeAnyData are not perfected but they compile
Concept is to take a structure of data and transfer it to the ESP8266 so if this part is confusting, my mind is struggling to piece together the concept also :slight_smile:

These accept any data of any length and type then stuff it through the 32 Byte buffer between the Uno and the ESP8266
template uint16_t readAnyData(T& Destination,uint32_t status )
template uint16_t writeAnyData( const T& Source,uint32_t status)

If this doesn't distract you from your project any ideas or suggestions would be great

ESPSafeMaster.h (2.8 KB)

Thanks zhomeslice. Great info.

I have already got the uno and esp talking over ISP using a pair of examples you posted elsewhere but I will check out your latest ESPSafeMaster.h

Am I to understand that your ESPSafeMaster.h replaces the majority of the SPI Safe Master Demo sketch which I used as a template?

I am still struggling with understanding this stuff whether radio, serial or isp.

template uint16_t readAnyData(T& Destination,uint32_t status )
template uint16_t writeAnyData( const T& Source,uint32_t status)

So if I use my familiar array of data containing rpm, on/off status, etc, would it become:
template uint16_t readAnyData(int spiRxArray[],uint32_t status )
template uint16_t writeAnyData( const int spiTxArray[],uint32_t status)
Correct?

And how do I then initiate read and write in loop?

My Uno code so far: (which is mostly yours) :slight_smile:

/*
   Pin allocation table
   A0
   A1
   A2
   A3
   A4
   A5
   D0
   D1
   D2
   D3 ESC In1
   D4 ESC EN
   D5
   D6
   D7
   D8
   D9
   D10 SPI Wemos D8
   D11 SPI Wemos D7
   D12 SPI Wemos D6
   D13 SPI Wemos D5

*/

//-----------------SPI-------------------
#include <SPI.h>
#include <ESPSafeMaster.h>
template <class T> uint16_t readAnyData(int spiRxArray[], uint32_t status );
template <class T> uint16_t writeAnyData( const int spiTxArray[], uint32_t status);
int spiTxArray[3];
int spiRxArray[3];

//-----------PID stuff--------------------
#include <PID_v2.h>

// Adjust these variables:
double Setpoint = 10;// RPM
int PulsesPerRevolution = 40;// From Tachometer How Many Pulses Pur Revolution
int SampleDuration = map(Setpoint, 1, 40, 1500, 50); // in Milliseconds -- How often do you want to trigger the PID Lower the number the more detailed control but the more the arduino processor time is used up
//SampleDuration needs to be moved to loop.
double consKp = 1, consKi = 0.01, consKd = 1;

#define pwmPin  3
#define interruptPin 2
#define  HBridgeEnable 4//pinMode(4, OUTPUT) digitalWrite(4, HIGH); // enables my H-Bridge Remove line 35 also 
//Define Variables we'll be connecting to

double Input, Output;

//Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint, consKp, consKi, consKd, DIRECT);

// Varables used for Calculations
volatile unsigned long timeX = 1;
unsigned long StallTimer = SampleDuration * 3;
volatile int Counts = 1;
double PulsesPerMinute;
volatile unsigned long LastTime;
volatile int PulseCtrX;
int PulseCtr;
unsigned long Counter;
unsigned long Time;


void setup() {
  pinMode (pwmPin, OUTPUT);
  pinMode (HBridgeEnable, OUTPUT);
  Serial.begin(115200);
  SPI.begin();


  PulsesPerMinute = (60 * 1000000) / (PulsesPerRevolution / Counts);// initialize
  pinMode(interruptPin, INPUT_PULLUP);
  pinMode(pwmPin, OUTPUT);
  //HBridgeEnable // Remove this if you don't need it
  attachInterrupt(digitalPinToInterrupt(interruptPin), sensorInterrupt, FALLING);
  myPID.SetSampleTime(1);
  myPID.SetOutputLimits(0, 255); // Standard PWM Range
  myPID.SetMode(AUTOMATIC);

}

void loop()
{

  SendReceiveData();


  readRpm();



  static unsigned long SpamTimer;
  if ((unsigned long)(millis() - SpamTimer) >= 15000) {
    SpamTimer = millis();
    Setpoint = (Setpoint < 5000) ? 7000 : 3000;
  }

}


void SendReceiveData()
{

}

// New version of sensorInterrupt
void sensorInterrupt()
{
  static int Ctr;
  unsigned long Time;
  Ctr++;
  if (Ctr >= Counts) { // so we are taking an average of "Counts" readings to use in our calculations
    Time = micros();
    timeX += (Time - LastTime); // this time is accumulative ovrer those "Counts" readings
    LastTime = Time;
    PulseCtrX ++; // will usually be 1 unless something else delays the sample
    Ctr = 0;
  }
}

void readRpm()
{
  static unsigned long STime;
  if (!PulseCtrX) {
    if ((unsigned long)(millis() - STime) >= StallTimer) {
      Time = micros();
      timeX += (Time - LastTime); // this time is accumulative ovrer those "Counts" readings
      LastTime = Time;
      PulseCtrX ++; // will usually be 1 unless something else delays the sample
      Serial.print(" Stall \t");
      //Setpoint +=100;
    } else return; // << Added lets not stop interrupts unless we know we are ready (keep other code happy).
  }
  cli ();         // clear interrupts flag
  Time = timeX;   // Make a copy so if an interrupt occurs timeX can be altered and not affect the results.
  timeX = 0;
  PulseCtr = PulseCtrX ;
  PulseCtrX = 0;
  sei ();         // set interrupts flag
  if (PulseCtr > 0) {
    Input =  (double) (PulsesPerMinute /  (double)(( (unsigned long)Time ) *  (unsigned long)PulseCtr)); // double has more percision
    //   PulseCtr = 0; // set pulse Ctr to zero
    AverageCapture(Input);
    debug();
    myPID.Compute();
    analogWrite(pwmPin, Output);
    long DSDur = ((long) Time *  (long)PulseCtr) - ((long) SampleDuration * 1000L);
    Counts -= DSDur * .015;
    Counts = max(Counts, max(1, PulsesPerRevolution * .25));
    Time = 0; // set time to zero to wait for the next rpm trigger.
    Counter += PulseCtr;
    PulseCtr = 0; // set pulse Ctr to zero
    PulsesPerMinute = (60.0 * 1000000.0) / (double)((double)PulsesPerRevolution / (double)Counts);
    STime = millis();
  }
}
float AvgArray[100];
int Readings = 0;
void AverageCapture(float in) {
  static int Position = 0;
  AvgArray[Position] = in;
  Position++;
  Readings++;
  Readings = min (100, Readings); // 100 readings 0-99;
  if (Position >= 100)Position = 0;//99 spots
}
float AverageValue() {
  float Total = 0;
  float Average;
  if (!Readings)return (0.0);
  for (int Position = 0; Position < Readings; Position++) {
    Total += AvgArray[Position];
  }
  Average = Total / Readings;
  spiTxArray[0] = Average;
  return (Average);
}

void debug() {
  char S[20];
  for (static long QTimer = millis(); (long)( millis() - QTimer ) >= 100; QTimer = millis() ) {  // one line Spam Delay at 100 miliseconds
    Serial.print("Counts: "); Serial.print(Counts );
    //    Serial.print(" Target RPM: ");Serial.print(RPM );
    //    Serial.print(" Counts: "); Serial.print(Counts );
    //    Serial.print(" time: "); Serial.print(Time );
    //    Serial.print(" DeltaT: "); Serial.print(Time *  PulseCtr);
    //    Serial.print(" PulseCtr: "); Serial.print(PulseCtr );
    //    Serial.print(" PulsesPerMinute: "); Serial.print(dtostrf(PulsesPerMinute, 6, 1, S));
    Serial.print("\t Average: "); Serial.print(dtostrf(AverageValue(), 6, 1, S));
    //    Serial.print(" Setpoint: "); Serial.print(dtostrf(Setpoint, 6, 1, S) );
    //    Serial.print(" Calculated RPM: "); Serial.print(dtostrf(Input, 6, 1, S));
    //    Serial.print(" Output: "); Serial.print(dtostrf(Output, 6, 2, S));
    Serial.print("\t ");
    //    Serial.println();

  }
}

Just a thought........would raspberry pi be more suited for this type of application ?

nineball:
Just a thought........would raspberry pi be more suited for this type of application ?

That's a question I cannot answer, but an interesting thought. I am very new to arduino but I am a 15 year+ linux user which I understand is what raspberry pi runs so maybe the learning curve wouldn't be so steep.

If you can pass on any info, I would be grateful.

zhomeslice:
Note that the ESP8266 has to deal with many other things interrupting them may have adverse affects but keeping the interrupts as short as possible your plan may work well. the ESP8266 is quite powerful! I think its 17 times faster than the UNO.

Z

I have been wondering for a while now whether the ESP could handle the whole work load. Since the ping sensor is replaced by an induction sensor for height sensing, the work load should be reduced considerably, although the wifi may be as taxing, if not more so than nrf24l01 radio's.
So the ESP would be doing:
AP mode and web server for machine control,
tacho sensor,
height sensor,
current sensor, ??
gps s/serial Rx,
Setpoint calculated according to ground speed,
pwm output via PID.

What do you think? Worth a try?

Question mark on current sensor is because it is there to warn operator of overloaded mechanism. If I could think of a better way to do this.....

moose4621:
I have been wondering for a while now whether the ESP could handle the whole work load. Since the ping sensor is replaced by an induction sensor for height sensing, the work load should be reduced considerably, although the wifi may be as taxing, if not more so than nrf24l01 radio's.
So the ESP would be doing:
AP mode and web server for machine control,
tacho sensor,
height sensor,
current sensor, ??
gps s/serial Rx,
Setpoint calculated according to ground speed,
pwm output via PID.

What do you think? Worth a try?

Question mark on current sensor is because it is there to warn operator of overloaded mechanism. If I could think of a better way to do this.....

Ya! It would resolve many issues with communication and if the interrupts don't affect the web server side of things I don't see a problem with it.

I am still needing to keep 2 separate processors because the ESP has a lack of inputs i need but if you can fit it in 1 processor that would be best!

If you had to breaking out the speed control to a dedicate UNO, this would be a simple way to distribute the load, and only if you needed to. Send the uno the speed you wish to maintain through ISP and it returns the RPM the same way. I would move as much over as possible for the ESP8266 to handle. My only concern is interrupts I haven't found where I read about that but I believe is was on the ESP8266.com site.

I will give it a try.

Mainly because I am currently having trouble understanding the SPI tranfer, and it would substantially reduce the physical size of the assembly. (Not that it's size is a big issue).

Preliminary code attached.

Need to work on the html or java interface for remote device. :confused:

Wemos_D1_Mini_Seeder_Controller.ino (7.85 KB)

I have been playing around with trying to get it all running on just the ESP8266 Wemos D1 mini and, as expected,I have run into a couple of snags.
The esp8266 has interrupts on every digital pin so I just adapted the code to the esp8266, put a voltage divider on the sensor output to 3.3v, and ran it using a H bridge esc.

The tacho is going haywire.
I loaded a simple rpm sensor test, and it ran ok so the fault is in the seeder code somewhere.

Output from serial debug.

Input 26621698103305.79	Setpoint  10.00	DeltaTuS   1036	DeltaTS 0.001036	PTerm -26621698103295.79	ITerm   0.00	DTerm   0.00	Output   0.00
Counts: 2147483647	 Average: 20819828801536.0	  GPS Status 1 Motor Status 0
	Input 309822590218.33	Setpoint  10.00	DeltaTuS  10397	DeltaTS 0.010397	PTerm -309822590208.33	ITerm   0.00	DTerm 117175433684972.05	Output 1023.00
Counts: 2147483647	 Average: 13358925348864.0	  GPS Status 1 Motor Status 0
	Input 1043480878036.93	Setpoint  10.00	DeltaTuS   8321	DeltaTS 0.008321	PTerm -1043480878026.93	ITerm   0.00	DTerm 57200468527101.82	Output 1023.00
Counts: 2147483647	 Average: 5951282741248.0	  GPS Status 1 Motor Status 0
	Input 770628102990.43	Setpoint  10.00	DeltaTuS  10423	DeltaTS 0.010423	PTerm -770628102980.43	ITerm   0.00	DTerm 25629649301607.20	Output 1023.00
Counts: 2147483647	 Average: 1203459457024.0	  GPS Status 1 Motor Status 0
	Input 778826274298.84	Setpoint  10.00	DeltaTuS   9388	DeltaTS 0.009388	PTerm -778826274288.84	ITerm   0.00	DTerm 26698780251314.33	Output 1023.00
Counts: 2147483647	 Average: 1212275884032.0	  GPS Status 1 Motor Status 0
	Input 343634037817.37	Setpoint  10.00	DeltaTuS   9374	DeltaTS 0.009374	PTerm -343634037807.37	ITerm   0.00	DTerm 46505886765092.03	Output 1023.00
Counts: 2147483647	 Average: 25181280010240.0	  GPS Status 1 Motor Status 0
	Input 26403487463114.75	Setpoint  10.00	DeltaTuS   1112	DeltaTS 0.001112	PTerm -26403487463104.76	ITerm   0.00	DTerm 196232590099848.26	Output 1023.00
Counts: 2147483647	 Average: 25199523135488.0	  GPS Status 1 Motor Status 0
	Input 26621698103305.79	Setpoint  10.00	DeltaTuS   1002	DeltaTS 0.001002	PTerm -26621698103295.79	ITerm   0.00	DTerm -217775090011009.23	Output   0.00
Counts: 2147483647	 Average: 25115817410560.0	  GPS Status 1 Motor Status 0
	Input 26403487463114.75	Setpoint  10.00	DeltaTuS   1112	DeltaTS 0.001112	PTerm -26403487463104.76	ITerm   0.00	DTerm 196232590099848.26	Output 1023.00
Counts: 2147483647	 Average: 25194357850112.0	  GPS Status 1 Motor Status 0
	Input 26403487463114.75	Setpoint  10.00	DeltaTuS   1112	DeltaTS 0.001112	PTerm -26403487463104.76	ITerm   0.00	DTerm 196232590099848.26	Output 1023.00
Counts: 2147483647	 Average: 25297315430400.0	  GPS Status 1 Motor Status 0
	Input 26621698103305.79	Setpoint  10.00	DeltaTuS   1001	DeltaTS 0.001001	PTerm -26621698103295.79	ITerm   0.00	DTerm   0.00	Output   0.00
Counts: 2147483647	 Average: 18420108099584.0	  GPS Status 1 Motor Status 0
	Input 343048505910.54	Setpoint  10.00	DeltaTuS  10363	DeltaTS 0.010363	PTerm -343048505900.54	ITerm   0.00	DTerm 113796175757310.71	Output 1023.00
Counts: 2147483647	 Average: 11141761728512.0	  GPS Status 1 Motor Status 0
	Input 280937159471.48	Setpoint  10.00	DeltaTuS  11469	DeltaTS 0.011469	PTerm -280937159461.48	ITerm   0.00	DTerm 42922719356610.23	Output 1023.00
Counts: 2147483647	 Average: 4113123770368.0	  GPS Status 1 Motor Status 0

The other problem I have is sending the rpm to the mobile device. I googled to no avail so I am seeking expert help.

Wemos_D1_Mini_Seeder_Controller.ino (11.8 KB)

Updated code with working remote on - off via mobile device.

Wemos_D1_Mini_Seeder_Controller.ino (12.1 KB)