Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Confused with usage of external ADC. #46

Closed
astorun opened this issue Jan 9, 2024 · 37 comments
Closed

Confused with usage of external ADC. #46

astorun opened this issue Jan 9, 2024 · 37 comments
Assignees
Labels
question Further information is requested

Comments

@astorun
Copy link

astorun commented Jan 9, 2024

Hello,

I am trying to read ACS712 value on ESP32 board which is connected through ADS1015 pin 1.

However I cannot figure out how to use it properly since example "ACS712_20_DC_external_ADC.ino" does not provide enough context to me how to read a specific pin.

Moreover when I include "ACS.setADC(wrapperACSPin, 5.0, 4095);" Other libraries starts doing some weird stuff like delaying function executions in random durations e.g. Ticker library wont function properly. It is like ESP32 is hanging.

Sorry for being a noob here. I am providing a portion of the code I am using below to read ACS value over ADS1015 pin 1.

Thank you.

#include <Arduino.h>
#include <ACS712.h>
#include <ADS1X15.h>

#define ADS1015_ADDRESS 0x48
#define ADS1015_SCL 1
#define ADS1015_SDA 2
#define ADS1015_RDY 3

//#define SENSOR_TMP6_ADSPIN 0
#define SENSOR_ACS712_ADSPIN 1
//#define SENSOR_DIST_ADSPIN 2

// ADS SETUP ///
ADS1015 ads1015(ADS1015_ADDRESS);

// ACS SENSOR SETUP ///
ACS712 ACS(4, 5.0, 4095, 100);

// ACS SENSOR WRAPPER FOR ADS1015 ///
uint16_t wrapperACSPin(uint8_t pin)
{
    int16_t ACSVal = ads1015.readADC(SENSOR_ACS712_ADSPIN);
    return ACSVal;
}

void readCurrent() {
    sensorCurrentValueRaw = ACS.mA_DC(10);
}

void sensorsInit() {
    ACS.setADC(wrapperACSPin, 5.0, 4095);
    ACS.autoMidPoint(0, 10);
}

void setup() {
    Wire.begin(ADS1015_SDA, ADS1015_SCL);
    Wire.setClock(400000);

    sensorsInit();
}

void loop {
    //Other code here...
}
@astorun
Copy link
Author

astorun commented Jan 9, 2024

Maybe including a much simpler method to read the ACS712 using ADS1X15 Library might be a better solution. Like including another definition in ACS712 constructor to indicate whether your ADS1X15 library is being used to read from it like below?.

ACS712(uint8_t analogPin, float volts = 5.0, uint16_t maxADC = 1023, float mVperAmpere = 100, 
             bool useADS1X15 = true, char nameADS1X15 = ADS1015, int pinADS1X15 = 0) 

@RobTillaart RobTillaart self-assigned this Jan 9, 2024
@RobTillaart RobTillaart added the question Further information is requested label Jan 9, 2024
@RobTillaart
Copy link
Owner

Thanks for the issue.
Will try to come back to it today.

@RobTillaart
Copy link
Owner

The idea of a specific constructor for the ADS1x15 sounds creative however there are at least a dozen other ADC devices.
It would make things too specific and it would result in a forest of constructors.
Still imho the idea is good thinking.

@RobTillaart
Copy link
Owner

However I cannot figure out how to use it properly since example "ACS712_20_DC_external_ADC.ino" does not provide enough context to me how to read a specific pin.

The example is indeed not very instructive, so there should be a new one. Your project with an ADS1x15 seems a more explicit case. Have some other tasks so I'll try later this evening (19:00 here now)

@RobTillaart
Copy link
Owner

@astorun

I have no hardware nearby to test, so the code will need adjustments as I do not know the ADS1015 details from my head.

Tried to keep the code minimal to read a DC current.
Please give it a try and post the output of the sketch.

//    FILE: ACS712_ESP32_external_ADC.ino
//  AUTHOR: Rob Tillaart
// PURPOSE: demo to measure mA DC with external ADC
//     URL: https://github.com/RobTillaart/ACS712


#include <Arduino.h>
#include <ACS712.h>
#include <ADS1X15.h>


//  I2C config
#define ADS1015_ADDRESS         0x48
#define ADS1015_SCL             22   //  default SCL ESP32
#define ADS1015_SDA             21   //  default SDA ESP32

//  ADS1x15 config
#define SENSOR_ACS712_ADSPIN    1


//  explicit params for demo
ADS1015 ads1015(ADS1015_ADDRESS, &Wire);  //  ADS1015 == 12 bit


//  SENSOR_ACS712_ADSPIN sets pin 1 of the ADS1015, 5.0 volt, 4095 = 12 bit, 100 = mVperAmpere
ACS712 ACS(SENSOR_ACS712_ADSPIN, 5.0, 4095, 100);


//  ACS712 ADC WRAPPER FOR ADS1015
uint16_t readADS1015(uint8_t pin)
{
  uint16_t ADS_raw = ads1015.readADC(pin);
  //  Serial.print("ADS_raw: ");
  //  Serial.println(ADS_raw);
  return ADS_raw;
}

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

void setup() 
{
  Serial.begin(115200);
  while (!Serial);
  Serial.println(__FILE__);
  Serial.print("ACS712_LIB_VERSION: ");
  Serial.println(ACS712_LIB_VERSION);
  Serial.println();

  //  ESP32 set wire pins explicitly
  Wire.begin(ADS1015_SDA, ADS1015_SCL);
  Wire.setClock(400000);

  //  initialize ADS1015, if fail => report
  if (ads1015.begin() == false)
  {
    Serial.println("ADS1x15 not found. Check wires and pins. Reboot.");
    while(1);
  }

  //  verify the ADS is working in raw mode
  Serial.println(readADS1015(SENSOR_ACS712_ADSPIN));

  //  set up the external ADC for the ACS712
  ACS.setADC(readADS1015, 5.0, 4095);
}

void loop()
{
  int mA = ACS.mA_DC();
  Serial.println(mA);
  delay(1000);
}

//  -- END OF FILE --

@astorun
Copy link
Author

astorun commented Jan 10, 2024

Thanks for the updated code,

Your code works as intended when running alone.

Then we implemented it to our code but it is still adding a huge delay to the arduino ticker library when we include the ACS.setADC(readADS1015, 5.0, 4095);

Output is around -2 with ACS.autoMidPoint(0, 10);

We have been using your ACS712 library before without any ADC in between and it works great. However including ADS1015 with your ADS1X15 library adding a delayed response to Ticker library behavior in random durations, as much as a up to a minute.

We can be able to get a raw data from ACS712 via ADS1015 without any issues with the following code below using ADS1X15 library and we have other sensors connected to the same ADS1015 and they are working as supposed to. ACS712 raw output is around 1250.

void debugInit() {
    
    int adspin0 = ads1015.readADC(SENSOR_TMP36_ADSPIN);
    int adspin1 = ads1015.readADC(SENSOR_ACS712_ADSPIN);
    int adspin2 = ads1015.readADC(SENSOR_DIST_ADSPIN);
    int adspin3 = ads1015.readADC(NA_ADSPIN);

    Serial.print("adspin0 TMP36:");
    Serial.print(adspin0);
    Serial.print("  -  adspin1 ACS712:");
    Serial.print(adspin1);
    Serial.print("  -  adspin2 DIST:");
    Serial.print(adspin2);
    Serial.print("  -  adspin3 NA:");
    Serial.println(adspin3);
    
    Serial.print("sensorCurrentValueRaw:");
    Serial.println(sensorCurrentValueRaw);
    Serial.println("-");
}

Our code consists about 50000 lines of code and as far as I can tell it only affects the Ticker library for some reason. Not sure if the other part of our code is responsible of this issue or there is some conflict between Ticker library and Wrapper code.

@RobTillaart
Copy link
Owner

RobTillaart commented Jan 10, 2024

The ACS712 code for setADC() just setting a function pointer, so I expect that part is not the prime suspect.
Therefor I will dive into the ADS1x15 library to see if I can find clues.

Output is around -2 with ACS.autoMidPoint(0, 10);

The ADS1015 is an ADC that returns a signed 15 bit so the midpoint is around zero. So seems OK.


However including ADS1015 with your ADS1X15 library adding a delayed response to Ticker library behavior in random durations, as much as a up to a minute.

Test 1

Can you add this line after ads1015.begin() to see what the data-rate setting is? Expect value 4.

Serial.println(ads1015.getDataRate();

@RobTillaart
Copy link
Owner

Test 2

Can you add this line after ads1015.begin()

ads1015.reset();

@RobTillaart
Copy link
Owner

@astorun

Have you seen this?

From the GITHUB site of the ticker library: https://github.com/sstaub/Ticker

Advice: for use with ESP boards and mbed based Arduino boards like Arduino Nano RP2040 Connect and Raspberry Pi Pico (using the official Arduino core) the TickTwo library https://github.com/sstaub/TickTwo is recommanded avoiding name conflicts.

Don't know if you have name conflicts as mentioned here.

@RobTillaart
Copy link
Owner

RobTillaart commented Jan 10, 2024

@astorun

in the Ticker constructor there is a strange line

if (resolution == MICROS) timer = timer * 1000;

If you set the timer to 1.000.000 micros (== 1 second) in the constructor, it multiplies the timer by 1000.
Do you somewhere use Ticker with micros?

I do not understand why there is this factor 1000, can you explain it?

@astorun
Copy link
Author

astorun commented Jan 10, 2024

We are using built-in official ESP32 ticker library for arduino: https://github.com/espressif/arduino-esp32/tree/master/libraries/Ticker,

Which is different(?) from the https://github.com/sstaub/Ticker I suppose.

I will include test 1 and test 2 to our code and will report the outputs in a moment.

@RobTillaart
Copy link
Owner

RobTillaart commented Jan 10, 2024

Which is different(?) from the https://github.com/sstaub/Ticker I suppose.

Yes, very different

OK, other Ticker library, I'll have a look at it.

What do your calls to Ticker look like?
Arguments of the call back functions may not exceed 32 bit.

@RobTillaart
Copy link
Owner

I see that arguments are casted to unsigned int .
Are there signed parameters in your call back functions?

@RobTillaart
Copy link
Owner

@astorun
Do you use other I2C devices in your project?
Could it be that the I2C is interfering with the esp_timer?

@RobTillaart
Copy link
Owner

@astorun
Esp_timer library has sometimes problems, I found this describing wifi as disruptive force.
https://www.esp32.com/viewtopic.php?t=25642

Maybe such interference is only there if the ticker and I2C runs on same core?

@astorun
Copy link
Author

astorun commented Jan 10, 2024

I see that arguments are casted to unsigned int . Are there signed parameters in your call back functions?

Nope. There are total of 5 that we coded by ourselves. 4 of them are float and the only unsigned int is the one with acs712 wrapper function.

However, we are using tons of other libraries and we are not sure if they have any functions built-in with signed int.

#include <Arduino.h>
#include <SPI.h>
#include <Ticker.h>
#include <functions.h>
#include <Preferences.h>
#include <settings.h>

#include <WiFi.h>
#include <wifiConnect.h>
#include <HTTPClient.h>
#include <firmwareUpdate.h>

#include <ACS712.h>
#include <ADS1X15.h>

#include <device.h>
#include <beeper.h>
#include <sensorCalibrate.h>

#include <displayLGFX.h>
#include <renderUI.h>

No. We have only one I2C device and that is ADS1015.
Not sure if that helps but we are using tons of tickers to read from sensor values and call some other functions constantly with varying times.

Also, after couple of hours today, we realized ESP32 hangs and restarts itself if ACS.setADC(readADS1015, 5.0, 4095); is included.

This is our setup function below, if that helps.

void setup() {
    #if SERIAL_ENABLED
        Serial.begin(115200);
    #endif

    // WIRE BEGIN //
    Wire.begin(ADS1015_SDA, ADS1015_SCL);
    Wire.setClock(100000);

    // MACHINE INFO //
    device_setup_init();
    delay(10);

    // DISPLAY SETUP //
    display_setup_init();
    renderui_setup_init();
    delay(10);

    // MACHINE AUTH CHECK //
    delay(10);

    // ADS SETUP //
    //  initialize ADS1015, if fail => report
    if (ads1015.begin() == false)
    {
        Serial.println("ADS1x15 not found. Check wires and pins. Reboot.");
        while(1);
    }
    ads1015.setGain(1);                                 //  4.096V - 0=6.144V, 1=4.096V, 2=2.048V, 4=1.024V, 8=0.512V, 16=0.256V	
    ads1015.setDataRate(7);                             //  3300 SPS - 0=128,  1=250, 2=490, 3=920, 4=1600, 5=2400, 6=3300, 7=3300
    ads1015.setMode(1);                                 //  single mode
    Serial.print("ads1015.getDataRate: ");
    Serial.println(ads1015.getDataRate());
    ads1015.reset();

    ACS.setADC(wrapperACSPin, 3.3, 4095);
    ACS.autoMidPoint(0, 10);

    // PIN SETUP //
    pinMode(BEEPER_PIN, OUTPUT);

    pinMode(MOTOR_PIN_ENA, OUTPUT);
    pinMode(MOTOR_PIN_ROT, OUTPUT);
    pinMode(MOTOR_PIN_SPD, OUTPUT);
    pinMode(MOTOR_PIN_RELAY, OUTPUT);

    pinMode(SENSOR_BIN_PIN, INPUT_PULLDOWN);
    pinMode(SENSOR_DOOR_PIN, INPUT_PULLDOWN);

    digitalWrite(MOTOR_PIN_ENA, LOW);
    digitalWrite(MOTOR_PIN_RELAY, LOW);
    delay(10);

    // INITIALIZE SENSORS //
    sensorCheck();
    sensorsInit();
    delay(10);

    // MOTOR PWM SETUP //
    motorPWMSetup();
    delay(10);

    // CHECK SETTINGS FROM FILESYSTEM //
    checkSettings();
    delay(10);

    // LOAD SETTINGS FROM FILESYSTEM //
    loadSettings();
    delay(10);

    // SET SETTINGS FROM MEMORY //
    setSettingsValues();
    delay(10);

    // RUNTIME CHECK //
    runtimePreferences.begin("runtime");
    totalRuntimeInit();
    delay(10);

    // START TICKERS //
    tickersInit();
    delay(10);

    // WIFI SETUP //
    wifiSetup();
    delay(10);
    fotaSetup();
    delay(10);
}

@astorun
Copy link
Author

astorun commented Jan 10, 2024

@astorun Esp_timer library has sometimes problems, I found this describing wifi as disruptive force. https://www.esp32.com/viewtopic.php?t=25642

Maybe such interference is only there if the ticker and I2C runs on same core?

On default, wifi is turned off during startup and it wont powered off until user requests it.
Also in our trials, we did not turned on Wifi. So, it was always off. This is the code called during setup process when esp32 is booted to setup wifi.

void wifiSetup() {
  WiFi.setSleep(WIFI_PS_NONE); // Disable WiFi sleep
  WiFi.mode(WIFI_OFF);
  WiFi.onEvent(wiFiEvent);
  WiFi.setHostname(hostname.c_str()); /*ESP32 hostname set*/
}

@astorun
Copy link
Author

astorun commented Jan 10, 2024

If that helps, we have and SPI touchscreen running with LovyanGFX and LVGL library. Not sure if it is related. Here is the code below for screen initialization.


/////////////////////////////////////////////////////////////////
/*
  ESP32 | LVGL8 | Ep 0. GFX Setup (ft. LovyanGFX)
  Video Tutorial: https://youtu.be/IPCvQ4o_WP8
  Created by Eric N. (ThatProject)
*/
/////////////////////////////////////////////////////////////////

#include <lvgl.h>
//#define LGFX_USE_V1
#include <LovyanGFX.hpp>
#include "ui.h"

class LGFX : public lgfx::LGFX_Device
{
lgfx::Panel_ST7796      _panel_instance;
lgfx::Bus_SPI           _bus_instance;   // SPI
lgfx::Light_PWM         _light_instance;
lgfx::Touch_XPT2046     _touch_instance;

public:
  LGFX(void)
  {
    {
      auto cfg = _bus_instance.config();
      cfg.spi_host = SPI3_HOST;                 // (VSPI_HOST or HSPI_HOST)
      cfg.spi_mode = 0;                         // SPI (0 ~ 3)
      cfg.freq_write = 40000000;                // SPI (80MHz)
      cfg.freq_read  = 16000000;                // 
      cfg.spi_3wire  = false;                   //
      cfg.use_lock   = true;                    //
      cfg.dma_channel = SPI_DMA_CH_AUTO;        // Set the DMA channel (1 or 2. 0=disable)
      cfg.pin_sclk = 16;                        // LCD_SCLK
      cfg.pin_mosi = 17;                        // LCD_MOSI
      cfg.pin_miso = 18;                        // LCD_MISO     (-1 = disable)
      cfg.pin_dc   = 15;                        // LCD_DC       SPI (-1 = disable) Data Command control pin
      _bus_instance.config(cfg);                //
      _panel_instance.setBus(&_bus_instance);
    }

    {
      auto cfg = _panel_instance.config();
      cfg.pin_cs           =    11;             // LCD_CS      (-1 = disable)
      cfg.pin_rst          =    13;             // LCD_RST     (-1 = disable)
      cfg.pin_busy         =    -1;             // BUSY        (-1 = disable)
      cfg.memory_width     =   320;
      cfg.memory_height    =   480;
      cfg.panel_width      =   320;
      cfg.panel_height     =   480;
      cfg.offset_x         =     0;
      cfg.offset_y         =     0;
      cfg.offset_rotation  =     0;
      cfg.dummy_read_pixel =     8;
      cfg.dummy_read_bits  =     1;
      cfg.readable         =  true;
      cfg.invert           = false;
      cfg.rgb_order        = false;
      cfg.dlen_16bit       = false;
      cfg.bus_shared       =  true;
      _panel_instance.config(cfg);
    }

    {
      auto cfg = _light_instance.config();
      cfg.pin_bl = 14;                          // LCD_BL
      cfg.invert = false;
      cfg.freq   = 44100;
      cfg.pwm_channel = 7;
      _light_instance.config(cfg);
      _panel_instance.setLight(&_light_instance);
    }
    
    {
      auto cfg = _touch_instance.config();
      cfg.x_min      = 0;
      cfg.x_max      = 319;
      cfg.y_min      = 0;
      cfg.y_max      = 479;
      cfg.pin_int    = -1;                      // INT pin number to which is connected
      cfg.bus_shared = true;                    // Set to true when using a common bus with the screen
      cfg.offset_rotation = 0;                  // Adjustment when the display and touch direction do not match Set with a value of 0 to 7
      cfg.spi_host = SPI3_HOST;
      cfg.freq = 2500000;                       // SPI set clock
      cfg.pin_sclk = 16;                        // LCD_SCLK
      cfg.pin_mosi = 17;                        // LCD_MOSI
      cfg.pin_miso = 18;                        // LCD_MISO
      cfg.pin_cs   = 12;                        // LCD_TOUCH_CS
      _touch_instance.config(cfg);
      _panel_instance.setTouch(&_touch_instance);
    }

    setPanel(&_panel_instance);
  }
};

LGFX tft;

//Change to your screen resolution
static const uint32_t screenWidth  = 480;
static const uint32_t screenHeight = 320;
static lv_disp_draw_buf_t draw_buf;

/*Static or global buffer(s). The second buffer is optional*/
static lv_color_t buf_1[screenWidth * 10];
//static lv_color_t buf_2[screenWidth * 10];

//Display flushing 
void disp_flush( lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p )
{
   uint32_t w = ( area->x2 - area->x1 + 1 );
   uint32_t h = ( area->y2 - area->y1 + 1 );

   tft.startWrite();
   tft.setAddrWindow( area->x1, area->y1, w, h );
   //tft.pushColors( ( uint16_t * )&color_p->full, w * h, true );
   tft.writePixels((lgfx::rgb565_t *)&color_p->full, w * h);
   tft.endWrite();

   lv_disp_flush_ready( disp );
}

//Read the touchpad
void touchpad_read( lv_indev_drv_t * indev_driver, lv_indev_data_t * data )
{
   uint16_t touchX, touchY;
   bool touched = tft.getTouch( &touchX, &touchY);
   if( !touched )
   {
      data->state = LV_INDEV_STATE_REL;
   }
   else
   {
      data->state = LV_INDEV_STATE_PR;

      //Set the coordinates
      data->point.x = touchX;
      data->point.y = touchY;

   }
}

//Give feedback to the user
void feedback_audio(_lv_indev_drv_t *, uint8_t event) {

    if (event == LV_EVENT_PRESSED) {
      Serial.println("LV EVENT PRESSED DETECTED");
    }

    if (event == LV_EVENT_RELEASED) {
      Serial.println("LV EVENT RELEASED DETECTED");
    }
}

void display_setup_init()
{
  tft.setBrightness(0);
  tft.begin();
  tft.setRotation(1);

  lv_init();
  uint16_t calData[] = { 3787, 3868, 3835, 200, 205, 3885, 168, 193};
  tft.setTouchCalibrate(calData);

  /*
  std::uint16_t fg = TFT_WHITE;
  std::uint16_t bg = TFT_BLACK;
  if (tft.isEPD()) std::swap(fg, bg);
  uint16_t calibrationData[8];
  tft.calibrateTouch(calibrationData, fg, bg, std::max(tft.width(), tft.height()) >> 3);

  for(int i=0; i<8; i++){
    Serial.print(calibrationData[i]);
    Serial.print(", ");
  }
  */

  /*Initialize `disp_buf` with the buffer(s). With only one buffer use NULL instead buf_2 */
  lv_disp_draw_buf_init(&draw_buf, buf_1, NULL, screenWidth * 10);

  //Initialize the display
  static lv_disp_drv_t disp_drv;
  lv_disp_drv_init( &disp_drv );

  //Change the following line to your display resolution
  disp_drv.hor_res = screenWidth;
  disp_drv.ver_res = screenHeight;
  disp_drv.flush_cb = disp_flush;
  disp_drv.draw_buf = &draw_buf;
  lv_disp_drv_register( &disp_drv );

  //Initialize the input device driver
  static lv_indev_drv_t indev_drv;
  lv_indev_drv_init(&indev_drv);                                 //Basic initialization
  indev_drv.type = LV_INDEV_TYPE_POINTER;                        //Touch pad is a pointer-like device
  indev_drv.read_cb = touchpad_read;                             //Set your driver function
  indev_drv.feedback_cb = feedback_audio;                           //Set your driver function
  lv_indev_t * disp_indev = lv_indev_drv_register(&indev_drv);   //Register the driver in LVGL and save the created input device object

  ui_init();
}

void displayInit()
{
    lv_timer_handler();
}

@RobTillaart
Copy link
Owner

that is a lot of information...

Also, after couple of hours today, we realized ESP32 hangs and restarts itself if ACS.setADC(readADS1015, 5.0, 4095); is included.

Does this mean it works for some hours and then it stops?
Or does it not work at all?

@RobTillaart
Copy link
Owner

Is it possible to add another I2C device (eg pcf8574) and see if that breaks the project too?
(my gut feeling points to i2C, just don't know how exactly it interferes)

@astorun
Copy link
Author

astorun commented Jan 10, 2024

Ok I think we found the culprit!
If we set a sampling value, it starts doing some weird stuff and slows down the esp32.
sensorCurrentValueRaw = ACS.mA_DC(10);

However if we do not provide sampling value, it works as intended!
sensorCurrentValueRaw = ACS.mA_DC();

From the mainpage of AC712 library, there is an description how cycles are working.
If we understand correctly, providing a sample value reads the sensor by given number or times and averages to smooth out the results.

The library has 4 core functions:

float mA_peak2peak(frequency = 50, cycles = 1)
float mA_DC(cycles = 1)
float mA_AC(frequency = 50, cycles = 1)
float mA_AC_sampling(frequency = 50, cycles = 1)
The parameter cycles is used to measure multiple cycles and average them.

To measure DC current a single analogRead() with conversion math is sufficient to get a value. To stabilize the signal analogRead() is called at least twice.

@astorun
Copy link
Author

astorun commented Jan 10, 2024

Is it possible to add another I2C device (eg pcf8574) and see if that breaks the project too? (my gut feeling points to i2C, just don't know how exactly it interferes)

Sadly no, we are using a custom manufactured PCB and there is no way we can add another device.

@astorun
Copy link
Author

astorun commented Jan 10, 2024

By your suggestion that I2C might be the issue, I increased the I2C clock from 100000 to 400000 to see if that is going to make any difference and tested with sensorCurrentValueRaw = ACS.mA_DC(10);.

However, the issue is still persists. We reduced the wire speed to 100000 due to pcb limitation. We are opted to use 10k resistors instead of 1k on I2C SDA/SCL pins.

On ADS1015 datasheet it suggests using lower resistor values for faster I2C speeds, around 2k. Not sure if that contributes the issue.

@RobTillaart
Copy link
Owner

Ok I think we found the culprit!
If we set a sampling value, it starts doing some weird stuff and slows down the esp32.
sensorCurrentValueRaw = ACS.mA_DC(10);

That might be blocking indeed (not for minutes but enough to corrupt scheduling).

Maybe adding a call to yield() in some loops of the ACS712 library to provide context switching and give the ticket thread a chance to run.
(Should be easy to test)

To be continued tomorrow (for me at least)

@astorun
Copy link
Author

astorun commented Jan 10, 2024

Ok I think we found the culprit!
If we set a sampling value, it starts doing some weird stuff and slows down the esp32.
sensorCurrentValueRaw = ACS.mA_DC(10);

That might be blocking indeed (not for minutes but enough to corrupt scheduling).

Maybe adding a call to yield() in some loops of the ACS712 library to provide context switching and give the ticket thread a chance to run. (Should be easy to test)

To be continued tomorrow (for me at least)

Thanks for the update and possible solution! We will be waiting for any update. Until then we are going to run the library without any sampling. Moreover, adding ADS1015 seems doing a better job than ESP32 internal ADC and we might not need a software smoothing at all :).

@RobTillaart
Copy link
Owner

The esp32 (s1?) has non linearities in the adc, which cannot be removed by averaging only. These need end of scale adjustments.

@RobTillaart
Copy link
Owner

From the mainpage of AC712 library, there is an description how cycles are working.
If we understand correctly, providing a sample value reads the sensor by given number or times and averages to smooth out the results.

Yes, as the internal ADC's of various boards are not that precise this helps to improve reading to certain extend.
, ghly a factor 2 times more samples improves 0.5 bit accuracy.
So factor 4 gives 1 bit, factor 8 gives 1.5 bit, factor 16 gives 2 bits improvement. (noise can be around half a bit).

Problem with oversampling is that the duration of the measurement increases. Thus if you have a fluctuating signal oversampling will smooth out part of the detailed signal.

Today I will create a develop branch you can test to see if the yield() solution meets your needs. .

@RobTillaart
Copy link
Owner

Created develop branch - https://github.com/RobTillaart/ACS712/tree/develop -
This version calls yield in mA_DC() every 3 analogReads so it is far less blocking but only for DC.
For AC the solution is to create a separate thread for the INA226 I think.
All AC measurements needs many samples to get the signal,

@astorun
Copy link
Author

astorun commented Jan 11, 2024

We tested with develop branch and result is the same. The delay is still there.
Moreover we discovered another issue.

While I was looking at the library we saw a new midpoint feature specifically for DC.
uint16_t autoMidPointDC(uint16_t cycles = 1)

Decided to give it a try with a recommended value of 100.
ACS.autoMidPointDC(100);

However this time we got the following error and firmware refused to work alltogether.
[ 4485][E][Wire.cpp:513] requestFrom(): i2cRead returned Error 263

Not sure if both issues are related.

@astorun
Copy link
Author

astorun commented Jan 12, 2024

Also if it helps, our hardware is ESP32-S3 N8R2

@RobTillaart
Copy link
Owner

Error 263 is a timeout google told me, one of the causes can be bad wiring. It seems to occur more often with ESP32 and other I2C devices. So it seems not a problem of the ads1x15 library, but an ESP32 one.

E.g. with a Sensirion sensor
https://www.google.com/url?sa=t&source=web&rct=j&opi=89978449&url=https://github.com/espressif/arduino-esp32/issues/8056&ved=2ahUKEwj7pL3-ndeDAxWn_rsIHUz8CxgQFnoECBsQAQ&usg=AOvVaw2K9RSnKXVA5faMQ9_j30yh

Possibly the ESP32 wire library allows to set a wire timeout to shorten the delay.
An other option might be to check with an other version of the ESP32 compiler version.

@RobTillaart
Copy link
Owner

We tested with develop branch and result is the same. The delay is still there.

Thanks for testing,

Does your code uses explicit multithreading, and have you divided the liad over the two cores based upon the losd per thread?

@astorun
Copy link
Author

astorun commented Jan 12, 2024

Thanks for the quick reply.

No, we do not have any multithreading in our application.

We suspect it might be caused by our resistor choice on I2C lines that might not allowing full 400000 clock speed.
For now everything working as intended without providing any sampling value.

We will revise our hardware and test it again to see if it is going to make any difference.

@RobTillaart
Copy link
Owner

Do you have an oscilloscope to see the sinal quality of the I2C lines?

The longer the wires the smaller the pull up resistors on the SDA an SCL. My rule of thumb is
25 cm == 10K
50 cm == 4K7
100 cm == 2K2
250 cm == 1K
Formula: R == 250 / length

Furthermore you should use shielded wire for the I2C if there is a chance of noise from motors etc. Either from your own project or the environment your product has to operate in.

@RobTillaart
Copy link
Owner

@astorun
Any progress to report?

@astorun
Copy link
Author

astorun commented Feb 4, 2024

Sorry for the late reply. We have been extremely busy lately with the hardware itself.

Today, we had the chance to try different things with the library, however it does not play out again.
At the end, we decided to removed the library and used the following code below to get the readings from ADS1X15 library and manually calculated the current with the following code below.

void readCurrent() {
    int16_t currentADCVal = ads1015.readADC(SENSOR_ACS712_ADSPIN);

    // Convert the ADC value to voltage
    float currentADCVolt = ads1015.toVoltage(currentADCVal);
    sensorCurrentValueFloat = (currentADCVolt - 2.5) / 0.100;
    sensorCurrentValueInt = sensorCurrentValueFloat * 1000;
}

We are calling the readCurrent function with ticker function within every 5ms.
readCurrentTicker.attach_ms(readCurrentInterval, readCurrent);

It is working without any issues.

I believe wrapper implementation is not efficient enough to deal with the complex code structure or calling it too often causes other function to block or hang the esp32.

I suggest a different implementation method of ACS712 library with ADS1X15 without using any wrapper function. We do not know how ACS712 library the works in the background, but using it with ADS1X15 causing some sort of bottleneck under specific scenarios for our application, where timing is critical. Maybe implementing another method to adjust the time between each readings or finding another method without blocking too much might solve the issue.

Thanks for the help though.

@RobTillaart
Copy link
Owner

Thanks for the update,
Your solution is straightforward and if it works then you should use it. Nothing beats simplicity.

If there are no more questions you may close the issue.

@astorun astorun closed this as completed Feb 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants