Skip to content

[2.0.0] Adds the setRXInterrupt #4656

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 46 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
4d262c5
Merge pull request #1 from espressif/master
timkoers Dec 23, 2020
a4df65f
Added UART RX interrupt function
timkoers Dec 23, 2020
b20b3cd
Merge branch 'master' of github.com:timkoers/arduino-esp32
timkoers Dec 23, 2020
725ad11
Updated HardwareSerial class with the interrupt functions
timkoers Dec 23, 2020
e8272c7
Added Serial 'tests' for Interrupt and stock code
timkoers Dec 23, 2020
623796a
This should add the Serial interrupt and no interrupt ino's to the co…
timkoers Dec 23, 2020
000482e
Platform IO sketch sources are messing everything up
timkoers Dec 23, 2020
2e657a7
Platform IO install script fixed
timkoers Dec 23, 2020
38914ff
Arduino Core install script fixed
timkoers Dec 23, 2020
8a27284
Fixed HardwareSerial setRXInterrupt argument type
timkoers Dec 23, 2020
ba351a8
Whoopsie
timkoers Dec 23, 2020
8db7e7f
tested and verified to work
timkoers Dec 24, 2020
96fa55f
Should fix compilation error
timkoers Dec 29, 2020
7d84946
Rewrite of the interrupt handlers for the uart devs
timkoers Jan 8, 2021
054c1a9
Should fix compilation error
timkoers Jan 8, 2021
c344425
Better use else if, or else the input get's duplicated whilest it is …
timkoers Jan 8, 2021
1ba43fc
Updated code formatting and removed commented lines from ci test scripts
timkoers Jan 12, 2021
cadb74d
Updated code formatting once again
timkoers Jan 12, 2021
02b29fd
Last code formatting fix
timkoers Jan 12, 2021
d63d896
Accidentally removed some args whilest formatting the code
timkoers Jan 12, 2021
7bea5a4
Free the interupt from the array on enable/disable of the interrupt, …
timkoers Jan 12, 2021
def8f9c
Renamed the interrupt management functions
timkoers Jan 12, 2021
21043d5
Removed tests from Windows and Mac
timkoers Jan 12, 2021
55ae3be
Fixed setRxInterrupt in HardwareSerial.cpp and fixed double deletion …
timkoers Jan 12, 2021
be5000b
Resolved the if statements
timkoers Jan 12, 2021
6d7e8eb
Cleaned up and commented the Interrupt example
timkoers Jan 12, 2021
e3298ea
Changed serials to Serial and Serial2
timkoers Jan 12, 2021
3bfadf8
Changed example to buffer
timkoers Jan 12, 2021
8cda769
Fixed compilation error
timkoers Jan 12, 2021
36ebb83
Fixed compilation error
timkoers Jan 12, 2021
cf98504
Lost my focus a bit, I think
timkoers Jan 12, 2021
0b2ae4e
Updated uart hal code, with comments regarding the interrupt clearing…
timkoers Jan 12, 2021
715f399
Reverted scripts back to it's original form
timkoers Jan 12, 2021
4f80e57
Reverted on-push to stock form
timkoers Jan 12, 2021
f33c332
Fixed ISR concurrency problems in example
timkoers Jan 12, 2021
5beb727
Update on-push.sh
timkoers Jan 12, 2021
52b8a41
Update on-push.sh
timkoers Jan 12, 2021
f948c78
Replaced tabs with spaces ;-)
timkoers Jan 13, 2021
4441190
And fixed the remaining things
timkoers Jan 13, 2021
ce3952d
Fixed whitespace refactoring and compilation error
timkoers Jan 13, 2021
d8f54ee
Added documentation to the examples and added another Interrupt handl…
timkoers Jan 17, 2021
b3511e5
Don't forget to actually add the example ;-)
timkoers Jan 17, 2021
32a7446
Fixed wrong define name
timkoers Jan 17, 2021
f89b634
Added include for queue.h
timkoers Jan 17, 2021
41a869a
Not sure how I got confused with Queue_t, but fixed
timkoers Jan 17, 2021
12b8725
Fixed
timkoers Jan 17, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions cores/esp32/HardwareSerial.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ void HardwareSerial::updateBaudRate(unsigned long baud)
uartSetBaudRate(_uart, baud);
}

// Make sure that the function that the function pointer is pointing to is inside IRAM
void HardwareSerial::setRxInterrupt(void (*arg)(uint8_t, void*), void* user_arg){

// Make sure that the previous interrupt_info is not used anymore
uartDisableRxInterrupt(_uart);
uartEnableRxInterrupt(_uart, &interrupt_info, arg, user_arg);
}

void HardwareSerial::end()
{
if(uartGetDebug() == _uart_nr) {
Expand Down
3 changes: 3 additions & 0 deletions cores/esp32/HardwareSerial.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
Modified 31 March 2015 by Markus Sattler (rewrite the code for UART0 + UART1 support in ESP8266)
Modified 25 April 2015 by Thomas Flayols (add configuration different from 8N1 in ESP8266)
Modified 13 October 2018 by Jeroen Döll (add baudrate detection)
Modified 08 januari 2012 by Tim Koers (added RX IRQ handling)
Baudrate detection example usage (detection on Serial1):
void setup() {
Serial.begin(115200);
Expand Down Expand Up @@ -58,6 +59,7 @@ class HardwareSerial: public Stream
void begin(unsigned long baud, uint32_t config=SERIAL_8N1, int8_t rxPin=-1, int8_t txPin=-1, bool invert=false, unsigned long timeout_ms = 20000UL);
void end();
void updateBaudRate(unsigned long baud);
void setRxInterrupt(void (*arg)(uint8_t, void*), void* user_arg);
int available(void);
int availableForWrite(void);
int peek(void);
Expand Down Expand Up @@ -108,6 +110,7 @@ class HardwareSerial: public Stream
uart_t* _uart;
uint8_t _tx_pin;
uint8_t _rx_pin;
uart_interrupt_struct_t* interrupt_info;
};

extern void serialEventRun(void) __attribute__((weak));
Expand Down
98 changes: 87 additions & 11 deletions cores/esp32/esp32-hal-uart.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
static int s_uart_debug_nr = 0;

struct uart_struct_t {
uart_dev_t * dev;
uart_dev_t *dev;
#if !CONFIG_DISABLE_HAL_LOCKS
xSemaphoreHandle lock;
#endif
Expand All @@ -47,6 +47,13 @@ struct uart_struct_t {
intr_handle_t intr_handle;
};

struct uart_interrupt_struct_t
{
void (*func)(uint8_t, void*);
void *user_arg;
uart_t *dev;
};

#if CONFIG_DISABLE_HAL_LOCKS
#define UART_MUTEX_LOCK()
#define UART_MUTEX_UNLOCK()
Expand All @@ -69,23 +76,48 @@ static uart_t _uart_bus_array[3] = {

static void uart_on_apb_change(void * arg, apb_change_ev_t ev_type, uint32_t old_apb, uint32_t new_apb);

static uart_interrupt_t *_uart_interrupt_array[3] = {NULL, NULL, NULL};

static void IRAM_ATTR _uart_isr(void *arg)
{
uint8_t i, c;
BaseType_t xHigherPriorityTaskWoken;
uart_t* uart;
uart_interrupt_t *uart_interrupt;

// Loop through all the uart devices
for(i=0;i<3;i++){

// Get the current uart device
uart = &_uart_bus_array[i];
if(uart->intr_handle == NULL){

// Get the interrupt description for this uart device
uart_interrupt = _uart_interrupt_array[i];

// If there is no interrupt handle, skip the rest of the for loop's body
if (uart->intr_handle == NULL) {
continue;
}

// There are cases where bytes might come in between the time you check and handle the bytes and the time you clear the interrupt.
// In that case you will not get an ISR for those bytes.
// We had that happen and scratched heads for quite some time.
// There was another case that I do not recall at this time as well.
// https://github.com/espressif/arduino-esp32/pull/4656#discussion_r555780523
uart->dev->int_clr.rxfifo_full = 1;
uart->dev->int_clr.frm_err = 1;
uart->dev->int_clr.rxfifo_tout = 1;
while(uart->dev->status.rxfifo_cnt || (uart->dev->mem_rx_status.wr_addr != uart->dev->mem_rx_status.rd_addr)) {

// Read until fifo is empty
while (uart->dev->status.rxfifo_cnt || (uart->dev->mem_rx_status.wr_addr != uart->dev->mem_rx_status.rd_addr)) {
c = uart->dev->fifo.rw_byte;
if(uart->queue != NULL) {

// Check if an user defined interrupt handling function is present
if (uart_interrupt != NULL && uart_interrupt->dev->num == uart->num && uart_interrupt->func != NULL) {
// Fully optimized code would not create the queue anymore if an function has been specified as an argument.
(*uart_interrupt->func)(c, uart_interrupt->user_arg);
}else if (uart->queue != NULL) {
// No user function is present, handle as you normally would
xQueueSendFromISR(uart->queue, &c, &xHigherPriorityTaskWoken);
}
}
Expand All @@ -96,7 +128,20 @@ static void IRAM_ATTR _uart_isr(void *arg)
}
}

void uartEnableInterrupt(uart_t* uart)
/**
* @brief Enables the UART RX interrupt on the specified UART device.
*
* This function will register the interrupt user function with arguments, to be called when an RX interupt occurs.
* The pointer to the uart_interrupt_t will be deleted when the interrupt is disabled, or another interrupt function is being registered.
* Please check for NULL on the uart_interrupt_t pointer before using it.

* @param[in] uart The uart device to register the function for.
* @param[out] arg The uart_interrupt description data that is returned when this function succeeds.
* @param[in] func The function to be called when the RX interrupt is fired. (void rx_int(uint8_t c, void* user_arg))
* @param[in] user_arg The user argument that will be passed to the user interrupt handler.
*
*/
void uartEnableRxInterrupt(uart_t* uart, uart_interrupt_t **arg, void (*func)(uint8_t, void*), void* user_arg)
{
UART_MUTEX_LOCK();
uart->dev->conf1.rxfifo_full_thrhd = 112;
Expand All @@ -107,17 +152,48 @@ void uartEnableInterrupt(uart_t* uart)
uart->dev->int_ena.rxfifo_tout = 1;
uart->dev->int_clr.val = 0xffffffff;

if(arg != NULL){
(*arg) = malloc(sizeof(uart_interrupt_t));
(*arg)->func = func;
(*arg)->dev = uart;
(*arg)->user_arg = user_arg;

if(_uart_interrupt_array[uart->num]){
free(_uart_interrupt_array[uart->num]);
}

_uart_interrupt_array[uart->num] = (*arg);
}

esp_intr_alloc(UART_INTR_SOURCE(uart->num), (int)ESP_INTR_FLAG_IRAM, _uart_isr, NULL, &uart->intr_handle);
UART_MUTEX_UNLOCK();
}

void uartDisableInterrupt(uart_t* uart)
/**
* @brief Disables the UART RX interrupt on the specified UART device.
*
* This function disables the RX interrupt for the specified UART device.
* This function will delete the uart_interrupt_t* that uartEnableRxInterrupt() creates.
* The pointer to the uart_interrupt_t will be deleted when the interrupt is disabled, or another interrupt function is being registered.
* Please check for NULL on the uart_interrupt_t pointer before using it.

* @param[in] uart The uart device to register the function for.
*
*/
void uartDisableRxInterrupt(uart_t* uart)
{
UART_MUTEX_LOCK();
uart->dev->conf1.val = 0;
uart->dev->int_ena.val = 0;
uart->dev->int_clr.val = 0xffffffff;

// Free uart rx interrupt
if(_uart_interrupt_array[uart->num]){
free(_uart_interrupt_array[uart->num]);
}

_uart_interrupt_array[uart->num] = NULL;

esp_intr_free(uart->intr_handle);
uart->intr_handle = NULL;

Expand All @@ -130,7 +206,7 @@ void uartDetachRx(uart_t* uart, uint8_t rxPin)
return;
}
pinMatrixInDetach(rxPin, false, false);
uartDisableInterrupt(uart);
uartDisableRxInterrupt(uart);
}

void uartDetachTx(uart_t* uart, uint8_t txPin)
Expand All @@ -148,7 +224,7 @@ void uartAttachRx(uart_t* uart, uint8_t rxPin, bool inverted)
}
pinMode(rxPin, INPUT);
pinMatrixInAttach(rxPin, UART_RXD_IDX(uart->num), inverted);
uartEnableInterrupt(uart);
uartEnableRxInterrupt(uart, NULL, NULL, NULL);
}

void uartAttachTx(uart_t* uart, uint8_t txPin, bool inverted)
Expand Down Expand Up @@ -309,7 +385,7 @@ void uartRxFifoToQueue(uart_t* uart)
uart->dev->int_ena.frm_err = 1;
uart->dev->int_ena.rxfifo_tout = 1;
uart->dev->int_clr.val = 0xffffffff;
UART_MUTEX_UNLOCK();
UART_MUTEX_UNLOCK();
}

uint8_t uartRead(uart_t* uart)
Expand All @@ -320,7 +396,7 @@ uint8_t uartRead(uart_t* uart)
uint8_t c;
if ((uxQueueMessagesWaiting(uart->queue) == 0) && (uart->dev->status.rxfifo_cnt > 0))
{
uartRxFifoToQueue(uart);
uartRxFifoToQueue(uart);
}
if(xQueueReceive(uart->queue, &c, 0)) {
return c;
Expand All @@ -336,7 +412,7 @@ uint8_t uartPeek(uart_t* uart)
uint8_t c;
if ((uxQueueMessagesWaiting(uart->queue) == 0) && (uart->dev->status.rxfifo_cnt > 0))
{
uartRxFifoToQueue(uart);
uartRxFifoToQueue(uart);
}
if(xQueuePeek(uart->queue, &c, 0)) {
return c;
Expand Down
6 changes: 6 additions & 0 deletions cores/esp32/esp32-hal-uart.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ extern "C" {
struct uart_struct_t;
typedef struct uart_struct_t uart_t;

struct uart_interrupt_struct_t;
typedef struct uart_interrupt_struct_t uart_interrupt_t;

uart_t* uartBegin(uint8_t uart_nr, uint32_t baudrate, uint32_t config, int8_t rxPin, int8_t txPin, uint16_t queueLen, bool inverted);
void uartEnd(uart_t* uart, uint8_t rxPin, uint8_t txPin);

Expand Down Expand Up @@ -80,6 +83,9 @@ unsigned long uartDetectBaudrate(uart_t *uart);

bool uartRxActive(uart_t* uart);

void uartDisableRxInterrupt(uart_t* uart);
void uartEnableRxInterrupt(uart_t *uart, uart_interrupt_t** arg, void (*func)(uint8_t, void*), void* user_arg);

#ifdef __cplusplus
}
#endif
Expand Down
77 changes: 77 additions & 0 deletions libraries/ESP32/examples/Serial/Interrupt/Interrupt.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/** Tim Koers - 2021
*
* This sketch shows the usage of a semaphore to receive Serial data.
* You can safely use this in a FreeRTOS environment and use the semaphore from different tasks on different CPU cores.
* However, the InterruptQueue example is much more efficient, since it uses less code.
* This sketch assumes that when Hi is sent, the device returns an 8 byte message.
*
*/

#define BUFFER_SIZE 8

// This semaphore is here to handle the interruption of the loop when the interrupt is running.
SemaphoreHandle_t bufferSemaphore;

static volatile char inputBuffer[BUFFER_SIZE];
static volatile size_t inputBufferLength = 0;

bool messageSent = false;

// Please keep in mind, since the ESP32 is dual core,
// the interrupt will be running on the same core as the setRxInterrupt function was called on.
static void IRAM_ATTR onSerialRX(uint8_t character, void* user_arg){

// Cast the user_arg back to a array
char* buffer = (char*)user_arg;

BaseType_t xHighPriorityTaskWoken;

if(xSemaphoreTakeFromISR(bufferSemaphore, &xHighPriorityTaskWoken) == pdTRUE){
if(inputBufferLength < BUFFER_SIZE){
buffer[inputBufferLength++] = (char)character;
}
xSemaphoreGiveFromISR(bufferSemaphore, &xHighPriorityTaskWoken);
}
}

void setup()
{
bufferSemaphore = xSemaphoreCreateBinary();

Serial.begin(115200);
Serial2.begin(115200);

// The user_arg is expected to be a void pointer (void*),
// so take the reference of the variable, and it to a void*
Serial2.setRxInterrupt(onSerialRX, (void*)&inputBuffer);
}

void loop()
{
if(!messageSent){
Serial.println("Hi");
messageSent = true;
}

// Check if the semaphore can be taken and if the inputBuffer length is long enough.
// Please keep in mind, since the variable inputBufferLength is also accessed inside the IRQ handler,
// the semaphore needs to be taken BEFORE you can access that variable.
if(xSemaphoreTake(bufferSemaphore, portMAX_DELAY) == pdTRUE && inputBufferLength == (BUFFER_SIZE - 1)){
for(size_t i = 0; i < inputBufferLength; i++){
Serial.write(inputBuffer[i]);

// Clear the buffer
inputBuffer[i] = '\0';
}
// Clear the bufferLength
inputBufferLength = 0;

// Give back the semaphore
xSemaphoreGive(bufferSemaphore);

// Allow the system to request another data set
messageSent = false;
}

delay(10); // Wait for 10ms to allow some data to come in
}
59 changes: 59 additions & 0 deletions libraries/ESP32/examples/Serial/InterruptQueue/InterruptQueue.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/** Tim Koers - 2021
*
* This sketch shows the usage of an queue, to store and read the characters that are sent to the interrupt.
* You can safely use this in a FreeRTOS environment and use the queue from different tasks on different CPU cores.
* This sketch assumes that when Hi is sent, the device returns an 8 byte message.
*
*/

#define BUFFER_SIZE 8

// This queue is here to handle the interruption of the loop when the interrupt is running.
QueueHandle_t bufferQueue;

bool messageSent = false;

// Please keep in mind, since the ESP32 is dual core,
// the interrupt will be running on the same core as the setRxInterrupt function was called on.
static void IRAM_ATTR onSerialRX(uint8_t character, void* user_arg){

BaseType_t xHighPriorityTaskWoken;

if(xQueueSendFromISR(bufferQueue, &character, &xHighPriorityTaskWoken) != pdTRUE){
log_e("IRQ", "Failed to put character onto the queue\n");
}
}

void setup()
{
bufferQueue = xQueueCreate(BUFFER_SIZE * 4, sizeof(char)); // Create a queue that can hold 4 messages of 8-bytes each.

assert(bufferQueue != NULL);

Serial.begin(115200);
Serial2.begin(115200);

Serial2.setRxInterrupt(onSerialRX, NULL);
Copy link

@mycbtec mycbtec Aug 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@timkoers I tested the code and it is working fine. I have only one issue at this point during Setup initilization... it sometimes stop at setrxInterrupt function. When I delete Serial2.begin and Serial2.setRxInterrupt -> upload the code -> then add Serial2.begin and Serial2.setRxInterrupt -> upload the code, everything starts to work fine again... I'm trying to figure out why this is happening...

}

void loop()
{
if(!messageSent){
Serial.println("Hi");
messageSent = true;
}

// Check if data in the queue and check if there is a complete message (8-bytes) in the queue
if(!(uxQueueMessagesWaiting(bufferQueue) % BUFFER_SIZE)){
char c;
// Check if the queue is not empty, 0 % 8 returns 0 instead, so does 24 % 8
while(uxQueueMessagesWaiting(bufferQueue) > 0){
// Get the character from the queue, but don't block if someone else is busy with the queue
if(xQueueReceive(bufferQueue, &c, 0) == pdTRUE)
Serial.write(c);
}

// Allow the system to request another data set
messageSent = false;
}
}
Loading