Is Part of the SPI Interface Missing in buildroot?

Thank you! Yes, I had found both files, but was not clear that SPINOR_MISO, etc… actually directly translated to SPI2_MISO (or SPI2_DI). I know it’s not enabled by default - so what is needed to enable by default, just changes to line 230 in duo-buildroot-sdk/u-boot-2021.10/board/cvitek/cv180x/board.c

        pinmux_config(PINMUX_SPI2);

So there is no need to make further changes to build/boards/cv180x/cv1800b_milkv_duo_sd/dts_riscv/cv1800b_milkv_duo_sd.dts?

Also, Enable SPI2 and the corresponding spidev on Milk-V Duo shows adding cs-gpios = <&porta 18 0>; to do dts – is that necessary?

What about the modification to cvitek_cv1800b_sophpi_duo_sd_defconfig shown in that post?


Here is my setup - the same setup that works without issue through /dev/spidev0.0 on Pi, etc…

The MCP3008 chip:

mcp3008-pinout

The chip configuration on breadboard:

Connection on GP6 - GP9 on the Milkv-Duo:

The problem is when read through /dev/spidev0.0 there are nothing but stray voltages on various channels:

# mcp3008spi "/dev/spidev0.0" 100
opened SPI device: /dev/spidev0.0 on file descriptor: 3
  mode    : 0
  bits    : 8
  clock   : 1350000
  delay   : 5
  samples : 100  (~20 seconds)

all channel output:

  chan[0] :  0.00
  chan[1] :  0.41
  chan[2] :  0.00
  chan[3] :  0.00
  chan[4] :  0.83
  chan[5] :  0.41
  chan[6] :  0.00
  chan[7] :  0.00

Where the voltages should be:

  chan[0] :  1.69
  chan[1] :  2.71
  chan[2] :  1.36
  chan[3] :  3.29
  chan[4] :  0.00
  chan[5] :  0.00
  chan[6] :  0.00
  chan[7] :  0.00

As shown by the ADS1115 ADC chip on II2C (same voltages going to the MCP3008 chip channels 0-3 just in different order) shown below:

# ads1115

ADS1115 each input channel (AINx) configured for
single-shot, 4.096V gain, 128 SPS

  Outputting 100 analog input samples at 5Hz  (~20 sec)

  channel, reg-values and voltages:

  channel[0] : 0x66d6  (3.29 V)
  channel[1] : 0x34b8  (1.69 V)
  channel[2] : 0x54b8  (2.71 V)
  channel[3] : 0x2a5c  (1.36 V)

So if everything is provided in the buildrood-sdk for SPI2 on /dev/spidev0.0 why is nothing working?

It just seems that there should be a /dev/spidev2.0 (bus 2 channel 0). Or do I just not understand how the Duo is configured?

For completeness, I’ll post the code as well:

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

typedef struct {          /* SPI device struct for mcp3008 */
  struct spi_ioc_transfer spi_xfer;
  uint8_t buf[6];
  int fd;
  uint8_t mode,
          cepin;
} spidev;

/* SPI and mcp3008 constants  */
#define SPIDEVFS      "/dev/spidev0.0"
#define BITS          8
#define CLOCK         1350000
#define DELAY         5

#ifdef MILKVFS
/* 3rd attempt with GPIOXX numbers - failed same vals as 2nd attempt
#define SPI_CLK      23
#define SPI_MOSI     22
#define SPI_MISO     21
#define SPI_CE       18
*/
/* 2nd attempt - very low voltage readings on wrong pins
 * next try enabling spidev2 in cv1800b_milkv_duo_sd.dts?
 */
#define SPI_CLK       6
#define SPI_MOSI      7
#define SPI_MISO      8
#define SPI_CE        9

/* 1st attempt through /dev/spidev0.0 - failed - no readings at all
#define SPI_CE       17
#define SPI_MISO     16
#define SPI_MOSI     19
#define SPI_CLK      18
*/
#else
#define SPI_CE        8
#define SPI_MISO      9
#define SPI_MOSI     10
#define SPI_CLK      11
#endif

/**
 * @brief initialize the SPI system (e.g. open "/dev/spidev0.0") and configure
 * the device struct for reading from the mcp3008.
 * @param spidevfs filesystem device node for SPI.
 * @param dev pointer to struct holding mcp3008 configuration.
 * @param delay microsecond delay for mcp3008 conversion (sample switching).
 * @param clock SPI bus speed in Hz.
 * @param bits bits per-bytes (e.g. CHAR_BITS).
 * @param mode SPI_MODE_x (single-ended or pseudo-differential pairs).
 * @param cepin CS/CE GPIO pin number.
 * @return returns 0 on success, -1 otherwise.
 */
int spi_device_init (const char *spidevfs, spidev *dev, uint16_t delay,
                      uint32_t clock, uint8_t bits, uint8_t mode, uint8_t cepin)
{
  /* initialize struct members */
  dev->spi_xfer.tx_buf = (unsigned long)dev->buf;
  dev->spi_xfer.rx_buf = (unsigned long)(dev->buf + 3);
  dev->spi_xfer.len = 3;
  dev->spi_xfer.delay_usecs = delay;
  dev->spi_xfer.speed_hz = clock;
  dev->spi_xfer.bits_per_word = bits;

  dev->mode = mode;
  dev->cepin = cepin;

  /* open spi device in Linux sysfs */
  if ((dev->fd = open (spidevfs, O_RDWR)) == -1) {
    perror ("open spidevfs");
    return -1;
  }
  /* set device mode */
  if (ioctl (dev->fd, SPI_IOC_WR_MODE, &dev->mode) == -1) {
    perror ("error init SPI_IOC_WR_MODE");
    return -1;
  }
  /* set number of bits per word (byte) */
  if (ioctl (dev->fd, SPI_IOC_WR_BITS_PER_WORD,
            &dev->spi_xfer.bits_per_word) == -1) {
    perror ("error init SPI_IOC_WR_BITS_PER_WORD");
    return -1;
  }
  /* send requested SPI bus speed */
  if (ioctl (dev->fd, SPI_IOC_WR_MAX_SPEED_HZ,
            &dev->spi_xfer.speed_hz) == -1) {
    perror ("error init SPI_IOC_WR_MAX_SPEED_HZ");
    return -1;
  }
  /* read configured SPI bus speed */
  if (ioctl (dev->fd, SPI_IOC_RD_MAX_SPEED_HZ,
            &dev->spi_xfer.speed_hz) == -1) {
    perror ("error init SPI_IOC_RD_MAX_SPEED_HZ");
    return -1;
  }

  return 0;
}


/**
 * @brief set channel to psuedo-differential compare mode.
 * @param channel channel to set control bits on (SGL/DIF = 0, D2=D1=D0=0).
 * @return returns control bits set for channel.
 */
uint8_t channel_cfg_differential (uint8_t channel)
{
  return (channel & 7) << 4;
}


/**
 * @brief set channel to single-ended input mode.
 * @param channel channel to set control bits on (SGL/DIF = 1, D2=D1=D0=0).
 * @return returns control bits set for channel.
 */
uint8_t channel_cfg_single (uint8_t channel)
{
  return (0x8 | channel) << 4;
}


/**
 * @brief read sample from mcp3008 for channel.
 * @param dev pointer to mcp3008 device struct.
 * @param channel analog input channel to read.
 * @return returns raw 10-bit value for sample.
 */
int spi_read_adc (spidev *dev, uint8_t channel)
{
  dev->buf[0] = 1;
  dev->buf[1] = channel_cfg_single (channel);
  dev->buf[2] = 0;

  if (ioctl (dev->fd, SPI_IOC_MESSAGE(1), &dev->spi_xfer) == -1) {
    perror ("ioctl SPI_IOC_MESSAGE()");
    abort();
  }

  return ((dev->buf[4] & 0x03) << 8) |
          (dev->buf[5] & 0xff);
}


/**
 * @brief simple output of all mcp3008 channels using ANSI escape
 * to overwrite values in place.
 * @param f array of float values to display.
 * @param n numer of elements in f.
 */
void prn_all_channels (float *f, int n)
{
  char fmt[32] = "";

  for (int i = 0; i < n; i++) {
    printf ("  chan[%d] : % 5.2f\n", i, f[i]);
  }

  sprintf (fmt, "\033[%dA", n);
  printf (fmt);
}


int main (int argc, char **argv) {

  const char *spidevfs = SPIDEVFS;
  uint8_t   channel = 0;
  unsigned  nsamples = 300;
  spidev    dev = { .spi_xfer = {0}};

  if (argc > 1) {   /* set SPI devfs pathname (if given) */
    spidevfs = argv[1];
  }

  if (argc > 2) {   /* set the number of samples, (default 300) */
    unsigned tmp;
    if (sscanf (argv[2], "%u", &tmp) != 1) {
      fprintf (stderr, "error: invalid unsigned count for argv[2] (%s)\n",
              argv[2]);
      return 1;
    }
    nsamples = tmp;
  }

  if (argc > 3) {   /* set single channel to read, (default all channels) */
    uint8_t tmp;
    if (sscanf (argv[3], "%hhu", &tmp) != 1 || tmp > 7) {
      fprintf (stderr, "error: invalid channel given on argv[3] (%s)\n",
              argv[3]);
      return 1;
    }
    channel = tmp;
  }

  /* initialize spidev interface and validate */
  if (spi_device_init (spidevfs, &dev, DELAY, CLOCK, BITS,
                        SPI_MODE_0, SPI_CE) == -1) {
    return 1;
  }

  printf ("opened SPI device: %s on file descriptor: %d\n"
          "  mode    : %d\n"
          "  bits    : %hhu\n"
          "  clock   : %u\n"
          "  delay   : %hu\n"
          "  samples : %u  (~%u seconds)\n\n",
          spidevfs, dev.fd, dev.mode, dev.spi_xfer.bits_per_word,
          dev.spi_xfer.speed_hz, dev.spi_xfer.delay_usecs,
          nsamples, nsamples / 5);

  if (argc > 3) { /* if channel argument provides display single-channel */
    puts ("single channel output:\n");

    /* do single-channel ADC read at 5Hz */
    for (unsigned i = 0; i < nsamples; i++) {
      int val = spi_read_adc (&dev, channel);
      float res = (float)val / 1023.f * 3.3f;
      printf ("  chan[%d] : % 5.2f\n\033[1A", channel, res);
      usleep (200000);
    }
  }
  else {  /* otherwise display sample values for all channels */
    puts ("all channel output:\n");

    /* do read of all channels at 5Hz */
    for (unsigned i = 0; i < nsamples; i++) {
      float fvals[8] = {0};
      for (uint8_t chan = 0; chan < 8; chan++) {
        int val = spi_read_adc (&dev, chan);
        fvals[chan] = (float)val / 1023.f * 3.3f;
     }
      prn_all_channels (fvals, 8);
      usleep (200000);
    }
    fputs ("\033[8B\n", stdout);
  }

  close (dev.fd);

  return 0;
}

The following is a short Makefile I use with most test code project on the Duo (changed as needed):

TARGET = mcp3008spi

CC = $(TOOLCHAIN_PREFIX)gcc

CFLAGS += -I$(SYSROOT)/usr/include -I./include
CFLAGS += -Wall -Wextra -pedantic -Wshadow -std=c11
CFLAGS += -D_GNU_SOURCE
CFLAGS += -DMILKVFS

LDFLAGS += -L$(SYSROOT)/usr/lib -L$(SYSROOT)/lib -lrt

SOURCE = $(wildcard *.c)
OBJS = $(patsubst %.c,%.o,$(SOURCE))

$(TARGET): $(OBJS)
	$(CC) -o $@ $(OBJS) $(LDFLAGS)

%.o: %.c
	$(CC) $(CFLAGS) -o $@ -c $<

.PHONY: clean
clean:
	@rm -rf *.o
	@rm -rf $(OBJS)
	@rm -rf $(TARGET)