eclipse / mraa

Linux Library for low speed IO Communication in C with bindings for C++, Python, Node.js & Java. Supports generic io platforms, as well as Intel Edison, Intel Joule, Raspberry Pi and many more.
http://mraa.io
MIT License
1.38k stars 615 forks source link

How I am running my device MAX31865 on SPI with a custom CS for workaround #137

Closed oloynet closed 9 years ago

oloynet commented 9 years ago

After investigating why I can't read values from the MAX31865 (RTD-to-Digital Converter), I've develop a custom chip select. The original MRAA custom select, change of state for each bytes sended, so I've develop a custom one, just to demonstrate that could fix in MRAA lib.

The program that should works but....

/*
 *  Max31865 chip on SPI bus with MRAA
 *  http://datasheets.maximintegrated.com/en/ds/MAX31865.pdf
 *
 *  gcc -lmraa -o max31865 max31865.c
 */

#include "mraa.h"
#include <unistd.h>
#include <stdint.h>

int main(int argc, char **argv)
{
    mraa_init();
    printf("MRAA Version: %s\n\n", mraa_get_version() );

    // -------------------------------------

    printf("Init SPI (mode 3 - 1MHz)\n\n");

    mraa_spi_context spi;
    spi = mraa_spi_init(0);
    mraa_spi_mode(spi, MRAA_SPI_MODE3);
    mraa_spi_frequency(spi, 1000000);
    usleep(1000);

    // -------------------------------------

    printf("Configure Max31865\n\n");

    uint8_t tx_init[] = {0x80, 0xc3}; // see Table 2. Configuration Register Definition - page 12
    uint8_t *rx_init;

    rx_init = mraa_spi_write_buf(spi, tx_init, 2);
    printf(" > Write 0x%02x%02x -- Read 0x%02x%02x \n", tx_init[0], tx_init[1],  rx_init[0], rx_init[1]);

    usleep(1000);
    printf("\n");

    // -------------------------------------

    printf("Start reading Max31865 from address 0x00 and all the 8 registers\n\n");

    unsigned int i;

    uint8_t tx_read[] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
    uint8_t *rx_read;

    rx_read = mraa_spi_write_buf(spi, tx_read, 9);

    printf(" > Write 0x%02x -- Read 0x%02x (Start address 0x00)\n",            tx_read[0], rx_read[0]);
    printf(" > Write 0x%02x -- Read 0x%02x (Reg. Configuration)\n",            tx_read[1], rx_read[1]);
    printf(" > Write 0x%02x -- Read 0x%02x (Reg. RTD MSBs)\n",                 tx_read[2], rx_read[2]);
    printf(" > Write 0x%02x -- Read 0x%02x (Reg. RTD LSBs)\n",                 tx_read[3], rx_read[3]);
    printf(" > Write 0x%02x -- Read 0x%02x (Reg. High Fault Threshold MSB)\n", tx_read[4], rx_read[4]);
    printf(" > Write 0x%02x -- Read 0x%02x (Reg. High Fault Threshold LSB)\n", tx_read[5], rx_read[5]);
    printf(" > Write 0x%02x -- Read 0x%02x (Reg. Low Fault Threshold MSB)\n",  tx_read[6], rx_read[6]);
    printf(" > Write 0x%02x -- Read 0x%02x (Reg. Low Fault Threshold LSB)\n",  tx_read[7], rx_read[7]);
    printf(" > Write 0x%02x -- Read 0x%02x (Reg. Fault Status)\n",             tx_read[8], rx_read[8]);
    printf("\n");

    // -------------------------------------

    printf("Decode Max31865 values\n\n");

    double Rref = 4000; // for PT1000

    unsigned int RTDdata;
    unsigned int ADCcode;
    double R;
    double temp;

    RTDdata = rx_read[2] << 8 | rx_read[3]; // MSB + LSB

    if (RTDdata & 1) {
        printf(" > Sensor connection fault");

    } else {
        ADCcode = RTDdata >> 1;
        R       = (double)ADCcode * Rref / 32768;
        temp    = ((double)ADCcode / 32) - 256;

        printf(" > RTDdata = %04x\n", RTDdata);
        printf(" > ADCcode = %d\n",   ADCcode);
        printf(" > R       = %f\n",   R);
        printf(" > temp    = %f\n",   temp);
    }
}

The result is

$ gcc -lmraa -o max31865 max31865.c

$ ./max31865

MRAA Version: v0.6.0

Init SPI (mode 3 - 1MHz)

Configure Max31865

 > Write 0x80c3 -- Read 0x0000 

Start reading Max31865 from address 0x00 and all the 8 registers

 > Write 0x00 -- Read 0x00 (Start address 0x00)
 > Write 0x00 -- Read 0x00 (Reg. Configuration)
 > Write 0x00 -- Read 0x00 (Reg. RTD MSBs)
 > Write 0x00 -- Read 0x00 (Reg. RTD LSBs)
 > Write 0x00 -- Read 0x00 (Reg. High Fault Threshold MSB)
 > Write 0x00 -- Read 0x00 (Reg. High Fault Threshold LSB)
 > Write 0x00 -- Read 0x00 (Reg. Low Fault Threshold MSB)
 > Write 0x00 -- Read 0x00 (Reg. Low Fault Threshold LSB)
 > Write 0x00 -- Read 0x00 (Reg. Fault Status)

Decode Max31865 values

 > RTDdata = 0000
 > ADCcode = 0
 > R       = 0.000000
 > temp    = -256.000000

From the logic analyser you can see the CS change for each bytes

configuration max31865-1-conf

get values max31865-2-read

And now a workaround with a custom chip select

/*
 *  Max31865 chip on SPI bus with MRAA and a custom chip select on pin#8
 *  http://datasheets.maximintegrated.com/en/ds/MAX31865.pdf
 *
 *  gcc -lmraa -o max31865-custom-cs max31865-custom-cs.c
 */

#include "mraa.h"
#include <unistd.h>
#include <stdint.h>

int main(int argc, char **argv)
{
    mraa_init();
    printf("MRAA Version: %s\n\n", mraa_get_version() );

    // -------------------------------------

    printf("Init GPIO for a custom CS (pin #8 - out) \n\n");

    mraa_gpio_context cs;
    cs = mraa_gpio_init(8);
    mraa_gpio_mode(cs, MRAA_GPIO_PULLUP);
    mraa_gpio_dir(cs, MRAA_GPIO_OUT);
    mraa_gpio_write(cs, 1);
    usleep(1000);

    // -------------------------------------

    printf("Init SPI (mode 3 - 1MHz) \n\n");

    mraa_spi_context spi;
    spi = mraa_spi_init(0);
    mraa_spi_mode(spi, MRAA_SPI_MODE3);
    mraa_spi_frequency(spi, 1000000);
    usleep(1000);

    // -------------------------------------

    printf("Workaround for SPI - The first 'mraa_spi_write_buf' change the state of the clock and interfere with custom CS\n\n");

    uint8_t tx_fake[] = {0x00};
    uint8_t *rx_fake;
    rx_fake = mraa_spi_write_buf(spi, tx_fake, 1);

    // -------------------------------------

    printf("Configure Max31865\n\n");

    uint8_t tx_init[] = {0x80, 0xC3}; // see Table 2. Configuration Register Definition - page 12
    uint8_t *rx_init;

    mraa_gpio_write(cs, 0);
    rx_init = mraa_spi_write_buf(spi, tx_init, 2);
    mraa_gpio_write(cs, 1);

    printf(" > Write 0x%02x%02x -- Read 0x%02x%02x \n", tx_init[0], tx_init[1],  rx_init[0], rx_init[1]);

    usleep(1000);
    printf("\n");

    // -------------------------------------

    printf("Start reading Max31865 from address 0x00 and all the 8 registers\n\n");

    unsigned int i;

    uint8_t tx_read[] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
    uint8_t *rx_read;

    mraa_gpio_write(cs, 0);
    rx_read = mraa_spi_write_buf(spi, tx_read, 9);
    mraa_gpio_write(cs, 1);

    printf(" > Write 0x%02x -- Read 0x%02x (Start address 0x00)\n",            tx_read[0], rx_read[0]);
    printf(" > Write 0x%02x -- Read 0x%02x (Reg. Configuration)\n",            tx_read[1], rx_read[1]);
    printf(" > Write 0x%02x -- Read 0x%02x (Reg. RTD MSBs)\n",                 tx_read[2], rx_read[2]);
    printf(" > Write 0x%02x -- Read 0x%02x (Reg. RTD LSBs)\n",                 tx_read[3], rx_read[3]);
    printf(" > Write 0x%02x -- Read 0x%02x (Reg. High Fault Threshold MSB)\n", tx_read[4], rx_read[4]);
    printf(" > Write 0x%02x -- Read 0x%02x (Reg. High Fault Threshold LSB)\n", tx_read[5], rx_read[5]);
    printf(" > Write 0x%02x -- Read 0x%02x (Reg. Low Fault Threshold MSB)\n",  tx_read[6], rx_read[6]);
    printf(" > Write 0x%02x -- Read 0x%02x (Reg. Low Fault Threshold LSB)\n",  tx_read[7], rx_read[7]);
    printf(" > Write 0x%02x -- Read 0x%02x (Reg. Fault Status)\n",             tx_read[8], rx_read[8]);
    printf("\n");

    // -------------------------------------

    printf("Decode Max31865 values\n\n");

    double Rref = 4000; // for PT1000

    unsigned int RTDdata;
    unsigned int ADCcode;
    double R;
    double temp;

    RTDdata = rx_read[2] << 8 | rx_read[3]; // MSB + LSB

    if (RTDdata & 1) {
        printf(" > Sensor connection fault");

    } else {
        ADCcode = RTDdata >> 1;
        R       = (double)ADCcode * Rref / 32768;
        temp    = ((double)ADCcode / 32) - 256;

        printf(" > RTDdata = %04x\n", RTDdata);
        printf(" > ADCcode = %d\n",   ADCcode);
        printf(" > R       = %f\n",   R);
        printf(" > temp    = %f\n",   temp);
    }
}

The result is

$ gcc -lmraa -o max31865-custom-cs max31865-custom-cs.c

$ ./max31865-custom-cs

MRAA Version: v0.6.0

Init GPIO for a custom CS (pin #8 - out) 

Init SPI (mode 3 - 1MHz) 

Workaround for SPI - The first 'mraa_spi_write_buf' change the state of the clock and interfere with custom CS

Configure Max31865

 > Write 0x80c3 -- Read 0x8000 

Start reading Max31865 from address 0x00 and all the 8 registers

 > Write 0x00 -- Read 0x00 (Start address 0x00)
 > Write 0x00 -- Read 0xc1 (Reg. Configuration)
 > Write 0x00 -- Read 0x44 (Reg. RTD MSBs)
 > Write 0x00 -- Read 0xd8 (Reg. RTD LSBs)
 > Write 0x00 -- Read 0xff (Reg. High Fault Threshold MSB)
 > Write 0x00 -- Read 0xff (Reg. High Fault Threshold LSB)
 > Write 0x00 -- Read 0x00 (Reg. Low Fault Threshold MSB)
 > Write 0x00 -- Read 0x00 (Reg. Low Fault Threshold LSB)
 > Write 0x00 -- Read 0x00 (Reg. Fault Status)

Decode Max31865 values

 > RTDdata = 44d8
 > ADCcode = 8812
 > R       = 1075.683594
 > temp    = 19.375000

and some snapshots from the analyser.

configuration with custom CS max31865-custom-cs-1-conf

configuration with custom CS zoomed max31865-custom-cs-2-conf-zoom

get values with custom CS max31865-custom-cs-3-read

get values with custom CS zoomed max31865-custom-cs-4-read-zoom

I put the original signal for CS on the first row.

To conclude

I'm not a specialist of SPI, but I think when you write, the CS still to be down until the end of transmission. And not change after each bytes sended. Is it a bug from MRAA ?

Before I've writing in C, I've made unsuccessful with JS and PYTHON programs.

I don't understand why before the fist data is write, the clock goes down and up ? max31865-clock

Also an another remark, if you have severals chips on the same SPI bus, how to you fix the CS for each chips. Can the SPI functions in MRAA libs can integrate differents CS in future ? Like choosing the CS pin when transmit data... A hardware solution can be done with mixing CS from MRAA (pin 10) and the custom CS (your pin) with a OR-gate chip and connect the output to the SPI chip

Regards, Olivier

arfoll commented 9 years ago

What mraa calls a spi context or object really is a SPI Bus + HW CS. So asking for '0' will give you a spidev5.1 we could make '1' give you spidev5.2 with a different CS. I'm reluctant to do any soft CS stuff because on some devices (like galileo gen2 or edison + arduino) doing so could cause the ADC to crash or on some devices (vocore) cause the MMC to be corrupted. With that in mind, no one is stopping you from making a CS manually like you've done :)

I don't think in SPI anything says you have to keep your CS low during the whole transfer period, however as with everything SPI there may some devices that may not like you doing so. We can change this behaviour via the spi_transfer.cs_change flag & spi_transfer.delay_usecs, I admit I don't think anyone has looked at this and what the correct default behaviour should be. Currently we stick to what the default for the driver is, maybe that's not idea. We can definately let users customise those fields with a new API, however I'd like to get an idea of what a sensible delay in usecs would be for yourself (@tingleby, @KurtE any opinions on this?). It looks like on edison we take <10us between transfers so maybe that's a sensible 'default'?

michael-ring commented 9 years ago

I have had a similar problem, here are my 10 cents:

Depending on Hardware (Intel Edison for example is fine) you can set the number of bits that spi transfers in mraa. I used this to transfer 16bits to the max7219 device (look at the example on github in mraa/examples) So with the right hardware you may not need to use a custom CS

One thing I wanted to do some day is to to the SPI ioctl to disable CS (so that no other devices are disturbed) to have more than 9 bits on Raspberry Pi. As you are currently working on this, could you try the ioctl? This could make the whole thing a lot saver.

oloynet commented 9 years ago

As you said, it works in 16 bits mode without a custom CS. I've made some change in my code to support the new format. One thing to know, data are sended in LSB mode. I've try to change this with _mraa_spilsbmode but I've the error : spidev spi5.1: setup: unsupported mode bits 8 ??????

Here the new code for who are interesting

/*
 *  Max31865 chip on SPI bus with MRAA
 *  http://datasheets.maximintegrated.com/en/ds/MAX31865.pdf
 *
 *  gcc -lmraa -o max31865-16bits max31865-16bits.c
 */

#include "mraa.h"
#include <unistd.h>
#include <stdint.h>

int main(int argc, char **argv)
{
    mraa_init();
    printf("MRAA Version: %s\n\n", mraa_get_version() );

    // -------------------------------------

    printf("Init SPI (mode 3 - 1MHz)\n\n");

    mraa_spi_context spi;
    spi = mraa_spi_init(1); // 0 = spidev5.1 / 1 = spidev5.2
    mraa_spi_mode(spi, MRAA_SPI_MODE3);
    mraa_spi_frequency(spi, 1000000);

    //mraa_spi_lsbmode(spi, 0); // don't work : spidev spi5.1: setup: unsupported mode bits 8 ???
    mraa_spi_bit_per_word(spi, 16);

    usleep(1000);

    // -------------------------------------

    printf("Configure Max31865\n\n");

    uint8_t tx_init[] = {0xc3, 0x80}; // see Table 2. Configuration Register Definition - page 12
    uint8_t *rx_init;

    rx_init = mraa_spi_write_buf(spi, tx_init, 2);
    printf(" > Write 0x%02x%02x -- Read 0x%02x%02x \n", tx_init[0], tx_init[1],  rx_init[0], rx_init[1]);

    usleep(1000);
    printf("\n");

    // -------------------------------------

    printf("Start reading Max31865 from address 0x00 and all the 8 registers\n\n");

    unsigned int i;

    uint8_t reg[8];
    uint8_t tx_read[] = {0x00,0x00};
    uint8_t *rx_read;

    for (i = 0; i < 8; i++) {
        tx_read[1] = i;
        rx_read = mraa_spi_write_buf(spi, tx_read, 2);
        reg[i] = rx_read[0];
        //printf(" > Write 0x%02x%02x -- Read 0x%02x%02x\n", tx_read[0], tx_read[1],  rx_read[0], rx_read[1]);
    }
    printf("\n");

    printf(" > Reg. Configuration            = %02x\n", reg[0]);
    printf(" > Reg. RTD MSBs                 = %02x\n", reg[1]);
    printf(" > Reg. RTD LSBs                 = %02x\n", reg[2]);
    printf(" > Reg. High Fault Threshold MSB = %02x\n", reg[3]);
    printf(" > Reg. High Fault Threshold LSB = %02x\n", reg[4]);
    printf(" > Reg. Low Fault Threshold MSB  = %02x\n", reg[5]);
    printf(" > Reg. Low Fault Threshold LSB  = %02x\n", reg[6]);
    printf(" > Reg. Fault Status             = %02x\n", reg[7]);
    printf("\n");

    // -------------------------------------

    printf("Decode Max31865 values\n\n");

    double Rref = 4000; // for PT1000

    unsigned int RTDdata;
    unsigned int ADCcode;
    double R;
    double temp;

    RTDdata = reg[1] << 8 | reg[2]; // MSB + LSB

    if (RTDdata & 1) {
        printf(" > Sensor connection fault");

    } else {
        ADCcode = RTDdata >> 1;
        R       = (double)ADCcode * Rref / 32768;
        temp    = ((double)ADCcode / 32) - 256;

        printf(" > RTDdata = %04x\n", RTDdata);
        printf(" > ADCcode = %d\n",   ADCcode);
        printf(" > R       = %f\n",   R);
        printf(" > temp    = %f\n",   temp);
    }
}

Now the CS is set low state during all the 16 bits transfer (ex. for the configuration) max31865-16bits

@arfoll : I understand it's difficult to change the CS by soft because it had repercussion on other parts like you mention, and also on spidev dependance. Do I close this issue and open one for customer parameters for SPI ?

Thanks for all answers.

arfoll commented 9 years ago

@oloynet this is weird, wonder if it's like this on other platforms I'll investigate and get back to you - keeping this bug open is fine, bit long but that's life ehehe! @michael-ring by disabling cs_change I wonder if we can just disable the HW CS. Again I'll try this and get back, if it's really that easy then it makes sense to do this IMHO.

michael-ring commented 9 years ago

I guess it is that simple, search for no-cs in https://www.kernel.org/doc/Documentation/spi/spidev_test.c

We can make the whole thing (nearly) transparent to the end user if the cs is not needed for more that 32 bits. On the Raspberry it might be that we can use the old CS pin, even though it is reserved by SPI. But I did not do any serious testing on that. If you consider implementing a feature for Soft-CS then I can dig deeper on Raspberry. Which other platforms would benefit? Beaglebone is fine, Edison is fine, both can do 32bits ootb, what about the other intel boards?

arfoll commented 9 years ago

I dont understand - "the cs is not needed for more that 32 bits"? Can you explain?

michael-ring commented 9 years ago

usually I see CS for 8,9,16 and 32 bits on spi devices. For those cases soft-spi could work as a drop in replacement for platforms like Raspberry that can only do 8 or 9 bit cs. The other usecase I see is to extend the number of available cs pins, but this would need more work because then there is a need to support 'virtual' spi devices that use the real spi device in no_cs mode and do the handling of the assigned gpio pin. This would make it possibe to have a lot more devices on the SPI bus.

The first case is fairly easy to implement, would mean that a device can get initialized with no_cs and there should be a new function that allows you to assign a gpio pin as replacement-CS. Then the whole logic for using cs can get hidden in the functions that send/receive byte,word and (perhaps) long data.

arfoll commented 9 years ago

Just to give a small update - this looks like an edison spi kernel issue and not a mraa issue, it's happening only on that platform. We're working on our end on to find out what's wrong there.

@michael-ring on rpi that sounds like the spi kernel driver should fix the CS, alot of platforms (edison included) use a gpio in kernel to replace the HW CS in cases when it's not possible to get the HW to do the right thing. As for adding 'soft-cs' from userspace, let's talk about that in a different issue :)

KurtE commented 9 years ago

Quick notes:

there appear to be issues with SPI with current beta. Sample program at top of this message tries to work around with output 0 character to get CS right... Appears to be new power management issue, I talk about in thread: https://communities.intel.com/thread/60326 I also mention a hack to the file I did to get the Adafruit display to work. (Intel_mid_ssp_spi.c)

Actually I think Edison uses normal IO pin in the kernel to do CS for SPIDEV device. (platform_spidev.c)

MRAA: Not currently supporting spi_ioc_transfer.cs_change field. Also not sure exactly how it is supposed to work. From Kernel, it looks like if asserted it does not change CS line? Some places imply that it should leave the CS asserted when the transfer completes.

arfoll commented 9 years ago

@KurtE yes edison should use a normal IO pin for cs spidev but it seems it does not in the case of writing bytes which is weird - do you know if the behaviour is the same on arduino code? I haven't tried that. Anyways the edison kernel guys are on it, I'll post an update here.

cs_change does something else you're right, we have to change spi mode thought that doesn't seem to work, I tried with spidev_test like:

root@edison:~# ./spidev_test -D /dev/spidev5.1 -s 1000000 -N
can't set default spi mode: Invalid argument
Aborted

So it seems that changing the spi mode doesn't work on edison - if it works on other platforms then I'm happy to include something like this http://dpaste.com/1N276PZ or similar to disable the HW cs - we can just disable it on platforms where this is not possible.

michael-ring commented 9 years ago

Nice!

I would like to take this code and extend it a little, please keep me posted when you add more functionality to the disable cs code.

arfoll commented 9 years ago

Since this thread got too long, #149 is for NOCS discussion. The edison issue of CS going high between every byte is now fixed.

sriranjanr commented 9 years ago

Hello,

I also want to use a custom chipselect.I want the CS to be high initially just before the transfer and go low with the first falling edge of the SCK first bit transfered. How do I achieve this?I m working with edison and I also need 16 bit transfers My CS (yellow) and SCK (green) should be as in the following diagram. img_20150801_151815