==== Main.c ====
//UAV parafoil main code
//#define GROUND //comment\uncomment as you wish
//#define EEPROM //put these in the makefile
#include "main.h"
//globals
#include "sqrt_density.h"
#ifdef GROUND
volatile u16 diff;
volatile u08 counter=0;
#endif
const char config_string[44] PROGMEM={0x10,0xBB,0x03,0x00,0x03,0x03,0x00,0x3D,0xB2,0xCA,0x58,
0x40,0x00,0x00,0x00,0x41,0x40,0x00,0x00,0x40,0xC0,0x00,0x00,0x1E,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x03}; //new 0xBB packet to configure air filter mode, with floats unchanged
const char comma PROGMEM=','; //saves repetitivly storing commas
signed char EEMEM windeast[254]; //wind data
signed char EEMEM windnorth[254];
unsigned char EEMEM flight_status; //what stage of flight
#ifdef EEPROM
uint16_t EEMEM I2Crectransit; //where in I2C EEPROM we go to 24 byte records
extern volatile u08 I2Cerr;
#endif
unsigned char EEMEM why_cutdown; //why we cutdown
volatile kalman_state our_state;
/*volatile*/ kalman_model our_model;
volatile u08 Kalman_flag;
volatile float Heading;
volatile float Target;
volatile u08 Heading_flag;
volatile u08 gyro_error=0x00;
volatile u16 temperature;
u08 checksum;
volatile gps_type Gps;
//ground control globals
#ifdef GROUND
extern volatile u08 pwm_counter;
extern volatile u08 pwm_period;
#endif
uint8_t mcusr_mirror __attribute__ ((section (".noinit")));
void get_mcusr(void) __attribute__((naked)) __attribute__((section(".init3")));
void get_mcusr(void)
{
mcusr_mirror = MCUSR;
MCUSR = 0;
wdt_disable();
} //avr-libc wdt code
void setup()
{
outb(UCSR0B, BV(RXCIE0)|BV(RXEN0)|BV(TXEN0)); //mega xx8 registers - enable tx and rx, with interrupts on RX only
outb(UCSR0C, BV(UPM01)|BV(UPM00)|BV(UCSZ01)|BV(UCSZ00)); //setup 8O1
outb(UBRR0L, BAUDDIV);
outb(UBRR0H, BAUDDIV>>8); //end of UART setup
//ADC setup
sbi(ADCSR, ADEN); // enable ADC (turn on ADC power)
cbi(ADCSR, ADFR); // default to single sample convert mode
outb(ADCSR, ((inb(ADCSR) & ~0x07) | 0x06)); // set prescaler 64
outb(ADMUX, ((inb(ADMUX) & ~0xC0) | (0x03<<6))); // set reference 1.10V
cbi(ADMUX, ADLAR); // set to right-adjusted result
//sbi(ADCSR, ADIE); // enable ADC interrupts
//IO setup
GYRO_OFF; //SS to the gyro - pullups
PORTD|= (1<<2) ; //pullup to radio cts
PORTC|= (1<<1) ; //dead man switch pullup
DDRB=0b00101110; //talk to servo and gyro
DDRC=0b00001100; //indicator leds
DDRD=0b11111000; //cutdown line, SMPS /EN, led, suart tx, ground led
SET; //set software uart to gps
sbi(PINB,2); //experimental hack to set the /SS input to 1 ?
//enable the power to the servo
ENABLE_SMPS;
//SPI setup - do this here as the /SS pin is already set as an output
SPCR = (1<>1) ) & 0x07FF; //11 bit resolution - offset by one bit to the left
}
ISR(TIMER1_COMPB_vect) //fires at the end of the PWM pulse
{
sei(); //we need to be able to nest UART interrupts in here
vector measurement_vector;
static u08 timer;
static vector control_vector;
static kalman_state our_state_local;
static float integral;
static float target;
float a;
control_vector.t=0.0; //we control one servo, bottom
if(!timer)
{
our_state_local=our_state; //sets up the correct local state
}
if(Heading_flag)
{
our_model.H.tl=1.0;
measurement_vector.t=Heading; //from the main loop nav code
target=Target; //copy over the global variable
Heading_flag=FALSE;
}
else
{
our_model.H.tl=0.0; //setup the model to reflect the data
measurement_vector.t=0.0;
}
measurement_vector.b=(float)(5.23598776/1024.0)*(float)(read_mlx90609(read_rate)-gyro_null); //in radians, +-300 gyro
if(gyro_error)
{
our_model.H.br=0.0;
measurement_vector.b=0.0; //faulty gyro, limp home mode
}
else
{
our_model.H.br=1.0;
}
predict_and_update(&our_state_local,&our_model,&control_vector,&measurement_vector);
if(our_state_local.state.t>PI) //keeps us in the +-PI range
{
our_state_local.state.t-=2*PI;
}
if(our_state_local.state.t<-PI)
{
our_state_local.state.t+=2*PI;
}
a=our_state_local.state.t-target;
if(a>PI) //heading offset
{
a-=2*PI;
}
if(a<-PI)
{
a+=2*PI;
}
integral+=a; //integral term limits
if(integral>INTEGRAL_LIMIT)
{
integral=INTEGRAL_LIMIT;
}
if(integral<-INTEGRAL_LIMIT)
{
integral=-INTEGRAL_LIMIT;
}
if(timer>4)
{
control_vector.b=P_C*a+I_C*integral+D_C*our_state_local.state.b; //PID controller
if(control_vector.b>1) //servo limits
{
control_vector.b=1.0;
}
if(control_vector.b<-1)
{
control_vector.b=-1.0;
}
OCR1B=(u16)(PWM_COUNT_TIME*(3.0+control_vector.b)); //pwm = 1.5 +-0.5ms
}
else
{
timer++; //we use timer to turn on control after a few seconds
}
#ifdef GROUND
run_ground_control();
if(GROUND_LED_SET) //if under ground control, use the recorded pwm as a control input
{ //( from OC1B as its not subject to interrupts)
control_vector.b=((1.0/(float)PWM_COUNT_TIME)*(float)(OCR1B))-3.0;//1500us->0,2000->+1,1000->-1
}
#endif
if(!Kalman_flag) //our global kalman state is unlocked
{
our_state=our_state_local;
temperature=read_mlx90609(read_temp);
}
}
u08 cutdownrulecheck(int time,volatile float * altitude)
{
if(time=timelimit) //lets us know what happened - radio will be in ready state after cutdown
{
rprintfProgStr(PSTR("Time\r\n"));
eeprom_write_byte(&why_cutdown,0x01);
return 1;
}
else
{
rprintfProgStr(PSTR("Altitude\r\n"));
eeprom_write_byte(&why_cutdown,0x02);
return 2;
}
}
void wiggleservo()
{
static s08 position;
position+=(0x04&(position<<2))-2; //lsb is incr or decr bit
if(position>=7)
{
cbi(position,0);
}
OCR1B=(u16) (((float)F_CPU*0.0005/48.0)*(position+18)); //1.5ms +-0.5ms
if(position<=-6)
{
sbi(position,0);
}
}
float getbatteryvoltage()
{
outb(ADMUX, (inb(ADMUX) & ~0x1F) | (battery_chan & 0x1F)); // set channel
sbi(ADCSR, ADIF); // clear hardware "conversion complete" flag
sbi(ADCSR, ADSC); // start conversion
while( bit_is_set(ADCSR, ADSC) ); // wait until conversion complete
// CAUTION: MUST READ ADCL BEFORE ADCH!!!
return battery_factor*((float)(inb(ADCL) | (inb(ADCH)<<8))); // read ADC (full 10 bits);
}
void getgps()
{
wdt_reset();
Gps.packetflag=FALSE; //"Unlock" the global variable, so GPS parsing ISR can update it
while(!Gps.packetflag); //wait for some new gps
}
void dataprint(u08 flight_status,volatile gps_type * gps)
{
u08 local_char;
rprintfProgStr(PSTR("UKHAS"));
rprintfChar(flight_status); //>==ascending, <==descending, -==landed
PRINT_COMMA;
checksum=0x00; //reset the checksum
rprintfFloat(6,(double)gps->latitude);
PRINT_COMMA;
if (gps->latitude>0)
{
local_char='N';
}
else
{
local_char='S';
}
rprintfChar(local_char);
PRINT_COMMA;
rprintfFloat(6,(double)gps->longitude);
PRINT_COMMA;
if (gps->longitude>0)
{
local_char='E';
}
else
{
local_char='W';
}
rprintfChar(local_char);
PRINT_COMMA;
rprintfFloat(1,(double)gps->altitude);
PRINT_COMMA;
rprintfChar('M');
PRINT_COMMA;
rprintf("%d",(int)gps->nosats);
PRINT_COMMA;
if(flight_status=='<') //print the kalman data if the control loop is running
{
rprintfFloat(1,(double)Heading);
PRINT_COMMA;
rprintfFloat(2,(double)Target);
PRINT_COMMA;
Kalman_flag=TRUE; //lock the kalman data
rprintfFloat(1,(double)our_state.state.t);
PRINT_COMMA;
rprintfFloat(1,(double)our_state.state.b);
Kalman_flag=FALSE;
PRINT_COMMA;
#ifdef GROUND //only print ground control info if ground control is enabled
rprintf("%x",counter);
PRINT_COMMA;
#endif
}
rprintfFloat(1,(double)getbatteryvoltage());
PRINT_COMMA;
rprintfFloat(1,(double) (0.1953*(float)(temperature-1277)) );//mlx90609 rough temperature conversion
PRINT_COMMA;
rprintf("%x",checksum);
rprintfCRLF();
return ;
}
void put_char(unsigned char c)
{
loop_until_bit_is_set(UCSR0A, UDRE0); //unbuffered tx comms
UDR0 = c;
checksum+=c;
}
void _delay_10ms(char time)
{
for(;time;time--)
{
_delay_loop_2((u16)((float)F_CPU*0.0025)); //10ms delay - loop uses 4 cycles per iteration
}
}
int main()
{
wdt_enable(WDTO_8S); //watchdog set to 4 seconds
#ifdef EEPROM
u32 I2Caddress;
#endif
u16 time;
int L;
u08 Heightupto=1; //this rounds up
float K;
float b,descent_variable=0.05882,descent_variable_noise=0.002;
float descent_drift_n=0;
float descent_drift_e=0;
float Wind_e=0.0;
float Wind_n=0.0;
setup(); //all the major setup stuff
#ifdef EEPROM
init_i2c();
#endif //initialise the i2c bit rate
//FILE mystdio_usart = FDEV_SETUP_STREAM(put_char, get_char, _FDEV_SETUP_RW); //so we can printf to the radio
//stdout= &mystdio_usart;
rprintfInit(put_char);
for(L=0;L<44;L++)
{
suart_send(pgm_read_byte(&config_string[L])); //configs gps
}
sei();
if(mcusr_mirror&(1<=time) //time is number of records before transition to 24 byte records
L++;
else
L+=2;
if(L>=6)
{
rprintf("%d",I2Cerr);
rprintfCRLF();
L=0;
}
wdt_reset();
}
I2Caddress=0;
}
else
{
rprintfProgStr(PSTR("[FAIL]:"));
rprintf("%d",I2Cerr);
rprintfCRLF();
}
}
rprintfProgStr(PSTR("Press to wipe EEPROM\r\n"));
_delay_10ms(100);
wdt_reset();
if(USER_PRESENT)
{
rprintfProgStr(PSTR("Wiping...\r\n"));
I2Cerr=0;
painteeprom();
I2Caddress=0; //paint the eeprom and send us to the start
rprintfProgStr(PSTR("Error status:"));
rprintf("%d",I2Cerr);
rprintfCRLF();
}
wdt_reset();
#endif
rprintfProgStr(PSTR("Config done\r\n"));
}
else //--------------------- erranous reset recovery code -----------------------------------------
{
#ifdef EEPROM
I2Caddress=findtop(); //we need to avoid overwiting old data
#endif
if(eeprom_read_byte(&flight_status)==0x01) //we are in descent phase - find Heightupto from GPS
{
getgps(); //get the gps - hopefully we have a valid altitude
Heightupto=(int)(Gps.altitude/100.0)+1; //c rounds down - we need to add one
}
else
{
for(L=0;((s08)eeprom_read_byte((u08*)&windeast[L])!=-126) && L<255;L++) //calculate heightupto
{
Heightupto++;
}
}
L=0;
rprintfProgStr(PSTR("Setup complete, EEPROM top="));
rprintf("%d",I2Caddress/12);
rprintfCRLF();
}
if(!eeprom_read_byte(&flight_status)) //0x00=ascent
{
/////////////////////////////Ascent loop
for(time=0;!cutdownrulecheck(time,&(Gps.altitude));time++)
{
Wind_e += Gps.veast; //add it to our total wind
Wind_n += Gps.vnorth;
L++; //incrament the denominator for our averaging
if (Gps.altitude > Heightupto*100) //do we now fall into another (higher) 100m incrament ?
{
Wind_e/=L;
Wind_n/=L;
eeprom_write_byte((u08*)&windeast[Heightupto],(s08)(Wind_e));//we are storing as a signed byte
eeprom_write_byte((u08*)&windnorth[Heightupto],(s08)(Wind_n));//we find the average and shove it in the eeprom
Heightupto++;
L = 0;
Wind_e=0.0;
Wind_n=0.0;
}
wiggleservo();
if (radio_cts) //CTS from radio
{
temperature=read_mlx90609(read_temp);
dataprint('>',&Gps);
}
#ifdef EEPROM
set_address(&I2Caddress);
write_data((u08*)&Gps.altitude,12,&I2Caddress); //shove the position in the eeprom
i2cstop();
#endif
getgps();
}
}
//we need to update the GPS and Height counter after cutdown
getgps();
Heightupto=(int)(Gps.altitude/100.0)+1;
/////////////////////////////Decent guidance loop
sbi(TIMSK1, OCIE1B); //enable output compare interrupt - turns on guidance code
time=0;
while(time<8 && eeprom_read_byte(&flight_status)!=0x02) //while we havent landed and we havent previously landed
{
if((s08)eeprom_read_byte((u08*)&windeast[Heightupto])!=-126) //put wind in if its not blank painted eeprom
{
Wind_e=(float)(s08)eeprom_read_byte((u08*)&windeast[Heightupto]);
Wind_n=(float)(s08)eeprom_read_byte((u08*)&windnorth[Heightupto]);
}
else
{
if(L>0) //allows us to use data from last layer
{ //if we are still in it and have arrived
Wind_e/=L; //here from ascent loop with some data
Wind_n/=L;
L=-1;
}
else if (L!=-1) //if we havent used last layer data for find a solution
{
Wind_e=0.0; //painted eeprom has no valid data
Wind_n=0.0;
}
}
//clockwise is defined as +ive, everything uses the local ENU wind frame / wind in this frame
//direction to target (uses "equatorial radian units" - my invention, distance equal to PI on equator)
Target = atan2((targeteast - Gps.longitude)*cos(targetnorth) - descent_drift_e,targetnorth - Gps.latitude - descent_drift_n);
//wind compensation in ENU frame
Heading = atan2(Gps.veast-Wind_e,Gps.vnorth-Wind_n);
if(!(Gps.vnorth-Wind_n))
{
Heading = 0.0; //stops us flooding with nan if zero wind and stationary
}
Heading_flag=TRUE; //Kalman filter is now free to run with the latest data
//"eye candy"
K=Heading-Target; //k is now our heading offset (from now on is just for leds)
if (K >PI)
{
K -= 2*PI;
} //all to keep us in +-180 degree range
if(K < -PI)
{
K += 2*PI;
}
if (K > 0)
{
led_left; //left/right indictor: bicolour LED between 3 and 4
}
else
{
led_right;
}
//data
if (radio_cts) //CTS from radio
{
dataprint('<',&Gps);
}
#ifdef GROUND
if(Gps.altitude<200.0) //near the ground, ground control
{
enable_ground_control();
}
#endif
//have we (crash) landed?
if(Gps.altitude<200.0 && Gps.veast<0.1 && Gps.vnorth<0.1)
{
time++;
}
else
{
time=0;
}
#ifdef EEPROM
set_address(&I2Caddress);
write_data((u08*)&Gps.altitude,12,&I2Caddress);
write_data((u08*)&Target,4,&I2Caddress);
Kalman_flag=TRUE; //lock the data
write_data((u08*)&(our_state.state),8,&I2Caddress); //shove ze data in the eeprom
Kalman_flag=FALSE;
i2cstop();
#endif
L=(int)(Gps.altitude/100.0);
if(descent_variable>LOWER_DESCENT_LIMIT && descent_variable-50.0) //sanity check for a descent
{ //we run if we are descending and estimate the transit time coefficient
b=(float)pgm_read_byte(&sqrt_density[Heightupto]);
K=descent_variable_noise/((GPS_VEL_NOISE*b*b*descent_variable*descent_variable*descent_variable*descent_variable)+descent_variable_noise);//this is K
b=(-100.0)/(6378000.0*Gps.vup*b); //estimate of the proportionality constant
descent_variable+=K*(b-descent_variable);
descent_variable_noise-=descent_variable_noise*K;
}
getgps();
}
if(eeprom_read_byte(&flight_status)!=0x02) //0x02== landed
{
eeprom_write_byte(&flight_status,0x02); //set status to landed if it isnt already
}
/////////////////////////////Recovery loop
cbi(TIMSK1, OCIE1B); //turn off kalman
cbi(TCCR1A, COM1B1); //turn off servo
#ifdef GROUND
disable_ground_control(); //turn off input capture
#endif
DISABLE_SMPS; //turn off SMPS - always turn off the servo pwm first!
for(;;) //low power use recovery/landing mode
{
if (radio_cts) //CTS from radio
{
temperature=read_mlx90609(read_temp);
dataprint('-',&Gps); //send data
}
getgps();
}
}
==== Main.h ====
#include "global.h"
#include "avrlibdefs.h"
#include "avrlibtypes.h"
#include "matrix.h"
#include "kalman.h"
#include "TSIP.h"
#include "rprintf.h"
#include "i2cmem.h"
#include
#include
#include
#include
#include
#include
#include
#include
#define BAUDRATE (int)9600 //9600 baud for lassen iq
#define DELTA_TIME (float)0.02 //20ms
#define DELAY _delay_loop_2((u16)((float)F_CPU/(4.0*(float)BAUDRATE))) //delay for suart routine
#define SET PORTD |= 0x10 //PORTD.4 is the tx
#define CLEAR PORTD &= ~0x10
//kalman filter - NOTE: these constants are airframe specific
#define DAMPING_CONSTANT (float)0.3 //approx how fast oscillations die down as faction per second
#define CONTROL (float)0.75 //full control input of 1 gives ~ this turn rate
#define CONTROL_GAIN (DAMPING_CONSTANT*CONTROL) //as used in the kalman filter
#define TOP_COUNT (u16)((float)F_CPU*DELTA_TIME/8.0) //timer1 pwm frequency
#define PWM_COUNT_TIME (u16)((float)F_CPU*0.0005/8.0) //500us
#define I_C -0.001 //control loop
#define P_C -0.3
#define D_C -0.5
#define INTEGRAL_LIMIT 0.2/I_C //integral servo shift limited to 1/5 of full scale range
#define GPS_VEL_NOISE (0.25*(6378000.0/100.0)*(6378000.0/100.0))//these seem to be tolerant to errors of ~an order of mag
#define DESCENT_PROCESS_NOISE 1.0e-22 //tested with badger data
#define DESCENT_INIT 1.5e-8
#define DESCENT_NOISE_INIT 5.0e-17
#define read_rate (u08)0b10010100 //gyro specific
#define read_temp (u08)0b10011100
#define read_melexis (u08)0x80
#define gyro_null 1022
#define altitudelimit 10000 //flight termination conditions
#define timelimit 30 //NOTE: for debug
#define targeteast (float)(0.1*Deg2rad) //target waypoint ~ Cambridge
#define targetnorth (float)(52.0*Deg2rad)
#define Rad2deg 180.0/PI //bloody obvious
#define Deg2rad PI/180.0
#define PRINT_COMMA rprintfChar(pgm_read_byte(&comma)) //prints a comma
#define battery_factor (float)0.02505 //for measuring battery voltage- checked from test
#define battery_chan 0x00 //ADC0
#define toggle_pin PIND=0x20 //led on port D.5
#define BAUDDIV (u16)( ((float)F_CPU/(BAUDRATE*16UL)) -1 )//baud divider
#define radio_cts bit_is_set(PIND,2) //hardware, however its wired up
#define led_left PORTC = ((PORTC & ~0x0C) | 0x04)
#define led_right PORTC = ((PORTC & ~0x0C) | 0x08)
#define USER_PRESENT !(PINC&0x02)
#define cutter_on PORTD|=(1<<7) //release mechanism (hot resistor off mosfet)
#define cutter_off PORTD&=~(1<<7)
#define DISABLE_SMPS PORTD|=(1<<6) //turns the SMPS for the servo on/off - never send pwm if its off
#define ENABLE_SMPS PORTD&=~(1<<6)
#define GYRO_OFF PORTB|=1<<1 //SS line
#define GYRO_ON PORTB&=~(1<<1)
#ifdef ADCSRA
#ifndef ADCSR
#define ADCSR ADCSRA
#endif
#endif
#ifdef ADATE
#define ADFR ADATE
#endif
#ifdef GROUND
void enable_ground_control(); //enables the input capture interrupt
void disable_ground_control(); //disables input capture interrupt
#define PULSE_MIN (u16)(0.0008*(float)F_CPU/8.0) //pwm must be in the range 0.8 to 2.2 ms
#define PULSE_MAX (u16)(0.0022*(float)F_CPU/8.0) //due to the bit shift we divide by 16
#define GROUND_LED_ON PORTD|=(1<<3)
#define GROUND_LED_OFF PORTD&=~(1<<3)
#define GROUND_LED_SET PORTD&(1<<3)
void run_ground_control();
#endif
typedef struct
{
float vup;
float vnorth;
float veast;
float time;
float altitude;
float longitude;
float latitude;
u08 packetflag; //packetflag lets us see when our packet has been updated
u08 status;
u08 nosats;
} gps_type;
void put_char(unsigned char c); //talk to the UART
void suart_send(char c);
void run_ground_control();
void _delay_10ms(char time);
float driftfactor(int altitude);
u08 I2Cdataprint(u32* I2Caddress);