To accomplish the task, we need to build a system that generates a high-frequency PWM signal within the 5 kHz to 10 kHz range. The duty cycle of this PWM signal will be varied using a potentiometer.
This involves:
Note: We can use any of the potentiometers with values between 1kΩ and 10kΩ.
Verification
So, by considering the above points, we can implement the task.
Below are the solutions to the given task using different microcontrollers
We’re using an STM32 NUCLEO-F103RB board, which runs at a 3.3V logic level.
Circuit Diagram
Project Setup in STM32CubeIDE:
SystemClock_Config
).HAL_Init()
– Initializes the HAL library.SystemClock_Config()
– Configures HSI + PLL system clock.MX_GPIO_Init()
– Configures GPIOs.MX_USART2_UART_Init()
– Initializes UART2.MX_ADC1_Init()
– Configures ADC1.MX_TIM1_Init()
– Configures Timer 1 as a PWM generator.ADC Initialization (MX_ADC1_Init):
hadc1.Instance = ADC1;
hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
This configures ADC1 for single-channel operation with right-aligned 12-bit results.
Timer Initialization (MX_TIM1_Init):
htim1.Init.Period = 800-1; // ARR register value
htim1.Init.Prescaler = 0; // No prescaling
sConfigOC.OCMode = TIM_OCMODE_PWM1; // PWM Mode 1
These functions configure timers for PWM generation.
Macro Definitions:
The provided code defines cleaner code organization:
// ADC value range (12-bit ADC)
#define ADC_MAX_VALUE 4095
// PWM duty cycle range
#define PWM_MAX_VALUE 800
Initialization in main():
Before the while loop, the PWM channel is enabled:
// Start PWM generation on Timer 1, Channel 1
// This enables the PWM output on the specified timer channel
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
Main while Loop:
while (1) {
// Start ADC conversion
HAL_ADC_Start(&hadc1);
// Wait for ADC conversion to complete with a timeout of 20ms
HAL_ADC_PollForConversion(&hadc1, 20);
// Read the converted ADC value (0-4095 for 12-bit ADC)
uint16_t adcValue = HAL_ADC_GetValue(&hadc1);
uint16_t pwmVlaue = (PWM_MAX_VALUE * adcValue) / ADC_MAX_VALUE;
// Update PWM duty cycle based on ADC reading
// This creates a direct relationship between the analog input and the PWM output
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, pwmVlaue);
// Small delay to stabilize the system and prevent overwhelming the ADC
HAL_Delay(1);
}
Maintaining a constant PWM frequency while adjusting duty cycle ensures motor speed control is smooth and consistent.
The complete STM32CubeIDE project (including .ioc
configuration, main.c
, and HAL files) is available here:
We are using the ESP32 DevKit v4 development board and programming it using the Arduino IDE.
The ESP32’s MCPWM unit is a dedicated motor-control peripheral, providing high-resolution PWM, flexible frequency, dead-time insertion, and complementary outputs, unlike LEDC, which is optimized for LEDs.
Circuit Diagram
#include "driver/mcpwm.h"
#define MOTOR_PIN 14 // PWM output to motor driver input
#define POT_PIN 34 // Potentiometer input (ADC1, GPIO34 is input-only)
void setup() {
Serial.begin(115200);
//Configure PWM on GPIO2
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, MOTOR_PIN);
//Initialize PWM configuration
mcpwm_config_t pwm_config;
pwm_config.frequency = 10000; // 10 kHz PWM (quiet, safe for motor)
pwm_config.cmpr_a = 0; // Initial duty cycle = 0%
pwm_config.cmpr_b = 0; // Not used
pwm_config.duty_mode = MCPWM_DUTY_MODE_0; // Active high
pwm_config.counter_mode = MCPWM_UP_COUNTER; // Count-up mode
mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config);
//Configure ADC
analogReadResolution(12); // 12-bit ADC (0–4095)
}
void loop() {
// Read potentiometer (0–4095)
int potValue = analogRead(POT_PIN);
// Convert to duty cycle (0–100%)
float dutyCycle = ((float)potValue / 4095.0) * 100.0;
// Update motor speed (apply duty cycle)
mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, dutyCycle);
mcpwm_set_duty_type(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, MCPWM_DUTY_MODE_0);
// Debugging
// Serial.printf("Pot: %d Duty: %.2f%%\n", potValue, dutyCycle);
delay(10); // Small delay for stable serial output
}
PWM Setup
MOTOR_PIN (GPIO14)
is configured as MCPWM output (MCPWM0A
) to drive the motor.ADC Setup
POT_PIN (GPIO34)
reads a potentiometer.Loop Operation
potValue
.mcpwm_set_duty()
→ motor speed changes accordingly.mcpwm_set_duty_type()
ensures duty cycle mode is active-high.We are using the Arduino UNO development board and programming it using the Arduino IDE.
First of all, let's do the hardware connection,
To control the duty cycle of PWM:
map()
function.Default PWM Frequency:
analogWrite()
).How to Change PWM Frequency on Arduino UNO:
To generate a PWM signal between 5 kHz and 10 kHz on Pin 3 using Timer2,
Formula:
PWM Frequency = Clock Frequency / ( Prescaler * 256 )
Where Clock Frequency = 16 MHz (Arduino clock speed).
Prescaler Calculation:
Prescaler = Clock Frequency / ( PWM Frequency * 256 )
Resulting Frequency:
With prescaler = 8.
PWM Frequency = 16000000/ ( 8 * 256) = ~ 7.8 kHz
Timer2 Configuration:
TCCR2A = (1 << WGM20) | (1 << WGM21) | (1 << COM2B1); // Fast PWM, non-inverted
TCCR2B = (TCCR2B & 0b11111000) | 0x02; // Prescaler = 8
Final PWM Frequency:
Achieved frequency = 7.8 kHz, within the desired range.
#define pwmPin 3 // PWM output on Pin 3
#define potPin A0 // Potentiometer connected to Analog Pin A0
/*
Setup function: Configures PWM on Pin 3 using Timer2
*/
void setup() {
pinMode(pwmPin, OUTPUT); // Set Pin 3 as output
/*
Configure Timer2 for Fast PWM mode with a frequency of ~7.8 kHz
- Fast PWM: WGM20 and WGM21 set
- Non-inverted PWM: COM2B1 set
- Prescaler: 8 (TCCR2B = 0x02)
*/
TCCR2A = (1 << WGM20) | (1 << WGM21) | (1 << COM2B1);
TCCR2B = (TCCR2B & 0b11111000) | 0x02;
}
/*
Main loop: Reads potentiometer and adjusts PWM duty cycle
*/
void loop() {
// Read analog value (0-1023) from potentiometer
int potValue = analogRead(potPin);
// Map analog value to PWM duty cycle (0-255)
int pwmValue = map(potValue, 0, 1023, 0, 255);
// Set PWM duty cycle for Pin 3
OCR2B = pwmValue;
// Add a small delay to stabilize readings
delay(10);
}
PWM with 20% Duty Cycle :
PWM with 70% Duty Cycle :