// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/blob/master/LICENSE.md #include "EinkDisplay.hpp" #include #include #include #include #include #include #include namespace hal::eink { namespace { constexpr auto defaultSurroundingTemperature = -1000; constexpr auto LUTDSize = 16385; constexpr auto LUTCSize = 64; constexpr auto LUTRSize = 256; ///< Needed due to \ref LutsFileName structure constexpr auto LUTSTotalSize = LUTDSize + LUTCSize + LUTRSize; constexpr auto LUTTemperatureMinimal = 0; constexpr auto LUTTemperatureSubcritical = 38; constexpr auto LUTTemperatureCritical = 43; constexpr auto LUTTemperatureOffsetInterval = 3; constexpr auto LUTTemperatureOffsetSubcritical = 12; constexpr auto LUTTemperatureOffsetCritical = 13; constexpr auto einkDisplayMaxInitRetries = 10U; const auto lutsFilePath = purefs::dir::getAssetsDirPath() / "luts.bin"; constexpr auto createDefaultWaveformSettings(bsp::eink::EinkWaveform waveformMode) -> bsp::eink::EinkWaveformSettings { bsp::eink::EinkWaveformSettings settings{}; settings.mode = waveformMode; settings.temperature = defaultSurroundingTemperature; settings.useCounter = 0; settings.LUTCData = nullptr; settings.LUTCSize = 0; settings.LUTDData = nullptr; settings.LUTDSize = 0; return settings; } constexpr auto toWaveformTemperatureOffset(std::int32_t temperature) noexcept -> unsigned { if (temperature >= LUTTemperatureCritical) { return LUTTemperatureOffsetCritical; } if (temperature >= LUTTemperatureSubcritical) { return LUTTemperatureOffsetSubcritical; } if (temperature < LUTTemperatureMinimal) { temperature = LUTTemperatureMinimal; } return temperature / LUTTemperatureOffsetInterval; } constexpr auto toWaveformOffset(unsigned short LUTbank, unsigned temperatureOffset) noexcept -> unsigned { constexpr auto singleLUTOffset = (LUTTemperatureOffsetCritical + 1); return LUTSTotalSize * (singleLUTOffset * LUTbank + temperatureOffset); } constexpr auto toWaveformOffset(bsp::eink::EinkWaveform mode, unsigned temperatureOffset) -> unsigned { switch (mode) { case bsp::eink::EinkWaveform::INIT: return toWaveformOffset(0, temperatureOffset); case bsp::eink::EinkWaveform::A2: return toWaveformOffset(1, temperatureOffset); case bsp::eink::EinkWaveform::DU2: return toWaveformOffset(2, temperatureOffset); case bsp::eink::EinkWaveform::GLD16: return toWaveformOffset(3, temperatureOffset); case bsp::eink::EinkWaveform::GC16: return toWaveformOffset(4, temperatureOffset); default: throw std::invalid_argument{"Invalid waveform mode."}; } } constexpr auto translateDisplayColorMode(EinkDisplayColorMode mode) -> bsp::eink::EinkDisplayColorMode { return mode == EinkDisplayColorMode::EinkDisplayColorModeStandard ? bsp::eink::EinkDisplayColorMode::Standard : bsp::eink::EinkDisplayColorMode::Inverted; } constexpr auto translateStatus(bsp::eink::EinkStatus status) -> EinkStatus { switch (status) { case bsp::eink::EinkStatus::OK: return EinkStatus::EinkOK; case bsp::eink::EinkStatus::Error: return EinkStatus::EinkError; case bsp::eink::EinkStatus::SPIErr: return EinkStatus::EinkSPIErr; case bsp::eink::EinkStatus::SPINotInitializedErr: return EinkStatus::EinkSPINotInitializedErr; case bsp::eink::EinkStatus::DMAErr: return EinkStatus::EinkDMAErr; case bsp::eink::EinkStatus::InitErr: return EinkStatus::EinkInitErr; case bsp::eink::EinkStatus::Timeout: return EinkStatus::EinkTimeout; case bsp::eink::EinkStatus::NoMem: return EinkStatus::EinkNoMem; case bsp::eink::EinkStatus::WaveformsFileOpenFail: return EinkStatus::EinkWaveformsFileOpenFail; default: return EinkStatus::EinkUnknown; } } } // namespace EinkDisplay::EinkDisplay(FrameSize size) : size{size}, currentWaveform{createDefaultWaveformSettings(bsp::eink::EinkWaveform::GC16)}, displayMode{EinkDisplayColorMode::EinkDisplayColorModeStandard} { #if defined(TARGET_RT1051) driverLPSPI = drivers::DriverLPSPI::Create( "EInk", static_cast(BoardDefinitions::EINK_LPSPI_INSTANCE)); #endif } EinkDisplay::~EinkDisplay() noexcept { delete[] currentWaveform.LUTCData; delete[] currentWaveform.LUTDData; } auto EinkDisplay::prepareDisplay(EinkRefreshMode refreshMode, WaveformTemperature behaviour) -> EinkStatus { if (const auto status = reinitAndPowerOn(); status != EinkStatus::EinkOK) { return status; } const auto temperature = (behaviour == WaveformTemperature::KeepCurrent) ? getLastTemperature() : bsp::eink::getTemperatureInternal(); if (refreshMode == EinkRefreshMode::REFRESH_DEEP) { auto status = setWaveform(bsp::eink::EinkWaveform::GC16, temperature); if (status == EinkStatus::EinkOK) { if (const auto ditherStatus = dither(); ditherStatus != EinkStatus::EinkOK) { return ditherStatus; } } return status; } return setWaveform(bsp::eink::EinkWaveform::DU2, temperature); } auto EinkDisplay::updateDisplay(EinkFrame frame, const std::uint8_t *frameBuffer) -> EinkStatus { return translateStatus( updateFrame(bsp::eink::EinkFrame{frame.pos_x, frame.pos_y, frame.size.width, frame.size.height}, frameBuffer, getCurrentBitsPerPixelFormat(), translateDisplayColorMode(displayMode))); } auto EinkDisplay::refreshDisplay(EinkFrame frame) -> EinkStatus { currentWaveform.useCounter++; return translateStatus(bsp::eink::refreshImage( bsp::eink::EinkFrame{frame.pos_x, frame.pos_y, frame.size.width, frame.size.height})); } auto EinkDisplay::showImageUpdate(const std::vector &updateFrames, const std::uint8_t *frameBuffer) -> EinkStatus { if (const auto status = reinitAndPowerOn(); status != EinkStatus::EinkOK) { return status; } for (const EinkFrame &frame : updateFrames) { const std::uint8_t *buffer = frameBuffer + frame.pos_y * frame.size.width; if (const auto status = updateDisplay(frame, buffer); status != EinkStatus::EinkOK) { return status; } } return EinkStatus::EinkOK; } auto EinkDisplay::showImageRefresh(const EinkFrame &refreshFrame, const EinkRefreshMode refreshMode) -> EinkStatus { if (const auto status = prepareDisplay(refreshMode, WaveformTemperature::KeepCurrent); status != EinkStatus::EinkOK) { return status; } if (const auto status = refreshDisplay(refreshFrame); status != EinkStatus::EinkOK) { return status; } return EinkStatus::EinkOK; } auto EinkDisplay::showImage(const std::vector &updateFrames, const EinkFrame &refreshFrame, const std::uint8_t *frameBuffer, const EinkRefreshMode refreshMode) -> EinkStatus { if (const auto status = prepareDisplay(refreshMode, WaveformTemperature::KeepCurrent); status != EinkStatus::EinkOK) { return status; } for (const EinkFrame &frame : updateFrames) { const std::uint8_t *buffer = frameBuffer + frame.pos_y * frame.size.width; if (const auto status = updateDisplay(frame, buffer); status != EinkStatus::EinkOK) { return status; } } if (const auto status = refreshDisplay(refreshFrame); status != EinkStatus::EinkOK) { return status; } return EinkStatus::EinkOK; } auto EinkDisplay::prepareEarlyRequest(const EinkRefreshMode refreshMode, const WaveformTemperature behaviour) -> void { prepareDisplay(refreshMode, behaviour); } auto EinkDisplay::resetAndInit() -> EinkStatus { return translateStatus(bsp::eink::resetAndInitialize()); } auto EinkDisplay::dither() -> EinkStatus { return translateStatus(bsp::eink::ditherDisplay()); } auto EinkDisplay::powerOn() -> EinkStatus { if (driverLPSPI) { driverLPSPI->Enable(); } return translateStatus(bsp::eink::powerOn()); } auto EinkDisplay::powerOff() -> EinkStatus { const auto status = translateStatus(bsp::eink::powerOff()); if (driverLPSPI) { driverLPSPI->Disable(); } return status; } auto EinkDisplay::shutdown() -> EinkStatus { const auto status = translateStatus(bsp::eink::powerDown()); if (driverLPSPI) { driverLPSPI->Disable(); } return status; } auto EinkDisplay::wipeOut() -> EinkStatus { if (const auto status = prepareDisplay(EinkRefreshMode::REFRESH_DEEP, WaveformTemperature::KeepCurrent); status != EinkStatus::EinkOK) { return status; } return translateStatus(bsp::eink::fillScreenWithColor(bsp::eink::EinkDisplayColorFilling::White)); } auto EinkDisplay::getCurrentBitsPerPixelFormat() const noexcept -> bsp::eink::EinkBpp { if ((currentWaveform.mode == bsp::eink::EinkWaveform::A2) || (currentWaveform.mode == bsp::eink::EinkWaveform::DU2)) { /* Should be 1Bpp, but the OS is not ready for this and probably never will (in 1Bpp halftones disappear) */ return bsp::eink::EinkBpp::Eink4Bpp; } return bsp::eink::EinkBpp::Eink4Bpp; } auto EinkDisplay::isNewWaveformNeeded(bsp::eink::EinkWaveform newMode, std::int32_t newTemperature) const -> bool { constexpr auto lenientTemperatureUseCounter = 50; // Arbitrary, not documented const auto allowLenientTemperature = currentWaveform.useCounter < lenientTemperatureUseCounter; // At least: modes cannot differ if (allowLenientTemperature && newMode == currentWaveform.mode) { bool temperatureFine = false; switch (currentWaveform.mode) { case bsp::eink::EinkWaveform::A2: case bsp::eink::EinkWaveform::DU2: temperatureFine = abs(newTemperature - currentWaveform.temperature) <= 3; break; case bsp::eink::EinkWaveform::INIT: case bsp::eink::EinkWaveform::GLD16: case bsp::eink::EinkWaveform::GC16: temperatureFine = abs(newTemperature - currentWaveform.temperature) <= 2; break; } if (temperatureFine) { return false; } } return true; } auto EinkDisplay::setWaveform(bsp::eink::EinkWaveform mode, std::int32_t temperature) -> EinkStatus { if (!isNewWaveformNeeded(mode, temperature)) { updateWaveform(¤tWaveform); return EinkStatus::EinkOK; } const auto currentOffset = toWaveformOffset(currentWaveform.mode, toWaveformTemperatureOffset(currentWaveform.temperature)); // Assume it's changed currentWaveform.useCounter = 0; currentWaveform.temperature = temperature; currentWaveform.mode = mode; auto offset = static_cast(toWaveformOffset(mode, toWaveformTemperatureOffset(temperature))); if (offset == currentOffset) { // Current waveform is still the best fit return EinkStatus::EinkOK; } auto file = std::fopen(lutsFilePath.c_str(), "rb"); if (file == nullptr) { LOG_FATAL("Could not find LUTS file in '%s'! Returning...", lutsFilePath.c_str()); return EinkStatus::EinkWaveformsFileOpenFail; } auto fileHandlerCleanup = gsl::finally([&file]() { std::fclose(file); }); resetWaveformSettings(); std::fseek(file, offset, SEEK_SET); std::fread(¤tWaveform.LUTDData[1], 1, LUTDSize, file); // 0x00 - 1 frame, ... , 0x0F - 16 frames const std::uint8_t waveformFrameCount = currentWaveform.LUTDData[1] + 1; // (frameCount * 64) - size of actual LUT; (+1) - the byte containing frameCount; (+1) - EinkLUTD command currentWaveform.LUTDSize = (waveformFrameCount * 64) + 1 + 1; offset += LUTDSize; std::fseek(file, offset, SEEK_SET); std::fread(¤tWaveform.LUTCData[1], 1, LUTCSize, file); updateWaveform(¤tWaveform); return EinkStatus::EinkOK; } auto EinkDisplay::resetWaveformSettings() -> void { delete[] currentWaveform.LUTDData; currentWaveform.LUTDSize = 0; currentWaveform.LUTDData = new std::uint8_t[LUTDSize + 1]; currentWaveform.LUTDData[0] = EinkLUTD; delete[] currentWaveform.LUTCData; currentWaveform.LUTCSize = LUTCSize; currentWaveform.LUTCData = new std::uint8_t[LUTCSize + 1]; currentWaveform.LUTCData[0] = EinkLUTC; } auto EinkDisplay::setMode(EinkDisplayColorMode mode) noexcept -> void { displayMode = mode; } auto EinkDisplay::getMode() const noexcept -> EinkDisplayColorMode { return displayMode; } auto EinkDisplay::getLastTemperature() const noexcept -> std::int32_t { return currentWaveform.temperature; } auto EinkDisplay::getDevice() const noexcept -> std::shared_ptr { return driverLPSPI; } auto AbstractEinkDisplay::Factory::create(FrameSize size) -> std::unique_ptr { return std::make_unique(size); } auto EinkDisplay::reinitAndPowerOn() -> EinkStatus { auto errorCounter = 0U; auto status = EinkStatus::EinkUnknown; if (bsp::eink::isPoweredOn()) { return EinkStatus::EinkOK; } while (status != EinkStatus::EinkOK && errorCounter++ < einkDisplayMaxInitRetries) { status = tryReinitAndPowerOn(); if (status != EinkStatus::EinkOK) { if (errorCounter < einkDisplayMaxInitRetries) { LOG_WARN("Failed to initialize and power on Eink, trying once more..."); } else { LOG_ERROR("Permanently failed to initialize and power on Eink"); } } } return status; } auto EinkDisplay::tryReinitAndPowerOn() -> EinkStatus { if (const auto status = resetAndInit(); status != EinkStatus::EinkOK) { LOG_WARN("Eink initialization failed: %s", magic_enum::enum_name(status).data()); return status; } if (const auto status = powerOn(); status != EinkStatus::EinkOK) { LOG_WARN("Eink power on failed: %s", magic_enum::enum_name(status).data()); return status; } return EinkStatus::EinkOK; } } // namespace hal::eink