Secret Code

Arduino Data Logger

using ChatGPT

Jim Cove

Issue 76, November 2023

This article includes additional downloadable resources.
Please log in to access.

Log in

How to design a data logger and see how effective ChatGPT is to design the code.

The primary aim of this exercise was to evaluate chatGPT's ability to generate Arduino code based on a basic scope.

The second goal was to design a data logger to track the running times of a 12V water pump in a caravan where I suspected it was regularly running for a brief period due to a passing non-return valve.

What is chatGPT

One way of answering that is to ask ‘it’ that very question. As it was proud to tell me, chatGPT is “an AI-powered chatbot developed by OpenAI based on the GPT (Generative Pretrained Transformer) language model.

It is a language model that has been trained on a massive dataset of text data from the internet, allowing it to generate human-like responses to a wide range of questions and prompts”.

With chatGPT, users can converse with the model on a wide range of topics, from current events and general knowledge to jokes and trivia.

Put simply, chatGPT is an advanced chatbot that responds to prompts in a human-like way. It can generate text in various styles, including poems, essays, sales copy, and letters or your resignation letter.

If you don’t like its answer, you can ask it to regenerate its response and it can recognise you are adding information to fine tune its response – it generally does an amazing job!

With those capabilities, asking it to write some Arduino code seemed like a good test and with some fine-tuning tweaks, the code it generated worked perfectly.

It is important to ensure your initial specifications align with your needs. In the realm of information processing, the principle holds true: quality input yields quality output (or to put it another way, Garbage in = Garbage out).

The Hardware

This project used a genuine Arduino Uno but any of the clones would be fine.

There are a few Data Logging shields available. When buying one, make sure you get the information about its pins and libraries.

The one used here uses the very common DS1307Z Real Time Clock (RTC). An RTC is a clock module that keeps accurate time even when unpowered, using a crystal oscillator for accuracy and a small button battery as its voltage source. The DS1307 communicates with the Arduino using the I2C protocol. I2C is a type of bi-directional serial bus that allows the Arduino to read from the RTC and write to it to set the time.

Only two bidirectional wires are required to establish communication among many modules.

Up to 128 addresses can be controlled on one I2C bus, minimising the number of Arduino pins required to control multiple devices.

Adding an I2C display is relatively easy and your project will not suffer from conflicts that can occur when more than one device needs to use the limited number of control pins available from the Arduino.

Data is stored on an SD card, so a module (and its library) is also required to facilitate that. This code generates a text file but if you wish to analyse the data in a spreadsheet, copy the code into chatGPT and tell it to convert the code to produce a CSV file format!

Alternatively, you could copy the file logging section (void logData) into ChatGPT and ask it to convert it to save the data in CSV format then copy the answer back to replace it in the sketch.

The Data Logger board also includes a couple of LEDs (L1 & L2) that could be used to display additional information, however, neither were used in this project.

Finally, a GY-712 current sensor module was used to monitor when the pump motor ran. This device is rated at 30 amperes and powered/interfaced with a 5 volt supply, making it perfect for this task.

The GY-712 uses a Hall Effect Sensor chip to detect plus or minus 30 amps and produces a corresponding analogue output of 66mV/Amp. When no current is flowing, the output of this module is 2.5V increasing to 4.48V at 30A in a ‘positive’ direction or decreasing to 0.52V at 30A in a ‘negative’ direction. As it can monitor both positive and negative current flow, it is able to measure both AC and DC currents.

A DC-DC Buck/Boost adjustable power supply module set to about 7 volts was used to give some voltage protection to the Arduino Uno.

As the logger is to be used on a ‘12V battery’, the voltage of that system could easily exceed 14V when being charged. A simple ‘Buck’ module would have sufficed.

Construction:

Arduino header shields are an extension to the Arduino board and are designed to be used as a platform for interfacing various electronic components with the Arduino board. The main advantage of using an Arduino header shield is that it eliminates the need for messy wiring and makes it easier for the user to connect different components to the board.

Additionally, the header shields offer a compact and organised way of connecting components, thereby making the overall setup of the project neater and more organised. You can easily prototype your ideas using various shields and validate your concepts.

The additional holes in the Data Logger shield can be linked using wires soldered to them and provide a platform to easily add additional components, inputs and outputs to the board.

Whilst the pictures in this article show the Current Sense Module connected by three wires, you could easily desolder its three pins and resolder them on the bottom of the board and then through the data shield to make a more robust and permanent project.

A relay and associated circuitry could also be added to provide an output/control based on an event detected by the Data Logger (with a small amount of additional code).

The code

The sketch logs data to an SD card. The data comprises date and time along with the value of an analogue input pin (A0).

The sketch first initialises the serial communication and the SD card. If the SD card fails to initialise, the sketch will stop.

In the Setup function, the code also initialises an object of the RTC library to communicate with an RTC (real-time clock) module, which is used to keep track of the date and time.

Data received from the GY-712 is averaged for the first 5 seconds after the program boots up. Averaging the value of the data provides a 'smoothed' baseline reference value for comparing the measured values when monitoring is occurring. If the monitored event occurs within that 5 second window, the baseline will be incorrect and the logging uncertain.

Each time there is data to be logged, the code creates or opens a file named datalog.txt on the SD card and logs the data, closing the file after each update.

This code contains several features that are useful for data logging and sensing applications:

  1. Averaging of Readings:
    • After bootup, the code reading from an analogue input (pin A0) and calculates a running average. The average is then used as a reference value to compare against individual readings.
  2. Data Trigger:
    • If the difference between a new reading and the average is greater than TriggerValue (programmed as 4, so 4 x 0.066 ≈ 0.25A), the data is recorded to an SD card. Logging also occurs when the measured value drops back below the ‘average+TriggerValue’.
  3. Real-Time Clock:
    • The code uses the RTClib library to get the current date and time and includes it with each recorded data point. This information is useful for time-stamping the data and keeping track of when the data was recorded.
  4. Error Handling:
    • The code includes several error handling checks, such as checking if the SD card was initialised correctly, if the RTC is found, if the file was opened or created successfully, and if the data was written to the file correctly. These checks help ensure that the code runs smoothly, and any issues can be quickly identified and addressed.

The sketch initialises the necessary libraries, constants, and variables as follows:

Libraries:

The sketch includes three libraries:

  • SD.h: Used for working with SD cards, providing functions for file read and write operations.
  • Wire.h: Included for I2C communication, though it's not explicitly used in the provided code.
  • RTClib.h: Used to manage real-time clock modules, which helps timestamp the data.

Constants:

  • chipSelect: Defines the chip select pin (CS) for the SD card. The SD library uses this pin for communication.
  • numReadings: Specifies the size of the circular buffer. This is the number of previous readings for calculating the moving average for initial calibration.
  • TriggerValue: Sets the threshold value for detecting significant changes in sensor readings.

Variables:

  • readings[numReadings]: An array that stores sensor readings for averaging.
  • total: An accumulator variable for calculating the average of sensor readings.
  • average: The initial averaged value.
  • previousState: A boolean flag to track the previous state of a step change.
  • RTC_DS1307 rtc: An object of the RTC (Real Time Clock) for working with date and time.
  • File myFile: An object for file handling (used for data logging).

Setup Function:

  • Initialises serial communication with a baud rate of 9600.
  • Waits for the serial connection to be established.
  • Prints information about the sketch.
  • Initialises the SD card; if it fails, the program halts.
  • Initialises the RTC; if it fails, the program halts.
  • Initialises the readings array with sensor values for initial averaging.
  • Calculates the initial average of sensor readings.

    Loop Function:

    The ‘loop’ function continuously reads the current sensor value from analogue pin A0 and checks for significant changes compared to the average value. If significant changes are detected, the code logs the event. The ‘logData’ function is responsible for opening the data log file, writing the data with timestamps, and closing the file. It also prints the data to the serial monitor for real-time monitoring.

    Overall, this code provides a good starting point for a data logging application that requires accurate time-stamping and the ability to record data only when significant changes occur.

    ChatGPT – an intelligent helper:

    If you would like more information on any snippet of code, enter a question into ChatGPT such as: “what does this code snippet do?” and simply copy it and paste the snippet into ChatGPT.

    Similarly, if you want to modify the code to perhaps add an output to control a relay upon a certain event, ask ChatGPT to modify the sketch to perform the task, paste in the entire code and then use the result.

    ChatGPT doesn’t always get it right. Often, some fine tuning will be required but it does provide a good basis for generating the code you need. If you aren’t getting the result(s) you expect, tell ChatGPT – it will offer solutions. If you end up with a compile error, use the Copy Error Messages button on the Arduino IDE and paste into ChatGPT – it is very good at working out syntax errors!

    /*
    ChatGPT generated Arduino sketch for a data logger that records analog sensor readings to an SD card,
    along with timestamps, and checks if the reading significantly differs from the average value. The sketch includes three libraries for SD card handling, I2C communication, and real-time clock management.
    */
    //Libraries: 
    #include <SD.h>                
    // Include the SD library for working with SD cards
    #include <Wire.h>              
    // Include the Wire library for I2C communication
    #include <RTClib.h>            
    // Include the RTClib library for managing 
    // real-time clock modules
    //Constants: 
    const int chipSelect = 10; // Chip select pin 
    // for SD card for Deek Robot card
    const int numReadings = 10;    // Size of the 
    // circular buffer. No. previous readings for 
    // calculating the moving average.
    const int TriggerValue = 4;    // Threshold value 
    // for detecting step changes in sensor readings
    //Variables:
    int readings[numReadings];     // Array to store 
    // sensor readings for averaging
    int total = 0;                 // Accumulator for 
    // calculating the average of sensor readings
    float average = 0;   // The initial average value
    boolean previousState = false; // Flag to track the 
    // previous state of a step change
    RTC_DS1307 rtc;                // Create an RTC 
    // (Real Time Clock) object
    File myFile;                   // Create a file 
    // object for data logging
    int numSamples = 0;
    void setup() {
      Serial.begin(9600);    // Start serial 
          // communication at a baud rate of 9600
      while (!Serial);       // Wait for the serial 
          // connection to be established
      // Print information about the sketch
      Serial.println("nData Logger:");
      Serial.println(__FILE__);
      Serial.println("");
      // Initialise the SD card
      if (!SD.begin(chipSelect)) {
        Serial.println("SD initialization failed!");
        while (1);
      }
      Serial.println("SD initialization done.");
      // Initialise the RTC (Real Time Clock)
      if (!rtc.begin()) {
        Serial.println("RTC initialization failed!");
        while (1);
      }
      Serial.println("RTC initialization done.");
      // Initialise the readings array with the first 
      // sensor values
      unsigned long samplingStartTime = millis();
      unsigned long samplingEndTime = samplingStartTime + 5000;
      numSamples = 0;
      total = 0;
      // Calculate the initial average of sensor 
      // readings
      while (millis() < samplingEndTime) {
        int sensorValue = analogRead(A0);
        readings[numSamples % numReadings] = sensorValue;
        total += sensorValue;
        average = (float)total / min(numSamples, numReadings);
        numSamples++;
        delay(200);
      }
      if (numSamples > 0) {
        average = (float)total / numSamples;
      }
      // Print the initial data to the serial monitor
      Serial.print("Number of samples: ");
      Serial.println(numSamples);
      Serial.println("");
      Serial.print("Total of samples: ");
      Serial.println(total);
      Serial.println("");
      Serial.print("Input average value: ");
      Serial.println(average);
      Serial.println("");
    }
    void loop() {
      int sensorValue = analogRead(A0);  
    // Read the current sensor value from A0
      // Check if the sensor value is  different from 
      // the average by the TriggerValue
      if (abs(sensorValue - average) > TriggerValue) {
        if (!previousState) {
          logData(sensorValue, "Step Change Start");
          previousState = true;
        }
      } else if (previousState) {
        logData(sensorValue, "Step Change End");
        previousState = false;
      }
    }
    void logData(int value, const char* message) {
      // Open the data log file for writing with the 
      // ".txt" file extension
      myFile = SD.open("datalog.txt", FILE_WRITE);
      if (!myFile) {
        Serial.println("Error opening datalog.txt");
      } else {
      // Write the date, time, sensor value, average 
      // value, and the provided message to the data 
      // log file
        DateTime now = rtc.now();
        myFile.print(now.year(), DEC);
        myFile.print(' ');
        myFile.print(now.month(), DEC);
        myFile.print(' ');
        myFile.print(now.day(), DEC);
        myFile.print(' ');
        myFile.print(now.hour(), DEC);
        myFile.print(':');
        myFile.print(now.minute(), DEC);
        myFile.print(':');
        myFile.print(now.second(), DEC);
        myFile.print(" - Sensor Value: ");
        myFile.print(value);
        myFile.print(" - Average Value: ");
        myFile.print(average);
        myFile.print(" - ");
        myFile.println(message);
        myFile.close();            
    // Close the data log file
       // Print the same data to the serial monitor 
       // for real-time monitoring    
        Serial.print(now.year(), DEC);
        Serial.print(' ');
        Serial.print(now.month(), DEC);
        Serial.print(' ');
        Serial.print(now.day(), DEC);
        Serial.print(' ');
        Serial.print(now.hour(), DEC);
        Serial.print(':');
        Serial.print(now.minute(), DEC);
        Serial.print(':');
        Serial.print(now.second(), DEC);
        Serial.print(" - Sensor Value: ");
        Serial.print(value);
        Serial.print(" - ");
        Serial.println(message);
      }
    }

    Setting the correct time on the RTC:

    The clock on the RTC needs to be set and the easiest way to do this is from the PC. Simply create a new sketch, paste the following code into it and then upload the code to the Arduino.

    As soon as the code runs, it will use the time/date etc. from the PC to update the RTC and then display the results in the Serial Monitor.

    Create a new sketch and copy/paste this into it. Save it as RTC_Clock_Set_From_PC or similar then upload the sketch to the Arduino. When it runs it will set the clock and display the date/time details on the serial monitor.

    You should only have to do this once and you can then upload the Data Logger sketch.

    #include <Wire.h>
    #include <RTClib.h>
    RTC_DS1307 rtc;
    void setup() {
      Serial.begin(9600);
      Wire.begin();
      rtc.begin();
      // Set the RTC to the computer's time and date
      rtc.adjust(DateTime(__DATE__, __TIME__));
    }
    void loop() {
      DateTime now = rtc.now();
      Serial.print(now.year(), DEC);
      Serial.print('/');
      Serial.print(now.month(), DEC);
      Serial.print('/');
      Serial.print(now.day(), DEC);
      Serial.print(' ');
      Serial.print(now.hour(), DEC);
      Serial.print(':');
      Serial.print(now.minute(), DEC);
      Serial.print(':');
      Serial.print(now.second(), DEC);
      Serial.println();
      delay(1000);  
    // Print the time once per second
    }

    ChatGPT's Own Thoughts

    Out of curiosity, I typed in to ChatGPT “write about the virtues of using chatGPT to write arduino code”

    chatGPT replied:

    There are several benefits of using OpenAI's language model ChatGPT to write code for the Arduino platform:

    1. Time-saving: ChatGPT can generate code snippets and complete programs quickly, saving valuable time for the user who may not have extensive experience in coding. This can be especially helpful when working on a tight deadline or in a situation where time is of the essence.
    2. Error reduction: ChatGPT has been trained on a massive amount of data and can write accurate code with fewer errors compared to manual coding. This reduces the risk of bugs and malfunctions in the final product, leading to higher-quality results.
    3. Accessibility: ChatGPT is a natural language model and can respond to simple language queries, making it accessible to users who are not experts in programming. This allows people with a diverse range of backgrounds and skills to use the technology to write code for the Arduino.
    4. Versatility: ChatGPT can generate code for a wide range of applications and purposes, including but not limited to controlling sensors, motor drivers, and other peripherals on the Arduino platform.
    5. Cost-effective: ChatGPT is an AI-based tool that can be used free of charge, making it a cost-effective option for individuals and small organizations who may not have the resources to hire a dedicated programmer.

    Overall, ChatGPT's ability to write code quickly, accurately, and with a low risk of errors makes it a valuable tool for anyone working with the Arduino platform.

    About The Author

    Often described as a "Jack of All Trades," Jim's formal qualifications originated from his studies and work at State Electricity Commission. He established a home-based business specialising in electronic and communications equipment repair. Over time, this venture expanded to include computer assembly and sales.

    Following a 20-year career at the SEC, during which he owner built their family home, Jim and his wife made the decision to sell their house and accompanied by their two teenage daughters, embarked on a 12-month journey around Australia. Settling in Queensland, Jim and his wife successfully ran a Management Rights Holliday Accommodation business. In partnership with a lifelong friend, Jim then ventured into a precision laser cutting and metal etching business, serving as the Managing Director for 15 years until retirement. In that time, the company grew from a single laser operation to a thriving business with six lasers and a significantly expanded workforce.

    Now in retirement, Jim is looking forward to rekindling his passion for electronics and Ham Radio. It marks an exciting new chapter in his journey, allowing him to delve back into the world that has always held a special place in his heart.