From cfe6c45122c7376ce940bf3c8975cb0b3cc83a31 Mon Sep 17 00:00:00 2001 From: Marius Vikhammer Date: Wed, 30 Oct 2024 09:32:52 +0800 Subject: [PATCH] feat(lp_core): added lp-spi example for lp core Added an example of using lp-spi from lp-core to read sensor data from a BME280 sensor. --- examples/system/.build-test-rules.yml | 6 + .../system/ulp/lp_core/lp_spi/CMakeLists.txt | 6 + examples/system/ulp/lp_core/lp_spi/README.md | 54 +++++ .../ulp/lp_core/lp_spi/main/CMakeLists.txt | 25 +++ .../lp_core/lp_spi/main/lp_core/bme280_defs.h | 40 ++++ .../ulp/lp_core/lp_spi/main/lp_core/main.c | 186 ++++++++++++++++++ .../ulp/lp_core/lp_spi/main/lp_spi_main.c | 102 ++++++++++ .../ulp/lp_core/lp_spi/sdkconfig.defaults | 10 + 8 files changed, 429 insertions(+) create mode 100644 examples/system/ulp/lp_core/lp_spi/CMakeLists.txt create mode 100644 examples/system/ulp/lp_core/lp_spi/README.md create mode 100644 examples/system/ulp/lp_core/lp_spi/main/CMakeLists.txt create mode 100644 examples/system/ulp/lp_core/lp_spi/main/lp_core/bme280_defs.h create mode 100644 examples/system/ulp/lp_core/lp_spi/main/lp_core/main.c create mode 100644 examples/system/ulp/lp_core/lp_spi/main/lp_spi_main.c create mode 100644 examples/system/ulp/lp_core/lp_spi/sdkconfig.defaults diff --git a/examples/system/.build-test-rules.yml b/examples/system/.build-test-rules.yml index c5311cbf9f..9462d180e6 100644 --- a/examples/system/.build-test-rules.yml +++ b/examples/system/.build-test-rules.yml @@ -327,6 +327,12 @@ examples/system/ulp/lp_core/lp_i2c: depends_components: - ulp +examples/system/ulp/lp_core/lp_spi: + enable: + - if: SOC_LP_SPI_SUPPORTED == 1 and SOC_DEEP_SLEEP_SUPPORTED == 1 + depends_components: + - ulp + examples/system/ulp/lp_core/lp_uart/lp_uart_echo: disable: - if: (SOC_ULP_LP_UART_SUPPORTED != 1) or (SOC_DEEP_SLEEP_SUPPORTED != 1) diff --git a/examples/system/ulp/lp_core/lp_spi/CMakeLists.txt b/examples/system/ulp/lp_core/lp_spi/CMakeLists.txt new file mode 100644 index 0000000000..0eaa5362f3 --- /dev/null +++ b/examples/system/ulp/lp_core/lp_spi/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following five 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(lp_spi) diff --git a/examples/system/ulp/lp_core/lp_spi/README.md b/examples/system/ulp/lp_core/lp_spi/README.md new file mode 100644 index 0000000000..8823028fe2 --- /dev/null +++ b/examples/system/ulp/lp_core/lp_spi/README.md @@ -0,0 +1,54 @@ +| Supported Targets | ESP32-P4 | +| ----------------- | -------- | + +# LP I2C Example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +## Overview + +This example demonstrates the basic usage of the LP SPI driver from the LP core by reading to and writing from a sensor connected over SPI. The ULP will periodically read temperature and humidity measurements from the sensor and wake up the HP CPU if `WAKEUP_HP_CPU_LIMIT_CELSIUS` (30 degrees by default) is exceeded. + +## How to use example + +### Hardware Required + +To run this example, you should have an ESP based development board that supports the LP SPI peripheral on the LP Core as well as a BME280 sensor. BME280 is a combined temperature, humidity and pressure sensor. More information about it can be found in at [BME280](https://www.bosch-sensortec.com/products/environmental-sensors/humidity-sensors-bme280/). + +#### Pin Assignment: + +**Note:** The following pin assignments are used by default. + +| | SDI(MISO) | SDO(MOSI) | SCK | CSB (CS) | +| ----------------------- | ----------| ----------| ----- | -------- | +| ESP32-P4 LP SPI Master | GPIO6 | GPIO7 | GPIO8 | GPIO4 | + +### Build and Flash + +Enter `idf.py -p PORT flash monitor` to build, flash and monitor the project. + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +The log output should indicate that the LP core and the LP SPI peripheral have been successfully initialized. The main CPU would then enter deep sleep mode. + +```bash +Not an LP core wakeup. Cause = 0 +Initializing... +LP SPI initialized successfully +LP core loaded with firmware successfully +Entering deep sleep... + +(When the BME280 sensor is exposed to a temperature above normal room temperature, defined as 30 degree by default in the ULP code, it will wake up the HP CPU) + +LP core woke up the main CPU +Temperature 31.31 degree celsius, humidity 66.01%RH +Entering deep sleep... +``` + +## Troubleshooting + +(For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you as soon as possible.) diff --git a/examples/system/ulp/lp_core/lp_spi/main/CMakeLists.txt b/examples/system/ulp/lp_core/lp_spi/main/CMakeLists.txt new file mode 100644 index 0000000000..2e46304acb --- /dev/null +++ b/examples/system/ulp/lp_core/lp_spi/main/CMakeLists.txt @@ -0,0 +1,25 @@ +# Register the component +idf_component_register(SRCS "lp_spi_main.c" + INCLUDE_DIRS "" + REQUIRES ulp) + +# +# ULP support additions to component CMakeLists.txt. +# +# 1. The LP Core app name must be unique (if multiple components use LP Core). +set(ulp_app_name lp_core_${COMPONENT_NAME}) +# +# 2. Specify all C files. +# Files should be placed into a separate directory (in this case, lp_core/), +# which should not be added to COMPONENT_SRCS. +set(ulp_lp_core_sources "lp_core/main.c") + +# +# 3. List all the component source files which include automatically +# generated LP Core export file, ${ulp_app_name}.h: +set(ulp_exp_dep_srcs "lp_spi_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_lp_core_sources}" "${ulp_exp_dep_srcs}") diff --git a/examples/system/ulp/lp_core/lp_spi/main/lp_core/bme280_defs.h b/examples/system/ulp/lp_core/lp_spi/main/lp_core/bme280_defs.h new file mode 100644 index 0000000000..ae5ae6d011 --- /dev/null +++ b/examples/system/ulp/lp_core/lp_spi/main/lp_core/bme280_defs.h @@ -0,0 +1,40 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +// Contains only the subset of registers used in this example + +#define BME280_CHIP_ID_REG 0xD0 // Chip ID Register + +#define BME280_RESET_REG 0xE0 // Soft Reset Register + +#define BME280_CTRL_HUM_REG 0xF2 // Humidity control +#define BME280_HUM_OVERSAMPLING_BIT 0 // Bit position for humidity oversampling +#define BME280_HUM_OVERSAMPLING_1X 0x1 // Value for 1x oversampling + +#define BME280_STATUS_REG 0xF3 // Status Register +#define BME280_MEASURING_BIT 3 // Bit position for measuring in progress status + +#define BME280_CTRL_MEAS_REG 0xF4 // Measurement Control Register +#define BME280_MODE_BIT 0 // Bit position for mode +#define BME280_MODE_FORCED 0x1 // Value for setting forced mode +#define BME280_TEMP_OVERSAMPLING_BIT 5 // Bit position for temperature oversampling +#define BME280_TEMP_OVERSAMPLING_1X 0x1 // Value for 1x oversampling + +#define BME280_CONFIG_REG 0xF5 // Configuration Register + +#define BME280_TEMPERATURE_MSB_REG 0xFA // Temperature data MSB +#define BME280_TEMPERATURE_LSB_REG 0xFB // Temperature data LSB +#define BME280_TEMPERATURE_XLSB_REG 0xFC // Temperature data XLSB +#define BME280_HUMIDITY_MSB_REG 0xFD // Humidity data MSB +#define BME280_HUMIDITY_LSB_REG 0xFE // Humidity data LSB + +#define BME280_TRIM_PARAM_TEMP_1_REG 0x88 // Trimming Parameter T1 +#define BME280_TRIM_PARAM_HUM_1_REG 0xA1 // Trimming Parameter H1 +#define BME280_TRIM_PARAM_HUM_2_REG 0xE1 // Trimming Parameter H2 + +#define BME280_RESET_VAL 0xB6 // Write value to trigger a reset +#define BME280_CHIP_ID_VAL 0x60 // Chip ID diff --git a/examples/system/ulp/lp_core/lp_spi/main/lp_core/main.c b/examples/system/ulp/lp_core/lp_spi/main/lp_core/main.c new file mode 100644 index 0000000000..82753c3742 --- /dev/null +++ b/examples/system/ulp/lp_core/lp_spi/main/lp_core/main.c @@ -0,0 +1,186 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "ulp_lp_core_spi.h" +#include "ulp_lp_core_utils.h" +#include "ulp_lp_core_print.h" +#include "bme280_defs.h" + +#define LP_SPI_TRANS_WAIT_FOREVER -1 +#define WAKEUP_HP_CPU_LIMIT_CELSIUS 30 + +// Uncomment to print debug logs +// #define DEBUG + +#ifdef DEBUG +#define DEBUG_LOG lp_core_printf +#else +#define DEBUG_LOG (void) +#endif + +static bool start_up = true; + +// Misc values used to compensate the measurements +static uint16_t dig_T1; +static int16_t dig_T2; +static int16_t dig_T3; +static int32_t t_fine; + +static uint16_t dig_H1; +static int16_t dig_H2; +static uint16_t dig_H3; +static int16_t dig_H4; +static int16_t dig_H5; +static int16_t dig_H6; + + +static void bme280_write(uint8_t reg_addr, uint8_t data) +{ + lp_spi_transaction_t trans_desc = { + .tx_buffer = &data, + .tx_length = 1, + .address = reg_addr & ~(1 << 7), // Clear MSB of register addr to indicate it is a write + .address_bits = 8, + }; + + esp_err_t err = lp_core_lp_spi_master_transfer(&trans_desc, LP_SPI_TRANS_WAIT_FOREVER); + if(err != ESP_OK) { + DEBUG_LOG("Failed to write register: 0x%X, with data = 0x%X\n", reg_addr, data); + abort(); + } +} + +static void bme280_read(uint8_t reg_addr, uint8_t* read_data, size_t read_len) +{ + lp_spi_transaction_t trans_desc = { + .tx_buffer = read_data, + .tx_length = read_len, + .rx_buffer = read_data, + .rx_length = read_len, + .address = reg_addr, + .address_bits = 8, + }; + + + esp_err_t err = lp_core_lp_spi_master_transfer(&trans_desc, LP_SPI_TRANS_WAIT_FOREVER); + if(err != ESP_OK) { + DEBUG_LOG("Failed to read register: 0x%X, with len = 0x%X\n", reg_addr, read_len); + abort(); + } +} + + + +void bme280_read_compensation_params() +{ + uint8_t param_buf[6]; + // Temperature compensation params are all in consecutive registers, read them all in one go + bme280_read(BME280_TRIM_PARAM_TEMP_1_REG, param_buf, 6); + dig_T1 = (param_buf[1] << 8) | param_buf[0]; + dig_T2 = (param_buf[3] << 8) | param_buf[2]; + dig_T3 = (param_buf[5] << 8) | param_buf[4]; + + // Humidity compensation params are in two separate regions, read twice + bme280_read(BME280_TRIM_PARAM_HUM_1_REG, param_buf, 1); + dig_H1 = param_buf[0]; + + bme280_read(BME280_TRIM_PARAM_HUM_2_REG, param_buf, 7); + dig_H2 = (param_buf[1] << 8) | param_buf[0]; + dig_H3 = param_buf[2]; + dig_H4 = (param_buf[3] << 4) | (param_buf[4] & 0xF); + dig_H5 = (param_buf[4] >> 4) | (param_buf[5] << 4); + dig_H6 = param_buf[6]; + +} + +// Returns temperature in DegC, resolution is 0.01 DegC. Output value of “5123” equals 51.23 DegC. +// t_fine carries fine temperature as global value +// Function is taken from BME280 datasheet +static int32_t convert_temp(int32_t adc_t) +{ + int32_t var1, var2, T; + var1 = ((((adc_t >> 3) - ((int32_t)dig_T1<<1)))*((int32_t)dig_T2)) >> 11; + var2 = (((((adc_t>>4) - ((int32_t)dig_T1)) * ((adc_t>>4) - ((int32_t)dig_T1))) >> 12) * ((int32_t)dig_T3)) >> 14; + t_fine = var1 + var2; + T = (t_fine * 5 + 128) >> 8; + return T; +} + +// Returns humidity in %RH as unsigned 32 bit integer in Q22.10 format (22 integer and 10 fractional bits). +// Output value of “47445” represents 47445/1024 = 46.333 %RH +// Function is taken from BME280 datasheet +static uint32_t convert_humidity(int32_t adc_h) +{ + int32_t v_x1_u32r; + v_x1_u32r = (t_fine - ((int32_t)76800)); + v_x1_u32r = (((((adc_h << 14) - (((int32_t)dig_H4) << 20) - (((int32_t)dig_H5) * v_x1_u32r)) + ((int32_t)16384)) >> 15) * (((((((v_x1_u32r * ((int32_t)dig_H6)) >> 10) * (((v_x1_u32r * ((int32_t)dig_H3)) >> 11) + ((int32_t)32768))) >> 10) + ((int32_t)2097152)) * ((int32_t)dig_H2) + 8192) >> 14)); + v_x1_u32r = (v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) * ((int32_t)dig_H1)) >> 4)); + v_x1_u32r = (v_x1_u32r < 0 ? 0 : v_x1_u32r); + v_x1_u32r = (v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r); + return (uint32_t)(v_x1_u32r>>12); +} + +static void bme280_read_environment_data(int32_t *temperature, uint32_t *humidity) +{ + uint8_t ctrl_hum = BME280_HUM_OVERSAMPLING_1X << BME280_HUM_OVERSAMPLING_BIT; + bme280_write(BME280_CTRL_HUM_REG, ctrl_hum); + + uint8_t ctrl_meas = (BME280_TEMP_OVERSAMPLING_1X << BME280_TEMP_OVERSAMPLING_BIT) | (BME280_MODE_FORCED << BME280_MODE_BIT); + bme280_write(BME280_CTRL_MEAS_REG, ctrl_meas); + + bool measuring = true; + while(measuring) { + uint8_t status = 0; + bme280_read(BME280_STATUS_REG, &status, 1); + measuring = status & (1 << BME280_MEASURING_BIT); + } + + uint8_t env_data_buf[8] = {}; + bme280_read(BME280_TEMPERATURE_MSB_REG, env_data_buf, 5); + uint32_t adc_temp = (env_data_buf[0] << 12) | (env_data_buf[1] << 4) | (env_data_buf[2] >> 4); + uint32_t adc_hum = (env_data_buf[3] << 8) | (env_data_buf[4]); + *temperature = convert_temp(adc_temp); + *humidity = convert_humidity(adc_hum); +} + + +static void init_sensor(void) +{ + bme280_write(BME280_RESET_REG, BME280_RESET_VAL); + // Give the sensor some time to reset + ulp_lp_core_delay_us(2000); + + uint8_t chip_id = 0; + bme280_read(BME280_CHIP_ID_REG, &chip_id, sizeof(chip_id)); + DEBUG_LOG("Read chip id = 0x%X, expected 0x%X\n", chip_id, BME280_CHIP_ID_VAL); + if(chip_id != BME280_CHIP_ID_VAL) { + abort(); + } +} + +int32_t temperature; +uint32_t humidity; + +int main (void) +{ + if(start_up) { + init_sensor(); + bme280_read_compensation_params(); + start_up = false; + } + + bme280_read_environment_data(&temperature, &humidity); + DEBUG_LOG("Temperature: %d.%d degree Celsius, humidity: %d.%d\%%RH\n", temperature / 100, temperature % 100, humidity / 1024, humidity % 1024); + + if(temperature/100 > WAKEUP_HP_CPU_LIMIT_CELSIUS) { + ulp_lp_core_wakeup_main_processor(); + } + + return 0; +} diff --git a/examples/system/ulp/lp_core/lp_spi/main/lp_spi_main.c b/examples/system/ulp/lp_core/lp_spi/main/lp_spi_main.c new file mode 100644 index 0000000000..ba7513f032 --- /dev/null +++ b/examples/system/ulp/lp_core/lp_spi/main/lp_spi_main.c @@ -0,0 +1,102 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "esp_sleep.h" +#include "lp_core_main.h" +#include "ulp_lp_core.h" +#include "lp_core_spi.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +extern const uint8_t lp_core_main_bin_start[] asm("_binary_lp_core_main_bin_start"); +extern const uint8_t lp_core_main_bin_end[] asm("_binary_lp_core_main_bin_end"); + +#define LP_SPI_MOSI_PIN 7 +#define LP_SPI_MISO_PIN 6 +#define LP_SPI_SCLK_PIN 8 +#define LP_SPI_CS_PIN 4 + +#define LP_CORE_WAKEUP_PERIOD_US 1*1000*1000 + +static void lp_core_init(void) +{ + esp_err_t ret = ESP_OK; + + ulp_lp_core_cfg_t cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_LP_TIMER, + .lp_timer_sleep_duration_us = LP_CORE_WAKEUP_PERIOD_US + }; + + ret = ulp_lp_core_load_binary(lp_core_main_bin_start, (lp_core_main_bin_end - lp_core_main_bin_start)); + if (ret != ESP_OK) { + printf("LP Core load failed\n"); + abort(); + } + + ret = ulp_lp_core_run(&cfg); + if (ret != ESP_OK) { + printf("LP Core run failed\n"); + abort(); + } + printf("LP core loaded with firmware successfully\n"); +} + +static void lp_spi_init(void) +{ + lp_spi_host_t host_id = 0; + + lp_spi_bus_config_t bus_config = { + .miso_io_num = LP_SPI_MISO_PIN, + .mosi_io_num = LP_SPI_MOSI_PIN, + .sclk_io_num = LP_SPI_SCLK_PIN, + }; + + /* Base LP SPI device settings */ + lp_spi_device_config_t device = { + .cs_io_num = LP_SPI_CS_PIN, + .clock_speed_hz = 10 * 1000, // 10 MHz + .duty_cycle = 128, // 50% duty cycle + }; + + ESP_ERROR_CHECK(lp_core_lp_spi_bus_initialize(host_id, &bus_config)); + + /* Add LP SPI device */ + ESP_ERROR_CHECK(lp_core_lp_spi_bus_add_device(host_id, &device)); + printf("LP SPI initialized successfully\n"); +} + +void app_main(void) +{ + /* If user is using USB-serial-jtag then idf monitor needs some time to + * re-connect to the USB port. We wait 1 sec here to allow for it to make the reconnection + * before we print anything. Otherwise the chip will go back to sleep again before the user + * has time to monitor any output. + */ + vTaskDelay(pdMS_TO_TICKS(1000)); + + esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause(); + if (cause != ESP_SLEEP_WAKEUP_ULP) { + printf("Not an LP core wakeup. Cause = %d\n", cause); + printf("Initializing...\n"); + + /* Initialize LP_SPI from the main processor */ + lp_spi_init(); + + /* Load LP Core binary and start the coprocessor */ + lp_core_init(); + } else if (cause == ESP_SLEEP_WAKEUP_ULP) { + printf("LP core woke up the main CPU\n"); + printf("Temperature %.2f degree celsius, humidity %.2f%%RH\n", ulp_temperature / 100.0, ulp_humidity / 1024.0); + } + + /* Setup wakeup triggers */ + ESP_ERROR_CHECK(esp_sleep_enable_ulp_wakeup()); + + /* Enter Deep Sleep */ + printf("Entering deep sleep...\n"); + esp_deep_sleep_start(); +} diff --git a/examples/system/ulp/lp_core/lp_spi/sdkconfig.defaults b/examples/system/ulp/lp_core/lp_spi/sdkconfig.defaults new file mode 100644 index 0000000000..54483bf1d2 --- /dev/null +++ b/examples/system/ulp/lp_core/lp_spi/sdkconfig.defaults @@ -0,0 +1,10 @@ +# Enable LP Core +CONFIG_ULP_COPROC_ENABLED=y +CONFIG_ULP_COPROC_TYPE_LP_CORE=y +CONFIG_ULP_COPROC_RESERVE_MEM=8128 + +# 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