AVRly - AVR Development Resources
ccs811.c
Go to the documentation of this file.
1/******************************************************************************
2 @copyright Copyright © 2022 by Jason Duffy.
3
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is
9 furnished to do so, subject to the following conditions:
10
11 The above copyright notice and this permission notice shall be included in all
12 copies or substantial portions of the Software.
13
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 SOFTWARE.
21******************************************************************************/
22
23
24/**
25 * @file ccs811.c
26 * @ingroup ccs811
27 * @author Jason Duffy
28 * @date 4th March 2022
29 * @brief Driver for the CCS811 gas sensor.
30 * @bug No known bugs.
31 *
32 * This file provides the basic low-level functionality for the CCS81 gas
33 * sensor / air quality sensor. It was tested with the CJMCU-811 module which
34 * provides pin breakout to THT for breadboard prototyping, and some peripheral
35 * circuitry. The CCS811 eatures an on-board MCU to interface with, greatly
36 * reducing the load on the host MCU.
37 *
38 * When the sensor is new, a burn-in period of 48hr is necessary for the
39 * resistive elements to level out and make readings more accurate. The CCS81
40 * controls the burn-in period, allowing eCO2 and eTVOC readings to be used
41 * from first power-on after 60 minutes of operation in modes 1-3.
42 *
43 * After early-life (Burn-In) use the conditioning period is the time required
44 * to achieve good sensor stability before measuring VOCs after a long idle
45 * period. After starting the application and calling init_ccs811(), run the
46 * sensor for 20 minutes before accurate readings are generated. The
47 * conditioning period must also be observed before writing to the BASELINE
48 * register.
49 *
50 * This driver was written using the datasheet for the ams CCS81 gas sensor,
51 * which can be found at the link below.
52 * @see
53 * https://pdf1.alldatasheet.com/datasheet-pdf/view/1047395/AMSCO/CCS811.html
54 */
55
56#include <util/delay.h>
57#include "ccs811.h"
58#include "i2c.h"
59
60// 7 bit address of i2c device followwed by write bit (0) or read bit (1)
61#define CCS811_ADDRESS_W 0xB4
62#define CCS811_ADDRESS_R 0xB5
63
64// Register mailbox ID's
65#define STATUS_REG 0x00 // Read
66#define MEAS_MODE_REG 0x01 // Read/Write
67#define ALG_RESULT_DATA_REG 0x02 // Read
68#define RAW_DATA_REG 0x03 // Read
69#define ENV_DATA_REG 0x05 // Write
70#define THRESHOLDS_REG 0x10 // Write
71#define BASELINE_REG 0x11 // Read/Write
72#define HW_ID_REG 0x20 // Read - should return 0x81
73#define HW_VERSION_REG 0x21 // Read
74#define FW_BOOT_VERSION_REG 0x23 // Read
75#define FW_APP_VERSION_REG 0x24 // Read
76#define INTERNAL_STATE_REG 0xA0 // Read
77#define ERROR_ID_REG 0xE0 // Read
78#define SW_RESET_REG 0xFF // Write
79
80// Register sizes (BYTES)
81#define ALG_RESULT_DATA_SIZE 8
82#define ETVOC_SIZE 4
83#define RAW_DATA_SIZE 2
84#define ENV_DATA_SIZE 4
85#define THRESHOLDS_SIZE 5
86#define BASELINE_SIZE 2
87#define FW_BOOT_VERSION_SIZE 2
88#define FW_APP_VERSION_SIZE 2
89#define SW_RESET_SIZE 4
90
91// Command mailbox ID's
92#define APP_START 0xF4 // Go from boot state to application mode
93#define GPIO_WAKE 0x5
94
95// Error codes (bit numbers)
96#define HEATER_SUPPLY 5
97#define HEATER_FAULT 4
98#define MAX_RESISTANCE 3
99#define MEASMODE_INVALID 2
100#define READ_REG_INVALID 1
101#define WRITE_REG_INVALID 0
102
103// Meas_mode register (bit numbers)
104#define DRIVE_MODE_BITS 4
105#define INT_DATARDY_BIT 3
106#define INT_THRESH_BIT 2
107
108// Status register (bit numbers)
109#define FW_MODE_BIT 7
110#define APP_ERASE_BIT 6
111#define APP_VERIFY_BIT 5
112#define APP_VALID_BIT 4
113#define DATA_READY_BIT 3
114#define ERROR_BIT 0
115
116#define BUS_SPEED_100KHZ 100000U
117#define ATTEMPTS_MAX 10
118#define CCS811_ID 0x81
119
120static ccs811_config_t *p_config_global;
121
122static uint8_t read_buffer[10];
123
124// Forward declarations
125uint8_t ccs811_read_byte(uint8_t reg);
126void ccs811_burst_read(uint8_t reg, uint8_t length);
127uint16_t ccs811_read_word(uint8_t reg);
128void ccs811_write_byte(uint8_t reg, uint8_t byte);
129uint8_t ccs811_get_status(void);
130
131
132
133uint8_t init_ccs811(ccs811_config_t *p_config)
134{
135 p_config_global = p_config; // Make a copy of pointer with file scope.
136 uint8_t ret = 0; // For return value
137 init_i2c(BUS_SPEED_100KHZ);
138 if (ccs811_read_byte(HW_ID_REG) == CCS811_ID)
139 {
140 uint8_t status = ccs811_get_status();
141 if (bit_is_set(status, APP_VALID_BIT))
142 {
143 i2c_start();
144 i2c_send(CCS811_ADDRESS_W);
145 i2c_send(APP_START);
146 i2c_stop();
147 _delay_ms(100);
148
149 uint8_t byte = 0;
150 byte |= (p_config_global->drive_mode << DRIVE_MODE_BITS);
151 byte |= (p_config_global->interrupt_dataready << INT_DATARDY_BIT);
152 byte |= (p_config_global->interrupt_threshold <<INT_THRESH_BIT);
153
154 for (uint8_t count = 0; count < ATTEMPTS_MAX; ++count)
155 {
156 ccs811_write_byte(MEAS_MODE_REG, byte);
157 _delay_ms(500);
158 uint8_t mode = ccs811_read_byte(MEAS_MODE_REG);
159 if (mode == byte)
160 {
161 break; // Success
162 }
163 if (count == (ATTEMPTS_MAX - 1))
164 {
165 ret = 3; // Max attempts reached
166 }
167 }
168 }
169 else
170 {
171 ret = 2; // Error - app invalid
172 }
173 }
174 else
175 {
176 ret = 1; // ccs81 ID failed
177 }
178 return ret;
179}
180
181
183{
184 uint16_t data = ccs811_read_word(ALG_RESULT_DATA_REG);
185 return data;
186}
187
188
190{
191 uint8_t data = 0;
192 ccs811_burst_read(ALG_RESULT_DATA_REG, ETVOC_SIZE);
193 uint8_t msb = read_buffer[ETVOC_SIZE - 2];
194 data = read_buffer[ETVOC_SIZE - 1];
195 data |= (msb << 8);
196 return data;
197}
198
199
201{
202 bool ret = false;
203 uint8_t status = ccs811_get_status();
204 if (bit_is_set(status, DATA_READY_BIT))
205 {
206 ret = true;
207 }
208 return ret;
209}
210
212{
213 ccs811_burst_read(ALG_RESULT_DATA_REG, ALG_RESULT_DATA_SIZE);
214}
215
216void ccs811_update_env_data(uint8_t humidity, uint8_t temp)
217{
218 uint8_t lsb = 0;
219
220 i2c_start();
221 i2c_send(CCS811_ADDRESS_W);
222 i2c_send(ENV_DATA_REG);
223 i2c_send(humidity << 1);
224 i2c_send(lsb);
225 i2c_send(((temp + 25) << 1));
226 i2c_send(lsb);
227 i2c_stop();
228}
229
230
232{
233 char *msg;
234 uint8_t error = ccs811_read_byte(ERROR_ID_REG);
235 if (bit_is_set(error, HEATER_SUPPLY))
236 {
237 msg = "Heater Supply";
238 }
239 else if (bit_is_set(error, HEATER_FAULT))
240 {
241 msg = "Heater Fault";
242 }
243 else if (bit_is_set(error, MAX_RESISTANCE))
244 {
245 msg = "Max Resistance";
246 }
247 else if (bit_is_set(error, MEASMODE_INVALID))
248 {
249 msg = "Invalid Meas Mode";
250 }
251 else if (bit_is_set(error, READ_REG_INVALID))
252 {
253 msg = "Read Reg Invalid";
254 }
255 else if (bit_is_set(error, WRITE_REG_INVALID))
256 {
257 msg = "Write Reg Invalid";
258 }
259 else
260 {
261 msg = "Unknown Error";
262 }
263 return msg;
264}
265
266
267///////////////////////////////////////////////////////////////////////////////
268// Private Helper Functions //
269///////////////////////////////////////////////////////////////////////////////
270
271uint8_t ccs811_get_status(void)
272{
273 uint8_t status = ccs811_read_byte(STATUS_REG);
274 return status;
275}
276
277void ccs811_write_byte(uint8_t reg, uint8_t byte)
278{
279 i2c_start();
280 i2c_send(CCS811_ADDRESS_W);
281 i2c_send(reg);
282 i2c_send(byte);
283 i2c_stop();
284}
285
286uint8_t ccs811_read_byte(uint8_t reg)
287{
288 uint8_t byte;
289
290 i2c_start();
291 i2c_send(CCS811_ADDRESS_W);
292 i2c_send(reg);
293 i2c_start();
294 i2c_send(CCS811_ADDRESS_R);
295 byte = i2c_read_no_ack();
296 i2c_stop();
297 return byte;
298}
299
300void ccs811_burst_read(uint8_t reg, uint8_t length)
301{
302 i2c_start();
303 i2c_send(CCS811_ADDRESS_W);
304 i2c_send(reg);
305 i2c_start();
306 i2c_send(CCS811_ADDRESS_R);
307
308 for (uint8_t i = 0; i < length; ++i)
309 {
310 read_buffer[i] = i2c_read_ack();
311 }
312 i2c_stop();
313}
314
315uint16_t ccs811_read_word(uint8_t reg)
316{
317 uint16_t msb;
318 uint16_t byte;
319
320 i2c_start();
321 i2c_send(CCS811_ADDRESS_W);
322 i2c_send(reg);
323 i2c_start();
324 i2c_send(CCS811_ADDRESS_R);
325
326 msb = i2c_read_ack();
327 byte = i2c_read_no_ack();
328 byte |= (msb << 8);
329
330 return byte;
331}
332
333
334/*** end of file ***/
void init_i2c(uint32_t bus_speed)
Sets pullups and initializes i2c clock to desired bus speed.
Definition: atmega_i2c.c:49
uint8_t i2c_read_no_ack(void)
Read in from slave, sending NOACK when done (no TWEA).
Definition: atmega_i2c.c:115
uint8_t i2c_read_ack(void)
Read in from slave, sending ACK when done (sets TWEA).
Definition: atmega_i2c.c:102
void i2c_stop(void)
Sends a stop condition (sets TWSTO).
Definition: atmega_i2c.c:80
void i2c_send(uint8_t data)
Loads data, sends it out, waiting for completion.
Definition: atmega_i2c.c:90
void i2c_start(void)
Sends a start condition (sets TWSTA).
Definition: atmega_i2c.c:70
void ccs811_update_env_data(uint8_t humidity, uint8_t temp)
(Optional) Write environmental data from another sensor to the CCS811.
Definition: ccs811.c:216
uint16_t ccs811_get_etvoc_level(void)
Read eTVOC (equivalent total volatile organic compounds) level from sensor.
Definition: ccs811.c:189
uint16_t ccs811_get_eco2_level(void)
Read eCO2 (equivalent carbon dioxide) level from sensor.
Definition: ccs811.c:182
char * ccs811_error_to_string(void)
For error handling/logging.
Definition: ccs811.c:231
uint8_t init_ccs811(ccs811_config_t *p_config)
Initialisation routine (run once at startup).
Definition: ccs811.c:133
void ccs811_get_alg_result_data(void)
Perform a read operation on all 8 bytes of ALG_RESULT_REGISTER.
Definition: ccs811.c:211
bool ccs811_data_ready_check(void)
Read the status register and check if the data ready flag is set.
Definition: ccs811.c:200
Driver for the CCS811 gas sensor .
I2C communications driver for AVR MCU's.
Config object, to be instantiated and values assigned to members before passing the object address in...
Definition: ccs811.h:87