CW32L011电机开发板测评
hello,大家好,主播是某不知名公司的实习生一名,最近在学习FOC开发,之前用的是公司前辈制作的FOC控制板,因pcb设计问题总是烧芯片,数据乱码各种原因甚是苦恼。但在几天前水群的时候看到了,芯源半导体正在支持的一个活动:
1元得!电机驱动器开发板,限100套!
企业用户满足条件可 1元 获得开发板体验-需提交申请:
限100名企业用户,参与CW32L011电机开发板测评:
可付100元押金申请CW32L011电机驱动开发套件,满足活动条件的,退还99元押金。
这个活动主播甚是喜欢,于是便申请了该板子,还没有第三天,板子就到了主播手里了,板子还带有金属外壳非常讨主播欢喜
下面是板子的外观和内部裸露结构:

外观

电路板背面

电路板正面
在主播获得板子以后,主播迅速进行了资料的浏览,并且由此进行里下面的准备

引脚重焊
正式测评:
说明:主播因为买的咸鱼电机是HALL60°的,例程代码里面的有感就不能测评了,
直接开始将代码烧录进入开发板:(很令人伤心的是我把电机功率全开,把我的采样电阻给烧坏了,大家使用的时候要注意)

电机旋转
/*
file:foc.c
user: Liushuangkai
Email:331193752@qq.com
release:0.1
根据开源灯哥模型,重构编写。
*/
#include "foc.h"
#include "HALL.h"
//#include "MT6816.h"
//#include "user_config.h"
#define _3PI_2 4.71238898038f
#define _constrain(amt,low,high) ((amt)< (low)?(low):((amt) >(high)?(high):(amt)))
#define SIGN(x) ( (x) > 0 ? 1 : ((x) < 0 ? -1 : 0) )
#if CONTROL_MODE == CLOSE_CONTROL
#define _electricalAngle(shaft_angle,pole_pairs) (shaft_angle-zero_Angle) * pole_pairs*DIR
#elif CONTROL_MODE == OPEN_CONTROL
#define _electricalAngle(shaft_angle,pole_pairs) shaft_angle * pole_pairs
#endif
U_xy foc_target;
U_xy foc_inv_park;
U_abc foc_inv_clarke;
U_abc foc_output_duty;
PWM_abc open_pwm;
float OUT_Voltage;
float Mechanical_Angle;
float electrical_Angle;
float zero_Angle=0;
unsigned int TimeCountCompuSpeed=0,TimeCountTemp=0,TimeCountSTDelay=0,Icount=0;
#if CONTROL_MODE == CLOSE_CONTROL
arm_pid_instance_f32 close_pos_pid;
void foc_init()
{
close_pos_pid.Kd=0;
close_pos_pid.Ki=0;
close_pos_pid.Kp=0.03333;//差值P,本系统使用24V电压,偏移12V,则最大电压为12V,采用90度为最大P值输出点,则P约为 12/90=0.133
arm_pid_init_f32(&close_pos_pid,1);
//MT6816_Init(&hspi1,SPI_CS_GPIO_Port,SPI_CS_Pin);
//foc_target.U_x=0;
#if angle_calibration == 0 //该校准需要无负载校准//系统开机需支持无负债开机
electrical_Angle = 0;
foc_target.U_y =VOTAGE_POWER_OFFSET/2.0;
foc_feedforward_path();
Delay_Ms(3000);
foc_target.U_y = 0;
foc_feedforward_path();
//zero_Angle =get_angle();
angle_init();
#elif angle_calibration == 1
#endif
//TIME_ELAPSE();
}
float midd,last_angle;
void foc_control(float * motor_target)//_close_pos
{
Mechanical_Angle = get_angle();
//Mechanical_Angle =_normalizeAngle(Mechanical_Angle+Ts**motor_target*PI*2);
//foc_target.U_y = _constrain(arm_pid_f32(&close_pos_pid,*motor_target-Mechanical_Angle),-VOTAGE_POWER_OFFSET,VOTAGE_POWER_OFFSET);
float Ts = 0.002;//TIME_ELAPSE();
foc_target.U_y = VOTAGE_POWER_OFFSET/2;
if(Mechanical_Angle!=0)
midd = _normalizeAngle(midd+(Ts)*0.1*PI*2);
electrical_Angle =_normalizeAngle(_electricalAngle(midd,POLE_PAIRS));
//if(Mechanical_Angle!=last_angle)
//{
// midd = Mechanical_Angle;
// last_angle=Mechanical_Angle;
//}else
//{
//midd = midd+(Ts)*2*PI*2*SIGN(Mechanical_Angle);
//}
// foc_target.U_y = _constrain(arm_pid_f32(&close_pos_pid,*motor_target-Mechanical_Angle),-VOTAGE_POWER_OFFSET,VOTAGE_POWER_OFFSET);
// electrical_Angle = _electricalAngle(Mechanical_Angle*PI/180,POLE_PAIRS);
foc_feedforward_path();
}
#elif CONTROL_MODE == OPEN_CONTROL
void foc_init()
{
foc_target.U_x=0;
foc_target.U_y = VOTAGE_POWER_OFFSET;
//TIME_ELAPSE();
}
//开环foc控制时间推算通过dwt计数器完成 //使用的DSP库函数进行的克拉克和帕克变换,如果,使用的库函数封装不同请自行更改。
void foc_control(float * target_velocity)
{
float Ts = 0.002;//TIME_ELAPSE();
Mechanical_Angle =_normalizeAngle(Mechanical_Angle+(Ts)*(*target_velocity)*PI*2);
electrical_Angle =_normalizeAngle(_electricalAngle(Mechanical_Angle,POLE_PAIRS));
foc_feedforward_path();
}
#endif
float _normalizeAngle(float angle){
float a = fmod(angle, 2*PI); //取余运算可以用于归一化,列出特殊值例子算便知
return a >= 0 ? a : (a + 2*PI);
//三目运算符。格式:condition ? expr1 : expr2
//其中,condition 是要求值的条件表达式,如果条件成立,则返回 expr1 的值,否则返回 expr2 的值。可以将三目运算符视为 if-else 语句的简化形式。
//fmod 函数的余数的符号与除数相同。因此,当 angle 的值为负数时,余数的符号将与 _2PI 的符号相反。也就是说,如果 angle 的值小于 0 且 _2PI 的值为正数,则 fmod(angle, _2PI) 的余数将为负数。
//例如,当 angle 的值为 -PI/2,_2PI 的值为 2PI 时,fmod(angle, _2PI) 将返回一个负数。在这种情况下,可以通过将负数的余数加上 _2PI 来将角度归一化到 [0, 2PI] 的范围内,以确保角度的值始终为正数。
}
void foc_feedforward_path()
{
// 帕克逆变换
arm_inv_park_f32(foc_target.U_x,foc_target.U_y,&foc_inv_park.U_x,&foc_inv_park.U_y,arm_sin_f32(electrical_Angle),arm_cos_f32( electrical_Angle));
arm_inv_clarke_f32(foc_inv_park.U_x,foc_inv_park.U_y,&foc_inv_clarke.Ua,&foc_inv_clarke.Ub);
foc_inv_clarke.Uc = -foc_inv_clarke.Ua-foc_inv_clarke.Ub+VOTAGE_POWER_OFFSET;
foc_inv_clarke.Ua+= VOTAGE_POWER_OFFSET;
foc_inv_clarke.Ub+= VOTAGE_POWER_OFFSET;
foc_output_duty.Ua= foc_inv_clarke.Ua/VOTAGE_POWER_SUPPLY;
foc_output_duty.Ub= foc_inv_clarke.Ub/VOTAGE_POWER_SUPPLY;
foc_output_duty.Uc = foc_inv_clarke.Uc/VOTAGE_POWER_SUPPLY;
open_pwm.PWMa = foc_output_duty.Ua* PWM_COUNT;
open_pwm.PWMb = foc_output_duty.Ub * PWM_COUNT;
open_pwm.PWMc = foc_output_duty.Uc * PWM_COUNT;
FOC_PWMA=open_pwm.PWMa;
FOC_PWMB=open_pwm.PWMb;
FOC_PWMC=open_pwm.PWMc;
}
void SVPWM()
{
}
void BLDC_Configuration(void)
{
__SYSCTRL_ATIM_CLK_ENABLE();
__SYSCTRL_GPIOA_CLK_ENABLE();
__SYSCTRL_GPIOB_CLK_ENABLE();
NVIC_EnableIRQ(ATIM_IRQn);
//上桥引脚
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.IT = GPIO_IT_NONE;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pins = PWM_AP_PIN | PWM_BP_PIN;
GPIO_Init(CW_GPIOB, &GPIO_InitStruct);
GPIO_InitStruct.Pins = PWM_CP_PIN;
GPIO_Init(CW_GPIOB, &GPIO_InitStruct);
PB05_AFx_ATIMCH1();
PB06_AFx_ATIMCH2();
PB07_AFx_ATIMCH3();
//下桥引脚
GPIO_InitStruct.IT = GPIO_IT_NONE;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pins = PWM_AN_PIN ;
GPIO_Init(CW_GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pins = PWM_BN_PIN|PWM_CN_PIN;
GPIO_Init(CW_GPIOB, &GPIO_InitStruct);
PB04_AFx_ATIMCH3N();
PB03_AFx_ATIMCH2N();
PA15_AFx_ATIMCH1N();
ATIM_InitTypeDef ATIM_InitStruct;
ATIM_OCInitTypeDef ATIM_OCInitStruct = {0};
ATIM_InitStruct.BufferState = DISABLE; //使能缓存寄存器
ATIM_InitStruct.CounterAlignedMode = ATIM_COUNT_ALIGN_MODE_CENTER_BOTH; //中心对齐
ATIM_InitStruct.CounterDirection = ATIM_COUNTING_UP; //向上计数;
ATIM_InitStruct.CounterOPMode = ATIM_OP_MODE_REPETITIVE; //连续运行模式
ATIM_InitStruct.Prescaler = 1 - 1; // 计算时钟 96 MHz
ATIM_InitStruct.ReloadValue = PWM_PERIOD - 1; // PWM_TS = 2399
ATIM_InitStruct.RepetitionCounter = 0;
ATIM_Init(&ATIM_InitStruct);
ATIM_OCInitStruct.BufferState = DISABLE;
ATIM_OCInitStruct.OCComplement = ENABLE;
ATIM_OCInitStruct.OCFastMode = DISABLE;
ATIM_OCInitStruct.OCInterruptState = ENABLE;
ATIM_OCInitStruct.OCMode = ATIM_OCMODE_PWM1;
ATIM_OCInitStruct.OCPolarity = ATIM_OCPOLARITY_NONINVERT;
ATIM_OC1Init(&ATIM_OCInitStruct);
ATIM_OC2Init(&ATIM_OCInitStruct);
ATIM_OC3Init(&ATIM_OCInitStruct);
ATIM_OC4Init(&ATIM_OCInitStruct);
ATIM_ITConfig(ATIM_IT_UIE, ENABLE); // 有重复计数器溢出产生进入中断
ATIM_SetCompare1(0);
ATIM_SetCompare2(0);
ATIM_SetCompare3(0);
ATIM_SetCompare4(PWM_PERIOD/2);
ATIM_SetPWMDeadtime(20, 40, ENABLE); // 前死区为20个单位,后死区为40个单位,死区计算见用户手册
ATIM_CH1Config(ENABLE);
ATIM_CH2Config(ENABLE);
ATIM_CH3Config(ENABLE);
ATIM_CH4Config(ENABLE);
ATIM_CtrlPWMOutputs(ENABLE);
ATIM_Cmd(ENABLE);
}
#ifndef _FOC_H_
#define _FOC_H_
//#include "dwt.h"
#include "arm_math.h"
#include "main.h"
#include "hall.h"
#define DIR -1 //编码器方向与a- >b- >c相向则 逆 -1 顺 1
//foc模式控制
#define CONTROL_MODE OPEN_CONTROL
#define OPEN_CONTROL 0
#define CLOSE_CONTROL 1
#define PWM_AP_PORT (CW_GPIOB)
#define PWM_AP_PIN (GPIO_PIN_5)
#define PWM_AN_PORT (CW_GPIOA)
#define PWM_AN_PIN (GPIO_PIN_15)
#define PWM_BP_PORT (CW_GPIOB)
#define PWM_BP_PIN (GPIO_PIN_6)
#define PWM_BN_PORT (CW_GPIOB)
#define PWM_BN_PIN (GPIO_PIN_3)
#define PWM_CP_PORT (CW_GPIOB)
#define PWM_CP_PIN (GPIO_PIN_7)
#define PWM_CN_PORT (CW_GPIOB)
#define PWM_CN_PIN (GPIO_PIN_4)
#define FOC_PWMA CW_ATIM- >CCR1
#define FOC_PWMB CW_ATIM- >CCR2
#define FOC_PWMC CW_ATIM- >CCR3
#define PWM_AH_OFF GPIO_WritePin(PWM_AP_PORT,PWM_AP_PIN,GPIO_Pin_RESET)
#define PWM_BH_OFF GPIO_WritePin(PWM_BP_PORT,PWM_BP_PIN,GPIO_Pin_RESET)
#define PWM_CH_OFF GPIO_WritePin(PWM_CP_PORT,PWM_CP_PIN,GPIO_Pin_RESET)
#define PWM_AH_ON GPIO_WritePin(PWM_AP_PORT,PWM_AP_PIN,GPIO_Pin_SET)
#define PWM_BH_ON GPIO_WritePin(PWM_BP_PORT,PWM_BP_PIN,GPIO_Pin_SET)
#define PWM_CH_ON GPIO_WritePin(PWM_CP_PORT,PWM_CP_PIN,GPIO_Pin_SET)
//上管调制,下管开关控制, 上高电平开关管导通
#define PWM_AL_OFF GPIO_WritePin(PWM_AN_PORT,PWM_AN_PIN,GPIO_Pin_RESET)
#define PWM_BL_OFF GPIO_WritePin(PWM_BN_PORT,PWM_BN_PIN,GPIO_Pin_RESET)
#define PWM_CL_OFF GPIO_WritePin(PWM_CN_PORT,PWM_CN_PIN,GPIO_Pin_RESET)
#define PWM_AL_ON GPIO_WritePin(PWM_AN_PORT,PWM_AN_PIN,GPIO_Pin_SET)
#define PWM_BL_ON GPIO_WritePin(PWM_BN_PORT,PWM_BN_PIN,GPIO_Pin_SET)
#define PWM_CL_ON GPIO_WritePin(PWM_CN_PORT,PWM_CN_PIN,GPIO_Pin_SET)
//载波频率15k
#define PWM_PERIOD 6400
// (96000000 / 15000 )-1;
#define VOTAGE_POWER_SUPPLY 24
#define PWM_COUNT 6400
#define VOTAGE_POWER_OFFSET 12
#define POLE_PAIRS 10
#define get_angle() hall_get_angle() //HALL获取机械角度
#define angle_calibration 0 //校准模式 1代表的是已经将校准值储存到flash/eprom中直接读取,0代表的是需要电位延时置零
#define Delay_Ms(x) SysTickDelay(x)
#define TIME_ELAPSE() reckon_elapse_us()*1e-6f
typedef struct
{
float U_x;
float U_y;
}U_xy;
typedef struct
{
float Ua;
float Ub;
float Uc;
}U_abc;
typedef struct
{
uint16_t PWMa;
uint16_t PWMb;
uint16_t PWMc;
}PWM_abc;
extern unsigned int TimeCountCompuSpeed,TimeCountTemp,TimeCountSTDelay,Icount;
void foc_control(float * motor_target);
void foc_init(void);
float _normalizeAngle(float angle);
void foc_feedforward_path(void);
#endif
过程:
/** single precision floating point number (4 byte) */
//typedef float float32_t;
/** double precision floating point number (8 byte) */
//typedef double float64_t;
注释掉就行
5.主播也是初学FOC控制,这里推荐灯哥foc(不过我推荐入了门就行,没必要买课{:lol:}),并且灯哥的FOC用的是arduino,可以自己尝试在32上使用。
6.这块板子的例程代码是六步换向的,主播因为以前都是用的直流电机,在学习无刷电机时候没有接触六部换向,把例程的吗当成foc的代码进行移植,结果初始化都有问题,这里主播把初始化放在这里(主播因为懒惰,以前比较喜欢cubemax,面对这一堆配置,主播也不是全部都懂,不过抄就完了)
主播将个人代码烧录进入开发板:
这个是无感FOC(偏移法的控制电机)1ms进行一次计算(需要使用定时器进行,如果你用的别的板子,带有DWT->CYCCNT,可以用它,因为主播喜欢{:lol:}),主播因为也是最近开始学习,所以有感foc还没有开始里面有一部分有感foc代码(所以里面有一堆垃圾),不过还没有写对,如果要用它的话,建议VOTAGE_POWER_OFFSET设置成1V,不然可能因为电机电阻过小,烧电路
最后是电机跑起来的图片:

无感FOC旋转
/*
file:HALL.c
user: Liushuangkai
Email:331193752@qq.com
release:0.1
*/
#include "HALL.h"
#include "cw32l011_gpio.h"
#define HALL_INSTA HALL_60
#define HALL_60 60
#define HALL_120 120
#define HALL_BIT 3 //这个是hall的数量,并没有使用,这里作为标注
#define POLE_PAIRS 10
#define HALL_ANGLE 6 // 360/(POLE_PAIRS*HALL_BIT*2)
uint8_t last_hall = 0xFF;
Circ6_t HALL_CAP;//HALL方向读取储存
#if HALL_INSTA==HALL_60
const unsigned int hall_tbl[6]={3,1,5,4,6,2};//3,1,5,4,6,2(顺时针为正) 3,2,6,4,5,1(逆时针为负)
float Angle;
void angle_init()
{
Angle=0;
}
/*需要将HALL判断插入中断中*/
void HALL_Callback()
{
unsigned char x;
x=HALL_Check();
Angle += HALL_ANGLE*hall_dir(last_hall,x);
last_hall = x;
//circ6_push(&HALL_CAP,x);//查询HALL数据方向,当你的电机不知道方向是否正确时候,把HALL_CALLBACK写入中断,电机开环逆时针运行进入调试,查看HALL_CAP
//if(x==0||x==7){return;} //HALL错位检查
}
float hall_get_angle()
{
return Angle+3;
}
#elif HALL_INSTA == HALL_120
const unsigned char STEP_TAB[2][6]={{4,0,5,2,3,1},{1,3,2,5,0,4}};
void HALL_Callback()
{
}
#endif
unsigned char HALL_Check(void)
{
// static unsigned char hallerrnum=0;
unsigned char x=0;
if (GET_HALL_A==HALL_STATE_SET)x=0x01;
if (GET_HALL_B==HALL_STATE_SET)x|=0x2;
if (GET_HALL_C==HALL_STATE_SET)x|=0x4;
// if(x==0||x==7)
// {hallerrnum++;if(hallerrnum >=10){hallerrnum=10;}}//ErrorCode=2;}}
// else hallerrnum=0;
return x;
}
/*HALL方向判定函数*/
static inline Dir_t hall_dir(uint8_t last, uint8_t now)
{
if (last == now) return DIR_NONE;
int8_t idx_last = -1, idx_now = -1;
for (uint8_t i = 0; i < 6; i++) {
if (hall_tbl[i] == last) idx_last = i;
if (hall_tbl[i] == now) idx_now = i;
}
if (idx_last < 0 || idx_now < 0) return DIR_NONE; // 非法值
int8_t diff = idx_now - idx_last;
if (diff == 1 || diff == -5) return DIR_CW; // 右移一格
if (diff == -1 || diff == 5) return DIR_CCW; // 左移一格
return DIR_NONE; // 跳变/丢步
}
/*查询HALL数据方向函数*/
static inline void circ6_push(Circ6_t *q, uint8_t v)
{
q- >buf[q- >tail] = v;
q- >tail = (q- >tail + 1U) % CAP;
if (q- >tail == q- >head) // 满 - > 覆盖旧数据
q- >head = (q- >head + 1U) % CAP;
}
#ifndef _HALL_H_
#define _HALL_H_
#include < stdint.h >
#define GET_HALL_A GPIO_ReadPin(CW_GPIOA,GPIO_PIN_0)
#define GET_HALL_B GPIO_ReadPin(CW_GPIOA,GPIO_PIN_1)
#define GET_HALL_C GPIO_ReadPin(CW_GPIOA,GPIO_PIN_2)
#define CAP 6U // 容量固定 6
typedef struct {
uint8_t buf[CAP];
uint8_t head; // 读指针
uint8_t tail; // 写指针
} Circ6_t;
typedef enum
{
DIR_CW = 1,
DIR_CCW = -1,
DIR_NONE = 0
} Dir_t;// 方向结果
typedef enum
{
HALL_STATE_RESET = 0,
HALL_STATE_SET
} HALL_State;
unsigned char HALL_Check(void);
void HALL_Callback();
void angle_init();
static inline Dir_t hall_dir(uint8_t last, uint8_t now);
float hall_get_angle();
#endif
更多回帖