GPS controlled boat (solved)

Hi
I am a noob. I have only very basic ability when it comes to writing code. I am limited to searching for examples of something similar and then cutting and pasting and then trying to make it work. I have completed a few projects successfully this way but this time I have bit off more than I can chew.

I am trying to build a GPS controlled "torpedo" capable of travelling a few hundred meters from a beach hauling a fishing line and then drop a bait. Here is a link to something similar

" Fish Seeker Contiki - Catch more fish, all year round! – TV Shop"

I have found a similar project on this forum "title GPS controlled boat waypoint setting" where OP Beretta
was doing exactly the same thing but it doesn't look like he posted the completed project. its a few years ago so I thought I would start a new post.

I have followed all the links and have tried to use the NeoGPS library posted by SlashDevlin and also the advice he has given in many many other posts relating to his Library.

I have forked together code to run the motors to provide steering. I have also found code to read heading from a QMC5883L magnetometer and some code to operate a servo to release a latch and drop the bait. I have also fabricated the torpedo. I have found a bluetooth control panel app for smart phone by Martyn Currey which allows me to configure a few initial settings and transfer to the arduino via bluetooth. I have got the code for this bit working as well I think.

So my circuit comprises, Arduino nano v3. Gps Neo 6m module. QMC5883L magnetometer, L298N motor driver, HC 05 bluetooth module, 9g Servo motor.

I was hoping to be able to
arrive at a beach.
Switch on the Arduino
Acquire GPS fix for the shore.
Acquire a bearing from the QMC button press on bluetooth app
send a distance of say 400m via bluetooth to the board.
Calculate a waypoint for the baitdrop 400m off shore at bearing.
send a motor start signal via bluetooth app
launch the torpedo
navigate to waypoint and stop motors and drop bait.

The part I am struggling with is the code to fix the shore location
fix the baitdrop location and then use the GPS read of current location to start navigating.
I'm sure it is something blindingly obvious but at the moment I can't see the wood for the trees.

The initial part of the code does seem to work.
It will serial print the shore location
It will serial print the baitdrop location
it will calculate distance and bearing to the baitdrop but the last bit of the loop I want the torpedo to read its location "fix" compare that to baitdrop distance and bearing and then start navigating Any help or advice would be appreciated

Its a long post so will attach code in the next post

[size=0.8em]Code: [url=https://arduinogetstarted.com/tools/arduino-code-highlighter]see how to post code[/url] [/size]

---



```
[size=0.8em]#include[nobbc] <NMEAGPS.h>[/nobbc]

[nobbc]// Kontiki control sketch fixes gps position on the shore and sets a baitdrop[/nobbc]
[nobbc]// position at a set distance and bearing from the beach[/nobbc]

NMEAGPS    GPS;
#define[nobbc] gpsPort [/nobbc]Serial[nobbc][/nobbc]
#define[nobbc] GPS_PORT_NAME [/nobbc]"Serial"

using namespace NeoGPS;
static[nobbc] gps_fix    shore;            [/nobbc][nobbc]// Initial shore location good GPS data[/nobbc]
static[nobbc] Location_t shoreLoc;        [/nobbc][nobbc]// shore gps fix stored in special format[/nobbc]
static[nobbc] Location_t baitDrop;[/nobbc]
static uint16_t[nobbc]  count = 0;          [/nobbc][nobbc]// number of samples[/nobbc]
float[nobbc] Shore_bearing = 20;  [/nobbc][nobbc]// degrees heading from shore to baitdrop[/nobbc]
float[nobbc] Shore_bearingrads = 0;[/nobbc]
float[nobbc] SendDistance = .40; [/nobbc][nobbc]//km  distance shore to baitdrop[/nobbc]

void setupnobbc[/nobbc]
{
[nobbc]  [/nobbc]Serial[nobbc].[/nobbc]beginnobbc;[/nobbc]
  delaynobbc;[/nobbc]
[nobbc]  [/nobbc]Serial[nobbc].[/nobbc]println[nobbc]( F([/nobbc]"GPS Shore to baitdrop!"[nobbc]) ); [/nobbc][nobbc]// F macro saves RAM[/nobbc]

[nobbc]  gpsPort.[/nobbc]beginnobbc;[/nobbc]
[nobbc]  GPS.send_P( &gpsPort, F([/nobbc]"PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"[nobbc]) ); [/nobbc][nobbc]// RMC only[/nobbc]
[nobbc]  GPS.send_P( &gpsPort, F([/nobbc]"PMTK220,1000"[nobbc]) );  [/nobbc][nobbc]// 1 Hz update rate[/nobbc]
}

void loopnobbc {[/nobbc]

[nobbc]// Process GPS characters[/nobbc]
  if[nobbc] (GPS.[/nobbc]availablenobbc) {[/nobbc]

[nobbc]// A new fix structure is  ready.[/nobbc]
[nobbc]    gps_fix fix = GPS.[/nobbc]readnobbc;[/nobbc]

if[nobbc] (fix.valid.location) {[/nobbc]
      if[nobbc] (count == 0) {[/nobbc]
        shore = fix;
        count = 1;
        shoreLoc.lat(shore.location.lat());
        shoreLoc.lon(shore.location.lon());

Shore_bearingrads = Shore_bearing * (M_PI / 180);

[nobbc]        [/nobbc]Serial[nobbc].[/nobbc]printlnnobbc;[/nobbc]
[nobbc]        [/nobbc]Serial[nobbc].[/nobbc]printlnnobbc;[/nobbc]
[nobbc]        [/nobbc]Serial[nobbc].[/nobbc]printlnnobbc;[/nobbc]

Location_t baitDrop( shoreLoc );
        baitDrop.OffsetBy( SendDistance / Location_t::EARTH_RADIUS_KM, Shore_bearingrads );
[nobbc]        [/nobbc]Serial[nobbc].[/nobbc]printlnnobbc;[/nobbc]
[nobbc]        [/nobbc]Serial[nobbc].[/nobbc]printlnnobbc;[/nobbc]
[nobbc]        [/nobbc]Serial[nobbc].[/nobbc]printlnnobbc;[/nobbc]
[nobbc]        [/nobbc]Serial[nobbc].[/nobbc]println[nobbc]( F([/nobbc]"Shore_bearing radians and degrees"[nobbc]));[/nobbc]
[nobbc]        [/nobbc]Serial[nobbc].[/nobbc]printlnnobbc;[/nobbc]
[nobbc]        [/nobbc]Serial[nobbc].[/nobbc]printlnnobbc;[/nobbc]
        float[nobbc] dist = NeoGPS::Location_t::DistanceKm( shore.location, baitDrop );[/nobbc]
        float[nobbc] bearing = shore.location.BearingToDegrees( baitDrop );[/nobbc]
[nobbc]        [/nobbc]Serial[nobbc].[/nobbc]printlnnobbc;[/nobbc]
[nobbc]        [/nobbc]Serial[nobbc].[/nobbc]printlnnobbc;[/nobbc]

} else[nobbc] {[/nobbc]
[nobbc]        gps_fix fix = GPS.[/nobbc]readnobbc;[/nobbc]

float[nobbc] dist = NeoGPS::Location_t::DistanceKm( fix.location, baitDrop );[/nobbc]
        float[nobbc] bearing = fix.location.BearingToDegrees( baitDrop );[/nobbc]
[nobbc]        [/nobbc]Serial[nobbc].[/nobbc]printnobbc;[/nobbc]
[nobbc]        [/nobbc]Serial[nobbc].[/nobbc]printnobbc;[/nobbc]
[nobbc]        [/nobbc]Serial[nobbc].[/nobbc]printnobbc;[/nobbc]
[nobbc]        [/nobbc]Serial[nobbc].[/nobbc]printnobbc;[/nobbc]
[nobbc]        [/nobbc]Serial[nobbc].[/nobbc]printnobbc;[/nobbc]
[nobbc]        [/nobbc]Serial[nobbc].[/nobbc]printnobbc;[/nobbc]
[nobbc]        [/nobbc]Serial[nobbc].[/nobbc]printnobbc;[/nobbc]

}

}
  }

}[/size]
```

|

Please can you post your code in code tags as described in the few to use the forum sticks at the top. What you have is pretty, but won't compile.

How will you steer the torpedo? I don't see any mention of a rudder.

To navigate to a waypoint, in addition to the GPS, you need rudder or twin screw steering and an electronic compass to determine the vessel's heading. Most people use PID to control the steering, by comparing the bearing and the heading.

This very advanced project is not something that anyone can do with just "cut and paste" skills. However, most of the work has been done in the Ardupilot project.

Bad Start. I missed the code tags. My apologises. Hope this is correct

#include <NMEAGPS.h>

// Kontiki control sketch fixes gps position on the shore and sets a baitdrop
// position at a set distance and bearing from the beach

NMEAGPS     GPS;
#define gpsPort Serial
#define GPS_PORT_NAME "Serial"



using namespace NeoGPS;
static gps_fix    shore;            // Initial shore location good GPS data
static Location_t shoreLoc;         // shore gps fix stored in special format
static Location_t baitDrop;
static uint16_t   count = 0;          // number of samples
float Shore_bearing = 20;  // degrees heading from shore to baitdrop
float Shore_bearingrads = 0;
float SendDistance = .40; //km  distance shore to baitdrop

void setup()
{
  Serial.begin(9600);
  delay(500);
  Serial.println( F("GPS Shore to baitdrop!") ); // F macro saves RAM

  gpsPort.begin(9600);
  GPS.send_P( &gpsPort, F("PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0") ); // RMC only
  GPS.send_P( &gpsPort, F("PMTK220,1000") );   // 1 Hz update rate
}



void loop() {

  // Process GPS characters
  if (GPS.available( gpsPort )) {

    // A new fix structure is  ready.
    gps_fix fix = GPS.read();

    if (fix.valid.location) {
      if (count == 0) {
        shore = fix;
        count = 1;
        shoreLoc.lat(shore.location.lat());
        shoreLoc.lon(shore.location.lon());

        Shore_bearingrads = Shore_bearing * (M_PI / 180);

        Serial.println( F("Shore:"));
        Serial.println(shoreLoc.lat());
        Serial.println(shoreLoc.lon());

        Location_t baitDrop( shoreLoc );
        baitDrop.OffsetBy( SendDistance / Location_t::EARTH_RADIUS_KM, Shore_bearingrads );
        Serial.println( F("Baitdrop:") );
        Serial.println( baitDrop.lat());
        Serial.println( baitDrop.lon());
        Serial.println( F("Shore_bearing radians and degrees"));
        Serial.println(Shore_bearingrads);
        Serial.println(Shore_bearing);
        float dist = NeoGPS::Location_t::DistanceKm( shore.location, baitDrop );
        float bearing = shore.location.BearingToDegrees( baitDrop );
        Serial.println(dist);
        Serial.println(bearing);

      } else {
        gps_fix fix = GPS.read();

        float dist = NeoGPS::Location_t::DistanceKm( fix.location, baitDrop );
        float bearing = fix.location.BearingToDegrees( baitDrop );
        Serial.print(dist);
        Serial.print(',');
        Serial.print(bearing);
        Serial.print(',');
        Serial.print(baitDrop.lat());
        Serial.print(',');
        Serial.print(fix.location.lat());

      }


    }
  }

}

Thanks for the replies.
I used the Arduino code highlighter tool and posted the BBC code which was an error. Hope this is correct now.

The above code does compile. The BBC code does not as rightly pointed out.
Steering is by 2 motors, speed one up and slow the other down to provide steering. I have written a loop to achieve this using a L298n motor driver and the PWM pins of the nano. That bit works at least on the bench. The QMC5883L provides a compass heading which I can compare to the target bearing and proportionally (Not full PID) adjust the steering. I have got everything hooked up on a board and by slowly rotating it I can test the motor control and it seems to work. I did not include that portion of code here.

The GPS part of the code will return a first fix which I try to save as "shore" in the code. It will also calculate a location a fixed distance and bearing from the Shore and save it as a location "baitdrop". For the moment I have just entered 400m as the distance and 20 degrees as the bearing.The code will also confirm the distance and bearing of the baitdrop from the shore. I can confirm all the above by the serial.println lines. Feeding the lat and longs returned by the above into google confirm shore is my house and baitdrop is 400m away.

Where I go wrong is in the last Else portion. I was hoping to GPS.read the fix of the current location (same as the shore before the torpedo is launched) calculate the bearing and distance to the bait drop and pass the result to my steering function. However when I serial.println from the last "Else" part of the code it returns zero for distance and 90 for bearing and zero for lat and zero for long. Which is not what I expected.

I appreciate the entire project is quite complex but broken down into smaller portions it is easier to understand.

The NeoGPS library contains examples which do most of what I am trying to do and I have used them to get this far. But I am struggling with te last bit.

I have used a NEO-6M GPS. It tells heading and speed. Make the torpedo go in the selected direction the required time at the speed told by the GPS. Your are surely not looking for millimeter precision.

Drop the gps.read from the else. I think you already have a valid fix at that point, you're just deciding whether to store it for your shore position or to use it for navigation.

@Jremington. Thanks for the ardupilot link I will check it out.

@Railroader
Your right precision is not important in this application. If it gets to within 20 or 30m that will be fine. I just don't want it 100m down the beach because of wind or current.

@Wildbill
your right it already has a fix. I dropped the gps.read from the else and I now get the correct return for the fix lat and lon but the distance to the baitdrop, bearing to the baitdrop and baitdrop lat that are returned by the serial print are not what I expected. Let me work on it for awhile and come back and let you know whats happening.

I tried a couple of things. I think the problem lies in the way I am declaring the Location_t baitdrop.

If I do it like this, the serial print from the while section gives me strange values for nDist nBearing and baitDrop.lat and baitDrop.lon. Image of serial print attached. baitdrop missing.jpeg

#include <NMEAGPS.h>

// Kontiki control sketch fixes gps position on the shore and sets a baitdrop
// position at a set distance and bearing from the beach

NMEAGPS     GPS;
#define gpsPort Serial
#define GPS_PORT_NAME "Serial"



using namespace NeoGPS;
static gps_fix    shore;            // Initial shore location good GPS data
static Location_t shoreLoc;         // shore gps fix stored in special format
static Location_t baitDrop;
static uint16_t   count = 0;          // number of samples
float Shore_bearing = 20;  // degrees heading from shore to baitdrop
float Shore_bearingrads = 0;
float SendDistance = .40; //km  distance shore to baitdrop
static Location_t currLoc;
void setup()
{
  Serial.begin(9600);
  delay(500);
  Serial.println( F("GPS Shore to baitdrop!") ); // F macro saves RAM

  gpsPort.begin(9600);
  GPS.send_P( &gpsPort, F("PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0") ); // RMC only
  GPS.send_P( &gpsPort, F("PMTK220,1000") );   // 1 Hz update rate
}



void loop() {

  // Process GPS characters
  if (GPS.available( gpsPort )) {

    // A new fix structure is  ready.
    gps_fix fix = GPS.read();

    if (fix.valid.location) {
      if (count == 0) {
        shore = fix;
        count = 1;
        shoreLoc.lat(shore.location.lat());
        shoreLoc.lon(shore.location.lon());

        Shore_bearingrads = Shore_bearing * (M_PI / 180);

        Serial.println( F("Shore:"));
        Serial.println(shoreLoc.lat());
        Serial.println(shoreLoc.lon());

        Location_t baitDrop( shoreLoc );
        baitDrop.OffsetBy( SendDistance / Location_t::EARTH_RADIUS_KM, Shore_bearingrads );
        Serial.println( F("Baitdrop:") );
        Serial.println( baitDrop.lat());
        Serial.println( baitDrop.lon());
        Serial.println( F("Shore_bearing radians and degrees"));
        Serial.println(Shore_bearingrads);
        Serial.println(Shore_bearing);
        float dist = NeoGPS::Location_t::DistanceKm( shoreLoc, baitDrop );
        float bearing = shoreLoc.BearingToDegrees( baitDrop );
        Serial.println(dist);
        Serial.println(bearing);

      } else {
       
        currLoc.lat(fix.location.lat());
        currLoc.lon(fix.location.lon());
        float nDist = NeoGPS::Location_t::DistanceKm( currLoc, baitDrop );//recalculated distance to baitdrop used during navigation
        float nBearing = currLoc.BearingToDegrees( baitDrop );// recalculated bearing
        Serial.println(F("nDist,nBearing,baitDrop.lat,baitDrop.lon,baitDrop.lon,currLoc.lat,currLoc.lat,shoreLoc.lat,shoreLoc.lon,fix.location.lat,fix.location.lon")); 
        Serial.print( nDist );
        Serial.print(',');
        Serial.print( nBearing );
        Serial.print(',');
        Serial.print( baitDrop.lat());
        Serial.print(',');
        Serial.print( baitDrop.lon());
        Serial.print(',');
        Serial.print(currLoc.lat());
        Serial.print(',');
        Serial.print(currLoc.lon());
        Serial.print(',');
        Serial.print(shoreLoc.lat());        
        Serial.print(',');
        Serial.print(shoreLoc.lon());
        Serial.print(',');
        Serial.print(fix.location.lat());
        Serial.print(',');
        Serial.print(fix.location.lon());

But if I do it like this i get the values I expected. baitdrop repeat.jpeg

} else {
        Location_t baitDrop( shoreLoc );
        baitDrop.OffsetBy( SendDistance / Location_t::EARTH_RADIUS_KM, Shore_bearingrads );
        currLoc.lat(fix.location.lat());
        currLoc.lon(fix.location.lon());
        float nDist = NeoGPS::Location_t::DistanceKm( currLoc, baitDrop );//recalculated distance to baitdrop used during navigation
        float nBearing = currLoc.BearingToDegrees( baitDrop );// recalculated bearing

Is anyone familiar with using the Location_t in the NeoGPS library and could help sort me out please?
ino for the second option attached

mark_location_v4.1.ino (3.4 KB)

You have mixed up local and global variables.

You have a global baitDrop, but in the first fragment, you declare a local version and set its location. It prints properly in the if clause, but when you get to the else clause, that local has gone out of scope and you're using the global version. Its location has never been set - lat & lon are both zero, hence the huge distance it shows from the shore location.

I'm not familiar with your library, but looking at the bits of code you have that are working, I suspect that you need to change this:

        Location_t baitDrop( shoreLoc );
        baitDrop.OffsetBy( SendDistance / Location_t::EARTH_RADIUS_KM, Shore_bearingrads );
        Serial.println( F("Baitdrop:") );
        Serial.println( baitDrop.lat());
        Serial.println( baitDrop.lon());

To

        baitDrop.lat(shore.location.lat());
        baitDrop.lon(shore.location.lon());
        baitDrop.OffsetBy( SendDistance / Location_t::EARTH_RADIUS_KM, Shore_bearingrads );
        Serial.println( F("Baitdrop:") );
        Serial.println( baitDrop.lat());
        Serial.println( baitDrop.lon());

wildbill:
You have mixed up local and global variables.

That's exactly what I have done. And your suggestion works as you said.
Thanks for your input.

I will go away and work on the next bit.

Hello again.
I have made significant progress on this project but have hit a problem I can't resolve.
I broke the project down into smaller steps and tested each bit before trying to combine all the elements.
My Bluetooth control panel seems to work without issue (BCP by Martyn Currie) I can connect to the bluetooth using a tablet and send initial settings to the arduino. I can see them being sent in the serial window.
The QMC5883l compass works though I did need to make sure the compass read call did not block the CPU. The GPS seems to work fine as well. I am using the NeoGPS library by /Dev. Again I hit some problems which I thought might be caused by printing too much during the quiet time of the GPS unit so I followed the instructions here to implement an interrupt driven approach.
"NeoGPS/Troubleshooting.md at master · SlashDevin/NeoGPS · GitHub"
Watching my serial window I don't seem to be dropping any GPS data so I think that is working correctly as well.
I made a sketch called PIDsteering attached below which will compare the current heading to a fixed heading of 100 degrees and vary the speed of the 2 motors proportionately to steer towards the target. This seems to work fine on its own. I attach a view of the serial port pidsterring.jpg to show what happens when I move the device to the port or starboard of the target bearing. In the window I can see from left to right, bearing, heading, output (from the PID), error , port (which is the value on the port enable pin) and starboard. As I rotate the device I can see the port and starboard values change from 75 (minimum to rotate the motor) to 255 max. The motors change speed as expected so this sketch works on its own.
When I combine everything into one sketch it does not work as expected. I attach image of the serial prints as locationtest4.71.jpeg. In this sketch I have changed to using NeoHWSerial from originally using Serial. If I have done that correctly it implements interrupts to ensure the GPS data is not dropped.
The values from left to right are. nDist, Bearing, Heading, output (from PID), error, port ,starboard.
This time the values of port and starboard seem to change as expected when I rotate the device. These are the values I expected to be written to the enable pins of the motors but if the value is less than 255 when I probe the pin, I measure a voltage value of 0 and the motor does not spin. When the value is 255 I measure around 6V which is close to what I would expect as a maximum value and the motor spins at full speed.
Incidentally it did not work when I used Serial instead of Neo HWSerial either.

I am at a loss to explain. Can anyone throw any light on the problem?

I attach the 2 sketches and the 2 images of the serial port.

mark_locationtest_v4.71.ino (15.4 KB)

PID_steeringv2.ino (3.63 KB)

locationtest 4.71.JPG

pidsteering.JPG

The description you give of the problem is a bit too vague for me to make sense of, so I took a look at your steering loop.

I don't think it is a good idea to use the PID library as a black box. The compass steering loop that I use implements only the P of PID, and works perfectly for a boat. With all the variables out in the open, you can log them for debugging.

Here it is:

// continue to run this leg, steering with IMU
// (may not have recent GPS info in the following)

 I2C_Read3Vectors(BNO055_A0,BNO055_FUSED_EULER, (int16_t *)v);  //get Euler angles
 result=I2C_ReadRegister(BNO055_A0,BNO055_CALIB_STAT); //check calibration status

 for(i=0; i<3; i++) v[i] >>= 4;

// get the current heading, 0 to 360:

 imu_heading = wrap360(v[0] + yaw_offset);  //correct for magnetic declination


// heading error and PID steering 

 error = heading_error(point_bearing, imu_heading);  //range -180 to 180, 0 is no heading error.
 
 //error is positive if current_heading > bearing (compass direction)
 //positive bias acts to reduce left motor speed, so bear left

 bias = (kp*error)/10;  //Kp in tenths (Ki, Kd not necessary in water)
 
              // log the result
 printf("I,%lu,%d,%d,%d,%d,%d,%d,%u,\n",millis(),
 point_bearing,imu_heading,error,bias,v[1],v[2],result);

 // the motor routines internally limit the argument to {-255, 255}

 set_m1_speed(motorSpeed-bias); //left motor
 set_m2_speed(motorSpeed+bias); //right motor

This is what a typical run looks like, for a preprogrammed course with initial bearing taken from the starting orientation:

Thanks for your input.
Yes. I have only set the Proportional element. Both Integral and differential elements are currently set to zero.
Sorry my description is vague I will try to rephrase. I am attempting to describe what I can observe and try not draw incorrect conclusions.
If I run the PIDSteer sketch and rotate the device the values of port and starboard vary from 75 to 255 as expected and I can print them to the serial port from the void steer function. So I conclude the correct values are analogWrite to the correct pins in the void motorRun function. The motors respond as expected so I think that is correct.

If I combine the PIDSteer sketch into the Locationtest sketch it appears that the correct values for port and starb can be printed from the void steer function as in the first test but the void motorRun function does not appear to work with the exception of when the port or starb value is equal to 255. When the value is 255 the correct motor turns in the correct direction. If I measure the voltage at the enable pin for the motor it is 0 unless the value printed in the serial port is 255. When it is 255 the voltage at the enable pin is 5v and the motor recieves 6v from the L298n controller and turns at full speed.

I think that means that the PWM output from the enable pin (d3 or d11) is only being written when the value is 255.

Could it be a conflict with the PWM timer maybe?

@jremington

Thanks for the code and the video. I have read a lot of your posts and I did come across your video whilst searching for solutions for my project.

I think that means that the PWM output from the enable pin (d3 or d11) is only being written when the value is 255.

Put in Serial.print statements to see what is output, and why.

My point earlier is that you can replace the entire PID library with one or two lines of code. Right now, you have no clear idea what it is doing.

Go with Ardupilot as jremington suggested.

All the hard yakka has been done, covers multiple types of controller units and still leaves enough work for one to do to realise some sense of achievement.

Currently using it in the setup on a model boat with an older Omnibus F4 Pro V2.0 controller and a BN220 gps unit.

Video on small details here......

Thanks for the suggestion to use Ardupilot. I can see its a fully featured project with extensive capability. Maybe I will try it as a future project but for now I'm keen to iron out this particular problem as a way of progressing a small way in my learning process with Arduino.

I followed Jremington suggestion to eliminate the PID library and use a simpler solution to implement proportional steering. It works as it should on its own. I attach the ino below. I think this proves the code works, the circuit connections are correct and the power supply is adequate.

When I add the same code to the main project it behaves in the same way as the first version. If the heading is more than 30 degrees adrift the steering writes a maximum value of 255 to the PWM pin connected to motor enable. I can measure nearly 5v on the pin (d3 or d11) In this position the motor runs at full speed. If the heading is between 30 degrees and 5 degrees of the target the steering function writes a value between 255 and 75 proportionate to the error in heading. In this case I measure 0V at the pin and the motor does not spin. In this version of steering if the error is less than 5 degrees steering writes 255 to both PWM pins, I can measure 5v and both motors spin at full speed.

Something in the main program seems to be preventing the output of the PWM pins if it is less than maximum.
I am using Altsoftserial to communicate with the Bluetooth HC05 module. I know Altsoftserial disables the timer on digital pin10 preventing the PWM from working properly but I don't think it effects d3 or d11.

Does anyone have any thoughts on what might be the issue?

PID_steeringv3.ino (3.14 KB)

mark_locationtest_v4.73.ino (15.4 KB)

I think I have discovered that ServoTimer2 disables pwm on pin 3 and pin11. This is probably the issue.
I will need to find a different way of driving my servo.

Why do you override the PID control of the motors like this? It doesn't make sense to me to ignore a 14 degree errors. This guarantees wild steering oscillations, even after you get the rest of the code working.

   int bias = (Kp * error) ;
    if (bias > -15 && bias < 15) {
      Port = 255;
      Starb = 255;
      NeoSerial.print(nDist);
      NeoSerial.print(',');
      NeoSerial.print(Bearing);
      NeoSerial.print(',');
      NeoSerial.print(Heading);
      NeoSerial.print(',');
      NeoSerial.print (error);
      NeoSerial.print(',');
      NeoSerial.print (Port);
      NeoSerial.print(',');
      NeoSerial.println(Starb);
    }
    else {
      Port = 165 - bias;
      if (Port >= 255) {
        Port = 255;
      }
      if (Port <= 75) {
        Port = 75;

      }

      Starb = 165 + bias;
      if (Starb >= 255) {
        Starb = 255;
      }
      if (Starb <= 75 ) {
        Starb = 75;
      }