非常抱歉,前段时间出差没时间评测该低压电机驱动板,今儿来分享一下使用APM32F035低压电机通用评估板驱动无感FOC驱动电机,APM32F035是一款基于Arm Cortex-M0+内核,集成 FOC算法中常用的数学运算加速器Cordic Svpwm,硬件除法器等),并且集成了运放、比较器等模拟外设,以及CAN控制器的高性能电机控制专用MCU。
板子的整体硬件系统设计框图:
整体硬件系统是采用外部24V电源供电并经过相应的电源降压电路转换后输出稳定的12V、5V、 3.3V电压其中12V电压输出给到Gate driver IC、3.3V电压输出给到APM32F035系列微处理器,而功率开关管则直接使用24V电源。同时该方案采用可变电阻旋钮调节0~3.3V的电压输入作为速度命令的输入端以此调节电机转速。用户实际使用可直接通过转动可变电阻旋钮以此调节输入电压,同时当输入电压值超过起动阈值时电机将会启动运行而当电压值低于阈值时电机将会关闭运行。
当电机启动后APM32F035处理器通过内置的运算放大器并经由相应的采样电路可获取三相的相电流Iu、Iv与Iw并将该数据经过坐标轴的转换后进而控制电机的力矩电流大小及相位;通过FOC控制计算环节后调节TMR1外设输出相应的三路互补的PWM波控制逆变器的开关元器件。
笔者在淘宝上买了一个尼得科无刷直流电机,带霍尔接口,该电机详情如下:
硬件资源准备好了,下面搭建软件开发环境,首先我们从极海半导体官方网站上下载基于APM32F035的SDK包,如下附件“APM32F035_SDK_V1.0”,将压缩文件解压,去到“APM32F035_SDK_V1.0\Package”目录下,安装基于Keil的pack支持包,这样才能在Keil编译工具上兼容APM32F035的编译。
极海半导体官方有提供参考设计例程,这里笔者借用了“APM32F035低压电机评估板demo例程”文件中的“APM32F035_Sensorless_FOC_Double_Shunt”例程,该例程通过对了UV两通道进行ADC电流采样,对旋钮输入信号做限幅处理,实现旋钮控制电机的正常启动与停止。板子上有预留USB转串口以及故障指示灯,以便开发者调试使用;两处按键负责控制电机运行方向及锁车功能。
部分参考代码展示如下:
#include "hardware.h"
#include "dev_include.h"
#include "m0cp.h"
#include "main.h"
void Board_Init(void)
{
/*Condiv M0CP Enable*/
MC_SystemClockInit();
M0CP_Firmware_Init();
M0CP_Init();
/* Condiv the system clock */
MC_SystemClockInit();
/* Initialize all GPIO */
IO_Init();
OPA_Init();
COMP_Init();
Drv_Pwm_Init(Motor_type.USER.u16PWMPeriod,Motor_type.USER.u16DeadTime);
PWM_CompareConfig(Motor_type.USER.u16PWMPeriod >> 1,Motor_type.USER.u16PWMPeriod >> 1,Motor_type.USER.u16PWMPeriod >> 1);
Drv_Adc_Init();
}
#include "apm32f035_adc.h"
#include "apm32f035_tmr.h"
#include "apm32f035_int.h"
#include "apm32f035_gpio.h"
#include "systick.h"
#include "main.h"
#include "FOC_Ctrl.h"
#include "StateMachine.h"
#include "drv_adc.h"
#define TIM_FLAG_Update ((uint16_t)0x0001)
#define TIM_FLAG_Break ((uint16_t)0x0080)
#define TIM_IT_Break ((uint16_t)0x0080)
u16 Flag_Tim1 = 0;
u32 u32_SysTick_Value = 0;
u32 u32_SysTick_Value_1 = 0;
uint32_t u32IbSum = 0;
uint32_t u32IaSum = 0;
int16_t Look_PLL_S16Q15_Theta;
int16_t Look_PLL_S16Q15_Speed;
void ADC_COMP_IRQHandler(void)
{
ADC->STS = ADC->STS;//清中断标志位
static uint8_t u8ADCTimeCnt = 0;
static uint16_t u16Cnt = 0;
if(u16Cnt <= 131)
{
u16Cnt++;
u32IbSum += (int16_t)ADC_GetValue(CURR_CHANNEL_V);
u32IaSum += (int16_t)ADC_GetValue(CURR_CHANNEL_U);
}
else if(u16Cnt == 132)
{
u16Cnt++;
u32IbSum = u32IbSum>>7;
u32IaSum = u32IaSum>>7;
Motor_type.FOC.sIabc_offset.s16q15_V = u32IbSum << 3;
Motor_type.FOC.sIabc_offset.s16q15_U = u32IaSum << 3;
u32IbSum = 0;
u32IaSum = 0;
}
else
{
Get_ADC_Result(&Motor_type);
/* Fast Loop Statemachine */
s_STATE_FAST[eM1_MainState]();
if(++u8ADCTimeCnt >= Motor_type.USER.u16SlowLoopDiv) //For slow loop state machine
{
u8ADCTimeCnt = 10;
Motor_type.USER.bSlowLoopFlag = 1;
}
}
}
void SysTick_Handler(void)
{
SysTick->CTRL;
Inc_Systicks();
}
#include "FOC_Ctrl.h"
#include "parameter.h"
#include "drv_adc.h"
#include "CoordTrans.h"
#include "apm32f035_m0cp_bak.h"
/**
* @brief Correct PWM duty cycle outputcalculation, based on the measured DC-bus voltage.
* @param sVAlBeIn s16Vbus
* @retval sVAlBe_Out
*/
void DcBus_Ripple_suppress(ab_para_t *sVAlBeIn, Q15_t s16Vbus, ab_para_t *sVAlBe_Out)
{
int32_t temp32;
temp32 = ((int32_t)(sVAlBeIn->s16q15_A)) * ((int32_t)(Q15(0.866)));
temp32 = M0CP_i32Div32(temp32,s16Vbus) << 1;//0.866*2 = sqrt3
temp32 = (temp32 > INT16_MAX) ? INT16_MAX : temp32; /* high saturation */
temp32 = (temp32 < INT16_MIN) ? INT16_MIN : temp32; /* low saturation */
sVAlBe_Out->s16q15_A = (int16_t)(temp32);
temp32 = ((int32_t)(sVAlBeIn->s16q15_B)) * ((int32_t)(Q15(0.866))); //0.866*2 = sqrt3
temp32 = M0CP_i32Div32(temp32, s16Vbus) << 1;
temp32 = (temp32 > INT16_MAX) ? INT16_MAX : temp32; /* high saturation */
temp32 = (temp32 < INT16_MIN) ? INT16_MIN : temp32; /* low saturation */
sVAlBe_Out->s16q15_B = (int16_t)(temp32);
}
/*
s16q15_Ramp_Target :实际旋钮输入的ADC的采样值的量化 0~3.3V:0~32767 限定范畴在0.5V~2.8V:4965~27803 控制输出为:0~19661
*/
int16_t s16q15_limit(s16 s16q15_Ramp_Target)
{
int16_t temp = 0;
if(s16q15_Ramp_Target > 0 && s16q15_Ramp_Target < 4965)
return 0;
else if(s16q15_Ramp_Target > 4965 && s16q15_Ramp_Target <27803)
{
temp = (s16q15_Ramp_Target - 4965) * 28209 >> 15; //* 0.86088
return temp;
}
else if(s16q15_Ramp_Target > 27803)
return 19661;
return 0;
}
/**
* @brief Get the ADC result
* @param Motor
* @retval None
*/
void Get_ADC_Result(Motor_TypeDef *Motor)
{
int16_t s16Ib_Temp, s16Ia_Temp;
/*get the two phase current */
s16Ib_Temp = (int16_t)ADC_GetValue(CURR_CHANNEL_V) << 3; // Read ADC register for phase B Sensing current adc is 12 bit (<< 3) for Q15 format
s16Ia_Temp = (int16_t)ADC_GetValue(CURR_CHANNEL_U) << 3; // Read ADC register for phase C Sensing current adc is 12 bit (<< 3) for Q15 format
Motor->FOC.sIabc.s16q15_V = (-s16Ib_Temp + Motor->FOC.sIabc_offset.s16q15_V)<<1; // (<< 1) VDD/2 ~ Imax
Motor->FOC.sIabc.s16q15_U = (-s16Ia_Temp + Motor->FOC.sIabc_offset.s16q15_U)<<1; // (<< 1) VDD/2 ~ Imax
Motor->FOC.sIabc.s16q15_W = -Motor->FOC.sIabc.s16q15_V - Motor->FOC.sIabc.s16q15_U; // IA= -IB - IC
Motor->FOC.s16Vbus = (int16_t)ADC_GetValue(VDC_CHANNEL) << 3; // adc is 12 bit (<< 3) for Q15 format
Motor->USER.s16VspCmd = (int16_t)ADC_GetValue(Handle_CHANNEL) << 3; // adc is 12 bit (<< 3) for Q15 format
Motor->USER.s16VspCmd = s16q15_limit(Motor->USER.s16VspCmd); //将输入的旋钮做限幅
}
/**
* @brief Sensorless SMO observer and calls
* PLL with the purpose of computing a new speed estimation and
* updating the estimated electrical angle
* @param Motor
* @retval None
*/
void PMSM_Obs(Motor_TypeDef *Motor)
{
/* SMO observer in AB system */
Motor->FOC.SMO.s16Ialpha = Motor->FOC.sIAlBe.s16q15_A;
Motor->FOC.SMO.s16Ibeta = Motor->FOC.sIAlBe.s16q15_B;
Motor->FOC.SMO.s16Valpha = Motor->FOC.sVAlBe.s16q15_A;
Motor->FOC.SMO.s16Vbeta = Motor->FOC.sVAlBe.s16q15_B;
smo(&Motor->FOC.SMO);
/* Calculate the speed */
stc_PllPara.stc_PiPll.s16_Error = Mul_Q15(-Motor->FOC.SMO.s16Ealpha_est, Motor->FOC.sAngle_cmd.s16q15_Cos)
- Mul_Q15(Motor->FOC.SMO.s16Ebeta_est, Motor->FOC.sAngle_cmd.s16q15_Sin);
anti_pi_q10(&(stc_PllPara.stc_PiPll));
stc_PllPara.s16q15_ThetaPll += Mul_Q15((stc_PllPara.stc_PiPll.s32_Out), SPEED_TO_THETA);
Motor->FOC.s16CmdTheta = stc_PllPara.s16q15_ThetaPll;
Motor->FOC.s16CmdTheta_Update = Motor->FOC.s16CmdTheta + Mul_Q15(Motor->FOC.s16SpdFilt, SPEED_TO_THETA);
Motor->FOC.SMO.s16Speed = stc_PllPara.stc_PiPll.s32_Out;
}
/*********************************************************************************************************
** Func name: Math_SqrtUint32()
** Param:
**
** Return:
** Description: U32开方
*********************************************************************************************************/
uint32_t Math_SqrtUint32_1(uint32_t ulVal)
{
uint32_t ulOp, ulRes, ulOne;
ulOp = ulVal;
ulRes = 0;
ulOne = 1UL << (32 - 2); //BITS_PER_LONG 32
while (ulOne > ulOp)
ulOne >>= 2;
while (ulOne != 0)
{
if (ulOp >= ulRes + ulOne)
{
ulOp = ulOp - (ulRes + ulOne);
ulRes = ulRes + 2 * ulOne;
}
ulRes = ulRes >> 1;
ulOne = ulOne >> 2;
}
return ulRes;
}
/**
* @brief It executes the core of FOC drive that is the controllers for Iqd
* currents regulation. It must be called periodically
* when new motor currents have been converted
* @param Motor.
* @retval None
*/
void FOC_Current_Controller(Motor_TypeDef *Motor)
{
int32_t s32Temp;
int16_t s16Temp;
/* Calculate the sincos of s16CmdTheta */
Motor->FOC.sAngle_cmd = sincos(Motor->FOC.s16CmdTheta);
/* PARK transform */
park(&Motor->FOC.sIAlBe, &Motor->FOC.sAngle_cmd, &Motor->FOC.sIdq);
/* Calculate the PI saturation for Vd */
s16Temp = Mul_Q15(Motor->USER.s16DutyLimitCmd, Motor->FOC.s16VbusAvg);
Motor->FOC.s16VMAX = Mul_Q15(s16Temp, Q15(0.6667)); //Amplitude invariant transformation 振幅不变变换
Motor->stc_IdPi.s32_Umax = Motor->FOC.s16VMAX;
Motor->stc_IdPi.s32_Umin = -Motor->stc_IdPi.s32_Umax;
/* PI regulator for Vd*/
Motor->stc_IdPi.s16_Error = Motor->FOC.sIdq_cmd.s16q15_D - Motor->FOC.sIdq.s16q15_D;
anti_pi_q15(&Motor->stc_IdPi);
Motor->FOC.sVdq.s16q15_D = Motor->stc_IdPi.s32_Out;
/* Calculate the PI saturation for Vq */
s32Temp = Motor->FOC.s16VMAX * Motor->FOC.s16VMAX - Motor->FOC.sVdq.s16q15_D * Motor->FOC.sVdq.s16q15_D;
Motor->stc_IqPi.s32_Umax = Math_SqrtUint32_1(s32Temp);
Motor->stc_IqPi.s32_Umin = -Motor->stc_IqPi.s32_Umax;
/* PI regulator for Vq*/
Motor->stc_IqPi.s16_Error = Motor->FOC.sIdq_cmd.s16q15_Q - Motor->FOC.sIdq.s16q15_Q;
anti_pi_q15(&Motor->stc_IqPi);
Motor->FOC.sVdq.s16q15_Q = Motor->stc_IqPi.s32_Out;
/* Update the sincos for invpark */
Motor->FOC.sAngle_cmd_update = Motor->FOC.sAngle_cmd;
/* Inv PARK transform*/
inv_park(&Motor->FOC.sVdq, &Motor->FOC.sAngle_cmd_update, &Motor->FOC.sVAlBe);
/* Elimination of the DC-bus voltage ripple */
DcBus_Ripple_suppress(&Motor->FOC.sVAlBe, Motor->FOC.s16VbusAvg, &Motor->FOC.sVAlBe_cmd);
}
/**
* @brief It executes the core of FOC drive that is the controllers for Iqd
* currents regulation. It must be called periodically
* when new motor currents have been converted
* @param Motor.
* @retval None
*/
void FOC_Current_Controller_Align(Motor_TypeDef *Motor)
{
int32_t s32Temp;
int16_t s16Temp;
/* Calculate the sincos of s16CmdTheta */
Motor->FOC.sAngle_cmd = sincos(Motor->FOC.s16CmdTheta);
/* PARK transform */
park(&Motor->FOC.sIAlBe, &Motor->FOC.sAngle_cmd, &Motor->FOC.sIdq);
/* Calculate the PI saturation for Vd */
s16Temp = Mul_Q15(Motor->USER.s16DutyLimitCmd, Motor->FOC.s16VbusAvg);
Motor->FOC.s16VMAX = Mul_Q15(s16Temp, Q15(0.6667)); //Amplitude invariant transformation 振幅不变变换
Motor->stc_IdPi.s32_Umax = Motor->FOC.s16VMAX;
Motor->stc_IdPi.s32_Umin = -Motor->stc_IdPi.s32_Umax;
/* PI regulator for Vd*/
Motor->stc_IdPi.s16_Error = Motor->FOC.sIdq_cmd.s16q15_D - Motor->FOC.sIdq.s16q15_D;
anti_pi_q15(&Motor->stc_IdPi);
Motor->FOC.sVdq.s16q15_D = Motor->stc_IdPi.s32_Out;
/* Calculate the PI saturation for Vq */
s32Temp = Motor->FOC.s16VMAX * Motor->FOC.s16VMAX - Motor->FOC.sVdq.s16q15_D * Motor->FOC.sVdq.s16q15_D;
Motor->stc_IqPi.s32_Umax = Math_SqrtUint32_1(s32Temp);
Motor->stc_IqPi.s32_Umin = -Motor->stc_IqPi.s32_Umax;
/* PI regulator for Vq*/
Motor->stc_IqPi.s16_Error = Motor->FOC.sIdq_cmd.s16q15_Q - Motor->FOC.sIdq.s16q15_Q;
anti_pi_q15(&Motor->stc_IqPi);
Motor->FOC.sVdq.s16q15_Q = Motor->stc_IqPi.s32_Out;
/* Update the sincos for invpark */
Motor->FOC.sAngle_cmd_update = Motor->FOC.sAngle_cmd;
/* Inv PARK transform*/
inv_park(&Motor->FOC.sVdq, &Motor->FOC.sAngle_cmd_update, &Motor->FOC.sVAlBe);
/* Elimination of the DC-bus voltage ripple */
DcBus_Ripple_suppress(&Motor->FOC.sVAlBe, Motor->FOC.s16VbusAvg, &Motor->FOC.sVAlBe_cmd);
}
编译完成后,使用JLink,连接板上的SWD调试接口,将程序下载到开发板中,板子的硬件连接图如下所示:
这里笔者采用12V的BLDC电机,所以适配的电源是12V/1A的,该开发板支持12~72VDC宽电压输入,最大功率100W的,有条件的可以试其它大功率的直流电机。由官方提供的参考示例可知,软件算法平台很全面,从应用层、中间层、器件层、硬件层,都做了很好的支持,而且例程中采用了电机专用运算、多种算法选择、多种控制方式接入,为产品开发实现快速落地提供了有力保障。此次分享告一段落,操作演示视频见底下视频,欢迎回复!
全部评论