谷动谷力

标题: 【产品方案】基于CW32 无刷直流空心杯电机有感控制驱动方案 [打印本页]

作者: sunsili    时间: 2024-4-9 22:49
标题: 【产品方案】基于CW32 无刷直流空心杯电机有感控制驱动方案
本帖最后由 sunsili 于 2024-4-9 23:00 编辑

【产品方案】基于CW32的无刷直流空心杯电机有感控制驱动方案


01 概述

空心杯电机(Hollow-Cup Motor)是一种特殊类型的微型无刷直流电机,具有空心的旋转部分。它通常由外部固定的外壳和内部旋转的空心杯组成。空心杯电机具有较高的功率密度和扭矩输出,适用于一些特定的应用场景,如精密仪器、机器人、医疗设备等。

空心杯电机的工作原理是基于无刷直流电机的原理。它采用无刷电机的结构,包括定子(固定部分)和转子(旋转部分)。定子包含一组永磁体,而转子则包含一组线圈。通过电流在线圈中的流动和永磁体之间的相互作用,产生电磁力,从而使转子旋转。


图1-1 空心杯电机结构

1.1空心杯电机的特点和优势

需要注意的是,空心杯电机由于结构紧凑的设计导致散热困难,并且其要实现高速和高精度的响应,因此空心杯电机的功率和扭矩都有一定的限制,需要根据具体工程问题选择合适的电机类型和配套的控制系统。

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-2 无刷直流电机的电路等效图

如果我们采用二二导通的方式,即同一时刻电机的绕组只有两相导通,本次实验的空心杯电机的内部为三角形连接,极对数为1。在一个电周期(360°)内,由上面提到的霍尔六种不同的状态来切换控制MOSFET的开通关断,使得定子电流也有六种状态,即定子绕组的合成磁动势有六种状态——所以,方波控制又被称为六步换相。以上文的霍尔状态举例,当霍尔传感器传出信号为5时,控制VT1和VT6开通,其余关断,所以A相电流为正,B相电流为负;电机旋转至霍尔信号为1时,控制VT1和VT2开通,其余关断;以此类推,完整地经历过六个状态后,电机也就旋转完了一圈,再次进行上述步骤就可以使得电机连续运行。

那么电机为什么会这样运行呢,我们在这里用较为通俗的语言解释,
如果读者有兴趣可以自行查阅相关资料。电机的定子通电后也具有磁性,根据“异性相吸”的原理,电机转子会向着通电的定子相运动直至二者“吸住”,
如果在转子运动到对应“相吸”定子前的一瞬间,断掉该定子的供电而对顺着转子运动方向相隔120°的下一相定子供电,则转子又会与下一相定子“相吸”,如此往复即可使转子不断转动,上文的电路等效图对应相关定子相的供电。
图2-3 直流无刷电机定转子运动示意图

一个完整系统的方波控制步骤如下:

1 设置控制系统
确定控制系统的输入和输出接口,选择适当的控制器(如微控制器)和驱动电路。

2 确定转子位置
根据霍尔传感器信号的真值表,确定电机转子此时的位置。

3 确定换相顺序
根据转子的位置情况,确定电机定子的换相顺序,即图2-2中VT1-6的通断顺序。

4 控制电机转速
通过对MOS管VT输入PWM信号,该变占空比来控制平均电压的大小即可控制电机的转速。

5 控制电机方向
通过改变换相顺序的运行方向,可以控制电机的运动方向。

6 反馈控制(可选)
如果需要更精确的控制,可以使用更加灵敏的传感器,如编码器,来进一步监测电机的位置,在程序里使用对应算法(如PID)精确控制电机的位置和速度。

注意事项:


04 CW32性能特点

本次实验采用的MCU为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文件的内容:
  1. #ifndef _HALL_H_
  2. #define _HALL_H_
  3. #include "cw32f030_rcc.h"
  4. #include "cw32f030_gpio.h"
  5. #include "cw32f030_gtim.h"
  6. #define HALLA_PORT         (CW_GPIOA)
  7. #define HALLB_PORT         (CW_GPIOB)
  8. #define HALLC_PORT         (CW_GPIOA)
  9. #define HALLA_PIN          (GPIO_PIN_15)
  10. #define HALLB_PIN          (GPIO_PIN_3)
  11. #define HALLC_PIN          (GPIO_PIN_2)
  12. extern void Commutation(unsigned int step,unsigned int OutPwmValue,unsigned int PWM_ON_flag);
  13. extern void GTIM2_IRQHandler(void);

  14. void HALL_Init(void);
  15. unsigned char  HALL_Check(void);
  16. #endif
复制代码
  1. #include "HALL.h"
  2. uint8_t ErrorCode;                                           //电机运行错误代码
  3. extern uint8_t Motor_Start_F;                                //电机启动运行标志
  4. extern uint8_t Cur_Step;                                     //当前HALL状态
  5. extern uint8_t Direction;                                    //电机方向,0为正转,1为反转
  6. const uint8_t STEP_TAB[2][6] = {{4,0,5,2,3,1},{1,3,2,5,0,4}};//电机换相序号
  7. extern uint32_t HALLcount;                                   //霍尔脉冲计数
  8. extern uint32_t OutPwm;                                      //输出PWM值
  9. //初始化霍尔传感器要用到的GPIO和定时器
  10. void HALL_Init(void)
  11. {
  12.   __RCC_GTIM2_CLK_ENABLE();                    //先打开对应时钟
  13.   __RCC_GPIOA_CLK_ENABLE();
  14.   __RCC_GPIOB_CLK_ENABLE();

  15.   GPIO_InitTypeDef GPIO_InitStruct;            //再配置对应接口
  16.   GPIO_InitStruct.IT = GPIO_IT_NONE;
  17.   GPIO_InitStruct.Mode =GPIO_MODE_INPUT_PULLUP;//霍尔输入配置;
  18.   GPIO_InitStruct.Pins = HALLA_PIN | HALLC_PIN;//PA15和PA2
  19.   GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
  20.   GPIO_Init(HALLA_PORT, &GPIO_InitStruct);

  21.   GPIO_InitStruct.IT = GPIO_IT_NONE;
  22.   GPIO_InitStruct.Mode =GPIO_MODE_INPUT_PULLUP;// 霍尔输入配置;
  23.   GPIO_InitStruct.Pins = HALLB_PIN;            //PB3
  24.   GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
  25.   GPIO_Init(HALLB_PORT, &GPIO_InitStruct);

  26.   PA15_AFx_GTIM2CH1();                         //GTIM2CH1();
  27.   PB03_AFx_GTIM2CH2();                         //GTIM2CH2();
  28.   PA02_AFx_GTIM2CH3();                         //GTIM2CH3();

  29.   __disable_irq();
  30.   NVIC_EnableIRQ(GTIM2_IRQn);                  //配置GTIM2输入捕获中断
  31.   __enable_irq();

  32.   GTIM_InitTypeDef GTIM_InitStruct;            //这里使用GTIM2的输入捕获功能
  33.   GTIM_ICInitTypeDef GTIM_ICInitStruct;

  34.   GTIM_InitStruct.Mode = GTIM_MODE_TIME;
  35.   GTIM_InitStruct.OneShotMode = GTIM_COUNT_CONTINUE;
  36.   GTIM_InitStruct.Prescaler = GTIM_PRESCALER_DIV1;
  37.   GTIM_InitStruct.ReloadValue = 0xFFFF;
  38.   GTIM_InitStruct.ToggleOutState = DISABLE;
  39.   GTIM_TimeBaseInit(CW_GTIM2, >IM_InitStruct);
  40.   GTIM_ICInitStruct.CHx = GTIM_CHANNEL1;       //GTIM2捕获通道配置
  41.   GTIM_ICInitStruct.ICFilter = GTIM_CHx_FILTER_PCLK_N2;
  42.   GTIM_ICInitStruct.ICInvert = GTIM_CHx_INVERT_OFF;
  43.   GTIM_ICInitStruct.ICPolarity = GTIM_ICPolarity_BothEdge;
  44.   GTIM_ICInit(CW_GTIM2, >IM_ICInitStruct);
  45.   GTIM_ICInitStruct.CHx = GTIM_CHANNEL2;
  46.   GTIM_ICInit(CW_GTIM2, >IM_ICInitStruct);

  47.   GTIM_ICInitStruct.CHx = GTIM_CHANNEL3;
  48.   GTIM_ICInit(CW_GTIM2, >IM_ICInitStruct);

  49.   GTIM_ITConfig(CW_GTIM2, GTIM_IT_CC1 | GTIM_IT_CC2 | GTIM_IT_CC3, ENABLE);
  50.   GTIM_Cmd(CW_GTIM2, ENABLE);
  51. }
  52. unsigned char  HALL_Check(void)                 //读取霍尔状态,确定换相顺序
  53. {
  54.   static unsigned char hallerrnum=0;
  55.   unsigned char Hall_State=0;

  56.   if(PA15_GETVALUE()!=0)Hall_State=0x1;         //对每个引脚状态分别判断,所以三个if而不是else if
  57.   if(PB03_GETVALUE()!=0)Hall_State|=0x2;        //或运算 010
  58.   if(PA02_GETVALUE()!=0)Hall_State|=0x4;        //或运算 100
  59.   if(Hall_State==0||Hall_State==7)              //000或者111都是异常状态
  60.     {
  61.       hallerrnum++;
  62.       if(hallerrnum>=10)
  63.       {hallerrnum=10;ErrorCode=2;}              //持续异常状态说明霍尔传感器有问题
  64.     }
  65.   else hallerrnum=0;
  66.   return Hall_State;
  67. }
  68. void GTIM2_IRQHandler(void)   //在GTIM2的中断服务程序里对霍尔脉冲计数、霍尔状态确定、换相确定
  69. {        
  70.   uint32_t Hall_State;      
  71.   /* USER CODE BEGIN */
  72.   if (GTIM_GetITStatus(CW_GTIM2, GTIM_IT_CC1))        //捕获输入变化就产生中断标志
  73.   {
  74.     GTIM_ClearITPendingBit(CW_GTIM2, GTIM_IT_CC1);    //清除中断标志
  75.   }
  76.   else if (GTIM_GetITStatus(CW_GTIM2, GTIM_IT_CC2))
  77.   {   
  78.     GTIM_ClearITPendingBit(CW_GTIM2, GTIM_IT_CC2);
  79.   }

  80.   else if (GTIM_GetITStatus(CW_GTIM2, GTIM_IT_CC3))
  81.   {   
  82.     GTIM_ClearITPendingBit(CW_GTIM2, GTIM_IT_CC3);
  83.   }

  84.    HALLcount++;                                       //霍尔脉冲计数        
  85.    Hall_State=HALL_Check();                           //读取霍尔状态
  86.    Cur_Step=STEP_TAB[Direction][Hall_State-1];        //获取换相序位,例如霍尔变化为513264,则Cur_Step变化为345012
  87.    if(Motor_Start_F==1&&ErrorCode==0)                 //根据启停状态 换相
  88.      Commutation(Cur_Step,OutPwm,Motor_Start_F);           
  89.   /* USER CODE END */
  90. }
复制代码

与电机相关的BLDC模块:
BLDC.h
  1. #include "main.h"
  2. /***********************                PWM        definition   *************************/
  3. #define PWM_HN_PORT                 (CW_GPIOA)      //上管引脚
  4. #define PWM_LN_PORT                 (CW_GPIOB)      //下管引脚
  5. #define PWM_AH_PIN                  (GPIO_PIN_8)
  6. #define PWM_BH_PIN                  (GPIO_PIN_9)
  7. #define PWM_CH_PIN                  (GPIO_PIN_10)
  8. #define PWM_AL_PIN                  (GPIO_PIN_13)
  9. #define PWM_BL_PIN                  (GPIO_PIN_14)
  10. #define PWM_CL_PIN                  (GPIO_PIN_15)
  11. //上管PWM调制控制,下管GPIO开关控制, 上管高电平开关管导通,下管反相
  12. #define PWM_AL_OFF GPIO_WritePin(PWM_LN_PORT,PWM_AL_PIN,GPIO_Pin_SET)
  13. #define PWM_BL_OFF GPIO_WritePin(PWM_LN_PORT,PWM_BL_PIN,GPIO_Pin_SET)
  14. #define PWM_CL_OFF GPIO_WritePin(PWM_LN_PORT,PWM_CL_PIN,GPIO_Pin_SET)
  15. #define PWM_AL_ON GPIO_WritePin(PWM_LN_PORT,PWM_AL_PIN,GPIO_Pin_RESET)
  16. #define PWM_BL_ON GPIO_WritePin(PWM_LN_PORT,PWM_BL_PIN,GPIO_Pin_RESET)
  17. #define PWM_CL_ON GPIO_WritePin(PWM_LN_PORT,PWM_CL_PIN,GPIO_Pin_RESET)
  18. #define PWM_FRQ                        (20000)       //PWM频率(HZ)
  19. #define PWM_TS                          3200
  20. //20K

  21. #define OUTMAXPWM  PWM_TS*0.25
  22. #define OUTMINPWM  PWM_TS*0.005
  23. void BLDC_Init(void);
  24. void BLDC_Motor_Start(uint8_t Dir);
  25. void BLDC_Motor_Stop(void);
  26. void Commutation(unsigned int step,unsigned int OutPwmValue,unsigned int PWM_ON_flag);
  27. void UPPWM(void);         //更新PWM占空比
  28. /////////////////////////
复制代码

BLDC.c
  1. #include "BLDC.h"
  2. extern const uint8_t STEP_TAB[2][6];//电机换相序号
  3. uint8_t Cur_Step;                   //当前HALL状态
  4. uint8_t STEP_last;                  //上次HALL状态
  5. extern uint8_t Direction;           //电机方向,0为正转,1为反转
  6. extern uint8_t Motor_Start_F;       //电机启动运行标志
  7. uint32_t OutPwm;                    //PWM占空比
  8. //初始化电机要用到的GPIO和定时器,上桥为PWM,下桥为引脚电平控制
  9. void BLDC_Init(void)
  10. {
  11.   __RCC_ATIM_CLK_ENABLE();         
  12.   __RCC_GPIOA_CLK_ENABLE();
  13.   __RCC_GPIOB_CLK_ENABLE();

  14.   //初始化下管GPIO
  15.   GPIO_InitTypeDef GPIO_InitStruct;
  16.   GPIO_InitStruct.IT = GPIO_IT_NONE;
  17.   GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  18.   GPIO_InitStruct.Pins = PWM_AL_PIN | PWM_BL_PIN | PWM_CL_PIN;
  19.   GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
  20.   GPIO_Init(PWM_LN_PORT,&GPIO_InitStruct);
  21.   //初始化上管GPIO
  22.   GPIO_InitStruct.Pins = PWM_AH_PIN | PWM_BH_PIN | PWM_CH_PIN;
  23.   GPIO_Init(PWM_HN_PORT,&GPIO_InitStruct);

  24.   PWM_AL_OFF;PWM_BL_OFF;PWM_CL_OFF;   //初始化先关闭下管

  25.   //初始化ATIM的PWM通道
  26.   ATIM_InitTypeDef ATIM_InitStruct;
  27.   ATIM_OCInitTypeDef ATIM_OCInitStruct;

  28.   PA08_AFx_ATIMCH1A();               //上管ABC三相
  29.   PA09_AFx_ATIMCH2A();
  30.   PA10_AFx_ATIMCH3A();

  31.   ATIM_InitStruct.BufferState = DISABLE;
  32.   ATIM_InitStruct.ClockSelect = ATIM_CLOCK_PCLK;
  33.   ATIM_InitStruct.CounterAlignedMode = ATIM_COUNT_MODE_EDGE_ALIGN;
  34.   ATIM_InitStruct.CounterDirection = ATIM_COUNTING_UP;
  35.   ATIM_InitStruct.CounterOPMode = ATIM_OP_MODE_REPETITIVE;
  36.   ATIM_InitStruct.OverFlowMask = DISABLE;
  37.   ATIM_InitStruct.Prescaler = ATIM_Prescaler_DIV1;    // 计算时钟1MHz
  38.   ATIM_InitStruct.ReloadValue = PWM_TS - 1;           // 20K
  39.   ATIM_InitStruct.RepetitionCounter = 0;
  40.   ATIM_InitStruct.UnderFlowMask = DISABLE;
  41.   ATIM_Init(&ATIM_InitStruct);
  42.   //初始化PWM通道
  43.   ATIM_OCInitStruct.BufferState = DISABLE;
  44.   ATIM_OCInitStruct.OCDMAState = DISABLE;
  45.   ATIM_OCInitStruct.OCInterruptSelect = ATIM_OC_IT_UP_COUNTER;
  46.   ATIM_OCInitStruct.OCInterruptState = ENABLE;
  47.   ATIM_OCInitStruct.OCMode = ATIM_OCMODE_PWM1;
  48.   ATIM_OCInitStruct.OCPolarity = ATIM_OCPOLARITY_NONINVERT;
  49.   ATIM_OC1AInit(&ATIM_OCInitStruct);
  50.   ATIM_OC2AInit(&ATIM_OCInitStruct);
  51.   ATIM_OC3AInit(&ATIM_OCInitStruct);
  52.   ATIM_SetCompare1A(0);        //初始化先关闭上管
  53.   ATIM_SetCompare2A(0);
  54.   ATIM_SetCompare3A(0);
  55.   ATIM_PWMOutputConfig(OCREFA_TYPE_SINGLE, OUTPUT_TYPE_COMP, 0);
  56.   ATIM_CtrlPWMOutputs(ENABLE);
  57.   ATIM_Cmd(ENABLE);
  58. }
  59. void ATIM_IRQHandler(void)
  60. {
  61.   if (ATIM_GetITStatus(ATIM_IT_OVF))
  62.   {
  63.     ATIM_ClearITPendingBit(ATIM_IT_OVF);               
  64.   }
  65. }
  66. //step,为当前换相序号,OutPwmValue 输出PWM值,PWM_ON_flag=1时启动PWM输出
  67. void Commutation(unsigned int step,unsigned int OutPwmValue,unsigned int PWM_ON_flag)
  68. {
  69. if(PWM_ON_flag==0) //不启动则关闭输出
  70.    {
  71.      CW_ATIM->CH1CCRA=0;CW_ATIM->CH2CCRA=0;CW_ATIM->CH3CCRA=0;        
  72.      ATIM_CtrlPWMOutputs(DISABLE);
  73.      PWM_AL_OFF;PWM_BL_OFF;PWM_CL_OFF;
  74.      return;
  75.    }
  76.      PWM_AL_OFF;PWM_BL_OFF;PWM_CL_OFF;           //先关闭输出,避免意外
  77.      //输出上桥
  78.      if(step==0||step==1){         CW_ATIM->CH1CCRA=OutPwmValue;CW_ATIM->CH2CCRA=0;CW_ATIM->CH3CCRA=0;        } //0:AB; 1:AC
  79.      if(step==2||step==3){         CW_ATIM->CH1CCRA=0;CW_ATIM->CH2CCRA=OutPwmValue;CW_ATIM->CH3CCRA=0;        } //2:BC; 3:BA
  80.      if(step==4||step==5){         CW_ATIM->CH1CCRA=0;CW_ATIM->CH2CCRA=0;CW_ATIM->CH3CCRA=OutPwmValue;        } //4:CA; 5:CB

  81.      //输出下桥
  82.      if(step==0||step==5){PWM_AL_OFF;PWM_CL_OFF;PWM_BL_ON;}       //AB CB ; B下桥导通
  83.      else if(step==1||step==2){PWM_AL_OFF;PWM_BL_OFF;PWM_CL_ON;}//AC BC; C下桥导通
  84.      else if(step==3||step==4){PWM_BL_OFF;PWM_CL_OFF;PWM_AL_ON;}//BA CA; A下桥导通

  85.      ATIM_CtrlPWMOutputs(ENABLE);         //输出有效
  86.      STEP_last = step;
  87. }
  88. void UPPWM(void)         //更新PWM占空比
  89. {        
  90.   if(STEP_last==0||STEP_last==1){         CW_ATIM->CH2CCRA=0;CW_ATIM->CH3CCRA=0; CW_ATIM->CH1CCRA=OutPwm;        }
  91.   if(STEP_last==2||STEP_last==3){         CW_ATIM->CH1CCRA=0;CW_ATIM->CH3CCRA=0;CW_ATIM->CH2CCRA=OutPwm;        }
  92.   if(STEP_last==4||STEP_last==5){         CW_ATIM->CH1CCRA=0;CW_ATIM->CH2CCRA=0;CW_ATIM->CH3CCRA=OutPwm;        }
  93. }
  94. void BLDC_Motor_Start(uint8_t Dir)  //启动电机
  95. {         
  96.   uint32_t x;  

  97.   x=HALL_Check();
  98.   if(x==0||x==7) {x=1;}           //如果霍尔异常,输出一项,使电机先转起来
  99.   Cur_Step=STEP_TAB[Direction][x-1];
  100.   Motor_Start_F = 1;
  101.   OutPwm = OUTMINPWM;
  102.   Commutation(Cur_Step,OutPwm,Motor_Start_F);
  103. }
  104. void BLDC_Motor_Stop(void)         //停止电机
  105. {
  106.   Motor_Start_F = 0;
  107.   Commutation(Cur_Step,OutPwm,Motor_Start_F);;
  108. }
复制代码

与测速(BTIM1)相关的文件:Speed_Measure.h
  1. #ifndef _SPEED_MEASURE_H_
  2. #define _SPEED_MEASURE_H_
  3. #include "cw32f030_btim.h"
  4. #include "cw32f030_rcc.h"
  5. void Speed_Measure_Init(void);
  6. #endif
复制代码

Speed_Measure.c
  1. #include "Speed_Measure.h"
  2. extern uint32_t HALLcount;           //霍尔脉冲计数
  3. extern uint16_t ADC_TimeCount;       //电位器ADC采样计算计数
  4. extern uint16_t Hall_TimeCount;      //计数,进了2次BTIM1中断,即20ms对转速计算一次
  5. extern uint16_t OLED_FRESH_TimeCount;//计数,500ms刷新一次OLED显示
  6. void Speed_Measure_Init(void)        //BTIM1 10ms进一次中断,在中断里改变标志位           
  7. {
  8.   __RCC_BTIM_CLK_ENABLE();
  9.   __disable_irq();
  10.   NVIC_EnableIRQ(BTIM1_IRQn);
  11.   __enable_irq();

  12.   BTIM_TimeBaseInitTypeDef BTIM_InitStruct;
  13.   BTIM_InitStruct.BTIM_Mode = BTIM_Mode_TIMER;
  14.   BTIM_InitStruct.BTIM_OPMode = BTIM_OPMode_Repetitive;
  15.   BTIM_InitStruct.BTIM_Prescaler = BTIM_PRS_DIV64;
  16.   BTIM_InitStruct.BTIM_Period = 10000;           
  17.   BTIM_TimeBaseInit(CW_BTIM1, &BTIM_InitStruct);
  18.   BTIM_ITConfig(CW_BTIM1, BTIM_IT_OV, ENABLE);
  19.   BTIM_Cmd(CW_BTIM1, ENABLE);
  20. }        
  21. void BTIM1_IRQHandler(void)
  22. {
  23.   /* USER CODE BEGIN */
  24.   if(BTIM_GetITStatus(CW_BTIM1, BTIM_IT_OV))
  25.   {  
  26.     BTIM_ClearITPendingBit(CW_BTIM1, BTIM_IT_OV);
  27.     Hall_TimeCount++;        //计数,进了2次BTIM1中断,即20ms对转速计算一次
  28.     ADC_TimeCount++;         //计数,100ms检查一次电位器的电压大小,确定目标速度
  29.     OLED_FRESH_TimeCount++;  //计数,500ms刷新一次OLED显示
  30.   }
  31.   /* USER CODE END */
  32. }
复制代码

与电位器输入有关的文件:ADC_BLDC_Ctrl.h
  1. #ifndef _ADC_BLDC_CTRL_H_
  2. #define _ADC_BLDC_CTRL_H_
  3. #include "cw32f030_rcc.h"
  4. #include "cw32f030_gpio.h"
  5. #include "cw32f030_adc.h"
  6. #include "cw32f030_dma.h"
  7. void ADC_Configuration(void);
  8. void ADC_DMA_Trans(void);
  9. uint32_t ADC_SampleTarget(void);
  10. #endif
复制代码

ADC_BLDC_Ctrl.c

  1. #include "ADC_BLDC_Ctrl.h"
  2. uint32_t ADC_Result_Array;
  3. //ADC采集电位器的值,使用了DMA传输
  4. void ADC_Configuration(void)
  5. {
  6.   RCC_AHBPeriphClk_Enable(RCC_AHB_PERIPH_DMA | RCC_AHB_PERIPH_GPIOB, ENABLE);  //开启DMA和ADC使用GPIO引脚的时钟
  7.   RCC_APBPeriphClk_Enable2(RCC_APB2_PERIPH_ADC, ENABLE);    //开启ADC时钟
  8.   PB00_ANALOG_ENABLE();  //配置ADC测试IO口  电位器接口

  9.   //ADC初始化
  10.   ADC_InitTypeDef   ADC_InitStruct;         
  11.   ADC_InitStruct.ADC_OpMode = ADC_SingleChContinuousMode;
  12.   ADC_InitStruct.ADC_ClkDiv = ADC_Clk_Div8;          //PCLK 8MHz
  13.   ADC_InitStruct.ADC_SampleTime = ADC_SampTime10Clk; //10个ADC时钟周期
  14.   ADC_InitStruct.ADC_VrefSel = ADC_Vref_VDDA;        //外部3.3V参考电压
  15.   ADC_InitStruct.ADC_InBufEn = ADC_BufDisable;       //开启跟随器
  16.   ADC_InitStruct.ADC_TsEn = ADC_TsDisable;           //内置温度传感器禁用
  17.   ADC_InitStruct.ADC_DMAEn = ADC_DmaEnable;          //ADC转换完成触发DMA传输
  18.   ADC_InitStruct.ADC_Align = ADC_AlignRight;         //ADC转换结果右对齐
  19.   ADC_InitStruct.ADC_AccEn = ADC_AccDisable;         //转换结果累加不使能
  20.   ADC_Init(&ADC_InitStruct);                         //初始化ADC配置
  21.   CW_ADC->CR1_f.DISCARD = FALSE;                     //配置数据更新策略,覆盖未被读取的旧数据,保留新数据
  22.   CW_ADC->CR1_f.CHMUX = ADC_ExInputCH8;              //配置ADC输入通道

  23.   //ADC使能
  24.   ADC_Enable();   
  25.   ADC_SoftwareStartConvCmd(ENABLE);

  26.   //配置DMA
  27.   DMA_InitTypeDef   DMA_InitStruct;
  28.   DMA_StructInit( &DMA_InitStruct );
  29.   DMA_InitStruct.DMA_Mode = DMA_MODE_BLOCK;        //该模式在传输过程中会被更高级的响应打断
  30.   DMA_InitStruct.DMA_TransferWidth = DMA_TRANSFER_WIDTH_32BIT;//传输32位
  31.   DMA_InitStruct.DMA_SrcInc = DMA_SrcAddress_Fix;  //源地址增量方式固定
  32.   DMA_InitStruct.DMA_DstInc = DMA_DstAddress_Fix;  //目的地址增量方式固定
  33.   DMA_InitStruct.DMA_TransferCnt =60000;           
  34.   DMA_InitStruct.DMA_SrcAddress = (uint32_t) &(CW_ADC->RESULT0);//(0x00000020) RESULT0
  35.   DMA_InitStruct.DMA_DstAddress = (uint32_t)&ADC_Result_Array;
  36.   DMA_InitStruct.TrigMode = DMA_HardTrig;          //硬件触发
  37.   DMA_InitStruct.HardTrigSource = DMA_HardTrig_ADC_TRANSCOMPLETE; //ADC采集完成触发
  38.   DMA_Init(CW_DMACHANNEL3,&DMA_InitStruct);
  39.   DMA_ClearITPendingBit(DMA_IT_ALL);
  40.   DMA_ITConfig(CW_DMACHANNEL3, DMA_IT_TC|DMA_IT_TE , ENABLE);     //使能DMA_CHANNEL3中断
  41.   DMA_Cmd(CW_DMACHANNEL3, ENABLE);                 //使能DMA
  42. }
  43. void ADC_DMA_Trans(void)
  44. {
  45.   if (CW_DMA->ISR_f.TC3)
  46.   { //AD DMA 启动
  47.     CW_DMA->ICR_f.TC3 = 0;        
  48.     CW_DMACHANNEL3->CNT=bv16|60000;         //MUST RET AGAIN BEFORE CW_DMACHANNEL1->CNT=0
  49.     CW_DMACHANNEL3->CSR_f.EN = 1;                 
  50.   }
  51. }
  52. uint32_t ADC_SampleTarget(void)            //采集电压
  53. {
  54.   uint32_t Target = 0;

  55.   if(ADC_Result_Array >= 4000)Target = 4000;//限制大小,12位ADC采集值为:0-4096
  56.   else if(ADC_Result_Array < 3)Target = 0;
  57.   else Target = ADC_Result_Array;
  58.   return Target;
  59. }
复制代码

与显示有关的驱动函数由于篇幅原因不在此展示,下面展示main.c的内容:
  1. #include "main.h"
  2. uint8_t Direction;                //电机方向,0为正转,1为反转
  3. uint8_t Motor_Start_F=0;          //电机启动运行标志
  4. uint16_t ADC_TimeCount=0;         //电位器ADC采样计时计数
  5. uint16_t Hall_TimeCount=0;        //霍尔计时计数
  6. uint16_t OLED_FRESH_TimeCount=0;  //OLED刷新显示计时计数
  7. uint32_t HALLcount=0;             //霍尔脉冲计数
  8. uint32_t Motor_Speed = 0;         //电机实际转速,rpm
  9. extern uint32_t OutPwm;
  10. char Buffer1[48],Buffer2[48];
  11. uint32_t Pwm_Buffer;
  12. int main()
  13. {
  14.   RCC_Configuration();            //时钟树初始化
  15.   I2C_init();                                        //OLED初始化
  16.   I2C_OLED_Init();                //I2C初始化
  17.   BLDC_Init();                    //电机初始化
  18.   HALL_Init();                    //霍尔传感器初始化
  19.   Speed_Measure_Init();           //BTIM1初始化
  20.   ADC_Configuration();            //ADC初始化
  21.   I2C_OLED_Clear(1);              //清屏

  22.   Direction = 1;     //电机方向

  23.   sprintf(Buffer1,"Speed:%d rpm   ",Motor_Speed); //显示电机转速
  24.   sprintf(Buffer2,"PWM:%d %%   ",Pwm_Buffer);     //显示PWM占空比
  25.   I2C_OLED_ShowString(0,0,Buffer1);        
  26.   I2C_OLED_ShowString(0,15,Buffer2);
  27.   I2C_OLED_UPdata();

  28.   while(1)
  29.   {
  30.     ADC_DMA_Trans();          //DMA传输完毕则允许下一次传输

  31.     if(ADC_TimeCount > 10)    //100ms检查一次目标速度
  32.     {
  33.       ADC_TimeCount = 0;
  34.       OutPwm = ADC_SampleTarget() / 5;                   //设置占空比
  35.       if(OutPwm > 0 && Motor_Start_F == 0)BLDC_Motor_Start(Direction);//转速大于1000rpm才启动电机
  36.       else if(OutPwm > 0 && Motor_Start_F == 1)UPPWM();  //更新占空比
  37.       else BLDC_Motor_Stop(); //停止电机
  38.     }

  39.     if(Hall_TimeCount > 1)   //20ms测一次速
  40.     {
  41.       Hall_TimeCount = 0;
  42.       Motor_Speed = HALLcount * 500 / MotorPoles;  //转速计算,rpm
  43.       HALLcount = 0;
  44.     }

  45.     if(OLED_FRESH_TimeCount > 50)  //500ms OLED显示刷新一次
  46.     {
  47.       OLED_FRESH_TimeCount = 0;
  48.       sprintf(Buffer1,"Speed:%d rpm   ",Motor_Speed);    //显示电机转速
  49.       I2C_OLED_ShowString(0,0,Buffer1);

  50.       Pwm_Buffer = OutPwm/32;     //最大25%对应OutPwm的值:800
  51.       sprintf(Buffer2,"PWM:%d %%   ",Pwm_Buffer);        //显示占空比
  52.       I2C_OLED_ShowString(0,15,Buffer2);
  53.       I2C_OLED_UPdata();
  54.     }
  55.   }
  56. }
  57. void RCC_Configuration(void)
  58. {
  59.   RCC_HSI_Enable(RCC_HSIOSC_DIV6);
  60.   /* 1. 设置HCLK和PCLK的分频系数 */
  61.   RCC_HCLKPRS_Config(RCC_HCLK_DIV1);
  62.   RCC_PCLKPRS_Config(RCC_PCLK_DIV1);
  63.   /* 2. 使能PLL,通过HSI倍频到64MHz */
  64.   RCC_PLL_Enable(RCC_PLLSOURCE_HSI, 8000000, 8);     
  65.   // PLL输出频率64MHz
  66.   /*< 当使用的时钟源HCLK大于24M,小于等于48MHz:设置FLASH 读等待周期为2 cycle
  67.   < 当使用的时钟源HCLK大于48M,小于等于72MHz:设置FLASH 读等待周期为3 cycle */   
  68.   __RCC_FLASH_CLK_ENABLE();
  69.   FLASH_SetLatency(FLASH_Latency_3);
  70.   /* 3. 时钟切换到PLL */
  71.   RCC_SysClk_Switch(RCC_SYSCLKSRC_PLL);
  72.   RCC_SystemCoreClockUpdate(64000000);
  73. }
复制代码


最终的实验结果如下:
图5-1 有霍尔方波开环控制无刷直流空心杯电机


5.2 有霍尔方波闭环控制程序

闭环程序与开环程序相比,分别在main.c、Speed_Measure.c、ADC_BLDC_Ctrl.c文件中略有变化,同时新增了PID.c文件用于控制。

首先是main.c文件中的变化,新增了变量Flag_PID_TimeCount、Target_Speed,函数修改如下:
  1. int main()
  2. {
  3.   RCC_Configuration();            //时钟树初始化
  4.   I2C_init();                     //OLED初始化
  5.   I2C_OLED_Init();                //I2C初始化
  6.   BLDC_Init();                    //电机初始化
  7.   HALL_Init();                    //霍尔传感器初始化
  8.   Speed_Measure_Init();           //BTIM1初始化
  9.   PID_Init();                     //PID初始化
  10.   ADC_Configuration();            //ADC初始化
  11.   I2C_OLED_Clear(1);              //清屏

  12.   Direction = 1;     //电机方向

  13.   sprintf(Buffer1,"Target:%d rpm",Target_Speed);  //显示目标速度
  14.   sprintf(Buffer2,"Speed:%d rpm   ",Motor_Speed); //显示电机转速
  15.   I2C_OLED_ShowString(0,0,Buffer1);
  16.   I2C_OLED_ShowString(0,15,Buffer2);        
  17.   I2C_OLED_UPdata();

  18.   while(1)
  19.   {
  20.     ADC_DMA_Trans();          //DMA传输完毕则允许下一次传输
  21.     if(ADC_TimeCount > 10)    //100ms检查一次目标速度
  22.     {
  23.       ADC_TimeCount = 0;

  24.       Target_Speed = ADC_SampleTarget();  //采集目标速度
  25.       if(Target_Speed > 1000 && Motor_Start_F == 0)BLDC_Motor_Start(Direction);//转速大于1000rpm才启动电机
  26.       else if(Target_Speed > 1000 && Motor_Start_F == 1);  //没有操作,避免重复启动
  27.       else BLDC_Motor_Stop(); //停止电机
  28.     }

  29.     if(Hall_TimeCount > 1)   //20ms测一次速
  30.     {
  31.       Hall_TimeCount = 0;
  32.       Motor_Speed = HALLcount * 500 / MotorPoles;  //转速计算,HALLcount * 50 * 60 / 6 ,单位rpm
  33.       HALLcount = 0;
  34.     }

  35.     if(Flag_PID_TimeCount > 1) //20ms PID控制一次
  36.     {
  37.       Flag_PID_TimeCount = 0;
  38.       PID_Ctrl(Target_Speed);
  39.     }

  40.     if(OLED_FRESH_TimeCount > 50)  //500ms OLED显示刷新一次
  41.     {
  42.       OLED_FRESH_TimeCount = 0;
  43.       sprintf(Buffer1,"Target:%d rpm   ",Target_Speed);  //显示目标速度
  44.       sprintf(Buffer2,"Speed:%d rpm   ",Motor_Speed);    //显示电机转速
  45.       I2C_OLED_ShowString(0,0,Buffer1);
  46.       I2C_OLED_ShowString(0,15,Buffer2);        
  47.       I2C_OLED_UPdata();
  48.     }
  49.   }      
  50. }
复制代码


Speed_Measure.c中对BTIM1的中断服务程序修改:
  1. void BTIM1_IRQHandler(void)
  2. {
  3.   /* USER CODE BEGIN */
  4.   if(BTIM_GetITStatus(CW_BTIM1, BTIM_IT_OV))
  5.   {  
  6.     BTIM_ClearITPendingBit(CW_BTIM1, BTIM_IT_OV);

  7.     Hall_TimeCount++;        //计数,进了2次BTIM1中断,即20ms对转速计算一次
  8.     Flag_PID_TimeCount++;    //计数,进了2次BTIM1中断,即20ms对PID计算一次
  9.     ADC_TimeCount++;         //计数,100ms检查一次电位器的电压大小,确定目标速度
  10.     OLED_FRESH_TimeCount++;  //计数,500ms刷新一次OLED显示
  11.   }
  12.   /* USER CODE END */
  13. }
复制代码

ADC_BLDC_Ctrl.c中对电压采集函数作修改:
  1. uint32_t ADC_SampleTarget(void)        //采集电压
  2. {
  3.   uint32_t Target = 0;

  4.   if(ADC_Result_Array >= 4000)Target = 4000;//限制大小,12位ADC采集值为:0-4096
  5.   else if(ADC_Result_Array < 3)Target = 0;
  6.   else Target = ADC_Result_Array;

  7.   Target = Target * 5;                 //目标速度为采集值的5被则设置最大速度20000rpm,可自行修改

  8.   return Target;
  9. }
复制代码

新增PID文件如下:
  1. #include "PID.h"
  2. extern uint8_t Motor_Start_F;  //电机启动运行标志
  3. extern uint32_t OutPwm;        //输出PWM值,PID最终计算要得到一个确定的PWM占空比输出值
  4. extern uint32_t Motor_Speed;   //电机实际转速,rpm
  5. float V_Kp,V_Ki,V_Kd;
  6. void PID_Init(void)
  7. {
  8.   V_Kp = 25;
  9.   V_Ki = 5;
  10.   V_Kd = 0;
  11. }
  12. void PID_Ctrl(uint32_t Target)
  13. {
  14.   static int Error,LastError;
  15.   int PID=0;

  16.   Error = Target - Motor_Speed;

  17.   PID = (V_Kp/1000) * (Error - LastError) + (V_Ki/1000) * Error;

  18.   if(PID>10)PID=10;         //牺牲响应速度换取稳定性,避免占空比从0突增到25
  19.   else if(PID<-10)PID=-10;

  20.   OutPwm += PID;

  21.   if(OutPwm > OUTMAXPWM)OutPwm = OUTMAXPWM;  //占空比输出限制
  22.   else if(OutPwm < OUTMINPWM)
  23.   {
  24.     if(Target > 100)OutPwm = OUTMINPWM;
  25.     else OutPwm = 0;
  26.   }

  27.   if(Motor_Start_F == 0)OutPwm = 0;         //启停判断
  28.   else if(Motor_Start_F == 1);
  29.   else OutPwm = 0;

  30.   UPPWM();                                  //更新占空比
  31.   LastError = Error;
  32. }
复制代码

最终实验结果如下:

图5-2 有霍尔方波闭环控制无刷直流空心杯电机


06 调试过程中的问题与小提示

在调试过程中,作者曾烧板四次,前面两次烧毁MOS管,后两次烧毁PCB供电,针对此种问题有三条注意事项:
小提示







欢迎光临 谷动谷力 (http://bbs.sunsili.com/) Powered by Discuz! X3.2