Firmware SDK
 All Data Structures Functions Variables Typedefs Enumerations Enumerator Groups Pages
bc_sgpc3.c
1 #include <bc_sgpc3.h>
2 
3 #define _BC_SGPC3_DELAY_RUN 30
4 #define _BC_SGPC3_DELAY_INITIALIZE 500
5 #define _BC_SGPC3_DELAY_READ_FEATURE_SET 30
6 #define _BC_SGPC3_DELAY_SET_POWER_MODE 30
7 #define _BC_SGPC3_DELAY_INIT_AIR_QUALITY 30
8 #define _BC_SGPC3_DELAY_SET_HUMIDITY 30
9 #define _BC_SGPC3_DELAY_MEASURE_AIR_QUALITY 30
10 #define _BC_SGPC3_DELAY_READ_AIR_QUALITY 150
11 
12 static void _bc_sgpc3_task_interval(void *param);
13 
14 static void _bc_sgpc3_task_measure(void *param);
15 
16 static uint8_t _bc_sgpc3_calculate_crc(uint8_t *buffer, size_t length);
17 
18 void bc_sgpc3_init(bc_sgpc3_t *self, bc_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 = bc_scheduler_register(_bc_sgpc3_task_interval, self, BC_TICK_INFINITY);
26  self->_task_id_measure = bc_scheduler_register(_bc_sgpc3_task_measure, self, _BC_SGPC3_DELAY_RUN);
27 
28  self->_tick_ready = _BC_SGPC3_DELAY_RUN;
29 
30  bc_i2c_init(self->_i2c_channel, BC_I2C_SPEED_100_KHZ);
31 }
32 
33 void bc_sgpc3_set_event_handler(bc_sgpc3_t *self, void (*event_handler)(bc_sgpc3_t *, bc_sgpc3_event_t, void *), void *event_param)
34 {
35  self->_event_handler = event_handler;
36  self->_event_param = event_param;
37 }
38 
40 {
41  self->_update_interval = interval;
42 
43  if (self->_update_interval == BC_TICK_INFINITY)
44  {
45  bc_scheduler_plan_absolute(self->_task_id_interval, BC_TICK_INFINITY);
46  }
47  else
48  {
49  bc_scheduler_plan_relative(self->_task_id_interval, self->_update_interval);
50 
51  bc_sgpc3_measure(self);
52  }
53 }
54 
56 {
57  if (self->_event_handler == NULL)
58  {
59  return true;
60  }
61 
62  if (self->_hit_error && !self->_measurement_valid)
63  {
64  self->_event_handler(self, BC_SGPC3_EVENT_ERROR, self->_event_param);
65  }
66  else if (self->_measurement_valid)
67  {
68  self->_event_handler(self, BC_SGPC3_EVENT_UPDATE, self->_event_param);
69  }
70 
71  return true;
72 }
73 
74 bool bc_sgpc3_get_tvoc_ppb(bc_sgpc3_t *self, uint16_t *ppb)
75 {
76  if (!self->_measurement_valid)
77  {
78  return false;
79  }
80 
81  *ppb = self->_tvoc;
82 
83  return true;
84 }
85 
86 float bc_sgpc3_set_compensation(bc_sgpc3_t *self, float *t_celsius, float *rh_percentage)
87 {
88  if (t_celsius == NULL || rh_percentage == NULL)
89  {
90  self->_ah_scaled = 0;
91 
92  return 0.f;
93  }
94 
95  double t = *t_celsius;
96  double rh = *rh_percentage;
97  double pws = 611.2 * exp(17.67 * t / (243.5 + t));
98  double pw = pws * rh / 100.0;
99  double ah = 2.165 * pw / (273.15 + t);
100 
101  self->_ah_scaled = ((uint64_t) (ah * 1000.0) * 256 * 16777) >> 24;
102 
103  return ah;
104 }
105 
106 static void _bc_sgpc3_task_interval(void *param)
107 {
108  bc_sgpc3_t *self = param;
109 
110  bc_sgpc3_measure(self);
111 
112  bc_scheduler_plan_current_relative(self->_update_interval);
113 }
114 
115 static void _bc_sgpc3_task_measure(void *param)
116 {
117  bc_sgpc3_t *self = param;
118 
119 start:
120 
121  switch (self->_state)
122  {
123  case BC_SGPC3_STATE_ERROR:
124  {
125  self->_hit_error = true;
126 
127  self->_measurement_valid = false;
128 
129  self->_state = BC_SGPC3_STATE_INITIALIZE;
130 
131  bc_scheduler_plan_current_from_now(_BC_SGPC3_DELAY_INITIALIZE);
132 
133  return;
134  }
135  case BC_SGPC3_STATE_INITIALIZE:
136  {
137  self->_state = BC_SGPC3_STATE_GET_FEATURE_SET;
138 
139  goto start;
140  }
141  case BC_SGPC3_STATE_GET_FEATURE_SET:
142  {
143  self->_state = BC_SGPC3_STATE_ERROR;
144 
145  static const uint8_t buffer[] = { 0x20, 0x2f };
146 
147  bc_i2c_transfer_t transfer;
148 
149  transfer.device_address = self->_i2c_address;
150  transfer.buffer = (uint8_t *) buffer;
151  transfer.length = sizeof(buffer);
152 
153  if (!bc_i2c_write(self->_i2c_channel, &transfer))
154  {
155  goto start;
156  }
157 
158  self->_state = BC_SGPC3_STATE_READ_FEATURE_SET;
159 
160  bc_scheduler_plan_current_from_now(_BC_SGPC3_DELAY_SET_POWER_MODE);
161 
162  return;
163  }
164  case BC_SGPC3_STATE_SET_POWER_MODE:
165  {
166  self->_state = BC_SGPC3_STATE_ERROR;
167 
168  uint8_t buffer[5];
169 
170  buffer[0] = 0x20;
171  buffer[1] = 0x9f;
172  buffer[2] = 0x00;
173  buffer[3] = 0x00;
174  buffer[4] = _bc_sgpc3_calculate_crc(&buffer[2], 2);
175 
176  bc_i2c_transfer_t transfer;
177 
178  transfer.device_address = self->_i2c_address;
179  transfer.buffer = buffer;
180  transfer.length = sizeof(buffer);
181 
182  if (!bc_i2c_write(self->_i2c_channel, &transfer))
183  {
184  goto start;
185  }
186 
187  self->_state = BC_SGPC3_STATE_READ_FEATURE_SET;
188 
189  bc_scheduler_plan_current_from_now(_BC_SGPC3_DELAY_READ_FEATURE_SET);
190 
191  return;
192  }
193  case BC_SGPC3_STATE_READ_FEATURE_SET:
194  {
195  self->_state = BC_SGPC3_STATE_ERROR;
196 
197  uint8_t buffer[3];
198 
199  bc_i2c_transfer_t transfer;
200 
201  transfer.device_address = self->_i2c_address;
202  transfer.buffer = buffer;
203  transfer.length = sizeof(buffer);
204 
205  if (!bc_i2c_read(self->_i2c_channel, &transfer))
206  {
207  goto start;
208  }
209 
210  if (_bc_sgpc3_calculate_crc(&buffer[0], 3) != 0)
211  {
212  goto start;
213  }
214 
215  if (buffer[0] != 0x10 || buffer[1] != 0x06)
216  {
217  goto start;
218  }
219 
220  self->_state = BC_SGPC3_STATE_INIT_AIR_QUALITY;
221 
222  bc_scheduler_plan_current_from_now(_BC_SGPC3_DELAY_INIT_AIR_QUALITY);
223 
224  return;
225  }
226  case BC_SGPC3_STATE_INIT_AIR_QUALITY:
227  {
228  self->_state = BC_SGPC3_STATE_ERROR;
229 
230  static const uint8_t buffer[] = { 0x20, 0xae };
231 
232  bc_i2c_transfer_t transfer;
233 
234  transfer.device_address = self->_i2c_address;
235  transfer.buffer = (uint8_t *) buffer;
236  transfer.length = sizeof(buffer);
237 
238  if (!bc_i2c_write(self->_i2c_channel, &transfer))
239  {
240  goto start;
241  }
242 
243  self->_state = BC_SGPC3_STATE_SET_HUMIDITY;
244 
245  bc_scheduler_plan_current_from_now(_BC_SGPC3_DELAY_SET_HUMIDITY);
246 
247  return;
248  }
249  case BC_SGPC3_STATE_SET_HUMIDITY:
250  {
251  self->_state = BC_SGPC3_STATE_ERROR;
252 
253  uint8_t buffer[5];
254 
255  buffer[0] = 0x20;
256  buffer[1] = 0x61;
257  buffer[2] = self->_ah_scaled >> 8;
258  buffer[3] = self->_ah_scaled;
259  buffer[4] = _bc_sgpc3_calculate_crc(&buffer[2], 2);
260 
261  bc_i2c_transfer_t transfer;
262 
263  transfer.device_address = self->_i2c_address;
264  transfer.buffer = buffer;
265  transfer.length = sizeof(buffer);
266 
267  if (!bc_i2c_write(self->_i2c_channel, &transfer))
268  {
269  goto start;
270  }
271 
272  self->_state = BC_SGPC3_STATE_MEASURE_AIR_QUALITY;
273 
274  bc_scheduler_plan_current_from_now(_BC_SGPC3_DELAY_MEASURE_AIR_QUALITY);
275 
276  self->_tick_last_measurement = bc_scheduler_get_spin_tick();
277 
278  return;
279  }
280  case BC_SGPC3_STATE_MEASURE_AIR_QUALITY:
281  {
282  self->_state = BC_SGPC3_STATE_ERROR;
283 
284  static const uint8_t buffer[] = { 0x20, 0x08 };
285 
286  bc_i2c_transfer_t transfer;
287 
288  transfer.device_address = self->_i2c_address;
289  transfer.buffer = (uint8_t *) buffer;
290  transfer.length = sizeof(buffer);
291 
292  if (!bc_i2c_write(self->_i2c_channel, &transfer))
293  {
294  goto start;
295  }
296 
297  self->_state = BC_SGPC3_STATE_READ_AIR_QUALITY;
298 
299  bc_scheduler_plan_current_from_now(_BC_SGPC3_DELAY_READ_AIR_QUALITY);
300 
301  return;
302  }
303  case BC_SGPC3_STATE_READ_AIR_QUALITY:
304  {
305  self->_state = BC_SGPC3_STATE_ERROR;
306 
307  uint8_t buffer[3];
308 
309  bc_i2c_transfer_t transfer;
310 
311  transfer.device_address = self->_i2c_address;
312  transfer.buffer = buffer;
313  transfer.length = sizeof(buffer);
314 
315  if (!bc_i2c_read(self->_i2c_channel, &transfer))
316  {
317  goto start;
318  }
319 
320  if (_bc_sgpc3_calculate_crc(&buffer[0], 3) != 0)
321  {
322  goto start;
323  }
324 
325  self->_tvoc = (buffer[0] << 8) | buffer[1];
326 
327  self->_measurement_valid = true;
328 
329  self->_state = BC_SGPC3_STATE_SET_HUMIDITY;
330 
331  bc_scheduler_plan_current_absolute(self->_tick_last_measurement + 30000);
332 
333  return;
334  }
335  default:
336  {
337  self->_state = BC_SGPC3_STATE_ERROR;
338 
339  goto start;
340  }
341  }
342 }
343 
344 static uint8_t _bc_sgpc3_calculate_crc(uint8_t *buffer, size_t length)
345 {
346  uint8_t crc = 0xff;
347 
348  for (size_t i = 0; i < length; i++)
349  {
350  crc ^= buffer[i];
351 
352  for (int j = 0; j < 8; j++)
353  {
354  if ((crc & 0x80) != 0)
355  {
356  crc = (crc << 1) ^ 0x31;
357  }
358  else
359  {
360  crc <<= 1;
361  }
362  }
363  }
364 
365  return crc;
366 }
I2C transfer parameters.
Definition: bc_i2c.h:42
bc_sgpc3_event_t
Callback events.
Definition: bc_sgpc3.h:13
uint64_t bc_tick_t
Timestamp data type.
Definition: bc_tick.h:16
void bc_sgpc3_set_update_interval(bc_sgpc3_t *self, bc_tick_t interval)
Set measurement interval.
Definition: bc_sgpc3.c:39
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
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_sgpc3_init(bc_sgpc3_t *self, bc_i2c_channel_t i2c_channel, uint8_t i2c_address)
Initialize SGPC3.
Definition: bc_sgpc3.c:18
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
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_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
Update event.
Definition: bc_sgpc3.h:19
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
bool bc_sgpc3_get_tvoc_ppb(bc_sgpc3_t *self, uint16_t *ppb)
Get measured TVOC in ppb (parts per billion)
Definition: bc_sgpc3.c:74
bc_i2c_channel_t
I2C channels.
Definition: bc_i2c.h:15
bool bc_sgpc3_measure(bc_sgpc3_t *self)
Start measurement manually.
Definition: bc_sgpc3.c:55
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
Error event.
Definition: bc_sgpc3.h:16
#define BC_TICK_INFINITY
Maximum timestamp value.
Definition: bc_tick.h:12
void bc_sgpc3_set_event_handler(bc_sgpc3_t *self, void(*event_handler)(bc_sgpc3_t *, bc_sgpc3_event_t, void *), void *event_param)
Set callback function.
Definition: bc_sgpc3.c:33
float bc_sgpc3_set_compensation(bc_sgpc3_t *self, float *t_celsius, float *rh_percentage)
Set sensor compensation (absolute humidity is calculated from temperature and relative humidity) ...
Definition: bc_sgpc3.c:86
struct bc_sgpc3_t bc_sgpc3_t
SGPC3 instance.
Definition: bc_sgpc3.h:25