MiCS6814-I2C-Firmware/MiCS6814-I2C-Firmware-V1/MiCS6814-I2C-Firmware-V1.ino

267 lines
8.1 KiB
Arduino
Raw Permalink Normal View History

/*
* 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);
}
}