记录驱动三相无刷电机的过程
·920 字·4 分钟阅读
嵌入式开发
原理
简单来说就是电生磁,磁场的方向可以根据 右手螺旋定则 又称 安培定则 来判断,然后根据磁铁的同斥异吸产生的力,就可以带动磁铁旋转起来,这篇文章不做详细解释,只记录自己写代码和思考的过程
三相无刷电机结构图
硬件购买
2804电机 + AS5600编码器 + FOC驱动板(MS8313芯片)
六步换相开环驱动
代码如下:
void test1(void)
{
static uint8_t step;
U_Enable;
V_Enable;
W_Enable;
switch (step)
{
case 0:
U_HIGH;
W_LOW;
V_Disable;
break;
case 1:
U_HIGH;
V_LOW;
W_Disable;
break;
case 2:
W_HIGH;
V_LOW;
U_Disable;
break;
case 3:
W_HIGH;
U_LOW;
V_Disable;
break;
case 4:
V_HIGH;
U_LOW;
W_Disable;
break;
case 5:
V_HIGH;
W_LOW;
U_Disable;
break;
default:
break;
}
if (++step >= 6)
step = 0;
}
调用
while (1)
{
if (sysCnt >= 10)
{
sysCnt = 0;
if (++stepCnt >= 500) // 500ms调用一次
{
stepCnt = 0;
test1();
}
}
}
很显然,电机是一步一步走的,非常卡顿,并且是开环控制,我并不知道转到哪里了,接下来是我一步一步的优化和思考过程
testMotorDriver函数的调用频率问题
现在电机是一步一步走的,是因为我是500ms调用的,这让我有了思考,如果我把调用频率加快,它是不是就可以快速转动起来了,于是我改成1ms调用:
while (1)
{
if (sysCnt >= 10)
{
sysCnt = 0;
if (++stepCnt >= 500) // 500ms调用一次
{
stepCnt = 0;
}
test1();
}
}
发现电机速度并没有加快,并且抖动特别厉害 这个可能是由于换相过快,电机根本追不上来,盲目换相,导致的电机剧烈抖动
接下来一步一步实现闭环控制
了解机械角度和电角度的概念
机械角度: 转轴实际转过的物理角度。转一整圈就是 360°,和极数无关。
电角度: 以绕组感应电势的一个完整周期为 360° 来度量的角度。转子每扫过一对 N-S 磁极,定子绕组里的电势就经历一次完整的正弦周期,这就是一个"电角度 360°"。
核心公式:
为什么需要两个角度?
机械角度用于描述转速、位置(如编码器读数、转子机械位置)。
电角度用于换相控制(六步换相、FOC 矢量控制)。驱动器需要知道当前电角度来决定哪一相该通电、电流矢量的方向等。
使用AS5600读取转子的位置
uint16_t AS5600_ReadRawAngle(void)
{
uint8_t high, low;
uint16_t raw_angle;
IIC_Start();
IIC_SendAddr(AS5600_ADDR << 1);
IIC_SendByte(AS5600_RAW_ANGLE_HIGH);
IIC_Start();
IIC_SendAddr((AS5600_ADDR << 1) | 0x01);
IIC_Ack();
high = IIC_ReadByte();
IIC_Nck();
low = IIC_ReadByte();
IIC_Stop();
raw_angle = (((uint16_t)high << 8) | low) & 0x0FFF;
return raw_angle;
}
uint16_t AS5600_ReadAngle(void)
{
return (uint16_t)((uint32_t)AS5600_ReadRawAngle() * 3600 / 4096);
}
根据转子位置驱动无刷电机
void test2(void)
{
uint16_t angle = AS5600_ReadAngle();
uint16_t e_angle = (uint16_t)((angle * POLE_PAIRS) % 3600);
uint8_t step = e_angle / 600;
U_Enable;
V_Enable;
W_Enable;
switch (step)
{
case 0:
U_HIGH;
W_LOW;
V_Disable;
break;
case 1:
U_HIGH;
V_LOW;
W_Disable;
break;
case 2:
W_HIGH;
V_LOW;
U_Disable;
break;
case 3:
W_HIGH;
U_LOW;
V_Disable;
break;
case 4:
V_HIGH;
U_LOW;
W_Disable;
break;
case 5:
V_HIGH;
W_LOW;
U_Disable;
break;
default:
break;
}
}
将test2函数1ms调用一次,发现速度已经可以提上来了,但是噪音很大,
于是我上网查找噪音大的原因:
- 转矩脉动大:六步换相将360°电角度分为6个60°扇区,每步仅两相通电、第三相悬空,输出方波/梯形波电流。换相瞬间一相突断、一相突通,电感导致电流突变形成转矩冲击,每转产生6次脉冲,典型转矩脉动约13%。
- 磁场方向跳跃:定子磁场非连续旋转,而是每60°“跳”一次。大部分时间磁场与转子磁场不垂直,产生无用径向分力拉扯定子铁心引发振动。
- 谐波含量高:方波电流谐波丰富,直接转化为电磁噪声和机械振动。
磁场定向控制
原理解析
就是生成一个旋转的磁场带动转子转动,U、V、W三相物理位置上面可以等效为相差120°,我用动画演示一下:
制作正弦表
用到的数学公式:
代码如下:
// 正弦表生成
void sin_generate(int16_t *sin_table, uint16_t table_size)
{
/* 步长 d = 2π/256: cos(d)≈0.999699, sin(d)≈0.024531 */
// 注意:如果单片机空间不够用,这里最好是减少步长的精度,因为这里会生成一个大数组
// 由于浮点单片机不好算,这里转换成整型去算
const int16_t cosd = 32758; /* 0.999699 * 32768 */
const int16_t sind = 803; /* 0.024531 * 32768 */
// 初始时的x为0°
int16_t sinx = 0;
int16_t cosx = 32767;
int32_t s_next, c_next;
for (uint16_t i = 0; i < table_size; i++)
{
sin_table[i] = sinx;
s_next = (int32_t)(sinx * cosd) + (int32_t)(cosx * sind);
c_next = (int32_t)(cosx * cosd) - (int32_t)(sinx * sind);
sinx = (int16_t)(s_next >> 15);
cosx = (int16_t)(c_next >> 15);
}
/* 强制修正关键点, 消除256步累计误差: 0°/90°/180°/270° */
sin_table[0] = 0;
sin_table[64] = 32767;
sin_table[128] = 0;
sin_table[192] = -32767;
}
控制代码
// SPWM参数
int16_t sin_table[SIN_TABLE_SIZE];
uint16_t speed_duty = 0; // 软起动: 从0爬升到SPEED_DUTY
uint16_t sin_to_pwm(int16_t sin, uint16_t amplitude)
{
/* sin∈[-32767,32767] -> duty = PWM_PERIOD/2 ± amplitude, 中心对齐SPWM */
int32_t duty = (PWM_PERIOD / 2) + (((int32_t)sin * amplitude) / 32768);
if (duty < 0) duty = 0;
if (duty >= PWM_PERIOD) duty = PWM_PERIOD - 1;
return (uint16_t)duty;
}
void test_vector(void)
{
uint16_t angle = AS5600_ReadAngle();
uint16_t e_angle = (uint16_t)((angle * POLE_PAIRS) % PWM_PERIOD);
uint8_t idx = (uint8_t)((uint32_t)e_angle * SIN_TABLE_SIZE / PWM_PERIOD);
// 设置idx和offset都是0,然后读取此时的电角度(uint8_t)((uint32_t)e_angle * SIN_TABLE_SIZE / PWM_PERIOD)这个值是145,使用256-145+64得到offset
uint16_t offset = 175;
U_Enable;
V_Enable;
W_Enable;
TIM2->CCR1 = sin_to_pwm(sin_table[(uint8_t)(idx + IDX_OFF_U + offset) % SIN_TABLE_SIZE], speed_duty);
TIM2->CCR2 = sin_to_pwm(sin_table[(uint8_t)(idx + IDX_OFF_V + offset) % SIN_TABLE_SIZE], speed_duty);
TIM2->CCR3 = sin_to_pwm(sin_table[(uint8_t)(idx + IDX_OFF_W + offset) % SIN_TABLE_SIZE], speed_duty);
}
这样做后,发现还是有噪音,应该还有哪里不对,我先去网上找找成熟的方案学习一下