12 April 2010

Slow Progress is Still Progress

    I've been dragging my feet a little bit on this project lately. It's mostly due to two reasons. First the weather has been great and I am spending my weekends outside instead of writing code. Second I ended up paying $2500 in taxes this year...that coupled with the adjustment I made to my tax withholding has left my bank account somewhat empty lately. I still need to purchase cameras, servos, the temperature/pressure sensor, balloon, some sort of cable to connect the GPS to the circuit board (probably has to be a flex circuit...expensive), and miscellaneous mechanical stuff.
    The connector on the GPS unit is a very small surface mount connector that is impossible to work with. I've attempted to solder 30 gauge wires to the leads, but even with a good microscope it isn't really working. My first idea was that I could create a flex circuit with the mating connector and a standard 100 mil header on the other end. It would be fairly costly to make the flex circuit, but the GPS unit is quite nice and I'm sure to use it again for other projects. Looking at the actual cost of the flex circuit ($550) I've changed my mind. My new plan is to redesign the main PCB to stand upright in the capsule and have a small tab with the GPS connector where the GPS unit will mount to and stick out of the top of the capsule. The GPS unit needs to at least have the antenna protruding out of the top of the capsule to ensure good signal reception. Redesigning the main board will also give me a chance to fix the programming issues with the mega128.
    Now, as promised there is some code attached. This code has preliminary functions to test all aspects of the micro that is attached to the GPS and cellular modules. The other micro will be attached to the temperature, pressure, and innertial sensors. I haven't written any code for it, but it will look mostly the same. I'm not going to take a lot of time explaining the code. I could easily fill up a year's worth of blog pages with that. There are fairly good comments in the code, so have at it. More coming soon, supposed to be thunderstorms this Friday hahaha.


////////////////////////////////////////////////////////////////
// Project Name: Ahnung
// File Name: ahnung.c
// Description: This is the firmware for MCU1 on the Ahnung
// near space balloon. This firmware handles
// GPS tracking, cellular comms, and parachute deployment.
// Micro: ATMega128
// Date: 22 JAN 2010
// Author: Luke Wardensky
////////////////////////////////////////////////////////////////
#define F_CPU 8000000UL
#define F_PWM 60
#define MY_PWM_FREQ ((F_CPU/8)/F_PWM)-1
#define BAUD 9600
#define LATITUDE 4
#define LONGITUDE 5
#define ALTITUDE 6
#define HOURS 7
#define MINUTES 8
#define SECONDS 9
#define GROUND_SPEED 10
#define VERT_VELOCITY 11
#define COURSE 12
#define HORZ_ACCURACY 13
#define VERT_ACCURACY 14
#define STATUS 15
#define GET_LAT 0
#define GET_LONG 1
#define GET_ALT 2
#define GET_UTC 3
#define GET_GND_SPEED 4
#define GET_VERT_VEL 5
#define GET_COURSE 6
#define GET_HORZ_ACC 7
#define GET_VERT_ACC 8
#define GET_STATUS 9
#define GPS_NO_FIX 0
#define GPS_DEAD_RECKONING 1
#define GPS_READY 2
#define GPS_TIME_ONLY 3
#define NO_FUNCTION -1

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "SPI_routines.h"
#include "SD_routines.h"
#include "FAT32.h"

/////////////////////////////////////////
// Global Variables
/////////////////////////////////////////
unsigned int PWM_FREQ = MY_PWM_FREQ;

/////////////////////////////////////////
// Function Definitions
/////////////////////////////////////////
void micro_init();
int Cell_Init();
void SMS_Test();
char *GPS_Status();
void Test_Chute_Servo();
void Test_Cutdown_Servo();
void Test_SD(char *str);
void delay_seconds(unsigned int seconds);
void uart_gets(char *temp_str, int max_str_size);
int check_cell_response(const char *response);
double read_gps_float(char function, char gps_register);
long read_gps_long(char function, char gps_register);
void delay_seconds(unsigned int seconds);
void uart_gets(char *temp_str, int max_str_size);
int check_cell_response(const char *response);

int main()
{
/////////////////////////////////////////
// Main Variables
/////////////////////////////////////////
unsigned int command=UART_NO_DATA;
char status=0, gps_string[512];
double last_lat=0, last_lng=0, last_altitude=0, lat=0, lng=0, altitude=0, gnd_speed=0, vert_vel=0, horz_acc=0, vert_acc=0, course=0;
unsigned int hours=0, minutes=0, seconds=0;

/////////////////////////////////////////
// Setup the microcontroller ports and
// perripherals
/////////////////////////////////////////
micro_init();

/////////////////////////////////////////
// Configuration & Test Menu
/////////////////////////////////////////
while(command != '7')
{
command = UART_NO_DATA;
uart1_puts_p(PSTR("\r\n**************************************************\r\n            MCU1 Configuration Menu\r\n**************************************************\r\n\r\n"));
uart1_puts_p(PSTR("1. Turn on cellular module\r\n2. Send SMS test message\r\n3. Get GPS status\r\n4. Test parachute servo\r\n5. Test cutdown servo\r\n6. Test SD card\r\n7. Start data logging\r\n\r\n>>"));
while(command == UART_NO_DATA)
{
command = uart1_getc();
}

while((command < '1') || (command > '6'))
{
uart1_puts_p(PSTR("\r\nInvalid selection\r\n>>"));
do
{
command = uart1_getc();
}while(command == UART_NO_DATA);
}

if(command == '1')
{
if(Cell_Init())
{
uart1_puts_p(PSTR("failed!!!!!\r\n"));
}
else
{
uart1_puts_p(PSTR("Cellular module initialization complete.\r\n"));
}
}
else if(command == '2')
{
SMS_Test();
}
else if(command == '3')
{
uart1_puts(GPS_Status());
}
else if(command == '4')
{
Test_Chute_Servo();
}
else if(command == '5')
{
Test_Cutdown_Servo();
}
else if(command == '6')
{
Test_SD("This is a test of the Ahnung SD memory card.\r\n123456789\r\nabcdefghijklmnopqrstuvwxyz\r\nABCDEFGHIJKLMNOPQRSTUVWXYZ\r\n!@#$%^&*()-+=?.,<>\r\n");
}
}

while(1)
{
// Have the uM-FPU get NMEA sentances from the GPS receiver
fpu_write2(SERIN, 6);
fpu_wait();

// Disable the uM-FPU NMEA sentance mode to prevent buffer overflows
fpu_write2(SERIN, 0);

// Get the GPS receiver status
status = read_gps_long(GET_STATUS, STATUS);

if(status == GPS_READY)
{
// Get the timestamp
hours = read_gps_long(GET_UTC, HOURS);
minutes = read_gps_long(NO_FUNCTION, MINUTES);
seconds = read_gps_long(NO_FUNCTION, SECONDS);

// Get the latitude
lat = read_gps_float(GET_LAT, LATITUDE);

// Get the longitude
lng = read_gps_float(GET_LONG, LONGITUDE);

// Get the altitude
altitude = read_gps_float(GET_ALT, ALTITUDE);

// Get the speed over ground
gnd_speed = read_gps_float(GET_GND_SPEED, GROUND_SPEED);

// Get the vertical velocity
vert_vel = read_gps_float(GET_VERT_VEL, VERT_VELOCITY);

// Get the course heading
course = read_gps_float(GET_COURSE, COURSE);

// Get the horizontal accuracy
horz_acc = read_gps_float(GET_HORZ_ACC, HORZ_ACCURACY);

// Get the vertical accuracy
vert_acc = read_gps_float(GET_VERT_ACC, VERT_ACCURACY);

if((last_lat != lat) || (last_lng != lng) || (last_altitude != altitude))
{
sprintf(gps_string, "", lat, lng, altitude, gnd_speed, vert_vel, course, horz_acc, vert_acc, hours, minutes, seconds);
writeFile("GPS.XML", gps_string);

// Format the SMS message string
uart_puts_p(PSTR("AT+CMGS=500\r"));
_delay_ms(500);
// Enter the string into the cell module
uart_puts_p(PSTR("lwardens@gonzaga.edu "));
uart_puts(gps_string);

// Send the CTRL-Z character to send the SMS
uart_putc(0x1A);

last_lat = lat;
last_lng = lng;
last_altitude = altitude;
}
}

// Re-enable the uM-FPU NMEA sentance mode
fpu_write2(SERIN, 4);
}

return 0;
}

void micro_init()
{
/////////////////////////////////////////
// Setup Port A
// Bit: 7 6 5 4 3 2 1 0
// Dir: O O O O O O O O
// Pullup: N N N N N N N N
/////////////////////////////////////////
DDRA = 0xFF;
PORTA = 0x00;

/////////////////////////////////////////
// Setup Port B
// Bit: 7 6 5 4 3 2 1 0
// Dir: I I O O I O O O
// Pullup: P P P P P N N N
/////////////////////////////////////////
DDRB = 0x37;
PORTB = 0xF8;

/////////////////////////////////////////
// Setup Port C
// Bit: 7 6 5 4 3 2 1 0
// Dir: I I I I I I I O
// Pullup: P P P P P P P N
/////////////////////////////////////////
DDRC = 0x01;
PORTC = 0xFE;

/////////////////////////////////////////
// Setup Port D
// Bit: 7 6 5 4 3 2 1 0
// Dir: I I I I O I I I
// Pullup: P P P P N P P P
/////////////////////////////////////////
DDRD = 0x08;
PORTD = 0xF7;

/////////////////////////////////////////
// Setup Port E
// Bit: 7 6 5 4 3 2 1 0
// Dir: I I I O O I O I
// Pullup: P P P N N P N P
/////////////////////////////////////////
DDRE = 0x1A;
PORTE = 0xE5;

/////////////////////////////////////////
// Setup Port F
// Bit: 7 6 5 4 3 2 1 0
// Dir: O I I I I I I I
// Pullup: N P P P P N N N
/////////////////////////////////////////
DDRF = 0x80;
PORTF = 0x78;

/////////////////////////////////////////
// Setup Port G
// Bit: 7 6 5 4 3 2 1 0
// Dir: I I I
// Pullup: P P P
/////////////////////////////////////////
DDRG = 0x00;
PORTG = 0x07;

/////////////////////////////////////////
// Setup Timer 3 PWM
// non-inverting fast PWM
// Fclk = F_CPU/8
// frequency set by ICR3 Fpwm = Fclk/(1+ICR3);
/////////////////////////////////////////
TCCR3A = 0xA2;
TCCR3B = 0x1A;
ICR3 = PWM_FREQ;
OCR3A = PWM_FREQ * (0.321/3.3);
OCR3B = PWM_FREQ * (0.321/3.3);

/////////////////////////////////////////
// Setup SPI bus
/////////////////////////////////////////
spi_init();

/////////////////////////////////////////
// Setup SD card
/////////////////////////////////////////
SD_init();
SPI_HIGH_SPEED; //SCK - 4 MHz
_delay_ms(1);   //some delay

/////////////////////////////////////////
// Setup FAT tables
/////////////////////////////////////////
if(getBootSectorData())  //read boot sector and keep necessary data in global variables
{
uart1_puts("FAT32 not found!");
}

/////////////////////////////////////////
// Enable Interrupts
/////////////////////////////////////////
asm("SEI");

/////////////////////////////////////////
// Setup USART
/////////////////////////////////////////
uart_init(UART_BAUD_SELECT(BAUD, F_CPU));
uart1_init(UART_BAUD_SELECT(BAUD, F_CPU));

/////////////////////////////////////////
// Setup the uM-FPU
// Serial Port: 9600 8/N/1 NMEA parsing
/////////////////////////////////////////
fpu_reset();
fpu_write3(SEROUT, 0, 6);
fpu_write2(SERIN, 4);
}

/////////////////////////////////////////
// Cell_Init function
// This function initializes the cell
// module: 9600 baud, no command echos, auto band select,
// text mode SMS, no call or SMS notifications,
// and use SIM card memory
/////////////////////////////////////////
int Cell_Init()
{
uart1_puts("\r\n********************************************************************************\r\n                    CELLULAR MODULE INITIALIZATION\r\n********************************************************************************\r\n");

char cell_response[UART_RX_BUFFER_SIZE] = {'\0'};

// Check if the cell module is already powered on
uart1_puts_p(PSTR("checking if cell module is powered on..."));
uart_puts_p(PSTR("AT\r"));
if(check_cell_response("OK"))
{
// Power on the cell module
PORTA = 0x80;
delay_seconds(2);
PORTA = 0x00;
delay_seconds(5);
}
uart1_puts_p(PSTR("ok\r\n"));

// Set the cell module baud to 9600
uart1_puts(PSTR("setting baud to 9600..."));
uart_puts(PSTR("AT+IPR=9600\r"));
if(check_cell_response("OK"))
{
return 1;
}
uart1_puts_p(PSTR("ok\r\n"));

// Disable character echos from the cell module
uart1_puts_p(PSTR("disabling command echo..."));
uart_puts_p(PSTR("ATE0\r"));
if(check_cell_response("OK"))
{
uart1_puts_p(PSTR("failed\r\n"));
return 1;
}
uart1_puts_p(PSTR("ok\r\n"));

// Enable auto band selection
uart1_puts_p(PSTR("setting auto band selection..."));
uart_puts_p(PSTR("AT#AUTOBND=2\r"));
if(check_cell_response("OK"))
{
uart1_puts_p(PSTR("failed\r\n"));
return 1;
}
uart1_puts_p(PSTR("ok\r\n"));

// Use text mode for SMS editing
uart1_puts_p(PSTR("setting SMS text mode..."));
delay_seconds(5);
uart_puts_p(PSTR("AT+CMGF=1\r"));
if(check_cell_response("OK"))
{
uart1_puts_p(PSTR("failed\r\n"));
return 1;
}
uart1_puts_p(PSTR("ok\r\n"));

// Disable all incoming and outgoing alerts
uart1_puts_p(PSTR("disabling alerts..."));
delay_seconds(5);
uart_puts_p(PSTR("AT+CNMI=0,0,0,0,0\r"));
if(check_cell_response("OK"))
{
uart1_puts_p(PSTR("failed\r\n"));
return 1;
}
uart1_puts_p(PSTR("ok\r\n"));

// Use SIM card storage for text messages
uart1_puts_p(PSTR("setting SMS memory to SIM card..."));
delay_seconds(5);
uart_puts_p(PSTR("AT+CPMS=SM\r"));
if(check_cell_response("OK"))
{
uart1_puts_p(PSTR("failed\r\n"));
return 1;
}
uart1_puts_p(PSTR("ok\r\n"));

uart1_puts_p(PSTR("waiting for network registration..."));
// Wait for the cell module to register with a network
while(strstr(cell_response, "+CREG: 0,1") == NULL)
{
uart_puts_p(PSTR("AT+CREG?\r"));
delay_seconds(1);
uart_gets(cell_response, UART_RX_BUFFER_SIZE);
delay_seconds(5);
}
uart1_puts_p(PSTR("ok\r\n"));

return 0;
}

/////////////////////////////////////////
// SMS_Test function
// This function sends a SMS message
// including a test string to the hardwired
// email or phone number.
/////////////////////////////////////////
void SMS_Test()
{
char cell_response[UART_RX_BUFFER_SIZE] = {'\0'};

uart1_puts_p(PSTR("\r\n################################################################################\r\n                    SEND SMS TEST MESSAGE\r\n################################################################################\r\n"));

uart1_puts_p(PSTR("sending SMS message..."));

// If there is an error sending the message keep trying
while(strstr(cell_response, "OK") == NULL)
{
// Format the SMS message string
uart_puts_p(PSTR("AT+CMGS=500\r"));
delay_seconds(1);
// Enter the string into the cell module
uart_puts_p(PSTR("lwardens@gonzaga.edu This is a test of the Ahnung SMS message system.\r\nabcdefghijklmnopqrstuvwxyz\r\nABCDEFGHIJKLMNOPQRSTUVWXYZ\r\n1234567890~!@#$%^&*()_+<>"));

// Send the CTRL-Z character to send the SMS
uart_putc(0x1A);

// Check the response from the cell module
uart_gets(cell_response, UART_RX_BUFFER_SIZE);
delay_seconds(5);
}
uart1_puts_p(PSTR("ok\r\n"));
}

/////////////////////////////////////////
// GPS_Status function
// This function sets the uM-FPU to
// receive NMEA sentances and reads the
// GPS status register
/////////////////////////////////////////
char *GPS_Status()
{
char tmp_status;

uart1_puts_p(PSTR("\r\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r\n                    GPS STATUS\r\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r\n"));

// Have the uM-FPU get a NMEA sentance from the GPS receiver
fpu_write2(SERIN, 6);
fpu_wait();

// Get the GPS receiver status
tmp_status = read_gps_long(GET_STATUS, STATUS);
if(tmp_status == GPS_READY)
{
return "3D GPS Fix Ready\r\n";
}
else if(tmp_status == GPS_DEAD_RECKONING)
{
return "GPS Dead Reckoning Fix Only\r\n";
}
else if(tmp_status == GPS_TIME_ONLY)
{
return "GPS Time Only Fix\r\n";
}
else
{
return "GPS Searching For Singal\r\n";
}

// Disable the uM-FPU serial port to avoid buffer overflows
fpu_write2(SERIN, 0);
fpu_wait();
}

/////////////////////////////////////////
// Test_Chute_Servo function
// This function reads an angle entered
// by the user and converts it to PWM to
// be output on the OCR3B pin
/////////////////////////////////////////
void Test_Chute_Servo()
{
unsigned int servo_cmd=UART_NO_DATA, count=0;
char angle[5];

uart1_puts_p(PSTR("\r\n================================================================================\r\n                    PARACHUTE SERVO TEST\r\n================================================================================\r\n"));
while(1)
{
servo_cmd = UART_NO_DATA;
uart1_puts_p(PSTR("Enter an angle from 0 to 110 degrees or 'E' to exit.\r\n>>"));
while((servo_cmd != '\r') || (count == 0))
{
do
{
servo_cmd = uart1_getc();
}while(servo_cmd == UART_NO_DATA);

if((servo_cmd >= '0') && (servo_cmd <= '9') && (count < 3))
{
uart1_putc(servo_cmd);
angle[count] = servo_cmd;
count++;
angle[count] = '\0';
}
else if((servo_cmd == 'E') || (servo_cmd == 'e'))
{
return;
}
}

// Convert the angle to a PWM pulse width
OCR3B = PWM_FREQ * (0.227 + (atoi(angle) * 0.0022))/3.3;
count = 0;
}
}

/////////////////////////////////////////
// Test_Cutdown_Servo function
// This function reads an angle entered
// by the user and converts it to PWM to
// be output on the OCR3A pin
/////////////////////////////////////////
void Test_Cutdown_Servo()
{
unsigned int servo_cmd=UART_NO_DATA, count=0;
char angle[5];

uart1_puts_p(PSTR("\r\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\r\n                    CUTDOWN SERVO TEST\r\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\r\n"));
while(1)
{
servo_cmd = UART_NO_DATA;
uart1_puts_p(PSTR("Enter an angle from 0 to 110 degrees or 'E' to exit.\r\n>>"));
while((servo_cmd != '\r') || (count == 0))
{
do
{
servo_cmd = uart1_getc();
}while(servo_cmd == UART_NO_DATA);

if((servo_cmd >= '0') && (servo_cmd <= '9') && (count < 3))
{
uart1_putc(servo_cmd);
angle[count] = servo_cmd;
count++;
angle[count] = '\0';
}
else if((servo_cmd == 'E') || (servo_cmd == 'e'))
{
return;
}
}

// Convert the angle to a PWM pulse width
OCR3A = PWM_FREQ * (0.227 + (atoi(angle) * 0.0022))/3.3;
count = 0;
}
}

/////////////////////////////////////////
// Test_SD function
// This function writes the str string
// to the test.dat file on the SD card,
// waits for the user to modify the file on a PC,
// and reads out the modified file
/////////////////////////////////////////
void Test_SD(char *str)
{
unsigned int SD_cmd = UART_NO_DATA;

uart1_puts_p(PSTR("\r\n$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\r\n                   SD CARD TEST\r\n$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\r\n"));
uart1_puts_p(PSTR("Writing string to SD card test.dat file...\r\n"));
uart1_puts(str);
uart1_puts_p(PSTR("\r\n"));
writeFile("test.dat", str);
uart1_puts_p(PSTR("\r\nPlease remove the SD card, read the test.dat file on your PC, modify the file, re-insert the card in the balloon, and press 'G'\r\n>>"));

// Wait for the user to re-insert the SD card and press the G key
while((SD_cmd != 'G') && (SD_cmd != 'g'))
{
SD_cmd = uart1_getc();
}

// Re-initialize the SD card
SPI_SD;
SD_init();
SPI_HIGH_SPEED;
uart1_puts_p(PSTR("\r\nReading SD card test.dat file...\r\n"));
_delay_ms(1);

readFile(READ, "test.dat");
deleteFile("test.dat");
}

/////////////////////////////////////////
// delay_seconds function
// This function uses the _delay_ms()
// function to create a seconds long delay
/////////////////////////////////////////
void delay_seconds(unsigned int seconds)
{
int i = 0;

while(seconds > 0)
{
for(i=0; i<1000; i++)
{
_delay_ms(1);
}

seconds--;
}
}

/////////////////////////////////////////
// uart_gets function
// This function uses the uart_getc()
// function to read all valid characters in
// the uart buffer
/////////////////////////////////////////
void uart_gets(char *temp_str, int max_str_size)
{
int index=0;
unsigned long int timeout=0;
char temp;

// Keep getting characters until the buffer is full or the timeout is reached
while((index < max_str_size) && (timeout < 2000000))
{
temp = (char)uart_getc();
if(temp != 0)
{
temp_str[index] = temp;
index++;
}

timeout++;
}
temp_str[index] = '\0';
}

/////////////////////////////////////////
// check_cell_response function
// This function checks the cellular module's
// response string for 'response'. If 'response'
// is not found the module is powered off and 1 is
// returned, otherwise 0 is returned.
/////////////////////////////////////////
int check_cell_response(const char *response)
{
char temp_response[UART_RX_BUFFER_SIZE] = {'\0'};

uart_gets(temp_response, UART_RX_BUFFER_SIZE - 1);
if(strstr(temp_response, response) == NULL)
{
// Power off the cell module
PORTA = 0xFF;
delay_seconds(2);
PORTA = 0x7F;
delay_seconds(10);
return 1;
}

return 0;
}

/////////////////////////////////////////
// read_gps_float function
// Get one float from the specified
// uM-FPU register
/////////////////////////////////////////
double read_gps_float(char function, char gps_register)
{
if(function >= 0)
{
fpu_write2(FCALL, function);
}

fpu_wait();
fpu_write2(FREAD, gps_register);
return fpu_readFloat();
}

/////////////////////////////////////////
// read_gps_float function
// Get one long from the specified
// uM-FPU register
/////////////////////////////////////////
long read_gps_long(char function, char gps_register)
{
if(function >= 0)
{
fpu_write2(FCALL, function);
}

fpu_wait();
fpu_write2(LREAD, gps_register);
return fpu_readLong();
}