/* * ATmega controller for MiCS-6814 gas sensor - I2C Adapter * * Adapted from Firmware for Groove Multichannel Gas Sensor * Using ATmega core from: * https://raw.githubusercontent.com/carlosefr/atmega/master/package_carlosefr_atmega_index.json * * Pin mapping: * Heater Enable (out): PB0 / Digital 8 (Inverted logic) * NH3 resistance (in): PC0 / Analog 0 * CO resistance (in): PC1 / Analog 1 * NO2 resistance (in): PC2 / Analog 2 * SDA: PC4 * SCL: PC5 */ #include #include // Pin Constants #define HEATER_EN 8 #define NH3_IN A0 #define CO_IN A1 #define NO2_IN A2 // I2C Communication (Taken from Groove sensor, hoping to use the same library on client side) #define DEFAULT_I2C_ADDR 0x04 #define CMD_ADC_NH3 1 // NH3 channel #define CMD_ADC_CO 2 // CO channel #define CMD_ADC_NO2 3 // NO2 channel #define CMD_ADC_ALL 4 // All channels #define CMD_CHANGE_I2C 5 // I2C address change #define CMD_READ_EEPROM 6 // Read stored data from EEPROM as unsigned int #define CMD_SET_R0_ADC 7 // Set R0 ADC value #define CMD_GET_R0_ADC 8 // Get R0 ADC value #define CMD_GET_R0_ADC_DEFAULT 9 // Get factory R0 ADC value #define CMD_CONTROL_LED 10 // Control LED (no LED on my board, but Groove supports is, so ... meh!) #define CMD_CONTROL_PWR 11 // Heater control // EEPROM Addresses (Match Groove addresses) #define EEPROM_INIT_DONE 0 #define EEPROM_DEFAULT_ADC_NH3 2 #define EEPROM_DEFAULT_ADC_CO 4 #define EEPROM_DEFAULT_ADC_NO2 6 #define EEPROM_ADC_NH3 8 #define EEPROM_ADC_CO 10 #define EEPROM_ADC_NO2 12 #define EEPROM_I2C_ADDR 20 // Runtime storage for data uint32_t timer; // Timer value of last update uint8_t adcData[6]; // Storage for the current resistance values uint8_t i2CCmd; // Current command passed from onReceive to onRequest uint8_t i2CData; // Command parameters uint8_t i2CResp[6]; // Buffer for I2C response /** * EEPROM helper function to write a single uint16_t value * to two adjacient eeprom cells. * The upper half (MSB) will be written to the given address, * the lower half (LSB) will be written to the next address. * * @param addr * The eeprom addr to store the MSB in. * The next eeprom address will be used for the LSB. * @param value * The uint16_t value to store. */ void eeprom_write16(uint16_t addr, uint16_t value) { // Store MSB in address, shift value and mask EEPROM.write(addr, (value >> 8) & 0xFF); // Store LSB in next address EEPROM.write(addr + 1, value & 0xFF); } /** * EEPROM helper function to read a single uint16_t value * from two adjacient eeprom cells. * * @param addr * The eeprom addr to read the MSB from. * The the LSB will be read from the next eeprom address. * @return The stored uint16_t value. */ uint16_t eeprom_read16(uint16_t addr) { // Read two bytes separately uint8_t msb = EEPROM.read(addr); uint8_t lsb = EEPROM.read(addr + 1); // Combine through shifting and bitwise-or return (msb << 8) | lsb; } /** * Initialize EEPROM values if not already written */ void eeprom_init() { // Check if data has already been stored in EEPROM if (eeprom_read16(EEPROM_INIT_DONE)) { eeprom_write16(EEPROM_INIT_DONE, 1126); eeprom_write16(EEPROM_I2C_ADDR, DEFAULT_I2C_ADDR); eeprom_write16(EEPROM_DEFAULT_ADC_NH3, 123); eeprom_write16(EEPROM_DEFAULT_ADC_CO, 123); eeprom_write16(EEPROM_DEFAULT_ADC_NO2, 123); eeprom_write16(EEPROM_ADC_NH3, 123); eeprom_write16(EEPROM_ADC_CO, 123); eeprom_write16(EEPROM_ADC_NO2, 123); } } /** * Read the analog pin repeatedly and return the average value. * * @param pin * The (analog) pin to read from. * @return The average value read. */ uint16_t analogReadAvg(uint8_t pin) { // Analog read returns value between 0 and 1024, so at most 10 bits are needed. // If we read 64 times we can just store that in 16 bits of a uint16_t. uint16_t sum = 0; for (int c = 0; c < 16; ++c) { sum += analogRead(pin); } // Shift 6 bits right to calculate average (div by 64) return (sum >> 4); } /** * Reads the current values from the sensor * and stores the values in the global data. */ void updateValues() { uint16_t adcNH3 = analogReadAvg(NH3_IN); uint16_t adcCO = analogReadAvg(CO_IN); uint16_t adcNO2 = analogReadAvg(NO2_IN); adcData[0] = adcNH3 >> 8; // MSB of NH3 value adcData[1] = adcNH3 & 0xFF; // LSB of NH3 value adcData[2] = adcCO >> 8; // MSB of CO value adcData[3] = adcCO & 0xFF; // LSB of CO value adcData[4] = adcNO2 >> 8; // MSB of NO2 value adcData[5] = adcNO2 & 0xFF; // LSB of NO2 value } /** * Handler for data received from the master. */ void receiveCallback(uint16_t availData) { // Check possible package sizes if (availData == 1) { // Store command for the requestCallback i2CCmd = Wire.read(); } else if (availData == 2) { // Command and one byte of data i2CCmd = Wire.read(); i2CData = Wire.read(); // Process direct commands if (i2CCmd == CMD_CHANGE_I2C) { // Store new address in eeprom and restart Wire library with new address eeprom_write16(EEPROM_I2C_ADDR, i2CData); Wire.begin(i2CData); } else if (i2CCmd == CMD_CONTROL_PWR) { if (i2CData) { // Turn heater on digitalWrite(HEATER_EN, LOW); } else { // Turn heater off digitalWrite(HEATER_EN, HIGH); } } } else if (availData == 7) { // Lots of data, only happens for new calilbration data i2CCmd = Wire.read(); uint16_t newADCData[3]; // Read all three channels for (int i = 0; i < 3; ++i) { uint8_t msb = Wire.read(); uint8_t lsb = Wire.read(); newADCData[i] = (msb << 8) | lsb; } // Check if command valid if (i2CCmd == CMD_SET_R0_ADC) { eeprom_write16(EEPROM_ADC_NH3, newADCData[0]); eeprom_write16(EEPROM_ADC_CO, newADCData[1]); eeprom_write16(EEPROM_ADC_NO2, newADCData[2]); } } } /** * Handler for sending data to the master. * Requires command to be set by receiveCallback. */ void requestCallback() { switch (i2CCmd) { // Resistor data case CMD_ADC_NH3: Wire.write(&adcData[0], 2); break; case CMD_ADC_CO: Wire.write(&adcData[2], 2); break; case CMD_ADC_NO2: Wire.write(&adcData[4], 2); break; case CMD_ADC_ALL: Wire.write(adcData, 6); break; case CMD_READ_EEPROM: i2CResp[0] = EEPROM.read(i2CData); i2CResp[1] = EEPROM.read(i2CData + 1); Wire.write(i2CResp, 2); break; case CMD_GET_R0_ADC: i2CResp[0] = EEPROM.read(EEPROM_ADC_NH3); i2CResp[1] = EEPROM.read(EEPROM_ADC_NH3 + 1); i2CResp[2] = EEPROM.read(EEPROM_ADC_CO); i2CResp[3] = EEPROM.read(EEPROM_ADC_CO + 1); i2CResp[4] = EEPROM.read(EEPROM_ADC_NO2); i2CResp[5] = EEPROM.read(EEPROM_ADC_NO2 + 1); Wire.write(i2CResp, 6); break; case CMD_GET_R0_ADC_DEFAULT: i2CResp[0] = EEPROM.read(EEPROM_DEFAULT_ADC_NH3); i2CResp[1] = EEPROM.read(EEPROM_DEFAULT_ADC_NH3 + 1); i2CResp[2] = EEPROM.read(EEPROM_DEFAULT_ADC_CO); i2CResp[3] = EEPROM.read(EEPROM_DEFAULT_ADC_CO + 1); i2CResp[4] = EEPROM.read(EEPROM_DEFAULT_ADC_NO2); i2CResp[5] = EEPROM.read(EEPROM_DEFAULT_ADC_NO2 + 1); Wire.write(i2CResp, 6); break; default: break; } }