Firmware SDK
 All Data Structures Functions Variables Typedefs Enumerations Enumerator Groups Pages
bc_sps30.c
1 #include <bc_sps30.h>
2 
3 #define _BC_SPS30_DELAY_RUN 100
4 #define _BC_SPS30_DELAY_INITIALIZE 1000
5 #define _BC_SPS30_DELAY_READ 30
6 #define _BC_SPS30_DELAY_MEASUREMENT 250
7 
8 #define BC_SPS30_NUM_WORDS(x) (sizeof(x) / 2)
9 
10 #define be16_to_cpu(s) (((uint16_t)(s) << 8) | (0xff & ((uint16_t)(s)) >> 8))
11 #define be32_to_cpu(s) (((uint32_t)be16_to_cpu(s) << 16) | (0xffff & (be16_to_cpu((s) >> 16))))
12 
13 static void _bc_sps30_task_interval(void *param);
14 
15 static void _bc_sps30_task_measure(void *param);
16 
17 static uint8_t _bc_sps30_calculate_crc(uint8_t *buffer, size_t length);
18 static bool _bc_sps30_convert_to_words(uint8_t *buffer, size_t buffer_length, uint16_t *data, size_t data_length);
19 
20 void bc_sps30_init(bc_sps30_t *self, bc_i2c_channel_t i2c_channel, uint8_t i2c_address)
21 {
22  memset(self, 0, sizeof(*self));
23 
24  self->_i2c_channel = i2c_channel;
25  self->_i2c_address = i2c_address;
26 
27  self->_task_id_interval = bc_scheduler_register(_bc_sps30_task_interval, self, BC_TICK_INFINITY);
28  self->_task_id_measure = bc_scheduler_register(_bc_sps30_task_measure, self, _BC_SPS30_DELAY_RUN);
29 
30  self->_state = BC_SPS30_STATE_INITIALIZE;
31 
32  bc_i2c_init(self->_i2c_channel, BC_I2C_SPEED_100_KHZ);
33 }
34 
35 void bc_sps30_set_event_handler(bc_sps30_t *self, void (*event_handler)(bc_sps30_t *, bc_sps30_event_t, void *), void *event_param)
36 {
37  self->_event_handler = event_handler;
38  self->_event_param = event_param;
39 }
40 
42 {
43  self->_startup_time = startup_time;
44 }
45 
47 {
48  self->_update_interval = interval;
49 
50  if (self->_update_interval == BC_TICK_INFINITY)
51  {
52  bc_scheduler_plan_absolute(self->_task_id_interval, BC_TICK_INFINITY);
53  }
54  else
55  {
56  bc_scheduler_plan_relative(self->_task_id_interval, _BC_SPS30_DELAY_INITIALIZE);
57  }
58 }
59 
61 {
62  if (self->_state == BC_SPS30_STATE_READY)
63  {
64  self->_state = BC_SPS30_STATE_START_MEASUREMENT;
65  self->_start_time = bc_tick_get();
66 
67  bc_scheduler_plan_now(self->_task_id_measure);
68 
69  return true;
70  }
71 
72  return false;
73 }
74 
75 bool bc_sps30_get_mass_concentration(bc_sps30_t *self, bc_sps30_mass_concentration_t *mass_concentration)
76 {
77  if (!self->_measurement_valid)
78  {
79  return false;
80  }
81 
82  mass_concentration->mc_1p0 = self->_mass_concentration.mc_1p0;
83  mass_concentration->mc_2p5 = self->_mass_concentration.mc_2p5;
84  mass_concentration->mc_4p0 = self->_mass_concentration.mc_4p0;
85  mass_concentration->mc_10p0 = self->_mass_concentration.mc_10p0;
86 
87  return true;
88 }
89 
90 bool bc_sps30_get_number_concentration(bc_sps30_t *self, bc_sps30_number_concentration_t *number_concentration)
91 {
92  if (!self->_measurement_valid)
93  {
94  return false;
95  }
96 
97  number_concentration->nc_0p5 = self->_number_concentration.nc_0p5;
98  number_concentration->nc_1p0 = self->_number_concentration.nc_1p0;
99  number_concentration->nc_2p5 = self->_number_concentration.nc_2p5;
100  number_concentration->nc_4p0 = self->_number_concentration.nc_4p0;
101  number_concentration->nc_10p0 = self->_number_concentration.nc_10p0;
102 
103  return true;
104 }
105 
106 bool bc_sps30_get_typical_particle_size(bc_sps30_t *self, float *typical_particle_size)
107 {
108  if (!self->_measurement_valid)
109  {
110  return false;
111  }
112 
113  *typical_particle_size = self->_typical_particle_size;
114 
115  return true;
116 }
117 
118 static void _bc_sps30_task_interval(void *param)
119 {
120  bc_sps30_t *self = param;
121 
122  bc_sps30_measure(self);
123 
124  bc_scheduler_plan_current_relative(self->_update_interval);
125 }
126 
127 static void _bc_sps30_task_measure(void *param)
128 {
129  bc_sps30_t *self = param;
130 
131  while (true)
132  {
133  switch (self->_state)
134  {
135  case BC_SPS30_STATE_ERROR:
136  {
137  self->_measurement_valid = false;
138 
139  if (self->_event_handler != NULL)
140  {
141  self->_event_handler(self, BC_SPS30_EVENT_ERROR, self->_event_param);
142  }
143 
144  self->_state = BC_SPS30_STATE_INITIALIZE;
145 
146  continue;
147  }
148  case BC_SPS30_STATE_READY:
149  {
150  return;
151  }
152  case BC_SPS30_STATE_INITIALIZE:
153  {
154  self->_state = BC_SPS30_STATE_GET_SERIAL_NUMBER;
155 
156  continue;
157  }
158  case BC_SPS30_STATE_GET_SERIAL_NUMBER:
159  {
160  self->_state = BC_SPS30_STATE_ERROR;
161 
162  static const uint8_t buffer[] = { 0xd0, 0x33 };
163 
164  bc_i2c_transfer_t transfer;
165 
166  transfer.device_address = self->_i2c_address;
167  transfer.buffer = (uint8_t *) buffer;
168  transfer.length = sizeof(buffer);
169 
170  if (!bc_i2c_write(self->_i2c_channel, &transfer))
171  {
172  continue;
173  }
174 
175  self->_state = BC_SPS30_STATE_READ_SERIAL_NUMBER;
176 
177  bc_scheduler_plan_current_from_now(_BC_SPS30_DELAY_READ);
178 
179  return;
180  }
181  case BC_SPS30_STATE_READ_SERIAL_NUMBER:
182  {
183  self->_state = BC_SPS30_STATE_ERROR;
184 
185  uint8_t buffer[48];
186  union {
187  char serial[32];
188  uint16_t __enforce_alignment;
189  } data;
190 
191  bc_i2c_transfer_t transfer;
192 
193  transfer.device_address = self->_i2c_address;
194  transfer.buffer = buffer;
195  transfer.length = sizeof(buffer);
196 
197  if (!bc_i2c_read(self->_i2c_channel, &transfer))
198  {
199  continue;
200  }
201 
202  if (!_bc_sps30_convert_to_words(buffer, sizeof(buffer),
203  (uint16_t *) data.serial, BC_SPS30_NUM_WORDS(data.serial)))
204  {
205  continue;
206  }
207 
208  self->_state = BC_SPS30_STATE_READY;
209 
210  continue;
211  }
212  case BC_SPS30_STATE_START_MEASUREMENT:
213  {
214  self->_state = BC_SPS30_STATE_ERROR;
215 
216  uint8_t buffer[5];
217 
218  buffer[0] = 0x00;
219  buffer[1] = 0x10;
220  buffer[2] = 0x03;
221  buffer[3] = 0x00;
222  buffer[4] = _bc_sps30_calculate_crc(&buffer[2], 2);
223 
224  bc_i2c_transfer_t transfer;
225 
226  transfer.device_address = self->_i2c_address;
227  transfer.buffer = buffer;
228  transfer.length = sizeof(buffer);
229 
230  if (!bc_i2c_write(self->_i2c_channel, &transfer))
231  {
232  continue;
233  }
234 
235  self->_state = BC_SPS30_STATE_SET_DATAREADY_FLAG;
236 
237  continue;
238  }
239  case BC_SPS30_STATE_SET_DATAREADY_FLAG:
240  {
241  self->_state = BC_SPS30_STATE_ERROR;
242 
243  static const uint8_t buffer[] = { 0x02, 0x02 };
244 
245  bc_i2c_transfer_t transfer;
246 
247  transfer.device_address = self->_i2c_address;
248  transfer.buffer = (uint8_t *) buffer;
249  transfer.length = sizeof(buffer);
250 
251  if (!bc_i2c_write(self->_i2c_channel, &transfer))
252  {
253  continue;
254  }
255 
256  self->_state = BC_SPS30_STATE_READ_DATAREADY_FLAG;
257 
258  bc_scheduler_plan_current_from_now(_BC_SPS30_DELAY_READ);
259 
260  return;
261  }
262  case BC_SPS30_STATE_READ_DATAREADY_FLAG:
263  {
264  self->_state = BC_SPS30_STATE_ERROR;
265 
266  uint8_t buffer[3];
267 
268  bc_i2c_transfer_t transfer;
269 
270  transfer.device_address = self->_i2c_address;
271  transfer.buffer = buffer;
272  transfer.length = sizeof(buffer);
273 
274  if (!bc_i2c_read(self->_i2c_channel, &transfer))
275  {
276  continue;
277  }
278 
279  if (_bc_sps30_calculate_crc(&buffer[0], 2) != buffer[2])
280  {
281  continue;
282  }
283 
284  if (buffer[1] == 0x01)
285  {
286  self->_state = BC_SPS30_STATE_GET_MEASUREMENT_DATA;
287 
288  continue;
289  }
290 
291  self->_state = BC_SPS30_STATE_READ_DATAREADY_FLAG;
292 
293  bc_scheduler_plan_current_from_now(_BC_SPS30_DELAY_MEASUREMENT);
294 
295  return;
296  }
297  case BC_SPS30_STATE_GET_MEASUREMENT_DATA:
298  {
299  self->_state = BC_SPS30_STATE_ERROR;
300 
301  static const uint8_t buffer[] = { 0x03, 0x00 };
302 
303  bc_i2c_transfer_t transfer;
304 
305  transfer.device_address = self->_i2c_address;
306  transfer.buffer = (uint8_t *) buffer;
307  transfer.length = sizeof(buffer);
308 
309  if (!bc_i2c_write(self->_i2c_channel, &transfer))
310  {
311  continue;
312  }
313 
314  self->_state = BC_SPS30_STATE_READ_MEASUREMENT_DATA;
315 
316  bc_scheduler_plan_current_from_now(_BC_SPS30_DELAY_READ);
317 
318  return;
319  }
320  case BC_SPS30_STATE_READ_MEASUREMENT_DATA:
321  {
322  self->_state = BC_SPS30_STATE_ERROR;
323 
324  uint8_t buffer[60];
325  union {
326  uint16_t uint16_t[2];
327  uint32_t u;
328  float f;
329  } val, data[10];
330 
331  bc_i2c_transfer_t transfer;
332 
333  transfer.device_address = self->_i2c_address;
334  transfer.buffer = (uint8_t *) buffer;
335  transfer.length = sizeof(buffer);
336 
337  if (!bc_i2c_read(self->_i2c_channel, &transfer))
338  {
339  continue;
340  }
341 
342  if (!_bc_sps30_convert_to_words(buffer, sizeof(buffer), data->uint16_t, BC_SPS30_NUM_WORDS(data)))
343  {
344  continue;
345  }
346 
347  val.u = be32_to_cpu(data[0].u);
348  self->_mass_concentration.mc_1p0 = val.f;
349  val.u = be32_to_cpu(data[1].u);
350  self->_mass_concentration.mc_2p5 = val.f;
351  val.u = be32_to_cpu(data[2].u);
352  self->_mass_concentration.mc_4p0 = val.f;
353  val.u = be32_to_cpu(data[3].u);
354  self->_mass_concentration.mc_10p0 = val.f;
355  val.u = be32_to_cpu(data[4].u);
356  self->_number_concentration.nc_0p5 = val.f;
357  val.u = be32_to_cpu(data[5].u);
358  self->_number_concentration.nc_1p0 = val.f;
359  val.u = be32_to_cpu(data[6].u);
360  self->_number_concentration.nc_2p5 = val.f;
361  val.u = be32_to_cpu(data[7].u);
362  self->_number_concentration.nc_4p0 = val.f;
363  val.u = be32_to_cpu(data[8].u);
364  self->_number_concentration.nc_10p0 = val.f;
365  val.u = be32_to_cpu(data[9].u);
366  self->_typical_particle_size = val.f;
367 
368  self->_measurement_valid = true;
369 
370  if ((bc_tick_get() - self->_start_time) > self->_startup_time)
371  {
372  if (self->_event_handler != NULL)
373  {
374  self->_event_handler(self, BC_SPS30_EVENT_UPDATE, self->_event_param);
375  }
376 
377  self->_state = BC_SPS30_STATE_STOP_MEASUREMENT;
378  }
379  else
380  {
381  self->_state = BC_SPS30_STATE_SET_DATAREADY_FLAG;
382  }
383 
384  continue;
385  }
386  case BC_SPS30_STATE_STOP_MEASUREMENT:
387  {
388  self->_state = BC_SPS30_STATE_ERROR;
389 
390  static const uint8_t buffer[] = { 0x01, 0x04 };
391 
392  bc_i2c_transfer_t transfer;
393 
394  transfer.device_address = self->_i2c_address;
395  transfer.buffer = (uint8_t *) buffer;
396  transfer.length = sizeof(buffer);
397 
398  if (!bc_i2c_write(self->_i2c_channel, &transfer))
399  {
400  continue;
401  }
402 
403  self->_state = BC_SPS30_STATE_READY;
404 
405  continue;
406  }
407  default:
408  {
409  self->_state = BC_SPS30_STATE_ERROR;
410 
411  continue;
412  }
413  }
414  }
415 }
416 
417 static uint8_t _bc_sps30_calculate_crc(uint8_t *buffer, size_t length)
418 {
419  uint8_t crc = 0xff;
420 
421  for (size_t i = 0; i < length; i++)
422  {
423  crc ^= buffer[i];
424 
425  for (int j = 0; j < 8; j++)
426  {
427  if ((crc & 0x80) != 0)
428  {
429  crc = (crc << 1) ^ 0x31;
430  }
431  else
432  {
433  crc <<= 1;
434  }
435  }
436  }
437 
438  return crc;
439 }
440 
441 static bool _bc_sps30_convert_to_words(uint8_t *buffer, size_t buffer_length, uint16_t *data, size_t data_length)
442 {
443  uint8_t *data8 = (uint8_t *) data;
444  size_t i, j;
445 
446  if (buffer_length != (data_length * 3))
447  {
448  return false;
449  }
450 
451  for (i = 0, j = 0; i < buffer_length; i += 3)
452  {
453  if (_bc_sps30_calculate_crc(&buffer[i], 2) != buffer[i + 2])
454  {
455  return false;
456  }
457 
458  data8[j++] = buffer[i];
459  data8[j++] = buffer[i + 1];
460  }
461 
462  return true;
463 }
I2C transfer parameters.
Definition: bc_i2c.h:42
bool bc_sps30_get_typical_particle_size(bc_sps30_t *self, float *typical_particle_size)
Get measured typical particle size in μm.
Definition: bc_sps30.c:106
uint64_t bc_tick_t
Timestamp data type.
Definition: bc_tick.h:16
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
Error event.
Definition: bc_sps30.h:16
void bc_sps30_set_event_handler(bc_sps30_t *self, void(*event_handler)(bc_sps30_t *, bc_sps30_event_t, void *), void *event_param)
Set callback function.
Definition: bc_sps30.c:35
uint8_t device_address
7-bit I2C device address
Definition: bc_i2c.h:45
void bc_sps30_init(bc_sps30_t *self, bc_i2c_channel_t i2c_channel, uint8_t i2c_address)
Initialize SPS30.
Definition: bc_sps30.c:20
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_now(bc_scheduler_task_id_t task_id)
Schedule specified task for immediate execution.
Definition: bc_scheduler.c:119
bool bc_sps30_measure(bc_sps30_t *self)
Start measurement manually.
Definition: bc_sps30.c:60
void bc_sps30_set_startup_time(bc_sps30_t *self, bc_tick_t startup_time)
Set startup time (how long the fan blows air before the measurement)
Definition: bc_sps30.c:41
void bc_i2c_init(bc_i2c_channel_t channel, bc_i2c_speed_t speed)
Initialize I2C channel.
Definition: bc_i2c.c:54
bool bc_sps30_get_mass_concentration(bc_sps30_t *self, bc_sps30_mass_concentration_t *mass_concentration)
Get measured mass concentration in μg/m3.
Definition: bc_sps30.c:75
I2C communication speed is 100 kHz.
Definition: bc_i2c.h:33
bc_tick_t bc_tick_get(void)
Get absolute timestamp since start of program.
Definition: bc_tick.c:7
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
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_sps30_event_t
Callback events.
Definition: bc_sps30.h:13
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
struct bc_sps30_t bc_sps30_t
SPS30 instance.
Definition: bc_sps30.h:25
#define BC_TICK_INFINITY
Maximum timestamp value.
Definition: bc_tick.h:12
Update event.
Definition: bc_sps30.h:19
bool bc_sps30_get_number_concentration(bc_sps30_t *self, bc_sps30_number_concentration_t *number_concentration)
Get measured number concentration in #/cm3.
Definition: bc_sps30.c:90
void bc_sps30_set_update_interval(bc_sps30_t *self, bc_tick_t interval)
Set measurement interval.
Definition: bc_sps30.c:46