المؤقتات و PWM: التحكم بالزمن والسرعة والسطوع
المؤقتات في المتحكم: أكثر من مجرد عدّ
المؤقتات (Timers) من أهم الطرفيات في المتحكم الدقيق. هي عدّادات إلكترونية تعمل بشكل مستقل عن المعالج، وتُستخدم لإنشاء تأخيرات دقيقة، توليد إشارات PWM، قياس ترددات، وعدّ نبضات من حساسات دوران (Encoders).
في STM32F4 مثلاً، يوجد حتى 14 مؤقتاً بقدرات مختلفة:
- مؤقتات أساسية (TIM6, TIM7): عدّ فقط، بدون أطراف خارجية
- مؤقتات عامة (TIM2-TIM5): PWM، قياس، عدّ نبضات
- مؤقتات متقدمة (TIM1, TIM8): مخصصة للتحكم بالمحركات مع حماية
كيف يعمل المؤقت
المؤقت يعدّ من 0 إلى قيمة قصوى (Auto-Reload). عند الوصول للقيمة القصوى، يعود لـ 0 ويولّد حدث تجاوز (Overflow). سرعة العدّ تحددها ساعة المتحكم مقسومة على معامل القسمة (Prescaler).
تردد المؤقت = تردد الساعة / (Prescaler + 1)
فترة التجاوز = (Auto-Reload + 1) / تردد المؤقت
مؤقت التأخير: إنشاء فترات زمنية دقيقة
بدلاً من HAL_Delay() الذي يوقف المعالج، يمكن استخدام مؤقت لإنشاء تأخيرات دقيقة بالميكروثانية:
// مؤقت تأخير بالميكروثانية باستخدام TIM6
void delay_us_init(void) {
__HAL_RCC_TIM6_CLK_ENABLE();
TIM6->PSC = (SystemCoreClock / 1000000) - 1; // 1 MHz
TIM6->ARR = 0xFFFF;
TIM6->CR1 |= TIM_CR1_CEN;
}
void delay_us(uint16_t us) {
TIM6->CNT = 0;
while (TIM6->CNT < us);
}
مؤقت دوري بدون حجب المعالج
الطريقة الأفضل هي استخدام المقاطعات مع المؤقت:
// تنفيذ دالة كل 100 ميلي ثانية
void timer_periodic_init(void) {
__HAL_RCC_TIM7_CLK_ENABLE();
TIM7->PSC = (SystemCoreClock / 10000) - 1; // 10 KHz
TIM7->ARR = 1000 - 1; // 100 ms
TIM7->DIER |= TIM_DIER_UIE;
NVIC_EnableIRQ(TIM7_IRQn);
TIM7->CR1 |= TIM_CR1_CEN;
}
void TIM7_IRQHandler(void) {
if (TIM7->SR & TIM_SR_UIF) {
TIM7->SR &= ~TIM_SR_UIF;
// هنا تُنفَّذ المهمة الدورية
read_sensors();
}
}
PWM: التحكم بعرض النبضة
PWM (Pulse Width Modulation) يولّد إشارة مربعة بتردد ثابت ونسبة تشغيل (Duty Cycle) متغيرة. نسبة التشغيل تحدد متوسط الجهد على المخرج.
- Duty 0%: المخرج دائماً LOW (0V)
- Duty 50%: المخرج HIGH نصف الوقت (1.65V متوسط)
- Duty 100%: المخرج دائماً HIGH (3.3V)
TIM_HandleTypeDef htim3;
void pwm_init(uint32_t freq_hz) {
__HAL_RCC_TIM3_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitTypeDef gpio = {0};
gpio.Pin = GPIO_PIN_6; // PA6 = TIM3_CH1
gpio.Mode = GPIO_MODE_AF_PP;
gpio.Speed = GPIO_SPEED_FREQ_HIGH;
gpio.Alternate = GPIO_AF2_TIM3;
HAL_GPIO_Init(GPIOA, &gpio);
uint32_t timer_clk = SystemCoreClock;
uint32_t period = timer_clk / freq_hz;
htim3.Instance = TIM3;
htim3.Init.Prescaler = 0;
htim3.Init.Period = period - 1;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
HAL_TIM_PWM_Init(&htim3);
TIM_OC_InitTypeDef oc = {0};
oc.OCMode = TIM_OCMODE_PWM1;
oc.Pulse = 0;
HAL_TIM_PWM_ConfigChannel(&htim3, &oc, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
}
void pwm_set_duty(float duty_percent) {
uint32_t period = htim3.Init.Period + 1;
uint32_t pulse = (uint32_t)(period * duty_percent / 100.0f);
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, pulse);
}
التحكم بسرعة محرك DC عبر PWM
محرك DC يحتاج تيار أعلى مما يوفره المتحكم. نستخدم ترانزستور MOSFET أو دائرة H-Bridge (مثل L298N) كمرحلة قدرة. تردد PWM المناسب لمحركات DC هو 20-25 KHz (فوق نطاق السمع).
#define MOTOR_PWM_FREQ 25000 // 25 KHz
void motor_init(void) {
pwm_init(MOTOR_PWM_FREQ);
// طرف الاتجاه
GPIO_InitTypeDef gpio = {0};
gpio.Pin = GPIO_PIN_7;
gpio.Mode = GPIO_MODE_OUTPUT_PP;
HAL_GPIO_Init(GPIOA, &gpio);
}
void motor_set_speed(int8_t speed_percent) {
if (speed_percent >= 0) {
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_SET); // أمام
pwm_set_duty(speed_percent);
} else {
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_RESET); // خلف
pwm_set_duty(-speed_percent);
}
}
التحكم بزاوية محرك Servo
محرك Servo يحتاج إشارة PWM بتردد 50 Hz. عرض النبضة يحدد الزاوية:
- 1 ms (5% duty): زاوية 0 درجة
- 1.5 ms (7.5% duty): زاوية 90 درجة (الوسط)
- 2 ms (10% duty): زاوية 180 درجة
void servo_init(void) {
pwm_init(50); // 50 Hz
}
void servo_set_angle(float angle_deg) {
// تحويل الزاوية (0-180) إلى نسبة تشغيل (2.5%-12.5%)
float duty = 2.5f + (angle_deg / 180.0f) * 10.0f;
pwm_set_duty(duty);
}
مثال عملي: تحكم PID بسيط بسرعة مروحة
نظام تحكم PID يضبط سرعة مروحة تبريد بناءً على درجة حرارة المحرك. الهدف: الحفاظ على 60 درجة مئوية.
typedef struct {
float kp, ki, kd;
float setpoint;
float integral;
float prev_error;
float output_min, output_max;
} PID_Controller;
void pid_init(PID_Controller *pid, float kp, float ki, float kd, float setpoint) {
pid->kp = kp;
pid->ki = ki;
pid->kd = kd;
pid->setpoint = setpoint;
pid->integral = 0;
pid->prev_error = 0;
pid->output_min = 0;
pid->output_max = 100;
}
float pid_update(PID_Controller *pid, float measured, float dt) {
float error = pid->setpoint - measured;
pid->integral += error * dt;
float derivative = (error - pid->prev_error) / dt;
pid->prev_error = error;
float output = pid->kp * error + pid->ki * pid->integral + pid->kd * derivative;
// تحديد الحدود ومنع تراكم integral
if (output > pid->output_max) {
output = pid->output_max;
pid->integral -= error * dt;
} else if (output < pid->output_min) {
output = pid->output_min;
pid->integral -= error * dt;
}
return output;
}
int main(void) {
HAL_Init();
adc_init();
motor_init();
PID_Controller fan_pid;
pid_init(&fan_pid, 5.0f, 0.5f, 1.0f, 60.0f);
while (1) {
float temp = read_motor_temperature();
float fan_speed = pid_update(&fan_pid, temp, 0.1f);
motor_set_speed((int8_t)fan_speed);
HAL_Delay(100);
}
}
الخلاصة
المؤقتات و PWM هما أدوات التحكم بالزمن والطاقة في النظام المدمج. من تأخيرات دقيقة بالميكروثانية إلى تحكم PID بمحركات صناعية، هذه الأدوات أساسية في كل تطبيق أتمتة. في الدرس القادم سنتعلم المقاطعات وأنظمة التشغيل الحقيقية FreeRTOS لبناء أنظمة متعددة المهام.