├── Makefile ├── README ├── control.py ├── dyn_model.py ├── misc_utils.py ├── my_io.py ├── my_plot.py └── sim_1.py /Makefile: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | clean: 5 | rm -f *~ *.pyc 6 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Open-BLDC-pysim is a brushless motor and inverter simulation written in python using 2 | numpy. It's goal is to test the underlying mathematics of brushless motor 3 | simulation, to be used later in a C reimplementation directly in Open-BLDC 4 | running against the actual Open-BLDC control code. 5 | 6 | Implementation 7 | ============== 8 | Current implementation is based on the "Switching Pattern-Independent 9 | Simulation Model for Brushless DC Motors" paper by Yongjin Kang and Ji-Yoon Yoo 10 | (http://www.jpe.or.kr/On_line/admin/paper/files/8_JPE-10238.pdf). Some other 11 | papers were consulted during implementation but the above paper is the main 12 | reference. 13 | 14 | Reference 15 | ========= 16 | The implementation is crosschecked against the output of the free version of 17 | PSIM (http://www.powersimtech.com/index.php?name=psim) 18 | 19 | Dependencies 20 | ============ 21 | numpy 22 | scipy 23 | python 2.6 24 | 25 | How to run 26 | ========== 27 | $ ./sim_1.py 28 | -------------------------------------------------------------------------------- /control.py: -------------------------------------------------------------------------------- 1 | # 2 | # Open-BLDC pysim - Open BrushLess DC Motor Controller python simulator 3 | # Copyright (C) 2011 by Antoine Drouin 4 | # Copyright (C) 2011 by Piotr Esden-Tempski 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | # 19 | 20 | import numpy as np 21 | 22 | import dyn_model as dm 23 | 24 | import misc_utils as mu 25 | 26 | import math 27 | 28 | PWM_freq = 16000 29 | PWM_cycle_time = (1./16000) 30 | PWM_duty = 0.6 31 | PWM_duty_time = PWM_cycle_time * PWM_duty 32 | 33 | debug = False 34 | 35 | # 36 | # 37 | # Sp setpoint, Y output 38 | # 39 | def run_hpwm_l_on_bipol(Sp, Y, t): 40 | elec_angle = mu.norm_angle(Y[dm.ov_theta] * dm.NbPoles/2) 41 | 42 | U = np.zeros(dm.iv_size) 43 | 44 | step = "none" 45 | 46 | # switching pattern based on the "encoder" 47 | # H PWM L ON pattern 48 | if 0. <= elec_angle <= (math.pi * (1./6.)): # second half of step 1 49 | # U off 50 | # V low 51 | # W hpwm 52 | hu = 0 53 | lu = 0 54 | hv = 0 55 | lv = 1 56 | if math.fmod(t, PWM_cycle_time) <= PWM_duty_time: 57 | hw = 1 58 | else: 59 | hw = 0 60 | lw = 0 61 | step = "1b" 62 | elif (math.pi * (1.0/6.0)) < elec_angle <= (math.pi * (3.0/6.0)): # step 2 63 | # U hpwm 64 | # V low 65 | # W off 66 | if math.fmod(t, PWM_cycle_time) <= PWM_duty_time: 67 | hu = 1 68 | else: 69 | hu = 0 70 | lu = 0 71 | hv = 0 72 | lv = 1 73 | hw = 0 74 | lw = 0 75 | step = "2 " 76 | elif (math.pi * (3.0/6.0)) < elec_angle <= (math.pi * (5.0/6.0)): # step 3 77 | # U hpwm 78 | # V off 79 | # W low 80 | if math.fmod(t, PWM_cycle_time) <= PWM_duty_time: 81 | hu = 1 82 | else: 83 | hu = 0 84 | lu = 0 85 | hv = 0 86 | lv = 0 87 | hw = 0 88 | lw = 1 89 | step = "3 " 90 | elif (math.pi * (5.0/6.0)) < elec_angle <= (math.pi * (7.0/6.0)): # step 4 91 | # U off 92 | # V hpwm 93 | # W low 94 | hu = 0 95 | lu = 0 96 | if math.fmod(t, PWM_cycle_time) <= PWM_duty_time: 97 | hv = 1 98 | else: 99 | hv = 0 100 | lv = 0 101 | hw = 0 102 | lw = 1 103 | step = "4 " 104 | elif (math.pi * (7.0/6.0)) < elec_angle <= (math.pi * (9.0/6.0)): # step 5 105 | # U low 106 | # V hpwm 107 | # W off 108 | hu = 0 109 | lu = 1 110 | if math.fmod(t, PWM_cycle_time) <= PWM_duty_time: 111 | hv = 1 112 | else: 113 | hv = 0 114 | lv = 0 115 | hw = 0 116 | lw = 0 117 | step = "5 " 118 | elif (math.pi * (9.0/6.0)) < elec_angle <= (math.pi * (11.0/6.0)): # step 6 119 | # U low 120 | # V off 121 | # W hpwm 122 | hu = 0 123 | lu = 1 124 | hv = 0 125 | lv = 0 126 | if math.fmod(t, PWM_cycle_time) <= PWM_duty_time: 127 | hw = 1 128 | else: 129 | hw = 0 130 | lw = 0 131 | step = "6 " 132 | elif (math.pi * (11.0/6.0)) < elec_angle <= (math.pi * (12.0/6.0)): # first half of step 1 133 | # U off 134 | # V low 135 | # W hpwm 136 | hu = 0 137 | lu = 0 138 | hv = 0 139 | lv = 1 140 | if math.fmod(t, PWM_cycle_time) <= PWM_duty_time: 141 | hw = 1 142 | else: 143 | hw = 0 144 | lw = 0 145 | step = "1a" 146 | else: 147 | print 'ERROR: The electrical angle is out of range!!!' 148 | 149 | # Assigning the scheme phase values to the simulator phases 150 | # "Connecting the controller wires to the motor" ^^ 151 | # This way we can for example decide which direction we want to turn the motor 152 | U[dm.iv_hu] = hu 153 | U[dm.iv_lu] = lu 154 | U[dm.iv_hv] = hw 155 | U[dm.iv_lv] = lw 156 | U[dm.iv_hw] = hv 157 | U[dm.iv_lw] = lv 158 | 159 | if debug: 160 | print 'time {} step {} eangle {} switches {}'.format(t, step, mu.deg_of_rad(elec_angle), U) 161 | 162 | return U 163 | 164 | # 165 | # 166 | # Sp setpoint, Y output 167 | # 168 | def run_hpwm_l_on(Sp, Y, t): 169 | elec_angle = mu.norm_angle(Y[dm.ov_theta] * dm.NbPoles/2) 170 | 171 | U = np.zeros(dm.iv_size) 172 | 173 | step = "none" 174 | 175 | # switching pattern based on the "encoder" 176 | # H PWM L ON pattern bipolar 177 | if 0. <= elec_angle <= (math.pi * (1./6.)): # second half of step 1 178 | # U off 179 | # V low 180 | # W hpwm 181 | hu = 0 182 | lu = 0 183 | if math.fmod(t, PWM_cycle_time) <= PWM_duty_time: 184 | hw = 1 185 | lw = 0 186 | hv = 0 187 | lv = 1 188 | else: 189 | hw = 0 190 | lw = 1 191 | hv = 1 192 | lv = 0 193 | step = "1b" 194 | elif (math.pi * (1.0/6.0)) < elec_angle <= (math.pi * (3.0/6.0)): # step 2 195 | # U hpwm 196 | # V low 197 | # W off 198 | if math.fmod(t, PWM_cycle_time) <= PWM_duty_time: 199 | hu = 1 200 | lu = 0 201 | hv = 0 202 | lv = 1 203 | else: 204 | hu = 0 205 | lu = 1 206 | hv = 1 207 | lv = 0 208 | hw = 0 209 | lw = 0 210 | step = "2 " 211 | elif (math.pi * (3.0/6.0)) < elec_angle <= (math.pi * (5.0/6.0)): # step 3 212 | # U hpwm 213 | # V off 214 | # W low 215 | if math.fmod(t, PWM_cycle_time) <= PWM_duty_time: 216 | hu = 1 217 | lu = 0 218 | hw = 0 219 | lw = 1 220 | else: 221 | hu = 0 222 | lu = 1 223 | hw = 1 224 | lw = 0 225 | hv = 0 226 | lv = 0 227 | step = "3 " 228 | elif (math.pi * (5.0/6.0)) < elec_angle <= (math.pi * (7.0/6.0)): # step 4 229 | # U off 230 | # V hpwm 231 | # W low 232 | hu = 0 233 | lu = 0 234 | if math.fmod(t, PWM_cycle_time) <= PWM_duty_time: 235 | hv = 1 236 | lv = 0 237 | hw = 0 238 | lw = 1 239 | else: 240 | hv = 0 241 | lv = 1 242 | hw = 1 243 | lw = 0 244 | step = "4 " 245 | elif (math.pi * (7.0/6.0)) < elec_angle <= (math.pi * (9.0/6.0)): # step 5 246 | # U low 247 | # V hpwm 248 | # W off 249 | if math.fmod(t, PWM_cycle_time) <= PWM_duty_time: 250 | hv = 1 251 | lv = 0 252 | hu = 0 253 | lu = 1 254 | else: 255 | hv = 0 256 | lv = 1 257 | hu = 1 258 | lu = 0 259 | hw = 0 260 | lw = 0 261 | step = "5 " 262 | elif (math.pi * (9.0/6.0)) < elec_angle <= (math.pi * (11.0/6.0)): # step 6 263 | # U low 264 | # V off 265 | # W hpwm 266 | hv = 0 267 | lv = 0 268 | if math.fmod(t, PWM_cycle_time) <= PWM_duty_time: 269 | hw = 1 270 | lw = 0 271 | hu = 0 272 | lu = 1 273 | else: 274 | hw = 0 275 | lw = 1 276 | hu = 1 277 | lu = 0 278 | step = "6 " 279 | elif (math.pi * (11.0/6.0)) < elec_angle <= (math.pi * (12.0/6.0)): # first half of step 1 280 | # U off 281 | # V low 282 | # W hpwm 283 | hu = 0 284 | lu = 0 285 | if math.fmod(t, PWM_cycle_time) <= PWM_duty_time: 286 | hw = 1 287 | lw = 0 288 | hv = 0 289 | lv = 1 290 | else: 291 | hw = 0 292 | lw = 1 293 | hv = 1 294 | lv = 0 295 | step = "1a" 296 | else: 297 | print 'ERROR: The electrical angle is out of range!!!' 298 | 299 | # Assigning the scheme phase values to the simulator phases 300 | # "Connecting the controller wires to the motor" ^^ 301 | # This way we can for example decide which direction we want to turn the motor 302 | U[dm.iv_hu] = hu 303 | U[dm.iv_lu] = lu 304 | U[dm.iv_hv] = hw 305 | U[dm.iv_lv] = lw 306 | U[dm.iv_hw] = hv 307 | U[dm.iv_lw] = lv 308 | 309 | if debug: 310 | print 'time {} step {} eangle {} switches {}'.format(t, step, mu.deg_of_rad(elec_angle), U) 311 | 312 | return U 313 | 314 | 315 | # 316 | # 317 | # Sp setpoint, Y output 318 | # 319 | def run(Sp, Y, t): 320 | #return run_hpwm_l_on(Sp, Y, t) 321 | return run_hpwm_l_on_bipol(Sp, Y, t) 322 | -------------------------------------------------------------------------------- /dyn_model.py: -------------------------------------------------------------------------------- 1 | #-*- coding: utf-8 -*- 2 | # 3 | # Open-BLDC pysim - Open BrushLess DC Motor Controller python simulator 4 | # Copyright (C) 2011 by Antoine Drouin 5 | # Copyright (C) 2011 by Piotr Esden-Tempski 6 | # 7 | # This program is free software; you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation; either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | 21 | import numpy as np 22 | import math 23 | import misc_utils as mu 24 | 25 | # parameters 26 | pset = 2 27 | 28 | if pset == 0: 29 | Inertia = 0.0022 # aka. 'J' in kg/(m^2) 30 | Damping = 0.001 # aka. 'B' in Nm/(rad/s) 31 | Kv = 1700. # aka. motor constant in RPM/V 32 | L = 0.00312 # aka. Coil inductance in H 33 | M = 0.0 # aka. Mutual inductance in H 34 | R = 0.8 # aka. Phase resistence in Ohm 35 | VDC = 100. # aka. Supply voltage 36 | NbPoles = 14. # NbPoles / 2 = Number of pole pairs (you count the permanent magnets on the rotor to get NbPoles) 37 | dvf = .7 # aka. freewheeling diode forward voltage 38 | elif pset == 1: 39 | Inertia = 0.0022 # aka. 'J' in kg/(m^2) 40 | Damping = 0.001 # aka. 'B' in Nm/(rad/s) 41 | Kv = 70. # aka. motor constant in RPM/V 42 | L = 0.00521 # aka. Coil inductance in H 43 | M = 0.0 # aka. Mutual inductance in H 44 | R = 0.7 # aka. Phase resistence in Ohm 45 | VDC = 100. # aka. Supply voltage 46 | NbPoles = 4. # NbPoles / 2 = Number of pole pairs (you count the permanent magnets on the rotor to get NbPoles) 47 | dvf = .7 # aka. freewheeling diode forward voltage 48 | elif pset == 2: #psim 49 | Inertia = 0.000007 # aka. 'J' in kg/(m^2) 50 | tau_shaft = 0.006 51 | Damping = Inertia/tau_shaft # aka. 'B' in Nm/(rad/s) 52 | Kv = 1./32.3*1000 # aka. motor constant in RPM/V 53 | L = 0.00207 # aka. Coil inductance in H 54 | M = -0.00069 # aka. Mutual inductance in H 55 | R = 11.9 # aka. Phase resistence in Ohm 56 | VDC = 100. # aka. Supply voltage 57 | NbPoles = 4. # 58 | dvf = .0 # aka. freewheeling diode forward voltage 59 | elif pset == 3: #modified psim 60 | Inertia = 0.000059 # aka. 'J' in kg/(m^2) 61 | tau_shaft = 0.006 62 | Damping = Inertia/tau_shaft # aka. 'B' in Nm/(rad/s) 63 | Kv = 1./32.3*1000 # aka. motor constant in RPM/V 64 | L = 0.00207 # aka. Coil inductance in H 65 | M = -0.00069 # aka. Mutual inductance in H 66 | R = 11.9 # aka. Phase resistence in Ohm 67 | VDC = 300. # aka. Supply voltage 68 | NbPoles = 4. # 69 | dvf = .0 # aka. freewheeling diode forward voltage 70 | else: 71 | print "Unknown pset {}".format(pset) 72 | 73 | # Components of the state vector 74 | sv_theta = 0 # angle of the rotor 75 | sv_omega = 1 # angular speed of the rotor 76 | sv_iu = 2 # phase u current 77 | sv_iv = 3 # phase v current 78 | sv_iw = 4 # phase w current 79 | sv_size = 5 80 | 81 | 82 | # Components of the command vector 83 | iv_lu = 0 84 | iv_hu = 1 85 | iv_lv = 2 86 | iv_hv = 3 87 | iv_lw = 4 88 | iv_hw = 5 89 | iv_size = 6 90 | 91 | 92 | # Components of the perturbation vector 93 | pv_torque = 0 94 | pv_friction = 1 95 | pv_size = 2 96 | 97 | # Components of the output vector 98 | ov_iu = 0 99 | ov_iv = 1 100 | ov_iw = 2 101 | ov_vu = 3 102 | ov_vv = 4 103 | ov_vw = 5 104 | ov_theta = 6 105 | ov_omega = 7 106 | ov_size = 8 107 | 108 | # Phases and star vector designators 109 | ph_U = 0 110 | ph_V = 1 111 | ph_W = 2 112 | ph_star = 3 113 | ph_size = 4 114 | 115 | # Debug vector components 116 | dv_eu = 0 117 | dv_ev = 1 118 | dv_ew = 2 119 | dv_ph_U = 3 120 | dv_ph_V = 4 121 | dv_ph_W = 5 122 | dv_ph_star = 6 123 | dv_size = 7 124 | 125 | # 126 | # Calculate backemf at a given omega offset from the current rotor position 127 | # 128 | # Used to calculate the phase backemf aka. 'e' 129 | # 130 | def backemf(X,thetae_offset): 131 | phase_thetae = mu.norm_angle((X[sv_theta] * (NbPoles / 2.)) + thetae_offset) 132 | 133 | bemf_constant = mu.vpradps_of_rpmpv(Kv) # aka. ke in V/rad/s 134 | max_bemf = bemf_constant * X[sv_omega] 135 | 136 | bemf = 0. 137 | if 0. <= phase_thetae <= (math.pi * (1./6.)): 138 | bemf = (max_bemf / (math.pi * (1./6.))) * phase_thetae 139 | elif (math.pi/6.) < phase_thetae <= (math.pi * (5./6.)): 140 | bemf = max_bemf 141 | elif (math.pi * (5./6.)) < phase_thetae <= (math.pi * (7./6.)): 142 | bemf = -((max_bemf/(math.pi/6.))* (phase_thetae - math.pi)) 143 | elif (math.pi * (7./6.)) < phase_thetae <= (math.pi * (11./6.)): 144 | bemf = -max_bemf 145 | elif (math.pi * (11./6.)) < phase_thetae <= (2.0 * math.pi): 146 | bemf = (max_bemf/(math.pi/6.)) * (phase_thetae - (2. * math.pi)) 147 | else: 148 | print "ERROR: angle out of bounds can not calculate bemf {}".format(phase_thetae) 149 | 150 | return bemf 151 | 152 | # 153 | # Calculate phase voltages 154 | # Returns a vector of phase voltages in reference to the star point 155 | def voltages(X, U): 156 | 157 | eu = backemf(X, 0.) 158 | ev = backemf(X, math.pi * (2./3.)) 159 | ew = backemf(X, math.pi * (4./3.)) 160 | 161 | # Check which phases are excited 162 | pux = (U[iv_hu] == 1) or \ 163 | (U[iv_lu] == 1) 164 | 165 | pvx = (U[iv_hv] == 1) or \ 166 | (U[iv_lv] == 1) 167 | 168 | pwx = (U[iv_hw] == 1) or \ 169 | (U[iv_lw] == 1) 170 | 171 | vu = 0. 172 | vv = 0. 173 | vw = 0. 174 | vm = 0. 175 | 176 | if pux and pvx and pwx: 177 | if (U[iv_hu] == 1): 178 | vu = VDC/2. 179 | else: 180 | vu = -VDC/2. 181 | 182 | if (U[iv_hv] == 1): 183 | vv = VDC/2. 184 | else: 185 | vv = -VDC/2. 186 | 187 | if (U[iv_hw] == 1): 188 | vw = VDC/2. 189 | else: 190 | vw = -VDC/2. 191 | 192 | vm = (vu + vv + vw - eu - ev - ew) / 3. 193 | 194 | elif pux and pvx: 195 | 196 | # calculate excited phase voltages 197 | if (U[iv_hu] == 1): 198 | vu = VDC/2. 199 | else: 200 | vu = -VDC/2. 201 | 202 | if (U[iv_hv] == 1): 203 | vv = VDC/2. 204 | else: 205 | vv = -VDC/2. 206 | 207 | # calculate star voltage 208 | vm = (vu + vv - eu - ev) / 2. 209 | 210 | # calculate remaining phase voltage 211 | vw = ew + vm 212 | 213 | # clip the voltage to freewheeling diodes 214 | #if (vw > ((VDC/2) + dvf)): 215 | # vw = (VDC/2) + dvf; 216 | # vm = (vu + vv + vw - eu - ev - ew) / 3. 217 | #elif (vw < (-(VDC/2) - dvf)): 218 | # vw = -(VDC/2) - dvf; 219 | # vm = (vu + vv + vw - eu - ev - ew) / 3. 220 | 221 | elif pux and pwx: 222 | if (U[iv_hu] == 1): 223 | vu = VDC/2. 224 | else: 225 | vu = -VDC/2. 226 | 227 | if (U[iv_hw] == 1): 228 | vw = VDC/2. 229 | else: 230 | vw = -VDC/2. 231 | 232 | vm = (vu + vw - eu - ew) / 2. 233 | vv = ev + vm 234 | 235 | # clip the voltage to freewheeling diodes 236 | #if (vv > ((VDC/2) + dvf)): 237 | # vv = (VDC/2) + dvf; 238 | # vm = (vu + vv + vw - eu - ev - ew) / 3. 239 | #elif (vv < (-(VDC/2) - dvf)): 240 | # vv = -(VDC/2) - dvf; 241 | # vm = (vu + vv + vw - eu - ev - ew) / 3. 242 | 243 | elif pvx and pwx: 244 | if (U[iv_hv] == 1): 245 | vv = VDC/2. 246 | else: 247 | vv = -VDC/2. 248 | 249 | if (U[iv_hw] == 1): 250 | vw = VDC/2. 251 | else: 252 | vw = -VDC/2. 253 | 254 | vm = (vv + vw - ev - ew) / 2. 255 | vu = eu + vm 256 | 257 | # clip the voltage to freewheeling diodes 258 | #if (vu > ((VDC/2) + dvf)): 259 | # vu = (VDC/2) + dvf; 260 | # vm = (vu + vv + vw - eu - ev - ew) / 3. 261 | #elif (vu < (-(VDC/2) - dvf)): 262 | # vu = -(VDC/2) - dvf; 263 | # vm = (vu + vv + vw - eu - ev - ew) / 3. 264 | 265 | elif pux: 266 | if (U[iv_hu] == 1): 267 | vu = VDC/2 268 | else: 269 | vu = -VDC/2. 270 | 271 | vm = (vu - eu) 272 | vv = ev + vm 273 | vw = ew + vm 274 | 275 | # if we want to handle diodes properly how to do that here? 276 | 277 | elif pvx: 278 | if (U[iv_hv] == 1): 279 | vv = VDC/2 280 | else: 281 | vv = -VDC/2. 282 | 283 | vm = (vv - ev) 284 | vu = eu + vm 285 | vw = ew + vm 286 | elif pwx: 287 | if (U[iv_hw] == 1): 288 | vw = VDC/2 289 | else: 290 | vw = -VDC/2. 291 | 292 | vm = (vw - ew) 293 | vu = eu + vm 294 | vv = ev + vm 295 | else: 296 | vm = eu 297 | vv = ev 298 | vw = ew 299 | 300 | 301 | # # Initialize the imposed terminal voltages 302 | # vui = 0. 303 | # vvi = 0. 304 | # vwi = 0. 305 | # 306 | # # Phase input voltages based on the inverter switches states 307 | # if (U[iv_hu] == 1) or (U[iv_dhu] == 1): 308 | # vui = VDC/2. 309 | # if (U[iv_lu] == 1) or (U[iv_dlu] == 1): 310 | # vui = -VDC/2. 311 | # if (U[iv_hv] == 1) or (U[iv_dhv] == 1): 312 | # vvi = VDC/2. 313 | # if (U[iv_lv] == 1) or (U[iv_dlv] == 1): 314 | # vvi = -VDC/2. 315 | # if (U[iv_hw] == 1) or (U[iv_dhw] == 1): 316 | # vwi = VDC/2. 317 | # if (U[iv_lw] == 1) or (U[iv_dlw] == 1): 318 | # vwi = -VDC/2. 319 | # 320 | # #i_thr = 0.001 # current threshold saying that the phase is not conducting 321 | # i_thr = 0. # current threshold saying that the phase is not conducting 322 | # #if -i_thr < X[sv_iu] < i_thr: # phase V & W are conducting current 323 | # if not pux: # phase V & W are conducting current 324 | # vm = ((vvi + vwi) / 2.) - ((ev + ew) / 2.) 325 | # vu = eu 326 | # vv = vvi - vm 327 | # vw = vwi - vm 328 | # elif not pvx: # phase U & W are conducting current 329 | # vm = ((vui + vwi) / 2.) - ((eu + ew) / 2.) 330 | # vu = vui - vm 331 | # vv = ev 332 | # vw = vwi - vm 333 | # elif not pwx: # phase U & V are conducting current 334 | # vm = ((vui + vvi) / 2.) - ((eu + ev) / 2.) 335 | # vu = vui - vm 336 | # vv = vvi - vm 337 | # vw = ew 338 | # else: # all phases are corducting current 339 | # print "all phases are conducting!" 340 | # vm = ((vui + vvi + vwi) / 3.) - ((eu + ev + ew) / 3.) 341 | # vu = vui - vm 342 | # vv = vvi - vm 343 | # vw = vwi - vm 344 | 345 | 346 | # print "{} : {} {} {}".format(X[sv_omega], vu, vv, vw ) 347 | 348 | V = [ vu, 349 | vv, 350 | vw, 351 | vm 352 | ] 353 | 354 | return V 355 | 356 | # 357 | # Dynamic model 358 | # 359 | # X state, t time, U input, W perturbation 360 | # 361 | def dyn(X, t, U, W): 362 | Xd, Xdebug = dyn_debug(X, t, U, W) 363 | 364 | return Xd 365 | 366 | # Dynamic model with debug vector 367 | def dyn_debug(X, t, U, W): 368 | 369 | eu = backemf(X, 0.) 370 | ev = backemf(X, math.pi * (2./3.)) 371 | ew = backemf(X, math.pi * (4./3.)) 372 | 373 | # Electromagnetic torque 374 | etorque = (eu * X[sv_iu] + ev * X[sv_iv] + ew * X[sv_iw])/X[sv_omega] 375 | 376 | # Mechanical torque 377 | mtorque = ((etorque * (NbPoles / 2)) - (Damping * X[sv_omega]) - W[pv_torque]) 378 | 379 | if ((mtorque > 0) and (mtorque <= W[pv_friction])): 380 | mtorque = 0 381 | elif (mtorque >= W[pv_friction]): 382 | mtorque = mtorque - W[pv_friction] 383 | elif ((mtorque < 0) and (mtorque >= (-W[pv_friction]))): 384 | mtorque = 0 385 | elif (mtorque <= (-W[pv_friction])): 386 | mtorque = mtorque + W[pv_friction] 387 | 388 | # Acceleration of the rotor 389 | omega_dot = mtorque / Inertia 390 | 391 | V = voltages(X, U) 392 | 393 | pdt = VDC/2 + dvf 394 | 395 | iu_dot = (V[ph_U] - (R * X[sv_iu]) - eu - V[ph_star]) / (L - M) 396 | iv_dot = (V[ph_V] - (R * X[sv_iv]) - ev - V[ph_star]) / (L - M) 397 | iw_dot = (V[ph_W] - (R * X[sv_iw]) - ew - V[ph_star]) / (L - M) 398 | 399 | Xd = [ X[sv_omega], 400 | omega_dot, 401 | iu_dot, 402 | iv_dot, 403 | iw_dot 404 | ] 405 | 406 | Xdebug = [ 407 | eu, 408 | ev, 409 | ew, 410 | V[ph_U], 411 | V[ph_V], 412 | V[ph_W], 413 | V[ph_star] 414 | ] 415 | 416 | return Xd, Xdebug 417 | 418 | 419 | # 420 | # 421 | # 422 | def output(X, U): 423 | 424 | V = voltages(X, U) 425 | 426 | Y = [X[sv_iu], X[sv_iv], X[sv_iw], 427 | V[ph_U], V[ph_V], V[ph_W], 428 | X[sv_theta], X[sv_omega]] 429 | 430 | return Y 431 | -------------------------------------------------------------------------------- /misc_utils.py: -------------------------------------------------------------------------------- 1 | # 2 | # Open-BLDC pysim - Open BrushLess DC Motor Controller python simulator 3 | # Copyright (C) 2011 by Antoine Drouin 4 | # Copyright (C) 2011 by Piotr Esden-Tempski 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | # 19 | 20 | import math 21 | 22 | # 23 | def rad_of_deg(d): return d/180.*math.pi 24 | 25 | # 26 | def deg_of_rad(r): return r*180./math.pi 27 | 28 | # 29 | def rpm_of_radps(rps): return rps/(2*math.pi)*60 30 | 31 | # 32 | def degps_of_radps(rps): return rps/(2*math.pi)*60*360 33 | 34 | # 35 | def radps_of_rpm(rpm): return rpm*(2*math.pi)/60 36 | 37 | # 38 | def vpradps_of_rpmpv(vprpm): return 30/(vprpm*math.pi) 39 | 40 | # 41 | def norm_angle(alpha): 42 | alpha_n = math.fmod(alpha, 2*math.pi) 43 | 44 | if alpha_n < 0.: 45 | alpha_n = (2*math.pi) + alpha_n 46 | 47 | return alpha_n 48 | -------------------------------------------------------------------------------- /my_io.py: -------------------------------------------------------------------------------- 1 | # 2 | # Open-BLDC pysim - Open BrushLess DC Motor Controller python simulator 3 | # Copyright (C) 2011 by Antoine Drouin 4 | # Copyright (C) 2011 by Piotr Esden-Tempski 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | # 19 | 20 | import numpy as np 21 | import dyn_model as dm 22 | import misc_utils as mu 23 | 24 | def read_csv(filename): 25 | my_records = np.recfromcsv(filename) 26 | Y = np.zeros((my_records.time.size, dm.ov_size)) 27 | Y[:,dm.ov_iu] = my_records.ia 28 | Y[:,dm.ov_iv] = my_records.ib 29 | Y[:,dm.ov_iw] = my_records.ic 30 | Y[:,dm.ov_vu] = my_records.vag 31 | Y[:,dm.ov_vv] = my_records.vbg 32 | Y[:,dm.ov_vw] = my_records.vcg 33 | Y[:,dm.ov_omega] = mu.radps_of_rpm(my_records.nm) 34 | # import code; code.interact(local=locals()) 35 | return my_records.time, Y 36 | -------------------------------------------------------------------------------- /my_plot.py: -------------------------------------------------------------------------------- 1 | # 2 | # Open-BLDC pysim - Open BrushLess DC Motor Controller python simulator 3 | # Copyright (C) 2011 by Antoine Drouin 4 | # Copyright (C) 2011 by Piotr Esden-Tempski 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | # 19 | 20 | import matplotlib.pyplot as plt 21 | 22 | import dyn_model as dm 23 | import misc_utils as mu 24 | 25 | ang_unit_rad_s = 0 26 | ang_unit_deg_s = 1 27 | ang_unit_rpm = 2 28 | 29 | def plot_output(time, Y, ls): 30 | ang_unit = ang_unit_rpm 31 | 32 | # Phase current 33 | ax = plt.subplot(4, 1, 1) 34 | ax.yaxis.set_label_text('A', {'color' : 'k', 'fontsize' : 15 }) 35 | plt.plot(time,Y[:,dm.ov_iu], ls, linewidth=1.5) 36 | plt.plot(time,Y[:,dm.ov_iv], ls, linewidth=1.5) 37 | plt.plot(time,Y[:,dm.ov_iw], ls, linewidth=1.5) 38 | plt.legend(['$i_u$', '$i_v$', '$i_w$'], loc='upper right') 39 | plt.title('Phase current') 40 | 41 | # Phase terminal voltage 42 | ax = plt.subplot(4, 1, 2) 43 | ax.yaxis.set_label_text('V', {'color' : 'k', 'fontsize' : 15 }) 44 | plt.plot(time,Y[:,dm.ov_vu], ls, linewidth=1.5) 45 | plt.plot(time,Y[:,dm.ov_vv], ls, linewidth=1.5) 46 | plt.plot(time,Y[:,dm.ov_vw], ls, linewidth=1.5) 47 | plt.legend(['$v_u$', '$v_v$', '$v_w$'], loc='upper right') 48 | plt.title('Phase terminal voltage') 49 | 50 | # Rotor mechanical position 51 | ax = plt.subplot(4, 1, 3) 52 | ax.yaxis.set_label_text('Deg', {'color' : 'k', 'fontsize' : 15 }) 53 | plt.plot(time,mu.deg_of_rad(Y[:,dm.ov_theta]), ls, linewidth=1.5) 54 | # plt.plot(time, Y[:,dm.ov_theta], ls, linewidth=1.5) 55 | plt.title('Rotor angular position') 56 | 57 | # Rotor mechanical angular speed 58 | ax = plt.subplot(4, 1, 4) 59 | 60 | if (ang_unit == ang_unit_rad_s): 61 | ax.yaxis.set_label_text('Rad/s', {'color' : 'k', 'fontsize' : 15 }) 62 | plt.plot(time,Y[:,dm.ov_omega], ls, linewidth=1.5) 63 | elif (ang_unit == ang_unit_deg_s): 64 | ax.yaxis.set_label_text('Deg/s', {'color' : 'k', 'fontsize' : 15 }) 65 | plt.plot(time,mu.degps_of_radps(Y[:,dm.ov_omega]), ls, linewidth=1.5) 66 | elif (ang_unit == ang_unit_rpm): 67 | ax.yaxis.set_label_text('RPM', {'color' : 'k', 'fontsize' : 15 }) 68 | plt.plot(time,mu.rpm_of_radps(Y[:,dm.ov_omega]), ls, linewidth=1.5) 69 | 70 | plt.title('Rotor Rotational Velocity') 71 | 72 | def plot_debug(time, Xdebug): 73 | plt.subplot(4, 1, 1) 74 | 75 | plt.plot(time,Xdebug[:,dm.dv_eu], linewidth=1.5) 76 | plt.plot(time,Xdebug[:,dm.dv_ev], linewidth=1.5) 77 | plt.plot(time,Xdebug[:,dm.dv_ew], linewidth=1.5) 78 | plt.legend(['$U_{BEMF}$', '$V_{BEMF}$', '$W_{BEMF}$'], loc='upper right') 79 | 80 | plt.subplot(4, 1, 2) 81 | 82 | plt.plot(time,Xdebug[:,dm.dv_ph_U], linewidth=1.5) 83 | plt.plot(time,Xdebug[:,dm.dv_ph_V], linewidth=1.5) 84 | plt.plot(time,Xdebug[:,dm.dv_ph_W], linewidth=1.5) 85 | plt.legend(['$U$', '$V$', '$W$'], loc='upper right') 86 | 87 | plt.subplot(4, 1, 3) 88 | 89 | plt.plot(time,Xdebug[:,dm.dv_ph_star], linewidth=1.5) 90 | plt.legend(['$star$'], loc='upper right') 91 | 92 | def plot_diodes(time, D): 93 | 94 | titles_diodes = ['$dhu$', '$dlu$', '$dhv$', '$dlv$', '$dhw$', '$dlw$'] 95 | 96 | for i in range(0, dm.adc_size): 97 | plt.subplot(6, 2, 2*i+1) 98 | plt.plot(time,D[:,i], 'r', linewidth=1.5) 99 | plt.title(titles_diodes[i]) 100 | -------------------------------------------------------------------------------- /sim_1.py: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Open-BLDC pysim - Open BrushLess DC Motor Controller python simulator 4 | # Copyright (C) 2011 by Antoine Drouin 5 | # Copyright (C) 2011 by Piotr Esden-Tempski 6 | # 7 | # This program is free software; you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation; either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | 21 | import matplotlib 22 | matplotlib.use('MacOSX') 23 | #matplotlib.use('GTKCairo') 24 | import numpy as np 25 | import pylab as pl 26 | import matplotlib.pyplot as plt 27 | from scipy import integrate 28 | from scipy.signal import decimate 29 | 30 | import misc_utils as mu 31 | import dyn_model as dm 32 | import control as ctl 33 | import my_io as mio 34 | import my_plot as mp 35 | 36 | 37 | 38 | def display_state_and_command(time, X, U): 39 | 40 | titles_state = ['$\\theta$', '$\omega$', '$i_u$', '$i_v$', '$i_w$'] 41 | titles_cmd = ['$u_l$', '$u_h$', '$v_l$', '$v_h$', '$w_l$', '$w_h$'] 42 | for i in range(0, 2): 43 | plt.subplot(6, 2, 2*i+1) 44 | plt.plot(time,mu.deg_of_rad(X[:,i]), 'r', linewidth=3.0) 45 | plt.title(titles_state[i]) 46 | for i in range(2, dm.sv_size): 47 | plt.subplot(6, 2, 2*i+1) 48 | plt.plot(time, X[:,i], 'r', linewidth=3.0) 49 | plt.title(titles_state[i]) 50 | for i in range(0, 6): 51 | plt.subplot(6, 2, 2*i+2) 52 | plt.plot(time, U[:,i], 'r', linewidth=3.0) 53 | plt.title(titles_cmd[i]) 54 | 55 | def print_simulation_progress(count, steps): 56 | sim_perc_last = ((count-1)*100) / steps 57 | sim_perc = (count*100) / steps 58 | if (sim_perc_last != sim_perc): 59 | print "{}%".format(sim_perc) 60 | 61 | def drop_it(a, factor): 62 | new = [] 63 | for n, x in enumerate(a): 64 | if ((n % factor) == 0): 65 | new.append(x) 66 | return np.array(new) 67 | 68 | def compress(a, factor): 69 | return drop_it(a, factor) 70 | #return decimate(a, 8, n=8, axis=0) 71 | 72 | def main(): 73 | # t_psim, Y_psim = mio.read_csv('bldc_startup_psim_1us_resolution.csv') 74 | # mp.plot_output(t_psim, Y_psim, '.') 75 | 76 | freq_sim = 1e6 # simulation frequency 77 | compress_factor = 3 78 | time = pl.arange(0.0, 0.01, 1./freq_sim) # create time slice vector 79 | X = np.zeros((time.size, dm.sv_size)) # allocate state vector 80 | Xdebug = np.zeros((time.size, dm.dv_size)) # allocate debug data vector 81 | Y = np.zeros((time.size, dm.ov_size)) # allocate output vector 82 | U = np.zeros((time.size, dm.iv_size)) # allocate input vector 83 | X0 = [0, mu.rad_of_deg(0.1), 0, 0, 0] # 84 | X[0,:] = X0 85 | W = [0, 1] 86 | for i in range(1,time.size): 87 | 88 | if i==1: 89 | Uim2 = np.zeros(dm.iv_size) 90 | else: 91 | Uim2 = U[i-2,:] 92 | 93 | Y[i-1,:] = dm.output(X[i-1,:], Uim2) # get the output for the last step 94 | U[i-1,:] = ctl.run(0, Y[i-1,:], time[i-1]) # run the controller for the last step 95 | tmp = integrate.odeint(dm.dyn, X[i-1,:], [time[i-1], time[i]], args=(U[i-1,:], W)) # integrate 96 | X[i,:] = tmp[1,:] # copy integration output to the current step 97 | X[i, dm.sv_theta] = mu.norm_angle( X[i, dm.sv_theta]) # normalize the angle in the state 98 | tmp, Xdebug[i,:] = dm.dyn_debug(X[i-1,:], time[i-1], U[i-1,:], W) # get debug data 99 | print_simulation_progress(i, time.size) 100 | 101 | Y[-1,:] = Y[-2,:] 102 | U[-1,:] = U[-2,:] 103 | 104 | if compress_factor > 1: 105 | time = compress(time, compress_factor) 106 | Y = compress(Y, compress_factor) 107 | X = compress(X, compress_factor) 108 | U = compress(U, compress_factor) 109 | Xdebug = compress(Xdebug, compress_factor) 110 | 111 | mp.plot_output(time, Y, '-') 112 | # pl.show() 113 | plt.figure(figsize=(10.24, 5.12)) 114 | display_state_and_command(time, X, U) 115 | 116 | plt.figure(figsize=(10.24, 5.12)) 117 | mp.plot_debug(time, Xdebug) 118 | 119 | pl.show() 120 | 121 | if __name__ == "__main__": 122 | main() 123 | --------------------------------------------------------------------------------