#include #include #include #include #define OBSW_RTD_AUTO_MODE 1 #if OBSW_RTD_AUTO_MODE == 1 static constexpr uint8_t BASE_CFG = (MAX31865::Bias::ON << MAX31865::CfgBitPos::BIAS_SEL) | (MAX31865::Wires::FOUR_WIRE << MAX31865::CfgBitPos::WIRE_SEL) | (MAX31865::ConvMode::AUTO << MAX31865::CfgBitPos::CONV_MODE); #else static constexpr uint8_t BASE_CFG = (MAX31865::Bias::OFF << MAX31865::CfgBitPos::BIAS_SEL) | (MAX31865::Wires::FOUR_WIRE << MAX31865::CfgBitPos::WIRE_SEL) | (MAX31865::ConvMode::NORM_OFF << MAX31865::CfgBitPos::CONV_MODE); #endif Max31865RtdPolling::Max31865RtdPolling(object_id_t objectId, SpiComIF* lowLevelComIF, GpioIF* gpioIF) : SystemObject(objectId), rtds(EiveMax31855::NUM_RTDS), comIF(lowLevelComIF), gpioIF(gpioIF) { readerLock = MutexFactory::instance()->createMutex(); } ReturnValue_t Max31865RtdPolling::performOperation(uint8_t operationCode) { using namespace MAX31865; ReturnValue_t result = returnvalue::OK; static_cast(result); // Stopwatch watch; if (periodicInitHandling()) { #if OBSW_RTD_AUTO_MODE == 0 // 10 ms delay for VBIAS startup TaskFactory::delayTask(10); #endif } else { // No devices usable (e.g. TCS board off) return returnvalue::OK; } #if OBSW_RTD_AUTO_MODE == 0 result = periodicReadReqHandling(); if (result != returnvalue::OK) { return result; } // After requesting, 65 milliseconds delay required TaskFactory::delayTask(65); #endif return periodicReadHandling(); } bool Max31865RtdPolling::rtdIsActive(uint8_t idx) { if (rtds[idx]->on and rtds[idx]->db.active and rtds[idx]->db.configured) { return true; } return false; } bool Max31865RtdPolling::periodicInitHandling() { using namespace MAX31865; ReturnValue_t result = returnvalue::OK; for (auto& rtd : rtds) { if (rtd == nullptr) { continue; } MutexGuard mg(readerLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); if (mg.getLockResult() != returnvalue::OK) { sif::warning << "Max31865RtdReader::periodicInitHandling: Mutex lock failed" << std::endl; return false; } if ((rtd->on or rtd->db.active) and not rtd->db.configured and rtd->cd.hasTimedOut()) { // Please note that using the manual CS lock wrapper here is problematic. Might be a SPI // or hardware specific issue where the CS needs to be pulled high and then low again // between transfers result = writeCfgReg(rtd->spiCookie, BASE_CFG); if (result != returnvalue::OK) { handleSpiError(rtd, result, "writeCfgReg"); continue; } if (rtd->writeLowThreshold) { result = writeLowThreshold(rtd->spiCookie, rtd->lowThreshold); if (result != returnvalue::OK) { handleSpiError(rtd, result, "writeLowThreshold"); } } if (rtd->writeHighThreshold) { result = writeHighThreshold(rtd->spiCookie, rtd->highThreshold); if (result != returnvalue::OK) { handleSpiError(rtd, result, "writeHighThreshold"); } } result = clearFaultStatus(rtd->spiCookie); if (result != returnvalue::OK) { handleSpiError(rtd, result, "clearFaultStatus"); } rtd->db.configured = true; rtd->db.active = true; } } bool someRtdUsable = false; for (auto& rtd : rtds) { if (rtd == nullptr) { continue; } if (rtdIsActive(rtd->idx)) { #if OBSW_RTD_AUTO_MODE == 0 result = writeBiasSel(Bias::ON, rtd->spiCookie, BASE_CFG); #endif someRtdUsable = true; } } return someRtdUsable; } ReturnValue_t Max31865RtdPolling::periodicReadReqHandling() { using namespace MAX31865; // Now request one shot config for all active RTDs for (auto& rtd : rtds) { if (rtd == nullptr) { continue; } MutexGuard mg(readerLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); if (mg.getLockResult() != returnvalue::OK) { sif::warning << "Max31865RtdReader::periodicReadReqHandling: Mutex lock failed" << std::endl; return returnvalue::FAILED; } if (rtdIsActive(rtd->idx)) { ReturnValue_t result = writeCfgReg(rtd->spiCookie, BASE_CFG | (1 << CfgBitPos::ONE_SHOT)); if (result != returnvalue::OK) { handleSpiError(rtd, result, "writeCfgReg"); // Release mutex ASAP return returnvalue::FAILED; } } } return returnvalue::OK; } ReturnValue_t Max31865RtdPolling::periodicReadHandling() { using namespace MAX31865; auto result = returnvalue::OK; // Now read the RTD values for (auto& rtd : rtds) { if (rtd == nullptr) { continue; } MutexGuard mg(readerLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); if (mg.getLockResult() != returnvalue::OK) { sif::warning << "Max31865RtdReader::periodicReadHandling: Mutex lock failed" << std::endl; return returnvalue::FAILED; } if (rtdIsActive(rtd->idx)) { // Please note that using the manual CS lock wrapper here is problematic. Might be a SPI // or hardware specific issue where the CS needs to be pulled high and then low again // between transfers uint16_t rtdVal = 0; bool faultBitSet = false; result = writeCfgReg(rtd->spiCookie, BASE_CFG); if (result != returnvalue::OK) { handleSpiError(rtd, result, "writeCfgReg"); continue; } result = readRtdVal(rtd->spiCookie, rtdVal, faultBitSet); // sif::debug << "RTD Val: " << rtdVal << std::endl; if (result != returnvalue::OK) { handleSpiError(rtd, result, "readRtdVal"); continue; } if (faultBitSet) { rtd->db.faultBitSet = faultBitSet; } rtd->db.adcCode = rtdVal; } } #if OBSW_RTD_AUTO_MODE == 0 for (auto& rtd : rtds) { if (rtd == nullptr) { continue; } // Even if a device was made inactive, turn off the bias here. If it was turned off, not // necessary anymore.. if (rtd->on) { result = writeBiasSel(Bias::OFF, rtd->spiCookie, BASE_CFG); } } #endif return returnvalue::OK; } ReturnValue_t Max31865RtdPolling::initializeInterface(CookieIF* cookie) { if (cookie == nullptr) { throw std::invalid_argument("Invalid MAX31865 Reader Cookie"); } auto* rtdCookie = dynamic_cast(cookie); ReturnValue_t result = comIF->initializeInterface(rtdCookie->spiCookie); if (result != returnvalue::OK) { return result; } if (rtdCookie->idx > EiveMax31855::NUM_RTDS) { throw std::invalid_argument("Invalid RTD index"); } rtds[rtdCookie->idx] = rtdCookie; MutexGuard mg(readerLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); if (dbLen == 0) { dbLen = rtdCookie->db.getSerializedSize(); } return returnvalue::OK; } ReturnValue_t Max31865RtdPolling::sendMessage(CookieIF* cookie, const uint8_t* sendData, size_t sendLen) { if (cookie == nullptr) { return returnvalue::FAILED; } // Empty command.. don't fail for now if (sendLen < 1) { return returnvalue::OK; } MutexGuard mg(readerLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); if (mg.getLockResult() != returnvalue::OK) { sif::warning << "Max31865RtdReader::sendMessage: Mutex lock failed" << std::endl; return returnvalue::FAILED; } auto* rtdCookie = dynamic_cast(cookie); uint8_t cmdRaw = sendData[0]; if (cmdRaw > EiveMax31855::RtdCommands::NUM_CMDS) { sif::warning << "Max31865RtdReader::sendMessage: Invalid command" << std::endl; return returnvalue::FAILED; } auto thresholdHandler = [&]() { rtdCookie->lowThreshold = (sendData[1] << 8) | sendData[2]; rtdCookie->highThreshold = (sendData[3] << 8) | sendData[4]; rtdCookie->writeLowThreshold = true; rtdCookie->writeHighThreshold = true; }; auto cmd = static_cast(sendData[0]); switch (cmd) { case (EiveMax31855::RtdCommands::ON): { if (not rtdCookie->on) { rtdCookie->cd.setTimeout(MAX31865::WARMUP_MS); rtdCookie->cd.resetTimer(); rtdCookie->on = true; rtdCookie->db.active = false; rtdCookie->db.configured = false; if (sendLen == 5) { thresholdHandler(); } } break; } case (EiveMax31855::RtdCommands::ACTIVE): { if (not rtdCookie->on) { rtdCookie->cd.setTimeout(MAX31865::WARMUP_MS); rtdCookie->cd.resetTimer(); rtdCookie->on = true; rtdCookie->db.active = true; rtdCookie->db.configured = false; } else { rtdCookie->db.active = true; } if (sendLen == 5) { thresholdHandler(); } break; } case (EiveMax31855::RtdCommands::OFF): { rtdCookie->on = false; rtdCookie->db.active = false; rtdCookie->db.configured = false; break; } case (EiveMax31855::RtdCommands::HIGH_TRESHOLD): { if (sendLen == 3) { rtdCookie->highThreshold = (sendData[1] << 8) | sendData[2]; rtdCookie->writeHighThreshold = true; } else { return returnvalue::FAILED; } break; } case (EiveMax31855::RtdCommands::LOW_THRESHOLD): { if (sendLen == 3) { rtdCookie->lowThreshold = (sendData[1] << 8) | sendData[2]; rtdCookie->writeLowThreshold = true; } else { return returnvalue::FAILED; } break; } case (EiveMax31855::RtdCommands::CFG): { ReturnValue_t result = writeCfgReg(rtdCookie->spiCookie, BASE_CFG); if (result != returnvalue::OK) { handleSpiError(rtdCookie, result, "writeCfgReg"); } break; } default: { // TODO: Only implement if needed break; } } return returnvalue::OK; } ReturnValue_t Max31865RtdPolling::getSendSuccess(CookieIF* cookie) { return returnvalue::OK; } ReturnValue_t Max31865RtdPolling::requestReceiveMessage(CookieIF* cookie, size_t requestLen) { return returnvalue::OK; } ReturnValue_t Max31865RtdPolling::readReceivedMessage(CookieIF* cookie, uint8_t** buffer, size_t* size) { MutexGuard mg(readerLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); if (mg.getLockResult() != returnvalue::OK) { // TODO: Emit warning return returnvalue::FAILED; } auto* rtdCookie = dynamic_cast(cookie); if (rtdCookie == nullptr) { return returnvalue::FAILED; } uint8_t* exchangePtr = rtdCookie->exchangeBuf.data(); size_t serLen = 0; auto result = rtdCookie->db.serialize(&exchangePtr, &serLen, rtdCookie->exchangeBuf.size(), SerializeIF::Endianness::MACHINE); if (result != returnvalue::OK) { // TODO: Emit warning return returnvalue::FAILED; } *buffer = reinterpret_cast(rtdCookie->exchangeBuf.data()); *size = serLen; return returnvalue::OK; } ReturnValue_t Max31865RtdPolling::writeCfgReg(SpiCookie* cookie, uint8_t cfg) { using namespace MAX31865; return writeNToReg(cookie, CONFIG, 1, &cfg, nullptr); } ReturnValue_t Max31865RtdPolling::writeBiasSel(MAX31865::Bias bias, SpiCookie* cookie, uint8_t baseCfg) { using namespace MAX31865; if (bias == MAX31865::Bias::OFF) { baseCfg &= ~(1 << CfgBitPos::BIAS_SEL); } else { baseCfg |= (1 << CfgBitPos::BIAS_SEL); } return writeCfgReg(cookie, baseCfg); } ReturnValue_t Max31865RtdPolling::clearFaultStatus(SpiCookie* cookie) { using namespace MAX31865; // Read back the current configuration to avoid overwriting it when clearing te fault status uint8_t currentCfg = 0; auto result = readCfgReg(cookie, currentCfg); if (result != returnvalue::OK) { return result; } // Clear bytes 5, 3 and 2 which need to be 0 currentCfg &= ~0x2C; currentCfg |= (1 << CfgBitPos::FAULT_STATUS_CLEAR); return writeCfgReg(cookie, currentCfg); } ReturnValue_t Max31865RtdPolling::readCfgReg(SpiCookie* cookie, uint8_t& cfg) { using namespace MAX31865; uint8_t* replyPtr = nullptr; auto result = readNFromReg(cookie, CONFIG, 1, &replyPtr); if (result == returnvalue::OK) { cfg = replyPtr[0]; } return result; } ReturnValue_t Max31865RtdPolling::writeLowThreshold(SpiCookie* cookie, uint16_t val) { using namespace MAX31865; uint8_t cmd[2] = {static_cast((val >> 8) & 0xff), static_cast(val & 0xff)}; return writeNToReg(cookie, LOW_THRESHOLD, 2, cmd, nullptr); } ReturnValue_t Max31865RtdPolling::writeHighThreshold(SpiCookie* cookie, uint16_t val) { using namespace MAX31865; uint8_t cmd[2] = {static_cast((val >> 8) & 0xff), static_cast(val & 0xff)}; return writeNToReg(cookie, HIGH_THRESHOLD, 2, cmd, nullptr); } ReturnValue_t Max31865RtdPolling::readLowThreshold(SpiCookie* cookie, uint16_t& lowThreshold) { using namespace MAX31865; uint8_t* replyPtr = nullptr; auto result = readNFromReg(cookie, LOW_THRESHOLD, 2, &replyPtr); if (result == returnvalue::OK) { lowThreshold = (replyPtr[0] << 8) | replyPtr[1]; } return result; } ReturnValue_t Max31865RtdPolling::readHighThreshold(SpiCookie* cookie, uint16_t& highThreshold) { using namespace MAX31865; uint8_t* replyPtr = nullptr; auto result = readNFromReg(cookie, HIGH_THRESHOLD, 2, &replyPtr); if (result == returnvalue::OK) { highThreshold = (replyPtr[0] << 8) | replyPtr[1]; } return result; } ReturnValue_t Max31865RtdPolling::writeNToReg(SpiCookie* cookie, uint8_t reg, size_t n, uint8_t* cmd, uint8_t** reply) { using namespace MAX31865; if (n > cmdBuf.size() - 1) { return returnvalue::FAILED; } cmdBuf[0] = reg | WRITE_BIT; for (size_t idx = 0; idx < n; idx++) { cmdBuf[idx + 1] = cmd[idx]; } return comIF->sendMessage(cookie, cmdBuf.data(), n + 1); } ReturnValue_t Max31865RtdPolling::readRtdVal(SpiCookie* cookie, uint16_t& val, bool& faultBitSet) { using namespace MAX31865; uint8_t* replyPtr = nullptr; auto result = readNFromReg(cookie, RTD, 2, &replyPtr); if (result != returnvalue::OK) { return result; } if (replyPtr[1] & 0b0000'0001) { faultBitSet = true; } // Shift 1 to the right to remove fault bit val = ((replyPtr[0] << 8) | replyPtr[1]) >> 1; return result; } ReturnValue_t Max31865RtdPolling::readNFromReg(SpiCookie* cookie, uint8_t reg, size_t n, uint8_t** reply) { using namespace MAX31865; if (n > 4) { return returnvalue::FAILED; } // Clear write bit in any case reg &= ~WRITE_BIT; cmdBuf[0] = reg; std::memset(cmdBuf.data() + 1, 0, n); ReturnValue_t result = comIF->sendMessage(cookie, cmdBuf.data(), n + 1); if (result != returnvalue::OK) { return returnvalue::FAILED; } size_t dummyLen = 0; uint8_t* replyPtr = nullptr; result = comIF->readReceivedMessage(cookie, &replyPtr, &dummyLen); if (result != returnvalue::OK) { return result; } if (reply != nullptr) { *reply = replyPtr + 1; } return returnvalue::OK; } ReturnValue_t Max31865RtdPolling::handleSpiError(Max31865ReaderCookie* cookie, ReturnValue_t result, const char* ctx) { cookie->db.spiErrorCount.value += 1; sif::warning << "Max31865RtdReader::handleSpiError: " << ctx << " | Failed with result " << result << std::endl; return result; } ReturnValue_t Max31865RtdPolling::initialize() { csLock = comIF->getCsMutex(); return SystemObject::initialize(); }