UART2 RX interrupt how to do it?

BenBurger
Posts: 10
Joined: Sat Jan 07, 2023 12:05 pm

UART2 RX interrupt how to do it?

Postby BenBurger » Sun Aug 27, 2023 9:46 am

Hi All
I`ve been running circles here and nothing works that I have tried
I have followed all the example code as well as read the ESP-IDF docks
YET no where is this subject addressed clearly
How do you make use of the receive interrupt of the UARTs
The examples all make use of xTaskCreate... Yes I can do that no problem, but it becomes a blocking function that never returns to allow me to do other tasks
if I use uart_enable_rx_intr(UARTx) the CPU panics and reboots
Is the a simple vanilla re interrupt setup that works
My application is simple;
1- I need to transmit data to UART2, I have a device listening for commands
2- then I need to have a "callback interrupt function" listening for the response and puts the results in a buffer
3- I then have a state machine looking at the results and doing whatever is needed
I have written this code many times in the past, on the PIC`s but now need to port it to the ESP32
BUT the RTOS is a pain,
- Is there a way to NOT use the RTOS ! and simply use a main() with while(1) infinite loop and use good old fashioned interrupt handling routines...

Thanks for any feedback or help
................
//===============================================================================================================
void configUART(void)
{
/* Configure parameters of an UART driver,
* communication pins and install the driver */
uart_config_t uart_config = {
.baud_rate = 115200,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE
};

uart_driver_install(RBELL_UART, BUF_SIZE * 2, BUF_SIZE * 2, 10, &uart2_queue, 0);
uart_param_config(RBELL_UART, &uart_config);
uart_set_pin(RBELL_UART, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
//esp_intr_free(RBELL_UART);
xTaskCreate(uart_rx_intr_handle, "uart_receive", 2048, NULL, 12, NULL);
// uart_enable_rx_intr(RBELL_UART);

}
....
//===============================================================================================================
/*
* Define UART interrupt subroutine to ackowledge interrupt
*/
static void uart_rx_intr_handle(void *arg)
{
uint16_t rx_fifo_len,status;

uint16_t i=0;
while(1){

status = RB_UART.int_st.val; // read UART interrupt Status
rx_fifo_len = RB_UART.status.rxfifo_cnt; // read number of bytes in UART buffer
while(rx_fifo_len){
rxbuf = RB_UART.fifo.rw_byte; // read all bytes
printf("%x ",rxbuf);
i++;
rx_fifo_len--;
}
vTaskDelay(10);
printf("UART Trigger\r\n");
}
}
//===============================================================================================================
void app_main()
{
printf("Start of app\r\n");
configUART();

while(1){
printf("Mainloop\r\n");
vTaskDelay(1000/portTICK_PERIOD_MS);
}
}

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

Re: UART2 RX interrupt how to do it?

Postby ESP_Sprite » Mon Aug 28, 2023 12:46 am

BenBurger wrote:
Sun Aug 27, 2023 9:46 am
The examples all make use of xTaskCreate... Yes I can do that no problem, but it becomes a blocking function that never returns to allow me to do other tasks
Can you clarify this? xTaskCreate should return almost immediately because its function specifically is to allow you to do other tasks.

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

Re: UART2 RX interrupt how to do it?

Postby MicroController » Mon Aug 28, 2023 8:43 am

BenBurger wrote:
Sun Aug 27, 2023 9:46 am
then I need to have a "callback interrupt function" listening for the response and puts the results in a buffer
Note that the UART driver, like all IDF drivers, installs its own interrupt handler.

You can do without a callback by using the UART driver's events. This way, the UART driver does all the interrupt handling and puts an "event" into a queue when something happens. Outside any ISR, your code then just keeps processing events from that queue e.g. in a while(1) loop.

Another simple option is:

Code: Select all

uint8_t my_buffer[128];
while(1) {
 	// Wait for at least one byte to be received via UART
	if (uart_read_bytes(RBELL_UART, &(my_buffer[0]), 1, portMAX_DELAY) > 0) {
	
		uint32_t byte_cnt = 0;
		
	        // There may be more bytes in the driver's buffer. Check how many there are.
	        size_t bytes_available = 0;
		uart_get_buffered_data_len(RBELL_UART, &bytes_available);

		if( bytes_available > 0 ) {
			// Get as much data as is available and will fit in my_buffer
			byte_cnt = bytes_available < (sizeof(my_buffer)-1) ? bytes_available : (sizeof(my_buffer)-1);
			uart_read_bytes(RBELL_UART, &(my_buffer[1]), byte_cnt, 0);
		} 
		
		byte_cnt += 1; // The one byte we read initially...
		
		// We now have received byte_cnt bytes in my_buffer. Do something with it.		
	}
}
(Due to a bug in the UART driver, uart_read_bytes doesn't work correctly when given a finite timeout and a buffer length that's greater than the number of bytes actually available in the driver's buffer. Hence the work-around above.)

BenBurger
Posts: 10
Joined: Sat Jan 07, 2023 12:05 pm

Re: UART2 RX interrupt how to do it?

Postby BenBurger » Mon Aug 28, 2023 3:52 pm

Thank you for your answer, being a old school coder, I need to get my head around the whole RTOS concept, I`m used to writing everything myself and especially UART drivers
I like to write my own printf functions for variuos reasons
(I like to format my code as the device on the comport needs all kinds of checksums, and "xxxprintf" functions just works so easilly\

how can I implement this as part of my code here is an example:
//===============================================================
int dPrintf(const char *fmt, ...)
{
// int x;
// UINT tOut;
char tempBuf[128];
memset(tempBuf, 0, sizeof(tempBuf));
char *tBuf = (char *)&tempBuf;
va_list argptr;
va_start(argptr, fmt);
// tBuf = (char*) malloc(256);
vsprintf(tBuf, fmt, argptr);
va_end(argptr);
// x=0;
while (*tBuf)
{
// send data to uart 1 byte at a time
// while(!(UART4_StatusGet() & UART4_TX_COMPLETE ))
{
// Wait for the tranmission to complete
}
while (uart_write_bytes(UART_NUM_2, (const void *)tBuf, 1))
;
// UART4_Write((char)*tBuf);
tBuf++;
}
// free(tBuf);
return 0;
}
The unknown is the uart_write_bytes....

I want to call this code and send commands to my device as the response response dictates
Tx Ben

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

Re: UART2 RX interrupt how to do it?

Postby ESP_Sprite » Tue Aug 29, 2023 12:20 am

You'd do that using some sort of task interop mechanism, as in a RTOS environment it's very undesired to claim the CPU entirely yourself in a spinloop. Simplified, the uart_write_bytes pushes the data to the uart, then waits on some blocking RTOS thing, e.g. a semaphore, which causes the RTOS not to execute your task anymore. You then have an TX-completed interrupt which sets that semaphore, causing the RTOS to re-schedule your task. (If this sounds complicated, that's why we generally suggest people use the drivers, as they implement this logic already.)

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

Re: UART2 RX interrupt how to do it?

Postby MicroController » Wed Aug 30, 2023 8:50 pm

BenBurger wrote:
Mon Aug 28, 2023 3:52 pm
The unknown is the uart_write_bytes....
Not sure I understand what you mean by that. The UART driver provides uart_write_bytes(...) which does everything for you. You pass it a buffer of data and it gets it pushed out over the UART, blocking the calling task if needed until all data is handed over to the driver's or hardware TX buffer.

If you don't want to use the UART driver, you'll probably want to implement the same logic implemented there, i.e. 1. task calls "send" with n bytes of data 2. if space is available in the HW FIFO, the HW FIFO is filled with data 3. the UART transmission is started 4. the task, inside the "send" function, waits (using one of the OS's primitives, e.g. a semaphore) for some signal from the UART ISR 5. the ISR keeps pushing data to the FIFO until no more bytes are left 6. when no more bytes are left, the ISR sends a signal to the waiting task 7. the task continues.
There isn't much magic to it, but it's at least tedious to implement - especially as one will easily end up re-implementing much of what's already implemented in the IDF driver.

I understand that the RTOS concept of doing things may take some getting used to, but I believe it's really worth it. The ability for seperate tasks to run "concurrently" and for every task to be able to seamlessly block/wait at any time for some time or some event to happen, without spinning/burning CPU time, enables much better, cleaner, more flexible, likely easier and possibly even more performant code. That said, I recommend familiarizing yourself with the essential features FreeRTOS offers, which, besides creating multiple tasks, primarily include the synchronization primitives, i.e. semaphores, queues, mutexes and their blocking operations (those which take some TickType_t maxWaitTime argument.)

BenBurger
Posts: 10
Joined: Sat Jan 07, 2023 12:05 pm

Re: UART2 RX interrupt how to do it?

Postby BenBurger » Thu Aug 31, 2023 1:34 pm

Thanks for the feedback.
I`ve been going down the "Rabbit Hole" of RTOS and Queues etc. and managed to code working "partially"
Here are the issues I ran; again no documentation; samples or any to the such.. so I`m missing something here
1- I registered the ISR callback event for the UART`s; to my understanding only 1 callback is needed for UARTS, and all UARTS will report to the same routine
It works HOWEVER only 2 times and then stops, no more events generated
*in the main loop I check for the flags set and then the data received
-----------------------------------------
---- Sent utf8 encoded message: "1" ----
event0
event1
a0 05 01 72 08 02 de a0
---- Sent utf8 encoded message: "1" ----
event0
a0
---- Sent utf8 encoded message: "1" ----
---- Sent utf8 encoded message: "1" ----
---------------------------------------------------
* I wrote my own cPrintf function and it works well;
as printf is not working even when enable in config to output to the console; unreliable

Below almost stock code I used from the events example BUT why no more response after 2 bytes recived
-----------------------------------------------------
static QueueHandle_t uart2_queue, uart0_queue;
....
/* Configure parameters of an UART driver,
* communication pins and install the driver */
uart_config_t uart_config = {
.baud_rate = 115200,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.source_clk = UART_SCLK_APB,
};
// Install UART driver, and get the queue.
uart_driver_install(UART_NUM_0, 2048, 2048, 15, &uart0_queue, 0);
uart_param_config(UART_NUM_0, &uart_config);
//set just in case
uart_set_pin(UART_NUM_0, 1, 3, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
// Install UART driver, and get the queue.
uart_driver_install(EX_UART_NUM, BUF_SIZE * 2, BUF_SIZE * 2, 10, &uart2_queue, 0);
uart_param_config(EX_UART_NUM, &uart_config);
uart_set_pin(EX_UART_NUM, 21, 22, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
xTaskCreate(uart_event_task, "uart_event_task", 2048, NULL, tskIDLE_PRIORITY, NULL);
..........
/===================================================================================================
static void uart_event_task(void *pvParameters)
{
uart_event_t event;
size_t buffered_size;
uint8_t *dtmp = (uint8_t *)malloc(RD_BUF_SIZE);
uint8_t *dtmp0 = (uint8_t *)malloc(RD_BUF_SIZE);
for (;;)
{
// Waiting for UART_0 event.
if (xQueueReceive(uart0_queue, (void *)&event, (TickType_t)portMAX_DELAY))
{
cPrintf("event0\r\n");
bzero(dtmp0, RD_BUF_SIZE);
// ESP_LOGI(TAG, "uart[%d] event:", EX_UART_NUM);
switch (event.type)
{
// Event of UART receving data
/*We'd better handler data event fast, there would be much more data events than
other types of events. If we take too much time on data event, the queue might
be full.*/
case UART_DATA:
// ESP_LOGI(TAG, "[UART DATA]: %d", event.size);
//ESP_LOGI(TAG, "Data Received[%d]", event.size);
uart_read_bytes(UART_NUM_0, dtmp0, event.size, portMAX_DELAY);
dataSize0 = event.size;
memcpy(dataBuf0, dtmp0, event.size);
dataAvailable0 = true;
break;
// Event of HW FIFO overflow detected
case UART_FIFO_OVF:
cPrintf("Fifo\r\n");
//ESP_LOGI(TAG, "hw fifo overflow");
// If fifo overflow happened, you should consider adding flow control for your application.
// The ISR has already reset the rx FIFO,
// As an example, we directly flush the rx buffer here in order to read more data.
uart_flush_input(EX_UART_NUM);
xQueueReset(uart0_queue);
break;
// Event of UART ring buffer full
case UART_BUFFER_FULL:
cPrintf("Full\r\n");
//ESP_LOGI(TAG, "ring buffer full");
// If buffer full happened, you should consider increasing your buffer size
// As an example, we directly flush the rx buffer here in order to read more data.
uart_flush_input(EX_UART_NUM);
xQueueReset(uart0_queue);
break;
// Event of UART RX break detected
case UART_BREAK:
//ESP_LOGI(TAG, "uart rx break");
break;
// Event of UART parity check error
case UART_PARITY_ERR:
//ESP_LOGI(TAG, "uart parity error");
break;
// Event of UART frame error
case UART_FRAME_ERR:
//ESP_LOGI(TAG, "uart frame error");
break;
// UART_PATTERN_DET
case UART_PATTERN_DET:
uart_get_buffered_data_len(EX_UART_NUM, &buffered_size);
int pos = uart_pattern_pop_pos(EX_UART_NUM);
//ESP_LOGI(TAG, "[UART PATTERN DETECTED] pos: %d, buffered size: %d", pos, buffered_size);
if (pos == -1)
{
// There used to be a UART_PATTERN_DET event, but the pattern position queue is full so that it can not
// record the position. We should set a larger queue size.
// As an example, we directly flush the rx buffer here.
uart_flush_input(EX_UART_NUM);
}
else
{
uart_read_bytes(EX_UART_NUM, dtmp, pos, 100 / portTICK_PERIOD_MS);
//uint8_t pat[PATTERN_CHR_NUM + 1];
//memset(pat, 0, sizeof(pat));
//uart_read_bytes(EX_UART_NUM, pat, PATTERN_CHR_NUM, 100 / portTICK_PERIOD_MS);
//ESP_LOGI(TAG, "read data: %s", dtmp);
// ESP_LOGI(TAG, "read pat : %s", pat);
}
break;
// Others
default:
printf("uart event type: %d", event.type);
//ESP_LOGI(TAG, "uart event type: %d", event.type);
break;
}
}
if (xQueueReceive(uart2_queue, (void *)&event, (TickType_t)portMAX_DELAY))
{
cPrintf("event1\r\n");
bzero(dtmp, RD_BUF_SIZE);
// ESP_LOGI(TAG, "uart[%d] event:", EX_UART_NUM);
switch (event.type)
{
// Event of UART receving data
/*We'd better handler data event fast, there would be much more data events than
other types of events. If we take too much time on data event, the queue might
be full.*/
case UART_DATA:
// ESP_LOGI(TAG, "[UART DATA]: %d", event.size);
uart_read_bytes(EX_UART_NUM, dtmp, event.size, portMAX_DELAY);
dataSize = event.size;
memcpy(dataBuf, dtmp, event.size);
//ESP_LOGI(TAG, "Data Received[%d]", event.size);
if(event.size > 4) //only report when data is bigger than 4
dataAvailable = true;
break;
// Event of HW FIFO overflow detected
case UART_FIFO_OVF:
//ESP_LOGI(TAG, "hw fifo overflow");
// If fifo overflow happened, you should consider adding flow control for your application.
// The ISR has already reset the rx FIFO,
// As an example, we directly flush the rx buffer here in order to read more data.
uart_flush_input(EX_UART_NUM);
xQueueReset(uart2_queue);
break;
// Event of UART ring buffer full
case UART_BUFFER_FULL:
//ESP_LOGI(TAG, "ring buffer full");
// If buffer full happened, you should consider increasing your buffer size
// As an example, we directly flush the rx buffer here in order to read more data.
uart_flush_input(EX_UART_NUM);
xQueueReset(uart2_queue);
break;
// Event of UART RX break detected
case UART_BREAK:
//ESP_LOGI(TAG, "uart rx break");
break;
// Event of UART parity check error
case UART_PARITY_ERR:
//ESP_LOGI(TAG, "uart parity error");
break;
// Event of UART frame error
case UART_FRAME_ERR:
//ESP_LOGI(TAG, "uart frame error");
break;
// UART_PATTERN_DET
case UART_PATTERN_DET:
uart_get_buffered_data_len(EX_UART_NUM, &buffered_size);
int pos = uart_pattern_pop_pos(EX_UART_NUM);
// ESP_LOGI(TAG, "[UART PATTERN DETECTED] pos: %d, buffered size: %d", pos, buffered_size);
if (pos == -1)
{
// There used to be a UART_PATTERN_DET event, but the pattern position queue is full so that it can not
// record the position. We should set a larger queue size.
// As an example, we directly flush the rx buffer here.
uart_flush_input(EX_UART_NUM);
}
else
{
uart_read_bytes(EX_UART_NUM, dtmp, pos, 100 / portTICK_PERIOD_MS);
//uint8_t pat[PATTERN_CHR_NUM + 1];
//memset(pat, 0, sizeof(pat));
//uart_read_bytes(EX_UART_NUM, pat, PATTERN_CHR_NUM, 100 / portTICK_PERIOD_MS);
// ESP_LOGI(TAG, "read data: %s", dtmp);
// ESP_LOGI(TAG, "read pat : %s", pat);
}
break;
// Others
default:
//ESP_LOGI(TAG, "uart event type: %d", event.type);
cPrintf("-%d-",event.type);
break;
}
}
//vTaskDelay(10);
rtc_wdt_feed();
}
free(dtmp);
dtmp = NULL;
vTaskDelete(NULL);
}

Tx Ben

BenBurger
Posts: 10
Joined: Sat Jan 07, 2023 12:05 pm

Re: UART2 RX interrupt how to do it?

Postby BenBurger » Thu Aug 31, 2023 5:48 pm

Success
The problem wat my TASK creation methodology; my knowledge is too limited to make a proprly informed opinion
and by trial and error I managed to get the process working
...
static QueueHandle_t uart2_queue, uart0_queue;
.....
It seems the tasks for each UART ISR, it is handled separately and one has to create a QUEUEHandle for each ISR

Although the tasks seems to cross receive event for both UARTs (event.type) for both UARTs 0 and 2 one cannot check the event.type in the task for both
I found that you have to register 2 tasks, one for each UART and 2 different QUEUEs
xTaskCreate(uart_event_task0, "uart_event_task0", 2048, NULL, 12, NULL);
xTaskCreate(uart_event_task2, "uart_event_task2", 2048, NULL, 12, NULL);

This work now and I can send command via UARt0 and process then and then control the device on UART2 as per the command required

Still lots of work ahead as I want to create a WEB HTTP page to control the device via the WEB interface

*FYI.. This application will control the RODINBELL UHF RFID reader and I used a PIC in the past but now need to convert the code to ESP32

Tx Ben

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

Re: UART2 RX interrupt how to do it?

Postby MicroController » Sun Sep 10, 2023 9:43 am

BenBurger wrote:
Thu Aug 31, 2023 5:48 pm
I found that you have to register 2 tasks, one for each UART and 2 different QUEUEs
Yes, each UART needs its own queue. And usually one uses one task for each queue.

If needed, one could also make a single task process both queues via a queue set, but queue sets come with their own set of drawbacks/limitations.

Who is online

Users browsing this forum: Bing [Bot] and 265 guests