Firmware SDK
twr_lp8.c
1 #include <twr_lp8.h>
2 
3 #define _TWR_LP8_MODBUS_DEVICE_ADDRESS 0xfe
4 #define _TWR_LP8_MODBUS_WRITE 0x41
5 #define _TWR_LP8_MODBUS_READ 0x44
6 #define _TWR_LP8_INITIAL_MEASUREMENT 0x10
7 #define _TWR_LP8_SEQUENTIAL_MEASUREMENT 0x20
8 #define _TWR_LP8_RX_ERROR_STATUS0 (3 + 0xa7 - 0x80)
9 #define _TWR_LP8_RX_ERROR_STATUS1 (3 + 0xa6 - 0x80)
10 #define _TWR_LP8_RX_CONC (3 + 0x9a - 0x80)
11 
12 #define _TWR_LP8_CALIBRATION_TIMEOUT (8 * 24 * 60 * 60 * 1000)
13 
14 static void _twr_lp8_task_interval(void *param);
15 
16 static void _twr_lp8_task_measure(void *param);
17 
18 static uint16_t _twr_lp8_calculate_crc16(uint8_t *buffer, uint8_t length);
19 
20 void twr_lp8_init(twr_lp8_t *self, const twr_lp8_driver_t *driver)
21 {
22  memset(self, 0, sizeof(*self));
23 
24  self->_driver = driver;
25 
26  self->_pressure = 10124;
27  self->_calibration = TWR_LP8_CALIBRATION_ABC;
28  self->_tick_calibration = twr_tick_get() + _TWR_LP8_CALIBRATION_TIMEOUT;
29 
30  self->_task_id_interval = twr_scheduler_register(_twr_lp8_task_interval, self, TWR_TICK_INFINITY);
31  self->_task_id_measure = twr_scheduler_register(_twr_lp8_task_measure, self, 0);
32 
33  self->_driver->init();
34 }
35 
36 void twr_lp8_set_event_handler(twr_lp8_t *self, void (*event_handler)(twr_lp8_event_t, void *), void *event_param)
37 {
38  self->_event_handler = event_handler;
39  self->_event_param = event_param;
40 }
41 
43 {
44  self->_update_interval = interval;
45 
46  if (self->_update_interval == TWR_TICK_INFINITY)
47  {
48  twr_scheduler_plan_absolute(self->_task_id_interval, TWR_TICK_INFINITY);
49  }
50  else
51  {
52  twr_scheduler_plan_relative(self->_task_id_interval, self->_update_interval);
53 
54  twr_lp8_measure(self);
55  }
56 }
57 
59 {
60  if (self->_state == TWR_LP8_STATE_READY)
61  {
62  self->_state = TWR_LP8_STATE_PRECHARGE;
63 
64  twr_scheduler_plan_now(self->_task_id_measure);
65 
66  return true;
67  }
68  else if (self->_state == TWR_LP8_STATE_INITIALIZE)
69  {
70  twr_scheduler_plan_now(self->_task_id_measure);
71  }
72 
73  return false;
74 }
75 
77 {
78  if (!self->_valid)
79  {
80  *ppm = NAN;
81 
82  return false;
83  }
84 
85  *ppm = (float) self->_concentration;
86 
87  return true;
88 }
89 
90 bool twr_lp8_get_error(twr_lp8_t *self, twr_lp8_error_t *error)
91 {
92  *error = self->_error;
93 
94  return true;
95 }
96 
98 {
99  self->_calibration = calibration;
100 
101  self->_tick_calibration = 0;
102 
103  twr_lp8_measure(self);
104 }
105 
106 static void _twr_lp8_task_interval(void *param)
107 {
108  twr_lp8_t *self = (twr_lp8_t *) param;
109 
110  twr_lp8_measure(self);
111 
112  twr_scheduler_plan_current_relative(self->_update_interval);
113 }
114 
115 static void _twr_lp8_error(twr_lp8_t *self, twr_lp8_error_t error)
116 {
117  self->_state = TWR_LP8_STATE_ERROR;
118  self->_error = error;
120 }
121 
122 static void _twr_lp8_task_measure(void *param)
123 {
124  twr_lp8_t *self = (twr_lp8_t *) param;
125 
126 start:
127 
128  switch (self->_state)
129  {
130  case TWR_LP8_STATE_ERROR:
131  {
132  self->_valid = false;
133 
134  self->_first_measurement_done = false;
135 
136  self->_driver->uart_enable(false);
137  self->_driver->device_enable(false);
138  self->_driver->charge_enable(false);
139 
140  if (self->_event_handler != NULL)
141  {
142  self->_event_handler(TWR_LP8_EVENT_ERROR, self->_event_param);
143  }
144 
145  self->_state = TWR_LP8_STATE_INITIALIZE;
146 
148 
149  return;
150  }
151  case TWR_LP8_STATE_INITIALIZE:
152  {
153  if (!self->_driver->charge_enable(true))
154  {
155  _twr_lp8_error(self, TWR_LP8_ERROR_INITIALIZE);
156  goto start;
157  }
158 
159  self->_state = TWR_LP8_STATE_CHARGE;
160 
162 
163  return;
164  }
165  case TWR_LP8_STATE_READY:
166  {
167  return;
168  }
169  case TWR_LP8_STATE_PRECHARGE:
170  {
171  if (!self->_driver->charge_enable(true))
172  {
173  _twr_lp8_error(self, TWR_LP8_ERROR_PRECHARGE);
174  goto start;
175  }
176 
177  self->_state = TWR_LP8_STATE_CHARGE;
178 
180 
181  return;
182  }
183  case TWR_LP8_STATE_CHARGE:
184  {
185  if (!self->_driver->charge_enable(false))
186  {
187  _twr_lp8_error(self, TWR_LP8_ERROR_CHARGE_CHARGE_ENABLE);
188  goto start;
189  }
190 
191  if (!self->_driver->device_enable(true))
192  {
193  _twr_lp8_error(self, TWR_LP8_ERROR_CHARGE_DEVICE_ENABLE);
194  goto start;
195  }
196 
197  self->_state = TWR_LP8_STATE_BOOT;
198 
199  self->_tick_timeout = twr_tick_get() + 300;
200 
202 
203  return;
204  }
205  case TWR_LP8_STATE_BOOT:
206  {
207  int signal_rdy_value;
208 
209  size_t length;
210 
211  if (!self->_driver->read_signal_rdy(&signal_rdy_value))
212  {
213  _twr_lp8_error(self, TWR_LP8_ERROR_BOOT_SIGNAL_READY);
214  goto start;
215  }
216 
217  if (signal_rdy_value != 0)
218  {
219  if (twr_tick_get() >= self->_tick_timeout)
220  {
221  _twr_lp8_error(self, TWR_LP8_ERROR_BOOT_TIMEOUT);
222  goto start;
223  }
224  else
225  {
227 
228  return;
229  }
230  }
231 
232  if (!self->_first_measurement_done)
233  {
234  self->_tx_buffer[0] = _TWR_LP8_MODBUS_DEVICE_ADDRESS;
235  self->_tx_buffer[1] = _TWR_LP8_MODBUS_WRITE;
236  self->_tx_buffer[2] = 0x00;
237  self->_tx_buffer[3] = 0x80;
238  self->_tx_buffer[4] = 0x01;
239  self->_tx_buffer[5] = _TWR_LP8_INITIAL_MEASUREMENT;
240  self->_tx_buffer[6] = 0x28;
241  self->_tx_buffer[7] = 0x7e;
242 
243  length = 8;
244  }
245  else
246  {
247  uint16_t crc16;
248 
249  self->_tx_buffer[0] = _TWR_LP8_MODBUS_DEVICE_ADDRESS;
250  self->_tx_buffer[1] = _TWR_LP8_MODBUS_WRITE;
251  self->_tx_buffer[2] = 0x00;
252  self->_tx_buffer[3] = 0x80;
253  self->_tx_buffer[4] = 0x1a;
254 
255  self->_calibration_run = self->_tick_calibration < twr_tick_get();
256 
257  if (self->_calibration_run)
258  {
259  self->_tx_buffer[5] = self->_calibration;
260  }
261  else
262  {
263  self->_tx_buffer[5] = _TWR_LP8_SEQUENTIAL_MEASUREMENT;
264  }
265 
266  memcpy(&self->_tx_buffer[6], self->_sensor_state, 23);
267 
268  self->_tx_buffer[29] = self->_pressure >> 8;
269  self->_tx_buffer[30] = self->_pressure;
270 
271  crc16 = _twr_lp8_calculate_crc16(self->_tx_buffer, 31);
272 
273  self->_tx_buffer[31] = crc16;
274  self->_tx_buffer[32] = crc16 >> 8;
275 
276  length = 33;
277  }
278 
279  if (!self->_driver->uart_enable(true))
280  {
281  _twr_lp8_error(self, TWR_LP8_ERROR_BOOT_UART_ENABLE);
282 
283  goto start;
284  }
285 
286  if (self->_driver->uart_write(self->_tx_buffer, length) != length)
287  {
288  _twr_lp8_error(self, TWR_LP8_ERROR_BOOT_UART_WRITE);
289 
290  goto start;
291  }
292 
293  self->_rx_buffer_length = 0;
294 
295  self->_state = TWR_LP8_STATE_BOOT_READ;
296 
297  self->_tick_timeout = twr_tick_get() + 80;
298 
300 
301  return;
302  }
303  case TWR_LP8_STATE_BOOT_READ:
304  {
305  self->_rx_buffer_length += self->_driver->uart_read(self->_rx_buffer + self->_rx_buffer_length, 4 - self->_rx_buffer_length);
306 
307  if (self->_rx_buffer_length == 4)
308  {
309  if (!self->_driver->uart_enable(false))
310  {
311  _twr_lp8_error(self, TWR_LP8_ERROR_BOOT_READ_UART_ENABLE);
312 
313  goto start;
314  }
315 
316  if (self->_rx_buffer[0] != _TWR_LP8_MODBUS_DEVICE_ADDRESS)
317  {
318  _twr_lp8_error(self, TWR_LP8_ERROR_BOOT_READ_DEVICE_ADDRESS);
319 
320  goto start;
321  }
322 
323  if (self->_rx_buffer[1] != self->_tx_buffer[1])
324  {
325  _twr_lp8_error(self, TWR_LP8_ERROR_BOOT_READ_COMMAND);
326 
327  goto start;
328  }
329 
330  if (_twr_lp8_calculate_crc16(self->_rx_buffer, 4) != 0)
331  {
332  _twr_lp8_error(self, TWR_LP8_ERROR_BOOT_READ_CRC);
333 
334  goto start;
335  }
336 
337  self->_state = TWR_LP8_STATE_MEASURE;
338 
339  self->_tick_timeout = twr_tick_get() + (self->_calibration_run ? 1000 : 250);
340 
342 
343  return;
344  }
345 
346  if (twr_tick_get() >= self->_tick_timeout)
347  {
348  _twr_lp8_error(self, TWR_LP8_ERROR_BOOT_READ_TIMEOUT);
349 
350  goto start;
351  }
352 
354 
355  return;
356  }
357  case TWR_LP8_STATE_MEASURE:
358  {
359  int signal_rdy_value;
360 
361  if (!self->_driver->read_signal_rdy(&signal_rdy_value))
362  {
363  _twr_lp8_error(self, TWR_LP8_ERROR_MEASURE_SIGNAL_RDY);
364 
365  goto start;
366  }
367 
368  if (signal_rdy_value == 0)
369  {
370  if (twr_tick_get() >= self->_tick_timeout)
371  {
372  _twr_lp8_error(self, TWR_LP8_ERROR_MEASURE_SIGNAL_RDY_TIMEOUT);
373 
374  goto start;
375  }
376  else
377  {
379 
380  return;
381  }
382  }
383 
384  self->_tx_buffer[0] = _TWR_LP8_MODBUS_DEVICE_ADDRESS;
385  self->_tx_buffer[1] = _TWR_LP8_MODBUS_READ;
386  self->_tx_buffer[2] = 0x00;
387  self->_tx_buffer[3] = 0x80;
388  self->_tx_buffer[4] = 0x2c;
389 
390  uint16_t crc16 = _twr_lp8_calculate_crc16(self->_tx_buffer, 5);
391 
392  self->_tx_buffer[5] = crc16;
393  self->_tx_buffer[6] = crc16 >> 8;
394 
395  if (!self->_driver->uart_enable(true))
396  {
397  _twr_lp8_error(self, TWR_LP8_ERROR_MEASURE_UART_ENABLE);
398 
399  goto start;
400  }
401 
402  if (self->_driver->uart_write(self->_tx_buffer, 7) != 7)
403  {
404  _twr_lp8_error(self, TWR_LP8_ERROR_MEASURE_UART_WRITE);
405 
406  goto start;
407  }
408 
409  self->_rx_buffer_length = 0;
410 
411  self->_state = TWR_LP8_STATE_MEASURE_READ;
412 
413  self->_tick_timeout = twr_tick_get() + 100;
414 
416 
417  return;
418  }
419  case TWR_LP8_STATE_MEASURE_READ:
420  {
421  self->_rx_buffer_length += self->_driver->uart_read(self->_rx_buffer + self->_rx_buffer_length, 49 - self->_rx_buffer_length);
422 
423  if (self->_rx_buffer_length == 49)
424  {
425  if (!self->_driver->uart_enable(false))
426  {
427  _twr_lp8_error(self, TWR_LP8_ERROR_MEASURE_READ_UART_ENABLE);
428 
429  goto start;
430  }
431 
432  if (!self->_driver->device_enable(false))
433  {
434  _twr_lp8_error(self, TWR_LP8_ERROR_MEASURE_READ_DEVICE_ENABLE);
435 
436  goto start;
437  }
438 
439  if (self->_rx_buffer[0] != _TWR_LP8_MODBUS_DEVICE_ADDRESS)
440  {
441  _twr_lp8_error(self, TWR_LP8_ERROR_MEASURE_READ_DEVICE_ADDRESS);
442 
443  goto start;
444  }
445 
446  if (self->_rx_buffer[1] != self->_tx_buffer[1])
447  {
448  _twr_lp8_error(self, TWR_LP8_ERROR_MEASURE_READ_COMMAND);
449 
450  goto start;
451  }
452 
453  if (_twr_lp8_calculate_crc16(self->_rx_buffer, 49) != 0)
454  {
455  _twr_lp8_error(self, TWR_LP8_ERROR_MEASURE_READ_CRC);
456 
457  goto start;
458  }
459 
460  if ((self->_rx_buffer[_TWR_LP8_RX_ERROR_STATUS0] & 0xdd) != 0)
461  {
462 
463  if (self->_calibration_run)
464  {
465  if ((self->_rx_buffer[_TWR_LP8_RX_ERROR_STATUS0] == 8) &&
466  (self->_calibration != TWR_LP8_CALIBRATION_ABC) &&
467  (self->_calibration != TWR_LP8_CALIBRATION_ATWR_RF))
468  {
469  self->_state = TWR_LP8_STATE_CHARGE;
470 
471  twr_scheduler_plan_relative(self->_task_id_measure, 100);
472 
473  return;
474  }
475  }
476  else
477  {
478  _twr_lp8_error(self, TWR_LP8_ERROR_MEASURE_READ_CALIBRATION_RUN);
479 
480  goto start;
481  }
482  }
483 
484  if ((self->_rx_buffer[_TWR_LP8_RX_ERROR_STATUS1] & 0xf7) != 0)
485  {
486  _twr_lp8_error(self, TWR_LP8_ERROR_MEASURE_READ_STATUS1);
487 
488  goto start;
489  }
490 
491  if (self->_calibration_run)
492  {
493  self->_calibration = TWR_LP8_CALIBRATION_ABC;
494 
495  self->_tick_calibration = twr_tick_get() + _TWR_LP8_CALIBRATION_TIMEOUT;
496  }
497 
498  memcpy(self->_sensor_state, &self->_rx_buffer[4], 23);
499 
500  self->_first_measurement_done = true;
501 
502  self->_concentration = (int16_t) self->_rx_buffer[_TWR_LP8_RX_CONC] << 8;
503  self->_concentration |= (int16_t) self->_rx_buffer[_TWR_LP8_RX_CONC + 1];
504 
505  self->_valid = (self->_concentration >= 0) && (self->_concentration <= 10000);
506 
507  self->_state = TWR_LP8_STATE_READY;
508 
509  if (self->_event_handler != NULL)
510  {
511  self->_event_handler(TWR_LP8_EVENT_UPDATE, self->_event_param);
512  }
513 
514  return;
515  }
516  else
517  {
518  if (twr_tick_get() >= self->_tick_timeout)
519  {
520  _twr_lp8_error(self, TWR_LP8_ERROR_MEASURE_READ_TIMEOUT);
521 
522  goto start;
523  }
524  }
525 
527 
528  return;
529  }
530  default:
531  {
532  return;
533  }
534  }
535 }
536 
537 static uint16_t _twr_lp8_calculate_crc16(uint8_t *buffer, uint8_t length)
538 {
539  uint16_t crc16;
540 
541  for (crc16 = 0xffff; length != 0; length--, buffer++)
542  {
543  crc16 ^= *buffer;
544 
545  for (int i = 0; i < 8; i++)
546  {
547  if ((crc16 & 1) != 0)
548  {
549  crc16 >>= 1;
550  crc16 ^= 0xa001;
551  }
552  else
553  {
554  crc16 >>= 1;
555  }
556  }
557  }
558 
559  return crc16;
560 }
void twr_lp8_set_event_handler(twr_lp8_t *self, void(*event_handler)(twr_lp8_event_t, void *), void *event_param)
Set callback function.
Definition: twr_lp8.c:36
bool twr_lp8_measure(twr_lp8_t *self)
Start measurement manually.
Definition: twr_lp8.c:58
void twr_lp8_calibration(twr_lp8_t *self, twr_lp8_calibration_t calibration)
Request sensor calibration.
Definition: twr_lp8.c:97
void twr_lp8_set_update_interval(twr_lp8_t *self, twr_tick_t interval)
Set measurement interval.
Definition: twr_lp8.c:42
void twr_lp8_init(twr_lp8_t *self, const twr_lp8_driver_t *driver)
Initialize LP8.
Definition: twr_lp8.c:20
bool twr_lp8_get_concentration_ppm(twr_lp8_t *self, float *ppm)
Get CO2 concentration in ppm (parts per million)
Definition: twr_lp8.c:76
twr_lp8_event_t
Callback events.
Definition: twr_lp8.h:14
bool twr_lp8_get_error(twr_lp8_t *self, twr_lp8_error_t *error)
Get last error code.
Definition: twr_lp8.c:90
twr_lp8_calibration_t
Calibration.
Definition: twr_lp8.h:26
struct twr_lp8_t twr_lp8_t
LP8 instance.
Definition: twr_lp8.h:49
@ TWR_LP8_EVENT_UPDATE
Update event.
Definition: twr_lp8.h:19
@ TWR_LP8_EVENT_ERROR
Error event.
Definition: twr_lp8.h:16
@ TWR_LP8_CALIBRATION_ABC
ABC (based on filtered data)
Definition: twr_lp8.h:40
@ TWR_LP8_CALIBRATION_ATWR_RF
ABC (based on filtered data) + reset filters.
Definition: twr_lp8.h:43
void twr_scheduler_plan_current_from_now(twr_tick_t tick)
Schedule current task to tick relative from now.
void twr_scheduler_plan_current_relative(twr_tick_t tick)
Schedule current task to tick relative from current spin.
void twr_scheduler_plan_absolute(twr_scheduler_task_id_t task_id, twr_tick_t tick)
Schedule specified task to absolute tick.
void twr_scheduler_plan_now(twr_scheduler_task_id_t task_id)
Schedule specified task for immediate execution.
void twr_scheduler_plan_current_now(void)
Schedule current task for immediate execution.
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.
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
#define TWR_TICK_INFINITY
Maximum timestamp value.
Definition: twr_tick.h:12
twr_tick_t twr_tick_get(void)
Get absolute timestamp since start of program.
Definition: twr_tick.c:7
uint64_t twr_tick_t
Timestamp data type.
Definition: twr_tick.h:16
LP8 driver.
Definition: twr_lp8.h:54