fix(esp): harden shared SPI display locking

Make the T-Deck display SPI mutex recursive and track the owning task before releasing it, avoiding the FreeRTOS assert seen when loopTask tried to give a mutex held by gps_collect.

Keep the display address-window update and pixel payload under one shared SPI lock so SD-backed map tile work cannot interleave between them and corrupt tile fills.

Fixes #59
This commit is contained in:
liu weikai committed 2026-06-13 23:02:59 +08:00
1 parent 3f25e3d577
commit fbed1b092e
3 files changed
+122 -15

No files matched your search

@@ -2,6 +2,7 @@
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#include "platform/esp/common/shared_spi_lock.h"
#include <stddef.h>
@@ -131,6 +132,14 @@ class LilyGoDispArduinoSPI
const DispRotationConfig_t* _rotation_configs;
DispTransferConfig_t _transfer_config{};
SemaphoreHandle_t _lock = nullptr;
TaskHandle_t _lock_owner = nullptr;
uint32_t _lock_depth = 0;
void pushColorsLocked(uint16_t* data, uint32_t len);
void writeParamsLocked(uint8_t cmd, uint8_t* data = nullptr, size_t length = 0);
void writeDataLocked(uint8_t data);
void writeCommandLocked(uint8_t cmd);
void setAddrWindowLocked(uint16_t xs, uint16_t ys, uint16_t xe, uint16_t ye);
public:
uint16_t _width = 0;
@@ -31,12 +31,52 @@ void shared_spi_unlock()
bool LilyGoDispArduinoSPI::lock(TickType_t xTicksToWait)
{
return xSemaphoreTake(_lock, xTicksToWait) == pdTRUE;
if (_lock == nullptr)
{
return false;
}
if (xSemaphoreTakeRecursive(_lock, xTicksToWait) != pdTRUE)
{
return false;
}
TaskHandle_t current = xTaskGetCurrentTaskHandle();
if (_lock_owner == current)
{
_lock_depth++;
}
else
{
_lock_owner = current;
_lock_depth = 1;
}
return true;
}
void LilyGoDispArduinoSPI::unlock()
{
xSemaphoreGive(_lock);
if (_lock == nullptr)
{
return;
}
TaskHandle_t current = xTaskGetCurrentTaskHandle();
if (_lock_owner != current || _lock_depth == 0)
{
return;
}
if (xSemaphoreGiveRecursive(_lock) != pdTRUE)
{
return;
}
_lock_depth--;
if (_lock_depth == 0)
{
_lock_owner = nullptr;
}
}
void LilyGoDispArduinoSPI::setBrightness(uint8_t level)
@@ -54,7 +94,12 @@ bool LilyGoDispArduinoSPI::init(int sck,
uint32_t freq_Mhz,
SPIClass& spi)
{
_lock = xSemaphoreCreateMutex();
_lock = xSemaphoreCreateRecursiveMutex();
if (_lock == nullptr)
{
return false;
}
_spi = &spi;
g_display_spi = this;
@@ -107,7 +152,6 @@ bool LilyGoDispArduinoSPI::init(int sck,
{
digitalWrite(_backlight, (_brightness > 0) ? HIGH : LOW);
}
xSemaphoreGive(_lock);
return true;
}
@@ -135,7 +179,16 @@ void LilyGoDispArduinoSPI::setRotation(uint8_t rotation)
void LilyGoDispArduinoSPI::pushColors(uint16_t* data, uint32_t len)
{
xSemaphoreTake(_lock, portMAX_DELAY);
if (!lock(portMAX_DELAY))
{
return;
}
pushColorsLocked(data, len);
unlock();
}
void LilyGoDispArduinoSPI::pushColorsLocked(uint16_t* data, uint32_t len)
{
digitalWrite(_cs, LOW);
_spi->beginTransaction(SPISettings(_spi_freq, MSBFIRST, SPI_MODE0));
digitalWrite(_dc, HIGH);
@@ -169,13 +222,17 @@ void LilyGoDispArduinoSPI::pushColors(uint16_t* data, uint32_t len)
}
_spi->endTransaction();
digitalWrite(_cs, HIGH);
xSemaphoreGive(_lock);
}
void LilyGoDispArduinoSPI::pushColors(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t* color)
{
setAddrWindow(x1, y1, x1 + x2 - 1, y1 + y2 - 1);
pushColors(color, x2 * y2);
if (!lock(portMAX_DELAY))
{
return;
}
setAddrWindowLocked(x1, y1, x1 + x2 - 1, y1 + y2 - 1);
pushColorsLocked(color, x2 * y2);
unlock();
}
void LilyGoDispArduinoSPI::sleep()
@@ -189,6 +246,16 @@ void LilyGoDispArduinoSPI::wakeup()
}
void LilyGoDispArduinoSPI::setAddrWindow(uint16_t xs, uint16_t ys, uint16_t xe, uint16_t ye)
{
if (!lock(portMAX_DELAY))
{
return;
}
setAddrWindowLocked(xs, ys, xe, ye);
unlock();
}
void LilyGoDispArduinoSPI::setAddrWindowLocked(uint16_t xs, uint16_t ys, uint16_t xe, uint16_t ye)
{
xs += _offset_x;
ys += _offset_y;
@@ -201,13 +268,22 @@ void LilyGoDispArduinoSPI::setAddrWindow(uint16_t xs, uint16_t ys, uint16_t xe,
};
for (uint32_t i = 0; i < 3; i++)
{
writeParams(t[i].cmd, t[i].data, t[i].len);
writeParamsLocked(t[i].cmd, t[i].data, t[i].len);
}
}
void LilyGoDispArduinoSPI::writeCommand(uint8_t cmd)
{
xSemaphoreTake(_lock, portMAX_DELAY);
if (!lock(portMAX_DELAY))
{
return;
}
writeCommandLocked(cmd);
unlock();
}
void LilyGoDispArduinoSPI::writeCommandLocked(uint8_t cmd)
{
digitalWrite(_cs, LOW);
_spi->beginTransaction(SPISettings(_spi_freq, MSBFIRST, SPI_MODE0));
digitalWrite(_dc, LOW);
@@ -215,29 +291,46 @@ void LilyGoDispArduinoSPI::writeCommand(uint8_t cmd)
digitalWrite(_dc, HIGH);
_spi->endTransaction();
digitalWrite(_cs, HIGH);
xSemaphoreGive(_lock);
}
void LilyGoDispArduinoSPI::writeData(uint8_t data)
{
xSemaphoreTake(_lock, portMAX_DELAY);
if (!lock(portMAX_DELAY))
{
return;
}
writeDataLocked(data);
unlock();
}
void LilyGoDispArduinoSPI::writeDataLocked(uint8_t data)
{
digitalWrite(_cs, LOW);
_spi->beginTransaction(SPISettings(_spi_freq, MSBFIRST, SPI_MODE0));
digitalWrite(_dc, HIGH);
_spi->write(data);
_spi->endTransaction();
digitalWrite(_cs, HIGH);
xSemaphoreGive(_lock);
}
void LilyGoDispArduinoSPI::writeParams(uint8_t cmd, uint8_t* data, size_t length)
{
writeCommand(cmd);
if (!lock(portMAX_DELAY))
{
return;
}
writeParamsLocked(cmd, data, length);
unlock();
}
void LilyGoDispArduinoSPI::writeParamsLocked(uint8_t cmd, uint8_t* data, size_t length)
{
writeCommandLocked(cmd);
if (data != nullptr)
{
for (size_t i = 0; i < length; i++)
{
writeData(data[i]);
writeDataLocked(data[i]);
}
}
}
@@ -19,6 +19,11 @@ class SharedSpiLockGuard
{
}
SharedSpiLockGuard(const SharedSpiLockGuard&) = delete;
SharedSpiLockGuard& operator=(const SharedSpiLockGuard&) = delete;
SharedSpiLockGuard(SharedSpiLockGuard&&) = delete;
SharedSpiLockGuard& operator=(SharedSpiLockGuard&&) = delete;
~SharedSpiLockGuard()
{
if (locked_)