Tab5 features, StackChan, fixes, drivers.... (#526)
This commit is contained in:
@@ -1,7 +1,5 @@
|
||||
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
|
||||
|
||||
idf_component_register(
|
||||
SRCS ${SOURCE_FILES}
|
||||
SRC_DIRS "Source"
|
||||
INCLUDE_DIRS "Source"
|
||||
REQUIRES Tactility esp_lvgl_port driver
|
||||
REQUIRES Tactility EspLcdCompat esp_lcd_touch_ft6336u driver
|
||||
)
|
||||
|
||||
@@ -1,134 +1,36 @@
|
||||
#include "Ft6x36Touch.h"
|
||||
|
||||
#include <Ft6x36Touch.h>
|
||||
#include <Tactility/Logger.h>
|
||||
|
||||
#include <esp_lcd_touch_ft6x36.h>
|
||||
#include <esp_err.h>
|
||||
#include <esp_lvgl_port.h>
|
||||
|
||||
static const auto LOGGER = tt::Logger("FT6x36");
|
||||
|
||||
void Ft6x36Touch::touchReadCallback(lv_indev_t* indev, lv_indev_data_t* data) {
|
||||
auto* touch = (Ft6x36Touch*)lv_indev_get_driver_data(indev);
|
||||
touch->mutex.lock();
|
||||
data->point = touch->lastPoint;
|
||||
data->state = touch->lastState;
|
||||
touch->mutex.unlock();
|
||||
bool Ft6x36Touch::createIoHandle(esp_lcd_panel_io_handle_t& outHandle) {
|
||||
esp_lcd_panel_io_i2c_config_t io_config = ESP_LCD_TOUCH_IO_I2C_FT6x36_CONFIG();
|
||||
return esp_lcd_new_panel_io_i2c(configuration->port, &io_config, &outHandle) == ESP_OK;
|
||||
}
|
||||
|
||||
Ft6x36Touch::Ft6x36Touch(std::unique_ptr<Configuration> inConfiguration) :
|
||||
configuration(std::move(inConfiguration)) {
|
||||
nativeTouch = std::make_shared<Ft6TouchDriver>(*this);
|
||||
bool Ft6x36Touch::createTouchHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_touch_config_t& configuration, esp_lcd_touch_handle_t& panelHandle) {
|
||||
return esp_lcd_touch_new_i2c_ft6x36(ioHandle, &configuration, &panelHandle) == ESP_OK;
|
||||
}
|
||||
|
||||
Ft6x36Touch::~Ft6x36Touch() {
|
||||
if (driverThread != nullptr && driverThread->getState() != tt::Thread::State::Stopped) {
|
||||
interruptDriverThread = true;
|
||||
driverThread->join();
|
||||
}
|
||||
}
|
||||
|
||||
void Ft6x36Touch::driverThreadMain() {
|
||||
TPoint point = { .x = 0, .y = 0 };
|
||||
TEvent event = TEvent::None;
|
||||
|
||||
while (!shouldInterruptDriverThread()) {
|
||||
driver.processTouch();
|
||||
driver.poll(&point, &event);
|
||||
|
||||
if (mutex.lock(100)) {
|
||||
switch (event) {
|
||||
case TEvent::TouchStart:
|
||||
case TEvent::TouchMove:
|
||||
case TEvent::DragStart:
|
||||
case TEvent::DragMove:
|
||||
case TEvent::DragEnd:
|
||||
lastState = LV_INDEV_STATE_PR;
|
||||
lastPoint.x = point.x;
|
||||
lastPoint.y = point.y;
|
||||
break;
|
||||
case TEvent::TouchEnd:
|
||||
lastState = LV_INDEV_STATE_REL;
|
||||
lastPoint.x = point.x;
|
||||
lastPoint.y = point.y;
|
||||
break;
|
||||
case TEvent::Tap:
|
||||
case TEvent::None:
|
||||
break;
|
||||
}
|
||||
mutex.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Ft6x36Touch::shouldInterruptDriverThread() const {
|
||||
bool interrupt = false;
|
||||
if (mutex.lock(50 / portTICK_PERIOD_MS)) {
|
||||
interrupt = interruptDriverThread;
|
||||
mutex.unlock();
|
||||
}
|
||||
return interrupt;
|
||||
}
|
||||
|
||||
bool Ft6x36Touch::start() {
|
||||
LOGGER.info("Start");
|
||||
|
||||
if (!driver.begin(FT6X36_DEFAULT_THRESHOLD, configuration->width, configuration->height)) {
|
||||
LOGGER.error("driver.begin() failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
mutex.lock();
|
||||
|
||||
interruptDriverThread = false;
|
||||
|
||||
driverThread = std::make_shared<tt::Thread>("ft6x36", 4096, [this] {
|
||||
driverThreadMain();
|
||||
return 0;
|
||||
});
|
||||
|
||||
driverThread->start();
|
||||
|
||||
mutex.unlock();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Ft6x36Touch::stop() {
|
||||
LOGGER.info("Stop");
|
||||
|
||||
mutex.lock();
|
||||
interruptDriverThread = true;
|
||||
mutex.unlock();
|
||||
|
||||
driverThread->join();
|
||||
|
||||
mutex.lock();
|
||||
driverThread = nullptr;
|
||||
mutex.unlock();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Ft6x36Touch::startLvgl(lv_display_t* display) {
|
||||
if (deviceHandle != nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
deviceHandle = lv_indev_create();
|
||||
lv_indev_set_type(deviceHandle, LV_INDEV_TYPE_POINTER);
|
||||
lv_indev_set_driver_data(deviceHandle, this);
|
||||
lv_indev_set_read_cb(deviceHandle, touchReadCallback);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Ft6x36Touch::stopLvgl() {
|
||||
if (deviceHandle == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
lv_indev_delete(deviceHandle);
|
||||
deviceHandle = nullptr;
|
||||
return true;
|
||||
esp_lcd_touch_config_t Ft6x36Touch::createEspLcdTouchConfig() {
|
||||
return {
|
||||
.x_max = configuration->xMax,
|
||||
.y_max = configuration->yMax,
|
||||
.rst_gpio_num = configuration->pinReset,
|
||||
.int_gpio_num = configuration->pinInterrupt,
|
||||
.levels = {
|
||||
.reset = configuration->pinResetLevel,
|
||||
.interrupt = configuration->pinInterruptLevel,
|
||||
},
|
||||
.flags = {
|
||||
.swap_xy = configuration->swapXy,
|
||||
.mirror_x = configuration->mirrorX,
|
||||
.mirror_y = configuration->mirrorY,
|
||||
},
|
||||
.process_coordinates = nullptr,
|
||||
.interrupt_callback = nullptr,
|
||||
.user_data = nullptr,
|
||||
.driver_data = nullptr
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,12 +2,11 @@
|
||||
|
||||
#include <Tactility/hal/touch/TouchDevice.h>
|
||||
#include <Tactility/TactilityCore.h>
|
||||
#include <Tactility/Thread.h>
|
||||
#include <driver/i2c.h>
|
||||
|
||||
#include "ft6x36/FT6X36.h"
|
||||
#include <EspLcdTouch.h>
|
||||
|
||||
class Ft6x36Touch final : public tt::hal::touch::TouchDevice {
|
||||
class Ft6x36Touch final : public EspLcdTouch {
|
||||
|
||||
public:
|
||||
|
||||
@@ -16,78 +15,56 @@ public:
|
||||
|
||||
Configuration(
|
||||
i2c_port_t port,
|
||||
gpio_num_t pinInterrupt,
|
||||
uint16_t width,
|
||||
uint16_t height
|
||||
uint16_t xMax,
|
||||
uint16_t yMax,
|
||||
bool swapXy = false,
|
||||
bool mirrorX = false,
|
||||
bool mirrorY = false,
|
||||
gpio_num_t pinReset = GPIO_NUM_NC,
|
||||
gpio_num_t pinInterrupt = GPIO_NUM_NC,
|
||||
unsigned int pinResetLevel = 0,
|
||||
unsigned int pinInterruptLevel = 0
|
||||
) : port(port),
|
||||
xMax(xMax),
|
||||
yMax(yMax),
|
||||
swapXy(swapXy),
|
||||
mirrorX(mirrorX),
|
||||
mirrorY(mirrorY),
|
||||
pinReset(pinReset),
|
||||
pinInterrupt(pinInterrupt),
|
||||
width(width),
|
||||
height(height)
|
||||
pinResetLevel(pinResetLevel),
|
||||
pinInterruptLevel(pinInterruptLevel)
|
||||
{}
|
||||
|
||||
i2c_port_t port;
|
||||
uint16_t xMax;
|
||||
uint16_t yMax;
|
||||
bool swapXy;
|
||||
bool mirrorX;
|
||||
bool mirrorY;
|
||||
gpio_num_t pinReset;
|
||||
gpio_num_t pinInterrupt;
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
};
|
||||
unsigned int pinResetLevel;
|
||||
unsigned int pinInterruptLevel;
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
std::unique_ptr<Configuration> configuration;
|
||||
lv_indev_t* deviceHandle = nullptr;
|
||||
FT6X36 driver = FT6X36(configuration->port, configuration->pinInterrupt);
|
||||
std::shared_ptr<tt::Thread> driverThread;
|
||||
bool interruptDriverThread = false;
|
||||
tt::Mutex mutex;
|
||||
std::shared_ptr<tt::hal::touch::TouchDriver> nativeTouch;
|
||||
|
||||
lv_point_t lastPoint = { .x = 0, .y = 0 };
|
||||
lv_indev_state_t lastState = LV_INDEV_STATE_RELEASED;
|
||||
bool createIoHandle(esp_lcd_panel_io_handle_t& outHandle) override;
|
||||
|
||||
bool shouldInterruptDriverThread() const;
|
||||
bool createTouchHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_touch_config_t& configuration, esp_lcd_touch_handle_t& panelHandle) override;
|
||||
|
||||
void driverThreadMain();
|
||||
|
||||
static void touchReadCallback(lv_indev_t* indev, lv_indev_data_t* data);
|
||||
esp_lcd_touch_config_t createEspLcdTouchConfig() override;
|
||||
|
||||
public:
|
||||
|
||||
explicit Ft6x36Touch(std::unique_ptr<Configuration> inConfiguration);
|
||||
~Ft6x36Touch() override;
|
||||
explicit Ft6x36Touch(std::unique_ptr<Configuration> inConfiguration) : configuration(std::move(inConfiguration)) {
|
||||
assert(configuration != nullptr);
|
||||
}
|
||||
|
||||
std::string getName() const override { return "FT6x36"; }
|
||||
|
||||
std::string getDescription() const override { return "FT6x36 I2C touch driver"; }
|
||||
|
||||
bool start() override;
|
||||
bool stop() override;
|
||||
|
||||
bool supportsLvgl() const override { return true; }
|
||||
bool startLvgl(lv_display_t* display) override;
|
||||
bool stopLvgl() override;
|
||||
|
||||
lv_indev_t* getLvglIndev() override { return deviceHandle; }
|
||||
|
||||
class Ft6TouchDriver : public tt::hal::touch::TouchDriver {
|
||||
public:
|
||||
const Ft6x36Touch& parent;
|
||||
Ft6TouchDriver(const Ft6x36Touch& parent) : parent(parent) {}
|
||||
|
||||
bool getTouchedPoints(uint16_t* x, uint16_t* y, uint16_t* _Nullable strength, uint8_t* pointCount, uint8_t maxPointCount) {
|
||||
auto lock = parent.mutex.asScopedLock();
|
||||
lock.lock();
|
||||
if (parent.lastState == LV_INDEV_STATE_PRESSED) {
|
||||
*x = parent.lastPoint.x;
|
||||
*y = parent.lastPoint.y;
|
||||
*pointCount = 1;
|
||||
return true;
|
||||
} else {
|
||||
*pointCount = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
bool supportsTouchDriver() override { return true; }
|
||||
|
||||
std::shared_ptr<tt::hal::touch::TouchDriver> _Nullable getTouchDriver() override { return nativeTouch; }
|
||||
};
|
||||
|
||||
@@ -1,378 +0,0 @@
|
||||
#include "FT6X36.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
|
||||
#define CONFIG_FT6X36_DEBUG false
|
||||
|
||||
FT6X36 *FT6X36::_instance = nullptr;
|
||||
static const char *TAG = "i2c-touch";
|
||||
|
||||
//Handle indicating I2C is ready to read the touch
|
||||
SemaphoreHandle_t TouchSemaphore = xSemaphoreCreateBinary();
|
||||
|
||||
FT6X36::FT6X36(i2c_port_t port, gpio_num_t interruptPin)
|
||||
{
|
||||
_instance = this;
|
||||
_port = port;
|
||||
_intPin = interruptPin;
|
||||
}
|
||||
|
||||
// Destructor should detach interrupt to the pin
|
||||
FT6X36::~FT6X36()
|
||||
{
|
||||
if (_intPin >= 0)
|
||||
gpio_isr_handler_remove((gpio_num_t)_intPin);
|
||||
}
|
||||
|
||||
bool FT6X36::begin(uint8_t threshold, uint16_t width, uint16_t height)
|
||||
{
|
||||
_touch_width = width;
|
||||
_touch_height = height;
|
||||
if (width == 0 || height ==0) {
|
||||
ESP_LOGE(TAG,"begin(uint8_t threshold, uint16_t width, uint16_t height) did not receive the width / height so touch cannot be rotation aware");
|
||||
}
|
||||
|
||||
uint8_t data_panel_id;
|
||||
readRegister8(FT6X36_REG_PANEL_ID, &data_panel_id);
|
||||
|
||||
if (data_panel_id != FT6X36_VENDID) {
|
||||
ESP_LOGE(TAG,"FT6X36_VENDID does not match. Received:0x%x Expected:0x%x\n",data_panel_id,FT6X36_VENDID);
|
||||
return false;
|
||||
}
|
||||
ESP_LOGI(TAG, "\tDevice ID: 0x%02x", data_panel_id);
|
||||
|
||||
uint8_t chip_id;
|
||||
readRegister8(FT6X36_REG_CHIPID, &chip_id);
|
||||
if (chip_id != FT6206_CHIPID && chip_id != FT6236_CHIPID && chip_id != FT6336_CHIPID) {
|
||||
ESP_LOGE(TAG,"FT6206_CHIPID does not match. Received:0x%x\n",chip_id);
|
||||
return false;
|
||||
}
|
||||
ESP_LOGI(TAG, "\tFound touch controller with Chip ID: 0x%02x", chip_id);
|
||||
|
||||
if (_intPin >= 0)
|
||||
{
|
||||
// INT pin triggers the callback function on the Falling edge of the GPIO
|
||||
gpio_config_t io_conf;
|
||||
io_conf.intr_type = GPIO_INTR_NEGEDGE; // GPIO_INTR_NEGEDGE repeats always interrupt
|
||||
io_conf.pin_bit_mask = 1ULL<<_intPin;
|
||||
io_conf.mode = GPIO_MODE_INPUT;
|
||||
io_conf.pull_down_en = (gpio_pulldown_t) 0; // disable pull-down mode
|
||||
io_conf.pull_up_en = (gpio_pullup_t) 1; // pull-up mode
|
||||
gpio_config(&io_conf);
|
||||
|
||||
esp_err_t isr_service = gpio_install_isr_service(0);
|
||||
printf("ISR trigger install response: 0x%x %s\n", isr_service, (isr_service==0)?"ESP_OK":"");
|
||||
gpio_isr_handler_add((gpio_num_t)_intPin, isr, (void*) 1);
|
||||
}
|
||||
|
||||
writeRegister8(FT6X36_REG_DEVICE_MODE, 0x00);
|
||||
writeRegister8(FT6X36_REG_THRESHHOLD, threshold);
|
||||
writeRegister8(FT6X36_REG_TOUCHRATE_ACTIVE, 0x0E);
|
||||
return true;
|
||||
}
|
||||
|
||||
void FT6X36::registerTouchHandler(void (*fn)(TPoint point, TEvent e))
|
||||
{
|
||||
_touchHandler = fn;
|
||||
if (CONFIG_FT6X36_DEBUG) printf("Touch handler function registered\n");
|
||||
}
|
||||
|
||||
uint8_t FT6X36::touched()
|
||||
{
|
||||
uint8_t data_buf;
|
||||
esp_err_t ret = readRegister8(FT6X36_REG_NUM_TOUCHES, &data_buf);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Error reading from device: %s", esp_err_to_name(ret));
|
||||
}
|
||||
|
||||
if (data_buf > 2)
|
||||
{
|
||||
data_buf = 0;
|
||||
}
|
||||
|
||||
return data_buf;
|
||||
}
|
||||
|
||||
void FT6X36::loop()
|
||||
{
|
||||
processTouch();
|
||||
}
|
||||
|
||||
void IRAM_ATTR FT6X36::isr(void* arg)
|
||||
{
|
||||
/* Un-block the interrupt processing task now */
|
||||
xSemaphoreGive(TouchSemaphore);
|
||||
//xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL);
|
||||
}
|
||||
|
||||
void FT6X36::processTouch()
|
||||
{
|
||||
/* Task move to Block state to wait for interrupt event */
|
||||
if (_intPin >= 0)
|
||||
{
|
||||
if (xSemaphoreTake(TouchSemaphore, portMAX_DELAY) == false) return;
|
||||
}
|
||||
|
||||
readData();
|
||||
uint8_t n = 0;
|
||||
TRawEvent event = (TRawEvent)_touchEvent[n];
|
||||
TPoint point{_touchX[n], _touchY[n]};
|
||||
|
||||
switch (event) {
|
||||
|
||||
case TRawEvent::PressDown:
|
||||
_points[0] = point;
|
||||
_dragMode = false;
|
||||
// Note: Is in microseconds. Ref https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/esp_timer.html
|
||||
_touchStartTime = esp_timer_get_time()/1000;
|
||||
fireEvent(point, TEvent::TouchStart);
|
||||
break;
|
||||
|
||||
case TRawEvent::Contact:
|
||||
// Dragging makes no sense IMHO. Since the X & Y are not getting updated while dragging
|
||||
// Dragging && _points[0].aboutEqual(point) - Not used IDEA 2: && (lastEvent == 2)
|
||||
if (!_dragMode &&
|
||||
(abs(lastX-_touchX[n]) <= maxDeviation || abs(lastY-_touchY[n])<=maxDeviation) &&
|
||||
esp_timer_get_time()/1000 - _touchStartTime > 300) {
|
||||
_dragMode = true;
|
||||
fireEvent(point, TEvent::DragStart);
|
||||
#if defined(CONFIG_FT6X36_DEBUG_EVENTS) && CONFIG_FT6X36_DEBUG_EVENTS==1
|
||||
printf("EV: DragStart\n");
|
||||
#endif
|
||||
|
||||
} else if (_dragMode) {
|
||||
fireEvent(point, TEvent::DragMove);
|
||||
#if defined(CONFIG_FT6X36_DEBUG_EVENTS) && CONFIG_FT6X36_DEBUG_EVENTS==1
|
||||
printf("EV: DragMove\n");
|
||||
#endif
|
||||
}
|
||||
fireEvent(point, TEvent::TouchMove);
|
||||
|
||||
// For me the _touchStartTime shouold be set in both PressDown & Contact events, but after Drag detection
|
||||
_touchStartTime = esp_timer_get_time()/1000;
|
||||
break;
|
||||
|
||||
case TRawEvent::LiftUp:
|
||||
|
||||
_points[9] = point;
|
||||
_touchEndTime = esp_timer_get_time()/1000;
|
||||
|
||||
//printf("TIMEDIFF: %lu End: %lu\n", _touchEndTime - _touchStartTime, _touchEndTime);
|
||||
|
||||
fireEvent(point, TEvent::TouchEnd);
|
||||
if (_dragMode) {
|
||||
fireEvent(point, TEvent::DragEnd);
|
||||
#if defined(CONFIG_FT6X36_DEBUG_EVENTS) && CONFIG_FT6X36_DEBUG_EVENTS==1
|
||||
printf("EV: DragEnd\n");
|
||||
#endif
|
||||
_dragMode = false;
|
||||
}
|
||||
|
||||
if ( _touchEndTime - _touchStartTime <= 900) {
|
||||
// Do not get why this: _points[0].aboutEqual(point) (Original library)
|
||||
fireEvent(point, TEvent::Tap);
|
||||
_points[0] = {0, 0};
|
||||
_touchStartTime = 0;
|
||||
|
||||
#if defined(CONFIG_FT6X36_DEBUG_EVENTS) && CONFIG_FT6X36_DEBUG_EVENTS==1
|
||||
printf("EV: Tap\n");
|
||||
#endif
|
||||
_dragMode = false;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case TRawEvent::NoEvent:
|
||||
#if defined(CONFIG_FT6X36_DEBUG_EVENTS) && CONFIG_FT6X36_DEBUG_EVENTS==1
|
||||
printf("EV: NoEvent\n");
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
// Store lastEvent
|
||||
lastEvent = (int) event;
|
||||
lastX = _touchX[0];
|
||||
lastY = _touchY[0];
|
||||
}
|
||||
|
||||
void FT6X36::poll(TPoint * point, TEvent * e)
|
||||
{
|
||||
readData();
|
||||
// TPoint point{_touchX[0], _touchY[0]};
|
||||
TRawEvent event = (TRawEvent)_touchEvent[0];
|
||||
|
||||
if (point != NULL)
|
||||
{
|
||||
point->x = _touchX[0];
|
||||
point->y = _touchY[0];
|
||||
}
|
||||
if (e != NULL)
|
||||
{
|
||||
switch (event)
|
||||
{
|
||||
case TRawEvent::PressDown:
|
||||
*e = TEvent::TouchStart;
|
||||
break;
|
||||
case TRawEvent::Contact:
|
||||
*e = TEvent::TouchMove;
|
||||
break;
|
||||
case TRawEvent::LiftUp:
|
||||
default:
|
||||
*e = TEvent::TouchEnd;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t FT6X36::read8(uint8_t regName) {
|
||||
uint8_t buf;
|
||||
readRegister8(regName, &buf);
|
||||
return buf;
|
||||
}
|
||||
|
||||
#define data_size 16 // Discarding last 2: 0x0E & 0x0F as not relevant
|
||||
bool FT6X36::readData(void)
|
||||
{
|
||||
esp_err_t ret;
|
||||
uint8_t data[data_size];
|
||||
uint8_t touch_pnt_cnt; // Number of detected touch points
|
||||
readRegister8(FT6X36_REG_NUM_TOUCHES, &touch_pnt_cnt);
|
||||
|
||||
// Read data
|
||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||
i2c_master_start(cmd);
|
||||
i2c_master_write_byte(cmd, (FT6X36_ADDR<<1), ACK_CHECK_EN);
|
||||
i2c_master_write_byte(cmd, 0, ACK_CHECK_EN);
|
||||
i2c_master_stop(cmd);
|
||||
ret = i2c_master_cmd_begin(_port, cmd, 1000 / portTICK_PERIOD_MS);
|
||||
i2c_cmd_link_delete(cmd);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
cmd = i2c_cmd_link_create();
|
||||
i2c_master_start(cmd);
|
||||
i2c_master_write_byte(cmd, (FT6X36_ADDR<<1)|1, ACK_CHECK_EN);
|
||||
i2c_master_read(cmd, data, data_size, I2C_MASTER_LAST_NACK);
|
||||
i2c_master_stop(cmd);
|
||||
ret = i2c_master_cmd_begin(_port, cmd, 1000 / portTICK_PERIOD_MS);
|
||||
i2c_cmd_link_delete(cmd);
|
||||
|
||||
if (CONFIG_FT6X36_DEBUG) {
|
||||
//printf("REGISTERS:\n");
|
||||
for (int16_t i = 0; i < data_size; i++)
|
||||
{
|
||||
printf("%x:%x ", i, data[i]);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
const uint8_t addrShift = 6;
|
||||
|
||||
// READ X, Y and Touch events (X 2)
|
||||
for (uint8_t i = 0; i < 2; i++)
|
||||
{
|
||||
_touchX[i] = data[FT6X36_REG_P1_XH + i * addrShift] & 0x0F;
|
||||
_touchX[i] <<= 8;
|
||||
_touchX[i] |= data[FT6X36_REG_P1_XL + i * addrShift];
|
||||
_touchY[i] = data[FT6X36_REG_P1_YH + i * addrShift] & 0x0F;
|
||||
_touchY[i] <<= 8;
|
||||
_touchY[i] |= data[FT6X36_REG_P1_YL + i * addrShift];
|
||||
_touchEvent[i] = data[FT6X36_REG_P1_XH + i * addrShift] >> 6;
|
||||
}
|
||||
|
||||
// Make _touchX[idx] and _touchY[idx] rotation aware
|
||||
switch (_rotation)
|
||||
{
|
||||
case 1:
|
||||
swap(_touchX[0], _touchY[0]);
|
||||
swap(_touchX[1], _touchY[1]);
|
||||
_touchY[0] = _touch_width - _touchY[0] -1;
|
||||
_touchY[1] = _touch_width - _touchY[1] -1;
|
||||
break;
|
||||
case 2:
|
||||
_touchX[0] = _touch_width - _touchX[0] - 1;
|
||||
_touchX[1] = _touch_width - _touchX[1] - 1;
|
||||
_touchY[0] = _touch_height - _touchY[0] - 1;
|
||||
_touchY[1] = _touch_height - _touchY[1] - 1;
|
||||
break;
|
||||
case 3:
|
||||
swap(_touchX[0], _touchY[0]);
|
||||
swap(_touchX[1], _touchY[1]);
|
||||
_touchX[0] = _touch_height - _touchX[0] - 1;
|
||||
_touchX[1] = _touch_height - _touchX[1] - 1;
|
||||
break;
|
||||
}
|
||||
if (CONFIG_FT6X36_DEBUG) {
|
||||
printf("X0:%d Y0:%d EVENT:%d\n", _touchX[0], _touchY[0], _touchEvent[0]);
|
||||
//printf("X1:%d Y1:%d EVENT:%d\n", _touchX[1], _touchY[1], _touchEvent[1]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void FT6X36::writeRegister8(uint8_t reg, uint8_t value)
|
||||
{
|
||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||
i2c_master_start(cmd);
|
||||
i2c_master_write_byte(cmd, FT6X36_ADDR << 1 | I2C_MASTER_WRITE, ACK_CHECK_EN);
|
||||
i2c_master_write_byte(cmd, reg , ACK_CHECK_EN);
|
||||
i2c_master_write_byte(cmd, value , ACK_CHECK_EN);
|
||||
i2c_master_stop(cmd);
|
||||
i2c_master_cmd_begin(_port, cmd, 1000 / portTICK_PERIOD_MS);
|
||||
i2c_cmd_link_delete(cmd);
|
||||
}
|
||||
|
||||
uint8_t FT6X36::readRegister8(uint8_t reg, uint8_t *data_buf)
|
||||
{
|
||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||
i2c_master_start(cmd);
|
||||
i2c_master_write_byte(cmd, FT6X36_ADDR << 1 | I2C_MASTER_WRITE, ACK_CHECK_EN);
|
||||
i2c_master_write_byte(cmd, reg, I2C_MASTER_ACK);
|
||||
// Research: Why it's started a 2nd time here
|
||||
i2c_master_start(cmd);
|
||||
i2c_master_write_byte(cmd, (FT6X36_ADDR << 1) | I2C_MASTER_READ, true);
|
||||
|
||||
i2c_master_read_byte(cmd, data_buf, I2C_MASTER_NACK);
|
||||
i2c_master_stop(cmd);
|
||||
esp_err_t ret = i2c_master_cmd_begin(_port, cmd, 1000 / portTICK_PERIOD_MS);
|
||||
i2c_cmd_link_delete(cmd);
|
||||
|
||||
|
||||
//FT6X36_REG_GESTURE_ID. Check if it can be read!
|
||||
#if defined(FT6X36_DEBUG) && FT6X36_DEBUG==1
|
||||
printf("REG 0x%x: 0x%x\n",reg,ret);
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void FT6X36::fireEvent(TPoint point, TEvent e)
|
||||
{
|
||||
if (_touchHandler)
|
||||
_touchHandler(point, e);
|
||||
}
|
||||
|
||||
void FT6X36::debugInfo()
|
||||
{
|
||||
printf(" TH_DIFF: %d CTRL: %d\n", read8(FT6X36_REG_FILTER_COEF), read8(FT6X36_REG_CTRL));
|
||||
printf(" TIMEENTERMONITOR: %d PERIODACTIVE: %d\n", read8(FT6X36_REG_TIME_ENTER_MONITOR), read8(FT6X36_REG_TOUCHRATE_ACTIVE));
|
||||
printf(" PERIODMONITOR: %d RADIAN_VALUE: %d\n", read8(FT6X36_REG_TOUCHRATE_MONITOR), read8(FT6X36_REG_RADIAN_VALUE));
|
||||
printf(" OFFSET_LEFT_RIGHT: %d OFFSET_UP_DOWN: %d\n", read8(FT6X36_REG_OFFSET_LEFT_RIGHT), read8(FT6X36_REG_OFFSET_UP_DOWN));
|
||||
printf("DISTANCE_LEFT_RIGHT: %d DISTANCE_UP_DOWN: %d\n", read8(FT6X36_REG_DISTANCE_LEFT_RIGHT), read8(FT6X36_REG_DISTANCE_UP_DOWN));
|
||||
printf(" DISTANCE_ZOOM: %d CIPHER: %d\n", read8(FT6X36_REG_DISTANCE_ZOOM), read8(FT6X36_REG_CHIPID));
|
||||
printf(" G_MODE: %d PWR_MODE: %d\n", read8(FT6X36_REG_INTERRUPT_MODE), read8(FT6X36_REG_POWER_MODE));
|
||||
printf(" FIRMID: %d FOCALTECH_ID: %d STATE: %d\n", read8(FT6X36_REG_FIRMWARE_VERSION), read8(FT6X36_REG_PANEL_ID), read8(FT6X36_REG_STATE));
|
||||
}
|
||||
|
||||
void FT6X36::setRotation(uint8_t rotation) {
|
||||
_rotation = rotation;
|
||||
}
|
||||
|
||||
void FT6X36::setTouchWidth(uint16_t width) {
|
||||
printf("touch w:%d\n",width);
|
||||
_touch_width = width;
|
||||
}
|
||||
|
||||
void FT6X36::setTouchHeight(uint16_t height) {
|
||||
printf("touch h:%d\n",height);
|
||||
_touch_height = height;
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "esp_log.h"
|
||||
#include "driver/i2c.h"
|
||||
#include "sdkconfig.h"
|
||||
#include <esp_timer.h>
|
||||
|
||||
#ifndef ft6x36_h
|
||||
#define ft6x36_h
|
||||
// I2C Constants
|
||||
#define I2C_MASTER_TX_BUF_DISABLE 0 /*!< I2C master doesn't need buffer */
|
||||
#define I2C_MASTER_RX_BUF_DISABLE 0 /*!< I2C master doesn't need buffer */
|
||||
|
||||
#define ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/
|
||||
#define ACK_CHECK_DIS 0x0 /*!< I2C master will not check ack from slave */
|
||||
#define ACK_VAL 0x0 /*!< I2C ack value */
|
||||
#define NACK_VAL 0x1 /*!< I2C nack value */
|
||||
|
||||
//SemaphoreHandle_t print_mux = NULL;
|
||||
|
||||
#define FT6X36_ADDR 0x38
|
||||
|
||||
#define FT6X36_REG_DEVICE_MODE 0x00
|
||||
#define FT6X36_REG_GESTURE_ID 0x01
|
||||
#define FT6X36_REG_NUM_TOUCHES 0x02
|
||||
#define FT6X36_REG_P1_XH 0x03
|
||||
#define FT6X36_REG_P1_XL 0x04
|
||||
#define FT6X36_REG_P1_YH 0x05
|
||||
#define FT6X36_REG_P1_YL 0x06
|
||||
#define FT6X36_REG_P1_WEIGHT 0x07
|
||||
#define FT6X36_REG_P1_MISC 0x08
|
||||
#define FT6X36_REG_P2_XH 0x09
|
||||
#define FT6X36_REG_P2_XL 0x0A
|
||||
#define FT6X36_REG_P2_YH 0x0B
|
||||
#define FT6X36_REG_P2_YL 0x0C
|
||||
#define FT6X36_REG_P2_WEIGHT 0x0D
|
||||
#define FT6X36_REG_P2_MISC 0x0E
|
||||
#define FT6X36_REG_THRESHHOLD 0x80
|
||||
#define FT6X36_REG_FILTER_COEF 0x85
|
||||
#define FT6X36_REG_CTRL 0x86
|
||||
#define FT6X36_REG_TIME_ENTER_MONITOR 0x87
|
||||
#define FT6X36_REG_TOUCHRATE_ACTIVE 0x88
|
||||
#define FT6X36_REG_TOUCHRATE_MONITOR 0x89 // value in ms
|
||||
#define FT6X36_REG_RADIAN_VALUE 0x91
|
||||
#define FT6X36_REG_OFFSET_LEFT_RIGHT 0x92
|
||||
#define FT6X36_REG_OFFSET_UP_DOWN 0x93
|
||||
#define FT6X36_REG_DISTANCE_LEFT_RIGHT 0x94
|
||||
#define FT6X36_REG_DISTANCE_UP_DOWN 0x95
|
||||
#define FT6X36_REG_DISTANCE_ZOOM 0x96
|
||||
#define FT6X36_REG_LIB_VERSION_H 0xA1
|
||||
#define FT6X36_REG_LIB_VERSION_L 0xA2
|
||||
#define FT6X36_REG_CHIPID 0xA3
|
||||
#define FT6X36_REG_INTERRUPT_MODE 0xA4
|
||||
#define FT6X36_REG_POWER_MODE 0xA5
|
||||
#define FT6X36_REG_FIRMWARE_VERSION 0xA6
|
||||
#define FT6X36_REG_PANEL_ID 0xA8
|
||||
#define FT6X36_REG_STATE 0xBC
|
||||
|
||||
#define FT6X36_PMODE_ACTIVE 0x00
|
||||
#define FT6X36_PMODE_MONITOR 0x01
|
||||
#define FT6X36_PMODE_STANDBY 0x02
|
||||
#define FT6X36_PMODE_HIBERNATE 0x03
|
||||
|
||||
/* Possible values returned by FT6X36_GEST_ID_REG */
|
||||
#define FT6X36_GEST_ID_NO_GESTURE 0x00
|
||||
#define FT6X36_GEST_ID_MOVE_UP 0x10
|
||||
#define FT6X36_GEST_ID_MOVE_RIGHT 0x14
|
||||
#define FT6X36_GEST_ID_MOVE_DOWN 0x18
|
||||
#define FT6X36_GEST_ID_MOVE_LEFT 0x1C
|
||||
#define FT6X36_GEST_ID_ZOOM_IN 0x48
|
||||
#define FT6X36_GEST_ID_ZOOM_OUT 0x49
|
||||
|
||||
#define FT6X36_VENDID 0x11
|
||||
#define FT6206_CHIPID 0x06
|
||||
#define FT6236_CHIPID 0x36
|
||||
#define FT6336_CHIPID 0x64
|
||||
|
||||
#define FT6X36_DEFAULT_THRESHOLD 22
|
||||
|
||||
// From: https://github.com/lvgl/lv_port_esp32/blob/master/components/lvgl_esp32_drivers/lvgl_touch/ft6x36.h
|
||||
#define FT6X36_MSB_MASK 0x0F
|
||||
#define FT6X36_LSB_MASK 0xFF
|
||||
|
||||
enum class TRawEvent
|
||||
{
|
||||
PressDown,
|
||||
LiftUp,
|
||||
Contact,
|
||||
NoEvent
|
||||
};
|
||||
|
||||
enum class TEvent
|
||||
{
|
||||
None,
|
||||
TouchStart,
|
||||
TouchMove,
|
||||
TouchEnd,
|
||||
Tap,
|
||||
DragStart,
|
||||
DragMove,
|
||||
DragEnd
|
||||
};
|
||||
|
||||
struct TPoint
|
||||
{
|
||||
uint16_t x;
|
||||
uint16_t y;
|
||||
/**
|
||||
* This is being used in the original library but I'm not using it in this implementation
|
||||
*/
|
||||
bool aboutEqual(const TPoint point)
|
||||
{
|
||||
return abs(x - point.x) <= 5 && abs(y - point.y) <= 5;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
class FT6X36
|
||||
{
|
||||
static void IRAM_ATTR isr(void* arg);
|
||||
public:
|
||||
// TwoWire * wire will be replaced by ESP-IDF https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2c.html
|
||||
FT6X36(i2c_port_t = I2C_NUM_0, gpio_num_t interruptPin = GPIO_NUM_NC);
|
||||
~FT6X36();
|
||||
bool begin(uint8_t threshold = FT6X36_DEFAULT_THRESHOLD, uint16_t width = 0, uint16_t height = 0);
|
||||
void registerTouchHandler(void(*fn)(TPoint point, TEvent e));
|
||||
uint8_t touched();
|
||||
void loop();
|
||||
void processTouch();
|
||||
void debugInfo();
|
||||
void poll(TPoint * point, TEvent * event);
|
||||
// Helper functions to make the touch display aware
|
||||
void setRotation(uint8_t rotation);
|
||||
void setTouchWidth(uint16_t width);
|
||||
void setTouchHeight(uint16_t height);
|
||||
// Pending implementation. How much x->touch y↓touch is placed (In case is smaller than display)
|
||||
void setXoffset(uint16_t x_offset);
|
||||
void setYoffset(uint16_t y_offset);
|
||||
// Smart template from EPD to swap x,y:
|
||||
template <typename T> static inline void
|
||||
swap(T& a, T& b)
|
||||
{
|
||||
T t = a;
|
||||
a = b;
|
||||
b = t;
|
||||
}
|
||||
void(*_touchHandler)(TPoint point, TEvent e) = nullptr;
|
||||
|
||||
bool readData(void);
|
||||
private:
|
||||
void writeRegister8(uint8_t reg, uint8_t val);
|
||||
uint8_t readRegister8(uint8_t reg, uint8_t *data_buf);
|
||||
void fireEvent(TPoint point, TEvent e);
|
||||
uint8_t read8(uint8_t regName);
|
||||
static FT6X36 * _instance;
|
||||
|
||||
i2c_port_t _port;
|
||||
int8_t _intPin;
|
||||
|
||||
// Make touch rotation aware:
|
||||
uint8_t _rotation = 0;
|
||||
uint16_t _touch_width = 0;
|
||||
uint16_t _touch_height = 0;
|
||||
|
||||
uint8_t _touches;
|
||||
uint16_t _touchX[2], _touchY[2], _touchEvent[2];
|
||||
TPoint _points[10];
|
||||
uint8_t _pointIdx = 0;
|
||||
unsigned long _touchStartTime = 0;
|
||||
unsigned long _touchEndTime = 0;
|
||||
uint8_t lastEvent = 3; // No event
|
||||
uint16_t lastX = 0;
|
||||
uint16_t lastY = 0;
|
||||
bool _dragMode = false;
|
||||
const uint8_t maxDeviation = 5;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 strange_v
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,8 +0,0 @@
|
||||
This project is an adaption of the code at https://github.com/martinberlin/FT6X36-IDF which is an adaptation of https://github.com/strange-v/FT6X36
|
||||
The original license is an MIT license and is included in this directory.
|
||||
|
||||
Changes:
|
||||
- Remove Kconfig-based configuratio
|
||||
- Removed I2C init code
|
||||
- Allow for passing a different I2C port
|
||||
|
||||
@@ -14,6 +14,7 @@ std::shared_ptr<EspLcdConfiguration> St7789Display::createEspLcdConfiguration(co
|
||||
.mirrorY = configuration.mirrorY,
|
||||
.invertColor = configuration.invertColor,
|
||||
.bufferSize = configuration.bufferSize,
|
||||
.buffSpiram = configuration.buffSpiram,
|
||||
.touch = configuration.touch,
|
||||
.backlightDutyFunction = configuration.backlightDutyFunction,
|
||||
.resetPin = configuration.resetPin,
|
||||
|
||||
@@ -25,6 +25,7 @@ public:
|
||||
std::function<void(uint8_t)> _Nullable backlightDutyFunction;
|
||||
gpio_num_t resetPin;
|
||||
bool lvglSwapBytes;
|
||||
bool buffSpiram = false;
|
||||
lcd_rgb_element_order_t rgbElementOrder = LCD_RGB_ELEMENT_ORDER_RGB;
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake")
|
||||
|
||||
file(GLOB_RECURSE SOURCE_FILES "source/*.c*")
|
||||
|
||||
tactility_add_module(ina226-module
|
||||
SRCS ${SOURCE_FILES}
|
||||
INCLUDE_DIRS include/
|
||||
REQUIRES TactilityKernel
|
||||
)
|
||||
@@ -0,0 +1,5 @@
|
||||
# INA226
|
||||
|
||||
Texas Instruments INA226 High-Side or Low-Side Measurement, Bi-Directional Current and Power Monitor.
|
||||
|
||||
- Datasheet: https://www.ti.com/lit/ds/symlink/ina226.pdf
|
||||
@@ -0,0 +1,12 @@
|
||||
description: TI INA226 voltage/current monitor
|
||||
|
||||
include: ["i2c-device.yaml"]
|
||||
|
||||
compatible: "ti,ina226"
|
||||
|
||||
properties:
|
||||
shunt-milliohms:
|
||||
type: int
|
||||
required: true
|
||||
# shunt value should be a minimum of 1
|
||||
description: Shunt resistor value in milliohms
|
||||
@@ -0,0 +1,3 @@
|
||||
dependencies:
|
||||
- TactilityKernel
|
||||
bindings: bindings
|
||||
@@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <tactility/bindings/bindings.h>
|
||||
#include <drivers/ina226.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
DEFINE_DEVICETREE(ina226, struct Ina226Config)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,26 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <tactility/error.h>
|
||||
|
||||
struct Device;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct Ina226Config {
|
||||
uint8_t address;
|
||||
uint16_t shunt_milliohms;
|
||||
};
|
||||
|
||||
/** Bus voltage in volts (1.25 mV/LSB) */
|
||||
error_t ina226_read_bus_voltage(struct Device* device, float* volts);
|
||||
|
||||
/** Shunt current in amps (positive = charging, negative = discharging) */
|
||||
error_t ina226_read_shunt_current(struct Device* device, float* amps);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,14 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <tactility/module.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern struct Module ina226_module;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,138 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <drivers/ina226.h>
|
||||
#include <ina226_module.h>
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/drivers/i2c_controller.h>
|
||||
#include <tactility/log.h>
|
||||
|
||||
#define TAG "INA226"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Register map
|
||||
// ---------------------------------------------------------------------------
|
||||
static constexpr uint8_t REG_CONFIG = 0x00; ///< RW - Configuration
|
||||
static constexpr uint8_t REG_SHUNT_VOLT = 0x01; ///< R - Shunt voltage (2.5 µV/LSB, signed)
|
||||
static constexpr uint8_t REG_BUS_VOLT = 0x02; ///< R - Bus voltage (1.25 mV/LSB)
|
||||
static constexpr uint8_t REG_CURRENT = 0x04; ///< R - Current (CURRENT_LSB/LSB, signed; valid after calibration)
|
||||
static constexpr uint8_t REG_CALIBRATION = 0x05; ///< RW - Calibration
|
||||
|
||||
static constexpr uint8_t REG_MANUFACTURER_ID = 0xFE;
|
||||
static constexpr uint16_t EXPECTED_MFR_ID = 0x5449; // "TI"
|
||||
|
||||
// CONFIG: AVG=16 (010), VBUSCT=1100µs (100), VSHCT=1100µs (100), MODE=continuous shunt+bus (111)
|
||||
static constexpr uint16_t CONFIG_VALUE = 0x4527;
|
||||
static constexpr uint16_t CONFIG_SHUTDOWN = 0x4520; // Same as CONFIG_VALUE but MODE=000
|
||||
|
||||
static constexpr float MAX_CURRENT_A = 8.192f;
|
||||
static constexpr float CURRENT_LSB = MAX_CURRENT_A / 32768.0f;
|
||||
|
||||
static constexpr TickType_t TIMEOUT = pdMS_TO_TICKS(50);
|
||||
|
||||
#define GET_CONFIG(device) (static_cast<const Ina226Config*>((device)->config))
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Driver lifecycle
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static error_t start(Device* device) {
|
||||
Device* i2c = device_get_parent(device);
|
||||
if (device_get_type(i2c) != &I2C_CONTROLLER_TYPE) {
|
||||
LOG_E(TAG, "Parent is not an I2C controller");
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
const uint8_t addr = GET_CONFIG(device)->address;
|
||||
|
||||
uint16_t mfr_id = 0;
|
||||
error_t err = i2c_controller_register16be_get(i2c, addr, REG_MANUFACTURER_ID, &mfr_id, TIMEOUT);
|
||||
if (err != ERROR_NONE) {
|
||||
LOG_E(TAG, "Failed to read INA226 manufacturer ID");
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
if (mfr_id != EXPECTED_MFR_ID) {
|
||||
LOG_E(TAG, "Wrong device detected (mfr_id=0x%04X, expected=0x%04X)", mfr_id, EXPECTED_MFR_ID);
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
if (i2c_controller_register16be_set(i2c, addr, REG_CONFIG, CONFIG_VALUE, TIMEOUT) != ERROR_NONE) {
|
||||
LOG_E(TAG, "Failed to configure INA226");
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
const uint16_t shunt_milliohms = GET_CONFIG(device)->shunt_milliohms;
|
||||
if (shunt_milliohms == 0) {
|
||||
LOG_E(TAG, "Invalid shunt value: 0 mOhms");
|
||||
return ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
const float shunt_ohms = shunt_milliohms / 1000.0f;
|
||||
const float cal_f = 0.00512f / (CURRENT_LSB * shunt_ohms);
|
||||
if (cal_f < 1.0f || cal_f > 65535.0f) {
|
||||
LOG_E(TAG, "Calibration out of range (shunt=%u mOhms)", shunt_milliohms);
|
||||
return ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
const uint16_t cal_value = static_cast<uint16_t>(cal_f);
|
||||
if (i2c_controller_register16be_set(i2c, addr, REG_CALIBRATION, cal_value, TIMEOUT) != ERROR_NONE) {
|
||||
LOG_E(TAG, "Failed to calibrate INA226");
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
LOG_I(TAG, "INA226 started (addr=0x%02X, shunt=%umΩ, cal=0x%04X)", addr, GET_CONFIG(device)->shunt_milliohms, cal_value);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t stop(Device* device) {
|
||||
auto* i2c = device_get_parent(device);
|
||||
if (device_get_type(i2c) != &I2C_CONTROLLER_TYPE) {
|
||||
LOG_E(TAG, "Parent is not an I2C controller");
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
auto addr = GET_CONFIG(device)->address;
|
||||
|
||||
// Put device into shutdown mode to save power
|
||||
if (i2c_controller_register16be_set(i2c, addr, REG_CONFIG, CONFIG_SHUTDOWN, TIMEOUT) != ERROR_NONE) {
|
||||
LOG_E(TAG, "Failed to shutdown INA226 (ignored)");
|
||||
}
|
||||
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public API
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
extern "C" {
|
||||
|
||||
error_t ina226_read_bus_voltage(Device* device, float* volts) {
|
||||
if (device == nullptr) return ERROR_INVALID_ARGUMENT;
|
||||
if (volts == nullptr) return ERROR_INVALID_ARGUMENT;
|
||||
uint16_t raw = 0;
|
||||
error_t err = i2c_controller_register16be_get(device_get_parent(device), GET_CONFIG(device)->address, REG_BUS_VOLT, &raw, TIMEOUT);
|
||||
if (err != ERROR_NONE) return err;
|
||||
*volts = static_cast<float>(raw) * 0.00125f;
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
error_t ina226_read_shunt_current(Device* device, float* amps) {
|
||||
if (device == nullptr) return ERROR_INVALID_ARGUMENT;
|
||||
if (amps == nullptr) return ERROR_INVALID_ARGUMENT;
|
||||
uint16_t raw = 0;
|
||||
error_t err = i2c_controller_register16be_get(device_get_parent(device), GET_CONFIG(device)->address, REG_CURRENT, &raw, TIMEOUT);
|
||||
if (err != ERROR_NONE) return err;
|
||||
*amps = static_cast<float>(static_cast<int16_t>(raw)) * CURRENT_LSB;
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
Driver ina226_driver = {
|
||||
.name = "ina226",
|
||||
.compatible = (const char*[]) { "ti,ina226", nullptr },
|
||||
.start_device = start,
|
||||
.stop_device = stop,
|
||||
.api = nullptr,
|
||||
.device_type = nullptr,
|
||||
.owner = &ina226_module,
|
||||
.internal = nullptr
|
||||
};
|
||||
|
||||
} // extern "C"
|
||||
@@ -0,0 +1,34 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <tactility/check.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
extern Driver ina226_driver;
|
||||
|
||||
static error_t start() {
|
||||
/* We crash when construct fails, because if a single driver fails to construct,
|
||||
* there is no guarantee that the previously constructed drivers can be destroyed */
|
||||
check(driver_construct_add(&ina226_driver) == ERROR_NONE);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t stop() {
|
||||
/* We crash when destruct fails, because if a single driver fails to destruct,
|
||||
* there is no guarantee that the previously destroyed drivers can be recovered */
|
||||
check(driver_remove_destruct(&ina226_driver) == ERROR_NONE);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
extern const ModuleSymbol ina226_module_symbols[];
|
||||
|
||||
Module ina226_module = {
|
||||
.name = "ina226",
|
||||
.start = start,
|
||||
.stop = stop,
|
||||
.symbols = ina226_module_symbols,
|
||||
.internal = nullptr
|
||||
};
|
||||
|
||||
} // extern "C"
|
||||
@@ -0,0 +1,9 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <drivers/ina226.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
const struct ModuleSymbol ina226_module_symbols[] = {
|
||||
DEFINE_MODULE_SYMBOL(ina226_read_bus_voltage),
|
||||
DEFINE_MODULE_SYMBOL(ina226_read_shunt_current),
|
||||
MODULE_SYMBOL_TERMINATOR
|
||||
};
|
||||
@@ -41,6 +41,8 @@ error_t m5pm1_is_charging(struct Device* device, bool* charging);
|
||||
error_t m5pm1_set_charge_enable(struct Device* device, bool enable);
|
||||
error_t m5pm1_set_boost_enable(struct Device* device, bool enable); ///< 5V BOOST / Grove power
|
||||
error_t m5pm1_set_ldo_enable(struct Device* device, bool enable); ///< 3.3V LDO
|
||||
/** PM1_G3: speaker amplifier enable (HIGH = on) */
|
||||
error_t m5pm1_set_speaker_enable(struct Device* device, bool enable);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Temperature (internal chip sensor)
|
||||
|
||||
@@ -49,6 +49,9 @@ static constexpr uint8_t ADC_CH_TEMP = 6;
|
||||
|
||||
// PM1_G2: LCD power enable on M5Stack StickS3
|
||||
static constexpr uint8_t LCD_POWER_BIT = (1U << 2U);
|
||||
// PM1_G3: Speaker amplifier enable on M5Stack StickS3
|
||||
static constexpr uint8_t SPEAKER_AMP_BIT = (1U << 3U);
|
||||
static constexpr uint8_t SPEAKER_AMP_FUNC_MASK = (0x3U << 6U); // GPIO3 function select bits
|
||||
|
||||
static constexpr TickType_t TIMEOUT = pdMS_TO_TICKS(50);
|
||||
|
||||
@@ -90,6 +93,13 @@ static error_t start(Device* device) {
|
||||
LOG_W(TAG, "Failed to disable I2C sleep (non-fatal)");
|
||||
}
|
||||
|
||||
// BOOST_EN → EXT_5V / Grove / Hat power rail always on
|
||||
if (i2c_controller_register8_set_bits(i2c, addr, REG_PWR_CFG, PWR_CFG_BOOST_EN, TIMEOUT) == ERROR_NONE) {
|
||||
LOG_I(TAG, "EXT_5V boost enabled");
|
||||
} else {
|
||||
LOG_W(TAG, "Failed to enable EXT_5V boost (non-fatal)");
|
||||
}
|
||||
|
||||
// PM1_G2 → LCD power enable (L3B rail on StickS3)
|
||||
// Sequence matches M5GFX: clear FUNC0 bit2, set MODE bit2 output, clear DRV bit2 push-pull, set OUT bit2 high
|
||||
bool lcd_ok =
|
||||
@@ -104,6 +114,18 @@ static error_t start(Device* device) {
|
||||
LOG_E(TAG, "Failed to enable LCD power via PM1_G2");
|
||||
}
|
||||
|
||||
// PM1_G3 → speaker amp EN, initially LOW (amp off until audio starts)
|
||||
bool spk_ok =
|
||||
i2c_controller_register8_reset_bits(i2c, addr, REG_GPIO_FUNC0, SPEAKER_AMP_FUNC_MASK, TIMEOUT) == ERROR_NONE &&
|
||||
i2c_controller_register8_set_bits (i2c, addr, REG_GPIO_MODE, SPEAKER_AMP_BIT, TIMEOUT) == ERROR_NONE &&
|
||||
i2c_controller_register8_reset_bits(i2c, addr, REG_GPIO_DRV, SPEAKER_AMP_BIT, TIMEOUT) == ERROR_NONE &&
|
||||
i2c_controller_register8_reset_bits(i2c, addr, REG_GPIO_OUT, SPEAKER_AMP_BIT, TIMEOUT) == ERROR_NONE;
|
||||
if (spk_ok) {
|
||||
LOG_I(TAG, "Speaker amp pin configured");
|
||||
} else {
|
||||
LOG_W(TAG, "Failed to configure speaker amp pin");
|
||||
}
|
||||
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
@@ -170,6 +192,16 @@ error_t m5pm1_set_ldo_enable(Device* device, bool enable) {
|
||||
}
|
||||
}
|
||||
|
||||
error_t m5pm1_set_speaker_enable(Device* device, bool enable) {
|
||||
Device* i2c = device_get_parent(device);
|
||||
const uint8_t addr = GET_CONFIG(device)->address;
|
||||
if (enable) {
|
||||
return i2c_controller_register8_set_bits(i2c, addr, REG_GPIO_OUT, SPEAKER_AMP_BIT, TIMEOUT);
|
||||
} else {
|
||||
return i2c_controller_register8_reset_bits(i2c, addr, REG_GPIO_OUT, SPEAKER_AMP_BIT, TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
error_t m5pm1_get_temperature(Device* device, uint16_t* decidegc) {
|
||||
Device* i2c = device_get_parent(device);
|
||||
uint8_t addr = GET_CONFIG(device)->address;
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake")
|
||||
|
||||
file(GLOB_RECURSE SOURCE_FILES "source/*.c*")
|
||||
|
||||
tactility_add_module(py32ioexpander-module
|
||||
SRCS ${SOURCE_FILES}
|
||||
INCLUDE_DIRS include/
|
||||
REQUIRES TactilityKernel
|
||||
)
|
||||
@@ -0,0 +1,5 @@
|
||||
description: M5Stack PY32 IO Expander (StackChan body module)
|
||||
|
||||
include: ["i2c-device.yaml"]
|
||||
|
||||
compatible: "m5stack,py32ioexpander"
|
||||
@@ -0,0 +1,3 @@
|
||||
dependencies:
|
||||
- TactilityKernel
|
||||
bindings: bindings
|
||||
@@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <tactility/bindings/bindings.h>
|
||||
#include <drivers/py32ioexpander.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
DEFINE_DEVICETREE(py32ioexpander, struct Py32IoExpanderConfig)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,34 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <tactility/error.h>
|
||||
|
||||
struct Device;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct Py32IoExpanderConfig {
|
||||
uint8_t address;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// GPIO (16 pins, index 0–15)
|
||||
// ---------------------------------------------------------------------------
|
||||
error_t py32_gpio_set_output(struct Device* device, uint8_t pin, bool value);
|
||||
error_t py32_gpio_get_input(struct Device* device, uint8_t pin, bool* value);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// NeoPixel LED ring (up to 32 WS2812C LEDs)
|
||||
// ---------------------------------------------------------------------------
|
||||
error_t py32_led_set_count(struct Device* device, uint8_t count);
|
||||
error_t py32_led_set_color(struct Device* device, uint8_t index, uint8_t r, uint8_t g, uint8_t b);
|
||||
error_t py32_led_refresh(struct Device* device);
|
||||
error_t py32_led_disable(struct Device* device);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,14 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <tactility/module.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern struct Module py32ioexpander_module;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,28 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <tactility/check.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
extern Driver py32ioexpander_driver;
|
||||
|
||||
static error_t start() {
|
||||
check(driver_construct_add(&py32ioexpander_driver) == ERROR_NONE);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t stop() {
|
||||
check(driver_remove_destruct(&py32ioexpander_driver) == ERROR_NONE);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
Module py32ioexpander_module = {
|
||||
.name = "py32ioexpander",
|
||||
.start = start,
|
||||
.stop = stop,
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
};
|
||||
|
||||
} // extern "C"
|
||||
@@ -0,0 +1,154 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Reference: https://github.com/m5stack/StackChan/tree/main/firmware/main/hal/drivers/PY32IOExpander_Class
|
||||
#include <drivers/py32ioexpander.h>
|
||||
#include <py32ioexpander_module.h>
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/drivers/i2c_controller.h>
|
||||
#include <tactility/log.h>
|
||||
|
||||
#define TAG "PY32IOExpander"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Register map
|
||||
// ---------------------------------------------------------------------------
|
||||
static constexpr uint8_t REG_UID_L = 0x00; ///< R - Unique ID low byte
|
||||
static constexpr uint8_t REG_VERSION = 0x02; ///< R - Firmware version
|
||||
static constexpr uint8_t REG_GPIO_MODE_L = 0x03; ///< RW - GPIO direction [15:0] (1=output, 0=input), little-endian
|
||||
static constexpr uint8_t REG_GPIO_OUT_L = 0x05; ///< RW - GPIO output level [15:0], little-endian
|
||||
static constexpr uint8_t REG_GPIO_IN_L = 0x07; ///< R - GPIO input state [15:0], little-endian
|
||||
static constexpr uint8_t REG_LED_CFG = 0x24; ///< RW - [6]=REFRESH [5:0]=LED_COUNT
|
||||
static constexpr uint8_t REG_LED_DATA = 0x30; ///< RW - RGB565 data, 2 bytes per LED (max 32)
|
||||
|
||||
static constexpr TickType_t TIMEOUT = pdMS_TO_TICKS(50);
|
||||
|
||||
#define GET_CONFIG(device) (static_cast<const Py32IoExpanderConfig*>((device)->config))
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Driver lifecycle
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static error_t start(Device* device) {
|
||||
Device* i2c = device_get_parent(device);
|
||||
if (device_get_type(i2c) != &I2C_CONTROLLER_TYPE) {
|
||||
LOG_E(TAG, "Parent is not an I2C controller");
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
const uint8_t addr = GET_CONFIG(device)->address;
|
||||
|
||||
// PY32 may need a moment after power-on. Retry with increasing delays.
|
||||
uint8_t version = 0;
|
||||
bool online = false;
|
||||
for (int attempt = 0; attempt < 5; attempt++) {
|
||||
if (i2c_controller_register8_get(i2c, addr, REG_VERSION, &version, TIMEOUT) == ERROR_NONE &&
|
||||
version != 0x00 && version != 0xFF) {
|
||||
online = true;
|
||||
break;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(20 * (attempt + 1)));
|
||||
}
|
||||
|
||||
if (!online) {
|
||||
LOG_E(TAG, "PY32IOExpander not responding at 0x%02X — LED ring will not work", addr);
|
||||
return ERROR_NONE; // non-fatal: don't crash the kernel
|
||||
}
|
||||
|
||||
LOG_I(TAG, "PY32IOExpander online (addr=0x%02X, fw=0x%02X)", addr, version);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t stop(Device* device) {
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public API
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
extern "C" {
|
||||
|
||||
error_t py32_gpio_set_output(Device* device, uint8_t pin, bool value) {
|
||||
if (pin > 15U) return ERROR_RESOURCE;
|
||||
Device* i2c = device_get_parent(device);
|
||||
const uint8_t addr = GET_CONFIG(device)->address;
|
||||
|
||||
// Ensure pin is in output mode
|
||||
uint16_t mode = 0;
|
||||
error_t err = i2c_controller_register16le_get(i2c, addr, REG_GPIO_MODE_L, &mode, TIMEOUT);
|
||||
if (err != ERROR_NONE) return err;
|
||||
mode |= static_cast<uint16_t>(1U << pin);
|
||||
err = i2c_controller_register16le_set(i2c, addr, REG_GPIO_MODE_L, mode, TIMEOUT);
|
||||
if (err != ERROR_NONE) return err;
|
||||
|
||||
// Set output level
|
||||
uint16_t out = 0;
|
||||
err = i2c_controller_register16le_get(i2c, addr, REG_GPIO_OUT_L, &out, TIMEOUT);
|
||||
if (err != ERROR_NONE) return err;
|
||||
if (value) {
|
||||
out |= static_cast<uint16_t>(1U << pin);
|
||||
} else {
|
||||
out &= static_cast<uint16_t>(~(1U << pin));
|
||||
}
|
||||
return i2c_controller_register16le_set(i2c, addr, REG_GPIO_OUT_L, out, TIMEOUT);
|
||||
}
|
||||
|
||||
error_t py32_gpio_get_input(Device* device, uint8_t pin, bool* value) {
|
||||
if (pin > 15U) return ERROR_RESOURCE;
|
||||
Device* i2c = device_get_parent(device);
|
||||
uint16_t in = 0;
|
||||
error_t err = i2c_controller_register16le_get(i2c, GET_CONFIG(device)->address, REG_GPIO_IN_L, &in, TIMEOUT);
|
||||
if (err != ERROR_NONE) return err;
|
||||
*value = (in >> pin) & 1U;
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
error_t py32_led_set_count(Device* device, uint8_t count) {
|
||||
if (count > 32U) return ERROR_RESOURCE;
|
||||
Device* i2c = device_get_parent(device);
|
||||
uint8_t cfg = count & 0x3FU;
|
||||
return i2c_controller_register8_set(i2c, GET_CONFIG(device)->address, REG_LED_CFG, cfg, TIMEOUT);
|
||||
}
|
||||
|
||||
error_t py32_led_set_color(Device* device, uint8_t index, uint8_t r, uint8_t g, uint8_t b) {
|
||||
if (index > 32U) return ERROR_RESOURCE;
|
||||
Device* i2c = device_get_parent(device);
|
||||
// RGB565: [15:11]=R5 [10:5]=G6 [4:0]=B5, stored little-endian
|
||||
uint16_t rgb565 = static_cast<uint16_t>(((r >> 3U) << 11U) | ((g >> 2U) << 5U) | (b >> 3U));
|
||||
uint8_t buf[2] = {static_cast<uint8_t>(rgb565 & 0xFFU), static_cast<uint8_t>(rgb565 >> 8U)};
|
||||
return i2c_controller_write_register(i2c, GET_CONFIG(device)->address, static_cast<uint8_t>(REG_LED_DATA + index * 2U), buf, 2, TIMEOUT);
|
||||
}
|
||||
|
||||
error_t py32_led_refresh(Device* device) {
|
||||
Device* i2c = device_get_parent(device);
|
||||
const uint8_t addr = GET_CONFIG(device)->address;
|
||||
// Read current LED_CFG (contains LED count), then set the REFRESH bit (6)
|
||||
uint8_t cfg = 0;
|
||||
error_t err = i2c_controller_register8_get(i2c, addr, REG_LED_CFG, &cfg, TIMEOUT);
|
||||
if (err != ERROR_NONE) return err;
|
||||
return i2c_controller_register8_set(i2c, addr, REG_LED_CFG, cfg | 0x40U, TIMEOUT);
|
||||
}
|
||||
|
||||
error_t py32_led_disable(Device* device) {
|
||||
// Write black to all 32 possible LED slots then refresh with count=32
|
||||
// Using the maximum count ensures no stale color data is displayed.
|
||||
static constexpr uint8_t BLACK[64] = {}; // 32 LEDs × 2 bytes, all zero
|
||||
Device* i2c = device_get_parent(device);
|
||||
uint8_t addr = GET_CONFIG(device)->address;
|
||||
error_t err = i2c_controller_write_register(i2c, addr, REG_LED_DATA, BLACK, sizeof(BLACK), TIMEOUT);
|
||||
if (err != ERROR_NONE) return err;
|
||||
// count=32 | REFRESH
|
||||
return i2c_controller_register8_set(i2c, addr, REG_LED_CFG, 0x60U, TIMEOUT);
|
||||
}
|
||||
|
||||
Driver py32ioexpander_driver = {
|
||||
.name = "py32ioexpander",
|
||||
.compatible = (const char*[]) {"m5stack,py32ioexpander", nullptr},
|
||||
.start_device = start,
|
||||
.stop_device = stop,
|
||||
.api = nullptr,
|
||||
.device_type = nullptr,
|
||||
.owner = &py32ioexpander_module,
|
||||
.internal = nullptr
|
||||
};
|
||||
|
||||
} // extern "C"
|
||||
Reference in New Issue
Block a user