#include "ImtqPollingTask.h" #include #include #include #include #include #include #include #include "fsfw/FSFW.h" ImtqPollingTask::ImtqPollingTask(object_id_t imtqPollingTask, std::atomic_uint16_t& i2cFatalErrors) : SystemObject(imtqPollingTask), i2cFatalErrors(i2cFatalErrors) { semaphore = SemaphoreFactory::instance()->createBinarySemaphore(); semaphore->acquire(); ipcLock = MutexFactory::instance()->createMutex(); bufLock = MutexFactory::instance()->createMutex(); } ReturnValue_t ImtqPollingTask::performOperation(uint8_t operationCode) { while (true) { ipcLock->lockMutex(); state = InternalState::IDLE; ipcLock->unlockMutex(); semaphore->acquire(); comStatus = returnvalue::OK; // Stopwatch watch; switch (currentRequest) { case imtq::RequestType::MEASURE_NO_ACTUATION: { // Measured to take 24 ms for debug and release builds. // Stopwatch watch; handleMeasureStep(); break; } case imtq::RequestType::ACTUATE: { handleActuateStep(); break; } default: { break; } }; } return returnvalue::OK; } void ImtqPollingTask::handleMeasureStep() { size_t replyLen = 0; uint8_t* replyPtr; ImtqRepliesDefault replies(replyBuf.data()); // If some startup handling is added later, set configured after it was done once. replies.setConfigured(); // Can be used later to verify correct timing (e.g. all data has been read) clearReadFlagsDefault(replies); auto i2cCmdExecMeasure = [&](imtq::CC::CC cc) { ccToReplyPtrMeasure(replies, cc, &replyPtr, replyLen); return i2cCmdExecDefault(cc, replyPtr, replyLen, imtq::MGM_MEASUREMENT_LOW_LEVEL_ERROR); }; cmdLen = 1; cmdBuf[0] = imtq::CC::GET_SYSTEM_STATE; if (i2cCmdExecMeasure(imtq::CC::GET_SYSTEM_STATE) != returnvalue::OK) { return; } ignoreNextActuateRequest = (replies.getSystemState()[2] == static_cast(imtq::mode::SELF_TEST)); if (ignoreNextActuateRequest) { // Do not command anything until self-test is done. return; } if (specialRequest != imtq::SpecialRequest::NONE) { auto executeSelfTest = [&](imtq::selfTest::Axis axis) { cmdBuf[0] = imtq::CC::SELF_TEST_CMD; cmdBuf[1] = axis; return i2cCmdExecMeasure(imtq::CC::SELF_TEST_CMD); }; // If a self-test is already ongoing, ignore the request. if (replies.getSystemState()[2] != static_cast(imtq::mode::SELF_TEST)) { switch (specialRequest) { case (imtq::SpecialRequest::DO_SELF_TEST_POS_X): { executeSelfTest(imtq::selfTest::Axis::X_POSITIVE); break; } case (imtq::SpecialRequest::DO_SELF_TEST_NEG_X): { executeSelfTest(imtq::selfTest::Axis::X_NEGATIVE); break; } case (imtq::SpecialRequest::DO_SELF_TEST_POS_Y): { executeSelfTest(imtq::selfTest::Axis::Y_POSITIVE); break; } case (imtq::SpecialRequest::DO_SELF_TEST_NEG_Y): { executeSelfTest(imtq::selfTest::Axis::Y_NEGATIVE); break; } case (imtq::SpecialRequest::DO_SELF_TEST_POS_Z): { executeSelfTest(imtq::selfTest::Axis::Z_POSITIVE); break; } case (imtq::SpecialRequest::DO_SELF_TEST_NEG_Z): { executeSelfTest(imtq::selfTest::Axis::Z_NEGATIVE); break; } case (imtq::SpecialRequest::GET_SELF_TEST_RESULT): { cmdBuf[0] = imtq::CC::GET_SELF_TEST_RESULT; i2cCmdExecMeasure(imtq::CC::GET_SELF_TEST_RESULT); break; } default: { // Should never happen break; } } // We are done. Only request self test or results here. return; } } // The I2C IP core on EIVE sometimes glitches out. Send start MTM measurement twice. cmdBuf[0] = imtq::CC::START_MTM_MEASUREMENT; if (i2cCmdExecMeasure(imtq::CC::START_MTM_MEASUREMENT) != returnvalue::OK) { return; } cmdBuf[0] = imtq::CC::START_MTM_MEASUREMENT; if (i2cCmdExecMeasure(imtq::CC::START_MTM_MEASUREMENT) != returnvalue::OK) { return; } // Takes a bit of time to take measurements. Subtract a bit because of the delay of previous // commands. TaskFactory::delayTask(currentIntegrationTimeMs + MGM_READ_BUFFER_TIME_MS); cmdBuf[0] = imtq::CC::GET_RAW_MTM_MEASUREMENT; if (i2cCmdExecMeasure(imtq::CC::GET_RAW_MTM_MEASUREMENT) != returnvalue::OK) { return; } bool mgmMeasurementTooOld = false; // See p.39 of the iMTQ user manual. If the NEW bit of the STAT bitfield is not set, we probably // have old data. Which can be really bad for ACS. And everything. if ((replyPtr[2] >> 7) == 0) { replyPtr[0] = false; mgmMeasurementTooOld = true; } cmdBuf[0] = imtq::CC::GET_ENG_HK_DATA; if (i2cCmdExecMeasure(imtq::CC::GET_ENG_HK_DATA) != returnvalue::OK) { return; } cmdBuf[0] = imtq::CC::GET_CAL_MTM_MEASUREMENT; if (i2cCmdExecMeasure(imtq::CC::GET_CAL_MTM_MEASUREMENT) != returnvalue::OK) { return; } if (mgmMeasurementTooOld) { sif::error << "IMTQ: MGM measurement too old" << std::endl; } return; } void ImtqPollingTask::handleActuateStep() { uint8_t* replyPtr = nullptr; size_t replyLen = 0; // No point when self-test mode is active. if (ignoreNextActuateRequest) { return; } ImtqRepliesWithTorque replies(replyBufActuation.data()); // Can be used later to verify correct timing (e.g. all data has been read) clearReadFlagsWithTorque(replies); auto i2cCmdExecActuate = [&](imtq::CC::CC cc) { ccToReplyPtrActuate(replies, cc, &replyPtr, replyLen); return i2cCmdExecDefault(cc, replyPtr, replyLen, imtq::ACTUATE_CMD_LOW_LEVEL_ERROR); }; buildDipoleCommand(); if (i2cCmdExecActuate(imtq::CC::START_ACTUATION_DIPOLE) != returnvalue::OK) { return; } TaskFactory::delayTask(10); cmdLen = 1; // The I2C IP core on EIVE sometimes glitches out. Send start MTM measurement twice. cmdBuf[0] = imtq::CC::START_MTM_MEASUREMENT; if (i2cCmdExecActuate(imtq::CC::START_MTM_MEASUREMENT) != returnvalue::OK) { return; } cmdBuf[0] = imtq::CC::START_MTM_MEASUREMENT; if (i2cCmdExecActuate(imtq::CC::START_MTM_MEASUREMENT) != returnvalue::OK) { return; } TaskFactory::delayTask(currentIntegrationTimeMs + MGM_READ_BUFFER_TIME_MS); cmdBuf[0] = imtq::CC::GET_RAW_MTM_MEASUREMENT; if (i2cCmdExecActuate(imtq::CC::GET_RAW_MTM_MEASUREMENT) != returnvalue::OK) { return; } bool measurementWasTooOld = false; // See p.39 of the iMTQ user manual. If the NEW bit of the STAT bitfield is not set, we probably // have old data. Which can be really bad for ACS. And everything. if ((replyPtr[2] >> 7) == 0) { measurementWasTooOld = true; replyPtr[0] = false; } cmdBuf[0] = imtq::CC::GET_ENG_HK_DATA; if (i2cCmdExecActuate(imtq::CC::GET_ENG_HK_DATA) != returnvalue::OK) { return; } if (measurementWasTooOld) { sif::error << "IMTQ: MGM measurement too old" << std::endl; } return; } ReturnValue_t ImtqPollingTask::initialize() { return returnvalue::OK; } ReturnValue_t ImtqPollingTask::initializeInterface(CookieIF* cookie) { i2cCookie = dynamic_cast(cookie); if (i2cCookie == nullptr) { sif::error << "ImtqPollingTask::initializeInterface: Invalid I2C cookie" << std::endl; return returnvalue::FAILED; } i2cDev = i2cCookie->getDeviceFile().c_str(); i2cAddr = i2cCookie->getAddress(); return returnvalue::OK; } ReturnValue_t ImtqPollingTask::sendMessage(CookieIF* cookie, const uint8_t* sendData, size_t sendLen) { const auto* imtqReq = reinterpret_cast(sendData); { MutexGuard mg(ipcLock); if (imtqReq->request == imtq::RequestType::ACTUATE) { std::memcpy(dipoles, imtqReq->dipoles, sizeof(dipoles)); torqueDuration = imtqReq->torqueDuration; } currentRequest = imtqReq->request; specialRequest = imtqReq->specialRequest; if (state != InternalState::IDLE) { return returnvalue::FAILED; } state = InternalState::BUSY; } semaphore->release(); return returnvalue::OK; } ReturnValue_t ImtqPollingTask::getSendSuccess(CookieIF* cookie) { return returnvalue::OK; } ReturnValue_t ImtqPollingTask::requestReceiveMessage(CookieIF* cookie, size_t requestLen) { return returnvalue::OK; } void ImtqPollingTask::ccToReplyPtrMeasure(ImtqRepliesDefault& replies, imtq::CC::CC cc, uint8_t** replyBuf, size_t& replyLen) { replyLen = imtq::getReplySize(cc); switch (cc) { case (imtq::CC::CC::GET_ENG_HK_DATA): { *replyBuf = replies.engHk; break; } case (imtq::CC::CC::SOFTWARE_RESET): { *replyBuf = replies.swReset; break; } case (imtq::CC::CC::GET_SYSTEM_STATE): { *replyBuf = replies.systemState; break; } case (imtq::CC::CC::START_MTM_MEASUREMENT): { *replyBuf = replies.startMtmMeasurement; break; } case (imtq::CC::CC::GET_RAW_MTM_MEASUREMENT): { *replyBuf = replies.rawMgmMeasurement; break; } case (imtq::CC::CC::GET_CAL_MTM_MEASUREMENT): { *replyBuf = replies.calibMgmMeasurement; break; } default: { *replyBuf = replies.specialRequestReply; break; } } } void ImtqPollingTask::ccToReplyPtrActuate(ImtqRepliesWithTorque& replies, imtq::CC::CC cc, uint8_t** replyBuf, size_t& replyLen) { replyLen = imtq::getReplySize(cc); switch (cc) { case (imtq::CC::CC::START_ACTUATION_DIPOLE): { *replyBuf = replies.dipoleActuation; break; } case (imtq::CC::CC::GET_ENG_HK_DATA): { *replyBuf = replies.engHk; break; } case (imtq::CC::CC::START_MTM_MEASUREMENT): { *replyBuf = replies.startMtmMeasurement; break; } case (imtq::CC::CC::GET_RAW_MTM_MEASUREMENT): { *replyBuf = replies.rawMgmMeasurement; break; } default: { *replyBuf = nullptr; replyLen = 0; break; } } } size_t ImtqPollingTask::getExchangeBufLen(imtq::SpecialRequest specialRequest) { size_t baseLen = ImtqRepliesDefault::BASE_LEN; switch (specialRequest) { case (imtq::SpecialRequest::NONE): case (imtq::SpecialRequest::DO_SELF_TEST_POS_X): case (imtq::SpecialRequest::DO_SELF_TEST_NEG_X): case (imtq::SpecialRequest::DO_SELF_TEST_POS_Y): case (imtq::SpecialRequest::DO_SELF_TEST_NEG_Y): case (imtq::SpecialRequest::DO_SELF_TEST_POS_Z): case (imtq::SpecialRequest::DO_SELF_TEST_NEG_Z): { break; } case (imtq::SpecialRequest::GET_SELF_TEST_RESULT): { baseLen += imtq::replySize::SELF_TEST_RESULTS; break; } } return baseLen; } void ImtqPollingTask::buildDipoleCommand() { cmdBuf[0] = imtq::CC::CC::START_ACTUATION_DIPOLE; uint8_t* serPtr = cmdBuf.data() + 1; size_t serLen = 0; for (uint8_t idx = 0; idx < 3; idx++) { SerializeAdapter::serialize(&dipoles[idx], &serPtr, &serLen, cmdBuf.size(), SerializeIF::Endianness::LITTLE); } SerializeAdapter::serialize(&torqueDuration, &serPtr, &serLen, cmdBuf.size(), SerializeIF::Endianness::LITTLE); // sif::debug << "Dipole X: " << dipoles[0] << std::endl; // sif::debug << "Torqeu Dur: " << torqueDuration << std::endl; cmdLen = 1 + serLen; } ReturnValue_t ImtqPollingTask::readReceivedMessage(CookieIF* cookie, uint8_t** buffer, size_t* size) { imtq::RequestType currentRequest; { MutexGuard mg(ipcLock); currentRequest = this->currentRequest; } size_t replyLen = 0; MutexGuard mg(bufLock); if (currentRequest == imtq::RequestType::MEASURE_NO_ACTUATION) { replyLen = getExchangeBufLen(specialRequest); memcpy(exchangeBuf.data(), replyBuf.data(), replyLen); } else if (currentRequest == imtq::RequestType::ACTUATE) { replyLen = ImtqRepliesWithTorque::BASE_LEN; memcpy(exchangeBuf.data(), replyBufActuation.data(), replyLen); } else { *size = 0; } *buffer = exchangeBuf.data(); *size = replyLen; return comStatus; } void ImtqPollingTask::clearReadFlagsDefault(ImtqRepliesDefault& replies) { replies.calibMgmMeasurement[0] = false; replies.rawMgmMeasurement[0] = false; replies.systemState[0] = false; replies.specialRequestReply[0] = false; replies.engHk[0] = false; } ReturnValue_t ImtqPollingTask::i2cCmdExecDefault(imtq::CC::CC cc, uint8_t* replyPtr, size_t replyLen, ReturnValue_t comErrIfFails) { ReturnValue_t res = performI2cFullRequest(replyPtr + 1, replyLen); if (res != returnvalue::OK) { sif::error << "IMTQ: I2C transaction for command 0x" << std::hex << std::setw(2) << cc << " failed" << std::dec << std::endl; comStatus = comErrIfFails; return returnvalue::FAILED; } if (replyPtr[1] != cc) { sif::warning << "IMTQ: Unexpected CC 0x" << std::hex << std::setw(2) << static_cast(replyPtr[1]) << " for command 0x" << cc << std::dec << std::endl; comStatus = comErrIfFails; return returnvalue::FAILED; } replyPtr[0] = true; return returnvalue::OK; } void ImtqPollingTask::clearReadFlagsWithTorque(ImtqRepliesWithTorque& replies) { replies.dipoleActuation[0] = false; replies.engHk[0] = false; replies.rawMgmMeasurement[0] = false; replies.startMtmMeasurement[0] = false; } ReturnValue_t ImtqPollingTask::performI2cFullRequest(uint8_t* reply, size_t replyLen) { int fd = 0; if (cmdLen == 0 or reply == nullptr) { return returnvalue::FAILED; } { UnixFileGuard fileHelper(i2cDev, fd, O_RDWR, "ImtqPollingTask::performI2cFullRequest"); if (fileHelper.getOpenResult() != returnvalue::OK) { return fileHelper.getOpenResult(); } if (ioctl(fd, I2C_SLAVE, i2cAddr) < 0) { sif::warning << "Opening IMTQ slave device failed with code " << errno << ": " << strerror(errno) << std::endl; if (errno == EBUSY) { i2cFatalErrors++; } } int written = write(fd, cmdBuf.data(), cmdLen); if (written < 0) { sif::error << "IMTQ: Failed to send with error code " << errno << ". Error description: " << strerror(errno) << std::endl; // This is a weird issue which sometimes occurs on debug builds. All I2C buses are busy // for all writes, if (errno == EBUSY) { i2cFatalErrors++; } return returnvalue::FAILED; } else if (static_cast(written) != cmdLen) { sif::error << "IMTQ: Could not write all bytes" << std::endl; return returnvalue::FAILED; } } #if FSFW_HAL_I2C_WIRETAPPING == 1 sif::info << "Sent I2C data to bus " << deviceFile << ":" << std::endl; arrayprinter::print(sendData, sendLen); #endif // wait 1 ms like specified in the datasheet. This is the time the IMTQ needs // to prepare a reply. usleep(1000); { UnixFileGuard fileHelper(i2cDev, fd, O_RDWR, "ImtqPollingTask::performI2cFullRequest"); if (fileHelper.getOpenResult() != returnvalue::OK) { return fileHelper.getOpenResult(); } if (ioctl(fd, I2C_SLAVE, i2cAddr) < 0) { sif::warning << "Opening IMTQ slave device failed with code " << errno << ": " << strerror(errno) << std::endl; } MutexGuard mg(bufLock); int readLen = read(fd, reply, replyLen); if (readLen != static_cast(replyLen)) { if (readLen < 0) { sif::warning << "IMTQ: Reading failed with error code " << errno << " | " << strerror(errno) << std::endl; } else { sif::warning << "IMTQ: Read only" << readLen << " from " << replyLen << " bytes" << std::endl; } } } if (reply[0] == 0xff or reply[1] == 0xff) { sif::warning << "IMTQ: No reply available after 1 millisecond"; return NO_REPLY_AVAILABLE; } return returnvalue::OK; }