Hello ESP_Sprite,
Sajeel and me working together on that issue. Please don’t wonder why I am posting here the minimal code.
Before starting, I want to make clear, that we use an Level-5 interrupt to “catch” the “DATAREADY” interrupt of an external ADC as fast as possible. The Level-5 interrupt handler then just fires up a pre-configurated SPI transaction running on HSPI to read out the ADC data. This approach was introduced by the user “bienvenue” in this post:
https://www.esp32.com/viewtopic.php?f=13&t=16093#p62375. For “better performance” we pinned the Level-5 interrupt to Core 1 by executing the interrupt allocation codes within a task which is pinned to Core 1. When the HSPI transaction finishes, thus all ADC data is read out, then the SPI (HSPI) handler is raised where we read out the SPI RX buffer to put the data into a buffer array. When the buffer array reaches the buffer limit, e.g., 256 packets, then the buffer array is send to an “RingBuffer”, so that Core0 can grab that data from the Ringbuffer. Also, the SPI (HSPI) handler is running on Core 1 since the codes for interrupt allocation are also executed within the same task pinned to Core 1 where the Level-5 interrupt is allocated. Now, our aim is to run the ENC28J60 codes (tasks, interrupt handler…) on Core0 to send the data received from the Ringbuffer via ethernet.
However, in the following you will find code fragments which, hopefully, makes the problem clearer. For the sake of overview, I will introduce every code fragment separately.
Level-5 interrupt handler (xt_hint5):
Code: Select all
#include …….
.data
_l5_intr_stack:
.space 12 //L5_INTR_STACK_SIZE
.section .iram1,"ax"
.global xt_highint5
.type xt_highint5,@function
.align 4
.literal .GPIO_STATUS1_W1TC_REG, 0x3FF44058
.literal .ADC_DRDY_PIN, (1<<3)
.literal .SPI_CMD_REG, 0x3FF64000
.literal .SPI_USR, (1<<18)
xt_highint5:
/* save contents of registers A2-A4 */
movi a0, _l5_intr_stack
s32i a2, a0, 0
s32i a3, a0, 4
s32i a4, a0, 8
/* clearing the interrupt status of GPIO_NUM_35 */
l32r a2, .GPIO_STATUS1_W1TC_REG
l32r a3, .ADC_DRDY_PIN
s32i a3, a2, 0
/* SPI2.cmd.usr = 1; */
/* The SPI peripheral takes a few hundred nanoseconds
to start, no need for extra delay */
l32r a2, .SPI_CMD_REG
l32r a3, .SPI_USR
s32i a3, a2, 0
/* restore contents of registers A2-A4 */
movi a0, _l5_intr_stack
l32i a2, a0, 0
l32i a3, a0, 4
l32i a4, a0, 8
rsync /* ensure register restored */
/* hand back from interrupt */
//rsr a0, EXCSAVE_5
rsr.excsave5 a0
rfi 5
.global ld_include_highint_hdl
ld_include_highint_hdl:
adc_init:
This function is executed by the task “vTask_adc_init” pinned on Core 1 to make sure that all ADC related “stuff” is running on Core 1.
Code: Select all
Void adc_init(){
// ….. SPI settngs here ……
ESP_ERROR_CHECK(spi_bus_initialize(HSPI_HOST, &adc_spi_buscfg, 1));
ets_delay_us(10);
ESP_ERROR_CHECK(spi_bus_add_device(HSPI_HOST, &adc_spi_devcfg, &adc_spi_dev_handle));
ets_delay_us(10);
ESP_ERROR_CHECK(spi_device_acquire_bus(adc_spi_dev_handle, portMAX_DELAY));
ets_delay_us(10);
ESP_ERROR_CHECK(spi_device_queue_trans(adc_spi_dev_handle, &adc_spi_transaction, portMAX_DELAY));
ESP_ERROR_CHECK(spi_device_get_trans_result(adc_spi_dev_handle, &adc_spi_transaction, portMAX_DELAY));
………..
//…. SPI (HSPI) handler configuration here……
intr_handle_t spi_int = spi_bus_get_intr(HSPI_HOST);
intr_handle_t adc_spi_intr_handle;
esp_intr_disable(spi_int);
esp_intr_free(spi_int);
esp_intr_alloc(ETS_SPI2_INTR_SOURCE, ESP_INTR_FLAG_LEVEL1 | ESP_INTR_FLAG_IRAM , adc_spi_isr, NULL,
&adc_spi_intr_handle);
esp_intr_enable(adc_spi_intr_handle)
………..
// Configuring the “DATAREADY” interrupt pin for Level-5
gpio_pad_select_gpio(adc_DRDY_INT_PIN);
gpio_config_t CONFIG_adc_DRDY_INT_PIN={
.intr_type=GPIO_INTR_NEGEDGE,
.pin_bit_mask=(1ULL<<adc_DRDY_INT_PIN),
.mode=GPIO_MODE_INPUT,
.pull_up_en=GPIO_PULLUP_DISABLE,
.pull_down_en=GPIO_PULLDOWN_DISABLE,
};
gpio_config(&CONFIG_adc_DRDY_INT_PIN);
// Here the interrupt number 31 (Priority 5 – Level Triggered) is set to Core 1 with source “GPIO Interrupt”
ESP_INTR_DISABLE(31);
intr_matrix_set(1, ETS_GPIO_INTR_SOURCE, 31);
ESP_INTR_ENABLE(31);
………..
}
adc_spi_isr(…):
This is the SPI (HSPI) handler which raises when transaction is finished...
Code: Select all
static void IRAM_ATTR adc_spi_isr(void *arg){
SPI2.slave.trans_done = 0; // reset the register
//ets_printf("IRAM_ATTR adc_spi_isr @ CoreID: %d\n",xPortGetCoreID());
if(samples_counter<SAMPLES_PER_CHANNEL){
samples_package[samples_counter*4+0] = (SPI_SWAP_DATA_RX(SPI2.data_buf[0],32) >> 8); //(byte1 >> 8)
samples_package[samples_counter*4+1] = ((SPI_SWAP_DATA_RX(SPI2.data_buf[0],32)&0x000000FF)<<16) | (SPI_SWAP_DATA_RX(SPI2.data_buf[1],32) >> 16); // ((byte1&0x000000FF)<<16) | (byte2 >> 16)
samples_package[samples_counter*4+2] = (((SPI_SWAP_DATA_RX(SPI2.data_buf[1],32) & 0X0000FFFF) << 8) | SPI_SWAP_DATA_RX(SPI2.data_buf[2],32) >> 24); // (((byte2 & 0X0000FFFF) << 8) | byte3 >> 24)
samples_package[samples_counter*4+3] = (SPI_SWAP_DATA_RX(SPI2.data_buf[2],32) & 0x00FFFFFF); // byte3 & 0x00FFFFFF)
samples_counter++;
}
else{
return_ringbuf_send = xRingbufferSendFromISR(ringbuf_samples_handle, samples_package, sizeof(samples_package), 0);
if (return_ringbuf_send != pdTRUE) {
ets_printf("Failed to send samples package to samples ringbuffer\n");
}
samples_counter=0;
}
}
emac_enc28j60_init(…)
As Sajeel mentioned in the initial post, we are using an external ENC28J60 for ethernet connection. We are using the example code provided here:
https://github.com/espressif/esp-idf/bl ... /README.md
After “analyzing” the code, one can find that the interrupt pin needed for the enc28j60 is configurated in the function “emac_enc28j60_init(…)”.
To ensure that all ethernet related code is running on core 0, we pinned the “ethernet main init” functions to the task “vTask_ethernet_init(..)” pinned to core 0. The “ethernet main init” will be introduced below.
Code: Select all
static esp_err_t emac_enc28j60_init(esp_eth_mac_t *mac)
{
printf("emac_enc28j60_init @ CoreID: %d\n",xPortGetCoreID())
//registering here the GPIO ISR
ESP_ERROR_CHECK(gpio_install_isr_service(0));
esp_err_t ret = ESP_OK;
emac_enc28j60_t *emac = __containerof(mac, emac_enc28j60_t, parent);
esp_eth_mediator_t *eth = emac->eth;
/* init gpio used for reporting enc28j60 interrupt */
gpio_reset_pin(emac->int_gpio_num);
gpio_pad_select_gpio(emac->int_gpio_num);
gpio_set_direction(emac->int_gpio_num, GPIO_MODE_INPUT);
gpio_set_pull_mode(emac->int_gpio_num, GPIO_PULLUP_ONLY);
gpio_set_intr_type(emac->int_gpio_num, GPIO_INTR_NEGEDGE);
//enabling the GPIO interrupt here and assigning the handler...
gpio_intr_enable(emac->int_gpio_num);
gpio_isr_handler_add(emac->int_gpio_num, enc28j60_isr_handler, emac);
......
}
ethernet_init(…):
(same code as in the ENC28j60 example’s app_main)
The ethernet_init(...) function is execuded by the task "vTask_ethernet_init(...)" pinned to Core 0.
Code: Select all
void ethernet_init(){
printf("ethernet_init @ CoreID: %d\n",xPortGetCoreID());
// Initialize TCP/IP network interface (should be called only once in application)
ESP_ERROR_CHECK(esp_netif_init());
// Create default event loop that running in background
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_config_t netif_cfg = ESP_NETIF_DEFAULT_ETH();
esp_netif_t *eth_netif = esp_netif_new(&netif_cfg);
// Set default handlers to process TCP/IP stuffs
ESP_ERROR_CHECK(esp_eth_set_default_handlers(eth_netif));
// Register user defined event handers
ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, ð_event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &got_ip_event_handler, NULL));
spi_bus_config_t buscfg = {
.miso_io_num = GPIO_NUM_19,
.mosi_io_num = GPIO_NUM_23,
.sclk_io_num = GPIO_NUM_18,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
};
ESP_ERROR_CHECK(spi_bus_initialize(VSPI_HOST, &buscfg, 2));
// ENC28J60 ethernet driver is based on spi driver /
spi_device_interface_config_t devcfg = {
.command_bits = 3,
.address_bits = 5,
.mode = 0,
.clock_speed_hz = 6 * 1000 * 1000,
.spics_io_num = GPIO_NUM_5,
.queue_size = 20,
.post_cb = NULL,
.pre_cb = NULL
};
spi_device_handle_t spi_handle = NULL;
ESP_ERROR_CHECK(spi_bus_add_device(VSPI_HOST, &devcfg, &spi_handle));
eth_enc28j60_config_t enc28j60_config = ETH_ENC28J60_DEFAULT_CONFIG(spi_handle);
enc28j60_config.int_gpio_num = GPIO_NUM_27;
eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
mac_config.smi_mdc_gpio_num = -1; // ENC28J60 doesn't have SMI interface
mac_config.smi_mdio_gpio_num = -1;
esp_eth_mac_t *mac = esp_eth_mac_new_enc28j60(&enc28j60_config, &mac_config);
eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
phy_config.autonego_timeout_ms = 0; // ENC28J60 doesn't support auto-negotiation
phy_config.reset_gpio_num = -1; // ENC28J60 doesn't have a pin to reset internal PHY
esp_eth_phy_t *phy = esp_eth_phy_new_enc28j60(&phy_config);
esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, phy);
esp_eth_handle_t eth_handle = NULL;
ESP_ERROR_CHECK(esp_eth_driver_install(ð_config, ð_handle)); //after this call, the ADC Interrupts are not working anymore
// ENC28J60 doesn't burn any factory MAC address, we need to set it manually.
// 02:00:00 is a Locally Administered OUI range so should not be used except when testing on a LAN under your control.
mac->set_addr(mac, (uint8_t[]) {
0x02, 0x00, 0x00, 0x12, 0x34, 0x56
});
// attach Ethernet driver to TCP/IP stack
ESP_ERROR_CHECK(esp_netif_attach(eth_netif, esp_eth_new_netif_glue(eth_handle)));
// start Ethernet driver state machine
ESP_ERROR_CHECK(esp_eth_start(eth_handle));
vTaskDelay(5000/portTICK_RATE_MS)
}
Now the code fragments of app_main with two tasks pinned to Core 0 and Core 1:
Code: Select all
void vTask_ethernet_init(void * pvParameters){
ethernet_init();
while(1){
vTaskDelay(1);
}
}
void vTask_adc_spi_init(void * pvParameters){
printf("vTask_adc_spi_init @ CoreID:%d\n",xPortGetCoreID());
adc_init();
while(1){
vTaskDelay(1);
}
}
app_main(..){
……
xTaskCreatePinnedToCore(vTask_ethernet_init,"",4096,NULL,1,NULL,0);
vTaskDelay(2500/portTICK_RATE_MS);
xTaskCreatePinnedToCore(vTask_adc_spi_init,"",DAQ_TASK_STACKSIZE,NULL,1,NULL,1);
while(1){
vTaskDelay(1);
}
…
}
Thank you very much for your effort and time !
Best regards,
opcode_x64