Closed Algonsi closed 3 months 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.
Thank you so much, @boarchuz. I'm going to try to make the module work with bitbanging as you suggested.
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?
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);
}
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!
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:
And this is what I get:
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?