But the ESP32’s hardware SPI doesn’t seem to provide a means to do this. Therefore I split up the data in a sequence of 2-byte SPI transactions, after each one I set and cleared the DAC’s CS-line. This however resulted in a whopping 10 us delay in between transactions! The documentation indeed mentions this on https://docs.espressif.com/projects/esp ... iderations but I cannot understand why this was implemented with such an absurd overhead.
I finally resorted to implementing SPI in software, see tx_16bit_to_DAC(..) in code below; now I got 12 us per DAC update which is 9.6 times too slow.
I then reworked this to the lowest level code I could, see tx_16bit_to_DAC_low_level(..); now 2.6 us per update, still 2.1 times too slow.
Here is the entire runnable program:
Code: Select all
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <inttypes.h>
#include <math.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_attr.h"
#include "FreeRTOSConfig.h"
#include "esp_pm.h"
#include "soc/gpio_struct.h"
#include "esp_timer.h"
// GPIO output pins for SPI
#define SCLK_PIN 18
#define MOSI_PIN 23
#define CS_PIN 5
#define OUTPUT_PINS (1ULL << SCLK_PIN) | (1ULL << MOSI_PIN) | (1ULL << CS_PIN)
#define SPI_DATA_SIZE 256
DRAM_ATTR uint16_t _spi_tx_data[SPI_DATA_SIZE];
static uint32_t SCLK_VALUE = 1 << SCLK_PIN;
static uint32_t MOSI_VALUE = 1 << MOSI_PIN;
static uint32_t CS_VALUE = 1 << CS_PIN;
static void init_gpio() {
gpio_config_t io_conf = {};
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pin_bit_mask = OUTPUT_PINS; // bit mask of the pins that you want to set,e.g.GPIO18/19
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; // == 0
io_conf.pull_up_en = GPIO_PULLUP_DISABLE; // == 0
ESP_ERROR_CHECK(gpio_config(&io_conf));
ESP_ERROR_CHECK(gpio_set_level(CS_PIN, 1));
}
static void init_sine_data() {
float PI = 3.14159265;
uint32_t max_value = 4095; // 12 bit
uint32_t step_size = max_value / SPI_DATA_SIZE;
for (int i = 0; i < SPI_DATA_SIZE; i++) {
uint16_t value = 2047 + round(2047 * sin(i * step_size * PI / 180));
_spi_tx_data[i] = (value & 0x0FFF) | 0x3000; // value is 12 bit OR 0x3000 for Gain = 1
}
}
static void IRAM_ATTR tx_16bit_to_DAC(uint16_t value) {
gpio_set_level(CS_PIN, 0); // activate CS DAC
for (uint16_t mask = 0x8000; mask > 0; mask >>= 1) {
gpio_set_level(SCLK_PIN, 0);
gpio_set_level(MOSI_PIN, (value & mask) != 0);
gpio_set_level(SCLK_PIN, 1);
}
gpio_set_level(SCLK_PIN, 0);
gpio_set_level(CS_PIN, 1); // deactivate CS DAC
}
static void IRAM_ATTR tx_16bit_to_DAC_low_level(uint16_t value) {
// Setting and clearing GPIO pins using out_w1ts & out_w1tc only for GPIO pin numbers < 32 !!
// See gpio_ll.h line 430: 'gpio_ll_set_level(..)'
GPIO.out_w1tc = CS_VALUE; // gpio_set_level(CS_PIN, 0); // activate CS DAC
for (uint16_t mask = 0x8000; mask > 0; mask >>= 1) {
GPIO.out_w1tc = SCLK_VALUE; // gpio_set_level(SCLK_PIN, 0);
if ((value & mask) != 0) { // gpio_set_level(MOSI_PIN, (value & mask) != 0);
GPIO.out_w1ts = MOSI_VALUE;
}
else {
GPIO.out_w1tc = MOSI_VALUE;
}
GPIO.out_w1ts = SCLK_VALUE; // gpio_set_level(SCLK_PIN, 1);
}
GPIO.out_w1tc = SCLK_VALUE; // gpio_set_level(SCLK_PIN, 0);
GPIO.out_w1ts = CS_VALUE; // gpio_set_level(CS_PIN, 1); // deactivate CS DAC
}
static void IRAM_ATTR tx_signal_to_DAC() {
for (int i = 0; i < SPI_DATA_SIZE; i++) {
// tx_16bit_to_DAC(_spi_tx_data[i]); // 1 update takes 12 us, 9.6 times too slow
tx_16bit_to_DAC_low_level(_spi_tx_data[i]); // 1 update takes 2.6 us, 2.1 times too slow
}
}
void spi_test(void * param) {
init_gpio();
init_sine_data(); // init table with sine values
while (true) {
tx_signal_to_DAC();
vTaskDelay(200 / portTICK_PERIOD_MS);
}
}
void app_main(void) {
static uint8_t ucParams = 23;
TaskHandle_t pCreatedTask = NULL;
// Run on APP CORE
xTaskCreatePinnedToCore(spi_test, "SPI_test", 2048, &ucParams, 19 | portPRIVILEGE_BIT, &pCreatedTask, 1);
configASSERT(pCreatedTask);
}
What else can I do to speed things up?
Convert this into assembly?
In viewtopic.php?t=17549 ESP_Sprite writes ‘fast GPIO’ options are available in the -S2 and -C3, would this help?
I was surprised to find only 1 person on the internet with the same question; AutogolazzoJr in viewtopic.php?p=109215#p109215: are we the only 2 people who are trying to do this (unlikely), or are we missing something trivially? I posted my question to him but so far no reply.
I chose the ESP32 for my project because I was impressed with its 240 MHz clock speed, powerful SPI at 80 MHz, hardware timers, etc., but I must say this SPI-to-DAC experience has been a pretty disappointing one . Hopefully someone can help me out.