01、概述
空心杯电机(Hollow-Cup Motor)是一种特殊类型的微型无刷直流电机,具有空心的旋转部分。它通常由外部固定的外壳和内部旋转的空心杯组成。空心杯电机具有较高的功率密度和扭矩输出,适用于一些特定的应用场景,如精密仪器、机器人、医疗设备等。
空心杯电机的工作原理是基于无刷直流电机的原理。它采用无刷电机的结构,包括定子(固定部分)和转子(旋转部分)。定子包含一组永磁体,而转子则包含一组线圈。通过电流在线圈中的流动和永磁体之间的相互作用,产生电磁力,从而使转子旋转。
图1-1 空心杯电机结构
1.1空心杯电机的特点和优势
空心结构:空心杯设计使得电机的旋转部分中心为空,可以通过空心轴传递其他信号、光线或气体,并且由于绕组无铁芯,转矩分布均匀。
高功率密度:由于其紧凑的设计和高效的电机结构,空心杯电机具有较高的功率密度,可以在有限的空间内提供更大的扭矩输出。
平滑运行:空心杯电机通常具有平滑的运行特性,可以提供稳定的转速和低噪音。
高精度和可控性:空心杯电机的设计使得其具有较高的精度和可控性,适用于需要精确位置控制的应用。
快速响应:由于其转动惯量小,空心杯电机能够快速响应控制信号,机械时间常数可以达到ms级,适用于需要高速动态响应的应用场景。
需要注意的是,空心杯电机由于结构紧凑的设计导致散热困难,并且其要实现高速和高精度的响应,因此空心杯电机的功率和扭矩都有一定的限制,需要根据具体工程问题选择合适的电机类型和配套的控制系统。
1.2应用场景
机器人技术:空心杯电机广泛应用于机器人的关节驱动器中,能够提供高精度的运动控制和力矩输出。机器人的关节通常需要快速而准确地执行各种动作,而空心杯电机可以满足这些要求。
自动化设备:在自动化设备中,如自动装配线、自动化仪器等,空心杯电机可用于驱动各种传送带、传送装置和旋转平台,以实现工件的快速、精确定位和搬运。
医疗器械:空心杯电机在医疗器械中的应用广泛,例如手术机器人、医疗影像装置、药物输送系统等。这些应用需要高度精确的运动控制和定位,而空心杯电机能够提供稳定的力矩输出和高精度的位置控制。
光学设备:在需要进行旋转、调焦、变焦等精密光学操作的设备中,如摄像机、望远镜、激光器等,空心杯电机可用于驱动相应的部件,实现精确的光路控制和图像稳定。
回转平台:空心杯电机常被用于回转平台或转台,例如航天器的天线转动、摄影设备的平稳旋转等。通过空心杯电机的驱动,可以实现平稳、高速的旋转,并且减小了传动装置的尺寸和重量。
02、控制原理
2.1 霍尔传感器
霍尔传感器是一种基于霍尔效应原理的传感器,用于检测磁场的存在和变化。它通常由霍尔元件、信号调理电路和输出接口组成。霍尔元件是一种半导体材料,当其受到外部磁场的作用时,会产生一个电压信号。这个电压信号经过信号调理电路处理后,就可以输出给控制系统进行相应的处理。
霍尔传感器的工作原理基于霍尔效应,即当电流通过某些材料时,受到垂直于电流方向的磁场的影响,会在材料两侧产生一种电势差。这个电势差被称为霍尔电压,其大小与外部磁场的强度成正比。
霍尔传感器具有以下特点和优势:
非接触式检测:霍尔传感器通过检测磁场,而无需与被检测物直接接触,从而避免了物理接触可能带来的摩擦和磨损。
快速响应:由于霍尔传感器是基于半导体材料的电子器件,其响应速度非常快,可以实时检测和响应磁场变化。
高精度:霍尔传感器能够提供精确的磁场测量和检测,可用于测量磁场的强度、方向和变化。
宽工作温度范围:霍尔传感器具有较宽的工作温度范围,可以在高温或低温环境下正常工作。
可靠性和耐用性:霍尔传感器不受机械磨损的影响,具有较长的使用寿命和可靠性。
低功耗:霍尔传感器通常具有低功耗特性,适用于电池供电或对能源消耗敏感的应用。
在本次实验中,我们使用霍尔传感器对转子位置进行检测。通过将三个霍尔传感器相隔120°安装在电机定子的不同位置,即可根据霍尔传感器的电平信号确定电机转子的位置。下图是本次实验用到的霍尔真值表,理解真值表后对程序的编写有着重要作用:
图2-1 120°霍尔真值表
上图左为正转,右为反转。从上图可以看出,A、B、C三相霍尔传感器分别在空间上间隔120°放置,当转子的磁极运动到对应的霍尔传感器位置时,对应的相产生高电平,高电平的持续角度为180°(电角度,当电机极对数为1时也等于机械角度)。所以我们根据上面的真值表可以写出电机运行时的六种状态,以C相为高位:101、001、011、010、110、100;用十六进制的表示方式为:5、1、3、2、6、4,也就是说电机在正转时,霍尔传感器的信号只会按照513264的大小依次出现,在程序里读取对应霍尔引脚的电平状态即可判断此时电机转子的位置,这对于后续的方波控制尤为重要。
2.2 方波控制
方波控制是通过改变电机的输入电压信号来控制电机的转速和方向,这里的方波是指在电机运行过程中定子电流的波形近似方波。
图2-1 120°霍尔真值表
如果我们采用二二导通的方式,即同一时刻电机的绕组只有两相导通,本次实验的空心杯电机的内部为三角形连接,极对数为1。在一个电周期(360°)内,由上面提到的霍尔六种不同的状态来切换控制MOSFET的开通关断,使得定子电流也有六种状态,即定子绕组的合成磁动势有六种状态——所以,方波控制又被称为六步换相。
以上文的霍尔状态举例,当霍尔传感器传出信号为5时,控制VT1和VT6开通,其余关断,所以A相电流为正,B相电流为负;电机旋转至霍尔信号为1时,控制VT1和VT2开通,其余关断;以此类推,完整地经历过六个状态后,电机也就旋转完了一圈,再次进行上述步骤就可以使得电机连续运行。
那么电机为什么会这样运行呢,我们在这里用较为通俗的语言解释,如果读者有兴趣可以自行查阅相关资料。电机的定子通电后也具有磁性,根据“异性相吸”的原理,电机转子会向着通电的定子相运动直至二者“吸住”,如果在转子运动到对应“相吸”定子前的一瞬间,断掉该定子的供电而对顺着转子运动方向相隔120°的下一相定子供电,则转子又会与下一相定子“相吸”,如此往复即可使转子不断转动,上文的电路等效图对应相关定子相的供电。
图2-3 直流无刷电机定转子运动示意图
一个完整系统的方波控制步骤如下:
01)设置控制系统
确定控制系统的输入和输出接口,选择适当的控制器(如微控制器)和驱动电路。
02)确定转子位置
根据霍尔传感器信号的真值表,确定电机转子此时的位置。
03)确定换相顺序
根据转子的位置情况,确定电机定子的换相顺序,即图2-2中VT1-6的通断顺序。
04)控制电机转速
通过对MOS管VT输入PWM信号,该变占空比来控制平均电压的大小即可控制电机的转速。
05)控制电机方向
通过改变换相顺序的运行方向,可以控制电机的运动方向。
06)反馈控制(可选)
如果需要更精确的控制,可以使用更加灵敏的传感器,如编码器,来进一步监测电机的位置,在程序里使用对应算法(如PID)精确控制电机的位置和速度。
注意事项:
控制器的选择应考虑到方波控制的要求,如频率范围、引脚采样速度和分辨率等。驱动电路的设计应与电机的额定电压和电流匹配,并具备过流、过压等保护功能。在实际应用中,应注意电机的负载特性、惯性等因素对控制的影响,可能需要进行参数调整和系统优化。
04、CW32性能特点
本次实验采用的MCU为CW32F030C8T6,其性能特点如下:
架构和处理能力:CW32F030C8T6采用了ARM Cortex-M0+处理器核心,具有高性能和低功耗的特点。Cortex-M0+是ARM架构中的一种32位处理器核心,适用于对功耗要求较高的应用场景。
主频和存储器:CW32F030C8T6的主频可以高达48MHz,提供了较高的处理速度。它具有8KB的SRAM(静态随机存储器)和32KB的闪存(用于存储程序代码和数据),可用于存储应用程序和数据。
低功耗特性:CW32F030C8T6在低功耗方面表现出色,具有多种省电模式和功耗管理功能,可实现对系统功耗的有效控制。这对于需要长时间运行的电池供电设备或对功耗敏感的应用非常重要。
外设和接口:CW32F030C8T6提供了丰富的外设和接口,包括多个通用输入输出引脚(GPIO)、SPI(串行外设接口)、I2C(串行通信接口)、UART(通用异步收发器)等。这些接口可用于与外部传感器、存储器、通信模块等设备进行通信和连接。
定时器和中断控制:CW32F030C8T6配备了多个定时器和中断控制功能,可用于实现精确的定时和事件触发。定时器可以用于生成精确的时间延迟、PWM(脉冲宽度调制)输出等应用,而中断控制则可以实现对外部事件的快速响应。
安全性和保护机制:CW32F030C8T6提供了多种安全性和保护机制,包括存储器保护单元、访问控制等。这些机制可以帮助保护系统免受潜在的安全威胁和未授权访问。
本次实验我们使用了CW32的ATIM、GTIM、BTIM、ADC和DMA外设,下面分别简要介绍这五种外设。
3.1 高级定时器(ATIM)
高级定时器 (ATIM) 由一个 16 位的自动重载计数器和 7 个比较单元组成,并由一个可编程的预分频器驱动。ATIM 支持 6 个独立的捕获 / 比较通道,可实现 6 路独立 PWM 输出或 3 对互补 PWM 输出或对 6 路输入进行捕获。可 用于基本的定时 / 计数、测量输入信号的脉冲宽度和周期、产生输出波形(PWM、单脉冲、插入死区时间的互补 PWM 等)。在本次实验中,我们使用 ATIM 来产生PWM波驱动上桥。
图3-1 ATIM 功能框图
3.2 通用定时器(GTIM)
CW32F030 内部集成 4 个通用定时器 (GTIM),每个 GTIM 完全独立且功能完全相同,各包含一个 16bit 自动重装载计数器并由一个可编程预分频器驱动。GTIM 支持定时器模式、计数器模式、触发启动模式和门控模式 4 种基本工作模式,每组带 4 路独立的捕获 / 比较通道,可以测量输入信号的脉冲宽度(输入捕获)或者产生输出波形(输出比较和 PWM)。本次实验使用 GTIM 的输入捕获功能来触发获取霍尔传感器的数据。
图3-2 GTIM功能框图
3.3 基本定时器(BTIM)
CW32F030 内部集成 3 个基本定时器 (BTIM),每个 BTIM 完全独立且功能完全相同,各包含一个 16bit 自动重装载计数器并由一个可编程预分频器驱动。BTIM 支持定时器模式、计数器模式、触发启动模式和门控模式 4 种工作模式,支持溢出事件触发中断请求和 DMA 请求。得益于对触发信号的精细处理设计,使得 BTIM 可以由硬件自动执行触发信号的滤波操作,还能令触发事件产生中断和 DMA 请求。本次实验使用BTIM的定时器中断功能,10ms进入一次定时器中断,在中断中修改相关功能的标志位,在主函数的 while 循环里根据标志位判断相关功能本次是否执行。
图3-3 BTIM功能框图
3.4 模数转换器(ADC)
CW32F030 内部集成一个 12 位精度、最高 1M SPS 转换速度的逐次逼近型模数转换器 (SAR ADC),最多可将 16 路模拟信号转换为数字信号。现实世界中的绝大多数信号都是模拟量,如光、电、声、图像信号等,都要由 ADC 转换成数字信号,才能由 MCU 进行数字化处理。本次实验使用 ADC 采集电位器的电压值,根据电位器的电压大小控制目标值的设定。
图3-4 ADC 功能框图
3.5 直接内存访问(DMA)
CW32F030 支持直接内存访问(DMA),无需 CPU 干预,即可实现外设和存储器之间、外设和外设之间、存储器和存储器之间的高速数据传输。DMA 控制器内部的优先级仲裁器,可实现 DMA 和 CPU 对外设总线控制权的仲裁,以及多 DMA 通道之间的调度执行。本次实验使用 DMA 将 ADC 采集的数据写入内存,DMA 传输由 ADC 转换完成信号触发。
图3-5 DMA 功能框图
04、实验设备
4.1 CW32-BLDC电机驱动板
本次实验我们使用的无刷电机驱动板为CW32_BLDC_EVA V5开发板,其配置如下:
图4-1 CW32_BLCD_EVA 评估板资源配置图
4.2 空心杯电机与连接
本次实验使用的空心杯电机如下图:
图4-2 空心杯电机实物图
连接示意图如下:
图4-3 电机与驱动板连接示意图
下面展示电机驱动板的原理图:
图4-4 电机驱动板原理图1
图4-5 电机驱动板原理图2
图4-6 电机驱动板原理图3
05、程序编写
5.1 有霍尔方波开环控制程序
下面会将控制程序按照不同的功能模块向读者展示。
首先是与霍尔传感器相关的模块,存放在HALL.c文件中,先展示HALL.h文件的内容:
#ifndef _HALL_H_
#define _HALL_H_
#include "cw32f030_rcc.h"
#include "cw32f030_gpio.h"
#include "cw32f030_gtim.h"
#define HALLA_PORT (CW_GPIOA)
#define HALLB_PORT (CW_GPIOB)
#define HALLC_PORT (CW_GPIOA)
#define HALLA_PIN (GPIO_PIN_15)
#define HALLB_PIN (GPIO_PIN_3)
#define HALLC_PIN (GPIO_PIN_2)
extern void Commutation(unsigned int step,unsigned int OutPwmValue,unsigned int PWM_ON_flag);
extern void GTIM2_IRQHandler(void);
void HALL_Init(void);
unsigned char HALL_Check(void);
#endif
HALL.c文件
#include "HALL.h"uint8_t ErrorCode; //电机运行错误代码
extern uint8_t Motor_Start_F; //电机启动运行标志
extern uint8_t Cur_Step; //当前HALL状态
extern uint8_t Direction; //电机方向,0为正转,1为反转
const uint8_t STEP_TAB[2][6] = {{4,0,5,2,3,1},{1,3,2,5,0,4}};//电机换相序号
extern uint32_t HALLcount; //霍尔脉冲计数
extern uint32_t OutPwm; //输出PWM值
//初始化霍尔传感器要用到的GPIO和定时器
void HALL_Init(void)
{
__RCC_GTIM2_CLK_ENABLE(); //先打开对应时钟
__RCC_GPIOA_CLK_ENABLE();
__RCC_GPIOB_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct; //再配置对应接口
GPIO_InitStruct.IT = GPIO_IT_NONE;
GPIO_InitStruct.Mode =GPIO_MODE_INPUT_PULLUP;//霍尔输入配置;
GPIO_InitStruct.Pins = HALLA_PIN | HALLC_PIN;//PA15和PA2
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
GPIO_Init(HALLA_PORT, &GPIO_InitStruct);
GPIO_InitStruct.IT = GPIO_IT_NONE;
GPIO_InitStruct.Mode =GPIO_MODE_INPUT_PULLUP;// 霍尔输入配置;
GPIO_InitStruct.Pins = HALLB_PIN; //PB3
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
GPIO_Init(HALLB_PORT, &GPIO_InitStruct);
PA15_AFx_GTIM2CH1(); //GTIM2CH1();
PB03_AFx_GTIM2CH2(); //GTIM2CH2();
PA02_AFx_GTIM2CH3(); //GTIM2CH3();
__disable_irq();
NVIC_EnableIRQ(GTIM2_IRQn); //配置GTIM2输入捕获中断
__enable_irq(); GTIM_InitTypeDef GTIM_InitStruct; //这里使用GTIM2的输入捕获功能
GTIM_ICInitTypeDef GTIM_ICInitStruct;
GTIM_InitStruct.Mode = GTIM_MODE_TIME;
GTIM_InitStruct.OneShotMode = GTIM_COUNT_CONTINUE;
GTIM_InitStruct.Prescaler = GTIM_PRESCALER_DIV1;
GTIM_InitStruct.ReloadValue = 0xFFFF;
GTIM_InitStruct.ToggleOutState = DISABLE;
GTIM_TimeBaseInit(CW_GTIM2, >IM_InitStruct);
GTIM_ICInitStruct.CHx = GTIM_CHANNEL1; //GTIM2捕获通道配置
GTIM_ICInitStruct.ICFilter = GTIM_CHx_FILTER_PCLK_N2;
GTIM_ICInitStruct.ICInvert = GTIM_CHx_INVERT_OFF;
GTIM_ICInitStruct.ICPolarity = GTIM_ICPolarity_BothEdge;
GTIM_ICInit(CW_GTIM2, >IM_ICInitStruct);
GTIM_ICInitStruct.CHx = GTIM_CHANNEL2;
GTIM_ICInit(CW_GTIM2, >IM_ICInitStruct);
GTIM_ICInitStruct.CHx = GTIM_CHANNEL3;
GTIM_ICInit(CW_GTIM2, >IM_ICInitStruct);
GTIM_ITConfig(CW_GTIM2, GTIM_IT_CC1 | GTIM_IT_CC2 | GTIM_IT_CC3, ENABLE);
GTIM_Cmd(CW_GTIM2, ENABLE);
}
unsigned char HALL_Check(void) //读取霍尔状态,确定换相顺序
{
static unsigned char hallerrnum=0;
unsigned char Hall_State=0;
if(PA15_GETVALUE()!=0)Hall_State=0x1; //对每个引脚状态分别判断,所以三个if而不是else if
if(PB03_GETVALUE()!=0)Hall_State|=0x2; //或运算 010
if(PA02_GETVALUE()!=0)Hall_State|=0x4; //或运算 100
if(Hall_State==0||Hall_State==7) //000或者111都是异常状态
{
hallerrnum++;
if(hallerrnum>=10)
{
hallerrnum=10;
ErrorCode=2;
} //持续异常状态说明霍尔传感器有问题
}
else hallerrnum=0;
return Hall_State;
}
void GTIM2_IRQHandler(void) //在GTIM2的中断服务程序里对霍尔脉冲计数、霍尔状态确定、换相确定
{
uint32_t Hall_State;
/* USER CODE BEGIN */
if (GTIM_GetITStatus(CW_GTIM2, GTIM_IT_CC1)) //捕获输入变化就产生中断标志
{
GTIM_ClearITPendingBit(CW_GTIM2, GTIM_IT_CC1); //清除中断标志
}
else if (GTIM_GetITStatus(CW_GTIM2, GTIM_IT_CC2))
{
GTIM_ClearITPendingBit(CW_GTIM2, GTIM_IT_CC2);
}
else if (GTIM_GetITStatus(CW_GTIM2, GTIM_IT_CC3))
{
GTIM_ClearITPendingBit(CW_GTIM2, GTIM_IT_CC3);
}
HALLcount++; //霍尔脉冲计数
Hall_State=HALL_Check(); //读取霍尔状态
Cur_Step=STEP_TAB[Direction][Hall_State-1]; //获取换相序位,例如霍尔变化为513264,则Cur_Step变化为345012
if(Motor_Start_F==1&&ErrorCode==0) //根据启停状态 换相
Commutation(Cur_Step,OutPwm,Motor_Start_F);
/* USER CODE END */
}
与电机相关的BLDC模块:
BLDC.h
#include "main.h"
/*********************** PWM definition *************************/
#define PWM_HN_PORT (CW_GPIOA) //上管引脚
#define PWM_LN_PORT (CW_GPIOB) //下管引脚
#define PWM_AH_PIN (GPIO_PIN_8)
#define PWM_BH_PIN (GPIO_PIN_9)
#define PWM_CH_PIN (GPIO_PIN_10)
#define PWM_AL_PIN (GPIO_PIN_13)
#define PWM_BL_PIN (GPIO_PIN_14)
#define PWM_CL_PIN (GPIO_PIN_15)
//上管PWM调制控制,下管GPIO开关控制, 上管高电平开关管导通,下管反相
#define PWM_AL_OFF GPIO_WritePin(PWM_LN_PORT,PWM_AL_PIN,GPIO_Pin_SET)
#define PWM_BL_OFF GPIO_WritePin(PWM_LN_PORT,PWM_BL_PIN,GPIO_Pin_SET)
#define PWM_CL_OFF GPIO_WritePin(PWM_LN_PORT,PWM_CL_PIN,GPIO_Pin_SET)
#define PWM_AL_ON GPIO_WritePin(PWM_LN_PORT,PWM_AL_PIN,GPIO_Pin_RESET)
#define PWM_BL_ON GPIO_WritePin(PWM_LN_PORT,PWM_BL_PIN,GPIO_Pin_RESET)
#define PWM_CL_ON GPIO_WritePin(PWM_LN_PORT,PWM_CL_PIN,GPIO_Pin_RESET)
#define PWM_FRQ (20000) //PWM频率(HZ)
#define PWM_TS 3200//20K
#define OUTMAXPWM PWM_TS*0.25
#define OUTMINPWM PWM_TS*0.005
void BLDC_Init(void);
void BLDC_Motor_Start(uint8_t Dir);
void BLDC_Motor_Stop(void);
void Commutation(unsigned int step,unsigned int OutPwmValue,unsigned int PWM_ON_flag);
void UPPWM(void); //更新PWM占空比
/////////////////////////
BLDC.c
#include "BLDC.h"
extern const uint8_t STEP_TAB[2][6];//电机换相序号
uint8_t Cur_Step; //当前HALL状态
uint8_t STEP_last; //上次HALL状态
extern uint8_t Direction; //电机方向,0为正转,1为反转
extern uint8_t Motor_Start_F; //电机启动运行标志
uint32_t OutPwm; //PWM占空比//初始化电机要用到的GPIO和定时器,上桥为PWM,下桥为引脚电平控制
void BLDC_Init(void)
{
__RCC_ATIM_CLK_ENABLE();
__RCC_GPIOA_CLK_ENABLE();
__RCC_GPIOB_CLK_ENABLE();
//初始化下管GPIO
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.IT = GPIO_IT_NONE;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; G
PIO_InitStruct.Pins = PWM_AL_PIN | PWM_BL_PIN | PWM_CL_PIN;
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
GPIO_Init(PWM_LN_PORT,&GPIO_InitStruct);
//初始化上管
GPIO GPIO_InitStruct.Pins = PWM_AH_PIN | PWM_BH_PIN | PWM_CH_PIN;
GPIO_Init(PWM_HN_PORT,&GPIO_InitStruct);
PWM_AL_OFF; PWM_BL_OFF;PWM_CL_OFF; //初始化先关闭下管
//初始化ATIM的PWM通道
ATIM_InitTypeDef ATIM_InitStruct;
ATIM_OCInitTypeDef ATIM_OCInitStruct;
PA08_AFx_ATIMCH1A(); //上管ABC三相
PA09_AFx_ATIMCH2A();
PA10_AFx_ATIMCH3A();
ATIM_InitStruct.BufferState = DISABLE;
ATIM_InitStruct.ClockSelect = ATIM_CLOCK_PCLK;
ATIM_InitStruct.CounterAlignedMode = ATIM_COUNT_MODE_EDGE_ALIGN;
ATIM_InitStruct.CounterDirection = ATIM_COUNTING_UP;
ATIM_InitStruct.CounterOPMode = ATIM_OP_MODE_REPETITIVE;
ATIM_InitStruct.OverFlowMask = DISABLE;
ATIM_InitStruct.Prescaler = ATIM_Prescaler_DIV1; // 计算时钟1MHz
ATIM_InitStruct.ReloadValue = PWM_TS - 1; // 20K
ATIM_InitStruct.RepetitionCounter = 0;
ATIM_InitStruct.UnderFlowMask = DISABLE;
ATIM_Init(&ATIM_InitStruct);
//初始化PWM通道
ATIM_OCInitStruct.BufferState = DISABLE;
ATIM_OCInitStruct.OCDMAState = DISABLE;
ATIM_OCInitStruct.OCInterruptSelect = ATIM_OC_IT_UP_COUNTER;
ATIM_OCInitStruct.OCInterruptState = ENABLE;
ATIM_OCInitStruct.OCMode = ATIM_OCMODE_PWM1;
ATIM_OCInitStruct.OCPolarity = ATIM_OCPOLARITY_NONINVERT;
ATIM_OC1AInit(&ATIM_OCInitStruct);
ATIM_OC2AInit(&ATIM_OCInitStruct);
ATIM_OC3AInit(&ATIM_OCInitStruct);
ATIM_SetCompare1A(0); //初始化先关闭上管
ATIM_SetCompare2A(0);
ATIM_SetCompare3A(0);
ATIM_PWMOutputConfig(OCREFA_TYPE_SINGLE, OUTPUT_TYPE_COMP, 0);
ATIM_CtrlPWMOutputs(ENABLE);
ATIM_Cmd(ENABLE);}void ATIM_IRQHandler(void)
{
if (ATIM_GetITStatus(ATIM_IT_OVF))
{
ATIM_ClearITPendingBit(ATIM_IT_OVF);
}
}
//step,为当前换相序号,OutPwmValue 输出PWM值,PWM_ON_flag=1时启动PWM输出
void Commutation(unsigned int step,unsigned int OutPwmValue,unsigned int PWM_ON_flag)
{
if(PWM_ON_flag==0) //不启动则关闭输出
{
CW_ATIM->CH1CCRA=0;CW_ATIM->CH2CCRA=0;CW_ATIM->CH3CCRA=0;
ATIM_CtrlPWMOutputs(DISABLE);
PWM_AL_OFF;PWM_BL_OFF;PWM_CL_OFF;
return;
}
PWM_AL_OFF;PWM_BL_OFF;PWM_CL_OFF; //先关闭输出,避免意外
//输出上桥
if(step==0||step==1){ CW_ATIM->CH1CCRA=OutPwmValue;CW_ATIM->CH2CCRA=0;CW_ATIM->CH3CCRA=0; } //0:AB; 1:AC
if(step==2||step==3){ CW_ATIM->CH1CCRA=0;CW_ATIM->CH2CCRA=OutPwmValue;CW_ATIM->CH3CCRA=0; } //2:BC; 3:BA
if(step==4||step==5){ CW_ATIM->CH1CCRA=0;CW_ATIM->CH2CCRA=0;CW_ATIM->CH3CCRA=OutPwmValue; } //4:CA; 5:CB
//输出下桥
if(step==0||step==5){PWM_AL_OFF;PWM_CL_OFF;PWM_BL_ON;} //AB CB ; B下桥导通
else if(step==1||step==2){PWM_AL_OFF;PWM_BL_OFF;PWM_CL_ON;}//AC BC; C下桥导通
else if(step==3||step==4){PWM_BL_OFF;PWM_CL_OFF;PWM_AL_ON;}//BA CA; A下桥导通
ATIM_CtrlPWMOutputs(ENABLE); //输出有效
STEP_last = step;
}
void UPPWM(void) //更新PWM占空比
{
if(STEP_last==0||STEP_last==1){ CW_ATIM->CH2CCRA=0;CW_ATIM->CH3CCRA=0; CW_ATIM->CH1CCRA=OutPwm; }
if(STEP_last==2||STEP_last==3){ CW_ATIM->CH1CCRA=0;CW_ATIM->CH3CCRA=0;CW_ATIM->CH2CCRA=OutPwm; }
if(STEP_last==4||STEP_last==5){ CW_ATIM->CH1CCRA=0;CW_ATIM->CH2CCRA=0;CW_ATIM->CH3CCRA=OutPwm; }
}
void BLDC_Motor_Start(uint8_t Dir) //启动电机
{
uint32_t x;
x=HALL_Check();
if(x==0||x==7) {x=1;} //如果霍尔异常,输出一项,使电机先转起来
Cur_Step=STEP_TAB[Direction][x-1];
Motor_Start_F = 1;
OutPwm = OUTMINPWM;
Commutation(Cur_Step,OutPwm,Motor_Start_F);
}
void BLDC_Motor_Stop(void) //停止电机
{
Motor_Start_F = 0;
Commutation(Cur_Step,OutPwm,Motor_Start_F);;
}
与测速(BTIM1)相关的文件:Speed_Measure.h
#ifndef _SPEED_MEASURE_H_
#define _SPEED_MEASURE_H_
#include "cw32f030_btim.h"
#include "cw32f030_rcc.h"
void Speed_Measure_Init(void);
#endif
Speed_Measure.c
#include "Speed_Measure.h"
extern uint32_t HALLcount; //霍尔脉冲计数
extern uint16_t ADC_TimeCount; //电位器ADC采样计算计数
extern uint16_t Hall_TimeCount; //计数,进了2次BTIM1中断,即20ms对转速计算一次
extern uint16_t OLED_FRESH_TimeCount;//计数,500ms刷新一次OLED显示
void Speed_Measure_Init(void) //BTIM1 10ms进一次中断,在中断里改变标志位
{
__RCC_BTIM_CLK_ENABLE();
__disable_irq();
NVIC_EnableIRQ(BTIM1_IRQn);
__enable_irq();
BTIM_TimeBaseInitTypeDef BTIM_InitStruct;
BTIM_InitStruct.BTIM_Mode = BTIM_Mode_TIMER;
BTIM_InitStruct.BTIM_OPMode = BTIM_OPMode_Repetitive;
BTIM_InitStruct.BTIM_Prescaler = BTIM_PRS_DIV64;
BTIM_InitStruct.BTIM_Period = 10000;
BTIM_TimeBaseInit(CW_BTIM1, &BTIM_InitStruct);
BTIM_ITConfig(CW_BTIM1, BTIM_IT_OV, ENABLE);
BTIM_Cmd(CW_BTIM1, ENABLE);
}
void BTIM1_IRQHandler(void)
{
/* USER CODE BEGIN */
if(BTIM_GetITStatus(CW_BTIM1, BTIM_IT_OV))
{
BTIM_ClearITPendingBit(CW_BTIM1, BTIM_IT_OV);
Hall_TimeCount++; //计数,进了2次BTIM1中断,即20ms对转速计算一次
ADC_TimeCount++; //计数,100ms检查一次电位器的电压大小,确定目标速度
OLED_FRESH_TimeCount++; //计数,500ms刷新一次OLED显示
}
/* USER CODE END */
}
与电位器输入有关的文件:ADC_BLDC_Ctrl.h
#ifndef _ADC_BLDC_CTRL_H_
#define _ADC_BLDC_CTRL_H_
#include "cw32f030_rcc.h"
#include "cw32f030_gpio.h"
#include "cw32f030_adc.h"
#include "cw32f030_dma.h"
void ADC_Configuration(void);
void ADC_DMA_Trans(void);
uint32_t ADC_SampleTarget(void);
#endif
ADC_BLDC_Ctrl.c
#include "ADC_BLDC_Ctrl.h"
uint32_t ADC_Result_Array;//ADC采集电位器的值,使用了DMA传输
void ADC_Configuration(void)
{
RCC_AHBPeriphClk_Enable(RCC_AHB_PERIPH_DMA | RCC_AHB_PERIPH_GPIOB, ENABLE); //开启DMA和ADC使用GPIO引脚的时钟
RCC_APBPeriphClk_Enable2(RCC_APB2_PERIPH_ADC, ENABLE); //开启ADC时钟
PB00_ANALOG_ENABLE(); //配置ADC测试IO口 电位器接口
//ADC初始化
ADC_InitTypeDef ADC_InitStruct;
ADC_InitStruct.ADC_OpMode = ADC_SingleChContinuousMode;
ADC_InitStruct.ADC_ClkDiv = ADC_Clk_Div8; //PCLK 8MHz
ADC_InitStruct.ADC_SampleTime = ADC_SampTime10Clk; //10个ADC时钟周期
ADC_InitStruct.ADC_VrefSel = ADC_Vref_VDDA; //外部3.3V参考电压
ADC_InitStruct.ADC_InBufEn = ADC_BufDisable; //开启跟随器
ADC_InitStruct.ADC_TsEn = ADC_TsDisable; //内置温度传感器禁用
ADC_InitStruct.ADC_DMAEn = ADC_DmaEnable; //ADC转换完成触发DMA传输
ADC_InitStruct.ADC_Align = ADC_AlignRight; //ADC转换结果右对齐
ADC_InitStruct.ADC_AccEn = ADC_AccDisable; //转换结果累加不使能
ADC_Init(&ADC_InitStruct); //初始化ADC配置
CW_ADC->CR1_f.DISCARD = FALSE; //配置数据更新策略,覆盖未被读取的旧数据,保留新数据
CW_ADC->CR1_f.CHMUX = ADC_ExInputCH8; //配置ADC输入通道
//ADC使能
ADC_Enable();
ADC_SoftwareStartConvCmd(ENABLE);
//配置DMA
DMA_InitTypeDef DMA_InitStruct;
DMA_StructInit( &DMA_InitStruct );
DMA_InitStruct.DMA_Mode = DMA_MODE_BLOCK; //该模式在传输过程中会被更高级的响应打断
DMA_InitStruct.DMA_TransferWidth = DMA_TRANSFER_WIDTH_32BIT;//传输32位
DMA_InitStruct.DMA_SrcInc = DMA_SrcAddress_Fix; //源地址增量方式固定
DMA_InitStruct.DMA_DstInc = DMA_DstAddress_Fix; //目的地址增量方式固定
DMA_InitStruct.DMA_TransferCnt =60000;
DMA_InitStruct.DMA_SrcAddress = (uint32_t) &(CW_ADC->RESULT0);//(0x00000020) RESULT0
DMA_InitStruct.DMA_DstAddress = (uint32_t)&ADC_Result_Array;
DMA_InitStruct.TrigMode = DMA_HardTrig; //硬件触发
DMA_InitStruct.HardTrigSource = DMA_HardTrig_ADC_TRANSCOMPLETE; //ADC采集完成触发
DMA_Init(CW_DMACHANNEL3,&DMA_InitStruct);
DMA_ClearITPendingBit(DMA_IT_ALL);
DMA_ITConfig(CW_DMACHANNEL3, DMA_IT_TC|DMA_IT_TE , ENABLE); //使能DMA_CHANNEL3中断
DMA_Cmd(CW_DMACHANNEL3, ENABLE); //使能DMA
}
void ADC_DMA_Trans(void)
{
if (CW_DMA->ISR_f.TC3)
{ //AD DMA 启动
CW_DMA->ICR_f.TC3 = 0;
CW_DMACHANNEL3->CNT=bv16|60000; //MUST RET AGAIN BEFORE
CW_DMACHANNEL1->CNT=0 CW_DMACHANNEL3->CSR_f.EN = 1;
}
}
uint32_t ADC_SampleTarget(void) //采集电压
{
uint32_t Target = 0;
if(ADC_Result_Array >= 4000)Target = 4000;//限制大小,12位ADC采集值为:0-4096
else if(ADC_Result_Array < 3)Target = 0;
else Target = ADC_Result_Array;
return Target;
}
与显示有关的驱动函数由于篇幅原因不在此展示,下面展示main.c的内容:
#include "main.h"
uint8_t Direction; //电机方向,0为正转,1为反转
uint8_t Motor_Start_F=0; //电机启动运行标志
uint16_t ADC_TimeCount=0; //电位器ADC采样计时计数
uint16_t Hall_TimeCount=0; //霍尔计时计数
uint16_t OLED_FRESH_TimeCount=0; //OLED刷新显示计时计数
uint32_t HALLcount=0; //霍尔脉冲计数
uint32_t Motor_Speed = 0; //电机实际转速,rpm
extern uint32_t OutPwm;
char Buffer1[48],Buffer2[48];
uint32_t Pwm_Buffer;
int main()
{
RCC_Configuration(); //时钟树初始化
I2C_init(); //OLED初始化
I2C_OLED_Init(); //I2C初始化
BLDC_Init(); //电机初始化
HALL_Init(); //霍尔传感器初始化
Speed_Measure_Init(); //BTIM1初始化
ADC_Configuration(); //ADC初始化
I2C_OLED_Clear(1);
//清屏
Direction = 1; //电机方向
sprintf(Buffer1,"Speed:%d rpm ",Motor_Speed); //显示电机转速
sprintf(Buffer2,"PWM:%d %% ",Pwm_Buffer); //显示PWM占空比
I2C_OLED_ShowString(0,0,Buffer1);
I2C_OLED_ShowString(0,15,Buffer2);
I2C_OLED_UPdata();
while(1)
{
ADC_DMA_Trans(); //DMA传输完毕则允许下一次传输
if(ADC_TimeCount > 10) //100ms检查一次目标速度
{
ADC_TimeCount = 0;
OutPwm = ADC_SampleTarget() / 5; //设置占空比
if(OutPwm > 0 && Motor_Start_F == 0)BLDC_Motor_Start(Direction);//转速大于1000rpm才启动电机
else if(OutPwm > 0 && Motor_Start_F == 1)UPPWM(); //更新占空比
else BLDC_Motor_Stop(); //停止电机
}
if(Hall_TimeCount > 1) //20ms测一次速
{
Hall_TimeCount = 0;
Motor_Speed = HALLcount * 500 / MotorPoles; //转速计算,rpm
HALLcount = 0;
}
if(OLED_FRESH_TimeCount > 50) //500ms OLED显示刷新一次
{
OLED_FRESH_TimeCount = 0;
sprintf(Buffer1,"Speed:%d rpm ",Motor_Speed); //显示电机转速
I2C_OLED_ShowString(0,0,Buffer1);
Pwm_Buffer = OutPwm/32; //最大25%对应OutPwm的值:800
sprintf(Buffer2,"PWM:%d %% ",Pwm_Buffer); //显示占空比
I2C_OLED_ShowString(0,15,Buffer2);
I2C_OLED_UPdata();
}
}
}
void RCC_Configuration(void)
{
RCC_HSI_Enable(RCC_HSIOSC_DIV6);
/* 1. 设置HCLK和PCLK的分频系数 */
RCC_HCLKPRS_Config(RCC_HCLK_DIV1);
RCC_PCLKPRS_Config(RCC_PCLK_DIV1);
/* 2. 使能PLL,通过HSI倍频到64MHz */
RCC_PLL_Enable(RCC_PLLSOURCE_HSI, 8000000, 8);
// PLL输出频率64MHz
/*< 当使用的时钟源HCLK大于24M,小于等于48MHz:设置FLASH 读等待周期为2 cycle
< 当使用的时钟源HCLK大于48M,小于等于72MHz:设置FLASH 读等待周期为3 cycle */
__RCC_FLASH_CLK_ENABLE();
FLASH_SetLatency(FLASH_Latency_3);
/* 3. 时钟切换到PLL */
RCC_SysClk_Switch(RCC_SYSCLKSRC_PLL);
RCC_SystemCoreClockUpdate(64000000);
}
最终的实验结果如下:
图5-1 有霍尔方波开环控制无刷直流空心杯电机
5.2 有霍尔方波闭环控制程序
闭环程序与开环程序相比,分别在main.c、Speed_Measure.c、ADC_BLDC_Ctrl.c文件中略有变化,同时新增了PID.c文件用于控制。
首先是main.c文件中的变化,新增了变量Flag_PID_TimeCount、Target_Speed,函数修改如下:
int main()
{
RCC_Configuration(); //时钟树初始化
I2C_init(); //OLED初始化
I2C_OLED_Init(); //I2C初始化
BLDC_Init(); //电机初始化
HALL_Init(); //霍尔传感器初始化
Speed_Measure_Init(); //BTIM1初始化
PID_Init(); //PID初始化
ADC_Configuration(); //ADC初始化
I2C_OLED_Clear(1); //清屏
Direction = 1; //电机方向
sprintf(Buffer1,"Target:%d rpm",Target_Speed); //显示目标速度
sprintf(Buffer2,"Speed:%d rpm ",Motor_Speed); //显示电机转速
I2C_OLED_ShowString(0,0,Buffer1);
I2C_OLED_ShowString(0,15,Buffer2);
I2C_OLED_UPdata();
while(1)
{
ADC_DMA_Trans(); //DMA传输完毕则允许下一次传输
if(ADC_TimeCount > 10) //100ms检查一次目标速度
{
ADC_TimeCount = 0;
Target_Speed = ADC_SampleTarget(); //采集目标速度
if(Target_Speed > 1000 && Motor_Start_F == 0)BLDC_Motor_Start(Direction);//转速大于1000rpm才启动电机
else if(Target_Speed > 1000 && Motor_Start_F == 1); //没有操作,避免重复启动
else BLDC_Motor_Stop(); //停止电机
}
if(Hall_TimeCount > 1) //20ms测一次速
{
Hall_TimeCount = 0;
Motor_Speed = HALLcount * 500 / MotorPoles; //转速计算,HALLcount * 50 * 60 / 6 ,单位rpm
HALLcount = 0;
}
if(Flag_PID_TimeCount > 1) //20ms PID控制一次
{
Flag_PID_TimeCount = 0;
PID_Ctrl(Target_Speed);
}
if(OLED_FRESH_TimeCount > 50) //500ms OLED显示刷新一次
{
OLED_FRESH_TimeCount = 0;
sprintf(Buffer1,"Target:%d rpm ",Target_Speed); //显示目标速度
sprintf(Buffer2,"Speed:%d rpm ",Motor_Speed); //显示电机转速
I2C_OLED_ShowString(0,0,Buffer1);
I2C_OLED_ShowString(0,15,Buffer2);
I2C_OLED_UPdata();
}
}
}
Speed_Measure.c中对BTIM1的中断服务程序修改:
void BTIM1_IRQHandler(void)
{
/* USER CODE BEGIN */
if(BTIM_GetITStatus(CW_BTIM1, BTIM_IT_OV))
{
BTIM_ClearITPendingBit(CW_BTIM1, BTIM_IT_OV);
Hall_TimeCount++; //计数,进了2次BTIM1中断,即20ms对转速计算一次
Flag_PID_TimeCount++; //计数,进了2次BTIM1中断,即20ms对PID计算一次
ADC_TimeCount++; //计数,100ms检查一次电位器的电压大小,确定目标速度
OLED_FRESH_TimeCount++; //计数,500ms刷新一次OLED显示
}
/* USER CODE END */
}
ADC_BLDC_Ctrl.c中对电压采集函数作修改:
uint32_t ADC_SampleTarget(void) //采集电压
{
uint32_t Target = 0;
if(ADC_Result_Array >= 4000)Target = 4000;//限制大小,12位ADC采集值为:0-4096
else if(ADC_Result_Array < 3)Target = 0;
else Target = ADC_Result_Array;
Target = Target * 5; //目标速度为采集值的5被则设置最大速度20000rpm,可自行修改
return Target;
}
新增PID文件如下:
#include "PID.h"
extern uint8_t Motor_Start_F; //电机启动运行标志
extern uint32_t OutPwm; //输出PWM值,PID最终计算要得到一个确定的PWM占空比输出值
extern uint32_t Motor_Speed; //电机实际转速,rpm
float V_Kp,V_Ki,V_Kd;
void PID_Init(void)
{
V_Kp = 25;
V_Ki = 5;
V_Kd = 0;
}
void PID_Ctrl(uint32_t Target)
{
static int Error,LastError;
int PID=0;
Error = Target - Motor_Speed;
PID = (V_Kp/1000) * (Error - LastError) + (V_Ki/1000) * Error;
if(PID>10)PID=10; //牺牲响应速度换取稳定性,避免占空比从0突增到25
else if(PID<-10)PID=-10;
OutPwm += PID;
if(OutPwm > OUTMAXPWM)OutPwm = OUTMAXPWM; //占空比输出限制
else if(OutPwm < OUTMINPWM)
{
if(Target > 100)OutPwm = OUTMINPWM;
else OutPwm = 0;
}
if(Motor_Start_F == 0)OutPwm = 0; //启停判断
else if(Motor_Start_F == 1);
else OutPwm = 0;
UPPWM(); //更新占空比
LastError = Error;
}
最终实验结果如下:
图5-2 有霍尔方波闭环控制无刷直流空心杯电机
06、调试过程中的问题与小提示
在调试过程中,作者曾烧板四次,前面两次烧毁MOS管,后两次烧毁PCB供电,针对此种问题有三条注意事项:
程序在KEIL5在进入和退出调试窗口的过程中存在未知的运行情况,所有烧毁都在进入和退出调试窗口时发生,推荐使用性能较好的线性电源限流0.2A进行供电,开关电源限流响应较慢,也会导致板子或电机烧毁。
如果缺乏对应电源,可以在进入和退出调试窗口前断掉PCB供电,待进入调试后再对PCB上电。
如果发生烧毁情况,首先检查PCB供电的芯片XL7005是否损坏,如若正常则依次检查EG3013的15V供电,板上的5V和3.3V供电。如果电机仍然无法转动,再使用万用表测量MOS管是否烧毁。
小提示
对于BLDC.c文件中的程序,需要确认逻辑是否严密,切记不可发生上下桥同时导通的情况。
在 ADC_SampleTarget 函数可以自行修改 Target 的值来规定目标速度上限。
本程序的方向切换在程序里手动设置,如果读者想要通过按键等控制方向,需要在方向改变前先停止电机,再切换方向,不可在电机运行时直接改变方向和换相顺序。
对于转速的测量要及时,虽然由于硬件原因,测量转速时间间隔越小,转速变化的梯度越大,但是未获得实时速度会导致PID控制效果不佳。先测速再进行PID运算。
来源:CW32生态社区
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。