AS7331.cpp 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149
  1. /**
  2. * @file AS7331.cpp
  3. * @brief Implementation of AS7331 UV Spectral Sensor Driver
  4. */
  5. #include "AS7331.hpp"
  6. #include <furi_hal_i2c.h>
  7. #include <furi_hal.h>
  8. #include <cmath> // For pow()
  9. // Prevent compiler optimization, for debugging.
  10. // #pragma GCC optimize("O0")
  11. // Timeout value for I2C operations (in milliseconds)
  12. #define AS7331_I2C_TIMEOUT 100
  13. // Constructor
  14. AS7331::AS7331(uint8_t i2c_address_7bit)
  15. // Convert 7-bit to 8-bit address
  16. : _i2c_addr_8bit(i2c_address_7bit << 1)
  17. // Initialize to invalid mode to force first-time setup
  18. , _deviceMode(static_cast<as7331_device_mode_t>(0x00))
  19. , _power_down(true)
  20. , _gain(GAIN_2)
  21. , _integration_time(TIME_64MS)
  22. , _enable_divider(false)
  23. , _divider(DIV_2)
  24. , _clock_frequency(CCLK_1_024_MHZ)
  25. , _standby(false)
  26. , _measurement_mode(MEASUREMENT_MODE_COMMAND) {
  27. }
  28. // Initialize the sensor
  29. bool AS7331::init(const uint8_t& i2c_address_7bit) {
  30. uint8_t detected_address_7bit = 0;
  31. if(i2c_address_7bit == 0x0) {
  32. // Scan I2C bus for AS7331 device
  33. detected_address_7bit = scan_i2c_bus();
  34. if(detected_address_7bit == 0x0) {
  35. FURI_LOG_E(
  36. "AS7331",
  37. "Failed to find an AS7331 device with one of the four valid addresses on the I2C bus.");
  38. return false;
  39. }
  40. } else {
  41. if(deviceReady(i2c_address_7bit)) {
  42. detected_address_7bit = i2c_address_7bit;
  43. } else {
  44. FURI_LOG_E("AS7331", "No device found at I2C address 0x%02X.", i2c_address_7bit);
  45. return false;
  46. }
  47. }
  48. // Set the I2C address (7-Bit to 8-Bit)
  49. _i2c_addr_8bit = detected_address_7bit << 1;
  50. // Wake up the device from power-down mode
  51. if(!setPowerDown(false)) {
  52. FURI_LOG_E("AS7331", "Failed to wake up the device from power-down mode.");
  53. return false;
  54. }
  55. // Set the device into configuration mode
  56. furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
  57. bool success = setDeviceMode(DEVICE_MODE_CONFIG);
  58. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  59. if(!success) {
  60. FURI_LOG_E("AS7331", "Failed to set the device into configuration mode.");
  61. return false;
  62. }
  63. // Populate local configuration variables from device registers
  64. if(!updateLocalConfig()) {
  65. FURI_LOG_E("AS7331", "Failed to read device configuration registers.");
  66. return false;
  67. }
  68. // Read the device ID
  69. as7331_agen_reg_t deviceID;
  70. if(!getDeviceID(deviceID)) {
  71. FURI_LOG_E("AS7331", "Failed to read device ID.");
  72. return false;
  73. }
  74. // Compare with expected Device ID
  75. if(deviceID.byte != ExpectedAGENContent) {
  76. FURI_LOG_E(
  77. "AS7331",
  78. "Device ID mismatch: expected 0x%02X, got 0x%02X.",
  79. ExpectedAGENContent,
  80. deviceID.byte);
  81. return false;
  82. }
  83. return true;
  84. }
  85. // Set sensor gain (CREG1:GAIN)
  86. bool AS7331::setGain(const as7331_gain_t& gain) {
  87. if(gain > GAIN_1) {
  88. FURI_LOG_E("AS7331", "Invalid gain value: %d.", gain);
  89. return false;
  90. }
  91. as7331_creg1_reg_t creg1;
  92. furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
  93. // Ensure we are in configuration mode
  94. if(!setDeviceMode(DEVICE_MODE_CONFIG)) {
  95. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  96. FURI_LOG_E("AS7331", "Failed set device mode to configuration.");
  97. return false;
  98. }
  99. // Read CREG1
  100. if(!readRegister(RegCfgCreg1, creg1.byte)) {
  101. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  102. FURI_LOG_E("AS7331", "Failed to read CREG1 register from device.");
  103. return false;
  104. }
  105. // Write new gain value
  106. creg1.gain = gain;
  107. if(!writeRegister(RegCfgCreg1, creg1.byte)) {
  108. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  109. FURI_LOG_E("AS7331", "Failed to write gain value to device: %d.", creg1.gain);
  110. return false;
  111. }
  112. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  113. // Update local config
  114. _gain = gain;
  115. return true;
  116. }
  117. // Set integration time (CREG1:TIME)
  118. bool AS7331::setIntegrationTime(const as7331_integration_time_t& time) {
  119. // Validate integration time value
  120. if(time > TIME_16384MS) {
  121. FURI_LOG_E("AS7331", "Invalid integration time value: %d.", time);
  122. return false;
  123. }
  124. // Automatically set divider depending on ADC resolution to prevent overflow
  125. // Since this voids advantages of higher integration times, remove once overflow checking is implemented.
  126. /*
  127. int adc_resolution;
  128. if(time == 15) {
  129. // Special case: Same as TIME 0
  130. adc_resolution = 10;
  131. } else {
  132. adc_resolution = 10 + time;
  133. }
  134. if(adc_resolution > 16) {
  135. as7331_divider_t divider = static_cast<as7331_divider_t>(adc_resolution - 17);
  136. setDivider(divider, true);
  137. } else if(_enable_divider) {
  138. setDivider(DIV_2, false);
  139. }
  140. */
  141. as7331_creg1_reg_t creg1;
  142. furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
  143. // Ensure we are in configuration mode
  144. if(!setDeviceMode(DEVICE_MODE_CONFIG)) {
  145. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  146. FURI_LOG_E("AS7331", "Failed to set device mode to configuration.");
  147. return false;
  148. }
  149. // Read CREG1
  150. if(!readRegister(RegCfgCreg1, creg1.byte)) {
  151. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  152. FURI_LOG_E("AS7331", "Failed to read CREG1 register from device.");
  153. return false;
  154. }
  155. // Write new integration time value
  156. creg1.integration_time = time;
  157. if(!writeRegister(RegCfgCreg1, creg1.byte)) {
  158. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  159. FURI_LOG_E(
  160. "AS7331",
  161. "Failed to write integration time value to device: %d.",
  162. creg1.integration_time);
  163. return false;
  164. }
  165. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  166. // Update local config
  167. _integration_time = time;
  168. return true;
  169. }
  170. // Set output divider (CREG2:DIV)
  171. bool AS7331::setDivider(const as7331_divider_t& divider, const bool enable) {
  172. // Validate divider value
  173. if(divider > DIV_256) {
  174. FURI_LOG_E("AS7331", "Invalid divider value: %d.", divider);
  175. return false;
  176. }
  177. as7331_creg2_reg_t creg2;
  178. furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
  179. // Ensure we are in configuration mode
  180. if(!setDeviceMode(DEVICE_MODE_CONFIG)) {
  181. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  182. FURI_LOG_E("AS7331", "Failed to set device mode to configuration.");
  183. return false;
  184. }
  185. // Read CREG2
  186. if(!readRegister(RegCfgCreg2, creg2.byte)) {
  187. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  188. FURI_LOG_E("AS7331", "Failed to read CREG2 register from device.");
  189. return false;
  190. }
  191. // Write new divider value and enable/disable it
  192. creg2.divider = divider;
  193. creg2.enable_divider = enable;
  194. if(!writeRegister(RegCfgCreg2, creg2.byte)) {
  195. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  196. FURI_LOG_E(
  197. "AS7331",
  198. "Failed to write divider settings to device: DIV=%d, ENABLE=%d.",
  199. creg2.divider,
  200. creg2.enable_divider);
  201. return false;
  202. }
  203. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  204. // Update local config
  205. _enable_divider = enable;
  206. _divider = divider;
  207. return true;
  208. }
  209. // Set clock frequency (CREG3:CCLK)
  210. bool AS7331::setClockFrequency(const as7331_clock_frequency_t& frequency) {
  211. // Validate clock frequency value
  212. if(frequency > CCLK_8_192_MHZ) {
  213. FURI_LOG_E("AS7331", "Invalid clock frequency value: %d.", frequency);
  214. return false;
  215. }
  216. as7331_creg3_reg_t creg3;
  217. furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
  218. // Ensure we are in configuration mode
  219. if(!setDeviceMode(DEVICE_MODE_CONFIG)) {
  220. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  221. FURI_LOG_E("AS7331", "Failed to set device mode to configuration.");
  222. return false;
  223. }
  224. // Read CREG3
  225. if(!readRegister(RegCfgCreg3, creg3.byte)) {
  226. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  227. FURI_LOG_E("AS7331", "Failed to read CREG3 register from device.");
  228. return false;
  229. }
  230. // Write new clock frequency value
  231. creg3.clock_frequency = frequency;
  232. if(!writeRegister(RegCfgCreg3, creg3.byte)) {
  233. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  234. FURI_LOG_E(
  235. "AS7331",
  236. "Failed to write clock frequency value to device: %d.",
  237. creg3.clock_frequency);
  238. return false;
  239. }
  240. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  241. // Update local config
  242. _clock_frequency = frequency;
  243. return true;
  244. }
  245. // Set measurement mode (CREG3:MMODE)
  246. bool AS7331::setMeasurementMode(const as7331_measurement_mode_t& mode) {
  247. // Validate measurement mode value
  248. if(mode > MEASUREMENT_MODE_SYNC_START_END) {
  249. FURI_LOG_E("AS7331", "Invalid measurement mode value: %d.", mode);
  250. return false;
  251. }
  252. as7331_creg3_reg_t creg3;
  253. furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
  254. // Ensure we are in configuration mode
  255. if(!setDeviceMode(DEVICE_MODE_CONFIG)) {
  256. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  257. FURI_LOG_E("AS7331", "Failed to set device mode to configuration.");
  258. return false;
  259. }
  260. // Read CREG3
  261. if(!readRegister(RegCfgCreg3, creg3.byte)) {
  262. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  263. FURI_LOG_E("AS7331", "Failed to read CREG3 register from device.");
  264. return false;
  265. }
  266. // Write new measurement mode value
  267. creg3.measurement_mode = mode;
  268. if(!writeRegister(RegCfgCreg3, creg3.byte)) {
  269. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  270. FURI_LOG_E(
  271. "AS7331", "Failed to write measurement mode to device: %d.", creg3.measurement_mode);
  272. return false;
  273. }
  274. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  275. // Update local config
  276. _measurement_mode = mode;
  277. return true;
  278. }
  279. // Enable or disable power down state (OSR:PD)
  280. bool AS7331::setPowerDown(const bool& power_down) {
  281. // The power-down feature affects both operational states: configuration and measurement.
  282. // When the power-down state is activated while the device is in a measurement cycle,
  283. // the power-down action is only executed during the intervals between consecutive conversions.
  284. // Power Down Current Consumption: max 1µA
  285. // Startup Time after Power Down state: 1.2-2ms
  286. as7331_osr_reg_t osr;
  287. furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
  288. // OSR is accessible in any mode
  289. // Read OSR register
  290. if(!readOSRRegister(osr)) {
  291. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  292. FURI_LOG_E("AS7331", "Failed to read OSR register from device.");
  293. return false;
  294. }
  295. // Check if the desired power down state is already set
  296. if(osr.power_down != power_down) {
  297. osr.power_down = power_down; // Set PD bit
  298. // Write back the OSR register
  299. // Local configuration (_power_down) is updated in writeOSRRegister
  300. if(!writeOSRRegister(osr)) {
  301. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  302. FURI_LOG_E("AS7331", "Failed to write power down state to device: %d.", power_down);
  303. return false;
  304. }
  305. }
  306. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  307. return true;
  308. }
  309. // Put the sensor in standby mode or wake it up (CREG3:SB)
  310. bool AS7331::setStandby(const bool& standby) {
  311. // Standby Current Consumption: max 970 µA
  312. // Startup Time after Standby state: 4-5 µs
  313. as7331_creg3_reg_t creg3;
  314. furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
  315. // Ensure we are in configuration mode
  316. if(!setDeviceMode(DEVICE_MODE_CONFIG)) {
  317. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  318. FURI_LOG_E("AS7331", "Failed to set device mode to configuration.");
  319. return false;
  320. }
  321. // Read CREG3
  322. if(!readRegister(RegCfgCreg3, creg3.byte)) {
  323. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  324. FURI_LOG_E("AS7331", "Failed to read CREG3 register from device.");
  325. return false;
  326. }
  327. // Check if the desired standby state is already set
  328. if(creg3.standby != standby) {
  329. creg3.standby = standby; // Set SB bit
  330. // Write back the CREG3 register
  331. if(!writeRegister(RegCfgCreg3, creg3.byte)) {
  332. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  333. FURI_LOG_E("AS7331", "Failed to write standby state to device: %d.", standby);
  334. return false;
  335. }
  336. }
  337. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  338. // Update local config
  339. _standby = standby;
  340. return true;
  341. }
  342. // Start the measurement (OSR:SS = 1)
  343. bool AS7331::startMeasurement() {
  344. as7331_osr_reg_t osr;
  345. furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
  346. // Ensure we are in measurement mode
  347. if(!setDeviceMode(DEVICE_MODE_MEASURE)) {
  348. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  349. FURI_LOG_E("AS7331", "Failed to set device mode to measurement.");
  350. return false;
  351. }
  352. // Read OSR register
  353. if(!readOSRRegister(osr)) {
  354. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  355. FURI_LOG_E("AS7331", "Failed to read OSR register from device.");
  356. return false;
  357. }
  358. // Check if measurement is already started
  359. if(osr.start_state != 1) {
  360. osr.start_state = 1; // Set SS bit to start measurement
  361. // Write back OSR register
  362. if(!writeOSRRegister(osr)) {
  363. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  364. FURI_LOG_E("AS7331", "Failed to write OSR register to start measurement.");
  365. return false;
  366. }
  367. }
  368. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  369. return true;
  370. }
  371. // Stop the measurement (OSR:SS = 0)
  372. bool AS7331::stopMeasurement() {
  373. as7331_osr_reg_t osr;
  374. furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
  375. // Read OSR register
  376. if(!readOSRRegister(osr)) {
  377. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  378. FURI_LOG_E("AS7331", "Failed to read OSR register from device.");
  379. return false;
  380. }
  381. // Check if measurement is already stopped
  382. if(osr.start_state != 0) {
  383. osr.start_state = 0; // Clear SS bit to stop measurement
  384. // Write back OSR register
  385. if(!writeOSRRegister(osr)) {
  386. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  387. FURI_LOG_E("AS7331", "Failed to write OSR register to stop measurement.");
  388. return false;
  389. }
  390. }
  391. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  392. return true;
  393. }
  394. // Wait for measurement to complete based on conversion time
  395. void AS7331::waitForMeasurement() {
  396. double TCONV = getConversionTime();
  397. if(TCONV > 0) {
  398. // Wait for TCONV time, adding a small buffer
  399. furi_delay_ms(static_cast<uint32_t>(TCONV * 1000) + 1); // Add 1 ms to ensure completion
  400. }
  401. }
  402. // Get raw measurement results
  403. bool AS7331::getRawResults(RawResults& rawResults) {
  404. uint8_t buffer[6]; // Each result is 2 bytes, so 3 results * 2 bytes = 6 bytes
  405. furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
  406. // Ensure we are in measurement mode
  407. if(!setDeviceMode(DEVICE_MODE_MEASURE)) {
  408. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  409. FURI_LOG_E("AS7331", "Failed to set device mode to measurement.");
  410. return false;
  411. }
  412. // Read all measurement results in one I²C transaction
  413. if(!readRegisters(RegMeasResultA, buffer, 6)) {
  414. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  415. FURI_LOG_E("AS7331", "Failed to read measurement results from device.");
  416. return false;
  417. }
  418. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  419. // Combine the bytes into 16-bit values (LSB first)
  420. rawResults.uv_a = (static_cast<uint16_t>(buffer[1]) << 8) | buffer[0];
  421. rawResults.uv_b = (static_cast<uint16_t>(buffer[3]) << 8) | buffer[2];
  422. rawResults.uv_c = (static_cast<uint16_t>(buffer[5]) << 8) | buffer[4];
  423. return true;
  424. }
  425. // Get processed measurement results with raw data
  426. bool AS7331::getResults(Results& results, RawResults& rawResults) {
  427. bool success = true;
  428. // Get the raw measurement results
  429. success &= getRawResults(rawResults);
  430. if(!success) {
  431. return false;
  432. }
  433. // Calculate FSREe for each channel
  434. double FSREe_A = calculateFSREe(UV_A, false);
  435. double FSREe_B = calculateFSREe(UV_B, false);
  436. double FSREe_C = calculateFSREe(UV_C, false);
  437. FURI_LOG_I("AS7331", "FSREe_A: %f", FSREe_B);
  438. FURI_LOG_I("AS7331", "FSREe_B: %f", FSREe_C);
  439. FURI_LOG_I("AS7331", "FSREe_C: %f", FSREe_C);
  440. if(FSREe_A < 0 || FSREe_B < 0 || FSREe_C < 0) {
  441. // Invalid FSREe calculation
  442. return false;
  443. }
  444. // Adjust FSREe for clock frequency
  445. // Since FSREe is inversely proportional to fCLK,
  446. // we adjust FSREe by dividing by the clock frequency ratio
  447. double fCLK_ratio = static_cast<double>(1 << _clock_frequency); // fCLK_actual / fCLK_base
  448. FSREe_A /= fCLK_ratio;
  449. FSREe_B /= fCLK_ratio;
  450. FSREe_C /= fCLK_ratio;
  451. // Get NCLK from TIME_code
  452. uint32_t NCLK;
  453. if(_integration_time == 15) {
  454. NCLK = 1024;
  455. } else if(_integration_time <= 14) {
  456. NCLK = 1 << (10 + _integration_time);
  457. } else {
  458. return false; // Invalid TIME_code
  459. }
  460. // Adjust NCLK and FSREe for Divider if enabled
  461. if(_enable_divider) {
  462. int divider_factor = 1 << (_divider + 1); // Divider factor is 2^(DIV + 1)
  463. NCLK /= divider_factor; // Adjust NCLK
  464. // Alternatively, we could adjust MRES, but adjusting NCLK is equivalent and simpler
  465. }
  466. // Calculate LSB for each channel
  467. double LSB_A = FSREe_A / NCLK;
  468. double LSB_B = FSREe_B / NCLK;
  469. double LSB_C = FSREe_C / NCLK;
  470. // Calculate Ee (Irradiance in µW/cm²) for each channel
  471. results.uv_a = rawResults.uv_a * LSB_A;
  472. results.uv_b = rawResults.uv_b * LSB_B;
  473. results.uv_c = rawResults.uv_c * LSB_C;
  474. return true;
  475. }
  476. // Get processed measurement results
  477. bool AS7331::getResults(Results& results) {
  478. RawResults rawResults; // Temporary variable
  479. return getResults(results, rawResults); // Call the core function
  480. }
  481. // Read temperature measurement (16-bit)
  482. bool AS7331::getTemperature(double& temperature) {
  483. uint16_t temperature_raw;
  484. furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
  485. // Ensure we are in measurement mode
  486. if(!setDeviceMode(DEVICE_MODE_MEASURE)) {
  487. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  488. FURI_LOG_E("AS7331", "Failed to set device mode to measurement.");
  489. return false;
  490. }
  491. // Read temperature register
  492. if(!readRegister16(RegMeasTemp, temperature_raw)) {
  493. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  494. FURI_LOG_E("AS7331", "Failed to read temperature register from device.");
  495. return false;
  496. }
  497. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  498. // Calculate temperature in Celsius (Datasheet p. 42)
  499. temperature = temperature_raw * 0.05 - 66.9; // [°C]
  500. return true;
  501. }
  502. // Check if the device is ready
  503. bool AS7331::deviceReady(const uint8_t& i2c_address_7bit) {
  504. uint8_t i2c_address_8bit;
  505. if(i2c_address_7bit == 0) {
  506. i2c_address_8bit = _i2c_addr_8bit;
  507. } else {
  508. i2c_address_8bit = i2c_address_7bit << 1; // Convert 7-bit to 8-bit address
  509. }
  510. furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
  511. bool ready = furi_hal_i2c_is_device_ready(
  512. &furi_hal_i2c_handle_external, i2c_address_8bit, AS7331_I2C_TIMEOUT);
  513. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  514. return ready;
  515. }
  516. // Perform a software reset (OSR:SW_RES = 1)
  517. bool AS7331::reset() {
  518. // Setting SW_RES to '1' triggers a software reset of the AS7331.
  519. as7331_osr_reg_t osr;
  520. furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
  521. // OSR is accessible in any mode
  522. if(!readOSRRegister(osr)) {
  523. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  524. FURI_LOG_E("AS7331", "Failed to read OSR register from device.");
  525. return false;
  526. }
  527. osr.software_reset = 1; // Set SW_RES bit
  528. if(!writeOSRRegister(osr)) {
  529. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  530. FURI_LOG_E("AS7331", "Failed to write OSR register to reset device.");
  531. return false;
  532. }
  533. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  534. // Update local configuration variables from device registers
  535. _deviceMode = DEVICE_MODE_CONFIG;
  536. if(!updateLocalConfig()) {
  537. FURI_LOG_E("AS7331", "Failed to read device configuration registers.");
  538. return false;
  539. }
  540. return true;
  541. }
  542. // Getter methods
  543. // Read device ID (including mutation number)
  544. bool AS7331::getDeviceID(as7331_agen_reg_t& deviceID) {
  545. furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
  546. if(!readRegister(RegCfgAgen, deviceID.byte)) {
  547. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  548. FURI_LOG_E("AS7331", "Failed to read device ID register from device.");
  549. return false;
  550. }
  551. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  552. return true;
  553. }
  554. // Read status register in measurement mode
  555. bool AS7331::getStatus(as7331_osr_status_reg_t& status) {
  556. furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
  557. // Ensure we are in measurement mode
  558. if(!setDeviceMode(DEVICE_MODE_MEASURE)) {
  559. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  560. FURI_LOG_E("AS7331", "Failed to set device mode to measurement.");
  561. return false;
  562. }
  563. if(!readRegister16(RegMeasOsrStatus, status.word)) {
  564. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  565. FURI_LOG_E("AS7331", "Failed to read status register from device.");
  566. return false;
  567. }
  568. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  569. return true;
  570. }
  571. // Local config getter methods
  572. // Getter for gain code
  573. as7331_gain_t AS7331::getGain() const {
  574. return _gain;
  575. }
  576. // Getter for actual gain value
  577. int16_t AS7331::getGainValue() const {
  578. // Gain value is calculated using the formula: gain = 2^(11 - gain_code)
  579. return 1 << (11 - _gain);
  580. }
  581. // Getter for integration time code
  582. as7331_integration_time_t AS7331::getIntegrationTime() const {
  583. return _integration_time;
  584. }
  585. // Get conversion time in seconds
  586. double AS7331::getConversionTime() const {
  587. // Base clock frequency is 1.024 MHz
  588. double fCLK_base = 1.024e6; // Hz
  589. // Actual clock frequency based on CCLK code
  590. double fCLK = fCLK_base * (1 << _clock_frequency);
  591. // Calculate NCLK based on TIME code
  592. uint32_t NCLK; // Number of clock cycles within conversion time
  593. if(_integration_time == 15) {
  594. NCLK = 1024; // Special case: Same as TIME 0
  595. } else if(_integration_time <= 14) {
  596. NCLK = 1 << (10 + _integration_time);
  597. } else {
  598. // Invalid TIME code
  599. return -1.0;
  600. }
  601. // Conversion time TCONV
  602. double TCONV = static_cast<double>(NCLK) / fCLK; // in seconds
  603. return TCONV;
  604. }
  605. // Getter for divider code
  606. as7331_divider_t AS7331::getDivider() const {
  607. return _divider;
  608. }
  609. // Getter for divider enabled state
  610. bool AS7331::isDividerEnabled() const {
  611. return _enable_divider;
  612. }
  613. // Getter for actual divider value
  614. uint8_t AS7331::getDividerValue() const {
  615. // Divider value is calculated using the formula: divider = 2^(1 + divider_code)
  616. uint8_t divider_value = 1 << (1 + _divider);
  617. return divider_value;
  618. }
  619. // Getter for clock frequency code
  620. as7331_clock_frequency_t AS7331::getClockFrequency() const {
  621. return _clock_frequency;
  622. }
  623. // Getter for actual clock frequency value
  624. double AS7331::getClockFrequencyValue() const {
  625. // Clock frequency is calculated using the formula: fCLK = 1.024 * 2^clock_frequency_code (in MHz)
  626. uint32_t freq_multiplier = 1 << _clock_frequency; // 2^clock_frequency_code
  627. double clock_frequency_value = 1.024 * freq_multiplier; // in MHz
  628. return clock_frequency_value;
  629. }
  630. // Getter for measurement mode
  631. as7331_measurement_mode_t AS7331::getMeasurementMode() const {
  632. return _measurement_mode;
  633. }
  634. // Getter for power down state
  635. bool AS7331::isPowerDown() const {
  636. return _power_down;
  637. }
  638. // Getter for standby state
  639. bool AS7331::isStandby() const {
  640. return _standby;
  641. }
  642. // Internal helper methods
  643. // Set the device mode
  644. bool AS7331::setDeviceMode(const as7331_device_mode_t& mode) {
  645. if(mode != DEVICE_MODE_CONFIG && mode != DEVICE_MODE_MEASURE) {
  646. FURI_LOG_E("AS7331", "Invalid device mode value: %d.", mode);
  647. return false;
  648. }
  649. if(_deviceMode == mode) {
  650. return true; // Already in desired mode
  651. }
  652. // Read OSR register
  653. as7331_osr_reg_t osr;
  654. if(!readOSRRegister(osr)) {
  655. FURI_LOG_E("AS7331", "Failed to read OSR register from device.");
  656. return false;
  657. }
  658. // Set operating state (DOS bits)
  659. osr.operating_state = mode;
  660. // If transitioning to Measurement state, ensure power-down is disabled
  661. if(mode == DEVICE_MODE_MEASURE) {
  662. osr.power_down = 0;
  663. }
  664. // Write back OSR register
  665. // '_deviceMode' is updated in writeOSRRegister
  666. if(!writeOSRRegister(osr)) {
  667. FURI_LOG_E("AS7331", "Failed to write OSR register to set device mode.");
  668. return false;
  669. }
  670. return true;
  671. }
  672. // Read OSR register and update local config
  673. bool AS7331::readOSRRegister(as7331_osr_reg_t& osr) {
  674. if(!readRegister(RegCfgOsr, osr.byte)) {
  675. FURI_LOG_E("AS7331", "Failed to read OSR Register from device.");
  676. return false;
  677. }
  678. if(osr.operating_state != DEVICE_MODE_CONFIG && osr.operating_state != DEVICE_MODE_MEASURE) {
  679. FURI_LOG_E(
  680. "AS7331", "Invalid operating state value read from device: %d.", osr.operating_state);
  681. return false;
  682. }
  683. // Update local config
  684. _deviceMode = osr.operating_state;
  685. _power_down = osr.power_down;
  686. return true;
  687. }
  688. // Write OSR register and update local config
  689. bool AS7331::writeOSRRegister(const as7331_osr_reg_t& osr) {
  690. if(osr.operating_state != DEVICE_MODE_CONFIG && osr.operating_state != DEVICE_MODE_MEASURE) {
  691. FURI_LOG_E("AS7331", "Invalid device mode value: %d.", osr.operating_state);
  692. return false;
  693. }
  694. // Write back OSR register
  695. if(!writeRegister(RegCfgOsr, osr.byte)) {
  696. FURI_LOG_E("AS7331", "Failed to write OSR to device: %d.", osr.byte);
  697. return false;
  698. }
  699. // Update local config
  700. _deviceMode = osr.operating_state;
  701. _power_down = osr.power_down;
  702. return true;
  703. }
  704. // Generalized method to read multiple bytes from consecutive registers
  705. bool AS7331::readRegisters(uint8_t start_register_addr, uint8_t* buffer, size_t length) {
  706. bool success;
  707. // Write Phase: Send the start register address, end with AwaitRestart
  708. success = furi_hal_i2c_tx_ext(
  709. &furi_hal_i2c_handle_external,
  710. _i2c_addr_8bit,
  711. false, // 7-bit address
  712. &start_register_addr,
  713. 1,
  714. FuriHalI2cBeginStart,
  715. FuriHalI2cEndAwaitRestart, // Prepare for repeated start
  716. AS7331_I2C_TIMEOUT);
  717. if(!success) {
  718. FURI_LOG_E("AS7331", "Failed to send start register address");
  719. return false;
  720. }
  721. // Read Phase: Read the data, begin with Restart, end with Stop
  722. success = furi_hal_i2c_rx_ext(
  723. &furi_hal_i2c_handle_external,
  724. _i2c_addr_8bit,
  725. false, // 7-bit address
  726. buffer,
  727. length,
  728. FuriHalI2cBeginRestart, // Repeated start
  729. FuriHalI2cEndStop,
  730. AS7331_I2C_TIMEOUT);
  731. if(!success) {
  732. FURI_LOG_E("AS7331", "Failed to read data");
  733. return false;
  734. }
  735. return true;
  736. }
  737. // Function to read an 8-bit register
  738. bool AS7331::readRegister(uint8_t register_addr, uint8_t& data) {
  739. return readRegisters(register_addr, &data, 1);
  740. }
  741. // Function to read a 16-bit register
  742. bool AS7331::readRegister16(uint8_t register_addr, uint16_t& data) {
  743. uint8_t buffer[2];
  744. bool success = readRegisters(register_addr, buffer, 2);
  745. if(!success) {
  746. return false;
  747. }
  748. // Combine the two bytes into a 16-bit value (LSB first)
  749. data = (static_cast<uint16_t>(buffer[1]) << 8) | buffer[0];
  750. return true;
  751. }
  752. // Function to write an 8-bit register
  753. bool AS7331::writeRegister(uint8_t register_addr, const uint8_t& data) {
  754. bool success;
  755. uint8_t buffer[2] = {register_addr, data};
  756. // Write the register address and data in one transaction
  757. success = furi_hal_i2c_tx_ext(
  758. &furi_hal_i2c_handle_external,
  759. _i2c_addr_8bit,
  760. false, // 7-bit address
  761. buffer,
  762. 2,
  763. FuriHalI2cBeginStart,
  764. FuriHalI2cEndStop,
  765. AS7331_I2C_TIMEOUT);
  766. if(!success) {
  767. FURI_LOG_E("AS7331", "Failed to write data");
  768. return false;
  769. }
  770. return true;
  771. }
  772. // Scan I2C bus for AS7331 device
  773. uint8_t AS7331::scan_i2c_bus() {
  774. uint8_t addr_found = 0; // Will hold the address if found
  775. furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
  776. // Iterate through possible I2C addresses
  777. for(uint8_t addr_7bit = DefaultI2CAddr; addr_7bit <= QuaternaryI2CAddr; addr_7bit++) {
  778. uint8_t addr_8bit = addr_7bit << 1;
  779. // Check for device readiness
  780. if(furi_hal_i2c_is_device_ready(
  781. &furi_hal_i2c_handle_external, addr_8bit, AS7331_I2C_TIMEOUT)) {
  782. addr_found = addr_7bit;
  783. break;
  784. }
  785. }
  786. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  787. if(addr_found == 0) {
  788. FURI_LOG_E("AS7331", "No AS7331 device found on I²C bus.");
  789. }
  790. return addr_found;
  791. }
  792. // Update local configuration variables from device registers
  793. bool AS7331::updateLocalConfig() {
  794. bool success = true;
  795. as7331_creg1_reg_t creg1;
  796. as7331_creg2_reg_t creg2;
  797. as7331_creg3_reg_t creg3;
  798. furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
  799. // Ensure we are in configuration mode
  800. success &= setDeviceMode(DEVICE_MODE_CONFIG);
  801. // Read configuration registers
  802. success &= readRegister(RegCfgCreg1, creg1.byte);
  803. success &= readRegister(RegCfgCreg2, creg2.byte);
  804. success &= readRegister(RegCfgCreg3, creg3.byte);
  805. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  806. if(!success) {
  807. FURI_LOG_E("AS7331", "Failed to read configuration registers.");
  808. return false;
  809. }
  810. // Validate and populate local configuration variables
  811. if(creg1.gain <= GAIN_1) {
  812. _gain = static_cast<as7331_gain_t>(creg1.gain);
  813. } else {
  814. FURI_LOG_E("AS7331", "Invalid gain value read from device: %d.", creg1.gain);
  815. return false;
  816. }
  817. if(creg1.integration_time <= TIME_16384MS) {
  818. _integration_time = static_cast<as7331_integration_time_t>(creg1.integration_time);
  819. } else {
  820. FURI_LOG_E(
  821. "AS7331",
  822. "Invalid integration time value read from device: %d.",
  823. creg1.integration_time);
  824. return false;
  825. }
  826. _enable_divider = creg2.enable_divider;
  827. if(creg2.divider <= DIV_256) {
  828. _divider = static_cast<as7331_divider_t>(creg2.divider);
  829. } else {
  830. FURI_LOG_E("AS7331", "Invalid divider value read from device: %d.", creg2.divider);
  831. return false;
  832. }
  833. if(creg3.clock_frequency <= CCLK_8_192_MHZ) {
  834. _clock_frequency = static_cast<as7331_clock_frequency_t>(creg3.clock_frequency);
  835. } else {
  836. FURI_LOG_E(
  837. "AS7331",
  838. "Invalid clock frequency value read from device: %d.",
  839. creg3.clock_frequency);
  840. return false;
  841. }
  842. _standby = creg3.standby;
  843. if(creg3.measurement_mode <= MEASUREMENT_MODE_SYNC_START_END) {
  844. _measurement_mode = static_cast<as7331_measurement_mode_t>(creg3.measurement_mode);
  845. } else {
  846. FURI_LOG_E(
  847. "AS7331",
  848. "Invalid measurement mode value read from device: %d.",
  849. creg3.measurement_mode);
  850. return false;
  851. }
  852. // _deviceMode and _power_down is updated in readOSRRegister()/writeOSRRegister()
  853. return true;
  854. }
  855. /**
  856. * @brief Calculates the Full-Scale Range irradiance (FSREe) for a given UV type.
  857. *
  858. * This function computes the FSREe, which is the maximum detectable irradiance (Ee) that the sensor can measure without saturating the ADC, based on the selected GAIN settings. Optionally, it can adjust FSREe for the integration TIME setting.
  859. *
  860. * **Definitions:**
  861. * - **FSR (FSREe in equations)**: Full-Scale Range—the maximum detectable irradiance (Ee) the sensor can measure without saturating the ADC, given the selected GAIN (and optionally TIME) settings.
  862. * - **LSB**: Least Significant Bit—the smallest change in irradiance that the sensor can detect with the selected settings. In the datasheet, it's given in nW/cm², but here we calculate it in µW/cm² (same as FSREe).
  863. * - **NCLK**: Number of clock cycles during the conversion.
  864. * - **MRES**: ADC count. The most straightforward way to get Ee is:
  865. * - *Ee = MRES × LSB* (ADC count times the value of a single step)
  866. * - **LSB Calculation**:
  867. * - *LSB = FSR / NCLK*
  868. *
  869. * **Note:**
  870. * - Both LSB and FSR depend on the clock frequency (fCLK); the tables in the datasheet assume an fCLK of 1.024 MHz.
  871. * - When calculating LSB, FSREe should **not** be adjusted for integration TIME, as higher TIME values increase the resolution beyond 16 bits, and only the least 16 bits are stored in the result registers.
  872. *
  873. * @param uvType The type of UV measurement (UV_A, UV_B, or UV_C).
  874. * @param adjustForIntegrationTime Optional. A boolean flag indicating whether to adjust FSREe for the integration TIME setting. Default is `true`.
  875. * @return The calculated FSREe value in µW/cm², or `-1.0` if an error occurs.
  876. */
  877. double AS7331::calculateFSREe(as7331_uv_type_t uvType, bool adjustForIntegrationTime) {
  878. // Base FSREe at GAIN = 1x for each channel, from datasheet (p.32 - p.38)
  879. double FSREe_gain1;
  880. switch(uvType) {
  881. case UV_A: // Channel A
  882. FSREe_gain1 = 348160.0; // µW/cm² at GAIN=1x, TIME code 6
  883. break;
  884. case UV_B: // Channel B
  885. FSREe_gain1 = 387072.0; // µW/cm² at GAIN=1x, TIME code 6
  886. break;
  887. case UV_C: // Channel C
  888. FSREe_gain1 = 169984.0; // µW/cm² at GAIN=1x, TIME code 3
  889. break;
  890. default:
  891. // Invalid UV type
  892. return -1.0;
  893. }
  894. // Calculate the actual gain value
  895. int GAIN_value = getGainValue();
  896. if(GAIN_value <= 0) {
  897. return -1.0; // Invalid GAIN
  898. }
  899. // Calculate the base FSREe for the current gain setting
  900. double FSREe = FSREe_gain1 / GAIN_value;
  901. if(adjustForIntegrationTime) {
  902. // Adjust FSREe for integration TIME
  903. if(uvType == UV_A || uvType == UV_B) {
  904. if(_integration_time <= 6) {
  905. // No adjustment needed for TIME codes 0-6
  906. } else if(_integration_time <= 14) {
  907. // For each TIME code above 6, FSREe halves
  908. int time_diff = _integration_time - 6;
  909. FSREe /= (1 << time_diff); // FSREe /= 2^(TIME_code - 6)
  910. } else if(_integration_time == 15) {
  911. // Special case: TIME code 15 is equivalent to TIME code 0
  912. // No adjustment needed
  913. } else {
  914. return -1.0; // Invalid TIME code
  915. }
  916. } else if(uvType == UV_C) {
  917. if(_integration_time <= 3) {
  918. // No adjustment needed for TIME codes 0-3
  919. } else if(_integration_time <= 7) {
  920. // Divide FSREe by 2
  921. FSREe /= 2;
  922. } else if(_integration_time <= 14) {
  923. // For each TIME code above 7, FSREe halves
  924. int time_diff = _integration_time - 7;
  925. FSREe /= (1 << time_diff); // FSREe /= 2^(TIME_code - 3)
  926. } else if(_integration_time == 15) {
  927. // Special case: TIME code 15 is equivalent to TIME code 0
  928. // No adjustment needed
  929. } else {
  930. return -1.0; // Invalid TIME code
  931. }
  932. }
  933. }
  934. return FSREe;
  935. }