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