/* * MIT License * * Copyright (c) 2018 Nis Wechselberg * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ /** * Firmware for MiCS-6841-based I2C adapter board. * Implements a behaviour compatible to the Seeedstudio * Grove Mutichannel Gas Sensor-library. */ #include "MiCS6814-I2C-Firmware-V1.h" /** * 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_VERSION_ID)) { eeprom_write16(EEPROM_VERSION_ID, DATA_VERSION_ID); eeprom_write16(EEPROM_I2C_ADDR, DATA_I2C_ADDR); eeprom_write16(EEPROM_R0_DEFAULT_NH3, DATA_R0_DEFAULT_NH3); eeprom_write16(EEPROM_R0_DEFAULT_RED, DATA_R0_DEFAULT_RED); eeprom_write16(EEPROM_R0_DEFAULT_OX, DATA_R0_DEFAULT_OX); eeprom_write16(EEPROM_R0_NH3, DATA_R0_DEFAULT_NH3); eeprom_write16(EEPROM_R0_RED, DATA_R0_DEFAULT_RED); eeprom_write16(EEPROM_R0_OX, DATA_R0_DEFAULT_OX); } } /** * 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 at most 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 4 bits right to calculate average (div by 16) return (sum >> 4); } /** * Handler for data received from the master. * * @param availData * Number of bytes received from the master */ void receiveHandler(uint16_t numBytes) { // Check possible package sizes uint8_t cmd = 0; uint8_t data = 0; uint16_t dataArr[3]; switch (numBytes) { case 1: // A single byte usually means we are going to get a request next. // All we need to do is prepare the data for the requestHandler cmd = Wire.read(); // Check the command from master switch (cmd) { case CMD_GET_NH3: i2CResp[0] = resistanceNH3 >> 8; i2CResp[1] = resistanceNH3 & 0xFF; i2CRespLength = 2; break; case CMD_GET_RED: i2CResp[0] = resistanceRED >> 8; i2CResp[1] = resistanceRED & 0xFF; i2CRespLength = 2; break; case CMD_GET_OX: i2CResp[0] = resistanceOX >> 8; i2CResp[1] = resistanceOX & 0xFF; i2CRespLength = 2; break; case CMD_GET_ALL: i2CResp[0] = resistanceNH3 >> 8; i2CResp[1] = resistanceNH3 & 0xFF; i2CResp[2] = resistanceRED >> 8; i2CResp[3] = resistanceRED & 0xFF; i2CResp[4] = resistanceOX >> 8; i2CResp[5] = resistanceOX & 0xFF; i2CRespLength = 6; break; case CMD_GET_R0: i2CResp[0] = EEPROM.read(EEPROM_R0_NH3); i2CResp[1] = EEPROM.read(EEPROM_R0_NH3 + 1); i2CResp[2] = EEPROM.read(EEPROM_R0_RED); i2CResp[3] = EEPROM.read(EEPROM_R0_RED + 1); i2CResp[4] = EEPROM.read(EEPROM_R0_OX); i2CResp[5] = EEPROM.read(EEPROM_R0_OX + 1); i2CRespLength = 6; break; case CMD_GET_R0_DEFAULT: i2CResp[0] = EEPROM.read(EEPROM_R0_DEFAULT_NH3); i2CResp[1] = EEPROM.read(EEPROM_R0_DEFAULT_NH3 + 1); i2CResp[2] = EEPROM.read(EEPROM_R0_DEFAULT_RED); i2CResp[3] = EEPROM.read(EEPROM_R0_DEFAULT_RED + 1); i2CResp[4] = EEPROM.read(EEPROM_R0_DEFAULT_OX); i2CResp[5] = EEPROM.read(EEPROM_R0_DEFAULT_OX + 1); i2CRespLength = 6; break; default: break; } break; case 2: // Two bytes from the master could mean multiple things cmd = Wire.read(); data = Wire.read(); // Check the command from master switch (cmd) { case CMD_CHANGE_I2C: // Store new I2C address and restart connection eeprom_write16(EEPROM_I2C_ADDR, data); Wire.begin(data); break; case CMD_READ_EEPROM: // Prepare the data from eeprom for the next request i2CResp[0] = EEPROM.read(data); i2CResp[1] = EEPROM.read(data + 1); i2CRespLength = 2; break; case CMD_CONTROL_PWR: digitalWrite(HEATER, data ? LOW : HIGH); break; default: break; } break; case 7: // Seven bytes from the master are only used to set new R0 values cmd = Wire.read(); for (uint8_t i = 0; i < 3; ++i) { uint8_t msb = Wire.read(); uint8_t lsb = Wire.read(); dataArr[i] = (msb << 8) | lsb; } if (cmd == CMD_SET_R0) { eeprom_write16(EEPROM_R0_NH3, dataArr[0]); eeprom_write16(EEPROM_R0_RED, dataArr[1]); eeprom_write16(EEPROM_R0_OX, dataArr[2]); } break; default: break; } } /** * Handler for sending data to the master. * Requires data to be prepared by receiveHandler. */ void requestHandler() { Wire.write(i2CResp, i2CRespLength); } /** * Setup code for Arduino envrionment. * Called once at startup. */ void setup() { // Prepare data pins pinMode(NH3_IN, INPUT); pinMode(RED_IN, INPUT); pinMode(OX_IN, INPUT); // Prepare heater pin pinMode(HEATER, OUTPUT); // Initially disable heater digitalWrite(HEATER, HIGH); // Prepare EEPROM if needed eeprom_init(); // Read I2C address from EEPROM uint8_t i2cAddr = eeprom_read16(EEPROM_I2C_ADDR); // Connect to I2C bus Wire.begin(i2cAddr); // Register callbacks Wire.onReceive(receiveHandler); Wire.onRequest(requestHandler); } /** * Default arduino event loop */ void loop() { // Check if the timer is expired uint32_t newTime = millis(); if (newTime - timer > 1000) { // Timer is expired or overflown // (overflow of unsigned values is properly handled automatically) timer = newTime; // Update the values repeatedly resistanceNH3 = analogReadAvg(NH3_IN); resistanceRED = analogReadAvg(RED_IN); resistanceOX = analogReadAvg(OX_IN); } }