RMT Communication with MCP3910
Posted: 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:
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.
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.
Looking forward to your ideas, cheers.
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
Code: Select all
static void IRAM_ATTR rmt_driver_isr_default(void* arg){
...
ESP_EARLY_LOGE(RMT_TAG, "RMT[%d] ERR", channel);
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);
}