I'm trying to setup an SPI communication between an EVK of the Intan RHD2216 and the nRF 52840.
Browsing similar issues, I read the following topics already
Intan RHD2164 with NRF52840 (SPI) - Double Data Rate Issue
Intan RHD2216 SPI communication with Nordic 52840 NCK:Intan RHD2216 SPI communication with nRF 52840-- my spi_transieve working at high sample rate bacomes very slow.
I apparently got the SPI to work since on my oscilloscope I can see CS and SCK behaving. But the I/O data doesn't correspond with the docs
https://intantech.com/files/Intan_RHD2000_series_datasheet.pdf
In particular, when I perform a WRITE(R,D) (page 18) and wait the 3-messages delay, I would expect to have the LSB portion of the 16-bits message to match the one that I sent; but that's not the case.
Here is my main.c code
/*
* Copyright (c) 2012-2014 Wind River Systems, Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
/*
* include
*/
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/spi.h>
typedef unsigned char u8_t;
typedef unsigned short u16_t;
/*
* marco define and struct define
*/
// marco define
#define SLEEP_TIME_MS 2000
static u16_t T_result[19];
// RHD command
// CONVERT command LSB set to 0(option)TODO
static u16_t RHD_CONVERT[19] = {0x0000, 0x0100, 0x0200, 0x0300, 0x0400, 0x0500, 0x0600, 0x0700,
0x0800, 0x0900, 0x0a00, 0x0b00, 0x0c00, 0x0d00, 0x0e00, 0x0f00, 0xFF00, 0xFF00, 0xFF00};
// 16 channels + 3 dummy command(read ROM 63)(user define)
// 0x3f00 -> CONVERT(63)
// ADC self-calibration command ,this command needs nine dummy command to generate the necessary clock cycles to run.
#define CALIBRATE 0x5500
static const u16_t NINE_DUMMPY[9] = {0xbf00, 0xbf00, 0xbf00, 0xbf00, 0xbf00, 0xbf00, 0xbf00, 0xbf00, 0xbf00};
#define CLEAR 0x6A00 // not necessary to use this command
// Registers configuration using write command
#define Register0 0x80DE // amp fast settle is 0 ,20-150HZ filter ,enable ADC AND amp(disable is OK)
#define Register1 0x8120 // VDD sense disable ,using 16 * 1KS/s ADC
#define Register2 0x8228 // MUX bias current, configuration as above
#define Register3 0x8302 // diable tempS and digout
#define Register4 0x84B0 // absmode enable + unsigned ADC + weak MISO + DSP high-pass filter enable(differentiator)
// Impedance check
#define Register5 0x8500 // Impedance check control ,which is disable
#define Register6 0x8600 // DAC output voltage ,there is 0
#define Register7 0x8700 // Impedance check electrode select, this is 0
// on-chip Amplifier bandwidth Select
#define Register8 0x882C // using 20-150 Hz bandwidth
#define Register9 0x8911
#define Register10 0x8a08
#define Register11 0x8b15
#define Register12 0x8c36
#define Register13 0x8d00
// indicidual Amplifier Power ,all set to one for using all channels' Amplifier
#define Register14 0x8eff
#define Register15 0x8fff
#define Register16 0x90ff
#define Register17 0x91ff
// spi struct
static u16_t tx_buffer[1];
static u16_t rx_buffer[1];
const struct spi_buf tx_buf = {
.buf = tx_buffer,
.len = sizeof(tx_buffer)};
const struct spi_buf_set tx = {
.buffers = &tx_buf,
.count = 1};
struct spi_buf rx_buf = {
.buf = rx_buffer,
.len = sizeof(rx_buffer),
};
const struct spi_buf_set rx = {
.buffers = &rx_buf,
.count = 1};
// LED
/* The devicetree node identifier for the "led0" alias. */
#define LED0_NODE DT_ALIAS(led0) // macro function of devicetree
#if DT_NODE_HAS_STATUS(LED0_NODE, okay)
#define LED0 DT_GPIO_CTLR(LED0_NODE, gpios)
#define PIN DT_GPIO_PIN(LED0_NODE, gpios)
#define FLAGS DT_GPIO_FLAGS(LED0_NODE, gpios)
#else
/* A build error here means your board isn't set up to blink an LED. */
// #error "Unsupported board: led0 devicetree alias is not defined"
#define LED0 ""
#define PIN 0
#define FLAGS 0
#endif
// SPI RHD cs
#if DT_SPI_HAS_CS_GPIOS(DT_NODELABEL(my_spi_master))
#define RHD2216 DT_SPI_DEV_CS_GPIOS_LABEL(DT_NODELABEL(reg_my_spi_master)) // get CS label a is RHD2216
#define RHD_PIN DT_SPI_DEV_CS_GPIOS_PIN(DT_NODELABEL(reg_my_spi_master)) // get RHD cs pin
#define RHD_FLAGS DT_SPI_DEV_CS_GPIOS_FLAGS(DT_NODELABEL(reg_my_spi_master)) // get RHD FLAGS
#else
#define RHD2216 ""
#define RHD_PIN 0
#define RHD_FLAGS 0
#endif
// timer
struct k_timer RHD_timer;
extern void my_timer_handler(struct k_timer *timer_id); // cb function call
// SPI
#define SPI_NODE DT_NODELABEL(my_spi_master) // macro function of devicetree
#define MY_SPI_MASTER_CS_DT_SPEC SPI_CS_GPIOS_DT_SPEC_GET(DT_NODELABEL(reg_my_spi_master))
const struct device *spi_dev = DEVICE_DT_GET(SPI_NODE);
// spi config
static const struct spi_config spi_cfg = {
.operation = SPI_WORD_SET(8) | SPI_TRANSFER_MSB | SPI_MODE_CPOL | SPI_MODE_CPHA ,
.frequency = 16000000,
//.slave = 1,
//.cs = {.gpio = MY_SPI_MASTER_CS_DT_SPEC, .delay = 0},
};
// RHD_CS
bool RHD_SAMPLE = true;
int ret;
int RHD_err;
// LED Blinky
static const struct gpio_dt_spec LED_dev = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
bool led_is_on = true;
int ret_LED;
/*
* function define
*/
// timer function
// SPI
static void spi_init(void)
{
if (!device_is_ready(spi_dev))
{
printk("Could not get %s device\n", spi_dev->name);
return;
}
printk("Device %s Correctly configured!\n", spi_dev->name);
}
u16_t spi_trans(u16_t command) // SPI trans in the timer period
{
int err;
tx_buffer[0] = command;
err = spi_transceive(spi_dev, &spi_cfg, &tx, &rx);
RHD_SAMPLE = !RHD_SAMPLE;
if (err)
{
printk("SPI error: %d\n", err);
}
return rx_buffer[0];
}
// RHD init function
static int RHD2216_init(void)
{
static const u16_t Register_config[20] = {Register0, Register1, Register2, Register3, Register4, Register5, Register6, Register7,
Register8, Register9, Register10, Register11, Register12, Register13, Register14, Register15,
Register16, Register17, 0xbf00, 0xbf00};
static u16_t result[20];
int err;
u16_t calibrate_err;
for (int i = 0; i < sizeof(Register_config) / 2; i++)
{
result[i] = spi_trans(Register_config[i]);
}
// loopback to test the correct result (write command result)
for (int j = 0; j < (sizeof(Register_config) / 2) - 2; j++)
{
printk("SPI I/O: %02X %02X \n", (0xFF & Register_config[j]), (0xFF & result[2+j]));
if ((0xFF & result[2 + j]) != (0xFF & Register_config[j]))
{
err = 1;
}
}
if(err) {
printk("loopback test failed! \n");
return err;
}
k_sleep(K_USEC(100)); // 100us delay before ADC calibrate
calibrate_err = spi_trans(CALIBRATE);
for (int j = 0; j < 9; j++)
{ // using generate nine SCLK to calibrate ADC
calibrate_err = spi_trans(NINE_DUMMPY[j]);
}
if (calibrate_err == 0x8000)
{ // only check last calibrate result this is a loopback test ,true value is 0x8000 TODO
printk("calibrate success! \n");
}
else
{
printk("calibrate failed! \n");
err = 1;
return err;
}
return err;
}
/*
* cb function define
*/
// timer cb
void RHD_handler(struct k_work *work) // using round-robin fashion ,a timer callback to sample all needed channels
{
// static u16_t T_result[19];
uint64_t stamp;
int64_t delta;
stamp = k_uptime_get_32();
/* do the processing that needs to be done periodically */
for (int j = 0; j < 10; j++)
{
for (int i = 0; i < 19; i++)
{
T_result[i] = spi_trans(RHD_CONVERT[i]);
}
}
delta = k_uptime_delta(&stamp);
printk("SPI Use time is:%lld ms \n", delta);
}
K_WORK_DEFINE(my_work, RHD_handler); // define RHD_handler as my_work
void my_timer_handler(struct k_timer *dummy)
{
k_work_submit(&my_work);
}
// #include "hal/nrf_gpio.h"
// #define YOUR_PIN NRF_GPIO_PIN_MAP(1, 10) // Example connection - P1.03
/*
* main function loop
*/
int main(void)
{
printf("Hello World! %s\n", CONFIG_BOARD_TARGET);
k_msleep(SLEEP_TIME_MS);
// nrf_gpio_cfg_output(YOUR_PIN); /* Initialize with default config of pin */
/*
* setup
*/
// timer
k_timer_init(&RHD_timer, my_timer_handler, NULL); // init timer
// Extract the device driver implementation
// LED_dev = device_get_binding("led0"); // combine the driver and devicetree node
// LED_dev = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
if (!gpio_is_ready_dt(&LED_dev))
{
printf("Issue on device_get_binding\n");
return 1;
}
// Configure the GPIO pin
ret_LED = gpio_pin_configure_dt(&LED_dev, GPIO_OUTPUT_ACTIVE);
if (ret_LED < 0)
{
printf("Issue on gpio_pin_configure\n");
return 1;
}
// SPI
printk("SPIM Example\n");
spi_init();
printk("RHD_FLAGS %x\n", RHD_FLAGS);
RHD_err = RHD2216_init();
if (RHD_err)
{
printk("RHD2216 init fail \n");
}
else
{
RHD_err = spi_trans(RHD_CONVERT[0]);
/* start periodic timer that expires once at 1kHz */
k_timer_start(&RHD_timer, K_SECONDS(3), K_USEC(5000)); // first param is timer duration(initial timer duration)
}
/*
* Loop function : other data process code
*/
printk("Going to main loop! \n");
bool led_state = true;
while (true)
{
// nrf_gpio_pin_toggle(YOUR_PIN);
ret = gpio_pin_toggle_dt(&LED_dev);
if (ret < 0)
{
return 0;
}
led_state = !led_state;
// printf("LED state: %s\n", led_state ? "ON" : "OFF");
k_msleep(SLEEP_TIME_MS);
// printk("This is main loop! \n");
}
return 0;
}
my .overlay file
// To get started, press Ctrl+Space to bring up the completion menu and view the available nodes.
// You can also use the buttons in the sidebar to perform actions on nodes.
// Actions currently available include:
// * Enabling / disabling the node
// * Adding the bus to a bus
// * Removing the node
// * Connecting ADC channels
// For more help, browse the DeviceTree documentation at https://docs.zephyrproject.org/latest/guides/dts/index.html
// You can also visit the nRF DeviceTree extension documentation at https://docs.nordicsemi.com/bundle/nrf-connect-vscode/page/guides/ncs_configure_app.html#devicetree-support-in-the-extension
/ {
aliases {
spi0 = &spi0;
};
};
&pinctrl {
spi0_default {
group1 {
psels = <NRF_PSEL(SPIM_SCK, 1, 15)>,
<NRF_PSEL(SPIM_MOSI, 1, 13)>,
<NRF_PSEL(SPIM_MISO, 1, 14)>;
};
};
spi0_sleep {
group1 {
psels = <NRF_PSEL(SPIM_SCK, 1, 15)>,
<NRF_PSEL(SPIM_MOSI, 1, 13)>,
<NRF_PSEL(SPIM_MISO, 1, 14)>;
low-power-enable;
};
};
};
my_spi_master: &spi0 {
compatible = "nordic,nrf-spim";
status = "okay";
pinctrl-0 = <&spi0_default>;
pinctrl-1 = <&spi0_sleep>;
pinctrl-names = "default", "sleep";
cs-gpios = <&gpio1 12 GPIO_ACTIVE_LOW>;
reg_my_spi_master: spi-dev-a@0 {
reg = <0>;
};
max-frequency = <DT_FREQ_M(16)>;
};
&spi1 {
compatible = "nordic,nrf-spi";
status = "disabled";
};
&spi3 {
status = "disabled";
};
and my prj.conf file
CONFIG_GPIO=y CONFIG_SPI=y
I'm still a bit confused about 2 things:
- the definition of the SPI, even though I copied from the other forum posts, does not match the RHD docs. I would expect something like the code here below, also because the SPI on nRF side I understood cannot work at more than 8 mega
static const struct spi_config spi_cfg = { .operation = SPI_WORD_SET(8) | SPI_TRANSFER_MSB, .frequency = 8000000, .slave = 1, .cs = {.gpio = MY_SPI_MASTER_CS_DT_SPEC, .delay = 0}, };
- the .overlay file keeps complaining with the message "Only spi nodes accepted in /soc/spi@40003000/.". but everything compiles.
Any suggestion is really appreciated.