espressif / esp-idf

Espressif IoT Development Framework. Official development framework for Espressif SoCs.
Apache License 2.0
13.37k stars 7.21k forks source link

attach C function to NMI interrupt (IDFGH-7914) #9433

Open marchingband opened 2 years ago

marchingband commented 2 years ago

Is your feature request related to a problem? Please describe.

I am writing a software UART driver for the ESP32-S3. I have an external XTAL connected to a GPIO, which provides interrupts at 2x the baud rate. Using a scope and logic analyzer, I can see that with a level 3 interrupt, and nothing else running on this core (apart from RTOS), I still get occasional small inconsistencies in the timing.

I understand that it's possible to write .asm for NMI level interrupts, but my C function is very complicated, and I don't want to port it to asm. I have an entire core devoted to servicing this interrupt, so i would like to experiment with calling C from the NMI interrupt. How can I do this?

ESP32 is a microcontroller, so I think it is reasonable to expect access to NMI interrupts.

Describe the solution you'd like

I can imagine 3 ergonomic solutions: 1) write a small wrapper function in asm which calls my C code. 2) somehow compile my C function to asm and link it. 3) somehow just let me register my C interrupt handler and give it the NMI flag.

Describe alternatives you've considered

adding a SAMD21, or other MCU to the PCB. porting the entire project (5k LOC so far) to RPI2040 and using the PIO peripheral.

Additional context

The C function reads some global variables to obtain some config, then either reads or writes to 4 GPIO pins ( 4 UARTs ), each is either RX or TX depending on config. It uses a global array to read/write bits to/from, it also calls the RTOS queue API to get a new byte, or to send a new byte after a completed read. The data is MIDI, so its 31250 baud, 8N1. I have an interrupt every 16us, which gives 2x the baud rate. The function takes about 6us to execute

KaeLL commented 2 years ago

https://github.com/espressif/esp-idf/issues/7959#issuecomment-1094582152

Dazza0 commented 2 years ago

@marchingband C interrupts generally have the following issues which make them unsuitable for low latency purposes:

High priority assembly interrupts don't suffer from the issues above, thus their latency is quite low (latency is typically a couple of CPU clock cycles). However, high priority assembly interrupts have the following downsides:

If you are planning to bit bang, I'm wondering if it's possible just to use the RMT peripheral to handle the bit/banging for you.

marchingband commented 2 years ago

@Dazza0 I really appreciate your response. Thank you. The RMT peripheral is really cool, but won't help me to do UART RX. I had seen this suggestion elsewhere as well :)

Entry to a C callable interrupt requires saving the CPU context on to the interruptee stack and migrating to a new interrupt stack. This causes interrupt latency to C interrupts on the ESP32 to take at least a couple of microseconds.

latency is not as much a concern, consistency is the big issue.

Critical sections will disable all low/medium priority interrupts (i.e., C interrupts), thus this is likely the cause of the inconsistencies you are seeing.

This is the feature I am requesting. I have a core with nothing else running on it, besides RTOS and whatever other ESP background tasks are happening without my knowledge, and still I cannot avoid significant jitter. This to me still sounds like a solvable problem. I want to be able to write a C function that runs, with whatever latency is needed, and can interrupt everything else going on, ie. a high level interrupt. I understand it takes time to set up the stack, but so long as that takes the same amount of time, each time, I can use this for timing critical code. Jitter is the issue. I understand there may be limits to what I can do in that function, there may be risks, I may get strange results sometimes, it could be in the docs as "not recommended", Espressif could say it doesn't officially offer support for the results, but I would like to be able to do it anyway.

must be written in assembly.

I have a suspicion that this is a hard fact, maybe something about the architecture means it literally MUST be written in asm, if it is going to be able to interrupt critical sections, but I have not heard anyone say so explicitly. I am submitting this feature request with the hopes that this is not true, that it -could- be written in C, but the framework has been set up to avoid it happening, for whatever reason, and that could be changed. Googling this issue over the past 6 months, I am seeing a great many people struggling with this, and similar issues, and I truly believe it would open up a whole new range of programs to esp32, for which people are currently redirected to stm etc.

something like #7959 where asm does the setup and then calls a c handler seems totally perfect, so why cant this be wrapped into a convenient function, as I'm sure esp-idf does similar stuff all over the place? I know we can't call RTOS api, thats ok, maybe there are other issues, that's ok, it would be nice to have the option anyway.

marchingband commented 2 years ago

FWIW here is the solution that has been working for me in this specific scenario, sharing incase google takes anyone else here, and to demonstrate how wild of a workaround is needed here.

Disable watchdogs in menuconfig

#include "xtensa/core-macros.h"

#define BAUD_X5_CLOCKS 1536 // 240,000,000 / (31,250 * 5)

static void IRAM_ATTR uart_brute_force(void *dummy)
{
    portDISABLE_INTERRUPTS();
    XTHAL_SET_CCOUNT(0);
    while(1)
    {
        if(XTHAL_GET_CCOUNT() >= BAUD_X5_CLOCKS)
        {
            XTHAL_SET_CCOUNT(0);
            uart_tick(); // my 5x UART logic
        }
    }
}

void start_uart(void)
{
    xTaskCreatePinnedToCore(uart_brute_force, "uart_brute_force", 4096, NULL, 5, NULL, 1);
}

Interesting to note that with this approach freeRTOS API calls (t least to queues) seem to work perfectly inside uart_tick(), and the timing is very very solid. Unfortunately the core cant really do anything else, but that's ok, I upgraded from S2 to S3 just to get a dedicated core to run this software UART.

JimDrewGH commented 5 months ago

FWIW - being 31,250 baud is what you are trying to use, you are probably doing something midi related. The ESP32 support 31,250 baud directly. There are several midi devices using the ESP32 and it's UART for Tx/Rx midi functions. It's not likely that you need to synchronize the ESP32's UART to anything you are doing externally.

marchingband commented 5 months ago

I am trying to implement 4 midi inputs on S3, have opted to wait for P4, although this works, it consumes the entire core.