Since the voting regarding which angle of attack to use on tackling the out of memory problem resulted in a tie I decided to take a half-of-each approach. The code would be optimized as much as possible, but instead of dumping more RAM into the mix, I would set up a second microcontroller as a math co-processor.

I had a few right here to chose from. Two PIC chips, two ATMega644P’s, a Sanguinololu (which another project already had dibs on unless I was guaranteed it could do the job, and two Parallax Propeller P8X32A QuickStart boards.

The Parallax Propeller is an interesting board. It’s a 32-bit 8-core processor that supports development in C, C++, SPIN, and ASM. Each core operates at a user-defined speed ranging from 20kHz to 80MHz (the k in kHz wasn’t a typo). The 32-bit architecture also lent itself nicely to the floating-point arithmetic needed by the solar position calculations. It also has 32kb of program memory and 32kb of RAM.

Here’s a picture of it as if that helps at all:

So I programmed it up and TADA! It was out of memory. Because the fucker only allocates 4k per core and there’s nothing you can do to get at the rest.

Fuck. So I bought a RAM upgrade for the original microcontroller.

$20 lighter.

This is the pre-optimized code in C++ for a 32 bit compiler if anyone has any ideas:

#include <Arduino.h>
#include "SolarPosition.h"
#include <Time.h>
#include <HardwareSerial.h>
int inputMonth;
int inputDate;
int inputYear;
int inputHour;
int inputMinute;
int inputAMPM;
double degreesToRadians = 3.1416 / 180.0;
double radiansToDegrees = 180.0 / 3.1416;
double feetToMeters = 1.0 / 3.28;
double solarMinutesAfterMidnight;
SolarPosition::SolarPosition(){
degreesToRadians = 3.1416 / 180.0000;
radiansToDegrees = 180.0000 / 3.1416;
feetToMeters = 1.0000 / 3.2800;
}
double SolarPosition::getOutputEOT(){
return outputEOT;
}
double SolarPosition::getSolarMinutesAfterMidnight(){
return solarMinutesAfterMidnight;
}
String SolarPosition::getOutputLSOT(){
return outputLSOT;
}
String SolarPosition::getOutputClockTime(){
return outputClockTime;
}
double SolarPosition::getOutputHourAngle(){
return outputHourAngle;
}
double SolarPosition::getOutputAltitude(){
return outputAltitude;
}
double SolarPosition::getOutputDeclination(){
return outputDeclination;
}
String SolarPosition::getOutputSunrise(){
return outputSunrise;
}
String SolarPosition::getOutputSunset(){
return outputSunset;
}
double SolarPosition::getOutputAzimuth(){
return outputAzimuth;
}
void SolarPosition::setTime(int Year, int Month, int Date, int Hour, int Minute, int AMPM){
inputYear = Year;
inputDate = Date;
inputMonth = Month;
inputHour = Hour;
inputMinute = Minute;
inputAMPM = AMPM;
}
void SolarPosition::Calculate(int Year, int Month, int Date, int Hour, int Minute, int AMPM) {
inputYear = Year;
inputDate = Date;
inputMonth = Month;
inputHour = Hour;
inputMinute = Minute;
inputAMPM = AMPM;
inputLongitude = 77.45;
inputEastWest = 0; // West = 0, East = 1
inputLatitude = 43.22;
inputNorthSouth = 0; // North = 0, South = 1;
inputElevation = 465;
inputFeetMeters = 0; // Feet = 0, Meters = 1
inputTimeZone = -5;
inputDaylight = 1;
inputZeroAzimuth = 0;// 0 = North, 1 = South
if (inputEastWest == 1) inputLongitude *= -1; // [0] = east, [1] = west
if (inputNorthSouth == 1) inputLatitude *= -1; // [0] = north, [1] = south
if (inputMonth > 2) {
inputMonth = inputMonth - 3;
}
else {
inputYear = inputYear - 1;
inputMonth = inputMonth + 9;
}
if (inputLongitude > 180) inputLongitude = inputLongitude - 360;
if (inputLongitude < -180) inputLongitude = inputLongitude + 360;
if (inputFeetMeters == 0) inputElevation *= feetToMeters;
int meridian = inputTimeZone * -15;
if (inputAMPM == 0 && inputHour == 12) inputHour = 0;
if (inputAMPM == 1) { if (inputHour != 12) inputHour += 12; }
double inputHoursAfterMidnight = inputHour + (float)inputMinute / 60;
double inputMinutesAfterMidnight = inputHour * 60 + inputMinute;
double UT = inputHoursAfterMidnight - inputTimeZone - inputDaylight;
double t = ((UT / 24) + inputDate + floor(30.6 * double(inputMonth) + 0.5) + floor(365.25 * double(inputYear - 1976)) - 8707.5) / 36525;
double G = NormalizeTo360(357.528 + 35999.05 * t);
double C = (1.915 * sin(G * degreesToRadians)) + (0.020 * sin(2.0 * G * degreesToRadians));
double L = NormalizeTo360(280.460 + (36000.77 * t) + C);
double alpha = L - 2.466 * sin(2.0 * L * degreesToRadians) + 0.053 * sin(4.0 * L * degreesToRadians);
double GHA = NormalizeTo360(UT * 15 - 180 - C + L - alpha);
outputDeclination = atan(tan((23.4393 - 0.013 * t) * degreesToRadians) * sin(alpha * degreesToRadians)) * radiansToDegrees;
outputEOT = (L - C - alpha) / 15;
double clockTimeToLSOTAdjustment = ((inputLongitude - double(meridian)) / 15.0) - outputEOT + double(inputDaylight); // in hours
double solarHourAngle = NormalizeTo180(GHA - inputLongitude);
double apparentSolarTime = NormalizeTo24(12 + solarHourAngle / 15.0);
solarMinutesAfterMidnight = inputMinutesAfterMidnight - (clockTimeToLSOTAdjustment * 60.0);
double whichDay = 0;
if (solarMinutesAfterMidnight < 0)
{
solarMinutesAfterMidnight += 24 * 60;
whichDay = -1;
}
if (solarMinutesAfterMidnight >= 24 * 60)
{
solarMinutesAfterMidnight -= 24 * 60;
whichDay = 1;
}
String solarTime = MinutesToClockTime(int(solarMinutesAfterMidnight));
if (whichDay == -1) outputLSOT = solarTime + "-";
if (whichDay == 0) outputLSOT = solarTime;
if (whichDay == 1) outputLSOT = solarTime + "+";
whichDay = 0;
if (inputMinutesAfterMidnight < 0)
{ // it's the day before
inputMinutesAfterMidnight += 24 * 60;
whichDay = -1;
}
if (inputMinutesAfterMidnight >= 24 * 60)
{ // it's the next day
inputMinutesAfterMidnight -= 24 * 60;
whichDay = 1;
}
String clockTime = MinutesToClockTime((int)inputMinutesAfterMidnight);
if (whichDay == -1) outputClockTime = clockTime + "-";
if (whichDay == 0) outputClockTime = clockTime;
if (whichDay == 1) outputClockTime = clockTime + "+";*/
outputHourAngle = (solarMinutesAfterMidnight - (12 * 60)) / 4;
double altitudeAngle = radiansToDegrees * asin(
(sin(inputLatitude * degreesToRadians) *
sin(outputDeclination * degreesToRadians)) -
(cos(inputLatitude * degreesToRadians) *
cos(outputDeclination * degreesToRadians) *
cos((solarHourAngle + 180) * degreesToRadians)));
outputAltitude = altitudeAngle;
double preAzimuthAngle = radiansToDegrees * acos(
(cos(outputDeclination * degreesToRadians) *
((cos(inputLatitude * degreesToRadians) *
tan(outputDeclination * degreesToRadians)) +
(sin(inputLatitude * degreesToRadians) *
cos((solarHourAngle + 180) * degreesToRadians)))) /
cos(altitudeAngle * degreesToRadians));
outputAzimuth = preAzimuthAngle + (inputZeroAzimuth - 180);
if (inputZeroAzimuth == 0)
outputAzimuth = ChangeSign(outputAzimuth, 0, outputHourAngle);
else
outputAzimuth = ChangeSign(outputAzimuth, 1, outputHourAngle);
double sunRiseSetLSoTMinutes = radiansToDegrees * acos(-1 *
(sin(inputLatitude * degreesToRadians) *
sin(outputDeclination * degreesToRadians) -
sin((-0.8333 - 0.0347 * sqrt(inputElevation)) * degreesToRadians)) /
cos(inputLongitude * degreesToRadians) /
cos(outputDeclination * degreesToRadians)) * 4;
outputSunrise = MinutesToClockTime((int)(12 * 60 - sunRiseSetLSoTMinutes + (clockTimeToLSOTAdjustment * 60)));
outputSunset = MinutesToClockTime((int)(12 * 60 + sunRiseSetLSoTMinutes + (clockTimeToLSOTAdjustment * 60)));
}
double SolarPosition::ChangeSign (double input, int mode, double basis) {
if (mode == 0) {
if ((input * basis) < 0) {
input *= -1;
}
}
else {
if ((input * basis) > 0) {
input *= -1;
}
}
return input;
}
String SolarPosition::doubleToString(double input,int decimalPlaces){
if(decimalPlaces!=0){
String string = String((int)(input*pow(10,decimalPlaces)));
if(abs(input)<1){
if(input>0)
string = "0"+string;
else if(input<0)
string = string.substring(0,1)+"0"+string.substring(1);
}
return string.substring(0,string.length()-decimalPlaces)+"."+string.substring(string.length()-decimalPlaces);
} else {
return String((int)input);
}
}
String SolarPosition::MinutesToClockTime (int totalMinutes) {
String output;
int theHours = floor(totalMinutes / 60);
int theMinutes = (totalMinutes % 60);
if (theMinutes < 10) output = "0" + String(theMinutes); // add leading "0" if necessary
else output = String(theMinutes);
if (theHours < 12) {
if (theHours == 0) theHours = 12;
output = String(theHours) + ":" + output + "am";
}
else {
if (theHours == 12) theHours = 24;
output = (String(theHours - 12)) + ":" + output + "pm";
}
return output;
}
double SolarPosition::NormalizeTo360 (double theThing) {
return (theThing - floor(theThing / 360.0) * 360);
}
double SolarPosition::NormalizeTo180 (double theThing) {
theThing = NormalizeTo360(theThing);
if (theThing > 180) { theThing = theThing - 360; }
return (theThing);
}
double SolarPosition::NormalizeTo24 (double theThing) {
return (theThing - floor (theThing / 24.0) * 24);
}