⚠️ This forum has been restored as a read-only archive so the knowledge shared by the community over many years remains available. New registrations and posting are disabled.

All times are UTC + 8 hours




Post new topic Reply to topic  [ 433 posts ]  Go to page Previous  1 ... 9, 10, 11, 12, 13, 14, 15 ... 29  Next
Author Message
PostPosted: Aug 26th, '15, 15:35 
Newbie
Newbie

Joined: Aug 26th, '15, 15:24
Posts: 23
Gender: Male
Are you human?: YES
Location: East Moline, IL USA
I have been watching this thread. It has inspired me to create one of my own although my system is much smaller. I have a couple questions for you.

1. Does your mega freeze up from time to time? mine does and I am currently trying to eliminate all Strings to reduce the SRAM usage on the system. The really strange thing is my free memory is around 6000 bytes yet every couple hours my system locks up. which leads me to my next question...

2. other than the Serial3.begin command and...
Code:
Serial3.print(t1Value); // Send water temp for calibrated pH reading
  Serial3.print("\r"); // Send carriage return to end command
  delay(700); // Wait for serial data
  holding = 0; // Reset buffer counter
  if(Serial3.available() > 1) { // If there is serial data in buffer
    holding = Serial3.available(); // Set buffer counter to number of bytes
    for(i=0; i<holding; i++){ // For loop to read out number of bytes in buffer
      stamp_data_ph[i]= Serial3.read(); // Read serial data to char array
    }
  } else {
    Serial.println(F("pH ERR ")); // Error if no data rx in serial buffer
  }
  phValue = atof(stamp_data_ph); // Convert string to float
  lcd.setCursor(8, 0);
  lcd.print("pH:      ");
  lcd.setCursor(11, 0);
  lcd.print(phValue);

  // DO Sensor board rev6.0
  Serial2.print(t1Value);
  Serial2.print("\r");
  Serial2.print("R"); // Send water temp for calibrated DO reading
  Serial2.print("\r"); // Send carriage return to end command


I didnt see any other code for the pH circuit. Is this all of it (including the variables)?

3. since you manually poll the pH circuit, should there be a:
Serial3.print("R"); // Send water temp for calibrated pH reading
Serial3.print("\r"); // Send carriage return to end command
set of commands before the above code? I see where you send the temp for the temperature compensation, but dont see where you are asking the circuit to send data. When I run this code, I get my ph float all over the place.

4. How do you calibrate these probes with 2 of them connected?

I have asked the Arduino community some of these questions, but they don't understand the aquaponics side of things AS WELL as the code... just the code.

Thanks in advance

(yes, I registered specifically for this however, I do look forward to throwing my .02 in from time to time)


Top
 Profile  
Reply with quote  
    Advertisement
 
PostPosted: Aug 28th, '15, 14:59 
Xtreme Contributor
Xtreme Contributor
User avatar

Joined: May 17th, '14, 07:21
Posts: 180
Gender: Male
Are you human?: YES
Location: Vancouver BC Canada
adamlwvdc36 wrote:
I have been watching this thread. It has inspired me to create one of my own although my system is much smaller. I have a couple questions for you.

1. Does your mega freeze up from time to time? mine does and I am currently trying to eliminate all Strings to reduce the SRAM usage on the system. The really strange thing is my free memory is around 6000 bytes yet every couple hours my system locks up. which leads me to my next question...

Nope, rock solid. In fact grovestreams tells me that I haven't missed a data transmission for 4 months now and previous to that I was still tinkering with the controller. When you say free memory are you talking SRAM or EEPROM? If you have 6Kb of SRAM left, then it is not likely a stack problem. None the less, the heap could still be fragmenting in only a certain function. Add the function below to your sketch.
Code:
int freeRam() {
  extern int __heap_start, *__brkval;
  int v;
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
}


And add this function call to various places (changing the "Ram1:" title) to see where/if the stack is getting screwed up.
Code:
Serial.print(F("Ram1:"));
Serial.println(freeRam());


The number one most common cause of Arduino lockup is poor power. How are you powering your circuit?

adamlwvdc36 wrote:
2. other than the Serial3.begin command and...
I didnt see any other code for the pH circuit. Is this all of it (including the variables)?

You are missing a few lines. Also be sure about the version of your pH and DO stamp. This code should work for everything up to the EZO version. Atlas changed the syntax for the new EZO version. Try the following sketch and see if it works for you.
Code:
float phValue;
float doValue;
char stamp_data_ph[30]; // char array for serial data from ph stamp
char stamp_data_do[30]; // char array for serial data from do stamp
byte holding; // counter for serial chars
byte i; //for loop counter

void setup()
{
  Serial.begin(115200); //For debug
  Serial3.begin(38400); // Init serial for pH sensor
  Serial2.begin(38400); // Init serial for DO sensor
  Serial3.print("E"); // Set the pH board to single reading mode
  Serial3.print("\r"); // Send carriage return to end command
  Serial2.print("E"); // Set the DO board to single reading mode
  Serial2.print("\r"); // Send carriage return to end command
}

void loop()
{
  // pH Sensor board rev4.0
  Serial3.print("R"); // Send water temp for calibrated pH reading
  Serial3.print("\r"); // Send carriage return to end command
  delay(700); // Wait for serial data
  holding = 0; // Reset buffer counter
  if(Serial3.available() > 1) { // If there is serial data in buffer
    holding = Serial3.available(); // Set buffer counter to number of bytes
    for(i=0; i<holding; i++){ // For loop to read out number of bytes in buffer
      stamp_data_ph[i]= Serial3.read(); // Read serial data to char array
    }
  } else {
    Serial.println(F("pH ERR ")); // Error if no data rx in serial buffer
  }
  phValue = atof(stamp_data_ph); // Convert string to float

  Serial.print("pH:      ");
  Serial.println(phValue);

  // DO Sensor board rev6.0
  Serial2.print("R"); // Send water temp for calibrated DO reading
  Serial2.print("\r"); // Send carriage return to end command
  delay(700); // Wait for serial data
  holding = 0; // Reset buffer counter
  if(Serial2.available() > 1) { // If there is serial data in buffer
    holding = Serial2.available(); // Set buffer counter to number of bytes
    for(i=0; i<holding; i++){ // For loop to read out number of bytes in buffer
      stamp_data_do[i]= Serial2.read(); // Read serial data to char array
    }
  } else {
    Serial.println(F("ERR ")); // Error if no data rx in serial buffer
  }
  doValue = atof(stamp_data_do); // Convert string to float

  Serial.print("DO:      ");
  Serial.println(doValue);
}


adamlwvdc36 wrote:
3. since you manually poll the pH circuit, should there be a:
Serial3.print("R"); // Send water temp for calibrated pH reading
Serial3.print("\r"); // Send carriage return to end command
set of commands before the above code? I see where you send the temp for the temperature compensation, but dont see where you are asking the circuit to send data. When I run this code, I get my ph float all over the place.

The "R" instruction tells the stamp to return a single measurement without temperature compensation. Sending a temperature (t1Value) instructs the stamp to return a temperature compensated measurement. You should use only one of these commands

I used both commands in the DO section temporarily just for testing and was corrected many revisions ago. You are working off an old revision. Maybe I should put the code on GitHub... :think:

adamlwvdc36 wrote:
4. How do you calibrate these probes with 2 of them connected?

Go back to page 2 and read Johny5's posts. There can be an issue with noise causing bouncing, erroneous readings and general no workiness. I ran into this issue as well when both probes were in the vicinity of my water pump. I added a power isolation circuit to each stamp and it cleared the problem completely.
https://www.atlas-scientific.com/product_pages/circuits/pwr-iso.html?

adamlwvdc36 wrote:
I have asked the Arduino community some of these questions, but they don't understand the aquaponics side of things AS WELL as the code... just the code.

Thanks in advance

(yes, I registered specifically for this however, I do look forward to throwing my .02 in from time to time)

If you have any more issues, let me know. If you post your code, I can take a look for you.


Top
 Profile  
Reply with quote  
PostPosted: Aug 28th, '15, 15:45 
Newbie
Newbie

Joined: Aug 26th, '15, 15:24
Posts: 23
Gender: Male
Are you human?: YES
Location: East Moline, IL USA
Thanks. I do have the newer version so for me to put it in on demand mode is "C,0\r". On my board sending temperature ("T,25\r" [for 25 degrees C]) just yields "*OK. I think that is why I had some syntax questions. It is funny you say power issues. Tonight, after wiring in an LCD, the thing started blinking and ticking like crazy. I unplug the LCD backlight, and it goes away. You would think that someone with a background in electronics engineering would remember the CURRENT LIMITING resistor. One 280 ohm and so far so good.

After I posted my questions I looked at your code again and noticed the calibration screens. I need to figure out how to incorporate something similar. Maybe some hidden buttons or something like that. I will post my code tomorrow sometime for you to check out. The system is 85 gallons a 55 and 29 gallon aquarium tied together with a water bridge. Basically, the system operates in 15 minute cycles. Fills the bed, detects draining of the beds bell siphon (through a simple transistor switching circuit) and If the flow is steady for a given time declares a flood circumstance and starts recirculating water. The original point of this was so the system, which is tied to my RO system in the house can self fill. This was... umm "buggy" and made my Mrs pretty upset. The system checks and corrects pH using buffers and dosing pumps as well.

I really do get a kick out of it and most of the time I'm at "nerd level 10" trying to figure out what to do next.


Top
 Profile  
Reply with quote  
PostPosted: Aug 29th, '15, 01:14 
Xtreme Contributor
Xtreme Contributor
User avatar

Joined: May 17th, '14, 07:21
Posts: 180
Gender: Male
Are you human?: YES
Location: Vancouver BC Canada
adamlwvdc36 wrote:
Thanks. I do have the newer version so for me to put it in on demand mode is "C,0\r". On my board sending temperature ("T,25\r" [for 25 degrees C]) just yields "*OK. I think that is why I had some syntax questions.

Yes the EZO version needs to disable continuous mode, set a temp compensation value and then read the value. Three steps.
Code:
C,0<CR>
T,19.5<CR>
R<CR>


adamlwvdc36 wrote:
It is funny you say power issues. Tonight, after wiring in an LCD, the thing started blinking and ticking like crazy. I unplug the LCD backlight, and it goes away. You would think that someone with a background in electronics engineering would remember the CURRENT LIMITING resistor. One 280 ohm and so far so good.

Some LCDs have them, some don't. The only way to tell is by reading the datasheet.

adamlwvdc36 wrote:
After I posted my questions I looked at your code again and noticed the calibration screens. I need to figure out how to incorporate something similar. Maybe some hidden buttons or something like that. I will post my code tomorrow sometime for you to check out. The system is 85 gallons a 55 and 29 gallon aquarium tied together with a water bridge. Basically, the system operates in 15 minute cycles. Fills the bed, detects draining of the beds bell siphon (through a simple transistor switching circuit) and If the flow is steady for a given time declares a flood circumstance and starts recirculating water. The original point of this was so the system, which is tied to my RO system in the house can self fill. This was... umm "buggy" and made my Mrs pretty upset. The system checks and corrects pH using buffers and dosing pumps as well.

I really do get a kick out of it and most of the time I'm at "nerd level 10" trying to figure out what to do next.

LOL. Nothing wrong with a little nerding.


Top
 Profile  
Reply with quote  
PostPosted: Aug 29th, '15, 10:34 
Newbie
Newbie

Joined: Aug 26th, '15, 15:24
Posts: 23
Gender: Male
Are you human?: YES
Location: East Moline, IL USA
I have uploaded a video to YouTube of my system. You can check it out if you want. [youtube]https://www.youtube.com/watch?v=RRdf3rdoOTY[/youtube]

I know my code is long (although not as long as yours) but if you could go through and see if there are any places I could save on either memory or process time and still have it function I would greatly appreciate it. I have the SD card disabled at this time since more detailed information is available on the server.


Code:
/*
Version 1.0.0 scraped for new design using recirc valve alone to control flow to growbed
Version 1.1.0 Started over with just code for pH probe. This code was directly from Atlas Scientific.
Version 1.1.1 Assigned float for pH.
version 1.2.0 Added logic for fill and drain functionality.
Version 1.2.1 Added "hasFlood" integer triggered by exceeding "floodThreshold".  This was needed because erronious readings from drainSensor caused this to be a one shot.. this ensures
              that we truly have a drain situation before recirculating.
Version 1.3.0 Added Water level probe function following lessons learned from the flood/drain situation.
Version 1.4.0 Added ability to correct pH during acid/baseOn and acid/baseOff window.  Adjust these windows to add more or less acid/base for correction.
Version 1.4.1 Added a temperature average function to smooth out temperature readings over a second.
Version 1.4.2 Added temperature compensation to pH circuit
Version 1.4.3 Removed response code from ph circuit (circuit was returning *OK everytime we sent it the new temperature. The arduino saw this as the current pH and base pump would run)
Version 1.4.4 Increased Drain threshold to be sure grow bed does drain ( 100 - 1000)
Version 1.5.0 Added SD capabilities.  Will add another version once I get it to initialize
Version 1.5.1 Completed SD capabilities logging an ID, timestamp (in millis), pH, and Temperature to LOG.CSV file
Version 1.6.0 Adding Web Server capabilities
Version 1.6.1 Added more values to be saved in csv file ID, Time In Cycle (mS), pH, Temperature, Acid Pump Status, Base Pump Status, Recirc Status, Drain Sensor, Fill Sensor, Fill Status
Version 2.0.0 Removed self filling capabilities.  This enables me to allow for faster pH corrections since the system naturally buffers itself after 5 minutes and it takes 3 minutes for
              dosing to become apparent in THIS system.  Changing total cycle to 5 minutes to allow dosing to mix within system and for pH to equalize.
Version 2.1.0 Changed main cycle time to 10 minutes since new growbed medium requires more flow time to fill.
Version 2.2.0 Added Real Time Clock capabilities (very buggy)
Version 2.2.1 Bug fixes for Real Time Clock capabilities.  Also added more expainations to most variables.  I was bored on an international flight to Hong Kong from Chicago
Version 3.0.0 Added online data storage capabilities through PHP and MySQL.  Website to follow.  This will allow for monitoring anywhere.
Version 3.0.1 Moved data logging to the loop and added a timer to upload data to MySQL only ever 5 seconds to reduce the amount of data sent to the server. Current settings will use approximately 600MB in a year.
               ***NOTE 8/3/2015 webpage for system is online and operating
Version 3.0.2 Using MYSQL current time and date variables to perform time and date stamps. This should take some load off of the Controller
Version 3.1.0 Noticed that after 12 hours or so the Data logging lags over the web.  Suspect memory capacity on Arduino is being used due to high amounts of data logging. Added an auto reset function to automatically
              reset the controller ever 21600000 milliseconds (6 hours)
Version 3.1.1 Changed reset value to 4 hours instead of 6
Version 3.2.0 Removed reset function. Decided to try only posting to php once instead of twice for each table.  Will modify the add.php on the webserver to query the database to both tables instead of a separate
              php file and multiple lines of code on Arduino.  This, hopefully, should eliminate the memory issues and conflicts.
Version 3.2.1 Removed the datastring to be written to the csv file.  Apparently, strings are really bad for RAM and the issue I am having is caused by running out of RAM. Also, Some of the small integers were changed to bytes
              for the same reason.
Version 3.6.0 Changed the method of sending data to MySQL from "GET" to "POST"
Version 3.6.1 Changed cycle back to 15 minutes due to new growbed being larger.  The system would barely begin to recirculate before the end of the cycle.

*/

// WHAT LIBRARIES ARE TO BE USED
#include <Math.h>
#include <Thermistor.h>
#include <SPI.h>
#include <Ethernet.h>
#include <Time.h>
#include <EthernetUdp.h>
#include <SD.h>
#include <MemoryFree.h>
#include <LiquidCrystalFast.h>

// LETS SETUP SOME VALUES

// TIMING VARIABLES
const int timeZone = -5;                                     // WHAT TIME ZONE SET TO Central Daylight Time
unsigned long previousRate = 0;                              // ASSIGNS 0 TO PREVIOUS RATE ON INITIALIZATION
unsigned long previousMillis = 0;                            // ASSIGNS 0 TO PREVIOUS TIMING ON INTIALIZATION
unsigned long previouspH = 0;                                // ASSIGNS 0 TO PREVIOUS PH TIMING ON INITIALIZATION
unsigned long previousLogRate = 0;                           // ASSIGNS 0 TO PREVIOUS LOG RATE ON INITIALIZATION
const long acidOn = 436000;                                  // MILLISECONDS IN CYCLE TO ENABLE ACID DOSING (IF NEEDED)
const long acidOff = 440000;                                 // MILLISECONDS IN CYCLE TO DISABLE ACID DOSING (IF NEEDED)
const long acidOn2 = 882000;
const long acidOff2 = 890000;
const long baseOn = 870000;                                  // MILLISECONDS IN CYCLE TO ENABLE BASE DOSING (IF NEEDED)
const long baseOff = 890000;                                 // MILLISECONDS IN CYCLE TO DISABLE BASE DOSING (IF NEEDED)
const short rate = 1000;                                      // MILLISECONDS BETWEEN TEMPERATURE READINGS
const short logRate = 5000;                                   // MILLISECONDS BETWEEN WRITING NEW LINE TO CSV LOGFILE
//const long fillOn = 601000;                                // MILLISECONDS IN CYCLE TO ENABLE WATER FILLING (IF NEEDED)
//const long fillOff = 899999;                               // MILLISECONDS IN CYCLE TO DISABLE WATER FILLING (IF NEEDED)
const long recircOn = 301000;                                // MILLISECONDS IN CYCLE TO ENABLE WATER RECIRCULATION (IF NEEDED)
const long recircOff = 899999;                               // MILLISECONDS IN CYCLE TO DISABLE WATER RECIRCULATION (IF NEEDED)
const long cycle = 900000;                                   // MILLISECONDS TOTAL IN MAIN CYCLE
const long pHcycle = 900000;                                 // MILLISECONDS TOTAL IN PH CYCLE



//NETWORKING VARIABLES
byte mac[] = {  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};         // MAC ADDRESS OF ETHERNET CARD
byte ip[] = {192, 168, 2, 7};                               // IP ADDRESS OF ETHERNET CARD

byte gateway[] = {192, 168, 2, 1};
byte subnet[] = {255, 255, 255, 0};


// IPAddress timeServer(132, 163, 4, 101);                      // time-a.timefreq.bldrdoc.gov
 IPAddress timeServer(132, 163, 4, 102); // time-b.timefreq.bldrdoc.gov UNSWITCH COMMENT TO ACTIVATE THIS SERVER
// IPAddress timeServer(132, 163, 4, 103); // time-c.timefreq.bldrdoc.gov UNSWITCH COMMENT TO ACTIVATE THIS SERVER
EthernetUDP Udp;
unsigned int localPort = 8888;                               // local port to listen for UDP packets
EthernetClient client;
char server[] = "myserver";                            // IP Address (or name) of server to dump data to

// I/O VARIABLES
byte acidPump = 44;                                 // ACID PUMP OUTPUT ON PIN 44
byte basePump = 42;                                     // BASE PUMP OUTPUT ON PIN 44
byte recirc = 48;                                       // RECIRC VALVE OUTPUT ON PIN 48
byte drainSensor = 49;                                  // DRAIN SENSOR INPUT ON PIN 49
byte levelSensor = 47;                                  // LEVEL SENSOR INPUT ON PIN 47
byte fillValve = 46;                                    // FILL VALVE OUTPUT ON


Thermistor temp(A0);                                          // WATER TEMP THERMISTOR IS CONNECTED TO ANALOG PIN 0
Thermistor gtemp(A1);                                         // GROW BED THERMISTOR IS CONNECTED TO ANALOG PIN 1
byte SD_pin = 4;                                            // SLAVE SELECTOR FOR SD CARD IS PIN 10
byte EN_pin = 10;                                            // SLAVE SELECTOR FOR W5100

// MATH VARIABLES
byte acidStatus = 0;                                         // IS ACID PUMP RUNNING
byte baseStatus = 0;                                         // IS BASE PUMP RUNNING
byte recircStatus = 0;                                       // IS WATER RECIRCULATING
byte hasFlood = 0;                                           // HAS THERE BEEN A GROWBED FLOOD WITHIN THE MAIN CYCLE
byte drainSensorVal = 0;                                     // ACCUMULATION OF DRAIN VALUES WORKS WITH floodThreshold
byte levelSensorVal = 0;
byte recircEnable = 0;                                       // ARE WE WITHIN THE RECIRCULATION WINDOW OF TIME
long waterDetect = 0;                                        // DEFAULT VALUE FOR DRAIN DETECTION
const long floodThreshold = 10000;                                  // HOW HIGH DOES drainSensorVal HAVE TO BE TO DECLARE FLOOD IN GROWBED
int temperature = 0;                                        // PLACE HOLDER FOR TEMPERATURE READINGS (IN *C)
int tempReadings = 0;                                       // ACCUMULATION OF NUMBER OF TEMPERATURE READINGS IN "RATE"
float tempTotal = 0;                                        // MATHEMATICAL ACCUMULATION OF TEMERATURE READINGS
float tempAverage = 0;                                      // DIVIDEND OF tempTotal/tempReadings
float gtemperature = 0;                                     // PLACE HOLDER FOR GROW BED TEMPERATURE READINGS (IN *C)
int gtempReadings = 0;                                      // ACCUMULATION OF NUMBER OF TEMPERATURE READINGS IN "RATE" FOR GROW BED
float gtempTotal = 0;                                       // MATHEMATICAL ACCUMULATION OF GROW BED TEMPERATURE READINGS
float gtempAverage = 0;                                     // DIVIDEND OF gtempTotal/gtempReadings

char phchar[6];
char phphp[6] = "00.00";
char tempchar[6];
char gtempchar[6];
char dochar[6] = "0.00";

char astatus[4] = "";                                        // STRING FOR RUN/OFF FOR ACID PUMP FOR MYSQL
char bstatus[4] = "";                                        // STRING FOR RUN/OFF FOR BASE PUMP FOR MYSQL
char rstatus[7] = "";                                        // STRING FOR RECIRC/FLOOD FOR RECIRC FOR MYSQL
char dsensor[6] = "";                                        // STRING FOR WATER/DRY FOR DRAIN FOR MYSQL
char fsensor[6] = "Water";                                        // STRING FOR WATER/DRY FOR FILL FOR MYSQL
char fstatus[5] = "Off";                                        // STRING FOR FILL/OFF FOR DRAIN FOR MYSQL
char rstatuslcd[21] = "";
char timelcd[9] = "";
char datelcd[11] = "";
char params[200] = "";
int inChar;


float ph;                                                   // CREATION OF A PH FLOAT
float dissolved = 0;                                        // CRATEION OF A DISSOLVED OXYGEN FLOAT***this circuit does not exist...YET!****
byte acidEnable = 0;                                         // ARE WE WITHIN THE ACID DOSING WINDOW OF TIME
byte baseEnable = 0;                                         // ARE WE WITHIN THE BASE DOSING WINDOW OF TIME
short waterDetectFail = 0;                                    // IF YOU HAD WATER DETECTION BUT IT DIDNT LAST LONG ENOUGH TO SATISFY floodThreshold THIS BECOMES 1
float acidThreshold = 7.10;                                 // KEEP PH BELOW THIS VALUE BUT...
float baseThreshold = 6.70;                                 // ABOVE THIS VALUE

//ATLAS SCIENTIFIC VALUES
char stamp_data_ph[30]; // char array for serial data from ph stamp
byte holding; // counter for serial chars
byte i; //for loop counter



time_t prevDisplay = 0;                                      // when the digital clock was displayed
const int numRows = 4;
const int numCols = 20;

// initialize the library with the numbers of the interface pins
LiquidCrystalFast lcd(33, 35, 31, 29, 27, 25, 23);


void setup() {                                             // set up the hardware

  // OUTPUTS
  pinMode(acidPump, OUTPUT);
  pinMode(basePump, OUTPUT);
  pinMode(recirc, OUTPUT);
  //INPUTS
  pinMode(drainSensor, INPUT);
  pinMode(levelSensor, INPUT);


  Serial.begin(38400);                                                       //set baud rate for the hardware serial port_0 to 9600
  Serial3.begin(38400);                                                      //set baud rate for software serial port_3 to 9600
  lcd.begin(numCols, numRows);
  lcd.setCursor(0, 0);
  lcd.write("Initializing Card");

  Serial.println("Initializing Card");
  lcd.setCursor(0, 1);
  lcd.write("MCM-APC-55 VER-4.0.0");

  Serial.println("McMillin Aquaponics version 4.0.0");
  //disable w5100 SPI while setting up SD
  pinMode(SD_pin, OUTPUT);
  pinMode(EN_pin, OUTPUT);

    // set up SD
/*    if (!SD.begin(SD_pin)) {
      lcd.setCursor(0, 2);
      lcd.write("CARD INT FAILED");
      Serial.println("Card Initialization Failed");
    }
    else {
      lcd.setCursor(0, 2);
      lcd.write("CARD INT SUCCESS!");
      Serial.println("Card Initialization Successful!");
    }
  */

  // set up w5100


  //Ethernet.begin(mac, ip, subnet);

  if (Ethernet.begin(mac) == 0) {
    // no point in carrying on, so do nothing forevermore:
    while (1) {
      Serial.println("Failed to configure Ethernet using DHCP");
      delay(10000);
    }
  }

  Serial.print("IP number assigned by DHCP is ");
  Serial.println(Ethernet.localIP());


  Udp.begin(localPort);
  // Serial.println("waiting for sync");
  setSyncProvider(getNtpTime);

  //////////////////////////////////////////////
  // takes a second for the w5100 to get ready
  delay(1000);
  lcd.clear();


  lcd.setCursor(6, 1);
  lcd.write("SETUP DONE");

  Serial.println("Setup done");
  Serial3.println("RESPONSE,0\r");
  delay(100);
  Serial3.print("C,0"); // Set the pH board to single reading mode
  Serial3.print("\r"); // Send carriage return to end command
  delay(100);
 
  /*
  //Write Log.csv File Header

  digitalWrite(SD_pin, LOW);                                                     // Select SD card
  digitalWrite(EN_pin, HIGH);                                                    // Deselect Ethernet

  File logFile = SD.open("LOG.csv", FILE_WRITE);
  if (logFile)
  {
    logFile.println(", , ,"); //Just a leading blank line, incase there was previous data
    logFile.println("Date, Time, pH, Temperature, Acid Pump Status, Base Pump Status, Recirc Status, Drain Sensor, Fill Sensor, Fill Status");
    logFile.close();
  }
  else
  {
    Serial.println("Couldn't open log file");
    return;
  }
  digitalWrite(SD_pin, HIGH);                                                     // Deselect SD card
  digitalWrite(EN_pin, HIGH);                                                    // Deselect Ethernet
  */
  delay(1000);
  lcd.clear();
  time_t prevDisplay = 0; // when the digital clock was displayed
}

void loop() {                                                                  //here we go....
  unsigned long currentRate = millis();
  unsigned long currentMillis = millis();
  unsigned long currentpH = millis();
  unsigned long currentLogRate = millis();
  drainSensorVal = digitalRead(drainSensor);
/*
  if (timeStatus() != timeNotSet) {
    if (now() != prevDisplay) { //update the display only if time has changed
      prevDisplay = now();
    }
  }
*/
  sprintf(datelcd, "%02d-%02d-%4d", month(), day(), year());
  sprintf(timelcd, "%02d:%02d:%02d", hour(), minute(), second());

 



  ////////////////////////GROW BED DRAIN DETECTION AND RECIRCULATION////////////////////////////////////////
  if (drainSensorVal == 0) {
    sprintf(dsensor, "Water");
  }
  else {
    sprintf(dsensor, "Dry");
  }
  if (drainSensorVal == 0 && hasFlood < 1) {
    waterDetect++;
    waterDetectFail = 0;
  }
  else {
    waterDetectFail++;
  }
  if (waterDetectFail >= 10) {
    waterDetect = 0;
  }
  if (waterDetect > floodThreshold) {
    hasFlood = 1;
  }
  if ((currentMillis - previousMillis) >= recircOn && (currentMillis - previousMillis) <= recircOff) {
      recircEnable = 1;
  }
  else {
    recircEnable = 0;
  }
  if (hasFlood == 1 || recircEnable == 1) {
    digitalWrite(recirc, HIGH);
    recircStatus = 1;
    sprintf(rstatus, "Recirc");
    sprintf(rstatuslcd, "    RECIRCULATING   ");
    //Serial.println("Recirculating");
  }
  else {
    digitalWrite(recirc, LOW);
    recircStatus = 0;
    sprintf(rstatus, "Flood");
    sprintf(rstatuslcd, "      FLOODING      ");
    //Serial.println("Filling Grow Bed");
  }
  //////////////////////////////////////////////////////////////////////////////////////////////////////////


  ///////////////////////////////WRITE TO SD CARD AND WEBSERVER/////////////////////////////////////////////
  if ((currentLogRate - previousLogRate) >= logRate) {
  //  writesd();
    sendDataToPHP();                                          // Call function to send data to webserver
    previousLogRate = currentLogRate;
  }
  //////////////////////////////////////////////////////////////////////////////////////////////////////////

  ////////////////////////////////////////PH CORRECTION/////////////////////////////////////////////////////
  if (((currentpH - previouspH) >= acidOn && (currentpH - previouspH) <= acidOff) || ((currentpH - previouspH) >= acidOn2 && (currentpH - previouspH) <= acidOff2)) {
    acidEnable = 1;
  }
  else {
    acidEnable = 0;
  }

  if ((currentpH - previouspH) >= baseOn && (currentpH - previouspH) <= baseOff) {
    baseEnable = 1;
  }
  else {
    baseEnable = 0;
  }

  if (baseEnable == 1 && ph < baseThreshold) {
    digitalWrite(basePump, HIGH);
    baseStatus = 1;
    sprintf(bstatus, "Run");
  }

  else if (acidEnable == 1 && ph > acidThreshold) {
    digitalWrite(acidPump, HIGH);
    acidStatus = 1;
    sprintf(astatus, "Run");
  }


  else {
    digitalWrite(acidPump, LOW);
    digitalWrite(basePump, LOW);
    baseStatus = 0;
    acidStatus = 0;
    sprintf(astatus, "Off");
    sprintf(bstatus, "Off");
  }
  //////////////////////////////////////////////////////////////////////////////////////////////////////////

  ///////////////////////GATHER TEMPERATURE READINGS FOR AVERAGES///////////////////////////////////////////
  temperature = temp.getTemp();
  tempTotal = tempTotal + temperature;
  tempReadings = tempReadings + 1;

  gtemperature = gtemp.getTemp();
  gtempTotal = gtempTotal + gtemperature;
  gtempReadings = gtempReadings + 1;
  //////////////////////////////////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////CALCULATE TEMERATURE AVERAGES///////////////////////////////////////////
  if ((currentRate - previousRate) > rate) {
    tempAverage = 0;                                                     
    tempAverage = tempTotal / tempReadings;
    float rounded_up = ceilf(tempAverage * 100) / 100;
    float avgForpH = ((rounded_up-32)/1.8);
    gtempAverage = 0;
    gtempAverage = gtempTotal / gtempReadings;
    float grounded_up = ceilf(gtempAverage * 100) / 100;
    dtostrf(rounded_up, 5, 2, tempchar);
    dtostrf(grounded_up, 5, 2, gtempchar);
    //////////////////////////////////////////////////////////////////////////////////////////////////////////

    ///////////////////////SEND TEMP TO PH CIRCUIT AND GET PH/////////////////////////////////////////////////

    Serial3.print("T,");
    Serial3.print(avgForpH);
    Serial3.print("\r");
   // delay(100);
    Serial3.print("R");                                       // Send water temp for calibrated pH reading
    Serial3.print("\r");                                      // Send carriage return to end command
    holding = 0;                                              // Reset buffer counter
    if (Serial3.available() > 1) {                            // If there is serial data in buffer
      holding = Serial3.available();                          // Set buffer counter to number of bytes
      for (i = 0; i < holding; i++) {                         // For loop to read out number of bytes in buffer
        stamp_data_ph[i] = Serial3.read();                    // Read serial data to char array
      }
    }
    else {
      Serial.println(F("pH ERR "));                           // Error if no data rx in serial buffer
    }
    ph = atof(stamp_data_ph);                                 // Write pH data to ph float for math
    dtostrf(ph, 4, 2, phphp);
    dtostrf(ph, 5, 2, phchar);                                // Write pH data to phchar for LCD display

    //////////////////////////////////////////////////////////////////////////////////////////////////////////
    writeLCD();
    ////////////////////////////////RESET VARIABLES FOR TEMP//////////////////////////////////////////////////

    previousRate = currentRate;
    tempTotal = 0;
    tempReadings = 0;
    gtempTotal = 0;
    gtempReadings = 0;
  }
  //////////////////////////////////////////////////////////////////////////////////////////////////////////

  ////////////////////////////////RESET VARIABLES FOR MAIN CYCLE////////////////////////////////////////////
  if (currentMillis - previousMillis >= cycle) {
    previousMillis = currentMillis;
    hasFlood = 0;
    waterDetect = 0;
  }
}

void writeLCD() {
  lcd.setCursor(0, 0);
  lcd.write(datelcd);
  lcd.write("  ");
  lcd.setCursor(12, 0);
  lcd.write(timelcd);
  lcd.setCursor(0, 1);
  lcd.write(rstatuslcd);
  lcd.setCursor(0, 2);
  lcd.write("pH:");
  lcd.setCursor(3, 2);
  lcd.write(phchar);
  lcd.setCursor(8, 2);
  lcd.write("  DO:");
  lcd.setCursor(15, 2);
  lcd.write(dochar);
  lcd.setCursor(0, 3);
  lcd.write("W:");
  lcd.setCursor(2, 3);
  lcd.write(tempchar);
  lcd.write((char)223);
  lcd.write("F");
  lcd.setCursor(9, 3);
  lcd.write("  B:");
  lcd.setCursor(13, 3);
  lcd.write(gtempchar);
  lcd.write((char)223);
  lcd.write("F");
}
void sendDataToPHP() {
  if (client.connect(server, 80)) {
client.print( "GET /addrecent.php?");
client.print("date=CURDATE()");
client.print("&&");
client.print("time=CURTIME()");
client.print("&&");
client.print("ph=");
client.print( ph );
client.print("&&");
client.print("dissolved=");
client.print( dissolved );
client.print("&&");
client.print("wtemp=");
client.print( tempAverage );
client.print("&&");
client.print("gtemp=");
client.print( gtempAverage );
client.print("&&");
client.print("astatus=");
client.print( astatus );
client.print("&&");
client.print("bstatus=");
client.print( bstatus );
client.print("&&");
client.print("rstatus=");
client.print( rstatus );
client.print("&&");
client.print("dsensor=");
client.print( dsensor );
client.print("&&");
client.print("fsensor=");
client.print( fsensor );
client.print("&&");
client.print("fstatus=");
client.print( fstatus );
client.println( " HTTP/1.1");
client.println( "Host: myserver" );
client.println( "Content-Type: application/x-www-form-urlencoded" );
client.println();
client.println();

  }
  else
  {
    Serial.println(F("failed"));

  }
  int connectLoop = 0;
  while (client.connected())
  {
    while(client.available())
    {
      inChar = client.read();
      Serial.write(inChar);
      connectLoop = 0;
     
    }
    client.stop();

   
    connectLoop++;
    if (connectLoop > 10000)
    {
      Serial.println();
      Serial.println(F("Timeout"));
      client.stop();
    }
  }
}

void writesd() {

  digitalWrite(SD_pin, LOW);                                                     // Select SD card
  digitalWrite(EN_pin, HIGH);                                                    // Deselect Ethernet

  File logFile = SD.open("LOG.csv", FILE_WRITE);
  while (logFile)
  {
    logFile.print(month());
    logFile.print("/");
    logFile.print(day());
    logFile.print("/");
    logFile.print(year());
    logFile.print(", ");
    logFile.print(hour());
    logFile.print(":");
    logFile.print(minute());
    logFile.print(":");
    logFile.print(second());
    logFile.print(",");
    logFile.print(ph);
    logFile.print(",");
    logFile.print(tempAverage);
    logFile.print(",");
    logFile.print(acidStatus);
    logFile.print(",");
    logFile.print(baseStatus);
    logFile.print(",");
    logFile.print(recircStatus);
    logFile.print(",");
    logFile.print(drainSensorVal);
    logFile.print(",");
    logFile.print(levelSensorVal);
    logFile.print(",");
  }
  digitalWrite(SD_pin, HIGH);                                                    // Deselect SD card
  digitalWrite(EN_pin, HIGH);                                                    // Deselect Ethernet
}

void printDigits(int digits) {
  // utility for digital clock display: prints preceding colon and leading 0
  Serial.print(":");
  if (digits < 10)
    Serial.print('0');
  Serial.print(digits);
}





/*-------- NTP code ----------*/

const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets

time_t getNtpTime()
{
  while (Udp.parsePacket() > 0) ; // discard any previously received packets
  Serial.println("Transmit NTP Request");
  sendNTPpacket(timeServer);
  uint32_t beginWait = millis();
  while (millis() - beginWait < 3600000) {
    int size = Udp.parsePacket();
    if (size >= NTP_PACKET_SIZE) {
      Serial.println("Receive NTP Response");
      Udp.read(packetBuffer, NTP_PACKET_SIZE);  // read packet into the buffer
      unsigned long secsSince1900;
      // convert four bytes starting at location 40 to a long integer
      secsSince1900 =  (unsigned long)packetBuffer[40] << 24;
      secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
      secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
      secsSince1900 |= (unsigned long)packetBuffer[43];
      return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
    }
  }
  Serial.println("No NTP Response :-(");
  return 0; // return 0 if unable to get the time
}

// send an NTP request to the time server at the given address
void sendNTPpacket(IPAddress & address)
{
  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12]  = 49;
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;
  // all NTP fields have been given values, now
  // you can send a packet requesting a timestamp:
  Udp.beginPacket(address, 123); //NTP requests are to port 123
  Udp.write(packetBuffer, NTP_PACKET_SIZE);
  Udp.endPacket();
}


Top
 Profile  
Reply with quote  
PostPosted: Aug 30th, '15, 00:12 

Joined: May 28th, '15, 04:28
Posts: 4
Gender: Male
Are you human?: Yup
Location: Serbia, Belgrade suburbs
Amazing job man! Respect!


Top
 Profile  
Reply with quote  
PostPosted: Aug 31st, '15, 09:19 
Xtreme Contributor
Xtreme Contributor
User avatar

Joined: May 17th, '14, 07:21
Posts: 180
Gender: Male
Are you human?: YES
Location: Vancouver BC Canada
Nice job on the project. I love it! I really have to put together a video of my complete project. Just a few more details to clean up.

The easiest way to save SRAM is to use the F modifier on your static strings.
In the "sendDataToPHP" function you can convert all the client.prints for non-varibles to this format.
client.print(F("ph="));

In the "getNtpTime" function all the serial.prints can change too.
Serial.println(F("Transmit NTP Request"));

All your logFile.print and lcd.write non-varible strings can change too I believe, though those don't make up a lot of memory.

The F modifier loads the string into EEPROM instead of SRAM. You'll probably save 1K doing this change.

Saving process time is a little more involved. I will look into the code in more detail when I get a chance.


Top
 Profile  
Reply with quote  
PostPosted: Aug 31st, '15, 09:32 
Newbie
Newbie

Joined: Aug 26th, '15, 15:24
Posts: 23
Gender: Male
Are you human?: YES
Location: East Moline, IL USA
Thanks for the compliments. My understanding on the F macro is that that data cannot be changed. So where I actually client.print a variable I can't f macro those. Or, am I mistaken on this?
[edit] I read your post again. Noticed the non variable part this time [/edit]

To temporarily "fix" my program hanging up I have implemented the watchdog function....which has an issue with the bootloader on the Mega. So, now I have to buy an Uno to use as an ISP to burn the bootloader.. and my kids want me to make a game for them so I need another Mega. This folks, is how addiction begins. 2 of my top 3 passions: electronics and 'ponics.... if only I could incorporate music.


Top
 Profile  
Reply with quote  
PostPosted: Aug 31st, '15, 12:10 
Xtreme Contributor
Xtreme Contributor
User avatar

Joined: May 17th, '14, 07:21
Posts: 180
Gender: Male
Are you human?: YES
Location: Vancouver BC Canada
Don't fall into the trap of relying on the watchdog. If the code is correct and the power is perfect, the arduino will not hang up...ever. Find the fault in the code and you will have a much more stable system.


Top
 Profile  
Reply with quote  
PostPosted: Aug 31st, '15, 14:21 
Newbie
Newbie

Joined: Aug 26th, '15, 15:24
Posts: 23
Gender: Male
Are you human?: YES
Location: East Moline, IL USA
Really wish I could. I've been trying this and that for the past month and still don't have an answer. I leave away from home on this Saturday and was hoping to have this resolved by then.


Top
 Profile  
Reply with quote  
PostPosted: Aug 31st, '15, 15:50 
Newbie
Newbie

Joined: Aug 26th, '15, 15:24
Posts: 23
Gender: Male
Are you human?: YES
Location: East Moline, IL USA
Ok, you were so right. I went through adding the F macro to my lines. Saved 750 bytes that way. Then, I went through and added alarms incase the pH gets too high or too low, temperature in the water same thing. and one more in case it takes to long to flood the grow bed, alarming me to check the pump.

You can check out the site http://218.m3allc.com/ if you want. There will be a flooding taking too long alarms since I have my recirc forced right now... (dont ask) :evil:

Still, waiting to see if this thing makes it through the night. Maybe it will now. Thanks for the help so far.


Top
 Profile  
Reply with quote  
PostPosted: Sep 1st, '15, 01:06 
Xtreme Contributor
Xtreme Contributor
User avatar

Joined: May 17th, '14, 07:21
Posts: 180
Gender: Male
Are you human?: YES
Location: Vancouver BC Canada
I would concentrate on the sendDataToPHP() function. Something doesn't look right there.

I don't see where client.stop() is called if connectLoop > 10000 is not true. Your socket will remain open always which is bad because the host can decide to close it and the arduino would never know. It sits there trying to send data and never gets a response back and never makes it out of the while(client.connected() loop.

I kinda get how your using connectLoop as a timeout reset loop but something is not quite right with the flow.

Your missing a "client.println(F("Connection: close"));" after the Host string is sent. This instructs the host to close the socket when you are done sending data. It's always better to open and close the socket so you know exactly the state of the connection and nothing is left for the host to decide.

This article is a great resource for learning about Arduino memory.
https://learn.adafruit.com/memories-of- ... ot-dot-dot


Top
 Profile  
Reply with quote  
PostPosted: Sep 2nd, '15, 01:53 
Newbie
Newbie

Joined: Aug 26th, '15, 15:24
Posts: 23
Gender: Male
Are you human?: YES
Location: East Moline, IL USA
Chiu,

I have DRASTICALLY modified the code. This has reduced the Dynamic memory usage from 31% to only 12%, yes, my sketch uses less than 1 Kb! This excites me. However, the LCD is still shows jumbled data once in a while, and the sketch will still hang. I ran blink on the mega overnight to ensure it wasnt the mega itself. I post this here becuase the people in the forum understand the negative ramifications of... say... an acid dosing pump failing in the "dose" situation, overnight (learned that the hard way, RIP fish. New fish now and a new found respect for the responsibility of this system (I put the pumps in "off" overnight and when I am away now).

Code:
/*
Version 1.0.0 scraped for new design using recirc valve alone to control flow to growbed
Version 1.1.0 Started over with just code for pH probe. This code was directly from Atlas Scientific.
Version 1.1.1 Assigned float for pH.
version 1.2.0 Added logic for fill and drain functionality.
Version 1.2.1 Added "hasFlood" integer triggered by exceeding "floodThreshold".  This was needed because erronious readings from drainSensor caused this to be a one shot.. this ensures
              that we truly have a drain situation before recirculating.
Version 1.3.0 Added Water level probe function following lessons learned from the flood/drain situation.
Version 1.4.0 Added ability to correct pH during acid/baseOn and acid/baseOff window.  Adjust these windows to add more or less acid/base for correction.
Version 1.4.1 Added a temperature average function to smooth out temperature readings over a second.
Version 1.4.2 Added temperature compensation to pH circuit
Version 1.4.3 Removed response code from ph circuit (circuit was returning *OK everytime we sent it the new temperature. The arduino saw this as the current pH and base pump would run)
Version 1.4.4 Increased Drain threshold to be sure grow bed does drain ( 100 - 1000)
Version 1.5.0 Added SD capabilities.  Will add another version once I get it to initialize
Version 1.5.1 Completed SD capabilities logging an ID, timestamp (in millis), pH, and Temperature to LOG.CSV file
Version 1.6.0 Adding Web Server capabilities
Version 1.6.1 Added more values to be saved in csv file ID, Time In Cycle (mS), pH, Temperature, Acid Pump Status, Base Pump Status, Recirc Status, Drain Sensor, Fill Sensor, Fill Status
Version 2.0.0 Removed self filling capabilities.  This enables me to allow for faster pH corrections since the system naturally buffers itself after 5 minutes and it takes 3 minutes for
              dosing to become apparent in THIS system.  Changing total cycle to 5 minutes to allow dosing to mix within system and for pH to equalize.
Version 2.1.0 Changed main cycle time to 10 minutes since new growbed medium requires more flow time to fill.
Version 2.2.0 Added Real Time Clock capabilities (very buggy)
Version 2.2.1 Bug fixes for Real Time Clock capabilities.  Also added more expainations to most variables.  I was bored on an international flight to Hong Kong from Chicago
Version 3.0.0 Added online data storage capabilities through PHP and MySQL.  Website to follow.  This will allow for monitoring anywhere.
Version 3.0.1 Moved data logging to the loop and added a timer to upload data to MySQL only ever 5 seconds to reduce the amount of data sent to the server. Current settings will use approximately 600MB in a year.
               ***NOTE 8/3/2015 webpage for system is online and operating
Version 3.0.2 Using MYSQL current time and date variables to perform time and date stamps. This should take some load off of the Controller
Version 3.1.0 Noticed that after 12 hours or so the Data logging lags over the web.  Suspect memory capacity on Arduino is being used due to high amounts of data logging. Added an auto reset function to automatically
              reset the controller ever 21600000 milliseconds (6 hours)
Version 3.1.1 Changed reset value to 4 hours instead of 6
Version 3.2.0 Removed reset function. Decided to try only posting to php once instead of twice for each table.  Will modify the add.php on the webserver to query the database to both tables instead of a separate
              php file and multiple lines of code on Arduino.  This, hopefully, should eliminate the memory issues and conflicts.
Version 3.2.1 Removed the datastring to be written to the csv file.  Apparently, strings are really bad for RAM and the issue I am having is caused by running out of RAM. Also, Some of the small integers were changed to bytes
              for the same reason.
Version 3.6.0 Changed the method of sending data to MySQL from "GET" to "POST"
Version 3.6.1 Changed cycle back to 15 minutes due to new growbed being larger.  The system would barely begin to recirculate before the end of the cycle.
Version 4.0.0 Added LCD
Version 4.0.1 Worked on variables trying to make changes to these variables faster and requiring less memory
Version 4.0.2 Disabled SD card.  Depending on testing, this may be permanent.
Version 4.0.3 Added Arduino Watchdog function to detect if arduino has hung up.  It will then reset the system.
Version 4.1.0 Add alarms to be published to the web site
Version 4.1.1 Were not going to talk about this one.  I added a BUNCH of Serial.print commands trying to diagnose "the problem"
Version 4.1.2 Added Close connection messages to the webserver
Version 4.1.2 Removed the php client timeout function.
Version 4.2.0 Moved a bunch of the display logic to the webserver.  this enables me to just send a 1 or 0 to the server instead of a whole string of data. For Example, one alarm
              required a HTTP GET sting over 180 characters long, now this is done with only 6 :)
*/

// WHAT LIBRARIES ARE TO BE USED
#include <Math.h>
#include <Thermistor.h>
#include <SPI.h>
#include <Ethernet.h>
#include <Time.h>
#include <EthernetUdp.h>
//#include <SD.h>
#include <MemoryFree.h>
#include <LiquidCrystalFast.h>

// LETS SETUP SOME VALUES
// ALARM VARIABLES
float phUpper = 8.00;
float phLower = 6.30;
float tempUpper = 86.00;
float tempLower = 70.00;
byte phHighAlarm = 0;
byte phLowAlarm = 0;
byte tempHighAlarm = 0;
byte tempLowAlarm = 0;
byte floodAlarm = 0;
// TIMING VARIABLES

const long timeZoneOffset = -18000L;                         // WHAT TIME ZONE SET TO Central Daylight Time
unsigned int ntpSyncTime = 7200;
unsigned long previousRate = 0;                              // ASSIGNS 0 TO PREVIOUS RATE ON INITIALIZATION
unsigned long previousMillis = 0;                            // ASSIGNS 0 TO PREVIOUS TIMING ON INTIALIZATION
unsigned long previouspH = 0;                                // ASSIGNS 0 TO PREVIOUS PH TIMING ON INITIALIZATION
unsigned long previousLogRate = 0;                           // ASSIGNS 0 TO PREVIOUS LOG RATE ON INITIALIZATION
const long acidOn = 436000;                                  // MILLISECONDS IN CYCLE TO ENABLE ACID DOSING (IF NEEDED)
const long acidOff = 440000;                                 // MILLISECONDS IN CYCLE TO DISABLE ACID DOSING (IF NEEDED)
const long acidOn2 = 882000;
const long acidOff2 = 890000;
const long baseOn = 870000;                                  // MILLISECONDS IN CYCLE TO ENABLE BASE DOSING (IF NEEDED)
const long baseOff = 890000;                                 // MILLISECONDS IN CYCLE TO DISABLE BASE DOSING (IF NEEDED)
const short rate = 1000;                                      // MILLISECONDS BETWEEN TEMPERATURE READINGS
const short logRate = 5000;                                   // MILLISECONDS BETWEEN WRITING NEW LINE TO CSV LOGFILE
//const long fillOn = 601000;                                // MILLISECONDS IN CYCLE TO ENABLE WATER FILLING (IF NEEDED)
//const long fillOff = 899999;                               // MILLISECONDS IN CYCLE TO DISABLE WATER FILLING (IF NEEDED)
const long recircOn = 601000;                                // MILLISECONDS IN CYCLE TO ENABLE WATER RECIRCULATION (IF NEEDED)
const long recircOff = 899999;                               // MILLISECONDS IN CYCLE TO DISABLE WATER RECIRCULATION (IF NEEDED)
const long cycle = 900000;                                   // MILLISECONDS TOTAL IN MAIN CYCLE
const long pHcycle = 900000;                                 // MILLISECONDS TOTAL IN PH CYCLE



//NETWORKING VARIABLES
byte mac[] = {  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};         // MAC ADDRESS OF ETHERNET CARD
byte ip[] = {192, 168, 2, 7};                               // IP ADDRESS OF ETHERNET CARD

byte gateway[] = {192, 168, 2, 1};
byte subnet[] = {255, 255, 255, 0};


// IPAddress timeServer(132, 163, 4, 101);                      // time-a.timefreq.bldrdoc.gov
IPAddress timeServer(132, 163, 4, 102); // time-b.timefreq.bldrdoc.gov UNSWITCH COMMENT TO ACTIVATE THIS SERVER
// IPAddress timeServer(132, 163, 4, 103); // time-c.timefreq.bldrdoc.gov UNSWITCH COMMENT TO ACTIVATE THIS SERVER

unsigned int localPort = 8888;                               // local port to listen for UDP packets
const int NTP_PACKET_SIZE = 48;
byte packetBuffer[NTP_PACKET_SIZE];
EthernetUDP Udp;
unsigned long ntpLastUpdate = 0;
time_t prevDisplay = 0;                                      // when the digital clock was displayed
EthernetClient client;
char server[] = "myhost";                            // IP Address (or name) of server to dump data to

// I/O VARIABLES
byte acidPump = 44;                                     // ACID PUMP OUTPUT ON PIN 44
byte basePump = 42;                                     // BASE PUMP OUTPUT ON PIN 44
byte recirc = 46;                                       // RECIRC VALVE OUTPUT ON PIN 48

Thermistor temp(A0);                                          // WATER TEMP THERMISTOR IS CONNECTED TO ANALOG PIN 0
Thermistor gtemp(A1);                                         // GROW BED THERMISTOR IS CONNECTED TO ANALOG PIN 1
byte SD_pin = 4;                                            // SLAVE SELECTOR FOR SD CARD IS PIN 10
byte EN_pin = 10;                                            // SLAVE SELECTOR FOR W5100

// MATH VARIABLES
byte hasFlood = 0;                                           // HAS THERE BEEN A GROWBED FLOOD WITHIN THE MAIN CYCLE
//byte drainSensorVal = 0;                                     // ACCUMULATION OF DRAIN VALUES WORKS WITH floodThreshold
//byte levelSensorVal = 0;
byte recircEnable = 0;                                       // ARE WE WITHIN THE RECIRCULATION WINDOW OF TIME
//long waterDetect = 0;                                        // DEFAULT VALUE FOR DRAIN DETECTION
//const long floodThreshold = 20000;                                  // HOW HIGH DOES drainSensorVal HAVE TO BE TO DECLARE FLOOD IN GROWBED
float temperature = 0;                                        // PLACE HOLDER FOR TEMPERATURE READINGS (IN *C)
int tempReadings = 0;                                       // ACCUMULATION OF NUMBER OF TEMPERATURE READINGS IN "RATE"
float tempTotal = 0;                                        // MATHEMATICAL ACCUMULATION OF TEMERATURE READINGS
float tempAverage = 0;                                      // DIVIDEND OF tempTotal/tempReadings
float gtemperature = 0;                                     // PLACE HOLDER FOR GROW BED TEMPERATURE READINGS (IN *C)
int gtempReadings = 0;                                      // ACCUMULATION OF NUMBER OF TEMPERATURE READINGS IN "RATE" FOR GROW BED
float gtempTotal = 0;                                       // MATHEMATICAL ACCUMULATION OF GROW BED TEMPERATURE READINGS
float gtempAverage = 0;                                     // DIVIDEND OF gtempTotal/gtempReadings
float rounded_up;



byte astatus = 0;                                        // STRING FOR RUN/OFF FOR ACID PUMP FOR MYSQL
byte bstatus = 0;                                        // STRING FOR RUN/OFF FOR BASE PUMP FOR MYSQL
byte rstatus = 0;                                        // STRING FOR RECIRC/FLOOD FOR RECIRC FOR MYSQL
byte dsensor = 0;                                        // STRING FOR WATER/DRY FOR DRAIN FOR MYSQL
byte fsensor = 0;                                        // STRING FOR WATER/DRY FOR FILL FOR MYSQL
byte fstatus = 0;                                        // STRING FOR FILL/OFF FOR DRAIN FOR MYSQL
byte plow = 0;
byte phigh = 0;
byte wlow = 0;
byte whigh = 0;
byte flood = 0;
int inChar;


float ph;                                                   // CREATION OF A PH FLOAT
float dissolved = 0.00;                                        // CRATEION OF A DISSOLVED OXYGEN FLOAT***this circuit does not exist...YET!****
byte acidEnable = 0;                                         // ARE WE WITHIN THE ACID DOSING WINDOW OF TIME
byte baseEnable = 0;                                         // ARE WE WITHIN THE BASE DOSING WINDOW OF TIME
//short waterDetectFail = 0;                                    // IF YOU HAD WATER DETECTION BUT IT DIDNT LAST LONG ENOUGH TO SATISFY floodThreshold THIS BECOMES 1
float acidThreshold = 7.10;                                 // KEEP PH BELOW THIS VALUE BUT...
float baseThreshold = 6.70;                                 // ABOVE THIS VALUE

//ATLAS SCIENTIFIC VALUES
char stamp_data_ph[30]; // char array for serial data from ph stamp
byte holding; // counter for serial chars
byte i; //for loop counter




const int numRows = 4;
const int numCols = 20;

// initialize the library with the numbers of the interface pins
LiquidCrystalFast lcd(33, 35, 31, 29, 27, 25, 23);


void setup() {                                             // set up the hardware

  // OUTPUTS

  pinMode(acidPump, OUTPUT);
  pinMode(basePump, OUTPUT);
  pinMode(recirc, OUTPUT);
  //INPUTS


  Serial.begin(38400);                                                       //set baud rate for the hardware serial port_0 to 9600
  Serial3.begin(38400);                                                      //set baud rate for software serial port_3 to 9600
  lcd.begin(numCols, numRows);
  lcd.setCursor(0, 0);
  lcd.print(F("Initializing System"));

  Serial.println(F("Initializing Card"));
  lcd.setCursor(0, 1);
  lcd.print(F("MCM-APC-55 VER-4.2.0"));

  Serial.println(F("McMillin Aquaponics version 4.2.0"));
  //disable w5100 SPI while setting up SD
  pinMode(SD_pin, OUTPUT);
  pinMode(EN_pin, OUTPUT);

  // set up SD
  /*    if (!SD.begin(SD_pin)) {
        lcd.setCursor(0, 2);
        lcd.write("CARD INT FAILED");
        Serial.println("Card Initialization Failed");
      }
      else {
        lcd.setCursor(0, 2);
        lcd.write("CARD INT SUCCESS!");
        Serial.println("Card Initialization Successful!");
      }
    */

  // set up w5100


  //Ethernet.begin(mac, ip, subnet);
  int i = 0;
  int DHCP = 0;
  DHCP = Ethernet.begin(mac);
  while (DHCP == 0 && i < 30) {
    delay(1000);
    DHCP = Ethernet.begin(mac);
    i++;
  }
  if (!DHCP) {
    Serial.println(F("DHCP FAILED"));
    for (;;);
  }
  Serial.print(F("IP number assigned by DHCP is "));
  Serial.println(Ethernet.localIP());

  int trys = 0;
  while (!getTimeAndDate() && trys < 10) {
    trys++;
  }

  //////////////////////////////////////////////
  // takes a second for the w5100 to get ready
  delay(1000);
  lcd.clear();


  lcd.setCursor(6, 1);
  lcd.print(F("SETUP DONE"));

  Serial.println(F("Setup done"));
  Serial3.println(F("RESPONSE,0\r"));
  delay(100);
  Serial3.print(F("C,0")); // Set the pH board to single reading mode
  Serial3.print(F("\r")); // Send carriage return to end command
  delay(100);

  /*
  //Write Log.csv File Header

  digitalWrite(SD_pin, LOW);                                                     // Select SD card
  digitalWrite(EN_pin, HIGH);                                                    // Deselect Ethernet

  File logFile = SD.open("LOG.csv", FILE_WRITE);
  if (logFile)
  {
    logFile.println(", , ,"); //Just a leading blank line, incase there was previous data
    logFile.println("Date, Time, pH, Temperature, Acid Pump Status, Base Pump Status, Recirc Status, Drain Sensor, Fill Sensor, Fill Status");
    logFile.close();
  }
  else
  {
    Serial.println("Couldn't open log file");
    return;
  }
  digitalWrite(SD_pin, HIGH);                                                     // Deselect SD card
  digitalWrite(EN_pin, HIGH);                                                    // Deselect Ethernet
  */
  delay(1000);
  lcd.clear();
  time_t prevDisplay = 0; // when the digital clock was displayed


}

void loop() {                        //here we go....

  Ethernet.maintain();


  // Update the time via NTP server as often as the time you set at the top
  if (now() - ntpLastUpdate > ntpSyncTime) {
    int trys = 0;
    while (!getTimeAndDate() && trys < 10) {
      trys++;
    }
    if (trys < 10) {
      Serial.println(F("ntp server update success"));
    }
    else {
      Serial.println(F("ntp server update failed"));
    }
  }


  int floodReading = analogRead(A2);
  unsigned long currentRate = millis();
  unsigned long currentMillis = millis();
  unsigned long currentpH = millis();
  unsigned long currentLogRate = millis();
  //  drainSensorVal = digitalRead(drainSensor);
  /*
    if (timeStatus() != timeNotSet) {
      if (now() != prevDisplay) { //update the display only if time has changed
        prevDisplay = now();
      }
    }
  */





  ////////////////////////GROW BED DRAIN DETECTION AND RECIRCULATION////////////////////////////////////////
  if (floodReading < 250) {
    dsensor = 0;
  }
  else {
    dsensor = 1;
  }
  if (floodReading <= 25) {
    hasFlood = 1;
  }
  /*if (drainSensorVal == 0 && hasFlood < 1) {
    waterDetect++;
    waterDetectFail = 0;
  }
  else {
    waterDetectFail++;
  }
  if (waterDetectFail >= 10) {
    waterDetect = 0;
  }
  if (waterDetect > floodThreshold) {
    hasFlood = 1;
  }
  */
  if ((currentMillis - previousMillis) >= recircOn && (currentMillis - previousMillis) <= recircOff) {
    recircEnable = 1;
  }
  else {
    recircEnable = 0;
  }

  if (hasFlood == 1 || recircEnable == 1) {
    digitalWrite(recirc, HIGH);
    rstatus = 1;
    lcd.setCursor(0, 1);
    lcd.print(F("    RECIRCULATING   "));
  }
  else {
    digitalWrite(recirc, LOW);
    rstatus = 0;
    lcd.setCursor(0, 1);
    lcd.print(F("      FLOODING      "));
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////////////


  ///////////////////////////////WRITE TO SD CARD AND WEBSERVER/////////////////////////////////////////////
  if ((currentLogRate - previousLogRate) >= logRate) {
    //  writesd();
    sendDataToPHP();                                          // Call function to send data to webserver


    if (hasFlood == 0 && recircEnable == 1) {
      flood = 1;
    }
    else {
      flood = 0;
    }

    if (ph >= phUpper) {
      phigh = 1;
    }
    else {
      phigh = 0;
    }

    if (ph <= phLower) {
      plow = 1;
    }
    else {
      plow = 0;
    }
    if (rounded_up >= tempUpper) {
      whigh = 1;
    }
    else {
      whigh = 0;
    }
    if (rounded_up <= tempLower) {
      wlow = 1;
    }
    else {
      wlow = 0;
    }

    previousLogRate = currentLogRate;


  }
  //////////////////////////////////////////////////////////////////////////////////////////////////////////

  ////////////////////////////////////////PH CORRECTION/////////////////////////////////////////////////////
  if (((currentpH - previouspH) >= acidOn && (currentpH - previouspH) <= acidOff) || ((currentpH - previouspH) >= acidOn2 && (currentpH - previouspH) <= acidOff2)) {
    acidEnable = 1;
  }
  else {
    acidEnable = 0;
  }

  if ((currentpH - previouspH) >= baseOn && (currentpH - previouspH) <= baseOff) {
    baseEnable = 1;
  }
  else {
    baseEnable = 0;
  }

  if (baseEnable == 1 && ph < baseThreshold) {
    digitalWrite(basePump, HIGH);
    bstatus = 1;
  }

  else if (acidEnable == 1 && ph > acidThreshold) {
    digitalWrite(acidPump, HIGH);
    astatus = 1;
  }


  else {
    digitalWrite(acidPump, LOW);
    digitalWrite(basePump, LOW);
    bstatus = 0;
    astatus = 0;
  }
  //////////////////////////////////////////////////////////////////////////////////////////////////////////

  ///////////////////////GATHER TEMPERATURE READINGS FOR AVERAGES///////////////////////////////////////////
  temperature = temp.getTemp();
  tempTotal = tempTotal + temperature;
  tempReadings = tempReadings + 1;

  gtemperature = gtemp.getTemp();
  gtempTotal = gtempTotal + gtemperature;
  gtempReadings = gtempReadings + 1;
  //////////////////////////////////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////CALCULATE TEMERATURE AVERAGES///////////////////////////////////////////
  if ((currentRate - previousRate) > rate) {
    tempAverage = 0;
    tempAverage = tempTotal / tempReadings;
    rounded_up = ceilf(tempAverage * 100) / 100;

    float avgForpH = ((rounded_up - 32) / 1.8);
   
    gtempAverage = 0;
    gtempAverage = gtempTotal / gtempReadings;
    float grounded_up = ceilf(gtempAverage * 100) / 100;
   

  lcd.setCursor(0, 3);
  lcd.print(F("W:"));
  lcd.setCursor(2, 3);
  lcd.print(rounded_up);
  lcd.write((char)223);
  lcd.print(F("F"));
  lcd.setCursor(9, 3);
  lcd.print(F("  B:"));
  lcd.setCursor(13, 3);
  lcd.print(grounded_up);
  lcd.write((char)223);
  lcd.print(F("F"));
   
    //////////////////////////////////////////////////////////////////////////////////////////////////////////

    ///////////////////////SEND TEMP TO PH CIRCUIT AND GET PH/////////////////////////////////////////////////

    Serial3.print(F("T,"));
    Serial3.print(avgForpH);
    Serial3.print(F("\r"));
    // delay(100);
    Serial3.print(F("R"));                                       // Send water temp for calibrated pH reading
    Serial3.print(F("\r"));                                      // Send carriage return to end command
    holding = 0;                                              // Reset buffer counter
    if (Serial3.available() > 1) {                            // If there is serial data in buffer
      holding = Serial3.available();                          // Set buffer counter to number of bytes
      for (i = 0; i < holding; i++) {                         // For loop to read out number of bytes in buffer
        stamp_data_ph[i] = Serial3.read();                    // Read serial data to char array
      }
    }
    else {
      Serial.println(F("pH ERR "));                           // Error if no data rx in serial buffer
    }
    ph = atof(stamp_data_ph);                                 // Write pH data to ph float for mathlcd.setCursor(0, 2);
   lcd.setCursor(0, 2);
  lcd.print(F("pH:"));
  lcd.setCursor(4, 2);
  lcd.print(ph);
  lcd.setCursor(8, 2);
  lcd.print(F("  DO:  "));
  lcd.setCursor(15, 2);
  lcd.print(dissolved);
    //////////////////////////////////////////////////////////////////////////////////////////////////////////
    writeLCD();
    ////////////////////////////////RESET VARIABLES FOR TEMP//////////////////////////////////////////////////


    previousRate = currentRate;
    tempTotal = 0;
    tempReadings = 0;
    gtempTotal = 0;
    gtempReadings = 0;
  }
  //////////////////////////////////////////////////////////////////////////////////////////////////////////

  ////////////////////////////////RESET VARIABLES FOR MAIN CYCLE////////////////////////////////////////////

  if (currentMillis - previousMillis >= cycle) {
    previousMillis = currentMillis;
    hasFlood = 0;
    //    waterDetect = 0;
  }
}

void writeLCD() {
 
  lcd.setCursor(0, 0);
  if (month() < 10) {
    lcd.print(F("0"));
    lcd.print(month());
  }
  else {
    lcd.print(month());
  }
  lcd.print(F("-"));
  if (day() < 10) {
    lcd.print(F("0"));
    lcd.print(day());
  }
  else {
    lcd.print(day());
  }
  lcd.print(F("-"));
  lcd.print(year());
  lcd.print(F("  "));
  lcd.setCursor(12, 0);
  if (hour() < 10) {
    lcd.print(F("0"));
    lcd.print(hour());
  }
  else {
    lcd.print(hour());
  }
  lcd.print(F(":"));
  if (minute() < 10) {
    lcd.print(F("0"));
    lcd.print(minute());
  }
  else {
    lcd.print(minute());
  }
  lcd.print(F(":"));
  if (second() < 10) {
    lcd.print(F("0"));
    lcd.print(second());
  }
  else {
    lcd.print(second());
  }
}
void sendDataToPHP() {
  if (client.connect(server, 80)) {
    client.print(F( "GET /add.php?"));
    client.print(F("d=CURDATE()"));
    client.print(F("&&"));
    client.print(F("t=CURTIME()"));
    client.print(F("&&"));
    client.print(F("p="));
    client.print( ph );
    client.print(F("&&"));
    client.print(F("do="));
    client.print( dissolved );
    client.print(F("&&"));
    client.print(F("w="));
    client.print( tempAverage );
    client.print(F("&&"));
    client.print(F("g="));
    client.print( gtempAverage );
    client.print(F("&&"));
    client.print(F("a="));
    client.print(astatus);
    client.print(F("&&"));
    client.print(F("b="));
    client.print(bstatus);
    client.print(F("&&"));
    client.print(F("r="));
    client.print(rstatus);
    client.print(F("&&"));
    client.print(F("ds="));
    client.print(dsensor);
    client.print(F("&&"));
    client.print(F("f="));
    client.print(fsensor);
    client.print(F("&&"));
    client.print(F("pl="));
    client.print(plow);
    client.print(F("ph="));
    client.print(phigh);
    client.print(F("wl="));
    client.print(wlow);
    client.print(F("wh="));
    client.print(whigh);
    client.print(F("fd="));
    client.print(flood);
    client.println(F( " HTTP/1.1"));
    client.println(F( "Host: myhost" ));
    client.println(F("Connection: close\r\n"));
    client.stop();
  }
  else
  {
    Serial.println(F("failed"));
  }
}


/*void writesd() {

  digitalWrite(SD_pin, LOW);                                                     // Select SD card
  digitalWrite(EN_pin, HIGH);                                                    // Deselect Ethernet

  File logFile = SD.open("LOG.csv", FILE_WRITE);
  while (logFile)
  {
    logFile.print(month());
    logFile.print("/");
    logFile.print(day());
    logFile.print("/");
    logFile.print(year());
    logFile.print(", ");
    logFile.print(hour());
    logFile.print(":");
    logFile.print(minute());
    logFile.print(":");
    logFile.print(second());
    logFile.print(",");
    logFile.print(ph);
    logFile.print(",");
    logFile.print(tempAverage);
    logFile.print(",");
    logFile.print(acidStatus);
    logFile.print(",");
    logFile.print(baseStatus);
    logFile.print(",");
    logFile.print(recircStatus);
    logFile.print(",");
    logFile.print(drainSensorVal);
    logFile.print(",");
    logFile.print(levelSensorVal);
    logFile.print(",");
  }
  digitalWrite(SD_pin, HIGH);                                                    // Deselect SD card
  digitalWrite(EN_pin, HIGH);                                                    // Deselect Ethernet
}
*/
// Do not alter this function, it is used by the system
int getTimeAndDate() {
  int flag = 0;
  Udp.begin(localPort);
  sendNTPpacket(timeServer);
  delay(1000);
  if (Udp.parsePacket()) {
    Udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer
    unsigned long highWord, lowWord, epoch;
    highWord = word(packetBuffer[40], packetBuffer[41]);
    lowWord = word(packetBuffer[42], packetBuffer[43]);
    epoch = highWord << 16 | lowWord;
    epoch = epoch - 2208988800 + timeZoneOffset;
    flag = 1;
    setTime(epoch);
    ntpLastUpdate = now();
  }
  return flag;
}

// Do not alter this function, it is used by the system
unsigned long sendNTPpacket(IPAddress& address)
{
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  packetBuffer[0] = 0b11100011;
  packetBuffer[1] = 0;
  packetBuffer[2] = 6;
  packetBuffer[3] = 0xEC;
  packetBuffer[12]  = 49;
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;
  Udp.beginPacket(address, 123);
  Udp.write(packetBuffer, NTP_PACKET_SIZE);
  Udp.endPacket();
}

// Clock display of the time and date (Basic)
void clockDisplay() {
  Serial.print(hour());
  printDigits(minute());
  printDigits(second());
  Serial.print(" ");
  Serial.print(day());
  Serial.print(" ");
  Serial.print(month());
  Serial.print(" ");
  Serial.print(year());
  Serial.println();
}

// Utility function for clock display: prints preceding colon and leading 0
void printDigits(int digits) {
  Serial.print(":");
  if (digits < 10)
    Serial.print('0');
  Serial.print(digits);
}


Top
 Profile  
Reply with quote  
PostPosted: Sep 3rd, '15, 00:53 
Xtreme Contributor
Xtreme Contributor
User avatar

Joined: May 17th, '14, 07:21
Posts: 180
Gender: Male
Are you human?: YES
Location: Vancouver BC Canada
In my experience, jumbled LCD characters are almost always caused by power bumps. How are you powering the Arduino?

Time to start your basic troubleshooting. I would start by commenting out the sendDataToPHP() call. See if it still hangs. Then try the writesd() call and test again. Then try getTimeAndDate(). Comment out one function call at a time until you find the culprit.


Top
 Profile  
Reply with quote  
PostPosted: Sep 6th, '15, 23:59 
Xtreme Contributor
Xtreme Contributor

Joined: Nov 14th, '07, 21:50
Posts: 128
Location: Oz, Littlehampton
Gender: Male
Are you human?: YES
Location: Oz, Littlehampton
Hi Chiumanfu, just a heads up that the download in the first post has gone 404.

I started soldering my shield and realised that my 3-pin terminal blocks bigger than yours, wrong pitch I guess, not sure what the correct one is. I can only fit 12 posts in one row where as you can fit 21. I can probably make it work if I share positive and ground commons on single posts.

Thanks


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 433 posts ]  Go to page Previous  1 ... 9, 10, 11, 12, 13, 14, 15 ... 29  Next

All times are UTC + 8 hours


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
cron

Powered by phpBB® Forum Software © phpBB Group
Portal by phpBB3 Portal © phpBB Türkiye
[ Time : 0.227s | 14 Queries | GZIP : Off ]