1 /*
2 * Copyright (C) 2018 Sensirion Inc.
3 * Author: Johannes Winkelmann, jwi@sensirion.com
4 *
5 * Driver for SCD30
6 * Based on SGP30 driver, and arduino-scd library
7 */
8 
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include "aos/kernel.h"
13 #include "sensor_drv_api.h"
14 #include "sensor_hal.h"
15 
16 #define POLYNOMIAL  0x131
17 
18 #define SCD30_I2C_SLAVE_ADDR                            0x61
19 
20 #define SCD30_ADDR_TRANS(n)                             ((n) << 1)
21 #define SCD30_I2C_ADDR                                  SCD30_ADDR_TRANS(SCD30_I2C_SLAVE_ADDR)
22 
23 #define CMD_CONT_MEASUREMENT_LEN 5
24 static  uint8_t CMD_CONT_MEASUREMENT[CMD_CONT_MEASUREMENT_LEN] = {
25     0x00, 0x10, 0x00, 0x00, 0x81
26 };
27 
28 // Note: in this library, we're hardcoding the interval to 2 seconds
29 #define CMD_SET_INTERVAL_LEN 5
30 static  uint8_t CMD_SET_INTERVAL[CMD_SET_INTERVAL_LEN] = { 0x46, 0x00,
31                                                                 0x00, 0x01,
32                                                                 0xE3 };
33 #define CMD_STOP_MEASUREMENT_LEN 2
34 static const uint8_t CMD_STOP_MEASUREMENT[CMD_STOP_MEASUREMENT_LEN] = { 0x01,
35                                                                         0x04 };
36 
37 #define CMD_READ_MEASUREMENT_LEN 2
38 #define CMD_READ_MEASUREMENT_RESULT_LEN 18
39 static uint8_t CMD_READ_MEASUREMENT[CMD_READ_MEASUREMENT_LEN] = { 0x03,
40                                                                         0x00 };
41 
42 #define CMD_DATA_READY_LEN 2
43 #define CMD_DATA_READY_RESULT_LEN 4
44 static uint8_t CMD_DATA_READY[CMD_DATA_READY_LEN] = { 0x02, 0x02 };
45 
46 /*
47  * default port = 3
48  * Use "GLOBAL_DEFINES += SENSIRION_SCD30_PORT=2" in a Makefile to override
49 */
50 #ifndef SENSIRION_SCD30_PORT
51 #define SENSIRION_SCD30_PORT 3
52 #endif /* SENSIRION_SCD30_PORT */
53 
54 i2c_dev_t scd30_ctx = {
55     .port = SENSIRION_SCD30_PORT,
56     .config.address_width = 8,
57     .config.freq = 100000,
58     .config.dev_addr = SCD30_I2C_ADDR,
59 };
60 
61 static float g_scd30_most_recent_value = 0;
62 
SCD30_CalcCrc(uint8_t data[],uint8_t nbrOfBytes)63 static uint8_t SCD30_CalcCrc(uint8_t data[], uint8_t nbrOfBytes)
64 {
65   uint8_t bit;        // bit mask
66   uint8_t crc = 0xFF; // calculated checksum
67   uint8_t byteCtr;    // byte counter
68 
69   // calculates 8-Bit checksum with given polynomial
70   for(byteCtr = 0; byteCtr < nbrOfBytes; byteCtr++)
71   {
72     crc ^= (data[byteCtr]);
73     for(bit = 8; bit > 0; --bit)
74     {
75       if(crc & 0x80) crc = (crc << 1) ^ POLYNOMIAL;
76       else           crc = (crc << 1);
77     }
78   }
79 
80   return crc;
81 }
scd30_convert_to_float(uint8_t * data)82 static float scd30_convert_to_float(uint8_t *data)
83 {
84     void* addr;
85     uint32_t val = (((uint32_t)data[0] << 24) | ((uint32_t)data[1] << 16) |
86                     ((uint32_t)data[3] << 8) | ((uint32_t)data[4]));
87     addr = (void*)(&val);
88     return *(float *)addr;
89 }
90 
scd30_delay_ms(uint32_t delay_time)91 static void scd30_delay_ms(uint32_t delay_time)
92 {
93     // TODO: krhino_task_sleep is currently blocking; therefore, I implemented
94     // a busy wait loop
95     //
96     // krhino_task_sleep(krhino_ms_to_ticks(delay_time));
97     uint32_t start_ms = aos_now_ms();
98     while (aos_now_ms() - start_ms < delay_time) {
99         // idle
100     }
101 }
102 
drv_scd30_cmd_write(i2c_dev_t * drv,uint8_t * cmd,uint8_t len)103 static int drv_scd30_cmd_write(i2c_dev_t* drv, uint8_t* cmd, uint8_t len)
104 {
105     return hal_i2c_master_send(drv, drv->config.dev_addr, cmd, len, AOS_WAIT_FOREVER);
106 }
107 
drv_scd30_result_read(i2c_dev_t * drv,uint8_t * data,uint16_t size)108 static int drv_scd30_result_read(i2c_dev_t* drv, uint8_t *data, uint16_t size)
109 {
110     if (data == NULL || size == 0)
111         return -1;
112 
113     return hal_i2c_master_recv(drv, drv->config.dev_addr, data, size, AOS_WAIT_FOREVER);
114 }
115 
scd30_check_data_ready(i2c_dev_t * drv)116 static int scd30_check_data_ready(i2c_dev_t* drv)
117 {
118   bool dataReady = false;
119   int ret = drv_scd30_cmd_write(drv, CMD_DATA_READY, CMD_DATA_READY_LEN);
120 
121   if (ret != 0) {
122     return 1;
123   } else {
124     uint8_t buf[CMD_DATA_READY_RESULT_LEN] = { 0 };
125     ret = drv_scd30_result_read(drv, buf, CMD_DATA_READY_RESULT_LEN);
126     if (ret != 0) {
127       return 2;
128     } else {
129       dataReady = (buf[1] == 1);
130     }
131   }
132   return dataReady;
133 }
134 
drv_scd30_read_raw_data(i2c_dev_t * drv,integer_data_t * pdata)135 static int drv_scd30_read_raw_data(i2c_dev_t *drv, integer_data_t *pdata)
136 {
137     int ret = 0;
138     uint8_t data[CMD_READ_MEASUREMENT_RESULT_LEN] = {0};
139 
140     ret = scd30_check_data_ready(drv);
141     if (ret) {
142         ret = drv_scd30_cmd_write(drv, CMD_READ_MEASUREMENT, CMD_READ_MEASUREMENT_LEN);
143         if (unlikely(ret)) {
144             return ret;
145         }
146         ret = drv_scd30_result_read(drv, data, CMD_READ_MEASUREMENT_RESULT_LEN);
147         if (unlikely(ret)) {
148             return ret;
149         }
150         g_scd30_most_recent_value = scd30_convert_to_float(data);
151     }
152 
153     pdata->data = g_scd30_most_recent_value;
154 
155     return ret;
156 }
157 
drv_scd30_init_sensor(i2c_dev_t * drv)158 static int drv_scd30_init_sensor(i2c_dev_t* drv)
159 {
160     int ret = 0;
161 
162     CMD_SET_INTERVAL[4] = SCD30_CalcCrc(&CMD_SET_INTERVAL[2],2);
163     ret = drv_scd30_cmd_write(drv, CMD_SET_INTERVAL, CMD_SET_INTERVAL_LEN);
164     if (unlikely(ret)) {
165         return ret;
166     }
167     // workaround for firmware bug in early samples
168     scd30_delay_ms(100);
169     CMD_CONT_MEASUREMENT[4] = SCD30_CalcCrc(&CMD_CONT_MEASUREMENT[2],2);
170     return drv_scd30_cmd_write(drv, CMD_CONT_MEASUREMENT, CMD_CONT_MEASUREMENT_LEN);
171 }
172 
173 
174 
drv_co2_sensirion_scd30_irq_handle(void)175 static void drv_co2_sensirion_scd30_irq_handle(void)
176 {
177     /* no handle so far */
178 }
179 
drv_co2_sensirion_scd30_open(void)180 static int drv_co2_sensirion_scd30_open(void)
181 {
182     LOG("%s %s successfully \n", SENSOR_STR, __func__);
183 
184     return 0;
185 }
186 
drv_co2_sensirion_scd30_close(void)187 static int drv_co2_sensirion_scd30_close(void)
188 {
189     LOG("%s %s successfully \n", SENSOR_STR, __func__);
190 
191     return 0;
192 }
193 
drv_co2_sensirion_scd30_read(void * buf,size_t len)194 static int drv_co2_sensirion_scd30_read(void *buf, size_t len)
195 {
196     int ret = 0;
197     const size_t size  = sizeof(integer_data_t);
198     integer_data_t * pdata = (integer_data_t *)buf;
199 
200     if (buf == NULL){
201         return -1;
202     }
203 
204     if (len < size){
205         return -1;
206     }
207 
208     ret = drv_scd30_read_raw_data(&scd30_ctx, pdata);
209     if (ret != 0)
210         return -1;
211 
212     pdata->timestamp = aos_now_ms();
213 
214     return (int)size;
215 }
216 
drv_co2_sensirion_scd30_write(const void * buf,size_t len)217 static int drv_co2_sensirion_scd30_write(const void *buf, size_t len)
218 {
219     (void)buf;
220     (void)len;
221     return 0;
222 }
223 
drv_co2_sensirion_scd30_ioctl(int cmd,unsigned long arg)224 static int drv_co2_sensirion_scd30_ioctl(int cmd, unsigned long arg)
225 {
226     switch (cmd) {
227         case SENSOR_IOCTL_GET_INFO:{
228             /* fill the dev info here */
229             dev_sensor_info_t *info = (dev_sensor_info_t *)arg;
230             info->model = "SCD30";
231             //info->unit = ppm;
232         }break;
233         default:
234             return -1;
235     }
236 
237     LOG("%s %s successfully \n", SENSOR_STR, __func__);
238     return 0;
239 }
240 
241 
drv_co2_sensirion_scd30_init(void)242 int drv_co2_sensirion_scd30_init(void)
243 {
244     int ret = 0;
245     sensor_obj_t sensor_temp;
246     memset(&sensor_temp, 0, sizeof(sensor_temp));
247 
248     ret = drv_scd30_init_sensor(&scd30_ctx);
249     if (unlikely(ret)) {
250         return -1;
251     }
252 
253     /* fill the sensor_temp obj parameters here */
254     sensor_temp.tag = TAG_DEV_CO2;
255     sensor_temp.path = dev_co2_path;
256     sensor_temp.io_port = I2C_PORT;
257     sensor_temp.open = drv_co2_sensirion_scd30_open;
258     sensor_temp.close = drv_co2_sensirion_scd30_close;
259     sensor_temp.read = drv_co2_sensirion_scd30_read;
260     sensor_temp.write = drv_co2_sensirion_scd30_write;
261     sensor_temp.ioctl = drv_co2_sensirion_scd30_ioctl;
262     sensor_temp.irq_handle = drv_co2_sensirion_scd30_irq_handle;
263 
264     ret = sensor_create_obj(&sensor_temp);
265     if (unlikely(ret)) {
266         return -1;
267     }
268 
269     LOG("%s %s successfully \n", SENSOR_STR, __func__);
270     return 0;
271 }
272 
273 SENSOR_DRV_ADD(drv_co2_sensirion_scd30_init);
274