otp538u: some fixes to get working on the Arduino 101 with Firmata

There are some issues using this device on the 101 with Firmata:

1. You cannot use any other ADC resolution than 1024.  By default the
driver would try to set 12b resolution for improved accuracy.  Doing
this on the 101 yielded nonsensical readings causing the driver to
fail.  Using 10b resolution will yield less accuracy, but at least the
driver will function.

2. After the first ADC read, and for some time period after, the MRAA
aio_read() calls will always return 0.  This would cause an exception
to be thrown by the driver since this is an invalid reading.  Now, we
do an analog read on each channel and sleep for .5 seconds in the ctor
to get around this problem.  It is a hack and should be properly fixed
somewhere else (firmata?  MRAA?).

Some code was reworked/renamed to make it more clear what is actually
going on.  In addition a setDebug() method was added to enable some
debugging output.

Signed-off-by: Jon Trulson <jtrulson@ics.com>
This commit is contained in:
Jon Trulson 2016-08-16 11:07:56 -06:00
parent 19d1af6a48
commit dc93fb11ff
3 changed files with 135 additions and 31 deletions

View File

@ -25,6 +25,7 @@
#include <unistd.h>
#include <iostream>
#include <iomanip>
#include <stdexcept>
#include <signal.h>
#include "otp538u.hpp"
@ -52,14 +53,24 @@ int main()
// Object temperature.
upm::OTP538U *temps = new upm::OTP538U(0, 1, OTP538U_AREF);
// enable debugging if you would like
// temps->setDebug(true);
// Output ambient and object temperatures
while (shouldRun)
{
cout << "Ambient temp: " << std::fixed << setprecision(2)
<< temps->ambientTemperature()
<< " C, Object temp: " << temps->objectTemperature()
<< " C" << endl;
try {
cout << "Ambient temp: " << std::fixed << setprecision(2)
<< temps->ambientTemperature()
<< " C, Object temp: " << temps->objectTemperature()
<< " C" << endl;
}
catch (std::out_of_range& e) {
cerr << "Temperature(s) are out of range: " << e.what()
<< endl;
}
cout << endl;
sleep(1);
}
//! [Interesting]

View File

@ -36,9 +36,24 @@ using namespace std;
OTP538U::OTP538U(int pinA, int pinO, float aref)
{
const int adcHighRes = 4095;
const int adcLowRes = 1023;
// for subplatforms like the Arduino 101, we need to limit ADC
// resolution to 10b currently. For this sensor unfortunately, this
// means readings will be less accurate. This sensor really does
// need to measure with about 1mV accuracy.
bool isSubplatform = false;
m_debug = false;
if (pinA >= 512 || pinO >= 512)
isSubplatform = true;
// this is the internal voltage reference on the Grove IR temp
// sensor module
m_vref = 2.5;
// sensor module for the thermistor.
m_internalVRef = 2.5;
// analog reference in use
m_aref = aref;
@ -51,29 +66,72 @@ OTP538U::OTP538U(int pinA, int pinO, float aref)
// can adjust as neccessary depending on your calibration.
m_offsetVoltage = 0.014;
// We need around 1mV resolution, so use 12 bit resolution (4096)
// with a default aref of 5.0.
m_adcResolution = 4096;
// We need around 1mV resolution (preferred), so use 12 bit
// resolution (4096) if we can.
//
// This logic is over complicated due to the fact that it is
// currently difficult to determine exactly what the capabilities of
// the platform (sub or otherwise) actually are. So for
// subplatforms, we always limit to 1024. Otherwise, we try 12b if
// the mraa_adc_raw_bits() says we can, though this isn't
// particularly accurate either, as it reports that the G2 can do
// 12b, when in reality it can not. We are just lucky that it works
// anyway (ie: will give 12b resolution, though underneath it's just
// scaling the real 10b value.). Sigh. But trying 12b resolution
// on the 101 via firmata will definitely break things, so don't
// even try until whatever the problem it has with 12b is fixed.
if (isSubplatform)
{
m_adcResolution = adcLowRes; // 10b
}
else
{
if (mraa_adc_raw_bits() == 12)
m_adcResolution = adcHighRes; // 12b
else
m_adcResolution = adcLowRes; // 10b
}
// notify the user
if (m_adcResolution == adcLowRes)
cerr << "Using 10 bit ADC resolution. Values will be less accurate."
<< endl;
if ( !(m_aioA = mraa_aio_init(pinA)) )
{
throw std::invalid_argument(std::string(__FUNCTION__) +
": mraa_gpio_init(pinA) failed, invalid pin?");
": mraa_gpio_init(pinA) failed");
return;
}
// enable 12 bit resolution
mraa_aio_set_bit(m_aioA, 12);
// enable 12 bit resolution, if we can
if (m_adcResolution == adcHighRes)
mraa_aio_set_bit(m_aioA, 12);
if ( !(m_aioO = mraa_aio_init(pinO)) )
{
throw std::invalid_argument(std::string(__FUNCTION__) +
": mraa_gpio_init(pinO) failed, invalid pin?");
": mraa_gpio_init(pinO) failed");
return;
}
// enable 12 bit resolution
mraa_aio_set_bit(m_aioO, 12);
// enable 12 bit resolution, if we can
if (m_adcResolution == adcHighRes)
mraa_aio_set_bit(m_aioO, 12);
if (isSubplatform)
{
// The first analog read always seems to return 0 on the 101
// with firmata, so just do a couple of reads here and discard
// them. Then sleep for half a second. THIS IS A HACK. The
// real problem should be fixed elsewhere (Firmata?).
mraa_aio_read(m_aioA);
mraa_aio_read(m_aioO);
usleep(500000);
}
}
OTP538U::~OTP538U()
@ -87,7 +145,7 @@ float OTP538U::ambientTemperature()
const int samples = 5;
int val = 0;
float temp = 0;
float res;
float res = 0;
for (int i=0; i<samples; i++)
{
@ -96,15 +154,25 @@ float OTP538U::ambientTemperature()
throw std::runtime_error(std::string(__FUNCTION__) +
": failed to do aio read");
}
temp += val;
temp += (float)val;
usleep(10000);
}
temp = temp / samples;
temp = temp * m_aref / m_adcResolution;
float volts = temp * m_aref / m_adcResolution;
if (m_debug)
{
cerr << "\tAMB sample " << temp << " m_aref " << m_aref
<< " VOLTS " << volts << endl;
}
// compute the resistance of the thermistor
res = m_vResistance * temp / (m_vref - temp);
res = m_vResistance * volts / (m_internalVRef - volts);
if (m_debug)
{
cerr << "\tAMB computed resistance: " << res << endl;
}
// look it up in the thermistor (RT) resistence/temperature table
int rawslot;
@ -119,7 +187,7 @@ float OTP538U::ambientTemperature()
if (j >= otp538u_rt_table_max)
{
throw std::out_of_range(std::string(__FUNCTION__) +
": ambient temperature out of range.");
": ambient temperature out of range (high).");
return 0;
}
@ -133,7 +201,7 @@ float OTP538U::ambientTemperature()
if (slot < 0)
{
throw std::out_of_range(std::string(__FUNCTION__) +
": ambient temperature out of range.");
": ambient temperature out of range (low).");
return 0;
}
@ -168,9 +236,18 @@ float OTP538U::objectTemperature()
temp = temp / samples;
float temp1 = temp * m_aref / m_adcResolution;
float sensorVolts = temp1 - (reference_vol + m_offsetVoltage);
// cout << "Sensor Voltage: " << sensorVolts << endl;
if (m_debug)
cerr << "\tOBJ sample " << temp << " ";
float volts = temp * m_aref / m_adcResolution;
if (m_debug)
cerr << "VOLTS: " << volts << " ";
float sensorVolts = volts - (reference_vol + m_offsetVoltage);
if (m_debug)
cerr << "Sensor Voltage (computed): " << sensorVolts << endl;
// search the VT (voltage/temperature) table to find the object
// temperature.
@ -198,8 +275,11 @@ float OTP538U::objectTemperature()
( otp538u_vt_table[slot + 1][voltOffset] -
otp538u_vt_table[slot][voltOffset] );
// cout << "TABLE VALUE [" << slot << "][" <<
// voltOffset << "] = " << otp538u_vt_table[slot][voltOffset] << endl;
if (m_debug)
{
cerr << "\tVoltage (" << voltage << "): TABLE VALUE [" << slot << "][" <<
voltOffset << "] = " << otp538u_vt_table[slot][voltOffset] << endl;
}
return (ambTemp + objTemp);
}

View File

@ -65,6 +65,10 @@ namespace upm {
* These tables assume the object to be measured is 9 cm (3.54
* inches) from the sensor.
*
* This sensor will not work at 3.3v on the Edsion or the Galileo 2.
* It works fine on both systems at 5v. It will work at 3.3v on the
* Arduino 101 (tested via firmata subplatform on edison).
*
* @image html otp538u.jpg
* @snippet otp538u.cxx Interesting
*/
@ -135,13 +139,22 @@ namespace upm {
* looking at the EAGLE files containing their schematics for this
* device.
*
* @param vref Reference voltage of the internal sensor; default is 2.5 V
* @param vref Reference voltage of the internal sensor; default
* is 2.5
*/
void setVRef(float vref) { m_vref = vref; };
void setVRef(float vref) { m_internalVRef = vref; };
/**
* Enable debugging output.
*
* @param true to enable some debug output, false otherwise
*/
void setDebug(bool enable) { m_debug = enable; };
private:
float m_vref;
bool m_debug;
float m_internalVRef;
float m_aref;
int m_vResistance;
float m_offsetVoltage;