How to interface BLDC motor with STM32
Today we have another tutorial covering another application of the STM32 Timer. This tutorial will cover how to interface the BLDC motor with STM32 using the PWM.
The basic requirement for this tutorial to work is that you must have a BLDC motor connected to the controller via an Electronic Speed Controller (ESC).
I have a 1400KV BLDC motor and a 30A ESC. Their pictures are shown below.
Let’s see the connections first.
Connection
Below is the image showing how the motor is connected with the controller.
The ESC gets the supply from the battery and powers both, the motor and the controller. The 2 side wires from the ESC can be connected to motor in either direction, but the center wire must be connected to the center wire of the motor.
The ESC also have a 3 pin wire, which can be connected to the controller.
The Red wire is the VCC, By combining with the ground, it can supply 5 volts to the controller.
The signal wire is connected to the pin PA8 of the controller, which is basically the PWM output pin, and will be used to control the speed of the motor.
I have also connected the potentiometer to the pin PA0 of the controller. The potentiometer values will be read using the ADC, and we will use this potentiometer to control the speed of the motor.
TouchGFX Setup
Let’s see the clock setup first
Here I have configured the external crystal for the clock. The board has been clocked by the 8 MHz crystal, and we are running the system at maximum 72 MHz clock.
Note that I have reduced the ADC clock to the minimum possible as per my setup. This is to make sure the ADC interrupts do not trigger at very high rate.
Also the APB2 timer clock is at 9MHz. This will be used by the TIM2, which we will use to generate the PWM for the motor.
ADC Setup
I have selected channel 0 of the ADC where the potentiometer is connected.
- The continuous conversion is enabled as I want the conversion to happen continuously.
- The Sampling time is selected as maximum as I don’t want the interrupts to trigger at very high rate.
- I have enabled the DMA to do the conversions.
- The DMA is enabled in circular mode, and the Data width is selected as half word.
- F103C8 have 12 bit ADC, so half word is enough to store the result.
Timer setup
We need to enable the PWM to control the speed of the motor. The BLDC works on the same principal as a servo motor.
We need to send a pulse of 20ms (50Hz) and then vary the width of the signal from 1ms to 2ms.
I have enabled the channel 1 for the PWM. The pin PA8 will be used as the PWM pin and is connected to the signal pin of the ESC.
The prescalar is set to 180 and the ARR is 1000. The calculation for them is shown below.
As shown above, in order to generate the frequency of 50Hz, we need to set the ARR of 1000 and the Prescalar of 180.
As I mentioned earlier, we need to keep the pulse width between 1ms to 2ms out of 20ms. This is equivalent of having the duty cycle between 5%(1ms/20ms) and 10%(2ms/20ms).
Since our ARR value is of 1000, the 5% duty means we need to set the capture compare value of 50. Similarly we need to set the value 100 for 10% duty.
We will set the duty cycle during the runtime.
Some Insight into the CODE
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)&ADC_Data, 1);
Inside the main function, we will start the TIM in the PWM mode. I am using tim1 and channel 1 for the PWM.
The ADC has been started in the DMA mode. The ADC data will be stored in the ADC_Data variable. Once the DMA finish the conversion a conversion complete callback will be called, which is written below.
uint16_t ADC_Data=0;
int ADC_Converted = 0;
long map(long x, long in_min, long in_max, long out_min, long out_max)
{
return (x - in_min) * (out_max - out_min + 1) / (in_max - in_min + 1) + out_min;
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
ADC_Converted = map(ADC_Data, 0, 4095, 50, 100);
TIM1->CCR1 = ADC_Converted;
}
Inside the callback function, we will simply map the raw potentiometer values to our range, i.e between 50 and 100.
I am using the map function, which I took from the Arduino source code.
Basically when the potentiometer is at minimum, the function will output 50, and when it is at the maximum, the function will output 100.
#if Calibrate
TIM1->CCR1 = 100; // Set the maximum pulse (2ms)
HAL_Delay (2000); // wait for 1 beep
TIM1->CCR1 = 50; // Set the minimum Pulse (1ms)
HAL_Delay (1000); // wait for 2 beeps
TIM1->CCR1 = 0; // reset to 0, so it can be controlled via ADC
#endif
There is also a small code for calibrating the ESC.
- Here we set the maximum pulse (2ms). The ESC will sound beep indicating it has been calibrated for the maximum pulse.
- Then we send the minimum pulse and wait for the ESC to sound the beep again.
- Once it does that it means the calibration is complete.
You can include or exclude this part of the code by setting the definition of the calibrate as 1 or 0.
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
#define Calibrate 1
/* USER CODE END PTD */
Result
Below are the images showing the debugger and the logic analyzer outputs.
- When the potentiometer is at minimum, the debugger shows the value 50 and the pulse width is 1ms.
- similarly when the potentiometer is at maximum, the debugger shows 100 and the pulse width is 2ms.
You can check out the video to see the motor working