Fork of the espurna firmware for `mhsw` switches
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

411 lines
12 KiB

Terminal: change command-line parser (#2247) Change the underlying command line handling: - switch to a custom parser, inspired by redis / sds - update terminalRegisterCommand signature, pass only bare minimum - clean-up `help` & `commands`. update settings `set`, `get` and `del` - allow our custom test suite to run command-line tests - clean-up Stream IO to allow us to print large things into debug stream (for example, `eeprom.dump`) - send parsing errors to the debug log As a proof of concept, introduce `TERMINAL_MQTT_SUPPORT` and `TERMINAL_WEB_API_SUPPORT` - MQTT subscribes to the `<root>/cmd/set` and sends response to the `<root>/cmd`. We can't output too much, as we don't have any large-send API. - Web API listens to the `/api/cmd?apikey=...&line=...` (or PUT, params inside the body). This one is intended as a possible replacement of the `API_SUPPORT`. Internals introduce a 'task' around the AsyncWebServerRequest object that will simulate what WiFiClient does and push data into it continuously, switching between CONT and SYS. Both are experimental. We only accept a single command and not every command is updated to use Print `ctx.output` object. We are also somewhat limited by the Print / Stream overall, perhaps I am overestimating the usefulness of Arduino compatibility to such an extent :) Web API handler can also sometimes show only part of the result, whenever the command tries to yield() by itself waiting for something. Perhaps we would need to create a custom request handler for that specific use-case.
4 years ago
Terminal: change command-line parser (#2247) Change the underlying command line handling: - switch to a custom parser, inspired by redis / sds - update terminalRegisterCommand signature, pass only bare minimum - clean-up `help` & `commands`. update settings `set`, `get` and `del` - allow our custom test suite to run command-line tests - clean-up Stream IO to allow us to print large things into debug stream (for example, `eeprom.dump`) - send parsing errors to the debug log As a proof of concept, introduce `TERMINAL_MQTT_SUPPORT` and `TERMINAL_WEB_API_SUPPORT` - MQTT subscribes to the `<root>/cmd/set` and sends response to the `<root>/cmd`. We can't output too much, as we don't have any large-send API. - Web API listens to the `/api/cmd?apikey=...&line=...` (or PUT, params inside the body). This one is intended as a possible replacement of the `API_SUPPORT`. Internals introduce a 'task' around the AsyncWebServerRequest object that will simulate what WiFiClient does and push data into it continuously, switching between CONT and SYS. Both are experimental. We only accept a single command and not every command is updated to use Print `ctx.output` object. We are also somewhat limited by the Print / Stream overall, perhaps I am overestimating the usefulness of Arduino compatibility to such an extent :) Web API handler can also sometimes show only part of the result, whenever the command tries to yield() by itself waiting for something. Perhaps we would need to create a custom request handler for that specific use-case.
4 years ago
6 years ago
  1. /*
  2. I2C MODULE
  3. Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
  4. */
  5. #include "i2c.h"
  6. #if I2C_SUPPORT
  7. #include <Wire.h>
  8. unsigned int _i2c_locked[16] = {0};
  9. #if I2C_USE_BRZO
  10. #include <brzo_i2c.h>
  11. unsigned long _i2c_scl_frequency = 0;
  12. #endif
  13. // -----------------------------------------------------------------------------
  14. // Private
  15. // -----------------------------------------------------------------------------
  16. int _i2cGetSDA() {
  17. return getSetting("i2cSDA", I2C_SDA_PIN);
  18. }
  19. int _i2cGetSCL() {
  20. return getSetting("i2cSCL", I2C_SCL_PIN);
  21. }
  22. int _i2cClearbus(int sda, int scl) {
  23. #if defined(TWCR) && defined(TWEN)
  24. // Disable the Atmel 2-Wire interface so we can control the SDA and SCL pins directly
  25. TWCR &= ~(_BV(TWEN));
  26. #endif
  27. // Make SDA (data) and SCL (clock) pins inputs with pullup
  28. pinMode(sda, INPUT_PULLUP);
  29. pinMode(scl, INPUT_PULLUP);
  30. nice_delay(2500);
  31. // Wait 2.5 secs. This is strictly only necessary on the first power
  32. // up of the DS3231 module to allow it to initialize properly,
  33. // but is also assists in reliable programming of FioV3 boards as it gives the
  34. // IDE a chance to start uploaded the program
  35. // before existing sketch confuses the IDE by sending Serial data.
  36. // If it is held low the device cannot become the I2C master
  37. // I2C bus error. Could not clear SCL clock line held low
  38. boolean scl_low = (digitalRead(scl) == LOW);
  39. if (scl_low) return 1;
  40. boolean sda_low = (digitalRead(sda) == LOW);
  41. int clockCount = 20; // > 2x9 clock
  42. // While SDA is low for at most 20 cycles
  43. while (sda_low && (clockCount > 0)) {
  44. clockCount--;
  45. // Note: I2C bus is open collector so do NOT drive SCL or SDA high
  46. pinMode(scl, INPUT); // release SCL pullup so that when made output it will be LOW
  47. pinMode(scl, OUTPUT); // then clock SCL Low
  48. delayMicroseconds(10); // for >5uS
  49. pinMode(scl, INPUT); // release SCL LOW
  50. pinMode(scl, INPUT_PULLUP); // turn on pullup resistors again
  51. // do not force high as slave may be holding it low for clock stretching
  52. delayMicroseconds(10); // The >5uS is so that even the slowest I2C devices are handled
  53. // loop waiting for SCL to become high only wait 2sec
  54. scl_low = (digitalRead(scl) == LOW);
  55. int counter = 20;
  56. while (scl_low && (counter > 0)) {
  57. counter--;
  58. nice_delay(100);
  59. scl_low = (digitalRead(scl) == LOW);
  60. }
  61. // If still low after 2 sec error
  62. // I2C bus error. Could not clear. SCL clock line held low by slave clock stretch for >2sec
  63. if (scl_low) return 2;
  64. sda_low = (digitalRead(sda) == LOW); // and check SDA input again and loop
  65. }
  66. // If still low
  67. // I2C bus error. Could not clear. SDA data line held low
  68. if (sda_low) return 3;
  69. // Pull SDA line low for "start" or "repeated start"
  70. pinMode(sda, INPUT); // remove pullup
  71. pinMode(sda, OUTPUT); // and then make it LOW i.e. send an I2C Start or Repeated start control
  72. // When there is only one I2C master a "start" or "repeat start" has the same function as a "stop" and clears the bus
  73. // A Repeat Start is a Start occurring after a Start with no intervening Stop.
  74. delayMicroseconds(10); // wait >5uS
  75. pinMode(sda, INPUT); // remove output low
  76. pinMode(sda, INPUT_PULLUP); // and make SDA high i.e. send I2C STOP control.
  77. delayMicroseconds(10); // wait >5uS
  78. pinMode(sda, INPUT); // and reset pins as tri-state inputs which is the default state on reset
  79. pinMode(scl, INPUT);
  80. // Everything OK
  81. return 0;
  82. }
  83. // ---------------------------------------------------------------------
  84. // I2C API
  85. // ---------------------------------------------------------------------
  86. #if I2C_USE_BRZO
  87. void i2c_wakeup(uint8_t address) {
  88. brzo_i2c_start_transaction(_address, _i2c_scl_frequency);
  89. brzo_i2c_end_transaction();
  90. }
  91. uint8_t i2c_write_uint8(uint8_t address, uint8_t value) {
  92. uint8_t buffer[1] = {value};
  93. brzo_i2c_start_transaction(_address, _i2c_scl_frequency);
  94. brzo_i2c_write_uint8(buffer, 1, false);
  95. return brzo_i2c_end_transaction();
  96. }
  97. uint8_t i2c_write_buffer(uint8_t address, uint8_t * buffer, size_t len) {
  98. brzo_i2c_start_transaction(_address, _i2c_scl_frequency);
  99. brzo_i2c_write_uint8(buffer, len, false);
  100. return brzo_i2c_end_transaction();
  101. }
  102. uint8_t i2c_read_uint8(uint8_t address) {
  103. uint8_t buffer[1] = {reg};
  104. brzo_i2c_start_transaction(_address, _i2c_scl_frequency);
  105. brzo_i2c_read(buffer, 1, false);
  106. brzo_i2c_end_transaction();
  107. return buffer[0];
  108. };
  109. uint8_t i2c_read_uint8(uint8_t address, uint8_t reg) {
  110. uint8_t buffer[1] = {reg};
  111. brzo_i2c_start_transaction(_address, _i2c_scl_frequency);
  112. brzo_i2c_write_uint8(buffer, 1, false);
  113. brzo_i2c_read(buffer, 1, false);
  114. brzo_i2c_end_transaction();
  115. return buffer[0];
  116. };
  117. uint16_t i2c_read_uint16(uint8_t address) {
  118. uint8_t buffer[2] = {reg, 0};
  119. brzo_i2c_start_transaction(_address, _i2c_scl_frequency);
  120. brzo_i2c_read(buffer, 2, false);
  121. brzo_i2c_end_transaction();
  122. return (buffer[0] * 256) | buffer[1];
  123. };
  124. uint16_t i2c_read_uint16(uint8_t address, uint8_t reg) {
  125. uint8_t buffer[2] = {reg, 0};
  126. brzo_i2c_start_transaction(_address, _i2c_scl_frequency);
  127. brzo_i2c_write_uint8(buffer, 1, false);
  128. brzo_i2c_read(buffer, 2, false);
  129. brzo_i2c_end_transaction();
  130. return (buffer[0] * 256) | buffer[1];
  131. };
  132. void i2c_read_buffer(uint8_t address, uint8_t * buffer, size_t len) {
  133. brzo_i2c_start_transaction(address, _i2c_scl_frequency);
  134. brzo_i2c_read(buffer, len, false);
  135. brzo_i2c_end_transaction();
  136. }
  137. #else // not I2C_USE_BRZO
  138. void i2c_wakeup(uint8_t address) {
  139. Wire.beginTransmission((uint8_t) address);
  140. Wire.endTransmission();
  141. }
  142. uint8_t i2c_write_uint8(uint8_t address, uint8_t value) {
  143. Wire.beginTransmission((uint8_t) address);
  144. Wire.write((uint8_t) value);
  145. return Wire.endTransmission();
  146. }
  147. uint8_t i2c_write_buffer(uint8_t address, uint8_t * buffer, size_t len) {
  148. Wire.beginTransmission((uint8_t) address);
  149. Wire.write(buffer, len);
  150. return Wire.endTransmission();
  151. }
  152. uint8_t i2c_read_uint8(uint8_t address) {
  153. uint8_t value;
  154. Wire.beginTransmission((uint8_t) address);
  155. Wire.requestFrom((uint8_t) address, (uint8_t) 1);
  156. value = Wire.read();
  157. Wire.endTransmission();
  158. return value;
  159. };
  160. uint8_t i2c_read_uint8(uint8_t address, uint8_t reg) {
  161. uint8_t value;
  162. Wire.beginTransmission((uint8_t) address);
  163. Wire.write((uint8_t) reg);
  164. Wire.endTransmission();
  165. Wire.requestFrom((uint8_t) address, (uint8_t) 1);
  166. value = Wire.read();
  167. Wire.endTransmission();
  168. return value;
  169. };
  170. uint16_t i2c_read_uint16(uint8_t address) {
  171. uint16_t value;
  172. Wire.beginTransmission((uint8_t) address);
  173. Wire.requestFrom((uint8_t) address, (uint8_t) 2);
  174. value = (Wire.read() * 256) | Wire.read();
  175. Wire.endTransmission();
  176. return value;
  177. };
  178. uint16_t i2c_read_uint16(uint8_t address, uint8_t reg) {
  179. uint16_t value;
  180. Wire.beginTransmission((uint8_t) address);
  181. Wire.write((uint8_t) reg);
  182. Wire.endTransmission();
  183. Wire.requestFrom((uint8_t) address, (uint8_t) 2);
  184. value = (Wire.read() * 256) | Wire.read();
  185. Wire.endTransmission();
  186. return value;
  187. };
  188. void i2c_read_buffer(uint8_t address, uint8_t * buffer, size_t len) {
  189. Wire.beginTransmission((uint8_t) address);
  190. Wire.requestFrom(address, (uint8_t) len);
  191. for (size_t i=0; i<len; i++) buffer[i] = Wire.read();
  192. Wire.endTransmission();
  193. }
  194. #endif // I2C_USE_BRZO
  195. uint8_t i2c_write_uint8(uint8_t address, uint8_t reg, uint8_t value) {
  196. uint8_t buffer[2] = {reg, value};
  197. return i2c_write_buffer(address, buffer, 2);
  198. }
  199. uint8_t i2c_write_uint8(uint8_t address, uint8_t reg, uint8_t value1, uint8_t value2) {
  200. uint8_t buffer[3] = {reg, value1, value2};
  201. return i2c_write_buffer(address, buffer, 3);
  202. }
  203. uint8_t i2c_write_uint16(uint8_t address, uint8_t reg, uint16_t value) {
  204. uint8_t buffer[3];
  205. buffer[0] = reg;
  206. buffer[1] = (value >> 8) & 0xFF;
  207. buffer[2] = (value >> 0) & 0xFF;
  208. return i2c_write_buffer(address, buffer, 3);
  209. }
  210. uint8_t i2c_write_uint16(uint8_t address, uint16_t value) {
  211. uint8_t buffer[2];
  212. buffer[0] = (value >> 8) & 0xFF;
  213. buffer[1] = (value >> 0) & 0xFF;
  214. return i2c_write_buffer(address, buffer, 2);
  215. }
  216. uint16_t i2c_read_uint16_le(uint8_t address, uint8_t reg) {
  217. uint16_t temp = i2c_read_uint16(address, reg);
  218. return (temp / 256) | (temp * 256);
  219. };
  220. int16_t i2c_read_int16(uint8_t address, uint8_t reg) {
  221. return (int16_t) i2c_read_uint16(address, reg);
  222. };
  223. int16_t i2c_read_int16_le(uint8_t address, uint8_t reg) {
  224. return (int16_t) i2c_read_uint16_le(address, reg);
  225. };
  226. // -----------------------------------------------------------------------------
  227. // Utils
  228. // -----------------------------------------------------------------------------
  229. void i2cClearBus() {
  230. DEBUG_MSG_P(
  231. PSTR("[I2C] Clear bus (response: %d)\n"),
  232. _i2cClearbus(_i2cGetSDA(), _i2cGetSCL())
  233. );
  234. }
  235. bool i2cCheck(unsigned char address) {
  236. #if I2C_USE_BRZO
  237. brzo_i2c_start_transaction(address, _i2c_scl_frequency);
  238. brzo_i2c_ACK_polling(1000);
  239. return brzo_i2c_end_transaction();
  240. #else
  241. Wire.beginTransmission(address);
  242. return Wire.endTransmission();
  243. #endif
  244. }
  245. bool i2cGetLock(unsigned char address) {
  246. unsigned char index = address / 8;
  247. unsigned char mask = 1 << (address % 8);
  248. if (_i2c_locked[index] & mask) return false;
  249. _i2c_locked[index] = _i2c_locked[index] | mask;
  250. DEBUG_MSG_P(PSTR("[I2C] Address 0x%02X locked\n"), address);
  251. return true;
  252. }
  253. bool i2cReleaseLock(unsigned char address) {
  254. unsigned char index = address / 8;
  255. unsigned char mask = 1 << (address % 8);
  256. if (_i2c_locked[index] & mask) {
  257. _i2c_locked[index] = _i2c_locked[index] & ~mask;
  258. return true;
  259. }
  260. return false;
  261. }
  262. unsigned char i2cFind(size_t size, unsigned char * addresses, unsigned char &start) {
  263. for (unsigned char i=start; i<size; i++) {
  264. if (i2cCheck(addresses[i]) == 0) {
  265. start = i;
  266. return addresses[i];
  267. }
  268. }
  269. return 0;
  270. }
  271. unsigned char i2cFind(size_t size, unsigned char * addresses) {
  272. unsigned char start = 0;
  273. return i2cFind(size, addresses, start);
  274. }
  275. unsigned char i2cFindAndLock(size_t size, unsigned char * addresses) {
  276. unsigned char start = 0;
  277. unsigned char address = 0;
  278. while ((address = i2cFind(size, addresses, start))) {
  279. if (i2cGetLock(address)) break;
  280. start++;
  281. }
  282. return address;
  283. }
  284. void i2cScan() {
  285. unsigned char nDevices = 0;
  286. for (unsigned char address = 1; address < 127; address++) {
  287. unsigned char error = i2cCheck(address);
  288. if (error == 0) {
  289. DEBUG_MSG_P(PSTR("[I2C] Device found at address 0x%02X\n"), address);
  290. nDevices++;
  291. }
  292. }
  293. if (nDevices == 0) DEBUG_MSG_P(PSTR("[I2C] No devices found\n"));
  294. }
  295. #if TERMINAL_SUPPORT
  296. void _i2cInitCommands() {
  297. terminalRegisterCommand(F("I2C.SCAN"), [](const terminal::CommandContext&) {
  298. i2cScan();
  299. terminalOK();
  300. });
  301. terminalRegisterCommand(F("I2C.CLEAR"), [](const terminal::CommandContext&) {
  302. i2cClearBus();
  303. terminalOK();
  304. });
  305. }
  306. #endif // TERMINAL_SUPPORT
  307. void i2cSetup() {
  308. const auto sda = _i2cGetSDA();
  309. const auto scl = _i2cGetSCL();
  310. #if I2C_USE_BRZO
  311. auto cst = getSetting("i2cCST", I2C_CLOCK_STRETCH_TIME);
  312. _i2c_scl_frequency = getSetting("i2cFreq", I2C_SCL_FREQUENCY);
  313. brzo_i2c_setup(sda, scl, cst);
  314. #else
  315. Wire.begin(sda, scl);
  316. #endif
  317. DEBUG_MSG_P(PSTR("[I2C] Using GPIO%02d for SDA and GPIO%02d for SCL\n"), sda, scl);
  318. #if TERMINAL_SUPPORT
  319. _i2cInitCommands();
  320. #endif
  321. #if I2C_CLEAR_BUS
  322. i2cClearBus();
  323. #endif
  324. #if I2C_PERFORM_SCAN
  325. i2cScan();
  326. #endif
  327. }
  328. #endif