refactor(ulp): build the ULP coprocessor code as per-architecture components

A ULP program is built against ulp_riscv, lp_core or ulp_fsm, each stating its
own sources, dependencies and memory layout. Sources shared with the driver
stay in components/ulp.
This commit is contained in:
Sudeep Mohanty
2026-09-02 09:31:07 +02:00
parent 1bec6e09d9
commit cb2b2f9b78
101 changed files with 461 additions and 429 deletions
+1 -1
View File
@@ -93,7 +93,7 @@ esp_err_t ulp_riscv_run(void);
* Different than ULP FSM, the binary program has no special format, it is the ELF
* file generated by RISC-V toolchain converted to binary format using objcopy.
*
* Linker script in components/ulp/ld/ulp_riscv.ld produces ELF files which
* Linker script in components/ulp/subproject/components/ulp_riscv/ld/ulp_riscv.ld produces ELF files which
* correspond to this format. This linker script produces binaries with load_addr == 0.
*
* @param program_binary pointer to program binary
@@ -1,35 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "ulp_riscv_register_ops.h"
#include "hal/adc_ll.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* Start an ADC conversion and get the converted value.
*
* @note Will block until the conversion is completed
*
* @note ADC should be initialized for ULP by main CPU by calling ulp_riscv_adc_init()
* before calling this.
* @note When using ADC_UNIT_2, the caller must ensure that no other module (e.g., Wi-Fi or BT)
* is accessing the ADC, as conflicts may lead to undefined behavior.
*
* @param adc_n ADC unit.
* @param channel ADC channel number.
*
* @return Converted value, -1 if conversion failed
*/
int32_t ulp_riscv_adc_read_channel(adc_unit_t adc_n, int channel);
#ifdef __cplusplus
}
#endif
@@ -1,137 +0,0 @@
/*
* SPDX-FileCopyrightText: 2010-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "sdkconfig.h"
#include "soc/soc_caps.h"
#include "soc/rtc_io_reg.h"
#include "soc/sens_reg.h"
#include "ulp_riscv_register_ops.h"
#include "hal/gpio_types.h"
#include "hal/rtc_io_ll.h"
#include "ulp_riscv_interrupt.h"
typedef enum {
ULP_RISCV_GPIO_INTR_DISABLE = 0, /*!< Disable RTC GPIO interrupt */
ULP_RISCV_GPIO_INTR_POSEDGE = 1, /*!< RTC GPIO interrupt type : rising edge */
ULP_RISCV_GPIO_INTR_NEGEDGE = 2, /*!< RTC GPIO interrupt type : falling edge */
ULP_RISCV_GPIO_INTR_ANYEDGE = 3, /*!< RTC GPIO interrupt type : both rising and falling edge */
ULP_RISCV_GPIO_INTR_LOW_LEVEL = 4, /*!< RTC GPIO interrupt type : input low level trigger */
ULP_RISCV_GPIO_INTR_HIGH_LEVEL = 5, /*!< RTC GPIO interrupt type : input high level trigger */
ULP_RISCV_GPIO_INTR_MAX
} ulp_riscv_gpio_int_type_t;
typedef enum {
RTCIO_MODE_OUTPUT = 0,
RTCIO_MODE_OUTPUT_OD = 1,
} rtc_io_out_mode_t;
static inline void ulp_riscv_gpio_init(gpio_num_t gpio_num)
{
#if SOC_LP_IO_CLOCK_IS_INDEPENDENT
rtcio_ll_enable_io_clock(true);
#endif
SET_PERI_REG_MASK(RTC_IO_TOUCH_PAD0_REG + gpio_num * 4, RTC_IO_TOUCH_PAD0_MUX_SEL);
REG_SET_FIELD(RTC_IO_TOUCH_PAD0_REG + gpio_num * 4, RTC_IO_TOUCH_PAD0_FUN_SEL, 0);
}
static inline void ulp_riscv_gpio_deinit(gpio_num_t gpio_num)
{
CLEAR_PERI_REG_MASK(RTC_IO_TOUCH_PAD0_REG + gpio_num * 4, RTC_IO_TOUCH_PAD0_MUX_SEL);
}
static inline void ulp_riscv_gpio_output_enable(gpio_num_t gpio_num)
{
REG_SET_FIELD(RTC_GPIO_ENABLE_W1TS_REG, RTC_GPIO_ENABLE_W1TS, BIT(gpio_num));
}
static inline void ulp_riscv_gpio_output_disable(gpio_num_t gpio_num)
{
REG_SET_FIELD(RTC_GPIO_ENABLE_W1TC_REG, RTC_GPIO_ENABLE_W1TC, BIT(gpio_num));
}
static inline void ulp_riscv_gpio_input_enable(gpio_num_t gpio_num)
{
SET_PERI_REG_MASK(RTC_IO_TOUCH_PAD0_REG + gpio_num * 4, RTC_IO_TOUCH_PAD0_FUN_IE);
}
static inline void ulp_riscv_gpio_input_disable(gpio_num_t gpio_num)
{
CLEAR_PERI_REG_MASK(RTC_IO_TOUCH_PAD0_REG + gpio_num * 4, RTC_IO_TOUCH_PAD0_FUN_IE);
}
static inline void ulp_riscv_gpio_output_level(gpio_num_t gpio_num, uint8_t level)
{
if (level) {
REG_SET_FIELD(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_OUT_DATA_W1TS, BIT(gpio_num));
} else {
REG_SET_FIELD(RTC_GPIO_OUT_W1TC_REG, RTC_GPIO_OUT_DATA_W1TS, BIT(gpio_num));
}
}
static inline uint8_t ulp_riscv_gpio_get_level(gpio_num_t gpio_num)
{
return (uint8_t)((REG_GET_FIELD(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT) & BIT(gpio_num)) ? 1 : 0);
}
static inline void ulp_riscv_gpio_set_output_mode(gpio_num_t gpio_num, rtc_io_out_mode_t mode)
{
REG_SET_FIELD(RTC_GPIO_PIN0_REG + gpio_num * 4, RTC_GPIO_PIN0_PAD_DRIVER, mode);
}
static inline void ulp_riscv_gpio_pullup(gpio_num_t gpio_num)
{
SET_PERI_REG_MASK(RTC_IO_TOUCH_PAD0_REG + gpio_num * 4, RTC_IO_TOUCH_PAD0_RUE);
}
static inline void ulp_riscv_gpio_pullup_disable(gpio_num_t gpio_num)
{
CLEAR_PERI_REG_MASK(RTC_IO_TOUCH_PAD0_REG + gpio_num * 4, RTC_IO_TOUCH_PAD0_RUE);
}
static inline void ulp_riscv_gpio_pulldown(gpio_num_t gpio_num)
{
SET_PERI_REG_MASK(RTC_IO_TOUCH_PAD0_REG + gpio_num * 4, RTC_IO_TOUCH_PAD0_RDE);
}
static inline void ulp_riscv_gpio_pulldown_disable(gpio_num_t gpio_num)
{
CLEAR_PERI_REG_MASK(RTC_IO_TOUCH_PAD0_REG + gpio_num * 4, RTC_IO_TOUCH_PAD0_RDE);
}
#if CONFIG_ULP_RISCV_INTERRUPT_ENABLE
/**
* @brief Set RTC IO interrupt type and handler
*
* @param gpio_num GPIO number
* @param intr_type Interrupt type (See rtc_io_types.h)
* @param handler Interrupt handler
* @param arg Interrupt handler argument
*
* @return ESP_OK on success
*/
esp_err_t ulp_riscv_gpio_isr_register(gpio_num_t gpio_num, ulp_riscv_gpio_int_type_t intr_type, intr_handler_t handler, void *arg);
/**
* @brief Remove RTC IO interrupt handler
*
* @param gpio_num GPIO number
*
* @return ESP_OK on success
*/
esp_err_t ulp_riscv_gpio_isr_deregister(gpio_num_t gpio_num);
#endif /* CONFIG_ULP_RISCV_INTERRUPT_ENABLE */
#ifdef __cplusplus
}
#endif
@@ -1,55 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stddef.h>
#include <stdint.h>
#include "esp_err.h"
/**
* @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
* @return esp_err_t ESP_OK when successful
*/
esp_err_t 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
* @return esp_err_t ESP_OK when successful
*/
esp_err_t ulp_riscv_i2c_master_write_to_device(const uint8_t *data_wr, size_t size);
#ifdef __cplusplus
}
#endif
@@ -1,71 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "esp_err.h"
#include "riscv/interrupt.h"
#ifdef __cplusplus
extern "C"
{
#endif
#if CONFIG_ULP_RISCV_INTERRUPT_ENABLE
/* ULP RISC-V Interrupt sources */
typedef enum {
ULP_RISCV_SW_INTR_SOURCE = 0, /**< Interrupt triggered by SW */
ULP_RISCV_RTCIO0_INTR_SOURCE, /**< Interrupt triggered by RTCIO 0 */
ULP_RISCV_RTCIO1_INTR_SOURCE, /**< Interrupt triggered by RTCIO 1 */
ULP_RISCV_RTCIO2_INTR_SOURCE, /**< Interrupt triggered by RTCIO 2 */
ULP_RISCV_RTCIO3_INTR_SOURCE, /**< Interrupt triggered by RTCIO 3 */
ULP_RISCV_RTCIO4_INTR_SOURCE, /**< Interrupt triggered by RTCIO 4 */
ULP_RISCV_RTCIO5_INTR_SOURCE, /**< Interrupt triggered by RTCIO 5 */
ULP_RISCV_RTCIO6_INTR_SOURCE, /**< Interrupt triggered by RTCIO 6 */
ULP_RISCV_RTCIO7_INTR_SOURCE, /**< Interrupt triggered by RTCIO 7 */
ULP_RISCV_RTCIO8_INTR_SOURCE, /**< Interrupt triggered by RTCIO 8 */
ULP_RISCV_RTCIO9_INTR_SOURCE, /**< Interrupt triggered by RTCIO 9 */
ULP_RISCV_RTCIO10_INTR_SOURCE, /**< Interrupt triggered by RTCIO 10 */
ULP_RISCV_RTCIO11_INTR_SOURCE, /**< Interrupt triggered by RTCIO 11 */
ULP_RISCV_RTCIO12_INTR_SOURCE, /**< Interrupt triggered by RTCIO 12 */
ULP_RISCV_RTCIO13_INTR_SOURCE, /**< Interrupt triggered by RTCIO 13 */
ULP_RISCV_RTCIO14_INTR_SOURCE, /**< Interrupt triggered by RTCIO 14 */
ULP_RISCV_RTCIO15_INTR_SOURCE, /**< Interrupt triggered by RTCIO 15 */
ULP_RISCV_RTCIO16_INTR_SOURCE, /**< Interrupt triggered by RTCIO 16 */
ULP_RISCV_RTCIO17_INTR_SOURCE, /**< Interrupt triggered by RTCIO 17 */
ULP_RISCV_RTCIO18_INTR_SOURCE, /**< Interrupt triggered by RTCIO 18 */
ULP_RISCV_RTCIO19_INTR_SOURCE, /**< Interrupt triggered by RTCIO 19 */
ULP_RISCV_RTCIO20_INTR_SOURCE, /**< Interrupt triggered by RTCIO 20 */
ULP_RISCV_RTCIO21_INTR_SOURCE, /**< Interrupt triggered by RTCIO 21 */
ULP_RISCV_MAX_INTR_SOURCE, /**< Total number of ULP RISC-V interrupt sources */
} ulp_riscv_interrupt_source_t;
/**
* @brief Allocate interrupt handler for a ULP RISC-V interrupt source
*
* @param source ULP RISC-V interrupt source
* @param handler Interrupt handler
* @param arg Interrupt handler argument
*
* @return esp_err_t ESP_OK when successful
*/
esp_err_t ulp_riscv_intr_alloc(ulp_riscv_interrupt_source_t source, intr_handler_t handler, void *arg);
/**
* @brief Free ULP RISC-V interrupt handler
*
* @param source ULP RISC-V interrupt source
*
* @return esp_err_t ESP_OK when successful
*/
esp_err_t ulp_riscv_intr_free(ulp_riscv_interrupt_source_t source);
#endif /* CONFIG_ULP_RISCV_INTERRUPT_ENABLE */
#ifdef __cplusplus
}
#endif
@@ -1,98 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Claire Xenia Wolf <claire@yosyshq.com>
* SPDX-FileContributor: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
/**
* This header file defines custom instructions for interrupt handling on the
* ULP RISC-V. The architecture of the processor and therefore, the interrupt
* handling is based on the PicoRV32 CPU. Details about the operations are
* available at https://github.com/YosysHQ/picorv32#custom-instructions-for-irq-handling
*/
/* Define encoding for all general purpose RISC-V registers */
#define regnum_zero 0
#define regnum_ra 1
#define regnum_sp 2
#define regnum_gp 3
#define regnum_tp 4
#define regnum_t0 5
#define regnum_t1 6
#define regnum_t2 7
#define regnum_s0 8
#define regnum_s1 9
#define regnum_a0 10
#define regnum_a1 11
#define regnum_a2 12
#define regnum_a3 13
#define regnum_a4 14
#define regnum_a5 15
#define regnum_a6 16
#define regnum_a7 17
#define regnum_s2 18
#define regnum_s3 19
#define regnum_s4 20
#define regnum_s5 21
#define regnum_s6 22
#define regnum_s7 23
#define regnum_s8 24
#define regnum_s9 25
#define regnum_s10 26
#define regnum_s11 27
#define regnum_t3 28
#define regnum_t4 29
#define regnum_t5 30
#define regnum_t6 31
/* Define encoding for special interrupt handling registers, viz., q0, q1, q2 and q3 */
#define regnum_q0 0
#define regnum_q1 1
#define regnum_q2 2
#define regnum_q3 3
/* All custom interrupt handling instructions follow the standard R-type instruction format from RISC-V ISA
* with the same opcode of custom0 (0001011).
*/
#define r_type_insn(_f7, _rs2, _rs1, _f3, _rd, _opc) \
.word (((_f7) << 25) | ((_rs2) << 20) | ((_rs1) << 15) | ((_f3) << 12) | ((_rd) << 7) | ((_opc) << 0))
/**
* Instruction: getq rd, qs
* Description: This instruction copies the value of Qx into a general purpose register rd
*/
#define getq_insn(_rd, _qs) \
r_type_insn(0b0000000, 0, regnum_ ## _qs, 0b100, regnum_ ## _rd, 0b0001011)
/**
* Instruction: setq qd, rs
* Description: This instruction copies the value of general purpose register rs to Qx
*/
#define setq_insn(_qd, _rs) \
r_type_insn(0b0000001, 0, regnum_ ## _rs, 0b010, regnum_ ## _qd, 0b0001011)
/**
* Instruction: retirq
* Description: This instruction copies the value of Q0 to CPU PC, and renables interrupts
*/
#define retirq_insn() \
r_type_insn(0b0000010, 0, 0, 0b000, 0, 0b0001011)
/**
* Instruction: maskirq rd, rs
* Description: This instruction copies the value of the register IRQ Mask to the register rd, and copies the value
* of register rs to to IRQ mask.
*/
#define maskirq_insn(_rd, _rs) \
r_type_insn(0b0000011, 0, regnum_ ## _rs, 0b110, regnum_ ## _rd, 0b0001011)
#ifdef __cplusplus
}
#endif
@@ -1,38 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "ulp_riscv_lock_shared.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Locks are based on the Peterson's algorithm, https://en.wikipedia.org/wiki/Peterson%27s_algorithm
*
*/
/**
* @brief Acquire the lock, preventing the main CPU from taking until released. Spins until lock is acquired.
*
* @note The lock is only designed for being used by a single thread on the ULP,
* it is not safe to try to acquire it from multiple threads.
*
* @param lock Pointer to lock struct, shared with main CPU
*/
void ulp_riscv_lock_acquire(ulp_riscv_lock_t *lock);
/**
* @brief Release the lock
*
* @param lock Pointer to lock struct, shared with main CPU
*/
void ulp_riscv_lock_release(ulp_riscv_lock_t *lock);
#ifdef __cplusplus
}
#endif
@@ -1,48 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
/* Underlying driver function for printing a char, e.g. over UART */
typedef void (*putc_fn_t)(const void *ctx, const char c);
/**
* @brief Installs a print driver that will be used for ulp_riscv_print calls
*
* @param putc Underlying driver function for printing a char, e.g. over UART
* @param putc_ctx Context that will be passed when calling the putc function
*/
void ulp_riscv_print_install(putc_fn_t putc, void *putc_ctx);
/**
* @brief Prints a null-terminated string
*
* @param str String to print
*/
void ulp_riscv_print_str(const char *str);
/**
* @brief Prints a hex number. Does not print 0x, only the digits
*
* @param Hex number to print
*/
void ulp_riscv_print_hex(int h);
/**
* @brief Prints a hex number with the specified number of digits. Does not print 0x, only the digits
*
* @param Hex number to print
* @param number_of_digits Number of digits to print.
*/
void ulp_riscv_print_hex_with_number_of_digits(int h, int number_of_digits);
#ifdef __cplusplus
}
#endif
@@ -1,138 +0,0 @@
/*
* SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
//Registers Operation {{
/*
* When COCPU accesses the RTC register, it needs to convert the access address.
* When COCPU accesses the RTC memory, dont need to convert the access address.
*/
#define WRITE_RTC_MEM(addr, val) (*((volatile int*)(addr))) = (int) (val)
#define READ_RTC_MEM(addr) (*(volatile int*)(addr))
/*
* When COCPU accesses the RTC register, it needs to convert the access address.
* When COCPU accesses the RTC memory, dont need to convert the access address.
*/
#define RISCV_REG_CONV(addr) (((addr&0xffff)<<3 & 0xe000) | (addr & 0x1fff) | 0x8000)
#define ETS_UNCACHED_ADDR(addr) (RISCV_REG_CONV(addr))
#ifndef __ASSEMBLER__
#define BIT(nr) (1UL << (nr))
#else
#define BIT(nr) (1 << (nr))
#endif
//write value to register
#define REG_WRITE(_r, _v) ({ \
(*(volatile uint32_t *)RISCV_REG_CONV(_r)) = (_v); \
})
//read value from register
#define REG_READ(_r) ({ \
(*(volatile uint32_t *)RISCV_REG_CONV(_r)); \
})
//get bit or get bits from register
#define REG_GET_BIT(_r, _b) ({ \
(*(volatile uint32_t*)RISCV_REG_CONV(_r) & (_b)); \
})
//set bit or set bits to register
#define REG_SET_BIT(_r, _b) ({ \
(*(volatile uint32_t*)RISCV_REG_CONV(_r) |= (_b)); \
})
//clear bit or clear bits of register
#define REG_CLR_BIT(_r, _b) ({ \
(*(volatile uint32_t*)RISCV_REG_CONV(_r) &= ~(_b)); \
})
//set bits of register controlled by mask
#define REG_SET_BITS(_r, _b, _m) ({ \
(*(volatile uint32_t*)RISCV_REG_CONV(_r) = (*(volatile uint32_t*)RISCV_REG_CONV(_r) & ~(_m)) | ((_b) & (_m))); \
})
//get field from register, uses field _S & _V to determine mask
#define REG_GET_FIELD(_r, _f) ({ \
((REG_READ(_r) >> (_f##_S)) & (_f##_V)); \
})
//set field of a register from variable, uses field _S & _V to determine mask
#define REG_SET_FIELD(_r, _f, _v) ({ \
(REG_WRITE((_r),((REG_READ(_r) & ~((_f##_V) << (_f##_S)))|(((_v) & (_f##_V))<<(_f##_S))))); \
})
//get field value from a variable, used when _f is not left shifted by _f##_S
#define VALUE_GET_FIELD(_r, _f) (((_r) >> (_f##_S)) & (_f))
//get field value from a variable, used when _f is left shifted by _f##_S
#define VALUE_GET_FIELD2(_r, _f) (((_r) & (_f))>> (_f##_S))
//set field value to a variable, used when _f is not left shifted by _f##_S
#define VALUE_SET_FIELD(_r, _f, _v) ((_r)=(((_r) & ~((_f) << (_f##_S)))|((_v)<<(_f##_S))))
//set field value to a variable, used when _f is left shifted by _f##_S
#define VALUE_SET_FIELD2(_r, _f, _v) ((_r)=(((_r) & ~(_f))|((_v)<<(_f##_S))))
//generate a value from a field value, used when _f is not left shifted by _f##_S
#define FIELD_TO_VALUE(_f, _v) (((_v)&(_f))<<_f##_S)
//generate a value from a field value, used when _f is left shifted by _f##_S
#define FIELD_TO_VALUE2(_f, _v) (((_v)<<_f##_S) & (_f))
//read value from register
#define READ_PERI_REG(addr) ({ \
(*((volatile uint32_t *)ETS_UNCACHED_ADDR(addr))); \
})
//write value to register
#define WRITE_PERI_REG(addr, val) ({ \
(*((volatile uint32_t *)ETS_UNCACHED_ADDR(addr))) = (uint32_t)(val); \
})
//clear bits of register controlled by mask
#define CLEAR_PERI_REG_MASK(reg, mask) ({ \
WRITE_PERI_REG((reg), (READ_PERI_REG(reg)&(~(mask)))); \
})
//set bits of register controlled by mask
#define SET_PERI_REG_MASK(reg, mask) ({ \
WRITE_PERI_REG((reg), (READ_PERI_REG(reg)|(mask))); \
})
//get bits of register controlled by mask
#define GET_PERI_REG_MASK(reg, mask) ({ \
(READ_PERI_REG(reg) & (mask)); \
})
//get bits of register controlled by highest bit and lowest bit
// #define GET_PERI_REG_BITS(reg, hipos,lowpos) ({
// ASSERT_IF_DPORT_REG((reg), GET_PERI_REG_BITS);
// ((READ_PERI_REG(reg)>>(lowpos))&((1UL<<((hipos)-(lowpos)+1))-1));
// })
#define GET_PERI_REG_BITS(reg, bit_map, shift) ((READ_PERI_REG(reg))&((bit_map)<<(shift)))>>shift
//set bits of register controlled by mask and shift
#define SET_PERI_REG_BITS(reg,bit_map,value,shift) ({ \
(WRITE_PERI_REG((reg),(READ_PERI_REG(reg)&(~((bit_map)<<(shift))))|(((value) & bit_map)<<(shift)) )); \
})
//get field of register
#define GET_PERI_REG_BITS2(reg, mask,shift) ({ \
((READ_PERI_REG(reg)>>(shift))&(mask)); \
})
//}}
#ifdef __cplusplus
}
#endif
@@ -1,96 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_err.h"
#include "ulp_riscv_register_ops.h"
#include "hal/touch_sens_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Read raw data of touch sensor on the ULP RISC-V core
* @note Refer `touch_pad_read_raw_data()` for more details
*
* @param touch_num Touch pad index
* @param raw_data Pointer to accept touch sensor value
* @return esp_err_t ESP_OK when successful
*/
esp_err_t ulp_riscv_touch_pad_read_raw_data(int touch_num, uint32_t *raw_data);
/**
* @brief Read benchmark of touch sensor on the ULP RISC-V core
* @note Refer `touch_pad_read_benchmark()` for more details
*
* @param touch_num Touch pad index
* @param benchmark Pointer to accept touch sensor benchmark value
* @return esp_err_t ESP_OK when successful
*/
esp_err_t ulp_riscv_touch_pad_read_benchmark(int touch_num, uint32_t *benchmark);
/**
* @brief Read the filtered (smoothened) touch sensor data on the ULP RISC-V core
* @note Refer `touch_pad_filter_read_smooth()` for more details
*
* @param touch_num Touch pad index
* @param smooth_data Pointer to accept smoothened touch sensor value
* @return esp_err_t ESP_OK when successful
*/
esp_err_t ulp_riscv_touch_pad_filter_read_smooth(int touch_num, uint32_t *smooth_data);
/**
* @brief Force reset benchmark to raw data of touch sensor.
* @note Refer `touch_pad_reset_benchmark()` for more details
*
* @param touch_num Touch pad index (TOUCH_PAD_MAX resets baseline of all channels)
* @return esp_err_t ESP_OK when successful
*/
esp_err_t ulp_riscv_touch_pad_reset_benchmark(int touch_num);
/**
* @brief Read raw data of touch sensor sleep channel on the ULP RISC-V core
* @note Refer `touch_pad_sleep_channel_read_data()` for more details
*
* @param touch_num Touch pad index (Only one touch sensor channel is supported in deep sleep)
* @param raw_data Pointer to accept touch sensor value
* @return esp_err_t ESP_OK when successful
*/
esp_err_t ulp_riscv_touch_pad_sleep_channel_read_data(int touch_num, uint32_t *raw_data);
/**
* @brief Read benchmark of touch sensor sleep channel on the ULP RISC-V core
* @note Refer `touch_pad_sleep_channel_read_benchmark()` for more details
*
* @param touch_num Touch pad index (Only one touch sensor channel is supported in deep sleep)
* @param benchmark Pointer to accept touch sensor benchmark value
* @return esp_err_t ESP_OK when successful
*/
esp_err_t ulp_riscv_touch_pad_sleep_channel_read_benchmark(int touch_num, uint32_t *benchmark);
/**
* @brief Read the filtered (smoothened) touch sensor sleep channel data on the ULP RISC-V core
* @note Refer `touch_pad_sleep_channel_read_smooth()` for more details
*
* @param touch_num Touch pad index (Only one touch sensor channel is supported in deep sleep)
* @param smooth_data Pointer to accept smoothened touch sensor value
* @return esp_err_t ESP_OK when successful
*/
esp_err_t ulp_riscv_touch_pad_sleep_channel_read_smooth(int touch_num, uint32_t *smooth_data);
/**
* @brief Reset benchmark of touch sensor sleep channel.
* @note Refer `touch_pad_sleep_channel_reset_benchmark()` for more details
*
* @return esp_err_t ESP_OK when successful
*/
esp_err_t ulp_riscv_touch_pad_sleep_channel_reset_benchmark(void);
#ifdef __cplusplus
}
#endif
@@ -1,45 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "ulp_riscv_gpio.h"
#ifdef __cplusplus
extern "C"
{
#endif
typedef struct {
gpio_num_t tx_pin; // TX pin number
} ulp_riscv_uart_cfg_t; // Config for the driver
typedef struct {
uint32_t bit_duration_cycles; // Number of cycles to hold the line for each bit
gpio_num_t tx_pin; // TX pin number
} ulp_riscv_uart_t; // Context for the driver, initialized by ulp_riscv_uart_init
/**
* @brief Initialize the bit-banged UART driver
*
* @note Will also initialize the underlying HW, i.e. the RTC GPIO used.
*
* @param uart Pointer to the struct that will contain the initialized context
* @param cfg Pointer to the config struct which will be used to initialize the driver
*/
void ulp_riscv_uart_init(ulp_riscv_uart_t *uart, const ulp_riscv_uart_cfg_t *cfg);
/**
* @brief Outputs a single byte on the tx pin
*
* @param uart Pointer to the initialized driver context
* @param c Byte to output
*/
void ulp_riscv_uart_putc(const ulp_riscv_uart_t *uart, const char c);
#ifdef __cplusplus
}
#endif
@@ -1,219 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "sdkconfig.h"
#include <stdint.h>
#include <stdbool.h>
#include "ulp_riscv_register_ops.h"
#include "ulp_riscv_interrupt.h"
#include "ulp_riscv_cpu_freq_shared.h"
/**
* @brief Wakeup main CPU from sleep or deep sleep.
*
* This raises a software interrupt signal, if the
* main CPU has configured the ULP as a wakeup source
* calling this function will make the main CPU to
* exit from sleep or deep sleep.
*/
void ulp_riscv_wakeup_main_processor(void);
/**
* @brief Rescues the cpu from monitor mode
*
* This function cancels the low power mode
* of the ULP-RISC-V, should be called
* every time the co-processor starts.
*
* @note by convenience this function is
* automatically called in startup code.
*/
void ulp_riscv_rescue_from_monitor(void);
/**
* @brief Finishes the ULP program and powers down the ULP
* until next wakeup.
*
* @note This function does not return. After called it will
* fully reset the ULP.
*
* @note Returning from main() in the ULP program results on
* calling this function.
*
* @note To stop the ULP from waking up, call ulp_riscv_timer_stop()
* before halting.
*
* This function should be called after the ULP program Finishes
* its processing, it will trigger the timer for the next wakeup,
* put the ULP in monitor mode and triggers a reset.
*
*/
void __attribute__((__noreturn__)) ulp_riscv_halt(void);
#define ulp_riscv_shutdown ulp_riscv_halt
/**
* @brief Stop the ULP timer
*
* @note This will stop the ULP from waking up if halted, but will not abort any program
* currently executing on the ULP.
*/
void ulp_riscv_timer_stop(void);
/**
* @brief Resumes the ULP timer
*
* @note This will resume an already configured timer, but does no other configuration
*
*/
void ulp_riscv_timer_resume(void);
#define ULP_RISCV_GET_CCOUNT() ({ int __ccount; \
asm volatile("rdcycle %0;" : "=r"(__ccount)); \
__ccount; })
#define ULP_RISCV_CYCLES_PER_US ULP_RISCV_CYCLES_PER_US_NUM / ULP_RISCV_CYCLES_PER_US_DENOM
#define ULP_RISCV_CYCLES_PER_MS 1000U * ULP_RISCV_CYCLES_PER_US
/**
* @brief Retrieves the current number of CPU cycles.
*
* @return The current CPU cycle count.
*/
static inline uint32_t ulp_riscv_get_cpu_cycles(void)
{
return ULP_RISCV_GET_CCOUNT();
}
/**
* @brief Check whether an mcycle-based timeout has elapsed.
*
* @note A timeout value of -1 means "wait forever".
* Other values are interpreted as unsigned cycle counts.
*
* @param start_cycle_count Cycle counter value captured at timeout start.
* @param cycles_to_wait Timeout in CPU cycles, or -1 to disable timeout.
*
* @return true if timeout elapsed, false otherwise.
*/
static inline bool ulp_riscv_is_timeout_elapsed(uint32_t start_cycle_count, int32_t cycles_to_wait)
{
if (cycles_to_wait == -1) {
return false;
}
return (ulp_riscv_get_cpu_cycles() - start_cycle_count) >= (uint32_t)cycles_to_wait;
}
/**
* @brief Makes the co-processor busy-wait for a certain number of CPU cycles.
*
* @note This function is not accurate for delays shorter than 20 cycles because the
* function overhead may exceed the requested delay.
*
* @note The maximum supported delay is 0x7FFFFFFF cycles.
* For larger values, the behavior is undefined. Split longer delays into smaller
* chunks if needed.
*
* For reference, this corresponds approximately to:
* - ESP32-S2 ULP-RISC-V @ 8.5 MHz: 0x7FFFFFFF cycles ≈ 252.645 s
* - ESP32-S3 ULP-RISC-V @ 17.5 MHz: 0x7FFFFFFF cycles ≈ 122.713 s
*
* @param cycles Number of cycles to busy-wait.
*/
static inline void ulp_riscv_delay_cycles(uint32_t cycles)
{
if (cycles <= 20U) { // estimate of cycles for this function overhead
return;
}
// To improve accuracy subtract (20 + 15) cycles overhead, defined by delay calibration test
uint32_t start = ULP_RISCV_GET_CCOUNT() - 20U - 15U;
while ((uint32_t)(ULP_RISCV_GET_CCOUNT() - start) < cycles) {
/* busy wait */
}
}
/**
* @brief Makes the co-processor busy-wait for a certain number of microseconds.
*
* @note This function is not accurate for short delays because the function overhead
* may exceed the requested delay. For very small delays the implementation uses
* a fixed sequence of NOPs (chip-dependent thresholds).
*
* @note The maximum supported delay depends on the ULP-RISC-V cycle counter width and on
* the internal cycles-per-microsecond conversion. For values above the limits below,
* the computed delay may overflow and the result is undefined.
* - ESP32-S2 ULP-RISC-V @ 8.5 MHz: delay_us must be <= 252645135 (about 252.6 s)
* - ESP32-S3 ULP-RISC-V @ 17.5 MHz: delay_us must be <= 122713351 (about 122.7 s)
*
* @param delay_us Number of microseconds to busy wait.
*/
void ulp_riscv_delay_us(uint32_t delay_us);
/**
* @brief Clears the GPIO wakeup interrupt bit
*
*/
void ulp_riscv_gpio_wakeup_clear(void);
#if CONFIG_ULP_RISCV_INTERRUPT_ENABLE
/**
* @brief Enable ULP RISC-V SW Interrupt
*
* @param handler Interrupt handler
* @param arg Interrupt handler argument
*/
void ulp_riscv_enable_sw_intr(intr_handler_t handler, void *arg);
/**
* @brief Disable ULP RISC-V SW Interrupt
*/
void ulp_riscv_disable_sw_intr(void);
/**
* @brief Trigger ULP RISC-V SW Interrupt
*
* @note The SW interrupt will only trigger if it has been enabled previously using ulp_riscv_enable_sw_intr().
*/
void ulp_riscv_trigger_sw_intr(void);
/**
* @brief Enter a critical section by disabling all interrupts
* This inline assembly construct uses the t0 register and is equivalent to:
*
* li t0, 0x80000007
* maskirq_insn(zero, t0) // Mask all interrupt bits
*/
#define ULP_RISCV_ENTER_CRITICAL() \
asm volatile ( \
"li t0, 0x80000007\n" \
".word 0x0602e00b" \
); \
/**
* @brief Exit a critical section by enabling all interrupts
* This inline assembly construct is equivalent to:
*
* maskirq_insn(zero, zero) // Unmask all interrupt bits
*/
#define ULP_RISCV_EXIT_CRITICAL() asm volatile (".word 0x0600600b");
#else /* CONFIG_ULP_RISCV_INTERRUPT_ENABLE */
#define ULP_RISCV_ENTER_CRITICAL()
#define ULP_RISCV_EXIT_CRITICAL()
#endif /* CONFIG_ULP_RISCV_INTERRUPT_ENABLE */
#ifdef __cplusplus
}
#endif
-29
View File
@@ -1,29 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "sdkconfig.h"
#include "ulp_riscv_interrupt_ops.h"
.section .text
.global __start
.type __start, %function
__start:
/* setup the stack pointer */
la sp, __stack_top
#if CONFIG_ULP_RISCV_INTERRUPT_ENABLE
/* Enable interrupts globally */
maskirq_insn(zero, zero)
#endif /* CONFIG_ULP_RISCV_INTERRUPT_ENABLE */
/* Start ULP user code */
call ulp_riscv_rescue_from_monitor
call main
call ulp_riscv_halt
loop:
j loop
.size __start, .-__start
@@ -1,33 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "ulp_riscv_adc_ulp_core.h"
#include "hal/adc_ll.h"
int32_t ulp_riscv_adc_read_channel(adc_unit_t adc_n, int channel)
{
uint32_t event = (adc_n == ADC_UNIT_1) ? ADC_LL_EVENT_ADC1_ONESHOT_DONE : ADC_LL_EVENT_ADC2_ONESHOT_DONE;
adc_oneshot_ll_clear_event(event);
adc_oneshot_ll_disable_all_unit();
adc_oneshot_ll_enable(adc_n);
/* Force SW control of the channel bitmap; deep-sleep entry can clear it, which would
* otherwise make the channel selection below take no effect. */
adc_ll_set_controller(adc_n, ADC_LL_CTRL_RTC);
adc_oneshot_ll_set_channel(adc_n, channel);
adc_oneshot_ll_start(adc_n);
while (adc_oneshot_ll_get_event(event) != true) {
;
}
int32_t out_raw = adc_oneshot_ll_get_raw_result(adc_n);
if (adc_oneshot_ll_raw_check_valid(adc_n, out_raw) == false) {
return -1;
}
//HW workaround: when enabling periph clock, this should be false
adc_oneshot_ll_disable_all_unit();
return out_raw;
}
@@ -1,36 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "sdkconfig.h"
#include "ulp_riscv_gpio.h"
#include "include/ulp_riscv_gpio.h"
#if CONFIG_ULP_RISCV_INTERRUPT_ENABLE
esp_err_t ulp_riscv_gpio_isr_register(gpio_num_t gpio_num, ulp_riscv_gpio_int_type_t intr_type, intr_handler_t handler, void *arg)
{
if (gpio_num < 0 || gpio_num >= GPIO_NUM_MAX) {
return ESP_ERR_INVALID_ARG;
}
if (intr_type < 0 || intr_type >= ULP_RISCV_GPIO_INTR_MAX) {
return ESP_ERR_INVALID_ARG;
}
if (!handler) {
return ESP_ERR_INVALID_ARG;
}
/* Set the interrupt type */
REG_SET_FIELD(RTC_GPIO_PIN0_REG + 4 * gpio_num, RTC_GPIO_PIN0_INT_TYPE, intr_type);
/* Set the interrupt handler */
return ulp_riscv_intr_alloc(ULP_RISCV_RTCIO0_INTR_SOURCE + gpio_num, handler, arg);
}
esp_err_t ulp_riscv_gpio_isr_deregister(gpio_num_t gpio_num)
{
return ulp_riscv_intr_free(ULP_RISCV_RTCIO0_INTR_SOURCE + gpio_num);
}
#endif /* CONFIG_ULP_RISCV_INTERRUPT_ENABLE */
@@ -1,289 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_err.h"
#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"
#include "sdkconfig.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 /*!<I2C restart command */
#define ULP_I2C_CMD_WRITE 1 /*!<I2C write command */
#define ULP_I2C_CMD_READ 2 /*!<I2C read command */
#define ULP_I2C_CMD_STOP 3 /*!<I2C stop command */
#define ULP_I2C_CMD_END 4 /*!<I2C end command */
#else
#define ULP_I2C_CMD_RESTART I2C_LL_CMD_RESTART /*!<I2C restart command */
#define ULP_I2C_CMD_WRITE I2C_LL_CMD_WRITE /*!<I2C write command */
#define ULP_I2C_CMD_READ I2C_LL_CMD_READ /*!<I2C read command */
#define ULP_I2C_CMD_STOP I2C_LL_CMD_STOP /*!<I2C stop command */
#define ULP_I2C_CMD_END I2C_LL_CMD_END /*!<I2C end command */
#endif // CONFIG_IDF_TARGET_ESP32S3
/* Read/Write timeout (number of iterationis) */
#define ULP_RISCV_I2C_RW_TIMEOUT CONFIG_ULP_RISCV_I2C_RW_TIMEOUT
/*
* 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)
{
uint32_t reg_addr = RTC_I2C_CMD0_REG + 4 * cmd_idx;
CLEAR_PERI_REG_MASK(reg_addr, 0xFFFFFFFF);
WRITE_PERI_REG(reg_addr,
(0 << 31) | // CMD Done
((op_code & 0x3) << 11) | // Opcode
((ack_val & 0x1) << 10) | // ACK bit sent by I2C controller during READ.
// Ignored during RSTART, STOP, END and WRITE cmds.
((ack_expected & 0x1) << 9) | // ACK bit expected by I2C controller during WRITE.
// Ignored during RSTART, STOP, END and READ cmds.
((ack_check_en & 0x1) << 8) | // 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.
((byte_num & 0xFF) << 0)); // Byte Num
}
static inline int32_t ulp_riscv_i2c_wait_for_interrupt(int32_t cycles_to_wait)
{
uint32_t status = 0;
uint32_t timeout_start = ulp_riscv_get_cpu_cycles();
while (1) {
status = READ_PERI_REG(RTC_I2C_INT_ST_REG);
/* If a NAK, Timeout, or Arbitration Loss occurs, abort immediately. */
#if CONFIG_IDF_TARGET_ESP32S2
if ((status & RTC_I2C_TIMEOUT_INT_ST) ||
#elif CONFIG_IDF_TARGET_ESP32S3
if ((status & RTC_I2C_TIME_OUT_INT_ST) ||
#endif // CONFIG_IDF_TARGET_ESP32S2
(status & RTC_I2C_ACK_ERR_INT_ST) ||
(status & RTC_I2C_ARBITRATION_LOST_INT_ST)) {
return -1;
}
/* Return 0 ONLY if hardware channels are error-free and data bits are latched. */
if ((status & RTC_I2C_TX_DATA_INT_ST) ||
(status & RTC_I2C_RX_DATA_INT_ST)) {
return 0;
}
/* Handle CPU clock-cycle tracking */
if (ulp_riscv_is_timeout_elapsed(timeout_start, cycles_to_wait)) {
return -1;
}
}
}
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 | | |
* |--------|--------|---------|--------|--------|--------|--------|---------|--------|--------|--------|--------|--------|--------|
*/
esp_err_t ulp_riscv_i2c_master_read_from_device(uint8_t *data_rd, size_t size)
{
uint32_t i = 0;
uint32_t cmd_idx = 0;
esp_err_t ret = ESP_OK;
if (size == 0) {
// Quietly return
return ESP_ERR_INVALID_ARG;
}
// Workaround for IDF-9145
ULP_RISCV_ENTER_CRITICAL();
/* 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 */
if (!ulp_riscv_i2c_wait_for_interrupt(ULP_RISCV_I2C_RW_TIMEOUT)) {
/* 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);
} else {
/* Error in transaction */
CLEAR_PERI_REG_MASK(RTC_I2C_INT_CLR_REG, READ_PERI_REG(RTC_I2C_INT_ST_REG));
ret = ESP_ERR_INVALID_RESPONSE;
break;
}
}
/* 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);
// Workaround for IDF-9145
ULP_RISCV_EXIT_CRITICAL();
return ret;
}
/*
* 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 | |
* |--------|--------|---------|--------|--------|--------|--------|--------|--------|--------|--------|
*/
esp_err_t ulp_riscv_i2c_master_write_to_device(const uint8_t *data_wr, size_t size)
{
uint32_t i = 0;
uint32_t cmd_idx = 0;
esp_err_t ret = ESP_OK;
if (size == 0) {
// Quietly return
return ESP_ERR_INVALID_ARG;
}
// Workaround for IDF-9145
ULP_RISCV_ENTER_CRITICAL();
/* 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 */
if (!ulp_riscv_i2c_wait_for_interrupt(ULP_RISCV_I2C_RW_TIMEOUT)) {
/* Clear the Tx data interrupt bit */
SET_PERI_REG_MASK(RTC_I2C_INT_CLR_REG, RTC_I2C_TX_DATA_INT_CLR);
} else {
SET_PERI_REG_MASK(RTC_I2C_INT_CLR_REG, READ_PERI_REG(RTC_I2C_INT_ST_REG));
ret = ESP_ERR_INVALID_RESPONSE;
break;
}
}
/* 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);
// Workaround for IDF-9145
ULP_RISCV_EXIT_CRITICAL();
return ret;
}
@@ -1,137 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include "sdkconfig.h"
#include "include/ulp_riscv_interrupt.h"
#include "ulp_riscv_register_ops.h"
#include "ulp_riscv_interrupt.h"
#include "ulp_riscv_gpio.h"
#include "soc/sens_reg.h"
#if CONFIG_ULP_RISCV_INTERRUPT_ENABLE
#define ULP_RISCV_TIMER_INT (1 << 0U) /* Internal Timer Interrupt */
#define ULP_RISCV_EBREAK_ECALL_ILLEGAL_INSN_INT (1 << 1U) /* EBREAK, ECALL or Illegal instruction */
#define ULP_RISCV_BUS_ERROR_INT (1 << 2U) /* Bus Error (Unaligned Memory Access) */
#define ULP_RISCV_PERIPHERAL_INTERRUPT (1 << 31U) /* RTC Peripheral Interrupt */
#define ULP_RISCV_INTERNAL_INTERRUPT (ULP_RISCV_TIMER_INT | ULP_RISCV_EBREAK_ECALL_ILLEGAL_INSN_INT | ULP_RISCV_BUS_ERROR_INT)
/* Interrupt handler structure */
typedef struct {
intr_handler_t handler;
void *arg;
} ulp_riscv_intr_handler_t;
/* Statically store all interrupt handlers */
static ulp_riscv_intr_handler_t s_intr_handlers[ULP_RISCV_MAX_INTR_SOURCE];
esp_err_t ulp_riscv_intr_alloc(ulp_riscv_interrupt_source_t source, intr_handler_t handler, void *arg)
{
/* Check the validity of the interrupt source */
if (source < 0 || source >= ULP_RISCV_MAX_INTR_SOURCE || handler == NULL) {
return ESP_ERR_INVALID_ARG;
}
/* Register interrupt handler */
if (s_intr_handlers[source].handler == NULL) {
s_intr_handlers[source].handler = handler;
s_intr_handlers[source].arg = arg;
} else {
/* Error: The interrupt handler for this interrupt source has already been allocated */
return ESP_ERR_NOT_FOUND;
}
return ESP_OK;
}
esp_err_t ulp_riscv_intr_free(ulp_riscv_interrupt_source_t source)
{
/* Check the validity of the interrupt source */
if (source < 0 || source >= ULP_RISCV_MAX_INTR_SOURCE) {
return ESP_ERR_INVALID_ARG;
}
/* De-register interrupt handler */
if (s_intr_handlers[source].handler != NULL) {
s_intr_handlers[source].handler = NULL;
s_intr_handlers[source].arg = NULL;
}
return ESP_OK;
}
/* This function -
* - Checks RTC peripheral interrupt status bit
* - Calls interrupt handler if it is registered
* - Clears interrupt bit
*/
static inline void ulp_riscv_handle_rtc_periph_intr(uint32_t status)
{
/* SW interrupt */
if (status & SENS_COCPU_SW_INT_ST) {
const ulp_riscv_intr_handler_t* entry = &s_intr_handlers[ULP_RISCV_SW_INTR_SOURCE];
if (entry->handler) {
entry->handler(entry->arg);
}
SET_PERI_REG_MASK(SENS_SAR_COCPU_INT_CLR_REG, SENS_COCPU_SW_INT_CLR);
}
}
/* This function -
* - Checks if one or more RTC IO interrupt status bits are set
* - Calls the interrupt handler for the RTC IO if it is registered
* - Clears all interrupt bits
*/
static inline void ulp_riscv_handle_rtc_io_intr(uint32_t status)
{
uint32_t handler_idx = 0;
for (int i = 0; i < GPIO_NUM_MAX; i++) {
if (status & (1U << i)) {
handler_idx = ULP_RISCV_RTCIO0_INTR_SOURCE + i;
ulp_riscv_intr_handler_t* entry = &s_intr_handlers[handler_idx];
if (entry->handler) {
entry->handler(entry->arg);
}
}
}
REG_SET_FIELD(RTC_GPIO_STATUS_W1TC_REG, RTC_GPIO_STATUS_INT_W1TC, status);
}
/* This is the global interrupt handler for ULP RISC-V.
* It is called from ulp_riscv_vectors.S
*/
void __attribute__((weak)) _ulp_riscv_interrupt_handler(uint32_t q1)
{
/* Call respective interrupt handlers based on the interrupt status in q1 */
/* Internal Interrupts */
if (q1 & ULP_RISCV_INTERNAL_INTERRUPT) {
// TODO
}
/* External/Peripheral interrupts */
if (q1 & ULP_RISCV_PERIPHERAL_INTERRUPT) {
/* RTC Peripheral interrupts */
uint32_t cocpu_int_st = READ_PERI_REG(SENS_SAR_COCPU_INT_ST_REG);
if (cocpu_int_st) {
ulp_riscv_handle_rtc_periph_intr(cocpu_int_st);
}
/* RTC IO interrupts */
uint32_t rtcio_int_st = REG_GET_FIELD(RTC_GPIO_STATUS_REG, RTC_GPIO_STATUS_INT);
if (rtcio_int_st) {
ulp_riscv_handle_rtc_io_intr(rtcio_int_st);
}
/* TODO: RTC I2C interrupt */
}
}
#endif /* CONFIG_ULP_RISCV_INTERRUPT_ENABLE */
@@ -1,21 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "ulp_riscv_lock.h"
#include "ulp_riscv_lock_shared.h"
void ulp_riscv_lock_acquire(ulp_riscv_lock_t *lock)
{
lock->critical_section_flag_ulp = true;
lock->turn = ULP_RISCV_LOCK_TURN_MAIN_CPU;
while (lock->critical_section_flag_main_cpu && (lock->turn == ULP_RISCV_LOCK_TURN_MAIN_CPU)) {
}
}
void ulp_riscv_lock_release(ulp_riscv_lock_t *lock)
{
lock->critical_section_flag_ulp = false;
}
@@ -1,97 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "ulp_riscv_print.h"
#include "ulp_riscv_utils.h"
typedef struct {
putc_fn_t putc_fn; // Putc function of the underlying driver, e.g. UART
void *putc_ctx; // Context passed to the putc function
} ulp_riscv_print_ctx_t;
static ulp_riscv_print_ctx_t s_print_ctx;
void ulp_riscv_print_install(putc_fn_t putc, void * putc_ctx)
{
s_print_ctx.putc_ctx = putc_ctx;
s_print_ctx.putc_fn = putc;
}
void ulp_riscv_print_str(const char *str)
{
if (!s_print_ctx.putc_fn) {
return;
}
/* Perform the bit-banged UART operation in a critical section */
ULP_RISCV_ENTER_CRITICAL();
for (int i = 0; str[i] != 0; i++) {
s_print_ctx.putc_fn(s_print_ctx.putc_ctx, str[i]);
}
ULP_RISCV_EXIT_CRITICAL();
}
void ulp_riscv_print_hex(int h)
{
int x;
int c;
if (!s_print_ctx.putc_fn) {
return;
}
/* Perform the bit-banged UART operation in a critical section */
ULP_RISCV_ENTER_CRITICAL();
// Does not print '0x', only the digits (8 digits to print)
for (x = 0; x < 8; x++) {
c = (h >> 28) & 0xf; // extract the leftmost byte
if (c < 10) {
s_print_ctx.putc_fn(s_print_ctx.putc_ctx, '0' + c);
} else {
s_print_ctx.putc_fn(s_print_ctx.putc_ctx, 'a' + c - 10);
}
h <<= 4; // move the 2nd leftmost byte to the left, to be extracted next
}
ULP_RISCV_EXIT_CRITICAL();
}
void ulp_riscv_print_hex_with_number_of_digits(int h, int number_of_digits)
{
int x;
int c;
if (!s_print_ctx.putc_fn) {
return;
}
if (number_of_digits < 1) {
return;
}
if (number_of_digits >= 8) {
ulp_riscv_print_hex(h);
return;
}
/* Perform the bit-banged UART operation in a critical section */
ULP_RISCV_ENTER_CRITICAL();
// Does not print '0x', only the digits specified by the number_of_digits argument
for (x = 0; x < number_of_digits; x++) {
c = (h >> ((number_of_digits - 1) * 4)) & 0xf; // extract the leftmost byte
if (c < 10) {
s_print_ctx.putc_fn(s_print_ctx.putc_ctx, '0' + c);
} else {
s_print_ctx.putc_fn(s_print_ctx.putc_ctx, 'a' + c - 10);
}
h <<= 4; // move the 2nd leftmost byte to the left, to be extracted next
}
ULP_RISCV_EXIT_CRITICAL();
}
@@ -1,123 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "ulp_riscv_touch_ulp_core.h"
#include "soc/soc_caps.h"
#include "hal/touch_sensor_ll.h"
/* Check Touch Channel correctness */
#define ULP_RISCV_TOUCH_CHANNEL_CHECK_AND_RETURN(channel) \
{ \
if (channel >= SOC_TOUCH_MAX_CHAN_ID || \
channel < SOC_TOUCH_MIN_CHAN_ID) { \
return ESP_ERR_INVALID_ARG; \
} \
} \
esp_err_t ulp_riscv_touch_pad_read_raw_data(int touch_num, uint32_t *raw_data)
{
/* Check Arguments */
if (!raw_data) {
return ESP_ERR_INVALID_ARG;
}
ULP_RISCV_TOUCH_CHANNEL_CHECK_AND_RETURN(touch_num);
/* Read raw touch data */
touch_ll_read_chan_data((int)touch_num, TOUCH_LL_READ_RAW, raw_data);
return ESP_OK;
}
esp_err_t ulp_riscv_touch_pad_read_benchmark(int touch_num, uint32_t *benchmark)
{
/* Check Arguments */
if (!benchmark) {
return ESP_ERR_INVALID_ARG;
}
ULP_RISCV_TOUCH_CHANNEL_CHECK_AND_RETURN(touch_num);
/* Read benchmark data */
touch_ll_read_chan_data((int)touch_num, TOUCH_LL_READ_BENCHMARK, benchmark);
return ESP_OK;
}
esp_err_t ulp_riscv_touch_pad_filter_read_smooth(int touch_num, uint32_t *smooth_data)
{
/* Check Arguments */
if (!smooth_data) {
return ESP_ERR_INVALID_ARG;
}
ULP_RISCV_TOUCH_CHANNEL_CHECK_AND_RETURN(touch_num);
/* Read smoothened touch sensor data */
touch_ll_read_chan_data((int)touch_num, TOUCH_LL_READ_SMOOTH, smooth_data);
return ESP_OK;
}
esp_err_t ulp_riscv_touch_pad_reset_benchmark(int touch_num)
{
/* Check Arguments */
if (touch_num > SOC_TOUCH_MAX_CHAN_ID || touch_num < 0) {
return ESP_ERR_INVALID_ARG;
}
/* Reset benchmark */
touch_ll_reset_chan_benchmark(BIT(touch_num));
return ESP_OK;
}
esp_err_t ulp_riscv_touch_pad_sleep_channel_read_data(int touch_num, uint32_t *raw_data)
{
/* Check Arguments */
if (!raw_data) {
return ESP_ERR_INVALID_ARG;
}
ULP_RISCV_TOUCH_CHANNEL_CHECK_AND_RETURN(touch_num);
/* Read raw touch data */
touch_ll_sleep_read_chan_data(TOUCH_LL_READ_RAW, raw_data);
return ESP_OK;
}
esp_err_t ulp_riscv_touch_pad_sleep_channel_read_benchmark(int touch_num, uint32_t *benchmark)
{
/* Check Arguments */
if (!benchmark) {
return ESP_ERR_INVALID_ARG;
}
ULP_RISCV_TOUCH_CHANNEL_CHECK_AND_RETURN(touch_num);
/* Read benchmark data */
touch_ll_sleep_read_chan_data(TOUCH_LL_READ_BENCHMARK, benchmark);
return ESP_OK;
}
esp_err_t ulp_riscv_touch_pad_sleep_channel_read_smooth(int touch_num, uint32_t *smooth_data)
{
/* Check Arguments */
if (!smooth_data) {
return ESP_ERR_INVALID_ARG;
}
ULP_RISCV_TOUCH_CHANNEL_CHECK_AND_RETURN(touch_num);
/* Read smoothened touch sensor data */
touch_ll_sleep_read_chan_data(TOUCH_LL_READ_SMOOTH, smooth_data);
return ESP_OK;
}
esp_err_t ulp_riscv_touch_pad_sleep_channel_reset_benchmark(void)
{
/* Reset benchmark */
touch_ll_sleep_reset_benchmark();
return ESP_OK;
}
@@ -1,50 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "sdkconfig.h"
#include "ulp_riscv.h"
#include "ulp_riscv_utils.h"
#include "ulp_riscv_gpio.h"
#include "ulp_riscv_uart_ulp_core.h"
/* We calculate the bit duration at compile time to speed up and avoid pulling in soft-float libs */
#define BIT_DURATION_CYCLES ( (ULP_RISCV_CYCLES_PER_US_NUM * 1000000) / (ULP_RISCV_CYCLES_PER_US_DENOM * CONFIG_ULP_RISCV_UART_BAUDRATE) )
void ulp_riscv_uart_init(ulp_riscv_uart_t *uart, const ulp_riscv_uart_cfg_t *cfg)
{
uart->tx_pin = cfg->tx_pin;
/* 1 bit duration with length given in clock cycles */
uart->bit_duration_cycles = BIT_DURATION_CYCLES;
/* Setup GPIO used for uart TX */
ulp_riscv_gpio_init(cfg->tx_pin);
ulp_riscv_gpio_output_enable(cfg->tx_pin);
ulp_riscv_gpio_set_output_mode(cfg->tx_pin, RTCIO_MODE_OUTPUT_OD);
ulp_riscv_gpio_pullup(cfg->tx_pin);
ulp_riscv_gpio_pulldown_disable(cfg->tx_pin);
ulp_riscv_gpio_output_level(cfg->tx_pin, 1);
}
void ulp_riscv_uart_putc(const ulp_riscv_uart_t *uart, const char c)
{
ulp_riscv_gpio_output_level(uart->tx_pin, 0);
for (int i = 0; i < 8; i++) {
/* Offset the delay to account for cycles spent setting the bit */
ulp_riscv_delay_cycles(uart->bit_duration_cycles - 100);
if ((1 << i) & c) {
ulp_riscv_gpio_output_level(uart->tx_pin, 1);
} else {
ulp_riscv_gpio_output_level(uart->tx_pin, 0);
}
}
ulp_riscv_delay_cycles(uart->bit_duration_cycles - 20);
ulp_riscv_gpio_output_level(uart->tx_pin, 1);
ulp_riscv_delay_cycles(uart->bit_duration_cycles);
}
@@ -1,148 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "sdkconfig.h"
#include "ulp_riscv_utils.h"
#include "ulp_riscv_register_ops.h"
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"
#include "soc/soc_ulp.h"
#include "soc/sens_reg.h"
#include "ulp_riscv_cpu_freq_shared.h"
void ulp_riscv_rescue_from_monitor(void)
{
/* Rescue RISCV from monitor state. */
CLEAR_PERI_REG_MASK(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_DONE | RTC_CNTL_COCPU_SHUT_RESET_EN);
}
void ulp_riscv_wakeup_main_processor(void)
{
SET_PERI_REG_MASK(RTC_CNTL_STATE0_REG, RTC_CNTL_SW_CPU_INT);
}
void ulp_riscv_halt(void)
{
/* Setting the delay time after RISCV recv `DONE` signal, Ensure that action `RESET` can be executed in time. */
REG_SET_FIELD(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_SHUT_2_CLK_DIS, 0x3F);
/* Suspends the ulp operation and reset the ULP core. Must be the final operation before going to halt. */
SET_PERI_REG_MASK(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_DONE | RTC_CNTL_COCPU_SHUT_RESET_EN);
while (1);
}
void ulp_riscv_timer_stop(void)
{
CLEAR_PERI_REG_MASK(RTC_CNTL_ULP_CP_TIMER_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN);
}
void ulp_riscv_timer_resume(void)
{
SET_PERI_REG_MASK(RTC_CNTL_ULP_CP_TIMER_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN);
}
void ulp_riscv_gpio_wakeup_clear(void)
{
SET_PERI_REG_MASK(RTC_CNTL_ULP_CP_TIMER_REG, RTC_CNTL_ULP_CP_GPIO_WAKEUP_CLR);
}
#if CONFIG_ULP_RISCV_INTERRUPT_ENABLE
void ulp_riscv_enable_sw_intr(intr_handler_t handler, void *arg)
{
/* Enable ULP RISC-V SW interrupt */
SET_PERI_REG_MASK(SENS_SAR_COCPU_INT_ENA_REG, SENS_COCPU_SW_INT_ENA);
/* Register interrupt handler */
if (handler) {
ulp_riscv_intr_alloc(ULP_RISCV_SW_INTR_SOURCE, handler, arg);
}
}
void ulp_riscv_disable_sw_intr(void)
{
/* Disable ULP RISC-V SW interrupt */
CLEAR_PERI_REG_MASK(SENS_SAR_COCPU_INT_ENA_REG, SENS_COCPU_SW_INT_ENA);
/* De-register interrupt handler */
ulp_riscv_intr_free(ULP_RISCV_SW_INTR_SOURCE);
}
void ulp_riscv_trigger_sw_intr(void)
{
SET_PERI_REG_MASK(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_SW_INT_TRIGGER);
}
#endif /* CONFIG_ULP_RISCV_INTERRUPT_ENABLE */
void ulp_riscv_delay_us(uint32_t delay_us)
{
#if CONFIG_IDF_TARGET_ESP32S3
/*
* For very small delays, entering the generic cycle-count loop adds too much fixed overhead.
* Use a short calibrated NOP path instead to improve small-delay accuracy on ESP32-S3.
*/
if (delay_us <= 5) {
goto fast_return;
}
#elif CONFIG_IDF_TARGET_ESP32S2
/*
* Same principle as S3, but with a different threshold due to target-specific timing/overhead.
*/
if (delay_us <= 10) {
goto fast_return;
}
#endif
/*
* Generic delay path:
* - Convert requested microseconds to CPU cycles using ratio macros.
* - Pre-subtract measured function overhead (55 cycles), so observed delay is closer to request.
*/
uint32_t start = ulp_riscv_get_cpu_cycles() - 55U;
uint32_t req_delay = delay_us * ULP_RISCV_CYCLES_PER_US_NUM / ULP_RISCV_CYCLES_PER_US_DENOM;
/*
* Busy-wait until elapsed cycles reach req_delay.
* uint32_t subtraction intentionally relies on wrap-around-safe arithmetic for cycle counter rollover.
*/
while ((uint32_t)(ulp_riscv_get_cpu_cycles() - start) < req_delay) {
/* busy wait */
}
return;
fast_return:
#if CONFIG_IDF_TARGET_ESP32S3
/*
* Fast path for tiny delays:
* Use discrete NOP counts calibrated for this target.
* Note: (delay_us == 0 || delay_us <= 2) is intentionally kept as-is to avoid behavior changes.
*/
if (delay_us == 0 || delay_us <= 2) {
return;
} else if (delay_us <= 3) {
asm volatile("nop\n");
} else if (delay_us <= 4) {
asm volatile("nop\n nop\n");
} else {
asm volatile("nop\n nop\n nop\n nop\n nop\n");
}
#elif CONFIG_IDF_TARGET_ESP32S2
/*
* ESP32-S2 calibrated NOP mapping for very short delays.
*/
if (delay_us == 0) {
return;
} else if (delay_us <= 6) {
asm volatile("nop\n");
} else if (delay_us <= 8) {
asm volatile("nop\n nop\n");
} else {
asm volatile("nop\n nop\n nop\n nop\n nop\n nop\n");
}
#endif
}
@@ -1,95 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "sdkconfig.h"
#include "ulp_riscv_interrupt_ops.h"
.equ SAVE_REGS, 17
.equ CONTEXT_SIZE, (SAVE_REGS * 4)
/* Macro which first allocates space on the stack to save general
* purpose registers, and then save them. GP register is excluded.
* The default size allocated on the stack is CONTEXT_SIZE, but it
* can be overridden.
*
* Note: We don't save the callee-saved s0-s11 registers to save space
*/
.macro save_general_regs cxt_size=CONTEXT_SIZE
addi sp, sp, -\cxt_size
sw ra, 0(sp)
sw tp, 4(sp)
sw t0, 8(sp)
sw t1, 12(sp)
sw t2, 16(sp)
sw a0, 20(sp)
sw a1, 24(sp)
sw a2, 28(sp)
sw a3, 32(sp)
sw a4, 36(sp)
sw a5, 40(sp)
sw a6, 44(sp)
sw a7, 48(sp)
sw t3, 52(sp)
sw t4, 56(sp)
sw t5, 60(sp)
sw t6, 64(sp)
.endm
/* Restore the general purpose registers (excluding gp) from the context on
* the stack. The context is then deallocated. The default size is CONTEXT_SIZE
* but it can be overridden. */
.macro restore_general_regs cxt_size=CONTEXT_SIZE
lw ra, 0(sp)
lw tp, 4(sp)
lw t0, 8(sp)
lw t1, 12(sp)
lw t2, 16(sp)
lw a0, 20(sp)
lw a1, 24(sp)
lw a2, 28(sp)
lw a3, 32(sp)
lw a4, 36(sp)
lw a5, 40(sp)
lw a6, 44(sp)
lw a7, 48(sp)
lw t3, 52(sp)
lw t4, 56(sp)
lw t5, 60(sp)
lw t6, 64(sp)
addi sp,sp, \cxt_size
.endm
.section .text.vectors
.global irq_vector
.global reset_vector
/* The reset vector, jumps to startup code */
reset_vector:
j __start
#if CONFIG_ULP_RISCV_INTERRUPT_ENABLE
/* Interrupt handler */
.balign 0x10
irq_vector:
/* Save the general gurpose register context before handling the interrupt */
save_general_regs
/* Fetch the interrupt status from the custom q1 register into a0 */
getq_insn(a0, q1)
/* Call the global C interrupt handler. The interrupt status is passed as the argument in a0.
* We do not re-enable interrupts before calling the C handler as ULP RISC-V does not
* support nested interrupts.
*/
jal _ulp_riscv_interrupt_handler
/* Restore the register context after returning from the C interrupt handler */
restore_general_regs
/* Exit interrupt handler by executing the custom retirq instruction which will restore pc and re-enable interrupts */
retirq_insn()
#endif /* CONFIG_ULP_RISCV_INTERRUPT_ENABLE */