Added version 1 firmware, replicating Grove sensor behaviour
This commit is contained in:
parent
a0496aead5
commit
b416a72add
2 changed files with 373 additions and 0 deletions
107
MiCS6814-I2C-Firmware-V1/MiCS6814-I2C-Firmware-V1.h
Normal file
107
MiCS6814-I2C-Firmware-V1/MiCS6814-I2C-Firmware-V1.h
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* Using ATmega core from:
|
||||||
|
* https://raw.githubusercontent.com/carlosefr/atmega/master/package_carlosefr_atmega_index.json
|
||||||
|
*
|
||||||
|
* Pin mapping:
|
||||||
|
* Heater Enable (out): PB0 / Digital 8 (Active low)
|
||||||
|
* NH3 resistance (in): PC0 / Analog 0
|
||||||
|
* RED resistance (in): PC1 / Analog 1
|
||||||
|
* OX resistance (in): PC2 / Analog 2
|
||||||
|
*
|
||||||
|
* SDA: PC4 / Analog 4
|
||||||
|
* SCL: PC5 / Analog 5
|
||||||
|
*/
|
||||||
|
#ifndef MiCS6814_V1_H
|
||||||
|
#define MiCS6814_V1_H
|
||||||
|
|
||||||
|
|
||||||
|
// Include Wire library for I2C support
|
||||||
|
#include <Wire.h>
|
||||||
|
#include <EEPROM.h>
|
||||||
|
|
||||||
|
|
||||||
|
// Pin mapping constants
|
||||||
|
#define HEATER 8
|
||||||
|
#define NH3_IN A0
|
||||||
|
#define RED_IN A1
|
||||||
|
#define OX_IN A2
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* I2C commands
|
||||||
|
*
|
||||||
|
* Taken from Grove Mutichannel Gas Sensor Library, just some renaming done here
|
||||||
|
*/
|
||||||
|
#define CMD_GET_NH3 1 // Retrieve current resistance for NH3 sensor channel
|
||||||
|
#define CMD_GET_RED 2 // Retrieve current resistance for RED sensor channel
|
||||||
|
#define CMD_GET_OX 3 // Retrieve current resistance for OX sensor channel
|
||||||
|
#define CMD_GET_ALL 4 // Retrieve all sensor resistance channels
|
||||||
|
#define CMD_CHANGE_I2C 5 // Change I2C address of the sensor
|
||||||
|
#define CMD_READ_EEPROM 6 // Read stored uint16_t data from EEPROM (MSB is transmitted first)
|
||||||
|
#define CMD_SET_R0 7 // Set custom R0 values
|
||||||
|
#define CMD_GET_R0 8 // Retrieve current R0 values
|
||||||
|
#define CMD_GET_R0_DEFAULT 9 // Retrieve default R0 values
|
||||||
|
#define CMD_CONTROL_LED 10 // Control status LED (no LED on my board, but Grove supports is, so ... meh!)
|
||||||
|
#define CMD_CONTROL_PWR 11 // Heater control
|
||||||
|
|
||||||
|
|
||||||
|
/* EEPROM Addresses
|
||||||
|
*
|
||||||
|
* These match the addresses used in the Grove library
|
||||||
|
* If the library was implemented in a sensible fashion I would
|
||||||
|
* be free to use my own mapping here.
|
||||||
|
* However, the library accesses eeprom data directly quite frequently, so ... meh!
|
||||||
|
*/
|
||||||
|
#define EEPROM_VERSION_ID 0 // Identifier for the current version, Grove uses value 1126 to identify version 2 of their sensor.
|
||||||
|
#define EEPROM_R0_DEFAULT_NH3 2
|
||||||
|
#define EEPROM_R0_DEFAULT_RED 4
|
||||||
|
#define EEPROM_R0_DEFAULT_OX 6
|
||||||
|
#define EEPROM_R0_NH3 8
|
||||||
|
#define EEPROM_R0_RED 10
|
||||||
|
#define EEPROM_R0_OX 12
|
||||||
|
#define EEPROM_I2C_ADDR 20
|
||||||
|
|
||||||
|
#define DATA_VERSION_ID 1126
|
||||||
|
#define DATA_I2C_ADDR 0x04
|
||||||
|
#define DATA_R0_DEFAULT_NH3 123
|
||||||
|
#define DATA_R0_DEFAULT_RED 123
|
||||||
|
#define DATA_R0_DEFAULT_OX 123
|
||||||
|
|
||||||
|
|
||||||
|
// Runtime data
|
||||||
|
uint32_t timer; // Timer value of last update
|
||||||
|
uint16_t resistanceNH3; // Current resistance value for NH3 channel
|
||||||
|
uint16_t resistanceRED; // Current resistance value for RED channel
|
||||||
|
uint16_t resistanceOX; // Current resistance value for OX channel
|
||||||
|
uint8_t i2CResp[6]; // Buffer for I2C response
|
||||||
|
uint8_t i2CRespLength; // Amount of bytes to be sent to master
|
||||||
|
|
||||||
|
#endif
|
266
MiCS6814-I2C-Firmware-V1/MiCS6814-I2C-Firmware-V1.ino
Normal file
266
MiCS6814-I2C-Firmware-V1/MiCS6814-I2C-Firmware-V1.ino
Normal file
|
@ -0,0 +1,266 @@
|
||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue