Struggling with ESP32 - Counts too many - Interrupt issue?

  • Simomax
  • Simomax's Avatar Topic Author
  • Offline
  • Moderator
  • Moderator
  • Nuts about radioactive everything!
More
1 year 10 months ago - 1 year 10 months ago #6328 by Simomax
Hello All,

I have been trying to get my ESP8266 counter sketch to work properly on the ESP32. It seems to count too many times when the interrupt is triggered. I'm using my development Geiger counter (a GK v5.5 circuit on breadboard) which has served me very well with all other MCUs I have tried it with, but not the ESP32.

At first I had the 5v output going straight into pin 23 of the ESP32 (see first oscilloscope waveform) and whilst it worked, it would count 3-4 counts every time the interrupt was triggered. After some trial and error testing (changing the internal pullup/rising/falling - nothing worked) I dropped in a 5v - 3.3v level shifter on the counter output to drop the level to what the ESP32 would be more comfortable with (see second oscilloscope waveform) and whilst it did reduce the false triggers, I am still getting two counts per pulse. So then I wrote a very simple debounce routine and that really helped a lot. A debounce of about 25us (microseconds) works well and at background counts the ESP32 is reading 1 count per pulse. Great?.... No so... By introducing the small debounce timer this is having a detrimental affect at higher counts per minute/second. So when testing with a LND712 and Amerecium 241 it seems to now be missing counts at the high end. I have a Pro Mini connected at the same time as the ESP32 to compare and the Pro Mini would count say 20,000 counts in X seconds, but the ESP32 would count about 14,000 counts in the same X seconds.

I have very little experience with the ESP32 (this is the first time I have ever used one) and I am at a complete loss as to resolving this. I don't understand why it would be picking up two counts from the 3.3v pulse as it seems clean. I have looked at some other ESP32 counter code and seems nobody else is using a debounce on the input, so is it my ESP32, or are the other people's sketches counting too much also? I have some more ESP32 on their way to me from Aliexpress, but I think until I get one of those to try I am going to shelve this for now util I can try with another ESP32 or find a solution somewhere. I have included the full code below. (The code is just set to print total counts, not CPM, for diagnostics.)

Waveform of 5v pulse - This gave approx 4 counts per pulse:
 

Waveform of 3.3v pulse - This gives approx 2 counts per pulse unless debounced:
 

And, here is the code:
/*
  -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  | ArduinoBB WROOM Jellyfish 0.1 (not for production use!!! - It counts too much, or too little....)                                                                                                       |
  | Bare-bones Arduino counter for Geiger counter and submission to Radmon.org written by Simomax.                                                                                                          |
  | This code was taken from a working project with all of the other code chopped out to give very basic Radmon.org CPM submission.                                                                         |
  | This is very basic code. It will detect pulses on GPIO 23 (pin 23 on ESP-WROOM-32) and calculate the CPM every second. It will then submit the CPM to Radmon.org every 60 seconds.                      |
  | It has a debug option and also option to print the CPM to serial every second, much like the NetIO- GC-10, which can also be used with the Radlog windows software.                                     |
  |                                                                                                                                                                                                         |
  | You are free to do anything you want with this code. There is no license. You are allowed to use/copy/change/share the code without having to attribute myself,                                         |
  | although it would be appreciated if you did. You are also free to use this in any commercial setting.                                                                                                   |
  | Radmon.org is free and always has been and in that sentiment, so is this code, as is all of my code that I share on the Radmon.org forums unless specifically stated.                                   |
  | If you would like to give something back then please consider a small donation to Radmon.org   (which I ahve no affiliation with whatsoever, ecxept from being a user and contributor                   |
  | to the forums) or even better, become a regular user of the Radmon.org forums. It has become a little quiet of late and some new users to the forum would be welcomed, by everyone I am sure.           |
  |                                                                                                                                                                                                         |
  | This code comes without warranty or support. I am happy to answer questions in the Radmon forums, but try and keep it within the code. I may not be able to help with your own code outside my own.     |
  | If it breaks you get to keep all the pieces!                                                                                                                                                            |
  |                                                                                                                                                                                                         |
  | Have fun and happy counting!                                                                                                                                                                            |
  | ~Simonmax                                                                                                                                                                                               |
  -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  */

#include <WiFi.h>
WiFiClient client;                           // Use WiFiClient class to create TCP connections
//#define LED_BUILTIN 2


// Your WiFi Network SSID and Password.
#define WIFI_SSID             "Your-SSID"
#define WIFI_PASS             "Your-WiFi-Password"

// Your Radmon.org username and submission password.
const char* RadmonHost = "radmon.org";        // No need to change this
const char* UserName = "username";             //Your Radmon.org user name
const char* PassWord = "password";          //Your Radmon.org submission password - Make sure this is the same as your 'data sending password' on your Radmon.org profile page.

// Debug - True prints information to serial.
int debug = true;

// Option to enable CPM to be printed to serial - When enables this emulates the NetIO GC-10 serial output
int printCPM = false;




// Variables used - You shouldn't need to change anything below this line unless customizing. --------------------------------------------------------------------------------
long CPM;
volatile unsigned long totalCounts = 0;       // Total counts - May be useful, but for now is only printed when debug enabled.
int cpsCount = 0;                             // Used for calculating CPM. This var counts the amount of interrupt events in one second.
int cpmArray[60];                             // Array used for storing CPS over 60 seconds. Each index equals the CPS over a given second.
int cpmArrayStep = 0;                         // Used for stepping the index of above array when calculating CPM.
static uint32_t debounce = 25;                // (Microseconds) Debounce period as too often the ESP32 will see 2 or 3 pulses on the interrupt. This smooths it out a bit, but in doing so affects it's ability to count strong sources and will give a lower CPM that other MCUs.
static uint32_t lastPulse=micros();           // Used for the debounce.
static uint32_t cpsTime=millis();             // Used for the 1 second timer. We count millis in the loop as using a delay would impact other functions and delays should generally not be used.
static uint32_t cpmTime=millis();             // And another for the 60 second timer for Radmon.org submission.
int cpmMillis = 1000;                         // How often to calculate CPM (1000ms = 1 second.)
int radmonMillis = 30000;                     // How often to submit to Radmon.org (60000ms = 60 seconds.) Please don't set this to less than 60000 as recommended minimum submission time for Radmon.org is 60 seconds.
int lastConnectionAttempt = millis();
int connectionDelay = 5000;                   // Try to reconnect WiFi every 5 seconds if connection lost.



void setup()
{
  Serial.begin(9600);                         // Set serial baud rate to 9600.
  delay(500);
  //pinMode(LED_BUILTIN, OUTPUT);               // Setup (internal) LED pin.
  //digitalWrite(LED_BUILTIN, HIGH);            // Turn the internal LED off - The internal LED on the Wemos D1 Mini uses reversed logic, so HIGH is off and LOW is on.
  pinMode(23, INPUT_PULLUP);                  // Set pin GPIO 13 (D7 on Wemos D1 Mini) as input and enable internal pullup.

  WiFi.mode(WIFI_STA); //Set WiFi mode

  attachInterrupt(23, GetEvent, RISING);     // Attach interrupt on GPIO 13.

  if (debug) {
    Serial.println("Running!!!!\r\n"); // ------------------------------------------------------------------------------------------------
  }
}

void updateRadmon() {                        // Send CPM to Radmon.org.
  if (debug) {
    Serial.println("\r\nUpdating Radmon...");
  }

  WiFiClient clientGet;                     // Set as client.
  const int httpGetPort = 80;

  String urlGet = "/radmon.php";            // Build the URL for Radmon.org CPM submission.
  urlGet += "?function=submit&user=";
  urlGet += UserName;
  urlGet += "&password=";
  urlGet += PassWord;
  urlGet += "&value=";
  urlGet += int(CPM);
  urlGet += "&unit=CPM";

  if (debug) {
    Serial.print(">>> Connecting to host: ");
    Serial.println(RadmonHost);
  }

  if (!clientGet.connect(RadmonHost, httpGetPort)) {  // Try and connect to Radmon.org.
    if (debug) {                                      // Connection failed!.
      Serial.print("Connection failed: ");
      Serial.println(RadmonHost);
    }

  } else {                                            // Connected - Success!
    if (debug) {
      Serial.println("Sending data....");
    }
    clientGet.println("GET " + urlGet + " HTTP/1.1"); // Send the datary goodness to Radmon.org.
    if (debug) {
      Serial.println("GET " + urlGet + " HTTP/1.1");
    }
    clientGet.print("Host: ");
    clientGet.println(RadmonHost);
    if (debug) {
      Serial.println("Host: " + String(RadmonHost));
    }
    clientGet.println("User-Agent: ArduinoBB WROOM Jellyfish 0.1");
    if (debug) {
      Serial.println("User-Agent: ArduinoBB WROOM Jellyfish 0.1");
    }
    clientGet.println("Connection: close\r\n\r\n");
    if (debug) {
      Serial.println("Connection: close\r\n\r\n");
    }

    unsigned long timeoutP = millis();        // We check the connection time. If longer than 10 seconds then stop the connection and exit the function - timeout.
    while (clientGet.available() == 0) {
      if (millis() - timeoutP > 10000) {
        if (debug) {
          Serial.print(">>> Client Timeout: ");
          Serial.println(RadmonHost);
        }
        clientGet.stop();
        return;
      }
    }

    if (debug) {
      Serial.println("End of sending data....\r\n\r\nResponse:"); // We didn't timeout so lets wait for a response from Radmon.org.
    }


    while (clientGet.available()) {          // We got a response so just check the 1st line of the server response. Could be expanded if needed.
      String retLine = clientGet.readStringUntil('\r');
      if (debug) {
        Serial.println(retLine);
      }
      break;
    }
  }                                         // End client connection as we are done.

  if (debug) {
    Serial.print(">>> Closing host: ");
    Serial.println(RadmonHost);
  }
  clientGet.stop();
}

void calculateCPM()                         // Calculate the CPM.
{
  if (debug) {
    //Serial.print("Calculating CPM: ");
    Serial.print("Total: ");
    Serial.println(totalCounts);
  }
  cpmArray[cpmArrayStep] = cpsCount;        // Set the index in the CPM array to that of the current CPS. The index initially starts at 0 when powered on.
  cpmArrayStep++;                           // The next index we want to record.
  if (cpmArrayStep >= 60)                   // There are 60 indexes, one for every second in a minute. If the index goes out of the bounds of our 60 indexes then cycle back to index 0.
  {
    cpmArrayStep = 0;
  }
  CPM = 0;                                  // Var used to temporarily calculate CPM.
  unsigned int i;
  for (i = 0; i < 60; i++)                  // Get the value at each index of the CPM array.
  {
    CPM += cpmArray[i];                     // Add each index together to give a total over 60 seconds.
  }
  cpsCount = 0;                             // Reset the CPS variable ready for sampling the next second.
  if (printCPM) {
    //Serial.println(CPM);                    // Print the current CPM to serial every interval if enabled - Much like the NetIO GC-10.
  }
  else if (debug) {
    //Serial.println(CPM);
  }
}

void GetEvent() {                           // ISR triggered for each new event (count).
  if (lastPulse + debounce < micros())
  {
    totalCounts ++;                           // Increase total counts each time the interrupt is fired.
    cpsCount ++;                              // Increase var each time the interrupt is fired.
    lastPulse = micros();
  }
  
}

void loop()
{    
  if ( (millis()-cpsTime) >= cpmMillis) {        // Check our 1 second interval.
    cpsTime = millis();
    calculateCPM();                         // 1 Second has surpassed so we calculate our CPM.
  }
  
  if ( (millis()-cpmTime) >= radmonMillis) {        // Check our 60 second interval.
    cpmTime = millis();
    updateRadmon();                         // 60 Seconds has surpassed so we submit the CPM to Radmon.org.
  }
  
  if (WiFi.status() != WL_CONNECTED)        // check WiFi connection:
  {
    // (optional) "offline" part of code - you can add code here to signal when WiFi is disconnected, such as lighting an LED.
    if (millis() - lastConnectionAttempt >= connectionDelay)
    {
      lastConnectionAttempt = millis();
      if (WIFI_PASS && strlen(WIFI_PASS))   // attempt to connect to Wifi network:
      {
        WiFi.begin((char*)WIFI_SSID, (char*)WIFI_PASS);
      }
      else
      {
        WiFi.begin((char*)WIFI_SSID);
      }
    }
  }
  else
  {
    // We are connected - you can add code here to signal when WiFi is connected, such as lighting an LED.
  }
}[/i]
Attachments:
Last edit: 1 year 10 months ago by Simomax.

Please Log in or Create an account to join the conversation.

More
1 year 9 months ago #6348 by jesterer
Here is some working code. Give it a try.

unsigned long CPM;
unsigned long cpmTime;
volatile unsigned long totalCounts = 0; // counts from interrupt pin. "volatile" required to keep it in RAM memory for the interrupt routine


void setup()
{
Serial.begin(115200); // Set serial baud rate to 115200.
delay(500);
pinMode(13, INPUT_PULLUP); // Set pin GPIO 13 (D7 on Wemos D1 Mini) as input and enable internal pullup.

// use digitalPinToInterrupt to get the right input parameter for attachInterrupt
attachInterrupt(digitalPinToInterrupt(13), GetEvent, RISING); // Attach interrupt on GPIO 13.

//pin 12 works on a feather Huzza. if you get multiple counts check if anything else is assigned to your pin
}



// NOTE do not call subroutines from an interrupt routine. keep it as short as possible.
// NOTE you need to tell the compiler to load this into ram with "ICACHE_RAM_ATTR"
ICACHE_RAM_ATTR void GetEvent() { // ISR triggered for each new event (count).
totalCounts ++; // Increase total counts each time the interrupt is fired.
}

void loop()
{

if ( (millis()-cpmTime) >= 60000) { // Check our 60 second interval.
CPM = totalCounts;
totalCounts = 0;
cpmTime = millis();
Serial.print("CPM:");
Serial.println(CPM);
}
}
The following user(s) said Thank You: Simomax

Please Log in or Create an account to join the conversation.

  • Simomax
  • Simomax's Avatar Topic Author
  • Offline
  • Moderator
  • Moderator
  • Nuts about radioactive everything!
More
1 year 9 months ago #6349 by Simomax
Thanks jesterer. I'll give this a whirl over the weekend and I'll let you know if this works on my ESP32.

I have bought another version of the ESP32 to try and see if it is something with the ESP32 I have, or my code. I'll give your's a whirl and see what happens!

Cheers

Please Log in or Create an account to join the conversation.

More
1 year 9 months ago #6350 by DonZalmrol
Feel free to check out my ESP32 version, its running very stable since 2022.

https://github.com/DonZalmrol/ESP32-Wrover-E-Dual-GM-tube-deadtime-calculator

Please Log in or Create an account to join the conversation.

  • Simomax
  • Simomax's Avatar Topic Author
  • Offline
  • Moderator
  • Moderator
  • Nuts about radioactive everything!
More
1 year 9 months ago - 1 year 9 months ago #6352 by Simomax
I have been poking and prodding at this all morning and still getting nowhere. I used jesterer's test code above, but thinned it down a little to just count and display every second to test the interrupt. The test code I used is below. Still no joy though, and still counting erratically.

I have done the following:
  • Tested with small test sketch instead of my larger code
  • Tested with different pins - 12, 13 & 23
  • Tested using RISING and FALLING interrupt parameters
  • Tested with two different ESP32's - One a WROOM-32 and one WROOM-32D
  • Tested with two different Geiger counters - My breadboard GK v5.5 which I have used to test many other MCU projects, and never had this issue. And a cheap CAJOE RadiationD v1.1
  • Tested using a logic level converter between the counter pulse and the ESP input pin - This does clean things a little, but not 100%
So I feel like I have really exhausted every possibility of getting this working nice and clean. One thing I haven't tried is using a NetIO GC-10 to test as I believe it has a logic pulse output instead of an analogue pulse. But that requires me to strip down a counter to get to the pins on the GC-10, and frankly, if I can't make the ESP32 work for me very simple, it's not worth me pursuing it to use with a counter as other MCUs work very effective and are cheaper. I would probably only ever use an ESP32 as a counter/web comms in part of a larger project, which I am unlikely to do. So I think I am going to throw in the towel on this.

I have made a small video on this happening FWIW.


This is the test code I used:
unsigned long cycle;
volatile unsigned long totalCounts = 0; // counts from interrupt pin. "volatile" required to keep it in RAM memory for the interrupt routine


void setup()
{
  Serial.begin(115200); // Set serial baud rate to 115200.
  delay(500);
  pinMode(13, INPUT_PULLUP); // Set pin GPIO 13 (D7 on Wemos D1 Mini) as input and enable internal pullup.

  // use digitalPinToInterrupt to get the right input parameter for attachInterrupt
  attachInterrupt(digitalPinToInterrupt(13), GetEvent, RISING); // Attach interrupt on GPIO 13.

  //pin 12 works on a feather Huzza. if you get multiple counts check if anything else is assigned to your pin
}



// NOTE do not call subroutines from an interrupt routine. keep it as short as possible.
// NOTE you need to tell the compiler to load this into ram with "ICACHE_RAM_ATTR"
ICACHE_RAM_ATTR void GetEvent() { // ISR triggered for each new event (count).
  totalCounts ++; // Increase total counts each time the interrupt is fired.
}

void loop()
{

  if ( (millis() - cycle) >= 1000) { // Check our 60 second interval.
    cycle = millis();
    Serial.print("Counts: ");
    Serial.println(totalCounts);
  }
}
Last edit: 1 year 9 months ago by Simomax.

Please Log in or Create an account to join the conversation.

More
1 year 9 months ago #6353 by Bert490
Maybe the interrupt is firing close to the steady state voltage, where there may be more noise.  Since the level shifter helped, maybe a different one might be better; I'm thinking a comparator chip set to 2V.  Then the negative-going input will cause a positive output, but narrower.  Chances of bouncing are less, and if they are still present, maybe an analog debounce of 10 uS with a small capacitor.  That would save one addition operation in the ISR and maybe improve accuracy at higher counts.
 

Please Log in or Create an account to join the conversation.

Moderators: Gamma-Man
Time to create page: 0.199 seconds
Powered by Kunena Forum
Everything's free. Please support us by considering a donation. Log in first!
Solar powered Raspberry Pi 4 server stats: CPU 28% Memory 14% Swap 46% CPU temp=55.0'C Uptime 54 Days