diff --git a/.github/workflows/libzedmd.yml b/.github/workflows/libzedmd.yml index 45a9b71..8659a15 100644 --- a/.github/workflows/libzedmd.yml +++ b/.github/workflows/libzedmd.yml @@ -63,6 +63,10 @@ jobs: name: Add autoconf and automake (mac runner) run: | brew install autoconf automake libtool + - if: (matrix.platform == 'linux' && matrix.arch == 'aarch64') + run: | + sudo apt-get update + sudo apt-get install -y libgpiod-dev - name: Build libzedmd-${{ matrix.platform }}-${{ matrix.arch }} run: | ./platforms/${{ matrix.platform }}/${{ matrix.arch }}/external.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 2e957cf..0472b59 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,8 @@ set(ARCH "x64" CACHE STRING "Arch") option(BUILD_SHARED "Option to build shared library" ON) option(BUILD_STATIC "Option to build static library" ON) option(POST_BUILD_COPY_EXT_LIBS "Option to copy external libraries to build directory" ON) +option(SPI_SUPPORT "SPI support" OFF) +add_compile_definitions($<$:SPI_SUPPORT>) message(STATUS "PLATFORM: ${PLATFORM}") message(STATUS "ARCH: ${ARCH}") @@ -73,6 +75,8 @@ set(CMAKE_C_VISIBILITY_PRESET hidden) set(ZEDMD_SOURCES src/ZeDMDComm.h src/ZeDMDComm.cpp + src/ZeDMDSpi.h + src/ZeDMDSpi.cpp src/ZeDMDWiFi.h src/ZeDMDWiFi.cpp src/ZeDMD.h @@ -81,6 +85,11 @@ set(ZEDMD_SOURCES third-party/include/miniz/miniz.c ) +if (ARCH STREQUAL "aarch64") + find_package(PkgConfig REQUIRED) + pkg_check_modules(GPIOD REQUIRED libgpiod) +endif() + set(ZEDMD_INCLUDE_DIRS src third-party/include @@ -110,7 +119,11 @@ if(BUILD_SHARED) target_link_directories(zedmd_shared PUBLIC third-party/runtime-libs/${PLATFORM}/${ARCH} ) - target_link_libraries(zedmd_shared PUBLIC serialport sockpp) + if (ARCH STREQUAL "aarch64") + target_link_libraries(zedmd_shared PUBLIC serialport sockpp ${GPIOD_LIBRARIES}) + else() + target_link_libraries(zedmd_shared PUBLIC serialport sockpp) + endif() elseif(PLATFORM STREQUAL "ios" OR PLATFORM STREQUAL "ios-simulator" OR PLATFORM STREQUAL "tvos") target_link_directories(zedmd_shared PUBLIC third-party/build-libs/${PLATFORM}/${ARCH} @@ -215,7 +228,11 @@ if(BUILD_STATIC) target_link_directories(zedmd-test-portable PUBLIC third-party/runtime-libs/${PLATFORM}/${ARCH} ) - target_link_libraries(zedmd-test-portable PUBLIC zedmd_static serialport sockpp) + if (ARCH STREQUAL "aarch64") + target_link_libraries(zedmd-test-portable PUBLIC zedmd_static serialport sockpp ${GPIOD_LIBRARIES}) + else() + target_link_libraries(zedmd-test-portable PUBLIC zedmd_static serialport sockpp) + endif() endif() if(POST_BUILD_COPY_EXT_LIBS) @@ -246,7 +263,11 @@ if(BUILD_STATIC) target_link_directories(zedmd-client-portable PUBLIC third-party/runtime-libs/${PLATFORM}/${ARCH} ) - target_link_libraries(zedmd-client-portable PUBLIC zedmd_static cargs serialport sockpp) + if (ARCH STREQUAL "aarch64") + target_link_libraries(zedmd-client-portable PUBLIC zedmd_static cargs serialport sockpp ${GPIOD_LIBRARIES}) + else() + target_link_libraries(zedmd-client-portable PUBLIC zedmd_static cargs serialport sockpp) + endif() endif() if(POST_BUILD_COPY_EXT_LIBS) diff --git a/src/ZeDMD.cpp b/src/ZeDMD.cpp index 318a0b1..2aac4e5 100644 --- a/src/ZeDMD.cpp +++ b/src/ZeDMD.cpp @@ -1,9 +1,11 @@ #include "ZeDMD.h" +#include #include #include "FrameUtil.h" #include "ZeDMDComm.h" +#include "ZeDMDSpi.h" #include "ZeDMDWiFi.h" const int endian_check = 1; @@ -20,73 +22,119 @@ ZeDMD::ZeDMD() m_pZeDMDComm = new ZeDMDComm(); m_pZeDMDWiFi = new ZeDMDWiFi(); + m_pZeDMDSpi = new ZeDMDSpi(); } ZeDMD::~ZeDMD() { delete m_pZeDMDComm; delete m_pZeDMDWiFi; + delete m_pZeDMDSpi; if (m_pFrameBuffer) { - delete m_pFrameBuffer; + free(m_pFrameBuffer); + m_pFrameBuffer = nullptr; } if (m_pScaledFrameBuffer) { - delete m_pScaledFrameBuffer; + free(m_pScaledFrameBuffer); + m_pScaledFrameBuffer = nullptr; } if (m_pRgb565Buffer) { - delete m_pRgb565Buffer; + free(m_pRgb565Buffer); + m_pRgb565Buffer = nullptr; } } +void ZeDMD::SetActiveZeDMD(ZeDMDComm* pActive, bool usb, bool wifi, bool spi) +{ + m_pActiveZeDMD = pActive; + m_usb = usb; + m_wifi = wifi; + m_spi = spi; +} + +ZeDMDComm* ZeDMD::GetActiveZeDMD() const { return m_pActiveZeDMD; } + +ZeDMDWiFi* ZeDMD::GetActiveZeDMDWiFi() const +{ + if (m_wifi) + { + return m_pZeDMDWiFi; + } + return nullptr; +} + +ZeDMDSpi* ZeDMD::GetActiveZeDMDSpi() const +{ + if (m_spi) + { + return m_pZeDMDSpi; + } + return nullptr; +} + void ZeDMD::SetLogCallback(ZeDMD_LogCallback callback, const void* userData) { m_pZeDMDComm->SetLogCallback(callback, userData); m_pZeDMDWiFi->SetLogCallback(callback, userData); + m_pZeDMDSpi->SetLogCallback(callback, userData); } void ZeDMD::Close() { - if (m_verbose) m_pZeDMDComm->Log("ZeDMD::Close"); + ZeDMDComm* pActive = GetActiveZeDMD(); + if (m_verbose && pActive) pActive->Log("ZeDMD::Close"); + + if (!pActive) + { + return; + } if (m_usb) { - m_pZeDMDComm->SoftReset(false); - m_pZeDMDComm->Disconnect(); + pActive->SoftReset(false); + pActive->Disconnect(); + } + else if (ZeDMDWiFi* pWiFi = GetActiveZeDMDWiFi()) + { + pWiFi->QueueCommand(ZEDMD_COMM_COMMAND::ClearScreen); + pWiFi->Flush(false); + pWiFi->Disconnect(); } - else if (m_wifi) + else if (ZeDMDSpi* pSpi = GetActiveZeDMDSpi()) { - m_pZeDMDWiFi->QueueCommand(ZEDMD_COMM_COMMAND::ClearScreen); - m_pZeDMDWiFi->Flush(false); - m_pZeDMDWiFi->Disconnect(); + pSpi->QueueCommand(ZEDMD_COMM_COMMAND::ClearScreen); + pSpi->Flush(false); + pSpi->Disconnect(); } + + SetActiveZeDMD(nullptr, false, false, false); } void ZeDMD::Reset() { - if (m_verbose) m_pZeDMDComm->Log("ZeDMD::Reset"); + ZeDMDComm* pActive = GetActiveZeDMD(); + if (m_verbose && pActive) pActive->Log("ZeDMD::Reset"); - if (m_usb) + if (pActive && !m_spi) { - m_pZeDMDComm->SoftReset(); - } - else if (m_wifi) - { - m_pZeDMDWiFi->SoftReset(); + pActive->SoftReset(); } } void ZeDMD::RebootToBootloader() { - if (m_verbose) m_pZeDMDComm->Log("ZeDMD::RebootToBootloader"); + ZeDMDComm* pActive = GetActiveZeDMD(); + if (m_verbose && pActive) pActive->Log("ZeDMD::RebootToBootloader"); - if (m_usb) + if (m_usb && pActive) { - m_pZeDMDComm->RebootToBootloader(); + pActive->RebootToBootloader(); } } @@ -102,20 +150,24 @@ void ZeDMD::SetFrameSize(uint16_t width, uint16_t height) uint16_t const ZeDMD::GetWidth() { - if (m_wifi) + ZeDMDComm* pActive = GetActiveZeDMD(); + if (pActive) { - return m_pZeDMDWiFi->GetWidth(); + return pActive->GetWidth(); } - return m_pZeDMDComm->GetWidth(); + + return 0; } uint16_t const ZeDMD::GetHeight() { - if (m_wifi) + ZeDMDComm* pActive = GetActiveZeDMD(); + if (pActive) { - return m_pZeDMDWiFi->GetHeight(); + return pActive->GetHeight(); } - return m_pZeDMDComm->GetHeight(); + + return 0; } uint16_t const ZeDMD::GetPanelWidth() { return GetWidth(); } @@ -124,13 +176,9 @@ uint16_t const ZeDMD::GetPanelHeight() { bool half = false; - if (m_wifi) - { - half = m_pZeDMDWiFi->IsHalf(); - } - else + if (ZeDMDComm* pActive = GetActiveZeDMD()) { - half = m_pZeDMDComm->IsHalf(); + half = pActive->IsHalf(); } return (half ? (GetHeight() * 2) : GetHeight()); @@ -138,31 +186,34 @@ uint16_t const ZeDMD::GetPanelHeight() bool const ZeDMD::IsS3() { - if (m_wifi) + if (ZeDMDComm* pActive = GetActiveZeDMD()) { - return m_pZeDMDWiFi->IsS3(); + return pActive->IsS3(); } - return m_pZeDMDComm->IsS3(); + + return false; } const char* ZeDMD::GetVersion() { return ZEDMD_VERSION; } const char* ZeDMD::GetFirmwareVersion() { - if (m_usb) + ZeDMDComm* pActive = GetActiveZeDMD(); + if (pActive && !m_spi) { - return m_pZeDMDComm->GetFirmwareVersion(); + return pActive->GetFirmwareVersion(); } - return m_pZeDMDWiFi->GetFirmwareVersion(); + return "SPI"; } uint16_t const ZeDMD::GetId() { - if (m_usb) + ZeDMDComm* pActive = GetActiveZeDMD(); + if (pActive && !m_spi) { - return m_pZeDMDComm->GetId(); + return pActive->GetId(); } - return m_pZeDMDWiFi->GetId(); + return 0; } const char* ZeDMD::GetIdString() @@ -174,202 +225,201 @@ const char* ZeDMD::GetIdString() const char* ZeDMD::GetWiFiSSID() { - if (m_wifi) + if (ZeDMDWiFi* pWiFi = GetActiveZeDMDWiFi()) { - return m_pZeDMDWiFi->GetWiFiSSID(); + return pWiFi->GetWiFiSSID(); } return ""; } const char* ZeDMD::GetIp() { - if (m_wifi) + if (ZeDMDWiFi* pWiFi = GetActiveZeDMDWiFi()) { - return m_pZeDMDWiFi->GetIp(); + return pWiFi->GetIp(); } return ""; } const char* ZeDMD::GetDevice() { - if (m_usb) + if (m_usb && GetActiveZeDMD()) { - return m_pZeDMDComm->GetDevice(); + return GetActiveZeDMD()->GetDevice(); } return ""; } int ZeDMD::GetWiFiPort() { - if (m_wifi) + if (ZeDMDWiFi* pWiFi = GetActiveZeDMDWiFi()) { - return m_pZeDMDWiFi->GetWiFiPort(); + return pWiFi->GetWiFiPort(); } return 0; } uint8_t ZeDMD::GetWiFiPower() { - if (m_wifi) + if (ZeDMDWiFi* pWiFi = GetActiveZeDMDWiFi()) { - return m_pZeDMDWiFi->GetWiFiPower(); + return pWiFi->GetWiFiPower(); } return 0; } void ZeDMD::StoreWiFiPassword() { - if (m_wifi) + if (ZeDMDWiFi* pWiFi = GetActiveZeDMDWiFi()) { - return m_pZeDMDWiFi->StoreWiFiPassword(); + return pWiFi->StoreWiFiPassword(); } } uint8_t ZeDMD::GetRGBOrder() { - if (m_usb) + ZeDMDComm* pActive = GetActiveZeDMD(); + if (pActive && !m_spi) { - return m_pZeDMDComm->GetRGBOrder(); + return pActive->GetRGBOrder(); } - return m_pZeDMDWiFi->GetRGBOrder(); + return 0; } uint8_t ZeDMD::GetBrightness() { - if (m_usb) + ZeDMDComm* pActive = GetActiveZeDMD(); + if (pActive && !m_spi) { - return m_pZeDMDComm->GetBrightness(); + return pActive->GetBrightness(); } - return m_pZeDMDWiFi->GetBrightness(); + return 0; } uint8_t ZeDMD::GetPanelClockPhase() { - if (m_usb) + ZeDMDComm* pActive = GetActiveZeDMD(); + if (pActive && !m_spi) { - return m_pZeDMDComm->GetPanelClockPhase(); + return pActive->GetPanelClockPhase(); } - return m_pZeDMDWiFi->GetPanelClockPhase(); + return 0; } uint8_t ZeDMD::GetPanelI2sSpeed() { - if (m_usb) + ZeDMDComm* pActive = GetActiveZeDMD(); + if (pActive && !m_spi) { - return m_pZeDMDComm->GetPanelI2sSpeed(); + return pActive->GetPanelI2sSpeed(); } - return m_pZeDMDWiFi->GetPanelI2sSpeed(); + return 0; } uint8_t ZeDMD::GetPanelLatchBlanking() { - if (m_usb) + ZeDMDComm* pActive = GetActiveZeDMD(); + if (pActive && !m_spi) { - return m_pZeDMDComm->GetPanelLatchBlanking(); + return pActive->GetPanelLatchBlanking(); } - return m_pZeDMDWiFi->GetPanelLatchBlanking(); + return 0; } uint8_t ZeDMD::GetPanelMinRefreshRate() { - if (m_usb) + ZeDMDComm* pActive = GetActiveZeDMD(); + if (pActive && !m_spi) { - return m_pZeDMDComm->GetPanelMinRefreshRate(); + return pActive->GetPanelMinRefreshRate(); } - return m_pZeDMDWiFi->GetPanelMinRefreshRate(); + return 0; } uint8_t ZeDMD::GetPanelDriver() { - if (m_usb) + ZeDMDComm* pActive = GetActiveZeDMD(); + if (pActive && !m_spi) { - return m_pZeDMDComm->GetPanelDriver(); + return pActive->GetPanelDriver(); } - return m_pZeDMDWiFi->GetPanelDriver(); + return 0; } uint8_t ZeDMD::GetTransport() { - if (m_usb) + if (ZeDMDComm* pActive = GetActiveZeDMD()) { - return m_pZeDMDComm->GetTransport(); + return pActive->GetTransport(); } - return m_pZeDMDWiFi->GetTransport(); + return 0; } uint8_t ZeDMD::GetUdpDelay() { - if (m_usb) + ZeDMDComm* pActive = GetActiveZeDMD(); + if (pActive && !m_spi) { - return m_pZeDMDComm->GetUdpDelay(); + return pActive->GetUdpDelay(); } - return m_pZeDMDWiFi->GetUdpDelay(); + return 0; } uint16_t ZeDMD::GetUsbPackageSize() { - if (m_usb) + ZeDMDComm* pActive = GetActiveZeDMD(); + if (pActive && !m_spi) { - return m_pZeDMDComm->GetUsbPackageSize(); + return pActive->GetUsbPackageSize(); } - return m_pZeDMDWiFi->GetUsbPackageSize(); + return 0; } uint8_t ZeDMD::GetYOffset() { - if (m_usb) + ZeDMDComm* pActive = GetActiveZeDMD(); + if (pActive && !m_spi) { - return m_pZeDMDComm->GetYOffset(); + return pActive->GetYOffset(); } - return m_pZeDMDWiFi->GetYOffset(); + return 0; } void ZeDMD::LedTest() { - if (m_usb) + ZeDMDComm* pActive = GetActiveZeDMD(); + if (!pActive) { - m_pZeDMDComm->QueueCommand(ZEDMD_COMM_COMMAND::LEDTest); - m_pZeDMDComm->DisableKeepAlive(); + return; } - else if (m_wifi) + + pActive->QueueCommand(ZEDMD_COMM_COMMAND::LEDTest); + if (!m_spi) { - m_pZeDMDWiFi->QueueCommand(ZEDMD_COMM_COMMAND::LEDTest); - m_pZeDMDWiFi->DisableKeepAlive(); + pActive->DisableKeepAlive(); } std::this_thread::sleep_for(std::chrono::milliseconds(8000)); - if (m_usb) + if (!m_spi) { - m_pZeDMDComm->EnableKeepAlive(); - } - else if (m_wifi) - { - m_pZeDMDWiFi->EnableKeepAlive(); + pActive->EnableKeepAlive(); } } void ZeDMD::EnableDebug() { - if (m_usb) - { - m_pZeDMDComm->QueueCommand(ZEDMD_COMM_COMMAND::EnableDebug); - } - else if (m_wifi) + if (ZeDMDComm* pActive = GetActiveZeDMD()) { - m_pZeDMDWiFi->QueueCommand(ZEDMD_COMM_COMMAND::EnableDebug); + pActive->QueueCommand(ZEDMD_COMM_COMMAND::EnableDebug); } } void ZeDMD::DisableDebug() { - if (m_usb) - { - m_pZeDMDComm->QueueCommand(ZEDMD_COMM_COMMAND::DisableDebug); - } - else if (m_wifi) + if (ZeDMDComm* pActive = GetActiveZeDMD()) { - m_pZeDMDWiFi->QueueCommand(ZEDMD_COMM_COMMAND::DisableDebug); + pActive->QueueCommand(ZEDMD_COMM_COMMAND::DisableDebug); } } @@ -378,6 +428,7 @@ void ZeDMD::EnableVerbose() m_verbose = true; m_pZeDMDComm->SetVerbose(true); m_pZeDMDWiFi->SetVerbose(true); + m_pZeDMDSpi->SetVerbose(true); } void ZeDMD::DisableVerbose() @@ -385,179 +436,157 @@ void ZeDMD::DisableVerbose() m_verbose = false; m_pZeDMDComm->SetVerbose(false); m_pZeDMDWiFi->SetVerbose(false); + m_pZeDMDSpi->SetVerbose(false); } void ZeDMD::SetRGBOrder(uint8_t rgbOrder) { - if (m_verbose) m_pZeDMDComm->Log("ZeDMD::SetRGBOrder %d", rgbOrder); + ZeDMDComm* pActive = GetActiveZeDMD(); + if (m_verbose && pActive) pActive->Log("ZeDMD::SetRGBOrder %d", rgbOrder); - if (m_usb) - { - m_pZeDMDComm->QueueCommand(ZEDMD_COMM_COMMAND::RGBOrder, rgbOrder); - } - else if (m_wifi) + if (pActive) { - m_pZeDMDWiFi->QueueCommand(ZEDMD_COMM_COMMAND::RGBOrder, rgbOrder); + pActive->QueueCommand(ZEDMD_COMM_COMMAND::RGBOrder, rgbOrder); } } void ZeDMD::SetBrightness(uint8_t brightness) { - if (m_verbose) m_pZeDMDComm->Log("ZeDMD::SetBrightness %d", brightness); + ZeDMDComm* pActive = GetActiveZeDMD(); + if (m_verbose && pActive) pActive->Log("ZeDMD::SetBrightness %d", brightness); - if (m_usb) - { - m_pZeDMDComm->QueueCommand(ZEDMD_COMM_COMMAND::Brightness, brightness); - } - else if (m_wifi) + if (pActive) { - m_pZeDMDWiFi->QueueCommand(ZEDMD_COMM_COMMAND::Brightness, brightness); + pActive->QueueCommand(ZEDMD_COMM_COMMAND::Brightness, brightness); } } void ZeDMD::SetPanelClockPhase(uint8_t clockPhase) { - if (m_verbose) m_pZeDMDComm->Log("ZeDMD::SetPanelClockPhase %d", clockPhase); + ZeDMDComm* pActive = GetActiveZeDMD(); + if (m_verbose && pActive) pActive->Log("ZeDMD::SetPanelClockPhase %d", clockPhase); - if (m_usb) + if (pActive) { - m_pZeDMDComm->QueueCommand(ZEDMD_COMM_COMMAND::SetClkphase, clockPhase); - } - else if (m_wifi) - { - m_pZeDMDWiFi->QueueCommand(ZEDMD_COMM_COMMAND::SetClkphase, clockPhase); + pActive->QueueCommand(ZEDMD_COMM_COMMAND::SetClkphase, clockPhase); } } void ZeDMD::SetPanelI2sSpeed(uint8_t i2sSpeed) { - if (m_verbose) m_pZeDMDComm->Log("ZeDMD::SetPanelI2sSpeed %d", i2sSpeed); + ZeDMDComm* pActive = GetActiveZeDMD(); + if (m_verbose && pActive) pActive->Log("ZeDMD::SetPanelI2sSpeed %d", i2sSpeed); - if (m_usb) - { - m_pZeDMDComm->QueueCommand(ZEDMD_COMM_COMMAND::SetI2sspeed, i2sSpeed); - } - else if (m_wifi) + if (pActive) { - m_pZeDMDWiFi->QueueCommand(ZEDMD_COMM_COMMAND::SetI2sspeed, i2sSpeed); + pActive->QueueCommand(ZEDMD_COMM_COMMAND::SetI2sspeed, i2sSpeed); } } void ZeDMD::SetPanelLatchBlanking(uint8_t latchBlanking) { - if (m_verbose) m_pZeDMDComm->Log("ZeDMD::SetPanelLatchBlanking %d", latchBlanking); + ZeDMDComm* pActive = GetActiveZeDMD(); + if (m_verbose && pActive) pActive->Log("ZeDMD::SetPanelLatchBlanking %d", latchBlanking); - if (m_usb) + if (pActive) { - m_pZeDMDComm->QueueCommand(ZEDMD_COMM_COMMAND::SetLatchBlanking, latchBlanking); - } - else if (m_wifi) - { - m_pZeDMDWiFi->QueueCommand(ZEDMD_COMM_COMMAND::SetLatchBlanking, latchBlanking); + pActive->QueueCommand(ZEDMD_COMM_COMMAND::SetLatchBlanking, latchBlanking); } } void ZeDMD::SetPanelMinRefreshRate(uint8_t minRefreshRate) { - if (m_verbose) m_pZeDMDComm->Log("ZeDMD::SetPanelMinRefreshRate %d", minRefreshRate); + ZeDMDComm* pActive = GetActiveZeDMD(); + if (m_verbose && pActive) pActive->Log("ZeDMD::SetPanelMinRefreshRate %d", minRefreshRate); - if (m_usb) + if (pActive) { - m_pZeDMDComm->QueueCommand(ZEDMD_COMM_COMMAND::SetMinRefreshRate, minRefreshRate); - } - else if (m_wifi) - { - m_pZeDMDWiFi->QueueCommand(ZEDMD_COMM_COMMAND::SetMinRefreshRate, minRefreshRate); + pActive->QueueCommand(ZEDMD_COMM_COMMAND::SetMinRefreshRate, minRefreshRate); } } void ZeDMD::SetPanelDriver(uint8_t driver) { - if (m_verbose) m_pZeDMDComm->Log("ZeDMD::SetPanelDriver %d", driver); + ZeDMDComm* pActive = GetActiveZeDMD(); + if (m_verbose && pActive) pActive->Log("ZeDMD::SetPanelDriver %d", driver); - if (m_usb) + if (pActive) { - m_pZeDMDComm->QueueCommand(ZEDMD_COMM_COMMAND::SetDriver, driver); - } - else if (m_wifi) - { - m_pZeDMDWiFi->QueueCommand(ZEDMD_COMM_COMMAND::SetDriver, driver); + pActive->QueueCommand(ZEDMD_COMM_COMMAND::SetDriver, driver); } } void ZeDMD::SetTransport(uint8_t transport) { - if (m_verbose) m_pZeDMDComm->Log("ZeDMD::SetTransport %d", transport); + ZeDMDComm* pActive = GetActiveZeDMD(); + if (m_verbose && pActive) pActive->Log("ZeDMD::SetTransport %d", transport); - if (m_usb) + if (pActive && !m_spi) { - m_pZeDMDComm->QueueCommand(ZEDMD_COMM_COMMAND::SetTransport, transport); - } - else if (m_wifi) - { - m_pZeDMDWiFi->QueueCommand(ZEDMD_COMM_COMMAND::SetTransport, transport); + pActive->QueueCommand(ZEDMD_COMM_COMMAND::SetTransport, transport); } } void ZeDMD::SetUdpDelay(uint8_t udpDelay) { - if (m_verbose) m_pZeDMDComm->Log("ZeDMD::SetUdpDelay %d", udpDelay); + ZeDMDComm* pActive = GetActiveZeDMD(); + if (m_verbose && pActive) pActive->Log("ZeDMD::SetUdpDelay %d", udpDelay); - if (m_usb) + if (pActive && !m_spi) { - m_pZeDMDComm->QueueCommand(ZEDMD_COMM_COMMAND::SetUdpDelay, udpDelay); - } - else if (m_wifi) - { - m_pZeDMDWiFi->QueueCommand(ZEDMD_COMM_COMMAND::SetUdpDelay, udpDelay); + pActive->QueueCommand(ZEDMD_COMM_COMMAND::SetUdpDelay, udpDelay); } } void ZeDMD::SetUsbPackageSize(uint16_t usbPackageSize) { - if (m_verbose) m_pZeDMDComm->Log("ZeDMD::SetUsbPackageSize %d", usbPackageSize); + ZeDMDComm* pActive = GetActiveZeDMD(); + if (m_verbose && pActive) pActive->Log("ZeDMD::SetUsbPackageSize %d", usbPackageSize); uint8_t multiplier = (uint8_t)(usbPackageSize / 32); - if (m_usb) - { - m_pZeDMDComm->QueueCommand(ZEDMD_COMM_COMMAND::SetUsbPackageSizeMultiplier, multiplier); - } - else if (m_wifi) + if (pActive && !m_spi) { - m_pZeDMDWiFi->QueueCommand(ZEDMD_COMM_COMMAND::SetUsbPackageSizeMultiplier, multiplier); + pActive->QueueCommand(ZEDMD_COMM_COMMAND::SetUsbPackageSizeMultiplier, multiplier); } } void ZeDMD::SetYOffset(uint8_t yOffset) { - if (m_verbose) m_pZeDMDComm->Log("ZeDMD::SetYOffset %d", yOffset); + ZeDMDComm* pActive = GetActiveZeDMD(); + if (m_verbose && pActive) pActive->Log("ZeDMD::SetYOffset %d", yOffset); - if (m_usb) - { - m_pZeDMDComm->QueueCommand(ZEDMD_COMM_COMMAND::SetYOffset, yOffset); - } - else if (m_wifi) + if (pActive) { - m_pZeDMDWiFi->QueueCommand(ZEDMD_COMM_COMMAND::SetYOffset, yOffset); + if (m_spi) + { + pActive->QueueCommand(ZEDMD_COMM_COMMAND::DisableDebug); + } + else + { + pActive->QueueCommand(ZEDMD_COMM_COMMAND::SetYOffset, yOffset); + } } } void ZeDMD::SaveSettings() { - if (m_verbose) m_pZeDMDComm->Log("ZeDMD::SaveSettings"); + ZeDMDComm* pActive = GetActiveZeDMD(); + if (m_verbose && pActive) pActive->Log("ZeDMD::SaveSettings"); - if (m_usb) + if (!pActive) { - m_pZeDMDComm->QueueCommand(ZEDMD_COMM_COMMAND::SaveSettings); - // Avoid that client resets the device before settings are saved. - m_pZeDMDComm->Flush(); + return; } - else if (m_wifi) + + if (m_spi) { - m_pZeDMDWiFi->QueueCommand(ZEDMD_COMM_COMMAND::SaveSettings); - // Avoid that client resets the device before settings are saved. - m_pZeDMDWiFi->Flush(); + pActive->QueueCommand(ZEDMD_COMM_COMMAND::DisableDebug); + return; } + + pActive->QueueCommand(ZEDMD_COMM_COMMAND::SaveSettings); + // Avoid that client resets the device before settings are saved. + pActive->Flush(); } void ZeDMD::EnableUpscaling() @@ -570,74 +599,56 @@ void ZeDMD::DisableUpscaling() { m_upscaling = false; } void ZeDMD::SetWiFiSSID(const char* const ssid) { - if (m_verbose) m_pZeDMDComm->Log("ZeDMD::SetWiFiSSID"); + ZeDMDComm* pActive = GetActiveZeDMD(); + if (m_verbose && pActive) pActive->Log("ZeDMD::SetWiFiSSID"); int size = strlen(ssid); - if (size <= 32) + if (size <= 32 && pActive && !m_spi) { uint8_t data[32] = {0}; memcpy(data, (uint8_t*)ssid, size); - if (m_usb) - { - m_pZeDMDComm->QueueCommand(ZEDMD_COMM_COMMAND::SetWiFiSSID, data, size); - } - else if (m_wifi) - { - m_pZeDMDWiFi->QueueCommand(ZEDMD_COMM_COMMAND::SetWiFiSSID, data, size); - } + pActive->QueueCommand(ZEDMD_COMM_COMMAND::SetWiFiSSID, data, size); } } void ZeDMD::SetWiFiPassword(const char* const password) { - if (m_verbose) m_pZeDMDComm->Log("ZeDMD::SetWiFiPassword"); + ZeDMDComm* pActive = GetActiveZeDMD(); + if (m_verbose && pActive) pActive->Log("ZeDMD::SetWiFiPassword"); int size = strlen(password); - if (size <= 32) + if (size <= 32 && pActive && !m_spi) { uint8_t data[32] = {0}; memcpy(data, (uint8_t*)password, size); - if (m_usb) - { - m_pZeDMDComm->QueueCommand(ZEDMD_COMM_COMMAND::SetWiFiPassword, data, size); - } - else if (m_wifi) - { - m_pZeDMDWiFi->QueueCommand(ZEDMD_COMM_COMMAND::SetWiFiPassword, data, size); - } + pActive->QueueCommand(ZEDMD_COMM_COMMAND::SetWiFiPassword, data, size); } } void ZeDMD::SetWiFiPort(int port) { - if (m_verbose) m_pZeDMDComm->Log("ZeDMD::SetWiFiPort %d", port); + ZeDMDComm* pActive = GetActiveZeDMD(); + if (m_verbose && pActive) pActive->Log("ZeDMD::SetWiFiPort %d", port); uint8_t data[2]; data[0] = (uint8_t)(port >> 8 & 0xFF); data[1] = (uint8_t)(port & 0xFF); - if (m_usb) - { - m_pZeDMDComm->QueueCommand(ZEDMD_COMM_COMMAND::SetWiFiPort, data, 2); - } - else if (m_wifi) + if (pActive && !m_spi) { - m_pZeDMDWiFi->QueueCommand(ZEDMD_COMM_COMMAND::SetWiFiPort, data, 2); + pActive->QueueCommand(ZEDMD_COMM_COMMAND::SetWiFiPort, data, 2); } } void ZeDMD::SetWiFiPower(uint8_t power) { - if (m_verbose) m_pZeDMDComm->Log("ZeDMD::SetWiFiPower %d", power); + ZeDMDComm* pActive = GetActiveZeDMD(); + if (m_verbose && pActive) pActive->Log("ZeDMD::SetWiFiPower %d", power); uint8_t data[1]; data[0] = power; - if (m_usb) - { - m_pZeDMDComm->QueueCommand(ZEDMD_COMM_COMMAND::SetWiFiPower, data, 1); - } - else if (m_wifi) + if (pActive && !m_spi) { - m_pZeDMDWiFi->QueueCommand(ZEDMD_COMM_COMMAND::SetWiFiPower, data, 1); + pActive->QueueCommand(ZEDMD_COMM_COMMAND::SetWiFiPower, data, 1); } } @@ -647,8 +658,9 @@ bool ZeDMD::OpenWiFi(const char* ip) m_wifi = m_pZeDMDWiFi->Connect(ip); - if (m_wifi && !m_usb) + if (m_wifi) { + SetActiveZeDMD(m_pZeDMDWiFi, false, true, false); uint16_t width = m_pZeDMDWiFi->GetWidth(); uint16_t height = m_pZeDMDWiFi->GetHeight(); m_hd = (width == 256); @@ -669,8 +681,9 @@ bool ZeDMD::Open() { m_usb = m_pZeDMDComm->Connect(); - if (m_usb && !m_wifi) + if (m_usb) { + SetActiveZeDMD(m_pZeDMDComm, true, false, false); uint16_t width = m_pZeDMDComm->GetWidth(); uint16_t height = m_pZeDMDComm->GetHeight(); m_hd = (width == 256); @@ -695,29 +708,54 @@ bool ZeDMD::Open(uint16_t width, uint16_t height) return m_usb; } -void ZeDMD::ClearScreen() +bool ZeDMD::OpenSpi(uint32_t speed, uint8_t framePause, uint16_t width, uint16_t height) { - if (m_verbose) m_pZeDMDComm->Log("ZeDMD::ClearScreen"); + m_pZeDMDSpi->SetSpeed(speed); + m_pZeDMDSpi->SetFramePause(framePause); + m_pZeDMDSpi->SetWidth(width); + m_pZeDMDSpi->SetHeight(height); - if (m_usb) + m_spi = m_pZeDMDSpi->Connect(); + + if (m_spi) { - m_pZeDMDComm->QueueCommand(ZEDMD_COMM_COMMAND::ClearScreen); + SetActiveZeDMD(m_pZeDMDSpi, false, false, true); + m_hd = (width == 256); + + m_pFrameBuffer = (uint8_t*)malloc(ZEDMD_MAX_WIDTH * ZEDMD_MAX_HEIGHT * 3); + m_pScaledFrameBuffer = (uint8_t*)malloc(ZEDMD_MAX_WIDTH * ZEDMD_MAX_HEIGHT * 3); + m_pRgb565Buffer = (uint8_t*)malloc(width * height * 2); + + m_pZeDMDSpi->Run(); } - else if (m_wifi) + + return m_spi; +} + +void ZeDMD::ClearScreen() +{ + ZeDMDComm* pActive = GetActiveZeDMD(); + if (m_verbose && pActive) pActive->Log("ZeDMD::ClearScreen"); + + if (pActive) { - m_pZeDMDWiFi->QueueCommand(ZEDMD_COMM_COMMAND::ClearScreen); + pActive->QueueCommand(ZEDMD_COMM_COMMAND::ClearScreen); } // "Blank" the frame buffer. - memset(m_pFrameBuffer, 0, ZEDMD_MAX_WIDTH * ZEDMD_MAX_HEIGHT * 3); + if (m_pFrameBuffer) + { + memset(m_pFrameBuffer, 0, ZEDMD_MAX_WIDTH * ZEDMD_MAX_HEIGHT * 3); + } } void ZeDMD::EnableTrueRgb888(bool enable) { m_rgb888 = enable; } void ZeDMD::RenderRgb888(uint8_t* pFrame) { - if (m_verbose) m_pZeDMDComm->Log("ZeDMD::RenderRgb888"); + ZeDMDComm* pActive = GetActiveZeDMD(); + if (m_verbose && pActive) pActive->Log("ZeDMD::RenderRgb888"); - if (!(m_usb || m_wifi) || !UpdateFrameBuffer888(pFrame)) + if (!pActive || !UpdateFrameBuffer888(pFrame)) { return; } @@ -726,14 +764,7 @@ void ZeDMD::RenderRgb888(uint8_t* pFrame) if (m_rgb888) { - if (m_wifi) - { - m_pZeDMDWiFi->QueueFrame(m_pScaledFrameBuffer, bufferSize, true); - } - else if (m_usb) - { - m_pZeDMDComm->QueueFrame(m_pScaledFrameBuffer, bufferSize, true); - } + pActive->QueueFrame(m_pScaledFrameBuffer, bufferSize, true); } else { @@ -747,52 +778,32 @@ void ZeDMD::RenderRgb888(uint8_t* pFrame) m_pRgb565Buffer[i * 2] = tmp & 0xFF; } - if (m_wifi) - { - m_pZeDMDWiFi->QueueFrame(m_pRgb565Buffer, rgb565Size * 2); - } - else if (m_usb) - { - m_pZeDMDComm->QueueFrame(m_pRgb565Buffer, rgb565Size * 2); - } + pActive->QueueFrame(m_pRgb565Buffer, rgb565Size * 2); } } void ZeDMD::RenderRgb565(uint16_t* pFrame) { - if (m_verbose) m_pZeDMDComm->Log("ZeDMD::RenderRgb565"); + ZeDMDComm* pActive = GetActiveZeDMD(); + if (m_verbose && pActive) pActive->Log("ZeDMD::RenderRgb565"); - if (!(m_usb || m_wifi) || !UpdateFrameBuffer565(pFrame)) + if (!pActive || !UpdateFrameBuffer565(pFrame)) { return; } int size = Scale565(m_pScaledFrameBuffer, pFrame, is_bigendian()); - if (m_wifi) - { - m_pZeDMDWiFi->QueueFrame(m_pScaledFrameBuffer, size); - } - else if (m_usb) - { - m_pZeDMDComm->QueueFrame(m_pScaledFrameBuffer, size); - } + pActive->QueueFrame(m_pScaledFrameBuffer, size); } bool ZeDMD::UpdateFrameBuffer888(uint8_t* pFrame) { - static auto lastExecutionTime = std::chrono::steady_clock::now() - std::chrono::milliseconds(100); - - auto currentTime = std::chrono::steady_clock::now(); - - // 12ms means 83Hz. Drop frames of that and higher frame rates because ZeDMD HD can't handle them. - if (std::chrono::duration_cast(currentTime - lastExecutionTime).count() < 12) + if (!m_pFrameBuffer) { return false; } - lastExecutionTime = currentTime; - if (0 == memcmp(m_pFrameBuffer, pFrame, m_romWidth * m_romHeight * 3)) { return false; @@ -804,6 +815,11 @@ bool ZeDMD::UpdateFrameBuffer888(uint8_t* pFrame) bool ZeDMD::UpdateFrameBuffer565(uint16_t* pFrame) { + if (!m_pFrameBuffer) + { + return false; + } + if (0 == memcmp(m_pFrameBuffer, pFrame, m_romWidth * m_romHeight * 2)) { return false; diff --git a/src/ZeDMD.h b/src/ZeDMD.h index a2a463e..ed55220 100644 --- a/src/ZeDMD.h +++ b/src/ZeDMD.h @@ -37,6 +37,7 @@ typedef void(ZEDMDCALLBACK* ZeDMD_LogCallback)(const char* format, va_list args, class ZeDMDComm; class ZeDMDWiFi; +class ZeDMDSpi; class ZEDMDAPI ZeDMD { @@ -132,6 +133,8 @@ class ZEDMDAPI ZeDMD */ bool OpenDefaultWiFi(); + bool OpenSpi(uint32_t speed, uint8_t framePause, uint16_t width, uint16_t height); + /** @brief Close connection to ZeDMD * * Close connection to ZeDMD. @@ -639,9 +642,15 @@ class ZEDMDAPI ZeDMD uint8_t GetScaleMode(uint16_t frameWidth, uint16_t frameHeight, uint8_t* pXOffset, uint8_t* pYOffset); int Scale888(uint8_t* pScaledFrame, uint8_t* pFrame, uint8_t bytes); int Scale565(uint8_t* pScaledFrame, uint16_t* pFrame, bool bigEndian); + void SetActiveZeDMD(ZeDMDComm* pActive, bool usb, bool wifi, bool spi); + ZeDMDComm* GetActiveZeDMD() const; + ZeDMDWiFi* GetActiveZeDMDWiFi() const; + ZeDMDSpi* GetActiveZeDMDSpi() const; ZeDMDComm* m_pZeDMDComm; + ZeDMDSpi* m_pZeDMDSpi; ZeDMDWiFi* m_pZeDMDWiFi; + ZeDMDComm* m_pActiveZeDMD = nullptr; uint16_t m_romWidth; uint16_t m_romHeight; diff --git a/src/ZeDMDComm.cpp b/src/ZeDMDComm.cpp index 231fcfe..167f5f5 100644 --- a/src/ZeDMDComm.cpp +++ b/src/ZeDMDComm.cpp @@ -87,6 +87,7 @@ void ZeDMDComm::Run() // All frames are sent, move delayed frame into the frames queue. if (m_delayedFrameReady) { + if (m_verbose) Log("libzedmd queuing dealyed command %02X", m_delayedFrame.command); m_frames.push(std::move(m_delayedFrame)); m_delayedFrameReady = false; m_delayedFrameMutex.unlock(); @@ -98,7 +99,7 @@ void ZeDMDComm::Run() m_frameQueueMutex.unlock(); KeepAlive(); - std::this_thread::sleep_for(std::chrono::milliseconds(1)); + std::this_thread::sleep_for(std::chrono::microseconds(10)); continue; } @@ -184,12 +185,23 @@ void ZeDMDComm::QueueCommand(char command, uint8_t value) { QueueCommand(command void ZeDMDComm::QueueCommand(char command) { QueueCommand(command, nullptr, 0); } -void ZeDMDComm::QueueFrame(uint8_t* data, int size) { - QueueFrame(uint8_t* data, int size, false); -} +void ZeDMDComm::QueueFrame(uint8_t* data, int size) { QueueFrame(data, size, false); } void ZeDMDComm::QueueFrame(uint8_t* data, int size, bool rgb888) { + if (!m_zoneStream) + { + ZeDMDFrame frame(rgb888 ? ZEDMD_COMM_COMMAND::RGB888Stream : ZEDMD_COMM_COMMAND::RGB565Stream, data, size); + + if (m_verbose) Log("libzedmd queuing command %02X", frame.command); + + m_frameQueueMutex.lock(); + m_frames.push(std::move(frame)); + m_frameQueueMutex.unlock(); + + return; + } + if (m_fullFrameFlag.load(std::memory_order_relaxed)) { m_fullFrameFlag.store(false, std::memory_order_release); @@ -223,7 +235,7 @@ void ZeDMDComm::QueueFrame(uint8_t* data, int size, bool rgb888) uint8_t idx = 0; uint8_t bitsPerPixel = rgb888 ? 3 : 2; - uint16_t zonesBytesLimit = (m_s3 && !m_cdc) ? ZEDMD_S3_ZONES_BYTE_LIMIT : ZEDMD_ZONES_BYTE_LIMIT; + uint16_t zonesBytesLimit = (rgb888) ? ZEDMD_ZONES_BYTE_LIMIT_RGB888 : ZEDMD_ZONES_BYTE_LIMIT_RGB565; const uint16_t zoneBytes = m_zoneWidth * m_zoneHeight * bitsPerPixel; const uint16_t zoneBytesTotal = zoneBytes + 1; uint8_t* zone = (uint8_t*)malloc(zoneBytes); @@ -866,8 +878,33 @@ void ZeDMDComm::RebootToBootloader(bool reenableKeepAive) bool ZeDMDComm::StreamBytes(ZeDMDFrame* pFrame) { - static uint8_t payload[36864] = {0}; - memset(payload, 0, 36864); + if (!m_zoneStream && !m_compression) + { + // Direct stream without compression and zones. + if (pFrame->command == ZEDMD_COMM_COMMAND::RGB565Stream || pFrame->command == ZEDMD_COMM_COMMAND::RGB888Stream) + { + for (auto it = pFrame->data.rbegin(); it != pFrame->data.rend(); ++it) + { + ZeDMDFrameData frameData = *it; + if (m_verbose) Log("StreamBytes, command %02X, length %d", pFrame->command, frameData.size); + + if (!SendChunks(frameData.data, frameData.size)) + { + Log("StreamBytes failed"); + return false; + } + + m_lastKeepAlive = std::chrono::steady_clock::now(); + } + } + + m_lastKeepAlive = std::chrono::steady_clock::now(); + return true; + } + + // 256*64*3 (RGB888) = 49152 + headers + static uint8_t payload[50176] = {0}; + memset(payload, 0, 50176); memcpy(payload, FRAME_HEADER, FRAME_HEADER_SIZE); uint16_t pos = FRAME_HEADER_SIZE; @@ -879,7 +916,8 @@ bool ZeDMDComm::StreamBytes(ZeDMDFrame* pFrame) { ZeDMDFrameData frameData = *it; - if (pFrame->command != ZEDMD_COMM_COMMAND::RGB565ZonesStream) + if (!m_compression || (pFrame->command != ZEDMD_COMM_COMMAND::RGB565ZonesStream && + pFrame->command != ZEDMD_COMM_COMMAND::RGB888ZonesStream)) { memcpy(&payload[pos], CTRL_CHARS_HEADER, CTRL_CHARS_HEADER_SIZE); pos += CTRL_CHARS_HEADER_SIZE; @@ -926,7 +964,8 @@ bool ZeDMDComm::StreamBytes(ZeDMDFrame* pFrame) } } - if (pFrame->command == ZEDMD_COMM_COMMAND::RGB565ZonesStream || pFrame->command == ZEDMD_COMM_COMMAND::RGB888ZonesStream) + if (pFrame->command == ZEDMD_COMM_COMMAND::RGB565ZonesStream || + pFrame->command == ZEDMD_COMM_COMMAND::RGB888ZonesStream) { memcpy(&payload[pos], CTRL_CHARS_HEADER, CTRL_CHARS_HEADER_SIZE); pos += CTRL_CHARS_HEADER_SIZE; @@ -943,7 +982,7 @@ bool ZeDMDComm::StreamBytes(ZeDMDFrame* pFrame) return true; } -bool ZeDMDComm::SendChunks(uint8_t* pData, uint16_t size) +bool ZeDMDComm::SendChunks(const uint8_t* pData, uint16_t size) { #if !( \ (defined(__APPLE__) && ((defined(TARGET_OS_IOS) && TARGET_OS_IOS) || (defined(TARGET_OS_TV) && TARGET_OS_TV))) || \ @@ -1076,7 +1115,7 @@ void ZeDMDComm::KeepAlive() { auto now = std::chrono::steady_clock::now(); - if (!m_keepAlive) + if (!m_keepAlive || m_keepAliveNotSupported) { m_lastKeepAlive = now; return; @@ -1087,6 +1126,8 @@ void ZeDMDComm::KeepAlive() m_lastKeepAlive = now; try { + if (m_verbose) Log("Keep alive ZeDMD connection"); + SendChunks(s_keepAliveData.get(), s_keepAliveSize); } catch (const std::exception& e) diff --git a/src/ZeDMDComm.h b/src/ZeDMDComm.h index 8fa6b93..b9d0d1f 100644 --- a/src/ZeDMDComm.h +++ b/src/ZeDMDComm.h @@ -41,8 +41,8 @@ #define ZEDMD_COMM_FRAME_QUEUE_SIZE_MAX 8 -#define ZEDMD_ZONES_BYTE_LIMIT (128 * 4 * 2 + 16) -#define ZEDMD_S3_ZONES_BYTE_LIMIT (ZEDMD_ZONES_BYTE_LIMIT) +#define ZEDMD_ZONES_BYTE_LIMIT_RGB565 (128 * 4 * 2 + 16) +#define ZEDMD_ZONES_BYTE_LIMIT_RGB888 (128 * 4 * 3 + 16) typedef enum { @@ -94,6 +94,8 @@ typedef enum RGB888ZonesStream = 0x04, RGB565ZonesStream = 0x05, RenderFrame = 0x06, + RGB888Stream = 0x07, + RGB565Stream = 0x08, ClearScreen = 0x0a, @@ -232,7 +234,7 @@ class ZeDMDComm void Flush(bool reenableKeepAive = true); void QueueFrame(uint8_t* buffer, int size); void QueueFrame(uint8_t* buffer, int size, bool rgb888); - void QueueCommand(char command, uint8_t* buffer, int size); + virtual void QueueCommand(char command, uint8_t* buffer, int size); void QueueCommand(char command); void QueueCommand(char command, uint8_t value); bool FillDelayed(); @@ -263,7 +265,7 @@ class ZeDMDComm void Log(const char* format, ...); protected: - virtual bool SendChunks(uint8_t* pData, uint16_t size); + virtual bool SendChunks(const uint8_t* pData, uint16_t size); virtual void Reset(); void ClearFrames(); bool IsQueueEmpty(); @@ -292,9 +294,14 @@ class ZeDMDComm uint8_t m_panelMinRefreshRate = 30; uint8_t m_udpDelay = 5; uint16_t m_writeAtOnce = ZEDMD_COMM_DEFAULT_SERIAL_WRITE_AT_ONCE; + const uint8_t m_allBlack[32768] = {0}; uint8_t m_currentCommand = 0; + bool m_compression = true; + bool m_zoneStream = true; + bool m_keepAliveNotSupported = false; + ZeDMD_DeviceType m_deviceType = ZeDMD_DeviceType::ESP32; private: @@ -306,7 +313,6 @@ class ZeDMDComm ZeDMD_LogCallback m_logCallback = nullptr; const void* m_logUserData = nullptr; uint64_t m_zoneHashes[128] = {0}; - const uint8_t m_allBlack[32768] = {0}; char m_ignoredDevices[10][32] = {0}; uint8_t m_ignoredDevicesCounter = 0; diff --git a/src/ZeDMDSpi.cpp b/src/ZeDMDSpi.cpp new file mode 100644 index 0000000..22bc2c3 --- /dev/null +++ b/src/ZeDMDSpi.cpp @@ -0,0 +1,278 @@ +#include "ZeDMDSpi.h" + +#if defined(SPI_SUPPORT) +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace +{ +constexpr uint8_t kSpiBitsPerWord = 8; +constexpr uint8_t kSpiMode = SPI_MODE_0; +constexpr unsigned int kCsGpio = 25; // GPIO25 on Raspberry Pi +constexpr const char kGpioConsumer[] = "ZeDMDSpi"; +constexpr const char kSpiBufSizePath[] = "/sys/module/spidev/parameters/bufsiz"; + +uint32_t GetSpiKernelBufSize() +{ + static uint32_t cachedBufSize = 0; + if (cachedBufSize != 0) + { + return cachedBufSize; + } + + std::ifstream bufFile(kSpiBufSizePath); + uint32_t parsed = 0; + if (bufFile.is_open() && (bufFile >> parsed) && parsed > 0) + { + cachedBufSize = parsed; + return cachedBufSize; + } + + cachedBufSize = 4096; // fallback if sysfs entry is unavailable + return cachedBufSize; +} +} // namespace + +bool ZeDMDSpi::IsSupportedPlatform() const +{ + std::ifstream model("/proc/device-tree/model"); + if (!model.is_open()) + { + return false; + } + std::string line; + std::getline(model, line); + return line.find("Raspberry Pi") != std::string::npos; +} + +bool ZeDMDSpi::Connect() +{ + if (!IsSupportedPlatform()) + { + Log("ZeDMDSpi: unsupported platform. This transport only runs on Raspberry Pi with Linux."); + return false; + } + + if (m_connected) + { + return true; + } + + m_fileDescriptor = open(SPI_DEVICE, O_RDWR | O_CLOEXEC); + if (m_fileDescriptor < 0) + { + Log("ZeDMDSpi: couldn't open SPI device %s: %s", SPI_DEVICE, strerror(errno)); + return false; + } + Log("ZeDMDSpi: opened SPI device %s", SPI_DEVICE); + + if (ioctl(m_fileDescriptor, SPI_IOC_WR_MODE, &kSpiMode) < 0) + { + Log("ZeDMDSpi: couldn't set SPI mode: %s", strerror(errno)); + Disconnect(); + return false; + } + // Disable kernel chip-select handling; we drive CS manually on GPIO25. +#if defined(SPI_NO_CS) && defined(SPI_IOC_WR_MODE32) + uint32_t mode32 = kSpiMode | SPI_NO_CS; + if (ioctl(m_fileDescriptor, SPI_IOC_WR_MODE32, &mode32) < 0) + { + Log("ZeDMDSpi: warning - couldn't set SPI_NO_CS: %s", strerror(errno)); + } +#endif + + uint8_t bitsPerWord = kSpiBitsPerWord; + if (ioctl(m_fileDescriptor, SPI_IOC_WR_BITS_PER_WORD, &bitsPerWord) < 0) + { + Log("ZeDMDSpi: couldn't set SPI bits/word: %s", strerror(errno)); + Disconnect(); + return false; + } + + if (ioctl(m_fileDescriptor, SPI_IOC_WR_MAX_SPEED_HZ, &m_speed) < 0) + { + Log("ZeDMDSpi: couldn't set SPI speed: %s", strerror(errno)); + Disconnect(); + return false; + } + Log("ZeDMDSpi: set SPI speed %d", m_speed); + + m_gpioChip = gpiod_chip_open(GPIO_CHIP); + if (!m_gpioChip) + { + Log("ZeDMDSpi: couldn't open gpio chip %s: %s", GPIO_CHIP, strerror(errno)); + Disconnect(); + return false; + } + + m_csLine = gpiod_chip_get_line(m_gpioChip, kCsGpio); + if (!m_csLine) + { + Log("ZeDMDSpi: couldn't get CS gpio %d: %s", kCsGpio, strerror(errno)); + Disconnect(); + return false; + } + + if (gpiod_line_request_output(m_csLine, kGpioConsumer, 1) < 0) + { + Log("ZeDMDSpi: couldn't request CS gpio %d as output: %s", kCsGpio, strerror(errno)); + Disconnect(); + return false; + } + + // Create a rising edge to switch ZeDMD from loopback to SPI mode. + // Keep CS high when idle. + gpiod_line_set_value(m_csLine, 0); + std::this_thread::sleep_for(std::chrono::microseconds(100)); + gpiod_line_set_value(m_csLine, 1); + + Log("ZeDMDSpi: signaling via GPIO %d established", kCsGpio); + + m_connected = true; + return true; +} + +void ZeDMDSpi::Disconnect() +{ + if (m_csLine) + { + gpiod_line_set_value(m_csLine, 1); + gpiod_line_release(m_csLine); + m_csLine = nullptr; + } + if (m_gpioChip) + { + gpiod_chip_close(m_gpioChip); + m_gpioChip = nullptr; + } + if (m_fileDescriptor >= 0) + { + close(m_fileDescriptor); + m_fileDescriptor = -1; + } + m_connected = false; +} + +bool ZeDMDSpi::IsConnected() { return m_connected; } + +void ZeDMDSpi::Reset() {} + +void ZeDMDSpi::QueueCommand(char command, uint8_t* buffer, int size) +{ + switch (command) + { + case ZEDMD_COMM_COMMAND::ClearScreen: + SendChunks(m_allBlack, GetWidth() * GetHeight() * 2); // RGB565 + break; + + default: + // ZeDMDComm::QueueCommand(command, buffer, size); + // Drop commands other than ClearScreen for SPI transport for now. + break; + } +} + +bool ZeDMDSpi::SendChunks(const uint8_t* pData, uint16_t size) +{ + if (!m_connected || m_fileDescriptor < 0) + { + Log("ZeDMDSpi: device not connected"); + return false; + } + + uint32_t remaining = size; + uint8_t* cursor = (uint8_t*)pData; + + if (m_csLine && gpiod_line_set_value(m_csLine, 0) < 0) + { + Log("ZeDMDSpi: failed to pull CS low: %s", strerror(errno)); + return false; + } + + std::this_thread::sleep_for(std::chrono::microseconds(10)); + const uint32_t spi_kernel_bufsize = GetSpiKernelBufSize(); + + while (remaining > 0) + { + uint32_t chunkSize = std::min(remaining, spi_kernel_bufsize); + struct spi_ioc_transfer transfer; + memset(&transfer, 0, sizeof(transfer)); + transfer.tx_buf = reinterpret_cast<__u64>(cursor); + transfer.len = chunkSize; + transfer.speed_hz = m_speed; + transfer.bits_per_word = kSpiBitsPerWord; + + int res = ioctl(m_fileDescriptor, SPI_IOC_MESSAGE(1), &transfer); + if (res < 0) + { + Log("ZeDMDSpi: SPI write failed: %s", strerror(errno)); + if (m_csLine) gpiod_line_set_value(m_csLine, 1); + std::this_thread::sleep_for(std::chrono::microseconds(100)); + return false; + } + const uint32_t bytesTransferred = static_cast(res); + if (bytesTransferred != chunkSize) + { + Log("ZeDMDSpi: partial SPI write (%u/%u bytes)", bytesTransferred, chunkSize); + if (bytesTransferred == 0) + { + if (m_csLine) gpiod_line_set_value(m_csLine, 1); + std::this_thread::sleep_for(std::chrono::microseconds(100)); + return false; + } + } + + cursor += bytesTransferred; + remaining -= bytesTransferred; + + if (m_verbose) Log("SendChunks, transferred %d, remaining %d", bytesTransferred, remaining); + } + + if (m_csLine && gpiod_line_set_value(m_csLine, 1) < 0) + { + Log("ZeDMDSpi: failed to release CS: %s", strerror(errno)); + return false; + } + + if (m_framePause > 0) + { + std::this_thread::sleep_for(std::chrono::milliseconds(m_framePause)); + } + else + { + std::this_thread::sleep_for(std::chrono::microseconds(100)); + } + + return true; +} + +#else // non-Linux or non-aarch64 stub + +bool ZeDMDSpi::IsSupportedPlatform() const { return false; } + +bool ZeDMDSpi::Connect() +{ + Log("ZeDMDSpi: unsupported platform. SPI only runs on Raspberry Pi with Linux."); + return false; +} + +void ZeDMDSpi::Disconnect() {} + +bool ZeDMDSpi::IsConnected() { return false; } + +void ZeDMDSpi::Reset() {} + +void ZeDMDSpi::QueueCommand(char command, uint8_t* buffer, int size) { ZeDMDComm::QueueCommand(command, buffer, size); } + +bool ZeDMDSpi::SendChunks(const uint8_t*, uint16_t) { return false; } + +#endif // __linux__ diff --git a/src/ZeDMDSpi.h b/src/ZeDMDSpi.h new file mode 100644 index 0000000..3781f1b --- /dev/null +++ b/src/ZeDMDSpi.h @@ -0,0 +1,60 @@ +#pragma once + +#if defined(__linux__) && defined(__aarch64__) && !defined(__ANDROID__) +#define SPI_SUPPORT 1 +#endif + +#include + +#include "ZeDMDComm.h" + +#if defined(SPI_SUPPORT) +#include +#include + +#define GPIO_CHIP "/dev/gpiochip0" +#define SPI_DEVICE "/dev/spidev1.0" +#else +// Forward declarations so non-Linux builds can compile the stub implementation. +struct gpiod_chip; +struct gpiod_line; +#endif + +class ZeDMDSpi : public ZeDMDComm +{ + public: + ZeDMDSpi() : ZeDMDComm() + { + m_compression = false; + m_zoneStream = false; + m_keepAliveNotSupported = true; + } + ~ZeDMDSpi() { Disconnect(); } + + bool Connect() override; + void Disconnect() override; + bool IsConnected() override; + using ZeDMDComm::QueueCommand; + void QueueCommand(char command, uint8_t* buffer, int size) override; + + void SetSpeed(uint32_t speed) { m_speed = speed; } + void SetFramePause(uint8_t pause) { m_framePause = pause; } + void SetWidth(uint16_t width) { m_width = width; } + void SetHeight(uint16_t height) { m_height = height; } + + protected: + bool SendChunks(const uint8_t* pData, uint16_t size) override; + void Reset() override; + + private: + bool IsSupportedPlatform() const; + + uint32_t m_speed = 72000000; // 72MHz + uint8_t m_framePause = 2; // 2ms + int m_fileDescriptor = -1; +#if defined(SPI_SUPPORT) + gpiod_chip* m_gpioChip = nullptr; + gpiod_line* m_csLine = nullptr; +#endif + bool m_connected = false; +}; diff --git a/src/ZeDMDWiFi.cpp b/src/ZeDMDWiFi.cpp index a1efa7d..3cee0b5 100644 --- a/src/ZeDMDWiFi.cpp +++ b/src/ZeDMDWiFi.cpp @@ -272,7 +272,7 @@ bool ZeDMDWiFi::DoConnect(const char* ip) } else { - Log("ZeDMD UDP delay could not be detected, falling back to 5ms deafult"); + Log("ZeDMD UDP delay could not be detected, falling back to 5ms default"); } } @@ -617,7 +617,7 @@ bool ZeDMDWiFi::IsConnected() { return m_connected; } void ZeDMDWiFi::Reset() {} -bool ZeDMDWiFi::SendChunks(uint8_t* pData, uint16_t size) +bool ZeDMDWiFi::SendChunks(const uint8_t* pData, uint16_t size) { if (m_tcp) { diff --git a/src/ZeDMDWiFi.h b/src/ZeDMDWiFi.h index c9e3cbc..64de3cb 100644 --- a/src/ZeDMDWiFi.h +++ b/src/ZeDMDWiFi.h @@ -34,7 +34,7 @@ class ZeDMDWiFi : public ZeDMDComm protected: bool DoConnect(const char* ip); - virtual bool SendChunks(uint8_t* pData, uint16_t size); + virtual bool SendChunks(const uint8_t* pData, uint16_t size); virtual void Reset(); bool openTcpConnection(); bool SendGetRequest(const std::string& path);