xdrv_15_pca9685.ino 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. /*
  2. xdrv_15_pca9685.ino - Support for I2C PCA9685 12bit 16 pin hardware PWM driver
  3. Copyright (C) 2018 Andre Thomas and Theo Arends
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. #ifdef USE_I2C
  16. #ifdef USE_PCA9685
  17. #define XDRV_15 15
  18. #define PCA9685_REG_MODE1 0x00
  19. #define PCA9685_REG_LED0_ON_L 0x06
  20. #define PCA9685_REG_PRE_SCALE 0xFE
  21. #ifndef USE_PCA9685_FREQ
  22. #define USE_PCA9685_FREQ 50
  23. #endif
  24. uint8_t pca9685_detected = 0;
  25. uint16_t pca9685_freq = USE_PCA9685_FREQ;
  26. uint16_t pca9685_pin_pwm_value[16];
  27. void PCA9685_Detect(void)
  28. {
  29. if (pca9685_detected) { return; }
  30. uint8_t buffer;
  31. if (I2cValidRead8(&buffer, USE_PCA9685_ADDR, PCA9685_REG_MODE1)) {
  32. I2cWrite8(USE_PCA9685_ADDR, PCA9685_REG_MODE1, 0x20);
  33. if (I2cValidRead8(&buffer, USE_PCA9685_ADDR, PCA9685_REG_MODE1)) {
  34. if (0x20 == buffer) {
  35. pca9685_detected = 1;
  36. snprintf_P(log_data, sizeof(log_data), S_LOG_I2C_FOUND_AT, "PCA9685", USE_PCA9685_ADDR);
  37. AddLog(LOG_LEVEL_DEBUG);
  38. PCA9685_Reset(); // Reset the controller
  39. }
  40. }
  41. }
  42. }
  43. void PCA9685_Reset(void)
  44. {
  45. I2cWrite8(USE_PCA9685_ADDR, PCA9685_REG_MODE1, 0x80);
  46. PCA9685_SetPWMfreq(USE_PCA9685_FREQ);
  47. for (uint8_t pin=0;pin<16;pin++) {
  48. PCA9685_SetPWM(pin,0,false);
  49. pca9685_pin_pwm_value[pin] = 0;
  50. }
  51. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"PCA9685\":{\"RESET\":\"OK\"}}"));
  52. }
  53. void PCA9685_SetPWMfreq(double freq) {
  54. /*
  55. 7.3.5 from datasheet
  56. prescale value = round(25000000/(4096*freq))-1;
  57. */
  58. if (freq > 23 && freq < 1527) {
  59. pca9685_freq=freq;
  60. } else {
  61. pca9685_freq=50;
  62. }
  63. uint8_t pre_scale_osc = round(25000000/(4096*pca9685_freq))-1;
  64. if (1526 == pca9685_freq) pre_scale_osc=0xFF; // force setting for 24hz because rounding causes 1526 to be 254
  65. uint8_t current_mode1 = I2cRead8(USE_PCA9685_ADDR, PCA9685_REG_MODE1); // read current value of MODE1 register
  66. uint8_t sleep_mode1 = (current_mode1&0x7F) | 0x10; // Determine register value to put PCA to sleep
  67. I2cWrite8(USE_PCA9685_ADDR, PCA9685_REG_MODE1, sleep_mode1); // Let's sleep a little
  68. I2cWrite8(USE_PCA9685_ADDR, PCA9685_REG_PRE_SCALE, pre_scale_osc); // Set the pre-scaler
  69. I2cWrite8(USE_PCA9685_ADDR, PCA9685_REG_MODE1, current_mode1 | 0xA0); // Reset MODE1 register to original state and enable auto increment
  70. }
  71. void PCA9685_SetPWM_Reg(uint8_t pin, uint16_t on, uint16_t off) {
  72. uint8_t led_reg = PCA9685_REG_LED0_ON_L + 4 * pin;
  73. uint32_t led_data = 0;
  74. I2cWrite8(USE_PCA9685_ADDR, led_reg, on);
  75. I2cWrite8(USE_PCA9685_ADDR, led_reg+1, (on >> 8));
  76. I2cWrite8(USE_PCA9685_ADDR, led_reg+2, off);
  77. I2cWrite8(USE_PCA9685_ADDR, led_reg+3, (off >> 8));
  78. }
  79. void PCA9685_SetPWM(uint8_t pin, uint16_t pwm, bool inverted) {
  80. if (4096 == pwm) {
  81. PCA9685_SetPWM_Reg(pin, 4096, 0); // Special use additional bit causes channel to turn on completely without PWM
  82. } else {
  83. PCA9685_SetPWM_Reg(pin, 0, pwm);
  84. }
  85. pca9685_pin_pwm_value[pin] = pwm;
  86. }
  87. bool PCA9685_Command(void)
  88. {
  89. boolean serviced = true;
  90. boolean validpin = false;
  91. uint8_t paramcount = 0;
  92. if (XdrvMailbox.data_len > 0) {
  93. paramcount=1;
  94. } else {
  95. serviced = false;
  96. return serviced;
  97. }
  98. char sub_string[XdrvMailbox.data_len];
  99. for (uint8_t ca=0;ca<XdrvMailbox.data_len;ca++) {
  100. if ((' ' == XdrvMailbox.data[ca]) || ('=' == XdrvMailbox.data[ca])) { XdrvMailbox.data[ca] = ','; }
  101. if (',' == XdrvMailbox.data[ca]) { paramcount++; }
  102. }
  103. UpperCase(XdrvMailbox.data,XdrvMailbox.data);
  104. if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"RESET")) { PCA9685_Reset(); return serviced; }
  105. if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"STATUS")) { PCA9685_OutputTelemetry(false); return serviced; }
  106. if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"PWMF")) {
  107. if (paramcount > 1) {
  108. uint16_t new_freq = atoi(subStr(sub_string, XdrvMailbox.data, ",", 2));
  109. if ((new_freq >= 24) && (new_freq <= 1526)) {
  110. PCA9685_SetPWMfreq(new_freq);
  111. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"PCA9685\":{\"PWMF\":%i, \"Result\":\"OK\"}}"),new_freq);
  112. return serviced;
  113. }
  114. } else { // No parameter was given for setfreq, so we return current setting
  115. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"PCA9685\":{\"PWMF\":%i}}"),pca9685_freq);
  116. return serviced;
  117. }
  118. }
  119. if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"PWM")) {
  120. if (paramcount > 1) {
  121. uint8_t pin = atoi(subStr(sub_string, XdrvMailbox.data, ",", 2));
  122. if (paramcount > 2) {
  123. if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 3), "ON")) {
  124. PCA9685_SetPWM(pin, 4096, false);
  125. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"PCA9685\":{\"PIN\":%i,\"PWM\":%i}}"),pin,4096);
  126. serviced = true;
  127. return serviced;
  128. }
  129. if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 3), "OFF")) {
  130. PCA9685_SetPWM(pin, 0, false);
  131. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"PCA9685\":{\"PIN\":%i,\"PWM\":%i}}"),pin,0);
  132. serviced = true;
  133. return serviced;
  134. }
  135. uint16_t pwm = atoi(subStr(sub_string, XdrvMailbox.data, ",", 3));
  136. if ((pin >= 0 && pin <= 15) && (pwm >= 0 && pwm <= 4096)) {
  137. PCA9685_SetPWM(pin, pwm, false);
  138. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"PCA9685\":{\"PIN\":%i,\"PWM\":%i}}"),pin,pwm);
  139. serviced = true;
  140. return serviced;
  141. }
  142. }
  143. }
  144. }
  145. return serviced;
  146. }
  147. void PCA9685_OutputTelemetry(bool telemetry) {
  148. if (0 == pca9685_detected) { return; } // We do not do this if the PCA9685 has not been detected
  149. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_JSON_TIME "\":\"%s\",\"PCA9685\": {"), GetDateAndTime(DT_LOCAL).c_str());
  150. snprintf_P(mqtt_data,sizeof(mqtt_data), PSTR("%s\"PWM_FREQ\":%i,"),mqtt_data,pca9685_freq);
  151. for (uint8_t pin=0;pin<16;pin++) {
  152. snprintf_P(mqtt_data,sizeof(mqtt_data), PSTR("%s\"PWM%i\":%i,"),mqtt_data,pin,pca9685_pin_pwm_value[pin]);
  153. }
  154. snprintf_P(mqtt_data,sizeof(mqtt_data),PSTR("%s\"END\":1}}"),mqtt_data);
  155. if (telemetry) {
  156. MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
  157. }
  158. }
  159. boolean Xdrv15(byte function)
  160. {
  161. boolean result = false;
  162. if (i2c_flg) {
  163. switch (function) {
  164. case FUNC_EVERY_SECOND:
  165. PCA9685_Detect();
  166. if (tele_period == 0) {
  167. PCA9685_OutputTelemetry(true);
  168. }
  169. break;
  170. case FUNC_COMMAND:
  171. if (XDRV_15 == XdrvMailbox.index) {
  172. PCA9685_Command();
  173. }
  174. break;
  175. default:
  176. break;
  177. }
  178. }
  179. return result;
  180. }
  181. #endif // USE_PCA9685
  182. #endif // USE_IC2