/* This file is part of the AberSailbot minimum viable control system (AMVCS). AMVCS is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Copyright Colin Sauze libraries required: timelib esp32servo tinygps */ #include //#include #include #include #include #include #include #include #include //Time.h became TimeLib.h as of version 1.6.1 #include "TimeLib.h" #include "TinyGPS.h" #define HMC6343_ADDRESS 0x19 #define HMC6343_HEADING_REG 0x50 #define GPS_ENABLE_PIN 12 #define CMPS12_ADDRESS 0x60 #define GPS_READ_INTERVAL 15 //how many seconds to leave between GPS reads #define WP_THRESHOLD 15 //how close (in metres) should we get before we change waypoint? #define rad2deg(x) (180/M_PI) * x #define deg2rad(x) x * M_PI/180 Servo rudderServo; // create servo object to control a servo //SoftwareSerial myDebug(7, 8); HardwareSerial myDebug(1); TinyGPS gps; #define HEADING 0 #define WIND_DIR 1 #define ROLL 2 #define PITCH 3 #define RUDDER 4 #define SAIL 5 #define LAT 6 #define LON 7 #define TIME 8 #define DEBUG_CRITICAL 1 //really important messages that we don't want to ignore and are prepared to sacrifice execution speed to see #define DEBUG_IMPORTANT 2 //fairly important messages that we probably want to see, but might cause issues with execution speed #define DEBUG_MINOR 3 //less important messages that we can safely turn off to improve execution speed #define DEBUG_VERBOSE 4 //Dan and Rosias debug messages #define DEBUG_THRESHOLD DEBUG_VERBOSE //set to 0 to show no debugging messages const char *ssid = "boat"; AsyncUDP udp; byte ledState=0; struct Data{ uint16_t heading; uint16_t wind_dir; int8_t roll; int8_t pitch; int8_t rudder; byte sail; float lat; float lon; long unixtime; } state; //make printf work static FILE uartout = { 0} ; static int uart_putchar (char c, FILE *stream) { myDebug.write(c) ; return 0 ; } static void say(byte level, char* msg) { if(level<=DEBUG_THRESHOLD) { myDebug.print("Debug"); myDebug.print(level); myDebug.print(": [Ctrl] "); myDebug.println(msg); } } //debugging printf that prepends "Debug:" to everything and can be easily turned off void dprintf(byte level, const char *fmt, ...) { char outbuf[1024]; if(level<=DEBUG_THRESHOLD) { printf("Debug%d: [Ctrl] ",level); va_list ap; va_start(ap, fmt); vsnprintf(outbuf,1024,fmt, ap); myDebug.print(outbuf); va_end(ap); } } void setup() { //Serial.begin(9600); //baud rate makes no difference on 32u4 Serial.begin(4800, SERIAL_8N1, 16, 17); //for GPS // 17 tx, 16 rx myDebug.begin(4800, SERIAL_8N1, 3, 1); //debug UART on GPIO 6 and 7 say(DEBUG_MINOR, "Debug serial system set up"); say(DEBUG_VERBOSE,"Control system start up"); //required for printf //but not on ESP32 //fdev_setup_stream (&uartout, uart_putchar, NULL, _FDEV_SETUP_WRITE); //stdout = &uartout ; dprintf(DEBUG_IMPORTANT,"Printf configured \r\n"); delay(5000); say(DEBUG_IMPORTANT,"Setting up servos..."); //Use .attach for setting up connection to the servo rudderServo.attach(5, 1060, 1920); // Attach, with the output limited // between 1000 and 2000 ms rudderServo.writeMicroseconds(1500); say(DEBUG_IMPORTANT,"Done"); say(DEBUG_IMPORTANT,"Setting up I2C..."); Wire.begin(); // Initialise i2c for compass say(DEBUG_IMPORTANT,"Done"); say(DEBUG_IMPORTANT,"Setting up GPS..."); pinMode(GPS_ENABLE_PIN, OUTPUT); //GPS on/off line //setup GPS digitalWrite(GPS_ENABLE_PIN,1); delay(1000); //turn off VTG Serial.println("$PSRF103,05,00,00,01*21\r"); //turn off RMC Serial.println("$PSRF103,04,00,00,01*20\r"); //turn off GSV Serial.println("$PSRF103,03,00,00,01*27\r"); //turn off GSA Serial.println("$PSRF103,02,00,00,01*26\r"); //turn off GLL Serial.println("$PSRF103,01,00,00,01*25\r"); //turn off GGA Serial.println("$PSRF103,00,00,00,01*24\r"); delay(1000); //leave GPS on to get its initial fix //digitalWrite(GPS_ENABLE_PIN,0); //setup WiFi say(DEBUG_IMPORTANT,"setting up WiFi"); WiFi.softAP(ssid); IPAddress myIP = WiFi.softAPIP(); say(DEBUG_IMPORTANT,"Done"); say(DEBUG_IMPORTANT,"Setup Complete\n"); } //computes an NMEA checksum byte compute_checksum(char *data,byte length) { byte computed_checksum=0; for (byte i = 0; i < length; i++) { computed_checksum = (byte)computed_checksum ^ (byte)data[i]; } return computed_checksum; } //reads heading from HMC6343 compass int readCompass() { unsigned char high_byte, low_byte, angle8; char pitch, roll; unsigned int heading; Wire.beginTransmission(CMPS12_ADDRESS); //starts communication with CMPS12 Wire.write(1); //Sends the register we wish to start reading from Wire.endTransmission(); // Request 5 bytes from the CMPS12 // this will give us the 8 bit bearing, // both bytes of the 16 bit bearing, pitch and roll Wire.requestFrom(CMPS12_ADDRESS, 5); while(Wire.available() < 5); // Wait for all bytes to come back angle8 = Wire.read(); // Read back the 5 bytes high_byte = Wire.read(); low_byte = Wire.read(); pitch = Wire.read(); roll = Wire.read(); // heading is angle16 heading = high_byte; // Calculate 16 bit angle heading <<= 8; heading += low_byte; // may need to reimplement this later heading=heading/10; roll=roll/10; pitch=pitch/10; myDebug.print("Heading = "); myDebug.print(heading); // rewrite this to set state to our values state.roll=(int8_t)roll; state.pitch=(int8_t)pitch; state.heading=(uint16_t)heading; dprintf(DEBUG_IMPORTANT,"Heading: %d Roll: %d Pitch: %d\r\n",state.heading,state.roll,state.pitch); printf("heading=%d\r\n",heading); delay(100); return (int)heading; // Print the sensor readings to the serial port. } void readGPS() { unsigned long fix_age=9999,time,date; say(DEBUG_VERBOSE,"About to read GPS"); digitalWrite(GPS_ENABLE_PIN,1); //turn the GPS on delay(1000); while(fix_age == TinyGPS::GPS_INVALID_AGE||fix_age>3000) //make sure the GPS has a fix, this might cause a wait the first time, but it should be quick any subsequent time { Serial.println("$PSRF103,04,01,00,01*21\r"); dprintf(DEBUG_MINOR,"NMEA string: "); unsigned long start = millis(); while(millis()=DEBUG_MINOR) { myDebug.write(c); } if(c=='\n') { break; } } } gps.get_datetime(&date,&time,&fix_age); dprintf(DEBUG_MINOR,"fix age = %ld\r\n",fix_age); if(fix_age == TinyGPS::GPS_INVALID_AGE) { dprintf(DEBUG_IMPORTANT,"Invalid fix, fix_age=%ld\r\n",fix_age); say(DEBUG_IMPORTANT,"No GPS fix"); } } digitalWrite(GPS_ENABLE_PIN,0); //turn the GPS off gps.get_datetime(&date,&time,&fix_age); gps.f_get_position(&state.lat,&state.lon,&fix_age); if(fix_age == TinyGPS::GPS_INVALID_AGE) { say(DEBUG_IMPORTANT,"Invalid fix"); } else { say(DEBUG_IMPORTANT,"Fix Valid"); dprintf(DEBUG_IMPORTANT,"lat=%ld lon=%ld\r\n",(long)(state.lat*1000),(long)(state.lon*1000)); int year; byte month,day,hour,min,sec; unsigned long age; gps.crack_datetime(&year,&month,&day,&hour,&min,&sec,NULL,&age); setTime(hour,min,sec,day,month,year); //sets the time in the time library, lets us get unix time } } int mod(int value){ //keeps angles betweeen 0 and 360 int newValue; if(value < 0){ newValue = value + 360; } else if(value >= 360){ newValue = value - 360; } else{ newValue = value; } return newValue; } //calculates difference between two headings taking wrap around into account int get_hdg_diff(int heading1,int heading2) { int result; result = heading1-heading2; if(result<-180) { result = 360 + result; return result; } if(result>180) { result = 0 - (360-result); } return result; } void loop() { say(DEBUG_VERBOSE, "Entered loop"); unsigned long last_gps_read=0; unsigned long last_time=0,time_now=0; int wp_hdg=0; float wp_dist=0.0; int wp_num=0; float igain=0.01; float pgain=0.1; float running_err=0.0; int hdg_err=0; int relwind; long last_telemetry=0; #define TELEMETRY_INTERVAL 10 #define TARGET_LOOP_INTERVAL 100 //number of milliseconds between loop intervals #define NUM_OF_WAYPOINTS 1 float wp_lats[NUM_OF_WAYPOINTS]; float wp_lons[NUM_OF_WAYPOINTS]; wp_lats[0]=52.4; wp_lons[0]=-4.4; while(1) { //make loop execute at constant speed time_now=millis(); if(time_now-last_time>0&&time_now-last_time(GPS_READ_INTERVAL*1000)||millis() 4000)) { running_err = 4000; // limit integral component } running_err = running_err * 0.9; /*dprintf("hdg_err = %d running_err = ",hdg_err); myDebug.println(running_err);*/ state.rudder = (int) round((pgain * (float)hdg_err) + (igain * running_err)); if(state.rudder<-5) { state.rudder=-5; } else if(state.rudder>5) { state.rudder=5; } say(DEBUG_VERBOSE, "correcting rudder"); rudderServo.writeMicroseconds(1500+(state.rudder*100)); say(DEBUG_VERBOSE, "possible telemetry incoming:"); if(last_telemetry+(TELEMETRY_INTERVAL*1000)0) { hemNS = 'N'; } else { hemNS = 'S'; } degrees = abs((int) state.lon); minutes = (fabs(state.lon) - (float)degrees) * 60.0; snprintf(lonstr,10,"%3d%2.4f",degrees,minutes); //get the hemisphere east/east if (state.lon>0) { hemEW = 'E'; } else { hemEW = 'W'; } //$GPGLL,LATMM.MMMM,N/S,LONMM.MMMM,E/W,HHMMSS,A,*SUM snprintf(nmeadata,79,"GPGLL,%s,%c,%s,%c,%s,A%",latstr,hemNS,lonstr,hemEW,timestr); checksum=compute_checksum(nmeadata,strlen(nmeadata)); snprintf(msgbuf,254,"$%s*%X\n",nmeadata,checksum); say(DEBUG_CRITICAL, msgbuf); //send to UDP port 10000 udp.broadcastTo(msgbuf,10000); if(DEBUG_THRESHOLD>=DEBUG_CRITICAL) { myDebug.print("lat="); myDebug.print(state.lat,5); myDebug.print(" lon="); myDebug.print(state.lon,5); myDebug.print(" wplat="); myDebug.print(wp_lats[wp_num],5); myDebug.print(" wplon="); myDebug.print(wp_lons[wp_num],5); myDebug.print(" running_err="); myDebug.println(running_err); } last_telemetry=millis(); } } }