From 8337fba96039b06faddc4f25c15f14e68a825d8f Mon Sep 17 00:00:00 2001 From: Scott Date: Sun, 23 Jan 2022 12:38:17 -0500 Subject: [PATCH 1/2] PCA9685 (driver15) - add INVERT option The INVERT option makes PWM and ON/OFF values meaningful for open-drain connected lights. - Activate with: driver15 invert 1 - PWM value is inverted, so 0 is "off" and 4096 is "on" - INVERT is included in status and telemetry --- tasmota/xdrv_15_pca9685.ino | 41 +++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/tasmota/xdrv_15_pca9685.ino b/tasmota/xdrv_15_pca9685.ino index 48eae7f1f..f5249a03a 100644 --- a/tasmota/xdrv_15_pca9685.ino +++ b/tasmota/xdrv_15_pca9685.ino @@ -39,6 +39,7 @@ #define USE_PCA9685_FREQ 50 #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]; @@ -64,13 +65,22 @@ void PCA9685_Reset(void) { 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,false); - pca9685_pin_pwm_value[pin] = 0; + PCA9685_SetPWM(pin,0,pca9685_inverted); + pca9685_pin_pwm_value[pin] = PCA9685_GetPWMvalue(0, pca9685_inverted); } Response_P(PSTR("{\"PCA9685\":{\"RESET\":\"OK\"}}")); } +uint16_t PCA9685_GetPWMvalue(uint16_t pwm, bool inverted) { + uint16_t pwm_val = pwm; + if (inverted) { + pwm_val = 4096-pwm; + } + return pwm_val; +} + void PCA9685_SetPWMfreq(double freq) { /* 7.3.5 from datasheet @@ -100,12 +110,13 @@ void PCA9685_SetPWM_Reg(uint8_t pin, uint16_t on, uint16_t off) { } void PCA9685_SetPWM(uint8_t pin, uint16_t pwm, bool inverted) { - if (4096 == pwm) { + 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); + PCA9685_SetPWM_Reg(pin, 0, pwm_val); } - pca9685_pin_pwm_value[pin] = pwm; + pca9685_pin_pwm_value[pin] = pwm_val; } bool PCA9685_Command(void) @@ -130,6 +141,16 @@ bool PCA9685_Command(void) if (!strcmp(ArgV(argument, 1),"STATUS")) { PCA9685_OutputTelemetry(false); return serviced; } + if (!strcmp(ArgV(argument, 1),"INVERT")) { + if (paramcount > 1) { + pca9685_inverted = (1 == atoi(ArgV(argument, 2))); + Response_P(PSTR("{\"PCA9685\":{\"INVERT\":%i, \"Result\":\"OK\"}}"), pca9685_inverted?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); + return serviced; + } + } if (!strcmp(ArgV(argument, 1),"PWMF")) { if (paramcount > 1) { uint16_t new_freq = atoi(ArgV(argument, 2)); @@ -148,20 +169,20 @@ bool PCA9685_Command(void) uint8_t pin = atoi(ArgV(argument, 2)); if (paramcount > 2) { if (!strcmp(ArgV(argument, 3), "ON")) { - PCA9685_SetPWM(pin, 4096, false); + PCA9685_SetPWM(pin, 4096, pca9685_inverted); Response_P(PSTR("{\"PCA9685\":{\"PIN\":%i,\"PWM\":%i}}"),pin,4096); serviced = true; return serviced; } if (!strcmp(ArgV(argument, 3), "OFF")) { - PCA9685_SetPWM(pin, 0, false); + PCA9685_SetPWM(pin, 0, pca9685_inverted); Response_P(PSTR("{\"PCA9685\":{\"PIN\":%i,\"PWM\":%i}}"),pin,0); serviced = true; return serviced; } uint16_t pwm = atoi(ArgV(argument, 3)); if ((pin >= 0 && pin <= 15) && (pwm >= 0 && pwm <= 4096)) { - PCA9685_SetPWM(pin, pwm, false); + PCA9685_SetPWM(pin, pwm, pca9685_inverted); Response_P(PSTR("{\"PCA9685\":{\"PIN\":%i,\"PWM\":%i}}"),pin,pwm); serviced = true; return serviced; @@ -175,8 +196,10 @@ 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++) { - ResponseAppend_P(PSTR("\"PWM%i\":%i,"),pin,pca9685_pin_pwm_value[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) { From fd550fd104bea87cfebd2fe37c3bc7e6285e35ef Mon Sep 17 00:00:00 2001 From: Scott Date: Sun, 23 Jan 2022 13:14:19 -0500 Subject: [PATCH 2/2] PCA9685 (driver15) - Allow the "ALL" virtual pin 61 for pwm values The datasheet shows register 250 is "ALL" pins, which corresponds to virtual pin 61 (61*4+6=250). Pin 61 was already allowed with PWM command for OFF and ON, but not specified pwm values. This update allows virtual pin 61 to be assigned pwm values for all channels. Example usage to set all channels to 2500: `driver15 pwm,61,2500` --- tasmota/xdrv_15_pca9685.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasmota/xdrv_15_pca9685.ino b/tasmota/xdrv_15_pca9685.ino index f5249a03a..9f6cee154 100644 --- a/tasmota/xdrv_15_pca9685.ino +++ b/tasmota/xdrv_15_pca9685.ino @@ -181,7 +181,7 @@ bool PCA9685_Command(void) return serviced; } uint16_t pwm = atoi(ArgV(argument, 3)); - if ((pin >= 0 && pin <= 15) && (pwm >= 0 && pwm <= 4096)) { + 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); serviced = true;