RMT Communication with MCP3910

kindaTall
Posts: 6
Joined: Mon Jan 20, 2020 4:21 pm

RMT Communication with MCP3910

Postby kindaTall » Tue Jan 21, 2020 9:01 am

Hello everybody,

I am trying to communicate with an MCP3910, ADFE. Besides running SPI as a protocol the developers have implemented a rather simple 2-wire protocol, which works as follows.

The master-device, in my case an esp32, uses one line to supply a clock signal (square wave, >40kHz). The MCP3910 then pulls the second wire up or down, thus sending a bitstream (measured on rising edge of clock signal). Depending on clock speed the signal is either 8 or 10 bytes long and starts and ends with a prespecified sequence. So far so good.

Looking around this forum I figured I'd use the rx-components of the "driver/rmt.h" in library for reading the signal and either the tx-components of the same library or the "/driver/ledc.h" for generating the clock signal (see code below). I have run into two issues though.

Using "driver/rmt.h" for clock signal:
The clock signal becomes very unstable, the generated signal is very far from uniform, periods taking more than twice the time, duty is very far off from 50% (can probably supply oscilloscope measurments if helpful).
Is the library just not equipped to handle this amount of "high-speed" I/0 communication or am I missing something?

Using "driver/ledc.h" for clock signal:
Directly after initialization, as far as I can tell, as soon as RX starts recieving I get the following error:

Code: Select all

E (364) rmt: RMT[0] ERR
E (364) rmt: status: 0x14000080
I strongly suspect its the following line in "rmt.c", but this doesn't help me. As of now, I haven't been able to deepdive into this library.

Code: Select all

static void IRAM_ATTR rmt_driver_isr_default(void* arg){
	...  
	ESP_EARLY_LOGE(RMT_TAG, "RMT[%d] ERR", channel);
Channel 0 is the channel used for RMT. I suspect that the issue arrises from a slight mismatch in clk and rx frequencies, though this is just a (barely educated) guess. I wouldn't be surprised if it were something else.

I hope I supplied you with everything you need and that I just missed something obvious, that can be taken care of easily.

As I promised, the code I'm running, reduced to the bare minimum (I think), you'll notice it is rather close to the rmt and ledc examples (ctrl+c/ctrl+v ;) ) I hope it's readable, I've done my best to pick variables appropriately and roughly comment the code.

Code: Select all

#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/ringbuf.h"
#include "esp_log.h"

// either use RMT to generate Clock, or comment to use LEDC
#define USE_RMT_CLK

#include "driver/rmt.h"
#include "driver/ledc.h"


#define FREQ   (100*1000)
#define TX_DIV (80000000/FREQ/4) // square signal is sent with 2 high ticks 2 low ticks => one period is 4 ticks, thus divisor devided by 4
#define RX_DIV (TX_DIV*4)

#define CHANNEL_MCP_RX (RMT_CHANNEL_0)
#define CHANNEL_CLK_TX (RMT_CHANNEL_1)

#define GPIO_MCP_RX_PIN  (5)
#define GPIO_CLK_TX_PIN (22)

#define LEDC_HS_TIMER          LEDC_TIMER_0
#define LEDC_HS_MODE           LEDC_HIGH_SPEED_MODE
#define LEDC_HS_CH0_GPIO       GPIO_CLK_TX_PIN
#define LEDC_HS_CH0_CHANNEL    LEDC_CHANNEL_0

static const char *TAG = "rmt_test";

#ifdef USE_RMT_CLK
/** @brief items for clk, last item is 1 short as rmt seems to send an extra 0 at the end */
const static rmt_item32_t items [] = {
    {{{2, 1, 2, 0}}}, {{{2, 1, 2, 0}}}, {{{2, 1, 2, 0}}}, {{{2, 1, 2, 0}}},
    {{{2, 1, 2, 0}}}, {{{2, 1, 2, 0}}}, {{{2, 1, 2, 0}}}, {{{2, 1, 2, 0}}},
    {{{2, 1, 2, 0}}}, {{{2, 1, 2, 0}}}, {{{2, 1, 2, 0}}}, {{{2, 1, 2, 0}}},
    {{{2, 1, 2, 0}}}, {{{2, 1, 2, 0}}}, {{{2, 1, 2, 0}}}, {{{2, 1, 2, 0}}},
    {{{2, 1, 2, 0}}}, {{{2, 1, 2, 0}}}, {{{2, 1, 2, 0}}}, {{{2, 1, 2, 0}}},
    {{{2, 1, 2, 0}}}, {{{2, 1, 2, 0}}}, {{{2, 1, 2, 0}}}, {{{2, 1, 2, 0}}},
    {{{2, 1, 2, 0}}}, {{{2, 1, 2, 0}}}, {{{2, 1, 2, 0}}}, {{{2, 1, 2, 0}}},
    {{{2, 1, 2, 0}}}, {{{2, 1, 2, 0}}}, {{{2, 1, 2, 0}}},
    {{{2, 1, 1, 0}}}
};
#endif

/**
 * @brief starts clock source on GPIO_CLK_TX_PIN for external device, chose library with "USE_RMT_CLK"
 */
esp_err_t start_clk(void){
#ifdef USE_RMT_CLK
    ESP_LOGI(TAG, "Starting rmt clock with %i kHz", 80000/TX_DIV);
    rmt_config_t rmt_tx_config = {
        .rmt_mode = RMT_MODE_TX,
        .channel = CHANNEL_CLK_TX,
        .gpio_num = GPIO_CLK_TX_PIN,
        .clk_div = TX_DIV,
        .mem_block_num = 1,
        .tx_config = {
            .carrier_en = false,
            .loop_en = true,
        }
    };

    // configure and install, no values expected thus no ringbuffer
    ESP_ERROR_CHECK(rmt_config(&rmt_tx_config));
    ESP_ERROR_CHECK(rmt_driver_install(CHANNEL_CLK_TX, 0, ESP_INTR_FLAG_IRAM));
//    ESP_ERROR_CHECK(rmt_set_source_clk(CHANNEL_CLK_TX, RMT_BASECLK_APB));
    ESP_ERROR_CHECK(rmt_write_items(CHANNEL_CLK_TX, items, sizeof(items) / sizeof(items[0]), false));

    return ESP_OK;
#else
    // Set timer0 to resolution 1 bit, channel0 duty to 1, making square wave function
    ESP_LOGI(TAG, "Starting ledc clock with %i kHz", FREQ/1000);
    ledc_timer_config_t ledc_timer = {
        .duty_resolution = 1,                 // resolution of PWM duty
        .freq_hz = FREQ,                      // frequency of PWM signal
        .speed_mode = LEDC_HS_MODE,           // timer mode
        .timer_num = LEDC_HS_TIMER            // timer index
    };

    ledc_channel_config_t ledc_channel = {
        .channel    = LEDC_HS_CH0_CHANNEL,
        .duty       = 1,
        .gpio_num   = GPIO_CLK_TX_PIN,
        .speed_mode = LEDC_HS_MODE,
        .hpoint     = 0,
        .timer_sel  = LEDC_HS_TIMER
    };

    // Set LED Controller with previously prepared configuration
    ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));
    // Set configuration of timer0 for high speed channels
    ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));
    return ESP_OK;
#endif
}


/**
 * @brief RMT Receive Task, manages communication from mcp3910
 *
 */
static void mcp_rx_task(void *arg)
{
    ESP_LOGI(TAG, "Starting RX at %i kHz", 80000/RX_DIV);
    uint32_t length = 0;

    RingbufHandle_t rb = NULL;
    rmt_item32_t *items = NULL;

    rmt_config_t rmt_rx_config =
    {
        .rmt_mode = RMT_MODE_RX,
        .channel = CHANNEL_MCP_RX,
        .gpio_num = GPIO_MCP_RX_PIN,
        .clk_div = RX_DIV,
        .mem_block_num = 2,
        .rx_config = {
            .idle_threshold = 10000,
            .filter_ticks_thresh = 50*2,
            .filter_en = false,
        },
    };


    ESP_LOGI(TAG, "Installing rmt-rx-driver");
    ESP_ERROR_CHECK(rmt_config(&rmt_rx_config));
    ESP_ERROR_CHECK(rmt_driver_install(CHANNEL_MCP_RX, 1000, ESP_INTR_FLAG_IRAM));
    ESP_ERROR_CHECK(rmt_set_source_clk(CHANNEL_MCP_RX, RMT_BASECLK_APB));
    ESP_ERROR_CHECK(rmt_get_ringbuf_handle(CHANNEL_MCP_RX, &rb));
    // Start receiving
    ESP_ERROR_CHECK(rmt_rx_start(CHANNEL_MCP_RX, true));

    while (rb) {
        // recieve items from ringbuffer
        items = (rmt_item32_t *) xRingbufferReceive(rb, &length, 1000);
        if (items) {
            length /= 4;  // one RMT = 4 Bytes

            // simple item handling
            for (int i = 0 ; i < length; i++){
                rmt_item32_t item = items[i];
                printf("%d for %3d ticks, %d for %3d ticks.\n", item.level0, item.duration0, item.level1, item.duration1);
            }

            // "free" item
            vRingbufferReturnItem(rb, (void *) items);
        } else {
            ESP_LOGE(TAG, "Ring buffer recieve failed");
            break;
        }
    }
    ESP_ERROR_CHECK(rmt_driver_uninstall(CHANNEL_MCP_RX));
    vTaskDelete(NULL);
}

void app_main(){
    start_clk();
    xTaskCreate(mcp_rx_task, "mcp_task", 8192, NULL, 10, NULL);
}
Looking forward to your ideas, cheers.

kindaTall
Posts: 6
Joined: Mon Jan 20, 2020 4:21 pm

Re: RMT Communication with MCP3910 (Update)

Postby kindaTall » Tue Jan 21, 2020 5:41 pm

Me again :) I found a forum with a somewhat related problem that suggested using the spi peripheral for digital reading at high frequency. So I did, code is posted below. I am running into the issue that between two spi-transactions there is a delay of about 90us. I assume that is just overhead one has to deal with. Sadly this seriously messes with above mentioned MCP3910.

Is there a peripheral that supports DMA and can read continuously, ideally interrupting after writing a certain chunk of data (so that it can be processed chunkwise)? I'm willing to look into more complicated libraries, but sadly am seriously out of my depth. Some direction would be greatly appreciated!

Here the promised code, enjoy (if possible :oops: )!

Code: Select all

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_system.h"
#include "driver/spi_master.h"
#include "soc/gpio_struct.h"
#include "driver/gpio.h"

#include "esp_log.h"

const char * TAG = "2_wire_spi";

#define FREQ   (100*1000)

#define GPIO_MCP_RX_PIN  (5)
#define GPIO_CLK_TX_PIN (22)

#define PIN_NUM_MOSI 25 //not used
#define PIN_NUM_MISO GPIO_MCP_RX_PIN
#define PIN_NUM_CLK  GPIO_CLK_TX_PIN
#define PIN_NUM_CS   23 //not used


#define RX_LEN 400 // relative overhead can be reduced at cost of memory
#define RX_N 3

DRAM_ATTR static char receive_buffer[RX_N][RX_LEN];
static spi_transaction_t trans_desc[RX_N];
static spi_device_handle_t device;

static spi_host_device_t host;

QueueHandle_t empty_buffer_queue;

/** @brief initialize queue that sends read buffers back and transaction descriptions */
void initialize_queues_and_buffers(){
    empty_buffer_queue = xQueueCreate(10, sizeof(spi_transaction_t *));


    for(int i = 0; i < RX_N; i++){
        trans_desc[i].tx_buffer = NULL;
        trans_desc[i].rx_buffer = receive_buffer[i];
        trans_desc[i].rxlength = RX_LEN*8;
        trans_desc[i].length = 0;
        trans_desc[i].flags &= ~SPI_TRANS_USE_TXDATA;
        ((char*)trans_desc[i].rx_buffer)[0] = 0;

        spi_transaction_t * ptr = &trans_desc[i];
        xQueueSendToBack(empty_buffer_queue, &ptr, portMAX_DELAY);
    }
}


/** @brief initialize spi bus and device */
void initialize_spi_device(){

    // initialize bus
    host = 2;
    spi_bus_config_t bus_config = {
        .mosi_io_num = PIN_NUM_MOSI,
        .miso_io_num = PIN_NUM_MISO,
        .sclk_io_num = PIN_NUM_CLK,
        .max_transfer_sz = RX_LEN,
    };
    int dma_chan = 1;
    spi_bus_initialize(host, &bus_config, dma_chan);

    // initalize device
    spi_device_interface_config_t dev_config = {
        .command_bits = 0,
        .address_bits = 0,
        .dummy_bits = 0,
        .mode = 0, //[0:3]
        .duty_cycle_pos = 0,    //0=>128
        .cs_ena_pretrans = 0,
        .cs_ena_posttrans = 0,
        .clock_speed_hz = FREQ,
        .input_delay_ns = 0,
        .spics_io_num = .1,
        .queue_size = 3, //RX_N,
        .flags = SPI_DEVICE_HALFDUPLEX,
    };

    spi_bus_add_device(host, &dev_config, &device);
}


/** @brief continuously queue transactions recieved through queue*/
void queue_transaction_requests(void * arg){
    spi_transaction_t * trans = NULL;

    // acquire bus to ensure this device gets quickest access
    spi_device_acquire_bus(device, portMAX_DELAY);

    while(1){
        if(xQueueReceive(empty_buffer_queue, &trans, 5)){

            /// @todo remove time constraints, currently looking for sources of delay
            if(ESP_OK != spi_device_queue_trans(device, trans, 5)){
                ESP_LOGE(TAG, "Delay queueing transaction, shouldn't happen");
                spi_device_queue_trans(device, trans, portMAX_DELAY);
            }
        }else{
            ESP_LOGE(TAG, "Queue empty, nowhere to write incoming spi");
        }
    }

    // clean up
    spi_device_release_bus(device);
    spi_bus_remove_device(device);
    spi_bus_free(host);
    vTaskDelete(NULL);
}


/** @brief get transaction results */
void handle_transfered_data_task(void * arg){
    spi_transaction_t * trans = NULL;
    while(1){
        spi_device_get_trans_result(device, &trans, portMAX_DELAY);
        // could handling trans would be here
        if(!xQueueSendToBack(empty_buffer_queue, &trans, 0)){
            ESP_LOGE(TAG, "Queue is full but shouldn't be");
        }
    }
}

/** @brief main */
void app_main()
{
    vTaskDelay(1000/portTICK_PERIOD_MS);
    initialize_spi_device();
    initialize_queues_and_buffers();

    xTaskCreate(handle_transfered_data_task, "printing", 4096, NULL, 2, NULL);
    xTaskCreate(queue_transaction_requests, "reading", 4096, NULL, 10, NULL);
}

Who is online

Users browsing this forum: Google [Bot] and 92 guests