From 732e0460dad31520703ebb729d0dc7004c7e2e31 Mon Sep 17 00:00:00 2001 From: mooocella <113719459+mooocella@users.noreply.github.com> Date: Sun, 23 Nov 2025 12:18:17 -0800 Subject: [PATCH 1/4] Update and rename motor_map_model.m to motor_op_point --- motor_map_model.m | 77 ------------------ motor_op_point | 201 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 201 insertions(+), 77 deletions(-) delete mode 100644 motor_map_model.m create mode 100644 motor_op_point diff --git a/motor_map_model.m b/motor_map_model.m deleted file mode 100644 index b37fd00..0000000 --- a/motor_map_model.m +++ /dev/null @@ -1,77 +0,0 @@ -% updated motor map demo with datasheet limits - -clear; clc; close all; -% Datasheet constants -p = 5; % pole pairs -R_s = 0.135; % stator resistance [ohm] -Ld = 0.24e-3; % [H] -Lq = 0.12e-3; % [H] -kt = 0.26; % Nm/Arms -J = 2.74e-4; % [kg*m^2] - -I_max = 105; % Arms -V_dc = 350; % V (nominal DC bus) -V_margin= 30; % V reserve margin - -% Load MAT files -data80 = load('A2370DD_T80C.mat'); -data100 = load('A2370DD_T100C.mat'); -data120 = load('A2370DD_T120C.mat'); - -% Axes setup (hopefully this is right) -rpm_vec = data100.Speed(:,1); -T_vec = data100.Shaft_Torque(1,:); - -% Build gridded interpolnts -fields = {'Shaft_Torque','Id_RMS','Iq_RMS','Stator_Current_Phase_RMS',... - 'Vd_RMS','Vq_RMS','Total_Loss','Stator_Copper_Loss',... - 'Iron_Loss','Magnet_Loss','Mechanical_Loss'}; - -maps80 = struct(); maps100 = struct(); maps120 = struct(); - -for i = 1:length(fields) - f = fields{i}; - maps80.(f) = griddedInterpolant({rpm_vec, T_vec}, data80.(f), 'linear','nearest'); - maps100.(f) = griddedInterpolant({rpm_vec, T_vec}, data100.(f), 'linear','nearest'); - maps120.(f) = griddedInterpolant({rpm_vec, T_vec}, data120.(f), 'linear','nearest'); -end - -% Example query (test with limits) -rpm_q = 8000; % rpm -Tq_req = 15; % requested torque [Nm] -Tmotor = 95; % degC - - -% Temperature blending (temp solution... I think) -tempBlend = @(map80,map120,rpmq,Tq,Tm) ... - clip((Tm-80)/(120-80), 0, 1) .* map120(rpmq,Tq) + ... - (1-clip((Tm-80)/(120-80), 0, 1)) .* map80(rpmq,Tq); -% Interpolated values -Iq_val = tempBlend(maps80.Iq_RMS, maps120.Iq_RMS, rpm_q, Tq_req, Tmotor); -Id_val = tempBlend(maps80.Id_RMS, maps120.Id_RMS, rpm_q, Tq_req, Tmotor); -Iphase = tempBlend(maps80.Stator_Current_Phase_RMS, maps120.Stator_Current_Phase_RMS, rpm_q, Tq_req, Tmotor); -Vd_val = tempBlend(maps80.Vd_RMS, maps120.Vd_RMS, rpm_q, Tq_req, Tmotor); -Vq_val = tempBlend(maps80.Vq_RMS, maps120.Vq_RMS, rpm_q, Tq_req, Tmotor); -Ploss = tempBlend(maps80.Total_Loss, maps120.Total_Loss, rpm_q, Tq_req, Tmotor); -Torque = tempBlend(maps80.Shaft_Torque, maps120.Shaft_Torque, rpm_q, Tq_req, Tmotor); - -% Enforce current limit -if Iphase > I_max - scale = I_max / Iphase; - Iq_val = Iq_val*scale; - Id_val = Id_val*scale; - Torque = Torque*scale; - Iphase = I_max; -end - -% Enforce voltage limit -V_lim = (V_dc - V_margin)/sqrt(3); % approximate phase RMS -V_req = sqrt(Vd_val^2 + Vq_val^2); -if V_req > V_lim - Torque = Torque * (V_lim/V_req); % scale torque down -end - -fprintf('At %.0f rpm, %.1f Nm req, %.0f °C:\n', rpm_q,Tq_req,Tmotor); -fprintf(' -> Torque=%.2f Nm, Iphase=%.1f A, Vreq=%.1f V, Loss=%.1f W\n',... - Torque,Iphase,V_req,Ploss); - diff --git a/motor_op_point b/motor_op_point new file mode 100644 index 0000000..9600551 --- /dev/null +++ b/motor_op_point @@ -0,0 +1,201 @@ +function res = motor_op_point(V_dc, T_dmd, Temp) +% +% Inputs: +% V_dc - DC bus / peak-phase voltage limit (V) +% T_dmd - requested electromagnetic torque (Nm) +% Temp - temperature selector (use 80-120 ideally; nearest file chosen otherwise) +% +% Output (struct res): +% res.I_op_idx - column index in table +% res.I_op - approximated phase current (A) corresponding to column +% res.T_emg_op - Electromagnetic torque value found (Nm) +% res.V_op - phase-peak voltage found (same units as Voltage_Phase_Peak) +% res.S_op - speed (rpm) corresponding to V_op +% res.currents - vector of current axis (A) +% res.speeds - vector of speed axis (rpm) +% res.notes - cell array of explanatory strings + +%% 1) Load the appropriate data file (choose nearest available Temp) +availableTemps = [80 100 120]; +[~, idxNearest] = min(abs(availableTemps - Temp)); +chosenTemp = availableTemps(idxNearest); + +switch chosenTemp + case 80 + dat = load('A2370DD_T80C.mat'); + case 100 + dat = load('A2370DD_T100C.mat'); + case 120 + dat = load('A2370DD_T120C.mat'); + otherwise + error('Unexpected temperature selection.'); +end + +notes = {}; +notes{end+1} = sprintf('Using data file for %d C (closest to requested %g C).', chosenTemp, Temp); + +Tmat = dat.Electromagnetic_Torque; +Vmat = dat.Voltage_Phase_Peak; + +% Matrix sizes +[NR, NC] = size(Tmat); +% Derive speed and current axes from matrix dimensions: +% assume speeds from 0 to 20000 rpm across NR rows (uniform) +% assume currents from 0 to 105 A across NC columns (uniform) +speeds = linspace(0,20000,NR).'; % column vector, rpm +currents = linspace(0,105,NC); % row vector, A + +% Save axes to results +res.currents = currents; +res.speeds = speeds; + +%% 2) Find minimum current column that can achieve T_dmd at any speed +% Torque decreases with speed, so check full column, not just first row. + +found_point = false; +found_col = NaN; +found_row = NaN; + +for col = NC:-1:1 % right -> left (highest current first) + % search rows from high speed down to low speed (bottom -> top) + for row = NR:-1:1 + if Tmat(row,col) >= T_dmd + % Found a torque-capable point at (row,col) + found_point = true; + found_col = col; + found_row = row; + notes{end+1} = sprintf('Torque-only candidate found at column %d (I=%.3g A), row %d (speed %.1f rpm) with T=%.3g Nm.', ... + col, currents(col), row, speeds(row), Tmat(row,col)); + break; % stop scanning rows for this column (we want the highest speed in this column) + end + end + if found_point + break; % stop scanning columns when first (rightmost) column with a valid row is found + end +end + +if ~found_point + % No column/row meets torque demand anywhere + % Fallback: choose maximum torque available at max current column + [T_emg_op_max, row_idx_max] = max(Tmat(:,NC)); + res.I_op_idx = NC; + res.I_op = currents(NC); + res.T_emg_op = T_emg_op_max; + res.S_op = speeds(row_idx_max); + res.V_op = Vmat(row_idx_max, NC); + notes{end+1} = sprintf(['Demanded torque (%.3g Nm) cannot be produced at any point in the map. ', ... + 'Returning best-effort: max current column %d (I=%.3g A) with max torque %.3g Nm at speed %.1f rpm.'], ... + T_dmd, NC, currents(NC), T_emg_op_max, speeds(row_idx_max)); + res.notes = notes; + return; +end + +% Record the torque-only candidate +I_op_idx = found_col; +row_idx = found_row; +T_emg_op = Tmat(row_idx, I_op_idx); + +res.I_op_idx = I_op_idx; +res.I_op = currents(I_op_idx); +res.T_emg_op = T_emg_op; + + +%% 3) In the found column, search Voltage_Phase_Peak(:,I_op_idx) for largest value <= V_dc +V_op = NaN; +S_op = NaN; +final_found = false; +epsV = 1e-3; %because some of the values are just slightly off + +col = I_op_idx; +while col >= 1 && ~final_found + % For each column, determine the maximum speed for which torque >= T_dmd. + % Because we moved columns earlier, the current column might be different now. + % Find highest-speed row in this column where T >= T_dmd (if any). + rows_torque = find(Tmat(:,col) >= T_dmd); + if isempty(rows_torque) + % this column cannot meet torque: move left + notes{end+1} = sprintf('Column %d (I=%.3g A) cannot meet torque; move left.', col, currents(col)); + col = col - 1; + continue; + end + % highest-speed row meeting torque in this column: + candidate_row = rows_torque(end); + + % Starting from candidate_row and moving toward lower speeds (smaller row index), + % find first row where V <= V_dc + vcol = Vmat(:,col); + le_rows = find(vcol(1:candidate_row) <= V_dc + epsV); % only consider rows up to candidate_row + if ~isempty(le_rows) + % choose the largest row index in le_rows (closest to candidate_row) + selected_row = le_rows(end); + V_op = vcol(selected_row); + S_op = speeds(selected_row); + I_op_idx = col; + T_emg_op = Tmat(selected_row, col); + final_found = true; + notes{end+1} = sprintf('Voltage constraint satisfied at column %d (I=%.3g A), row %d (speed %.1f rpm): V_op=%.3g <= V_dc=%.3g.', ... + col, currents(col), selected_row, S_op, V_op, V_dc); + break; + else + % This column cannot satisfy voltage at any torque-capable speed -> move left + notes{end+1} = sprintf('Column %d (I=%.3g A) had no rows <= V_dc up to its torque-capable max speed; move left.', ... + col, currents(col)); + col = col - 1; + end +end + +if ~final_found + % Could not find any (col,row) that satisfies both T and V + V_op = NaN; + S_op = NaN; + notes{end+1} = 'Searched leftwards but did not find any operating point that satisfies both torque and voltage constraints.'; +end + +res.V_op = V_op; +res.S_op = S_op; + +%% 4) Extract remaining outputs if an operable point exists +if ~isnan(V_op) && ~isnan(S_op) + + % Extract additional maps + if isfield(dat, 'Shaft_Torque') + res.T_Shaft = dat.Shaft_Torque(selected_row, I_op_idx); + else + res.T_Shaft = NaN; + notes{end+1} = 'Shaft_Torque map missing from data file.'; + end + + if isfield(dat, 'Stator_Current_Phase_RMS') + res.I_phase = dat.Stator_Current_Phase_RMS(selected_row, I_op_idx); + else + res.I_phase = NaN; + notes{end+1} = 'Stator_Current_Phase_RMS map missing from data file.'; + end + + if isfield(dat, 'Mechanical_Loss') + res.Mech_loss = dat.Mechanical_Loss(selected_row, I_op_idx); + else + res.Mech_loss = NaN; + notes{end+1} = 'Mechanical_Loss map missing from data file.'; + end + + if isfield(dat, 'Power_Factor') + res.Pf = dat.Power_Factor(selected_row, I_op_idx); + else + res.Pf = NaN; + notes{end+1} = 'Power_Factor map missing from data file.'; + end + +else + % No operable point → everything NaN + res.T_emg = NaN; + res.T_Shaft = NaN; + res.I_phase = NaN; + res.Mech_loss= NaN; + res.Pf = NaN; + notes{end+1} = 'No valid operating point. Additional outputs set to NaN.'; +end + +res.notes = notes; + +end From 28ee55d80b0ac986304fe88ed5f4e05a1a1e828f Mon Sep 17 00:00:00 2001 From: mooocella <113719459+mooocella@users.noreply.github.com> Date: Mon, 24 Nov 2025 00:11:34 -0800 Subject: [PATCH 2/4] Update motor_op_point Updated to make concise and sweep more accurately to optimize torque and rpm --- motor_op_point | 240 +++++++++++++++++++++---------------------------- 1 file changed, 104 insertions(+), 136 deletions(-) diff --git a/motor_op_point b/motor_op_point index 9600551..5bc9311 100644 --- a/motor_op_point +++ b/motor_op_point @@ -11,10 +11,18 @@ function res = motor_op_point(V_dc, T_dmd, Temp) % res.T_emg_op - Electromagnetic torque value found (Nm) % res.V_op - phase-peak voltage found (same units as Voltage_Phase_Peak) % res.S_op - speed (rpm) corresponding to V_op +% res.T_Shaft - shaft torque (usable torque) (Nm) +% res.I_phase - stator phase current (ARMS) +% res.Mech_loss - mechanical loss (W) +% res.Pf - power factor % res.currents - vector of current axis (A) % res.speeds - vector of speed axis (rpm) % res.notes - cell array of explanatory strings +%% 0) Tolerances +epsV = 1e-4; % V tolerance (rename of deltaV) +epsT = 1e-4; % torque tolerance (rename of deltaS) + %% 1) Load the appropriate data file (choose nearest available Temp) availableTemps = [80 100 120]; [~, idxNearest] = min(abs(availableTemps - Temp)); @@ -45,157 +53,117 @@ Vmat = dat.Voltage_Phase_Peak; speeds = linspace(0,20000,NR).'; % column vector, rpm currents = linspace(0,105,NC); % row vector, A -% Save axes to results +% Save axes res.currents = currents; res.speeds = speeds; -%% 2) Find minimum current column that can achieve T_dmd at any speed -% Torque decreases with speed, so check full column, not just first row. - -found_point = false; -found_col = NaN; -found_row = NaN; - -for col = NC:-1:1 % right -> left (highest current first) - % search rows from high speed down to low speed (bottom -> top) - for row = NR:-1:1 - if Tmat(row,col) >= T_dmd - % Found a torque-capable point at (row,col) - found_point = true; - found_col = col; - found_row = row; - notes{end+1} = sprintf('Torque-only candidate found at column %d (I=%.3g A), row %d (speed %.1f rpm) with T=%.3g Nm.', ... - col, currents(col), row, speeds(row), Tmat(row,col)); - break; % stop scanning rows for this column (we want the highest speed in this column) +%% 2) Sweep matrices according to flowchart +% Outer loop: high RPM -> low (NR down to 1) +% Inner loop: low current -> high (1 to NC) +% Objective: find first (row,col) where: +% Vmat(row,col) <= V_dc + epsV +% Tmat(row,col) >= T_dmd - epsT (note: use >= T_dmd - epsT to allow small tolerance) +% If V OK but T < demanded, track the best max T encountered under V condition (fallback). + +found_exact = false; +selected_row = NaN; +selected_col = NaN; + +% Track fallback candidate where V satisfied but T not; pick the one with max T +fallback_exists = false; +fallback_T = -Inf; +fallback_row = NaN; +fallback_col = NaN; + +for row = NR:-1:1 % highest speed first + for col = 1:NC % lowest current first + Tv = Tmat(row,col); + Vv = Vmat(row,col); + % Check voltage satisfied (with tolerance) + v_ok = (Vv <= V_dc + epsV); + % Check torque satisfied (with tolerance downward) + t_ok = (Tv >= T_dmd - epsT); + if v_ok && t_ok + % Exact acceptable operating point found (prefer high RPM, then low current) + selected_row = row; + selected_col = col; + found_exact = true; + notes{end+1} = sprintf('Exact operating point found at row %d (%.1f rpm), col %d (I=%.3g A): T=%.3g, V=%.3g.', ... + row, speeds(row), col, currents(col), Tv, Vv); + break; % break inner loop (we found the best point for this high RPM) + else + % If voltage satisfied but torque not, consider as fallback candidate + if v_ok && ~t_ok + if Tv > fallback_T + fallback_T = Tv; + fallback_row = row; + fallback_col = col; + fallback_exists = true; + end + end + % Otherwise (V not ok) we do nothing special, continue scanning end end - if found_point - break; % stop scanning columns when first (rightmost) column with a valid row is found + if found_exact + break; % exit outer loop end end -if ~found_point - % No column/row meets torque demand anywhere - % Fallback: choose maximum torque available at max current column - [T_emg_op_max, row_idx_max] = max(Tmat(:,NC)); - res.I_op_idx = NC; - res.I_op = currents(NC); - res.T_emg_op = T_emg_op_max; - res.S_op = speeds(row_idx_max); - res.V_op = Vmat(row_idx_max, NC); - notes{end+1} = sprintf(['Demanded torque (%.3g Nm) cannot be produced at any point in the map. ', ... - 'Returning best-effort: max current column %d (I=%.3g A) with max torque %.3g Nm at speed %.1f rpm.'], ... - T_dmd, NC, currents(NC), T_emg_op_max, speeds(row_idx_max)); +%% 3) Decide final operating point based on sweep results +if found_exact + final_row = selected_row; + final_col = selected_col; + final_T = Tmat(final_row, final_col); + final_V = Vmat(final_row, final_col); + final_S = speeds(final_row); + final_I = currents(final_col); +elseif fallback_exists + % Use fallback (best torque among those that satisfied voltage) + final_row = fallback_row; + final_col = fallback_col; + final_T = Tmat(final_row, final_col); + final_V = Vmat(final_row, final_col); + final_S = speeds(final_row); + final_I = currents(final_col); + notes{end+1} = sprintf(['Torque was either unachievable or limited by supplied voltage. Using next best operating point where voltage requirement was satisfied. \n ', ... + 'Chosen fallback at row %d (%.1f rpm), col %d (I=%.3g A) with T=%.3g and V=%.3g.'], ... + final_row, final_S, final_col, final_I, final_T, final_V); +else + % No valid point and no fallback -> voltage preventing operation + res.I_op_idx = NaN; + res.I_op = NaN; + res.T_emg_op = NaN; + res.V_op = NaN; + res.S_op = NaN; + % The additional outputs are required; fill with NaN + res.T_Shaft = NaN; + res.I_phase = NaN; + res.Mech_loss = NaN; + res.Pf = NaN; + notes{end+1} = 'No operating point found. Voltage constraint prevents operation'; res.notes = notes; return; end -% Record the torque-only candidate -I_op_idx = found_col; -row_idx = found_row; -T_emg_op = Tmat(row_idx, I_op_idx); - -res.I_op_idx = I_op_idx; -res.I_op = currents(I_op_idx); -res.T_emg_op = T_emg_op; - - -%% 3) In the found column, search Voltage_Phase_Peak(:,I_op_idx) for largest value <= V_dc -V_op = NaN; -S_op = NaN; -final_found = false; -epsV = 1e-3; %because some of the values are just slightly off - -col = I_op_idx; -while col >= 1 && ~final_found - % For each column, determine the maximum speed for which torque >= T_dmd. - % Because we moved columns earlier, the current column might be different now. - % Find highest-speed row in this column where T >= T_dmd (if any). - rows_torque = find(Tmat(:,col) >= T_dmd); - if isempty(rows_torque) - % this column cannot meet torque: move left - notes{end+1} = sprintf('Column %d (I=%.3g A) cannot meet torque; move left.', col, currents(col)); - col = col - 1; - continue; - end - % highest-speed row meeting torque in this column: - candidate_row = rows_torque(end); - - % Starting from candidate_row and moving toward lower speeds (smaller row index), - % find first row where V <= V_dc - vcol = Vmat(:,col); - le_rows = find(vcol(1:candidate_row) <= V_dc + epsV); % only consider rows up to candidate_row - if ~isempty(le_rows) - % choose the largest row index in le_rows (closest to candidate_row) - selected_row = le_rows(end); - V_op = vcol(selected_row); - S_op = speeds(selected_row); - I_op_idx = col; - T_emg_op = Tmat(selected_row, col); - final_found = true; - notes{end+1} = sprintf('Voltage constraint satisfied at column %d (I=%.3g A), row %d (speed %.1f rpm): V_op=%.3g <= V_dc=%.3g.', ... - col, currents(col), selected_row, S_op, V_op, V_dc); - break; - else - % This column cannot satisfy voltage at any torque-capable speed -> move left - notes{end+1} = sprintf('Column %d (I=%.3g A) had no rows <= V_dc up to its torque-capable max speed; move left.', ... - col, currents(col)); - col = col - 1; - end -end - -if ~final_found - % Could not find any (col,row) that satisfies both T and V - V_op = NaN; - S_op = NaN; - notes{end+1} = 'Searched leftwards but did not find any operating point that satisfies both torque and voltage constraints.'; -end - -res.V_op = V_op; -res.S_op = S_op; - -%% 4) Extract remaining outputs if an operable point exists -if ~isnan(V_op) && ~isnan(S_op) - - % Extract additional maps - if isfield(dat, 'Shaft_Torque') - res.T_Shaft = dat.Shaft_Torque(selected_row, I_op_idx); - else - res.T_Shaft = NaN; - notes{end+1} = 'Shaft_Torque map missing from data file.'; - end - - if isfield(dat, 'Stator_Current_Phase_RMS') - res.I_phase = dat.Stator_Current_Phase_RMS(selected_row, I_op_idx); - else - res.I_phase = NaN; - notes{end+1} = 'Stator_Current_Phase_RMS map missing from data file.'; - end - - if isfield(dat, 'Mechanical_Loss') - res.Mech_loss = dat.Mechanical_Loss(selected_row, I_op_idx); - else - res.Mech_loss = NaN; - notes{end+1} = 'Mechanical_Loss map missing from data file.'; - end +%% 4) Populate outputs from chosen final indices +res.I_op_idx = final_col; +res.I_op = final_I; +res.T_emg_op = final_T; +res.V_op = final_V; +res.S_op = final_S; - if isfield(dat, 'Power_Factor') - res.Pf = dat.Power_Factor(selected_row, I_op_idx); - else - res.Pf = NaN; - notes{end+1} = 'Power_Factor map missing from data file.'; - end +% Extract additional outputs from guaranteed maps +% Use final_row, final_col indexing +res.T_Shaft = dat.Shaft_Torque(final_row, final_col); +res.I_phase = dat.Stator_Current_Phase_RMS(final_row, final_col); +res.Mech_loss = dat.Mechanical_Loss(final_row, final_col); +res.Pf = dat.Power_Factor(final_row, final_col); -else - % No operable point → everything NaN - res.T_emg = NaN; - res.T_Shaft = NaN; - res.I_phase = NaN; - res.Mech_loss= NaN; - res.Pf = NaN; - notes{end+1} = 'No valid operating point. Additional outputs set to NaN.'; -end +notes{end+1} = sprintf('Final outputs extracted from maps at row %d, col %d.', final_row, final_col); +%% 5) Finalize notes & return +res.currents = currents; +res.speeds = speeds; res.notes = notes; end From 68f738051425081c7ce3e11abe477695b3688c3f Mon Sep 17 00:00:00 2001 From: mooocella <113719459+mooocella@users.noreply.github.com> Date: Mon, 24 Nov 2025 00:16:12 -0800 Subject: [PATCH 3/4] Rename motor_map_model.asv to OUTDATED_motor_map_model.asv --- motor_map_model.asv => OUTDATED_motor_map_model.asv | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename motor_map_model.asv => OUTDATED_motor_map_model.asv (100%) diff --git a/motor_map_model.asv b/OUTDATED_motor_map_model.asv similarity index 100% rename from motor_map_model.asv rename to OUTDATED_motor_map_model.asv From 8c9fc06bd2b42f27bf9c5f33333c08270695fab9 Mon Sep 17 00:00:00 2001 From: mooocella <113719459+mooocella@users.noreply.github.com> Date: Mon, 24 Nov 2025 00:16:46 -0800 Subject: [PATCH 4/4] Rename motor_map_model_function.m to OUTDATEDmotor_map_model_function.m --- motor_map_model_function.m => OUTDATEDmotor_map_model_function.m | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename motor_map_model_function.m => OUTDATEDmotor_map_model_function.m (100%) diff --git a/motor_map_model_function.m b/OUTDATEDmotor_map_model_function.m similarity index 100% rename from motor_map_model_function.m rename to OUTDATEDmotor_map_model_function.m