2022-11-27 19:06:54 +00:00
/*
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
*/
# include <stdio.h>
# include <Servo.h>
# include <Wire.h>
# include <SoftwareSerial.h>
# include <math.h>
# include "Time.h"
# include "TinyGPS.h"
# define HMC6343_ADDRESS 0x19
# define HMC6343_HEADING_REG 0x50
# define GPS_ENABLE_PIN 12
2022-11-30 13:34:48 +00:00
# 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?
2022-11-27 19:06:54 +00:00
2022-11-30 13:34:48 +00:00
# define rad2deg(x) (180 / M_PI) * x
# define deg2rad(x) x *M_PI / 180
2022-11-27 19:06:54 +00:00
2022-11-30 13:34:48 +00:00
Servo rudderServo ; // create servo object to control a servo
2022-11-27 19:06:54 +00:00
SoftwareSerial myDebug ( 7 , 8 ) ;
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
2022-11-30 13:34:48 +00:00
# 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
2022-11-27 19:06:54 +00:00
2022-11-30 13:34:48 +00:00
# define DEBUG_THRESHOLD DEBUG_IMPORTANT //set to 0 to show no debugging messages
2022-11-27 19:06:54 +00:00
2022-11-30 13:34:48 +00:00
byte ledState = 0 ;
2022-11-27 19:06:54 +00:00
2022-11-30 13:34:48 +00:00
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 ;
2022-11-27 19:06:54 +00:00
//make printf work
static FILE uartout = {
2022-11-30 13:34:48 +00:00
0
} ;
2022-11-27 19:06:54 +00:00
2022-11-30 13:34:48 +00:00
static int uart_putchar ( char c , FILE * stream ) {
myDebug . write ( c ) ;
return 0 ;
2022-11-27 19:06:54 +00:00
}
2022-11-30 13:34:48 +00:00
static void say ( byte level , char * msg ) {
if ( level < = DEBUG_THRESHOLD ) {
2022-11-27 19:06:54 +00:00
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
2022-11-30 13:34:48 +00:00
void dprintf ( byte level , const char * fmt , . . . ) {
if ( level < = DEBUG_THRESHOLD ) {
printf ( " Debug%d: [Ctrl] " , level ) ;
2022-11-27 19:06:54 +00:00
va_list ap ;
va_start ( ap , fmt ) ;
2022-11-30 13:34:48 +00:00
2022-11-27 19:06:54 +00:00
vprintf ( fmt , ap ) ;
va_end ( ap ) ;
}
}
2022-11-30 13:34:48 +00:00
void dprintf2 ( const char * fmt , . . . ) {
//printf("Debug%d: [Ctrl] ",level);
va_list ap ;
va_start ( ap , fmt ) ;
printf ( fmt , ap ) ;
va_end ( ap ) ;
2022-11-27 19:06:54 +00:00
}
2022-11-30 13:34:48 +00:00
void setup ( ) {
2022-11-27 19:06:54 +00:00
//Serial.begin(9600); //baud rate makes no difference on 32u4
//for GPS, we communicate with the GPS through the Serial Port
//so, using Serial.println() will actually send messages to the GPS
Serial . begin ( 4800 ) ;
2022-11-30 13:34:48 +00:00
myDebug . begin ( 4800 ) ; //debug UART
2022-11-27 19:06:54 +00:00
2022-11-30 13:34:48 +00:00
say ( DEBUG_CRITICAL , " Control system start up " ) ;
2022-11-27 19:06:54 +00:00
2022-11-30 13:34:48 +00:00
//required for printf
fdev_setup_stream ( & uartout , uart_putchar , NULL , _FDEV_SETUP_WRITE ) ;
stdout = & uartout ;
dprintf ( DEBUG_IMPORTANT , " Printf configured \r \n " ) ;
2022-11-27 19:06:54 +00:00
delay ( 5000 ) ;
2022-11-30 13:34:48 +00:00
say ( DEBUG_IMPORTANT , " Setting up servos... " ) ;
2022-11-27 19:06:54 +00:00
//Use .attach for setting up connection to the servo
2022-11-30 13:34:48 +00:00
rudderServo . attach ( 5 , 1060 , 1920 ) ; // Attach, with the output limited
2022-11-27 19:06:54 +00:00
// between 1000 and 2000 ms
rudderServo . writeMicroseconds ( 1500 ) ;
2022-11-30 13:34:48 +00:00
say ( DEBUG_IMPORTANT , " Done " ) ;
2022-11-27 19:06:54 +00:00
2022-11-30 13:34:48 +00:00
say ( DEBUG_IMPORTANT , " Setting up I2C... " ) ;
Wire . begin ( ) ; // Initialise i2c for compass
say ( DEBUG_IMPORTANT , " Done " ) ;
2022-11-27 19:06:54 +00:00
2022-11-30 13:34:48 +00:00
say ( DEBUG_IMPORTANT , " Setting up GPS... " ) ;
pinMode ( GPS_ENABLE_PIN , OUTPUT ) ; //GPS on/off line
2022-11-27 19:06:54 +00:00
//setup GPS
2022-11-30 13:34:48 +00:00
digitalWrite ( GPS_ENABLE_PIN , 1 ) ;
2022-11-27 19:06:54 +00:00
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);
2022-11-30 13:34:48 +00:00
say ( DEBUG_IMPORTANT , " Done " ) ;
2022-11-27 19:06:54 +00:00
2022-11-30 13:34:48 +00:00
say ( DEBUG_IMPORTANT , " Setup Complete \n " ) ;
2022-11-27 19:06:54 +00:00
}
//computes an NMEA checksum
2022-11-30 13:34:48 +00:00
byte compute_checksum ( byte * data , byte length ) {
byte computed_checksum = 0 ;
2022-11-27 19:06:54 +00:00
2022-11-30 13:34:48 +00:00
for ( byte i = 0 ; i < length ; i + + ) {
2022-11-27 19:06:54 +00:00
computed_checksum = ( byte ) computed_checksum ^ data [ i ] ;
}
return computed_checksum ;
}
//reads heading from HMC6343 compass
int readCompass ( ) {
byte buf [ 6 ] ;
2022-11-30 13:34:48 +00:00
Wire . beginTransmission ( HMC6343_ADDRESS ) ; // Start communicating with the HMC6343 compasss
Wire . write ( HMC6343_HEADING_REG ) ; // Send the address of the register that we want to read
2022-11-27 19:06:54 +00:00
//Wire.write(0x55); // Send the address of the register that we want to read
Wire . endTransmission ( ) ;
2022-11-30 13:34:48 +00:00
Wire . requestFrom ( HMC6343_ADDRESS , 6 ) ; // Request six bytes of data from the HMC6343 compasss
for ( int i = 0 ; i < 6 ; i + + ) {
while ( Wire . available ( ) < 1 )
; // Busy wait while there is no byte to receive
buf [ i ] = Wire . read ( ) ;
2022-11-27 19:06:54 +00:00
//printf("buf[%d]=%d\r\n",i,buf[i]);
}
2022-11-30 13:34:48 +00:00
int heading = ( ( buf [ 0 ] < < 8 ) + buf [ 1 ] ) ; // the heading in degrees
int pitch = ( ( buf [ 2 ] < < 8 ) + buf [ 3 ] ) ; // the pitch in degrees
int roll = ( ( buf [ 4 ] < < 8 ) + buf [ 5 ] ) ; // the roll in degrees*/
heading = heading / 10 ;
roll = roll / 10 ;
pitch = pitch / 10 ;
//myDebug.print("Heading = ");
//myDebug.print(heading);
state . roll = ( int8_t ) roll ;
state . pitch = ( int8_t ) pitch ;
state . heading = ( uint16_t ) heading ;
2022-11-27 19:06:54 +00:00
//dprintf(DEBUG_IMPORTANT,"Heading: %d Roll: %d Pitch: %d\r\n",state.heading,state.roll,state.pitch);
//printf("heading=%d\r\n",heading);
//dprintf2("heading=%d\r\n",heading);
delay ( 100 ) ;
2022-11-30 13:34:48 +00:00
return ( int ) heading ; // Print the sensor readings to the serial port.
2022-11-27 19:06:54 +00:00
}
void readGPS ( ) {
2022-11-30 13:34:48 +00:00
unsigned long fix_age = 9999 , time , date ;
2022-11-27 19:06:54 +00:00
2022-11-30 13:34:48 +00:00
say ( DEBUG_MINOR , " About to read GPS " ) ;
digitalWrite ( GPS_ENABLE_PIN , 1 ) ; //turn the GPS on
2022-11-27 19:06:54 +00:00
delay ( 1000 ) ;
2022-11-30 13:34:48 +00:00
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
2022-11-27 19:06:54 +00:00
{
Serial . println ( " $PSRF103,04,01,00,01*21 \r " ) ;
2022-11-30 13:34:48 +00:00
dprintf ( DEBUG_MINOR , " NMEA string: " ) ;
2022-11-27 19:06:54 +00:00
unsigned long start = millis ( ) ;
2022-11-30 13:34:48 +00:00
while ( millis ( ) < start + 2000 ) {
if ( Serial . available ( ) ) {
2022-11-27 19:06:54 +00:00
int c = Serial . read ( ) ;
gps . encode ( c ) ;
2022-11-30 13:34:48 +00:00
if ( DEBUG_THRESHOLD > = DEBUG_MINOR ) {
2022-11-27 19:06:54 +00:00
myDebug . write ( c ) ;
}
2022-11-30 13:34:48 +00:00
if ( c = = ' \n ' ) {
2022-11-27 19:06:54 +00:00
break ;
}
}
}
2022-11-30 13:34:48 +00:00
gps . get_datetime ( & date , & time , & fix_age ) ;
2022-11-27 19:06:54 +00:00
2022-11-30 13:34:48 +00:00
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 " ) ;
2022-11-27 19:06:54 +00:00
}
}
2022-11-30 13:34:48 +00:00
digitalWrite ( GPS_ENABLE_PIN , 0 ) ; //turn the GPS off
2022-11-27 19:06:54 +00:00
2022-11-30 13:34:48 +00:00
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 " ) ;
2022-11-27 19:06:54 +00:00
}
2022-11-30 13:34:48 +00:00
else {
say ( DEBUG_IMPORTANT , " Fix Valid " ) ;
dprintf ( DEBUG_IMPORTANT , " lat=%ld lon=%ld \r \n " , ( long ) ( state . lat * 1000 ) , ( long ) ( state . lon * 1000 ) ) ;
2022-11-27 19:06:54 +00:00
int year ;
2022-11-30 13:34:48 +00:00
byte month , day , hour , min , sec ;
2022-11-27 19:06:54 +00:00
unsigned long age ;
2022-11-30 13:34:48 +00:00
gps . crack_datetime ( & year , & month , & day , & hour , & min , & sec , NULL , & age ) ;
2022-11-27 19:06:54 +00:00
2022-11-30 13:34:48 +00:00
setTime ( hour , min , sec , day , month , year ) ; //sets the time in the time library, lets us get unix time
}
2022-11-27 19:06:54 +00:00
}
2022-11-30 13:34:48 +00:00
int mod ( int value ) { //keeps angles betweeen 0 and 360
2022-11-27 19:06:54 +00:00
int newValue ;
2022-11-30 13:34:48 +00:00
if ( value < 0 ) {
2022-11-27 19:06:54 +00:00
newValue = value + 360 ;
2022-11-30 13:34:48 +00:00
} else if ( value > = 360 ) {
2022-11-27 19:06:54 +00:00
newValue = value - 360 ;
2022-11-30 13:34:48 +00:00
} else {
2022-11-27 19:06:54 +00:00
newValue = value ;
}
return newValue ;
}
//calculates difference between two headings taking wrap around into account
2022-11-30 13:34:48 +00:00
int get_hdg_diff ( int heading1 , int heading2 ) {
2022-11-27 19:06:54 +00:00
int result ;
2022-11-30 13:34:48 +00:00
result = heading1 - heading2 ;
2022-11-27 19:06:54 +00:00
2022-11-30 13:34:48 +00:00
if ( result < - 180 ) {
2022-11-27 19:06:54 +00:00
result = 360 + result ;
return result ;
2022-11-30 13:34:48 +00:00
}
2022-11-27 19:06:54 +00:00
2022-11-30 13:34:48 +00:00
if ( result > 180 ) {
result = 0 - ( 360 - result ) ;
2022-11-27 19:06:54 +00:00
}
return result ;
}
2022-11-30 13:34:48 +00:00
void loop ( ) {
unsigned long last_gps_read = 0 ;
unsigned long last_time = 0 , time_now = 0 ;
int wp_hdg = 0 ; // heading to the next waypoint, in degrees
float wp_dist = 0.0 ; // distance to next waypoint
int wp_num = 0 ; // number of waypoints
2022-11-27 19:06:54 +00:00
2022-11-30 13:34:48 +00:00
float igain = 0.01 ; // i and p gain are multipliers for the rudder servo, they fix issues
float pgain = 0.1 ;
float running_err = 0.0 ;
int hdg_err = 0 ;
int relwind ; // wind direction relative to us
2022-11-27 19:06:54 +00:00
2022-11-30 13:34:48 +00:00
long last_telemetry = 0 ;
# define TELEMETRY_INTERVAL 10
# define TARGET_LOOP_INTERVAL 100 //number of milliseconds between loop intervals
2022-11-27 19:06:54 +00:00
2022-11-30 13:34:48 +00:00
# define NUM_OF_WAYPOINTS 1
2022-11-27 19:06:54 +00:00
2022-11-30 13:34:48 +00:00
float wp_lats [ NUM_OF_WAYPOINTS ] ;
2022-11-27 19:06:54 +00:00
float wp_lons [ NUM_OF_WAYPOINTS ] ;
2022-11-30 13:34:48 +00:00
wp_lats [ 0 ] = 52.4 ;
wp_lons [ 0 ] = - 4.4 ;
2022-11-27 19:06:54 +00:00
2022-11-30 13:34:48 +00:00
while ( 1 ) {
2022-11-27 19:06:54 +00:00
//make loop execute at constant speed
2022-11-30 13:34:48 +00:00
time_now = millis ( ) ;
2022-11-27 19:06:54 +00:00
2022-11-30 13:34:48 +00:00
if ( time_now - last_time > 0 & & time_now - last_time < TARGET_LOOP_INTERVAL ) {
delay ( TARGET_LOOP_INTERVAL - ( time_now - last_time ) ) ;
2022-11-27 19:06:54 +00:00
}
2022-11-30 13:34:48 +00:00
last_time = millis ( ) ;
2022-11-27 19:06:54 +00:00
2022-11-28 23:20:30 +00:00
//get external values
/*
* Grab compass value , work out the wind direction in relation to our heading , then grab out gps location and print some debug
* When we read the gps value we also check how close we are to our target waypoint , if we ' re close enough we move on to the next one .
* after doing this we do some stuff to make sure we arnt going to waypoints that dont exist then set our waypoint heading ( wp_heading )
* and some other data that will be used to steer towards the waypoint
*/
2022-11-27 19:06:54 +00:00
readCompass ( ) ;
//state.wind_dir=getTrueWind();
//no wind sensor, so just use a fixed wind direction
2022-11-30 13:34:48 +00:00
state . wind_dir = 270 ;
2022-11-27 19:06:54 +00:00
2022-11-30 13:34:48 +00:00
relwind = mod ( state . wind_dir - state . heading ) ;
2022-11-27 19:06:54 +00:00
2022-11-30 13:34:48 +00:00
if ( millis ( ) - last_gps_read > ( GPS_READ_INTERVAL * 1000 ) | | millis ( ) < last_gps_read ) //read the GPS at the specified interval or whenever the millis count wraps around
2022-11-27 19:06:54 +00:00
{
2022-11-30 13:34:48 +00:00
say ( DEBUG_MINOR , " Reading GPS " ) ;
2022-11-27 19:06:54 +00:00
readGPS ( ) ;
2022-11-30 13:34:48 +00:00
wp_hdg = ( int ) TinyGPS : : course_to ( state . lat , state . lon , wp_lats [ wp_num ] , wp_lons [ wp_num ] ) ;
wp_dist = TinyGPS : : distance_between ( state . lat , state . lon , wp_lats [ wp_num ] , wp_lons [ wp_num ] ) ;
if ( wp_dist < WP_THRESHOLD ) {
wp_num + + ;
if ( wp_num = = NUM_OF_WAYPOINTS ) //reached last waypoint already
2022-11-27 19:06:54 +00:00
{
2022-11-30 13:34:48 +00:00
wp_num - - ;
} else //reached new waypoint
2022-11-27 19:06:54 +00:00
{
2022-11-30 13:34:48 +00:00
wp_hdg = ( int ) TinyGPS : : course_to ( state . lat , state . lon , wp_lats [ wp_num ] , wp_lons [ wp_num ] ) ;
wp_dist = TinyGPS : : distance_between ( state . lat , state . lon , wp_lats [ wp_num ] , wp_lons [ wp_num ] ) ;
2022-11-27 19:06:54 +00:00
}
}
2022-11-30 13:34:48 +00:00
last_gps_read = millis ( ) ;
2022-11-27 19:06:54 +00:00
}
//sail logic
//sailLogic(relwind);
//no sail as we have a fixed one
//rudder logic
2022-11-28 23:20:30 +00:00
/*
* First we use get_hdg_diff to get work out how many degrees off the correct direction we are , this is hdg_err ( heading error )
* next we increase the running error and clamp it , this variaby slowly increases over time so that we can reach the heading ( withough this we just get closer and closer but never reach it )
* then we use the heading and the error to set what position the rudder should be in , clamp it then also store that value in state for reference later ( state holds all the data the arduino needs , see its definition at like 60 ~ )
*/
2022-11-30 13:34:48 +00:00
hdg_err = get_hdg_diff ( wp_hdg , state . heading ) ;
2022-11-27 19:06:54 +00:00
running_err = running_err + ( float ) hdg_err ;
2022-11-30 13:34:48 +00:00
if ( abs ( running_err > 4000 ) ) {
running_err = 4000 ; // limit integral component
2022-11-27 19:06:54 +00:00
}
running_err = running_err * 0.9 ;
/*dprintf("hdg_err = %d running_err = ",hdg_err);
myDebug . println ( running_err ) ; */
2022-11-30 13:34:48 +00:00
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 ;
2022-11-27 19:06:54 +00:00
}
2022-11-30 13:34:48 +00:00
rudderServo . writeMicroseconds ( 1500 + ( state . rudder * 100 ) ) ;
2022-11-27 19:06:54 +00:00
2022-11-28 23:20:30 +00:00
// print debug info
/*
* We print debug info every certain amount of seconds ( 10 by default ) .
* we mostly print info about where we are and where the waypoint is
*/
2022-11-30 13:34:48 +00:00
if ( last_telemetry + ( TELEMETRY_INTERVAL * 1000 ) < millis ( ) ) {
dprintf ( DEBUG_CRITICAL , " time=%ld hdg=%d hdg_err=%d roll=%d pitch=%d truewind=%d relwind=%d sail=%d rudder=%d wp_num=%d wp_hdg=%d wp_dist=%ld " , now ( ) , state . heading , hdg_err , state . roll , state . pitch , state . wind_dir , relwind , state . sail , state . rudder , wp_num , wp_hdg , ( long ) wp_dist ) ;
2022-11-27 19:06:54 +00:00
//time=181734082 hdg=-14836 hdg_err=9217 roll=-22526 pitch=24182 truewind=-25261 relwind=27648 sail=-6656 rudder=-7937 wp_hdg=2768 wp_dist=-284423467 lat=52.41648 lon=-4.06522 wplat=52.40000 wplon=-4.40000 running_err=966.57
2022-11-30 13:34:48 +00:00
2022-11-27 19:06:54 +00:00
// time=1398700112 hdg=158 hdg_err=107 roll=-26 pitch=-32 truewind=302 relwind=144 sail=4 rudder=5 Debug1: [Ctrl] wp_hdg=265 wp_dist=22842 lat=52.41666 lon=-4.06445 wplat=52.40000 wplon=-4.40000 running_err=963.96
2022-11-30 13:34:48 +00:00
if ( DEBUG_THRESHOLD > = DEBUG_CRITICAL ) {
2022-11-27 19:06:54 +00:00
myDebug . print ( " lat= " ) ;
2022-11-30 13:34:48 +00:00
myDebug . print ( state . lat , 5 ) ;
2022-11-27 19:06:54 +00:00
myDebug . print ( " lon= " ) ;
2022-11-30 13:34:48 +00:00
myDebug . print ( state . lon , 5 ) ;
2022-11-27 19:06:54 +00:00
myDebug . print ( " wplat= " ) ;
2022-11-30 13:34:48 +00:00
myDebug . print ( wp_lats [ wp_num ] , 5 ) ;
2022-11-27 19:06:54 +00:00
myDebug . print ( " wplon= " ) ;
2022-11-30 13:34:48 +00:00
myDebug . print ( wp_lons [ wp_num ] , 5 ) ;
2022-11-27 19:06:54 +00:00
myDebug . print ( " running_err= " ) ;
myDebug . println ( running_err ) ;
}
//transmit_data();
2022-11-30 13:34:48 +00:00
last_telemetry = millis ( ) ;
2022-11-27 19:06:54 +00:00
}
}
2022-11-30 13:34:48 +00:00
}