2024-09-17 06:00 PM - last edited on 2024-09-18 02:29 AM by Andrew Neil
Hi all,
I am working on a motor control project using STM32, and I am trying to calculate the RPM of a BLDC motor using Hall sensor pulses. I have set up a SysTick timer to trigger an interrupt every 1 millisecond, which should increment a counter (sys_tick_count). This counter is then used in my millis() function to calculate elapsed time.
However, I am facing an issue where the RPM calculation doesn't seem to trigger because the time difference (time_diff) remains zero throughout the execution. It seems like the millis() function is not returning an incrementing system time, so the RPM is never calculated.
Here's the relevant portion of my code:
// SysTick timer initialization
void SysTick_Init(void)
{
SysTick_Config(SystemCoreClock / 1000); // Interrupt every 1ms
}
// SysTick Handler
void SysTick_Handler(void)
{
sys_tick_count++;
}
// Simple function to get the time in milliseconds
uint32_t millis(void)
{
return sys_tick_count; // Return system uptime in milliseconds
}
// Function to calculate speed (RPM)
float calculate_speed(void)
{
static uint32_t last_time = 0;
uint32_t current_time = millis();
float rpm = 0;
uint32_t time_diff = current_time - last_time;
printf("Time difference: %lu ms\n", time_diff); // Debugging output
if (time_diff >= 1000) {
rpm = (hall_pulse_count / 6.0) * 60.0; // Assuming 6 pulses per revolution
printf("RPM calculation, hall_pulse_count: %lu, RPM: %f\n",
hall_pulse_count,
rpm);
hall_pulse_count = 0;
last_time = current_time;
}
else
{
printf("Waiting to calculate RPM, time remaining: %lu ms\n", 1000 - time_diff);
}
return rpm;
}
Any help would be greatly appreciated!
2024-09-17 07:01 PM
Use the </> when posting code so it's formatted and readable.
2024-09-17 07:03 PM
Maybe you didn't define sys_tick_count as volatile. Don't know because you didn't show how you declared it.
2024-09-17 07:05 PM
Is sys_tick_count defined volatile? Are you inside of another interrupt with same or higher priority?
Make the example simpler, remove all the code and just observe sys_tick_count in the debugger.
2024-09-17 10:45 PM
Thank you for your suggestions. I followed your advice and made sure that sys_tick_count is defined as volatile. I also checked if I was inside another interrupt of the same or higher priority, but everything seems correctly prioritized.
I simplified the code as you suggested, removing other logic, and monitored the sys_tick_count. The SysTick seems to be incrementing correctly, but the actual speed is still showing as zero, even though the motor is running and the Hall sensors appear to be functioning correctly. Could there be something I am missing with the Hall sensor signal counting or the timing? I appreciate any further insights you might have.
2024-09-17 10:46 PM
Thanks for your input! I ensured that sys_tick_count is declared as volatile. I followed the steps you outlined, and now the SysTick is functioning correctly and incrementing as expected.
However, the actual speed is still showing as zero, even though the motor is running. The Hall sensors seem to be working (I verified the signals). Could there be an issue with the logic in the way the speed is being calculated or the pulse counting? Any further advice would be very helpful.
2024-09-18 02:18 AM
Hello @romi,
Maybe add a debugging statement to print the "hall_pulse_count" value each time the interrupt handler is called, to see if the pulses are being counted correctly
To give better visibility on the answered topics, please click on Accept as Solution on the reply which solved your issue or answered your question.
2024-09-18 06:42 AM
You're not giving us enough info to determine that. When is calculate_speed called? We have no idea. Maybe it's never called. Maybe it's called too often. Maybe not enough, maybe at the wrong times. Verify it's getting called at the expected rate. What speeds do you expect?
2024-09-18 07:39 AM
The calculate_speed() function is called within the main loop, which includes a delay_ms(100) at the end. This means the loop, and therefore calculate_speed(), is called roughly every 100 milliseconds. However, within the calculate_speed() function itself, speed is only updated every 200 ms because I’ve included a condition that ensures it only recalculates speed if 200 ms has passed (if (current_time - last_time >= 200)).
I verified the Hall sensor output using USART, and it is functioning correctly, so the issue might be related to either the timing of the speed calculation or insufficient pulses within the 200 ms window. below is this is code:
<<<<<
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "lcd.h"
#include "adc.h"
#include "key.h"
#include "pwmdac.h"
#define TARGET_RPM 120
#define MAX_PWM 115
#define MIN_PWM 0
#define N_MAX 5000
#define POLE_PAIRS 2
#define IPBCLK 60000000
#define QT_PRESCALER 128
#define PERIOD_TO_SPEED (IPBCLK * 60 / (N_MAX * QT_PRESCALER * POLE_PAIRS))
#define HALL_A_PIN GPIO_Pin_6
#define HALL_B_PIN GPIO_Pin_7
#define HALL_C_PIN GPIO_Pin_8
#define HALL_PORT GPIOB
volatile uint32_t hall_pulse_count = 0;
float actual_rpm = 0;
int error_flag = 0;
extern volatile uint32_t sys_tick_counter;
int last_pin_state_A = -1;
int last_pin_state_B = -1;
int last_pin_state_C = -1;
int hall_sensor_error = 0;
float Kp = 0.8, Ki = 0.15, Kd = 0.1;
float integral = 0, previous_error = 0;
float target_speed = TARGET_RPM;
uint16_t pwm_output = 0;
void GPIO_HallSensor_Init(void);
void EXTI9_5_IRQHandler(void);
float calculate_speed(void);
void display_status(float speed, int error_flag);
uint16_t PID_Controller(float setpoint, float measured_speed);
void TIM3_PWM_Init(uint32_t arr, uint32_t psc);
void set_pwm_duty_cycle(uint16_t pwm_value);
uint32_t millis(void);
void check_hall_sensor_wires(void);
int main(void) {
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
delay_init(168);
uart_init(115200);
LED_Init();
LCD_Init();
KEY_Init();
TIM3_PWM_Init(255, 0);
GPIO_HallSensor_Init();
SysTick_Config(SystemCoreClock / 1000);
POINT_COLOR = RED;
LCD_ShowString(30, 50, 200, 16, 16, "STM32 Motor Control");
LCD_ShowString(30, 70, 200, 16, 16, "Target Speed: 120 RPM");
POINT_COLOR = BLUE;
while (1) {
check_hall_sensor_wires();
actual_rpm = calculate_speed();
pwm_output = PID_Controller(target_speed, actual_rpm);
set_pwm_duty_cycle(pwm_output);
display_status(actual_rpm, hall_sensor_error);
delay_ms(100);
}
}
void TIM3_PWM_Init(uint32_t arr, uint32_t psc) {
GPIO_InitTypeDef GPIO_InitStruct;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
TIM_OCInitTypeDef TIM_OCInitStruct;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOC, &GPIO_InitStruct);
GPIO_PinAFConfig(GPIOC, GPIO_PinSource6, GPIO_AF_TIM3);
TIM_TimeBaseStruct.TIM_Period = arr;
TIM_TimeBaseStruct.TIM_Prescaler = psc;
TIM_TimeBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStruct);
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStruct.TIM_Pulse = arr / 2;
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM3, &TIM_OCInitStruct);
TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM3, ENABLE);
TIM_Cmd(TIM3, ENABLE);
}
void set_pwm_duty_cycle(uint16_t pwm_value) {
if (pwm_value > MAX_PWM) pwm_value = MAX_PWM;
TIM_SetCompare1(TIM3, pwm_value);
}
void GPIO_HallSensor_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct;
EXTI_InitTypeDef EXTI_InitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
GPIO_InitStruct.GPIO_Pin = HALL_A_PIN | HALL_B_PIN | HALL_C_PIN;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(HALL_PORT, &GPIO_InitStruct);
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOB, EXTI_PinSource6);
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOB, EXTI_PinSource7);
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOB, EXTI_PinSource8);
EXTI_InitStruct.EXTI_Line = EXTI_Line6 | EXTI_Line7 | EXTI_Line8;
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStruct);
NVIC_InitStruct.NVIC_IRQChannel = EXTI9_5_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0x00;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0x00;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
}
void EXTI9_5_IRQHandler(void) {
if (EXTI_GetITStatus(EXTI_Line6) != RESET || EXTI_GetITStatus(EXTI_Line7) != RESET || EXTI_GetITStatus(EXTI_Line8) != RESET) {
hall_pulse_count++;
EXTI_ClearITPendingBit(EXTI_Line6 | EXTI_Line7 | EXTI_Line8);
}
}
void check_hall_sensor_wires(void) {
int pin_state_A = GPIO_ReadInputDataBit(HALL_PORT, HALL_A_PIN);
int pin_state_B = GPIO_ReadInputDataBit(HALL_PORT, HALL_B_PIN);
int pin_state_C = GPIO_ReadInputDataBit(HALL_PORT, HALL_C_PIN);
if (last_pin_state_A == pin_state_A && last_pin_state_B == pin_state_B && last_pin_state_C == pin_state_C) {
hall_sensor_error = 1;
} else {
hall_sensor_error = 0;
}
last_pin_state_A = pin_state_A;
last_pin_state_B = pin_state_B;
last_pin_state_C = pin_state_C;
}
float calculate_speed(void) {
static uint32_t last_time = 0;
uint32_t current_time = millis();
float rpm = 0;
if (current_time - last_time >= 200) {
if (hall_pulse_count > 0) {
rpm = (hall_pulse_count / 6.0) * 60.0;
}
hall_pulse_count = 0;
last_time = current_time;
}
return rpm;
}
uint16_t PID_Controller(float setpoint, float measured_speed) {
float error = setpoint - measured_speed;
float derivative;
float output;
integral += error;
derivative = error - previous_error;
output = Kp * error + Ki * integral + Kd * derivative;
previous_error = error;
if (output > MAX_PWM) output = MAX_PWM;
if (output < MIN_PWM) output = MIN_PWM;
return (uint16_t)output;
}
uint32_t millis(void) {
return sys_tick_counter;
}
void display_status(float speed, int error_flag) {
char buffer[16];
sprintf(buffer, "Speed: %0.1f RPM", speed);
LCD_ShowString(30, 100, 200, 16, 16, (u8*)buffer);
if (error_flag == 0) {
LCD_ShowString(30, 130, 200, 16, 16, "Sensors: OK");
} else {
LCD_ShowString(30, 130, 200, 16, 16, "Sensors: ERROR");
}
}
>>>>
2024-09-18 07:43 AM
thanks for your response. I’ve already verified that the Hall sensor is functioning correctly by toggling an external LED with each interrupt, which shows that the pulses are being detected at different motor speeds. The motor can run at various speeds as expected, but the issue is that the actual speed displayed on both the LCD and USART always reads as zero RPM.
While I haven’t added a debug statement to print hall_pulse_count each time the interrupt handler is called, I did confirm via the LED that the pulses are being generated consistently when the motor is running. So, it seems like the pulses are being counted correctly in the interrupt handler.
Given that the sensor is working fine, the problem likely lies with how the speed is being calculated or processed in the calculate_speed() function. Specifically, the Hall sensor pulses are being registered, but they are not translating into a correct RPM value, which is always displayed as zero.