From 4fde033a5fbf3edeaaf108d5d105db0c5e15fe50 Mon Sep 17 00:00:00 2001 From: Sudeep Mohanty Date: Tue, 9 Aug 2022 15:46:14 +0200 Subject: [PATCH] ulp: Added support for RTC I2C driver for ULP RISC-V on esp32s2 and esp32s3 This commit adds support for using the RTC I2C peripheral on the ULP RISC-V core for esp32s2 and esp32s3. It also adds an example to demonstrate the usage of the RTC I2C peripheral. This commit also modifies the rtc_i2c register structure files to enable the use of bitfields in the ULP RISC-V RTC I2C driver. --- .../soc/esp32c3/include/soc/rtc_i2c_struct.h | 6 +- .../soc/esp32s2/include/soc/rtc_i2c_struct.h | 8 +- .../soc/esp32s2/ld/esp32s2.peripherals.ld | 1 + .../soc/esp32s3/include/soc/rtc_i2c_struct.h | 315 +------------ .../soc/esp32s3/ld/esp32s3.peripherals.ld | 1 + components/ulp/CMakeLists.txt | 3 +- components/ulp/cmake/CMakeLists.txt | 1 + components/ulp/ld/esp32s2.peripherals.ld | 1 + components/ulp/ld/esp32s3.peripherals.ld | 1 + .../ulp/ulp_riscv/include/ulp_riscv_i2c.h | 99 ++++ .../ulp_core/include/ulp_riscv_i2c_ulp_core.h | 52 +++ .../ulp/ulp_riscv/ulp_core/ulp_riscv_i2c.c | 225 +++++++++ components/ulp/ulp_riscv/ulp_riscv_i2c.c | 441 ++++++++++++++++++ examples/system/.build-test-rules.yml | 4 + examples/system/ulp_riscv/i2c/CMakeLists.txt | 6 + examples/system/ulp_riscv/i2c/README.md | 76 +++ .../system/ulp_riscv/i2c/main/CMakeLists.txt | 25 + .../system/ulp_riscv/i2c/main/bmp180_defs.h | 110 +++++ examples/system/ulp_riscv/i2c/main/ulp/main.c | 126 +++++ .../i2c/main/ulp_riscv_rtc_i2c_example_main.c | 351 ++++++++++++++ .../system/ulp_riscv/i2c/sdkconfig.defaults | 9 + 21 files changed, 1559 insertions(+), 302 deletions(-) create mode 100644 components/ulp/ulp_riscv/include/ulp_riscv_i2c.h create mode 100644 components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_i2c_ulp_core.h create mode 100644 components/ulp/ulp_riscv/ulp_core/ulp_riscv_i2c.c create mode 100644 components/ulp/ulp_riscv/ulp_riscv_i2c.c create mode 100644 examples/system/ulp_riscv/i2c/CMakeLists.txt create mode 100644 examples/system/ulp_riscv/i2c/README.md create mode 100644 examples/system/ulp_riscv/i2c/main/CMakeLists.txt create mode 100644 examples/system/ulp_riscv/i2c/main/bmp180_defs.h create mode 100644 examples/system/ulp_riscv/i2c/main/ulp/main.c create mode 100644 examples/system/ulp_riscv/i2c/main/ulp_riscv_rtc_i2c_example_main.c create mode 100644 examples/system/ulp_riscv/i2c/sdkconfig.defaults diff --git a/components/soc/esp32c3/include/soc/rtc_i2c_struct.h b/components/soc/esp32c3/include/soc/rtc_i2c_struct.h index 2f41b0f10f..6bac0548ee 100644 --- a/components/soc/esp32c3/include/soc/rtc_i2c_struct.h +++ b/components/soc/esp32c3/include/soc/rtc_i2c_struct.h @@ -165,7 +165,11 @@ typedef volatile struct rtc_i2c_dev_s { } fifo_data; union { struct { - uint32_t command0: 14; /*command0*/ + uint32_t byte_num: 8; + uint32_t ack_en: 1; + uint32_t ack_exp: 1; + uint32_t ack_val: 1; + uint32_t op_code: 3; uint32_t reserved14: 17; uint32_t done: 1; /*command0_done*/ }; diff --git a/components/soc/esp32s2/include/soc/rtc_i2c_struct.h b/components/soc/esp32s2/include/soc/rtc_i2c_struct.h index 5831bd5ca2..edec99145e 100644 --- a/components/soc/esp32s2/include/soc/rtc_i2c_struct.h +++ b/components/soc/esp32s2/include/soc/rtc_i2c_struct.h @@ -166,9 +166,13 @@ typedef volatile struct rtc_i2c_dev_s { } fifo_data; union { struct { - uint32_t command0: 14; /*command0*/ + uint32_t byte_num: 8; + uint32_t ack_en: 1; + uint32_t ack_exp: 1; + uint32_t ack_val: 1; + uint32_t op_code: 3; uint32_t reserved14: 17; - uint32_t done: 1; /*command0_done*/ + uint32_t done: 1; }; uint32_t val; } command[16]; diff --git a/components/soc/esp32s2/ld/esp32s2.peripherals.ld b/components/soc/esp32s2/ld/esp32s2.peripherals.ld index b19649d18a..52ee92b278 100644 --- a/components/soc/esp32s2/ld/esp32s2.peripherals.ld +++ b/components/soc/esp32s2/ld/esp32s2.peripherals.ld @@ -11,6 +11,7 @@ PROVIDE ( SDM = 0x3f404f00 ); PROVIDE ( RTCCNTL = 0x3f408000 ); PROVIDE ( RTCIO = 0x3f408400 ); PROVIDE ( SENS = 0x3f408800 ); +PROVIDE ( RTC_I2C = 0x3f408C00 ); PROVIDE ( HINF = 0x3f40B000 ); PROVIDE ( I2S0 = 0x3f40F000 ); PROVIDE ( UART1 = 0x3f410000 ); diff --git a/components/soc/esp32s3/include/soc/rtc_i2c_struct.h b/components/soc/esp32s3/include/soc/rtc_i2c_struct.h index 8641683f16..2bac2fa580 100644 --- a/components/soc/esp32s3/include/soc/rtc_i2c_struct.h +++ b/components/soc/esp32s3/include/soc/rtc_i2c_struct.h @@ -190,294 +190,27 @@ typedef union { uint32_t val; } rtc_i2c_data_reg_t; -/** Type of i2c_cmd0 register - * i2c commond0 register +/** Type of i2c_cmd register + * i2c command register */ typedef union { struct { - /** i2c_command0 : R/W; bitpos: [13:0]; default: 2307; - * command0 + /** i2c_command : R/W; bitpos: [13:0]; default: 2307; + * command */ - uint32_t i2c_command0:14; - uint32_t reserved_14:17; - /** i2c_command0_done : RO; bitpos: [31]; default: 0; + uint32_t i2c_byte_num:8; + uint32_t i2c_ack_en:1; + uint32_t i2c_ack_exp:1; + uint32_t i2c_ack_val:1; + uint32_t i2c_op_code:3; + uint32_t reserved14:17; + /** i2c_command_done : RO; bitpos: [31]; default: 0; * command0_done */ - uint32_t i2c_command0_done:1; + uint32_t i2c_command_done:1; }; uint32_t val; -} rtc_i2c_cmd0_reg_t; - -/** Type of i2c_cmd1 register - * i2c commond1 register - */ -typedef union { - struct { - /** i2c_command1 : R/W; bitpos: [13:0]; default: 6401; - * command1 - */ - uint32_t i2c_command1:14; - uint32_t reserved_14:17; - /** i2c_command1_done : RO; bitpos: [31]; default: 0; - * command1_done - */ - uint32_t i2c_command1_done:1; - }; - uint32_t val; -} rtc_i2c_cmd1_reg_t; - -/** Type of i2c_cmd2 register - * i2c commond2 register - */ -typedef union { - struct { - /** i2c_command2 : R/W; bitpos: [13:0]; default: 2306; - * command2 - */ - uint32_t i2c_command2:14; - uint32_t reserved_14:17; - /** i2c_command2_done : RO; bitpos: [31]; default: 0; - * command2_done - */ - uint32_t i2c_command2_done:1; - }; - uint32_t val; -} rtc_i2c_cmd2_reg_t; - -/** Type of i2c_cmd3 register - * i2c commond3 register - */ -typedef union { - struct { - /** i2c_command3 : R/W; bitpos: [13:0]; default: 257; - * command3 - */ - uint32_t i2c_command3:14; - uint32_t reserved_14:17; - /** i2c_command3_done : RO; bitpos: [31]; default: 0; - * command3_done - */ - uint32_t i2c_command3_done:1; - }; - uint32_t val; -} rtc_i2c_cmd3_reg_t; - -/** Type of i2c_cmd4 register - * i2c commond4 register - */ -typedef union { - struct { - /** i2c_command4 : R/W; bitpos: [13:0]; default: 2305; - * command4 - */ - uint32_t i2c_command4:14; - uint32_t reserved_14:17; - /** i2c_command4_done : RO; bitpos: [31]; default: 0; - * command4_done - */ - uint32_t i2c_command4_done:1; - }; - uint32_t val; -} rtc_i2c_cmd4_reg_t; - -/** Type of i2c_cmd5 register - * i2c commond5_register - */ -typedef union { - struct { - /** i2c_command5 : R/W; bitpos: [13:0]; default: 5889; - * command5 - */ - uint32_t i2c_command5:14; - uint32_t reserved_14:17; - /** i2c_command5_done : RO; bitpos: [31]; default: 0; - * command5_done - */ - uint32_t i2c_command5_done:1; - }; - uint32_t val; -} rtc_i2c_cmd5_reg_t; - -/** Type of i2c_cmd6 register - * i2c commond6 register - */ -typedef union { - struct { - /** i2c_command6 : R/W; bitpos: [13:0]; default: 6401; - * command6 - */ - uint32_t i2c_command6:14; - uint32_t reserved_14:17; - /** i2c_command6_done : RO; bitpos: [31]; default: 0; - * command6_done - */ - uint32_t i2c_command6_done:1; - }; - uint32_t val; -} rtc_i2c_cmd6_reg_t; - -/** Type of i2c_cmd7 register - * i2c commond7 register - */ -typedef union { - struct { - /** i2c_command7 : R/W; bitpos: [13:0]; default: 2308; - * command7 - */ - uint32_t i2c_command7:14; - uint32_t reserved_14:17; - /** i2c_command7_done : RO; bitpos: [31]; default: 0; - * command7_done - */ - uint32_t i2c_command7_done:1; - }; - uint32_t val; -} rtc_i2c_cmd7_reg_t; - -/** Type of i2c_cmd8 register - * i2c commond8 register - */ -typedef union { - struct { - /** i2c_command8 : R/W; bitpos: [13:0]; default: 6401; - * command8 - */ - uint32_t i2c_command8:14; - uint32_t reserved_14:17; - /** i2c_command8_done : RO; bitpos: [31]; default: 0; - * command8_done - */ - uint32_t i2c_command8_done:1; - }; - uint32_t val; -} rtc_i2c_cmd8_reg_t; - -/** Type of i2c_cmd9 register - * i2c commond9 register - */ -typedef union { - struct { - /** i2c_command9 : R/W; bitpos: [13:0]; default: 2307; - * command9 - */ - uint32_t i2c_command9:14; - uint32_t reserved_14:17; - /** i2c_command9_done : RO; bitpos: [31]; default: 0; - * command9_done - */ - uint32_t i2c_command9_done:1; - }; - uint32_t val; -} rtc_i2c_cmd9_reg_t; - -/** Type of i2c_cmd10 register - * i2c commond10 register - */ -typedef union { - struct { - /** i2c_command10 : R/W; bitpos: [13:0]; default: 257; - * command10 - */ - uint32_t i2c_command10:14; - uint32_t reserved_14:17; - /** i2c_command10_done : RO; bitpos: [31]; default: 0; - * command10_done - */ - uint32_t i2c_command10_done:1; - }; - uint32_t val; -} rtc_i2c_cmd10_reg_t; - -/** Type of i2c_cmd11 register - * i2c commond11 register - */ -typedef union { - struct { - /** i2c_command11 : R/W; bitpos: [13:0]; default: 2305; - * command11 - */ - uint32_t i2c_command11:14; - uint32_t reserved_14:17; - /** i2c_command11_done : RO; bitpos: [31]; default: 0; - * command11_done - */ - uint32_t i2c_command11_done:1; - }; - uint32_t val; -} rtc_i2c_cmd11_reg_t; - -/** Type of i2c_cmd12 register - * i2c commond12 register - */ -typedef union { - struct { - /** i2c_command12 : R/W; bitpos: [13:0]; default: 5889; - * command12 - */ - uint32_t i2c_command12:14; - uint32_t reserved_14:17; - /** i2c_command12_done : RO; bitpos: [31]; default: 0; - * command12_done - */ - uint32_t i2c_command12_done:1; - }; - uint32_t val; -} rtc_i2c_cmd12_reg_t; - -/** Type of i2c_cmd13 register - * i2c commond13 register - */ -typedef union { - struct { - /** i2c_command13 : R/W; bitpos: [13:0]; default: 6401; - * command13 - */ - uint32_t i2c_command13:14; - uint32_t reserved_14:17; - /** i2c_command13_done : RO; bitpos: [31]; default: 0; - * command13_done - */ - uint32_t i2c_command13_done:1; - }; - uint32_t val; -} rtc_i2c_cmd13_reg_t; - -/** Type of i2c_cmd14 register - * i2c commond14 register - */ -typedef union { - struct { - /** i2c_command14 : R/W; bitpos: [13:0]; default: 0; - * command14 - */ - uint32_t i2c_command14:14; - uint32_t reserved_14:17; - /** i2c_command14_done : RO; bitpos: [31]; default: 0; - * command14_done - */ - uint32_t i2c_command14_done:1; - }; - uint32_t val; -} rtc_i2c_cmd14_reg_t; - -/** Type of i2c_cmd15 register - * i2c commond15 register - */ -typedef union { - struct { - /** i2c_command15 : R/W; bitpos: [13:0]; default: 0; - * command15 - */ - uint32_t i2c_command15:14; - uint32_t reserved_14:17; - /** i2c_command15_done : RO; bitpos: [31]; default: 0; - * command15_done - */ - uint32_t i2c_command15_done:1; - }; - uint32_t val; -} rtc_i2c_cmd15_reg_t; - +} rtc_i2c_cmd_reg_t; /** Group: status register */ /** Type of i2c_status register @@ -750,29 +483,15 @@ typedef struct { volatile rtc_i2c_int_st_reg_t i2c_int_st; volatile rtc_i2c_int_ena_reg_t i2c_int_ena; volatile rtc_i2c_data_reg_t i2c_data; - volatile rtc_i2c_cmd0_reg_t i2c_cmd0; - volatile rtc_i2c_cmd1_reg_t i2c_cmd1; - volatile rtc_i2c_cmd2_reg_t i2c_cmd2; - volatile rtc_i2c_cmd3_reg_t i2c_cmd3; - volatile rtc_i2c_cmd4_reg_t i2c_cmd4; - volatile rtc_i2c_cmd5_reg_t i2c_cmd5; - volatile rtc_i2c_cmd6_reg_t i2c_cmd6; - volatile rtc_i2c_cmd7_reg_t i2c_cmd7; - volatile rtc_i2c_cmd8_reg_t i2c_cmd8; - volatile rtc_i2c_cmd9_reg_t i2c_cmd9; - volatile rtc_i2c_cmd10_reg_t i2c_cmd10; - volatile rtc_i2c_cmd11_reg_t i2c_cmd11; - volatile rtc_i2c_cmd12_reg_t i2c_cmd12; - volatile rtc_i2c_cmd13_reg_t i2c_cmd13; - volatile rtc_i2c_cmd14_reg_t i2c_cmd14; - volatile rtc_i2c_cmd15_reg_t i2c_cmd15; + volatile rtc_i2c_cmd_reg_t i2c_cmd[16]; uint32_t reserved_078[33]; volatile rtc_i2c_date_reg_t i2c_date; -} rtc_dev_t; +} rtc_i2c_dev_t; +extern rtc_i2c_dev_t RTC_I2C; #ifndef __cplusplus -_Static_assert(sizeof(rtc_dev_t) == 0x100, "Invalid size of rtc_dev_t structure"); +_Static_assert(sizeof(rtc_i2c_dev_t) == 0x100, "Invalid size of rtc_i2c_dev_t structure"); #endif #ifdef __cplusplus diff --git a/components/soc/esp32s3/ld/esp32s3.peripherals.ld b/components/soc/esp32s3/ld/esp32s3.peripherals.ld index 66ed5c0387..2eb4198691 100644 --- a/components/soc/esp32s3/ld/esp32s3.peripherals.ld +++ b/components/soc/esp32s3/ld/esp32s3.peripherals.ld @@ -12,6 +12,7 @@ PROVIDE ( EFUSE = 0x60007000 ); PROVIDE ( RTCCNTL = 0x60008000 ); PROVIDE ( RTCIO = 0x60008400 ); PROVIDE ( SENS = 0x60008800 ); +PROVIDE ( RTC_I2C = 0x60008C00 ); PROVIDE ( HINF = 0x6000B000 ); PROVIDE ( I2S0 = 0x6000F000 ); PROVIDE ( I2S1 = 0x6002D000 ); diff --git a/components/ulp/CMakeLists.txt b/components/ulp/CMakeLists.txt index 3e5c1f71e1..b93c9b3a4f 100644 --- a/components/ulp/CMakeLists.txt +++ b/components/ulp/CMakeLists.txt @@ -25,7 +25,8 @@ if(CONFIG_SOC_ULP_SUPPORTED OR CONFIG_SOC_RISCV_COPROC_SUPPORTED) list(APPEND srcs "ulp_riscv/ulp_riscv.c" "ulp_riscv/ulp_riscv_lock.c" - "ulp_riscv/ulp_riscv_adc.c") + "ulp_riscv/ulp_riscv_adc.c" + "ulp_riscv/ulp_riscv_i2c.c") list(APPEND includes ulp_riscv/include diff --git a/components/ulp/cmake/CMakeLists.txt b/components/ulp/cmake/CMakeLists.txt index 8b39a30126..4d4f530dd0 100644 --- a/components/ulp/cmake/CMakeLists.txt +++ b/components/ulp/cmake/CMakeLists.txt @@ -76,6 +76,7 @@ if(ULP_COCPU_IS_RISCV) "${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_lock.c" "${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_uart.c" "${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_print.c" + "${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_i2c.c" "${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_utils.c") target_link_options(${ULP_APP_NAME} PRIVATE "-nostartfiles") diff --git a/components/ulp/ld/esp32s2.peripherals.ld b/components/ulp/ld/esp32s2.peripherals.ld index 391b20c1c1..291467eb1a 100644 --- a/components/ulp/ld/esp32s2.peripherals.ld +++ b/components/ulp/ld/esp32s2.peripherals.ld @@ -7,3 +7,4 @@ PROVIDE ( RTCCNTL = 0x8000 ); PROVIDE ( RTCIO = 0xA400 ); PROVIDE ( SENS = 0xC800 ); +PROVIDE ( RTC_I2C = 0x8C00 ); diff --git a/components/ulp/ld/esp32s3.peripherals.ld b/components/ulp/ld/esp32s3.peripherals.ld index 391b20c1c1..5d0753e011 100644 --- a/components/ulp/ld/esp32s3.peripherals.ld +++ b/components/ulp/ld/esp32s3.peripherals.ld @@ -7,3 +7,4 @@ PROVIDE ( RTCCNTL = 0x8000 ); PROVIDE ( RTCIO = 0xA400 ); PROVIDE ( SENS = 0xC800 ); +PROVIDE ( RTC_I2C = 0xEC00 ); diff --git a/components/ulp/ulp_riscv/include/ulp_riscv_i2c.h b/components/ulp/ulp_riscv/include/ulp_riscv_i2c.h new file mode 100644 index 0000000000..d77d2c361b --- /dev/null +++ b/components/ulp/ulp_riscv/include/ulp_riscv_i2c.h @@ -0,0 +1,99 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include "hal/gpio_types.h" +#include "esp_err.h" + +typedef struct { + uint32_t sda_io_num; // GPIO pin for SDA signal. Only GPIO#1 or GPIO#3 can be used as the SDA pin. + uint32_t scl_io_num; // GPIO pin for SCL signal. Only GPIO#0 or GPIO#2 can be used as the SCL pin. + bool sda_pullup_en; // SDA line enable internal pullup. Can be configured if external pullup is not used. + bool scl_pullup_en; // SCL line enable internal pullup. Can be configured if external pullup is not used. +} ulp_riscv_i2c_pin_cfg_t; + +typedef struct { + uint32_t scl_low_period; // SCL low period + uint32_t scl_high_period; // SCL high period + uint32_t sda_duty_period; // Period between the SDA switch and the falling edge of SCL + uint32_t scl_start_period; // Waiting time after the START condition + uint32_t scl_stop_period; // Waiting time before the END condition + uint32_t i2c_trans_timeout; // I2C transaction timeout +} ulp_riscv_i2c_timing_cfg_t; + +typedef struct { + ulp_riscv_i2c_pin_cfg_t i2c_pin_cfg; // RTC I2C pin configuration + ulp_riscv_i2c_timing_cfg_t i2c_timing_cfg; // RTC I2C timing configuration +} ulp_riscv_i2c_cfg_t; + +/* Nominal default GPIO settings and timing parametes */ +#define ULP_RISCV_I2C_DEFAULT_CONFIG() \ + { \ + .i2c_pin_cfg.sda_io_num = GPIO_NUM_3, \ + .i2c_pin_cfg.scl_io_num = GPIO_NUM_2, \ + .i2c_pin_cfg.sda_pullup_en = true, \ + .i2c_pin_cfg.scl_pullup_en = true, \ + .i2c_timing_cfg.scl_low_period = 5, \ + .i2c_timing_cfg.scl_high_period = 5, \ + .i2c_timing_cfg.sda_duty_period = 2, \ + .i2c_timing_cfg.scl_start_period = 3, \ + .i2c_timing_cfg.scl_stop_period = 6, \ + .i2c_timing_cfg.i2c_trans_timeout = 20, \ + } + +/** + * @brief Set the I2C slave device address + * + * @param slave_addr I2C slave address (7 bit) + */ +void ulp_riscv_i2c_master_set_slave_addr(uint8_t slave_addr); + +/** + * @brief Set the I2C slave device sub register address + * + * @param slave_reg_addr I2C slave sub register address + */ +void ulp_riscv_i2c_master_set_slave_reg_addr(uint8_t slave_reg_addr); + +/** + * @brief Read from I2C slave device + * + * @note The I2C slave device address must be configured at least once before invoking this API. + * + * @param data_rd Buffer to hold data to be read + * @param size Size of data to be read in bytes + */ +void ulp_riscv_i2c_master_read_from_device(uint8_t *data_rd, size_t size); + +/** + * @brief Write to I2C slave device + * + * @note The I2C slave device address must be configured at least once before invoking this API. + * + * @param data_wr Buffer which holds the data to be written + * @param size Size of data to be written in bytes + */ +void ulp_riscv_i2c_master_write_to_device(uint8_t *data_wr, size_t size); + +/** + * @brief Initialize and configure the RTC I2C for use by ULP RISC-V + * Currently RTC I2C can only be used in master mode + * + * @param cfg Configuration parameters + * @return esp_err_t ESP_OK when successful + */ +esp_err_t ulp_riscv_i2c_master_init(const ulp_riscv_i2c_cfg_t *cfg); + +#ifdef __cplusplus +} +#endif diff --git a/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_i2c_ulp_core.h b/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_i2c_ulp_core.h new file mode 100644 index 0000000000..41383245ea --- /dev/null +++ b/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_i2c_ulp_core.h @@ -0,0 +1,52 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/** + * @brief Set the I2C slave device address + * + * @param slave_addr I2C slave address (7 bit) + */ +void ulp_riscv_i2c_master_set_slave_addr(uint8_t slave_addr); + +/** + * @brief Set the I2C slave device sub register address + * + * @param slave_reg_addr I2C slave register address + */ +void ulp_riscv_i2c_master_set_slave_reg_addr(uint8_t slave_reg_addr); + +/** + * @brief Read from I2C slave device + * + * @note The I2C slave device address must be configured at least once before invoking this API. + * + * @param data_rd Buffer to hold data to be read + * @param size Size of data to be read in bytes + */ +void ulp_riscv_i2c_master_read_from_device(uint8_t *data_rd, size_t size); + +/** + * @brief Write to I2C slave device + * + * @note The I2C slave device address must be configured at least once before invoking this API. + * + * @param data_wr Buffer which holds the data to be written + * @param size Size of data to be written in bytes + */ +void ulp_riscv_i2c_master_write_to_device(uint8_t *data_wr, size_t size); + +#ifdef __cplusplus +} +#endif diff --git a/components/ulp/ulp_riscv/ulp_core/ulp_riscv_i2c.c b/components/ulp/ulp_riscv/ulp_core/ulp_riscv_i2c.c new file mode 100644 index 0000000000..de3f787c57 --- /dev/null +++ b/components/ulp/ulp_riscv/ulp_core/ulp_riscv_i2c.c @@ -0,0 +1,225 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "ulp_riscv_i2c_ulp_core.h" +#include "ulp_riscv_utils.h" +#include "soc/rtc_i2c_reg.h" +#include "soc/rtc_i2c_struct.h" +#include "soc/rtc_io_reg.h" +#include "soc/sens_reg.h" +#include "hal/i2c_ll.h" + +#define I2C_CTRL_SLAVE_ADDR_MASK (0xFF << 0) +#define I2C_CTRL_SLAVE_REG_ADDR_MASK (0xFF << 11) +#define I2C_CTRL_MASTER_TX_DATA_MASK (0xFF << 19) + +#if CONFIG_IDF_TARGET_ESP32S3 +#define ULP_I2C_CMD_RESTART 0 /*! 1) { + /* Read n - 1 bytes */ + ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_READ, 0, 0, 1, size - 1); + } + + /* Read last byte + NACK */ + ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_READ, 1, 1, 1, 1); + + /* STOP */ + ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_STOP, 0, 0, 0, 0); + + /* Configure the RTC I2C controller in read mode */ + SET_PERI_REG_BITS(SENS_SAR_I2C_CTRL_REG, 0x1, 0, 27); + + /* Enable Rx data interrupt */ + SET_PERI_REG_MASK(RTC_I2C_INT_ENA_REG, RTC_I2C_RX_DATA_INT_ENA); + + /* Start RTC I2C transmission */ + SET_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START_FORCE); + SET_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START); + + for (i = 0; i < size; i++) { + /* Poll for RTC I2C Rx Data interrupt bit to be set */ + while (!REG_GET_FIELD(RTC_I2C_INT_ST_REG, RTC_I2C_RX_DATA_INT_ST)) { } + + /* Read the data + * + * Unfortunately, the RTC I2C has no fifo buffer to help us with reading and storing + * multiple bytes of data. Therefore, we need to read one byte at a time and clear the + * Rx interrupt to get ready for the next byte. + */ +#if CONFIG_IDF_TARGET_ESP32S2 + data_rd[i] = REG_GET_FIELD(RTC_I2C_DATA_REG, RTC_I2C_RDATA); +#elif CONFIG_IDF_TARGET_ESP32S3 + data_rd[i] = REG_GET_FIELD(RTC_I2C_DATA_REG, RTC_I2C_I2C_RDATA); +#endif // CONFIG_IDF_TARGET_ESP32S2 + + /* Clear the Rx data interrupt bit */ + SET_PERI_REG_MASK(RTC_I2C_INT_CLR_REG, RTC_I2C_RX_DATA_INT_CLR); + } + + /* Clear the RTC I2C transmission bits */ + CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START_FORCE); + CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START); +} + +/* + * I2C transactions when master writes one byte of data to the slave device: + * + * |--------|--------|---------|--------|--------|--------|--------|--------|--------| + * | Master | START | SAD + W | | SUB | | DATA | | STOP | + * |--------|--------|---------|--------|--------|--------|--------|--------|--------| + * | Slave | | | ACK | | ACK | | ACK | | + * |--------|--------|---------|--------|--------|--------|--------|--------|--------| + * + * I2C transactions when master writes multiple bytes of data to the slave device: + * + * |--------|--------|---------|--------|--------|--------|--------|--------|--------|--------|--------| + * | Master | START | SAD + W | | SUB | | DATA | | DATA | | STOP | + * |--------|--------|---------|--------|--------|--------|--------|--------|--------|--------|--------| + * | Slave | | | ACK | | ACK | | ACK | | ACK | | + * |--------|--------|---------|--------|--------|--------|--------|--------|--------|--------|--------| + */ +void ulp_riscv_i2c_master_write_to_device(uint8_t *data_wr, size_t size) +{ + uint32_t i = 0; + uint32_t cmd_idx = 0; + + if (size == 0) { + // Quietly return + return; + } + + /* By default, RTC I2C controller is hard wired to use CMD0 and CMD1 registers for write operations */ + cmd_idx = 0; + + /* Write slave addr + reg addr + data */ + ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_WRITE, 0, 0, 1, 2 + size); + + /* Stop */ + ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_STOP, 0, 0, 0, 0); + + /* Configure the RTC I2C controller in write mode */ + SET_PERI_REG_BITS(SENS_SAR_I2C_CTRL_REG, 0x1, 1, 27); + + /* Enable Tx data interrupt */ + SET_PERI_REG_MASK(RTC_I2C_INT_ENA_REG, RTC_I2C_TX_DATA_INT_ENA); + + for (i = 0; i < size; i++) { + /* Write the data to be transmitted */ + CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, I2C_CTRL_MASTER_TX_DATA_MASK); + SET_PERI_REG_BITS(SENS_SAR_I2C_CTRL_REG, 0xFF, data_wr[i], 19); + + if (i == 0) { + /* Start RTC I2C transmission. (Needn't do it for every byte) */ + SET_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START_FORCE); + SET_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START); + } + + /* Poll for RTC I2C Tx Data interrupt bit to be set */ + while (!REG_GET_FIELD(RTC_I2C_INT_ST_REG, RTC_I2C_TX_DATA_INT_ST)) { } + + /* Clear the Tx data interrupt bit */ + SET_PERI_REG_MASK(RTC_I2C_INT_CLR_REG, RTC_I2C_TX_DATA_INT_CLR); + } + + /* Clear the RTC I2C transmission bits */ + CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START_FORCE); + CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START); +} diff --git a/components/ulp/ulp_riscv/ulp_riscv_i2c.c b/components/ulp/ulp_riscv/ulp_riscv_i2c.c new file mode 100644 index 0000000000..1f1234ed94 --- /dev/null +++ b/components/ulp/ulp_riscv/ulp_riscv_i2c.c @@ -0,0 +1,441 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "ulp_riscv_i2c.h" +#include "esp_check.h" +#include "soc/rtc_i2c_reg.h" +#include "soc/rtc_i2c_struct.h" +#include "soc/rtc_io_struct.h" +#include "soc/sens_reg.h" +#include "soc/clk_tree_defs.h" +#include "hal/i2c_ll.h" +#include "driver/rtc_io.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +static const char *RTCI2C_TAG = "ulp_riscv_i2c"; + +#define I2C_CTRL_SLAVE_ADDR_MASK (0xFF << 0) +#define I2C_CTRL_SLAVE_REG_ADDR_MASK (0xFF << 11) +#define I2C_CTRL_MASTER_TX_DATA_MASK (0xFF << 19) + +#if CONFIG_IDF_TARGET_ESP32S3 +#define ULP_I2C_CMD_RESTART 0 /*!i2c_pin_cfg.sda_io_num; + gpio_num_t scl_io_num = cfg->i2c_pin_cfg.scl_io_num; + bool sda_pullup_en = cfg->i2c_pin_cfg.sda_pullup_en; + bool scl_pullup_en = cfg->i2c_pin_cfg.scl_pullup_en; + + /* Verify that the I2C GPIOs are valid */ + ESP_RETURN_ON_ERROR(i2c_gpio_is_cfg_valid(sda_io_num, scl_io_num), RTCI2C_TAG, "RTC I2C GPIO config invalid"); + + /* Initialize SDA Pin */ + ESP_RETURN_ON_ERROR(i2c_configure_io(sda_io_num, sda_pullup_en), RTCI2C_TAG, "RTC I2C SDA pin config failed"); + + /* Initialize SCL Pin */ + ESP_RETURN_ON_ERROR(i2c_configure_io(scl_io_num, scl_pullup_en), RTCI2C_TAG, "RTC I2C SCL pin config failed"); + + /* Route SDA IO signal to the RTC subsystem */ + rtc_io_dev->touch_pad[sda_io_num].mux_sel = 1; + + /* Route SCL IO signal to the RTC subsystem */ + rtc_io_dev->touch_pad[scl_io_num].mux_sel = 1; + + /* Select RTC I2C function for SDA pin */ + rtc_io_dev->touch_pad[sda_io_num].fun_sel = 3; + + /* Select RTC I2C function for SCL pin */ + rtc_io_dev->touch_pad[scl_io_num].fun_sel = 3; + + /* Map the SDA and SCL signals to the RTC I2C controller */ + if (sda_io_num == GPIO_NUM_1) { + rtc_io_dev->sar_i2c_io.sda_sel = 0; + } else { + rtc_io_dev->sar_i2c_io.sda_sel = 1; + } + + if (scl_io_num == GPIO_NUM_0) { + rtc_io_dev->sar_i2c_io.scl_sel = 0; + } else { + rtc_io_dev->sar_i2c_io.scl_sel = 1; + } + + return ESP_OK; +} + +static esp_err_t i2c_set_timing(const ulp_riscv_i2c_cfg_t *cfg) +{ + /* Convert all timing parameters from micro-seconds to period in RTC_FAST_CLK cycles. + * RTC_FAST_CLK = 8.5 MHz for esp32s2 and 17.5 MHz for esp32s3. + * The following calculations approximate the period for each parameter. + */ + uint32_t scl_low_period = MICROSEC_TO_RTC_FAST_CLK(cfg->i2c_timing_cfg.scl_low_period); + uint32_t scl_high_period = MICROSEC_TO_RTC_FAST_CLK(cfg->i2c_timing_cfg.scl_high_period); + uint32_t sda_duty_period = MICROSEC_TO_RTC_FAST_CLK(cfg->i2c_timing_cfg.sda_duty_period); + uint32_t scl_start_period = MICROSEC_TO_RTC_FAST_CLK(cfg->i2c_timing_cfg.scl_start_period); + uint32_t scl_stop_period = MICROSEC_TO_RTC_FAST_CLK(cfg->i2c_timing_cfg.scl_stop_period); + uint32_t i2c_trans_timeout = MICROSEC_TO_RTC_FAST_CLK(cfg->i2c_timing_cfg.i2c_trans_timeout); + uint32_t setup_time_start = (cfg->i2c_timing_cfg.scl_high_period + cfg->i2c_timing_cfg.sda_duty_period); + uint32_t hold_time_start = (cfg->i2c_timing_cfg.scl_start_period - cfg->i2c_timing_cfg.sda_duty_period); + uint32_t setup_time_data = (cfg->i2c_timing_cfg.scl_low_period - cfg->i2c_timing_cfg.sda_duty_period); + + /* Verify timing constraints */ + ESP_RETURN_ON_FALSE((float)cfg->i2c_timing_cfg.scl_low_period > 1.3, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "SCL low period cannot be less than 1.3 micro seconds"); + ESP_RETURN_ON_FALSE((float)cfg->i2c_timing_cfg.scl_high_period > 0.6, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "SCL high period cannot be less than 0.6 micro seconds"); + ESP_RETURN_ON_FALSE((float)setup_time_start > 0.6, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "Setup time cannot be less than 0.6 micro seconds"); + ESP_RETURN_ON_FALSE((float)hold_time_start > 0.6, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "Data hold time cannot be less than 0.6 micro seconds"); + ESP_RETURN_ON_FALSE((float)cfg->i2c_timing_cfg.scl_stop_period > 0.6, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "Setup time cannot be less than 0.6 micro seconds"); + ESP_RETURN_ON_FALSE((float)cfg->i2c_timing_cfg.sda_duty_period < 3.45, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "Data hold time cannot be greater than 3.45 micro seconds"); + ESP_RETURN_ON_FALSE((float)(setup_time_data * 1000) > 250, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "Data setup time cannot be less than 250 nano seconds"); + + /* Verify filtering constrains + * + * I2C may have glitches on the transition edge, so the edge will be filtered in the design, + * which will also affect the value of the timing parameter register. + * Therefore, the following filtering constraints must be followed: + */ + ESP_RETURN_ON_FALSE(scl_stop_period > scl_high_period, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "SCL Stop period cannot be greater than SCL high period"); + ESP_RETURN_ON_FALSE(sda_duty_period < scl_low_period, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "SDA duty period cannot be less than the SCL low period"); + ESP_RETURN_ON_FALSE(scl_start_period > 8, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "SCL start period must be greater than 8 RTC_FAST_CLK cycles"); + ESP_RETURN_ON_FALSE((scl_low_period + scl_high_period - sda_duty_period) > 8, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "SCL low + SCL high - SDA duty must be greater than 8 RTC_FAST_CLK cycles"); + + /* Verify SDA duty num constraints */ + ESP_RETURN_ON_FALSE(sda_duty_period > 14, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "SDA duty period must be greater than 14 RTC_FAST_CLK cycles"); + + /* Set the RTC I2C timing parameters */ +#if CONFIG_IDF_TARGET_ESP32S2 + i2c_dev->scl_low.val = scl_low_period; // SCL low period + i2c_dev->scl_high.val = scl_high_period; // SCL high period + i2c_dev->sda_duty.val = sda_duty_period; // SDA duty cycle + i2c_dev->scl_start_period.val = scl_start_period; // Wait time after START condition + i2c_dev->scl_stop_period.val = scl_stop_period; // Wait time before END condition + i2c_dev->timeout.val = i2c_trans_timeout; // I2C transaction timeout +#elif CONFIG_IDF_TARGET_ESP32S3 + i2c_dev->i2c_scl_low.val = scl_low_period; // SCL low period + i2c_dev->i2c_scl_high.val = scl_high_period; // SCL high period + i2c_dev->i2c_sda_duty.val = sda_duty_period; // SDA duty cycle + i2c_dev->i2c_scl_start_period.val = scl_start_period; // Wait time after START condition + i2c_dev->i2c_scl_stop_period.val = scl_stop_period; // Wait time before END condition + i2c_dev->i2c_to.val = i2c_trans_timeout; // I2C transaction timeout +#endif // CONFIG_IDF_TARGET_ESP32S2 + + return ESP_OK; +} + +/* + * The RTC I2C controller follows the I2C command registers to perform read/write operations. + * The cmd registers have the following format: + * + * 31 30:14 13:11 10 9 8 7:0 + * |----------|----------|---------|---------|----------|------------|---------| + * | CMD_DONE | Reserved | OPCODE |ACK Value|ACK Expect|ACK Check En|Byte Num | + * |----------|----------|---------|---------|----------|------------|---------| + */ +static void ulp_riscv_i2c_format_cmd(uint32_t cmd_idx, uint8_t op_code, uint8_t ack_val, + uint8_t ack_expected, uint8_t ack_check_en, uint8_t byte_num) +{ +#if CONFIG_IDF_TARGET_ESP32S2 + /* Reset cmd register */ + i2c_dev->command[cmd_idx].val = 0; + + /* Write new command to cmd register */ + i2c_dev->command[cmd_idx].done = 0; // CMD Done + i2c_dev->command[cmd_idx].op_code = op_code; // Opcode + i2c_dev->command[cmd_idx].ack_val = ack_val; // ACK bit sent by I2C controller during READ. + // Ignored during RSTART, STOP, END and WRITE cmds. + i2c_dev->command[cmd_idx].ack_exp = ack_expected; // ACK bit expected by I2C controller during WRITE. + // Ignored during RSTART, STOP, END and READ cmds. + i2c_dev->command[cmd_idx].ack_en = ack_check_en; // I2C controller verifies that the ACK bit sent by the + // slave device matches the ACK expected bit during WRITE. + // Ignored during RSTART, STOP, END and READ cmds. + i2c_dev->command[cmd_idx].byte_num = byte_num; // Byte Num +#elif CONFIG_IDF_TARGET_ESP32S3 + /* Reset cmd register */ + i2c_dev->i2c_cmd[cmd_idx].val = 0; + + /* Write new command to cmd register */ + i2c_dev->i2c_cmd[cmd_idx].i2c_command_done = 0; // CMD Done + i2c_dev->i2c_cmd[cmd_idx].i2c_op_code = op_code; // Opcode + i2c_dev->i2c_cmd[cmd_idx].i2c_ack_val = ack_val; // ACK bit sent by I2C controller during READ. + // Ignored during RSTART, STOP, END and WRITE cmds. + i2c_dev->i2c_cmd[cmd_idx].i2c_ack_exp = ack_expected; // ACK bit expected by I2C controller during WRITE. + // Ignored during RSTART, STOP, END and READ cmds. + i2c_dev->i2c_cmd[cmd_idx].i2c_ack_en = ack_check_en; // I2C controller verifies that the ACK bit sent by the + // slave device matches the ACK expected bit during WRITE. + // Ignored during RSTART, STOP, END and READ cmds. + i2c_dev->i2c_cmd[cmd_idx].i2c_byte_num = byte_num; // Byte Num +#endif // CONFIG_IDF_TARGET_ESP32S2 +} + +void ulp_riscv_i2c_master_set_slave_addr(uint8_t slave_addr) +{ + CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, I2C_CTRL_SLAVE_ADDR_MASK); + SET_PERI_REG_BITS(SENS_SAR_I2C_CTRL_REG, 0xFF, slave_addr, 0); +} + +void ulp_riscv_i2c_master_set_slave_reg_addr(uint8_t slave_reg_addr) +{ + CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, I2C_CTRL_SLAVE_REG_ADDR_MASK); + SET_PERI_REG_BITS(SENS_SAR_I2C_CTRL_REG, 0xFF, slave_reg_addr, 11); +} + +/* + * I2C transactions when master reads one byte of data from the slave device: + * + * |--------|--------|---------|--------|--------|--------|--------|---------|--------|--------|--------|--------| + * | Master | START | SAD + W | | SUB | | SR | SAD + R | | | NACK | STOP | + * |--------|--------|---------|--------|--------|--------|--------|---------|--------|--------|--------|--------| + * | Slave | | | ACK | | ACK | | | ACK | DATA | | | + * |--------|--------|---------|--------|--------|--------|--------|---------|--------|--------|--------|--------| + * + * I2C transactions when master reads multiple bytes of data from the slave device: + * + * |--------|--------|---------|--------|--------|--------|--------|---------|--------|--------|--------|--------|--------|--------| + * | Master | START | SAD + W | | SUB | | SR | SAD + R | | | ACK | | NACK | STOP | + * |--------|--------|---------|--------|--------|--------|--------|---------|--------|--------|--------|--------|--------|--------| + * | Slave | | | ACK | | ACK | | | ACK | DATA | | DATA | | | + * |--------|--------|---------|--------|--------|--------|--------|---------|--------|--------|--------|--------|--------|--------| + */ +void ulp_riscv_i2c_master_read_from_device(uint8_t *data_rd, size_t size) +{ + uint32_t i = 0; + uint32_t cmd_idx = 0; + + if (size == 0) { + // Quietly return + return; + } + + /* By default, RTC I2C controller is hard wired to use CMD2 register onwards for read operations */ + cmd_idx = 2; + + /* Write slave addr */ + ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_WRITE, 0, 0, 1, 2); + + /* Repeated START */ + ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_RESTART, 0, 0, 0, 0); + + /* Write slave register addr */ + ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_WRITE, 0, 0, 1, 1); + + if (size > 1) { + /* Read n - 1 bytes */ + ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_READ, 0, 0, 1, size - 1); + } + + /* Read last byte + NACK */ + ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_READ, 1, 1, 1, 1); + + /* STOP */ + ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_STOP, 0, 0, 0, 0); + + /* Configure the RTC I2C controller in read mode */ + SET_PERI_REG_BITS(SENS_SAR_I2C_CTRL_REG, 0x1, 0, 27); + + /* Enable Rx data interrupt */ + SET_PERI_REG_MASK(RTC_I2C_INT_ENA_REG, RTC_I2C_RX_DATA_INT_ENA); + + /* Start RTC I2C transmission */ + SET_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START_FORCE); + SET_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START); + + for (i = 0; i < size; i++) { + /* Poll for RTC I2C Rx Data interrupt bit to be set */ + while (!REG_GET_FIELD(RTC_I2C_INT_ST_REG, RTC_I2C_RX_DATA_INT_ST)) { + /* Minimal delay to avoid hogging the CPU */ + vTaskDelay(1); + } + + /* Read the data + * + * Unfortunately, the RTC I2C has no fifo buffer to help us with reading and storing + * multiple bytes of data. Therefore, we need to read one byte at a time and clear the + * Rx interrupt to get ready for the next byte. + */ +#if CONFIG_IDF_TARGET_ESP32S2 + data_rd[i] = REG_GET_FIELD(RTC_I2C_DATA_REG, RTC_I2C_RDATA); +#elif CONFIG_IDF_TARGET_ESP32S3 + data_rd[i] = REG_GET_FIELD(RTC_I2C_DATA_REG, RTC_I2C_I2C_RDATA); +#endif // CONFIG_IDF_TARGET_ESP32S2 + + /* Clear the Rx data interrupt bit */ + SET_PERI_REG_MASK(RTC_I2C_INT_CLR_REG, RTC_I2C_RX_DATA_INT_CLR); + } + + /* Clear the RTC I2C transmission bits */ + CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START_FORCE); + CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START); +} + +/* + * I2C transactions when master writes one byte of data to the slave device: + * + * |--------|--------|---------|--------|--------|--------|--------|--------|--------| + * | Master | START | SAD + W | | SUB | | DATA | | STOP | + * |--------|--------|---------|--------|--------|--------|--------|--------|--------| + * | Slave | | | ACK | | ACK | | ACK | | + * |--------|--------|---------|--------|--------|--------|--------|--------|--------| + * + * I2C transactions when master writes multiple bytes of data to the slave device: + * + * |--------|--------|---------|--------|--------|--------|--------|--------|--------|--------|--------| + * | Master | START | SAD + W | | SUB | | DATA | | DATA | | STOP | + * |--------|--------|---------|--------|--------|--------|--------|--------|--------|--------|--------| + * | Slave | | | ACK | | ACK | | ACK | | ACK | | + * |--------|--------|---------|--------|--------|--------|--------|--------|--------|--------|--------| + */ +void ulp_riscv_i2c_master_write_to_device(uint8_t *data_wr, size_t size) +{ + uint32_t i = 0; + uint32_t cmd_idx = 0; + + if (size == 0) { + // Quietly return + return; + } + + /* By default, RTC I2C controller is hard wired to use CMD0 and CMD1 registers for write operations */ + cmd_idx = 0; + + /* Write slave addr + reg addr + data */ + ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_WRITE, 0, 0, 1, 2 + size); + + /* Stop */ + ulp_riscv_i2c_format_cmd(cmd_idx++, ULP_I2C_CMD_STOP, 0, 0, 0, 0); + + /* Configure the RTC I2C controller in write mode */ + SET_PERI_REG_BITS(SENS_SAR_I2C_CTRL_REG, 0x1, 1, 27); + + /* Enable Tx data interrupt */ + SET_PERI_REG_MASK(RTC_I2C_INT_ENA_REG, RTC_I2C_TX_DATA_INT_ENA); + + for (i = 0; i < size; i++) { + /* Write the data to be transmitted */ + CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, I2C_CTRL_MASTER_TX_DATA_MASK); + SET_PERI_REG_BITS(SENS_SAR_I2C_CTRL_REG, 0xFF, data_wr[i], 19); + + if (i == 0) { + /* Start RTC I2C transmission. (Needn't do it for every byte) */ + SET_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START_FORCE); + SET_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START); + } + + /* Poll for RTC I2C Tx Data interrupt bit to be set */ + while (!REG_GET_FIELD(RTC_I2C_INT_ST_REG, RTC_I2C_TX_DATA_INT_ST)) { + /* Minimal delay to avoid hogging the CPU */ + vTaskDelay(1); + } + + /* Clear the Tx data interrupt bit */ + SET_PERI_REG_MASK(RTC_I2C_INT_CLR_REG, RTC_I2C_TX_DATA_INT_CLR); + } + + /* Clear the RTC I2C transmission bits */ + CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START_FORCE); + CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START); +} + +esp_err_t ulp_riscv_i2c_master_init(const ulp_riscv_i2c_cfg_t *cfg) +{ + /* Reset RTC I2C */ +#if CONFIG_IDF_TARGET_ESP32S2 + i2c_dev->ctrl.i2c_reset = 1; + esp_rom_delay_us(20); + i2c_dev->ctrl.i2c_reset = 0; +#elif CONFIG_IDF_TARGET_ESP32S3 + SET_PERI_REG_MASK(SENS_SAR_PERI_RESET_CONF_REG, SENS_RTC_I2C_RESET); + i2c_dev->i2c_ctrl.i2c_i2c_reset = 1; + esp_rom_delay_us(20); + i2c_dev->i2c_ctrl.i2c_i2c_reset = 0; + CLEAR_PERI_REG_MASK(SENS_SAR_PERI_RESET_CONF_REG, SENS_RTC_I2C_RESET); +#endif // CONFIG_IDF_TARGET_ESP32S2 + + /* Verify that the input cfg param is valid */ + ESP_RETURN_ON_FALSE(cfg, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "RTC I2C configuration is NULL"); + + /* Configure RTC I2C GPIOs */ + ESP_RETURN_ON_ERROR(i2c_set_pin(cfg), RTCI2C_TAG, "Failed to configure RTC I2C GPIOs"); + +#if CONFIG_IDF_TARGET_ESP32S2 + /* Configure the RTC I2C controller in master mode */ + i2c_dev->ctrl.ms_mode = 1; + + /* Enable RTC I2C Clock gate */ + i2c_dev->ctrl.i2c_ctrl_clk_gate_en = 1; +#elif CONFIG_IDF_TARGET_ESP32S3 + /* For esp32s3, we need to enable the rtc_i2c clock gate before accessing rtc i2c registers */ + SET_PERI_REG_MASK(SENS_SAR_PERI_CLK_GATE_CONF_REG, SENS_RTC_I2C_CLK_EN); + + /* Configure the RTC I2C controller in master mode */ + i2c_dev->i2c_ctrl.i2c_ms_mode = 1; + + /* Enable RTC I2C Clock gate */ + i2c_dev->i2c_ctrl.i2c_i2c_ctrl_clk_gate_en = 1; +#endif // CONFIG_IDF_TARGET_ESP32S2 + + /* Configure RTC I2C timing paramters */ + ESP_RETURN_ON_ERROR(i2c_set_timing(cfg), RTCI2C_TAG, "Failed to configure RTC I2C timing"); + + return ESP_OK; +} diff --git a/examples/system/.build-test-rules.yml b/examples/system/.build-test-rules.yml index 26179c8764..35afa15081 100644 --- a/examples/system/.build-test-rules.yml +++ b/examples/system/.build-test-rules.yml @@ -194,6 +194,10 @@ examples/system/ulp_riscv/gpio_interrupt: temporary: true reason: the other targets are not tested yet +examples/system/ulp_riscv/i2c: + enable: + - if: SOC_RISCV_COPROC_SUPPORTED == 1 + examples/system/ulp_riscv/uart_print: enable: - if: SOC_RISCV_COPROC_SUPPORTED == 1 diff --git a/examples/system/ulp_riscv/i2c/CMakeLists.txt b/examples/system/ulp_riscv/i2c/CMakeLists.txt new file mode 100644 index 0000000000..d186d07abc --- /dev/null +++ b/examples/system/ulp_riscv/i2c/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(ulp-riscv-rtc-i2c-example) diff --git a/examples/system/ulp_riscv/i2c/README.md b/examples/system/ulp_riscv/i2c/README.md new file mode 100644 index 0000000000..11ba448c06 --- /dev/null +++ b/examples/system/ulp_riscv/i2c/README.md @@ -0,0 +1,76 @@ +| Supported Targets | ESP32-S2 | ESP32-S3 | +| ----------------- | -------- | -------- | + +# ULP RISC-V I2C Example + +This example demonstrates how to use the RTC I2C peripheral from the ULP RISC-V coprocessor in deep sleep. + +The ULP program is based on the BMP180 Temperature and Pressure sensor (https://cz.mouser.com/datasheet/2/783/BST-BMP180-DS000-1509579.pdf) which has an I2C interface. The main CPU initializes the RTC I2C peripheral, the BMP180 sensor and loads the ULP program. It then goes into deep sleep. + +The ULP program periodically measures the temperature and pressure values from the BMP180 sensor and wakesup the main CPU when the values are above a certain thershold. +### Hardware Required + +* A development board with a SOC which has a RISC-V ULP coprocessor (e.g., ESP32-S2 Saola) +* A BMP180 sensor module +* A USB cable for power supply and programming + +## Example output + +Below is the output from this example. + +``` +Not a ULP-RISC V wakeup (cause = 0) +Initializing RTC I2C ... +RTC_I2C_STATUS_REG = 0x00000000 +Reading calibration data from BMP180 ... +ac1 = 7819 +ac2 = -1152 +ac3 = -14317 +ac4 = 34252 +ac5 = 25122 +ac6 = 14289 +b1 = 6515 +b2 = 44 +mb = -32768 +mc = -11786 +md = 2746 + +Reading initial uncompensated temperature and pressure data ... +Uncompensated Temperature = 22865 +Uncompensated Pressure = 41768 + +Real Temperature = 24.900000 deg celcius +Real Pressure = 990.640000 hPa + +Entering in deep sleep + +ESP-ROM:esp32s2-rc4-20191025 +Build:Oct 25 2019 +rst:0x5 (DSLEEP),boot:0x9 (SPI_FAST_FLASH_BOOT) +SPIWP:0xee +mode:DIO, clock div:1 +load:0x3ffe6108,len:0x1298 +load:0x4004c000,len:0x92c +load:0x40050000,len:0x2f04 +entry 0x4004c154 +W (76) spi_flash: Detected size(4096k) larger than the size in the binary image header(2048k). Using the size in the binary image header. +ULP RISC-V woke up the main CPU +Uncompensated Temperature = 22865 +Uncompensated Pressure = 41765 +Reading calibration data from BMP180 ... +ac1 = 7819 +ac2 = -1152 +ac3 = -14317 +ac4 = 34252 +ac5 = 25122 +ac6 = 14289 +b1 = 6515 +b2 = 44 +mb = -32768 +mc = -11786 +md = 2746 + +New Real Temperature = 24.900000 deg celcius +New Real Pressure = 990.550000 hPa +Entering in deep sleep +``` diff --git a/examples/system/ulp_riscv/i2c/main/CMakeLists.txt b/examples/system/ulp_riscv/i2c/main/CMakeLists.txt new file mode 100644 index 0000000000..1f929ff573 --- /dev/null +++ b/examples/system/ulp_riscv/i2c/main/CMakeLists.txt @@ -0,0 +1,25 @@ +idf_component_register(SRCS "ulp_riscv_rtc_i2c_example_main.c" + INCLUDE_DIRS "" + REQUIRES soc ulp) +target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format") + +# +# ULP support additions to component CMakeLists.txt. +# +# 1. The ULP app name must be unique (if multiple components use ULP). +set(ulp_app_name ulp_${COMPONENT_NAME}) +# +# 2. Specify all C and Assembly source files. +# Files should be placed into a separate directory (in this case, ulp/), +# which should not be added to COMPONENT_SRCS. +set(ulp_riscv_sources "ulp/main.c") + +# +# 3. List all the component source files which include automatically +# generated ULP export file, ${ulp_app_name}.h: +set(ulp_exp_dep_srcs "ulp_riscv_rtc_i2c_example_main.c") + +# +# 4. Call function to build ULP binary and embed in project using the argument +# values above. +ulp_embed_binary(${ulp_app_name} "${ulp_riscv_sources}" "${ulp_exp_dep_srcs}") diff --git a/examples/system/ulp_riscv/i2c/main/bmp180_defs.h b/examples/system/ulp_riscv/i2c/main/bmp180_defs.h new file mode 100644 index 0000000000..d8ae173e92 --- /dev/null +++ b/examples/system/ulp_riscv/i2c/main/bmp180_defs.h @@ -0,0 +1,110 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#pragma once + +/*************************************************** + * BMP180 Register Addresses + ***************************************************/ +#define BMP180_SENSOR_I2C_ADDR 0x77 +#define BMP180_SENSOR_REG_ADDR_WHO_AM_I 0xD0 +#define BMP180_SENSOR_REG_ADDR_SOFT_RESET 0xE0 +#define BMP180_SENSOR_REG_ADDR_AC1_MSB 0xAA +#define BMP180_SENSOR_REG_ADDR_AC1_LSB 0xAB +#define BMP180_SENSOR_REG_ADDR_AC2_MSB 0xAC +#define BMP180_SENSOR_REG_ADDR_AC2_LSB 0xAD +#define BMP180_SENSOR_REG_ADDR_AC3_MSB 0xAE +#define BMP180_SENSOR_REG_ADDR_AC3_LSB 0xAF +#define BMP180_SENSOR_REG_ADDR_AC4_MSB 0xB0 +#define BMP180_SENSOR_REG_ADDR_AC4_LSB 0xB1 +#define BMP180_SENSOR_REG_ADDR_AC5_MSB 0xB2 +#define BMP180_SENSOR_REG_ADDR_AC5_LSB 0xB3 +#define BMP180_SENSOR_REG_ADDR_AC6_MSB 0xB4 +#define BMP180_SENSOR_REG_ADDR_AC6_LSB 0xB5 +#define BMP180_SENSOR_REG_ADDR_B1_MSB 0xB6 +#define BMP180_SENSOR_REG_ADDR_B1_LSB 0xB7 +#define BMP180_SENSOR_REG_ADDR_B2_MSB 0xB8 +#define BMP180_SENSOR_REG_ADDR_B2_LSB 0xB9 +#define BMP180_SENSOR_REG_ADDR_MB_MSB 0xBA +#define BMP180_SENSOR_REG_ADDR_MB_LSB 0xBB +#define BMP180_SENSOR_REG_ADDR_MC_MSB 0xBC +#define BMP180_SENSOR_REG_ADDR_MC_LSB 0xBD +#define BMP180_SENSOR_REG_ADDR_MD_MSB 0xBE +#define BMP180_SENSOR_REG_ADDR_MD_LSB 0xBF +#define BMP180_SENSOR_REG_ADDR_CTRL_REG 0xF4 +#define BMP180_SENSOR_REG_ADDR_SENSOR_DATA_MSB 0xF6 +#define BMP180_SENSOR_REG_ADDR_SENSOR_DATA_LSB 0xF7 +#define BMP180_SENSOR_REG_ADDR_SENSOR_DATA_XLSB 0xF8 + +/*************************************************** + * BMP180 Control Commands + ***************************************************/ +#define BMP180_SENSOR_CMD_READ_TEMPERATURE 0x2E +#define BMP180_SENSOR_CMD_READ_PRESSURE_OSS_0 0x34 +#define BMP180_SENSOR_CMD_READ_PRESSURE_OSS_1 0x74 +#define BMP180_SENSOR_CMD_READ_PRESSURE_OSS_2 0xB4 +#define BMP180_SENSOR_CMD_READ_PRESSURE_OSS_3 0xF4 +#define BMP180_SENSOR_CMD_SOFT_RESET 0xB6 + +/*************************************************** + * BMP180 Chip ID + ***************************************************/ +#define BMP180_SENSOR_CHIP_ID 0x55 + +/*************************************************** + * BMP180 Calibration Data + ***************************************************/ +typedef struct { + int16_t ac1; + int16_t ac2; + int16_t ac3; + uint16_t ac4; + uint16_t ac5; + uint16_t ac6; + int16_t b1; + int16_t b2; + int16_t mb; + int16_t mc; + int16_t md; +} bmp180_cal_data_t; + +bmp180_cal_data_t bmp180_cal_data; + +/*************************************************** + * BMP180 Oversampling setting to measure pressure + ***************************************************/ +typedef enum { + OSS_0 = 0, // Ultra low power + OSS_1 = 1, // Standard + OSS_2 = 2, // High resolution + OSS_3 = 3 // Ultra high resolution +} oss_mode_t; + +/*************************************************** + * BMP180 Interaction APIs + ***************************************************/ +static void bmp180_read_cal_data(void); // Read cal data +static void bmp180_read_ut_data(int16_t *ut_data); // Read uncompensated temperature +static void bmp180_read_up_data(int32_t *up_data, oss_mode_t oss_mode); // Read uncompensated pressure + +/************************************************ + * BMP180 Utility APIs + ************************************************/ +static void bmp180_read16(uint16_t *data_out, uint32_t reg_msb, uint32_t reg_lsb); +static int32_t bmp180_calculate_real_temp(int32_t ut_data); +static int32_t bmp180_calculate_real_pressure(int32_t up_data, int32_t ut_data, oss_mode_t oss_mode); + +/************************************************ + * Pressure measurement mode + ************************************************/ +#define EXAMPLE_OSS_MODE OSS_0 + +/************************************************ + * Temperature and Pressure thresholds (uncompensated) to wake up Main CPU + * The threshold values have been selected for demp purposes and may not + * represent real world use case. + ************************************************/ +#define EXAMPLE_UT_THRESHOLD 20000 +#define EXAMPLE_UP_THRESHOLD 40000 diff --git a/examples/system/ulp_riscv/i2c/main/ulp/main.c b/examples/system/ulp_riscv/i2c/main/ulp/main.c new file mode 100644 index 0000000000..ba545ac6ea --- /dev/null +++ b/examples/system/ulp_riscv/i2c/main/ulp/main.c @@ -0,0 +1,126 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +/* ULP RISC-V RTC I2C example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. + + This code runs on ULP RISC-V coprocessor +*/ + +#include +#include "ulp_riscv.h" +#include "ulp_riscv_utils.h" +#include "ulp_riscv_i2c_ulp_core.h" +#include "../bmp180_defs.h" + +/************************************************ + * Shared data between main CPU and ULP + ************************************************/ +int16_t ut_data = 0; +int32_t up_data = 0; +int32_t ut_threshold = EXAMPLE_UT_THRESHOLD; +int32_t up_threshold = EXAMPLE_UP_THRESHOLD; +oss_mode_t oss_mode = EXAMPLE_OSS_MODE; + +int main (void) +{ + /* Read uncompensated temperature */ + bmp180_read_ut_data(&ut_data); + + /* Read uncompensated pressure */ + bmp180_read_up_data(&up_data, oss_mode); + + /* Wakeup the main CPU if either the uncompensated temperature or uncompensated pressure values + * are more than their respective threshold values. + */ + if ((ut_data > ut_threshold) || (up_data > up_threshold)) { + ulp_riscv_wakeup_main_processor(); + } + + return 0; +} + +static void bmp180_read16(uint16_t *data_out, uint32_t reg_msb, uint32_t reg_lsb) +{ + uint8_t data_rd = 0; + *data_out = 0; + + ulp_riscv_i2c_master_set_slave_reg_addr(reg_msb); + ulp_riscv_i2c_master_read_from_device(&data_rd, 1); + *data_out |= (uint16_t)(data_rd << 8); + ulp_riscv_i2c_master_set_slave_reg_addr(reg_lsb); + data_rd = 0; + ulp_riscv_i2c_master_read_from_device(&data_rd, 1); + *data_out |= (uint16_t)(data_rd); +} + +static void bmp180_read_ut_data(int16_t *ut_data) +{ + /* Set slave register address to the control register */ + ulp_riscv_i2c_master_set_slave_reg_addr(BMP180_SENSOR_REG_ADDR_CTRL_REG); + + /* Setup control register to read temperature */ + uint8_t cmd = BMP180_SENSOR_CMD_READ_TEMPERATURE; + ulp_riscv_i2c_master_write_to_device(&cmd, 1); + + /* Wait at least 4.5 milliseconds for the sensor to complete the reading */ + ulp_riscv_delay_cycles(5 * ULP_RISCV_CYCLES_PER_US * 1000); + + /* Read uncompensated temperature data */ + bmp180_read16((uint16_t *)ut_data, BMP180_SENSOR_REG_ADDR_SENSOR_DATA_MSB, BMP180_SENSOR_REG_ADDR_SENSOR_DATA_LSB); +} + +static void bmp180_read_up_data(int32_t *up_data, oss_mode_t oss_mode) +{ + uint16_t press_high; + uint8_t press_low; + + /* Set slave register address to the control register */ + ulp_riscv_i2c_master_set_slave_reg_addr(BMP180_SENSOR_REG_ADDR_CTRL_REG); + + /* Setup control register to read pressure */ + uint8_t cmd = 0; + uint8_t wait = 0; + switch(oss_mode) + { + case OSS_0: + cmd = BMP180_SENSOR_CMD_READ_PRESSURE_OSS_0; + wait = 5; // Wait atleast 4.5 msec + break; + case OSS_1: + cmd = BMP180_SENSOR_CMD_READ_PRESSURE_OSS_1; + wait = 8; // Wait atleast 7.5 msec + break; + case OSS_2: + cmd = BMP180_SENSOR_CMD_READ_PRESSURE_OSS_2; + wait = 14; // Wait atleast 13.5 msec + break; + case OSS_3: + cmd = BMP180_SENSOR_CMD_READ_PRESSURE_OSS_3; + wait = 26; // Wait atleast 25.5 msec + break; + } + + ulp_riscv_i2c_master_write_to_device(&cmd, 1); + + /* Wait for the required amount of time for the sensor to complete the reading */ + ulp_riscv_delay_cycles(wait * ULP_RISCV_CYCLES_PER_US * 1000); + + /* Read uncompensated temperature data */ + + /* Read MSB + LSB */ + bmp180_read16(&press_high, BMP180_SENSOR_REG_ADDR_SENSOR_DATA_MSB, BMP180_SENSOR_REG_ADDR_SENSOR_DATA_LSB); + + /* Read XLSB */ + ulp_riscv_i2c_master_set_slave_reg_addr(BMP180_SENSOR_REG_ADDR_SENSOR_DATA_XLSB); + ulp_riscv_i2c_master_read_from_device(&press_low, 1); + + *up_data = (((uint32_t)press_high << 8) + (uint32_t)press_low) >> (8 - oss_mode); +} diff --git a/examples/system/ulp_riscv/i2c/main/ulp_riscv_rtc_i2c_example_main.c b/examples/system/ulp_riscv/i2c/main/ulp_riscv_rtc_i2c_example_main.c new file mode 100644 index 0000000000..3cb3ff2307 --- /dev/null +++ b/examples/system/ulp_riscv/i2c/main/ulp_riscv_rtc_i2c_example_main.c @@ -0,0 +1,351 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +/* ULP RISC-V RTC I2C example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include +#include "esp_sleep.h" +#include "ulp_riscv.h" +#include "ulp_riscv_i2c.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "ulp_main.h" +#include "bmp180_defs.h" + +extern const uint8_t ulp_main_bin_start[] asm("_binary_ulp_main_bin_start"); +extern const uint8_t ulp_main_bin_end[] asm("_binary_ulp_main_bin_end"); + +/************************************************ + * ULP utility APIs + ************************************************/ +static void init_ulp_program(void); + +/************************************************ + * RTC I2C utility APIs + ************************************************/ +static void init_i2c(void); + +void app_main(void) +{ + uint8_t data_rd = 0; + int16_t ut_data = 0; + int32_t up_data = 0; + int32_t temperature = 0; + int32_t pressure = 0; + oss_mode_t oss_mode; + + esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause(); + + /* Not a wakeup from ULP + * Initialize RTC I2C + * Setup BMP180 sensor + * Store current temperature and pressure values + * Load the ULP firmware + * Go to deep sleep + */ + if (cause != ESP_SLEEP_WAKEUP_ULP) { + printf("Not a ULP-RISC V wakeup (cause = %d)\n", cause); + + /* Initialize RTC I2C */ + init_i2c(); + + /* Configure I2C slave address */ + ulp_riscv_i2c_master_set_slave_addr(BMP180_SENSOR_I2C_ADDR); + + /* Reset the BMP180 sensor*/ + ulp_riscv_i2c_master_set_slave_reg_addr(BMP180_SENSOR_REG_ADDR_SOFT_RESET); + uint8_t data_wr = BMP180_SENSOR_CMD_SOFT_RESET; + ulp_riscv_i2c_master_write_to_device(&data_wr, 1); + + /* Confirm that the sensor is alive + * The BMP180 returns the chip id 0x55 on quering reg addr 0xD0 + */ + ulp_riscv_i2c_master_set_slave_reg_addr(BMP180_SENSOR_REG_ADDR_WHO_AM_I); + ulp_riscv_i2c_master_read_from_device(&data_rd, 1); + if (data_rd != BMP180_SENSOR_CHIP_ID) { + printf("ERROR: Cannot communicate with I2C sensor\n"); + abort(); + } + + /* Read the calibration data */ + printf("Reading calibration data from BMP180 ...\n"); + bmp180_read_cal_data(); + printf("\n"); + + /* Read uncompensated temperature and pressure */ + printf("Reading initial uncompensated temperature and pressure data ...\n"); + ut_data = 0; + up_data = 0; + oss_mode = EXAMPLE_OSS_MODE; + bmp180_read_ut_data(&ut_data); + bmp180_read_up_data(&up_data, oss_mode); + printf("Uncompensated Temperature = %d\n", ut_data); + printf("Uncompensated Pressure = %d\n", up_data); + printf("\n"); + + /* Calculate real temperature value */ + temperature = bmp180_calculate_real_temp((int32_t)ut_data); + printf("Real Temperature = %f deg celcius\n", (float)(temperature/10.0)); + + /* Calculate real pressure value */ + pressure = bmp180_calculate_real_pressure(up_data, (int32_t)ut_data, oss_mode); + printf("Real Pressure = %f hPa\n", pressure / 100.0); + printf("\n"); + + /* Load ULP firmware + * + * The ULP is responsible of monitoring the temperature and pressure values + * periodically. It will wakeup the main CPU if the temperature and pressure + * values are above a certain threshold. + */ + init_ulp_program(); + } + + /* ULP RISC-V read and detected a temperature or pressure above the limit */ + if (cause == ESP_SLEEP_WAKEUP_ULP) { + printf("ULP RISC-V woke up the main CPU\n"); + + /* Pause ULP while we are using the RTC I2C from the main CPU */ + ulp_timer_stop(); + ulp_riscv_halt(); + + printf("Uncompensated Temperature = %d\n", ulp_ut_data); + printf("Uncompensated Pressure = %d\n", ulp_up_data); + + /* Read the calibration data again */ + printf("Reading calibration data from BMP180 ...\n"); + bmp180_read_cal_data(); + printf("\n"); + + /* Calculate real temperature and pressure again */ + temperature = 0; + temperature = bmp180_calculate_real_temp((int32_t)ulp_ut_data); + printf("New Real Temperature = %f deg celcius\n", (float)(temperature/10.0)); + + /* Calculate real pressure value */ + pressure = 0; + pressure = bmp180_calculate_real_pressure(ulp_up_data, (int32_t)ulp_ut_data, oss_mode); + printf("New Real Pressure = %f hPa\n", pressure / 100.0); + + /* Resume ULP and go to deep sleep again */ + ulp_timer_resume(); + } + + + /* Add a delay for everything to the printed before heading in to light sleep */ + vTaskDelay(100); + + /* Go back to sleep, only the ULP RISC-V will run */ + printf("Entering deep sleep\n\n"); + + /* RTC peripheral power domain needs to be kept on to keep RTC I2C related configs during sleep */ + esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); + + ESP_ERROR_CHECK(esp_sleep_enable_ulp_wakeup()); + + esp_deep_sleep_start(); +} + +static void init_i2c(void) +{ + /* Configure RTC I2C */ + printf("Initializing RTC I2C ...\n"); + ulp_riscv_i2c_cfg_t i2c_cfg = ULP_RISCV_I2C_DEFAULT_CONFIG(); + ulp_riscv_i2c_master_init(&i2c_cfg); +} + +static void bmp180_read16(uint16_t *data_out, uint32_t reg_msb, uint32_t reg_lsb) +{ + uint8_t data_rd = 0; + *data_out = 0; + + ulp_riscv_i2c_master_set_slave_reg_addr(reg_msb); + ulp_riscv_i2c_master_read_from_device(&data_rd, 1); + *data_out |= (uint16_t)(data_rd << 8); + ulp_riscv_i2c_master_set_slave_reg_addr(reg_lsb); + data_rd = 0; + ulp_riscv_i2c_master_read_from_device(&data_rd, 1); + *data_out |= (uint16_t)(data_rd); +} + +static void bmp180_read_cal_data(void) +{ + /* AC1 */ + bmp180_read16((uint16_t *)&bmp180_cal_data.ac1, BMP180_SENSOR_REG_ADDR_AC1_MSB, BMP180_SENSOR_REG_ADDR_AC1_LSB); + printf("ac1 = %d\n", bmp180_cal_data.ac1); + + /* AC2 */ + bmp180_read16((uint16_t *)&bmp180_cal_data.ac2, BMP180_SENSOR_REG_ADDR_AC2_MSB, BMP180_SENSOR_REG_ADDR_AC2_LSB); + printf("ac2 = %d\n", bmp180_cal_data.ac2); + + /* AC3 */ + bmp180_read16((uint16_t *)&bmp180_cal_data.ac3, BMP180_SENSOR_REG_ADDR_AC3_MSB, BMP180_SENSOR_REG_ADDR_AC3_LSB); + printf("ac3 = %d\n", bmp180_cal_data.ac3); + + /* AC4 */ + bmp180_read16(&bmp180_cal_data.ac4, BMP180_SENSOR_REG_ADDR_AC4_MSB, BMP180_SENSOR_REG_ADDR_AC4_LSB); + printf("ac4 = %u\n", bmp180_cal_data.ac4); + + /* AC5 */ + bmp180_read16(&bmp180_cal_data.ac5, BMP180_SENSOR_REG_ADDR_AC5_MSB, BMP180_SENSOR_REG_ADDR_AC5_LSB); + printf("ac5 = %u\n", bmp180_cal_data.ac5); + + /* AC6 */ + bmp180_read16(&bmp180_cal_data.ac6, BMP180_SENSOR_REG_ADDR_AC6_MSB, BMP180_SENSOR_REG_ADDR_AC6_LSB); + printf("ac6 = %u\n", bmp180_cal_data.ac6); + + /* B1 */ + bmp180_read16((uint16_t *)&bmp180_cal_data.b1, BMP180_SENSOR_REG_ADDR_B1_MSB, BMP180_SENSOR_REG_ADDR_B1_LSB); + printf("b1 = %d\n", bmp180_cal_data.b1); + + /* B2 */ + bmp180_read16((uint16_t *)&bmp180_cal_data.b2, BMP180_SENSOR_REG_ADDR_B2_MSB, BMP180_SENSOR_REG_ADDR_B2_LSB); + printf("b2 = %d\n", bmp180_cal_data.b2); + + /* MB */ + bmp180_read16((uint16_t *)&bmp180_cal_data.mb, BMP180_SENSOR_REG_ADDR_MB_MSB, BMP180_SENSOR_REG_ADDR_MB_LSB); + printf("mb = %d\n", bmp180_cal_data.mb); + + /* MC */ + bmp180_read16((uint16_t *)&bmp180_cal_data.mc, BMP180_SENSOR_REG_ADDR_MC_MSB, BMP180_SENSOR_REG_ADDR_MC_LSB); + printf("mc = %d\n", bmp180_cal_data.mc); + + /* MD */ + bmp180_read16((uint16_t *)&bmp180_cal_data.md, BMP180_SENSOR_REG_ADDR_MD_MSB, BMP180_SENSOR_REG_ADDR_MD_LSB); + printf("md = %d\n", bmp180_cal_data.md); +} + +static void bmp180_read_ut_data(int16_t *ut_data) +{ + /* Set slave register address to the control register */ + ulp_riscv_i2c_master_set_slave_reg_addr(BMP180_SENSOR_REG_ADDR_CTRL_REG); + + /* Setup control register to read temperature */ + uint8_t cmd = BMP180_SENSOR_CMD_READ_TEMPERATURE; + ulp_riscv_i2c_master_write_to_device(&cmd, 1); + + /* Wait at least 4.5 milliseconds for the sensor to complete the reading */ + vTaskDelay(pdMS_TO_TICKS(5)); + + /* Read uncompensated temperature data */ + bmp180_read16((uint16_t *)ut_data, BMP180_SENSOR_REG_ADDR_SENSOR_DATA_MSB, BMP180_SENSOR_REG_ADDR_SENSOR_DATA_LSB); +} + +static int32_t computeb5(int32_t ut_data) +{ + int32_t x1 = (ut_data - (int32_t)bmp180_cal_data.ac6) * ((int32_t)bmp180_cal_data.ac5) >> 15; + int32_t x2 = ((int32_t)bmp180_cal_data.mc << 11) / (x1 + (int32_t)bmp180_cal_data.md); + return x1 + x2; +} + +static int32_t bmp180_calculate_real_temp(int32_t ut_data) +{ + int32_t b5 = computeb5(ut_data); + int32_t t = (b5 + 8) >> 4; + + return t; +} + +static void bmp180_read_up_data(int32_t *up_data, oss_mode_t oss_mode) +{ + uint16_t press_high; + uint8_t press_low; + + /* Set slave register address to the control register */ + ulp_riscv_i2c_master_set_slave_reg_addr(BMP180_SENSOR_REG_ADDR_CTRL_REG); + + /* Setup control register to read pressure */ + uint8_t cmd = 0; + uint8_t wait = 0; + switch(oss_mode) + { + case OSS_0: + cmd = BMP180_SENSOR_CMD_READ_PRESSURE_OSS_0; + wait = 5; // Wait atleast 4.5 msec + break; + case OSS_1: + cmd = BMP180_SENSOR_CMD_READ_PRESSURE_OSS_1; + wait = 8; // Wait atleast 7.5 msec + break; + case OSS_2: + cmd = BMP180_SENSOR_CMD_READ_PRESSURE_OSS_2; + wait = 14; // Wait atleast 13.5 msec + break; + case OSS_3: + cmd = BMP180_SENSOR_CMD_READ_PRESSURE_OSS_3; + wait = 26; // Wait atleast 25.5 msec + break; + } + + ulp_riscv_i2c_master_write_to_device(&cmd, 1); + + /* Wait for the required amount of time for the sensor to complete the reading */ + vTaskDelay(pdMS_TO_TICKS(wait)); + + /* Read uncompensated temperature data */ + + /* Read MSB + LSB */ + bmp180_read16(&press_high, BMP180_SENSOR_REG_ADDR_SENSOR_DATA_MSB, BMP180_SENSOR_REG_ADDR_SENSOR_DATA_LSB); + + /* Read XLSB */ + ulp_riscv_i2c_master_set_slave_reg_addr(BMP180_SENSOR_REG_ADDR_SENSOR_DATA_XLSB); + ulp_riscv_i2c_master_read_from_device(&press_low, 1); + + *up_data = (((uint32_t)press_high << 8) + (uint32_t)press_low) >> (8 - oss_mode); +} + +static int32_t bmp180_calculate_real_pressure(int32_t up_data, int32_t ut_data, oss_mode_t oss_mode) +{ + int32_t p, x1, x2, x3, b3, b5, b6; + uint32_t b4, b7; + + b5 = computeb5(ut_data); + b6 = b5 - 4000; + x1 = (bmp180_cal_data.b2 * ((b6 * b6) >> 12)) >> 11; + x2 = (bmp180_cal_data.ac2 * b6) >> 11; + x3 = x1 + x2; + b3 = (((((int32_t) bmp180_cal_data.ac1) * 4 + x3) << oss_mode) + 2) >> 2; + x1 = (bmp180_cal_data.ac3 * b6) >> 13; + x2 = (bmp180_cal_data.b1 * ((b6 * b6) >> 12)) >> 16; + x3 = ((x1 + x2) + 2) >> 2; + b4 = (bmp180_cal_data.ac4 * (uint32_t) (x3 + 32768)) >> 15; + b7 = ((uint32_t) (up_data - b3) * (50000 >> oss_mode)); + + if (b7 < 0x80000000) { + p = (b7 << 1) / b4; + } else { + p = (b7 / b4) << 1; + } + + x1 = (p >> 8) * (p >> 8); + x1 = (x1 * 3038) >> 16; + x2 = (-7357 * p) >> 16; + p = p + ((x1 + x2 + 3791) >> 4); + + return p; +} + +static void init_ulp_program(void) +{ + esp_err_t err = ulp_riscv_load_binary(ulp_main_bin_start, (ulp_main_bin_end - ulp_main_bin_start)); + ESP_ERROR_CHECK(err); + + /* The first argument is the period index, which is not used by the ULP-RISC-V timer + * The second argument is the period in microseconds, which gives a wakeup time period of: 20ms + */ + ulp_set_wakeup_period(0, 20000); + + /* Start the program */ + err = ulp_riscv_run(); + ESP_ERROR_CHECK(err); +} diff --git a/examples/system/ulp_riscv/i2c/sdkconfig.defaults b/examples/system/ulp_riscv/i2c/sdkconfig.defaults new file mode 100644 index 0000000000..a28f71162e --- /dev/null +++ b/examples/system/ulp_riscv/i2c/sdkconfig.defaults @@ -0,0 +1,9 @@ +# Enable ULP +CONFIG_ULP_COPROC_ENABLED=y +CONFIG_ULP_COPROC_RISCV=y +CONFIG_ULP_COPROC_RESERVE_MEM=4096 +# Set log level to Warning to produce clean output +CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y +CONFIG_BOOTLOADER_LOG_LEVEL=2 +CONFIG_LOG_DEFAULT_LEVEL_WARN=y +CONFIG_LOG_DEFAULT_LEVEL=2