From df899adc36e896cffff77916c61a1b57b798d64f Mon Sep 17 00:00:00 2001
From: ZioFabry <32312585+ZioFabry@users.noreply.github.com>
Date: Tue, 6 Jun 2023 17:04:15 +0200
Subject: [PATCH 1/2] PCA9685 v2 driver
---
.../tasmota_xdrv_driver/xdrv_15_pca9685.ino | 614 ++++++++++++++----
1 file changed, 497 insertions(+), 117 deletions(-)
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_15_pca9685.ino b/tasmota/tasmota_xdrv_driver/xdrv_15_pca9685.ino
index 8f82432c2c1d..10f3e8cbbee2 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_15_pca9685.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_15_pca9685.ino
@@ -2,6 +2,7 @@
xdrv_15_pca9685.ino - Support for I2C PCA9685 12bit 16 pin hardware PWM driver on Tasmota
Copyright (C) 2021 Andre Thomas and Theo Arends
+ 2023 Fabrizio Amodio)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -17,6 +18,55 @@
along with this program. If not, see .
*/
+/*
+ 2023-06-05 v2.0 Changelog by F.Amodio
+ - Code Refactoring
+
+ - Support for multiple PCA9685 without breaking support for previuos driver version
+ all command (PWM,INVERT,etc) now support the suffix from 0 to PCA9685_MAX_COUNT to address the board,
+ without the suffix the command is relative to the board 0, e.g.
+ DRIVER15 PWM 0 350 Board address 0 pin 0 value 350
+ DRIVER15 PWM0 2 300 Board address 0 pin 2 value 300
+ DRIVER15 PWM1 3 235 Board address 1 pin 3 value 235
+ DRIVER15 RESET2 Reset Board 2
+
+ - new command INTCLK to fine tuning the internal clock setting, unit: 0.1 MHz
+ this is a not permanent setting!
+ DRIVER15 INTCLK 270 Set to 27.0 MHz
+ DRIVER15 INTCLK 250 Set to 25.0 Mhz (default value, use USE_PCA9685_INT_CLOCK to change it at compile time)
+
+ - new command PWMTO to move all the required pin from the current pin position to a new one, all the move will be completed into the required time, so each motor will be stepped relative to timing
+
+ - new command PWMSTOP to stop all running movement
+
+ Updated Command List:
+
+ DRIVER15 STATUS // Will return a JSON string containing all the current settings / parameters for all board
+ DRIVER15 RESET[0-8] // Reset to power-up settings - i.e. F=50hz and all pins in OFF state for a specific board
+ DRIVER15 INVERT[0-8],pin[,0-1] // print or set the inversion bit on pin of the specific board
+ DRIVER15 INTCLK[0-8],clock // where clock is the Interal Clock value in 1/10 MHz (default USE_PCA9685_INT_CLOCK = 250)
+ DRIVER15 PMWF[0-8],frequency // where frequency is the PWM frequency from 24 to 1526 in Hz
+ DRIVER15 PWM[0-8],pin,pwmvalue // where pin=LED, pin 0 through 15 and pwmvalue is the pulse width between 0 and 4096
+ DRIVER15 PWM[0-8],pin,ON // Fully turn a specific board/pin/LED ON
+ DRIVER15 PWM[0-8],pin,OFF // Fully turn a specific board/pin/LED OFF
+
+ DRIVER15 PWMTO[0-8] seconds,pin,value[,pin,value[,pin,value...]] // Move all the specified pin to a new location in the specified time
+
+ e.g.
+ PWMTO 4 0 327 1 550 2 187 3 200
+ this move the PIN0 of the Board 0 from the current position to 327
+ the PIN1 of the Board 0 from the current position to 550
+ the PIN2 of the Board 0 from the current position to 187
+ the PIN3 of the Board 0 from the current position to 200
+
+ all the movements will be completed in 4 seconds, every PIN will be stepped relative to that @ 50ms step.
+
+ PWMTO1 4 0 327 1 550 2 187 3 200
+ same logic on the board #1
+
+ DRIVER15 PWMSTOP[0-8] // stop all the moment on the relative board
+*/
+
#ifdef USE_I2C
#ifdef USE_PCA9685
/*********************************************************************************************\
@@ -25,98 +75,249 @@
* I2C Address: 0x40 .. 0x47
\*********************************************************************************************/
-#define XDRV_15 15
-#define XI2C_01 1 // See I2CDEVICES.md
+#define XDRV_15 15
+#define XI2C_01 1 // See I2CDEVICES.md
+
+/*
+ default prescale value from datasheet 7.3.5
-#define PCA9685_REG_MODE1 0x00
-#define PCA9685_REG_LED0_ON_L 0x06
-#define PCA9685_REG_PRE_SCALE 0xFE
+ round(25000000/(4096*freq))-1;
+*/
+#ifndef USE_PCA9685_INT_CLOCK
+#define USE_PCA9685_INT_CLOCK 250
+#endif
#ifndef USE_PCA9685_ADDR
- #define USE_PCA9685_ADDR 0x40
+#define USE_PCA9685_ADDR 0x40
#endif
+
#ifndef USE_PCA9685_FREQ
- #define USE_PCA9685_FREQ 50
+#define USE_PCA9685_FREQ 50
+#endif
+
+#ifndef PCA9685_MAX_COUNT
+#define PCA9685_MAX_COUNT 4
#endif
-bool pca9685_inverted = false; // invert PWM for open-collector load
-bool pca9685_detected = false;
-uint16_t pca9685_freq = USE_PCA9685_FREQ;
-uint16_t pca9685_pin_pwm_value[16];
+#define PCA9685_REG_MODE1 0x00
+#define PCA9685_REG_LED0_ON_L 0x06
+#define PCA9685_REG_PRE_SCALE 0xFE
+
+typedef struct
+{
+ uint16_t pwm;
+ bool running;
+ uint16_t step;
+ uint16_t every;
+ uint16_t target;
+ int16_t direction; // 1 == UP , 0 == stop; -1 == down
+} tMotor;
+
+struct PCA9685
+{
+ uint8_t count;
+ char name[10];
+ bool inverted[PCA9685_MAX_COUNT];
+ bool detected[PCA9685_MAX_COUNT];
+ uint16_t intclk[PCA9685_MAX_COUNT];
+ uint16_t freq[PCA9685_MAX_COUNT];
+ tMotor motor[PCA9685_MAX_COUNT][16];
+} pca9685;
+
+#include
+
+Ticker TickerPCA9685;
+
+void PCA9685_SetName(uint8_t pca)
+{
+ if (pca9685.count > 1)
+ {
+ pca9685.name[7] = '_';
+ pca9685.name[8] = '0' + pca;
+ pca9685.name[9] = 0;
+ }
+ else
+ {
+ pca9685.name[7] = 0;
+ }
+}
void PCA9685_Detect(void)
{
- if (!I2cSetDevice(USE_PCA9685_ADDR)) { return; }
-
- uint8_t buffer;
- if (I2cValidRead8(&buffer, USE_PCA9685_ADDR, PCA9685_REG_MODE1)) {
- I2cWrite8(USE_PCA9685_ADDR, PCA9685_REG_MODE1, 0x20);
- if (I2cValidRead8(&buffer, USE_PCA9685_ADDR, PCA9685_REG_MODE1)) {
- if (0x20 == buffer) {
- pca9685_detected = true;
- I2cSetActiveFound(USE_PCA9685_ADDR, "PCA9685");
- PCA9685_Reset(); // Reset the controller
+ memset(&pca9685, 0x0, sizeof(PCA9685));
+
+ strcpy_P(pca9685.name, PSTR("PCA9685"));
+
+ for (uint8_t dev = 0; dev < PCA9685_MAX_COUNT; dev++)
+ {
+ uint32_t addr = USE_PCA9685_ADDR + dev;
+
+ if (!I2cSetDevice(addr))
+ {
+ continue;
+ }
+
+ pca9685.freq[dev] = USE_PCA9685_FREQ;
+ pca9685.intclk[dev] = USE_PCA9685_INT_CLOCK;
+
+ uint8_t buffer;
+ if (I2cValidRead8(&buffer, addr, PCA9685_REG_MODE1))
+ {
+ I2cWrite8(addr, PCA9685_REG_MODE1, 0x20);
+ if (I2cValidRead8(&buffer, addr, PCA9685_REG_MODE1))
+ {
+ if (0x20 == buffer)
+ {
+ // AddLog(LOG_LEVEL_DEBUG, "PCA9685[%02x] found", addr);
+ pca9685.count++;
+ pca9685.detected[dev] = true;
+ I2cSetActiveFound(addr, PSTR("PCA9685"));
+ PCA9685_Reset(dev); // Reset the controller
+ }
}
}
}
+
+ if (pca9685.count > 0)
+ {
+ TickerPCA9685.attach_ms(50, PCA9685_RunMotor);
+ }
}
-void PCA9685_Reset(void)
+void PCA9685_Reset(uint8_t pca)
{
- I2cWrite8(USE_PCA9685_ADDR, PCA9685_REG_MODE1, 0x80);
- PCA9685_SetPWMfreq(USE_PCA9685_FREQ);
- pca9685_inverted = false;
- for (uint32_t pin=0;pin<16;pin++) {
- PCA9685_SetPWM(pin,0,pca9685_inverted);
- pca9685_pin_pwm_value[pin] = PCA9685_GetPWMvalue(0, pca9685_inverted);
- }
- Response_P(PSTR("{\"PCA9685\":{\"RESET\":\"OK\"}}"));
+ if (!pca9685.detected[pca])
+ {
+ return;
+ }
+
+ PCA9685_SetName(pca);
+
+ I2cWrite8(USE_PCA9685_ADDR + pca, PCA9685_REG_MODE1, 0x80);
+ PCA9685_SetPWMfreq(pca, USE_PCA9685_FREQ);
+ pca9685.inverted[pca] = false;
+ for (uint32_t pin = 0; pin < 16; pin++)
+ {
+ PCA9685_SetPWM(pca, pin, 0, pca9685.inverted[pca]);
+ pca9685.motor[pca][pin].pwm = PCA9685_GetPWMvalue(0, pca9685.inverted[pca]);
+ }
+ Response_P(PSTR("{\"%s\":"), pca9685.name);
+ ResponseAppend_P(S_JSON_COMMAND_SVALUE, D_CMND_RESET, PSTR("OK"));
+ ResponseJsonEnd();
}
-uint16_t PCA9685_GetPWMvalue(uint16_t pwm, bool inverted) {
+uint16_t PCA9685_GetPWMvalue(uint16_t pwm, bool inverted)
+{
uint16_t pwm_val = pwm;
- if (inverted) {
- pwm_val = 4096-pwm;
+ if (inverted)
+ {
+ pwm_val = 4096 - pwm;
}
return pwm_val;
}
-void PCA9685_SetPWMfreq(double freq) {
-/*
- 7.3.5 from datasheet
- prescale value = round(25000000/(4096*freq))-1;
- */
- if (freq > 23 && freq < 1527) {
- pca9685_freq=freq;
- } else {
- pca9685_freq=50;
- }
- uint8_t pre_scale_osc = round(25000000/(4096*pca9685_freq))-1;
- if (1526 == pca9685_freq) pre_scale_osc=0xFF; // force setting for 24hz because rounding causes 1526 to be 254
- uint8_t current_mode1 = I2cRead8(USE_PCA9685_ADDR, PCA9685_REG_MODE1); // read current value of MODE1 register
- uint8_t sleep_mode1 = (current_mode1&0x7F) | 0x10; // Determine register value to put PCA to sleep
- I2cWrite8(USE_PCA9685_ADDR, PCA9685_REG_MODE1, sleep_mode1); // Let's sleep a little
- I2cWrite8(USE_PCA9685_ADDR, PCA9685_REG_PRE_SCALE, pre_scale_osc); // Set the pre-scaler
- I2cWrite8(USE_PCA9685_ADDR, PCA9685_REG_MODE1, current_mode1 | 0xA0); // Reset MODE1 register to original state and enable auto increment
+void PCA9685_SetPWMfreq(uint8_t pca, double freq)
+{
+ if (freq > 23 && freq < 1527)
+ {
+ pca9685.freq[pca] = freq;
+ }
+ else
+ {
+ pca9685.freq[pca] = 50;
+ }
+ uint8_t pre_scale_osc = round((pca9685.intclk[pca] * 100000) / (4096 * pca9685.freq[pca])) - 1;
+ if (1526 == pca9685.freq[pca])
+ pre_scale_osc = 0xFF; // force setting for 24hz because rounding causes 1526 to be 254
+ uint8_t current_mode1 = I2cRead8(USE_PCA9685_ADDR + pca, PCA9685_REG_MODE1); // read current value of MODE1 register
+ uint8_t sleep_mode1 = (current_mode1 & 0x7F) | 0x10; // Determine register value to put PCA to sleep
+ I2cWrite8(USE_PCA9685_ADDR + pca, PCA9685_REG_MODE1, sleep_mode1); // Let's sleep a little
+ I2cWrite8(USE_PCA9685_ADDR + pca, PCA9685_REG_PRE_SCALE, pre_scale_osc); // Set the pre-scaler
+ I2cWrite8(USE_PCA9685_ADDR + pca, PCA9685_REG_MODE1, current_mode1 | 0xA0); // Reset MODE1 register to original state and enable auto increment
}
-void PCA9685_SetPWM_Reg(uint8_t pin, uint16_t on, uint16_t off) {
+void PCA9685_SetPWM_Reg(uint8_t pca, uint8_t pin, uint16_t on, uint16_t off)
+{
uint8_t led_reg = PCA9685_REG_LED0_ON_L + 4 * pin;
uint32_t led_data = 0;
- I2cWrite8(USE_PCA9685_ADDR, led_reg, on);
- I2cWrite8(USE_PCA9685_ADDR, led_reg+1, (on >> 8));
- I2cWrite8(USE_PCA9685_ADDR, led_reg+2, off);
- I2cWrite8(USE_PCA9685_ADDR, led_reg+3, (off >> 8));
+ I2cWrite8(USE_PCA9685_ADDR + pca, led_reg, on);
+ I2cWrite8(USE_PCA9685_ADDR + pca, led_reg + 1, (on >> 8));
+ I2cWrite8(USE_PCA9685_ADDR + pca, led_reg + 2, off);
+ I2cWrite8(USE_PCA9685_ADDR + pca, led_reg + 3, (off >> 8));
}
-void PCA9685_SetPWM(uint8_t pin, uint16_t pwm, bool inverted) {
+void PCA9685_SetPWM(uint8_t pca, uint8_t pin, uint16_t pwm, bool inverted)
+{
uint16_t pwm_val = PCA9685_GetPWMvalue(pwm, inverted);
- if (4096 == pwm_val) {
- PCA9685_SetPWM_Reg(pin, 4096, 0); // Special use additional bit causes channel to turn on completely without PWM
- } else {
- PCA9685_SetPWM_Reg(pin, 0, pwm_val);
+ if (4096 == pwm_val)
+ {
+ PCA9685_SetPWM_Reg(pca, pin, 4096, 0); // Special use additional bit causes channel to turn on completely without PWM
+ }
+ else
+ {
+ PCA9685_SetPWM_Reg(pca, pin, 0, pwm_val);
+ }
+ pca9685.motor[pca][pin].pwm = pwm_val;
+}
+
+void PCA9685_RunMotor()
+{
+ for (uint8_t dev = 0; dev < PCA9685_MAX_COUNT; dev++)
+ {
+ if (!pca9685.detected[dev])
+ continue;
+
+ for (uint8_t pin = 0; pin < 15; pin++)
+ {
+ tMotor *m = &(pca9685.motor[dev][pin]);
+
+ if (!m->running)
+ continue;
+
+ if ((m->direction > 0 && m->pwm >= m->target) || (m->direction < 0 && m->pwm <= m->target))
+ {
+ m->running = false;
+ if (m->pwm != m->target)
+ {
+ PCA9685_SetPWM(dev, pin, m->target, pca9685.inverted[dev]);
+ }
+ continue;
+ }
+
+ if (m->step == 0 || (m->step % m->every == 0))
+ {
+ // AddLog(LOG_LEVEL_DEBUG, "PCA9685: MOTOR dev=%u pin=%u s=%u e=%u pwm=%lu target=%lu dir=%d",
+ // dev,
+ // pin,
+ // m->step,
+ // m->every,
+ // m->pwm,
+ // m->target,
+ // m->direction);
+
+ PCA9685_SetPWM(dev, pin, m->pwm + m->direction, pca9685.inverted[dev]);
+ }
+
+ m->step++;
+ }
+ }
+}
+
+void PCA9685_getCmdSuffix(char *command, uint8_t *suffixNumber)
+{
+ size_t commandLength = strlen(command);
+ uint8_t result = 0;
+ *suffixNumber = 0;
+
+ if (isdigit(command[commandLength - 1]))
+ {
+ result = command[commandLength - 1] - '0';
+ if (result >= 0 && result <= 7)
+ {
+ *suffixNumber = result;
+ command[commandLength - 1] = '\0';
+ }
}
- pca9685_pin_pwm_value[pin] = pwm_val;
}
bool PCA9685_Command(void)
@@ -124,66 +325,217 @@ bool PCA9685_Command(void)
bool serviced = true;
bool validpin = false;
uint8_t paramcount = 0;
- if (XdrvMailbox.data_len > 0) {
- paramcount=1;
- } else {
+
+ if (XdrvMailbox.data_len > 0)
+ {
+ paramcount = 1;
+ }
+ else
+ {
serviced = false;
return serviced;
}
char argument[XdrvMailbox.data_len];
- for (uint32_t ca=0;ca 1) {
- pca9685_inverted = (1 == atoi(ArgV(argument, 2)));
- Response_P(PSTR("{\"PCA9685\":{\"INVERT\":%i, \"Result\":\"OK\"}}"), pca9685_inverted?1:0);
+ if (!strcmp(command, "RESET"))
+ {
+ PCA9685_Reset(dev);
+ return serviced;
+ }
+
+ if (!strcmp(command, "STATUS"))
+ {
+ PCA9685_OutputTelemetry(false);
+ return serviced;
+ }
+
+ PCA9685_SetName(dev);
+
+ if (!strcmp(command, "INVERT"))
+ {
+ if (paramcount > 1)
+ {
+ pca9685.inverted[dev] = (1 == atoi(ArgV(argument, 2)));
+ Response_P(PSTR("{\"%s\":{\"INVERT\":%i, \"Result\":\"OK\"}}"), pca9685.name, pca9685.inverted[dev] ? 1 : 0);
return serviced;
- } else { // No parameter was given for invert, so we return current setting
- Response_P(PSTR("{\"PCA9685\":{\"INVERT\":%i}}"), pca9685_inverted?1:0);
+ }
+ else
+ { // No parameter was given for invert, so we return current setting
+ Response_P(PSTR("{\"%s\":{\"INVERT\":%i}}"), pca9685.name, pca9685.inverted[dev] ? 1 : 0);
return serviced;
}
}
- if (!strcmp(ArgV(argument, 1),"PWMF")) {
- if (paramcount > 1) {
+
+ if (!strcmp(command, "INTCLK"))
+ {
+ if (paramcount > 1)
+ {
+ pca9685.intclk[dev] = atoi(ArgV(argument, 2));
+ Response_P(PSTR("{\"%s\":{\"INTCLK\":%lu, \"Result\":\"OK\"}}"), pca9685.name, pca9685.intclk[dev]);
+ return serviced;
+ }
+ else
+ {
+ Response_P(PSTR("{\"%s\":{\"INTCLK\":%lu}}"), pca9685.name, pca9685.intclk[dev]);
+ return serviced;
+ }
+ }
+
+ /*
+ PWMTO timeinsec,pin,target[[,pin,target]...]
+ */
+ if (!strcmp(command, "PWMTO"))
+ {
+ uint8_t paramFrom = 1;
+ while (true)
+ {
+ if (paramcount > (2 + paramFrom))
+ {
+ uint16_t tis = atoi(ArgV(argument, 2)); // time in second to complete all the motors move
+
+ uint16_t pin = atoi(ArgV(argument, 2 + paramFrom));
+ /*
+ Sanity check - To be refactored
+ */
+ if (pin > 15)
+ pin = 15;
+ if (tis < 1)
+ tis = 1; // min 1 seconds to complete all the moves
+ if (tis > 60)
+ tis = 60; // max 60 seconds to complete all the moves
+
+ tMotor *m = &pca9685.motor[dev][pin];
+ m->target = atoi(ArgV(argument, 2 + paramFrom + 1));
+ if (m->target != m->pwm)
+ {
+ m->step = 0;
+ m->direction = m->target < m->pwm ? -1 : 1;
+
+ // AddLog(LOG_LEVEL_DEBUG, "PCA9685: PWMTO dev=%u pin=%u tis=%u e=? pwm=%lu target=%lu dir=%d",
+ // dev,
+ // pin,
+ // tis,
+ // m->pwm,
+ // m->target,
+ // m->direction);
+
+ m->every = 0;
+ while (m->every < 1)
+ {
+ uint16_t stepValue = abs((int16_t)m->pwm - (int16_t)m->target) / abs(m->direction);
+ if (stepValue < 1)
+ {
+ m->direction += m->target < m->pwm ? -1 : 1;
+ continue;
+ }
+
+ m->every = round((tis * 20) / stepValue);
+ if (m->every < 1)
+ {
+ m->direction += m->target < m->pwm ? -1 : 1;
+ continue;
+ }
+ }
+
+ m->running = true;
+ }
+ else
+ {
+ m->running = false;
+ }
+
+ paramFrom += 2;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ Response_P(PSTR("{\"%s\":{\"PWMTO\":\"OK\"}}"), pca9685.name);
+ return serviced;
+ }
+
+ if (!strcmp(command, "PWMSTOP"))
+ {
+ if (pca9685.detected[dev])
+ {
+ for (uint8_t pin = 0; pin < 15; pin++)
+ {
+ pca9685.motor[dev][pin].running = false;
+ }
+
+ Response_P(PSTR("{\"%s\":{\"PWMSTOP\":\"OK\"}}"), pca9685.name);
+ return serviced;
+ }
+ }
+
+ if (!strcmp(command, "PWMF"))
+ {
+ if (paramcount > 1)
+ {
uint16_t new_freq = atoi(ArgV(argument, 2));
- if ((new_freq >= 24) && (new_freq <= 1526)) {
- PCA9685_SetPWMfreq(new_freq);
- Response_P(PSTR("{\"PCA9685\":{\"PWMF\":%i, \"Result\":\"OK\"}}"),new_freq);
+ if ((new_freq >= 24) && (new_freq <= 1526))
+ {
+ PCA9685_SetPWMfreq(dev, new_freq);
+ Response_P(PSTR("{\"%s\":{\"PWMF\":%i, \"Result\":\"OK\"}}"), pca9685.name, new_freq);
return serviced;
}
- } else { // No parameter was given for setfreq, so we return current setting
- Response_P(PSTR("{\"PCA9685\":{\"PWMF\":%i}}"),pca9685_freq);
+ }
+ else
+ { // No parameter was given for setfreq, so we return current setting
+ Response_P(PSTR("{\"%s\":{\"PWMF\":%i}}"), pca9685.name, pca9685.freq[dev]);
return serviced;
}
}
- if (!strcmp(ArgV(argument, 1),"PWM")) {
- if (paramcount > 1) {
+
+ if (!strcmp(command, "PWM"))
+ {
+ if (paramcount > 1)
+ {
uint8_t pin = atoi(ArgV(argument, 2));
- if (paramcount > 2) {
- if (!strcmp(ArgV(argument, 3), "ON")) {
- PCA9685_SetPWM(pin, 4096, pca9685_inverted);
- Response_P(PSTR("{\"PCA9685\":{\"PIN\":%i,\"PWM\":%i}}"),pin,4096);
+ if (paramcount > 2)
+ {
+ // force motor stop
+ pca9685.motor[dev][pin].running = false;
+
+ if (!strcmp(ArgV(argument, 3), "ON"))
+ {
+ PCA9685_SetPWM(dev, pin, 4096, pca9685.inverted[dev]);
+ Response_P(PSTR("{\"%s\":{\"PIN\":%i,\"PWM\":%i}}"), pca9685.name, pin, 4096);
serviced = true;
return serviced;
}
- if (!strcmp(ArgV(argument, 3), "OFF")) {
- PCA9685_SetPWM(pin, 0, pca9685_inverted);
- Response_P(PSTR("{\"PCA9685\":{\"PIN\":%i,\"PWM\":%i}}"),pin,0);
+ if (!strcmp(ArgV(argument, 3), "OFF"))
+ {
+ PCA9685_SetPWM(dev, pin, 0, pca9685.inverted[dev]);
+ Response_P(PSTR("{\"%s\":{\"PIN\":%i,\"PWM\":%i}}"), pca9685.name, pin, 0);
serviced = true;
return serviced;
}
uint16_t pwm = atoi(ArgV(argument, 3));
- if ((pin >= 0 && pin <= 15 || pin==61) && (pwm >= 0 && pwm <= 4096)) {
- PCA9685_SetPWM(pin, pwm, pca9685_inverted);
- Response_P(PSTR("{\"PCA9685\":{\"PIN\":%i,\"PWM\":%i}}"),pin,pwm);
+ if ((pin >= 0 && pin <= 15 || pin == 61) && (pwm >= 0 && pwm <= 4096))
+ {
+ PCA9685_SetPWM(dev, pin, pwm, pca9685.inverted[dev]);
+ Response_P(PSTR("{\"%s\":{\"PIN\":%i,\"PWM\":%i}}"), pca9685.name, pin, pwm);
serviced = true;
return serviced;
}
@@ -195,39 +547,67 @@ bool PCA9685_Command(void)
void PCA9685_OutputTelemetry(bool telemetry)
{
- ResponseTime_P(PSTR(",\"PCA9685\":{\"PWM_FREQ\":%i,"),pca9685_freq);
- ResponseAppend_P(PSTR("\"INVERT\":%i,"), pca9685_inverted?1:0);
- for (uint32_t pin=0;pin<16;pin++) {
- uint16_t pwm_val = PCA9685_GetPWMvalue(pca9685_pin_pwm_value[pin], pca9685_inverted); // return logical (possibly inverted) pwm value
- ResponseAppend_P(PSTR("\"PWM%i\":%i,"),pin,pwm_val);
- }
- ResponseAppend_P(PSTR("\"END\":1}}"));
- if (telemetry) {
+ ResponseTime_P(PSTR(""));
+
+ for (uint8_t dev = 0; dev < PCA9685_MAX_COUNT; dev++)
+ {
+ if (!pca9685.detected[dev])
+ {
+ continue;
+ }
+
+ PCA9685_SetName(dev);
+
+ ResponseAppend_P(PSTR(",\"%s\":{\"PWM_FREQ\":%i"), pca9685.name, pca9685.freq[dev]);
+ ResponseAppend_P(PSTR(",\"INVERT\":%i"), pca9685.inverted[dev] ? 1 : 0);
+ ResponseAppend_P(PSTR(",\"INTCLK\":%lu"), pca9685.intclk[dev]);
+
+ for (uint32_t pin = 0; pin < 16; pin++)
+ {
+ uint16_t pwm_val = PCA9685_GetPWMvalue(pca9685.motor[dev][pin].pwm, pca9685.inverted[dev]); // return logical (possibly inverted) pwm value
+ ResponseAppend_P(PSTR(",\"PWM%i\":%i"), pin, pwm_val);
+ }
+
+ ResponseJsonEnd();
+ }
+
+ ResponseJsonEnd();
+
+ if (telemetry)
+ {
MqttPublishTeleSensor();
}
}
bool Xdrv15(uint32_t function)
{
- if (!I2cEnabled(XI2C_01)) { return false; }
+ if (!I2cEnabled(XI2C_01))
+ {
+ return false;
+ }
bool result = false;
- if (FUNC_INIT == function) {
+ if (FUNC_INIT == function)
+ {
PCA9685_Detect();
}
- else if (pca9685_detected) {
- switch (function) {
- case FUNC_EVERY_SECOND:
- if (TasmotaGlobal.tele_period == 0) {
- PCA9685_OutputTelemetry(true);
- }
- break;
- case FUNC_COMMAND_DRIVER:
- if (XDRV_15 == XdrvMailbox.index) {
- result = PCA9685_Command();
- }
- break;
+ else if (pca9685.count > 0)
+ {
+ switch (function)
+ {
+ case FUNC_EVERY_SECOND:
+ if (TasmotaGlobal.tele_period == 0)
+ {
+ PCA9685_OutputTelemetry(true);
+ }
+ break;
+ case FUNC_COMMAND_DRIVER:
+ if (XDRV_15 == XdrvMailbox.index)
+ {
+ result = PCA9685_Command();
+ }
+ break;
}
}
return result;
From 2d6c0b12c0464bea510afc33eb00caea112eca77 Mon Sep 17 00:00:00 2001
From: ZioFabry <32312585+ZioFabry@users.noreply.github.com>
Date: Tue, 4 Jul 2023 14:54:14 +0200
Subject: [PATCH 2/2] IndexSeparator + PWMTO Improvment
---
.../tasmota_xdrv_driver/xdrv_15_pca9685.ino | 76 ++++++++++---------
1 file changed, 41 insertions(+), 35 deletions(-)
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_15_pca9685.ino b/tasmota/tasmota_xdrv_driver/xdrv_15_pca9685.ino
index 10f3e8cbbee2..f8ef6599f41c 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_15_pca9685.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_15_pca9685.ino
@@ -2,7 +2,7 @@
xdrv_15_pca9685.ino - Support for I2C PCA9685 12bit 16 pin hardware PWM driver on Tasmota
Copyright (C) 2021 Andre Thomas and Theo Arends
- 2023 Fabrizio Amodio)
+ 2023 Fabrizio Amodio
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -50,10 +50,10 @@
DRIVER15 PWM[0-8],pin,ON // Fully turn a specific board/pin/LED ON
DRIVER15 PWM[0-8],pin,OFF // Fully turn a specific board/pin/LED OFF
- DRIVER15 PWMTO[0-8] seconds,pin,value[,pin,value[,pin,value...]] // Move all the specified pin to a new location in the specified time
+ DRIVER15 PWMTO[0-8] tensecs,pin,value[,pin,value[,pin,value...]] // Move all the specified pin to a new location in the specified time (1/10 sec resolution), if "tensecs" is zero it's equivalent to PWM command for all the pins
e.g.
- PWMTO 4 0 327 1 550 2 187 3 200
+ PWMTO 40 0 327 1 550 2 187 3 200
this move the PIN0 of the Board 0 from the current position to 327
the PIN1 of the Board 0 from the current position to 550
the PIN2 of the Board 0 from the current position to 187
@@ -61,9 +61,9 @@
all the movements will be completed in 4 seconds, every PIN will be stepped relative to that @ 50ms step.
- PWMTO1 4 0 327 1 550 2 187 3 200
+ PWMTO1 40 0 327 1 550 2 187 3 200
same logic on the board #1
-
+
DRIVER15 PWMSTOP[0-8] // stop all the moment on the relative board
*/
@@ -108,7 +108,7 @@ typedef struct
uint16_t pwm;
bool running;
uint16_t step;
- uint16_t every;
+ int16_t every;
uint16_t target;
int16_t direction; // 1 == UP , 0 == stop; -1 == down
} tMotor;
@@ -132,7 +132,7 @@ void PCA9685_SetName(uint8_t pca)
{
if (pca9685.count > 1)
{
- pca9685.name[7] = '_';
+ pca9685.name[7] = IndexSeparator();
pca9685.name[8] = '0' + pca;
pca9685.name[9] = 0;
}
@@ -274,7 +274,7 @@ void PCA9685_RunMotor()
if (!m->running)
continue;
- if ((m->direction > 0 && m->pwm >= m->target) || (m->direction < 0 && m->pwm <= m->target))
+ if (m->every == -1 || (m->direction > 0 && m->pwm >= m->target) || (m->direction < 0 && m->pwm <= m->target))
{
m->running = false;
if (m->pwm != m->target)
@@ -409,7 +409,7 @@ bool PCA9685_Command(void)
{
if (paramcount > (2 + paramFrom))
{
- uint16_t tis = atoi(ArgV(argument, 2)); // time in second to complete all the motors move
+ uint16_t tids = atoi(ArgV(argument, 2)); // time in 1/10 of second to complete all the motors move
uint16_t pin = atoi(ArgV(argument, 2 + paramFrom));
/*
@@ -417,10 +417,10 @@ bool PCA9685_Command(void)
*/
if (pin > 15)
pin = 15;
- if (tis < 1)
- tis = 1; // min 1 seconds to complete all the moves
- if (tis > 60)
- tis = 60; // max 60 seconds to complete all the moves
+ if (tids < 2)
+ tids = 0; // min 2/10 seconds to complete all the moves
+ if (tids > 600)
+ tids = 600; // max 60 seconds to complete all the moves
tMotor *m = &pca9685.motor[dev][pin];
m->target = atoi(ArgV(argument, 2 + paramFrom + 1));
@@ -429,33 +429,39 @@ bool PCA9685_Command(void)
m->step = 0;
m->direction = m->target < m->pwm ? -1 : 1;
- // AddLog(LOG_LEVEL_DEBUG, "PCA9685: PWMTO dev=%u pin=%u tis=%u e=? pwm=%lu target=%lu dir=%d",
- // dev,
- // pin,
- // tis,
- // m->pwm,
- // m->target,
- // m->direction);
-
- m->every = 0;
- while (m->every < 1)
+ if( tids == 0 )
{
- uint16_t stepValue = abs((int16_t)m->pwm - (int16_t)m->target) / abs(m->direction);
- if (stepValue < 1)
+ m->every = -1;
+ m->running = true;
+ } else {
+ // AddLog(LOG_LEVEL_DEBUG, "PCA9685: PWMTO dev=%u pin=%u tids=%u e=? pwm=%lu target=%lu dir=%d",
+ // dev,
+ // pin,
+ // tids,
+ // m->pwm,
+ // m->target,
+ // m->direction);
+
+ m->every = 0;
+ while (m->every < 1)
{
- m->direction += m->target < m->pwm ? -1 : 1;
- continue;
+ uint16_t stepValue = abs((int16_t)m->pwm - (int16_t)m->target) / abs(m->direction);
+ if (stepValue < 1)
+ {
+ m->direction += m->target < m->pwm ? -1 : 1;
+ continue;
+ }
+
+ m->every = round((tids * 200) / stepValue);
+ if (m->every < 1)
+ {
+ m->direction += m->target < m->pwm ? -1 : 1;
+ continue;
+ }
}
- m->every = round((tis * 20) / stepValue);
- if (m->every < 1)
- {
- m->direction += m->target < m->pwm ? -1 : 1;
- continue;
- }
+ m->running = true;
}
-
- m->running = true;
}
else
{