diff --git a/CPP/HriBoardLib/hriboard.cpp b/CPP/HriBoardLib/hriboard.cpp index 84eb8a2..f8131c5 100644 --- a/CPP/HriBoardLib/hriboard.cpp +++ b/CPP/HriBoardLib/hriboard.cpp @@ -1,415 +1,415 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "hriboard.h" #include #include #include #include #include #include const int SYNCVAR_LIST_ITEM_SIZE = SYNCVAR_NAME_SIZE + 3; /** * @brief Constructor. */ HriBoard::HriBoard() { } /** * @brief Establish the link with the board. * Establish the link with the board, then stops the streaming and requests the * variables list. * @param comPortName serial port name, in the format "COM1" on Windows, or * "/dev/ttyO1" on UNIX. * @throws A runtime_error is thrown if the serial port could not be opened. */ void HriBoard::openLink(QString comPortName) { streamID = 0; // Setup the serial port. serial.setPortName(comPortName); serial.setBaudRate(UART_BAUDRATE); serial.setDataBits(QSerialPort::Data8); serial.setFlowControl(QSerialPort::NoFlowControl); serial.setParity(QSerialPort::NoParity); connect(&serial, SIGNAL(readyRead()), this, SLOT(onReceivedData())); qDebug() << "Opening the serial COM port..."; if(serial.open(QIODevice::ReadWrite)) qDebug() << "COM port opened successfully."; else throw std::runtime_error("Can't open the COM port."); // Stop the streaming, in case it was enabled. QByteArray ba; ba.append((char)0); sendPacket(PC_MESSAGE_SET_STREAMED_VAR, ba); // Request the variables list. sendPacket(PC_MESSAGE_GET_VARS_LIST); } /** * @brief Sets the SyncVars to stream. * @param varsToStream list of the SyncVars to stream. * @param streamedVarsValues pointer to array where the streaming values will be * appended. This parameter can be nullptr to not use this feature. */ void HriBoard::setStreamedVars(QList varsToStream, QLinkedList > *streamedVarsValues) { this->streamedVarsValues = streamedVarsValues; streamedVarsValues->clear(); // Copy the list of variables to stream, and compute the size of a streaming // packet. streamedVars.clear(); streamPacketSize = sizeof(quint8) + sizeof(quint32); // Stream ID + timestamp. for(SyncVarBase* sv : varsToStream) { streamedVars.append(syncVars[sv->getIndex()]); streamPacketSize += sv->getSize(); } // streamID++; QByteArray ba; ba.append((quint8)varsToStream.size()); ba.append(streamID); for(SyncVarBase* sv : varsToStream) { int varIndex = sv->getIndex(); ba.append((quint8)varIndex); } sendPacket(PC_MESSAGE_SET_STREAMED_VAR, ba); } /** * @brief Updates a SyncVar on the board with the value of the local one. * @param var the SyncVar to synchronize. */ void HriBoard::writeRemoteVar(SyncVarBase *var) { QByteArray ba; ba.append(var->getIndex()); ba.append(var->getData()); sendPacket(PC_MESSAGE_SET_VAR, ba); } /** * @brief Updates a local SyncVar with the value of the SyncVar on the board. * @param var the SyncVar to synchronize. */ void HriBoard::readRemoteVar(SyncVarBase *var) { QByteArray ba; ba.append(var->getIndex()); sendPacket(PC_MESSAGE_GET_VAR, ba); var->setOutOfDate(); } /** * @brief Makes a list of all the candidate serial ports. * @return a list of all the serial port that use the right USB-to-UART chip. */ QStringList HriBoard::getComPorts() { QList ports = QSerialPortInfo::availablePorts(); for(int i=ports.size()-1; i >= 0; i--) { // Remove from the list all the serial COM ports that are not the CP210x // USB-to-serial chip. if(!ports[i].description().contains("CP210")) ports.removeAt(i); } // Make a list of the COM ports names. QStringList portsNames; for(QSerialPortInfo comInfo : ports) portsNames << comInfo.portName(); return portsNames; } /** * @brief Start logging the streamed variables to a CSV file. * @return true if the logfile could be created, false otherwise. */ bool HriBoard::startLoggingToFile() { stopLoggingToFile(); QDateTime now = QDateTime::currentDateTime(); logFile.setFileName("logs/log_" + now.toString("yyyy-MM-dd_hh-mm-ss") + ".csv"); if(QDir().mkpath("logs/") && !logFile.open(QFile::WriteOnly | QFile::Truncate)) { QMessageBox::warning(nullptr, qApp->applicationName(), "Could not create the logfile."); return false; } logStream.setDevice(&logFile); // Write the file header. logStream << "timestamp [s];"; logStream.setRealNumberPrecision(10); for(int i=0; igetName(); if(i < syncVars.size()-1) logStream << ";"; } logStream << endl; return true; } /** * @brief Stop logging the streamed variables to a file. * @remark This function does nothing if the logging was not enabled. */ void HriBoard::stopLoggingToFile() { if(logFile.isOpen()) { logFile.close(); QFileInfo fileInfo(logFile.fileName()); QMessageBox::information(nullptr, qApp->applicationName(), "Logfile saved as " + fileInfo.absoluteFilePath()); } } /** * @brief Return the list of the streamed variables. * @return A const reference to the list of the streamed variables. */ const QList &HriBoard::getStreamedVars() { return streamedVars; } /** * @brief Interprets the received bytes, and reacts accordingly. */ void HriBoard::onReceivedData() { QByteArray rxData = serial.readAll(); //qDebug() << "RX:" << rxData; for(int i=0; i 0) rxDataBytesBuffer[dataBytesReady-1] = (firstHalfByte<<4) + (rxByte & 0xf); switch(rxCurrentMessageType) { case STM_MESSAGE_START_INFO: if(dataBytesReady == 0) { // Request variables list. sendPacket(PC_MESSAGE_GET_VARS_LIST); } break; case STM_MESSAGE_VAR: if(dataBytesReady >= 1) { quint8 varIndex = rxDataBytesBuffer[0]; if(dataBytesReady == 1 + syncVars[varIndex]->getSize()) { QByteArray ba((char*)&rxDataBytesBuffer[1], syncVars[varIndex]->getSize()); syncVars[varIndex]->setData(ba); emit syncVarUpdated(syncVars[varIndex]); } } break; case STM_MESSAGE_VARS_LIST: if(dataBytesReady >= 1) { quint8 nVars = rxDataBytesBuffer[0]; if(dataBytesReady == 1 + nVars * SYNCVAR_LIST_ITEM_SIZE) { // Clear the SyncVar array. for(SyncVarBase *sv : syncVars) delete sv; syncVars.clear(); // quint8* p = &rxDataBytesBuffer[1]; for(int i=0; i values; // Decode the timestamp. quint32 timestamp; memcpy(×tamp, &rxDataBytesBuffer[1], sizeof(timestamp)); double time = ((double)timestamp) / 1000000.0; values.append(time); // Decode the variables values. quint8 const* p = &rxDataBytesBuffer[5]; for(int i=0; igetSize()); streamedVars[i]->setData(value); p += streamedVars[i]->getSize(); values.append(streamedVars[i]->toDouble()); } if(streamedVarsValues != nullptr) streamedVarsValues->append(values); emit streamedSyncVarsUpdated(time, streamedVars); // Log to file, if enabled. if(logFile.isOpen()) { logStream << time << ";"; for(int i=0; iisUpToDate()) logStream << syncVars[i]->toDouble(); else logStream << 0; if(i < syncVars.size()-1) logStream << ";"; } logStream << endl; } } } break; case STM_MESSAGE_DEBUG_TEXT: if(dataBytesReady > 0 && rxDataBytesBuffer[dataBytesReady-1] == '\0') { qDebug() << QString((const char*)rxDataBytesBuffer); rxCurrentMessageType = 0; } break; default: // Ignore. break; } } } } /** * @brief Sends a communication packet to the board. * @param messageType the type of the message. * @param dataBytes the data that will be part of the packet, which is * type-specific. If omitted, there will be no data in the message, so it will * contain only the type. */ void HriBoard::sendPacket(comm_PcMessage messageType, QByteArray dataBytes) { QByteArray txBuffer; txBuffer.append((1<<7) + (uint8_t)messageType); for(int i=0; i> 4); // MSB. txBuffer.append(((quint8)dataBytes[i]) & 0xf); // LSB. } serial.write(txBuffer); serial.waitForBytesWritten(-1); } diff --git a/CPP/HriBoardLib/hriboard.h b/CPP/HriBoardLib/hriboard.h index b6fb215..b569c17 100644 --- a/CPP/HriBoardLib/hriboard.h +++ b/CPP/HriBoardLib/hriboard.h @@ -1,169 +1,169 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef HRIBOARD_H #define HRIBOARD_H #include #include #include #include #include #include #include #include "syncvar.h" /** @defgroup HriBoardLib HRI board * @brief Set of classes to easily interface with the HRI board. * * @addtogroup HriBoardLib * @{ */ /** * @brief Class to interface with a HRI board. * * After creating an instance of this class, connect your custom slot functions * to the signals * * Then, call openLink() to establish the communication link with the board. * If successfull, this object will automatically request the synchronized * variables list, and call the given slot function. You can then inspect this * link and find useful variables to stream or modify. To operate on these * SyncVars, you can either directly interact with the SyncVarBase (generic) * pointers of the list, or create typed pointer by doing dynamic_cast or * calling getVarHandle(). * * To set the value of a SyncVar on the board, call writeRemoteVar(). * To get the value of a SyncVar from the board, call readRemoteVar(), and * wait until SyncVar::isUpToDate() becomes true. * To continuously receive the value of several variables, setup the streaming * with setStreamedVars(). The given queue object will then be filled * continuously, as the values are received from the board. */ class HriBoard : public QObject { Q_OBJECT public: HriBoard(); void openLink(QString comPortName); void setStreamedVars(QList varsToStream, QLinkedList> *streamedVarsValues); void writeRemoteVar(SyncVarBase *var); /** * @brief Updates a SyncVar on the board with the value of the local one. * @param var the SyncVar to synchronize. * @param value the new value that both the local and remote SyncVar should * have. */ template void writeRemoteVar(SyncVarBase *var, T value) { SyncVar *svt = dynamic_cast*>(var); if(svt == nullptr) throw std::runtime_error("writeRemoteVar(): value and SyncVar type do not match."); else { svt->setLocalValue(value); writeRemoteVar(svt); } } void readRemoteVar(SyncVarBase* var); static QStringList getComPorts(); /** * @brief Gets a typed handle to a SyncVar, from its name. * @param name name of the SyncVar. * return The typed pointer to the SyncVar, or nullptr if the given type * did not match the SyncVar's one, or if a variable with the given name * was not found. */ template SyncVar* getVarHandle(QString name) { for(SyncVarBase *sv : syncVars) { if(sv->getName() == name) return dynamic_cast*>(sv); } return nullptr; // Variable not found. } bool startLoggingToFile(); void stopLoggingToFile(); const QList &getStreamedVars(); public slots: void onReceivedData(); signals: /** * @brief Signal emitted when the syncVars list has been received. * @param syncVars list of all the SyncVars of the board. */ void syncVarsListReceived(const QList &syncVars); /** * @brief Signal emitted when a single variable value was received. * @param var pointer to the SyncVar that was just updated. */ void syncVarUpdated(SyncVarBase* var); /** * @brief Signal emitted when a streaming packet was received. * @param time board timestamp of the streamed variables values snapshot. * @param streamedVars list of the streamed variables, that have just been * updated. */ void streamedSyncVarsUpdated(double time, const QList& streamedVars); protected: void sendPacket(comm_PcMessage messageType, QByteArray dataBytes = QByteArray()); private: QSerialPort serial; ///< Serial port to communicate with the board. QList syncVars; ///< SyncVars list. QList streamedVars; ///< List of pointers to the SyncVars streamed by the board. QByteArray rxBuffer; ///< Byte buffer used for receiving bytes from the board. int rxCurrentMessageType; ///< Type of the board message being interpreted. int rxBytesCount; ///< Number of bytes of the board message being interpreted. quint8 firstHalfByte; ///< First byte of a data byte. quint8 rxDataBytesBuffer[1024]; ///< Temporary buffer to store the data bytes (made of two received bytes) of the message being interpreted. int streamID; ///< Identifier of the current streaming configuration, to check if the received streaming packet correspond to the request. int streamPacketSize; ///< Expected size of a streaming packet [byte]. QFile logFile; ///< CSV file to store the states of the streamed variables. QTextStream logStream; ///< Text stream interface for the logFile. QLinkedList> *streamedVarsValues; ///< Pointer to a user array, to be appended with the values of the streamed variables. }; /** * @} */ #endif diff --git a/CPP/HriBoardLib/syncvar.cpp b/CPP/HriBoardLib/syncvar.cpp index 2009d99..f384178 100644 --- a/CPP/HriBoardLib/syncvar.cpp +++ b/CPP/HriBoardLib/syncvar.cpp @@ -1,146 +1,146 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "syncvar.h" #include "hriboard.h" #include /** * @brief Constructor. * @param index index in the SyncVars list. * @param name name of the SyncVar. * @param access access right of the SyncVar. * @param data address of the bytes array to hold the value of the variable. * @param size size of the bytes array that holds the value of the variable. */ SyncVarBase::SyncVarBase(int index, QString name, VarAccess access, uint8_t *data, int size) : index(index), name(name), access(access), data(data), size(size) { upToDate = false; } /** * @brief Gets the index in the SyncVars list. * @return the index. */ int SyncVarBase::getIndex() const { return index; } /** * @brief Gets the name of the SyncVar. * @return the name. */ QString SyncVarBase::getName() const { return name; } /** * @brief Gets the size of the SyncVar value. * @return the value size [byte]. */ int SyncVarBase::getSize() const { return size; } /** * @brief Gets the SyncVar access rights. * @return the SyncVar access rights. */ VarAccess SyncVarBase::getAccess() const { return access; } /** * @brief Gets the local value in raw bytes form. * @return a copy of the value data. * @remark this value may not match the actual value of the variable on the * board, because this function does not perform the synchronisation. */ QByteArray SyncVarBase::getData() const { QByteArray ba((char*)data, size); return ba; } /** * @brief Sets the local value with raw bytes. * @param newData the new value data. * @remark this function only sets the local value of the SyncVar, not the value * of the one on the board (no synchronisation performed). */ void SyncVarBase::setData(QByteArray newData) { if(newData.size() != size) throw std::runtime_error("SyncVar::setData(): size do not match."); memcpy(data, newData.data(), size); upToDate = true; } /** * @brief Gets if the variable is up-to-date. * @return true if the variable value has been set, false if it has not been set * since the beginning, or the call to setOutOfDate(). */ bool SyncVarBase::isUpToDate() const { return upToDate; } /** * @brief Sets the variable as out-of-date. * This function is useful when the user need to know when the variable value * has been updated. */ void SyncVarBase::setOutOfDate() { upToDate = false; } /** * @brief Construct a SyncVar object with the given characteristics. * @param type type of the SyncVar. * @param index index of the SyncVar, as defined by the board. * @param name user-readable name of the SyncVar. * @param access access rights for this variable. * @return A pointer to the constructed SyncVar. */ SyncVarBase *makeSyncVar(VarType type, int index, QString name, VarAccess access) { switch(type) { case BOOL: return new SyncVar(index, name, access, 1); case UINT8: return new SyncVar(index, name, access, 1); case INT8: return new SyncVar(index, name, access, 1); case UINT16: return new SyncVar(index, name, access, 2); case INT16: return new SyncVar(index, name, access, 2); case UINT32: return new SyncVar(index, name, access, 4); case INT32: return new SyncVar(index, name, access, 4); case UINT64: return new SyncVar(index, name, access, 8); case INT64: return new SyncVar(index, name, access, 8); case FLOAT32: return new SyncVar(index, name, access, 4); case FLOAT64: return new SyncVar(index, name, access, 8); default: return nullptr; } } diff --git a/CPP/HriBoardLib/syncvar.h b/CPP/HriBoardLib/syncvar.h index 34ffd30..743b23f 100644 --- a/CPP/HriBoardLib/syncvar.h +++ b/CPP/HriBoardLib/syncvar.h @@ -1,108 +1,108 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef SYNCVAR_H #define SYNCVAR_H #include #include #include #include "../../Firmware/src/definitions.h" typedef comm_VarType VarType; typedef comm_VarAccess VarAccess; /** * @addtogroup HriBoardLib * @{ */ /** * @brief Variable that can be synchronized with its HRI board counterpart. */ class SyncVarBase { public: SyncVarBase(int index, QString name, VarAccess access, uint8_t* data, int size); virtual ~SyncVarBase() = default; int getIndex() const; QString getName() const; int getSize() const; VarAccess getAccess() const; QByteArray getData() const; void setData(QByteArray newData); bool isUpToDate() const; void setOutOfDate(); virtual double toDouble() const = 0; virtual void fromDouble(double value) = 0; private: int index; ///< Index of the SyncVar in the list. QString name; ///< Name describing the SyncVar. VarAccess access; ///< Access rights of the variable. bool upToDate; ///< Indicates whether the local value is up-to-date or not. uint8_t *const data; const int size; }; template class SyncVar : public SyncVarBase { public: SyncVar(int index, QString name, VarAccess access, int varSize) : SyncVarBase(index, name, access, (uint8_t*)&value, varSize) { } ~SyncVar() = default; void setLocalValue(T value) { this->value = value; } T getLocalValue() { return value; } double toDouble() const override { return (double)value; } void fromDouble(double value) override { this->value = (T)value; } protected: T value; }; SyncVarBase* makeSyncVar(VarType type, int index, QString name, VarAccess access); /** * @} */ #endif // SYNCVAR_H diff --git a/CPP/HriExampleProgram/main.cpp b/CPP/HriExampleProgram/main.cpp index a90129e..9af8d92 100644 --- a/CPP/HriExampleProgram/main.cpp +++ b/CPP/HriExampleProgram/main.cpp @@ -1,52 +1,52 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "mainwindow.h" #include /** @defgroup HriExampleProgram Example app to interact with the HRI board * @brief This interface demonstrates how to interact with the HRI. It shows * how to visually display the encoder position and the Hall voltage, and how * to control the LED intensity. * * @addtogroup HriExampleProgram * @{ */ /** * @brief Main function. * @param argc number of arguments. * @param argv arguments array. * @return 0 if the program exited normally, the error code otherwise. */ int main(int argc, char *argv[]) { QApplication a(argc, argv); a.setApplicationName("HRI_example_program"); a.setOrganizationDomain("lsro.epfl.ch"); a.setOrganizationName("LSRO"); MainWindow w; w.show(); return a.exec(); } /** * @} */ diff --git a/CPP/HriExampleProgram/mainwindow.cpp b/CPP/HriExampleProgram/mainwindow.cpp index b84da55..024d2cd 100644 --- a/CPP/HriExampleProgram/mainwindow.cpp +++ b/CPP/HriExampleProgram/mainwindow.cpp @@ -1,155 +1,155 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "mainwindow.h" #include "ui_mainwindow.h" #include #include #include #define UPDATE_PERIOD 50 ///< Update period of the data display [ms]. /** * @brief Constructor. * @param parent parent widget. */ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); // Ask for the COM port. QString comPortName; QStringList ports = HriBoard::getComPorts(); if(ports.size() == 0) { QMessageBox::critical(this, qApp->applicationName(), "No COM port."); exit(0); } else if(ports.size() == 1) comPortName = ports.first(); else { bool ok; QString portChoice = QInputDialog::getItem(this, qApp->applicationName(), "Serial port:", ports, 0, false, &ok); int portIndex = ports.indexOf(portChoice); if(portIndex == -1 || !ok) exit(0); comPortName = ports[portIndex]; } // Establish the link with the HRI board. try { hriBoard.openLink(comPortName); } catch(std::runtime_error) { QMessageBox::critical(this, qApp->applicationName(), "Could not open the COM port."); } connect(&hriBoard, SIGNAL(syncVarsListReceived(const QList&)), this, SLOT(onVarsListReceived(const QList&))); // Start the user interface update timer. updateTimer.setInterval(UPDATE_PERIOD); updateTimer.setSingleShot(false); connect(&updateTimer, SIGNAL(timeout()), this, SLOT(updateDisplay())); // Setup the action when the LED intensity is changed on the GUI. connect(ui->ledSlider, SIGNAL(valueChanged(int)), this, SLOT(setLedIntensity())); } /** * @brief Destructor. */ MainWindow::~MainWindow() { delete ui; } /** * @brief Gets the SyncVars handles from the list, and setup the data streaming. * @param syncVars syncVars list. */ void MainWindow::onVarsListReceived(const QList &syncVars) { // List all the variables. for(SyncVarBase *sv : syncVars) qDebug() << sv->getName(); // Get a convenient handle to the desired SyncVars. encoderPosition = hriBoard.getVarHandle("actual_position [deg]"); hallVoltage = hriBoard.getVarHandle("hall_voltage [V]"); ledIntensity = hriBoard.getVarHandle("led_0 [0.0-1.0]"); // If at least one SyncVar could not be found on the board's list, abort. if(encoderPosition == nullptr || ledIntensity == nullptr) { QMessageBox::critical(this, qApp->applicationName(), "A SyncVar could not be found on the board."); qApp->exit(); } // Setup the streaming to get the continuously get the encoder position. QList varsToStream; varsToStream.append(encoderPosition); varsToStream.append(hallVoltage); hriBoard.setStreamedVars(varsToStream, &streamedVarsValuesBuffer); } /** * @brief Updates the user interface widgets, with the latest variables values. */ void MainWindow::updateDisplay() { if(!streamedVarsValuesBuffer.isEmpty()) { // Get only the latest set of values and discard the rest, since we are // not interested in the older ones (for this simple example). // An other solution would be to read directly the SynVars (e.g. // encoderPosition->getLocalValue(). QList valuesSet = streamedVarsValuesBuffer.last(); streamedVarsValuesBuffer.clear(); // The first value is the timestamp, and the following are the values of // the streamed variables. ui->encoderProgressbar->setValue((int)valuesSet[1]); ui->hallVoltageLabel->setText(QString::number(valuesSet[2]) + " V"); } } /** * @brief Set the intensity of the first LED (LED 0), from the slider position. */ void MainWindow::setLedIntensity() { float intensityValue = ((double)ui->ledSlider->value()) / 100.0; hriBoard.writeRemoteVar(ledIntensity, intensityValue); } diff --git a/CPP/HriExampleProgram/mainwindow.h b/CPP/HriExampleProgram/mainwindow.h index c9a1be4..1268c58 100644 --- a/CPP/HriExampleProgram/mainwindow.h +++ b/CPP/HriExampleProgram/mainwindow.h @@ -1,60 +1,60 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef MAINWINDOW_H #define MAINWINDOW_H #include #include #include "../HriBoardLib/hriboard.h" namespace Ui { class MainWindow; } /** * @addtogroup HriExampleProgram * @{ */ class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); public slots: void onVarsListReceived(const QList &syncVars); void updateDisplay(); void setLedIntensity(); private: Ui::MainWindow *ui; ///< Graphical user interface handle. HriBoard hriBoard; ///< HRI board interface. QLinkedList> streamedVarsValuesBuffer; ///< Queue to receive the all the values of the streamed variables. SyncVar *encoderPosition, *hallVoltage, *ledIntensity; ///< Handle for the SyncVars used in this example. QTimer updateTimer; ///< Timer to update periodically the data display. }; /** * @} */ #endif diff --git a/CPP/HriPcController/main.cpp b/CPP/HriPcController/main.cpp index 73ec242..37426ac 100644 --- a/CPP/HriPcController/main.cpp +++ b/CPP/HriPcController/main.cpp @@ -1,50 +1,50 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "mainwindow.h" #include /** @defgroup HriPcController C++ control interface for the HRI board * @brief This interface can interact with a HRI board, by listing, reading, * writing, or plotting its shared variables. * * @addtogroup HriPcController * @{ */ /** * @brief Main function. * @param argc number of arguments. * @param argv arguments array. * @return 0 if the program exited normally, the error code otherwise. */ int main(int argc, char *argv[]) { QApplication a(argc, argv); a.setApplicationName("HRI_PC_Controller"); a.setOrganizationDomain("lsro.epfl.ch"); a.setOrganizationName("LSRO"); MainWindow w; w.show(); return a.exec(); } /** * @} */ diff --git a/CPP/HriPcController/mainwindow.cpp b/CPP/HriPcController/mainwindow.cpp index c63d415..9af4a4b 100644 --- a/CPP/HriPcController/mainwindow.cpp +++ b/CPP/HriPcController/mainwindow.cpp @@ -1,402 +1,402 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "mainwindow.h" #include "ui_mainwindow.h" #include #include #include #include #include #include #define GRAPH_UPDATE_PERIOD 50 ///< Plot window refresh period [ms]. #define SETTING_WINDOW_SIZE "window_size" /** * @brief Constructor * Setups the GUI, and try to connect to the HRI board. * @param parent parent of this widget. */ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { // ui->setupUi(this); variablesListLayout = new QGridLayout(); variablesListLayout->setAlignment(Qt::AlignTop); delete ui->scrollWidget->layout(); ui->scrollWidget->setLayout(variablesListLayout); chart = new QtCharts::QChart(); ui->graphicsView->setChart(chart); ui->graphicsView->setRenderHint(QPainter::Antialiasing); graphUpdateTimer.setInterval(GRAPH_UPDATE_PERIOD); graphUpdateTimer.setSingleShot(false); connect(&graphUpdateTimer, SIGNAL(timeout()), this, SLOT(updateGraph())); // syncVars = nullptr; // Resize the window to the previous size. if(settings.contains(SETTING_WINDOW_SIZE)) resize(settings.value(SETTING_WINDOW_SIZE).toSize()); // Ask for the COM port. QString comPortName; QStringList ports = HriBoard::getComPorts(); if(ports.size() == 0) { QMessageBox::critical(this, qApp->applicationName(), "No COM port."); exit(0); } else if(ports.size() == 1) comPortName = ports.first(); else { bool ok; QString portChoice = QInputDialog::getItem(this, qApp->applicationName(), "Serial port:", ports, 0, false, &ok); int portIndex = ports.indexOf(portChoice); if(portIndex == -1 || !ok) exit(0); comPortName = ports[portIndex]; } // connect(ui->clearButton, SIGNAL(clicked(bool)), this, SLOT(clearPlot())); connect(ui->logToFileCheckbox, SIGNAL(toggled(bool)), this, SLOT(onLogToFileCheckboxToggled())); // Establish the link with the HRI board. hriBoard.openLink(comPortName); connect(&hriBoard, SIGNAL(syncVarsListReceived(const QList&)), this, SLOT(onVarsListReceived(const QList&))); connect(&hriBoard, SIGNAL(syncVarUpdated(SyncVarBase*)), this, SLOT(onVarUpdated(SyncVarBase*))); } /** * @brief Destructor. */ MainWindow::~MainWindow() { // Store the window size. settings.setValue(SETTING_WINDOW_SIZE, size()); // delete ui; } /** * @brief Displays the SyncVars list, and starts the streaming. * @param syncVars SyncVars list. * @remark This slot function is called automatically by the HriBoard object, as * soon as the list is received from the board. */ void MainWindow::onVarsListReceived(const QList &syncVars) { this->syncVars = &syncVars; QLayoutItem *li; while((li = variablesListLayout->takeAt(0)) != nullptr) li->widget()->deleteLater(); syncVarsWidgets.clear(); for(int i=0; igetName()); variablesListLayout->addWidget(varsWidgets.name, i, 0); // Get button. if(syncVars[i]->getAccess() != WRITEONLY) { varsWidgets.getButton = new QPushButton("Get"); variablesListLayout->addWidget(varsWidgets.getButton, i, 1); connect(varsWidgets.getButton, SIGNAL(clicked(bool)), this, SLOT(onGetButtonPressed())); } // Value field. varsWidgets.valueSpinBox = new QDoubleSpinBox(); varsWidgets.valueSpinBox->setDecimals(4); varsWidgets.valueSpinBox->setMinimum(std::numeric_limits::lowest()); varsWidgets.valueSpinBox->setMaximum(std::numeric_limits::max()); variablesListLayout->addWidget(varsWidgets.valueSpinBox, i, 2); if(syncVars[i]->getAccess() == READONLY) { varsWidgets.valueSpinBox->setReadOnly(true); varsWidgets.valueSpinBox->setButtonSymbols(QAbstractSpinBox::NoButtons); } else { connect(varsWidgets.valueSpinBox, SIGNAL(editingFinished()), this, SLOT(onSetButtonPressed())); } // Set button. if(syncVars[i]->getAccess() != READONLY) { varsWidgets.setButton = new QPushButton("Set"); variablesListLayout->addWidget(varsWidgets.setButton, i, 3); connect(varsWidgets.setButton, SIGNAL(clicked(bool)), this, SLOT(onSetButtonPressed())); } // Stream checkbox. if(syncVars[i]->getAccess() != WRITEONLY) { varsWidgets.streamCheckbox = new QCheckBox("Stream"); variablesListLayout->addWidget(varsWidgets.streamCheckbox, i, 4); connect(varsWidgets.streamCheckbox, SIGNAL(toggled(bool)), this, SLOT(onStreamCheckboxToggled())); } if(syncVars[i]->getAccess() != WRITEONLY) { varsWidgets.scaleSpinbox = new QDoubleSpinBox(); variablesListLayout->addWidget(varsWidgets.scaleSpinbox, i, 5); varsWidgets.scaleSpinbox->setPrefix("x"); varsWidgets.scaleSpinbox->setValue(1.0); varsWidgets.scaleSpinbox->setRange(-1000000.0, 1000000.0); } syncVarsWidgets.append(varsWidgets); } // clearPlot(); } /** * @brief Updates the "value" field of a SyncVar. * @param var the SyncVar corresponding to the "value" field to update. */ void MainWindow::onVarUpdated(SyncVarBase *var) { int varIndex = syncVars->indexOf(var); if(varIndex >= 0 && varIndex < syncVarsWidgets.size()) syncVarsWidgets[varIndex].valueSpinBox->setValue(var->toDouble()); } /** * @brief Gets the value of a SyncVar whose the "Get" button was pressed. * @warning This function should only be called by the "Get" button signal. */ void MainWindow::onGetButtonPressed() { int varIndex = getVarIndexFromWidget((QWidget*)sender()); hriBoard.readRemoteVar(syncVars->at(varIndex)); } /** * @brief Sets the value of a SyncVar whose the "Set" button was pressed. * @warning This function should only be called by the "Set" button signal. */ void MainWindow::onSetButtonPressed() { int varIndex = getVarIndexFromWidget((QWidget*)sender()); SyncVarBase *sv = syncVars->at(varIndex); sv->fromDouble(syncVarsWidgets[varIndex].valueSpinBox->value()); hriBoard.writeRemoteVar(sv); } /** * @brief Updates the list of SyncVars to stream from the checkboxes' states. */ void MainWindow::onStreamCheckboxToggled() { // Setup the streaming. QList varsToStream; for(int i=0; iisChecked()) varsToStream.append(syncVars->at(i)); } hriBoard.setStreamedVars(varsToStream, &streamedVarsValuesBuffer); // Setup the graph. streamedVarsValuesBuffer.clear(); chart->removeAllSeries(); linesSeries.clear(); for(SyncVarBase *sv : varsToStream) { QtCharts::QLineSeries *series = new QtCharts::QLineSeries(); series->setName(sv->getName()); chart->addSeries(series); linesSeries.append(series); } chart->createDefaultAxes(); chart->axisX()->setTitleText("Time [s]"); // Setup the update timer. if(varsToStream.isEmpty()) graphUpdateTimer.stop(); else graphUpdateTimer.start(); } /** * @brief Clears the plot window. */ void MainWindow::clearPlot() { for(QtCharts::QLineSeries *ls : linesSeries) ls->clear(); } /** * @brief Starts or stops the streamed variables logging to file. */ void MainWindow::onLogToFileCheckboxToggled() { if(ui->logToFileCheckbox->isChecked()) { if(!hriBoard.startLoggingToFile()) { QSignalBlocker signalBlocker(ui->logToFileCheckbox); ui->logToFileCheckbox->setChecked(false); } } else hriBoard.stopLoggingToFile(); } /** * @brief Refreshes the plot frame. */ void MainWindow::updateGraph() { // Get the variables scales. const QList &streamedVars = hriBoard.getStreamedVars(); QList plotScales; for(SyncVarBase* sv : streamedVars) { int varIndex = syncVars->indexOf(sv); if(varIndex != -1) plotScales.append(syncVarsWidgets[varIndex].scaleSpinbox->value()); } // Add the new points to the graph series. int decimationCounter = 0; while(!streamedVarsValuesBuffer.isEmpty()) { if(decimationCounter <= 0) { decimationCounter = ui->plotDecimSpinbox->value(); QList &values = streamedVarsValuesBuffer.first(); for(int i=0; iappend(values[0], values[i+1] * plotScales[i]); } else decimationCounter--; streamedVarsValuesBuffer.removeFirst(); } // Remove the oldest points, to limit the number of points displayed. for(QtCharts::QLineSeries *ls : linesSeries) { if(ls->count() > ui->nPointsSpinbox->value()) ls->removePoints(0, ls->count() - ui->nPointsSpinbox->value()); } // Compute the display range. if(linesSeries.size() > 0 && linesSeries.first()->count() > 0) { // Compute the horizontal (time) range. double firstTime = linesSeries.first()->points().first().x(); double lastTime = linesSeries.first()->points().last().x(); chart->axisX()->setRange(firstTime, lastTime); // Compute the vertical range. double min = std::numeric_limits::max(); double max = std::numeric_limits::lowest(); for(QtCharts::QLineSeries *ls : linesSeries) { QVector points = ls->pointsVector(); for(QPointF p : points) { if(p.y() < min) min = p.y(); if(p.y() > max) max = p.y(); } } if(min == max) { min -= 1.0; max += 1.0; } else { min -= (max-min) * 0.05; // Add a 5% margin at the top and the max += (max-min) * 0.05; // bottom. } chart->axisY()->setRange(min, max); } } /** * @brief Gets the variable index from a widget of the SyncVars list. * @param widget the widget belonging to the variables list. * @return The index of the SyncVar corresponding to the given widget. */ int MainWindow::getVarIndexFromWidget(QWidget *widget) { int index = variablesListLayout->indexOf(widget); int row, column, rowSpan, columnSpan; variablesListLayout->getItemPosition(index, &row, &column, &rowSpan, &columnSpan); return row; } diff --git a/CPP/HriPcController/mainwindow.h b/CPP/HriPcController/mainwindow.h index 5d2329a..10b06ab 100644 --- a/CPP/HriPcController/mainwindow.h +++ b/CPP/HriPcController/mainwindow.h @@ -1,98 +1,98 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef MAINWINDOW_H #define MAINWINDOW_H #include #include #include #include #include #include #include #include #include #include #include #include #include "../HriBoardLib/hriboard.h" #include "../HriBoardLib/syncvar.h" namespace Ui { class MainWindow; } /** * @addtogroup HriPcController * @{ */ struct SyncVarWidgetsLine { QLabel *name; QPushButton *getButton, *setButton; QDoubleSpinBox *valueSpinBox; QCheckBox *streamCheckbox; QDoubleSpinBox *scaleSpinbox; }; /** * @brief Qt MainWindow with widgets to interact with SyncVars. */ class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); public slots: void onVarsListReceived(const QList &syncVars); void onVarUpdated(SyncVarBase *var); void onGetButtonPressed(); void onSetButtonPressed(); void onStreamCheckboxToggled(); void clearPlot(); void onLogToFileCheckboxToggled(); void updateGraph(); private: int getVarIndexFromWidget(QWidget* widget); Ui::MainWindow *ui; ///< Graphical user interface from the ui file. HriBoard hriBoard; ///< HRI board interface. const QList *syncVars; QList syncVarsWidgets; QSettings settings; QGridLayout *variablesListLayout; QtCharts::QChart *chart; QList linesSeries; QTimer graphUpdateTimer; QLinkedList> streamedVarsValuesBuffer; }; /** * @} */ #endif diff --git a/Firmware/src/communication.c b/Firmware/src/communication.c index 02e6528..ed564d8 100644 --- a/Firmware/src/communication.c +++ b/Firmware/src/communication.c @@ -1,983 +1,983 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "communication.h" #include "drivers/callback_timers.h" #include "drivers/adc.h" #include "drivers/dac.h" #include "drivers/incr_encoder.h" #include "drivers/hall.h" #include "drivers/uart.h" #include "lib/basic_filter.h" #include "lib/pid.h" #include "lib/utils.h" #include #include "torque_regulator.h" #define STREAMING_PERIOD 1000 // [us]. uint32_t selectedVariablesToStream; // Bitfield that indicates for each variable if it should be streamed or not. uint8_t txBuffer[1024]; #define COMM_BUFFER_SIZE 4096 #define DEBUG_MESSAGE_BUFFER_SIZE 1024 uint8_t comm_packetTxBuffer[COMM_BUFFER_SIZE]; char comm_debugMessageBuffer[DEBUG_MESSAGE_BUFFER_SIZE]; cb_CircularBuffer *comm_rxQueue; uint8_t rxCurrentMessageType = PC_MESSAGE_DO_NOTHING; // Current message type for RX bytes. uint32_t rxBytesCount; // Number of received bytes for the current message. uint8_t firstHalfByte; // First half of the data byte to receive. uint8_t rxDataBytesBuffer[32]; // Data bytes received (ready to use, bytes already merged). // SyncVar-related vars. #define N_SYNCVARS_MAX 25 comm_SyncVar comm_syncVars[N_SYNCVARS_MAX]; uint8_t comm_nSyncVars; volatile bool comm_varListLocked; uint8_t comm_streamId; uint8_t comm_nVarsToStream; comm_SyncVar const* comm_streamedVars[N_SYNCVARS_MAX]; extern volatile uint32_t hapt_timestamp; // [us]. // Private functions. void comm_SendPacket(uint8_t messageType, uint8_t *data, uint16_t dataLength); void comm_HandleByte(uint8_t rxData); void comm_Stream(void); void comm_GetVar(comm_SyncVar const *syncVar, uint8_t *varValueData); void comm_SetVar(comm_SyncVar *syncVar, uint8_t *varValueData); void comm_SendVarsList(void); /** * @brief Init the communication manager. */ void comm_Init(void) { comm_nSyncVars = 0; comm_varListLocked = false; comm_streamId = 0; comm_nVarsToStream = 0; // Setup the UART peripheral, and specify the function that will be called // each time a byte is received. uart_Init(); comm_rxQueue = uart_GetRxQueue(); rxCurrentMessageType = PC_MESSAGE_DO_NOTHING; // Make the streaming function periodically called by the timer 7. cbt_SetCommLoopTimer(comm_Stream, STREAMING_PERIOD); } /** * @brief Updates the communication manager. * Send the bytes waiting in the TX buffer, and process the bytes received. */ void comm_Step(void) { // Send the bytes in the TX queue, even if it is not full, to avoid latency. uart_FlushTx(); // Interpret the bytes in the RX queue. uart_Step(); while(!cb_IsEmpty(comm_rxQueue)) comm_HandleByte(cb_Pull(comm_rxQueue)); } /** * @brief Sends a packet to notify the PC that the board just (re)started. * This informs the PC software that the board is ready, and that the variables * list can be retrieved. */ void comm_NotifyReady(void) { comm_SendPacket(STM_MESSAGE_START_INFO, NULL, 0); } /** * @brief Generates and sends a data streaming packet. */ void comm_Stream() { // If the data streaming is enabled, send a stream packet to the PC. if(comm_nVarsToStream > 0) { uint8_t i; int nDataBytesToSend = 0; // Stream ID. txBuffer[nDataBytesToSend] = comm_streamId; nDataBytesToSend++; // Timestamp. memcpy(&txBuffer[nDataBytesToSend], (uint32_t*)&hapt_timestamp, sizeof(hapt_timestamp)); nDataBytesToSend += sizeof(hapt_timestamp); // SyncVars values. for(i=0; isize; } comm_SendPacket(STM_MESSAGE_STREAMING_PACKET, txBuffer, nDataBytesToSend); } } /** * @brief Gets the value of a SyncVar. * @param syncVar: address of the SyncVar to get the value from. * @param varValueData: start address of an array to copy the raw bytes of the * value of the SyncVar. */ void comm_GetVar(comm_SyncVar const *syncVar, uint8_t *varValueData) { comm_SyncVar const *v = syncVar; if(v->usesVarAddress) memcpy(varValueData, v->address, v->size); else { switch(v->type) { case BOOL: { bool tmpBool = ((bool (*)(void))v->getFunc)(); memcpy(varValueData, &tmpBool, v->size); } break; case UINT8: { uint8_t tmpUint8 = ((uint8_t (*)(void))v->getFunc)(); memcpy(varValueData, &tmpUint8, v->size); } break; case INT8: { int8_t tmpInt8 = ((int8_t (*)(void))v->getFunc)(); memcpy(varValueData, &tmpInt8, v->size); } break; case UINT16: { uint16_t tmpUint16 = ((uint16_t (*)(void))v->getFunc)(); memcpy(varValueData, &tmpUint16, v->size); } break; case INT16: { int16_t tmpInt16 = ((int16_t (*)(void))v->getFunc)(); memcpy(varValueData, &tmpInt16, v->size); } break; case UINT32: { uint32_t tmpUint32 = ((uint32_t (*)(void))v->getFunc)(); memcpy(varValueData, &tmpUint32, v->size); } break; case INT32: { int32_t tmpInt32 = ((int32_t (*)(void))v->getFunc)(); memcpy(varValueData, &tmpInt32, v->size); } break; case UINT64: { uint32_t tmpUint64 = ((uint64_t (*)(void))v->getFunc)(); memcpy(varValueData, &tmpUint64, v->size); } break; case INT64: { int32_t tmpInt64 = ((int64_t (*)(void))v->getFunc)(); memcpy(varValueData, &tmpInt64, v->size); } break; case FLOAT32: { float32_t tmpFloat = ((float32_t (*)(void))v->getFunc)(); memcpy(varValueData, &tmpFloat, v->size); } break; case FLOAT64: { double tmpDouble = ((double (*)(void))v->getFunc)(); memcpy(varValueData, &tmpDouble, v->size); } break; } } } /** * @brief Sets the value of a SyncVar. * @param syncVar: address of the SyncVar to set the value. * @param varValueData: start address of an array to copy the raw bytes to the * SyncVar value. */ void comm_SetVar(comm_SyncVar *syncVar, uint8_t *varValueData) { comm_SyncVar *v = syncVar; if(v->usesVarAddress) { if(v->access != READONLY) memcpy(v->address, varValueData, v->size); } else { if(v->setFunc == NULL) return; switch(v->type) { case BOOL: { bool tmp; memcpy(&tmp, varValueData, v->size); ((void (*)(bool))v->setFunc)(tmp); } break; case UINT8: { uint8_t tmp; memcpy(&tmp, varValueData, v->size); ((void (*)(uint8_t))v->setFunc)(tmp); } break; case INT8: { int8_t tmp; memcpy(&tmp, varValueData, v->size); ((void (*)(int8_t))v->setFunc)(tmp); } break; case UINT16: { uint16_t tmp; memcpy(&tmp, varValueData, v->size); ((void (*)(uint16_t))v->setFunc)(tmp); } break; case INT16: { int16_t tmp; memcpy(&tmp, varValueData, v->size); ((void (*)(int16_t))v->setFunc)(tmp); } break; case UINT32: { uint32_t tmp; memcpy(&tmp, varValueData, v->size); ((void (*)(uint32_t))v->setFunc)(tmp); } break; case INT32: { int32_t tmp; memcpy(&tmp, varValueData, v->size); ((void (*)(int32_t))v->setFunc)(tmp); } break; case UINT64: { uint64_t tmp; memcpy(&tmp, varValueData, v->size); ((void (*)(uint64_t))v->setFunc)(tmp); } break; case INT64: { int64_t tmp; memcpy(&tmp, varValueData, v->size); ((void (*)(int64_t))v->setFunc)(tmp); } break; case FLOAT32: { float32_t tmp; memcpy(&tmp, varValueData, v->size); ((void (*)(float32_t))v->setFunc)(tmp); } break; case FLOAT64: { double tmp; memcpy(&tmp, varValueData, v->size); ((void (*)(double))v->setFunc)(tmp); } break; } } } /** * @brief Sends Packet with a header and data bytes. * @param type: message type ("header" of the message). * @param data: array of data bytes to be sent ("content" of the message). * @param dataLength: number of data bytes to be sent. */ void comm_SendPacket(uint8_t type, uint8_t *data, uint16_t dataLength) { int i=0; uint8_t *p = comm_packetTxBuffer; *p = ((1<<7) | type); p++; for(i=0; i> 4); // Most significant bits. p++; *p = ((data[i]&0x0f)); // Least significant bits. p++; } uart_SendBytesAsync(comm_packetTxBuffer, dataLength*2+1); } /** * @brief Processes the received byte to interpret the messages. * @param rxData: the byte to be processed and interpreted. */ void comm_HandleByte(uint8_t rxData) { if(rxData & (1<<7)) // The start byte has the most significant bit high. { rxCurrentMessageType = (rxData & ~(1<<7)); // Remove the start bit. rxBytesCount = 0; } else rxBytesCount++; if(rxBytesCount % 2 == 1) // First half of the data byte has been received. firstHalfByte = rxData; // Store it until the second half arrives. else // Second half of the data byte has been received (or no data bytes yet). { int dataBytesReady = rxBytesCount/2; rxDataBytesBuffer[dataBytesReady-1] = (firstHalfByte<<4) + (rxData & 0xf); switch(rxCurrentMessageType) { case PC_MESSAGE_DO_NOTHING: if(dataBytesReady == 0) { // Do nothing. } break; case PC_MESSAGE_PING: if(dataBytesReady == 0) comm_SendPacket(STM_MESSAGE_PINGBACK, NULL, 0); break; case PC_MESSAGE_GET_VARS_LIST: if(dataBytesReady == 0 && comm_varListLocked) { int16_t i, length; uint8_t *p = &txBuffer[0]; *p = (uint8_t)comm_nSyncVars; p++; for(i=0; i= comm_nSyncVars) return; v = &comm_syncVars[variableIndex]; // If the variable is not readable, ignore the request. if((v->usesVarAddress && v->access == WRITEONLY) || (!v->usesVarAddress && v->getFunc == NULL)) { break; } // Prepare the message to be sent to the PC. // First byte: variable index. txBuffer[0] = variableIndex; // Following bytes: actual value of the variable. comm_GetVar(v, &txBuffer[1]); // Send the message to the PC. comm_SendPacket(STM_MESSAGE_VAR, txBuffer, 1 + v->size); } break; case PC_MESSAGE_SET_VAR: if(dataBytesReady >= 1) { comm_SyncVar *v; uint8_t variableIndex; // Extract the index (that indicates which variable will change), // and the new value of the variable. variableIndex = rxDataBytesBuffer[0]; // If the variable index is out of range, ignore the request. if(variableIndex >= comm_nSyncVars) return; v = &comm_syncVars[variableIndex]; // Set the selected variable with the new value. if(dataBytesReady == 1 + v->size) comm_SetVar(v, &rxDataBytesBuffer[1]); } break; case PC_MESSAGE_SET_STREAMED_VAR: if(dataBytesReady >= 1) { int i; comm_nVarsToStream = rxDataBytesBuffer[0]; if(dataBytesReady != 1 + 1 + comm_nVarsToStream) return; comm_streamId = rxDataBytesBuffer[1]; for(i=0; i= N_SYNCVARS_MAX) { comm_SendDebugMessage("Warning: can't add the \"%s\" SyncVar, because " "the list is full.", name); return; } // Trim the name if it is too long. if(strlen(name) > SYNCVAR_NAME_SIZE - 1) { memcpy(v.name, name, SYNCVAR_NAME_SIZE - 1); v.name[SYNCVAR_NAME_SIZE - 1] = '\0'; } else strcpy(v.name, name); // Build the SyncVar. v.address = address; v.type = type; v.size = size; v.access = access; v.usesVarAddress = true; // Add the SyncVar to the list. comm_syncVars[comm_nSyncVars] = v; comm_nSyncVars++; } /** * @brief Adds a monitored variable to the list, with getter/setter functions. * If the getter and setter are set to an actual function address (not NULL), * the variable will be READWRITE. If the getter is NULL but setter is not NULL, * the variable will be WRITEONLY. If the getter is not NULL the setter is NULL, * the variable will be READONLY. If the getter and setter are both NULL, the * variable will not be added to the variables list. * @param name: the description of the variable, as it should be displayed to * the user. It should also include the unit, if relevant. * @param type: the type of the variable. * @param size: the size of the variable [bytes]. * @param getFunc: function pointer on the getter function. * @param setFunc: function pointer on the setter function. */ void comm_monitorVarFunc(const char name[], comm_VarType type, uint8_t size, void (*getFunc)(void), void (*setFunc)(void)) { comm_SyncVar v; // Adding a variable to the list is not allowed if it has been locked. if(comm_varListLocked) { comm_SendDebugMessage("Warning: can't add the \"%s\" SyncVar, because " "the list has already been locked.", name); return; } // Adding a variable to the list is not allowed if it is full. if(comm_nSyncVars >= N_SYNCVARS_MAX) { comm_SendDebugMessage("Warning: can't add the \"%s\" SyncVar, because " "the list is full.", name); return; } // Trim the name if it is too long. if(strlen(name) > SYNCVAR_NAME_SIZE - 1) { memcpy(&v.name, &name, SYNCVAR_NAME_SIZE - 1); v.name[SYNCVAR_NAME_SIZE - 1] = '\0'; } else strcpy(v.name, name); // Build the SyncVar. v.getFunc = getFunc; v.setFunc = setFunc; v.type = type; v.size = size; v.usesVarAddress = false; // Determine the variable access. if(getFunc != NULL && setFunc == NULL) v.access = READONLY; else if(getFunc == NULL && setFunc != NULL) v.access = WRITEONLY; else if(getFunc != NULL && setFunc != NULL) v.access = READWRITE; else return; // No function provided at all, ignoring var. // Add the SyncVar to the list. comm_syncVars[comm_nSyncVars] = v; comm_nSyncVars++; } /** * @brief Adds a monitored bool variable to the list, with its address. * @param name: the description of the variable, as it should be displayed to * the user. It should also include the unit, if relevant. * @param address a pointer to the variable to monitor. * @param access: the access rights to this variable (READONLY, WRITEONLY, or * READWRITE). */ void comm_monitorBool(const char name[], bool *address, comm_VarAccess access) { comm_monitorVar(name, address, BOOL, 1, access); } /** * @brief Adds a monitored uint8_t variable to the list, with its address. * @param name: the description of the variable, as it should be displayed to * the user. It should also include the unit, if relevant. * @param address a pointer to the variable to monitor. * @param access: the access rights to this variable (READONLY, WRITEONLY, or * READWRITE). */ void comm_monitorUint8(const char name[], uint8_t *address, comm_VarAccess access) { comm_monitorVar(name, address, UINT8, 1, access); } /** * @brief Adds a monitored int8_t variable to the list, with its address. * @param name: the description of the variable, as it should be displayed to * the user. It should also include the unit, if relevant. * @param address a pointer to the variable to monitor. * @param access: the access rights to this variable (READONLY, WRITEONLY, or * READWRITE). */ void comm_monitorInt8(const char name[], int8_t *address, comm_VarAccess access) { comm_monitorVar(name, address, INT8, 1, access); } /** * @brief Adds a monitored uint16_t variable to the list, with its address. * @param name: the description of the variable, as it should be displayed to * the user. It should also include the unit, if relevant. * @param address a pointer to the variable to monitor. * @param access: the access rights to this variable (READONLY, WRITEONLY, or * READWRITE). */ void comm_monitorUint16(const char name[], uint16_t *address, comm_VarAccess access) { comm_monitorVar(name, address, UINT16, 2, access); } /** * @brief Adds a monitored int16_t variable to the list, with its address. * @param name: the description of the variable, as it should be displayed to * the user. It should also include the unit, if relevant. * @param address a pointer to the variable to monitor. * @param access: the access rights to this variable (READONLY, WRITEONLY, or * READWRITE). */ void comm_monitorInt16(const char name[], int16_t *address, comm_VarAccess access) { comm_monitorVar(name, address, INT16, 2, access); } /** * @brief Adds a monitored uint32_t variable to the list, with its address. * @param name: the description of the variable, as it should be displayed to * the user. It should also include the unit, if relevant. * @param address a pointer to the variable to monitor. * @param access: the access rights to this variable (READONLY, WRITEONLY, or * READWRITE). */ void comm_monitorUint32(const char name[], uint32_t *address, comm_VarAccess access) { comm_monitorVar(name, address, UINT32, 4, access); } /** * @brief Adds a monitored int32_t variable to the list, with its address. * @param name: the description of the variable, as it should be displayed to * the user. It should also include the unit, if relevant. * @param address a pointer to the variable to monitor. * @param access: the access rights to this variable (READONLY, WRITEONLY, or * READWRITE). */ void comm_monitorInt32(const char name[], int32_t *address, comm_VarAccess access) { comm_monitorVar(name, address, INT32, 4, access); } /** * @brief Adds a monitored uint64_t variable to the list, with its address. * @param name: the description of the variable, as it should be displayed to * the user. It should also include the unit, if relevant. * @param address a pointer to the variable to monitor. * @param access: the access rights to this variable (READONLY, WRITEONLY, or * READWRITE). */ void comm_monitorUint64(const char name[], uint64_t *address, comm_VarAccess access) { comm_monitorVar(name, address, UINT64, 8, access); } /** * @brief Adds a monitored int64_t variable to the list, with its address. * @param name: the description of the variable, as it should be displayed to * the user. It should also include the unit, if relevant. * @param address a pointer to the variable to monitor. * @param access: the access rights to this variable (READONLY, WRITEONLY, or * READWRITE). */ void comm_monitorInt64(const char name[], int64_t *address, comm_VarAccess access) { comm_monitorVar(name, address, INT64, 8, access); } /** * @brief Adds a monitored float variable to the list, with its address. * @param name: the description of the variable, as it should be displayed to * the user. It should also include the unit, if relevant. * @param address a pointer to the variable to monitor. * @param access: the access rights to this variable (READONLY, WRITEONLY, or * READWRITE). */ void comm_monitorFloat(const char name[], float *address, comm_VarAccess access) { comm_monitorVar(name, address, FLOAT32, 4, access); } /** * @brief Adds a monitored double variable to the list, with its address. * @param name: the description of the variable, as it should be displayed to * the user. It should also include the unit, if relevant. * @param address a pointer to the variable to monitor. * @param access: the access rights to this variable (READONLY, WRITEONLY, or * READWRITE). */ void comm_monitorDouble(const char name[], double *address, comm_VarAccess access) { comm_monitorVar(name, address, FLOAT64, 8, access); } /** * @brief Adds a monitored bool variable to the list, with getter/setter. * @param name: the description of the variable, as it should be displayed to * the user. It should also include the unit, if relevant. * @param getFunc: function pointer on the getter function. * @param setFunc: function pointer on the setter function. */ void comm_monitorBoolFunc(const char name[], bool (*getFunc)(void), void (*setFunc)(bool)) { comm_monitorVarFunc(name, BOOL, 1, (void (*)(void))getFunc, (void (*)(void))setFunc); } /** * @brief Adds a monitored uint8_t variable to the list, with getter/setter. * @param name: the description of the variable, as it should be displayed to * the user. It should also include the unit, if relevant. * @param getFunc: function pointer on the getter function. * @param setFunc: function pointer on the setter function. */ void comm_monitorUint8Func(const char name[], uint8_t (*getFunc)(void), void (*setFunc)(uint8_t)) { comm_monitorVarFunc(name, UINT8, 1, (void (*)(void))getFunc, (void (*)(void))setFunc); } /** * @brief Adds a monitored int8_t variable to the list, with getter/setter. * @param name: the description of the variable, as it should be displayed to * the user. It should also include the unit, if relevant. * @param getFunc: function pointer on the getter function. * @param setFunc: function pointer on the setter function. */ void comm_monitorInt8Func(const char name[], int8_t (*getFunc)(void), void (*setFunc)(int8_t)) { comm_monitorVarFunc(name, INT8, 1, (void (*)(void))getFunc, (void (*)(void))setFunc); } /** * @brief Adds a monitored uint16_t variable to the list, with getter/setter. * @param name: the description of the variable, as it should be displayed to * the user. It should also include the unit, if relevant. * @param getFunc: function pointer on the getter function. * @param setFunc: function pointer on the setter function. */ void comm_monitorUint16Func(const char name[], uint16_t (*getFunc)(void), void (*setFunc)(uint16_t)) { comm_monitorVarFunc(name, UINT16, 2, (void (*)(void))getFunc, (void (*)(void))setFunc); } /** * @brief Adds a monitored int16_t variable to the list, with getter/setter. * @param name: the description of the variable, as it should be displayed to * the user. It should also include the unit, if relevant. * @param getFunc: function pointer on the getter function. * @param setFunc: function pointer on the setter function. */ void comm_monitorInt16Func(const char name[], int16_t (*getFunc)(void), void (*setFunc)(int16_t)) { comm_monitorVarFunc(name, INT16, 2, (void (*)(void))getFunc, (void (*)(void))setFunc); } /** * @brief Adds a monitored uint32_t variable to the list, with getter/setter. * @param name: the description of the variable, as it should be displayed to * the user. It should also include the unit, if relevant. * @param getFunc: function pointer on the getter function. * @param setFunc: function pointer on the setter function. */ void comm_monitorUint32Func(const char name[], uint32_t (*getFunc)(void), void (*setFunc)(uint32_t)) { comm_monitorVarFunc(name, UINT32, 4, (void (*)(void))getFunc, (void (*)(void))setFunc); } /** * @brief Adds a monitored int32_t variable to the list, with getter/setter. * @param name: the description of the variable, as it should be displayed to * the user. It should also include the unit, if relevant. * @param getFunc: function pointer on the getter function. * @param setFunc: function pointer on the setter function. */ void comm_monitorInt32Func(const char name[], int32_t (*getFunc)(void), void (*setFunc)(int32_t)) { comm_monitorVarFunc(name, INT32, 4, (void (*)(void))getFunc, (void (*)(void))setFunc); } /** * @brief Adds a monitored uint64_t variable to the list, with getter/setter. * @param name: the description of the variable, as it should be displayed to * the user. It should also include the unit, if relevant. * @param getFunc: function pointer on the getter function. * @param setFunc: function pointer on the setter function. */ void comm_monitorUint64Func(const char name[], uint64_t (*getFunc)(void), void (*setFunc)(uint64_t)) { comm_monitorVarFunc(name, UINT64, 8, (void (*)(void))getFunc, (void (*)(void))setFunc); } /** * @brief Adds a monitored int64_t variable to the list, with getter/setter. * @param name: the description of the variable, as it should be displayed to * the user. It should also include the unit, if relevant. * @param getFunc: function pointer on the getter function. * @param setFunc: function pointer on the setter function. */ void comm_monitorInt64Func(const char name[], int64_t (*getFunc)(void), void (*setFunc)(int64_t)) { comm_monitorVarFunc(name, INT64, 8, (void (*)(void))getFunc, (void (*)(void))setFunc); } /** * @brief Adds a monitored float variable to the list, with getter/setter. * @param name: the description of the variable, as it should be displayed to * the user. It should also include the unit, if relevant. * @param getFunc: function pointer on the getter function. * @param setFunc: function pointer on the setter function. */ void comm_monitorFloatFunc(const char name[], float (*getFunc)(void), void (*setFunc)(float)) { comm_monitorVarFunc(name, FLOAT32, 4, (void (*)(void))getFunc, (void (*)(void))setFunc); } /** * @brief Adds a monitored double variable to the list, with getter/setter. * @param name: the description of the variable, as it should be displayed to * the user. It should also include the unit, if relevant. * @param getFunc: function pointer on the getter function. * @param setFunc: function pointer on the setter function. */ void comm_monitorDoubleFunc(const char name[], double (*getFunc)(void), void (*setFunc)(double)) { comm_monitorVarFunc(name, FLOAT64, 8, (void (*)(void))getFunc, (void (*)(void))setFunc); } /** * @brief Locks the monitored variables, so it can be used. * After the call to this function, adding variables will not be possible * anymore. The PC will not be able to get the variables list until this * function is called. */ void comm_LockSyncVarsList(void) { comm_varListLocked = true; } /** * @brief Generates and sends a packet describing the monitored variables. */ void comm_SendVarsList(void) { int i; uint8_t *p; uint16_t buffLength = 1 + comm_nSyncVars * (SYNCVAR_NAME_SIZE + 1 + 1); p = &txBuffer[0]; *p = comm_nSyncVars; p++; for(i=0; i /** @defgroup Communication Main / Communication * @brief Control the communication with the computer. * * This module controls all the communication logic between the board and the * computer. It uses a specific communication protocol between the * computer and the board, with a system of messages. Thanks to this, the * the MATLAB application is able to get the value of selected variables on the * STM, and is even capable of modifiying them remotely. * * Make sure that the files communication.h/.c are up-to-date with the Excel * spreadsheet "Protocol description.xlsx". * * Call comm_Init() to setup this module. Its interrupt function will be called * automatically when a message arrives, or periodically when the data * streaming is enabled. * * @addtogroup Communication * @{ */ typedef struct { char name[SYNCVAR_NAME_SIZE]; void *address; comm_VarType type; uint8_t size; comm_VarAccess access; bool usesVarAddress; void (*getFunc)(void); void (*setFunc)(void); } comm_SyncVar; void comm_Init(void); void comm_Step(void); void comm_NotifyReady(void); void comm_monitorVar(const char name[], void *address, comm_VarType type, uint8_t size, comm_VarAccess access); void comm_monitorVarFunc(const char name[], comm_VarType type, uint8_t size, void (*getFunc)(void), void (*setFunc)(void)); void comm_monitorBool(const char name[], bool *address, comm_VarAccess access); void comm_monitorUint8(const char name[], uint8_t *address, comm_VarAccess access); void comm_monitorInt8(const char name[], int8_t *address, comm_VarAccess access); void comm_monitorUint16(const char name[], uint16_t *address, comm_VarAccess access); void comm_monitorInt16(const char name[], int16_t *address, comm_VarAccess access); void comm_monitorUint32(const char name[], uint32_t *address, comm_VarAccess access); void comm_monitorInt32(const char name[], int32_t *address, comm_VarAccess access); void comm_monitorUint64(const char name[], uint64_t *address, comm_VarAccess access); void comm_monitorInt64(const char name[], int64_t *address, comm_VarAccess access); void comm_monitorFloat(const char name[], float *address, comm_VarAccess access); void comm_monitorDouble(const char name[], double *address, comm_VarAccess access); void comm_monitorBoolFunc(const char name[], bool (*getFunc)(void), void (*setFunc)(bool)); void comm_monitorUint8Func(const char name[], uint8_t (*getFunc)(void), void (*setFunc)(uint8_t)); void comm_monitorInt8Func(const char name[], int8_t (*getFunc)(void), void (*setFunc)(int8_t)); void comm_monitorUint16Func(const char name[], uint16_t (*getFunc)(void), void (*setFunc)(uint16_t)); void comm_monitorInt16Func(const char name[], int16_t (*getFunc)(void), void (*setFunc)(int16_t)); void comm_monitorUint32Func(const char name[], uint32_t (*getFunc)(void), void (*setFunc)(uint32_t)); void comm_monitorInt32Func(const char name[], int32_t (*getFunc)(void), void (*setFunc)(int32_t)); void comm_monitorUint64Func(const char name[], uint64_t (*getFunc)(void), void (*setFunc)(uint64_t)); void comm_monitorInt64Func(const char name[], int64_t (*getFunc)(void), void (*setFunc)(int64_t)); void comm_monitorFloatFunc(const char name[], float (*getFunc)(void), void (*setFunc)(float)); void comm_monitorDoubleFunc(const char name[], double (*getFunc)(void), void (*setFunc)(double)); void comm_LockSyncVarsList(void); void comm_SendDebugMessage(const char *format, ...); /** * @brief Sends a debug message to the computer, with decimation. * This macro is useful to print human-readable text, in a fast loop, to avoid * overloading the communication bus, or the computer. * @param decimation this macro will actually print once out of decimation, and * do nothing otherwise. * @param format format string. See the printf() documentation for format * specification. * @param ... variables to be printed in the format string. */ #define comm_SendDebugMessageDecimated(decimation, format, ...) \ do \ { \ static int comm_decim##__COUNTER__ = 0; \ if(comm_decim##__COUNTER__++ % decimation == 0) \ { \ comm_SendDebugMessage(format, ##__VA_ARGS__); \ } \ } while(0) /** * @} */ #endif diff --git a/Firmware/src/definitions.h b/Firmware/src/definitions.h index 0cb1b23..f56cd3b 100644 --- a/Firmware/src/definitions.h +++ b/Firmware/src/definitions.h @@ -1,64 +1,64 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef DEF_DEFINITIONS_H #define DEF_DEFINITIONS_H #include // Message IDs sent by host (PC) to the device (STM). typedef enum { PC_MESSAGE_DO_NOTHING = 0, ///< Do nothing. PC_MESSAGE_PING, ///< Request the board an answer, to check the connection status. PC_MESSAGE_GET_VARS_LIST, ///< Request the SyncVars list. PC_MESSAGE_SET_STREAMED_VAR, ///< Set the variables to be streamed continuously. PC_MESSAGE_GET_VAR, ///< Request the device to send the selected value. PC_MESSAGE_SET_VAR ///< Set the selected variable. } comm_PcMessage; // Message IDs sent by device (STM) to the device (PC). typedef enum { STM_MESSAGE_PINGBACK = 0, ///< Response to a ping request. STM_MESSAGE_VAR, ///< Variable state. STM_MESSAGE_STREAMING_PACKET, ///< Streaming packet. STM_MESSAGE_DEBUG_TEXT, ///< Debug text message. STM_MESSAGE_VARS_LIST, ///< Monitored variables list. STM_MESSAGE_START_INFO ///< Notification that the board has (re)started. } comm_StmMessage; // SyncVar. #define SYNCVAR_NAME_SIZE 50 // Max size of a SyncVar name, including the '\0' trailing character. typedef enum { READONLY = 0, WRITEONLY, READWRITE } comm_VarAccess; typedef enum { BOOL = 0, UINT8, INT8, UINT16, INT16, UINT32, INT32, UINT64, INT64, FLOAT32, FLOAT64 } comm_VarType; // UART baudrate [b/s]. #define UART_BAUDRATE 1843200 #endif diff --git a/Firmware/src/drivers/adc.c b/Firmware/src/drivers/adc.c index b3e0e06..ab06671 100644 --- a/Firmware/src/drivers/adc.c +++ b/Firmware/src/drivers/adc.c @@ -1,238 +1,238 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "adc.h" #include "../lib/basic_filter.h" #include "../lib/utils.h" volatile uint16_t adc_currentValuesBuffer[ADC_BUFFER_SIZE]; bfilt_BasicFilter adc_currentFilter; float32_t adc_currentSensOffset = 0.0f; void adc_DmaInit(void); /** * @brief Initialize the ADC converter (2 analog inputs + current sense). */ void adc_Init(void) { ADC_InitTypeDef ADC_InitStructure; ADC_CommonInitTypeDef ADC_CommonInitStructure; GPIO_InitTypeDef GPIO_InitStructure; adc_DmaInit(); // Periph clock enable RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1|RCC_APB2Periph_ADC2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC3,ENABLE); // GPIO configuration GPIO_InitStructure.GPIO_Pin = ADC_HALL_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(ADC_HALL_PORT, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = ADC_ANIN1_PIN; GPIO_Init(ADC_ANIN1_PORT, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = ADC_ANIN2_PIN; GPIO_Init(ADC_ANIN2_PORT, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = ADC_ANIN3_PIN; GPIO_Init(ADC_ANIN3_PORT, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = ADC_ANIN4_PIN; GPIO_Init(ADC_ANIN4_PORT, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = ADC_CURRENT_SENSE_PIN; GPIO_Init(ADC_CURRENT_SENSE_PORT, &GPIO_InitStructure); // ADC Common configuration ADC_DeInit(); ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent; ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles; // Useless here (only used in dual or triple interleaved modes). ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4; ADC_CommonInit(&ADC_CommonInitStructure); // ADC 1 configuration for general purpose. ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b; ADC_InitStructure.ADC_ScanConvMode = DISABLE; ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None; ADC_InitStructure.ADC_ExternalTrigConv = 0; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfConversion = 1; ADC_Init(ADC1, &ADC_InitStructure); ADC_RegularChannelConfig(ADC1, ADC_HALL_CHANNEL, 1, ADC_SampleTime_56Cycles); ADC_Cmd(ADC1, ENABLE); ADC_SoftwareStartConv(ADC1); // ADC 3 configuration for motor current sensing. // Synchronous sampling with the PWM replaced by a high-frequency sampling at ~300kHz (over-sampling), because of the commutation noise of the current measuring amplifier at ~120kHz. ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; ADC_Init(ADC3, &ADC_InitStructure); ADC_RegularChannelConfig(ADC3, ADC_CURRENT_SENSE_CHANNEL, 1, ADC_SampleTime_28Cycles); ADC_DMARequestAfterLastTransferCmd(ADC3, ENABLE); ADC_DMACmd(ADC3, ENABLE); ADC_Cmd(ADC3, ENABLE); ADC_SoftwareStartConv(ADC3); // Setup the filter of the ADC current samples over time. bfilt_Init(&adc_currentFilter, 0.05f, 0.0f); } /** * @brief Setup the DMA that copies the current ADC samples to the RAM. */ void adc_DmaInit(void) { DMA_InitTypeDef DMA_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE); DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&adc_currentValuesBuffer; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory; DMA_InitStructure.DMA_BufferSize = ADC_BUFFER_SIZE; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable; DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull; DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; DMA_InitStructure.DMA_Channel = DMA_Channel_2; DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC3->DR; DMA_Init(DMA2_Stream1, &DMA_InitStructure); DMA_Cmd(DMA2_Stream1, ENABLE); } /** * @brief Compute the current sense offset. * @note Run before enabling current regulation. */ void adc_CalibrateCurrentSens(void) { float32_t offset = 0.0f; int32_t i=0; for(i=0; iSR & ADC_SR_EOC) != 0; } /** * @brief Gets the voltage measured by the selected ADC channel. * @return the measured voltage [V]. * @note make sure that the conversion is finished before calling. */ float32_t adc_GetConversionResult(void) { float32_t adcRawVal, voltage; adcRawVal = (float32_t)ADC1->DR; voltage = (adcRawVal / ADC_MAX) * ADC_REF_VOLTAGE; return voltage; } /** * @brief Gets the voltage measured by the selected ADC channel. * @param channel: the channel of the ADC. * @return the measured voltage [V]. */ float32_t adc_GetChannelVoltage(AdcChannel channel) { int32_t conversionTime = 0; adc_StartConversion(channel); while(!adc_ConversionIsFinished()) { if(conversionTime < ADC_MAX_CONVERSION_TIME) conversionTime++; else break; } return adc_GetConversionResult(); } diff --git a/Firmware/src/drivers/adc.h b/Firmware/src/drivers/adc.h index 077cdd6..2a34e43 100644 --- a/Firmware/src/drivers/adc.h +++ b/Firmware/src/drivers/adc.h @@ -1,96 +1,96 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __ADC_H #define __ADC_H #include "../main.h" #define ADC_HALL_PIN GPIO_Pin_1 #define ADC_HALL_PORT GPIOB #define ADC_HALL_CHANNEL ADC_Channel_9 #define ADC_ANIN1_PIN GPIO_Pin_6 #define ADC_ANIN1_PORT GPIOA #define ADC_ANIN1_CHANNEL ADC_Channel_6 #define ADC_ANIN2_PIN GPIO_Pin_7 #define ADC_ANIN2_PORT GPIOA #define ADC_ANIN2_CHANNEL ADC_Channel_7 #define ADC_ANIN3_PIN GPIO_Pin_4 #define ADC_ANIN3_PORT GPIOC #define ADC_ANIN3_CHANNEL ADC_Channel_14 #define ADC_ANIN4_PIN GPIO_Pin_5 #define ADC_ANIN4_PORT GPIOC #define ADC_ANIN4_CHANNEL ADC_Channel_15 #define ADC_CURRENT_SENSE_PIN GPIO_Pin_3 #define ADC_CURRENT_SENSE_PORT GPIOA #define ADC_CURRENT_SENSE_CHANNEL ADC_Channel_3 #define ADC_MAX 4095.0f // Maximum value of the ADC register (2^12 - 1). #define ADC_MAX_CONVERSION_TIME 100 // To avoid locking if the conversion was not started properly. #define ADC_BUFFER_SIZE 33 // Fadc =~300kHz -> TE_ADC = 3.33us -> Average over 32 sample => usable bandwidth <10kHz #define ADC_CURRENT_SCALE (ADC_REF_VOLTAGE / (CURRENT_SHUNT_RESISTANCE * CURRENT_SHUNT_AMPLIFIER_GAIN * ADC_MAX)) // Scale between ADC increment and current [A/incr]. #define ADC_CALIB_N_SAMPLES 1000 /** @defgroup ADC Driver / ADC * @brief Driver for the analog-to-digital peripheral of the STM32. * * An analog-to-digital converter (ADC) is used to measure the voltage of one * or several microcontroller analog pins. * * In addition, there is an additional channel to measure the current going * through the motor (useful for current regulation). * * Call adc_Init() first, in the initialization code. Then, call * adc_GetChannelVoltage() every time you need the voltage. * * To measure accurately the motor current, a calibration has to be performed * first. Call dc_CalibrateCurrentSens() in the main(), when the current * regulation is disabled (see H-bridge documentation). Then, call * adc_GetCurrent() every time it is needed. This function returns the last * value transfered by the DMA, so there is no conversion delay when calling * this function. * * @addtogroup ADC * @{ */ /** * @brief Enum that corresponds to the two ADC input channels of the board. */ typedef enum { ADC_CHANNEL_9 = 9, ///< Pin HALL_INPUT (5) of the connector J9 (Hall position sensor). ADC_CHANNEL_6 = 6, ///< Pin AN_IN1 (29 & 30) of the connector J12 (analog extension). ADC_CHANNEL_7 = 7, ///< Pin AN_IN2 (33 & 34) of the connector J12 (analog extension). ADC_CHANNEL_14 = 14, ///< Pin AN_IN3 (37 & 38) of the connector J12 (analog extension). ADC_CHANNEL_15 = 15, ///< Pin AN_IN4 (41 & 42) of the connector J12 (analog extension). } AdcChannel; void adc_Init(void); void adc_CalibrateCurrentSens(void); float32_t adc_GetCurrent(void); // [mA]. float32_t adc_GetChannelVoltage(AdcChannel channel); /** * @} */ #endif diff --git a/Firmware/src/drivers/button.c b/Firmware/src/drivers/button.c index 6614ff7..fcd04c2 100644 --- a/Firmware/src/drivers/button.c +++ b/Firmware/src/drivers/button.c @@ -1,96 +1,96 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "button.h" #include "../communication.h" #define BOARD_BUTTON_PORT GPIOE #define BOARD_BUTTON_PIN GPIO_Pin_2 #define USER_BUTTON_IRQ_CHANNEL EXTI2_IRQn #define USER_BUTTON_IRQ_LINE EXTI_Line2 #define USER_BUTTON_IRQ_PINSOURCE EXTI_PinSource2 #define USER_BUTTON_IRQ_SOURCE EXTI_PortSourceGPIOE void (*userButtonStateChangedCallback)(bool); ///< Function called when the button state changes. The bool parameter is true if the button was released. /** * @brief Initializes the button module. * @param stateChangedCallback function pointer to the callback function that * should be called when the button state changes. If this feature is not used, * NULL can be passed instead. */ void but_Init(void (*stateChangedCallback)(bool)) { GPIO_InitTypeDef GPIO_InitStruct; EXTI_InitTypeDef EXTI_InitStruct; NVIC_InitTypeDef NVIC_InitStruct; // Initialize the pin as input. GPIO_InitStruct.GPIO_Pin = BOARD_BUTTON_PIN; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz; GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(BOARD_BUTTON_PORT, &GPIO_InitStruct); // Optionally setup the callback that triggers when the button is pressed or // released. userButtonStateChangedCallback = stateChangedCallback; if(stateChangedCallback != NULL) { SYSCFG_EXTILineConfig(USER_BUTTON_IRQ_SOURCE, USER_BUTTON_IRQ_PINSOURCE); EXTI_InitStruct.EXTI_Line = USER_BUTTON_IRQ_LINE; EXTI_InitStruct.EXTI_LineCmd = ENABLE; EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising_Falling; EXTI_Init(&EXTI_InitStruct); NVIC_InitStruct.NVIC_IRQChannel = USER_BUTTON_IRQ_CHANNEL; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = USER_BUTTON_IRQ_PRIORITY; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0x0; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct); } } /** * @brief Gets the current state of the user button. * @return the state of the button (1=released, 0=pressed). */ bool but_GetState(void) { return GPIO_ReadInputDataBit(BOARD_BUTTON_PORT, BOARD_BUTTON_PIN) != 0; } /** * @brief Calls the user button callback function. * @remark: this function is called automatically by the pin change interrupt of * the user button. */ void EXTI2_IRQHandler(void) { if(EXTI_GetITStatus(USER_BUTTON_IRQ_LINE) != RESET) { if(userButtonStateChangedCallback != NULL) userButtonStateChangedCallback(but_GetState()); // Clear interrupt flag. EXTI_ClearITPendingBit(USER_BUTTON_IRQ_LINE); } } diff --git a/Firmware/src/drivers/button.h b/Firmware/src/drivers/button.h index 5a5a3c6..982d705 100644 --- a/Firmware/src/drivers/button.h +++ b/Firmware/src/drivers/button.h @@ -1,43 +1,43 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __BUTTON_H #define __BUTTON_H #include "../main.h" /** @defgroup Button Driver / Button * @brief Driver to access a button * * Call but_Init() first in the initialization code. Then, call but_GetState() * to read the current state of the button. * * It is also possible to pass a function pointer to the but_Init() function. * Then, the given function will be called automatically when the button state * changes (pressed or released). * * @addtogroup Button * @{ */ void but_Init(void (*stateChangedCallback)(bool)); bool but_GetState(void); /** * @} */ #endif diff --git a/Firmware/src/drivers/callback_timers.c b/Firmware/src/drivers/callback_timers.c index 064cca8..2b32230 100644 --- a/Firmware/src/drivers/callback_timers.c +++ b/Firmware/src/drivers/callback_timers.c @@ -1,252 +1,252 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "callback_timers.h" #include "../lib/utils.h" #include "../torque_regulator.h" #include "../haptic_controller.h" #define TE_LOOP_MIN_VALUE 50 // Minimum period for any loop [us]. #define TE_LOOP_MAX_VALUE 65534 // Maximum period for any loop [us]. #define TE_CURRENT_LOOP_DEFAULT_VAL 50 // Current control loop period [us] (max 2^16-1) (default value at reset). #define TE_CONTROL_LOOP_DEFAULT_VAL 350 // Main control loop period [us] (max 2^16-1) (default value at reset). #define TE_DATA_LOOP_DEFAULT_VAL 1000 // Data loop period [us] (max 2^16-1) (default value at reset). cbt_PeriodicTaskFunc cbt_tim10Task, cbt_tim6Task, cbt_tim7Task; volatile float32_t cbt_ucLoad; // Processor load (%). void tim10InitFunc(void); void tim67InitFunc(void); /** * @brief Initialize the timers to call an interrupt routine periodically. */ void cbt_Init(void) { // Initialize the timers. tim10InitFunc(); tim67InitFunc(); // Initialize the function pointers to NULL, in order to be able to test // if they are already affected or not. cbt_tim10Task = NULL; cbt_tim6Task = NULL; cbt_tim7Task = NULL; } /** * @brief Set the function to call periodically by the timer 1. * @param f: the function to call periodically. * @param period: the period between each call of f [us]. */ void cbt_SetCurrentLoopTimer(cbt_PeriodicTaskFunc f, uint32_t period) { cbt_tim10Task = f; utils_SaturateU(&period, TE_LOOP_MIN_VALUE, TE_LOOP_MAX_VALUE); TIM10->ARR = (uint16_t) period; } /** * @brief Set the function to call periodically by the timer 6. * @param f: the function to call periodically. * @param period: the period between each call of f [us]. */ void cbt_SetPositionLoopTimer(cbt_PeriodicTaskFunc f, uint32_t period) { cbt_tim6Task = f; utils_SaturateU(&period, TE_LOOP_MIN_VALUE, TE_LOOP_MAX_VALUE); TIM6->ARR = (uint16_t) period; } /** * @brief Set the function to call periodically by the timer 7. * @param f: the function to call periodically. * @param period: the period between each call of f [us]. */ void cbt_SetCommLoopTimer(cbt_PeriodicTaskFunc f, uint32_t period) { cbt_tim7Task = f; utils_SaturateU(&period, TE_LOOP_MIN_VALUE, TE_LOOP_MAX_VALUE); TIM7->ARR = (uint16_t) period; } /** * @brief Initialize TIM10 used for timing the current loop (resolution 1us). */ void tim10InitFunc(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct; //TIM_OCInitTypeDef TIM_OCInitStruct; NVIC_InitTypeDef NVIC_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM10, ENABLE); NVIC_InitStruct.NVIC_IRQChannel = TIM1_UP_TIM10_IRQn; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = CURRENT_LOOP_IRQ_PRIORITY; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct); TIM_TimeBaseStruct.TIM_Period = (uint16_t)(TE_CURRENT_LOOP_DEFAULT_VAL-1); TIM_TimeBaseStruct.TIM_Prescaler = TIM10_PRESCALER; TIM_TimeBaseStruct.TIM_ClockDivision = 0; // TIM_CKD_DIV2; TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM10, &TIM_TimeBaseStruct); TIM_ITConfig(TIM10, TIM_IT_Update, ENABLE); TIM_Cmd(TIM10, ENABLE); } /** * @brief Initialize TIM6, for timing the main control loop (resolution 1us) * Initialize TIM7, for timing the data transmission loop (resolution 1us) */ void tim67InitFunc(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct; NVIC_InitTypeDef NVIC_InitStruct; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6|RCC_APB1Periph_TIM7, ENABLE); NVIC_InitStruct.NVIC_IRQChannel = TIM6_DAC_IRQn; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = CONTROL_LOOP_IRQ_PRIORITY; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct); NVIC_InitStruct.NVIC_IRQChannel = TIM7_IRQn; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = DATA_LOOP_IRQ_PRIORITY; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0; NVIC_Init(&NVIC_InitStruct); TIM_TimeBaseStructInit(&TIM_TimeBaseStruct); TIM_TimeBaseStruct.TIM_Prescaler = TIM6_PRESCALER; TIM_TimeBaseStruct.TIM_Period = (uint16_t)(TE_CONTROL_LOOP_DEFAULT_VAL-1); TIM_TimeBaseStruct.TIM_ClockDivision = 0; TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM6, &TIM_TimeBaseStruct); TIM_TimeBaseStructInit(&TIM_TimeBaseStruct); TIM_TimeBaseStruct.TIM_Prescaler = TIM7_PRESCALER; TIM_TimeBaseStruct.TIM_Period = (uint16_t)(TE_DATA_LOOP_DEFAULT_VAL-1); TIM_TimeBaseStruct.TIM_ClockDivision = 0; TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM7, &TIM_TimeBaseStruct); TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE); TIM_ITConfig(TIM7, TIM_IT_Update, ENABLE); TIM_Cmd(TIM6, ENABLE); TIM_Cmd(TIM7, ENABLE); } /** * @brief Interrupt from current control loop timer (TIM10) */ void TIM1_UP_TIM10_IRQHandler(void) { if(TIM_GetITStatus(TIM10, TIM_IT_Update) != RESET) { if(cbt_tim10Task != NULL) cbt_tim10Task(); TIM_ClearITPendingBit(TIM10, TIM_IT_Update); } } /** * @brief Interrupt from main control loop timer (TIM6) */ void TIM6_DAC_IRQHandler(void) { if(TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET) { if(cbt_tim6Task != NULL) cbt_tim6Task(); // Percentage of time consumed by the control task 0..100%. cbt_ucLoad = (((float32_t)(TIM6->CNT))*100)/((float32_t)(TIM6->ARR)); TIM_ClearITPendingBit(TIM6, TIM_IT_Update); } } /** * @brief Interrupt from data transmission loop timer (TIM7) (data loop) */ void TIM7_IRQHandler(void) { if(TIM_GetITStatus(TIM7, TIM_IT_Update) != RESET) { if(cbt_tim7Task != NULL) cbt_tim7Task(); TIM_ClearITPendingBit(TIM7, TIM_IT_Update); } } /** * @brief Set the period of the position loop. * @param period: the new period of the position loop [us]. */ void cbt_SetPositionLoopPeriod(uint32_t period) { utils_SaturateU(&period, TE_LOOP_MIN_VALUE, TE_LOOP_MAX_VALUE); TIM6->ARR = period; } /** * @brief Set the period of the communication loop. * @param period: the new period of the communication loop [us]. */ void cbt_SetCommLoopPeriod(uint32_t period) { utils_SaturateU(&period, TE_LOOP_MIN_VALUE, TE_LOOP_MAX_VALUE); TIM7->ARR = period; } /** * @brief Get the period of the current loop. * @return the period of the current loop [us]. */ uint32_t cbt_GetCurrentLoopPeriod(void) { return TIM10->ARR; } /** * @brief Get the period of the position loop. * @return the period of the position loop [us]. */ uint32_t cbt_GetPositionLoopPeriod(void) { return TIM6->ARR; } /** * @brief Get the period of the communication loop. * @return the period of the communication loop [us]. */ uint32_t cbt_GetCommLoopPeriod(void) { return TIM7->ARR; } diff --git a/Firmware/src/drivers/callback_timers.h b/Firmware/src/drivers/callback_timers.h index 1424974..e5ad273 100644 --- a/Firmware/src/drivers/callback_timers.h +++ b/Firmware/src/drivers/callback_timers.h @@ -1,59 +1,59 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __CALLBACK_TIMERS_H #define __CALLBACK_TIMERS_H #include "../main.h" typedef void (*cbt_PeriodicTaskFunc)(void); // TIMX_PERIOD is the clock divider at the input of the timers. // So, the timer will increment its counter, every TIMX_PERIOD ticks of the system clock (168 MHz). #define TIM10_PRESCALER ((uint16_t)(SystemCoreClock/APB2_PRESCALER*TIM_MULTIPLIER/1000000-1)) // CLK_CNT = 1[us] (current loop) #define TIM6_PRESCALER ((uint16_t)(SystemCoreClock/APB1_PRESCALER*TIM_MULTIPLIER/1000000-1)) // CLK_CNT = 1[us] (control loop) #define TIM7_PRESCALER ((uint16_t)(SystemCoreClock/APB1_PRESCALER*TIM_MULTIPLIER/1000000-1)) // CLK_CNT = 1[us] (data loop) /** @defgroup CallbackTimers Driver / Callback timers * @brief Driver to call functions at a fixed rate. * * This driver setups three timers of the STM32, in order to call at a precise * rate the control functions: the current regulation loop, the position * regulation loop and the communication loop (data streaming part only). * * In the initialization code, first call cbt_Init(). Then call each * cbt_Set*LoopTimer() function, giving the pointer to the function to call as * an argument. * * @addtogroup CallbackTimers * @{ */ void cbt_Init(void); void cbt_SetCurrentLoopTimer(cbt_PeriodicTaskFunc f, uint32_t period); void cbt_SetPositionLoopTimer(cbt_PeriodicTaskFunc f, uint32_t period); void cbt_SetCommLoopTimer(cbt_PeriodicTaskFunc f, uint32_t period); void cbt_SetPositionLoopPeriod(uint32_t period); void cbt_SetCommLoopPeriod(uint32_t period); uint32_t cbt_GetCurrentLoopPeriod(void); uint32_t cbt_GetPositionLoopPeriod(void); uint32_t cbt_GetCommLoopPeriod(void); /** * @} */ #endif diff --git a/Firmware/src/drivers/dac.c b/Firmware/src/drivers/dac.c index 428d3c0..7fca652 100644 --- a/Firmware/src/drivers/dac.c +++ b/Firmware/src/drivers/dac.c @@ -1,105 +1,105 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "dac.h" #include "../lib/utils.h" /** * @brief Setup a DAC with 2 channels. */ void dac_Init(void) { DAC_InitTypeDef DAC_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; // Periph clock enable. RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE); // GPIO configuration. GPIO_InitStructure.GPIO_Pin = DAC1_Pin|DAC2_Pin; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(DAC_Port, &GPIO_InitStructure); // DAC channel Configuration. DAC_InitStructure.DAC_Trigger = DAC_Trigger_None; DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None; DAC_InitStructure.DAC_LFSRUnmask_TriangleAmplitude = DAC_LFSRUnmask_Bit0; DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable; DAC_Init(DAC_Channel_1|DAC_Channel_2, &DAC_InitStructure); // Enable DAC Channel. DAC_Cmd(DAC_Channel_1, ENABLE); DAC_Cmd(DAC_Channel_2, ENABLE); // Enable DMA for DAC Channel. DAC_DMACmd(DAC_Channel_1|DAC_Channel_2, ENABLE); // Set initial voltage to zero. dac_SetVoltage1(0.0f); dac_SetVoltage2(0.0f); } /** * @brief Set the voltage of the channel 1 of the DAC. * @param finalVoltage: the new voltage [V], between +-DAC_FINAL_RANGE (+-9V). */ void dac_SetVoltage1(float32_t finalVoltage) { float32_t dacVoltageRatio = (-finalVoltage / DAC_FINAL_RANGE / 2.0f) + 0.5f; uint32_t regVal = (uint32_t)(dacVoltageRatio * (float32_t)DAC_MAX); utils_SaturateU(®Val, 0, DAC_MAX); DAC_SetChannel2Data(DAC_Align_12b_R, (uint16_t)regVal); } /** * @brief Set the voltage of the channel 2 of the DAC. * @param finalVoltage: the new voltage [V], between +-DAC_FINAL_RANGE (+-9V). */ void dac_SetVoltage2(float32_t finalVoltage) { float32_t dacVoltageRatio = (-finalVoltage / DAC_FINAL_RANGE / 2.0f) + 0.5f; uint32_t regVal = (uint32_t)(dacVoltageRatio * (float32_t)DAC_MAX); utils_SaturateU(®Val, 0, DAC_MAX); DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)regVal); } /** * @brief Get the current output voltage of the channel 1 of the DAC. * @retval The current voltage of the channel 1 of the DAC [V]. */ float32_t dac_GetVoltage1(void) { uint32_t regVal = DAC_GetDataOutputValue(DAC_Channel_2); return -((float32_t)regVal / (float32_t)DAC_MAX - 0.5f) * 2.0f * DAC_FINAL_RANGE; } /** * @brief Get the current output voltage of the channel 2 of the DAC. * @retval The current voltage of the channel 2 of the DAC [V]. */ float32_t dac_GetVoltage2(void) { uint32_t regVal = DAC_GetDataOutputValue(DAC_Channel_1); return -((float32_t)regVal / (float32_t)DAC_MAX - 0.5f) * 2.0f * DAC_FINAL_RANGE; } diff --git a/Firmware/src/drivers/dac.h b/Firmware/src/drivers/dac.h index 12ab099..ba2295a 100644 --- a/Firmware/src/drivers/dac.h +++ b/Firmware/src/drivers/dac.h @@ -1,54 +1,54 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __DAC_H #define __DAC_H #include "../main.h" #define DAC1_Pin GPIO_Pin_4 #define DAC2_Pin GPIO_Pin_5 #define DAC_Port GPIOA #define DAC_FINAL_RANGE 9.0f // -9 to 9V after the op-amp. #define DAC_MAX 4095 // 12-bits. /** @defgroup DAC Driver / DAC * @brief Driver for the digital-to-analog converter. * * The STM32 features a two channels DAC, which means it is able to drive two * pins with an analog voltage. On the HRI board, this voltage is mulltiplied * by an amplification stage, so the final output range is +-9V. The * corresponding output pins are on the J1 connector: ANOUT1 (2) and ANOUT2 (4). * * In the initialization code, call dac_Init() once. Then call * dac_GetVoltageX() everytime the output voltage needs to be updated. * * @addtogroup DAC * @{ */ void dac_Init(void); void dac_SetVoltage1(float32_t voltage); void dac_SetVoltage2(float32_t voltage); float32_t dac_GetVoltage1(void); float32_t dac_GetVoltage2(void); /** * @} */ #endif diff --git a/Firmware/src/drivers/debug_gpio.c b/Firmware/src/drivers/debug_gpio.c index 45794ff..6f651c1 100644 --- a/Firmware/src/drivers/debug_gpio.c +++ b/Firmware/src/drivers/debug_gpio.c @@ -1,66 +1,66 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "debug_gpio.h" #define N_GPIOS 3 #define DIO_PORT GPIOB const uint32_t dio_gpios[N_GPIOS] = { GPIO_Pin_14, GPIO_Pin_13, GPIO_Pin_12 }; void dio_Init(void) { int i; GPIO_InitTypeDef GPIO_InitStruct; for(i=0; i= 0 && pinIndex < N_GPIOS) return GPIO_ReadInputDataBit(DIO_PORT, dio_gpios[pinIndex]); else return 0; } void dio_Set(int pinIndex, bool high) { if(pinIndex >= 0 && pinIndex < N_GPIOS) { GPIO_WriteBit(DIO_PORT, dio_gpios[pinIndex], high ? Bit_SET : Bit_RESET); } } void dio_Toggle(int pinIndex) { if(pinIndex >= 0 && pinIndex < N_GPIOS) { bool newState = !GPIO_ReadInputDataBit(DIO_PORT, dio_gpios[pinIndex]); GPIO_WriteBit(DIO_PORT, dio_gpios[pinIndex], newState ? Bit_SET : Bit_RESET); } } diff --git a/Firmware/src/drivers/debug_gpio.h b/Firmware/src/drivers/debug_gpio.h index 6b2cdb5..43a80eb 100644 --- a/Firmware/src/drivers/debug_gpio.h +++ b/Firmware/src/drivers/debug_gpio.h @@ -1,41 +1,41 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __DEBUG_GPIO_H #define __DEBUG_GPIO_H #include "../main.h" /** @defgroup DGPIO Driver / Debug GPIOs * @brief Driver to control three GPIOs, to debug easily with an oscilloscope. * * Call dio_Init() first in the initialization code. Then, call dio_Set() to * set the GPIO states. * * @addtogroup DGPIO * @{ */ void dio_Init(void); bool dio_Get(int pinIndex); void dio_Set(int pinIndex, bool high); void dio_Toggle(int pinIndex); /** * @} */ #endif diff --git a/Firmware/src/drivers/ext_uart.c b/Firmware/src/drivers/ext_uart.c index 6bf979a..7e44e51 100644 --- a/Firmware/src/drivers/ext_uart.c +++ b/Firmware/src/drivers/ext_uart.c @@ -1,317 +1,317 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "ext_uart.h" #include "../lib/utils.h" #define EXUART_RX_Pin GPIO_Pin_7 #define EXUART_RX_PinSource GPIO_PinSource7 #define EXUART_RX_Port GPIOB #define EXUART_TX_Pin GPIO_Pin_6 #define EXUART_TX_PinSource GPIO_PinSource6 #define EXUART_TX_Port GPIOB #define EXUART_PERIPH USART1 #define RX_DMA DMA2_Stream2 #define RX_DMA_CHANNEL DMA_Channel_4 #define TX_DMA DMA2_Stream7 #define TX_DMA_CHANNEL DMA_Channel_4 #define TX_BUFFER_SIZE 512 #define RX_BUFFER_SIZE 1024 #define USER_RX_QUEUE_SIZE 2048 uint8_t exuart_txBuffer[2][TX_BUFFER_SIZE]; uint8_t exuart_currentTxBufferToWriteTo; uint16_t exuart_txBufferIndex; uint16_t exuart_nBytesToTransferDma; uint8_t exuart_rxBuffer[RX_BUFFER_SIZE]; uint8_t const * exuart_rxBuffTail; uint8_t exuart_userRxQueue[USER_RX_QUEUE_SIZE]; cb_CircularBuffer exuart_rxQueue; #define UART_DMA_TX_IS_BUSY (DMA_GetCmdStatus(TX_DMA) == ENABLE && DMA_GetCurrDataCounter(TX_DMA) > 0) void exuart_FlushTx(void); /** * @brief Initializes the UART module. * @param baudRate: UART communication frequency (baud rate) [b/s]. */ void exuart_Init(uint32_t baudRate) { GPIO_InitTypeDef GPIO_InitStruct; USART_InitTypeDef USART_InitStruct; DMA_InitTypeDef DMA_InitStruct; // Enable UART and DMA peripherals clocks. RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE); // Setup GPIOs as UART pins. GPIO_InitStruct.GPIO_Pin = EXUART_TX_Pin; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(EXUART_TX_Port, &GPIO_InitStruct); GPIO_PinAFConfig(EXUART_TX_Port, EXUART_TX_PinSource, GPIO_AF_USART1); GPIO_InitStruct.GPIO_Pin = EXUART_RX_Pin; GPIO_Init(EXUART_RX_Port, &GPIO_InitStruct); GPIO_PinAFConfig(EXUART_RX_Port, EXUART_RX_PinSource, GPIO_AF_USART1); // Setup the UART peripheral. USART_InitStruct.USART_BaudRate = baudRate; USART_InitStruct.USART_WordLength = USART_WordLength_8b; USART_InitStruct.USART_StopBits = USART_StopBits_1; USART_InitStruct.USART_Parity = USART_Parity_No; USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; USART_Init(EXUART_PERIPH, &USART_InitStruct); USART_Cmd(EXUART_PERIPH, ENABLE); // Setup the DMA for RX. DMA_DeInit(RX_DMA); DMA_InitStruct.DMA_Channel = RX_DMA_CHANNEL; DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&EXUART_PERIPH->DR; DMA_InitStruct.DMA_Memory0BaseAddr = (uint32_t)&exuart_rxBuffer[0]; DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralToMemory; DMA_InitStruct.DMA_BufferSize = RX_BUFFER_SIZE; DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_InitStruct.DMA_Mode = DMA_Mode_Circular; DMA_InitStruct.DMA_Priority = DMA_Priority_Medium; DMA_InitStruct.DMA_FIFOMode = DMA_FIFOMode_Disable; DMA_InitStruct.DMA_MemoryBurst = DMA_MemoryBurst_Single; DMA_InitStruct.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; DMA_Init(RX_DMA, &DMA_InitStruct); USART_DMACmd(EXUART_PERIPH, USART_DMAReq_Rx | USART_DMAReq_Tx, ENABLE); DMA_ITConfig(RX_DMA, DMA_IT_TC, ENABLE); DMA_Cmd(RX_DMA, ENABLE); exuart_rxBuffTail = &exuart_rxBuffer[0]; // Setup the DMA for TX. DMA_DeInit(TX_DMA); DMA_InitStruct.DMA_Channel = TX_DMA_CHANNEL; DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&EXUART_PERIPH->DR; DMA_InitStruct.DMA_Memory0BaseAddr = (uint32_t)&exuart_txBuffer[0][0]; DMA_InitStruct.DMA_DIR = DMA_DIR_MemoryToPeripheral; DMA_InitStruct.DMA_BufferSize = TX_BUFFER_SIZE; DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_InitStruct.DMA_Mode = DMA_Mode_Normal; DMA_InitStruct.DMA_Priority = DMA_Priority_Medium; DMA_InitStruct.DMA_FIFOMode = DMA_FIFOMode_Disable; DMA_InitStruct.DMA_MemoryBurst = DMA_MemoryBurst_Single; DMA_InitStruct.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; DMA_Init(TX_DMA, &DMA_InitStruct); USART_DMACmd(EXUART_PERIPH, USART_DMAReq_Rx | USART_DMAReq_Tx, ENABLE); DMA_ITConfig(TX_DMA, DMA_IT_TC, ENABLE); // Initialize the RX circular buffer. cb_Init(&exuart_rxQueue, exuart_userRxQueue, USER_RX_QUEUE_SIZE); // Initialize the variables for the UART TX. exuart_currentTxBufferToWriteTo = 0; exuart_txBufferIndex = 0; exuart_nBytesToTransferDma = 0; } /** * @brief Gets the number of bytes received and ready to read. * @return the number of bytes ready to be acquired with exuart_GetByte(). * @remark This function must called often, otherwise the DMA RX buffer may be * full, and data will be lost. */ uint16_t exuart_ReceivedBytesCount(void) { // Get the location of the location currently pointed by the DMA. uint8_t const * head = exuart_rxBuffer + RX_BUFFER_SIZE - DMA_GetCurrDataCounter(RX_DMA); // Even if the STM32F4 reference manual (RM0090) states that the NDTR // register is decremented after the transfer, this is not the case. // So we wait a few cycles to be sure that the DMA actually performed the // transfer. utils_DelayUs(1); // RX: add the received bytes into the user queue. while(exuart_rxBuffTail != head) { uint8_t b = *exuart_rxBuffTail; cb_Push(&exuart_rxQueue, b); exuart_rxBuffTail++; if(exuart_rxBuffTail >= exuart_rxBuffer + RX_BUFFER_SIZE) exuart_rxBuffTail -= RX_BUFFER_SIZE; } // Return the number of bytes ready to get. return cb_ItemsCount(&exuart_rxQueue); } /** * @brief Gets the next received byte. * @return the value of the received byte. * @remark call exuart_ReceivedBytesCount() before calling this function. * @warning this function return 0 if no byte is available. */ uint8_t exuart_GetByte(void) { if(cb_ItemsCount(&exuart_rxQueue) > 0) return cb_Pull(&exuart_rxQueue); else return 0; } /** * @brief Starts the DMA transfer to send bytes to UART peripheral. */ void exuart_StartDma(void) { DMA_InitTypeDef DMA_InitStruct; // exuart_nBytesToTransferDma = exuart_txBufferIndex; exuart_currentTxBufferToWriteTo = !exuart_currentTxBufferToWriteTo; exuart_txBufferIndex = 0; // Start the DMA transfer. DMA_DeInit(TX_DMA); DMA_InitStruct.DMA_Channel = TX_DMA_CHANNEL; DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&EXUART_PERIPH->DR; DMA_InitStruct.DMA_Memory0BaseAddr = (uint32_t)&exuart_txBuffer[!exuart_currentTxBufferToWriteTo][0]; DMA_InitStruct.DMA_DIR = DMA_DIR_MemoryToPeripheral; DMA_InitStruct.DMA_BufferSize = exuart_nBytesToTransferDma; DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_InitStruct.DMA_Mode = DMA_Mode_Normal; DMA_InitStruct.DMA_Priority = DMA_Priority_Medium; DMA_InitStruct.DMA_FIFOMode = DMA_FIFOMode_Disable; DMA_InitStruct.DMA_MemoryBurst = DMA_MemoryBurst_Single; DMA_InitStruct.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; DMA_Init(TX_DMA, &DMA_InitStruct); DMA_Cmd(TX_DMA, ENABLE); } /** * @brief Asynchronously sends the given byte through the UART bus. * @param data the data byte to send. */ void exuart_SendByteAsync(uint8_t data) { // Check that it is possible to write in the current buffer. if(exuart_txBufferIndex >= TX_BUFFER_SIZE) { // If the DMA is idle, switch to the other buffer. if(UART_DMA_TX_IS_BUSY) { utils_TrapCpu(); // Error, can't write anywhere! return; } else exuart_StartDma(); } // Write the byte in the buffer. exuart_txBuffer[exuart_currentTxBufferToWriteTo][exuart_txBufferIndex] = data; exuart_txBufferIndex++; // If the buffer is full, start the DMA transfer. if(exuart_txBufferIndex == TX_BUFFER_SIZE) exuart_StartDma(); // exuart_FlushTx(); } /** * @brief Asynchronously sends the given bytes through the UART bus. * @param data pointer to the data bytes array to send. * @param length number of bytes to send (array size). */ void exuart_SendBytesAsync(uint8_t *data, int length) { while(length > 0) { uint16_t nBytesToWriteInBuf; // Check that it is possible to write in the current buffer. if(exuart_txBufferIndex >= TX_BUFFER_SIZE) { // If the DMA is idle, switch to the other buffer. if(UART_DMA_TX_IS_BUSY) { utils_TrapCpu(); // Error, can't write anywhere! return; } else exuart_StartDma(); } // Write as many bytes as possible in the current buffer. nBytesToWriteInBuf = TX_BUFFER_SIZE - exuart_txBufferIndex; if(nBytesToWriteInBuf > length) nBytesToWriteInBuf = length; memcpy(&exuart_txBuffer[exuart_currentTxBufferToWriteTo][exuart_txBufferIndex], data, nBytesToWriteInBuf); exuart_txBufferIndex += nBytesToWriteInBuf; data += nBytesToWriteInBuf; length -= nBytesToWriteInBuf; // If the buffer is full, start the DMA transfer. if(exuart_txBufferIndex == TX_BUFFER_SIZE) exuart_StartDma(); } // exuart_FlushTx(); } /** * @brief Start the DMA to send the bytes waiting in the intermediate buffer. */ void exuart_FlushTx(void) { if(exuart_txBufferIndex > 0 && !UART_DMA_TX_IS_BUSY) exuart_StartDma(); } diff --git a/Firmware/src/drivers/ext_uart.h b/Firmware/src/drivers/ext_uart.h index 60d847a..0ac4bba 100644 --- a/Firmware/src/drivers/ext_uart.h +++ b/Firmware/src/drivers/ext_uart.h @@ -1,48 +1,48 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __EXUART_H #define __EXUART_H #include "../main.h" #include "../lib/circular_buffer.h" /** @defgroup EXT_UART Driver / Extension UART * @brief Driver for the UART serial communication peripheral. * * This driver controls the UART peripheral of the STM32, connected to the * digital extension connector. * * Call exuart_Init() first in the initialization code. To send data, call * exuart_SendByteAsync(). To receive data, check first that bytes are * available by calling exuart_ReceivedBytesCount(), then call * exuart_GetByte(). * * @addtogroup EXT_UART * @{ */ void exuart_Init(uint32_t baudRate); void exuart_SendByteAsync(uint8_t data); void exuart_SendBytesAsync(uint8_t *data, int length); uint16_t exuart_ReceivedBytesCount(void); uint8_t exuart_GetByte(void); /** * @} */ #endif diff --git a/Firmware/src/drivers/external_motorboard.c b/Firmware/src/drivers/external_motorboard.c index ebe98ce..bf41581 100644 --- a/Firmware/src/drivers/external_motorboard.c +++ b/Firmware/src/drivers/external_motorboard.c @@ -1,43 +1,43 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "external_motorboard.h" /** * @brief Initializes the motorboard driver. */ void emot_Init(void) { // Not implemented yet. } /** * @brief Set the motor torque. * @param torque the torque the motor should apply [N.m]. */ void emot_SetTorque(float32_t torque) { } /** * @brief Gets the current motor shaft position. * @return the motor position given by the encoder [deg]. */ float32_t emot_GetPosition(void) { return 0.0f; } diff --git a/Firmware/src/drivers/external_motorboard.h b/Firmware/src/drivers/external_motorboard.h index d98e00d..4c8ed8e 100644 --- a/Firmware/src/drivers/external_motorboard.h +++ b/Firmware/src/drivers/external_motorboard.h @@ -1,44 +1,44 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __EXTERNAL_MOTORBOARD_H #define __EXTERNAL_MOTORBOARD_H #include "../main.h" /** @defgroup ExtMotorboard Driver / External motorboard * @brief Driver for an external motorboard. * * This driver controls an external motorboard, controlled through a serial * link. This allows driving larger motors. * * Call emot_Init() first in the initialization code. Then, call * emot_SetTorque() to set the motor torque, and emot_GetPosition() to get the * motor shaft position. * * @addtogroup ExtMotorboard * @{ */ void emot_Init(void); void emot_SetTorque(float32_t torque); float32_t emot_GetPosition(void); /** * @} */ #endif diff --git a/Firmware/src/drivers/h_bridge.c b/Firmware/src/drivers/h_bridge.c index 772d974..5f43ddb 100644 --- a/Firmware/src/drivers/h_bridge.c +++ b/Firmware/src/drivers/h_bridge.c @@ -1,148 +1,148 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "h_bridge.h" void hb_Tim8Init(void); /** * @brief Initialize the pins and the PWM timer to control the H-bridge. */ void hb_Init(void) { // Init the GPIO pins. GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Pin = nFAULT_Pin; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN; GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(nFAULT_Port, &GPIO_InitStruct); GPIO_InitStruct.GPIO_Pin = nSLEEP_Pin; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(nSLEEP_Port, &GPIO_InitStruct); GPIO_WriteBit(nSLEEP_Port, nSLEEP_Pin, Bit_SET); GPIO_InitStruct.GPIO_Pin = (uint16_t)(1<>1)); //50% duty cycle TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_Low; TIM_OCInitStruct.TIM_OCIdleState = TIM_OCIdleState_Set; TIM_OC1Init(TIM8, &TIM_OCInitStruct); TIM_OCInitStruct.TIM_OCIdleState = TIM_OCIdleState_Reset; TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OC2Init(TIM8, &TIM_OCInitStruct); TIM_OC1PreloadConfig(TIM8, TIM_OCPreload_Enable); TIM_OC2PreloadConfig(TIM8, TIM_OCPreload_Enable); // TIM8 TRGO selection. TIM_SelectOutputTrigger(TIM8, TIM_TRGOSource_Update); TIM_Cmd(TIM8, ENABLE); TIM_CtrlPWMOutputs(TIM8, ENABLE); } /** * @brief Enable the motor driver. */ void hb_Enable() { GPIO_WriteBit(ENB1_Port, (uint16_t)(1<ARR)>>1)))); TIM8->CCR1 = pwmDutyCycle; // The same value is affected to both timers, because they have TIM8->CCR2 = pwmDutyCycle; // an opposite polarity. } /** * @brief Gets the fault state. * Gets the fault state of the H-Bridge from its nFAULT line. A fault may be * because of an undervoltage, overcurrent or overtemperature. * @return true if there is a fault, false otherwise. */ bool hb_HasFault(void) { return !GPIO_ReadInputDataBit(nFAULT_Port, nFAULT_Pin); } diff --git a/Firmware/src/drivers/h_bridge.h b/Firmware/src/drivers/h_bridge.h index aa8cead..9bc025b 100644 --- a/Firmware/src/drivers/h_bridge.h +++ b/Firmware/src/drivers/h_bridge.h @@ -1,66 +1,66 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __H_BRIDGE_H #define __H_BRIDGE_H #include "../main.h" #define PWM1_Pin 6 #define PWM1_Port GPIOC #define PWM2_Pin 7 #define PWM2_Port GPIOC #define ENB1_Pin 8 #define ENB1_Port GPIOC #define ENB2_Pin 9 #define ENB2_Port GPIOC #define nFAULT_Pin GPIO_Pin_15 #define nFAULT_Port GPIOD #define nSLEEP_Pin GPIO_Pin_14 #define nSLEEP_Port GPIOD #define PWM_FREQUENCY 30000 // Motor PWM freq. [Hz] #define PWM_RESOL_SHIFT_DWN 6 // PWM_SCALE = 0xFFFF/2^PWM_RESOL_SHIFT_DWN #define CURRENT_SCALE_RESOL ((uint32_t)(16-PWM_RESOL_SHIFT_DWN)) #define PWM_TIM_PERIODE ((int16_t)(0xFFFF>>PWM_RESOL_SHIFT_DWN)) #define PWM_TIM_PRESCALER ((int16_t)(SystemCoreClock/APB2_PRESCALER*TIM_MULTIPLIER/PWM_FREQUENCY/PWM_TIM_PERIODE-1)) /** @defgroup H_Bridge Driver / H-bridge * @brief Driver for the H-bridge of the motor. * * The DC motor is supplied by a H-bridge on the HRI board. These power * electronics are controlled by the microcontroller using a PWM timer. * * Call hb_Init() first in the initialization code. Then, call hb_Enable() * to enable the H-bridge chip (power on), and hb_SetPWM() to set the motor * voltage. * * @addtogroup H_Bridge * @{ */ void hb_Init(void); void hb_Enable(void); void hb_Disable(void); void hb_SetPWM(float32_t ratio); bool hb_HasFault(void); /** * @} */ #endif diff --git a/Firmware/src/drivers/hall.c b/Firmware/src/drivers/hall.c index 9549a51..fbf97d3 100644 --- a/Firmware/src/drivers/hall.c +++ b/Firmware/src/drivers/hall.c @@ -1,46 +1,46 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "hall.h" #define HALL_AMPLI_GAIN 1.1f #define HALL_VOLT_DIVIDER 0.5f AdcChannel hall_channel; /** * @brief Initialize the hall sensor driver. * @param channel: the ADC channel the hall sensor is wired to (0 or 1). */ void hall_Init(AdcChannel channel) { hall_channel = channel; } /** * @brief Return the Hall sensor output voltage. * @return The Hall sensor output voltage [V]. */ float32_t hall_GetVoltage(void) { // Because of the analog stages between the Hall sensor output pin and the // ADC pin, a conversion is necessary to obtain the Hall output voltage. float32_t voltage = adc_GetChannelVoltage(hall_channel); // Voltage at the ADC pin. voltage = voltage / HALL_VOLT_DIVIDER; // Voltage between the Bessel filter and the voltage divider. voltage = (voltage - ADC_REF_VOLTAGE) / HALL_AMPLI_GAIN + ADC_REF_VOLTAGE; // Voltage at the Hall output pin. return voltage; } diff --git a/Firmware/src/drivers/hall.h b/Firmware/src/drivers/hall.h index 70045bf..377d151 100644 --- a/Firmware/src/drivers/hall.h +++ b/Firmware/src/drivers/hall.h @@ -1,49 +1,49 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __HALL_H #define __HALL_H #include "../main.h" #include "adc.h" /** @defgroup Hall Driver / Hall sensor * @brief Driver for a Hall-effect angular sensor. * * This driver uses an ADC input, ANIN1 or ANIN2 (see ADC). * To use this, connect wire the sensor as this: * * hall red -> +5V (pin 1 or 5 of J2). * * hall yellow -> GND (pin 2 or 6 of J2). * * hall blue -> ANINX+ (pin 3 or 7 of J2). * * GND (pin 2 or 6 of J2) -> ANINX- (pin 4 or 8 of J2). * * Set the sensitivity switches to +-10V. * * Call hall_Init() first in the initialization code. Then, call hall_Get() to * get the angle of the paddle. * * @addtogroup Hall * @{ */ void hall_Init(AdcChannel channel); float32_t hall_GetVoltage(void); /** * @} */ #endif diff --git a/Firmware/src/drivers/i2c.c b/Firmware/src/drivers/i2c.c index a41b31b..8cdc3dc 100644 --- a/Firmware/src/drivers/i2c.c +++ b/Firmware/src/drivers/i2c.c @@ -1,262 +1,262 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "i2c.h" #include "stm32f4xx_i2c.h" #include "../lib/utils.h" const uint32_t TIMEOUT = 3200; ///< Approximate maximum time for a I2C operation to complete (approx. 100us). /** * @brief Repeats until the expression returns false, or the timeout is reached. */ #define WAIT_WITH_TIMEOUT(x) \ do \ { \ uint32_t duration = 0;\ while((x)) \ { \ if(duration++ > TIMEOUT) \ { \ i2c_Reset(); \ if(ok != NULL) \ *ok = false; \ return; \ } \ } \ } while(0) /** * @brief Initializes the I2C bus. */ void i2c_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; I2C_InitTypeDef I2C_InitStruct; // Enable the peripheral clock. RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); // Setup the pins as I2C SDA/SCL. GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStruct.GPIO_OType = GPIO_OType_OD; GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_InitStruct.GPIO_Pin = I2C_SDA_Pin; GPIO_Init(I2C_SDA_Port, &GPIO_InitStruct); GPIO_InitStruct.GPIO_Pin = I2C_SCL_Pin; GPIO_Init(I2C_SCL_Port, &GPIO_InitStruct); GPIO_PinAFConfig(I2C_SDA_Port, I2C_SDA_PinSource, I2C_GPIO_AF); GPIO_PinAFConfig(I2C_SCL_Port, I2C_SCL_PinSource, I2C_GPIO_AF); // Setup the I2C peripheral. I2C_InitStruct.I2C_ClockSpeed = 100000; // [Hz]. I2C_InitStruct.I2C_Mode = I2C_Mode_I2C; I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2; I2C_InitStruct.I2C_OwnAddress1 = 0x00; I2C_InitStruct.I2C_Ack = I2C_Ack_Enable; I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; I2C_DeInit(I2C_PERIPH); I2C_Init(I2C_PERIPH, &I2C_InitStruct); I2C_Cmd(I2C_PERIPH, ENABLE); // utils_DelayUs(50); I2C_GenerateSTART(I2C_PERIPH, ENABLE); utils_DelayUs(50); I2C_GenerateSTART(I2C_PERIPH, DISABLE); utils_DelayUs(50); I2C_GenerateSTOP(I2C_PERIPH, ENABLE); utils_DelayUs(50); I2C_GenerateSTOP(I2C_PERIPH, DISABLE); utils_DelayUs(50); } /** * @brief Performs a stop condition, to abort the current transaction. */ void i2c_Reset(void) { I2C_GenerateSTOP(I2C_PERIPH, ENABLE); utils_DelayUs(50); I2C_GenerateSTOP(I2C_PERIPH, DISABLE); utils_DelayUs(50); } /** * @brief Writes a 8-bit value to a register. * @param slaveAddress 7-bit I2C slave address. * @param registerAddress 8-bit register address. * @param registerValue value to write to the 8-bit register. * @param ok the value pointed will be set to true if the operation completed * successfully, or to false if an error occured. If a NULL pointer is given, * then it is ignored. */ void i2c_WriteRegister(uint8_t slaveAddress, uint8_t registerAddress, uint8_t registerValue, bool *ok) { i2c_WriteMultiBytesRegister(slaveAddress, registerAddress, ®isterValue, 1, ok); } /** * @brief Writes several bytes to a register. * @param slaveAddress 7-bit I2C slave address. * @param registerAddress 8-bit register address. * @param registerValue values array to write to the 8-bit register. * @param registerSize number of bytes to write. * @param ok the value pointed will be set to true if the operation completed * successfully, or to false if an error occured. If a NULL pointer is given, * then it is ignored. */ void i2c_WriteMultiBytesRegister(uint8_t slaveAddress, uint8_t registerAddress, uint8_t const *registerValue, uint8_t registerSize, bool *ok) { int i; // Wait until the I2C peripheral is ready. WAIT_WITH_TIMEOUT(I2C_GetFlagStatus(I2C_PERIPH, I2C_FLAG_BUSY)); // Send a start condition, and wait until the slave has acknowledged. I2C_GenerateSTART(I2C_PERIPH, ENABLE); WAIT_WITH_TIMEOUT(!I2C_CheckEvent(I2C_PERIPH, I2C_EVENT_MASTER_MODE_SELECT)); // Send slave address and write mode, and wait until the slave has // acknowledged. I2C_Send7bitAddress(I2C_PERIPH, slaveAddress<<1, I2C_Direction_Transmitter); WAIT_WITH_TIMEOUT(!I2C_CheckEvent(I2C_PERIPH, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); // Write the register address. I2C_SendData(I2C_PERIPH, registerAddress); WAIT_WITH_TIMEOUT(!I2C_CheckEvent(I2C_PERIPH, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); // Write the register content. for(i=0; i1000){ // motorPosTurn_old = motorPosTurn; // motorPosTurn++; // }else if(localVar<(-1000)){ // motorPosTurn_old = motorPosTurn; // motorPosTurn--; // }else{ // motorPosTurn_old_tmp = motorPosTurn; // motorPosTurn = motorPosTurn_old; // motorPosTurn_old = motorPosTurn_old_tmp; // } // // TIM_SetCounter(TIM4,(uint32_t)CODER_INDEX_POS); // if(~statusReg & REF_POS_INIT){ // First time index is detected. // statusReg |= REF_POS_INIT; // motorPosTurn = 0; // //TIM_SetCounter(TIM4,(uint32_t)6000); // } EXTI_ClearITPendingBit(EXTI_Line8); } } diff --git a/Firmware/src/drivers/incr_encoder.h b/Firmware/src/drivers/incr_encoder.h index 178472c..e9ad5f8 100644 --- a/Firmware/src/drivers/incr_encoder.h +++ b/Firmware/src/drivers/incr_encoder.h @@ -1,57 +1,57 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __INCR_ENCODERS_H #define __INCR_ENCODERS_H #include "../main.h" #define CODER_A_Pin GPIO_Pin_0 #define CODER_A_Port GPIOA #define CODER_A_PinSource GPIO_PinSource0 #define CODER_B_Pin GPIO_Pin_1 #define CODER_B_Port GPIOA #define CODER_B_PinSource GPIO_PinSource1 #define CODER_I_Pin GPIO_Pin_3 #define CODER_I_Port GPIOE #define CODER_RESOLUTION ((uint32_t)2000) // Number of increments per turn. /** @defgroup Encoder Driver / Incremental encoder * @brief Driver for an incremental encoder. * * This driver uses a timer of the STM32, configured as a quadrature decoder. * * Call enc_Init() first in the initialization code. Then, call enc_GetPosition() to * get the decoded value. * The output value is the paddle position in degrees, taking the reduction * ratio into account. * * @addtogroup Encoder * @{ */ void enc_Init(void); float32_t enc_GetPosition(void); void enc_SetPosition(float32_t newPosition); /** * @} */ #endif diff --git a/Firmware/src/drivers/led.c b/Firmware/src/drivers/led.c index e39ef3e..d7588b2 100644 --- a/Firmware/src/drivers/led.c +++ b/Firmware/src/drivers/led.c @@ -1,137 +1,137 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "led.h" #include "../communication.h" #define N_LEDS 4 #define LED_TIMER TIM1 #define LED_PORT GPIOA #define LED_MAX_DUTY 255 #define LED_PWM_FREQ 32000 // [Hz]. typedef struct { uint16_t const pin, pinSource; volatile uint32_t *const duty; } led_Led; led_Led led_leds[N_LEDS] = { { GPIO_Pin_8, GPIO_PinSource8, &LED_TIMER->CCR1 }, { GPIO_Pin_9, GPIO_PinSource9, &LED_TIMER->CCR2 }, { GPIO_Pin_10, GPIO_PinSource10, &LED_TIMER->CCR3 }, { GPIO_Pin_11, GPIO_PinSource11, &LED_TIMER->CCR4 }, }; void setLed0(float32_t brightness) { led_Set(0, brightness); }; void setLed1(float32_t brightness) { led_Set(1, brightness); }; void setLed2(float32_t brightness) { led_Set(2, brightness); }; void setLed3(float32_t brightness) { led_Set(3, brightness); }; float32_t getLed0(void) { return led_Get(0); }; float32_t getLed1(void) { return led_Get(1); }; float32_t getLed2(void) { return led_Get(2); }; float32_t getLed3(void) { return led_Get(3); }; /** * @brief Initializes the LEDs module. */ void led_Init(void) { int i; GPIO_InitTypeDef GPIO_InitStruct; TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct; TIM_OCInitTypeDef TIM_OCInitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); // Initialize each pin. for(i=0; i= 0 && ledIndex < N_LEDS) return ((float32_t)(*led_leds[ledIndex].duty)) / (float32_t)LED_MAX_DUTY; else return 0.0f; } /** * @brief Sets the intensity of a single LED. * @param ledIndex: LED index (0, 1, 2 or 3). * @param brightness: the LED intensity [0.0-1.0]. */ void led_Set(int ledIndex, float32_t brightness) { if(ledIndex >= 0 && ledIndex < N_LEDS && brightness >= 0.0f && brightness <= 1.0f) { *led_leds[ledIndex].duty = (uint32_t)(brightness * (float32_t)LED_MAX_DUTY); } } diff --git a/Firmware/src/drivers/led.h b/Firmware/src/drivers/led.h index a990cd0..a1e1f34 100644 --- a/Firmware/src/drivers/led.h +++ b/Firmware/src/drivers/led.h @@ -1,40 +1,40 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __LED_H #define __LED_H #include "../main.h" /** @defgroup LED Driver / LED * @brief Driver to control a LEDs row. * * Call led_Init() first in the initialization code. Then, call led_Set() to * set the LEDs ON or OFF. * * @addtogroup LED * @{ */ void led_Init(void); float32_t led_Get(int ledIndex); void led_Set(int ledIndex, float32_t brightness); /** * @} */ #endif diff --git a/Firmware/src/drivers/mpu_6050.c b/Firmware/src/drivers/mpu_6050.c index 5dc5fd0..d483891 100644 --- a/Firmware/src/drivers/mpu_6050.c +++ b/Firmware/src/drivers/mpu_6050.c @@ -1,228 +1,228 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "mpu_6050.h" #include #include "i2c.h" #include "../lib/utils.h" #define GRAVITY_INTENSITY 9.81f #define SINT16_MAX_F ((float)SHRT_MAX) #define SLAVE_ADDRESS 0x68 ///< 0x68 if the AD0 pin is low, 0x69 otherwise. #define REG_CONFIG 0x1A ///< Configuration register. #define REG_GYRO_CONFIG 0x1B ///< Gyroscope configuration register. #define REG_ACCEL_CONFIG 0x1C ///< Accelerometer configuration register. #define REG_SMPLRT_DIV 0x19 ///< Sample rate divider register. #define REG_ACCEL_VALUES 0x3b ///< Accelerometer measurements register. #define REG_TEMP_VALUES 0x41 ///< Temperature measurements register. #define REG_GYRO_VALUES 0x43 ///< Gyroscope measurements register. #define REG_SIG_PATH_RST 0x68 ///< Signal path reset register. #define REG_USER_CTRL 0x6A ///< User control register. #define REG_POWER_MGMT_1 0x6B ///< Power management 1 register. #define REG_WHO_AM_I 0x75 ///< Who am I register. #define WHO_AM_I_VAL 0x68 ///< Expected value of the REG_WHO_AM_I register. /// Gets the actual range value from a mpu_AccelRange enum value. const float MPU_ACCEL_RANGE_REG_TO_CONV_FACTOR[] = { 2.0f / SINT16_MAX_F * GRAVITY_INTENSITY, 4.0f / SINT16_MAX_F * GRAVITY_INTENSITY, 8.0f / SINT16_MAX_F * GRAVITY_INTENSITY, 16.0f / SINT16_MAX_F * GRAVITY_INTENSITY }; /// Gets the actual range value from a mpu_GyroRange enum value. const float MPU_GYRO_RANGE_REG_TO_CONV_FACTOR[] = { 250.0f / SINT16_MAX_F, 500.0f / SINT16_MAX_F, 1000.0f / SINT16_MAX_F, 2000.0f / SINT16_MAX_F }; bool mpu_initialized = false; ///< True if the MPU-6050 is initialized, false otherwise. float accelFactor, ///< Converts a raw 16-bit integer value to an acceleration [m/s^2]. gyroFactor; ///< Converts a raw 16-bit integer value to an angular speed [deg/s]. /** * @brief Initializes the MPU-6050 device. * @param accelRange accelerometer range. * @param gyroRange gyroscope range. * @param bandwidth bandwith of the accelerometer and gyroscope. * @return true if the initialization was successful, false otherwise. */ bool mpu_Init(mpu_AccelRange accelRange, mpu_GyroRange gyroRange, mpu_Bandwidth bandwidth) { uint16_t id; // accelFactor = MPU_ACCEL_RANGE_REG_TO_CONV_FACTOR[accelRange]; gyroFactor = MPU_GYRO_RANGE_REG_TO_CONV_FACTOR[gyroRange]; // Reset the MPU-6050. i2c_WriteRegister(SLAVE_ADDRESS, REG_POWER_MGMT_1, (1<<7)|(1<<6), NULL); // Device reset. utils_DelayMs(100); // Test the communication with the chip. id = i2c_ReadRegister(SLAVE_ADDRESS, REG_WHO_AM_I, NULL); if(id != WHO_AM_I_VAL) // Error. return false; // Reset the MPU-6050 again. i2c_WriteRegister(SLAVE_ADDRESS, REG_POWER_MGMT_1, (1<<7)|(1<<6), NULL); // Device reset. utils_DelayMs(100); i2c_WriteRegister(SLAVE_ADDRESS, REG_SIG_PATH_RST, (1<<2)|(1<<1)|(1<<0), NULL); // Signal path reset. utils_DelayMs(100); // Setup the MPU-60X0 registers. i2c_WriteRegister(SLAVE_ADDRESS, REG_USER_CTRL, (0<<6) | // Disable FIFO. (0<<5) | // Disable master mode for the external I2C sensor. (0<<4) | // Do not disable the I2C slave interface (mandatory for the MPU-6050). (0<<2) | // Do not reset the FIFO. (0<<1) | // Do not reset the I2C. (0<<0), // Do not reset the signal paths for the sensors. NULL); i2c_WriteRegister(SLAVE_ADDRESS, REG_POWER_MGMT_1, (0<<7) | // Do not reset the device. (0<<6) | // Disable sleep. (0<<5) | // Disable the "cycle" mode. (0<<3) | // Do not disable the temperature sensor. (3<<0), // PLL with Z axis gyro ref. NULL); i2c_WriteRegister(SLAVE_ADDRESS, REG_GYRO_CONFIG, (0<<7) | // Do not perform self-test for axis X. (0<<6) | // Do not perform self-test for axis Y. (0<<5) | // Do not perform self-test for axis Z. (gyroRange<<3), // Gyroscope range. NULL); i2c_WriteRegister(SLAVE_ADDRESS, REG_ACCEL_CONFIG, (0<<7) | // Do not perform self-test for axis X. (0<<6) | // Do not perform self-test for axis Y. (0<<5) | // Do not perform self-test for axis Z. (accelRange<<3), // Accelerometer range. NULL); i2c_WriteRegister(SLAVE_ADDRESS, REG_CONFIG, (0<<3) | // Disable synchronisation with FSYNC pin. (bandwidth<<0), // Accelerometer and gyroscope bandwidth. NULL); if(bandwidth == MPU_DLPF_BW_256HZ) i2c_WriteRegister(SLAVE_ADDRESS, REG_SMPLRT_DIV, 7, NULL); else i2c_WriteRegister(SLAVE_ADDRESS, REG_SMPLRT_DIV, 0, NULL); // mpu_initialized = true; return true; } /** * @brief Acquires the acceleration from the IMU. * @param ax variable to write the X-axis acceleration on [m/s^2]. * @param ay variable to write the Y-axis acceleration on [m/s^2]. * @param az variable to write the Z-axis acceleration on [m/s^2]. * @remark ax, ay, and az pointer must be valid (they can't be NULL). * @remark if the MPU-6050 is not initialized (mpu_Init() was not called * successfully), then this function does nothing. * @remark If the operation failed, then *ax, *ay and *az are not modified. */ void mpu_GetAcceleration(float *ax, float *ay, float *az) { bool ok; uint8_t accelData[6]; if(!mpu_initialized) return; i2c_ReadMultiBytesRegister(SLAVE_ADDRESS, REG_ACCEL_VALUES, &accelData[0], 6, &ok); if(!ok) return; *ax = ((float)(int16_t)((accelData[0]<<8) | accelData[1])) * accelFactor; *ay = ((float)(int16_t)((accelData[2]<<8) | accelData[3])) * accelFactor; *az = ((float)(int16_t)((accelData[4]<<8) | accelData[5])) * accelFactor; } /** * @brief Acquires the angular speed from the IMU. * @param gx variable to write the X-axis angular speed on [deg/s]. * @param gy variable to write the Y-axis angular speed on [deg/s]. * @param gz variable to write the Z-axis angular speed on [deg/s]. * @remark gx, gy, and gz pointer must be valid (they can't be NULL). * @remark if the MPU-6050 is not initialized (mpu_Init() was not called * successfully), then this function does nothing. * @remark If the operation failed, then *ax, *ay and *az are not modified. */ void mpu_GetAngularSpeed(float *gx, float *gy, float *gz) { bool ok; uint8_t gyroData[6]; if(!mpu_initialized) return; i2c_ReadMultiBytesRegister(SLAVE_ADDRESS, REG_GYRO_VALUES, &gyroData[0], 6, &ok); if(!ok) return; *gx = ((float)(int16_t)((gyroData[0]<<8) | gyroData[1])) * gyroFactor; *gy = ((float)(int16_t)((gyroData[2]<<8) | gyroData[3])) * gyroFactor; *gz = ((float)(int16_t)((gyroData[4]<<8) | gyroData[5])) * gyroFactor; } /** * @brief Acquires the temperature from the IMU. * @return the measured temperature [°C], or 0 if the operation failed. * @remark if the MPU-6050 is not initialized (mpu_Init() was not called * successfully), then this function does nothing. */ float mpu_GetTemperature(void) { bool ok; uint8_t tempData[2]; if(!mpu_initialized) return 0.0f; i2c_ReadMultiBytesRegister(SLAVE_ADDRESS, REG_TEMP_VALUES, &tempData[0], 2, &ok); if(!ok) return 0.0f; return ((float)(int16_t)((tempData[0]<<8) | tempData[1])) / 340.0f + 36.53f; // Coefs from the registers map spec. } diff --git a/Firmware/src/drivers/mpu_6050.h b/Firmware/src/drivers/mpu_6050.h index c9de4ba..312b9ee 100644 --- a/Firmware/src/drivers/mpu_6050.h +++ b/Firmware/src/drivers/mpu_6050.h @@ -1,83 +1,83 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __MPU_6050_H #define __MPU_6050_H #include "../main.h" /** @defgroup MPU-6050 Driver / MPU-6050 * @brief Driver for the MPU-6050, a 6-axis IMU from Invensense. * * This driver uses the I2C bus of the digital extension connector. * * Call mpu_Init() first in the initialization code. Then, call mpu_Get() to * get the accelerations and angular speeds. * * @note This driver setup the MPU-6050 sampling rate to 1 kHz. Calling * mpu_Get() more often will return the same values. * * @addtogroup MPU-6050 * @{ */ /** * @brief Enumeration of the possible accelerometer ranges. */ typedef enum { MPU_ACCEL_RANGE_2G = 0, ///< +-2 G (+-19.62 m/s^2). MPU_ACCEL_RANGE_4G = 1, ///< +-4 G (+-39.24 m/s^2). MPU_ACCEL_RANGE_8G = 2, ///< +-8 G (+-78.48 m/s^2). MPU_ACCEL_RANGE_16G = 3 ///< +-16 G (+-156.96 m/s^2). } mpu_AccelRange; /** * @brief Enumeration of the possible gyrometer ranges. */ typedef enum { MPU_GYRO_RANGE_250DPS = 0, ///< +- 250 deg/s. MPU_GYRO_RANGE_500DPS = 1, ///< +- 500 deg/s. MPU_GYRO_RANGE_1000DPS = 2, ///< +- 1000 deg/s. MPU_GYRO_RANGE_2000DPS = 3 ///< +- 2000 deg/s. } mpu_GyroRange; /** * @brief Enumeration of the possible bandwiths. */ typedef enum { MPU_DLPF_BW_256HZ = 0, ///< Cut-off freq: 256 Hz. IMU sampling freq: 8kHz. MPU_DLPF_BW_188HZ = 1, ///< Cut-off freq: 188 Hz. IMU sampling freq: 1kHz. MPU_DLPF_BW_98HZ = 2, ///< Cut-off freq: 98 Hz. IMU sampling freq: 1kHz. MPU_DLPF_BW_42HZ = 3, ///< Cut-off freq: 42 Hz. IMU sampling freq: 1kHz. MPU_DLPF_BW_20HZ = 4, ///< Cut-off freq: 20 Hz. IMU sampling freq: 1kHz. MPU_DLPF_BW_10HZ = 5, ///< Cut-off freq: 10 Hz. IMU sampling freq: 1kHz. MPU_DLPF_BW_5HZ = 6 ///< Cut-off freq: 5 Hz. IMU sampling freq: 1kHz. } mpu_Bandwidth; bool mpu_Init(mpu_AccelRange accelRange, mpu_GyroRange gyroRange, mpu_Bandwidth bandwidth); void mpu_GetAcceleration(float *ax, float *ay, float *az); void mpu_GetAngularSpeed(float *gx, float *gy, float *gz); float mpu_GetTemperature(void); /** * @} */ #endif diff --git a/Firmware/src/drivers/uart.c b/Firmware/src/drivers/uart.c index 6793333..093a009 100644 --- a/Firmware/src/drivers/uart.c +++ b/Firmware/src/drivers/uart.c @@ -1,297 +1,297 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "uart.h" #include "../lib/utils.h" #include "../communication.h" // TODO: REMOVE. DEBUG ONLY. #define UART_RX_DMA DMA1_Stream5 #define UART_RX_DMA_CHANNEL DMA_Channel_4 #define UART_TX_DMA DMA1_Stream6 #define UART_TX_DMA_CHANNEL DMA_Channel_4 #define UART_TX_BUFFER_SIZE 4096 #define UART_RX_BUFFER_SIZE 512 #define UART_USER_RX_QUEUE_SIZE 512 uint8_t uart_txBuffer[2][UART_TX_BUFFER_SIZE]; uint8_t uart_currentTxBufferToWriteTo; uint16_t uart_txBufferIndex; uint16_t uart_nBytesToTransferDma; uint8_t uart_rxBuffer[UART_RX_BUFFER_SIZE]; uint8_t const * uart_rxBuffTail; uint8_t uart_userRxQueue[UART_USER_RX_QUEUE_SIZE]; cb_CircularBuffer uart_rxQueue; #define UART_DMA_TX_IS_BUSY (DMA_GetCmdStatus(UART_TX_DMA) == ENABLE && DMA_GetCurrDataCounter(UART_TX_DMA) > 0) /** * @brief Initializes the UART module. */ void uart_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; USART_InitTypeDef USART_InitStruct; DMA_InitTypeDef DMA_InitStruct; // Enable UART and DMA peripherals clocks. RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE); // Setup GPIOs as UART pins. GPIO_InitStruct.GPIO_Pin = USART_TX_Pin; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(USART_TX_Port, &GPIO_InitStruct); GPIO_PinAFConfig(USART_TX_Port, USART_TX_PinSource, GPIO_AF_USART2); GPIO_InitStruct.GPIO_Pin = USART_RX_Pin; GPIO_Init(USART_RX_Port, &GPIO_InitStruct); GPIO_PinAFConfig(USART_RX_Port, USART_RX_PinSource, GPIO_AF_USART2); // Setup the UART peripheral. USART_InitStruct.USART_BaudRate = UART_BAUDRATE; USART_InitStruct.USART_WordLength = USART_WordLength_8b; USART_InitStruct.USART_StopBits = USART_StopBits_1; USART_InitStruct.USART_Parity = USART_Parity_No; USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; USART_Init(USART_PC_COMM, &USART_InitStruct); USART_Cmd(USART_PC_COMM, ENABLE); // Setup the DMA for RX. DMA_DeInit(UART_RX_DMA); DMA_InitStruct.DMA_Channel = UART_RX_DMA_CHANNEL; DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&USART2->DR; DMA_InitStruct.DMA_Memory0BaseAddr = (uint32_t)&uart_rxBuffer[0]; DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralToMemory; DMA_InitStruct.DMA_BufferSize = UART_RX_BUFFER_SIZE; DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_InitStruct.DMA_Mode = DMA_Mode_Circular; DMA_InitStruct.DMA_Priority = DMA_Priority_Medium; DMA_InitStruct.DMA_FIFOMode = DMA_FIFOMode_Disable; DMA_InitStruct.DMA_MemoryBurst = DMA_MemoryBurst_Single; DMA_InitStruct.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; DMA_Init(UART_RX_DMA, &DMA_InitStruct); USART_DMACmd(USART_PC_COMM, USART_DMAReq_Rx | USART_DMAReq_Tx, ENABLE); DMA_ITConfig(UART_RX_DMA, DMA_IT_TC, ENABLE); DMA_Cmd(UART_RX_DMA, ENABLE); uart_rxBuffTail = &uart_rxBuffer[0]; // Setup the DMA for TX. DMA_DeInit(UART_TX_DMA); DMA_InitStruct.DMA_Channel = UART_TX_DMA_CHANNEL; DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&USART2->DR; DMA_InitStruct.DMA_Memory0BaseAddr = (uint32_t)&uart_txBuffer[0][0]; DMA_InitStruct.DMA_DIR = DMA_DIR_MemoryToPeripheral; DMA_InitStruct.DMA_BufferSize = UART_TX_BUFFER_SIZE; DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_InitStruct.DMA_Mode = DMA_Mode_Normal; DMA_InitStruct.DMA_Priority = DMA_Priority_Medium; DMA_InitStruct.DMA_FIFOMode = DMA_FIFOMode_Disable; DMA_InitStruct.DMA_MemoryBurst = DMA_MemoryBurst_Single; DMA_InitStruct.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; DMA_Init(UART_TX_DMA, &DMA_InitStruct); USART_DMACmd(USART_PC_COMM, USART_DMAReq_Rx | USART_DMAReq_Tx, ENABLE); DMA_ITConfig(UART_TX_DMA, DMA_IT_TC, ENABLE); //DMA_Cmd(UART_TX_DMA, ENABLE); // Initialize the RX circular buffer. cb_Init(&uart_rxQueue, uart_userRxQueue, UART_USER_RX_QUEUE_SIZE); // Initialize the variables for the UART TX. uart_currentTxBufferToWriteTo = 0; uart_txBufferIndex = 0; uart_nBytesToTransferDma = 0; } /** * @brief Copies the received bytes into the user-accessible queue. * Reads all the available bytes in the DMA RX buffer, and copies them to the * user-accessible queue. * @remark This function must called often, otherwise the DMA RX buffer may be * full. */ void uart_Step(void) { // Get the location of the location currently pointed by the DMA. uint8_t const * head = uart_rxBuffer + UART_RX_BUFFER_SIZE - DMA_GetCurrDataCounter(UART_RX_DMA); // Even if the STM32F4 reference manual (RM0090) states that the NDTR // register is decremented after the transfer, this is not the case. // So we wait a few cycles to be sure that the DMA actually performed the // transfer. utils_DelayUs(1); // RX: add the received bytes into the user queue. while(uart_rxBuffTail != head) { uint8_t b = *uart_rxBuffTail; cb_Push(&uart_rxQueue, b); uart_rxBuffTail++; if(uart_rxBuffTail >= uart_rxBuffer + UART_RX_BUFFER_SIZE) uart_rxBuffTail -= UART_RX_BUFFER_SIZE; } } /** * @brief Gets the user-accessible queue of the received bytes. * @return the address of the queue. */ cb_CircularBuffer* uart_GetRxQueue(void) { return &uart_rxQueue; } /** * @brief Starts the DMA transfer to send bytes to UART peripheral. */ void uart_StartDma(void) { DMA_InitTypeDef DMA_InitStruct; // uart_nBytesToTransferDma = uart_txBufferIndex; uart_currentTxBufferToWriteTo = !uart_currentTxBufferToWriteTo; uart_txBufferIndex = 0; // Start the DMA transfer. DMA_DeInit(UART_TX_DMA); DMA_InitStruct.DMA_Channel = UART_TX_DMA_CHANNEL; DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&USART2->DR; DMA_InitStruct.DMA_Memory0BaseAddr = (uint32_t)&uart_txBuffer[!uart_currentTxBufferToWriteTo][0]; DMA_InitStruct.DMA_DIR = DMA_DIR_MemoryToPeripheral; DMA_InitStruct.DMA_BufferSize = uart_nBytesToTransferDma; DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_InitStruct.DMA_Mode = DMA_Mode_Normal; DMA_InitStruct.DMA_Priority = DMA_Priority_Medium; DMA_InitStruct.DMA_FIFOMode = DMA_FIFOMode_Disable; DMA_InitStruct.DMA_MemoryBurst = DMA_MemoryBurst_Single; DMA_InitStruct.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; DMA_Init(UART_TX_DMA, &DMA_InitStruct); DMA_Cmd(UART_TX_DMA, ENABLE); } /** * @brief Asynchronously sends the given byte through the UART bus. * @param data the data byte to send. */ void uart_SendByteAsync(uint8_t data) { // Check that it is possible to write in the current buffer. if(uart_txBufferIndex >= UART_TX_BUFFER_SIZE) { // If the DMA is idle, switch to the other buffer. if(UART_DMA_TX_IS_BUSY) { utils_TrapCpu(); // Error, can't write anywhere! return; } else uart_StartDma(); } // Write the byte in the buffer. uart_txBuffer[uart_currentTxBufferToWriteTo][uart_txBufferIndex] = data; uart_txBufferIndex++; // If the buffer is full, start the DMA transfer. if(uart_txBufferIndex == UART_TX_BUFFER_SIZE) uart_StartDma(); } /** * @brief Asynchronously sends the given bytes through the UART bus. * @param data pointer to the data bytes array to send. * @param length number of bytes to send (array size). * @remark The bytes may not be send immediately, they are stored temporarily in * an intermediate buffer that the DMA will copy to UART peripheral, when it is * full. Call uart_FlushTx() to start the DMA transfer immediately. */ void uart_SendBytesAsync(uint8_t *data, int length) { while(length > 0) { uint16_t nBytesToWriteInBuf; // Check that it is possible to write in the current buffer. if(uart_txBufferIndex >= UART_TX_BUFFER_SIZE) { // If the DMA is idle, switch to the other buffer. if(UART_DMA_TX_IS_BUSY) { utils_TrapCpu(); // Error, can't write anywhere! return; } else uart_StartDma(); } // Write as many bytes as possible in the current buffer. nBytesToWriteInBuf = UART_TX_BUFFER_SIZE - uart_txBufferIndex; if(nBytesToWriteInBuf > length) nBytesToWriteInBuf = length; memcpy(&uart_txBuffer[uart_currentTxBufferToWriteTo][uart_txBufferIndex], data, nBytesToWriteInBuf); uart_txBufferIndex += nBytesToWriteInBuf; data += nBytesToWriteInBuf; length -= nBytesToWriteInBuf; // If the buffer is full, start the DMA transfer. if(uart_txBufferIndex == UART_TX_BUFFER_SIZE) uart_StartDma(); } } /** * @brief Start the DMA to send the bytes waiting in the intermediate buffer. */ void uart_FlushTx(void) { if(uart_txBufferIndex > 0 && !UART_DMA_TX_IS_BUSY) uart_StartDma(); } diff --git a/Firmware/src/drivers/uart.h b/Firmware/src/drivers/uart.h index 7a3853b..d24b906 100644 --- a/Firmware/src/drivers/uart.h +++ b/Firmware/src/drivers/uart.h @@ -1,67 +1,67 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __UART_H #define __UART_H #include "../main.h" #include "../lib/circular_buffer.h" #define USART_RX_Pin GPIO_Pin_5 #define USART_RX_PinSource GPIO_PinSource5 #define USART_RX_Port GPIOD #define USART_TX_Pin GPIO_Pin_6 #define USART_TX_PinSource GPIO_PinSource6 #define USART_TX_Port GPIOD #define USART_PC_COMM USART2 // UART peripheral used for the comm with the PC. /** @defgroup UART Driver / UART * @brief Driver for the UART serial communication peripheral. * * This driver controls the UART peripheral of the STM32, connected to the * USB-to-serial chip. * * Call uart_Init() first in the initialization code, specifiying the function * to call when a byte arrives (sent from the computer). To send data, call * uart_SendByte() or uart_SendBytes(). * * Note that this module should not be used directly. It is a better option to * use it through the Communication module, to benefit from the already * implemented communication protocol between the board and MATLAB. * * @addtogroup UART * @{ */ /** * Typedef for a pointer to a function to call automatically when a byte * arrives from the computer. */ typedef void (*uart_rxByteHandlerFunc)(uint8_t rxByte); void uart_Init(void); void uart_Step(void); cb_CircularBuffer* uart_GetRxQueue(void); void uart_SendByteAsync(uint8_t data); void uart_SendBytesAsync(uint8_t *data, int length); void uart_FlushTx(void); /** * @} */ #endif diff --git a/Firmware/src/haptic_controller.c b/Firmware/src/haptic_controller.c index 5ac17a9..dc408b1 100644 --- a/Firmware/src/haptic_controller.c +++ b/Firmware/src/haptic_controller.c @@ -1,75 +1,75 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "haptic_controller.h" #include "communication.h" #include "drivers/adc.h" #include "drivers/incr_encoder.h" #include "drivers/hall.h" #include "drivers/callback_timers.h" #include "lib/utils.h" #include "torque_regulator.h" #define CONTROL_LOOP_PERIOD 350 // Main control loop period [us]. volatile uint32_t hapt_timestamp; // Time base of the controller, also used to timestamp the samples sent by streaming [us]. volatile float32_t hapt_hallVoltage; // Hall sensor output voltage [V]. volatile float32_t hapt_encoderPaddleAngle; // Paddle angle measured by the incremental encoder [deg]. volatile float32_t hapt_motorTorque; // Motor torque [N.m]. void hapt_Update(void); /** * @brief Initializes the haptic controller. */ void hapt_Init(void) { hapt_timestamp = 0.0f; // Make the timers call the update function periodically. cbt_SetPositionLoopTimer(hapt_Update, CONTROL_LOOP_PERIOD); // Share some variables with the computer. comm_monitorFloat("motor_torque [N.m]", (float32_t*)&hapt_motorTorque, READWRITE); comm_monitorFloat("encoder_paddle_pos [deg]", (float32_t*)&hapt_encoderPaddleAngle, READONLY); comm_monitorFloat("hall_voltage [V]", (float32_t*)&hapt_hallVoltage, READONLY); } /** * @brief Updates the haptic controller state. */ void hapt_Update() { float32_t motorShaftAngle; // [deg]. // Compute the dt (uncomment if you need it). //float32_t dt = ((float32_t)cbt_GetPositionLoopPeriod()) / 1000000.0f; // [s]. // Increment the timestamp. hapt_timestamp += cbt_GetPositionLoopPeriod(); // Get the Hall sensor voltage. hapt_hallVoltage = hall_GetVoltage(); // Get the encoder position. motorShaftAngle = enc_GetPosition(); hapt_encoderPaddleAngle = motorShaftAngle / REDUCTION_RATIO; // Compute the motor torque, and apply it. //hapt_motorTorque = 0.0f; torq_SetTorque(hapt_motorTorque); } diff --git a/Firmware/src/haptic_controller.h b/Firmware/src/haptic_controller.h index 4eb5b79..d488698 100644 --- a/Firmware/src/haptic_controller.h +++ b/Firmware/src/haptic_controller.h @@ -1,45 +1,45 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __HAPTIC_CONTROLLER_H #define __HAPTIC_CONTROLLER_H #include "main.h" /** @defgroup HapticController Main / Haptic controller * @brief Main haptic paddle controller. * * This module is the high-level controller of the board. Basically, its role * is to process the sensors data and then compute a motor torque, in order to * achieve a spicific haptic effect. * * The content of hapt_Update() is typically what the user of the board will * modify, depending on the selected sensors and control algorithms. * * Call hapt_Init() to setup this module. Its interrupt function will be called * automatically periodically. * * @addtogroup HapticController * @{ */ void hapt_Init(void); /** * @} */ #endif diff --git a/Firmware/src/lib/basic_filter.c b/Firmware/src/lib/basic_filter.c index 67b2f14..cbb7a16 100644 --- a/Firmware/src/lib/basic_filter.c +++ b/Firmware/src/lib/basic_filter.c @@ -1,42 +1,42 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "basic_filter.h" /** * @brief Initialize a BasicFilter structure. * @param filter: pointer on the structure to initialize. * @param tau: the contribution of every new value. 0.0 (max filtering) to 1.0 (no filtering). * @param initialValue: initial value for the filter. */ void bfilt_Init(bfilt_BasicFilter* filter, float32_t tau, float32_t initialValue) { filter->tau = tau; filter->filteredValue = initialValue; } /** * @brief Step a BasicFilter structure. * @param filter: pointer on the structure to initialize. * @param newValue: the new value of the signal to filter. * @retval the filtered value. * @note this implementation is very basic, and does not take the timestep (dt) into account. */ float32_t bfilt_Step(bfilt_BasicFilter* filter, float32_t newValue) { filter->filteredValue = filter->filteredValue*(1.0f-filter->tau) + newValue*filter->tau; return filter->filteredValue; } diff --git a/Firmware/src/lib/basic_filter.h b/Firmware/src/lib/basic_filter.h index 79d47a9..69b4efc 100644 --- a/Firmware/src/lib/basic_filter.h +++ b/Firmware/src/lib/basic_filter.h @@ -1,49 +1,49 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __BASIC_FILTER_H #define __BASIC_FILTER_H #include "../main.h" /** @defgroup BasicFilter Lib / Basic filter * @brief Basic iterative filter for smoothing. * * First, instance a bfilt_BasicFilter structure (e.g. "bfilt_BasicFilter * b;"), then initialize it once with bfilt_Init(). Then, every time a * new value of the signal is received, call bfilt_Step(). * * @addtogroup BasicFilter * @{ */ /** *@brief Basic filter structure. */ typedef struct { float32_t tau, ///< The strength of the filter (0.0-1.0). 0 filters the most, 1 does not filter. filteredValue; ///< Last computed filtered value. } bfilt_BasicFilter; void bfilt_Init(bfilt_BasicFilter* filter, float32_t tau, float32_t initialValue); float32_t bfilt_Step(bfilt_BasicFilter* filter, float32_t newValue); /** * @} */ #endif diff --git a/Firmware/src/lib/circular_buffer.c b/Firmware/src/lib/circular_buffer.c index 74c1e0a..795b7c6 100644 --- a/Firmware/src/lib/circular_buffer.c +++ b/Firmware/src/lib/circular_buffer.c @@ -1,125 +1,125 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "circular_buffer.h" #include "utils.h" /** * @brief Initializes a cb_CircularBuffer structure. * Initializes a cb_CircularBuffer structure with the given buffer. The buffer * has to be provided by the user, to avoid dynamic memory allocation. * @param cb the cb_CircularBuffer structure to initialize. * @param buffer pointer to an existing array. * @param bufferSize length of the circular buffer. The given buffer should have * a size greater or equal to this value. */ void cb_Init(cb_CircularBuffer* cb, uint8_t *buffer, uint16_t bufferSize) { cb->buffer = buffer; cb->bufferSize = bufferSize; cb->readIndex = 0; cb->writeIndex = 0; } /** * @brief Gets the number of bytes stored in the queue. * @param cb the cb_CircularBuffer to check. * @return the number of bytes stored in the queue. */ uint16_t cb_ItemsCount(cb_CircularBuffer* cb) { if (cb->writeIndex >= cb->readIndex) return cb->writeIndex - cb->readIndex; else return cb->bufferSize - cb->readIndex + cb->writeIndex; } /** * @brief Check if the queue is empty. * @param cb the cb_CircularBuffer to check. * @return 1 if there are no bytes stored in the queue, 0 otherwise. */ bool cb_IsEmpty(cb_CircularBuffer* cb) { return cb->writeIndex == cb->readIndex; } /** * @brief Check if the queue is full. * @param cb the cb_CircularBuffer to check. * @return 1 if the queue is full, 0 otherwise. */ bool cb_IsFull(cb_CircularBuffer* cb) { if (cb->writeIndex == cb->readIndex) return false; else if (cb->readIndex < cb->writeIndex) return (cb->readIndex == 0 && cb->writeIndex == cb->bufferSize - 1); else return (cb->writeIndex == cb->readIndex-1); } /** * @brief Add a item at the back of the queue. * @param cb the cb_CircularBuffer to affect. * @param newElem the byte to add to the back of the queue. * @warning If the queue is already full, this function does nothing if * CPU_TRAPS_ENABLED is 0. If CPU_TRAPS_ENABLED is 1, this function will block * forever the program execution, so the problem can be found with the * debugger. */ void cb_Push(cb_CircularBuffer* cb, uint8_t newElem) { if (!cb_IsFull(cb)) // Not full. { cb->buffer[cb->writeIndex] = newElem; cb->writeIndex++; if (cb->writeIndex >= cb->bufferSize) cb->writeIndex = 0; } else // Error, can't push an item, because the queue is full. utils_TrapCpu(); } /** * @brief Extract the item at the front of the queue. * Returns the value of the item at the front of the queue, and remove this item * from the queue. * @param cb the cb_CircularBuffer to affect. * @return the value of the byte that has been extracted from the queue. * @warning If the queue is empty and CPU_TRAPS_ENABLED is 0, this function * returns 0. If CPU_TRAPS_ENABLED is 1, this function will block forever the * program execution, so the problem can be found with the debugger. */ uint8_t cb_Pull(cb_CircularBuffer* cb) { if (cb->writeIndex != cb->readIndex) // Not empty. { uint8_t pulled = cb->buffer[cb->readIndex]; cb->readIndex++; if (cb->readIndex >= cb->bufferSize) cb->readIndex = 0; return pulled; } else // Error, can't pull an item, because the queue is empty. { utils_TrapCpu(); return 0; } } diff --git a/Firmware/src/lib/circular_buffer.h b/Firmware/src/lib/circular_buffer.h index 6ad7a4c..c511ca4 100644 --- a/Firmware/src/lib/circular_buffer.h +++ b/Firmware/src/lib/circular_buffer.h @@ -1,56 +1,56 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __CIRCULAR_BUFFER_H #define __CIRCULAR_BUFFER_H #include "../main.h" /** @defgroup CircularBuffer Lib / Circular buffer * @brief Bytes queue (FIFO) implemented with a circular buffer. * * This bytes container works as a queue, which means "first in, first out". * Create a cb_CircularBuffer structure, and initialize it with cb_Init(). * Then, add bytes using cb_Push(), and extract them with cb_Pull(). * * @ingroup Lib * @addtogroup CircularBuffer * @{ */ /** * @brief Circular buffer structure. */ typedef struct { uint8_t *buffer; ///< Pointer to the byte buffer. uint16_t bufferSize; ///< Size of buffer. volatile uint16_t readIndex, ///< Index of the element at the front of the queue. writeIndex; ///< Index of the next free location at the end of the queue. } cb_CircularBuffer; void cb_Init(cb_CircularBuffer* cb, uint8_t *buffer, uint16_t bufferSize); uint16_t cb_ItemsCount(cb_CircularBuffer* cb); bool cb_IsEmpty(cb_CircularBuffer* cb); bool cb_IsFull(cb_CircularBuffer* cb); void cb_Push(cb_CircularBuffer* cb, uint8_t newElem); uint8_t cb_Pull(cb_CircularBuffer* cb); /** * @} */ #endif diff --git a/Firmware/src/lib/pid.c b/Firmware/src/lib/pid.c index e9f5654..06eb37f 100644 --- a/Firmware/src/lib/pid.c +++ b/Firmware/src/lib/pid.c @@ -1,81 +1,81 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "pid.h" #include "utils.h" /** * @brief Initialize the PID structure. * @param pid: pointer to the PID structure. * @param kp: proportionnal coefficient of the PID. No effect if zero. * @param ki: integral coefficient of the PID. No effect if zero. * @param kd: derivative coefficient of the PID. No effect if zero. * @param arw: maximum value of the integrator (anti-reset windup). Disabled if negative. * @param feedforward: component proportional to the target (not the error). No effect if zero. */ void pid_Init(pid_Pid *pid, float32_t kp, float32_t ki, float32_t kd, float32_t arw, float32_t feedforward) { pid->kp = kp; pid->ki = ki; pid->kd = kd; pid->arw = arw; pid->previousErr = 0.0f; pid->integrator = 0.0f; pid->feedforward = feedforward; } /** * @brief Step the PID structure. * @param pid: pointer to the PID structure. * @param current: current state of the system to control. * @param target: target state of the system to control. * @param dt: timestep (time since the last call of this function) [s]. * @retval command to apply to the system. */ float32_t pid_Step(pid_Pid *pid, float32_t current, float32_t target, float32_t dt) { float32_t err; pid->current = current; pid->target = target; // Error computation. err = target - current; // Feedforward part. pid->command = pid->feedforward * target; // Proportionnal part. pid->command += pid->kp * err; // Integral part. if(pid->ki > 0.0f) { pid->integrator += pid->ki * (err * dt); if(pid->arw > 0.0f) utils_SaturateF(&pid->integrator, -pid->arw, pid->arw); pid->command += pid->integrator; } // Derivative part. pid->command += pid->kd * ((err - pid->previousErr) / dt); pid->previousErr = err; return pid->command; } diff --git a/Firmware/src/lib/pid.h b/Firmware/src/lib/pid.h index 1338ecc..66214a6 100644 --- a/Firmware/src/lib/pid.h +++ b/Firmware/src/lib/pid.h @@ -1,59 +1,59 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __PID_H #define __PID_H #include "../main.h" /** @defgroup PID Lib / PID regulator * @brief Proportionnal-integral-derivative regulator with integrator * saturation capability. * * First, instantiate a pid_Pid structure (e.g. "pid_Pid pid;"), then * initialize it once with pid_Init(). Then, every time a new command needs to * be computed (typically when a new measurement arrives), call pid_Step(). * * @addtogroup PID * @{ */ /** *@brief PID regulator structure */ typedef struct { float32_t kp, ///< Proportional coefficient. ki, ///< Integral coefficient. Disabled if negative or zero. kd, ///< Derivative coefficient. arw, ///< Max value of the integrator ("anti-reset windup"). Disabled if negative or zero. previousErr, ///< Error (current-target) at the previous timestep, for derivative computation. integrator, ///< Integrator value for the integral part of the PID. current, ///< Current state of the system to control. target, ///< Desired state of the system to control. command, ///< Output command computed by the PID regulator. feedforward; ///< Feedforward coefficient. } pid_Pid; void pid_Init(pid_Pid *pid, float32_t kp, float32_t ki, float32_t kd, float32_t arw, float32_t feedforward); float32_t pid_Step(pid_Pid *pid, float32_t current, float32_t target, float32_t dt); /** * @} */ #endif diff --git a/Firmware/src/lib/utils.c b/Firmware/src/lib/utils.c index cf27e76..70deedc 100644 --- a/Firmware/src/lib/utils.c +++ b/Firmware/src/lib/utils.c @@ -1,122 +1,122 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "utils.h" /** * @brief Endless loop function to stop the execution of the program here. * @note This function does nothing if CPU_TRAPS_ENABLED is set to zero. */ void utils_TrapCpu(void) { #if CPU_TRAPS_ENABLED while(1) ; #endif } /** * @brief "Busy wait" delay function * @param duration: Delay time in [us] (approximative value based on a 168MHz core clock) */ void utils_DelayUs(uint32_t duration) { uint32_t i = 0; uint32_t j = 0; for(i=0; i<=duration; i++) { for(j=0; j<=52; j++) { #ifdef __GNUC__ asm("nop"); #else __asm{NOP}; #endif } } } /** * @brief "Busy wait" delay function * @param duration: Delay time in [ms] (approximative value based on a 168MHz core clock). * @note This delay is approximative, and may last longer if there are many interrupts. */ void utils_DelayMs(uint32_t duration) { uint32_t i = 0; uint32_t j = 0; for(i=0; i<=duration; i++) { for(j=0; j<=33600; j++) { #ifdef __GNUC__ asm("nop"); #else __asm{NOP}; #endif } } } /** * @brief Saturate a float number between two bounds. * @param val: value to constrain between two limits. * @param min: minimum * @param max: maximum * @retval None. */ void utils_SaturateF(float32_t *val, float32_t min, float32_t max) { if(*val < min) *val = min; else if(*val > max) *val = max; } /** * @brief Saturate an integer number between two bounds. * @param val: value to constrain between the two limits. * @param min: lower limit. * @param max: upper limit. * @retval None. */ void utils_SaturateU(uint32_t *val, uint32_t min, uint32_t max) { if(*val < min) *val = min; else if(*val > max) *val = max; } /** * @brief Compute the mean of the array values. * @param array: array of float number to get the mean from. * @param size: size of the array. * @retval the mean of the array values. */ float32_t utils_Mean(float32_t *array, int size) { int i; float32_t sum = 0.0f; for(i=0; i #include #include "arm_math.h" #include "stm32f4xx_adc.h" #include "stm32f4xx_dac.h" #include "stm32f4xx_dma.h" #include "stm32f4xx_exti.h" #include "stm32f4xx_gpio.h" #include "stm32f4xx_i2c.h" #include "stm32f4xx_iwdg.h" #include "stm32f4xx_pwr.h" #include "stm32f4xx_rcc.h" #include "stm32f4xx_rtc.h" #include "stm32f4xx_spi.h" #include "stm32f4xx_syscfg.h" #include "stm32f4xx_tim.h" #include "stm32f4xx_usart.h" #include "stm32f4xx_wwdg.h" // Clocks configuration. #define STM_SYSCLOCK_FREQ 168000000 // [Hz]. #define APB1_PRESCALER 4 #define APB2_PRESCALER 2 #define TIM_MULTIPLIER 2 // Interupt priority. #define CURRENT_LOOP_IRQ_PRIORITY 1 // High freq loop, should interrupt all the others. #define CONTROL_LOOP_IRQ_PRIORITY 2 #define CODER_INDEX_IRQ_PRIORITY 2 // Useless, remove? #define UART_RX_IRQ_PRIORIY 3 #define DATA_LOOP_IRQ_PRIORITY 4 // Streaming packets. #define USER_BUTTON_IRQ_PRIORITY 4 // Electrical parameters. #define STM_SUPPLY_VOLTAGE 3.3f // Power supply voltage of the microcontroller [V]. #define ADC_REF_VOLTAGE 2.5f // Voltage reference of the ADC (VREF) [V]. #define H_BRIDGE_SUPPLY_VOLTAGE 24.0f // [V]. #define CURRENT_SHUNT_RESISTANCE 0.025f // [ohm]. #define CURRENT_SHUNT_AMPLIFIER_GAIN 30.0f // Gain of 60 (AD817) / 2 (voltage divider) []. #define MOTOR_RESISTANCE (10.6f + 5.0f) // 10.6 ohm according to the datasheet, actually more, depends on the motor [ohm]. // Mechanical parameters. #define REDUCTION_RATIO 16.5f #define MOTOR_TORQUE_CONST 0.0538f // [N.m/A]. #define MOTOR_SPEED_CONST 177.0f // [RPM/V]. #define MOTOR_NOMINAL_TORQUE 0.0323f // [N.m]. #endif diff --git a/Firmware/src/torque_regulator.c b/Firmware/src/torque_regulator.c index f883b13..7f141a3 100644 --- a/Firmware/src/torque_regulator.c +++ b/Firmware/src/torque_regulator.c @@ -1,152 +1,152 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "communication.h" #include "drivers/adc.h" #include "drivers/dac.h" #include "drivers/debug_gpio.h" #include "drivers/incr_encoder.h" #include "drivers/hall.h" #include "drivers/h_bridge.h" #include "drivers/callback_timers.h" #include "lib/basic_filter.h" #include "lib/pid.h" #include "lib/utils.h" #include "torque_regulator.h" #define KP_CURRENT_DEFAULT_VAL 100.0f // [V/A]. #define KI_CURRENT_DEFAULT_VAL 500000.0f // [V/(A.s)]. #define KD_CURRENT_DEFAULT_VAL 0.0f // [V/(A/s)]. #define FF_CURRENT_DEFAULT_VAL MOTOR_RESISTANCE // [V/A]. #define CURRENT_INTEGRATOR_SAT_DEFAULT_VAL 10.0f // [V]. #define CURRENT_LOOP_PWM_MAX_DUTY_CYCLE 0.98f // Max PWM value (normalized) duty cycle (less than 1 to ensure bootstrap capacitor charging). #define CURRENT_LOOP_PERIOD 50 // Current control loop period [us]. #define SOFTER_PID_DURATION 0.005f // Short time when softer PID settings are used, in case a H-bridge fault is detected [s]. volatile float32_t torq_targetCurrent; // Target current [A]. volatile pid_Pid torq_currentPid; bool torq_regulateCurrent; float32_t torq_pidSoftModeTime; // Remaining time for the "softer PID settings" mode [s]. void torq_RegulateCurrent(void); /** * @brief Initialize the position and current controllers. */ void torq_Init(void) { torq_targetCurrent = 0.0f; torq_pidSoftModeTime = 0.0f; // By default the current regulator is off, to allow the calibration of the // current sensor. torq_regulateCurrent = false; // Setup the PID. pid_Init((pid_Pid*)&torq_currentPid, KP_CURRENT_DEFAULT_VAL, KI_CURRENT_DEFAULT_VAL, KD_CURRENT_DEFAULT_VAL, CURRENT_INTEGRATOR_SAT_DEFAULT_VAL, FF_CURRENT_DEFAULT_VAL); // Make the timers call the regulation function periodically. cbt_SetCurrentLoopTimer(torq_RegulateCurrent, CURRENT_LOOP_PERIOD); // Share some variables with the computer. comm_monitorFloat("actual_current [A]", (float32_t*)&torq_currentPid.current, READONLY); comm_monitorFloat("target_current [A]", (float32_t*)&torq_currentPid.target, READONLY); } /** * @brief Start the current regulation. */ void torq_StartCurrentLoop(void) { torq_regulateCurrent = true; } /** * @brief Current regulation "loop" function. */ void torq_RegulateCurrent() { float32_t motorVoltage, // Motor command voltage [V]. pwmNormalizedDutyCycle; // Motor normalized PWM duty (-1 ot 1). float32_t motorCurrentCurrent; // [A]. float32_t dt; // [s]. // Compute the dt. dt = (float32_t)cbt_GetCurrentLoopPeriod()*MICROSECOND_TO_SECOND; // Get the actual current. motorCurrentCurrent = adc_GetCurrent(); // If the H-Bridge chip is in fault, it will block the current. So, reset // the PID integrator and soften the PID settings, to avoid a new current // surge when it will resume the current. This avoid the continuous // "beeping" that can appear in some conditions. if(hb_HasFault()) { torq_currentPid.kp = KP_CURRENT_DEFAULT_VAL / 4.0f; torq_currentPid.ki = KI_CURRENT_DEFAULT_VAL / 4.0f; torq_currentPid.integrator = 0.0f; torq_pidSoftModeTime = SOFTER_PID_DURATION; } // Manage the "softer PID" mode. if(torq_pidSoftModeTime > 0.0f) { torq_pidSoftModeTime -= dt; torq_currentPid.integrator = 0.0f; // Keep the integrator empty. if(torq_pidSoftModeTime <= 0.0f) { // Go back to the normal PID settings. torq_currentPid.kp = KP_CURRENT_DEFAULT_VAL; torq_currentPid.ki = KI_CURRENT_DEFAULT_VAL; } } // Regulate. motorVoltage = -pid_Step((pid_Pid*)&torq_currentPid, motorCurrentCurrent, torq_targetCurrent, (float32_t)cbt_GetCurrentLoopPeriod()*MICROSECOND_TO_SECOND); // Normalize to get a signed PWM duty (between -1 and 1). pwmNormalizedDutyCycle = motorVoltage / H_BRIDGE_SUPPLY_VOLTAGE; utils_SaturateF(&pwmNormalizedDutyCycle, -CURRENT_LOOP_PWM_MAX_DUTY_CYCLE, CURRENT_LOOP_PWM_MAX_DUTY_CYCLE); // Apply the computed PWM duty, if the current regulation is enabled. if(torq_regulateCurrent) hb_SetPWM(pwmNormalizedDutyCycle); else hb_SetPWM(0.0f); } /** * @brief Sets the target motor torque. * @param torque target motor torque [N.m]. * @remark The given value will be saturated to the motor torque, if larger. */ void torq_SetTorque(float32_t torque) { utils_SaturateF(&torque, -MOTOR_NOMINAL_TORQUE, MOTOR_NOMINAL_TORQUE); torq_targetCurrent = torque / MOTOR_TORQUE_CONST; } diff --git a/Firmware/src/torque_regulator.h b/Firmware/src/torque_regulator.h index 6e6fb87..e2dce23 100644 --- a/Firmware/src/torque_regulator.h +++ b/Firmware/src/torque_regulator.h @@ -1,42 +1,42 @@ /* - * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). + * Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __TORQUE_REGULATOR_H #define __TORQUE_REGULATOR_H #include "main.h" /** @defgroup TorqueRegulator Main / Torque Regulator * @brief Sets the desired motor, by controlling the current. * * Call torq_Init() to setup this module. Its interrupt function will be called * automatically periodically. Then, call torq_StartCurrentLoop() when the * current sensor is calibrated. torq_SetTorque() can now be called at any time * to set the target torque. * * @addtogroup TorqueRegulator * @{ */ void torq_Init(void); void torq_StartCurrentLoop(void); void torq_SetTorque(float32_t torque); /** * @} */ #endif diff --git a/MATLAB/hri_comm.m b/MATLAB/hri_comm.m index 3559981..36d4f26 100644 --- a/MATLAB/hri_comm.m +++ b/MATLAB/hri_comm.m @@ -1,507 +1,507 @@ -% Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). +% Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). % % Licensed under the Apache License, Version 2.0 (the "License"); % you may not use this file except in compliance with the License. % You may obtain a copy of the License at % % http://www.apache.org/licenses/LICENSE-2.0 % % Unless required by applicable law or agreed to in writing, software % distributed under the License is distributed on an "AS IS" BASIS, % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. % See the License for the specific language governing permissions and % limitations under the License. classdef hri_comm < handle % HHRIBOARD HHRI board class. % Manages the communication between MATLAB on PC and the HHRI board, % and allows remote operation. properties(Access = private) hSerial packetBuffer rxCurrentMessageType rxBytesCount firstHalfByte rxTimer pendingPingback pendingGetVarsList pendingGetVar logfile varsList streamId varsIdsToStream streamPacketSize streamBuffer currentStreamBufferIndex gotVarsListCallbackFunc boardOfflineCallbackFunc plotDecimCounter end properties(Hidden = true, Access = private, Constant = true) %% Messages sent by MATLAB to the board. PC_MESSAGE_DO_NOTHING = 0; % Do nothing. PC_MESSAGE_PING = 1; PC_MESSAGE_GET_VARS_LIST = 2; % Request the device to send the variables list. PC_MESSAGE_SET_STREAMED_VAR = 3; % Set the variables to be streamed. PC_MESSAGE_GET_VAR = 4; % Request the device to send the selected value. PC_MESSAGE_SET_VAR = 5; % Set the selected variable. %% Messages sent by the board to MATLAB. STM_MESSAGE_PINGBACK = 0; % Response to a ping request. STM_MESSAGE_VAR = 1; % Variable state. STM_MESSAGE_STREAMING_PACKET = 2; % Streaming packet. STM_MESSAGE_DEBUG_TEXT = 3; % Debug text message. STM_MESSAGE_VARS_LIST = 4; % Monitored variables list. STM_MESSAGE_START_INFO = 5; % Notification that the board has (re)started. %% Other constants. SYNCVAR_NAME_SIZE = 50; % Max size of a SyncVar name, including the '\0' trailing character. STREAMING_BUFF_LEN = 1000; % [bytes]. READ_BYTES_PERIOD = 0.100; % [s]. VAR_ACCESSES = {'readonly', 'writeonly', 'read+write'}; VAR_TYPES = {'bool', 'uint8', 'int8', 'uint16', 'int16', ... 'uint32', 'int32', 'uint64', 'int64', 'single', ... 'double'}; end methods function hbh = hri_comm(comPortName, gotVarsListCallbackFunc) % HHRIBOARD Class constructor. hbh.hSerial = serial(comPortName, ... 'BaudRate', 1843200, ... 'Parity', 'none', ... 'StopBits', 1, ... 'FlowControl', 'none', ... 'Timeout', 0.5, ... 'InputBufferSize', 100000, ... 'ReadAsyncMode', 'continuous'); hbh.packetBuffer = zeros(1, 1000, 'uint8'); hbh.rxCurrentMessageType = 0; hbh.rxBytesCount = 0; hbh.pendingPingback = 0; hbh.logfile = 0; hbh.rxTimer = 0; hbh.streamId = 0; hbh.gotVarsListCallbackFunc = gotVarsListCallbackFunc; hbh.plotDecimCounter = 1; end function open(hbh) % OPEN Open the serial link, and start receiving data. % Open the serial link. fopen(hbh.hSerial); flushinput(hbh.hSerial); % Setup the callback function, called periodically to read the % bytes sent by the board. hbh.rxTimer = timer('Period', hbh.READ_BYTES_PERIOD, ... 'TimerFcn', @(~,~)readBytes(hbh), ... 'ExecutionMode', 'fixedRate', ... 'BusyMode', 'drop'); start(hbh.rxTimer); % Stop the streaming, if it was running. setStreamedVars(hbh, []); % Get the remote variables list, if the board is OK. if ping(hbh) requestVarsList(hbh); end end function close(hbh) % CLOSE Close the serial link. if hbh.rxTimer ~= 0 stop(hbh.rxTimer); end if strcmp(hbh.hSerial.Status, 'open') fclose(hbh.hSerial); delete(hbh); end end function delete(hbh) % DELETE Class destructor. close(hbh); end function ok = ping(hbh) % PING Check if the board is responding to a ping request. hbh.pendingPingback = 1; sendPacket(hbh, hbh.PC_MESSAGE_PING, []); startTime = tic; while hbh.pendingPingback && toc(startTime) < 1.0 pause(0.1); end ok = (hbh.pendingPingback == 0); end % function ok = getLinkStatus(hbh) % % end function startLogging(hbh) % STARTLOGGING Start the logging to a text file. % wmh WalkiMotorboard object to set. hri_constants; % Load the parameters file. % Get the directory of this file, to avoid problems when % changing the current MATLAB directory. [baseDirectory, ~, ~] = fileparts(mfilename('fullpath')); addpath(baseDirectory); % If the "logs" directory does not exist, create it. logsDirectory = [baseDirectory '/' 'logs/']; if ~exist(logsDirectory, 'dir') mkdir(logsDirectory); end % Open the logfile to write. if strcmp(LOGFILE_NAMING, 'date') % Use the current date/time to get a unique filename. filenameSuffix = datestr(now, 'yyyy-mm-dd_HH_MM_SS'); elseif strcmp(LOGFILE_NAMING, 'counter') % List the existing logfiles, and look for the highest % number, in order to determine the next number. logfilesNames = dir([logsDirectory 'log_*.csv']); logfilesNames = {logfilesNames.name}; a = regexp(logfilesNames, 'log_(?\d{6}).csv', 'tokens'); a(cellfun(@isempty, a)) = []; maxNumber = max(cellfun(@(x) str2double(x{1}), a)); filenameSuffix = sprintf('%06i', maxNumber+1); end hbh.logfile = fopen([logsDirectory 'log_' filenameSuffix ... '.csv'], 'w'); % Build the header. header = 'timestamp [us];'; for i=hbh.varsIdsToStream header = [header hbh.varsList(i).name ';']; %#ok end header(end) = []; fprintf(hbh.logfile, [header '\n']); end function stopLogging(hbh) % STOPLOGGING Stop the logging to a text file. % wmh WalkiMotorboard object to set. if hbh.logfile ~= 0 fclose(hbh.logfile); hbh.logfile = 0; end end function requestVarsList(hbh) sendPacket(hbh, hbh.PC_MESSAGE_GET_VARS_LIST, []); end function varsList = getVarsList(hbh) sendPacket(hbh, hbh.PC_MESSAGE_GET_VARS_LIST, []); hbh.pendingGetVarsList = 1; while hbh.pendingGetVarsList pause(0.1); end varsList = hbh.varsList; end function setVar(hbh, varId, newValue) % SETVAR Set a board variable to the desired value. % wmh WalkiMotorboard object to set. % varId the selected variable to set. % newValue the new desired value of the selected variable. The % given value will be cast to the correct type. % Send a packet to set the value of the variable. if strcmp(hbh.varsList(varId).type, 'bool') newValue = cast(newValue, 'uint8'); else newValue = cast(newValue, hbh.varsList(varId).type); end data = [varId-1 typecast(newValue, 'uint8')]; sendPacket(hbh, hbh.PC_MESSAGE_SET_VAR, data); end function varValue = getVar(hbh, varId) % REQUESTVAR Get the variable value from the board. % This function blocks until the variable has been received. % wmh WalkiMotorboard object to set. % varId the ID of the selected variable to get. hbh.pendingGetVar = 1; sendPacket(hbh, hbh.PC_MESSAGE_GET_VAR, varId-1); while(hbh.pendingGetVar) pause(0.01); end varValue = hbh.varsList(varId).value{:}; end function dispVarsList(hbh) for i=1:length(hbh.varsList) fprintf('#%i %s %s (size %i) %s\n', i, ... hbh.varsList(i).name, ... hbh.varsList(i).type, ... hbh.varsList(i).size, ... hbh.varsList(i).access); end end function setStreamedVars(hbh, varsIdsToStream) % Allocate the buffer. hbh.streamBuffer = zeros(1000, length(varsIdsToStream)); hbh.currentStreamBufferIndex = 1; % Build the communication packet. hbh.streamId = hbh.streamId + 1; hbh.streamPacketSize = 1 + 4; % ID + timestamp. hbh.varsIdsToStream = varsIdsToStream; data = zeros(1, 2 + length(varsIdsToStream)); data(1) = uint8(length(varsIdsToStream)); data(2) = uint8(hbh.streamId); for i=1:length(varsIdsToStream) data(2+i) = uint8(varsIdsToStream(i)-1); hbh.streamPacketSize = hbh.streamPacketSize + ... hbh.varsList(varsIdsToStream(i)).size; end sendPacket(hbh, hbh.PC_MESSAGE_SET_STREAMED_VAR, data); % TODO: remove. % This avoids a bug when removing a variable while streaming. pause(0.1); end function vars = getStreaming(hbh) vars = hbh.streamBuffer(1:hbh.currentStreamBufferIndex-1, :); hbh.currentStreamBufferIndex = 1; end end methods(Static) function boardComPort = getHriComPort() hwinfo = instrhwinfo('Serial'); ports = hwinfo.AvailableSerialPorts; for i=1:length(ports) try port = ports{i}; h = hri_comm(port, 0); open(h); if ping(h) boardComPort = port; delete(h); return; end catch % Ignore the error, a keep trying the other serial % ports. end if exist('h', 'var') delete(h); clear h; end end boardComPort = ''; end end methods(Access = private) function sendPacket(wmh, type, data) % Build the packet. txBuffer = zeros(1, 1 + length(data) * 2, 'uint8'); txBuffer(1) = bitset(type, 8); % Set bit 8 to 1 ("start byte"). for i = 1:length(data) b = data(i); txBuffer(i*2+0) = bitshift(b, -4); % MSB. txBuffer(i*2+1) = bitshift(bitshift(b, 4), -4); % LSB. end % Send the packet through the serial link. fwrite(wmh.hSerial, txBuffer); % TODO: set to 'async'? end function readBytes(hbh) try hri_constants; % Read the available bytes from the serial link. nAvailableBytes = hbh.hSerial.BytesAvailable; if nAvailableBytes == 0 return; end rxBytes = fread(hbh.hSerial, nAvailableBytes, 'uint8'); % Cache the most accessed members. rxBytesCountC = hbh.rxBytesCount; packetBufferC = hbh.packetBuffer; % Process each byte, one by one. for rxByte = rxBytes' if bitget(rxByte, 8) == 1 % Start byte. hbh.rxCurrentMessageType = bitset(rxByte, 8, 0); rxBytesCountC = 0; else % "Normal" byte. rxBytesCountC = rxBytesCountC + 1; end if rem(rxBytesCountC, 2) == 1 % Got first half byte. hbh.firstHalfByte = rxByte; else % Got nothing, or the second half byte. dataBytesReady = floor(rxBytesCountC / 2); % Accumulate the incoming byte in the RX packet buffer. if dataBytesReady > 0 packetBufferC(dataBytesReady) = ... bitshift(hbh.firstHalfByte, 4) + ... % MSB. bitshift(bitshift(rxByte, 4), -4); % LSB. end % If enough data bytes have been received, interpret % them. switch hbh.rxCurrentMessageType case hbh.STM_MESSAGE_PINGBACK hbh.pendingPingback = 0; case hbh.STM_MESSAGE_VAR if dataBytesReady >= 1 varId = packetBufferC(1) + 1; varSize = hbh.varsList(varId).size; if dataBytesReady == 1 + varSize if strcmp(hbh.varsList(varId).type, 'bool') hbh.varsList(varId).value = {(packetBufferC(2) ~= 0) * 1}; else hbh.varsList(varId).value = {typecast(packetBufferC(2:1+varSize), hbh.varsList(varId).type)}; end end hbh.pendingGetVar = 0; end case hbh.STM_MESSAGE_STREAMING_PACKET if dataBytesReady == hbh.streamPacketSize if packetBufferC(1) == hbh.streamId timestamp = cast(typecast(packetBufferC(2:5), 'uint32'), 'double'); p = 6; for varId=hbh.varsIdsToStream if strcmp(hbh.varsList(varId).type, 'bool') hbh.varsList(varId).value = {(packetBufferC(p) ~= 0) * 1}; p = p + 1; else varSize = hbh.varsList(varId).size; hbh.varsList(varId).value = {typecast(packetBufferC(p:p+varSize-1), hbh.varsList(varId).type)}; p = p + varSize; end end % Add the values into the streaming buffer. streamBufferLine = cellfun(@(x)cast(x{:}, 'double'), {hbh.varsList(hbh.varsIdsToStream).value}); if hbh.plotDecimCounter >= PLOT_DOWNSAMPLING hbh.plotDecimCounter = 1; hbh.streamBuffer(hbh.currentStreamBufferIndex,:) = streamBufferLine; hbh.currentStreamBufferIndex = hbh.currentStreamBufferIndex + 1; % If the streaming buffer is full, restart. if hbh.currentStreamBufferIndex >= hbh.STREAMING_BUFF_LEN hbh.currentStreamBufferIndex = 1; warning('The streaming buffer was full, and has been reseted.'); end else hbh.plotDecimCounter = hbh.plotDecimCounter + 1; end % Log to file, if enabled. if hbh.logfile ~= 0 fprintf(hbh.logfile, '%f;', [timestamp streamBufferLine]); fprintf(hbh.logfile, '\n'); end end end case hbh.STM_MESSAGE_DEBUG_TEXT if dataBytesReady > 0 && ... packetBufferC(dataBytesReady) == 0 message = char(... packetBufferC(... 1:find(packetBufferC == 0)-1)); message(message == 13) = []; fprintf([message '\n']); end case hbh.STM_MESSAGE_VARS_LIST if dataBytesReady > 0 nVars = double(packetBufferC(1)); hbh.varsList = []; if dataBytesReady == 1 + nVars * (hbh.SYNCVAR_NAME_SIZE + 3) j = 2; for i=1:nVars name = packetBufferC(j:j+hbh.SYNCVAR_NAME_SIZE); hbh.varsList(i).name = char(name(1:find(name == 0, 1, 'first')-1)); j = j + hbh.SYNCVAR_NAME_SIZE; hbh.varsList(i).type = hbh.VAR_TYPES{packetBufferC(j)+1}; j = j + 1; hbh.varsList(i).access = hbh.VAR_ACCESSES{packetBufferC(j)+1}; j = j + 1; hbh.varsList(i).size = double(packetBufferC(j)); j = j + 1; if strcmp(hbh.varsList(i).type, 'bool') hbh.varsList(i).value = 0; else hbh.varsList(i).value = {cast(0, hbh.varsList(i).type)}; end end hbh.pendingGetVarsList = 0; if isa(hbh.gotVarsListCallbackFunc, 'function_handle') hbh.gotVarsListCallbackFunc(hbh.varsList); end end end case hbh.STM_MESSAGE_START_INFO if dataBytesReady == 0 disp('The board has (re)started.'); requestVarsList(hbh); end end end end % De-cache the cached variables. hbh.rxBytesCount = rxBytesCountC; hbh.packetBuffer = packetBufferC; catch e % Having this variable in global allows accessing it from % the main workspace. global err; %#ok err = e; rethrow(e); end end end end \ No newline at end of file diff --git a/MATLAB/hri_constants.m b/MATLAB/hri_constants.m index 6464cd2..f0f3218 100644 --- a/MATLAB/hri_constants.m +++ b/MATLAB/hri_constants.m @@ -1,26 +1,26 @@ -% Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). +% Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). % % Licensed under the Apache License, Version 2.0 (the "License"); % you may not use this file except in compliance with the License. % You may obtain a copy of the License at % % http://www.apache.org/licenses/LICENSE-2.0 % % Unless required by applicable law or agreed to in writing, software % distributed under the License is distributed on an "AS IS" BASIS, % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. % See the License for the specific language governing permissions and % limitations under the License. %% HRI_GUI CONSTANTS %% GUI-related constants. N_VARS_MAX = 25; % Maximum number of variables. PLOT_SAMPLING_FREQS = [2 10]; % Plot frequencies proposed by the combobox [Hz]. %% Plot-related constants. PLOT_N_SAMPLES = 1000; % Width (in samples) of the plot window. PLOT_DOWNSAMPLING = 10; % Downsampling factor before plotting. %% Logfiles options. LOGFILE_NAMING = 'date'; % 'date' or 'counter'. \ No newline at end of file diff --git a/MATLAB/hri_gui.m b/MATLAB/hri_gui.m index 1e69a06..a161a4b 100644 --- a/MATLAB/hri_gui.m +++ b/MATLAB/hri_gui.m @@ -1,436 +1,436 @@ -% Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). +% Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). % % Licensed under the Apache License, Version 2.0 (the "License"); % you may not use this file except in compliance with the License. % You may obtain a copy of the License at % % http://www.apache.org/licenses/LICENSE-2.0 % % Unless required by applicable law or agreed to in writing, software % distributed under the License is distributed on an "AS IS" BASIS, % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. % See the License for the specific language governing permissions and % limitations under the License. function varargout = hri_gui(varargin) % HRI_GUI MATLAB code for hri_gui.fig % HRI_GUI, by itself, creates a new HRI_GUI or raises the existing % singleton*. % % H = HRI_GUI returns the handle to a new HRI_GUI or the handle to % the existing singleton*. % % HRI_GUI('CALLBACK',hObject,eventData,handles,...) calls the local % function named CALLBACK in HRI_GUI.M with the given input arguments. % % HRI_GUI('Property','Value',...) creates a new HRI_GUI or raises the % existing singleton*. Starting from the left, property value pairs are % applied to the GUI before hri_gui_OpeningFcn gets called. An % unrecognized property name or invalid value makes property application % stop. All inputs are passed to hri_gui_OpeningFcn via varargin. % % *See GUI Options on GUIDE's Tools menu. Choose "GUI allows only one % instance to run (singleton)". % % See also: GUIDE, GUIDATA, GUIHANDLES % Edit the above text to modify the response to help hri_gui % Last Modified by GUIDE v2.5 26-Dec-2015 20:20:49 % Begin initialization code - DO NOT EDIT gui_Singleton = 1; gui_State = struct('gui_Name', mfilename, ... 'gui_Singleton', gui_Singleton, ... 'gui_OpeningFcn', @hri_gui_OpeningFcn, ... 'gui_OutputFcn', @hri_gui_OutputFcn, ... 'gui_LayoutFcn', [] , ... 'gui_Callback', []); if nargin && ischar(varargin{1}) gui_State.gui_Callback = str2func(varargin{1}); end if nargout [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:}); else gui_mainfcn(gui_State, varargin{:}); end % End initialization code - DO NOT EDIT % --- Executes just before hri_gui is made visible. function hri_gui_OpeningFcn(hObject, eventdata, handles, varargin) %#ok % This function has no output args, see OutputFcn. % hObject handle to figure % eventdata reserved - to be defined in a future version of MATLAB % handles structure with handles and user data (see GUIDATA) % varargin command line arguments to hri_gui (see VARARGIN) % Choose default command line output for hri_gui handles.output = hObject; % UIWAIT makes hri_gui wait for user response (see UIRESUME) % uiwait(handles.main_window); hri_constants; handles.streamedVars = []; handles.varsWidgets = []; handles.plotDataCurrentIndex = 1; handles.plotData = []; handles.plotFig = findobj('Tag', 'plot_figure'); handles.dataStreamingCheckbox = findobj('Tag', 'data_streaming_checkbox'); % Add the current directory, in order to ensure the callbacks work, even if % the current directory is changed by user. [baseDirectory, ~, ~] = fileparts(mfilename('fullpath')); addpath(baseDirectory); % Create the variables widgets. lineHeight = 1 / N_VARS_MAX; y = 1 - lineHeight; for i=1:N_VARS_MAX % Create the UI controls at the right location. handles.varsWidgets(i).checkbox = uicontrol('Parent', handles.vars_panel, ... 'Style', 'checkbox', ... 'Units', 'normalized', ... 'HandleVisibility', 'callback', ... 'Position', [0 y 3/6 lineHeight], ... 'Visible', 'off'); handles.varsWidgets(i).get = uicontrol('Parent', handles.vars_panel, ... 'Units', 'normalized', ... 'HandleVisibility', 'callback', ... 'Position', [3/6 y 1/6 lineHeight], ... 'String', 'Get', ... 'Visible', 'off'); handles.varsWidgets(i).edit = uicontrol('Parent', handles.vars_panel, ... 'Style', 'edit', ... 'Units', 'normalized', ... 'HandleVisibility', 'callback', ... 'Position', [4/6 y 1/6 lineHeight], ... 'String', '', ... 'Visible', 'off'); handles.varsWidgets(i).set = uicontrol('Parent', handles.vars_panel, ... 'Units', 'normalized', ... 'HandleVisibility', 'callback', ... 'Position', [5/6 y 1/6 lineHeight], ... 'String', 'Set', ... 'Visible', 'off'); y = y - lineHeight; end if isempty(varargin) % Find which serial port corresponds to the board. comPort = hri_comm.getHriComPort(); if isempty(comPort) warning('The board is not detected.'); handles.hbh = []; guidata(hObject, handles); return; else fprintf('The board was detected on %s.\n', comPort); end else comPort = varargin{1}; end % Open the serial link with the board. handles.hbh = hri_comm(comPort, ... @(varsList) hri_gui_UpdateVarsList(hObject, varsList)); open(handles.hbh); % Set the plot figure update timer. handles.plotFreqPopup = findobj('Tag', 'plot_freq_popup'); plotFreq = PLOT_SAMPLING_FREQS(handles.plotFreqPopup.Value); handles.plotTimer = timer('Period', 1.0 / plotFreq, ... 'TimerFcn', @(~,~)updatePlot(hObject), ... 'ExecutionMode', 'fixedRate', ... 'BusyMode', 'drop'); start(handles.plotTimer); % guidata(hObject, handles); function hri_gui_UpdateVarsList(hObject, varsList) hri_constants; handles = guidata(hObject); handles.varsList = varsList; % Stop data streaming and logging, if running. handles.streamedVars = []; set(handles.data_streaming_checkbox, 'Value', 0); set(handles.log_to_file_checkbox, 'Value', 0); % for i=1:length(handles.varsList) % Modify the widgets to they match the associated variable. set(handles.varsWidgets(i).checkbox, 'String', handles.varsList(i).name); set(handles.varsWidgets(i).checkbox, 'Value', 0); set(handles.varsWidgets(i).checkbox, 'Visible', 'on'); set(handles.varsWidgets(i).checkbox, 'ForegroundColor', 'black'); if ~strcmp(varsList(i).access, 'writeonly') set(handles.varsWidgets(i).get, 'Visible', 'on'); end set(handles.varsWidgets(i).edit, 'Visible', 'on'); set(handles.varsWidgets(i).edit, 'String', ''); if ~strcmp(varsList(i).access, 'readonly') set(handles.varsWidgets(i).set, 'Visible', 'on'); end % Set the callbacks. handles.varsWidgets(i).checkbox.Callback = @(h,~)onToggleVarCheckbox(h,i); if handles.varsWidgets(i).get ~= 0 handles.varsWidgets(i).get.Callback = @(h,~)onGetVar(h,i); end if handles.varsWidgets(i).set ~= 0 handles.varsWidgets(i).set.Callback = @(h,~)onSetVar(h,i); end end % Hide the other widgets. for i=length(handles.varsList)+1:N_VARS_MAX set(handles.varsWidgets(i).checkbox, 'Visible', 'off'); set(handles.varsWidgets(i).get, 'Visible', 'off'); set(handles.varsWidgets(i).edit, 'Visible', 'off'); set(handles.varsWidgets(i).set, 'Visible', 'off'); end % Update handles structure. guidata(hObject, handles); function onToggleVarCheckbox(hObject, varID, ~) % Get handles structure. handles = guidata(hObject); % Add or remove the variable ID associated to the checkbox. if handles.varsWidgets(varID).checkbox.Value == 0 handles.streamedVars(handles.streamedVars == varID) = []; else handles.streamedVars = sort([handles.streamedVars varID]); end % Change the streaming of the board, if it currently running. guidata(hObject, handles); if handles.dataStreamingCheckbox.Value ~= 0 setupStreaming(hObject, handles.streamedVars); end function setupStreaming(hObject, streamedVars) hri_constants; handles = guidata(hObject); % Stop the streaming, if running. handles.log_to_file_checkbox.Value = 0; % Request the board to change the streamed vars. setStreamedVars(handles.hbh, streamedVars); % Pre-allocate the plot points buffer. % TODO: keep data for the variable that remain the same. if ~isempty(streamedVars) handles.plotData = nan(PLOT_N_SAMPLES, length(handles.streamedVars)); if ~isempty(handles.plotData) handles.plotFig = plot(1:PLOT_N_SAMPLES, handles.plotData); end end % Color the variables labels to match the lines colors. % The standard MATLAB legend is too slow for live plots. colors = get(handles.plot_figure, 'ColorOrder'); for i = 1:N_VARS_MAX streamVarIndex = find(i == streamedVars, 1); if ~isempty(streamVarIndex) set(handles.varsWidgets(i).checkbox, ... 'ForegroundColor', colors(rem(streamVarIndex-1,7)+1, :)); else set(handles.varsWidgets(i).checkbox, ... 'ForegroundColor', 'black'); end end guidata(hObject, handles); function onGetVar(hObject, varID) % Get handles structure. handles = guidata(hObject); % Get the variable value from the board. varValue = getVar(handles.hbh, varID); % Display the value on the associated edit box widget. set(handles.varsWidgets(varID).edit, 'String', varValue); function onSetVar(hObject, varID) % Get handles structure. handles = guidata(hObject); % Get the desired variable value from the associated edit box widget. varValue = str2double(handles.varsWidgets(varID).edit.String); % Set the variable value on the board. setVar(handles.hbh, varID, varValue); function updatePlot(hObject) hri_constants; try handles = guidata(hObject); catch return; end if ~isfield(handles, 'plotData') || isempty(handles.plotData) return; end vars = getStreaming(handles.hbh); if isempty(vars) || (size(vars, 2) ~= size(handles.plotData, 2)) return; end % If the plot buffer is full, restart. if handles.plotDataCurrentIndex + size(vars,1) > PLOT_N_SAMPLES - 1 handles.plotDataCurrentIndex = 1; end range = handles.plotDataCurrentIndex:handles.plotDataCurrentIndex+size(vars,1)-1; handles.plotData(range, :) = vars; handles.plotData(handles.plotDataCurrentIndex+size(vars,1), :) = nan; handles.plotDataCurrentIndex = handles.plotDataCurrentIndex + size(vars,1); for i=1:size(handles.plotData, 2) set(handles.plotFig(i), 'ydata', handles.plotData(:,i)); end guidata(hObject, handles); % --- Outputs from this function are returned to the command line. function varargout = hri_gui_OutputFcn(hObject, eventdata, handles) %#ok % varargout cell array for returning output args (see VARARGOUT); % hObject handle to figure % eventdata reserved - to be defined in a future version of MATLAB % handles structure with handles and user data (see GUIDATA) % Get default command line output from handles structure varargout{1} = handles.output; % --- Executes on button press in data_streaming_checkbox. function data_streaming_checkbox_Callback(hObject, ~, handles) %#ok % hObject handle to data_streaming_checkbox (see GCBO) % eventdata reserved - to be defined in a future version of MATLAB % handles structure with handles and user data (see GUIDATA) if get(hObject, 'Value') == 0 % Stop the streaming. setupStreaming(hObject, []); % Stop the logging. handles.log_to_file_checkbox.Value = 0; guidata(hObject, handles); stopLogging(handles.hbh); else setupStreaming(hObject, handles.streamedVars); end % --- Executes on button press in clear_plot_button. function clear_plot_button_Callback(hObject, ~, handles) %#ok % hObject handle to clear_plot_button (see GCBO) % eventdata reserved - to be defined in a future version of MATLAB % handles structure with handles and user data (see GUIDATA) handles.plotData = nan(size(handles.plotData)); handles.plotDataCurrentIndex = 1; for i=1:size(handles.plotData, 2) set(handles.plotFig(i), 'ydata', handles.plotData(:,i)); end guidata(hObject, handles); % --- Executes on button press in log_to_file_checkbox. function log_to_file_checkbox_Callback(hObject, ~, handles) %#ok % hObject handle to log_to_file_checkbox (see GCBO) % eventdata reserved - to be defined in a future version of MATLAB % handles structure with handles and user data (see GUIDATA) if get(hObject,'Value') == 0 % Stop the logging. stopLogging(handles.hbh); % Unlock the streaming checkboxes of the variables list. for j=1:length(handles.varsList)+1 handles.varsWidgets(j).checkbox.Enable = 'on'; end else % Start the logging. startLogging(handles.hbh); % Lock the streaming checkboxes of the variables list. for j=1:length(handles.varsList)+1 handles.varsWidgets(j).checkbox.Enable = 'off'; end end % Update handles structure. guidata(hObject, handles); function main_window_CloseRequestFcn(hObject, ~, ~) %#ok handles = guidata(hObject); if isfield(handles, 'plotTimer') stop(handles.plotTimer); delete(handles.plotTimer); handles = rmfield(handles, 'plotTimer'); end if isfield(handles, 'hbh') delete(handles.hbh); handles = rmfield(handles, 'hbh'); end guidata(hObject, handles); delete(hObject); close(gcf); % --- Executes on selection change in plot_freq_popup. function plot_freq_popup_Callback(hObject, ~, handles) %#ok % hObject handle to plot_freq_popup (see GCBO) % eventdata reserved - to be defined in a future version of MATLAB % handles structure with handles and user data (see GUIDATA) hri_constants; % Get the constants. currentIndex = hObject.Value; plotFreq = PLOT_SAMPLING_FREQS(currentIndex); stop(handles.plotTimer); set(handles.plotTimer, 'Period', 1.0 / plotFreq); start(handles.plotTimer); diff --git a/MATLAB/hri_load_logfile.m b/MATLAB/hri_load_logfile.m index b4ba7c2..d3dae40 100644 --- a/MATLAB/hri_load_logfile.m +++ b/MATLAB/hri_load_logfile.m @@ -1,50 +1,50 @@ -% Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). +% Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). % % Licensed under the Apache License, Version 2.0 (the "License"); % you may not use this file except in compliance with the License. % You may obtain a copy of the License at % % http://www.apache.org/licenses/LICENSE-2.0 % % Unless required by applicable law or agreed to in writing, software % distributed under the License is distributed on an "AS IS" BASIS, % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. % See the License for the specific language governing permissions and % limitations under the License. function variables = hri_load_logfile(filename) %HRI_LOAD_LOGFILE Loads a HRI logfile to a MATLAB structure. % Loads the filename with the given filename. A MATLAB structure is % generated, each named field is an array of the history of the variable % value. %% Read the header to get the variables names. fid = fopen(filename, 'r'); line = fgetl(fid); varNames = textscan(line, '%s', 'Delimiter', ';'); varNames = varNames{:}; %% % Replace the special characters by '_'. for i = 1:length(varNames) varName = varNames{i}; varName((varName < 'a' | varName > 'z') & ... (varName < 'A' | varName > 'Z') & ... (varName < '0' | varName > '9')) = '_'; varNames{i} = varName; end fclose(fid); %% Read the variables values over time. A = dlmread(filename, ';', 1, 0); %% Create a structure with a field per variable. for i=1:length(varNames) variables.(varNames{i}) = A(:,i); end end \ No newline at end of file diff --git a/NOTICE b/NOTICE index dfa996f..78ec022 100644 --- a/NOTICE +++ b/NOTICE @@ -1,10 +1,10 @@ HHRI-Software -Copyright 2017 EPFL-LSRO (Laboratoire de Systèmes Robotiques). +Copyright 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques). This project is licensed under the Apache License, Version 2.0 (the "License"); you may not use this project except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. This software contains code developped by STMicroelectronics under the MCD-ST Liberty SW License Agreement V2.