SCD40.c 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. /*
  2. Unitemp - Universal temperature reader
  3. Copyright (C) 2022-2023 Victor Nikitchuk (https://github.com/quen0n)
  4. Contributed by divinebird (https://github.com/divinebird)
  5. This program is free software: you can redistribute it and/or modify
  6. it under the terms of the GNU General Public License as published by
  7. the Free Software Foundation, either version 3 of the License, or
  8. (at your option) any later version.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU General Public License for more details.
  13. You should have received a copy of the GNU General Public License
  14. along with this program. If not, see <https://www.gnu.org/licenses/>.
  15. */
  16. // Some information may be seen on https://github.com/sparkfun/SparkFun_SCD30_Arduino_Library
  17. #include "SCD30.h"
  18. #include "../interfaces/I2CSensor.h"
  19. #include "../interfaces/endianness.h"
  20. //#include <3rdparty/everest/include/everest/kremlin/c_endianness.h>
  21. bool unitemp_SCD40_alloc(Sensor* sensor, char* args);
  22. bool unitemp_SCD40_init(Sensor* sensor);
  23. bool unitemp_SCD40_deinit(Sensor* sensor);
  24. UnitempStatus unitemp_SCD40_update(Sensor* sensor);
  25. bool unitemp_SCD40_free(Sensor* sensor);
  26. const SensorType SCD40 = {
  27. .typename = "SCD40",
  28. .interface = &I2C,
  29. .datatype = UT_DATA_TYPE_TEMP_HUM_CO2,
  30. .pollingInterval = 5000,
  31. .allocator = unitemp_SCD40_alloc,
  32. .mem_releaser = unitemp_SCD40_free,
  33. .initializer = unitemp_SCD40_init,
  34. .deinitializer = unitemp_SCD40_deinit,
  35. .updater = unitemp_SCD40_update};
  36. #define SCD40_ID 0x62
  37. #define COMMAND_START_PERIODIC_MEASUREMENT 0X21B1
  38. #define COMMAND_READ_MEASUREMENT 0XEC05
  39. #define COMMAND_STOP_PERIODIC_MEASUREMENT 0X3F86
  40. #define COMMAND_PERSIST_SETTINGS 0X3615
  41. #define COMMAND_GET_SERIAL_NUMBER 0X3682
  42. #define COMMAND_PERFORM_SELF_TEST 0X3639
  43. #define COMMAND_PERFORM_FACTORY_RESET 0X3632
  44. #define COMMAND_REINIT 0X3646
  45. #define COMMAND_SET_TEMPERATURE_OFFSET 0X241D
  46. #define COMMAND_GET_TEMPERATURE_OFFSET 0X2318
  47. #define COMMAND_SET_SENSOR_ALTITUDE 0X2427
  48. #define COMMAND_GET_SENSOR_ALTITUDE 0X2322
  49. #define COMMAND_SET_AMBIENT_PRESSURE 0XE000
  50. #define COMMAND_PERFORM_FORCED_RECALIBRATION 0X362F
  51. #define COMMAND_SET_AUTOMATIC_SELF_CALIBRATION_ENABLED 0X2416
  52. #define COMMAND_GET_AUTOMATIC_SELF_CALIBRATION_ENABLED 0X2313
  53. static bool readMeasurement(Sensor* sensor) __attribute__((unused));
  54. static void reset(Sensor* sensor) __attribute__((unused));
  55. static bool setAutoSelfCalibration(Sensor* sensor, bool enable) __attribute__((unused));
  56. static bool getAutoSelfCalibration(Sensor* sensor) __attribute__((unused));
  57. static bool getFirmwareVersion(Sensor* sensor, uint16_t* val) __attribute__((unused));
  58. static float getTemperatureOffset(Sensor* sensor) __attribute__((unused));
  59. static bool setTemperatureOffset(Sensor* sensor, float tempOffset) __attribute__((unused));
  60. static bool beginMeasuring(Sensor* sensor) __attribute__((unused));
  61. static bool stopMeasurement(Sensor* sensor) __attribute__((unused));
  62. bool unitemp_SCD40_alloc(Sensor* sensor, char* args) {
  63. UNUSED(args);
  64. I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance;
  65. i2c_sensor->minI2CAdr = SCD40_ID << 1;
  66. i2c_sensor->maxI2CAdr = SCD40_ID << 1;
  67. return true;
  68. }
  69. bool unitemp_SCD40_free(Sensor* sensor) {
  70. //Нечего высвобождать, так как ничего не было выделено
  71. UNUSED(sensor);
  72. return true;
  73. }
  74. bool unitemp_SCD40_init(Sensor* sensor) {
  75. return beginMeasuring(sensor);
  76. }
  77. bool unitemp_SCD40_deinit(Sensor* sensor) {
  78. return stopMeasurement(sensor);
  79. }
  80. UnitempStatus unitemp_SCD40_update(Sensor* sensor) {
  81. readMeasurement(sensor);
  82. return UT_SENSORSTATUS_OK;
  83. }
  84. #define CRC8_POLYNOMIAL 0x31
  85. #define CRC8_INIT 0xFF
  86. static uint8_t computeCRC8(uint8_t* message, uint8_t len) {
  87. uint8_t crc = CRC8_INIT; // Init with 0xFF
  88. for(uint8_t x = 0; x < len; x++) {
  89. crc ^= message[x]; // XOR-in the next input byte
  90. for(uint8_t i = 0; i < 8; i++) {
  91. if((crc & 0x80) != 0)
  92. crc = (uint8_t)((crc << 1) ^ CRC8_POLYNOMIAL);
  93. else
  94. crc <<= 1;
  95. }
  96. }
  97. return crc; // No output reflection
  98. }
  99. // Sends a command along with arguments and CRC
  100. static bool sendCommandWithCRC(Sensor* sensor, uint16_t command, uint16_t arguments) {
  101. static const uint8_t cmdSize = 5;
  102. uint8_t bytes[cmdSize];
  103. uint8_t* pointer = bytes;
  104. store16_be(pointer, command);
  105. pointer += 2;
  106. uint8_t* argPos = pointer;
  107. store16_be(pointer, arguments);
  108. pointer += 2;
  109. *pointer = computeCRC8(argPos, pointer - argPos);
  110. I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance;
  111. return unitemp_i2c_writeArray(i2c_sensor, cmdSize, bytes);
  112. }
  113. // Sends just a command, no arguments, no CRC
  114. static bool sendCommand(Sensor* sensor, uint16_t command) {
  115. static const uint8_t cmdSize = 2;
  116. uint8_t bytes[cmdSize];
  117. store16_be(bytes, command);
  118. I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance;
  119. return unitemp_i2c_writeArray(i2c_sensor, cmdSize, bytes);
  120. }
  121. static uint16_t readRegister(Sensor* sensor, uint16_t registerAddress) {
  122. static const uint8_t regSize = 2;
  123. if(!sendCommand(sensor, registerAddress)) return 0; // Sensor did not ACK
  124. furi_delay_ms(3);
  125. uint8_t bytes[regSize];
  126. I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance;
  127. if(!unitemp_i2c_readArray(i2c_sensor, regSize, bytes)) return 0;
  128. return load16_be(bytes);
  129. }
  130. static bool loadWord(uint8_t* buff, uint16_t* val) {
  131. uint16_t tmp = load16_be(buff);
  132. uint8_t expectedCRC = computeCRC8(buff, 2);
  133. if(buff[2] != expectedCRC) return false;
  134. *val = tmp;
  135. return true;
  136. }
  137. static bool getSettingValue(Sensor* sensor, uint16_t registerAddress, uint16_t* val) {
  138. static const uint8_t respSize = 3;
  139. if(!sendCommand(sensor, registerAddress)) return false; // Sensor did not ACK
  140. furi_delay_ms(3);
  141. uint8_t bytes[respSize];
  142. I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance;
  143. if(!unitemp_i2c_readArray(i2c_sensor, respSize, bytes)) return false;
  144. return loadWord(bytes, val);
  145. }
  146. // Get 18 bytes from SCD30
  147. // Updates global variables with floats
  148. // Returns true if success
  149. static bool readMeasurement(Sensor* sensor) {
  150. if(!sendCommand(sensor, COMMAND_READ_MEASUREMENT)) {
  151. FURI_LOG_E(APP_NAME, "Sensor did not ACK");
  152. return false; // Sensor did not ACK
  153. }
  154. furi_delay_ms(3);
  155. static const uint8_t respSize = 9;
  156. uint8_t buff[respSize];
  157. uint8_t* bytes = buff;
  158. I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance;
  159. if(!unitemp_i2c_readArray(i2c_sensor, respSize, bytes)) {
  160. FURI_LOG_E(APP_NAME, "Error while read measures");
  161. return false;
  162. }
  163. uint16_t tmpValue;
  164. bool error = false;
  165. if(loadWord(bytes, &tmpValue)) {
  166. sensor->co2 = tmpValue;
  167. } else {
  168. FURI_LOG_E(APP_NAME, "Error while parsing CO2");
  169. error = true;
  170. }
  171. bytes += 3;
  172. if(loadWord(bytes, &tmpValue)) {
  173. sensor->temp = (float)tmpValue * 175.0f / 65535.0f - 45.0f;
  174. } else {
  175. FURI_LOG_E(APP_NAME, "Error while parsing temp");
  176. error = true;
  177. }
  178. bytes += 3;
  179. if(loadWord(bytes, &tmpValue)) {
  180. sensor->hum = (float)tmpValue * 100.0f / 65535.0f;
  181. } else {
  182. FURI_LOG_E(APP_NAME, "Error while parsing humidity");
  183. error = true;
  184. }
  185. return !error;
  186. }
  187. static void reset(Sensor* sensor) {
  188. sendCommand(sensor, COMMAND_REINIT);
  189. }
  190. static bool setAutoSelfCalibration(Sensor* sensor, bool enable) {
  191. return sendCommandWithCRC(
  192. sensor, COMMAND_SET_AUTOMATIC_SELF_CALIBRATION_ENABLED, enable); // Activate continuous ASC
  193. }
  194. // Get the current ASC setting
  195. static bool getAutoSelfCalibration(Sensor* sensor) {
  196. return 1 == readRegister(sensor, COMMAND_GET_AUTOMATIC_SELF_CALIBRATION_ENABLED);
  197. }
  198. // Unfinished
  199. static bool getFirmwareVersion(Sensor* sensor, uint16_t* val) {
  200. if(!sendCommand(sensor, COMMAND_READ_MEASUREMENT)) {
  201. FURI_LOG_E(APP_NAME, "Sensor did not ACK");
  202. return false; // Sensor did not ACK
  203. }
  204. static const uint8_t respSize = 9;
  205. uint8_t buff[respSize];
  206. uint8_t* bytes = buff;
  207. I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance;
  208. if(!unitemp_i2c_readArray(i2c_sensor, respSize, bytes)) {
  209. FURI_LOG_E(APP_NAME, "Error while read measures");
  210. return false;
  211. }
  212. *val = 0;
  213. return true;
  214. }
  215. static bool beginMeasuring(Sensor* sensor) {
  216. return sendCommand(sensor, COMMAND_START_PERIODIC_MEASUREMENT);
  217. }
  218. // Stop continuous measurement
  219. static bool stopMeasurement(Sensor* sensor) {
  220. return sendCommand(sensor, COMMAND_READ_MEASUREMENT);
  221. }
  222. static float getTemperatureOffset(Sensor* sensor) {
  223. uint16_t curOffset;
  224. if(!getSettingValue(sensor, COMMAND_GET_TEMPERATURE_OFFSET, &curOffset)) return 0.0;
  225. return (float)curOffset * 175.0f / 65536.0f;
  226. }
  227. static bool setTemperatureOffset(Sensor* sensor, float tempOffset) {
  228. uint16_t newOffset = tempOffset * 65536.0 / 175.0 + 0.5f;
  229. return sendCommandWithCRC(
  230. sensor, COMMAND_SET_TEMPERATURE_OFFSET, newOffset); // Activate continuous ASC
  231. }