boarchuz / HULP

ESP32 ULP Coprocessor Helper
MIT License
180 stars 18 forks source link

HULP I2C Hardware Read Wemos Ambient Light sensor BH1750 question #22

Closed Algonsi closed 3 months ago

Algonsi commented 1 year ago

Hi @boarchuz, Thank you very much for your incredible library that makes it possible for me to approach the ULP programming. I'm working with ESP32 (ESP32-DevKitM-1 with an ESP32-MINI-1 MCU) in Arduino IDE configured as generic ESP32 Dev Module. I'm trying the I2C Hardware Read example included in your library, slightly modified for use with the Wemos ambient light sensor BH1750 (I2C address is 0x23), but I can't get it to work right. I don't know about subaddress.

This is the code:


#include <Arduino.h>
#include <hulp_arduino.h>

#define ULP_REPEAT_INTERVAL_MS 1000
#define SCL_PIN GPIO_NUM_4            // D1
#define SDA_PIN GPIO_NUM_15           // D2
#define LIGHT_SENSOR_I2C_ADDR 35      // 0x23
#define LIGHT_SENSOR_I2C_SUBADDR 0    // 0x00

RTC_DATA_ATTR ulp_var_t ulp_data;

void init_ulp()
{
  const ulp_insn_t program[] = {
    I_MOVI(R2, 0),
    I_I2C_READ(0, LIGHT_SENSOR_I2C_SUBADDR),
    I_PUT(R0, R2, ulp_data),
    I_HALT(),
  };

  hulp_register_i2c_slave(0, LIGHT_SENSOR_I2C_ADDR);
  ESP_ERROR_CHECK(hulp_configure_i2c_pins(SCL_PIN, SDA_PIN, true, true));

  const hulp_i2c_controller_config_t config = HULP_I2C_CONTROLLER_CONFIG_DEFAULT();

  ESP_ERROR_CHECK(hulp_configure_i2c_controller(&config));
  ESP_ERROR_CHECK(hulp_ulp_load(program, sizeof(program), ULP_REPEAT_INTERVAL_MS * 1000, 0));
  ESP_ERROR_CHECK(hulp_ulp_run(0));
}

void setup()
{
  Serial.begin(115200);
  init_ulp();
}

void loop()
{
  Serial.print(F("Light sensor I2C data: "));
  Serial.println(ulp_data.val);
  delay(1000);
}

And this is what I get:

10:35:24.270 -> ets Jul 29 2019 12:21:46 10:35:24.270 -> 10:35:24.270 -> rst:0x1 (POWERON_RESET),boot:0x17 (SPI_FAST_FLASH_BOOT) 10:35:24.270 -> configsip: 188777542, SPIWP:0xee 10:35:24.270 -> clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00 10:35:24.270 -> mode:DIO, clock div:1 10:35:24.270 -> load:0x3fff0030,len:1344 10:35:24.270 -> load:0x40078000,len:13516 10:35:24.312 -> load:0x40080400,len:3604 10:35:24.312 -> entry 0x400805f0 10:35:24.394 -> Light sensor I2C data: 0 10:35:25.409 -> Light sensor I2C data: 1 10:35:26.385 -> Light sensor I2C data: 1 (repeated indefinitely)

I've also tried with VSCode and PlatformIO in Arduino framework; same results. On the other hand, the module works without any problem outside of the ULP/HULP programming, that is, with the main processor and the Wire.h and BH1750.h libraries.

Could you please help me by telling me what I am doing wrong?

boarchuz commented 1 year ago

Hi @Algonsi I2C is very limited when using ULP commands. You can only do the most typical I2C write (address+register+write_8b) and read (address+register->restart->+read_8b) operations, which don't seem to be quite right for the BH1750.

There are bitbanging subroutines in HULP that will do the job. The SHT3X example (https://github.com/boarchuz/HULP/blob/master/examples/I2C/Bitbang/Cmd/SHT3X/main/main.cpp) seems very similar to the pattern described in the BH1750 data sheet so that should be a good starting point.

Unfortunately these subroutines can take up a fair bit of memory despite being optimised, so you might struggle to fit in much other logic due to Arduino's 512 byte default configuration.

Try to make sense of it, and don't hesitate to ask if you're having any issues.

Algonsi commented 1 year ago

Thank you so much, @boarchuz. I'm going to try to make the module work with bitbanging as you suggested.

Algonsi commented 1 year ago

Well, I'm thinking about it and I'm going crazy. According to the datasheet and as I can understand, to read the 16-bit measurement result from the sensor, the ESP32 has to do the following I2C measurement sequence as master, every time it wants to get it:

Start
Slave address | 0 (7 bits + 'Write' bit)
--- Receive slave's Ack
Measurement mode instruction (8 bits setting Continuous/One Time measurement and High/Low resolution)
--- Receive slave's Ack
Stop

Wait for the slave measurement to complete. Depending on the sensor's resolution mode, it can be up to 180 ms (H-resolution mode) or up to 24 ms (L-resolution mode). Then,

Start
Slave address | 1 (7 bits + 'Read' bit)
--- Receive slave's Ack
--- High Byte is received
Master's Ack
--- Low Byte is received
Master's NAck
Stop

It seems simple but I can't get it. How could I do it with HULP, please?

boarchuz commented 1 year ago

Hi @Algonsi, Give this a try. It's not tested but should be pretty close. If it doesn't work please include your updated code and a I2C capture using an oscilloscope, if possible.

#define ULP_REPEAT_INTERVAL_MS 1000
#define SCL_PIN GPIO_NUM_4            // D1
#define SDA_PIN GPIO_NUM_15           // D2
#define LIGHT_SENSOR_I2C_ADDR 35      // 0x23

#define LIGHT_SENSOR_COMMAND_ONETIME_LOW_RESOLUTION 0b00100011
#define LIGHT_SENSOR_LOW_RESOLUTION_WAIT_MS 24

// Write slave address + command
static RTC_SLOW_ATTR ulp_var_t ulp_write_cmd[] = {
    HULP_I2C_CMD_HDR(LIGHT_SENSOR_I2C_ADDR, LIGHT_SENSOR_COMMAND_ONETIME_LOW_RESOLUTION, 0),
};

// Read result (16 bits)
static RTC_SLOW_ATTR ulp_var_t ulp_read_cmd[HULP_I2C_CMD_BUF_SIZE(2] = {
    HULP_I2C_CMD_HDR_NO_PTR(LIGHT_SENSOR_I2C_ADDR, 2),
};

static RTC_DATA_ATTR ulp_var_t ulp_error_code;
static RTC_DATA_ATTR ulp_var_t ulp_measurement_count;

void init_ulp() {
    enum {
        LABEL_I2C_READ,
        LABEL_I2C_WRITE,
        LABEL_I2C_ERROR,
    };

    const ulp_insn_t program[] = {
            I_MOVI(R2, 0),

            // Prepare write command
            I_MOVO(R1, ulp_write_cmd),
            // Go to subroutine
            M_RETURN(HULP_LBLA(), R3, LABEL_I2C_WRITE),
            // Check error code upon return
            M_BGE(LABEL_I2C_ERROR, 1),

            // Wait 24ms for result to be ready
            M_DELAY_MS_20_1000(LIGHT_SENSOR_LOW_RESOLUTION_WAIT_MS),

            // Prepare read command
            I_MOVO(R1, ulp_read_cmd),
            // Go to subroutine
            M_RETURN(HULP_LBLA(), R3, LABEL_I2C_READ),
            // Check error code upon return
            M_BGE(LABEL_I2C_ERROR, 1),

            // Success
            I_GET(R1, R2, ulp_measurement_count),
            I_ADDI(R1, R1, 1),
            I_PUT(R1, R2, ulp_measurement_count),
            // fall through to clear error code (R0==0) then halt

        M_LABEL(LABEL_I2C_ERROR),
            I_PUT(R0, R2, ulp_error_code),
            I_HALT(),

            M_INCLUDE_I2CBB_CMD(LABEL_I2C_READ, LABEL_I2C_WRITE, SCL_PIN, SDA_PIN)
    };

    ESP_ERROR_CHECK(hulp_configure_pin(SCL_PIN, RTC_GPIO_MODE_INPUT_ONLY, GPIO_PULLUP_ONLY, 0));
    ESP_ERROR_CHECK(hulp_configure_pin(SDA_PIN, RTC_GPIO_MODE_INPUT_ONLY, GPIO_PULLUP_ONLY, 0));

    ESP_ERROR_CHECK(hulp_ulp_load(program, sizeof(program), ULP_REPEAT_INTERVAL_MS * 1000, 0));
    ESP_ERROR_CHECK(hulp_ulp_run(0));
}

SoC:

for(;;)
{
    const uint16_t error_code = ulp_error_code.val;
    if(error_code != 0)
    {
        printf("ULP error: %u\n", error_code);
    }
    else
    {
        const uint16_t count = ulp_measurement_count.val;
        const uint16_t sensor_value = ulp_read_cmd[HULP_I2C_CMD_DATA_OFFSET].val;
        printf("ULP count: %u, sensor_value: %u\n", count, sensor_value);
    }
    vTaskDelay(250 / portTICK_PERIOD_MS);
}
Algonsi commented 1 year ago

Hi @boarchuz, Really thanks for your help. Now it works. I've made a few tiny modifications to your code for the Arduino IDE:

#include <hulp_arduino.h>

#define ULP_REPEAT_INTERVAL_MS 1000
#define SCL_PIN GPIO_NUM_4            // D1
#define SDA_PIN GPIO_NUM_15           // D2
#define LIGHT_SENSOR_I2C_ADDR 0x23

#define LIGHT_SENSOR_COMMAND_ONETIME_LOW_RESOLUTION 0b00100011
#define LIGHT_SENSOR_LOW_RESOLUTION_WAIT_MS 24

// Write slave address + command
static RTC_SLOW_ATTR ulp_var_t ulp_write_cmd[] = {
  HULP_I2C_CMD_HDR(LIGHT_SENSOR_I2C_ADDR, LIGHT_SENSOR_COMMAND_ONETIME_LOW_RESOLUTION, 0),
};

// Read result (16 bits)
static RTC_SLOW_ATTR ulp_var_t ulp_read_cmd[HULP_I2C_CMD_BUF_SIZE(2)] = {
  HULP_I2C_CMD_HDR_NO_PTR(LIGHT_SENSOR_I2C_ADDR, 2),
};

static RTC_DATA_ATTR ulp_var_t ulp_error_code;
static RTC_DATA_ATTR ulp_var_t ulp_measurement_count;
volatile RTC_DATA_ATTR ulp_var_t renew; // > 0 if a new read is ready

void init_ulp() {
  enum {
    LABEL_I2C_READ,
    LABEL_I2C_WRITE,
    LABEL_I2C_ERROR,
  };

  const ulp_insn_t program[] = {
    I_MOVI(R2, 0),

    // Prepare write command
    I_MOVO(R1, ulp_write_cmd),
    // Go to subroutine
    M_RETURN(HULP_LBLA(), R3, LABEL_I2C_WRITE),
    // Check error code upon return
    M_BGE(LABEL_I2C_ERROR, 1),

    // Wait 24ms for result to be ready
    M_DELAY_MS_20_1000(LIGHT_SENSOR_LOW_RESOLUTION_WAIT_MS),

    // Prepare read command
    I_MOVO(R1, ulp_read_cmd),
    // Go to subroutine
    M_RETURN(HULP_LBLA(), R3, LABEL_I2C_READ),
    // Check error code upon return
    M_BGE(LABEL_I2C_ERROR, 1),

    // Success
    I_MOVI(R2, 0),
    I_GET(R1, R2, ulp_measurement_count),
    I_ADDI(R1, R1, 1),
    I_PUT(R1, R2, ulp_measurement_count),

    // fall through to clear error code (R0==0) then halt
    M_LABEL(LABEL_I2C_ERROR),
    I_MOVI(R2, 0),
    I_PUT(R0, R2, ulp_error_code),

    // A new read is ready
    I_MOVI(R1, 1),
    I_PUT(R1, R2, renew),

    I_HALT(),

    M_INCLUDE_I2CBB_CMD(LABEL_I2C_READ, LABEL_I2C_WRITE, SCL_PIN, SDA_PIN)
  };

  ESP_ERROR_CHECK(hulp_configure_pin(SCL_PIN, RTC_GPIO_MODE_INPUT_ONLY, GPIO_PULLUP_ONLY, 0));
  ESP_ERROR_CHECK(hulp_configure_pin(SDA_PIN, RTC_GPIO_MODE_INPUT_ONLY, GPIO_PULLUP_ONLY, 0));

  ESP_ERROR_CHECK(hulp_ulp_load(program, sizeof(program), ULP_REPEAT_INTERVAL_MS * 1000, 0));
  ESP_ERROR_CHECK(hulp_ulp_run(0));
}

void setup()
{
  renew.val = 0;
  init_ulp();
}

void loop()
{
  if (renew.val)
  {
    renew.val = 0;
    const uint16_t error_code = ulp_error_code.val;
    if (error_code != 0)
    {
      printf("ULP error: %u\n", error_code);
    }
    else
    {
      const uint16_t count = ulp_measurement_count.val;
      const uint16_t sensor_value = ulp_read_cmd[HULP_I2C_CMD_DATA_OFFSET].val;
      printf("ULP count: %u, sensor_value: %u\n", count, sensor_value);
    }
  }
  delay(1);
}

Btw, there are several warnings during compilation due to the 'M_RETURN' subroutine call:

In file included from C:\Users\algonsi\ArduinoProjects\libraries\HULP-master\src/hulp_compat.h:17, from C:\Users\algonsi\ArduinoProjects\libraries\HULP-master\src/hulp.h:8, from C:\Users\algonsi\ArduinoProjects\libraries\HULP-master\src/hulp_arduino.h:1, from C:\Users\algonsi\ArduinoProjects\prueba_ulp_i2c_sensor_luz\prueba_ulp_i2c_sensor_luz.ino:1: C:\Users\algonsi\ArduinoProjects\prueba_ulp_i2c_sensor_luz\prueba_ulp_i2c_sensor_luz.ino: In function 'void init_ulp()': C:\Users\algonsi\ArduinoProjects\libraries\HULP-master\src/hulp_macros.h:35:53: warning: narrowing conversion of '({...})' from 'int' to 'uint32_t' {aka 'unsigned int'} inside { } [-Wnarrowing] (CONFIG_HULP_LABEL_AUTO_BASE + LINE); \ ^ C:\Users\algonsi\AppData\Local\Arduino15\packages\esp32\hardware\esp32\2.0.3/tools/sdk/esp32/include/ulp/include/esp32/ulp.h:815:14: note: in definition of macro 'M_LABELPC' .label = label_num, \ ^~~~~ C:\Users\algonsi\ArduinoProjects\libraries\HULP-master\src/hulp_macros.h:142:5: note: in expansion of macro 'M_MOVL' M_MOVL(reg, label_return_point), \ ^~ C:\Users\algonsi\ArduinoProjects\prueba_ulp_i2c_sensor_luz\prueba_ulp_i2c_sensor_luz.ino:38:5: note: in expansion of macro 'M_RETURN' M_RETURN(HULP_LBLA(), R3, LABEL_I2C_WRITE), ^~~~ C:\Users\algonsi\ArduinoProjects\prueba_ulp_i2c_sensor_luz\prueba_ulp_i2c_sensor_luz.ino:38:14: note: in expansion of macro 'HULP_LBLA' M_RETURN(HULP_LBLA(), R3, LABEL_I2C_WRITE), ^~~~~ C:\Users\algonsi\ArduinoProjects\libraries\HULP-master\src/hulp_macros.h:35:53: warning: narrowing conversion of '({...})' from 'int' to 'uint32_t' {aka 'unsigned int'} inside { } [-Wnarrowing] (CONFIG_HULP_LABEL_AUTO_BASE + LINE); \ ^ C:\Users\algonsi\AppData\Local\Arduino15\packages\esp32\hardware\esp32\2.0.3/tools/sdk/esp32/include/ulp/include/esp32/ulp.h:795:14: note: in definition of macro 'M_LABEL' .label = label_num, \ ^~~~~ C:\Users\algonsi\ArduinoProjects\prueba_ulp_i2c_sensor_luz\prueba_ulp_i2c_sensor_luz.ino:38:5: note: in expansion of macro 'M_RETURN' M_RETURN(HULP_LBLA(), R3, LABEL_I2C_WRITE), ^~~~ C:\Users\algonsi\ArduinoProjects\prueba_ulp_i2c_sensor_luz\prueba_ulp_i2c_sensor_luz.ino:38:14: note: in expansion of macro 'HULP_LBLA' M_RETURN(HULP_LBLA(), R3, LABEL_I2C_WRITE), ^~~~~ C:\Users\algonsi\ArduinoProjects\libraries\HULP-master\src/hulp_macros.h:35:53: warning: narrowing conversion of '({...})' from 'int' to 'uint32_t' {aka 'unsigned int'} inside { } [-Wnarrowing] (CONFIG_HULP_LABEL_AUTO_BASE + LINE); \ ^ C:\Users\algonsi\AppData\Local\Arduino15\packages\esp32\hardware\esp32\2.0.3/tools/sdk/esp32/include/ulp/include/esp32/ulp.h:815:14: note: in definition of macro 'M_LABELPC' .label = label_num, \ ^~~~~ C:\Users\algonsi\ArduinoProjects\libraries\HULP-master\src/hulp_macros.h:142:5: note: in expansion of macro 'M_MOVL' M_MOVL(reg, label_return_point), \ ^~ C:\Users\algonsi\ArduinoProjects\prueba_ulp_i2c_sensor_luz\prueba_ulp_i2c_sensor_luz.ino:48:5: note: in expansion of macro 'M_RETURN' M_RETURN(HULP_LBLA(), R3, LABEL_I2C_READ), ^~~~ C:\Users\algonsi\ArduinoProjects\prueba_ulp_i2c_sensor_luz\prueba_ulp_i2c_sensor_luz.ino:48:14: note: in expansion of macro 'HULP_LBLA' M_RETURN(HULP_LBLA(), R3, LABEL_I2C_READ), ^~~~~ C:\Users\algonsi\ArduinoProjects\libraries\HULP-master\src/hulp_macros.h:35:53: warning: narrowing conversion of '({...})' from 'int' to 'uint32_t' {aka 'unsigned int'} inside { } [-Wnarrowing] (CONFIG_HULP_LABEL_AUTO_BASE + LINE); \ ^ C:\Users\algonsi\AppData\Local\Arduino15\packages\esp32\hardware\esp32\2.0.3/tools/sdk/esp32/include/ulp/include/esp32/ulp.h:795:14: note: in definition of macro 'M_LABEL' .label = label_num, \ ^~~~~ C:\Users\algonsi\ArduinoProjects\prueba_ulp_i2c_sensor_luz\prueba_ulp_i2c_sensor_luz.ino:48:5: note: in expansion of macro 'M_RETURN' M_RETURN(HULP_LBLA(), R3, LABEL_I2C_READ), ^~~~ C:\Users\algonsi\ArduinoProjects\prueba_ulp_i2c_sensor_luz\prueba_ulp_i2c_sensor_luz.ino:48:14: note: in expansion of macro 'HULP_LBLA' M_RETURN(HULP_LBLA(), R3, LABEL_I2C_READ), ^~~~~

Thanks again and have a nice weekend!