空心杯电机(Hollow-Cup Motor)是一种特殊类型的微型无刷直流电机,具有空心的旋转部分。它通常由外部固定的外壳和内部旋转的空心杯组成。空心杯电机具有较高的功率密度和扭矩输出,适用于一些特定的应用场景,如精密仪器、机器人、医疗设备等。
空心杯电机的工作原理是基于无刷直流电机的原理。它采用无刷电机的结构,包括定子(固定部分)和转子(旋转部分)。定子包含一组永磁体,而转子则包含一组线圈。通过电流在线圈中的流动和永磁体之间的相互作用,产生电磁力,从而使转子旋转。
图1-1 空心杯电机结构
空心杯电机的特点和优势包括:
需要注意的是,空心杯电机由于结构紧凑的设计导致散热困难,并且其要实现高速和高精度的响应,因此空心杯电机的功率和扭矩都有一定的限制,需要根据具体工程问题选择合适的电机类型和配套的控制系统。
应用场景:
霍尔传感器是一种基于霍尔效应原理的传感器,用于检测磁场的存在和变化。它通常由霍尔元件、信号调理威廉希尔官方网站 和输出接口组成。霍尔元件是一种半导体材料,当其受到外部磁场的作用时,会产生一个电压信号。这个电压信号经过信号调理威廉希尔官方网站 处理后,就可以输出给控制系统进行相应的处理。
霍尔传感器的工作原理基于霍尔效应,即当电流通过某些材料时,受到垂直于电流方向的磁场的影响,会在材料两侧产生一种电势差。这个电势差被称为霍尔电压,其大小与外部磁场的强度成正比。
霍尔传感器具有以下特点和优势:
图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 无刷直流电机的威廉希尔官方网站 等效图
如果我们采用二二导通的方式,即同一时刻电机的绕组只有两相导通,本次实验的空心杯电机的内部为三角形连接,极对数为1。在一个电周期(360°)内,由上面提到的霍尔六种不同的状态来切换控制MOSFET的开通关断,使得定子电流也有六种状态,即定子绕组的合成磁动势有六种状态——所以,方波控制又被称为六步换相。
以上文的霍尔状态举例,当霍尔传感器传出信号为5时,控制VT1和VT6开通,其余关断,所以A相电流为正,B相电流为负;电机旋转至霍尔信号为1时,控制VT1和VT2开通,其余关断;以此类推,完整地经历过六个状态后,电机也就旋转完了一圈,再次进行上述步骤就可以使得电机连续运行。
那么电机为什么会这样运行呢,我们在这里用较为通俗的语言解释,如果读者有兴趣可以自行查阅相关资料。电机的定子通电后也具有磁性,根据“异性相吸”的原理,电机转子会向着通电的定子相运动直至二者“吸住”,如果在转子运动到对应“相吸”定子前的一瞬间,断掉该定子的供电而对顺着转子运动方向相隔120°的下一相定子供电,则转子又会与下一相定子“相吸”,如此往复即可使转子不断转动,上文的威廉希尔官方网站
等效图对应相关定子相的供电。
图2-3 直流无刷电机定转子运动示意图
一个完整系统的方波控制步骤如下:
注意事项:
本次实验采用的MCU为CW32F030C8T6,其性能特点如下:
高级定时器 (ATIM) 由一个 16 位的自动重载计数器和 7 个比较单元组成,并由一个可编程的预分频器驱动。ATIM 支持 6 个独立的捕获 / 比较通道,可实现 6 路独立 PWM 输出或 3 对互补 PWM 输出或对 6 路输入进行捕获。可 用于基本的定时 / 计数、测量输入信号的脉冲宽度和周期、产生输出波形(PWM、单脉冲、插入死区时间的互补 PWM 等)。 在本次实验中,我们使用 ATIM 来产生PWM波驱动上桥。
图3-1 ATIM 功能框图
CW32F030 内部集成 4 个通用定时器 (GTIM),每个 GTIM 完全独立且功能完全相同,各包含一个 16bit 自动重装载计数器并由一个可编程预分频器驱动。GTIM 支持定时器模式、计数器模式、触发启动模式和门控模式 4 种基本工作模式,每组带 4 路独立的捕获 / 比较通道,可以测量输入信号的脉冲宽度(输入捕获)或者产生输出波形(输出比较和 PWM)。本次实验使用 GTIM 的输入捕获功能来触发获取霍尔传感器的数据。
图3-2 GTIM功能框图
CW32F030 内部集成 3 个基本定时器 (BTIM),每个 BTIM 完全独立且功能完全相同,各包含一个 16bit 自动重装载计数器并由一个可编程预分频器驱动。BTIM 支持定时器模式、计数器模式、触发启动模式和门控模式 4 种工作模式,支持溢出事件触发中断请求和 DMA 请求。得益于对触发信号的精细处理设计,使得 BTIM 可以由硬件自动执行触发信号的滤波操作,还能令触发事件产生中断和 DMA 请求。 本次实验使用BTIM的定时器中断功能,10ms进入一次定时器中断,在中断中修改相关功能的标志位,在主函数的 while 循环里根据标志位判断相关功能本次是否执行。
图3-3 BTIM功能框图
CW32F030 内部集成一个 12 位精度、最高 1M SPS 转换速度的逐次逼近型模数转换器 (SAR ADC),最多可将 16 路模拟信号转换为数字信号。现实世界中的绝大多数信号都是模拟量,如光、电、声、图像信号等,都要由 ADC 转换成数字信号,才能由 MCU 进行数字化处理。本次实验使用 ADC 采集电位器的电压值,根据电位器的电压大小控制目标值的设定。
图3-4 ADC 功能框图
CW32F030 支持直接内存访问(DMA),无需 CPU 干预,即可实现外设和存储器之间、外设和外设之间、存储器和存储器之间的高速数据传输。DMA 控制器内部的优先级仲裁器,可实现 DMA 和 CPU 对外设总线控制权的仲裁,以及多 DMA 通道之间的调度执行。本次实验使用 DMA 将 ADC 采集的数据写入内存,DMA 传输由 ADC 转换完成信号触发。
图3-5 DMA 功能框图
本次实验我们使用的无刷电机驱动板为CW32_BLDC_EVA V5开发板,其配置如下:
图4-1 CW32_BLCD_EVA 评估板资源配置图
本次实验使用的空心杯电机参考图如下:
图4-2 空心杯电机实物图
连接示意图如下:
图4-3 电机与驱动板连接示意图
下面展示电机驱动板的原理图:
图4-4 电机驱动板原理图1
图4-5 电机驱动板原理图2
图4-6 电机驱动板原理图3
下面会将控制程序按照不同的功能模块向读者展示。
首先是与霍尔传感器相关的模块,存放在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;
GPIO_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 有霍尔方波开环控制无刷直流空心杯电机
闭环程序与开环程序相比,分别在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 有霍尔方波闭环控制无刷直流空心杯电机
在调试过程中作者曾烧板四次,前面两次烧毁MOS管,后两次烧毁PCB供电,针对此种问题有三条注意事项:
小提示
审核编辑 黄宇
全部0条评论
快来发表一下你的评论吧 !