Skip to content

Commit

Permalink
feat: Add hardware abstraction component for the ESP32 TimerCam. (#318)
Browse files Browse the repository at this point in the history
* Add ESP32-Timer-Cam hardware abstraction

* add timer cam example

* update readme

* update to use new logger functions

* ci: update

* doc: update

* readme: update
  • Loading branch information
finger563 authored Aug 27, 2024
1 parent 3064211 commit cad9e08
Show file tree
Hide file tree
Showing 14 changed files with 489 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ jobs:
target: esp32
- path: 'components/encoder/example'
target: esp32
- path: 'components/esp32-timer-cam/example'
target: esp32
- path: 'components/esp-box/example'
target: esp32s3
- path: 'components/event_manager/example'
Expand Down
6 changes: 6 additions & 0 deletions components/esp32-timer-cam/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
idf_component_register(
INCLUDE_DIRS "include"
SRC_DIRS "src"
REQUIRES driver adc base_component bm8563 i2c interrupt led math task
REQUIRED_IDF_TARGETS "esp32"
)
7 changes: 7 additions & 0 deletions components/esp32-timer-cam/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
menu "Esp-Box Configuration"
config ESP_TIMER_CAM_INTERRUPT_STACK_SIZE
int "Interrupt stack size"
default 4096
help
Size of the stack used for the interrupt handler.
endmenu
21 changes: 21 additions & 0 deletions components/esp32-timer-cam/example/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# 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.5)

include($ENV{IDF_PATH}/tools/cmake/project.cmake)

# add the component directories that we want to use
set(EXTRA_COMPONENT_DIRS
"../../../components/"
)

set(
COMPONENTS
"main esptool_py esp32-timer-cam"
CACHE STRING
"List of components to include"
)

project(esp_timer_cam_example)

set(CMAKE_CXX_STANDARD 20)
37 changes: 37 additions & 0 deletions components/esp32-timer-cam/example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# ESP32-TIMER-CAM Example

This example shows how to use the `espp::EspTimerCam` hardware abstraction
component to automatically detect and initialize components on the
ESP32-TimerCam.

It initializes the LED, RTC, I2C, and ADC.

https://github.com/user-attachments/assets/b0c0eb28-0ce1-466d-b6e5-debbc0e7f3f6

## How to use example

### Hardware Required

This example is designed to run on the ESP32-TimerCam.

### Build and Flash

Build the project and flash it to the board, then run monitor tool to view
serial output:

```
idf.py -p PORT -b 1500000 flash monitor
```

(Replace PORT with the name of the serial port to use.)

Note: the baudrate for the ESP32 TimerCam cannot go up to 2 MBaud, so 1.5MBaud
is recommended.

(To exit the serial monitor, type ``Ctrl-]``.)

See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.

## Example Output

![CleanShot 2024-08-27 at 11 31 16](https://github.com/user-attachments/assets/9c3ebe48-af46-4021-afab-6235870eddf4)
2 changes: 2 additions & 0 deletions components/esp32-timer-cam/example/main/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
idf_component_register(SRC_DIRS "."
INCLUDE_DIRS ".")
43 changes: 43 additions & 0 deletions components/esp32-timer-cam/example/main/esp_timer_cam_example.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#include <chrono>
#include <deque>
#include <stdlib.h>
#include <vector>

#include "esp32-timer-cam.hpp"

using namespace std::chrono_literals;

extern "C" void app_main(void) {
espp::Logger logger({.tag = "ESP32 TimerCam Example", .level = espp::Logger::Verbosity::INFO});
logger.info("Starting example!");

//! [esp timer cam example]
espp::EspTimerCam &timer_cam = espp::EspTimerCam::get();

// initialize the LED
static constexpr float led_breathing_period = 3.5f;
if (!timer_cam.initialize_led(led_breathing_period)) {
logger.error("Failed to initialize LED!");
return;
}

// initialize the rtc
if (!timer_cam.initialize_rtc()) {
logger.error("Failed to initialize RTC!");
return;
}

// start the LED breathing
timer_cam.start_led_breathing();

// loop forever
while (true) {
// print out the battery voltage
logger.info("Battery voltage: {:.02f} V", timer_cam.get_battery_voltage());
std::this_thread::sleep_for(100ms);
// go up a line to overwrite the previous message
logger.move_up();
logger.clear_line();
}
//! [esp timer cam example]
}
22 changes: 22 additions & 0 deletions components/esp32-timer-cam/example/sdkconfig.defaults
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
CONFIG_IDF_TARGET="esp32"

CONFIG_FREERTOS_HZ=1000

# set compiler optimization level to -O2 (compile for performance)
CONFIG_COMPILER_OPTIMIZATION_PERF=y

CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
CONFIG_ESPTOOLPY_FLASHSIZE="4MB"

# ESP32-specific
#
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=240

# Common ESP-related
#
CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=4096
CONFIG_ESP_MAIN_TASK_STACK_SIZE=16384

# Set esp-timer task stack size to 6KB
CONFIG_ESP_TIMER_TASK_STACK_SIZE=6144
184 changes: 184 additions & 0 deletions components/esp32-timer-cam/include/esp32-timer-cam.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
#pragma once

#include <memory>
#include <string>
#include <vector>

#include <driver/gpio.h>
#include <driver/i2s_std.h>
#include <driver/spi_master.h>
#include <hal/spi_types.h>

#include <freertos/FreeRTOS.h>
#include <freertos/queue.h>
#include <freertos/stream_buffer.h>
#include <freertos/task.h>

#include "base_component.hpp"
#include "bm8563.hpp"
#include "gaussian.hpp"
#include "i2c.hpp"
#include "interrupt.hpp"
#include "led.hpp"
#include "oneshot_adc.hpp"

namespace espp {
/// The EspTimerCam class provides an interface to the ESP32-S3-BOX and
/// ESP32-S3-BOX-3 development boards.
///
/// The class provides access to the following features:
/// - I2C
/// - LED
/// - Battery Voltage Measurement
/// - RTC
/// - Camera pin definition
///
/// The class is a singleton and can be accessed using the get() method.
///
/// \section esp_timer_cam_example Example
/// \snippet esp_timer_cam_example.cpp esp timer cam example
class EspTimerCam : public BaseComponent {
public:
using Rtc = espp::Bm8563;

/// @brief Access the singleton instance of the EspTimerCam class
/// @return Reference to the singleton instance of the EspTimerCam class
static EspTimerCam &get() {
static EspTimerCam instance;
return instance;
}

EspTimerCam(const EspTimerCam &) = delete;
EspTimerCam &operator=(const EspTimerCam &) = delete;
EspTimerCam(EspTimerCam &&) = delete;
EspTimerCam &operator=(EspTimerCam &&) = delete;

/// Get a reference to the internal I2C bus
/// \return A reference to the internal I2C bus
/// \note The internal I2C bus is used for the touchscreen and audio codec
I2c &internal_i2c();

/// Get a reference to the interrupts
/// \return A reference to the interrupts
espp::Interrupt &interrupts();

/////////////////////////////////////////////////////////////////////////////
// LED
/////////////////////////////////////////////////////////////////////////////

/// @brief Initialize the LED
/// @param breathing_period The period of the LED breathing effect
/// @return True if the LED was successfully initialized, false otherwise
bool initialize_led(float breathing_period=3.5f);

/// @brief Start the LED breathing effect
void start_led_breathing();

/// @brief Stop the LED breathing effect
void stop_led_breathing();

/// @brief Set the LED brightness
/// @param brightness The brightness of the LED, in the range [0, 1]
/// @return True if the LED brightness was successfully set, false otherwise
/// @note The LED brightness can only be set if the LED is not currently
/// breathing
bool set_led_brightness(float brightness);

/// @brief Get the LED brightness
/// @return The brightness of the LED, in the range [0, 1]
float get_led_brightness();

/// @brief Set the LED breathing period
/// @param period The period of the LED breathing effect in seconds
/// @return True if the LED breathing period was successfully set, false
/// otherwise
bool set_led_breathing_period(float period);

/// @brief Get the LED breathing period
/// @return The period of the LED breathing effect in seconds
float get_led_breathing_period();

/// @brief Get the LED
/// @return A shared pointer to the LED
std::shared_ptr<espp::Led> led();

/// @brief Get the Gaussian waveform used for the LED breathing effect
/// @return A reference to the Gaussian waveform used for the LED breathing
/// effect
espp::Gaussian &gaussian();

/////////////////////////////////////////////////////////////////////////////
// Battery
/////////////////////////////////////////////////////////////////////////////

/// @brief Get the battery voltage
/// @return The battery voltage in volts
float get_battery_voltage();

/////////////////////////////////////////////////////////////////////////////
// RTC
/////////////////////////////////////////////////////////////////////////////

/// @brief Initialize the RTC
/// @return True if the RTC was successfully initialized, false otherwise
bool initialize_rtc();

/// @brief Get the RTC
/// @return A shared pointer to the RTC
std::shared_ptr<Rtc> rtc();

protected:
EspTimerCam();
float led_breathe();
bool led_task_callback(std::mutex &m, std::condition_variable &cv);

// internal i2c (touchscreen, audio codec)
static constexpr auto internal_i2c_port = I2C_NUM_0;
static constexpr auto internal_i2c_clock_speed = 400 * 1000;
static constexpr gpio_num_t internal_i2c_sda = GPIO_NUM_12;
static constexpr gpio_num_t internal_i2c_scl = GPIO_NUM_14;

// Camera


// TODO: allow core id configuration
I2c internal_i2c_{{.port = internal_i2c_port,
.sda_io_num = internal_i2c_sda,
.scl_io_num = internal_i2c_scl,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE}};

// we'll only add each interrupt pin if the initialize method is called
espp::Interrupt interrupts_{
{.interrupts = {},
.task_config = {.name = "esp timer cam interrupts",
.stack_size_bytes = CONFIG_ESP_TIMER_CAM_INTERRUPT_STACK_SIZE}}};

// LED
std::vector<espp::Led::ChannelConfig> led_channels_{{
.gpio = 2,
.channel = LEDC_CHANNEL_5,
.timer = LEDC_TIMER_2,
}};
std::shared_ptr<espp::Led> led_;
std::unique_ptr<espp::Task> led_task_;
std::atomic<float> breathing_period_{3.5f};
std::chrono::high_resolution_clock::time_point breathing_start_{};
espp::Gaussian gaussian_{{.gamma = 0.1f, .alpha = 1.0f, .beta = 0.5f}};

// RTC
std::shared_ptr<Rtc> rtc_;

// Battery ADC
espp::AdcConfig battery_channel_{.unit = ADC_UNIT_1,
.channel = ADC_CHANNEL_2, // GPIO 38
.attenuation = ADC_ATTEN_DB_12};

// NOTE: for some reason, I cannot use Continuous ADC in combination with
// esp32-camera...
espp::OneshotAdc adc_{{.unit = battery_channel_.unit,
.channels = {battery_channel_},
.log_level = espp::Logger::Verbosity::WARN}};

}; // class EspTimerCam
} // namespace espp
Loading

0 comments on commit cad9e08

Please sign in to comment.