Skip to content

Commit

Permalink
Add espp::SsRoundDisplay hardware abstraction component for the See…
Browse files Browse the repository at this point in the history
…ed Studio Round Display (#315)

* add seeed-studio-round-display component and example

* remove esp-lcd from component requires, instead adding it to display_drivers since that is the actual requirement

* Update to support interface for definiting what pins are connected to the display - including built-in defines for xiao s3 and qtpys3

* update display config

* minor update to example

* add comments

* doc: update

* -ci: update

* add missing chsc6x touch driver component

* wip final updates

* added default values for the pinconfig and comparison operators - use that in the constructor to log an error if no pin config is provided

* refactor for readability

* flesh out docs some more

* add chsc6x example and update workflow

* update readme

* update docs

* fix bug in chsc6x not returning true when there was actually new data; minor refactor and update some defaults

* readme: update
  • Loading branch information
finger563 authored Aug 26, 2024
1 parent 48cfa48 commit dc86501
Show file tree
Hide file tree
Showing 36 changed files with 1,401 additions and 13 deletions.
6 changes: 5 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,14 @@ jobs:
target: esp32
- path: 'components/button/example'
target: esp32
- path: 'components/controller/example'
- path: 'components/chsc6x/example'
target: esp32s3
- path: 'components/cli/example'
target: esp32
- path: 'components/color/example'
target: esp32
- path: 'components/controller/example'
target: esp32s3
- path: 'components/cst816/example'
target: esp32s3
- path: 'components/csv/example'
Expand Down Expand Up @@ -111,6 +113,8 @@ jobs:
target: esp32s3
- path: 'components/rtsp/example'
target: esp32
- path: 'components/seeed-studio-round-display/example'
target: esp32s3
- path: 'components/serialization/example'
target: esp32
- path: 'components/socket/example'
Expand Down
4 changes: 4 additions & 0 deletions components/chsc6x/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
idf_component_register(
INCLUDE_DIRS "include"
REQUIRES "base_peripheral"
)
21 changes: 21 additions & 0 deletions components/chsc6x/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 task chsc6x i2c"
CACHE STRING
"List of components to include"
)

project(chsc6x_example)

set(CMAKE_CXX_STANDARD 20)
37 changes: 37 additions & 0 deletions components/chsc6x/example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# CHSC6X Example

This example shows how to use the CHSC6X touch controller with ESP32. It is
designed to run on a Seeed Studio Round Display.

## How to use example

### Hardware Required

Seeed Studio Round Display (or any other dev board with a CHSC6X touch
controller)

### Configure

```
idf.py menuconfig
```

Set the hardware configuration for the example.

### Build and Flash

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

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

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

(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-26 at 08 21 47](https://github.com/user-attachments/assets/44ed3371-0c8f-44d1-992d-01ee1c983efc)
2 changes: 2 additions & 0 deletions components/chsc6x/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 ".")
39 changes: 39 additions & 0 deletions components/chsc6x/example/main/Kconfig.projbuild
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
menu "Example Configuration"

choice EXAMPLE_HARDWARE
prompt "Hardware"
default EXAMPLE_HARDWARE_XIAOS3
help
Select the hardware to run this example on.

config EXAMPLE_HARDWARE_XIAOS3
depends on IDF_TARGET_ESP32S3
bool "XIAO-S3"

config EXAMPLE_HARDWARE_QTPYS3
depends on IDF_TARGET_ESP32S3
bool "QTPY ESP32 S3"

config EXAMPLE_HARDWARE_CUSTOM
bool "Custom"
endchoice

config EXAMPLE_I2C_SCL_GPIO
int "SCL GPIO Num"
range 0 50
default 6 if EXAMPLE_HARDWARE_XIAOS3
default 6 if EXAMPLE_HARDWARE_QTPYS3
default 19 if EXAMPLE_HARDWARE_CUSTOM
help
GPIO number for I2C Master clock line.

config EXAMPLE_I2C_SDA_GPIO
int "SDA GPIO Num"
range 0 50
default 5 if EXAMPLE_HARDWARE_XIAOS3
default 7 if EXAMPLE_HARDWARE_QTPYS3
default 22 if EXAMPLE_HARDWARE_CUSTOM
help
GPIO number for I2C Master data line.

endmenu
81 changes: 81 additions & 0 deletions components/chsc6x/example/main/chsc6x_example.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#include <chrono>
#include <sdkconfig.h>
#include <vector>

#include "chsc6x.hpp"
#include "i2c.hpp"
#include "task.hpp"

using namespace std::chrono_literals;

extern "C" void app_main(void) {
{
std::atomic<bool> quit_test = false;
fmt::print("Starting chsc6x example\n");
//! [chsc6x example]
// make the I2C that we'll use to communicate
espp::I2c i2c({
.port = I2C_NUM_0,
.sda_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SDA_GPIO,
.scl_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SCL_GPIO,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.timeout_ms = 100,
.clk_speed = 400 * 1000,
});

bool has_chsc6x = i2c.probe_device(espp::Chsc6x::DEFAULT_ADDRESS);
fmt::print("Touchpad probe: {}\n", has_chsc6x);

// now make the chsc6x which decodes the data
espp::Chsc6x chsc6x({.write = std::bind(&espp::I2c::write, &i2c, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3),
.read = std::bind(&espp::I2c::read, &i2c, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3),
.log_level = espp::Logger::Verbosity::WARN});

// and finally, make the task to periodically poll the chsc6x and print
// the state
auto task_fn = [&chsc6x](std::mutex &m, std::condition_variable &cv) {
std::error_code ec;
// update the state
bool new_data = chsc6x.update(ec);
if (ec) {
fmt::print("Could not update state\n");
return false;
}
if (!new_data) {
return false; // don't stop the task
}
// get the state
uint8_t num_touch_points = 0;
uint16_t x = 0, y = 0;
chsc6x.get_touch_point(&num_touch_points, &x, &y);
if (ec) {
fmt::print("Could not get touch point\n");
return false;
}
fmt::print("num_touch_points: {}, x: {}, y: {}\n", num_touch_points, x, y);
// NOTE: sleeping in this way allows the sleep to exit early when the
// task is being stopped / destroyed
{
std::unique_lock<std::mutex> lk(m);
cv.wait_for(lk, 50ms);
}
return false; // don't stop the task
};
auto task = espp::Task(
{.name = "Chsc6x Task", .callback = task_fn, .log_level = espp::Logger::Verbosity::WARN});
task.start();
//! [chsc6x example]
while (true) {
std::this_thread::sleep_for(100ms);
}
}

fmt::print("Chsc6x example complete!\n");

while (true) {
std::this_thread::sleep_for(1s);
}
}
6 changes: 6 additions & 0 deletions components/chsc6x/example/sdkconfig.defaults
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CONFIG_IDF_TARGET="esp32s3"

# Common ESP-related
#
CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=4096
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
80 changes: 80 additions & 0 deletions components/chsc6x/include/chsc6x.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#pragma once

#include <atomic>
#include <functional>

#include "base_peripheral.hpp"

namespace espp {
/// @brief Driver for the Chsc6x touch controller
///
/// \section Example
/// \snippet chsc6x_example.cpp chsc6x example
class Chsc6x : public BasePeripheral<> {
public:
/// Default address for the CHSC6X chip
static constexpr uint8_t DEFAULT_ADDRESS = 0x2E;

/// @brief Configuration for the CHSC6X driver
struct Config {
BasePeripheral::write_fn write; ///< Function for writing to the CHSC6X chip
BasePeripheral::read_fn read; ///< Function for reading from the CHSC6X chip
uint8_t address = DEFAULT_ADDRESS; ///< Which address to use for this chip?
espp::Logger::Verbosity log_level{
espp::Logger::Verbosity::WARN}; ///< Log verbosity for the input driver.
};

/// @brief Constructor for the CHSC6X driver
/// @param config The configuration for the driver
explicit Chsc6x(const Config &config)
: BasePeripheral({.address = config.address, .write = config.write, .read = config.read},
"Chsc6x", config.log_level) {}

/// @brief Update the state of the CHSC6X driver
/// @param ec Error code to set if an error occurs
/// @return True if the CHSC6X has new data, false otherwise
bool update(std::error_code &ec) {
static constexpr size_t DATA_LEN = 5;
static uint8_t data[DATA_LEN];
read_many_from_register(0, data, DATA_LEN, ec);
if (ec)
return false;

// first byte is non-zero when touched, 3rd byte is x, 5th byte is y
if (data[0] == 0) {
x_ = 0;
y_ = 0;
num_touch_points_ = 0;
return true;
}
x_ = data[2];
y_ = data[4];
num_touch_points_ = 1;
logger_.debug("Touch at ({}, {})", x_, y_);
return true;
}

/// @brief Get the number of touch points
/// @return The number of touch points as of the last update
/// @note This is a cached value from the last update() call
uint8_t get_num_touch_points() const { return num_touch_points_; }

/// @brief Get the touch point data
/// @param num_touch_points The number of touch points as of the last update
/// @param x The x coordinate of the touch point
/// @param y The y coordinate of the touch point
/// @note This is a cached value from the last update() call
void get_touch_point(uint8_t *num_touch_points, uint16_t *x, uint16_t *y) const {
*num_touch_points = get_num_touch_points();
if (*num_touch_points != 0) {
*x = x_;
*y = y_;
}
}

protected:
std::atomic<uint8_t> num_touch_points_;
std::atomic<uint16_t> x_;
std::atomic<uint16_t> y_;
}; // class Chsc6x
} // namespace espp
3 changes: 1 addition & 2 deletions components/display_drivers/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
idf_component_register(
INCLUDE_DIRS "include"
SRC_DIRS "src"
PRIV_REQUIRES display driver esp_lcd
REQUIRES led
REQUIRES display driver esp_lcd led
)
2 changes: 1 addition & 1 deletion components/esp-box/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
idf_component_register(
INCLUDE_DIRS "include"
SRC_DIRS "src"
REQUIRES driver esp_lcd base_component codec display display_drivers i2c input_drivers interrupt gt911 task tt21100
REQUIRES driver base_component codec display display_drivers i2c input_drivers interrupt gt911 task tt21100
REQUIRED_IDF_TARGETS "esp32s3"
)
4 changes: 3 additions & 1 deletion components/esp-box/example/sdkconfig.defaults
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@ CONFIG_ESP_TIMER_TASK_STACK_SIZE=6144
# set the functions into IRAM
CONFIG_SPI_MASTER_IN_IRAM=y

CONFIG_LV_DEF_REFR_PERIOD=16

#
# LVGL configuration - # Themes
#
CONFIG_LV_USE_THEME_DEFAULT=y
CONFIG_LV_THEME_DEFAULT_DARK=y
CONFIG_LV_THEME_DEFAULT_GROW=y
CONFIG_LV_THEME_DEFAULT_TRANSITION_TIME=80
CONFIG_LV_THEME_DEFAULT_TRANSITION_TIME=30
2 changes: 1 addition & 1 deletion components/matouch-rotary-display/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
idf_component_register(
INCLUDE_DIRS "include"
SRC_DIRS "src"
REQUIRES driver esp_lcd base_component cst816 encoder display display_drivers i2c input_drivers interrupt task
REQUIRES driver base_component cst816 encoder display display_drivers i2c input_drivers interrupt task
REQUIRED_IDF_TARGETS "esp32s3"
)
4 changes: 3 additions & 1 deletion components/matouch-rotary-display/example/sdkconfig.defaults
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@ CONFIG_ESP_TIMER_TASK_STACK_SIZE=6144
# set the functions into IRAM
CONFIG_SPI_MASTER_IN_IRAM=y

CONFIG_LV_DEF_REFR_PERIOD=16

#
# LVGL configuration - # Themes
#
CONFIG_LV_USE_THEME_DEFAULT=y
CONFIG_LV_THEME_DEFAULT_DARK=y
CONFIG_LV_THEME_DEFAULT_GROW=y
CONFIG_LV_THEME_DEFAULT_TRANSITION_TIME=80
CONFIG_LV_THEME_DEFAULT_TRANSITION_TIME=30
5 changes: 5 additions & 0 deletions components/seeed-studio-round-display/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
idf_component_register(
INCLUDE_DIRS "include"
SRC_DIRS "src"
REQUIRES driver base_component display display_drivers i2c input_drivers interrupt task chsc6x
)
8 changes: 8 additions & 0 deletions components/seeed-studio-round-display/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
menu "Seeed Studio Round Display Configuration"
config SEEED_STUDIO_ROUND_DISPLAY_INTERRUPT_STACK_SIZE
int "Interrupt stack size"
default 4096
help
Size of the stack used for the interrupt handler. Used by the touch
callback.
endmenu
21 changes: 21 additions & 0 deletions components/seeed-studio-round-display/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 seeed-studio-round-display"
CACHE STRING
"List of components to include"
)

project(seeed_studio_roudn_display_example)

set(CMAKE_CXX_STANDARD 20)
Loading

0 comments on commit dc86501

Please sign in to comment.