Skip to content

Commit

Permalink
Add CST816 touch controller and MaTouch Rotary Display components (#286)
Browse files Browse the repository at this point in the history
* Add MaTouch Rotary Display component
* Update button to expose event callback function from interrupt for easier use
* Add CS816 touch driver peripheral
* Add MaTouch Rotary Display component

* update to use interrupt class for both button and touch update

* update docs and ci

* update cs816 example

* readme: update

* update docs

* update ci

* readme: update

* readme: update

* readme: update
  • Loading branch information
finger563 authored Jul 9, 2024
1 parent bc8100c commit 15bbda3
Show file tree
Hide file tree
Showing 26 changed files with 1,435 additions and 3 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ jobs:
target: esp32
- path: 'components/color/example'
target: esp32
- path: 'components/cst816/example'
target: esp32s3
- path: 'components/csv/example'
target: esp32
- path: 'components/display_drivers/example'
Expand Down Expand Up @@ -81,6 +83,8 @@ jobs:
target: esp32
- path: 'components/math/example'
target: esp32
- path: 'components/matouch-rotary-display/example'
target: esp32s3
- path: 'components/max1704x/example'
target: esp32
- path: 'components/monitor/example'
Expand Down
7 changes: 4 additions & 3 deletions components/button/include/button.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ namespace espp {
class Button : protected Interrupt {
public:
// Expose some types from the Interrupt class for convenience
using Event = Interrupt::Event; ///< The event type for the button
using InterruptType = Interrupt::Type; ///< The type of interrupt for the button
using ActiveLevel = Interrupt::ActiveLevel; ///< The active level of the button
using Event = Interrupt::Event; ///< The event type for the button
using InterruptType = Interrupt::Type; ///< The type of interrupt for the button
using ActiveLevel = Interrupt::ActiveLevel; ///< The active level of the button
using callback_t = Interrupt::event_callback_fn; ///< The callback function type for the button

/// \brief The configuration for the button
struct Config {
Expand Down
4 changes: 4 additions & 0 deletions components/cst816/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/cst816/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 cst816 i2c"
CACHE STRING
"List of components to include"
)

project(cst816_example)

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

This example shows how to use the CST816 touch controller with ESP32. It is
designed to run on a [Matouch Rotary
Display](https://wiki.makerfabs.com/MaTouch_ESP32_S3_Rotary_IPS_Display_1.28_GC9A01.html).

## How to use example

### Hardware Required

The [Matouch Rotary
Display](https://wiki.makerfabs.com/MaTouch_ESP32_S3_Rotary_IPS_Display_1.28_GC9A01.html)
is required for this example.

If you have other hardware with CST816, you can run menuconfig, and select the
`HARDWARE_CUSTOM` option. and then select which pins are connected to the
CST816.

### 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-07-08 at 21 57 00](https://github.com/esp-cpp/espp/assets/213467/c275598a-0759-4d40-b22b-224d2197be8f)
2 changes: 2 additions & 0 deletions components/cst816/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 ".")
33 changes: 33 additions & 0 deletions components/cst816/example/main/Kconfig.projbuild
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
menu "Example Configuration"

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

config EXAMPLE_HARDWARE_MATOUCH_ROTARY_DISPLAY
depends on IDF_TARGET_ESP32S3
bool "ESP32-S3 Matouch Rotary Display"

config EXAMPLE_HARDWARE_CUSTOM
bool "Custom"
endchoice

config EXAMPLE_I2C_SCL_GPIO
int "SCL GPIO Num"
range 0 50
default 39 if EXAMPLE_HARDWARE_MATOUCH_ROTARY_DISPLAY
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 38 if EXAMPLE_HARDWARE_MATOUCH_ROTARY_DISPLAY
default 22 if EXAMPLE_HARDWARE_CUSTOM
help
GPIO number for I2C Master data line.

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

#include "cst816.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 cst816 example\n");
//! [cst816 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_cst816 = i2c.probe_device(espp::Cst816::DEFAULT_ADDRESS);
fmt::print("Touchpad probe: {}\n", has_cst816);

// now make the cst816 which decodes the data
espp::Cst816 cst816({.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 cst816 and print
// the state
auto task_fn = [&cst816](std::mutex &m, std::condition_variable &cv) {
std::error_code ec;
// update the state
bool new_data = cst816.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
bool home_pressed = false;
home_pressed = cst816.get_home_button_state();
fmt::print("home_pressed: {}\n", home_pressed);
uint8_t num_touch_points = 0;
uint16_t x = 0, y = 0;
cst816.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 = "Cst816 Task", .callback = task_fn, .log_level = espp::Logger::Verbosity::WARN});
task.start();
//! [cst816 example]
while (true) {
std::this_thread::sleep_for(100ms);
}
}

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

while (true) {
std::this_thread::sleep_for(1s);
}
}
6 changes: 6 additions & 0 deletions components/cst816/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
105 changes: 105 additions & 0 deletions components/cst816/include/cst816.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#pragma once

#include <atomic>
#include <functional>

#include "base_peripheral.hpp"

namespace espp {
/// @brief Driver for the CST816 touch controller
/// @note This chip does not respond to I2C commands normally, only after an
/// event. To properly interact with this chip, you should register an
/// interrupt on the chip's IRQ line. After the IRQ line is asserted, it
/// will respond to I2C reads for a short period of time.
///
/// For more information, you can look at some reference code and the datasheet
/// here:
/// https://github.com/espressif/esp-bsp/tree/master/components/lcd_touch/esp_lcd_touch_cst816s
///
/// \section Example
/// \snippet cst816_example.cpp cst816 example
class Cst816 : public BasePeripheral<std::uint8_t> {
public:
/// Default address for the CST816 chip
static constexpr uint8_t DEFAULT_ADDRESS = 0x15;

/// @brief Configuration for the CST816 driver
struct Config {
BasePeripheral::write_fn write; ///< Function for writing to the CST816 chip
BasePeripheral::read_fn read; ///< Function for reading from the CST816 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 CST816 driver
/// @param config The configuration for the driver
explicit Cst816(const Config &config)
: BasePeripheral({.address = config.address, .write = config.write, .read = config.read},
"Cst816", config.log_level) {}

/// @brief Update the state of the CST816 driver
/// @param ec Error code to set if an error occurs
/// @return True if the CST816 has new data, false otherwise
bool update(std::error_code &ec) {
bool new_data = false;
Data data{};
read_many_from_register((uint8_t)Registers::DATA_START, (uint8_t *)&data, sizeof(data), ec);
if (ec)
return false;

num_touch_points_ = data.num;
x_ = (data.x_h << 8) | data.x_l;
y_ = (data.y_h << 8) | data.y_l;
home_button_pressed_ = false;
new_data = true;
return new_data;
}

/// @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_;
}
}

/// @brief Get the home button state
/// @return True if the home button is pressed, false otherwise
/// @note This is a cached value from the last update() call
bool get_home_button_state() const { return home_button_pressed_; }

protected:
static constexpr int MAX_CONTACTS = 1;

struct Data {
uint8_t num = 0;
uint8_t x_h : 4;
uint8_t : 4;
uint8_t x_l = 0;
uint8_t y_h : 4;
uint8_t : 4;
uint8_t y_l = 0;
};

enum class Registers : uint8_t {
DATA_START = 0x02,
CHIP_ID = 0xA7,
};

std::atomic<bool> home_button_pressed_{false};
std::atomic<uint8_t> num_touch_points_;
std::atomic<uint16_t> x_;
std::atomic<uint16_t> y_;
};
} // namespace espp
7 changes: 7 additions & 0 deletions components/matouch-rotary-display/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# only register the component if the target is esp32s3
idf_component_register(
INCLUDE_DIRS "include"
SRC_DIRS "src"
REQUIRES driver esp_lcd base_component button cst816 encoder display display_drivers i2c input_drivers task
REQUIRED_IDF_TARGETS "esp32s3"
)
8 changes: 8 additions & 0 deletions components/matouch-rotary-display/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
menu "Matouch Rotary Display Configuration"
config MATOUCH_INTERRUPT_STACK_SIZE
int "Interrupt stack size"
default 4096
help
Size of the stack used for the interrupt handler. Shared by the
button callback and the touch callbacks.
endmenu
21 changes: 21 additions & 0 deletions components/matouch-rotary-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 matouch-rotary-display"
CACHE STRING
"List of components to include"
)

project(matouch_rotary_display_example)

set(CMAKE_CXX_STANDARD 20)
Loading

0 comments on commit 15bbda3

Please sign in to comment.