diff --git a/ATmega-MiCS6814-I2C/ATmega-MiCS6814-I2C.ino b/ATmega-MiCS6814-I2C/ATmega-MiCS6814-I2C.ino new file mode 100644 index 0000000..1c74683 --- /dev/null +++ b/ATmega-MiCS6814-I2C/ATmega-MiCS6814-I2C.ino @@ -0,0 +1,249 @@ +/* + * 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; + } +} + + diff --git a/Mega2560-MiCS6814-Client/Mega2560-MiCS6814-Client.ino b/Mega2560-MiCS6814-Client/Mega2560-MiCS6814-Client.ino index 438e5ef..4743ac8 100644 --- a/Mega2560-MiCS6814-Client/Mega2560-MiCS6814-Client.ino +++ b/Mega2560-MiCS6814-Client/Mega2560-MiCS6814-Client.ino @@ -29,7 +29,7 @@ void setup() { Serial.begin(115200); // Connect to sensor through wire lib directly - Wire.begin(D3, D4); + Wire.begin(); // Check version Wire.beginTransmission(I2C_ADDR); @@ -58,6 +58,7 @@ void loop() { Wire.beginTransmission(I2C_ADDR); Wire.write(CMD_ADC_ALL); Wire.endTransmission(); + delayMicroseconds(50); uint8_t ret = Wire.requestFrom(I2C_ADDR, 6); if (ret == 6 && Wire.available() == 6) { uint16_t data[3]; @@ -72,7 +73,8 @@ void loop() { Serial.print("\t"); Serial.println(data[2]); } else { - Serial.println("Request error!"); + Serial.print("Request error! - Returned bytes: "); + Serial.println(ret); } delay(50); diff --git a/MiCS6814-I2C-Tests/MiCS6814-I2C-Tests.ino b/MiCS6814-I2C-Tests/MiCS6814-I2C-Tests.ino new file mode 100644 index 0000000..3131afc --- /dev/null +++ b/MiCS6814-I2C-Tests/MiCS6814-I2C-Tests.ino @@ -0,0 +1,42 @@ +#include + +MiCS6814 sensor; + +void setup() { + Serial.begin(115200); + if (sensor.begin()) { + sensor.powerOn(); + + Serial.println("Current base resistances:"); + Serial.print("NH3: "); + Serial.print(sensor.getBaseResistance(CH_NH3)); + Serial.print("\tRED: "); + Serial.print(sensor.getBaseResistance(CH_RED)); + Serial.print("\tOX: "); + Serial.println(sensor.getBaseResistance(CH_OX)); + + Serial.println("---"); + + Serial.println("Starting calibration"); + sensor.calibrate(); + + Serial.println("Calibration done"); + Serial.println("New base resistances:"); + Serial.print("NH3: "); + Serial.print(sensor.getBaseResistance(CH_NH3)); + Serial.print("\tRED: "); + Serial.print(sensor.getBaseResistance(CH_RED)); + Serial.print("\tOX: "); + Serial.println(sensor.getBaseResistance(CH_OX)); + + + + } + + +} + +void loop() { + // put your main code here, to run repeatedly: + +}