Controlling ADC with new I2C version

leenowell
Posts: 155
Joined: Tue Jan 15, 2019 1:50 pm

Controlling ADC with new I2C version

Postby leenowell » Mon Jan 06, 2025 8:51 am

Hi All,

I haven't used I2C before and am trying to use the new version to control an ADC. I have tried looking for examples but struggling to find any based on IDF. I seem to be able to set it up ok but now don't know how to send "instructions" to the chip. At the moment I just want to read the value of each ADC pin but can't see what I need to send to it to tell it which pin to give me the value of. Having gone past this, I then want to test out the other features of the chip e.g. differential readings. Also, my board says ADS1015/ ADS1115 so not sure which chip I actually have on it. Is there an "instruction" I can send to the chip to ask it?

Many thanks for your help

Lee.

MicroController
Posts: 2045
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: Controlling ADC with new I2C version

Postby MicroController » Mon Jan 06, 2025 12:04 pm

With the ADS1x15 you have to write the MUX bits in the config register to select a channel. All subsequent conversion will then sample that selected channel. (This means writing the full config register to the ADC for every channel change, including all the other config bits.)

I can't see a defined way to identify the ADS variant from software. You may want to look if the board has some kind of (pen) marking as to the model number, look at the chip itself, or perform a bunch of AD conversions and look at the results. The ADS1015 has only 12 bits of resolution, so the lowest 4 bits of the 16-bit conversion result are always 0, while I'd expect to see some noise in those bits from an ADS1115.

Are you using a library or accessing the ADC 'directly' via I2C?

Btw: Notice that the ADS1x15 communicates in big-endian byte order. The ESP32 is little-endian, so you need to remember to swap bytes when reading or writing a (u)int16_t to the ADC.

Btw(2): You may want to read the ADS1115's datasheet; specifically "7.5 Programming". It explains how to use the I2C interface in a very condensed way.

MicroController
Posts: 2045
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: Controlling ADC with new I2C version

Postby MicroController » Mon Jan 06, 2025 12:50 pm

With the IDF's "new" I2C driver, communication with the ADC could look like this:

Code: Select all

esp_err_t writeAdsRegister(i2c_master_dev_handle_t i2c_dev, uint8_t regNumber, uint16_t value) {
  uint8_t bytes[3]; // Need to send 3 bytes: first the register number, then 16 bits of data for the register.

  bytes[0] = regNumber; // First byte written selects the register
  bytes[1] = (uint8_t)(value >> 8); // high byte
  bytes[2] = (uint8_t)value; // low byte
  
  return i2c_master_transmit(i2c_dev, &bytes[0], sizeof(bytes), 1000/portTICK_PERIOD_MS);
}

esp_err_t readAdsRegister(i2c_master_dev_handle_t i2c_dev, uint8_t regNumber, uint16_t* out_value) {

  esp_err_t r = i2c_master_transmit(i2c_dev, &regNumber, 1, 1000/portTICK_PERIOD_MS); // WRITE the register number
  
  if (r == ESP_OK) {
    uint8_t bytes[2];
    r = i2c_master_receive(i2c_dev, &bytes[0], sizeof(bytes), 1000/portTICK_PERIOD_MS); // READ two bytes of data from the register selected
    if (r == ESP_OK) {
      *out_value = (((uint16_t)bytes[0]) << 8) | bytes[1]; // big-endian -> little-endian
    }
  }
  return r;
}

leenowell
Posts: 155
Joined: Tue Jan 15, 2019 1:50 pm

Re: Controlling ADC with new I2C version

Postby leenowell » Mon Jan 06, 2025 1:32 pm

Thanks very much for your reply it is much appreciated.

Yes I am trying to use the IDF's new driver directly. I read the data sheet before but TBH assumed the IDF was doing all that for me although it did go over my head a bit :) Your example was super helpful many thanks. I *think* I am starting to understand it now. I have tried using your code to do the read but am getting errors for I2C.

The code to init I2C and then my modified version of the read following your example is as follows

Code: Select all

#include <stdio.h>
#include "sdkconfig.h"
#include "esp_log.h"
#include "driver/i2c_master.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

i2c_master_bus_handle_t bus_handle;
i2c_master_dev_handle_t dev_handle;

#define ADC_I2C_ADDRESS 0x48
#define ADC_READ_TIMEOUT 100
#define ADC_I2C_FREQUENCY_HZ 400000
#define I2C_MASTER_SCL 4
#define I2C_MASTER_SDA 5

static const char *I2C_ADC_TAG = "I2C_ADC";		// Tag used for logging purposes

void I2C_ADC_init()
{
    i2c_master_bus_config_t bus_config = {
        .i2c_port = I2C_NUM_0,
        .sda_io_num = I2C_MASTER_SDA,
        .scl_io_num = I2C_MASTER_SCL,
        .clk_source = SOC_MOD_CLK_APB,
        .glitch_ignore_cnt = 7,
        .flags.enable_internal_pullup = true,
    };
    ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &bus_handle));

    i2c_device_config_t dev_config = {
        .dev_addr_length = I2C_ADDR_BIT_LEN_7,
        .device_address = ADC_I2C_ADDRESS,
        .scl_speed_hz = ADC_I2C_FREQUENCY_HZ,
    };
    ESP_ERROR_CHECK(i2c_master_bus_add_device(bus_handle, &dev_config, &dev_handle));
    ESP_LOGI(I2C_ADC_TAG, "I2C initialized successfully");
}

esp_err_t I2C_ADC_read(uint8_t nRegNumb, uint16_t *data, int len)
{
	esp_err_t r = i2c_master_transmit(dev_handle, &nRegNumb, 1, 1000/portTICK_PERIOD_MS); // WRITE the register number
  
	if (r == ESP_OK) 
	{
		uint8_t bytes[2];
		r = i2c_master_receive(dev_handle, &bytes[0], sizeof(bytes), 1000/portTICK_PERIOD_MS); // READ two bytes of data from the register selected
		if (r == ESP_OK) 
		{
			*data = (((uint16_t)bytes[0]) << 8) | bytes[1]; // big-endian -> little-endian
		}
	}

	return r;

}
Then the code calling the read is as follows

Code: Select all

	I2C_ADC_read(2, I2C_ADC_data, 1);
        ESP_LOGI(BBQ_THERMOMETER_TAG, "DoTask: I2C_ADC 2 [%d]", I2C_ADC_data[0]);
It seems it initialise ok but then on the transmit I get the following errors
E (923) i2c.master: I2C software timeout
E (923) i2c.master: s_i2c_synchronous_transaction(918): I2C transaction failed
E (923) i2c.master: i2c_master_multi_buffer_transmit(1180): I2C transaction failed
I (923) BBQThermometer: DoTask: I2C_ADC 2 [0]

***ERROR*** A stack overflow in task sys_evt has been detected.


Backtrace: 0x40081f45:0x3ffbd430 0x4008aaa1:0x3ffbd450 0x4008b96e:0x3ffbd470 0x4008ccaf:0x3ffbd4f0 0x4008ba78:0x3ffbd510 0x4008ba2a:0x3ffbd4c4 |<-CORRUPTED
Any ideas? I feel I am much closer now thanks to your help.

Thanks

Lee.
Last edited by leenowell on Mon Jan 06, 2025 2:49 pm, edited 1 time in total.

MicroController
Posts: 2045
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: Controlling ADC with new I2C version

Postby MicroController » Mon Jan 06, 2025 2:44 pm

Hmm.
What ESP SoC are you using?

Code: Select all

.clk_source = SOC_MOD_CLK_APB,
doesn't look right.

Also make sure you have the correct pins connected.

Code: Select all

***ERROR*** A stack overflow in task sys_evt has been detected.
Seems like something's corrupting some memory.
Can you share the code leading up to I2C_ADC_read(2, I2C_ADC_data, 1)?

leenowell
Posts: 155
Joined: Tue Jan 15, 2019 1:50 pm

Re: Controlling ADC with new I2C version

Postby leenowell » Mon Jan 06, 2025 2:58 pm

Thanks for your reply. I updated my post to say that the error is on the transmit call rather than the read call if that matters. Regarding the clock source I just copied that from some sample code in IDF (I think) so can change it if needed. Also checked the wiring and I believe it is correct.

The code up to the call is as follows. It is called from within the DoTask function

Code: Select all

#include <stdio.h>
#include <inttypes.h>
#include <math.h>
#include <string.h>
#include "esp_err.h"
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h"
#include "esp_now.h"
#include "esp_sleep.h"
#include "esp_log.h"
#include "esp_mac.h"
#include "hal/adc_types.h"
#include "nvs_flash.h"
#include <esp_wifi.h>
#include "esp_adc/adc_oneshot.h"
#include "esp_adc/adc_cali.h"
#include "esp_adc/adc_cali_scheme.h"

#include "BBQThermometer.h"
#include "wifi_node.h"
#include "I2C_ADC.h"

#define THERMOMETER_PIN ADC_CHANNEL_4
#define NUMB_READS 5
#define THERMOMTER_ADC_ATTEN ADC_ATTEN_DB_12

#define THERMOMTER_ADC1_CHAN0          ADC_CHANNEL_4
#define THERMOMTER_ADC1_CHAN1          ADC_CHANNEL_7


static const char *BBQ_THERMOMETER_TAG = "BBQThermometer";		// Tag used for logging purposes
uint8_t nWiFiChannel = 11; // All devices need to be on the same channel so need to put this in the library somewhere same for encryption key
uint8_t macAddressToSendTo[ESP_NOW_ETH_ALEN] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };  // MAC to send data to default to broadcast to all
//uint8_t macAddressToSendTo[ESP_NOW_ETH_ALEN] = { 0xF0, 0x86, 0x20, 0x13, 0xB4, 0x33 };  // MAC to send data to default to broadcast to all

uint8_t lmk[ESP_NOW_KEY_LEN];           /**< ESPNOW peer local master key that is used to encrypt data */

static void cbOnESPNowReceive(const esp_now_recv_info_t *recv_info, const uint8_t *data, int len)
{
	ESP_LOGI(BBQ_THERMOMETER_TAG, "cbOnESPNowReceive: Received data from [" MACSTR "] Data is [%s]", MAC2STR(recv_info->src_addr), (const char*) data);
}

static void cbOnESPNowSend(const uint8_t *mac_addr, esp_now_send_status_t status)
{
	ESP_LOGI(BBQ_THERMOMETER_TAG, "cbOnESPNowSend: Sent data to [" MACSTR "] Status is [%d]", MAC2STR(mac_addr),(int) status);

}

void PrintADCCaliSettings()
{
	adc_cali_scheme_ver_t adcScheme = { 0 };
	ESP_ERROR_CHECK(adc_cali_check_scheme(&adcScheme));
	ESP_LOGI(BBQ_THERMOMETER_TAG, "CheckADCCaliSettings: ADC Cali scheme [%d] is [%s]", (int) adcScheme
		, (adcScheme==ADC_CALI_SCHEME_VER_LINE_FITTING) ? "ADC_CALI_SCHEME_VER_LINE_FITTING" 
		: (adcScheme == ADC_CALI_SCHEME_VER_CURVE_FITTING) ? "ADC_CALI_SCHEME_VER_CURVE_FITTING" 
		: "unknown");
	
	adc_cali_line_fitting_efuse_val_t adcEfuse = { 0 };
	ESP_ERROR_CHECK(adc_cali_scheme_line_fitting_check_efuse(&adcEfuse));
	ESP_LOGI(BBQ_THERMOMETER_TAG, "CheckADCCaliSettings: ADC eFuse [%d] is [%s]", (int) adcEfuse
		, (adcEfuse==ADC_CALI_LINE_FITTING_EFUSE_VAL_EFUSE_TP) ? "ADC_CALI_LINE_FITTING_EFUSE_VAL_EFUSE_TP" 
		: (adcEfuse == ADC_CALI_LINE_FITTING_EFUSE_VAL_EFUSE_VREF) ? "ADC_CALI_LINE_FITTING_EFUSE_VAL_EFUSE_VREF" 
		: (adcEfuse == ADC_CALI_LINE_FITTING_EFUSE_VAL_DEFAULT_VREF) ? "ADC_CALI_LINE_FITTING_EFUSE_VAL_DEFAULT_VREF" 
		: "unknown");
}



static void InitialiseCalibration(adc_unit_t unit, adc_channel_t channel, adc_atten_t atten, adc_cali_handle_t *out_handle)
{
    ESP_LOGI(BBQ_THERMOMETER_TAG, "InitialiseCalibration: calibration scheme version is %s", "Line Fitting");
    adc_cali_line_fitting_config_t cali_config = {
        .unit_id = unit,
        .atten = atten,
        .bitwidth = ADC_BITWIDTH_DEFAULT,
//        .default_vref=1100,  // LEE Need to see if we can determine this as the chip doesn't have this stored in eFuse
    };
    ESP_ERROR_CHECK(adc_cali_create_scheme_line_fitting(&cali_config, out_handle));
}


void InitialiseADC(adc_oneshot_unit_handle_t *pADCHandle, adc_cali_handle_t *pADCCaliHandleChan0, adc_cali_handle_t *pADCCaliHandleChan1)
{
// Initialise the onboard ADC first
	// Print the chip's ADC settings the rest of this initialisation is hard coded to use what was seen when I run it (i.e. no eFus, line fitting etc.)
	PrintADCCaliSettings();
	
	// Get handle to ADC1.  ADC2 is linked to WiFi so can't use both hence no good for this scenario
    adc_oneshot_unit_init_cfg_t init_config1 = {
        .unit_id = ADC_UNIT_1,
    };
    ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config1, pADCHandle));

	// Configure ADC 1
    adc_oneshot_chan_cfg_t config = {
        .atten = THERMOMTER_ADC_ATTEN,
        .bitwidth = ADC_BITWIDTH_DEFAULT,
    };
    ESP_ERROR_CHECK(adc_oneshot_config_channel(*pADCHandle, THERMOMTER_ADC1_CHAN0, &config));
    ESP_ERROR_CHECK(adc_oneshot_config_channel(*pADCHandle, THERMOMTER_ADC1_CHAN1, &config));

	// Initialise calibratation of both ADC 1 channels
    adc_cali_handle_t adc1_cali_chan0_handle = NULL;
    adc_cali_handle_t adc1_cali_chan1_handle = NULL;
    InitialiseCalibration(ADC_UNIT_1, THERMOMTER_ADC1_CHAN0, THERMOMTER_ADC_ATTEN, pADCCaliHandleChan0);
    InitialiseCalibration(ADC_UNIT_1, THERMOMTER_ADC1_CHAN1, THERMOMTER_ADC_ATTEN, pADCCaliHandleChan1);

// Initialise the external ADC
	I2C_ADC_init();
}

void InitialiseESPNow()
{
// Start espnow
    ESP_ERROR_CHECK( esp_now_init() );
    ESP_ERROR_CHECK( esp_now_register_send_cb(cbOnESPNowSend) );
    ESP_ERROR_CHECK( esp_now_register_recv_cb(cbOnESPNowReceive) );
    
    // The following are for the power save options. Every interval (ms) stay on for window (ms)
    // remove for now but need to see if this is better than deep sleeping 
//    ESP_ERROR_CHECK( esp_now_set_wake_window(CONFIG_ESPNOW_WAKE_WINDOW) );
//    ESP_ERROR_CHECK( esp_wifi_connectionless_module_set_wake_interval(CONFIG_ESPNOW_WAKE_INTERVAL) );
	
}

void DoTask()
{
	long lADCTotal = 0;
	long lVoltageTotal = 0;
	int nADCValue = 0;  // LEE check this as the example has an int [10] array for this for some reason.
	int nVoltage = 0;
	int i;
    uint16_t I2C_ADC_data[2];

		
	adc_oneshot_unit_handle_t 	adcHandle;
	adc_cali_handle_t			adcCaliHandleChan0 = NULL;
	adc_cali_handle_t			adcCaliHandleChan1 = NULL;
	
	InitialiseADC(&adcHandle, &adcCaliHandleChan0, &adcCaliHandleChan1);

	for (i=0; i<NUMB_READS; i++)
	{
        ESP_ERROR_CHECK(adc_oneshot_read(adcHandle, THERMOMTER_ADC1_CHAN0, &nADCValue));
        lADCTotal += nADCValue;
        ESP_LOGI(BBQ_THERMOMETER_TAG, "DoTask: ADC%d Channel[%d] ADC Raw Data: [%d] ADC Total[%ld]", ADC_UNIT_1 + 1, THERMOMTER_ADC1_CHAN0, nADCValue, lADCTotal);
        ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adcCaliHandleChan0, nADCValue, &nVoltage));
		lVoltageTotal += nVoltage;
        ESP_LOGI(BBQ_THERMOMETER_TAG, "DoTask: ADC%d Channel[%d] Cali Voltage: [%d mV] Voltage Total [%ld mV]", ADC_UNIT_1 + 1, THERMOMTER_ADC1_CHAN0, nVoltage, lVoltageTotal);

        ESP_ERROR_CHECK(adc_oneshot_read(adcHandle, THERMOMTER_ADC1_CHAN1, &nADCValue));
        lADCTotal += nADCValue;
        ESP_LOGI(BBQ_THERMOMETER_TAG, "DoTask: ADC%d Channel[%d] ADC Raw Data: [%d] ADC Total[%ld]", ADC_UNIT_1 + 1, THERMOMTER_ADC1_CHAN1, nADCValue, lADCTotal);
        ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adcCaliHandleChan1, nADCValue, &nVoltage));
		lVoltageTotal += nVoltage;
        ESP_LOGI(BBQ_THERMOMETER_TAG, "DoTask: ADC%d Channel[%d] Cali Voltage: [%d mV] Voltage Total [%ld mV]", ADC_UNIT_1 + 1, THERMOMTER_ADC1_CHAN1, nVoltage, lVoltageTotal);

		I2C_ADC_read(2, I2C_ADC_data, 1);
        ESP_LOGI(BBQ_THERMOMETER_TAG, "DoTask: I2C_ADC 2 [%d]", I2C_ADC_data[0]);
		I2C_ADC_read(3, I2C_ADC_data, 1);
        ESP_LOGI(BBQ_THERMOMETER_TAG, "DoTask: I2C_ADC 3 [%d]", I2C_ADC_data[0]);
	}

	// average the reading
	nADCValue = round(lADCTotal/(NUMB_READS*2)); // *2 because we are reading 2 channels in each iteration
	nVoltage = round(lVoltageTotal/ (NUMB_READS*2));
    ESP_LOGI(BBQ_THERMOMETER_TAG, "DoTask: Finished reading averages are ADC [%d]  Voltage [%d mV]", nADCValue, nVoltage );

	char sMessage[100];
    sprintf((char*) sMessage, "<probe1_temp>%d|%d</probe1_temp>", nADCValue, nVoltage);

	ESP_LOGI(BBQ_THERMOMETER_TAG, "DoTask: about to send message to [" MACSTR "] message [%s] length [%d]", MAC2STR(macAddressToSendTo), sMessage, strlen(sMessage)+1);
 	ESP_ERROR_CHECK(esp_now_send(macAddressToSendTo, (void*) sMessage, strlen(sMessage)+1));

    for (int i = 1; i >= 0; i--) {
        printf("Restarting in %d seconds...\n", i);
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
    printf("Restarting now.\n");
 	
 	const int nSleepTime = 5;
    ESP_LOGI(BBQ_THERMOMETER_TAG, "DoTask: Entering deep sleep for %d seconds", nSleepTime);
    esp_deep_sleep(500000LL * nSleepTime);

}

void cbOnWifiStarted()
{
	uint8_t nChannel1 = 0;
	wifi_second_chan_t nChannel2;

	ESP_LOGI(BBQ_THERMOMETER_TAG, "app_main: About to set channel to [%d]", nWiFiChannel);
	ESP_ERROR_CHECK(esp_wifi_set_channel(nWiFiChannel, nChannel2));

	
	ESP_ERROR_CHECK(esp_wifi_get_channel(&nChannel1, &nChannel2));	
	ESP_LOGI(BBQ_THERMOMETER_TAG, "cbOnWifiStarted: Channel 1 [%d], Channel 2 [%d]", (int) nChannel1, (int) nChannel2);
	
	
	ESP_LOGI(BBQ_THERMOMETER_TAG, "cbOnWifiStarted: Starting ESPNow");
	InitialiseESPNow();	

	// Add broadcast address as a peer	
    esp_now_peer_info_t *peer = malloc(sizeof(esp_now_peer_info_t));
    memset(peer, 0, sizeof(esp_now_peer_info_t));
    peer->channel = nWiFiChannel;
    peer->ifidx = ESP_IF_WIFI_STA;
    peer->encrypt = false;
    memcpy(peer->peer_addr, macAddressToSendTo, ESP_NOW_ETH_ALEN);
    ESP_ERROR_CHECK( esp_now_add_peer(peer) );
    free(peer);
    
    DoTask();
}

void app_main(void)
{
// Initialize NVS
    esp_err_t err = nvs_flash_init();
    if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        // NVS partition was truncated and needs to be erased
        // Retry nvs_flash_init
        ESP_ERROR_CHECK(nvs_flash_erase());
        err = nvs_flash_init();
    }
    ESP_ERROR_CHECK( err );

// Start wifi first
	ESP_LOGI(BBQ_THERMOMETER_TAG, "app_main: Starting Wifi");
	wifi_node_init(cbOnWifiStarted);
	wifi_node_start();

/*
    for (int i = 10; i >= 0; i--) {
        printf("Restarting in %d seconds...\n", i);
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
    printf("Restarting now.\n");
    fflush(stdout);
    esp_restart();
*/
}


leenowell
Posts: 155
Joined: Tue Jan 15, 2019 1:50 pm

Re: Controlling ADC with new I2C version

Postby leenowell » Mon Jan 06, 2025 2:59 pm

Thanks for your reply. I updated my post to say that the error is on the transmit call rather than the read call if that matters. Regarding the clock source I just copied that from some sample code in IDF (I think) so can change it if needed. Also checked the wiring and I believe it is correct.

The code up to the call is as follows. It is called from within the DoTask function

Code: Select all

#include <stdio.h>
#include <inttypes.h>
#include <math.h>
#include <string.h>
#include "esp_err.h"
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h"
#include "esp_now.h"
#include "esp_sleep.h"
#include "esp_log.h"
#include "esp_mac.h"
#include "hal/adc_types.h"
#include "nvs_flash.h"
#include <esp_wifi.h>
#include "esp_adc/adc_oneshot.h"
#include "esp_adc/adc_cali.h"
#include "esp_adc/adc_cali_scheme.h"

#include "BBQThermometer.h"
#include "wifi_node.h"
#include "I2C_ADC.h"

#define THERMOMETER_PIN ADC_CHANNEL_4
#define NUMB_READS 5
#define THERMOMTER_ADC_ATTEN ADC_ATTEN_DB_12

#define THERMOMTER_ADC1_CHAN0          ADC_CHANNEL_4
#define THERMOMTER_ADC1_CHAN1          ADC_CHANNEL_7


static const char *BBQ_THERMOMETER_TAG = "BBQThermometer";		// Tag used for logging purposes
uint8_t nWiFiChannel = 11; // All devices need to be on the same channel so need to put this in the library somewhere same for encryption key
uint8_t macAddressToSendTo[ESP_NOW_ETH_ALEN] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };  // MAC to send data to default to broadcast to all
//uint8_t macAddressToSendTo[ESP_NOW_ETH_ALEN] = { 0xF0, 0x86, 0x20, 0x13, 0xB4, 0x33 };  // MAC to send data to default to broadcast to all

uint8_t lmk[ESP_NOW_KEY_LEN];           /**< ESPNOW peer local master key that is used to encrypt data */

static void cbOnESPNowReceive(const esp_now_recv_info_t *recv_info, const uint8_t *data, int len)
{
	ESP_LOGI(BBQ_THERMOMETER_TAG, "cbOnESPNowReceive: Received data from [" MACSTR "] Data is [%s]", MAC2STR(recv_info->src_addr), (const char*) data);
}

static void cbOnESPNowSend(const uint8_t *mac_addr, esp_now_send_status_t status)
{
	ESP_LOGI(BBQ_THERMOMETER_TAG, "cbOnESPNowSend: Sent data to [" MACSTR "] Status is [%d]", MAC2STR(mac_addr),(int) status);

}

void PrintADCCaliSettings()
{
	adc_cali_scheme_ver_t adcScheme = { 0 };
	ESP_ERROR_CHECK(adc_cali_check_scheme(&adcScheme));
	ESP_LOGI(BBQ_THERMOMETER_TAG, "CheckADCCaliSettings: ADC Cali scheme [%d] is [%s]", (int) adcScheme
		, (adcScheme==ADC_CALI_SCHEME_VER_LINE_FITTING) ? "ADC_CALI_SCHEME_VER_LINE_FITTING" 
		: (adcScheme == ADC_CALI_SCHEME_VER_CURVE_FITTING) ? "ADC_CALI_SCHEME_VER_CURVE_FITTING" 
		: "unknown");
	
	adc_cali_line_fitting_efuse_val_t adcEfuse = { 0 };
	ESP_ERROR_CHECK(adc_cali_scheme_line_fitting_check_efuse(&adcEfuse));
	ESP_LOGI(BBQ_THERMOMETER_TAG, "CheckADCCaliSettings: ADC eFuse [%d] is [%s]", (int) adcEfuse
		, (adcEfuse==ADC_CALI_LINE_FITTING_EFUSE_VAL_EFUSE_TP) ? "ADC_CALI_LINE_FITTING_EFUSE_VAL_EFUSE_TP" 
		: (adcEfuse == ADC_CALI_LINE_FITTING_EFUSE_VAL_EFUSE_VREF) ? "ADC_CALI_LINE_FITTING_EFUSE_VAL_EFUSE_VREF" 
		: (adcEfuse == ADC_CALI_LINE_FITTING_EFUSE_VAL_DEFAULT_VREF) ? "ADC_CALI_LINE_FITTING_EFUSE_VAL_DEFAULT_VREF" 
		: "unknown");
}



static void InitialiseCalibration(adc_unit_t unit, adc_channel_t channel, adc_atten_t atten, adc_cali_handle_t *out_handle)
{
    ESP_LOGI(BBQ_THERMOMETER_TAG, "InitialiseCalibration: calibration scheme version is %s", "Line Fitting");
    adc_cali_line_fitting_config_t cali_config = {
        .unit_id = unit,
        .atten = atten,
        .bitwidth = ADC_BITWIDTH_DEFAULT,
//        .default_vref=1100,  // LEE Need to see if we can determine this as the chip doesn't have this stored in eFuse
    };
    ESP_ERROR_CHECK(adc_cali_create_scheme_line_fitting(&cali_config, out_handle));
}


void InitialiseADC(adc_oneshot_unit_handle_t *pADCHandle, adc_cali_handle_t *pADCCaliHandleChan0, adc_cali_handle_t *pADCCaliHandleChan1)
{
// Initialise the onboard ADC first
	// Print the chip's ADC settings the rest of this initialisation is hard coded to use what was seen when I run it (i.e. no eFus, line fitting etc.)
	PrintADCCaliSettings();
	
	// Get handle to ADC1.  ADC2 is linked to WiFi so can't use both hence no good for this scenario
    adc_oneshot_unit_init_cfg_t init_config1 = {
        .unit_id = ADC_UNIT_1,
    };
    ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config1, pADCHandle));

	// Configure ADC 1
    adc_oneshot_chan_cfg_t config = {
        .atten = THERMOMTER_ADC_ATTEN,
        .bitwidth = ADC_BITWIDTH_DEFAULT,
    };
    ESP_ERROR_CHECK(adc_oneshot_config_channel(*pADCHandle, THERMOMTER_ADC1_CHAN0, &config));
    ESP_ERROR_CHECK(adc_oneshot_config_channel(*pADCHandle, THERMOMTER_ADC1_CHAN1, &config));

	// Initialise calibratation of both ADC 1 channels
    adc_cali_handle_t adc1_cali_chan0_handle = NULL;
    adc_cali_handle_t adc1_cali_chan1_handle = NULL;
    InitialiseCalibration(ADC_UNIT_1, THERMOMTER_ADC1_CHAN0, THERMOMTER_ADC_ATTEN, pADCCaliHandleChan0);
    InitialiseCalibration(ADC_UNIT_1, THERMOMTER_ADC1_CHAN1, THERMOMTER_ADC_ATTEN, pADCCaliHandleChan1);

// Initialise the external ADC
	I2C_ADC_init();
}

void InitialiseESPNow()
{
// Start espnow
    ESP_ERROR_CHECK( esp_now_init() );
    ESP_ERROR_CHECK( esp_now_register_send_cb(cbOnESPNowSend) );
    ESP_ERROR_CHECK( esp_now_register_recv_cb(cbOnESPNowReceive) );
    
    // The following are for the power save options. Every interval (ms) stay on for window (ms)
    // remove for now but need to see if this is better than deep sleeping 
//    ESP_ERROR_CHECK( esp_now_set_wake_window(CONFIG_ESPNOW_WAKE_WINDOW) );
//    ESP_ERROR_CHECK( esp_wifi_connectionless_module_set_wake_interval(CONFIG_ESPNOW_WAKE_INTERVAL) );
	
}

void DoTask()
{
	long lADCTotal = 0;
	long lVoltageTotal = 0;
	int nADCValue = 0;  // LEE check this as the example has an int [10] array for this for some reason.
	int nVoltage = 0;
	int i;
    uint16_t I2C_ADC_data[2];

		
	adc_oneshot_unit_handle_t 	adcHandle;
	adc_cali_handle_t			adcCaliHandleChan0 = NULL;
	adc_cali_handle_t			adcCaliHandleChan1 = NULL;
	
	InitialiseADC(&adcHandle, &adcCaliHandleChan0, &adcCaliHandleChan1);

	for (i=0; i<NUMB_READS; i++)
	{
        ESP_ERROR_CHECK(adc_oneshot_read(adcHandle, THERMOMTER_ADC1_CHAN0, &nADCValue));
        lADCTotal += nADCValue;
        ESP_LOGI(BBQ_THERMOMETER_TAG, "DoTask: ADC%d Channel[%d] ADC Raw Data: [%d] ADC Total[%ld]", ADC_UNIT_1 + 1, THERMOMTER_ADC1_CHAN0, nADCValue, lADCTotal);
        ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adcCaliHandleChan0, nADCValue, &nVoltage));
		lVoltageTotal += nVoltage;
        ESP_LOGI(BBQ_THERMOMETER_TAG, "DoTask: ADC%d Channel[%d] Cali Voltage: [%d mV] Voltage Total [%ld mV]", ADC_UNIT_1 + 1, THERMOMTER_ADC1_CHAN0, nVoltage, lVoltageTotal);

        ESP_ERROR_CHECK(adc_oneshot_read(adcHandle, THERMOMTER_ADC1_CHAN1, &nADCValue));
        lADCTotal += nADCValue;
        ESP_LOGI(BBQ_THERMOMETER_TAG, "DoTask: ADC%d Channel[%d] ADC Raw Data: [%d] ADC Total[%ld]", ADC_UNIT_1 + 1, THERMOMTER_ADC1_CHAN1, nADCValue, lADCTotal);
        ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adcCaliHandleChan1, nADCValue, &nVoltage));
		lVoltageTotal += nVoltage;
        ESP_LOGI(BBQ_THERMOMETER_TAG, "DoTask: ADC%d Channel[%d] Cali Voltage: [%d mV] Voltage Total [%ld mV]", ADC_UNIT_1 + 1, THERMOMTER_ADC1_CHAN1, nVoltage, lVoltageTotal);

		I2C_ADC_read(2, I2C_ADC_data, 1);
        ESP_LOGI(BBQ_THERMOMETER_TAG, "DoTask: I2C_ADC 2 [%d]", I2C_ADC_data[0]);
		I2C_ADC_read(3, I2C_ADC_data, 1);
        ESP_LOGI(BBQ_THERMOMETER_TAG, "DoTask: I2C_ADC 3 [%d]", I2C_ADC_data[0]);
	}

	// average the reading
	nADCValue = round(lADCTotal/(NUMB_READS*2)); // *2 because we are reading 2 channels in each iteration
	nVoltage = round(lVoltageTotal/ (NUMB_READS*2));
    ESP_LOGI(BBQ_THERMOMETER_TAG, "DoTask: Finished reading averages are ADC [%d]  Voltage [%d mV]", nADCValue, nVoltage );

	char sMessage[100];
    sprintf((char*) sMessage, "<probe1_temp>%d|%d</probe1_temp>", nADCValue, nVoltage);

	ESP_LOGI(BBQ_THERMOMETER_TAG, "DoTask: about to send message to [" MACSTR "] message [%s] length [%d]", MAC2STR(macAddressToSendTo), sMessage, strlen(sMessage)+1);
 	ESP_ERROR_CHECK(esp_now_send(macAddressToSendTo, (void*) sMessage, strlen(sMessage)+1));

    for (int i = 1; i >= 0; i--) {
        printf("Restarting in %d seconds...\n", i);
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
    printf("Restarting now.\n");
 	
 	const int nSleepTime = 5;
    ESP_LOGI(BBQ_THERMOMETER_TAG, "DoTask: Entering deep sleep for %d seconds", nSleepTime);
    esp_deep_sleep(500000LL * nSleepTime);

}

void cbOnWifiStarted()
{
	uint8_t nChannel1 = 0;
	wifi_second_chan_t nChannel2;

	ESP_LOGI(BBQ_THERMOMETER_TAG, "app_main: About to set channel to [%d]", nWiFiChannel);
	ESP_ERROR_CHECK(esp_wifi_set_channel(nWiFiChannel, nChannel2));

	
	ESP_ERROR_CHECK(esp_wifi_get_channel(&nChannel1, &nChannel2));	
	ESP_LOGI(BBQ_THERMOMETER_TAG, "cbOnWifiStarted: Channel 1 [%d], Channel 2 [%d]", (int) nChannel1, (int) nChannel2);
	
	
	ESP_LOGI(BBQ_THERMOMETER_TAG, "cbOnWifiStarted: Starting ESPNow");
	InitialiseESPNow();	

	// Add broadcast address as a peer	
    esp_now_peer_info_t *peer = malloc(sizeof(esp_now_peer_info_t));
    memset(peer, 0, sizeof(esp_now_peer_info_t));
    peer->channel = nWiFiChannel;
    peer->ifidx = ESP_IF_WIFI_STA;
    peer->encrypt = false;
    memcpy(peer->peer_addr, macAddressToSendTo, ESP_NOW_ETH_ALEN);
    ESP_ERROR_CHECK( esp_now_add_peer(peer) );
    free(peer);
    
    DoTask();
}

void app_main(void)
{
// Initialize NVS
    esp_err_t err = nvs_flash_init();
    if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        // NVS partition was truncated and needs to be erased
        // Retry nvs_flash_init
        ESP_ERROR_CHECK(nvs_flash_erase());
        err = nvs_flash_init();
    }
    ESP_ERROR_CHECK( err );

// Start wifi first
	ESP_LOGI(BBQ_THERMOMETER_TAG, "app_main: Starting Wifi");
	wifi_node_init(cbOnWifiStarted);
	wifi_node_start();

/*
    for (int i = 10; i >= 0; i--) {
        printf("Restarting in %d seconds...\n", i);
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
    printf("Restarting now.\n");
    fflush(stdout);
    esp_restart();
*/
}


MicroController
Posts: 2045
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: Controlling ADC with new I2C version

Postby MicroController » Mon Jan 06, 2025 3:36 pm

Yeah, that might be a problem:
You're calling DoTask() from the WiFi callback; the WiFi callback in turn is executed on the event loop task (sys_evt), so DoTask() blocks the event loop and may use more stack space than sys_evt has allocated.

This is usually solved by making one task (e.g. the main task (app_main()) or a dedicated "doTask") wait for the callback to happen and send a signal to the task; for example by using a FreeRTOS semaphore:
1. initialize the semaphore (initial count=0), storing its handle in some global variable.
2. start WiFi.
3.1. in the WiFi callback do a "give" on the semaphore handle from the global variable.
3.2. the other task "takes" the semaphore (with a timeout of "forever", i.e. portMAX_DELAY), causing it to be blocked until the callback has "given" it.
4. the other task can continue and do its thing w/o interfering with the event loop or anything else.

And for the I2C clock source, try I2C_CLK_SRC_DEFAULT.

leenowell
Posts: 155
Joined: Tue Jan 15, 2019 1:50 pm

Re: Controlling ADC with new I2C version

Postby leenowell » Mon Jan 06, 2025 6:11 pm

You are a star thanks very much. I have another project which had the stack overflow issue so I just extended it but your fix is much better so will update that one to.

Unfortunately the I2C problem remains though. I have changed the clk_source to be I2C_CLK_SRC_DEFAULT as suggested but still the same unfortunately. I also tried updating the timeout by a factor of 10 and still no joy. I am developing in Eclipse so use it to look up the type for clk_source in i2c_master_bus_config_t. It came up with the following from i2c_master.h

Code: Select all

typedef struct {
    i2c_port_num_t i2c_port;              /*!< I2C port number, `-1` for auto selecting, (not include LP I2C instance) */
    gpio_num_t sda_io_num;                /*!< GPIO number of I2C SDA signal, pulled-up internally */
    gpio_num_t scl_io_num;                /*!< GPIO number of I2C SCL signal, pulled-up internally */
    union {
        i2c_clock_source_t clk_source;        /*!< Clock source of I2C master bus */
#if SOC_LP_I2C_SUPPORTED
        lp_i2c_clock_source_t lp_source_clk;       /*!< LP_UART source clock selection */
#endif
    };
    uint8_t glitch_ignore_cnt;            /*!< If the glitch period on the line is less than this value, it can be filtered out, typically value is 7 (unit: I2C module clock cycle)*/
    int intr_priority;                    /*!< I2C interrupt priority, if set to 0, driver will select the default priority (1,2,3). */
    size_t trans_queue_depth;             /*!< Depth of internal transfer queue, increase this value can support more transfers pending in the background, only valid in asynchronous transaction. (Typically max_device_num * per_transaction)*/
    struct {
        uint32_t enable_internal_pullup: 1;  /*!< Enable internal pullups. Note: This is not strong enough to pullup buses under high-speed frequency. Recommend proper external pull-up if possible */
        uint32_t allow_pd:               1;  /*!< If set, the driver will backup/restore the I2C registers before/after entering/exist sleep mode.
                                              By this approach, the system can power off I2C's power domain.
                                              This can save power, but at the expense of more RAM being consumed */
    } flags;                              /*!< I2C master config flags */
} i2c_master_bus_config_t;

so clk_source is a i2c_clock_source_t which curiously is a

Code: Select all

typedef soc_periph_i2c_clk_src_t i2c_clock_source_t;
typedef enum {
    I2C_CLK_SRC_APB = SOC_MOD_CLK_APB,
    I2C_CLK_SRC_DEFAULT = SOC_MOD_CLK_APB,
} soc_periph_i2c_clk_src_t;
So in essence whichever option you choose it will always be SOC_MOD_CLK_APB.

MicroController
Posts: 2045
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: Controlling ADC with new I2C version

Postby MicroController » Mon Jan 06, 2025 7:29 pm

Try calling i2c_master_bus_reset() before starting communication on the bus. This "synchronizes" the ESP (I2C master) and slaves attached to the bus and can be necessary especially after a (possibly unexpected) reboot of the ESP (and doesn't hurt to do once after power up).

You can try the I2C tools example (-> "i2cdetect") to test if the ADC is detected on the I2C bus; if it's not, check the wiring/pin-assignments and the pull-ups.

I assume the ADS1x15 board you have also has pull-up resistors on the I2C lines; if not, the ESP's internal pull-ups alone (~50kOhm) may be too weak, especially at higher I2C speeds.
In any case, you can also try turning the I2C speed down, to 100kHz ("standard" I2C speed) or even 10-20kHz, which may help things work with weak pull-ups, just to check if communication is possible at all, i.e. wiring/pins/slave-address are correct.

Who is online

Users browsing this forum: floitsch_toit and 142 guests