Skip to content

Commit 5565557

Browse files
authored
Merge pull request #394 from teknynja/esp32_rmt_memory_allocation_fix_safe
RMT Buffer Allocation Fix (Thread-Safe) for Issue #375
2 parents aa798ff + d3c88cd commit 5565557

File tree

3 files changed

+93
-25
lines changed

3 files changed

+93
-25
lines changed

Adafruit_NeoPixel.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,11 @@ Adafruit_NeoPixel::Adafruit_NeoPixel(uint16_t n, int16_t p, neoPixelType t)
9292
}
9393
init = true;
9494
#endif
95+
#if defined(ESP32)
96+
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
97+
espInit();
98+
#endif
99+
#endif
95100
}
96101

97102
/*!
@@ -389,6 +394,7 @@ extern "C" IRAM_ATTR void espShow(uint16_t pin, uint8_t *pixels,
389394
#elif defined(ESP32)
390395
extern "C" void espShow(uint16_t pin, uint8_t *pixels, uint32_t numBytes,
391396
uint8_t type);
397+
392398
#endif // ESP8266
393399

394400
#if defined(K210)

Adafruit_NeoPixel.h

+9
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,15 @@ static const uint8_t PROGMEM _NeoPixelGammaTable[256] = {
208208
218, 220, 223, 225, 227, 230, 232, 235, 237, 240, 242, 245, 247, 250, 252,
209209
255};
210210

211+
/* Declare external methods required by the Adafruit_NeoPixel implementation
212+
for specific hardware/library versions
213+
*/
214+
#if defined(ESP32)
215+
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
216+
extern "C" void espInit();
217+
#endif
218+
#endif
219+
211220
/*!
212221
@brief Class that stores state and functions for interacting with
213222
Adafruit NeoPixels and compatible devices.

esp.c

+78-25
Original file line numberDiff line numberDiff line change
@@ -31,40 +31,93 @@
3131
#endif
3232

3333

34-
3534
#ifdef HAS_ESP_IDF_5
3635

36+
static SemaphoreHandle_t show_mutex = NULL;
37+
3738
void espShow(uint8_t pin, uint8_t *pixels, uint32_t numBytes, boolean is800KHz) {
38-
rmt_data_t led_data[numBytes * 8];
39+
// Note: Because rmtPin is shared between all instances, we will
40+
// end up releasing/initializing the RMT channels each time we
41+
// invoke on different pins. This is probably ok, just not
42+
// efficient. led_data is shared between all instances but will
43+
// be allocated with enough space for the largest instance; data
44+
// is not used beyond the mutex lock so this should be fine.
45+
46+
#define SEMAPHORE_TIMEOUT_MS 50
47+
48+
static rmt_data_t *led_data = NULL;
49+
static uint32_t led_data_size = 0;
50+
static int rmtPin = -1;
51+
52+
if (show_mutex && xSemaphoreTake(show_mutex, SEMAPHORE_TIMEOUT_MS / portTICK_PERIOD_MS) == pdTRUE) {
53+
uint32_t requiredSize = numBytes * 8;
54+
if (requiredSize > led_data_size) {
55+
free(led_data);
56+
if (led_data = (rmt_data_t *)malloc(requiredSize * sizeof(rmt_data_t))) {
57+
led_data_size = requiredSize;
58+
} else {
59+
led_data_size = 0;
60+
}
61+
} else if (requiredSize == 0) {
62+
// To release RMT resources (RMT channels and led_data), call
63+
// .updateLength(0) to set number of pixels/bytes to zero,
64+
// then call .show() to invoke this code and free resources.
65+
free(led_data);
66+
led_data = NULL;
67+
if (rmtPin >= 0) {
68+
rmtDeinit(rmtPin);
69+
rmtPin = -1;
70+
}
71+
led_data_size = 0;
72+
}
3973

40-
if (!rmtInit(pin, RMT_TX_MODE, RMT_MEM_NUM_BLOCKS_1, 10000000)) {
41-
log_e("Failed to init RMT TX mode on pin %d", pin);
42-
return;
43-
}
74+
if (led_data_size > 0 && requiredSize <= led_data_size) {
75+
if (pin != rmtPin) {
76+
if (rmtPin >= 0) {
77+
rmtDeinit(rmtPin);
78+
rmtPin = -1;
79+
}
80+
if (!rmtInit(pin, RMT_TX_MODE, RMT_MEM_NUM_BLOCKS_1, 10000000)) {
81+
log_e("Failed to init RMT TX mode on pin %d", pin);
82+
return;
83+
}
84+
rmtPin = pin;
85+
}
4486

45-
int i=0;
46-
for (int b=0; b < numBytes; b++) {
47-
for (int bit=0; bit<8; bit++){
48-
if ( pixels[b] & (1<<(7-bit)) ) {
49-
led_data[i].level0 = 1;
50-
led_data[i].duration0 = 8;
51-
led_data[i].level1 = 0;
52-
led_data[i].duration1 = 4;
53-
} else {
54-
led_data[i].level0 = 1;
55-
led_data[i].duration0 = 4;
56-
led_data[i].level1 = 0;
57-
led_data[i].duration1 = 8;
87+
if (rmtPin >= 0) {
88+
int i=0;
89+
for (int b=0; b < numBytes; b++) {
90+
for (int bit=0; bit<8; bit++){
91+
if ( pixels[b] & (1<<(7-bit)) ) {
92+
led_data[i].level0 = 1;
93+
led_data[i].duration0 = 8;
94+
led_data[i].level1 = 0;
95+
led_data[i].duration1 = 4;
96+
} else {
97+
led_data[i].level0 = 1;
98+
led_data[i].duration0 = 4;
99+
led_data[i].level1 = 0;
100+
led_data[i].duration1 = 8;
101+
}
102+
i++;
103+
}
104+
}
105+
106+
rmtWrite(pin, led_data, numBytes * 8, RMT_WAIT_FOR_EVER);
58107
}
59-
i++;
60108
}
61-
}
62109

63-
//pinMode(pin, OUTPUT); // don't do this, will cause the rmt to disable!
64-
rmtWrite(pin, led_data, numBytes * 8, RMT_WAIT_FOR_EVER);
110+
xSemaphoreGive(show_mutex);
111+
}
65112
}
66113

67-
114+
// To avoid race condition initializing the mutex, all instances of
115+
// Adafruit_NeoPixel must be constructed before launching and child threads
116+
void espInit() {
117+
if (!show_mutex) {
118+
show_mutex = xSemaphoreCreateMutex();
119+
}
120+
}
68121

69122
#else
70123

@@ -219,6 +272,6 @@ void espShow(uint8_t pin, uint8_t *pixels, uint32_t numBytes, boolean is800KHz)
219272
}
220273

221274
#endif // ifndef IDF5
222-
275+
223276

224277
#endif // ifdef(ESP32)

0 commit comments

Comments
 (0)