2023-01-01 13:02:22 +00:00
|
|
|
/*
|
2023-01-01 13:29:41 +00:00
|
|
|
xdrv_64_pca9632.ino - Support for I2C PCA9632 4-channel 8-bit hardware PWM driver on Tasmota
|
2023-01-01 13:02:22 +00:00
|
|
|
|
|
|
|
Copyright (C) 2022 Pascal Heinrich
|
|
|
|
|
|
|
|
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
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef USE_I2C
|
|
|
|
#ifdef USE_PCA9632
|
|
|
|
/*********************************************************************************************\
|
|
|
|
* PCA9632 - 4-channel 8-bit pwm driver
|
|
|
|
*
|
|
|
|
* I2C Address: 0x60 .. 0x63
|
|
|
|
\*********************************************************************************************/
|
|
|
|
|
2023-01-01 13:29:41 +00:00
|
|
|
#define XDRV_64 64
|
2023-01-01 13:02:22 +00:00
|
|
|
#define XI2C_75 75 // See I2CDEVICES.md
|
|
|
|
|
|
|
|
#define PCA9632_REG_MODE1 0x00
|
|
|
|
#define PCA9632_REG_MODE2 0x01
|
|
|
|
#define PCA9632_REG_PWM_BASE 0x02
|
|
|
|
#define PCA9632_REG_PWM_1 PCA9632_REG_PWM_BASE + 0
|
|
|
|
#define PCA9632_REG_PWM_2 PCA9632_REG_PWM_BASE + 1
|
|
|
|
#define PCA9632_REG_PWM_3 PCA9632_REG_PWM_BASE + 2
|
|
|
|
#define PCA9632_REG_PWM_4 PCA9632_REG_PWM_BASE + 3
|
|
|
|
#define PCA9632_REG_GRPPWM 0x06
|
|
|
|
#define PCA9632_REG_GRPGREQ 0x07
|
|
|
|
#define PCA9632_REG_LEDOUT 0x08
|
|
|
|
#define PCA9632_AUTO_INC 0x80
|
|
|
|
|
|
|
|
#ifndef USE_PCA9632_ADDR
|
2023-01-01 13:29:41 +00:00
|
|
|
#define USE_PCA9632_ADDR 0x60
|
2023-01-01 13:02:22 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef USE_PCA9632_CM_0
|
|
|
|
#define USE_PCA9632_CM_0 0
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef USE_PCA9632_CM_1
|
|
|
|
#define USE_PCA9632_CM_1 1
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef USE_PCA9632_CM_2
|
|
|
|
#define USE_PCA9632_CM_2 2
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef USE_PCA9632_CM_3
|
|
|
|
#define USE_PCA9632_CM_3 3
|
|
|
|
#endif
|
|
|
|
|
|
|
|
bool pca9632_inverted = false; // invert PWM for open-collector load
|
|
|
|
bool pca9632_detected = false;
|
|
|
|
uint8_t pca9632_pin_pwm_value[4];
|
|
|
|
|
|
|
|
bool PCA9632_Detect(void) {
|
|
|
|
if (I2cSetDevice(USE_PCA9632_ADDR)) {
|
|
|
|
uint8_t buffer;
|
|
|
|
if (I2cValidRead8(&buffer, USE_PCA9632_ADDR, PCA9632_REG_MODE1)) {
|
|
|
|
I2cWrite8(USE_PCA9632_ADDR, PCA9632_REG_MODE1, 0x10);
|
|
|
|
if (I2cValidRead8(&buffer, USE_PCA9632_ADDR, PCA9632_REG_MODE1)) {
|
|
|
|
if (0x10 == buffer) {
|
|
|
|
I2cSetActiveFound(USE_PCA9632_ADDR, "PCA9632");
|
|
|
|
PCA9632_Reset(); // Reset the controller
|
|
|
|
return pca9632_detected = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PCA9632_Init(void) {
|
|
|
|
// configure none inverted and totem pole
|
|
|
|
I2cWrite8(USE_PCA9632_ADDR, PCA9632_REG_MODE2, 0x14);
|
|
|
|
|
|
|
|
// turn off sleep mode
|
|
|
|
I2cWrite8(USE_PCA9632_ADDR, PCA9632_REG_MODE1, 0x1);
|
|
|
|
}
|
|
|
|
|
|
|
|
void PCA9632_Reset(void) {
|
|
|
|
I2cWrite8(USE_PCA9632_ADDR, PCA9632_REG_MODE1, 0x6);
|
|
|
|
pca9632_inverted = false;
|
|
|
|
for (uint8_t pin = 0; pin < 4; pin++) {
|
|
|
|
PCA9632_SetPWM(pin, 0);
|
|
|
|
pca9632_pin_pwm_value[pin] = 0;
|
|
|
|
}
|
|
|
|
Response_P(PSTR("{\"PCA9632\":{\"RESET\":\"OK\"}}"));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool PCA9632_SetInvert(bool on) {
|
|
|
|
uint8_t buffer;
|
|
|
|
if(I2cValidRead8(&buffer, USE_PCA9632_ADDR, PCA9632_REG_MODE2)) {
|
|
|
|
I2cWrite8(USE_PCA9632_ADDR, PCA9632_REG_MODE2, buffer | ((on ? 1 : 0) >> 4));
|
|
|
|
}
|
|
|
|
return on;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool PCA9632_SetPWM(uint8_t pin, uint8_t pwm) {
|
2023-01-01 13:29:41 +00:00
|
|
|
|
2023-01-01 13:02:22 +00:00
|
|
|
uint8_t pin_mapping = PCA9632_PinMapping(pin);
|
|
|
|
I2cWrite8(USE_PCA9632_ADDR, PCA9632_REG_PWM_BASE + pin_mapping, pwm);
|
|
|
|
pca9632_pin_pwm_value[pin_mapping] = pwm;
|
|
|
|
|
|
|
|
return pwm > 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
bool PCA9632_SetPWM_Bulk(uint8_t *pwm, uint16_t len) {
|
|
|
|
uint8_t buffer[4];
|
|
|
|
|
|
|
|
// map the pwm values to the final pins
|
|
|
|
for (uint8_t pin = 0; pin < 4; pin++) {
|
|
|
|
uint8_t pin_mapping = PCA9632_PinMapping(pin);
|
|
|
|
buffer[pin_mapping] = pwm[pin];
|
|
|
|
}
|
|
|
|
|
|
|
|
I2cWriteBuffer(USE_PCA9632_ADDR, PCA9632_REG_PWM_BASE | PCA9632_AUTO_INC, buffer, len);
|
|
|
|
|
|
|
|
// set the pwm values for later use
|
|
|
|
bool enable = false;
|
|
|
|
for (uint8_t pin = 0; pin < 4; pin++) {
|
|
|
|
uint8_t value = buffer[pin];
|
|
|
|
|
|
|
|
pca9632_pin_pwm_value[pin] = value;
|
|
|
|
|
|
|
|
if (value > 0) {
|
|
|
|
enable |= true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return enable;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PCA9632_Enable(bool enable) {
|
|
|
|
DEBUG_TRACE_LOG(PSTR("DRV: PCA9632 enable %d"), enable);
|
|
|
|
I2cWrite8(USE_PCA9632_ADDR, PCA9632_REG_LEDOUT, enable ? 0xFF : 0x0);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool PCA9632_Command(void) {
|
|
|
|
bool serviced = true;
|
|
|
|
bool validpin = false;
|
|
|
|
uint8_t paramcount = 0;
|
|
|
|
if (XdrvMailbox.data_len > 0) {
|
|
|
|
paramcount = 1;
|
|
|
|
} else {
|
|
|
|
serviced = false;
|
|
|
|
return serviced;
|
|
|
|
}
|
|
|
|
char argument[XdrvMailbox.data_len];
|
|
|
|
for (uint32_t ca = 0; ca < XdrvMailbox.data_len; ca++) {
|
|
|
|
if ((' ' == XdrvMailbox.data[ca]) || ('=' == XdrvMailbox.data[ca])) { XdrvMailbox.data[ca] = ','; }
|
|
|
|
if (',' == XdrvMailbox.data[ca]) { paramcount++; }
|
|
|
|
}
|
|
|
|
UpperCase(XdrvMailbox.data, XdrvMailbox.data);
|
|
|
|
|
|
|
|
if (!strcmp(ArgV(argument, 1),"RESET")) { PCA9632_Reset(); return serviced; }
|
|
|
|
|
|
|
|
if (!strcmp(ArgV(argument, 1),"STATUS")) { PCA9632_OutputTelemetry(false); return serviced; }
|
|
|
|
|
|
|
|
if (!strcmp(ArgV(argument, 1),"INVERT")) {
|
|
|
|
if (paramcount > 1) {
|
|
|
|
pca9632_inverted = PCA9632_SetInvert(1 == atoi(ArgV(argument, 2)));
|
|
|
|
Response_P(PSTR("{\"PCA9632\":{\"INVERT\":%i, \"Result\":\"OK\"}}"), pca9632_inverted);
|
|
|
|
return serviced;
|
|
|
|
} else { // No parameter was given for invert, so we return current setting
|
|
|
|
Response_P(PSTR("{\"PCA9632\":{\"INVERT\":%i}}"), pca9632_inverted);
|
|
|
|
return serviced;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!strcmp(ArgV(argument, 1),"PWM")) {
|
|
|
|
if (paramcount > 1) {
|
|
|
|
uint8_t pin = atoi(ArgV(argument, 2));
|
|
|
|
if (paramcount > 2) {
|
|
|
|
if (!strcmp(ArgV(argument, 3), "ON")) {
|
|
|
|
PCA9632_SetPWM(pin, 255);
|
|
|
|
Response_P(PSTR("{\"PCA9632\":{\"PIN\":%i,\"PWM\":%i}}"), pin, 255);
|
|
|
|
serviced = true;
|
|
|
|
return serviced;
|
|
|
|
}
|
|
|
|
if (!strcmp(ArgV(argument, 3), "OFF")) {
|
|
|
|
PCA9632_SetPWM(pin, 0);
|
|
|
|
Response_P(PSTR("{\"PCA9632\":{\"PIN\":%i,\"PWM\":%i}}"), pin, 0);
|
|
|
|
serviced = true;
|
|
|
|
return serviced;
|
|
|
|
}
|
|
|
|
uint16_t pwm = atoi(ArgV(argument, 3));
|
|
|
|
if ((pin >= 0 && pin <= 3) && (pwm >= 0 && pwm <= 255)) {
|
|
|
|
PCA9632_SetPWM(pin, pwm);
|
|
|
|
Response_P(PSTR("{\"PCA9632\":{\"PIN\":%i,\"PWM\":%i}}"), pin, pwm);
|
|
|
|
serviced = true;
|
|
|
|
return serviced;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!strcmp(ArgV(argument, 1),"ENABLE")) {
|
|
|
|
PCA9632_Enable(true);
|
|
|
|
Response_P(PSTR("{\"PCA9632\":{\"ENABLE\":true}}"));
|
|
|
|
}
|
|
|
|
if (!strcmp(ArgV(argument, 1),"DISABLE")) {
|
|
|
|
PCA9632_Enable(false);
|
|
|
|
Response_P(PSTR("{\"PCA9632\":{\"ENABLE\":false}}"));
|
|
|
|
}
|
|
|
|
return serviced;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PCA9632_OutputTelemetry(bool telemetry) {
|
|
|
|
ResponseAppend_P(PSTR("\"INVERT\":%i,"), pca9632_inverted?1:0);
|
|
|
|
for (uint32_t pin = 0; pin < 4; pin++) {
|
|
|
|
uint8_t pin_mapping = PCA9632_PinMapping(pin);
|
|
|
|
ResponseAppend_P(PSTR("\"PWM%i\":%i,"), pin_mapping, pca9632_pin_pwm_value[pin_mapping]);
|
|
|
|
}
|
|
|
|
ResponseAppend_P(PSTR("\"END\":1}}"));
|
|
|
|
if (telemetry) {
|
|
|
|
MqttPublishTeleSensor();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t PCA9632_PinMapping(uint8_t pin) {
|
|
|
|
switch(pin) {
|
|
|
|
case 0:
|
|
|
|
return USE_PCA9632_CM_0;
|
|
|
|
case 1:
|
|
|
|
return USE_PCA9632_CM_1;
|
|
|
|
case 2:
|
|
|
|
return USE_PCA9632_CM_2;
|
|
|
|
case 3:
|
|
|
|
return USE_PCA9632_CM_3;
|
|
|
|
default:
|
|
|
|
return USE_PCA9632_CM_0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-01 13:29:41 +00:00
|
|
|
bool Xdrv64(uint32_t function) {
|
2023-01-01 13:02:22 +00:00
|
|
|
if (!I2cEnabled(XI2C_75)) { return false; }
|
|
|
|
|
|
|
|
bool result = false;
|
|
|
|
|
|
|
|
if (FUNC_INIT == function) {
|
|
|
|
if (PCA9632_Detect()) {
|
|
|
|
PCA9632_Init();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (pca9632_detected) {
|
|
|
|
switch (function) {
|
|
|
|
case FUNC_EVERY_SECOND:
|
|
|
|
if (TasmotaGlobal.tele_period == 0) {
|
|
|
|
PCA9632_OutputTelemetry(true);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case FUNC_COMMAND_DRIVER:
|
2023-01-01 13:29:41 +00:00
|
|
|
if (XDRV_64 == XdrvMailbox.index) {
|
2023-01-01 13:02:22 +00:00
|
|
|
result = PCA9632_Command();
|
|
|
|
}
|
|
|
|
break;
|
2023-12-27 21:03:56 +00:00
|
|
|
case FUNC_ACTIVE:
|
|
|
|
result = true;
|
|
|
|
break;
|
2023-01-01 13:02:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif // USE_PCA9632
|
|
|
|
#endif // USE_IC2
|