前言
Byd这个难度还是稍微有点大的,我还是先用中文写吧,不然理不清楚思路了。
有无数的铁道宅都喜欢录制各个厂家的VVVF声音,还有猛男在这个过程中摸索出了各自的调制策略,这也为本文提供了巨大的帮助。
关于VVVF最常见的就是在电车上面了,但是其实在电梯上面,VVVF也有很广泛的利用,这里提一嘴不是因为是电梯迷啥的,而是很多神秘的知识是从一个电梯的网站上面看到的。
扯了这么多,本文默认读者已经基本读完了大物的内容。
了解你的捍卫者:交流异步电机
在我们开始大扯特扯电机学之前,先聊一个很出名的问题:
无刷直流电机和交流异步电机有何区别?
在测试VVVF中,按照常理你是需要一个交流异步电机的,这玩意通常能够在闲鱼收到一些,但是有一个问题:相电阻很大。
这就意味你需要很高的电压来驱动它,与此同时也就意味你的驱动最好整好点。
所以我们可以找到一个其实比较合适的平替:无刷直流电机。
讲道理这两者的差距在结构上并不是很小,主要体现在无刷直流电机使用了永磁体,而交流异步电机的外部磁场直接来自于定子的通电线圈,而中心的鼠笼则是不带电的。
但是其实这两者的控制方法又很有些共通的地方,所以先用无刷电机做早期的一些测试是完全可行的。
无刷电机(BLDC)的结构与控制思路
所谓无刷电机那么必然是相对于“有刷电机”而言的,在高中我们就学过的使用电刷作为换向器的有刷电机控制方法相当的简单,但是有一些很大的问题,首当其冲的就是寿命问题,由于电刷本身的摩擦结构,注定了其寿命必将受到电刷的寿命影响,以及其在高转速下发生的打火花等等,在更多的应用中我们一般使用无刷电机。
先来复习一下无刷电机的结构
转子由一块永磁体组成,而定子则是绕组线圈,由绕组线圈流经电流的不同来产生对应的磁场,磁场再进行合成来驱动中间的转子转动,其等效为这样。
我们假设电流和磁场强度直接线性相关,电流流进就必然流出,三项120°交错就必然能产生一个矢量合成的磁场,最简单的控制方法就是直接用相位分别相差1/3个周期的方波:
但是方波必然导致其磁场变化不是连续的,这样电机的运行噪音和发热量都会很大,并且还会有必然的抖动,所以就需要使用正弦波来调制
理想状况下正弦波就应该是最佳的控制方法,但是不对。
同步/异步控制问题
在上面我们知道了电机中间存在两个磁场的矢量:即转子磁极的磁场方向Vc与周围定子合成之磁场方向Vd。
这就有了下一个问题,在理想的情况下,我们假设转子没有任何的质量,即当我们尝试对Vd进行调制时,所产生的磁场作用在转子上是瞬时响应的,换句话说,Vd 与Vc 方向将是一样的,这种理想的情况其实就是我们同步控制所奢求的。
之所以说是奢求,那么说明这必然是达不到的。
首先我们先说说异步控制
异步控制策略
转子的方向要确定有很多办法,诸如使用编码器,霍尔传感之类的东西,但是我们在这之前有一个很取巧的办法,不管转子的方向。
什么意思呢,我们给的旋转的磁场并不要求转子能够跟上,我们也不在乎其是否真的能跟上,速度的控制与实际速度没有强的关联性。
这在VVVF调制中即是第一步,电机起转的异步控制策略。
通常来说我们会产生一个一个的SIN波形,但是对单片机稍有了解的人都知道,单片机的DAC输出通常是PWM信号,即脉宽调制,怎么才能让这个方波一样的东西变成正弦波呢。
我们需要用到SPWM调制方法,当然,这里不展开说,一会单独开一章讲一下,就先想象成有一个能输入频率和幅值自动生成正弦波的单片机就好。
那么我们就依次命令单片机的三路DAC生成相位相差2/3pi大小的正弦波就好了。
依次从频率低到高输出给你的电机,你就会听到一个熟悉的,在每一个地铁站你都能听到的,列车启动的声音:
恭喜,到这一步就完成了第一步调制策略了,即异步调制。
接下来在解释同步控制策略之前,先来关注我们的核心:单片机
如何产生并不完美的正弦波
如果你对电机了解一些,你会知道另一个电机控制方法:FOC(磁场矢量控制法)
我们这里并不具体探究FOC的控制策略,直接说结论就是FOC相比较于VVVF的控制在噪音,发热控制,精确度都是降维打击的。
如果你对FOC感兴趣,可以去SimpleFoc的官网瞅瞅:SimpleFOCproject | Home
FOC自上世纪七十年代提出以来,为什么这么多的地方仍然还在使用VVVF控制方法呢,答案很简单,因为VVVF控制相比较于FOC简单太多了,这里的简单不是单单指你代码的简单,而是对于单片机其控制电机高速运行是消耗的算力来说,VVVF对于单片机是及其友好的。
为什么会发生这样的事情,我们来聊一下FOC与VVVF都会用到的SPMW
在单片机内我们通过具体寄存器的改变来实现引脚电平的改变,而这个电平通常来说都是离散的高与低,正弦波明显看起来并不是一个很好的高或者低就能描述的波形,所以我们要用PWM信号来伪造一个正弦波。
先来看一个最符合直觉的调制方法。
第一步,我们先规定采样率,即每秒钟对正弦波进行多少次的采样来确定PWM的占空比,然后是PWM的频率以及我们需要的正弦波的频率与幅值,接下来我们就可以进行更多的操作了。
下一步就是了解单片机的定时器中断。
单片机通常并不会给很方便的多线程,除非你用CentOS之类的东西,否则就得乖乖用中断,CentOS的内容远远超出本文的写作范围,这就不探讨了。
我们把中断频率和采样率直接混为一谈,因为这样写起来比较方便(确信
单片机通过定时器与分频器来实现每隔一段固定的时间调用一次回调函数,而这个过程正是我们更改pwm占空比或者说i/o输出的好时机,所以接下来就是探索这个函数需要做什么了。
ps:有一个问题就是其实pwm也是用自己的定时器实现的,所以你的pwm频率应该比主定时器中断频率高个好几倍为好。
自然采样法
第一种产生波形的方法叫做自然采样法,什么意思呢,请看:
在我们生成sin波形的同时以更高的频率生成一个三角波,但是正负周期与正弦波取反。
(顺带一提这个三角波频率除以正弦波频率就是一会会用到的”n分频”中的分频系数:n)
你可以用以下代码实现生成这个图像:
import matplotlib.pyplot as plt
import numpy as np
def made(freq, rate):
# 我们假设载波波频率为正常sin波频率的八倍
angel_list =[]
det_list = []
# 首先生成载波
single_rate = int(rate/freq)
quart = int(freq/4)
i = 0
# 假设幅值是16吧,我瞎写的
angle_step = 1/single_rate
# 规避一些神秘的除不尽问题
rate_list = np.linspace(0, 2*np.pi,num=rate)
sin_list = np.sin(rate_list)
for j in range(quart):
for k in range(single_rate):
angel_list.append(i)
i += angle_step
for k in range(single_rate):
angel_list.append(i)
i -= angle_step
for j in range(quart):
for k in range(single_rate):
angel_list.append(-i)
i += angle_step
for k in range(single_rate):
angel_list.append(-i)
i -= angle_step
# 生成调制结果
i = 0
# print(rate)
for i in range(int(rate/2)):
if sin_list[i]> angel_list[i]:
det_list.append(1)
else:
det_list.append(0)
for i in range(int(rate/2)):
if sin_list[i]> angel_list[i]:
det_list.append(-1)
else:
det_list.append(0)
return rate_list, sin_list, angel_list, det_list
if __name__ == "__main__":
plt.figure()
for i in range (8,56,8):
print(i)
rate_list, sin_list, angel_list, det_list = made(i,24000)
# 创建一个图形对象
# 绘制折线图
plt.plot(rate_list, sin_list,linewidth=0.5)
plt.plot(rate_list, angel_list, linewidth=0.5)
plt.plot(rate_list, det_list, linewidth=0.8)
plt.fill_between(rate_list, det_list, color='red', alpha=0.5, label='Positive Area')
plt.xlabel('X-axis')
plt.ylabel('Y-axis')
# 显示图形
plt.show()
我们在三角波与正弦波交点处对i/o取反,这样即实现了一个很完美的调制,但是有一个很暴力的问题,在模拟电路中要比较两个电平的大小是很简单的,但是在单片机这样的数字电路中,你要获得这两者的交点并且按时给它改变io状态就显得比较麻烦而且占用太多中断了。
所以还有下一个采样方法
规则采样法
在每一次等腰三角形的尖端处对两侧的时间进行记录,用这个需要拉高的时间使用pwm的占空比进行调制,注意到和上面的单片机中断方法中的定时中断刚刚好非常完美实现,故我们就用这个方法来写了。
在Pico上实现
先写一下开环控制部分:
import machine
import math
import time
machine.freq(250000000)
class OpenRun:
def __init__(self):
# 提前计算正弦波的所需采样
self.sinelist = []
# 正弦波的采样次数
self.sampling = 100
self.spare_sampling = int(self.sampling / 3)
self.pins = [2, 3, 6] # 分别定义不同的引脚位置
self.pwms = None
self.time_now = 0
self.step = 1 # 每次在正弦波列表里面步进的长度,使用这个可以改变其频率
self.interrupt_freq = 1000 # 定时器中断频率
self.count = None #编码器计数
def make_sine(self):
step = (2 * math.pi) / self.sampling # 计算每个采样点的角度步长
for i in range(self.sampling + 1):
angle = step * i
u_num = int((math.sin(angle) * 65535) / 2 + 32768) # Pico DAC最大精度,按照这个保存占空比表
self.sinelist.append(u_num)
def hardware_init(self):
pwm_pins = [machine.Pin(pin) for pin in self.pins]
pwms = [machine.PWM(pin) for pin in pwm_pins]
for pwm in pwms:
pwm.freq(20000) # 设置PWM频率为20kHz
self.pwms = pwms
# 初始化计时器中断函数与回调函数
timer = machine.Timer()
timer.init(freq=self.interrupt_freq, mode=machine.Timer.PERIODIC, callback=self.timer_interrupt)
# 注册引脚中断,使用上升沿中断计数来测量
encoder_pin = machine.Pin(5, machine.Pin.IN, machine.Pin.PULL_UP) # 中断引脚
encoder_pin.irq(trigger=machine.Pin.IRQ_RISING, handler=self.encoder_interrupt) # 注册中断处理函数
def timer_interrupt(self, _): # 增加一个参数以符合回调函数的要求
self.time_now = (self.time_now + self.step) % self.sampling
time_u = (self.time_now + self.spare_sampling) % self.sampling
time_v = (time_u + self.spare_sampling) % self.sampling
u_rate = self.sinelist[self.time_now]
v_rate = self.sinelist[time_u]
w_rate = self.sinelist[time_v]
self.pwms[0].duty_u16(u_rate)
self.pwms[1].duty_u16(v_rate)
self.pwms[2].duty_u16(w_rate)
# 编码器中断函数
def encoder_interrupt(self,_):
self.count += 1
pass
def change_freq(self, stp):
self.step = stp
def low_7_inround(self):
freq_first_step = 1000 # 第一次同步中断频率
# 示例使用
run = OpenRun()
run.make_sine()
run.hardware_init()
while True:
run.change_freq(1)
time.sleep(0.5)
run.change_freq(2)
time.sleep(0.5)
run.change_freq(3)
time.sleep(0.5)
run.change_freq(4)
time.sleep(0.5)
run.change_freq(5)
time.sleep(0.5)
run.change_freq(6)
time.sleep(0.5)
run.change_freq(5)
time.sleep(0.5)
run.change_freq(4)
time.sleep(0.5)
run.change_freq(3)
time.sleep(0.5)
run.change_freq(2)
time.sleep(0.5)
所以我的电机和驱动什么时候还给我啊
不还(