Firmware SDK
 All Data Structures Functions Variables Typedefs Enumerations Enumerator Groups Pages
bc_sgp30.c
1 #include <bc_sgp30.h>
2 
3 #define _BC_SGP30_DELAY_RUN 100
4 #define _BC_SGP30_DELAY_INITIALIZE 500
5 #define _BC_SGP30_DELAY_READ_FEATURE_SET 30
6 #define _BC_SGP30_DELAY_INIT_AIR_QUALITY 30
7 #define _BC_SGP30_DELAY_SET_HUMIDITY 30
8 #define _BC_SGP30_DELAY_MEASURE_AIR_QUALITY 30
9 #define _BC_SGP30_DELAY_READ_AIR_QUALITY 30
10 
11 static void _bc_sgp30_task_interval(void *param);
12 
13 static void _bc_sgp30_task_measure(void *param);
14 
15 static uint8_t _bc_sgp30_calculate_crc(uint8_t *buffer, size_t length);
16 
17 void bc_sgp30_init(bc_sgp30_t *self, bc_i2c_channel_t i2c_channel, uint8_t i2c_address)
18 {
19  memset(self, 0, sizeof(*self));
20 
21  self->_i2c_channel = i2c_channel;
22  self->_i2c_address = i2c_address;
23 
24  self->_task_id_interval = bc_scheduler_register(_bc_sgp30_task_interval, self, BC_TICK_INFINITY);
25  self->_task_id_measure = bc_scheduler_register(_bc_sgp30_task_measure, self, _BC_SGP30_DELAY_RUN);
26 
27  self->_tick_ready = _BC_SGP30_DELAY_RUN;
28 
29  bc_i2c_init(self->_i2c_channel, BC_I2C_SPEED_100_KHZ);
30 }
31 
32 void bc_sgp30_set_event_handler(bc_sgp30_t *self, void (*event_handler)(bc_sgp30_t *, bc_sgp30_event_t, void *), void *event_param)
33 {
34  self->_event_handler = event_handler;
35  self->_event_param = event_param;
36 }
37 
39 {
40  self->_update_interval = interval;
41 
42  if (self->_update_interval == BC_TICK_INFINITY)
43  {
44  bc_scheduler_plan_absolute(self->_task_id_interval, BC_TICK_INFINITY);
45  }
46  else
47  {
48  bc_scheduler_plan_relative(self->_task_id_interval, self->_update_interval);
49 
50  bc_sgp30_measure(self);
51  }
52 }
53 
55 {
56  if (self->_event_handler == NULL)
57  {
58  return true;
59  }
60 
61  if (self->_hit_error && !self->_measurement_valid)
62  {
63  self->_event_handler(self, BC_SGP30_EVENT_ERROR, self->_event_param);
64  }
65  else if (self->_measurement_valid)
66  {
67  self->_event_handler(self, BC_SGP30_EVENT_UPDATE, self->_event_param);
68  }
69 
70  return true;
71 }
72 
73 bool bc_sgp30_get_co2eq_ppm(bc_sgp30_t *self, uint16_t *ppm)
74 {
75  if (!self->_measurement_valid)
76  {
77  return false;
78  }
79 
80  *ppm = self->_co2eq;
81 
82  return true;
83 }
84 
85 bool bc_sgp30_get_tvoc_ppb(bc_sgp30_t *self, uint16_t *ppb)
86 {
87  if (!self->_measurement_valid)
88  {
89  return false;
90  }
91 
92  *ppb = self->_tvoc;
93 
94  return true;
95 }
96 
97 float bc_sgp30_set_compensation(bc_sgp30_t *self, float *t_celsius, float *rh_percentage)
98 {
99  if (t_celsius == NULL || rh_percentage == NULL)
100  {
101  self->_ah_scaled = 0;
102 
103  return 0.f;
104  }
105 
106  double t = *t_celsius;
107  double rh = *rh_percentage;
108  double pws = 611.2 * exp(17.67 * t / (243.5 + t));
109  double pw = pws * rh / 100.0;
110  double ah = 2.165 * pw / (273.15 + t);
111 
112  self->_ah_scaled = ((uint64_t) (ah * 1000.0) * 256 * 16777) >> 24;
113 
114  return ah;
115 }
116 
117 static void _bc_sgp30_task_interval(void *param)
118 {
119  bc_sgp30_t *self = param;
120 
121  bc_sgp30_measure(self);
122 
123  bc_scheduler_plan_current_relative(self->_update_interval);
124 }
125 
126 static void _bc_sgp30_task_measure(void *param)
127 {
128  bc_sgp30_t *self = param;
129 
130 start:
131 
132  switch (self->_state)
133  {
134  case BC_SGP30_STATE_ERROR:
135  {
136  self->_hit_error = true;
137 
138  self->_measurement_valid = false;
139 
140  self->_state = BC_SGP30_STATE_INITIALIZE;
141 
142  bc_scheduler_plan_current_from_now(_BC_SGP30_DELAY_INITIALIZE);
143 
144  return;
145  }
146  case BC_SGP30_STATE_INITIALIZE:
147  {
148  self->_state = BC_SGP30_STATE_GET_FEATURE_SET;
149 
150  goto start;
151  }
152  case BC_SGP30_STATE_GET_FEATURE_SET:
153  {
154  self->_state = BC_SGP30_STATE_ERROR;
155 
156  static const uint8_t buffer[] = { 0x20, 0x2f };
157 
158  bc_i2c_transfer_t transfer;
159 
160  transfer.device_address = self->_i2c_address;
161  transfer.buffer = (uint8_t *) buffer;
162  transfer.length = sizeof(buffer);
163 
164  if (!bc_i2c_write(self->_i2c_channel, &transfer))
165  {
166  goto start;
167  }
168 
169  self->_state = BC_SGP30_STATE_READ_FEATURE_SET;
170 
171  bc_scheduler_plan_current_from_now(_BC_SGP30_DELAY_READ_FEATURE_SET);
172 
173  return;
174  }
175  case BC_SGP30_STATE_READ_FEATURE_SET:
176  {
177  self->_state = BC_SGP30_STATE_ERROR;
178 
179  uint8_t buffer[3];
180 
181  bc_i2c_transfer_t transfer;
182 
183  transfer.device_address = self->_i2c_address;
184  transfer.buffer = buffer;
185  transfer.length = sizeof(buffer);
186 
187  if (!bc_i2c_read(self->_i2c_channel, &transfer))
188  {
189  goto start;
190  }
191 
192  if (_bc_sgp30_calculate_crc(&buffer[0], 3) != 0)
193  {
194  goto start;
195  }
196 
197  if (buffer[0] != 0x00 || buffer[1] != 0x20)
198  {
199  goto start;
200  }
201 
202  self->_state = BC_SGP30_STATE_INIT_AIR_QUALITY;
203 
204  bc_scheduler_plan_current_from_now(_BC_SGP30_DELAY_INIT_AIR_QUALITY);
205 
206  return;
207  }
208  case BC_SGP30_STATE_INIT_AIR_QUALITY:
209  {
210  self->_state = BC_SGP30_STATE_ERROR;
211 
212  static const uint8_t buffer[] = { 0x20, 0x03 };
213 
214  bc_i2c_transfer_t transfer;
215 
216  transfer.device_address = self->_i2c_address;
217  transfer.buffer = (uint8_t *) buffer;
218  transfer.length = sizeof(buffer);
219 
220  if (!bc_i2c_write(self->_i2c_channel, &transfer))
221  {
222  goto start;
223  }
224 
225  self->_state = BC_SGP30_STATE_SET_HUMIDITY;
226 
227  bc_scheduler_plan_current_from_now(_BC_SGP30_DELAY_SET_HUMIDITY);
228 
229  return;
230  }
231  case BC_SGP30_STATE_SET_HUMIDITY:
232  {
233  self->_state = BC_SGP30_STATE_ERROR;
234 
235  uint8_t buffer[5];
236 
237  buffer[0] = 0x20;
238  buffer[1] = 0x61;
239  buffer[2] = self->_ah_scaled >> 8;
240  buffer[3] = self->_ah_scaled;
241  buffer[4] = _bc_sgp30_calculate_crc(&buffer[2], 2);
242 
243  bc_i2c_transfer_t transfer;
244 
245  transfer.device_address = self->_i2c_address;
246  transfer.buffer = buffer;
247  transfer.length = sizeof(buffer);
248 
249  if (!bc_i2c_write(self->_i2c_channel, &transfer))
250  {
251  goto start;
252  }
253 
254  self->_state = BC_SGP30_STATE_MEASURE_AIR_QUALITY;
255 
256  bc_scheduler_plan_current_from_now(_BC_SGP30_DELAY_MEASURE_AIR_QUALITY);
257 
258  self->_tick_last_measurement = bc_scheduler_get_spin_tick();
259 
260  return;
261  }
262  case BC_SGP30_STATE_MEASURE_AIR_QUALITY:
263  {
264  self->_state = BC_SGP30_STATE_ERROR;
265 
266  static const uint8_t buffer[] = { 0x20, 0x08 };
267 
268  bc_i2c_transfer_t transfer;
269 
270  transfer.device_address = self->_i2c_address;
271  transfer.buffer = (uint8_t *) buffer;
272  transfer.length = sizeof(buffer);
273 
274  if (!bc_i2c_write(self->_i2c_channel, &transfer))
275  {
276  goto start;
277  }
278 
279  self->_state = BC_SGP30_STATE_READ_AIR_QUALITY;
280 
281  bc_scheduler_plan_current_from_now(_BC_SGP30_DELAY_READ_AIR_QUALITY);
282 
283  return;
284  }
285  case BC_SGP30_STATE_READ_AIR_QUALITY:
286  {
287  self->_state = BC_SGP30_STATE_ERROR;
288 
289  uint8_t buffer[6];
290 
291  bc_i2c_transfer_t transfer;
292 
293  transfer.device_address = self->_i2c_address;
294  transfer.buffer = buffer;
295  transfer.length = sizeof(buffer);
296 
297  if (!bc_i2c_read(self->_i2c_channel, &transfer))
298  {
299  goto start;
300  }
301 
302  if (_bc_sgp30_calculate_crc(&buffer[0], 3) != 0 ||
303  _bc_sgp30_calculate_crc(&buffer[3], 3) != 0)
304  {
305  goto start;
306  }
307 
308  self->_co2eq = (buffer[0] << 8) | buffer[1];
309  self->_tvoc = (buffer[3] << 8) | buffer[4];
310 
311  self->_measurement_valid = true;
312 
313  self->_state = BC_SGP30_STATE_SET_HUMIDITY;
314 
315  bc_scheduler_plan_current_absolute(self->_tick_last_measurement + 1000);
316 
317  return;
318  }
319  default:
320  {
321  self->_state = BC_SGP30_STATE_ERROR;
322 
323  goto start;
324  }
325  }
326 }
327 
328 static uint8_t _bc_sgp30_calculate_crc(uint8_t *buffer, size_t length)
329 {
330  uint8_t crc = 0xff;
331 
332  for (size_t i = 0; i < length; i++)
333  {
334  crc ^= buffer[i];
335 
336  for (int j = 0; j < 8; j++)
337  {
338  if ((crc & 0x80) != 0)
339  {
340  crc = (crc << 1) ^ 0x31;
341  }
342  else
343  {
344  crc <<= 1;
345  }
346  }
347  }
348 
349  return crc;
350 }
I2C transfer parameters.
Definition: bc_i2c.h:42
void bc_sgp30_set_update_interval(bc_sgp30_t *self, bc_tick_t interval)
Set measurement interval.
Definition: bc_sgp30.c:38
uint64_t bc_tick_t
Timestamp data type.
Definition: bc_tick.h:16
bool bc_sgp30_get_co2eq_ppm(bc_sgp30_t *self, uint16_t *ppm)
Get measured CO2eq in ppm (parts per million)
Definition: bc_sgp30.c:73
void bc_scheduler_plan_absolute(bc_scheduler_task_id_t task_id, bc_tick_t tick)
Schedule specified task to absolute tick.
Definition: bc_scheduler.c:124
bc_tick_t bc_scheduler_get_spin_tick(void)
Get current tick of spin in which task has been run.
Definition: bc_scheduler.c:104
void bc_sgp30_set_event_handler(bc_sgp30_t *self, void(*event_handler)(bc_sgp30_t *, bc_sgp30_event_t, void *), void *event_param)
Set callback function.
Definition: bc_sgp30.c:32
struct bc_sgp30_t bc_sgp30_t
SGP30 instance.
Definition: bc_sgp30.h:25
bc_sgp30_event_t
Callback events.
Definition: bc_sgp30.h:13
bool bc_sgp30_measure(bc_sgp30_t *self)
Start measurement manually.
Definition: bc_sgp30.c:54
Error event.
Definition: bc_sgp30.h:16
uint8_t device_address
7-bit I2C device address
Definition: bc_i2c.h:45
bool bc_i2c_read(bc_i2c_channel_t channel, const bc_i2c_transfer_t *transfer)
Read from I2C channel.
Definition: bc_i2c.c:283
bc_scheduler_task_id_t bc_scheduler_register(void(*task)(void *), void *param, bc_tick_t tick)
Register task in scheduler.
Definition: bc_scheduler.c:56
void bc_scheduler_plan_current_absolute(bc_tick_t tick)
Schedule current task to absolute tick.
Definition: bc_scheduler.c:144
void bc_i2c_init(bc_i2c_channel_t channel, bc_i2c_speed_t speed)
Initialize I2C channel.
Definition: bc_i2c.c:54
float bc_sgp30_set_compensation(bc_sgp30_t *self, float *t_celsius, float *rh_percentage)
Set sensor compensation (absolute humidity is calculated from temperature and relative humidity) ...
Definition: bc_sgp30.c:97
I2C communication speed is 100 kHz.
Definition: bc_i2c.h:33
void * buffer
Pointer to buffer which is being written or read.
Definition: bc_i2c.h:48
void bc_sgp30_init(bc_sgp30_t *self, bc_i2c_channel_t i2c_channel, uint8_t i2c_address)
Initialize SGP30.
Definition: bc_sgp30.c:17
void bc_scheduler_plan_current_relative(bc_tick_t tick)
Schedule current task to tick relative from current spin.
Definition: bc_scheduler.c:149
bool bc_i2c_write(bc_i2c_channel_t channel, const bc_i2c_transfer_t *transfer)
Write to I2C channel.
Definition: bc_i2c.c:237
void bc_scheduler_plan_relative(bc_scheduler_task_id_t task_id, bc_tick_t tick)
Schedule specified task to tick relative from current spin.
Definition: bc_scheduler.c:129
bc_i2c_channel_t
I2C channels.
Definition: bc_i2c.h:15
size_t length
Length of buffer which is being written or read.
Definition: bc_i2c.h:51
void bc_scheduler_plan_current_from_now(bc_tick_t tick)
Schedule current task to tick relative from now.
Definition: bc_scheduler.c:154
#define BC_TICK_INFINITY
Maximum timestamp value.
Definition: bc_tick.h:12
Update event.
Definition: bc_sgp30.h:19
bool bc_sgp30_get_tvoc_ppb(bc_sgp30_t *self, uint16_t *ppb)
Get measured TVOC in ppb (parts per billion)
Definition: bc_sgp30.c:85