一、概述
空心杯電機(Hollow-Cup Motor)是一種特殊類型的微型無刷直流電機,具有空心的旋轉部分。它通常由外部固定的外殼和內部旋轉的空心杯組成。空心杯電機具有較高的功率密度和扭矩輸出,適用于一些特定的應用場景,如精密儀器、機器人、醫療設備等。
空心杯電機的工作原理是基于無刷直流電機的原理。它采用無刷電機的結構,包括定子(固定部分)和轉子(旋轉部分)。定子包含一組永磁體,而轉子則包含一組線圈。通過電流在線圈中的流動和永磁體之間的相互作用,產生電磁力,從而使轉子旋轉。
圖1-1 空心杯電機結構
空心杯電機的特點和優勢包括:
- 空心結構:空心杯設計使得電機的旋轉部分中心為空,可以通過空心軸傳遞其他信號、光線或氣體,并且由于繞組無鐵芯,轉矩分布均勻。
- 高功率密度:由于其緊湊的設計和高效的電機結構,空心杯電機具有較高的功率密度,可以在有限的空間內提供更大的扭矩輸出。
- 平滑運行:空心杯電機通常具有平滑的運行特性,可以提供穩定的轉速和低噪音。
- 高精度和可控性:空心杯電機的設計使得其具有較高的精度和可控性,適用于需要精確位置控制的應用。
- 快速響應:由于其轉動慣量小,空心杯電機能夠快速響應控制信號,機械時間常數可以達到ms級,適用于需要高速動態響應的應用場景。
需要注意的是,空心杯電機由于結構緊湊的設計導致散熱困難,并且其要實現高速和高精度的響應,因此空心杯電機的功率和扭矩都有一定的限制,需要根據具體工程問題選擇合適的電機類型和配套的控制系統。
應用場景:
- 機器人技術:空心杯電機廣泛應用于機器人的關節驅動器中,能夠提供高精度的運動控制和力矩輸出。機器人的關節通常需要快速而準確地執行各種動作,而空心杯電機可以滿足這些要求。
- 自動化設備:在自動化設備中,如自動裝配線、自動化儀器等,空心杯電機可用于驅動各種傳送帶、傳送裝置和旋轉平臺,以實現工件的快速、精確定位和搬運。
- 醫療器械:空心杯電機在醫療器械中的應用廣泛,例如手術機器人、醫療影像裝置、藥物輸送系統等。這些應用需要高度精確的運動控制和定位,而空心杯電機能夠提供穩定的力矩輸出和高精度的位置控制。
- 光學設備:在需要進行旋轉、調焦、變焦等精密光學操作的設備中,如攝像機、望遠鏡、激光器等,空心杯電機可用于驅動相應的部件,實現精確的光路控制和圖像穩定。
- 回轉平臺:空心杯電機常被用于回轉平臺或轉臺,例如航天器的天線轉動、攝影設備的平穩旋轉等。通過空心杯電機的驅動,可以實現平穩、高速的旋轉,并且減小了傳動裝置的尺寸和重量。
二、控制原理
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 直流無刷電機定轉子運動示意圖
一個完整系統的方波控制步驟如下:
- 設置控制系統:確定控制系統的輸入和輸出接口,選擇適當的控制器(如微控制器)和驅動電路。
- 確定轉子位置:根據霍爾傳感器信號的真值表,確定電機轉子此時的位置。
- 確定換相順序:根據轉子的位置情況,確定電機定子的換相順序,即圖2-2中VT1-6的通斷順序。
- 控制電機轉速:通過對MOS管VT輸入PWM信號,該變占空比來控制平均電壓的大小即可控制電機的轉速。
- 控制電機方向:通過改變換相順序的運行方向,可以控制電機的運動方向。
- 反饋控制(可選):如果需要更精確的控制,可以使用更加靈敏的傳感器,如編碼器,來進一步監測電機的位置,在程序里使用對應算法(如PID)精確控制電機的位置和速度。
注意事項:
- 控制器的選擇應考慮到方波控制的要求,如頻率范圍、引腳采樣速度和分辨率等。
- 驅動電路的設計應與電機的額定電壓和電流匹配,并具備過流、過壓等保護功能。
- 在實際應用中,應注意電機的負載特性、慣性等因素對控制的影響,可能需要進行參數調整和系統優化。
三、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 功能框圖
四、實驗設備
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
五、程序編寫
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;
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 有霍爾方波開環控制無刷直流空心杯電機
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 有霍爾方波閉環控制無刷直流空心杯電機
六、調試過程中的問題與小提示
在調試過程中作者曾燒板四次,前面兩次燒毀MOS管,后兩次燒毀PCB供電,針對此種問題有三條注意事項:
- 程序在KEIL5在進入和退出調試窗口的過程中存在未知的運行情況,所有燒毀都在進入和退出調試窗口時發生,推薦使用類似學校教學使用的線性電源限流0.2A進行供電,開關電源限流速度較慢也會發生燒毀。
- 如果缺乏對應電源,可以在進入和退出調試窗口前斷掉PCB供電,待進入調試后再對PCB上電。
- 如果發生燒毀情況,首先檢查PCB供電的芯片XL7005是否損壞,如若正常則依次檢查EG3013的15V供電,板上的5V和3.3V供電。如果電機仍然無法轉動,再使用萬用表通斷檔測量MOS管是否燒毀。
小提示
- 對于BLDC.c文件中的程序,需要確認邏輯是否嚴密,切記不可發生上下橋同時導通的情況。
- 在 ADC_SampleTarget 函數可以自行修改 Target 的值來規定目標速度上限。
- 本程序的方向切換在程序里手動設置,如果讀者想要通過按鍵等控制方向,需要在方向改變前先停止電機,再切換方向,不可在電機運行時直接改變方向和換相順序。
- 對于轉速的測量要及時,雖然由于硬件原因,測量轉速時間間隔越小,轉速變化的梯度越大,但是未獲得實時速度會導致PID控制效果不佳。先測速再進行PID運算。
審核編輯 黃宇
-
電機
+關注
關注
142文章
9000瀏覽量
145337 -
PID
+關注
關注
35文章
1472瀏覽量
85478 -
無刷電機
+關注
關注
57文章
838瀏覽量
45823 -
CW32
+關注
關注
1文章
203瀏覽量
626
發布評論請先 登錄
相關推薦
評論