SVPWM 空间矢量脉宽调制

image


import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import FancyArrowPatch, Polygon, Circle
from matplotlib.animation import FuncAnimation

# ===================== 解决中文显示问题 =====================
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'WenQuanYi Micro Hei']
plt.rcParams['axes.unicode_minus'] = False

# ===================== SVPWM参数(合成矢量=外接圆半径)=====================
# 6个基本电压矢量的标准角度(弧度)
V_ANGLES = [0, np.pi/3, 2*np.pi/3, np.pi, 4*np.pi/3, 5*np.pi/3]
V_LABELS = ['V4(1,0)', 'V6(0,1)', 'V2(-1,1)', 'V3(-1,0)', 'V1(0,-1)', 'V5(1,-1)']

# 六边形外接圆半径 = 基本矢量最大长度 = 合成矢量固定长度
V_BASE_LEN = 1.0
V_REF_FIXED_LEN = V_BASE_LEN  # 合成矢量等于外接圆半径
# 每个扇区60°
SECTOR_STEP = np.pi/3

# ===================== 画布设置(左右双图)=====================
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, 8))

# 左侧矢量图设置
ax1.set_aspect('equal')
ax1.set_xlim(-1.5, 1.5)
ax1.set_ylim(-1.5, 1.5)
ax1.set_title('SVPWM 空间矢量调制原理', fontsize=16, pad=20)
ax1.set_xlabel('α 轴 (g)', fontsize=14)
ax1.set_ylabel('β 轴 (h)', fontsize=14)

# 右侧三相正弦波图设置
ax2.set_xlim(0, 2*np.pi)
ax2.set_ylim(-1.2, 1.2)
ax2.set_title('输出三相正弦电压波形', fontsize=16, pad=20)
ax2.set_xlabel('电角度 (rad)', fontsize=14)
ax2.set_ylabel('电压幅值', fontsize=14)
ax2.grid(True, alpha=0.3)

# ===================== 左侧绘制几何图形 =====================
# 1. 蓝色虚线外接圆
outer_circle = Circle((0, 0), V_BASE_LEN, color='#2196F3', fill=False, 
                     linestyle='--', lw=2, alpha=0.8)
ax1.add_patch(outer_circle)

# 2. 黑色实线六边形边界
hex_vertices = [[V_BASE_LEN*np.cos(ang), V_BASE_LEN*np.sin(ang)] for ang in V_ANGLES]
hexagon = Polygon(hex_vertices, edgecolor='black', facecolor='none', lw=2.5)
ax1.add_patch(hexagon)

# 3. 灰色扇区分割虚线
for ang in V_ANGLES:
    ax1.plot([0, 1.4*np.cos(ang)], [0, 1.4*np.sin(ang)], 'k--', lw=0.8, alpha=0.6)

# 标注基本矢量
for i in range(6):
    x = 1.25 * np.cos(V_ANGLES[i])
    y = 1.25 * np.sin(V_ANGLES[i])
    ax1.text(x, y, V_LABELS[i], fontsize=12, ha='center', va='center',
            bbox=dict(facecolor='white', alpha=0.8, edgecolor='none', pad=2))

# 标注扇区编号(I~VI)
sector_labels = ['I', 'II', 'III', 'IV', 'V', 'VI']
for i in range(6):
    ang = i * SECTOR_STEP + SECTOR_STEP/2
    x = 0.8 * np.cos(ang)
    y = 0.8 * np.sin(ang)
    ax1.text(x, y, sector_labels[i], fontsize=20, ha='center', va='center',
             bbox=dict(facecolor='white', alpha=0.7, edgecolor='none', pad=4))

# ===================== 左侧初始化矢量 =====================
# 6个基本电压矢量(初始隐藏,最大长度严格限制为V_BASE_LEN)
basic_arrows = []
colors = ['#d62728', '#9467bd', '#1f77b4', '#ff7f0e', '#2ca02c', '#8c564b']
for i in range(6):
    arr = FancyArrowPatch((0, 0), (0, 0), color=colors[i],
                         mutation_scale=14, arrowstyle='->', lw=3)
    arr.set_visible(False)
    basic_arrows.append(arr)
    ax1.add_patch(arr)

# 合成参考矢量Vref(红色粗箭头,长度=外接圆半径,固定不变)
ref_arrow = FancyArrowPatch((0, 0), (0, 0), color='green',
                           mutation_scale=20, arrowstyle='->', lw=4)
ax1.add_patch(ref_arrow)

# 扇区高亮
sector_highlight = Polygon([[0,0], [0,0], [0,0]],
                          facecolor='yellow', alpha=0.25, edgecolor='none')
ax1.add_patch(sector_highlight)

# ===================== 右侧初始化三相正弦波 =====================
x_data = np.linspace(0, 2*np.pi, 1000)
# A相(红色)、B相(绿色)、C相(蓝色),相位互差120°
line_a, = ax2.plot(x_data, np.zeros_like(x_data), 'r-', lw=2.5, label='A相')
line_b, = ax2.plot(x_data, np.zeros_like(x_data), 'g-', lw=2.5, label='B相')
line_c, = ax2.plot(x_data, np.zeros_like(x_data), 'b-', lw=2.5, label='C相')
ax2.legend(loc='upper right', fontsize=12)

# ===================== 动画更新函数 =====================
angle = 0.0

def update(frame):
    global angle
    angle += 0.03
    if angle > 2*np.pi:
        angle = 0
    
    # ========== 左侧矢量图更新 ==========
    # 1. 合成参考矢量(长度=外接圆半径,沿着蓝色虚线圆匀速旋转)
    v_ref_x = V_REF_FIXED_LEN * np.cos(angle)
    v_ref_y = V_REF_FIXED_LEN * np.sin(angle)
    ref_arrow.set_positions((0, 0), (v_ref_x, v_ref_y))
    
    # 2. 判断当前扇区
    sector = int(np.floor(angle / SECTOR_STEP)) % 6
    
    # 3. 扇区内角度θ(0~60°)
    theta = angle - sector * SECTOR_STEP
    
    # 4. 计算原始占空比
    T1 = np.sqrt(3) * np.sin(np.pi/3 - theta)
    T2 = np.sqrt(3) * np.sin(theta)
    
    # 5. 占空比归一化,保证T1+T2=1,基本矢量绝不超出六边形顶点
    total = T1 + T2
    if total > 1.0:
        T1 = T1 / total
        T2 = T2 / total
    
    # 6. 扇区-矢量对应关系(完全匹配原理图)
    sector_vectors = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 0)]
    v1_idx, v2_idx = sector_vectors[sector]
    
    # 7. 隐藏所有基本矢量
    for arr in basic_arrows:
        arr.set_visible(False)
    
    # 8. 只显示当前扇区的两个基本矢量(方向固定,长度≤V_BASE_LEN)
    # 矢量1(最大长度=六边形顶点)
    v1_ang = V_ANGLES[v1_idx]
    v1_x = T1 * V_BASE_LEN * np.cos(v1_ang)
    v1_y = T1 * V_BASE_LEN * np.sin(v1_ang)
    basic_arrows[v1_idx].set_positions((0, 0), (v1_x, v1_y))
    basic_arrows[v1_idx].set_visible(True)
    
    # 矢量2(最大长度=六边形顶点)
    v2_ang = V_ANGLES[v2_idx]
    v2_x = T2 * V_BASE_LEN * np.cos(v2_ang)
    v2_y = T2 * V_BASE_LEN * np.sin(v2_ang)
    basic_arrows[v2_idx].set_positions((0, 0), (v2_x, v2_y))
    basic_arrows[v2_idx].set_visible(True)
    
    # 9. 更新扇区高亮
    sector_ang1 = sector * SECTOR_STEP
    sector_ang2 = (sector + 1) * SECTOR_STEP
    highlight_verts = [
        [0, 0],
        [V_BASE_LEN*np.cos(sector_ang1), V_BASE_LEN*np.sin(sector_ang1)],
        [V_BASE_LEN*np.cos(sector_ang2), V_BASE_LEN*np.sin(sector_ang2)]
    ]
    sector_highlight.set_xy(highlight_verts)
    
    # ========== 右侧三相正弦波更新 ==========
    # 三相电压相位互差120°,与合成矢量角度完全同步
    line_a.set_ydata(0.5*np.sin(x_data + angle))
    line_b.set_ydata(0.5*np.sin(x_data + angle - 2*np.pi/3))
    line_c.set_ydata(0.5*np.sin(x_data + angle + 2*np.pi/3))
    
    return (*basic_arrows, ref_arrow, sector_highlight, line_a, line_b, line_c)

# ===================== 生成动画 =====================
ani = FuncAnimation(fig, update, frames=np.arange(0, 210), interval=30, blit=True, repeat=True)
ani.save('svpwm_final_with_three_phase.gif', writer='pillow', fps=20, dpi=120)

plt.tight_layout()
plt.show()





posted @ 2026-06-22 22:52  redufa  阅读(15)  评论(0)    收藏  举报