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