Controlling ADC with new I2C version
Controlling ADC with new I2C version
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.
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.
-
- Posts: 2045
- Joined: Mon Oct 17, 2022 7:38 pm
- Location: Europe, Germany
Re: Controlling ADC with new I2C version
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.
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.
-
- Posts: 2045
- Joined: Mon Oct 17, 2022 7:38 pm
- Location: Europe, Germany
Re: Controlling ADC with new I2C version
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, ®Number, 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;
}
Re: Controlling ADC with new I2C version
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
Then the code calling the read is as follows
It seems it initialise ok but then on the transmit I get the following errors
Thanks
Lee.
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

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;
}
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]);
Any ideas? I feel I am much closer now thanks to your help.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
Thanks
Lee.
Last edited by leenowell on Mon Jan 06, 2025 2:49 pm, edited 1 time in total.
-
- Posts: 2045
- Joined: Mon Oct 17, 2022 7:38 pm
- Location: Europe, Germany
Re: Controlling ADC with new I2C version
Hmm.
What ESP SoC are you using?
doesn't look right.
Also make sure you have the correct pins connected.
Can you share the code leading up to I2C_ADC_read(2, I2C_ADC_data, 1)?
What ESP SoC are you using?
Code: Select all
.clk_source = SOC_MOD_CLK_APB,
Also make sure you have the correct pins connected.
Seems like something's corrupting some memory.Code: Select all
***ERROR*** A stack overflow in task sys_evt has been detected.
Can you share the code leading up to I2C_ADC_read(2, I2C_ADC_data, 1)?
Re: Controlling ADC with new I2C version
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
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();
*/
}
Re: Controlling ADC with new I2C version
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
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();
*/
}
-
- Posts: 2045
- Joined: Mon Oct 17, 2022 7:38 pm
- Location: Europe, Germany
Re: Controlling ADC with new I2C version
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.
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.
Re: Controlling ADC with new I2C version
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
so clk_source is a i2c_clock_source_t which curiously is a
So in essence whichever option you choose it will always be SOC_MOD_CLK_APB.
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;
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;
-
- Posts: 2045
- Joined: Mon Oct 17, 2022 7:38 pm
- Location: Europe, Germany
Re: Controlling ADC with new I2C version
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.
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