Add initial support for PCF8574 I2C I/O Expander

Add initial support for PCF8574 I2C I/O Expander (currently output only) by Stefan Bode
This commit is contained in:
Theo Arends 2019-10-01 16:33:39 +02:00
parent 92c2196e73
commit aa5b5e891e
5 changed files with 258 additions and 8 deletions

View File

@ -6,6 +6,7 @@
* Add Zigbee more support - collect endpoints and clusters, added ZigbeeDump command * Add Zigbee more support - collect endpoints and clusters, added ZigbeeDump command
* Add initial support for shutters by Stefan Bode (#288) * Add initial support for shutters by Stefan Bode (#288)
* Add command to MCP230xx: sensor29 pin,0/1/2 for OFF/ON/TOGGLE * Add command to MCP230xx: sensor29 pin,0/1/2 for OFF/ON/TOGGLE
* Add initial support for PCF8574 I2C I/O Expander (currently output only) by Stefan Bode
* *
* 6.6.0.13 20190922 * 6.6.0.13 20190922
* Add command EnergyReset4 x,x to initialize total usage for two tarrifs * Add command EnergyReset4 x,x to initialize total usage for two tarrifs

View File

@ -93,8 +93,8 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu
uint32_t spare27 : 1; uint32_t spare27 : 1;
uint32_t spare28 : 1; uint32_t spare28 : 1;
uint32_t spare29 : 1; uint32_t spare29 : 1;
uint32_t shutter_mode : 1; // bit 30 (v6.6.0.15) - SetOption80 - Enable shutter support uint32_t shutter_mode : 1; // bit 30 (v6.6.0.14) - SetOption80 - Enable shutter support
uint32_t spare31 : 1; uint32_t pcf8574_ports_inverted : 1; // bit 31 (v6.6.0.14) - SetOption81 - Invert all ports on PCF8574 devices
}; };
} SysBitfield3; } SysBitfield3;
@ -376,7 +376,6 @@ struct SYSCFG {
uint16_t ina226_r_shunt[4]; // E20 uint16_t ina226_r_shunt[4]; // E20
uint16_t ina226_i_fs[4]; // E28 uint16_t ina226_i_fs[4]; // E28
uint16_t tariff[4][2]; // E30 uint16_t tariff[4][2]; // E30
uint16_t shutter_opentime[MAX_SHUTTERS]; // E40 uint16_t shutter_opentime[MAX_SHUTTERS]; // E40
uint16_t shutter_closetime[MAX_SHUTTERS]; // E48 uint16_t shutter_closetime[MAX_SHUTTERS]; // E48
int16_t shuttercoeff[5][MAX_SHUTTERS]; // E50 int16_t shuttercoeff[5][MAX_SHUTTERS]; // E50
@ -384,8 +383,9 @@ struct SYSCFG {
uint8_t shutter_set50percent[MAX_SHUTTERS]; // E7C uint8_t shutter_set50percent[MAX_SHUTTERS]; // E7C
uint8_t shutter_position[MAX_SHUTTERS]; // E80 uint8_t shutter_position[MAX_SHUTTERS]; // E80
uint8_t shutter_startrelay[MAX_SHUTTERS]; // E84 uint8_t shutter_startrelay[MAX_SHUTTERS]; // E84
uint8_t pcf8574_config[MAX_PCF8574]; // E88
uint8_t free_e88[368]; // E88 uint8_t free_e90[360]; // E90
uint32_t cfg_timestamp; // FF8 uint32_t cfg_timestamp; // FF8
uint32_t cfg_crc32; // FFC uint32_t cfg_crc32; // FFC

View File

@ -68,6 +68,7 @@ const uint8_t MAX_XDSP_DRIVERS = 32; // Max number of allowed display dri
const uint8_t MAX_XDRV_DRIVERS = 96; // Max number of allowed driver drivers const uint8_t MAX_XDRV_DRIVERS = 96; // Max number of allowed driver drivers
const uint8_t MAX_XSNS_DRIVERS = 96; // Max number of allowed sensor drivers const uint8_t MAX_XSNS_DRIVERS = 96; // Max number of allowed sensor drivers
const uint8_t MAX_SHUTTERS = 4; // Max number of shutters const uint8_t MAX_SHUTTERS = 4; // Max number of shutters
const uint8_t MAX_PCF8574 = 8; // Max number of PCF8574 devices
const uint8_t MAX_RULE_MEMS = 5; // Max number of saved vars const uint8_t MAX_RULE_MEMS = 5; // Max number of saved vars
const uint8_t MAX_RULE_SETS = 3; // Max number of rule sets of size 512 characters const uint8_t MAX_RULE_SETS = 3; // Max number of rule sets of size 512 characters
const uint16_t MAX_RULE_SIZE = 512; // Max number of characters in rules const uint16_t MAX_RULE_SIZE = 512; // Max number of characters in rules

View File

@ -107,8 +107,6 @@ void ShutterInit(void)
Shutter.mask = 0; Shutter.mask = 0;
//Initialize to get relay that changed //Initialize to get relay that changed
Shutter.old_power = power; Shutter.old_power = power;
char shutter_open_chr[10];
char shutter_close_chr[10];
bool relay_in_interlock = false; bool relay_in_interlock = false;
AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Accuracy digits: %d"), Settings.shutter_accuracy); AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Accuracy digits: %d"), Settings.shutter_accuracy);
@ -164,9 +162,11 @@ void ShutterInit(void)
Shutter.real_position[i] = ShutterPercentToRealPosition(Settings.shutter_position[i], i); Shutter.real_position[i] = ShutterPercentToRealPosition(Settings.shutter_position[i], i);
//Shutter.real_position[i] = Settings.shutter_position[i] <= 5 ? Settings.shuttercoeff[2][i] * Settings.shutter_position[i] : Settings.shuttercoeff[1][i] * Settings.shutter_position[i] + Settings.shuttercoeff[0,i]; //Shutter.real_position[i] = Settings.shutter_position[i] <= 5 ? Settings.shuttercoeff[2][i] * Settings.shutter_position[i] : Settings.shuttercoeff[1][i] * Settings.shutter_position[i] + Settings.shuttercoeff[0,i];
Shutter.start_position[i] = Shutter.real_position[i]; Shutter.start_position[i] = Shutter.real_position[i];
dtostrfd((float)Shutter.open_time[i] / 10 , 1, shutter_open_chr);
dtostrfd((float)Shutter.close_time[i] / 10, 1, shutter_close_chr);
char shutter_open_chr[10];
dtostrfd((float)Shutter.open_time[i] / 10 , 1, shutter_open_chr);
char shutter_close_chr[10];
dtostrfd((float)Shutter.close_time[i] / 10, 1, shutter_close_chr);
AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Shutter %d (Relay:%d): Init. Pos: %d [%d %%], Open Vel.: 100 Close Vel.: %d , Max Way: %d, Opentime %s [s], Closetime %s [s], CoedffCalc: c0: %d, c1 %d, c2: %d, c3: %d, c4: %d, binmask %d, is inverted %d, shuttermode %d"), AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Shutter %d (Relay:%d): Init. Pos: %d [%d %%], Open Vel.: 100 Close Vel.: %d , Max Way: %d, Opentime %s [s], Closetime %s [s], CoedffCalc: c0: %d, c1 %d, c2: %d, c3: %d, c4: %d, binmask %d, is inverted %d, shuttermode %d"),
i, Settings.shutter_startrelay[i], Shutter.real_position[i], Settings.shutter_position[i], Shutter.close_velocity[i], Shutter.open_max[i], shutter_open_chr, shutter_close_chr, i, Settings.shutter_startrelay[i], Shutter.real_position[i], Settings.shutter_position[i], Shutter.close_velocity[i], Shutter.open_max[i], shutter_open_chr, shutter_close_chr,
Settings.shuttercoeff[0][i], Settings.shuttercoeff[1][i], Settings.shuttercoeff[2][i], Settings.shuttercoeff[3][i], Settings.shuttercoeff[4][i], Settings.shuttercoeff[0][i], Settings.shuttercoeff[1][i], Settings.shuttercoeff[2][i], Settings.shuttercoeff[3][i], Settings.shuttercoeff[4][i],

248
sonoff/xdrv_28_pcf8574.ino Normal file
View File

@ -0,0 +1,248 @@
/*
xdrv_28_pcf8574.ino - PCF8574 I2C support for Sonoff-Tasmota
Copyright (C) 2019 Stefan Bode
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_PCF8574
/*********************************************************************************************\
* PCF8574 - I2C IO Expander
*
* I2C Address: PCF8574 = 0x20 .. 0x27, PCF8574A = 0x38 .. 0x3F
\*********************************************************************************************/
#define XDRV_28 28
#define PCF8574_ADDR1 0x20 // PCF8574
#define PCF8574_ADDR2 0x38 // PCF8574A
struct PCF8574 {
int error;
uint8_t pin[64];
uint8_t address[MAX_PCF8574];
uint8_t pin_mask[MAX_PCF8574] = { 0 };
uint8_t max_connected_ports = 0; // Max numbers of devices comming from PCF8574 modules
uint8_t max_devices = 0; // Max numbers of PCF8574 modules
char stype[8];
bool type = true;
} Pcf8574;
void Pcf8574SwitchRelay(void)
{
for (uint32_t i = 0; i < devices_present; i++) {
uint8_t relay_state = bitRead(XdrvMailbox.index, i);
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("PCF: Pcf8574.max_devices %d requested pin %d"), Pcf8574.max_devices,Pcf8574.pin[i]);
if (Pcf8574.max_devices > 0 && Pcf8574.pin[i] < 99) {
uint8_t board = Pcf8574.pin[i]>>3;
uint8_t oldpinmask = Pcf8574.pin_mask[board];
uint8_t _val = bitRead(rel_inverted, i) ? !relay_state : relay_state;
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("PCF: Pcf8574SwitchRelay %d on pin %d"), i,state);
if (_val) {
Pcf8574.pin_mask[board] |= _val << (Pcf8574.pin[i]&0x7);
} else {
Pcf8574.pin_mask[board] &= ~(1 << (Pcf8574.pin[i]&0x7));
}
if (oldpinmask != Pcf8574.pin_mask[board]) {
Wire.beginTransmission(Pcf8574.address[board]);
Wire.write(Pcf8574.pin_mask[board]);
Pcf8574.error = Wire.endTransmission();
}
//pcf8574.write(Pcf8574.pin[i]&0x7, rel_inverted[i] ? !state : state);
}
}
}
void Pcf8574Init()
{
Pcf8574.type = false;
uint8_t pcf8574_address = PCF8574_ADDR1;
for (uint32_t i = 0; i < MAX_PCF8574; i++) {
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("PCF: Probing addr: 0x%x for PCF8574"), pcf8574_address);
if (I2cDevice(pcf8574_address)) {
I2cSetActive(pcf8574_address);
Pcf8574.type = true;
Pcf8574.address[Pcf8574.max_devices] = pcf8574_address;
Pcf8574.max_devices++;
strcpy(Pcf8574.stype, "PCF8574");
if (pcf8574_address >= PCF8574_ADDR2) {
strcpy(Pcf8574.stype, "PCF8574A");
}
AddLog_P2(LOG_LEVEL_DEBUG, S_LOG_I2C_FOUND_AT, Pcf8574.stype, pcf8574_address);
}
pcf8574_address++;
if ((PCF8574_ADDR1 + 8) == pcf8574_address) {
pcf8574_address = PCF8574_ADDR2;
}
}
if (Pcf8574.max_devices) {
for (uint32_t i = 0; i < sizeof(Pcf8574.pin); i++) {
Pcf8574.pin[i] = 99;
}
devices_present = devices_present - Pcf8574.max_connected_ports; // reset no of devices to avoid duplicate ports on duplicate init.
Pcf8574.max_connected_ports = 0; // reset no of devices to avoid duplicate ports on duplicate init.
for (uint32_t idx = 0; idx < Pcf8574.max_devices; idx++) { // suport up to 8 boards PCF8574
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("PCF: Device %d config 0x%02x"), idx +1, Settings.pcf8574_config[idx]);
for (uint32_t i = 0; i < 8; i++) {
uint8_t _result = Settings.pcf8574_config[idx] >> i &1;
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("PCF: I2C shift i %d: %d. Powerstate: %d, devices_present: %d"), i,_result, Settings.power>>i&1, devices_present);
if (_result > 0) {
Pcf8574.pin[devices_present] = i + 8 * idx;
bitWrite(rel_inverted, devices_present, Settings.flag3.pcf8574_ports_inverted);
devices_present++;
Pcf8574.max_connected_ports++;
}
}
}
AddLog_P2(LOG_LEVEL_INFO, PSTR("PCF: Total devices %d, PCF8574 output ports %d"), devices_present, Pcf8574.max_connected_ports);
}
}
/*********************************************************************************************\
* Presentation
\*********************************************************************************************/
#ifdef USE_WEBSERVER
#define WEB_HANDLE_PCF8574 "pcf"
const char HTTP_BTN_MENU_PCF8574[] PROGMEM =
"<p><form action='" WEB_HANDLE_PCF8574 "' method='get'><button>" D_CONFIGURE_PCF8574 "</button></form></p>";
const char HTTP_FORM_I2C_PCF8574_1[] PROGMEM =
"<fieldset><legend><b>&nbsp;" D_PCF8574_PARAMETERS "&nbsp;</b></legend>"
"<form method='get' action='" WEB_HANDLE_PCF8574 "'>"
"<p><input id='b1' name='b1' type='checkbox'%s><b>" D_INVERT_PORTS "</b></p><hr/>";
const char HTTP_FORM_I2C_PCF8574_2[] PROGMEM =
"<tr><td><b>" D_DEVICE " %d " D_PORT " %d</b></td><td style='width:100px'><select id='i2cs%d' name='i2cs%d'>"
"<option%s value='0'>" D_DEVICE_INPUT "</option>"
"<option%s value='1'>" D_DEVICE_OUTPUT "</option>"
"</select></td></tr>";
void HandlePcf8574(void)
{
if (!HttpCheckPriviledgedAccess()) { return; }
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_CONFIGURE_PCF8574));
if (WebServer->hasArg("save")) {
Pcf8574SaveSettings();
WebRestart(1);
return;
}
WSContentStart_P(D_CONFIGURE_PCF8574);
WSContentSendStyle();
WSContentSend_P(HTTP_FORM_I2C_PCF8574_1, (Settings.flag3.pcf8574_ports_inverted) ? " checked" : "");
WSContentSend_P(HTTP_TABLE100);
for (uint32_t idx = 0; idx < Pcf8574.max_devices; idx++) {
for (uint32_t idx2 = 0; idx2 < 8; idx2++) { // 8 ports on PCF8574
uint8_t helper = 1 << idx2;
WSContentSend_P(HTTP_FORM_I2C_PCF8574_2,
idx +1, idx2,
idx2 + 8*idx,
idx2 + 8*idx,
((helper & Settings.pcf8574_config[idx]) >> idx2 == 0) ? " selected " : " ",
((helper & Settings.pcf8574_config[idx]) >> idx2 == 1) ? " selected " : " "
);
}
}
WSContentSend_P(PSTR("</table>"));
WSContentSend_P(HTTP_FORM_END);
WSContentSpaceButton(BUTTON_CONFIGURATION);
WSContentStop();
}
void Pcf8574SaveSettings()
{
char stemp[7];
char tmp[100];
//AddLog_P(LOG_LEVEL_DEBUG, PSTR("PCF: Start working on Save arguements: inverted:%d")), WebServer->hasArg("b1");
Settings.flag3.pcf8574_ports_inverted = WebServer->hasArg("b1");
for (byte idx = 0; idx < Pcf8574.max_devices; idx++) {
byte count=0;
byte n = Settings.pcf8574_config[idx];
while(n!=0) {
n = n&(n-1);
count++;
}
if (count <= devices_present) {
devices_present = devices_present - count;
}
for (byte i = 0; i < 8; i++) {
snprintf_P(stemp, sizeof(stemp), PSTR("i2cs%d"), i+8*idx);
WebGetArg(stemp, tmp, sizeof(tmp));
byte _value = (!strlen(tmp)) ? 0 : atoi(tmp);
if (_value) {
Settings.pcf8574_config[idx] = Settings.pcf8574_config[idx] | 1 << i;
devices_present++;
Pcf8574.max_connected_ports++;
} else {
Settings.pcf8574_config[idx] = Settings.pcf8574_config[idx] & ~(1 << i );
}
}
//Settings.pcf8574_config[0] = (!strlen(webServer->arg("i2cs0").c_str())) ? 0 : atoi(webServer->arg("i2cs0").c_str());
//AddLog_P2(LOG_LEVEL_INFO, PSTR("PCF: I2C Board: %d, Config: %2x")), idx, Settings.pcf8574_config[idx];
}
}
#endif // USE_WEBSERVER
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
bool Xdrv28(uint8_t function)
{
bool result = false;
if (i2c_flg && Pcf8574.type) {
switch (function) {
case FUNC_SET_POWER:
Pcf8574SwitchRelay();
break;
#ifdef USE_WEBSERVER
case FUNC_WEB_ADD_BUTTON:
WSContentSend_P(HTTP_BTN_MENU_PCF8574);
break;
case FUNC_WEB_ADD_HANDLER:
WebServer->on("/" WEB_HANDLE_PCF8574, HandlePcf8574);
break;
#endif // USE_WEBSERVER
case FUNC_PRE_INIT:
Pcf8574Init();
break;
}
}
return result;
}
#endif // USE_PCF8574
#endif // USE_I2C