/* Copyright 2018 Benjamin Vedder benjamin@vedder.se This file is part of VESC Tool. VESC Tool is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. VESC Tool is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ import QtQuick 2.7 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 import Vedder.vesc.bmsinterface 1.0 import Vedder.vesc.commands 1.0 import Vedder.vesc.configparams 1.0 Item { property int parentWidth: 10 property real res: 0.0 property real ind: 0.0 property real lambda: 0.0 property real kp: 0.0 property real ki: 0.0 property real gain: 0.0 property Commands mCommands: VescIf.commands() property ConfigParams mbmsConfig: VescIf.bmsConfig() property ConfigParams mInfoConf: VescIf.infoConfig() function openDialog() { dialog.open() } function updateDisplay() { resultArea.text = "R : " + parseFloat(res * 1e3).toFixed(2) + " m\u03A9\n" + "L : " + parseFloat(ind * 1e6).toFixed(2) + " µH\n" + "\u03BB : " + parseFloat(lambda * 1e3).toFixed(3) + " mWb\n" + "KP : " + parseFloat(kp).toFixed(4) + "\n" + "KI : " + parseFloat(ki).toFixed(2) + "\n" + "Gain : " + parseFloat(gain).toFixed(2) } function calcKpKi() { if (res < 1e-10) { VescIf.emitMessageDialog("Calculate Error", "R is 0. Please measure it first.", false, false) return; } if (ind < 1e-10) { VescIf.emitMessageDialog("Calculate Error", "L is 0. Please measure it first.", false, false) return; } // https://e2e.ti.com/blogs_/b/motordrivecontrol/archive/2015/07/20/teaching-your-pi-controller-to-behave-part-ii var tc = tcBox.realValue * 1e-6 var bw = 1.0 / tc kp = ind * bw; ki = res * bw; updateDisplay() } function calcGain() { if (lambda < 1e-10) { VescIf.emitMessageDialog("Calculate Error", "\u03BB is 0. Please measure it first.", false, false) return; } gain = 0.001 / (lambda * lambda) updateDisplay() } function testConnected() { if (VescIf.isPortConnected()) { return true } else { VescIf.emitMessageDialog( "Connection Error", "The VESC is not connected. Please connect it to run detection.", false, false) return false } } Component.onCompleted: { updateDisplay() } Dialog { id: dialog standardButtons: Dialog.Close modal: true focus: true width: parentWidth - 20 height: Math.min(implicitHeight, column.height - 40) closePolicy: Popup.CloseOnEscape x: 10 y: 10 ScrollView { anchors.fill: parent clip: true contentWidth: parent.width ColumnLayout { anchors.fill: parent spacing: 0 DoubleSpinBox { id: currentBox Layout.fillWidth: true decimals: 2 realValue: 5.0 realFrom: 0.0 realTo: 200.0 prefix: "I: " suffix: " A" } DoubleSpinBox { id: dutyBox Layout.fillWidth: true decimals: 2 realValue: 0.5 realFrom: 0.0 realTo: 1.0 realStepSize: 0.1 prefix: "D: " } DoubleSpinBox { id: erpmBox Layout.fillWidth: true decimals: 1 realValue: 450.0 realFrom: 0.0 realTo: 20000.0 realStepSize: 10.0 prefix: "\u03C9: " suffix: " ERPM" } DoubleSpinBox { id: tcBox Layout.fillWidth: true decimals: 1 realValue: 1000.0 realFrom: 0.0 realTo: 1000000.0 realStepSize: 100.0 prefix: "T: " suffix: " µS" } Button { text: "Help" Layout.fillWidth: true onClicked: { VescIf.emitMessageDialog( mInfoConf.getLongName("help_foc_detect"), mInfoConf.getDescription("help_foc_detect"), true, true) } } RowLayout { Layout.fillWidth: true Button { text: "Detect R&L" Layout.preferredWidth: 50 Layout.fillWidth: true onClicked: { if (!testConnected()) { return } detectRlDialog.open() } } Button { text: "DETECT \u03BB" font.capitalization: Font.MixedCase Layout.preferredWidth: 50 Layout.fillWidth: true onClicked: { if (!testConnected()) { return } if (res < 1e-9) { VescIf.emitMessageDialog("Detect", "R is 0. Please measure it first.", false, false) } else { detectLambdaDialog.open() } } } } Button { text: "CALCULATE KP, KI AND \u03BB" font.capitalization: Font.MixedCase Layout.fillWidth: true onClicked: { calcKpKi() calcGain() } } TextArea { id: resultArea Layout.fillWidth: true Layout.preferredHeight: 200 readOnly: true font.family: "DejaVu Sans Mono" } Button { text: "Apply & Close" Layout.fillWidth: true onClicked: { if (res < 1e-10) { VescIf.emitMessageDialog("Apply Error", "R is 0. Please measure it first.", false, false) return } if (ind < 1e-10) { VescIf.emitMessageDialog("Apply Error", "L is 0. Please measure it first.", false, false) return } if (lambda < 1e-10) { VescIf.emitMessageDialog("Apply Error", "\u03BB is 0. Please measure it first.", false, false) return } calcKpKi() calcGain() mbmsConfig.updateParamDouble("foc_motor_r", res) mbmsConfig.updateParamDouble("foc_motor_l", ind) mbmsConfig.updateParamDouble("foc_motor_flux_linkage", lambda) mbmsConfig.updateParamDouble("foc_current_kp", kp) mbmsConfig.updateParamDouble("foc_current_ki", ki) mbmsConfig.updateParamDouble("foc_observer_gain", gain * 1e6) dialog.close() } } } } } Dialog { id: detectRlDialog standardButtons: Dialog.Ok modal: true focus: true width: parentWidth - 20 closePolicy: Popup.CloseOnEscape title: "Measure R & L" x: 10 y: dialog.y + dialog.height / 2 - height / 2 Text { id: detectRlLabel verticalAlignment: Text.AlignVCenter anchors.fill: parent wrapMode: Text.WordWrap text: "When measuring R & L the motor is going to make some noises, but " + "not rotate. These noises are completely normal, so don't unplug " + "anything unless you see smoke." } onAccepted: { mCommands.measureRL() } } Dialog { id: detectLambdaDialog standardButtons: Dialog.Ok | Dialog.Cancel modal: true focus: true width: parentWidth - 20 closePolicy: Popup.CloseOnEscape title: "Warning" x: 10 y: dialog.y + dialog.height / 2 - height / 2 Text { id: detectLambdaLabel verticalAlignment: Text.AlignVCenter anchors.fill: parent wrapMode: Text.WordWrap text: "Warning: " + "This is going to spin up the motor. Make " + "sure that nothing is in the way." } onAccepted: { mCommands.measureLinkage(currentBox.realValue, erpmBox.realValue, dutyBox.realValue, res) } } Connections { target: mCommands onMotorRLReceived: { if (r < 1e-9 && l < 1e-9) { VescIf.emitStatusMessage("Bad FOC Detection Result Received", false) VescIf.emitMessageDialog("Bad Detection Result", "Could not measure the motor resistance and inductance.", false, false) } else { VescIf.emitStatusMessage("FOC Detection Result Received", true) res = r ind = l * 1e-6 calcKpKi() } } onMotorLinkageReceived: { if (flux_linkage < 1e-9) { VescIf.emitStatusMessage("Bad FOC Detection Result Received", false) VescIf.emitMessageDialog("Bad Detection Result", "Could not measure the flux linkage properly. Adjust " + "the start parameters according to the help text and try again.", false, false) } else { VescIf.emitStatusMessage("FOC Detection Result Received", true) lambda = flux_linkage calcGain() } } } }