UART Interrupt without UART driver

engine32
Posts: 1
Joined: Fri May 13, 2022 6:03 pm

UART Interrupt without UART driver

Postby engine32 » Fri May 20, 2022 6:56 pm

Hi there,

ESP32-C3, VSCode.

I am used to work with AT(X)mega and PIC32 microcontrollers in C. For ATXmega, all I need to do is to configure the UART by writing few registers and write the interrupt handler like this:

Code: Select all

ISR(USARTC1_RXC_vect) {
  // Interrupt handler code
}
As a beginner with ESP32, I would like to be able to work in the same way. I do not need a ring buffer other than the hardware one. I do not use RTOS, WIFI nor Bluetooth for this project.

So far I got the GPIO working and the timer with alarm, and configured USART except the receiving interrupt. I can send and receive bytes by writing directly to hardware registers. My code so far is like this:

Code: Select all

#include <stdio.h>
#include "soc/soc.h"
#include "soc/io_mux_reg.h"
#include "soc/gpio_reg.h"
#include "soc/system_reg.h"
#include "soc/timer_group_reg.h"
#include "soc/uart_reg.h"
#include "rom/ets_sys.h"

uint16_t u16Cnt1s;
uint16_t u16Cnt4ms;

uint32_t a;
uint32_t u32GpFlag1s;
uint32_t u32GpFlag4ms;

void app_main(void)
{
  WRITE_PERI_REG(IO_MUX_GPIO0_REG, 1 << 7 | 1 << 2 | 1 << 0);
  WRITE_PERI_REG(GPIO_ENABLE_W1TS_REG, 1);

  // UART
  WRITE_PERI_REG(SYSTEM_PERIP_CLK_EN0_REG, READ_PERI_REG(SYSTEM_PERIP_CLK_EN0_REG) | 1 << SYSTEM_UART_MEM_CLK_EN_S | 1 << SYSTEM_UART_CLK_EN_S); // set bits
  WRITE_PERI_REG(SYSTEM_PERIP_RST_EN0_REG, READ_PERI_REG(SYSTEM_PERIP_RST_EN0_REG) & ~(1 << SYSTEM_UART_RST_S)); // clear bit

  // write 1 to UART_RST_CORE in UART_CLK_CONF_REG
  WRITE_PERI_REG(UART_CLK_CONF_REG(0), (READ_PERI_REG(UART_CLK_CONF_REG(0)) | (1 << UART_RST_CORE_S)));

  // write 1 to SYSTEM_UARTn_RST in SYSTEM_PERIP_RST_EN0_REG
  WRITE_PERI_REG(SYSTEM_PERIP_RST_EN0_REG, (READ_PERI_REG(SYSTEM_PERIP_RST_EN0_REG) | (1 << SYSTEM_UART_RST_S)));

  // clear SYSTEM_UARTn_RST in SYSTEM_PERIP_RST_EN0_REG
  WRITE_PERI_REG(SYSTEM_PERIP_RST_EN0_REG, (READ_PERI_REG(SYSTEM_PERIP_RST_EN0_REG) & ~(1 << SYSTEM_UART_RST_S)));

  // clear UART_RST_CORE in UART_CLK_CONF_REG
  WRITE_PERI_REG(UART_CLK_CONF_REG(0), (READ_PERI_REG(UART_CLK_CONF_REG(0)) & ~(1 << UART_RST_CORE_S)));

  // enable register synchronization by clearing UART_UPDATE_CTRL
  WRITE_PERI_REG(UART_ID_REG(0), (READ_PERI_REG(UART_ID_REG(0)) & ~(1 << UART_HIGH_SPEED_S)));

  // 24.5.2.2 Configuring UARTn Communication
  while(READ_PERI_REG(UART_ID_REG(0)) & (1 << UART_UPDATE_S)) {}

  // write 3 (XTAL 40MHz clock select) to UART_CLK_CONF_REG
  WRITE_PERI_REG(UART_CLK_CONF_REG(0), (1 << UART_RX_SCLK_EN_S | 1 << UART_TX_SCLK_EN_S | 1 << UART_SCLK_EN_S | 3 << UART_SCLK_SEL_S | 2 << UART_SCLK_DIV_NUM_S | 1 << UART_SCLK_DIV_A_S | 1 << UART_SCLK_DIV_B_S));

  // configure baudrate in UART_CLKDIV_REG register
  WRITE_PERI_REG(UART_CLKDIV_REG(0), (40 << UART_CLKDIV_S)); // UART baudrate, no fraction

  // configure0 UART
  WRITE_PERI_REG(UART_CONF0_REG(0), (1 << UART_MEM_CLK_EN_S | 1 << UART_CLK_EN_S | 1 << UART_TXFIFO_RST_S | 1 << UART_RXFIFO_RST_S | 2 << UART_STOP_BIT_NUM_S | 3 << UART_BIT_NUM_S)); // UART baudrate, no fraction

  // clear bits UART_TXFIFO_RST, UART_RXFIFO_RST
  WRITE_PERI_REG(UART_CONF0_REG(0), (1 << UART_MEM_CLK_EN_S | 1 << UART_CLK_EN_S | 3 << UART_STOP_BIT_NUM_S | 3 << UART_BIT_NUM_S)); // UART baudrate, no fraction

  // configure1 UART
  WRITE_PERI_REG(UART_CONF1_REG(0), (4 << UART_TXFIFO_EMPTY_THRHD_S | 0 << UART_RXFIFO_FULL_THRHD_S));

  // no interrupts
  WRITE_PERI_REG(UART_INT_ENA_REG(0), 0);

  // synchronize
  WRITE_PERI_REG(UART_ID_REG(0), (1 << UART_UPDATE_S));

  // TC
  WRITE_PERI_REG(TIMG_T0CONFIG_REG(0), (1 << TIMG_T0_INCREASE_S | 1 << TIMG_T0_AUTORELOAD_S | 32 << TIMG_T0_DIVIDER_S | 1 << TIMG_T0_ALARM_EN_S));
  WRITE_PERI_REG(TIMG_T0CONFIG_REG(0), (1 << TIMG_T0_INCREASE_S | 1 << TIMG_T0_AUTORELOAD_S | 32 << TIMG_T0_DIVIDER_S | 1 << TIMG_T0_DIVCNT_RST_S | 1 << TIMG_T0_ALARM_EN_S));


  WRITE_PERI_REG(TIMG_T0LOADLO_REG(0), 1);
  WRITE_PERI_REG(TIMG_T0LOADHI_REG(0), 0);
  WRITE_PERI_REG(TIMG_T0LOAD_REG(0), 0);

  WRITE_PERI_REG(TIMG_T0ALARMLO_REG(0), 10000);
  WRITE_PERI_REG(TIMG_T0ALARMHI_REG(0), 0);

  WRITE_PERI_REG(TIMG_T0CONFIG_REG(0), (1 << TIMG_T0_EN_S | 1 << TIMG_T0_INCREASE_S | 1 << TIMG_T0_AUTORELOAD_S | 32 << TIMG_T0_DIVIDER_S | 1 << TIMG_T0_ALARM_EN_S));

  while (1) {
    WRITE_PERI_REG(TIMG_T0UPDATE_REG(0), 0); // read raw timer value
    if ((READ_PERI_REG(TIMG_T0CONFIG_REG(0)) & (1 << TIMG_T0_ALARM_EN_S)) == 0) {
      WRITE_PERI_REG(TIMG_T0CONFIG_REG(0), (1 << TIMG_T0_EN_S | 1 << TIMG_T0_INCREASE_S | 1 << TIMG_T0_AUTORELOAD_S | 32 << TIMG_T0_DIVIDER_S | 1 << TIMG_T0_ALARM_EN_S));

			u32GpFlag4ms = 0xFFFFFFFF; // set 4ms flags for general registers
			u16Cnt4ms ++;
			
			// place here code to run every 4ms
      WRITE_PERI_REG(GPIO_OUT_W1TS_REG, 1);
      ets_delay_us(100);
      WRITE_PERI_REG(GPIO_OUT_W1TC_REG, 1);
      ets_delay_us(100); 

			if (u16Cnt4ms > 249) {
				u16Cnt4ms = 0;
				u32GpFlag1s = 0xFFFFFFFF;
				u16Cnt1s ++;
				
				// place here code to run every second
	// send some bytes			
        WRITE_PERI_REG(UART_FIFO_REG(0), 0);
        WRITE_PERI_REG(UART_FIFO_REG(0), 1);
        WRITE_PERI_REG(UART_FIFO_REG(0), 2);
        WRITE_PERI_REG(UART_FIFO_REG(0), 3);
        WRITE_PERI_REG(UART_FIFO_REG(0), 4);
        WRITE_PERI_REG(UART_FIFO_REG(0), 5);
			}
    }
  }
}
This gives me some nice pulses and sends some bytes.

Is it possible to modify some files, or add to them so all I would have to do is declare and write my own interrupt handler by just dealing with the hardware registers ? I expect from the interrupt handler to deal with the preamble and postamble. I plan to use only one interrupt level among all interrupts I use. I am looking for help as to how to configure VSCode to achieve this. I am not knowledgeable enough to walk this step by myself.

If the effort is high, I am open to pay for the help.

To not waste time, please do not come with questions as why I want it this way, and tell what are the benefits of a UART driver, RTOS are etc.

Thank you.

ESP_Sprite
Posts: 8921
Joined: Thu Nov 26, 2015 4:08 am

Re: UART Interrupt without UART driver

Postby ESP_Sprite » Sun May 22, 2022 4:40 am

Suggest you read the technical reference manual (in as far as you haven't already) to handle interrupts on the UART side, but to connect the interrupt to an ISR, it'd be best to use esp_intr_alloc. You can do it by poking directly into the relevant registers as well, but since the ESP32 does run a RTOS and has other software running alongside yours as well, like it or not, you're best off using that function to get at least your interrupt routine set up.

Just a friendly warning: Generally, you're absolutely free to develop in whatever way you want, but I do hope you realize that 'bare metal' programming is one of the lesser trodden paths in ESP32 development; you're likely to find less examples and run into more gotchas than when you simply use ESP-IDF or another higher level SDK. Also, the WiFi stack does depend on the RTOS, so if your code doesn't play nice with that (e.g. in your code you're using ets_delay_us which is a spinloop) you're likely to run into issues there.

coderooni
Posts: 11
Joined: Tue Apr 04, 2023 4:35 pm

Re: UART Interrupt without UART driver

Postby coderooni » Fri May 26, 2023 5:32 pm

ESP_Sprite wrote:
Sun May 22, 2022 4:40 am
Suggest you read the technical reference manual (in as far as you haven't already) to handle interrupts on the UART side, but to connect the interrupt to an ISR, it'd be best to use esp_intr_alloc. You can do it by poking directly into the relevant registers as well, but since the ESP32 does run a RTOS and has other software running alongside yours as well, like it or not, you're best off using that function to get at least your interrupt routine set up.

Just a friendly warning: Generally, you're absolutely free to develop in whatever way you want, but I do hope you realize that 'bare metal' programming is one of the lesser trodden paths in ESP32 development; you're likely to find less examples and run into more gotchas than when you simply use ESP-IDF or another higher level SDK. Also, the WiFi stack does depend on the RTOS, so if your code doesn't play nice with that (e.g. in your code you're using ets_delay_us which is a spinloop) you're likely to run into issues there.

Hi Sprite, I know this is an old post but the content of this post didn't necessitate to create a new one so I'm continuing with this. After looking into the peripheral interrupt configuration registers mentioned in the TRM, it's unclear as to how I can attach/allocate a UART interrupt to an ISR and how to register it 'bare metal'. There are some vague terminologies that appear out of nowhere and are then never used properly in any register or maybe I'm mistaken.
Another thing is when I try enabling certain bits in the UART_INT_ENA register after configuring the IO_MUX pins for UART2, it doesn't enable them. I believe it has something to do with allocating the interrupt to a CPU (APP or PRO), only then will I be able to configure the respective bits.
So, in short I'd like to be able to write an interrupt handler in C (low level interrupt) and allocate the interrupt source to a CPU but I'm stuck. Appreciate any help you can provide.

ESP_Sprite
Posts: 8921
Joined: Thu Nov 26, 2015 4:08 am

Re: UART Interrupt without UART driver

Postby ESP_Sprite » Sat May 27, 2023 10:59 am

'Bare metal' = poking the hardware directly, like on an ATMega. This can be done on an ESP32, but it generally isn't how most things are coded and as such there's not that much documentation on it; given that the ESP32 is a more complex beast, both in hardware and software, there's also lots more things you need to keep in mind. Suggest you use the uart driver instead as this abstracts away all this. This can use a blocking API so you don't even need to (as in: you shouldn't) write an interrupt handler; that's all taken care of in the driver

coderooni
Posts: 11
Joined: Tue Apr 04, 2023 4:35 pm

Re: UART Interrupt without UART driver

Postby coderooni » Sat May 27, 2023 11:12 am

ESP_Sprite wrote:
Sat May 27, 2023 10:59 am
'Bare metal' = poking the hardware directly, like on an ATMega. This can be done on an ESP32, but it generally isn't how most things are coded and as such there's not that much documentation on it; given that the ESP32 is a more complex beast, both in hardware and software, there's also lots more things you need to keep in mind. Suggest you use the uart driver instead as this abstracts away all this. This can use a blocking API so you don't even need to (as in: you shouldn't) write an interrupt handler; that's all taken care of in the driver

I understand this abstracts everything and makes it extremely simple for the end user to develop with but since I need to poke the hardware directly, I dont want to use the drivers. so if there are any resources in this regard, that'll be great otherwise I guess I'll have to poke around until I figure it out.

ESP_Sprite
Posts: 8921
Joined: Thu Nov 26, 2015 4:08 am

Re: UART Interrupt without UART driver

Postby ESP_Sprite » Sat May 27, 2023 11:27 am

Okay, so the thing is, to attach an interrupt you need to use at least a little bit of higher-level code. In the end, you're working with ESP-IDF, and even if you're not using it, per default it does have control over the interrupt vector table. Go look here for more information. You want to call esp_intr_alloc with the uart as the interrupt source, your ISR as the interrupt handler, and the rest can be whatever you please. After that, you don't need ESP-IDF anymore. If you still want to do it 'more manually', you can check the code for that function call, see what it does, then replicate it as needed.

coderooni
Posts: 11
Joined: Tue Apr 04, 2023 4:35 pm

Re: UART Interrupt without UART driver

Postby coderooni » Thu Jun 01, 2023 3:07 pm

ESP_Sprite wrote:
Sat May 27, 2023 11:27 am
Okay, so the thing is, to attach an interrupt you need to use at least a little bit of higher-level code. In the end, you're working with ESP-IDF, and even if you're not using it, per default it does have control over the interrupt vector table. Go look here for more information. You want to call esp_intr_alloc with the uart as the interrupt source, your ISR as the interrupt handler, and the rest can be whatever you please. After that, you don't need ESP-IDF anymore. If you still want to do it 'more manually', you can check the code for that function call, see what it does, then replicate it as needed.

Alright, I've looked at the ROM APIs as well and one of the header files mentioned the steps to attaching a hardware source to a CPU interrupt which is basically setting the interrupt matrix, interrupt handler, enabling the interrupt for CPU, and enabling the interrupt in the module. The first three steps all go well but when I uncomment enabling the interrupt in the module/peripheral section which is the UART_INT_ENA register, I get the "abort() was called at PC 0x400829bb on core 0" error thrown every time. My interrupt handler has only a printf() function which isn't ideal but I just needed a handler to pass to the function.

MicroController
Posts: 1136
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: UART Interrupt without UART driver

Postby MicroController » Thu Jun 01, 2023 6:07 pm

Is your ISR called and crashes, or does enabling the IRQ crash already?

Not sure about ESP32, but on many platforms, including Atmel AVR, an ISR is different from a normal C function in that it needs a special pro-&epilogue to save and restore the full context (CPU registers...) it interrupted.
My interrupt handler has only a printf() function which isn't ideal
No, definitely not "ideal", in general and especially in a UART ISR. printf() would output to (another?) UART too...

MicroController
Posts: 1136
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: UART Interrupt without UART driver

Postby MicroController » Thu Jun 01, 2023 6:17 pm

Btw, I can sort-of understand where you're coming from. But the ESPs are really not well suited for DIY "register poking". They are pretty complex beasts and, well, pretty badly documented when it comes to the low-level stuff. Plus, the IDF does most things acceptably well for you, and if/when it doesn't you can still hack the specific pieces of IDF code while still benefitting from the 98% of code that's already there.

If you are in it for learning/experimenting with low-level stuff, I recommend considering the RP2040 which is much more bare-bones to use and seems to be better documented. (You won't get any WiFi on the ESP without IDF anyway.)

coderooni
Posts: 11
Joined: Tue Apr 04, 2023 4:35 pm

Re: UART Interrupt without UART driver

Postby coderooni » Fri Jun 02, 2023 6:44 am

MicroController wrote:
Thu Jun 01, 2023 6:07 pm
Is your ISR called and crashes, or does enabling the IRQ crash already?

Not sure about ESP32, but on many platforms, including Atmel AVR, an ISR is different from a normal C function in that it needs a special pro-&epilogue to save and restore the full context (CPU registers...) it interrupted.
My interrupt handler has only a printf() function which isn't ideal
No, definitely not "ideal", in general and especially in a UART ISR. printf() would output to (another?) UART too...
From what it seems, the ISR isn't called, it's when I enable the bits for the uart interrupt enable registers, it crashes.
I did realize it that this board isn't suitable for poking registers here and there, especially when the documentation has figure captions but the figures themselves are missing. It's just that I've pretty much reached the end of my 'poking the ESP32 registers' journey and setting up an interrupt handler alongside the peripheral interrupts is the last thing I need to figure out how to do bare-metal.

Who is online

Users browsing this forum: ESP_Dazz and 119 guests