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

外观

电路板背面

电路板正面
在主播获得板子以后,主播迅速进行了资料的浏览,并且由此进行里下面的准备
- 主播因为独自一人在外地实习,自己的三相无刷霍尔电机并没有带在身上,于是主播在昨晚在咸鱼上迅速购买了一个垃圾霍尔电机(赌狗赌霍尔能够使用)。
- 并且发现cw官方的已经非常牛X的把这块开发板的控制代码给写好了,于是在淘宝上把个电位器给买来了。
- 还有就是这个开发板上的RS485的ttl转差分信号的芯片并没有焊接,但是主播认为,本人个人使用,并不需要RS485信号,使用普通的TTL信号就可以了,就没有购买信号转接芯片。
- 因为主播在之前的学习中用的角度传感方式是MT6816绝对编码器芯片,并没有使用HALL传感器,所以在评测过程可能会以无感FOC作为主要评测方式,以后可能会更新PWM读取MT6816的绝对角度和HALL传感器读取角度的测评,说到这这块开发板芯片IO口的利用率极高,这是主播作为还未毕业实习生的非常震惊的一点,但是主播的MT6816似乎的就不能用了,不过主播用翻阅了其芯片的资料,似乎MT6816也支持HALL模式以及PWM角度读取模式,并且这块评测板中的VSR(VE)接口作为电位器接口,内置了运算放大器,以及对应的IO口支持 测量输入信号的脉冲宽度(输入捕获)所以未来主播会在这块开发板的基础上添加上相应的PWM角度读取方式。
- 板子的下载口使用的是直插排针,并不适合下载最新的代码,于是主播将阵脚换成了弯角排针

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

电机旋转
使用个人设置代码测试无感FOC控制
#include "foc.h"
#include "HALL.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;
arm_pid_init_f32(&close_pos_pid,1);
#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();
angle_init();
#elif angle_calibration == 1
#endif
}
float midd,last_angle;
void foc_control(float * motor_target)
{
Mechanical_Angle = get_angle();
float Ts = 0.002;
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));
foc_feedforward_path();
}
#elif CONTROL_MODE == OPEN_CONTROL
void foc_init()
{
foc_target.U_x=0;
foc_target.U_y = VOTAGE_POWER_OFFSET;
}
void foc_control(float * target_velocity)
{
float Ts = 0.002;
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);
}
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;
ATIM_InitStruct.ReloadValue = PWM_PERIOD - 1;
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);
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 "arm_math.h"
#include "main.h"
#include "hall.h"
#define DIR -1
#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)
#define PWM_PERIOD 6400
#define VOTAGE_POWER_SUPPLY 24
#define PWM_COUNT 6400
#define VOTAGE_POWER_OFFSET 12
#define POLE_PAIRS 10
#define get_angle() hall_get_angle()
#define angle_calibration 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
过程:
- 主播试图使用之前的写过的FOC代码进行尝试直接移植,结果发现cw32作为一款低功耗32芯片,将DWT中的CYCCNT寄存器给裁剪掉了,非常令人伤心{?},本人平常小计时都是使用它的,结果却没有了{:titter:},只能使用定时器了{:lol:}。
- 主播将代码里的DSP库重新加载(本人懒鬼),数学函数并不愿意自己写用的DSP库{?} ,这里有两种方法从git上拉取库自己引入工程,第二种直接在KEIL上加载,想要了解具体过程的可以自己去人工智能一下,并且DSP库与CW库文件有冲突,如果需要,把CW里面base_type.h里面的
/** 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旋转
使用个人设置代码测试HALL+FOC控制
- 前面主播提到,主播购买的是60°hall传感器电机,主播打算将60°HALL加上位置环,进行位置定位,不幸的是主播的HALL电机为10对极电机,HALL精度为6°,精度太小,会伴有振动发生,并且一点都不顺滑,当主播只好打算进行速度闭环控制,结果发现咸鱼垃圾电机的上面的覆铜掉了,主播的34块钱啊,只好把位置代码贴这这个帖子上了,想看的看一下(原本打算滤波加速度检测,这里滤波还是用的DSP库函数,DSP万岁)(小知识:HALL无刷电机的极性为保证,hall角统一一般为1,2,10对),等主播在咸鱼上在物色一块垃圾电机,在更新。
#include "HALL.h"
#include "cw32l011_gpio.h"
#define HALL_INSTA HALL_60
#define HALL_60 60
#define HALL_120 120
#define HALL_BIT 3
#define POLE_PAIRS 10
#define HALL_ANGLE 6
uint8_t last_hall = 0xFF;
Circ6_t HALL_CAP;
#if HALL_INSTA==HALL_60
const unsigned int hall_tbl[6]={3,1,5,4,6,2};
float Angle;
void angle_init()
{
Angle=0;
}
void HALL_Callback()
{
unsigned char x;
x=HALL_Check();
Angle += HALL_ANGLE*hall_dir(last_hall,x);
last_hall = x;
}
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)
{
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;
return x;
}
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;
}
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
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
下一步计划:等待主播购买一块新的电机,HALL的?或者磁编的(主播再自己画上PWM读取角度的板子),预计9月中