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.
This commit is contained in:
Marius Vikhammer
2024-10-30 09:32:52 +08:00
parent e4c92855ee
commit cfe6c45122
8 changed files with 429 additions and 0 deletions

View File

@@ -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)

View File

@@ -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)

View File

@@ -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.)

View File

@@ -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}")

View File

@@ -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

View File

@@ -0,0 +1,186 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#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;
}

View File

@@ -0,0 +1,102 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#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();
}

View File

@@ -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