本帖最后由 猫吠 于 2025-8-20 12:13 编辑
CW32L011电机开发板测评
hello,大家好,主播是某不知名公司的实习生一名,最近在学习FOC开发,之前用的是公司前辈制作的FOC控制板,因pcb设计问题总是烧芯片,数据乱码各种原因甚是苦恼。但在几天前水群的时候看到了,芯源半导体正在支持的一个活动:
1元得!电机驱动器开发板,限100套!
企业用户满足条件可 1元 获得开发板体验-需提交申请:限100名企业用户,参与CW32L011电机开发板测评:
可付100元押金申请CW32L011电机驱动开发套件,满足活动条件的,退还99元押金。
在主播获得板子以后,主播迅速进行了资料的浏览,并且由此进行里下面的准备
- 主播试图使用之前的写过的FOC代码进行尝试直接移植,结果发现cw32作为一款低功耗32芯片,将DWT中的CYCCNT寄存器给裁剪掉了,非常令人伤心
,本人平常小计时都是使用它的,结果却没有了 ,只能使用定时器了 。
- 主播将代码里的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(不过我推荐入了门就行,没必要买课 ),并且灯哥的FOC用的是arduino,可以自己尝试在32上使用。 6.这块板子的例程代码是六步换向的,主播因为以前都是用的直流电机,在学习无刷电机时候没有接触六部换向,把例程的吗当成foc的代码进行移植,结果初始化都有问题,这里主播把初始化放在这里(主播因为懒惰,以前比较喜欢cubemax,面对这一堆配置,主播也不是全部都懂,不过抄就完了) 主播将个人代码烧录进入开发板: 这个是无感FOC(偏移法的控制电机)1ms进行一次计算(需要使用定时器进行,如果你用的别的板子,带有DWT->CYCCNT,可以用它,因为主播喜欢 ),主播因为也是最近开始学习,所以有感foc还没有开始里面有一部分有感foc代码(所以里面有一堆垃圾 ),不过还没有写对,如果要用它的话,建议VOTAGE_POWER_OFFSET设置成1V,不然可能因为电机电阻过小,烧电路 - 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);
- }
- /*
- 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()
- {
- }
- #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
最后是电机跑起来的图片:
- 前面主播提到,主播购买的是60°hall传感器电机,主播打算将60°HALL加上位置环,进行位置定位,不幸的是主播的HALL电机为10对极电机,HALL精度为6°,精度太小,会伴有振动发生,并且一点都不顺滑,当主播只好打算进行速度闭环控制,结果发现咸鱼垃圾电机的上面的覆铜掉了,主播的34块钱啊
,只好把位置代码贴这这个帖子上了,想看的看一下(原本打算滤波加速度检测,这里滤波还是用的DSP库函数,DSP万岁 )(小知识:HALL无刷电机的极性为保证,hall角统一一般为1,2,10对),等主播在咸鱼上在物色一块垃圾电机,在更新{:lol:}。
- /*
- 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
最后我将CW32社区群挂在末端了{:lol:}
CW32生态社区QQ群(3群):610403240 CW32生态社区QQ群(4群):478586307
|