【DRV8320HでBLDCを回したい】第4回:NUCLEO-F302R8でセンサレス120度通電制御
はじめに
第3回ではBEMFをADCで読み取る方法について書きました。
第3回では上手く読み取れているように書きましたが、実際にADCの結果を使用してセンサレス制御をやってみても上手くいきませんでした。 かなり行き詰まっていた中で、たまたま下記記事を見つけて参考にした所、センサレスでBLDCを回すことができました。この記事を見つけてなかったら未だに回せていないと思います、本当に助かりました!
また、今回はゼロクロスをADCではなくF302R8内蔵のコンパレータを使用して行いました(コンパレータはF411REには搭載されていなかったのでF302R8を使いました)。 理由としては、①ADC→②仮想中点と各相電圧を比較→③ゼロクロス判定といった処理の①②をコンパレータを使えばハード側でやってくれるのでソフトの負荷軽減になるかなと思ったからです。
NUCLEO-F302R8の設定
Clockの設定
Clockはとりあえず最大動作周波数に設定
TIM1設定
TIM1はモータへのPWM出力を行うために使用します。 Channelの1~4を「PWM Generation CHx」に設定。
- CH1:U相制御用出力
- CH2:V相制御用出力
- CH3:W相制御用出力
- CH4:ゼロクロス判定割り込み用出力
「Counter Mode」は「Center Aligned mode 1」に設定。Center Aligned modeにすることでカウンタがノコギリ波ではなく三角波になります。modeは1~3までありますがキャプチャコンペア割り込み発生タイミングが変わるようです。
「Counter Period」はPWM周波数が40kHzになるように設定しています。一応PWM周波数はソフトの方で可変できるように実装しました。
また「TIM1 capture compare interrupt」を有効にしておきます。これはCH4のコンペアマッチタイミングでゼロクロス判定を行うためです。
TIM2設定
TIM2はゼロクロスの間隔を計測するために使用します。 Clock Sourceを「Internal Clock」に変更
PrescalerはTIM2のカウンタが1usで1カウントするように設定しています。
あくまでもゼロクロス間隔を計測するだけなので割り込みはありません。
TIM6設定
TIM6はゼロクロスを検出したタイミングから次の転流(ゼロクロス間隔/2、電気角でいうと30deg後?)タイミングで実際に転流を行うための割り込みを発生させる為に使用します。 Clock Sourceを「Internal Clock」に変更
PrescalerはTIM2と同じ1usで1カウントするように設定しています。
割り込みも忘れずに有効にします。
COMP2, 4, 6設定
COMPはゼロクロス検出のために使用します。
今回はCOMPタイミングで割り込み等は発生させないので、機能を有効化させるだけです。COMP4, 6も同じように設定すればOK。
GPIO設定
あとはデバッグとDRV8320Hを制御するのに必要なGPIOの設定を行いました。赤枠の部分を「GPIO_Output」で設定しています。
これでCube MXの設定は終了なのでGenerate codeします。
1つ注意点なのが、CubeMXのバグかわかりませんがCOMP4, 6のInvertingInputに代入する値が間違って生成されるので直す必要があります。「COMP_INVERTINGINPUT_IO2」が正しい設定です。
プログラム
BLDCのセンサレス駆動で追加したコードに付いて下記に示します。
main.h
プロトタイプ宣言が一つあります。main.cに本体がいてstm32f3xx_it.cで使用したいため.hに宣言しています。
/* USER CODE BEGIN EFP */ void BLDC_CC_interrupt(); // TIM1CH4CC割り込みコールバック /* USER CODE END EFP */
main.c
シリアル関連で使用するのでインクルードしました。
/* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include <stdio.h> /* USER CODE END Includes */
BLDC_PWM_FREQ は初期のPWM周波数、BLDC_PWM_ON は初期のDutyです。
/* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ #define BLDC_PWM_FREQ 900 // PWM周波数 900 = 40kHz #define BLDC_PWM_ON 150 // ON duty 150 / 900 * 100[%] #define BLDC_PWM_OFF 0 // OFF duty #define BLDC_PWM_ZEROCROSS_CHECK_TIMING 10 // ゼロクロス判定タイミングduty // UVWのコンパレータ状態 #define COMPARATOR_STATE_0 0b101 // U:1 V:0 W:1 #define COMPARATOR_STATE_1 0b100 // U:1 V:0 W:0 #define COMPARATOR_STATE_2 0b110 // U:1 V:1 W:0 #define COMPARATOR_STATE_3 0b010 // U:0 V:1 W:0 #define COMPARATOR_STATE_4 0b011 // U:0 V:1 W:1 #define COMPARATOR_STATE_5 0b001 // U:0 V:0 W:1 // 転流状態 typedef enum { COMMUTATION_STATE_0 = 0, COMMUTATION_STATE_1, COMMUTATION_STATE_2, COMMUTATION_STATE_3, COMMUTATION_STATE_4, COMMUTATION_STATE_5, COMMUTATION_STATE_ERROR, }BLDC_CommutationState_enum; /* USER CODE END PD */
BLDCを回す上で必要な情報は構造体でまとめています。
/* USER CODE BEGIN PV */ typedef struct { int duty; int comm_state; // 現在の転流状態 int comm_flag; // 転流したフラグ int zero_cross_interval_us; // ゼロクロス間隔[us] int comp_UVW; // 現在のコンパレータの状態(UVWまとめたやつ) int comp_UVW_old; // 1つ前のコンパレータの状態 }BLDC_Data_struct; BLDC_Data_struct BLDC; /* USER CODE END PV */
センサレス駆動用に追加した関数のプロトタイプ宣言。
/* USER CODE BEGIN PFP */ void BLDC_Initialization(); // 初期化 void BLDC_Update_CommutationState(int state); // 実際に転流させる void BLDC_Set_Duty(int duty); // Duty設定用 int BLDC_Get_Comparator(); // コンパレータの状態を取得 int BLDC_Get_NextCommutationState(int comp_uvw); // コンパレータの状態を渡して次の転流状態を取得 int BLDC_Get_ZeroCrossInterval(); // ゼロクロス間隔を取得 void BLDC_Set_NextCommutationTiming(int zero_cross_interval_us); // 次の転流タイミングを設定 void BLDC_Check_ZeroCross(); // ゼロクロス判定 void BLDC_TIM6_interrupt(); // TIM6割り込みコールバック /* USER CODE END PFP */
BLDCセンサレス駆動用に追加した関数
/* Private user code ---------------------------------------------------------*/ /* USER CODE BEGIN 0 */ void BLDC_Initialization() { // PWM duty BLDC_Set_Duty(BLDC_PWM_ON); // DRV8320H enable HAL_GPIO_WritePin(ENABLE_GPIO_Port, ENABLE_Pin, GPIO_PIN_SET); // Initialize duty value __HAL_TIM_SET_AUTORELOAD(&htim1, BLDC_PWM_FREQ-1); __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, BLDC_PWM_OFF); __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, BLDC_PWM_OFF); __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_3, BLDC_PWM_OFF); __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_4, BLDC_PWM_ZEROCROSS_CHECK_TIMING); // PWM start HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1); HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2); HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3); HAL_TIM_PWM_Start_IT(&htim1, TIM_CHANNEL_4); // Comparator start HAL_COMP_Start(&hcomp2); HAL_COMP_Start(&hcomp4); HAL_COMP_Start(&hcomp6); //Zero cross interval counter start HAL_TIM_Base_Start(&htim2); } void BLDC_Update_CommutationState(int state) { switch(state) { case COMMUTATION_STATE_0: // U -> V __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, BLDC.duty); __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, BLDC_PWM_OFF); __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_3, BLDC_PWM_OFF); HAL_GPIO_WritePin(INL_U_GPIO_Port, INL_U_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(INL_V_GPIO_Port, INL_V_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(INL_W_GPIO_Port, INL_W_Pin, GPIO_PIN_RESET); break; case COMMUTATION_STATE_1: // U -> W __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, BLDC.duty); __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, BLDC_PWM_OFF); __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_3, BLDC_PWM_OFF); HAL_GPIO_WritePin(INL_U_GPIO_Port, INL_U_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(INL_V_GPIO_Port, INL_V_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(INL_W_GPIO_Port, INL_W_Pin, GPIO_PIN_SET); break; case COMMUTATION_STATE_2: // V -> W __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, BLDC_PWM_OFF); __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, BLDC.duty); __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_3, BLDC_PWM_OFF); HAL_GPIO_WritePin(INL_U_GPIO_Port, INL_U_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(INL_V_GPIO_Port, INL_V_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(INL_W_GPIO_Port, INL_W_Pin, GPIO_PIN_SET); break; case COMMUTATION_STATE_3: // V -> U __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, BLDC_PWM_OFF); __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, BLDC.duty); __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_3, BLDC_PWM_OFF); HAL_GPIO_WritePin(INL_U_GPIO_Port, INL_U_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(INL_V_GPIO_Port, INL_V_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(INL_W_GPIO_Port, INL_W_Pin, GPIO_PIN_RESET); break; case COMMUTATION_STATE_4: // W -> U __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, BLDC_PWM_OFF); __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, BLDC_PWM_OFF); __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_3, BLDC.duty); HAL_GPIO_WritePin(INL_U_GPIO_Port, INL_U_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(INL_V_GPIO_Port, INL_V_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(INL_W_GPIO_Port, INL_W_Pin, GPIO_PIN_RESET); break; case COMMUTATION_STATE_5: // W -> V __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, BLDC_PWM_OFF); __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, BLDC_PWM_OFF); __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_3, BLDC.duty); HAL_GPIO_WritePin(INL_U_GPIO_Port, INL_U_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(INL_V_GPIO_Port, INL_V_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(INL_W_GPIO_Port, INL_W_Pin, GPIO_PIN_RESET); break; default: break; } } void BLDC_Set_Duty(int duty) { BLDC.duty = duty; } int BLDC_Get_Comparator() { int comp_u; int comp_v; int comp_w; int comp_uvw; comp_u = (READ_BIT(hcomp2.Instance->CSR, COMP_CSR_COMPxOUT)) >> COMP_CSR_COMPxOUT_Pos; comp_v = (READ_BIT(hcomp4.Instance->CSR, COMP_CSR_COMPxOUT)) >> COMP_CSR_COMPxOUT_Pos; comp_w = (READ_BIT(hcomp6.Instance->CSR, COMP_CSR_COMPxOUT)) >> COMP_CSR_COMPxOUT_Pos; comp_uvw = (comp_u << 2) | (comp_v << 1) | comp_w; return comp_uvw; } int BLDC_Get_NextCommutationState(int comp_uvw) { int comm_next_state; switch(comp_uvw) { case COMPARATOR_STATE_0: comm_next_state = COMMUTATION_STATE_0; break; case COMPARATOR_STATE_1: comm_next_state = COMMUTATION_STATE_1; break; case COMPARATOR_STATE_2: comm_next_state = COMMUTATION_STATE_2; break; case COMPARATOR_STATE_3: comm_next_state = COMMUTATION_STATE_3; break; case COMPARATOR_STATE_4: comm_next_state = COMMUTATION_STATE_4; break; case COMPARATOR_STATE_5: comm_next_state = COMMUTATION_STATE_5; break; default: comm_next_state = COMMUTATION_STATE_ERROR; break; } return comm_next_state; } int BLDC_Get_ZeroCrossInterval() { int zero_cross_interval_us; zero_cross_interval_us = __HAL_TIM_GET_COUNTER(&htim2); __HAL_TIM_SET_COUNTER(&htim2, 0); return zero_cross_interval_us; } void BLDC_Set_NextCommutationTiming(int zero_cross_interval_us) { __HAL_TIM_SET_AUTORELOAD(&htim6, (int)(zero_cross_interval_us / 2)); __HAL_TIM_SET_COUNTER(&htim6, 0); HAL_TIM_Base_Start_IT(&htim6); } void BLDC_Check_ZeroCross() { if(BLDC.comp_UVW != BLDC.comp_UVW_old) { BLDC.zero_cross_interval_us = BLDC_Get_ZeroCrossInterval(); BLDC_Set_NextCommutationTiming(BLDC.zero_cross_interval_us); BLDC.comp_UVW_old = BLDC.comp_UVW; HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin); } } void BLDC_CC_interrupt() { if(BLDC.comm_flag == 0) { BLDC.comp_UVW = BLDC_Get_Comparator(); BLDC_Check_ZeroCross(); } else { BLDC.comm_flag = 0; } } void BLDC_TIM6_interrupt() { BLDC.comm_state = BLDC_Get_NextCommutationState(BLDC.comp_UVW); BLDC_Update_CommutationState(BLDC.comm_state); BLDC.comm_flag = 1; HAL_TIM_Base_Stop_IT(&htim6); HAL_GPIO_TogglePin(DEBUG_A_GPIO_Port, DEBUG_A_Pin); } void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim == &htim6){ BLDC_TIM6_interrupt(); } } /* USER CODE END 0 */
main関数の中身
int main(void) { /* USER CODE BEGIN 1 */ int bldc_step = 0; int i = 0; int duty = BLDC_PWM_ON; int freq = BLDC_PWM_FREQ; uint8_t buff[1]; HAL_StatusTypeDef ret; /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_USART2_UART_Init(); MX_TIM1_Init(); MX_COMP2_Init(); MX_COMP4_Init(); MX_COMP6_Init(); MX_TIM2_Init(); MX_TIM6_Init(); /* USER CODE BEGIN 2 */ BLDC_Initialization(); // Forced commutation for(i = 0; i < 5; i ++) { BLDC_Update_CommutationState(bldc_step); bldc_step++; bldc_step %= 6; HAL_Delay(5); } /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { ret = HAL_UART_Receive(&huart2, buff, 1, 1000); // B1スイッチを押しながらシリアルで「1」「2」を送信すると周波数が変化 // B1スイッチを押さずにシリアルで「1」「2」を送信するとDutyが変化 if(HAL_GPIO_ReadPin(B1_GPIO_Port, B1_Pin) == 1) { if (ret == HAL_OK) { if(buff[0] == '1') { duty += 10; } else if(buff[0] == '2') { duty -= 10; } } BLDC_Set_Duty(duty); } else { if (ret == HAL_OK) { if(buff[0] == '1') { freq += 10; } else if(buff[0] == '2') { freq -= 10; } } __HAL_TIM_SET_AUTORELOAD(&htim1, freq-1); } if (ret == HAL_OK) { char str[100] = {0}; sprintf(str,"%d\t%d\r\n",duty,freq); HAL_UART_Transmit(&huart2, (uint8_t*)str, sizeof(str), 1000); } /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ }
stm32f4xx_it.c
コールバック関数追加
void TIM1_CC_IRQHandler(void) { /* USER CODE BEGIN TIM1_CC_IRQn 0 */ BLDC_CC_interrupt(); /* USER CODE END TIM1_CC_IRQn 0 */ HAL_TIM_IRQHandler(&htim1); /* USER CODE BEGIN TIM1_CC_IRQn 1 */ /* USER CODE END TIM1_CC_IRQn 1 */ }
BEMF検出回路
今回は下記のような回路でBEMF検出を行っています。フィルターはなく単純に分圧しているだけです。電源電圧7.4Vのときに3.3Vを超えないように手持ちの抵抗で設計しました。
フィルター無しで検出できるのはソフトの方でPWMがONのタイミングでのみBEMF検出を行っているのでPWMパルスのノイズを無視できています。 この方法の欠点としてはPWMのDutyが小さいとPWM ONの区間が短すぎてBEMF検出処理が間に合わなくなって正常に回らなくなったりしました。
動作確認
プログラムを動作させたときの波形を示します。ちゃんとゼロクロスを検出して転流することができています。
- BEMF検出回路を通った後のU相電圧(黃色)
- BEMF検出回路を通った後のV相電圧(水色)
- BEMF検出回路を通った後の仮想中性点(紫色)
- ゼロクロス検出トグル出力(青色)
ちなみに今回のプログラムは下記のような駆動方法なのですが、
試しに下記のような相補PWMのような駆動方法を試すと
下記のような波形になりました。自分は雰囲気でモータ回してるので駆動方法による特性とかは全く分かってませんが、上の波形と比べると同じDutyで回しているのにこちらのほうが回転数が遅くなりました。
- BEMF検出回路を通った後のU相電圧(黃色)
- BEMF検出回路を通った後のV相電圧(水色)
- BEMF検出回路を通った後の仮想中性点(紫色)
- ゼロクロス検出トグル出力(青色)
おわりに
今回でなんとかBLDCをセンサレス駆動させることができました。これで自作ESCが作れるぞと思ったのも束の間、色々調べてみると課題が出てきました。
今回の手法だと1000KVモータはDuy100%まで回せたのですが、6000KVモータはどうやらゼロクロス判定が40kHz周期では粗すぎるようでDutyを上げていくと回転数が飽和してしまいました。 ゼロクロス判定周期を増やそうとしているのですが中々上手く行ってないです。
今回のプログラムだと100k eRPMで回るかどうかという感じですが、市販のESCは500k eRPMとかで回ると仕様に書いてあるのでこれくらい回せないと6000KVとかは回らないのかなあとか思っています。
このあたりを解決しないとドローン飛ばせないと思うので、自作ESCはまだまだ先になりそうです。