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();
}

15 March 2010

What's This PBS?

    I've been making slow and steady progress on the balloon's firmware. Since the microcontrollers on my PCB are throwing a tantrum and not letting me program them in-system I've been using an STK600 and an ATMega64 to do my development. The real balloon will be using an ATMega128, so the Mega64 I'm using for development should keep me well under the memory size for the real balloon. I currently have the diagnostic and startup functions coded. When the balloon starts up both microcontrollers will boot to a configuration menu. The menu will allow the operator to use a laptop and a terminal program to exercise each of the balloon's functions. There's an option in the menu to start the flight program and data logging functions for the real flight. All that's left is to write the data logging, flight routines (things like cutting off the balloon after it bursts and deploying the parachute at the right altitude), and assembling the hardware components together.
    To keep me inspired I've been watching some scientific and creative TV shows from Netflix. I've just found a fantastic show that aired on PBS in 2002. The show is called Rough Science. It's a mix of Mythbuster's type challenges and MacGuyver type science. It has really inspired me and shown how fun science can be. The first season the team used basic chemistry, botany, physics, and very simple tools to collect gold and smelt it into jewelry! The second season, which I'm currently watching, is all about space as simulated in Death Valley. If you want to recapture the excitement of high school science experiments without all the work and weird teacher guys, you need to watch this show!
    Hopefully next time I'll post up some code...that is if I have more to show you than some terminal output. See you next time space cowboys! *cue banjo music and gun shots*

02 March 2010

Launch and Landing

    While I've been working on the hardware and firmware for the balloon I've also been thinking about where to launch from and where it might land. I've been using the Near Space Flight Tracking Utility from Near Space Ventures, Inc. While the Google map plotting doesn't seem to work, the XML file is well formatted with altitudes and lat/long. I've entered in data assuming a 3 pound capsule, a kaymont 1500 gram cold weather balloon, and the secret design item I've been alluding to... When the balloon reaches burst altitude I've added in the hardware capability to separate the balloon scraps from the capsule. I've also added in hardware to release a parachute at a specified altitude. Finally, Arhan will be programming two servos that control steering fins on the end of the capsule, which will be rocket shaped. All of this added together amounts to something close to a GPS guided bomb, without the explosives of course. The capsule will separate from the balloon after burst, attempt to steer itself back to its launch location, and at a safe altitude it will deploy the parachute. All of that should reduce the effects of wind drift during landing.
    I'm living in the D.C. area now, and everywhere I look is city or dense trees. I haven't been in the area all that long either and I'm not certain about good launch sites, so I decided to go with what I know. I've picked a few launch sites near Billings, MT where I know there are few people, few trees, and predictable weather patterns. The maps below show the predictions from Near Space Ventures that I've taken over the past couple months. You can see that I have several options depending on which way the wind decides to blow on launch day. Along with the launch I plan on taking my summer vacation in Yellowstone National Park this year! If everything goes well with the hardware and firmware I'm planning on an early July launch.

19 February 2010

Work and FIRST Robotics

    Not much new has happened since my last post. I've been very busy at work and I've been volunteering for the FIRST robotics competition. My team, Rolling Thunder Robotics, is almost finished assembling our robot. We have one last night of building before the scrimmage Saturday morning. Being a FIRST mentor has been great and I encourage anyone who is interested to join in. You don't need to know anything about robots to help, but most of the work is about designing and building a robot...mechanical things seem to be the stumbling point for most teams. www.usfirst.org
    With my excuses and public service announcements out of the way it's on to some firmware! As I've mentioned before I decided to use Atmel's ATmega128 microcontroller for this project. There are actually two of them (one for the GPS and cellular module and one for the temp, pressure, acceleration, and rotation sensors). I could have gotten away with just one, but they're cheap and I have room to expand this way. Each micro will have an SD card with a FAT32 file system for data logging. It's somewhat scary using file systems for the first time, but you'll find it can be very easy! I've found a pre-made FAT32 library for AVR's from Dharmanitech (www.dharmanitech.com/2009/01/sd-card-interfacing-with-atmega8-fat32.html). It is written with the ATmega32 in mind, but it's easy enough to change the UART and SPI definitions for use with any AVR. I highly recommend running the example project before trying to modify it. Since the code is a little bit hard to follow it will give you a much better idea of how it works. Below are some pointers on how to get your custom version up and running.

SD FAT32 Initialization:
You must run the following functions to setup communications and the FAT32 tables:
  • spi_init()
  • SD_init()
  • getBootSectorData()
SD FAT32 File Operations:
Reading, writing, and deleting files is as easy as calling a function...
  • readFile(READ, fileName)
    • fileName is a null terminated string containing the name of the file and the extension
    • the output is sent to whatever UART port is defined in the UART_routines.h file
  • writeFile(fileName, dataString)
    • fileName is a null terminated string containing the name of the file and the extension
    • dataString is the text to write to the file terminated by the '~' character
    • this function will append dataString to a file if it already exists or create the file if it doesn't
  • deleteFile(fileName)
    • fileName is a null terminated string containing the name of the file and the extension
!!!!BUG REPORT!!!!
    I found a bug in the SD FAT32 library that really made me scratch my head for a while. The problem happens when you use the readFile, writeFile, or deleteFile functions with the file name entered directly.

e.g. writeFile("Test.txt", data);

    Inside these functions the file name gets expanded to a format that is expected by the FAT32 file system. This format is 13 bytes long. If your file name is shorter than 12 characters you will overwrite whatever data is in memory behind the fileName variable used by these functions... The fix is pretty easy and hopefully it will be integrated into the library for the next update. Follow the steps below and you're all set until then!

In FAT32.c:
1. Place this code at the top of convertFileName:
int j=0, k;

// Copy the file name to a large buffer so memory isn't corrupted by the convertFileName function
while((fileName[j] != 0) && (j<16))
{
FName[j] = fileName[j];
j++;
}
FName[j] = 0;
j=0;

2. In the writeFile function change "dir->name[j] = fileName[j];" to "dir->name[j] = FName[j];"

3. In the readFile function change "dir = findFiles(GET_FILE, fileName); //get the file location" to dir = findFiles(GET_FILE, FName); //get the file location"

4. In the deleteFile function change "findFiles(DELETE, fileName);" to "findFiles(DELETE, FName);"

In FAT32.h:
1. Place this variable declaration with the other global variables:
volatile char FName[16];

12 February 2010

D.C. Blizzard of 2010

    I'm finally back to work after 4 days of online training from home. I don't particularly enjoy giving up my time to work for someone else, but today I'm actually happy to get away from my apartment and do something other than stare at a computer screen. The blizzard of 2010 was very unspectacular to me, but there were 40 MPH winds and about 1.5 feet of snow. The biggest problem I see is that the roads and parking lots here weren't designed with snow in mind. For that matter they weren't even really designed with cars in mind...they are really meant for horses... Since the snowplows have nowhere to push the snow to most people just have to wait for it to melt before they can drive anywhere.
    Now on to the important stuff! I did not make any progress with being able to program the microcontrollers on my board. I actually have no idea what the problem is. I checked proper voltages, the programming connections, clock sources, part numbers, and my sanity several times. Nothing seems to work, but there is another way. The plan is to work with my STK600 to develop the code as best I can and use it to program the chips through a ZIF socket. Then I can solder the programmed chips onto the board and see if the program runs as expected. I was smart enough to bring UART connections to some headers for debugging purposes so it will be easy to verify that my programs and circuits work. This is definitely not an ideal situation, but it will save me from spending more money on redesigning the balloon board.
    I have also enlisted the help of my friend Arhan to create a tracking application for the balloon's flight. The idea so far is to send periodic SMS messages from the balloon to a second cellular module connected to a laptop. The laptop will take in the SMS messages over USB and plot GPS coordinates, altitude, speed, etc. I have not tested any of my antenna ideas yet, so I don't know if we will achieve constant communication, but any time the balloon is below 30,000 feet we will be able to track it. I'm really only concerned about knowing where it lands. All the data collected during the flight will be stored on SD cards on the payload so we can always reconstruct the flight if we can recover the payload. See you next time space cowboys!

05 February 2010

PCB's are in!!!

    The circuit boards are in and I've partially assembled them. The weather forecast is predicting a top 10 worst snow storm for the D.C. area. I was sent home early from work because of the paranoia. Having grown up in Montana I'm not scared by any amount of snow. Let's see how the weather was when I started the assembly...
 

    Below you can see the circuit board, my soldering iron, and some bread. Bread...never solder without it! Seeing as my kitchen counter tops are the only solder resistant surfaces in the apartment that's where I decided to work. I've decided to put on the two ATmega128 micros, their crystals, and the programming headers. With those components on the board I should be able to load a program into the micros and set the fuses to use the external crystals.
 


     When I hooked the board up to my STK600 for programming I couldn't get a connection. I checked through the schematics, took some measurements with my multimeter, and retried several times. Since I had no luck there I decided the STK600 may not be providing enough power through the ISP cable so I soldered on the regulators and battery connector. I connected the battery and measured all the voltage rails. Everything looked good, so I tried the programming again... Still no luck. My thoughts now are that the micros have not been completely soldered to the board. Since they are the QFN style package with a very tiny bit of pin showing it is extremely hard to solder properly. Seeing as I don't have a microscope it's even harder. I may wait until Monday and use the hot air rework station at work...after hours of course.
    Now, about four hours after I started assembling and pretty close to when my work day should have ended...

     This tiny amount of snow wouldn't have stopped even the most timid of D.C. drivers, but I'm happy to have an early Friday off so I could work on the balloon computer and share the progress with you. Maybe tomorrow will include a revelation on my microcontroller programming problems.

02 February 2010

The VectorNav VN-100

    VectorNav is currently a one product company. That is music to my ears when it comes to electronics. It means I will most likely get fantastic support and a great quality product from a small company that will treat me like royalty. I haven't actually had to ask for help on this module yet, so I couldn't say for sure on the support. The module is a great piece of design and functionality though. In the space of a large postage stamp (it even has the castellations like a stamp would have) VectorNav has managed to cram in three axis of accelerometer, three axis of gyro, and three axis of magnetometer. The best part is that the on-board processing power takes care of all the analog, fills up some well defined registers, and lets you choose between reading the registers manually or getting NMEA formatted sentences automatically.


    Probably the most useful part of this module is the ability to get an accurate yaw/pitch/roll in degrees with zero work. The module reads its sensors and computes the angles for you. There is also the ability to upload the current magnetic map of your area, just in case the government finally created that machine that stops the Earth's core from spinning. The Russians are also trying to steal the magnetic north pole by quietly moving it towards themselves about 40 miles per year*. There is a fantastic development kit that allows RS-232, USB, or your own hand soldered connections to the module. The software that comes with it will give you all the sensor data and display real-time 3D rotating version of the board.
    I plan on using this sensor to measure my heading and also test the effectiveness of a small gyro placed inside the capsule to keep it from spinning like so many other balloon projects have. It's not very fun to look at blurred pictures of the landscape whizzing past because your capsule won't stay still. The other key component of this will be a rotatable clip to attach the balloon to the capsule. Something like those plastic key chains with the stretchy coiled cord should work. Now, why do I really need to know my heading....something to do with the circular shaped PCB no doubt.


* The Russians are not actually attempting to steal the magnetic north pole. It is a natural phenomenon that has been observed to happen in geologic studies several times over Earth's history. In fact many believe that the magnetic poles will flip sometime in the near future based on those geologic records.

01 February 2010

The Printed Circuit Board (PCB)

I finished the schematic capture and PCB layout about a week ago and finally my PCB is being manufactured! I did the design using Altium Designer. It's an easy to use tool that is almost as powerful as the industry leader, Cadence. It also costs much much less than Cadence. The quote for my PCB came in at $650 for two boards on a 10 day build schedule. The component cost came in at about $200, but I am reusing some parts from previous projects like the GM862 and some SD memory cards. You can enjoy some PCB pron below and I'll be sure to take pictures during the assembly of the real board when it comes in. Does this circular shape allude to some devious plan that I have for my payload capsule?

30 January 2010

What happened NASA

    All through my childhood I was fascinated by the moon landing, the space shuttle, and all the things NASA engineers were able to do. Last night I got curious about how my near space balloon computer would stack up against the Apollo 11 computer. Apollo 11 was the first manned moon landing for those of you who aren't up on space history. Seeing as my iPhone has roughly 10 times more of everything than my first PC, I figure my balloon computer should come pretty close to what Apollo 11 had. The specs for both are below.

Luke's Near Space Balloon Computer:
  • 2 ATMega128 microcontrollers
    • 8 MHz clock
    • 128k flash memory
    • 4k SRAM memory
  • 2 uM-FPU co-processors
    • 29 MHz clock
    • ~2k flash memory
  • Telit GM862 Cellular Module
    • Python script interpreter
    • 2MB flash memory
    • 1.2MB RAM
Apollo 11 Command Module Computer:
  • Custom Made Integrated Circuits
    • 1 MHz clock
    • 72k ROM
    • 4K RAM
    To be fair the Apollo command module also had the worlds best computer built-in...the human brain. My balloon will be unmanned so it must handle all the problems itself. It's still incredible that NASA was able to keep track of so many sensors and control systems with the equivalent of a $1 microcontroller in today's world. Be careful if you're thinking NASA's budget is way overinflated, though. The sensors, actuators, testing, and development are where the expensive stuff is. Take for example my balloon. The microcontrollers cost me about $15 each, but the IPT temperature/pressure sensor will cost $500. My components aren't rated to handle the large amounts of radiation that circle just outside our atmosphere either. Ionizing radiation will erase the contents of flash memory and that's where the computer's programming is stored!

29 January 2010

The Tellit GM862 Cellular Module

    There are many challenges in the communications design for a high altitude balloon. With small standard antennas cellular service stops working anywhere between 20k - 40k feet. There are several arguments as to why, but the most accepted reason is that at altitude multiple cell towers begin to receive the cellular signal and the network becomes confused. Many balloon designs avoid the cellular problem by using amateur radio devices or HAM radio. There is certainly no problem receiving a HAM signal from high altitudes, but as the balloon descends to the ground the line-of-sight becomes obstructed and the communications link can be lost. In my opinion it is better to know where the balloon lands than to keep constant communication with it in the air. Of course both HAM and cellular could be used on the same balloon, but weight is also an issue. With all that in mind I decided to go with a cellular only solution and conduct some experiments with cantennas and waveguide antennas to combat the multi-tower problem.
    I chose to use the GM862 cellular module from Tellit because they are readily available, they include the SIM card connector on-module, and they use a built-in antenna connector. It's quite difficult to do impedance matching on a PCB when you don't have all of the professional tools for layout. The module can be bought with built-in GPS, but the SiRF III chipset is limited to operation below 60k feet. Since I'm heading to almost twice that altitude I won't be able to use that chipset. The cellular part of the module connects to a "data terminal" through a TTL level serial connection. That makes it very easy to experiment with the module using a computer and a TTL to RS-232 level sifter. The commands are in the standard Hayes AT modem commands with a few specialty ones. All of this is well documented in the Software User's Guide and the AT Reference Guide.
    When using the GM862 with a microcontroller things can get a bit tricky. A micro doesn't have a built-in serial buffer like a data terminal has, so there is lots of overhead in the code to capture all the data coming from the GM862 and deal with the data terminal style formatting. It can be a pain to parse the buffer for a status and I usually end up counting the number of characters I expect the module to return for a particular command. If I don't see the status I expected I dig deeper into error cases. It helps a lot if you turn off the GM862's echo feature. That way the commands you send aren't echoed back to you as you send them and there are less characters to confuse the returned data. The echo is enabled by default and can be turned off by sending the ATE0 command. Echo can be re-enabled by sending ATE1. Below is a list of commands you need to get a cellular call and SMS message going, quick and dirty!

Quick Start Cellular Call:
  • AT#AUTOBND=2    Sets the module to automatically scan all 4 cellular bands for a network
  • AT+CREG?    Checks if the module is registered on a network. You'll get "AT+CREG 1,0\r\nOK\r\n" if it's registered
  • AT+FCLASS=8    Tells the module that you are only making phone calls, if you don't use this you have to place a semicolon after your phone call command. Don't use this command if you are going to send SMS messages also.
  • AT#CAP=2    Tells the module to use the MT microphone input as the source audio
  • ATD {phone number} [; if you didn't use AT+FCLASS=8]   This starts the phone call. Sometimes the network requires the full 10 digit phone number with area code, so it's best to just put it all in there.
  • ATH   This will end the voice call.
Quick Start SMS:
  • AT+CMGF=1    This command sets the SMS editing mode to TEXT mode. If you understand PDU mode you can use that also, but I prefer to read and write in english.
  • AT+CPMS="SM"    This sets the module to store SMS messages on the SIM card
  • AT+CNMI=0,0,0,0,0    This command sets the notifications that the module sends out about SMS messages. The first zero disables notifications about incoming messages. The second zero disables notifications about messages being sent. The last three zeros deal with low-level network stuff and will probably always be zero for your project.
  • AT+CMGS="{phone number} This command tells the module to start a new SMS message. After the command is sent the module will return a command prompt (>) and then you enter the text you want to send. To actually send the message you must send CTRL-Z (0x1A in hex) to the module. 

28 January 2010

The Micromega uM-FPU

    I found this great floating point co-processor on the SparkFun website. If you haven't been to www.sparkfun.com and you're looking for hobby electronics parts I highly recommend you go! If you're wondering what a floating point co-processor is good for read on.
    Microcontrollers were designed to be simple processors with a lot of peripheral devices built into the same chip. They are great for many tasks, but most of them do not include hardware support for floating point calculations. If you can deal with working only in integer numbers that isn't a problem, but if you need to use decimal precision of any kind you might have to include a very large math library to your code. The other alternative is to use a floating point co-processor.
    The uM-FPU attaches to the microcontroller via a SPI, I2C, or UART bus. Once the connection is made the microcontroller sends commands (in the uM-FPU machine code format) to the co-processor to do any number of floating point, integer, unit conversion, or NMEA sentence parsing. If you're thinking, "Great...now I have to hand code the bus transactions for the uM-FPU machine code" that may not be the case. The Micromega website, www.micromegacorp.com, has API libraries for many of the popular micros like PIC, AVR, Basic ATOM, etc.
    The uM-FPU is also capable of storing user defined functions in its internal memory. This is handy if you have a lengthy set of commands that you run regularly, like retrieving the lat/long co-ordinates from a NMEA string. To program the user defined functions you'll need the uM-FPU IDE from the Micromega website. The documentation on the IDE and the command set is a little lacking so there are a few pointers below. Once you write a few lines of code with this co-processor you'll be surprised how simple and powerful it is!

Making your own functions:
  • The first tab on the IDE lets you enter code and compile it for a number of different target micros or the uM-FPU itself. To compile a function to be stored on the uM-FPU choose "uM-FPU Assembler" from the dropdown list.
  • User functions must start with #FUNCTION [optional function name]
  • Your code must be enclosed in #asm and #endasm tags
  • Your function must end with #END after the #endasm tag
  • After you click compile you can see a list of your functions on the Functions tab
 The Chip Select Pin:
  • The chip select pin is setup to choose between SPI and I2C interfaces by default. It does not work as a normal SPI chip select pin unless you use the IDE to program the settings.
  • If you want to use the IDE to program the device you must tie the chip select pin high to force the UART connection on powerup. It is possible to debug without holding the chip select pin high.
The MCLR# Pin:
  • You must tie this pin high for the chip to work, it does not have an internal pullup like all the AVR folks will be use to.
The Support Forum:
  • Unlike most support forums, Micromega has a staff member that routinely checks this forum. You can expect to have your questions replied to by the staff in a day or so. They are extremely helpful!
  • http://tech.groups.yahoo.com/group/uMFPU/messages