diff --git a/DRV2605.h b/DRV2605.h new file mode 100644 index 0000000..67e2a03 --- /dev/null +++ b/DRV2605.h @@ -0,0 +1,136 @@ +// DRV2605 Haptic Driver — Minimal I2C driver for T-Watch Ultra +// ERM vibration motor with 117 built-in effects +// EN pin controlled by XL9555 EXPANDS_DRV_EN (port 0, pin 6) + +#ifndef DRV2605_H +#define DRV2605_H + +#if BOARD_MODEL == BOARD_TWATCH_ULT + +#include + +#define DRV2605_ADDR 0x5A + +// Registers +#define DRV2605_STATUS 0x00 +#define DRV2605_MODE 0x01 +#define DRV2605_RTPIN 0x02 +#define DRV2605_LIBRARY 0x03 +#define DRV2605_WAVESEQ1 0x04 +#define DRV2605_GO 0x0C +#define DRV2605_OVERDRIVE 0x0D +#define DRV2605_SUSTAINPOS 0x0E +#define DRV2605_SUSTAINNEG 0x0F +#define DRV2605_BRAKE 0x10 +#define DRV2605_FEEDBACK 0x1A +#define DRV2605_CONTROL3 0x1D + +// Named effects for watch use cases (ERM Library 1, effects 1-117) +#define HAPTIC_STRONG_CLICK 1 // Strong Click - 100% +#define HAPTIC_MEDIUM_CLICK 2 // Strong Click - 60% +#define HAPTIC_LIGHT_CLICK 3 // Strong Click - 30% +#define HAPTIC_SHARP_CLICK 4 // Sharp Click - 100% +#define HAPTIC_SOFT_BUMP 7 // Soft Bump - 100% +#define HAPTIC_DOUBLE_CLICK 10 // Double Click - 100% +#define HAPTIC_TRIPLE_CLICK 12 // Triple Click - 100% +#define HAPTIC_BUZZ 14 // Strong Buzz - 100% +#define HAPTIC_ALERT 15 // 750ms Alert - 100% +#define HAPTIC_LONG_ALERT 16 // 1000ms Alert - 100% +#define HAPTIC_TICK 4 // Sharp Click (subtle tick) +#define HAPTIC_TRANSITION 47 // Transition Click - 100% + +static bool drv2605_ready = false; + +static void drv2605_write(uint8_t reg, uint8_t val) { + Wire.beginTransmission(DRV2605_ADDR); + Wire.write(reg); + Wire.write(val); + Wire.endTransmission(); +} + +static uint8_t drv2605_read(uint8_t reg) { + Wire.beginTransmission(DRV2605_ADDR); + Wire.write(reg); + Wire.endTransmission(false); + Wire.requestFrom((uint8_t)DRV2605_ADDR, (uint8_t)1); + return Wire.available() ? Wire.read() : 0; +} + +bool drv2605_init() { + // Probe device + Wire.beginTransmission(DRV2605_ADDR); + if (Wire.endTransmission() != 0) return false; + + // Verify chip ID (bits 7:5 of STATUS should be 3 or 7) + uint8_t id = drv2605_read(DRV2605_STATUS) >> 5; + if (id != 3 && id != 7) return false; + + // Exit standby + drv2605_write(DRV2605_MODE, 0x00); + + // Disable real-time playback input + drv2605_write(DRV2605_RTPIN, 0x00); + + // Select ERM mode: clear bit 7 of FEEDBACK register + uint8_t fb = drv2605_read(DRV2605_FEEDBACK); + drv2605_write(DRV2605_FEEDBACK, fb & 0x7F); + + // Enable open-loop drive: set bit 5 of CONTROL3 + uint8_t ctrl3 = drv2605_read(DRV2605_CONTROL3); + drv2605_write(DRV2605_CONTROL3, ctrl3 | 0x20); + + // Select ERM effect library 1 + drv2605_write(DRV2605_LIBRARY, 1); + + // Clear timing offsets + drv2605_write(DRV2605_OVERDRIVE, 0); + drv2605_write(DRV2605_SUSTAINPOS, 0); + drv2605_write(DRV2605_SUSTAINNEG, 0); + drv2605_write(DRV2605_BRAKE, 0); + + // Clear all waveform slots + for (uint8_t i = 0; i < 8; i++) { + drv2605_write(DRV2605_WAVESEQ1 + i, 0); + } + + drv2605_ready = true; + return true; +} + +// Play a single effect (1-117 from ERM library) +void drv2605_play(uint8_t effect) { + if (!drv2605_ready) return; + drv2605_write(DRV2605_MODE, 0x00); // Internal trigger mode + drv2605_write(DRV2605_WAVESEQ1, effect); // Effect in slot 1 + drv2605_write(DRV2605_WAVESEQ1 + 1, 0); // End sequence + drv2605_write(DRV2605_GO, 1); // Start playback +} + +// Play a sequence of up to 8 effects +void drv2605_sequence(const uint8_t *effects, uint8_t count) { + if (!drv2605_ready || count == 0) return; + if (count > 8) count = 8; + drv2605_write(DRV2605_MODE, 0x00); + for (uint8_t i = 0; i < count; i++) { + drv2605_write(DRV2605_WAVESEQ1 + i, effects[i]); + } + if (count < 8) { + drv2605_write(DRV2605_WAVESEQ1 + count, 0); // Terminate + } + drv2605_write(DRV2605_GO, 1); +} + +// Stop any playing effect +void drv2605_stop() { + if (!drv2605_ready) return; + drv2605_write(DRV2605_GO, 0); +} + +// Check if an effect is still playing +bool drv2605_busy() { + if (!drv2605_ready) return false; + return drv2605_read(DRV2605_GO) & 1; +} + +#endif // BOARD_MODEL == BOARD_TWATCH_ULT +#endif // DRV2605_H diff --git a/Makefile b/Makefile index 9938f99..a8f6f90 100644 --- a/Makefile +++ b/Makefile @@ -121,11 +121,13 @@ firmware-twatch_ultra: arduino-cli compile --log --fqbn "esp32:esp32:esp32s3:CDCOnBoot=cdc" -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=-DBOARD_MODEL=0x45" upload-twatch_ultra: firmware-twatch_ultra - @echo "Flashing T-Watch Ultra via OpenOCD JTAG..." + @echo "Flashing T-Watch Ultra app via JTAG (no BOOT+RST needed)..." $(HOME)/.arduino15/packages/esp32/tools/openocd-esp32/v0.12.0-esp32-20230921/bin/openocd \ -s $(HOME)/.arduino15/packages/esp32/tools/openocd-esp32/v0.12.0-esp32-20230921/share/openocd/scripts \ -f board/esp32s3-builtin.cfg \ -c "program_esp build/esp32.esp32.esp32s3/RNode_Firmware.ino.bin 0x10000 verify reset exit" + @sleep 5 + rnodeconf /dev/ttyACM4 --firmware-hash $$(./partition_hashes ./build/esp32.esp32.esp32s3/RNode_Firmware.ino.bin) firmware-lora32_v10: check_bt_buffers arduino-cli compile --log --fqbn esp32:esp32:ttgo-lora32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x39\"" diff --git a/RNode_Firmware.ino b/RNode_Firmware.ino index 2d729f0..e0cf049 100644 --- a/RNode_Firmware.ino +++ b/RNode_Firmware.ino @@ -20,6 +20,7 @@ #if BOARD_MODEL == BOARD_TWATCH_ULT #include "XL9555.h" #include "CO5300.h" + #include "DRV2605.h" #endif #define CHANNEL_FIFO_SIZE (CONFIG_UART_BUFFER_SIZE / NUM_CHANNELS) @@ -274,6 +275,11 @@ void setup() { #if BOARD_MODEL == BOARD_TWATCH_ULT xl9555_init(); xl9555_enable_lora_antenna(); + xl9555_set(EXPANDS_DRV_EN, true); // Enable haptic motor driver + xl9555_set(EXPANDS_DISP_EN, true); // Enable display power gate + delay(10); + drv2605_init(); + if (drv2605_ready) drv2605_play(HAPTIC_SHARP_CLICK); // Boot feedback // Beacon timer wakeup: if we woke from deep sleep via timer, // take the fast path — init GPS/LoRa only, transmit, sleep again. @@ -1999,6 +2005,12 @@ void loop() { // Safely shuts down peripherals and enters ESP32 deep sleep. // Does not return — device reboots on wake. void twatch_enter_deep_sleep(bool beacon_timer) { + // 0. Haptic feedback before sleep + if (drv2605_ready) { + drv2605_play(HAPTIC_SOFT_BUMP); + delay(150); // Let the motor spin briefly before powering down + } + // 1. Put display controller into sleep mode (must happen before SPI.end) #if HAS_DISPLAY co5300_sleep();