0

I've attached a soil hygrometer I bought on ebay to my rpi via an MCP3008 ADC. All I want is a small program that can spit out the current value as an integer. I don't want to use Python...

I must emphasize that I'm not a C programmer at all, so I'm pretty much stumbling in the dark here.

My problem is that my program's output varies a lot. The value varies by about 20 every time I run it. If I use the Python implementation of SPI that everyone refer to I get a pretty stable value. Also, it's a little higher than the largest value my C program returns.

Here's my code:

#include <stdint.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>

#define ARRAY_SIZE(array) sizeof(array)/sizeof(array[0])

static const char *device = "/dev/spidev0.0";
static uint8_t mode = SPI_MODE_0;
static uint8_t bits = 8;
static uint32_t speed = 1000000;
static uint16_t delay = 0;

static int prepare(int fd) {
    int ret;
    if ((ret = ioctl(fd, SPI_IOC_WR_MODE, &mode)) == -1) {
        perror("Can't set mode");
        return ret;
    }

    if ((ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits)) == -1) {
        perror("Can't set number of bits");
        return ret;
    }

    if ((ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed)) == -1) {
        perror("Can't set write speed");
        return ret;
    }

    if ((ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed)) == -1) {
        perror("Can't set read speed");
        return ret;
    }

    return 0;
}


static void transfer(int fd, uint8_t tx[], uint8_t rx[]) {
    int ret;
    struct spi_ioc_transfer tr = {
        .tx_buf = (unsigned long)tx,
        .rx_buf = (unsigned long)rx,
        .len = ARRAY_SIZE(tx),
        .delay_usecs = delay,
        .speed_hz = speed,
        .bits_per_word = bits,
    };

    ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
    if (ret == 1) {
        perror("Can't send API message");
        abort();
    }

    printf("%d\n", ((rx[2] & 3) << 8) + rx[3]);
    for (ret = 0; ret < ARRAY_SIZE(tx); ret++) {
        if (!(ret % 6))
            puts("");
        printf("%.2X ", rx[ret]);
    }
    puts("");
}

int main(int argc, char **argv) {
    int i,fd;
    int ret = 0;
    uint8_t wr_buf[]={0b0000, 0b0001, 0b1000, 0b0000, 0b0000, 0b0000, 0b0000, 0b0000};
    uint8_t rd_buf[8];

    fd = open(device, O_RDWR);
    if (fd<=0) {
        printf("Device %s not found\n", device);
        exit(1);
    }

    if ((ret = prepare(fd)) == 0)
        transfer(fd, wr_buf, rd_buf);

    close(fd);
    return ret;
}

This is an example of the Python code that works, taken from here

import spidev

spi = spidev.SpiDev()
spi.open(0,0)

def readadc(adcnum):
    if ((adcnum > 7) or (adcnum < 0)):
            return -1
    r = spi.xfer2([1,(8+adcnum)<<4,0])
    adcout = ((r[1]&3) << 8) + r[2]
    return adcout

value = readadc(0)

Could somebody give me any hints?

  • I don't want to use Python... -- what's wrong with python? it works, gives you good results... – lenik Jul 21 '13 at 4:33
  • I haven't used spidev but I don't see any glaring errors in your C code. Python is built on the native C libraries, so if it works in python, you should be able to make it work in C. I guess you could look at the C source for the python lib... – goldilocks Jul 21 '13 at 8:35
  • @lenik My main program is written in node.js and I would like to convert the C program into some sort of library later on. – fiskeben Jul 21 '13 at 10:29
  • @goldilocks Yes, I've looked at it already. Will look some more. – fiskeben Jul 21 '13 at 10:29
  • 1
    Isn't your wr_buf wrong? After quick look at datasheet I believe You should send 3 bytes of data, not four. Your buffer seems to have proper values if you remove last byte. There is start bit in tx buffer so chances are that this is not a problem for ADC chip but you should change the address in rx[] buffer from which you read the data (I believe it should be rx[1]&3+rx[2]). – Krzysztof Adamski Jul 23 '13 at 10:46
2

Based on the Python code I believe they are sending slightly different things on the SPI bus.

On line 74, wr_buf should only be 3 bytes long, assuming adcnum is 0:

uint8_t wr_buf[] = {1, 128, 0};

or as binary:

uint8_t wr_buf[] = {0b00000001, 0b10000000, 0b00000000};

Also the read buffer on line 75 only needs to be 3 bytes long:

uint8_t rd_buf[3];

Finally the result on line 62 should be using the same index numbers as the Python code:

printf("%d\n", ((rx[1] & 3) << 8) + rx[2]);

Otherwise the code looks fine.

  • Only other difference is that in python module, nothing that is set in prepare function is set. They all have default values. – Krzysztof Adamski Jul 23 '13 at 11:41
  • It works after applying these changes. Thanks a lot! – fiskeben Jul 23 '13 at 17:07
0

I haven't analysed your use of the SPI device in detail, but the single biggest problem I can see with your code is using this:

#define ARRAY_SIZE(array) sizeof(array)/sizeof(array[0])

This macro is very useful, but it won't work in the case where the size of the array is not known. You're using it on an array declared as uint8_t tx[] - the size of tx is not known, hence the empty brackets. Instead of using ARRAY_SIZE() to get the length you need to pass the length as an extra parameter to the transfer() function.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.