upm  1.7.1
Sensor/Actuator repository for libmraa (v2.0.0)
Making a UPM module for MAX31855

The Maxim Integrated MAX31855 is a thermocouple amplifier allowing you to read from a K type thermocouple. My board comes from the Pmod kit form Maxim (MAX31855PMB1) but you can get this from many different sources. The adafruit people made arduino code already so we'll use that as a reference.

Basics

This is a spi module so we will use the mraa spi functions to build our module. First thing to do is to create a tree structure like this in upm/src/max31855:

And then an example file to use & test our lib with in upm/examples/max31855.cxx.

Swig

The .i files are used by swig, there is one for each python & javascript. They contain essentially the same thing and are very simple. The only thing to change between the javascript & node.js one is the argument to module.

The include parameter defines which functions will be available to the node/python module created, Whilst the headers inside %{} will be explicitly required during compilation. Typically only the top level header is required in either of those args. The upm.i is just a shortcut to include some commonly used swig wrappers for UPM sensors, it's not obligatory but recommended.

API

Then we create the header (max31855.hpp) , a very simple header in our case we will have only a very basic api. We provide a getTemp() function which will return the same type as in the arduino library, a double.

double getTemp();

Note that the header contains both the io that we will use, the gpio is in this case used as the chip select pin.

Implementing our API

In the adafruit library the read function (our chip is a 3pin SPI so only read is possible), the spiread32() does all the work. It starts by setting up the io so we will do the same in our constructor.

Note unlike on Arduino, we'll just set a 2Mhz clock and let the chip do the work.

MAX31855::MAX31855(int bus, int cs)
{
// initialise chip select as a normal gpio
if ( !(m_gpio = mraa_gpio_init(cs)) )
{
throw std::invalid_argument(std::string(__FUNCTION__) +
": mraa_gpio_init(cs) failed, invalid pin?");
return;
}
mraa_gpio_dir(m_gpio, MRAA_GPIO_OUT);
// initialise the spi bus with a 2Mhz clock
m_sensor = mraa_spi_init(bus);
mraa_spi_frequency(m_sensor, 2000000);
}

Then we also need to implement a nice cleanup in our destructor.

MAX31855::~MAX31855()
{
// close both m_sensor & m_gpio cleanly
mraa_result_t error;
error = mraa_spi_stop(m_sensor);
if (error != MRAA_SUCCESS) {
mraa_result_print(error);
}
error = mraa_gpio_close(m_gpio);
if (error != MRAA_SUCCESS) {
mraa_result_print(error);
}
}

Then to read data, we will use spi_write_buf which will allow us to write a whole uint32_t in order to get one back, which is what the arduino code does in spiread32. Obviously we set our chip select to low first. Here is the start of the implementation of MAX31855::getTemp()

// set chip select low
mraa_gpio_write(m_gpio, 0);
uint8_t buf[4];
// set our input buffer to 0, this is clean but not required
memset(buf, 0, sizeof(uint8_t)*4);
// Write buffer to the spi slave
uint8_t* x = mraa_spi_write_buf(m_sensor, buf, 4);

Then using the arduino code as reference we simply reconstruct form the 4 uint8_t values a 32bit int value and select only the valuable parts of information from that. The MAX31855 datasheet explains exactly which bits are useful, we will just do the same as the adafruit code, first checking the error bit and then scrapping everything but the 14bit of thermocouple data that are useful to us and converting it to a double.

// Endian correct way of making our char array into an 32bit int
int32_t temp = (x[0] << 24) | (x[1] << 16) | (x[2] << 8) | x[3];;
// mraa_spi_write_buf does not free the return buffer
free(x);
if (temp & 0x7) {
std::cerr << "Something went very wrong!" << std::endl;
}
// scrap all the data we dont care about
temp >>= 18;
// LSB = 0.25 degrees C
double c = (double) temp;
c *= 0.25;

Finalizing

Our final example, very easy to use API!

#include "max31855.hpp"
int
main(int argc, char** argv)
{
upm::MAX31855 temp(0, 8);
std::cout << temp.getTemp() << std::endl;
return 0;
}

Building

The we need to add it to the examples/CMakeLists.txt. Only three lines are required

1 add_executable (max31855-example max31855.cxx)
2 include_directories (${PROJECT_SOURCE_DIR}/src/max31855)
3 target_link_libraries (max31855-example max31855 ${CMAKE_THREAD_LIBS_INIT})

Note you don't have to rebuild everything, cmake keeps target lists so if you named your example target modulename-example you can simply do make max31855-example and both the library & example will build.