diff --git a/source/applications/current-control/continuous-time/cvcr/index.md b/source/applications/current-control/continuous-time/cvcr/index.md new file mode 100644 index 00000000..86a85b8d --- /dev/null +++ b/source/applications/current-control/continuous-time/cvcr/index.md @@ -0,0 +1 @@ +# Complex Vector Current Regulator (CVCR) \ No newline at end of file diff --git a/source/applications/current-control/continuous-time/index.md b/source/applications/current-control/continuous-time/index.md new file mode 100644 index 00000000..e8e3cc94 --- /dev/null +++ b/source/applications/current-control/continuous-time/index.md @@ -0,0 +1,12 @@ +# Continuous-Time Control + +Foo bar + +```{toctree} +:hidden: + +Single-Phase +Three-Phase +sfpi/index +cvcr/index +``` \ No newline at end of file diff --git a/source/applications/current-control/continuous-time/sfpi/index.md b/source/applications/current-control/continuous-time/sfpi/index.md new file mode 100644 index 00000000..819571e8 --- /dev/null +++ b/source/applications/current-control/continuous-time/sfpi/index.md @@ -0,0 +1,3 @@ +# Synchronous Frame PI (SFPI) Current Regulation + +Foo bar \ No newline at end of file diff --git a/source/applications/current-control/continuous-time/single-phase/index.md b/source/applications/current-control/continuous-time/single-phase/index.md new file mode 100644 index 00000000..0e095f17 --- /dev/null +++ b/source/applications/current-control/continuous-time/single-phase/index.md @@ -0,0 +1,3 @@ +# Single-Phase Current Regulation + +Foo bar \ No newline at end of file diff --git a/source/applications/current-control/continuous-time/three-phase/index.md b/source/applications/current-control/continuous-time/three-phase/index.md new file mode 100644 index 00000000..c4447fac --- /dev/null +++ b/source/applications/current-control/continuous-time/three-phase/index.md @@ -0,0 +1,3 @@ +# Three-Phase Current Regulation + +Foo bar \ No newline at end of file diff --git a/source/applications/current-control/discrete-time/c2d/index.md b/source/applications/current-control/discrete-time/c2d/index.md new file mode 100644 index 00000000..be440135 --- /dev/null +++ b/source/applications/current-control/discrete-time/c2d/index.md @@ -0,0 +1,3 @@ +# Continuous-to-Discrete Approximations + +Foo bar \ No newline at end of file diff --git a/source/applications/current-control/discrete-time/direct-digital-cvcr/index.md b/source/applications/current-control/discrete-time/direct-digital-cvcr/index.md new file mode 100644 index 00000000..b934e87e --- /dev/null +++ b/source/applications/current-control/discrete-time/direct-digital-cvcr/index.md @@ -0,0 +1,3 @@ +# Direct Digital CVCR + +Foo bar \ No newline at end of file diff --git a/source/applications/current-control/discrete-time/index.md b/source/applications/current-control/discrete-time/index.md new file mode 100644 index 00000000..111d7f0c --- /dev/null +++ b/source/applications/current-control/discrete-time/index.md @@ -0,0 +1,8 @@ +# Discrete-Time Control + +```{toctree} +:hidden: + +c2d/index +direct-digital-cvcr/index +``` \ No newline at end of file diff --git a/source/applications/current-control/index.md b/source/applications/current-control/index.md new file mode 100644 index 00000000..23251a1c --- /dev/null +++ b/source/applications/current-control/index.md @@ -0,0 +1,11 @@ +# Current Control + +Foo bar + +```{toctree} +:hidden: + +load-modeling/index +continuous-time/index +discrete-time/index +``` \ No newline at end of file diff --git a/source/applications/current-control/load-modeling/dq-machine/index.md b/source/applications/current-control/load-modeling/dq-machine/index.md new file mode 100644 index 00000000..8c7c14a9 --- /dev/null +++ b/source/applications/current-control/load-modeling/dq-machine/index.md @@ -0,0 +1,3 @@ +# DQ Machine Model + +Foo bar diff --git a/source/applications/current-control/load-modeling/index.md b/source/applications/current-control/load-modeling/index.md new file mode 100644 index 00000000..441b005b --- /dev/null +++ b/source/applications/current-control/load-modeling/index.md @@ -0,0 +1,9 @@ +# Load Modeling + +```{toctree} +:hidden: + +rl-load/index +dq-machine/index +mp-winding/index +``` \ No newline at end of file diff --git a/source/applications/current-control/load-modeling/mp-winding/index.md b/source/applications/current-control/load-modeling/mp-winding/index.md new file mode 100644 index 00000000..8e31b609 --- /dev/null +++ b/source/applications/current-control/load-modeling/mp-winding/index.md @@ -0,0 +1,3 @@ +# Multi-Phase Winding + +Introduce generalized Clarke transform and how to apply it to $m$ phase windings, decoupled subspaces, etc. \ No newline at end of file diff --git a/source/applications/current-control/load-modeling/rl-load/index.md b/source/applications/current-control/load-modeling/rl-load/index.md new file mode 100644 index 00000000..5881c553 --- /dev/null +++ b/source/applications/current-control/load-modeling/rl-load/index.md @@ -0,0 +1,9 @@ +# RL Load Model + +Single phase RL load. Content from the lecture series on RL load. This will be a short note. +- Circuit diagram +- Governing differential equation relating V and I +- Transfer function in Laplace domain relating V and I +- Summarize as plant that a controller could use. + + diff --git a/source/applications/maglev-control/index.md b/source/applications/maglev-control/index.md new file mode 100644 index 00000000..f4adc1b0 --- /dev/null +++ b/source/applications/maglev-control/index.md @@ -0,0 +1,3 @@ +# Maglev Displacement Control + +Basics of levitation systems, mechanical system stability via PID control. \ No newline at end of file diff --git a/source/applications/speed-control/index.md b/source/applications/speed-control/index.md new file mode 100644 index 00000000..7f684070 --- /dev/null +++ b/source/applications/speed-control/index.md @@ -0,0 +1,3 @@ +# Speed Control + +How to get a motor to spin at a desired speed using FOC? \ No newline at end of file diff --git a/source/getting-started/control-with-amdc/encoder-fb/index.md b/source/getting-started/control-with-amdc/encoder-fb/index.md new file mode 100644 index 00000000..40ae502d --- /dev/null +++ b/source/getting-started/control-with-amdc/encoder-fb/index.md @@ -0,0 +1,178 @@ +# Encoder Feedback + +## Background + +Encoders are used to determine the rotor position and speed, and are the typical method of feedback to the control system in a motor drive. This document explains how to use the AMDC's encoder interface to extract high quality rotor position and speed data. + +For more information: + +- on how encoders work and are interfaced with the AMDC, see the [encoder hardware subsystem page](/hardware/subsystems/encoder.md); +- on the driver functionality included with the AMDC firmware, see the [encoder driver architecture page](/firmware/arch/drivers/encoder.md). + +## Rotor Position + +The AMDC supports [incremental encoders with quadrature ABZ outputs](https://en.wikipedia.org/wiki/Incremental_encoder#Quadrature_outputs) and a fixed number of counts per revolution `CPR` (for example, `CPR = 1024`). The user needs to provide code that interfaces to the AMDC's drivers to read the encoder count and convert it into usable angular information that is suitable for use within the control code. + +```{image} resources/motor-cross-section.svg +:alt: Motor Cross-Section with Encoder Angles +:width: 350px +:align: right +``` + +This document assumes the configuration shown to the right, where the control code expects a measurement of the angle of the rotor's north pole relative to the phase $u$ magnetic axis, labeled as $\theta_{\rm m}$. The encoder provides $\theta_{\rm enc}$, which is the number of counts since the last z-pulse. The user's code needs to convert $\theta_{\rm enc}$ (in units of counts) into $\theta_{\rm m}$ (likely in units of radians) and handle an offset angle $\theta_{\rm off}$ between the encoder's 0 position and the phase $u$ axis. + +### Configuring the encoder + +Upon powerup, the AMDC configures the encoder to a default number of counts per revolution. This is handled in `encoder.c` as part of the standard firmware package. When using an encoder that has a different number counts per revolution, the user must inform the driver by calling `encoder_set_counts_per_rev()`. + +Example code for a 10 bit encoder: + +``` C +#define USER_ENCODER_COUNTS_PER_REV_BITS (10) +#define USER_ENCODER_COUNTS_PER_REV (1 << USER_ENCODER_COUNTS_PER_REV_BITS) + +int task_user_app_init(void) +{ + encoder_set_counts_per_rev(USER_ENCODER_COUNTS_PER_REV); + + // other user app one-time initialization code + // ... +``` + +```{tip} +The AMDC provides a convenience function that can be used as an alternate to `encoder_set_counts_per_rev()` when the encoder is specified as a number of bits: `encoder_set_counts_per_rev_bits(USER_ENCODER_COUNTS_PER_REV_BITS).` +``` + +### Converting the encoder count into rotor position + +The recommended approach to reading the shaft position from the encoder is illustrated in the figure below: + + + +First, the AMDC [`drv/encoder`](/firmware/arch/drivers/encoder.md) driver module function `encoder_get_position()` is used to obtain the the encoder's count $\theta_{\rm enc}$ since the last z-pulse. + +```{tip} +The [`drv/encoder`](/firmware/arch/drivers/encoder.md) driver module also has a function called `encoder_get_steps()` which returns the encoder's count since power-on. One rotation direction increments, the other decrements. This value does not wrap around (it ignores `encoder_set_counts_per_rev()` and the z-pulse). Users are advised to use `encoder_get_position()`, which does wrap around and tracks the z-pulse. +``` + +Next, the user should calculate $\theta_{\rm m}$ from $\theta_{\rm enc}$. This is done by 1) removing the offset and 2) converting counts into radians. For the the angles defined as shown in the image above, this is simply calculated as + +$$ +\theta_{\rm m} = \tfrac{2\pi}{\rm COUNTS\_PER\_REV} \left( \theta_{\rm enc} - \theta_{\rm off} \right) +$$ (eq:convCCW) + +In this case, a counter-clockwise rotation of the rotor causes the $\theta_{\rm enc}$ to increase. However, in some teststands a clockwise rotation causes $\theta_{\rm enc}$ to increment. For these encoders, $\theta_{\rm m}$ is calculated as + +$$ +\theta_{\rm m} &= \tfrac{2\pi}{\rm COUNTS\_PER\_REV} \left({\scriptstyle \rm COUNTS\_PER\_REV} - \theta_{\rm enc} + \theta_{\rm off} \right) \\ &= 2\pi - \theta_{\rm m, CCW} +$$ (eq:convCW) + +```{tip} +The user can experimentally determine whether the encoder count increases with counter-clockwise rotation of the shaft by rotating the shaft and using [logging](/getting-started/user-guide/logging/index.md) to observe the trend of $\theta_{\rm enc}$. +``` + +Finally, the user must ensure that angle is within the bounds of $0$ and $2\pi$ by appropriately wrapping the $\theta_{\rm m}$. This can be accomplished in C by using the `mod` function. This is shown in the final block in the diagram. + +Here is example code to convert the encoder to angular position in radians (note that this assumes the encoder offset $\theta_{\rm off}$ is already know; a procedure to determine this is described in the next [subsection](#finding-the-offset)): +```C +double task_get_theta_m(void) +{ + // User to set encoder offset + double theta_off = 100; + + // User to set encoder count per revolution + double ENCODER_COUNT_PER_REV = 1024; + + // User to set 1 if encoder count increases with CCW rotation of shaft, set 0 if encoder count increases with CW rotation of shaft + int CCW_ROTATION_FLAG = 1; + + // Angular position to be computed + double theta_m; + + // Get raw encoder position + uint32_t theta_enc; + encoder_get_position(&theta_enc); + + // Convert to radians + theta_m = (double) PI2 * ( ((double)theta_enc - theta_off) / (double) ENCODER_COUNT_PER_REV); + + if (!CCW_ROTATION_FLAG){ + theta_m = PI2 - theta_m; + } + + // Mod by 2 pi + theta_m = fmod(theta_m,PI2); + return theta_m; +} +``` + +### Finding the offset + +The example code shown above makes uses of an encoder offset value, `enc_theta_m_offset`. It is necessary for the user to find this offset experimentally for their machine. For synchronous machines, this offset is the count value measured by the encoder when the d-axis of the rotor is aligned with the phase U winding axis of the stator. + +To determine the appropriate offset value, eliminate any source of load torque on the shaft and apply a large current vector at 0 degrees ($I_u = I_0$, $I_v = I_w = -\frac{1}{2} I_0$). This should cause the rotor to align with the phase U winding axis. The offset value can now be obtained as `enc_theta_m_offset = encoder_get_position()`. Alternately, the user may also inject a current along the d-axis using the AMDC [Signal Injection](https://docs.amdc.dev/getting-started/user-guide/injection/index.html) module. While injecting d-axis current, the user must ensure that the rotor position is set to zero in the control code. + +After obtaining the offset, the user needs to set the variable `enc_theta_m_offset` to the appropriate value in the `task_get_theta_m()` function. + +To ensure that the obtained encoder offset is correct, the user may perform additional validation. For a permanent magnet synchronous motor, this can be done as follows: + +1. Spin the motor up-to a steady speed under no load conditions +1. Measure the d-axis voltage commanded by the current regulator +1. Repeat the experiment for a few different rotor speeds +1. Plot the d-axis voltage against the rotor speed +1. The d-axis voltage should be close to zero for all speeds, if the offset is tuned correctly +1. In-case there is an error in the offset value, a significant speed dependent voltage will appear on the d-axis voltage. In this case, the user may have to re-measure the encoder offset. + + + +## Computing Speed from Position + +The user needs to compute a rotor speed signal from the obtained position signal to be used in the control algorithm. There are several ways to do this. + +### Difference Equation Approach + +A simple, but naive, way to do this would be to compute the discrete time derivative of the position signal in the controller as shown below. This can be referred to as $\Omega_{raw}$. + +$$ +\Omega_\text{raw}[k] = \frac{\theta_m[k] - \theta_m[k-1]}{T_s} +$$ + + +Unfortunately, using this approach results in noise in $\Omega_\text{raw}$ due to the derivative operation and the digital nature of the incremental encoder. + +### Low Pass Filter Approach + +To solve this, _a low pass filter_ may be applied to this signal. This is shown below to obtain a filtered speed, $\Omega_\text{lpf}$. + +$$ + \Omega_\text{lpf}[k] = \Omega_\text{raw}[k](1 - e^{\omega_b T_s}) + \Omega_\text{lpf}[k-1]e^{\omega_b T_s} +$$ + +Here, $T_{\rm s}$ is the control sample rate and $\omega_b$ is the low pass filter bandwidth. The user must select this bandwidth to obtain a sufficiently clean speed signal. The optimal bandwidth to use is going to vary based on the motor system. Typically, a bandwidth of 10 Hz is a reasonable starting point. This can be reduced if the speed signal remains too noisy, or increased for higher speed controls. + +Note that this low pass filter approach will always produce a lagging speed estimate due to phase delay in the filter transfer function. This may be unacceptable higher performance motor control algorithms. + +### Observer Approach + +To obtain a no-lag estimate of the rotor speed, users may create an observer [[1]](#1), which implements a mechanical model of the rotor as shown below. + + + + +The estimate of rotor speed is denoted by $\Omega_\text{sf}$. To implement this observer, the user needs to know the system parameters: +- `J`: the inertia of the rotor +- `b` the damping coefficient of the rotor. + +It is also necessary to provide the electromechanical torque, $T_{em}$ as input to the mechanical model. + +The `PI` portion of the observer closes the loop on the speed, with $\Omega_\text{raw}$ being the reference input. The recommended tuning approach is as follows: +$$ +K_p = \omega_{sf}b, K_i = \omega_{sf}J +$$ + +This tuning ensures a pole zero cancellation in the closed transfer function, resulting in a unity transfer function for speed tracking under ideal parameter estimates of `J` and `b`. An observer bandwidth of 10 Hz is typical of most systems, but similar to the low pass filter approach, users may need to alter this based on the unique aspects of their system. + + +# References + 1. R. D. Lorenz and K. W. Van Patten, "High-resolution velocity estimation for all-digital, AC servo drives," in IEEE Transactions on Industry Applications, vol. 27, no. 4, pp. 701-705, July-Aug. 1991, doi: 10.1109/28.85485. keywords: {Servomechanisms;Optical feedback;Optical signal processing;Transducers;Signal resolution;Velocity measurement;Position control;Feedback loop;Velocity control;Noise reduction} + diff --git a/source/getting-started/control-with-amdc/encoder-fb/resources/EncoderCodeBlockDiagram.svg b/source/getting-started/control-with-amdc/encoder-fb/resources/EncoderCodeBlockDiagram.svg new file mode 100644 index 00000000..9c3b51c1 --- /dev/null +++ b/source/getting-started/control-with-amdc/encoder-fb/resources/EncoderCodeBlockDiagram.svg @@ -0,0 +1,512 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + task_get_theta_m() + + + Get encodercount usingencoder_get_position()function + + + θm + Encoder raw signals + + + + 2π( (count - offset)/ENCODER_COUNT_PER_REV) + if CCW + else + 2π( (ENCODER_COUNT_PER_REV - 1 - count + offset)/ENCODER_COUNT_PER_REV) + + + + + + + + mod by 2π + + + + + + + + count + offset* + ENCODER_COUNT_PER_REV* + rotor position + * User input + + CCW* + + diff --git a/source/getting-started/control-with-amdc/encoder-fb/resources/ObserverFigure.svg b/source/getting-started/control-with-amdc/encoder-fb/resources/ObserverFigure.svg new file mode 100644 index 00000000..6306e1a2 --- /dev/null +++ b/source/getting-started/control-with-amdc/encoder-fb/resources/ObserverFigure.svg @@ -0,0 +1,635 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Ki + + + + + 1 + s + + + + + + + + + + + + + + + + + + 1 + J + + + + + + + + + + b + + + + Kp + + + + + + + + + + + + + + + 1 + s + + + + + + + + + + + + + + + + + + + + + + + + Ωraw + Ωsf + Tem + + diff --git a/source/getting-started/control-with-amdc/encoder-fb/resources/motor-cross-section.svg b/source/getting-started/control-with-amdc/encoder-fb/resources/motor-cross-section.svg new file mode 100644 index 00000000..6c6b7b65 --- /dev/null +++ b/source/getting-started/control-with-amdc/encoder-fb/resources/motor-cross-section.svg @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/getting-started/control-with-amdc/encoder-fb/resources/motor-cross-section.tex b/source/getting-started/control-with-amdc/encoder-fb/resources/motor-cross-section.tex new file mode 100644 index 00000000..d5878df3 --- /dev/null +++ b/source/getting-started/control-with-amdc/encoder-fb/resources/motor-cross-section.tex @@ -0,0 +1,261 @@ +\documentclass[class=IEEEtran]{standalone}%[conference,10pt]{IEEEtran} +\usepackage{amsmath} +\ifCLASSINFOpdf +\usepackage[pdftex]{graphicx} +\else +\usepackage[dvips]{graphicx} +\fi + +\usepackage{color} % required for `\textcolor' (yatex added) + +%ERIC ADDED: +\usepackage{xcolor, tikz, pgfplots, ifthen} +\usepackage[american,cuteinductors,smartlabels]{circuitikz} +\pgfplotsset{compat=newest} +\usetikzlibrary{intersections,calc,backgrounds} + +\begin{document} + + + \centering + \newcommand{\thisScale}{.09} + + +%THE CALLING TEX FILE MUST DEFINE TWO COMMANDS: +%\thisScale and \setupFig +%for example: +% \newcommand{\setupFig} +%{ +% \def\TopLayerLabels{{"v", "v", "-u", "w", "w", "-v", "u", "u", "-w", "v", "v", "-u", "w", "w", "-v", "u", "u", "-w","v", "v", "-u", "w", "w", "-v", "u", "u", "-w","v", "v", "-u", "w", "w", "-v", "u", "u", "-w"}} +% \def\BotLayerLabels{{"v", "-u", "-u", "w", "-v", "-v", "u", "-w", "-w", "v", "-u", "-u", "w", "-v", "-v", "u", "-w", "-w","v", "-u", "-u", "w", "-v", "-v", "u", "-w", "-w","v", "-u", "-u", "w", "-v", "-v", "u", "-w", "-w"}} +%} +\definecolor{motoGreen}{RGB}{34, 139, 34} +\definecolor{motoRed}{RGB}{196,0,100} % HEX #fedeed +\definecolor{motoBlue}{RGB}{0,110,191} + +%PREAMBLE: +% \usetikzlibrary{intersections} +% \usetikzlibrary{calc} +% +% \newcommand{\setupFig}{\def\showX{0}} +% \newcommand{\thisScale}{1/10} +% +\scalebox{3}{ +\begin{tikzpicture}[font=\tiny,scale=\thisScale,remember picture, >=stealth] + + +%default values: +\def\StatorOR{25} +\def\StatorIR{17.5} + +\def\IntraSlotDepth{.3} +\def\IntraSlotWidth{2} +\def\SlotWidth{2} +\def\SlotDepth{4.1} +\def\NumSlots{36} +\def\CondRadius{1.25} +\def\UseCond{0} +\def\NumberSlots{0} +\def\StatorRotOffset{5} %angle that slot 1 should be at with respect to the x axis +\def\TopLayerLabels{{"v", "v", "-u", "w", "w", "-v", "u", "u", "-w", "v", "v", "-u", "w", "w", "-v", "u", "u", "-w","v", "v", "-u", "w", "w", "-v", "u", "u", "-w","v", "v", "-u", "w", "w", "-v", "u", "u", "-w"}} +\def\BotLayerLabels{{"v", "-u", "-u", "w", "-v", "-v", "u", "-w", "-w", "v", "-u", "-u", "w", "-v", "-v", "u", "-w", "-w","v", "-u", "-u", "w", "-v", "-v", "u", "-w", "-w","v", "-u", "-u", "w", "-v", "-v", "u", "-w", "-w"}} +\def\phaseUcolor{"motoBlue!30!white"} +\def\phaseVcolor{"motoRed!30!white"} +\def\phaseWcolor{"motoGreen!30!white"} +\def\TopCondColors{{\phaseUcolor,\phaseUcolor,\phaseVcolor,\phaseVcolor,\phaseWcolor,\phaseWcolor,\phaseUcolor,\phaseUcolor,\phaseVcolor,\phaseVcolor,\phaseWcolor,\phaseWcolor}} +\def\BotCondColors{{\phaseVcolor,\phaseVcolor,\phaseWcolor,\phaseWcolor,\phaseWcolor,\phaseWcolor,\phaseUcolor,\phaseUcolor,\phaseWcolor,\phaseVcolor,\phaseWcolor,\phaseWcolor}} +\def\nodeFontSize{\tiny} + +%draw rotor: +\def\drawPMrotor{0} +\def\PMrotorPoles{4} +\def\PMrotorRotation{15} +\def\PMrotorPMthick{2} +\def\PMrotorAG{1} +\def\PMnorthColor{red!20!white} +\def\PMsouthColor{green!20!white} +\def\showRotorAngle{1} +\def\rotorAngleLabel{$\theta$} +\def\showRotorAngleTwo{0} +\def\rotorAngleTwoStart{15} +\def\rotorAngleTwoLabel{$\theta_b$} +\def\showAlpha{1} + + +% Overrides for this image +\newcommand{\setupFig} +{ + \def\TopLayerLabels{{ + " ", " ", " "," "," "," ", + " "," "," "," "," "," "}} %back of the slot + \def\BotLayerLabels{{ + " ", " "," "," "," "," ", + " "," "," "," "," "," "}} %front of the slot + \def\TopCondColors{{\phaseUcolor,\phaseVcolor,\phaseWcolor,\phaseUcolor,\phaseVcolor, + \phaseWcolor,\phaseUcolor,\phaseVcolor,\phaseWcolor,\phaseUcolor,\phaseVcolor,\phaseWcolor}} %back of slot + \def\BotCondColors{{\phaseWcolor,\phaseUcolor,\phaseVcolor,\phaseWcolor,\phaseUcolor,\phaseVcolor, + \phaseWcolor,\phaseUcolor,\phaseVcolor,\phaseWcolor,\phaseUcolor,\phaseVcolor}} %front of slot + \def\NumSlots{0} + \def\SlotWidth{3.6} + \def\SlotDepth{6.0} + \def\IntraSlotDepth{.75} + \def\StatorRotOffset{-60} %angle that slot 1 should be at with respect to the x axis + \def\StatorOR{16} + \def\StatorIR{12} + \def\drawPMrotor{1} + \def\PMrotorPoles{2} + \def\PMnorthColor{red!20!white} + \def\PMsouthColor{green!20!white} + \def\showRotorAngle{1} + \def\showRotorAngleTwo{1} + \def\rotorAngleTwoStart{-30} + \def\PMrotorRotation{15} + \def\showAlpha{0} + \def\NumberSlots{0} + \def\UseCond{0} + \def\rotorAngleTwoLabel{$\theta_{\rm enc}$} + \def\rotorAngleLabel{$\theta_{\rm m}$} +} + +\setupFig %must be defined, this can overwrite the above definitions + + + +%Stator laminations +\filldraw[fill=gray!40!white] circle(\StatorOR); +\filldraw[fill=white,very thin] circle (\StatorIR); + + +%make slots: +%find x y coordinate for slot + \pgfmathparse{(\SlotWidth/4.5)/\StatorOR}; %was: \SlotWidth/6 + \edef\slope{\pgfmathresult}; + \pgfmathparse{sqrt((\StatorIR)^2/(1+\slope^2))}; + \edef\xcord{\pgfmathresult}; + \pgfmathparse{sqrt((\StatorIR)^2/(1+(1/\slope)^2))}; + \edef\ycord{\pgfmathresult}; + + \pgfmathparse{sqrt((\StatorIR-1)^2/(1+\slope^2))}; + \edef\xcordsnip{\pgfmathresult}; + \pgfmathparse{sqrt((\StatorIR-1)^2/(1+(1/\slope)^2))}; + \edef\ycordsnip{\pgfmathresult}; + + \ifthenelse{\NumSlots = 0}{}{ + \begin{scope} + \foreach \x in {1,2,...,\NumSlots} + { + \path[fill= white,draw=black,rotate=360/\NumSlots*(\x-1)+\StatorRotOffset, name path =slotPath] (\xcord, \ycord) -- ++(\IntraSlotDepth,0) -- ++(0, \SlotWidth/3) -- ++(\SlotDepth, 0) -- ++(0,-\SlotWidth) -- ++(-\SlotDepth,0) -- ++(0, \SlotWidth/3) -- ++(-\IntraSlotDepth,0) -- cycle; + } + \end{scope} + + \begin{scope} + \foreach \x in {1,2,...,\NumSlots} + { + \pgfmathparse{\TopLayerLabels[\x-1]}; + \edef\TopLayerLabel{\pgfmathresult}; + \pgfmathparse{\BotLayerLabels[\x-1]}; + \edef\BotLayerLabel{\pgfmathresult}; + \path[fill= white,rotate=360/\NumSlots*(\x-1)+\StatorRotOffset, name path =slotPath](\xcordsnip, \ycordsnip) -- ++(\IntraSlotDepth+1,0) -- ++(0, \SlotWidth/3) -- ++(\SlotDepth, 0) -- ++(0,-\SlotWidth) -- ++(-\SlotDepth,0) -- ++(0, \SlotWidth/3) -- ++(-\IntraSlotDepth-1,0) -- cycle; + \ifthenelse{\UseCond = 1} + { + \pgfmathparse{\TopCondColors[\x-1]} + \edef\TopCondColor{\pgfmathresult} + \pgfmathparse{\BotCondColors[\x-1]} + \edef\BotCondColor{\pgfmathresult} + + \filldraw[fill = \BotCondColor, very thin,rotate=360/\NumSlots*(\x-1)+\StatorRotOffset-.5] (\StatorIR+\IntraSlotDepth+\CondRadius+.25,0) circle(\CondRadius) node[font=\tiny]{\BotLayerLabel}; + \filldraw[fill = \TopCondColor, very thin, rotate=360/\NumSlots*(\x-1)+\StatorRotOffset-.5] (\StatorIR+\IntraSlotDepth+\SlotDepth-\CondRadius-.25,0) circle(\CondRadius) node[font=\nodeFontSize]{\TopLayerLabel}; + } + { + \draw[rotate=360/\NumSlots*(\x-1)+\StatorRotOffset] (\StatorIR + \IntraSlotDepth+\SlotDepth/2, -\SlotWidth/2) -- +(0, \SlotWidth); + \draw [rotate=360/\NumSlots*(\x-1)+\StatorRotOffset](\StatorIR + \IntraSlotDepth + \SlotDepth/4, 0) node{\BotLayerLabel}; + \draw [rotate=360/\NumSlots*(\x-1)+\StatorRotOffset](\StatorIR + \IntraSlotDepth + 3*\SlotDepth/4, 0) node{\TopLayerLabel}; + } + \ifthenelse{\NumberSlots = 1} + { + \draw [rotate=360/\NumSlots*(\x-1)+\StatorRotOffset](\StatorIR + \IntraSlotDepth + 5*\SlotDepth/4, 0) node{\x}; + }{} + } + \end{scope}} + + \begin{scope}[rotate = \PMrotorRotation] + + \ifthenelse{\drawPMrotor = 1} + { + \edef\rotorPMOR{\StatorIR-\PMrotorAG} + \edef\rotorShaftOR{\rotorPMOR - \PMrotorPMthick} + \edef\PMangle{180/\PMrotorPoles} + + \fill[gray!40!white] (0,0) circle(\rotorShaftOR); + \foreach \x in {1,2,...,\PMrotorPoles} + { + \begin{scope}[rotate=(\x-1)*\PMangle*2] + \pgfmathparse{int(mod(\x,2))} + \edef\NorthPole{\pgfmathresult} + + \def\pmColor{\PMnorthColor} + \def\pmColor{\PMsouthColor} + \ifthenelse{\NorthPole = 1} + { + \def\pmColor{\PMnorthColor} + } + { + \def\pmColor{\PMsouthColor} + } + %\filldraw[gray!40!white,draw=black] ([shift=(-180\PMrotorPoles:\rotorShaftOR)]0,0) arc (-180/\PMrotorPoles:180/\PMrotorPole:\rotorShaftOR); + \fill[\pmColor] ([shift=(-\PMangle:\rotorShaftOR)]0,0) arc (-\PMangle:\PMangle:\rotorShaftOR) + -- (\PMangle:\rotorPMOR) + -- ([shift=(\PMangle:\rotorPMOR)]0,0) arc (\PMangle:-\PMangle:\rotorPMOR) + -- (-\PMangle:\rotorShaftOR); + \draw([shift=(-\PMangle:\rotorShaftOR)]0,0) arc (-\PMangle:\PMangle:\rotorShaftOR) + -- (\PMangle:\rotorPMOR) + -- ([shift=(\PMangle:\rotorPMOR)]0,0) arc (\PMangle:-\PMangle:\rotorPMOR); + + + \ifthenelse{\NorthPole = 1} + { + \draw[->] (\rotorShaftOR+.25, 0) -- (\rotorPMOR-.25, 0); + } + { + \draw[<-] (\rotorShaftOR+.25, 0) -- (\rotorPMOR-.25, 0); + } + \end{scope} + } + + } + {} +\end{scope} + +\ifthenelse{\showRotorAngle = 1} + { + \draw(\PMrotorRotation:\StatorIR-\PMrotorAG/2) -- (\PMrotorRotation:\StatorOR+5); + \draw(0:\StatorOR+.5) -- (0:\StatorOR+9) node[right]{phase $u$ axis}; + \draw[rotate=120](0:\StatorOR+.5) -- (0:\StatorOR+9) node[right, xshift=-2, yshift=9, rotate=120+180]{phase $v$ axis}; + \draw[rotate=240](0:\StatorOR+.5) -- (0:\StatorOR+9) node[right, xshift=-7, yshift=-8, rotate=240+180]{phase $w$ axis}; + %\draw[dashed](0:\StatorOR+5) -- (0:\StatorOR+10) node[right]{phase $u$ axis}; + \draw[->]([shift=(0:\StatorOR+2.5)]0,0) arc (0:\PMrotorRotation:\StatorOR+2.5) node[xshift=-3,pos=0.5, right]{\rotorAngleLabel}; + } + {} + +\ifthenelse{\showRotorAngleTwo = 1} +{ + \draw(\PMrotorRotation:\StatorIR-\PMrotorAG/2) -- (\PMrotorRotation:\StatorOR+10); + \draw(\rotorAngleTwoStart:\StatorOR+.5) -- (\rotorAngleTwoStart:\StatorOR+10) node[pos=1, right,rotate=\rotorAngleTwoStart] {z-pulse}; + \draw[->]([shift=(\rotorAngleTwoStart:\StatorOR+7.5)]0,0) arc (\rotorAngleTwoStart:\PMrotorRotation:\StatorOR+7.5) node[xshift=-2,pos=0.85, right]{\rotorAngleTwoLabel}; + \draw[->]([shift=(\rotorAngleTwoStart:\StatorOR+2.5)]0,0) arc (\rotorAngleTwoStart:0:\StatorOR+2.5) node[xshift=-3,pos=0.5, right]{$\theta_{\rm off}$}; +} +{} + +\ifthenelse{\showAlpha = 1} +{ + \draw(0,0) -- (60:\StatorIR-\PMrotorAG-\PMrotorPMthick*1.5); + \draw(0,0) -- (0:\StatorIR-\PMrotorAG-\PMrotorPMthick*1.5); + %\draw(0:\StatorOR+.5) -- (0:\StatorOR+5); + \draw[->]([shift=(0:\StatorIR-\PMrotorAG-\PMrotorPMthick*1.5-2.5)]0,0) arc (0:60:\StatorIR-\PMrotorAG-\PMrotorPMthick*1.5-2.5) node[pos=0.95, right]{$\alpha$}; +} +{} +\showAlpha{1} +\end{tikzpicture} +} +\end{document} + diff --git a/source/getting-started/control-with-amdc/encoder-fb/simulink/.gitignore b/source/getting-started/control-with-amdc/encoder-fb/simulink/.gitignore new file mode 100644 index 00000000..1f886b6c --- /dev/null +++ b/source/getting-started/control-with-amdc/encoder-fb/simulink/.gitignore @@ -0,0 +1,32 @@ +########## +# MATLAB # +########## + +# Windows default autosave extension +*.asv + +# OSX / *nix default autosave extension +*.m~ + +# Compiled MEX binaries (all platforms) +*.mex* + +# Packaged app and toolbox files +*.mlappinstall +*.mltbx + +# Generated helpsearch folders +helpsearch*/ + +# Simulink code generation folders +slprj/ +sccprj/ + +# Matlab code generation folders +codegen/ + +# Simulink autosave extension +*.autosave + +# Simulink cache files +*.slxc diff --git a/source/getting-started/control-with-amdc/encoder-fb/simulink/lowPassFilter.slx b/source/getting-started/control-with-amdc/encoder-fb/simulink/lowPassFilter.slx new file mode 100644 index 00000000..4e655dcd Binary files /dev/null and b/source/getting-started/control-with-amdc/encoder-fb/simulink/lowPassFilter.slx differ diff --git a/source/getting-started/control-with-amdc/encoder-fb/simulink/observer.slx b/source/getting-started/control-with-amdc/encoder-fb/simulink/observer.slx new file mode 100644 index 00000000..f001b3df Binary files /dev/null and b/source/getting-started/control-with-amdc/encoder-fb/simulink/observer.slx differ diff --git a/source/getting-started/control-with-amdc/encoder-fb/simulink/pllEncoder.slx b/source/getting-started/control-with-amdc/encoder-fb/simulink/pllEncoder.slx new file mode 100644 index 00000000..86772b69 Binary files /dev/null and b/source/getting-started/control-with-amdc/encoder-fb/simulink/pllEncoder.slx differ diff --git a/source/getting-started/control-with-amdc/encoder-fb/simulink/setup.m b/source/getting-started/control-with-amdc/encoder-fb/simulink/setup.m new file mode 100644 index 00000000..8634526b --- /dev/null +++ b/source/getting-started/control-with-amdc/encoder-fb/simulink/setup.m @@ -0,0 +1,131 @@ +clear +close all + +Ts = 1e-4; +Tsim = 1e-5; + + +%% Switch winding that you develop +load_system('speedControlSimulation') % load Simulink model +% The input of this function should be 'lowPassFilter', 'pllEncoder', 'observer' +switch_speed_calculation('observer') + +Tend = 0.2; + +p = 1; % number of pole +speed_cmd = 3000; % rotational speed (r/min) + +% Parameters for plant +J_z = 29.54e-6; +b = 4.28e-6; + +% Parameters for low pass filter +f_lpf = 100; % low pass fileter cut-off frequency (Hz) +omega_lpf = 2*pi*f_lpf; % low pass fileter cut-off frequency (rad/s) + +% Parameters for PLL +pole_1_Hz = -10; +pole_2_Hz = -100; +w1 = 2*pi*pole_1_Hz; +w2 = 2*pi*pole_2_Hz; + +f_sf = 10; % motion state fileter cut-off frequency (Hz) +wb_sf = 2*pi*f_sf; +b_o_sf = wb_sf*J_z; +K_io_sf = wb_sf*b; + +% Parameters for chirp signal +f_init = 0.1; % initial frequency of chirp [Hz] +f_target = 1000; % chirp frequency at target time [Hz] + +% Parameters for speed control +fb_speed = 100; +omega_b_speed = 2*pi*fb_speed; +Kp_speed = omega_b_speed*J_z; +Ki_speed = omega_b_speed*b; + +%% Run simulation +% set_param('compute_speed/Speed Controller LPF', 'Commented', 'off'); +% set_param('compute_speed/Speed Controller PLL', 'Commented', 'on'); +% set_param('compute_speed/Speed Control Observer', 'Commented', 'on'); +sim('speedControlSimulation.slx'); +run1 = Simulink.sdi.Run.getLatest; + +% set_param('compute_speed/Speed Controller LPF', 'Commented', 'on'); +% set_param('compute_speed/Speed Controller PLL', 'Commented', 'off'); +% set_param('compute_speed/Speed Control Observer', 'Commented', 'on'); +% sim('compute_speed.slx'); +% run2 = Simulink.sdi.Run.getLatest; +% +% set_param('compute_speed/Speed Controller LPF', 'Commented', 'on'); +% set_param('compute_speed/Speed Controller PLL', 'Commented', 'on'); +% set_param('compute_speed/Speed Control Observer', 'Commented', 'off'); +% sim('compute_speed.slx'); +% run3 = Simulink.sdi.Run.getLatest; + +%% Post processing +% List of variables to extract +% obj2ext = {'time', 'omega_raw', 'omega_lpf', 'omega_pll', 'omega_sf'}; +obj2ext = {'time', 'omega'}; +% runs = {run1, run2, run3}; +runs = {run1}; + +for r = 1:1 + runObj = runs{r}; + for i = 1:length(obj2ext) + sigID = getSignalIDsByName(runObj, obj2ext{i}); + if ~isempty(sigID) + sig_obj{r}.(obj2ext{i}) = Simulink.sdi.getSignal(sigID); + sig_val{r}.(obj2ext{i}) = sig_obj{r}.(obj2ext{i}).Values.Data; + time{r} = sig_obj{r}.(obj2ext{i}).Values.Time; + end + end +end + +%% Plot figure +width = 2*5.43; +height = 1.2*3*4.38/3/2; +set(0,'units','inches'); +Inch_SS = get(0,'screensize'); +lw = 1; % line width + +figure1 = figure; +% Plot omega +hold on; +% plot(time{3}, squeeze(sig_val{3}.omega_raw), 'Color', 'k', 'LineWidth', lw); +plot(time{1}, squeeze(sig_val{1}.omega), 'Color', 'r', 'LineWidth', lw); +% plot(time{2}, squeeze(sig_val{2}.omega_pll), 'Color', 'b', 'LineWidth', lw); +% plot(time{3}, squeeze(sig_val{3}.omega_sf), '--', 'Color', 'g', 'LineWidth', lw); +xlabel('Time [s]','Interpreter','latex'); +ylabel('$\Omega$ (rad/s)','Interpreter','latex'); +xlim([0 Tend]); +% ylim([0 400]); +% legend('$\Omega_{\mathrm{raw}}$','$\Omega_{\mathrm{lpf}}$', '$\Omega_{\mathrm{pll}}$', '$\Omega_{\mathrm{sf}}$', 'Interpreter','latex','Location','east'); + +set(findall(gcf, '-property', 'FontName'), 'FontName', 'Times New Roman'); + +set(figure1,'Units','inches','Position',[(Inch_SS(3)-width)/2 (Inch_SS(4)-height)/2 width height]); +print(figure1, '-dsvg','-noui','plot_results'); +print(figure1, '-dpng','-r300','plot_results'); + +function switch_speed_calculation(speed_calculation_type) + % Define maps for speed calculation models + speed_calculation_models = containers.Map( ... + {'lowPassFilter', 'pllEncoder', 'observer'}, ... + {'lowPassFilter', 'pllEncoder', 'observer'} ... + ); + + if ~isKey(speed_calculation_models, speed_calculation_type) + error('Unknown speed calculation type: %s. Choose from: %s', speed_calculation_type, strjoin(keys(speed_calculation_models), ', ')); + end + + speed_calculation_name = speed_calculation_models(speed_calculation_type); + + % Load the referenced models + load_system(speed_calculation_name); + + % Update the reference blocks + set_param('speedControlSimulation', 'ModelName', speed_calculation_name); + + fprintf('Speed calculation block has been switched to referenced model of %s and %s\n', controller_name); +end \ No newline at end of file diff --git a/source/getting-started/control-with-amdc/encoder-fb/simulink/speedControlSimulation.slx b/source/getting-started/control-with-amdc/encoder-fb/simulink/speedControlSimulation.slx new file mode 100644 index 00000000..88a63908 Binary files /dev/null and b/source/getting-started/control-with-amdc/encoder-fb/simulink/speedControlSimulation.slx differ diff --git a/source/getting-started/control-with-amdc/index.md b/source/getting-started/control-with-amdc/index.md index c7b5d929..80c9e7df 100644 --- a/source/getting-started/control-with-amdc/index.md +++ b/source/getting-started/control-with-amdc/index.md @@ -9,5 +9,10 @@ The AMDC Platform provides a versatile and open foundation for establishing high ```{toctree} :hidden: + Current Sensor Calibration +inv-pwm-dt-comp/index +encoder-fb/index +integrator-anti-windup/index +simulink-code-gen/index ``` diff --git a/source/getting-started/control-with-amdc/integrator-anti-windup/index.md b/source/getting-started/control-with-amdc/integrator-anti-windup/index.md new file mode 100644 index 00000000..f35afa77 --- /dev/null +++ b/source/getting-started/control-with-amdc/integrator-anti-windup/index.md @@ -0,0 +1,9 @@ +# Integrator Anti-Windup + +- How to think about performance of anti-windup +- Different methods + - clamping + - back tracking +- Implementation (continuous/discrete-time) + - Simulink + - Handwritten C code \ No newline at end of file diff --git a/source/getting-started/control-with-amdc/inv-pwm-dt-comp/index.md b/source/getting-started/control-with-amdc/inv-pwm-dt-comp/index.md new file mode 100644 index 00000000..32787da4 --- /dev/null +++ b/source/getting-started/control-with-amdc/inv-pwm-dt-comp/index.md @@ -0,0 +1,6 @@ +# Inverter PWM Dead-Time Compensation + +- Sources of non-linearity + - Dead-time +- How to do +- Example results \ No newline at end of file diff --git a/source/getting-started/control-with-amdc/simulink-code-gen/index.md b/source/getting-started/control-with-amdc/simulink-code-gen/index.md new file mode 100644 index 00000000..5500ece9 --- /dev/null +++ b/source/getting-started/control-with-amdc/simulink-code-gen/index.md @@ -0,0 +1,3 @@ +# Simulink Control Code Generation + +This should basically be a port of the AMDC-Examples autogen docs, examples, etc \ No newline at end of file diff --git a/source/index.rst b/source/index.rst index 82fdcb70..8b48e077 100644 --- a/source/index.rst +++ b/source/index.rst @@ -24,6 +24,14 @@ AMDC Platform Documentation getting-started/user-guide/index getting-started/control-with-amdc/index +.. toctree:: + :hidden: + :caption: Applications + + applications/current-control/index + applications/speed-control/index + applications/maglev-control/index + .. toctree:: :hidden: :caption: Hardware