| /******************************************************************************* | |
| * Copyright (c) 2016 - 2018 Johannes Messmer (admin@jomess.com), fortiss GmbH | |
| * This program and the accompanying materials are made available under the | |
| * terms of the Eclipse Public License 2.0 which is available at | |
| * http://www.eclipse.org/legal/epl-2.0. | |
| * | |
| * SPDX-License-Identifier: EPL-2.0 | |
| * | |
| * Contributors: | |
| * Johannes Messmer - initial API and implementation and/or initial documentation | |
| * Jose Cabral - Cleaning of namespaces | |
| *******************************************************************************/ | |
| #include "bus.h" | |
| #include <algorithm> | |
| #include <slave/slave.h> | |
| #include <slave/packages.h> | |
| #include <slave/handles/bit.h> | |
| #include <slave/handles/analog.h> | |
| #include <slave/handles/analog10.h> | |
| #include "criticalregion.h" | |
| #include <utils/timespec_utils.h> | |
| const char * const EmbrickBusHandler::scmSlaveUpdateFailed = "Update of slave failed."; | |
| const char * const EmbrickBusHandler::scmNoSlavesFound = "No slave modules found."; | |
| EmbrickBusHandler::EmbrickBusHandler(CDeviceExecution& paDeviceExecution) : forte::core::io::IODeviceMultiController(paDeviceExecution), | |
| mSpi(0), mSlaveSelect(0), mSlaves(0), mSlaveCount(0), mLoopActive(false), mSList(0) { | |
| // Set init time | |
| struct timespec ts; | |
| // TODO Check compile error. Had to to add rt libary to c++ make flags | |
| clock_gettime(CLOCK_MONOTONIC, &ts); | |
| mInitTime = ts.tv_sec; | |
| mLastTransfer = micros(); | |
| // Default config | |
| mConfig.mBusInterface = 1; | |
| mConfig.mBusSelectPin = 49; | |
| mConfig.mBusInitSpeed = EmbrickSPIHandler::scmDefaultSpiSpeed; | |
| mConfig.mBusLoopSpeed = EmbrickSPIHandler::scmMaxSpiSpeed; | |
| } | |
| void EmbrickBusHandler::setConfig(struct forte::core::io::IODeviceController::Config* paConfig) { | |
| // Check if BusHandler is active -> configuration changes are not allowed | |
| if (isAlive()) { | |
| DEVLOG_ERROR( | |
| "emBrick[BusHandler]: Cannot change configuration while running.\n"); | |
| return; | |
| } | |
| this->mConfig = *static_cast<Config*>(paConfig); | |
| } | |
| const char* EmbrickBusHandler::init() { | |
| // Start handlers | |
| mSpi = new EmbrickSPIHandler(mConfig.mBusInterface); | |
| mSlaveSelect = new EmbrickPinHandler(mConfig.mBusSelectPin); | |
| mSlaves = new TSlaveList(); | |
| // Check handlers | |
| if (checkHandlerError()) { | |
| return mError; | |
| } | |
| // Set SPI sped for initialization | |
| mSpi->setSpeed(mConfig.mBusInitSpeed); | |
| // Disable slave select -> reset all slaves | |
| mSlaveSelect->disable(); | |
| // Wait for reset | |
| sleep(1); | |
| // Enable slave select -> the first slave waits for initialization | |
| mSlaveSelect->enable(); | |
| // Wait for init | |
| microsleep(SyncGapDuration * 2); | |
| // Init the slaves sequentially. Abort if the init package is ignored -> no further slaves found. | |
| int slaveCounter = 1; | |
| int attempts = 0; | |
| do { | |
| EmbrickSlaveHandler *slave = EmbrickSlaveHandler::sendInit(this, slaveCounter); | |
| if (slave != 0) { | |
| mSlaves->pushBack(slave); | |
| // Activate next slave by sending the 'SelectNextSlave' command to the current slave | |
| // It enables the slave select pin for the next slave on the bus | |
| transfer(slave->mAddress, SelectNextSlave); | |
| slaveCounter++; | |
| attempts = 0; | |
| } | |
| microsleep(SyncGapDuration * 2); | |
| } while (++attempts < 3); | |
| mSlaveCount = slaveCounter - 1; | |
| if (mSlaveCount == 0) { | |
| return scmNoSlavesFound; | |
| } | |
| // Increase the speed of the bus for data transfers | |
| mSpi->setSpeed(mConfig.mBusLoopSpeed); | |
| return 0; | |
| } | |
| void EmbrickBusHandler::deInit() { | |
| // Free memory -> delete all slave instances and hardware handlers | |
| TSlaveList::Iterator itEnd(mSlaves->end()); | |
| for(TSlaveList::Iterator it = mSlaves->begin(); it != itEnd; ++it) { | |
| delete *it; | |
| } | |
| delete mSlaves; | |
| mSlaves = 0; | |
| delete mSpi; | |
| delete mSlaveSelect; | |
| } | |
| forte::core::io::IOHandle* EmbrickBusHandler::initHandle( | |
| forte::core::io::IODeviceController::HandleDescriptor *paHandleDescriptor) { | |
| HandleDescriptor &desc = static_cast<HandleDescriptor&>(*paHandleDescriptor); | |
| EmbrickSlaveHandler *slave = getSlave(desc.mSlaveIndex); | |
| if(slave == 0) { | |
| return 0; | |
| } | |
| switch (desc.mType) { | |
| case Bit: | |
| return new EmbrickBitSlaveHandle(this, desc.mDirection, desc.mOffset, desc.mPosition, | |
| slave); | |
| case Analog: | |
| return new EmbrickAnalogSlaveHandle(this, desc.mDirection, desc.mOffset, slave); | |
| case Analog10: | |
| return new EmbrickAnalog10SlaveHandle(this, desc.mDirection, desc.mOffset, slave); | |
| } | |
| return 0; | |
| } | |
| void EmbrickBusHandler::prepareLoop() { | |
| // Get current time | |
| clock_gettime(CLOCK_MONOTONIC, &mNextLoop); | |
| // Scheduling | |
| // TODO Combine slaves list and scheduling information | |
| // DISCUSS Speed of array and forte_list? | |
| TSlaveList::Iterator it = mSlaves->begin(); | |
| mSList = (struct SEntry**) forte_malloc(sizeof(struct SEntry*) * mSlaveCount); | |
| for (int i = 0; i < mSlaveCount; i++) { | |
| mSList[i] = (struct SEntry*) forte_malloc(sizeof(struct SEntry)); | |
| mSList[i]->mSlave = *it; | |
| mSList[i]->mNextDeadline = mNextLoop; | |
| mSList[i]->mLastDuration = 0; | |
| mSList[i]->mForced = false; | |
| mSList[i]->mDelayed = false; | |
| ++it; | |
| } | |
| mSNext = mSList[0]; | |
| mLoopActive = false; | |
| } | |
| void EmbrickBusHandler::runLoop() { | |
| prepareLoop(); | |
| // Init loop variables | |
| SEntry *sCur = 0; | |
| while (isAlive()) { | |
| // Sleep till next deadline is reached or loop is waked up | |
| struct timespec now; | |
| clock_gettime(CLOCK_MONOTONIC, &now); | |
| if(timespecLessThan(&now, &mSNext->mNextDeadline)){ //only wait if the deadline is in the future | |
| struct timespec timeToSleep; | |
| timespecSub(&mSNext->mNextDeadline, &now, &timeToSleep); | |
| mForceLoop.timedWait(timeToSleep.tv_sec * 1E9 + timeToSleep.tv_nsec); | |
| } | |
| // Set current slave | |
| mLoopActive = true; | |
| sCur = mSNext; | |
| // Set next deadline of current slave | |
| clock_gettime(CLOCK_MONOTONIC, &sCur->mNextDeadline); | |
| addTime(sCur->mNextDeadline, 1000000 / sCur->mSlave->mConfig.mUpdateInterval); | |
| // Remove delayed and forced flag | |
| sCur->mForced = false; | |
| sCur->mDelayed = false; | |
| // Remove lock during blocking operation -> allows forced update interrupts | |
| mSyncObject.unlock(); | |
| uint64_t ms = micros(); | |
| // Perform update on current slave | |
| if (-1 == sCur->mSlave->update()) { | |
| mError = scmSlaveUpdateFailed; | |
| // Check for critical bus errors | |
| if(checkHandlerError() || hasError()) { | |
| break; | |
| } | |
| } | |
| // Search for next deadline -> set lock to avoid changes of list | |
| mSyncObject.lock(); | |
| mLoopActive = false; | |
| // Store update duration | |
| sCur->mLastDuration = (uint16_t) (micros() - ms); | |
| // If current slave is forced again -> add update duration to deadline | |
| if(sCur->mForced) { | |
| addTime(sCur->mNextDeadline, sCur->mLastDuration); | |
| } | |
| for (unsigned int i = 0; i < mSlaveCount; i++) { | |
| if (timespecLessThan(&mSList[i]->mNextDeadline, &mSNext->mNextDeadline)) { | |
| mSNext = mSList[i]; | |
| } | |
| } | |
| } | |
| // Clean loop | |
| cleanLoop(); | |
| } | |
| void EmbrickBusHandler::cleanLoop() { | |
| // Free memory of list | |
| for(int i = 0; i < mSlaveCount; i++) { | |
| forte_free(mSList[i]); | |
| } | |
| forte_free(mSList); | |
| mSList = 0; | |
| } | |
| bool EmbrickBusHandler::checkHandlerError() { | |
| if (mSpi->hasError()) { | |
| mError = mSpi->mError; | |
| return true; | |
| } | |
| if (mSlaveSelect->hasError()) { | |
| mError = mSlaveSelect->paError; | |
| return true; | |
| } | |
| return false; | |
| } | |
| EmbrickSlaveHandler* EmbrickBusHandler::getSlave(int paIndex) { | |
| if (mSlaves == 0) { | |
| return 0; | |
| } | |
| TSlaveList::Iterator itEnd = mSlaves->end(); | |
| int i = 0; | |
| for(TSlaveList::Iterator it = mSlaves->begin(); it != itEnd; ++it, i++) { | |
| if (paIndex == i) { | |
| return *it; | |
| } | |
| } | |
| return 0; | |
| } | |
| void EmbrickBusHandler::forceUpdate(int paIndex) { | |
| mSyncObject.lock(); | |
| if (mSList == 0 || mSlaveCount <= paIndex || mSList[paIndex]->mForced) { | |
| mSyncObject.unlock(); | |
| return; | |
| } | |
| SEntry *e = mSList[paIndex]; | |
| e->mForced = true; | |
| clock_gettime(CLOCK_MONOTONIC, &e->mNextDeadline); | |
| if (!mLoopActive) { | |
| if(!mSNext->mDelayed && !mSNext->mForced) { | |
| struct timespec ts = e->mNextDeadline; | |
| addTime(ts, e->mLastDuration * 2); | |
| if(!timespecLessThan(&ts, &mSNext->mNextDeadline)) { | |
| mSNext->mDelayed = true; | |
| } | |
| mSNext = e; | |
| } | |
| // Force next loop | |
| mForceLoop.inc(); | |
| } | |
| mSyncObject.unlock(); | |
| } | |
| void EmbrickBusHandler::addSlaveHandle(int paIndex, forte::core::io::IOHandle* paHandle) { | |
| EmbrickSlaveHandler* slave = getSlave(paIndex); | |
| if(slave == 0) { | |
| return; | |
| } | |
| slave->addHandle((EmbrickSlaveHandle*) paHandle); | |
| } | |
| void EmbrickBusHandler::dropSlaveHandles(int paIndex) { | |
| EmbrickSlaveHandler* slave = getSlave(paIndex); | |
| if(slave == 0) { | |
| return; | |
| } | |
| slave->dropHandles(); | |
| } | |
| bool EmbrickBusHandler::isSlaveAvailable(int paIndex) { | |
| return getSlave(paIndex) != 0; | |
| } | |
| bool EmbrickBusHandler::checkSlaveType(int paIndex, int paType) { | |
| EmbrickSlaveHandler* slave = getSlave(paIndex); | |
| if(slave == 0) { | |
| return false; | |
| } | |
| return slave->mType == paType; | |
| } | |
| bool EmbrickBusHandler::transfer(unsigned int paTarget, Command paCmd, | |
| unsigned char* paDataSend, int paDataSendLength, unsigned char* paDataReceive, | |
| int paDataReceiveLength, EmbrickSlaveHandler::SlaveStatus* paStatus, CSyncObject *paSyncMutex) { | |
| unsigned int dataLength = std::max(paDataSendLength, paDataReceiveLength + 1); // + 1 status byte | |
| unsigned int bufferLength = sizeof(EmbrickHeaderPackage) + dataLength + 1; // + 1 checksum byte | |
| if (bufferLength > TransferBufferLength) { | |
| DEVLOG_ERROR("emBrick[BusHandler]: Transfer buffer size exceeded.\n"); | |
| return false; | |
| } | |
| memset(mSendBuffer, 0, bufferLength); | |
| memset(mRecvBuffer, 0, bufferLength); | |
| // Prepare header | |
| EmbrickHeaderPackage* header = (EmbrickHeaderPackage*) mSendBuffer; | |
| header->mAddress = (char) paTarget; | |
| header->mCommand = paCmd; | |
| header->mChecksum = calcChecksum((unsigned char*) header, 2); | |
| if(paSyncMutex) { | |
| paSyncMutex->lock(); | |
| } | |
| memcpy(mSendBuffer + sizeof(EmbrickHeaderPackage), paDataSend, paDataSendLength); | |
| if(paSyncMutex) { | |
| paSyncMutex->unlock(); | |
| } | |
| mSendBuffer[sizeof(EmbrickHeaderPackage) + paDataSendLength] = calcChecksum( | |
| mSendBuffer + sizeof(EmbrickHeaderPackage), paDataSendLength); | |
| // Invert data of master | |
| for(unsigned int i = 0; i < bufferLength; i++) { | |
| mSendBuffer[i] = (unsigned char) ~mSendBuffer[i]; | |
| } | |
| // Send and receive buffer via SPI | |
| int attempts = 3; | |
| int fails = 0; | |
| bool ok; | |
| do { | |
| // Wait required microseconds between messages | |
| uint64_t microTime = micros(); | |
| if(mLastTransfer + SyncGapDuration > microTime) { | |
| microsleep(mLastTransfer + (uint64_t) SyncGapDuration - microTime); | |
| } | |
| ok = mSpi->transfer(mSendBuffer, mRecvBuffer, bufferLength); | |
| mLastTransfer = micros(); | |
| // Critical error of bus -> break immediately | |
| if(!ok) { | |
| break; | |
| } | |
| // Validate checksum | |
| ok = calcChecksum(mRecvBuffer + sizeof(EmbrickHeaderPackage), | |
| bufferLength - sizeof(EmbrickHeaderPackage)) == 0; | |
| if (!ok) { | |
| DEVLOG_DEBUG("emBrick[BusHandler]: Transfer - Invalid checksum\n"); | |
| } | |
| } while (!ok && ++fails < attempts); | |
| // Check if command was transmitted successfully | |
| if (!ok) { | |
| if (paTarget != 0 && paCmd != Data) { | |
| DEVLOG_DEBUG( | |
| "emBrick[BusHandler]: Failed to send command %d to slave %d.\n", paCmd, | |
| paTarget); | |
| } | |
| return false; | |
| } | |
| // Copy result | |
| if(paSyncMutex) { | |
| paSyncMutex->lock(); | |
| } | |
| if(paStatus) { | |
| *paStatus = (EmbrickSlaveHandler::SlaveStatus) mRecvBuffer[sizeof(EmbrickHeaderPackage)]; | |
| } | |
| memcpy(paDataReceive, mRecvBuffer + sizeof(EmbrickHeaderPackage) + 1, | |
| paDataReceiveLength); | |
| if(paSyncMutex) { | |
| paSyncMutex->unlock(); | |
| } | |
| return true; | |
| } | |
| unsigned char EmbrickBusHandler::calcChecksum(unsigned char * paData, int paDataLen) { | |
| unsigned char c = 0; | |
| for(int i = 0; i < paDataLen; i++) { | |
| c = static_cast<unsigned char>(c + paData[i]); | |
| } | |
| return static_cast<unsigned char>(scmChecksumConstant - c); | |
| } | |
| uint64_t EmbrickBusHandler::micros() { | |
| struct timespec ts; | |
| clock_gettime(CLOCK_MONOTONIC, &ts); | |
| return round(ts.tv_nsec / 1.0e3) + (ts.tv_sec - mInitTime) * 1E6; | |
| } | |
| unsigned long EmbrickBusHandler::millis() { | |
| // TODO Improve timing func. Maybe replace with existing forte implementation. | |
| struct timespec ts; | |
| clock_gettime(CLOCK_MONOTONIC, &ts); | |
| return static_cast<unsigned long>(round(ts.tv_nsec / 1.0e6)) + (ts.tv_sec - mInitTime) * 1000; | |
| } | |
| void EmbrickBusHandler::microsleep(uint64_t paMicroseconds) { | |
| struct timespec ts; | |
| ts.tv_sec = (long) (paMicroseconds / (uint64_t) 1E6); | |
| ts.tv_nsec = (long) paMicroseconds * (long) 1E3 - ts.tv_sec * (long) 1E9; | |
| nanosleep(&ts, 0); | |
| } | |
| void EmbrickBusHandler::addTime(struct timespec& paTs, unsigned long paMicroseconds) { | |
| paTs.tv_sec += paMicroseconds / (unsigned long) 1E6; | |
| unsigned long t = paTs.tv_nsec + paMicroseconds * (unsigned long) 1E3 | |
| - (paMicroseconds / (unsigned long) 1E6) * (unsigned long) 1E9; | |
| if (t >= (unsigned long) 1E9) { | |
| t -= (unsigned long) 1E9; | |
| paTs.tv_sec++; | |
| } | |
| paTs.tv_nsec = t; | |
| } |