Closed-Loop DC Motor Velocity Control with Optical Quadrature Encoder
Harness hardware microcontroller timer encoder modes to decode high-resolution quadrature signals and regulate motor velocity under varying mechanical loads.
2. Prerequisites & Background Theory:
H-Bridge MOSFET commutation, quadrature phase decoding, back-EMF dynamics, and discrete frequency measurement.
Goal & Engineering Objectives
Maintain constant rotational shaft velocity of a high-torque DC gearmotor under severe dynamic load disturbances using hardware quadrature decoding and high-frequency PWM switching.
Governing Mathematical Formulation & Physics
System Architecture & Block Flow Diagram
[12V DC Motor with Optical Encoder] <==== (Shaft Coupling) ====> [Mechanical Load]
| ^
(Enc A / B) (High-Side / Low-Side PWM)
v |
[STM32 Timer 3] [BTS7960 / L298N H-Bridge]
(Encoder Mode) ^
| |
+---> [Discrete PID Task] ---+
Required Hardware Bill of Materials (BOM)
| Hardware Component | Qty | Engineering Specification |
|---|---|---|
| 12V DC Planetary Gearmotor with Integrated Encoder | 1 | 500 PPR optical disk, 12V DC, 450 RPM nominal |
| BTS7960 High-Current 43A H-Bridge Motor Driver | 1 | Integrated low-side current sensing and thermal protection |
| STM32F401 "Black Pill" Development Board | 1 | Hardware Timer quadrature encoder input |
| 12V 10A Benchtop DC Power Supply | 1 | Regulated motor drive voltage source |
| Schottky Flyback Diodes (1N5822) | 4 | Ultra-fast reverse recovery time (< 30 ns) |
Wiring, Pinout & Schematics Mapping
Firmware Implementation & Algorithms
// Configure STM32 Hardware Timer for 4X Quadrature Decoding
void init_timer_encoder(void) {
RCC->APB1ENR |= RCC_APB1ENR_TIM3EN; // Enable TIM3 clock
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; // Enable GPIOA clock
// Configure PA6 and PA7 as Alternate Function AF2 (TIM3)
GPIOA->MODER |= (2 << GPIO_MODER_MODER6_Pos) | (2 << GPIO_MODER_MODER7_Pos);
GPIOA->AFR[0] |= (2 << GPIO_AFRL_AFSEL6_Pos) | (2 << GPIO_AFRL_AFSEL7_Pos);
// Set SMS = 011: Encoder mode 3 (counts both TI1 and TI2 edges for 4X resolution)
TIM3->SMCR |= TIM_SMCR_SMS_0 | TIM_SMCR_SMS_1;
TIM3->ARR = 0xFFFF; // 16-bit auto-reload
TIM3->CR1 |= TIM_CR1_CEN; // Start Timer 3
}
int16_t get_encoder_delta(void) {
static uint16_t last_count = 0;
uint16_t current_count = TIM3->CNT;
int16_t delta = (int16_t)(current_count - last_count);
last_count = current_count;
return delta;
}