سیستم های نهفته برخی از فعالیت ها را بر اساس زمان انجام می دهند. برای تاخیرهای بسیار ساده و غیر دقیق، یک حلقه می تواند این کار را انجام دهد، اما استفاده از هسته CPU برای انجام فعالیت های مربوط به زمان هرگز راه حل هوشمندانه ای نیست. به همین دلیل، همه میکروکنترلرها تجهیزات جانبی سخت افزاری اختصاصی به نام تایمر را ارائه می دهند. تایمرها نه تنها مولد زمان هستند، بلکه چندین ویژگی اضافی را نیز برای تعامل با هسته Cortex-M و سایر تجهیزات جانبی، داخلی و خارجی MCU ارائه می دهند.
بسته به خانواده و پکیج مورد استفاده، میکروکنترلرهای STM32 دارای تعداد متغیری از تایمرها می باشند که هر کدام دارای ویژگی های خاصی هستند. برخی می توانند تا 14 تایمر مستقل را ارائه دهند. تایمرها تقریباً در تمام سری های STM32 به صورت یکسان پیاده سازی و در 9 دسته مجزا گروه بندی می شوند. مرتبط ترین آنها عبارتند از: تایمرهای پایه، عمومی و پیشرفته.
تایمر در میکروهای STM32 یک واحد جانبی پیشرفته است که طیف گستردهای از قابلیت ها را ارائه میدهد. میکروکنترلر STM32F303CC دارای 11 تایمر است.
- MasterSlaveMode: برای فعال/غیرفعال کردن حالت master/slave یک تایمر استفاده می شود. می تواند مقادیر TIM_MASTERSLAVEMODE_ENABLE یا TIM_MASTERSLAVEMODE_DISABLE را در بر گیرد.
در ادامه نحوه پیکربندی TIM1 و TIM3 را در حالت آبشاری با تنظیم TIM1 به عنوان master برای تایمر TIM3 نشان میدهیم. TIM1 به عنوان منبع کلاک برای TIM3 از طریق خط ITR0 استفاده می شود. علاوه بر این، TIM1 به گونه ای پیکربندی شده است که روی یک رویداد خارجی در خط TI1FP1 شروع به شمارش می کند، که در Nucleo-F030 مربوط به پین PA8 است: زمانی که پایه PA8 بالا می رود، TIM1 شروع به شمارش می کند و سپس تایمر TIM3 را از طریق خط ITR0. تغذیه (کلاک) می کند.
- MasterSlaveMode: برای فعال/غیرفعال کردن حالت master/slave یک تایمر استفاده می شود. می تواند مقادیر TIM_MASTERSLAVEMODE_ENABLE یا TIM_MASTERSLAVEMODE_DISABLE را در بر گیرد.
در ادامه نحوه پیکربندی TIM1 و TIM3 را در حالت آبشاری با تنظیم TIM1 به عنوان master برای تایمر TIM3 نشان میدهیم. TIM1 به عنوان منبع کلاک برای TIM3 از طریق خط ITR0 استفاده می شود. علاوه بر این، TIM1 به گونه ای پیکربندی شده است که روی یک رویداد خارجی در خط TI1FP1 شروع به شمارش می کند، که در Nucleo-F030 مربوط به پین PA8 است: زمانی که پایه PA8 بالا می رود، TIM1 شروع به شمارش می کند و سپس تایمر TIM3 را از طریق خط ITR0. تغذیه (کلاک) می کند.
- MasterSlaveMode: برای فعال/غیرفعال کردن حالت master/slave یک تایمر استفاده می شود. می تواند مقادیر TIM_MASTERSLAVEMODE_ENABLE یا TIM_MASTERSLAVEMODE_DISABLE را در بر گیرد.
در ادامه نحوه پیکربندی TIM1 و TIM3 را در حالت آبشاری با تنظیم TIM1 به عنوان master برای تایمر TIM3 نشان میدهیم. TIM1 به عنوان منبع کلاک برای TIM3 از طریق خط ITR0 استفاده می شود. علاوه بر این، TIM1 به گونه ای پیکربندی شده است که روی یک رویداد خارجی در خط TI1FP1 شروع به شمارش می کند، که در Nucleo-F030 مربوط به پین PA8 است: زمانی که پایه PA8 بالا می رود، TIM1 شروع به شمارش می کند و سپس تایمر TIM3 را از طریق خط ITR0. تغذیه (کلاک) می کند.
int main(void) {
13 HAL_Init();
14 15 Nucleo_BSP_Init();
16 MX_TIM1_Init();
17 MX_TIM3_Init();
18 19 HAL_TIM_Base_Start_IT(&htim3);
20 21 while (1);
22 }
23
24 void MX_TIM1_Init(void) {
25 TIM_ClockConfigTypeDef sClockSourceConfig;
26 TIM_MasterConfigTypeDef sMasterConfig;
27 TIM_SlaveConfigTypeDef sSlaveConfig;
28
29 htim1.Instance = TIM1;
30 htim1.Init.Prescaler = 47999;
31 htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
32 htim1.Init.Period = 249;
33 htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
34 htim1.Init.RepetitionCounter = 0;
35 HAL_TIM_Base_Init(&htim1);
36
37 sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
38 HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig);
39
40 sSlaveConfig.SlaveMode = TIM_SLAVEMODE_TRIGGER;
41 sSlaveConfig.InputTrigger = TIM_TS_TI1FP1;
42 sSlaveConfig.TriggerPolarity = TIM_TRIGGERPOLARITY_RISING;
43 sSlaveConfig.TriggerFilter = 15;
44 HAL_TIM_SlaveConfigSynchronization(&htim1, &sSlaveConfig);
45
46 sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
47 sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_ENABLE;
48 HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig);
49 }
50
51 void MX_TIM3_Init(void) {
52 TIM_SlaveConfigTypeDef sSlaveConfig;
53
54 htim3.Instance = TIM3;
55 htim3.Init.Prescaler = 0;
56 htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
57 htim3.Init.Period = 1;
58 htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
59 HAL_TIM_Base_Init(&htim3);
60
61 sSlaveConfig.SlaveMode = TIM_SLAVEMODE_EXTERNAL1;
62 sSlaveConfig.InputTrigger = TIM_TS_ITR0;
63 HAL_TIM_SlaveConfigSynchronization(&htim3, &sSlaveConfig);
64
65 HAL_NVIC_SetPriority(TIM3_IRQn, 0, 0);
66 HAL_NVIC_EnableIRQ(TIM3_IRQn);
67 }
68
69 void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* htim_base) {
70 GPIO_InitTypeDef GPIO_InitStruct;
71 if(htim_base->Instance==TIM3) {
72 __HAL_RCC_TIM3_CLK_ENABLE();
73 }
74
75 if(htim_base->Instance==TIM1) {
76 __HAL_RCC_TIM1_CLK_ENABLE();
77
78 GPIO_InitStruct.Pin = GPIO_PIN_8;
79 GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
80 GPIO_InitStruct.Pull = GPIO_PULLDOWN;
81 GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
82 GPIO_InitStruct.Alternate = GPIO_AF2_TIM1;
83 HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
84 }
85 }
معرفی تایمر
تایمر یک شمارنده با فرکانس شمارش کسری از منبع کلاک خود است. سرعت شمارش را می توان با استفاده از یک prescaler اختصاصی برای هر تایمر کاهش داد. بسته به نوع تایمر، می توان آن را با استفاده از کلاک داخلی (که از گذرگاهی که به آن متصل است گرفته شده است)، توسط یک منبع کلاک خارجی یا توسط تایمر دیگری که به عنوان “master” استفاده می شود، کلاک کرد.
معمولاً یک تایمر از صفر تا یک مقدار داده شده را می شمارد که نمی تواند از حداکثر مقدار بدون علامت آن بیشتر باشد (مثلاً یک تایمر 16 بیتی وقتی شمارنده به 65535 می رسد سرریز می شود) اما می تواند برعکس نیز شمارش کند.
پیشرفته ترین تایمرها در میکروکنترلر STM32 چندین ویژگی دارند:
- می توان از آنها به عنوان مولد پایه زمانی استفاده کرد (که ویژگی مشترک تمام تایمرهای STM32 است).
- می توان از آنها برای اندازه گیری فرکانس یک رویداد خارجی (input capture mode) استفاده کرد.
- می توان از آنها برای کنترل شکل موج خروجی، یا نشان دادن زمان سپری شده یک دوره زمانی (حالت مقایسه خروجی). استفاده کرد.
- حالت یک پالس (OPM) One pulse mode مورد خاصی از حالت input capture mode and output compare mode است که اجازه می دهد تا شمارنده در پاسخ به یک محرک راه اندازی شود و پس از یک تاخیر قابل برنامه ریزی، پالسی با طول قابل برنامه ریزی تولید کند.
- می توان از آنها برای تولید سیگنال های PWM در حالت edge-aligned یا حالت center-aligned به طور مستقل در هر کانال (حالت PWM). استفاده کرد.
در برخی از MCU های STM32 (به ویژه از STM32F3 و سری های اخیر STM32L4)، برخی تایمرها می توانند سیگنال های PWM center-aligned را با یک تاخیر قابل برنامه ریزی و تغییر فاز تولید کنند.
بسته به نوع تایمر، تایمر میتواند وقفه ها یا درخواست های DMA را در موارد زیر ایجاد کند:
- رویدادهای به روزرسانی
–سرریز/زیر جریان شمارنده
– مقداردهی شمارنده
– دیگر….
- تریگر یا تحریک
–شروع/توقف شمارش
– مقداردهی شمارنده
– دیگر….
- مقایسه ورودی/خروجی
معرفی دسته بندی تایمر در میکروکنترلر STM32
تایمرهای STM32 عمدتاً می توانند در 9 دسته گروه بندی شوند. نگاهی کوتاه به هر یک از آنها خواهیم داشت:
- Basic timers: تایمرهای این دسته ساده ترین شکل تایمرها در MCU های STM32 هستند. تایمرهای 16 بیتی ای که به عنوان مولد پایه زمانی استفاده می شوند و پایه های خروجی/ورودی ندارند. تایمرهای پایه برای تغذیه واحد جانبی DAC نیز استفاده میشوند، زیرا رویداد بهروزرسانی update event آنها میتواند درخواستهای DMA را برای DAC راهاندازی کند (به همین دلیل معمولاً در میکرو های STM32 ای موجود هستند که حداقل یک DAC را ارائه میدهند). تایمرهای پایه را می توان به عنوان “master” برای تایمرهای دیگر نیز استفاده کرد.
- General purpose timers : تایمرهای 16/32 بیتی (بسته به سری STM32) که ویژگی های کلاسیکی که از یک میکروکنترلر مدرن انتظار می رود اجرا کند را ، ارائه می دهند. آنها در هر کاربردی برای output compare (تولید زمان و تأخیر)، حالت تک پالس One-Pulse ، گرفتن ورودی input capture (برای اندازه گیری فرکانس سیگنال خارجی)، رابط سنسور (encoder, hall سنسور) و غیره استفاده می شوند. بدیهی است که یک تایمر عمومی می تواند مانند یک تایمر پایه Basic timer به عنوان مولد پایه زمانی نیز، استفاده شود. تایمرهای این دسته، کانال های ورودی/خروجی چهارگانه قابل برنامه ریزی ای را ارائه می دهند.
– 1 کانال / 2 کانال: آنها دو زیر گروه از تایمرهای عمومی هستند که فقط یک / دو کانال ورودی / خروجی را ارائه می دهند.
– 1 کانال / 2 کانال با یک خروجی: مانند نوع قبلی بوده ، اما دارای یک مولد زمان مرده dead time در یک کانال خود می باشد. این امر باعث می شود تا سیگنال های مکمل با یک پایه زمانی مستقل از تایمرهای پیشرفته داشته باشید.
- تایمرهای پیشرفته: این تایمرها کاملترین تایمرهای میکروکنترلر STM32 هستند. علاوه بر ویژگیهای موجود در یک تایمر عمومی، آنها شامل چندین ویژگی مرتبط با کنترل موتور و کاربردهای تبدیل توان دیجیتال هستند: سه سیگنال مکمل با درج زمان مرده dead time و خاموش شدن اضطراری ورودی.
- تایمر با وضوح بالا: تایمر با وضوح بالا (HRTIM1) تایمر ویژه ای است که توسط برخی میکروکنترلرهای سری STM32F3 (سری اختصاص داده شده به کنترل موتور و تبدیل توان) ارائه می شود.با استفاده از این تایمر، امکان تولید سیگنال های دیجیتال با زمان بندی دقت بالا، مانند PWM یا پالس های phase-shifted فراهم می شود. این تایمر شامل 6 تایمر فرعی، 1 Master و 5 slave، مجموعا 10 خروجی با وضوح بالا است که هر جفت از آنها با درج زمان مرده dead time می توانند با هم کوپل شوند. همچنین دارای 5 ورودی خطا برای اهداف حفاظتی و 10 ورودی برای رسیدگی به رویدادهای خارجی مانند محدودیت جریان، سوئیچینگ ولتاژ صفر یا جریان صفر است.
تایمر HRTIM1 از یک هسته کرنل با کلاک 144 مگاهرتز و خطوط تاخیر ساخته شده است. خطوط تاخیری با کنترل حلقه بسته رزولوشن 217ps را فارغ از ولتاژ، دما یا انحراف فرآیند تولید تراشه به تراشه، تضمین میکنند. وضوح بالا در 10 خروجی در همه حالتهای عملیاتی موجود است: duty cycle متغیر، فرکانس متغیر و زمان روشن ثابت.
- تایمرهای کم مصرف: تایمرهای این گروه مخصوصاً برای کاربردهای کم مصرف طراحی شده اند. به لطف تنوع منابع کلاک، این تایمرها می توانند در تمام حالت های power modes (به جز حالت Standby) به کار خود ادامه دهند. با توجه به این قابلیت حتی بدون منبع کلاک داخلی نیز، تایمرهای کم مصرف را می توان به عنوان یک “پالس شمار” استفاده کرد که می تواند در برخی از کاربرد ها مفید باشد. همچنین این قابلیت را دارند که سیستم را از حالت های کم مصرف low-power بیدار کنند.
تایمر های پایه (Basic Timers)
تایمرهای پایه TIM6، TIM7 و TIM18 ساده ترین تایمرهای موجود در میکروکنترلرهای STM32 هستند. حتی اگر آنها توسط همه MCU های STM32 ارائه نشده باشند. دوباره تاکید می کنیم که تایمرهای STM32 به گونه ای طراحی شده اند که تایمرهای پیشرفته تر همان قابلیت های تایمرهای ساده تر و ضعیف تر را (به همان روش) اجرا می کنند. در واقع استفاده از یک تایمر general purpose به همان شیوه تایمر basic اولیه کاملاً ممکن است.کتابخانه CubeHAL تمامی پیاده سازی سخت افزاری را انجام می دهد. عملیات های پایه ، ساده و ابتدایی در تمام تایمرها با استفاده از توابع HAL_TIM_Base_XXX انجام می شود.
یک تایمر تکی با استفاده از نمونه ای از ساختار C به نام TIM_HandleTypeDef ارجاع داده شده که به شکل زیر تعریف می شود:
typedef struct {
TIM_TypeDef *Instance; /* Pointer to timer descriptor */
TIM_Base_InitTypeDef Init; /* TIM Time Base required parameters */
HAL_TIM_ActiveChannel Channel; /* Active channel */
DMA_HandleTypeDef *hdma[7]; /* DMA Handlers array */
HAL_LockTypeDef Lock; /* Locking object */
__IO HAL_TIM_StateTypeDef State; /* TIM operation state */
} TIM_HandleTypeDef;
حال میخواهیم مهمترین بخش های این ساختار را عمیق تر بررسی کنیم:
Instance: اشاره گری به توصیفگر TIM است که قرار است از آن استفاده کنیم. برای مثال، TIM6 یکی از تایمرهای اساسی موجود در اکثر میکروکنترلرهای STM32 است.
Init: نمونه ای از ساختار C به نام TIM_Base_InitTypeDef است که برای پیکربندی عملکردهای پایه تایمر استفاده می شود.
Channel: تعداد کانال های فعال در تایمرها را نشان می دهد که یک یا چند کانال ورودی/خروجی را ارائه می دهند (این مورد در تایمرهای basic نیست). در واقع می تواند یک یا چند مقدار را از enum HAL_TIM_ActiveChannel داشته باشد.
*hdma[7]: آرایه ای است که شامل اشاره گرهایی به توصیفگر DMA_HandleTypeDef برای درخواست های DMA مرتبط با تایمر است. همانطور که بعداً خواهیم دید، یک تایمر می تواند تا هفت درخواست DMA را ایجاد کند که برای هدایت ویژگی های آن استفاده می شود.
State: این بخش به صورت داخلی توسط HAL برای پیگیری وضعیت تایمر استفاده می شود.
console.log( 'Code is Poetry' );
Prescaler: کلاک تایمر را بر ضریبی از 1 تا 65535 تقسیم می کند (به این معنی که رجیستر prescaler دارای 16 بیت وضوح است). برای مثال، اگر گذرگاهی که تایمر به آن وصل است با فرکانس 48 مگاهرتز کار کند، مقدار prescaler برابر با 48 فرکانس شمارش را به 1 مگاهرتز کاهش می دهد.
CounterMode: جهت شمارش تایمر را مشخص می کند و می تواند یکی از مقادیر جدول زیر را در بر بگیرد. برخی از حالت های شمارش فقط در تایمرهای عمومی general purpose و پیشرفته advanced در دسترس هستند. برای تایمرهای basic اولیه، فقط TIM_COUNTERMODE_UP تعریف شده است.
Period: حداکثر مقدار را برای شمارنده تایمر ، قبل از شروع مجدد شمارش، تنظیم می کند. این فیلد می تواند مقداری از x1 تا xFFFF (65535) برای تایمرهای 16 بیتی و از x1 تا xFFFF FFFF برای تایمرهای TIM2 و TIM5 در میکروهایی که آنها را به عنوان تایمرهای 32 بیتی پیاده سازی می کنند، در بر بگیرد. اگر Period روی x0 تنظیم شده باشد، تایمر شروع نمی شود.
ClockDivision: این bit-field نسبت تقسیم بین فرکانس کلاک داخلی تایمر و کلاک نمونه برداری که توسط فیلترهای دیجیتال روی پین های ETRx و TIx استفاده می شود را نشان می دهد و می تواند یک مقدار را از جدول زیر در بر بگیرد و فقط در تایمرهای عمومی و پیشرفته در دسترس است. در ادامه ، فیلترهای دیجیتال روی پایه های ورودی یک تایمر را مطالعه خواهیم کرد. این فیلد توسط مولد dead time زمان مرده نیز استفاده می شود .
RepetitionCounter: هر تایمر دارای یک رجیستر به روز رسانی update register خاص است که وضعیت overflow/underflow تایمر را پیگیری می کند. همانطور که در ادامه خواهیم دید، می تواند یک IRQ خاص نیز ایجاد کند. RepetitionCounter میگوید که چند بار تایمر قبل از SET شدن رجیستر بهروزرسانی، overflow/underflow داشته باشد و رویداد event مربوطه (در صورت فعال بودن) انجام میشود. RepetitionCounter فقط در تایمرهای پیشرفته موجود است.
تنظیمات پروژه تایمر
در این قسمت قصد داریم 2 تایمر را به ترتیب با 250 میلی ثانیه و 1 ثانیه راه اندازی کرده و با هر کدام یک متغیر را بشماریم. راهاندازی تایمرهای دیگر نیز به همین روال است. برای این منظور مطابق شکل زیر، در قسمت تایمرها و بخش TIM1، منبع کلاک به عنوان کلاک داخلی تنظیم شده است. در قسمت تنظیمات پارامتر، Prescaler را روی 7999 و Counter Period را روی 249 قرار میدهیم. سپس از قسمت تنظیمات NVIC، وقفه آپدیت TIM1 را فعال میکنیم.
برای فعالسازی TIM6 آیکون مربوطه را انتخاب میکنیم. در قسمت تنظیمات پارامتر، Prescaler را روی 7999 و Counter Period را روی 999 قرار میدهیم. سپس از قسمت تنظیمات NVIC، وقفه سراسری TIM6 را همانطور که در شکل زیر نشان داده شده است؛ فعال میکنیم.
همانطور که در شکل زیر نشان داده شده است، فرکانسهای APB1 (TIM6) و تایمر APB2 (TIM1) 8 مگاهرتز انتخاب شدهاند. برای تولید وقفههای TIM1 در هر 250 میلی ثانیه، مقدار Prescaler برابر با 7999 را انتخاب کنید تا فرکانس 8 مگاهرتز TIM1 را به 8000 (از 0 تا 7999) تقسیم کنید تا فرکانس 1 کیلوهرتز با دوره 1 میلی ثانیه تولید شود. برای Counter Period ،249 (از 0 تا 249) را برای تولید 250 میلی ثانیه انتخاب کنید. به طور مشابه، برای تولید وقفه های TIM6 در هر 1 ثانیه، مقدار Prescaler را برابر با 7999 انتخاب کنید تا فرکانس 8 مگاهرتز TIM6 را به 8000 (از 0 تا 7999) تقسیم کنید تا فرکانس 1 کیلوهرتز با دوره 1 میلی ثانیه تولید شود. همچنین، برای Counter Period، 999 (از 0 تا 999) را برای تولید 1s انتخاب کنید. همانطور که در شکل زیر نشان داده شده است، برای تعیین اولویت وقفه از قسمت پیکربندی NVIC، 1 را برای TIM1 و 2 را برای TIM6 Preemption Priority انتخاب کنید.
پس از وارد کردن تنظیمات بالا، از تب مدیریت پروژه، کد پروژه را تولید کنید:
پس از ایجاد پروژه و باز کردن فایل مربوطه بدنه تایمرهای 1 و 6 در داخل فایل stm32f1xx_hal_tim.c هستند که در شکل زیر نشان داده شده است:
تایمر 2:
توابع سرویس به وقفههای تایمر نیز در داخل stm32f1xx_it.c می باشند:
همانطور که قبلاً ذکر شد، ما در نظر داریم دو تایمر (TIM1 و TIM6) را راهاندازی کنیم و با TIM1 یک وقفه 250 میلی ثانیه و با TIM6 یک وقفه 1 ثانیه ایجاد میکنیم و سپس با هر تایمر یک متغیر را بشماریم. همچنین با تعریف یک متغیر و تغییر آن در وقفه 250 میلی ثانیه، LED_USER را در متن برنامه تغییر وضعیت دهیم. برای این منظور ابتدا متغیرهای TIMER1_COUNTER ,TIMER6_COUNTER , LED_TOGGLEرا تعریف میکنیم:
سپس با استفاده از تابع HAL_TIM_Base_Start_IT تایمرها را راه اندازی میکنیم:
در فایل stm32f3xx_it.c مجددا متغیرها را تعریف کرده و با استفاده از تابع HAL_TIM_PeriodElapsedCallback تغییرات مورد نظر در وقفه را به هر یک از متغیرها اعمال میکنیم:
در انتها با تغییرات مقادیر متغیر LED_TOGGLE در تابع وقفه و متن برنامه، وضعیت LED_USER هر 250 میلی ثانیه تغییر پیدا میکند.
تنظیمات پروژه کانتر
تایمرها شمارندههایی هستند که پالسهای منظم یک نوسانساز را میشمارند. تایمرها همچنین میتوانند پالس هایی را که به صورت خارجی به یک پین اعمال میشوند، بشمارند. در این حالت تایمر را شمارنده (کانتر) مینامند. ما میخواهیم واحد TIM15 را بهعنوان شمارنده پالسهای خارجی فعال کنیم و با دکمه USER_BUTTON که به پین PA3 وصل شده با پالسهای لبهای پایین رونده آن را تحریک کرده و آنها را با یک متغیر مرتبط با شمارنده شمارش کنیم. برای این منظور مطابق شکل زیر از قسمت Pinout & Configuration و بخش TIM15، حالت Slave را روی External Clock Mode 1 و Trigger Source را به صورت TI1FP2 تنظیم میکنیم. از بخش کانفیگ پارامترها، تنظیماتی مانند Prescaler (0)، Counter Mode (بالا)، Counter Period (65535 برای مقدار 16 بیت)، Trigger Polarity (Rising Edge) و Trigger Filter (15) را انجام میدهیم. در بخش System Core و زیربخش TIM، حالت Alternate Function Open Drain را برای حالت GPIO تنظیم کنید (با توجه به مدار بسته شده روی پین PA3). از بخش System Core و زیربخش NVIC، وقفه بهروزرسانی TIM15 را فعال کنید. هنگامی که مقدار شمارنده به مقدار Counter Period میرسد، وقفه 5TIM1 فعال میشود.
استفاده از تایمرها در حالت وقفه
قبل از دیدن یک مثال کامل، بهتر است آنچه را که تاکنون بررسی کرده ایم را خلاصه می کنیم. یک basic timer تایمر اولیه:
- یک شمارنده آزاد است که از 0 تا مقدار مشخص شده در فیلد Period در ساختار TIM_Base_InitTypeDef شمارش می کند، که می تواند حداکثر مقدار xFFFF را دریافت کند (xFFFF FFFF برای تایمرهای 32 بیتی).
- فرکانس شمارش به سرعت گذرگاهی که تایمر متصل است بستگی دارد و می توان آن را تا 65536 بار با تنظیم رجیستر Prescaler در ساختار اولیه کاهش داد.
- وقتی تایمر به مقدار Period می رسد، سرریز می شود و پرچم به روز رسانی رویداد (UEV) یک می شود. تایمر به طور خودکار شمارش را از مقدار اولیه (که همیشه برای تایمرهای اصلی صفر است) دوباره شروع می کند.
رجیسترهای Period و Prescaler فرکانس تایمر را تعیین میکنند، یعنی چقدر طول میکشد تا سرریز شود (یا هر چند وقت یکبار یک Update Event ایجاد میشود)، طبق این فرمول ساده:
UpdateEvent = Timerclock /(P rescaler + 1)(P eriod + 1) [1]
به عنوان مثال، فرض کنید یک تایمر متصل به گذرگاه APB1 در میکروکنترلر STM32F030، با HCLK تنظیم شده روی 48 مگاهرتز، مقدار Prescaler برابر با 47999 و مقدار Period برابر با 499 داریم که تایمر در هر 0.5 ثانیه سرریز خواهد شد:
UpdateEvent = 48000000/(47999 + 1)(499 + 1) = 2Hz = 1/2s = 0.5s
کد زیر که برای اجرا بر روی Nucleo-F030R8 طراحی شده است، یک مثال کامل با استفاده از TIM6 را نشان می دهد. این بار از یک تایمر اولیه برای محاسبه تاخیرها برای LED چشمک زن استفاده می کنیم.
7 TIM_HandleTypeDef htim6;
8
9int main(void) {
10 HAL_Init();
11
12 Nucleo_BSP_Init();
13
14 htim6.Instance = TIM6;
15 htim6.Init.Prescaler = 47999; //48MHz/48000 = 1000Hz
16 htim6.Init.Period = 499; //1000HZ / 500 = 2Hz = 0.5s
17
18 __HAL_RCC_TIM6_CLK_ENABLE(); //Enable the TIM6 peripheral
19
20 HAL_NVIC_SetPriority(TIM6_IRQn, 0, 0); //Enable the peripheral IRQ
21 HAL_NVIC_EnableIRQ(TIM6_IRQn);
22
23 HAL_TIM_Base_Init(&htim6); //Configure the timer
24 HAL_TIM_Base_Start_IT(&htim6); //Start the timer
25
26 while (1);
27 }
28
29 void TIM6_IRQHandler(void) {
30 // Pass the control to HAL, which processes the IRQ
31 HAL_TIM_IRQHandler(&htim6);
32 }
33
34 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
35 // This callback is automatically called by the HAL on the UEV event
36 if(htim->Instance == TIM6)
37 HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
38 }
خطوط [15:17] TIM6 را با استفاده از مقادیر Prescaler و Period محاسبه شده قبلی پیکربندی می کنند. سپس واحد جانبی تایمر با استفاده از ماکرو در خط 18 فعال می شود. همین امر در مورد IRQ آن نیز صدق می کند. سپس تایمر در خط 23 پیکربندی شده و در حالت وقفه با استفاده از تابع HAL_TIM_Base_Start_IT()شروع می شود.
تابع ()TIM6_IRQHandler با سرریز شدن تایمر فعال می شود و سپس HAL_TIM_IRQHandler() فراخوانی می شود. HAL بهطور خودکار تمام کارهای لازم برای مدیریت update event رویداد بهروزرسانی را انجام میدهد و با فراخوانی روال HAL_TIM_PeriodElapsedCallback() اعلام میکند که تایمر سرریز شده است.
عملکرد روتین HAL_TIM_IRQHandler()
برای تایمرهایی که با سرعت بسیار بالا کار می کنند، HAL_TIM_IRQHandler() سربار غیر قابل اغماضی را دارد. این تابع برای بررسی حداکثر نه وضعیت مختلف وقفه طراحی شده است که برای انجام این کار به چندین دستورالعمل اسمبلی ARM نیاز دارد. اگر می خواهید وقفه ها را در زمان کمتری پردازش کنید، بهتر است خودتان IRQ را مدیریت کنید. HAL برای ارائه راحت تر جزئیات سخت افزاری به کاربر طراحی شده است، اما ضعف هایی نیز در عملکرد دارد که هر توسعه دهنده باید از آن اطلاع داشته باشد.
تولید پایه زمانی Time Base در تایمرهای پیشرفته
تاکنون دیدهایم که تمام عملکردهای پایه یک تایمر از طریق نمونهای از ساختار TIM_Base_InitTypeDef پیکربندی میشوند. این ساختار حاوی فیلدی به نام RepetitionCounter است که برای افزایش بیشتر دوره بین دو رویداد بهروزرسانی update events متوالی استفاده میشود: تایمر قبل از تنظیم رویداد مورد نظر و اجرای وقفه مربوطه، به تعداد معینی شروع به شمارش میکند. RepetitionCounter فقط در تایمرهای پیشرفته در دسترس است که فرمول محاسبه فرکانس رویدادهای به روز رسانی update events به صورت زیر می باشد:
UpdateEvent = Timerclock/(Prescaler + 1)(Period + 1)(RepetitionCounter + 1)
با صفر گذاشتن RepetitionCounter (پیشفرض)، همان حالت کار تایمر ساده basic timer را به دست میآوریم.
استفاده از تایمرها در حالت Polling Mode
کتابخانه CubeHAL سه راه برای استفاده از تایمر ها ارائه می دهد: Polling ، interrupt و DMA. به همین دلیل، HAL سه تابع مجزا برای شروع یک تایمر ارائه میکند: HAL_TIM_Base_Start()، HAL_TIM_Base_Start_IT() و HAL_TIM_Base_Start_DMA(). روش انجام حالت Polling این است که رجیستر شمارنده تایمر (TIMx->CNT) به طور مداوم برای بررسی یک مقدار مشخص بررسی می شود. اما هنگام استفاده از حالت Polling تایمر باید مراقب بود. به عنوان مثال، کد زیر بسیار رایج است:
...
while (1) {
if(__HAL_TIM_GET_COUNTER(&tim) == value)
...
این روش Polling برای تایمر حتی اگر در برخی نمونه ها کار کند ،کاملاً اشتباه است. چرا؟
تایمرها به طور مستقل از هسته Cortex-M اجرا می شوند. یک تایمر می تواند بسیار سریع حتی تا مشابه فرکانس کلاک هسته CPU بشمارد. اما بررسی شمارنده تایمر برای برابری (یعنی بررسی شود آیا برابر با یک مقدار معین شده) به چندین دستورالعمل اسمبلی ARM و چندین چرخه کلاک نیاز دارد. هیچ تضمینی وجود ندارد که CPU دقیقاً همزمان با رسیدن به مقدار مشخص شده در رجیستر شمارنده، به آن دسترسی پیدا کند (فقط در صورتی این اتفاق میافتد که تایمر واقعا کند کار کند). راه بهتر این است که بررسی کنید مقدار شمارنده فعلی تایمر برابر یا بزرگتر از مقدار مشخص شده در رجیستر شمارنده باشد یا وضعیت UIF را بررسی کنید. در بدترین حالت تغییر در اندازه گیری زمان خواهیم داشت، اما رویداد را از دست نخواهیم داد. (مگر اینکه تایمر آنقدر سریع اجرا شود که رویدادهای بعدی را به دلیل پوشانده (Mask) شدن وقفه از دست بدهیم یعنی قبل از اینکه به صورت دستی توسط ما یا به طور خودکار توسط HAL پاک شود، UIF خودکار یک شود).
...
while (1) {
if(__HAL_TIM_GET_FLAG(&tim) == TIM_FLAG_UPDATE) {
//Clear the IRQ flag otherwise we lose other events
__HAL_TIM_CLEAR_IT(htim, TIM_IT_UPDATE);
...
با این حال، تایمرها تجهیزات جانبی غیر همزمان (آسنکرون) هستند و راه صحیح مدیریت overflow/underflow استفاده از وقفه است. هیچ دلیلی برای استفاده نکردن از تایمر در حالت وقفه وجود ندارد، مگر اینکه تایمر انچنان سریع کار کند که میکروکنترلر را توسط وقفه هایی که پس از چند میکروثانیه (یا حتی نانوثانیه) اتفاق می افتد، به طور کامل غرق خود کرده و از پردازش دستورالعملهای دیگر جلوگیری کند.
استفاده از تایمرها در حالت DMA Mode
تایمرها اغلب برای کار در حالت DMA برنامه ریزی می شوند، به خصوص زمانی که از آنها به عنوان مولدهای timebase استفاده نمی شود. در این حالت تضمین می شود که عملیات انجام شده توسط تایمر قطعی بوده و با کمترین تأخیر ممکن همراه است، به خصوص اگر آنها واقعا سریع اجرا شوند. علاوه بر این، هسته Cortex-M از مدیریت تایمر آزاد می شود، که معمولاً شامل مدیریت ISRهای بسیار مکرر است که می تواند CPU را مشغول کند. در نهایت، در برخی از حالتهای پیشرفته، مانند حالت PWM در خروجی، دسترسی به فرکانسهای سوئیچینگ خاص بدون استفاده از تایمر در حالت DMA تقریبا غیرممکن است.
به این دلایل، تایمرها حداکثر هفت درخواست DMA را ارائه میدهند که در جدول 6 فهرست شدهاند.
تایمرهای پایه basic timer فقط درخواست TIM_DMA_UPDATE را اجرا میکنند، زیرا ورودی/خروجی ندارند. با این حال، استفاده از درخواست TIMx_UP در شرایطی که میخواهیم انتقال DMA را بر اساس زمان انجام دهیم، واقعاً مفید است.
مثال زیر نوع دیگری از برنامه LED چشمک زن است، اما این بار از یک تایمر در حالت DMA برای روشن/خاموش کردن LED استفاده می کنیم. میخواهیم از تایمر TIM6 که برای سرریز شدن در هر 500 میلیثانیه برنامهریزی شده است ، استفاده کنیم: وقتی تایمر سرریز شد، تایمر درخواست TIM6_UP را ایجاد میکند (که در میکروکنترلر STM32F0303 به کانال سوم DMA1 متصل است) و خانه بعدی بافر به رجیستر GPIOA->ODR در حالت کاری دایره ای DMA منتقل میشود و باعث می شود LD2 به طور نامحدود چشمک بزند.
13 int main(void) }
14 uint8_t data[] = {0xFF, 0x0};
15 16 HAL_Init(); 17 Nucleo_BSP_Init(); 18 MX_DMA_Init(); 19
20 htim6.Instance = TIM6;
21 htim6.Init.Prescaler = 47999; //48MHz/48000 = 1000Hz
22 htim6.Init.Period = 499; //1000HZ / 500 = 2Hz = 0.5s
23 htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
24 __HAL_RCC_TIM6_CLK_ENABLE();
25
26 HAL_TIM_Base_Init(&htim6);
27 HAL_TIM_Base_Start(&htim6);
28
29 hdma_tim6_up.Instance = DMA1_Channel3;
30 hdma_tim6_up.Init.Direction = DMA_MEMORY_TO_PERIPH;
31 hdma_tim6_up.Init.PeriphInc = DMA_PINC_DISABLE;
32 hdma_tim6_up.Init.MemInc = DMA_MINC_ENABLE;
33 hdma_tim6_up.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
34 hdma_tim6_up.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
35 hdma_tim6_up.Init.Mode = DMA_CIRCULAR;
36 hdma_tim6_up.Init.Priority = DMA_PRIORITY_LOW;
37 HAL_DMA_Init(&hdma_tim6_up);
38
39 HAL_DMA_Start(&hdma_tim6_up, (uint32_t)data, (uint32_t)&GPIOA->ODR, 2);
40 __HAL_TIM_ENABLE_DMA(&htim6, TIM_DMA_UPDATE);
41
42 while (1);
43 }
خطوط [29:37] DMA_HandleTypeDef را برای DMA1_Channel3 در حالت دایره ای circular پیکربندی می کنند. سپس خط 39 انتقال داده توسط DMA را در هر درخواست TIM6_UP شروع می کند و محتوای بافر داده در هر سرریز تایمر ، به داخل رجیستر GPIOA->ODR منتقل می شود. این امر باعث می شود که LED LD2 چشمک بزند. می بینید که ما در اینجا از تابع HAL_TIM_Base_Start_DMA() استفاده نمی کنیم.اما چرا ؟
با نگاهی به اجرای تابع HAL_TIM_Base_Start_DMA، می بینید که مهندسان ST آن را به گونه ای تعریف کرده اند که انتقال DMA از بافر حافظه به TIM6->ARR، که مربوط به Period است، انجام می شود.
اساساً، از تابع HAL_TIM_Base_Start_DMA() فقط برای تغییر دوره timer در هر بار که سرریز میشود استفاده کنیم. بنابراین برای انجام این انتقال باید تنظیمات DMA را خودمان انجام دهیم.
توقف تایمر
CubeHAL سه تابع را برای توقف تایمر در حال اجرا ، با توجه به حالت کاری تایمر ، ارائه میکند: HAL_TIM_Base_Stop()، HAL_TIM_Base_Stop_IT() و HAL_TIM_Base_Stop_DMA(). (به عنوان مثال، اگر تایمر را در حالت وقفه راه اندازی کرده باشیم، باید آن را با استفاده از تابع HAL_TIM_Base_Stop_IT() متوقف کنیم). هر تابع برای غیرفعال کردن صحیح تنظیمات IRQ و DMA طراحی شده است.
تایمر های General Purpose
اکثر تایمرهای میکروکنترلرهای STM32 تایمرهای general purpose هستند. برخلاف تایمرهای اولیه ای basic timers که قبلا دیدیم، آنها به لطف چهار کانال مستقل که می توانند برای اندازه گیری سیگنال های ورودی، ارائه سیگنال در خروجی بر اساس زمان، تولید سیگنال های مدولاسیون عرض پالس (PWM) استفاده شوند، قابلیت های بسیار زیادی را ارائه می دهند.
تولید پایه زمان Time Base با منابع کلاک خارجی
شکل زیر بلوک دیاگرام یک تایمر general purpose را نشان می دهد. زمانی که کلاک APB به عنوان منبع انتخاب می شود، مسیری که با رنگ قرمز مشخص شده است برای تغذیه تایمر استفاده می شود: کلاک داخلی CK_INT در واقع Prescaler (PSC) را تغذیه کرده و تعیین می کند که رجیستر شمارنده (CNT) با چه سرعتی افزایش/کاهش یابد. این رجیستر با محتوای auto-reload register (که با مقدار فیلد TIM_Base_InitTypeDef.Period پر شده است) مقایسه می شود. هنگامی که مطابقت پیدا کردند، رویداد UEV ایجاد شده و IRQ مربوطه در صورتی که فعال باشد ، رخ می دهد.
با نگاهی به شکل بالا ، می بینیم که تایمر می تواند منبع “محرک” را از منابع دیگر نیز دریافت کند. این منابع تحریک را می توان به دو گروه اصلی تقسیم کرد:
- منابع Clock ، برای کلاک تایمر استفاده می شوند. آنها می توانند از منابع خارجی متصل به پین های میکروکنترلر یا سایر تایمرهایی که به صورت داخلی به MCU متصل هستند، نشات بگیرند. توجه داشته باشید که یک تایمر نمی تواند بدون منبع کلاک کار کند، زیرا از آن برای افزایش رجیستر counter استفاده می شود.
- منابع Trigger ، برای همگام سازی تایمر با منابع خارجی متصل به پین های میکروکنترلر یا سایر تایمرهایی که به صورت داخلی به میکروکنترلر متصل هستند ، استفاده می شود. به عنوان مثال، یک تایمر را می توان طوری پیکربندی کرد که وقتی یک رویداد خارجی آن را تحریک می کند، شروع به شمارش کند. در این حالت تایمر توسط یک منبع کلاک دیگر (که می تواند هم گذرگاه APBx یا یک منبع کلاک خارجی متصل به پین ETR2 باشد) کلاک می شود و توسط دستگاه دیگری کنترل می شود (یعنی چه زمانی شروع به شمارش کند …).
بسته به نوع تایمر و اجرای آن، یک تایمر می تواند از منابع زیر کلاک شود:
- TIMx_CLK داخلی ارائه شده توسط RCC
- ورودی trigger داخلی 0 تا 3
– ITR0، ITR1، ITR2 و ITR3 با استفاده از تایمر دیگری (master) به عنوان prescaler این تایمر (slave)
- پین های کانال ورودی خارجی
– پین 1: TI1FP1 یا TI1F_ED
– پین 2: TI2FP2
- پین های ETR خارجی:
– پین ETR1
– پین ETR2
همچنین یک تایمر می تواند از موارد زیر تحریک شود:
- ورودی های trigger داخلی 0 تا 3
– ITR0، ITR1، ITR2 و ITR3 با استفاده از تایمر دیگری به عنوان master
- پین های کانال ورودی خارجی
– پین 1: TI1FP1 یا TI1F_ED
– پین 2: TI2FP2
- پین خارجی ETR1
External Clock Mode 2
تایمرهای General purpose این قابلیت را دارند که از منابع خارجی کلاک شوند و می توانید آنها را در دو حالت متمایز تنظیم کنید: External Clock Source Mode 1 and 2 . حالت اول زمانی قابل اجرا است که تایمر در حالت slave پیکربندی شده باشد.
حالت دوم، به سادگی با استفاده از یک منبع کلاک خارجی فعال می شود. این امر باعث می شود تا از منابع دقیق تر و اختصاصی استفاده کنید و در نهایت تعداد دفعات شمارش را کاهش دهید. هنگامی که حالت منبع کلاک خارجی 2 انتخاب می شود، فرمول محاسبه فرکانس به روز رسانی رویدادها به صورت زیر در می آید:
UpdateEvent = EXTclock/(EXTclockP rescaler)(P rescaler + 1)(P eriod + 1)(RepetitionCounter + 1)
که EXTclock فرکانس منبع خارجی است و EXTclockP rescaler یک تقسیم کننده فرکانس منبع است که می تواند مقادیر 1، 2، 4 و 8 را در برگیرد.
منبع کلاک یک تایمر general purpose را می توان با استفاده از تابع HAL_TIM_ConfigClockSource() و نمونه ای از ساختار TIM_ClockConfigTypeDef انتخاب کرد که به صورت زیر تعریف می شود:
typedef struct {
uint32_t ClockSource; /* TIM clock sources */
uint32_t ClockPolarity; /* TIM clock polarity */
uint32_t ClockPrescaler; /* TIM clock prescaler */
uint32_t ClockFilter; /* TIM clock filter */
{ TIM_ClockConfigTypeDef;
- ClockSource: منبع سیگنال کلاک مورد استفاده برای بایاس تایمر را مشخص می کند و می تواند مقادیر جدول زیر را در بر گیرد. به طور پیش فرض، حالت TIM_CLOCKSOURCE_INTERNAL انتخاب شده است.
- ClockPolarity: قطبیت سیگنال کلاک مورد استفاده برای بایاس تایمر را نشان می دهد و می تواند مقادیر جدول زیر را در بر گیرد. به طور پیش فرض، حالت TIM_CLOCKPOLARITY_RISING انتخاب شده است.
- ClockPrescaler: مقدار prescaler را برای منبع کلاک خارجی مشخص می کند و می تواند مقادیر جدول زیر را در بر گیرد. به طور پیش فرض، مقدار TIM_CLOCKPRESCALER_DIV1 انتخاب شده است.
- ClockFilter: این فیلد 4 بیتی فرکانس مورد استفاده برای نمونه برداری از سیگنال کلاک خارجی و طول فیلتر دیجیتال اعمال شده روی آن را مشخص می کند. فیلتر دیجیتال از یک شمارنده رویداد ساخته شده است که در آن N رویداد متوالی برای تأیید یک انتقال در خروجی مورد نیاز است. در مورد نحوه محاسبه fDT S (سیگنال Dead-Time) به برگه دیتاشیت مراجعه کنید. به طور پیش فرض، این فیلتر غیرفعال است.
در ادامه نحوه استفاده از منبع کلاک خارجی برای تایمر TIM3 را شرح می دهیم.در این مثال پین خروجی Master Clock Output (MCO) را به پایه TIM3_ETR2 ، که متناظر با پین PD2 برای همه بردهای Nucleo است ، متصل می کنیم (شکل زیر).
پین MCO فعال بوده و به منبع کلاک LSE، که با فرکانس 32.768 کیلوهرتز کار میکند، وصل شده است. کد زیر مهم ترین بخش مثال بالا را نشان می دهد:
23 void MX_TIM3_Init(void){
24 TIM_ClockConfigTypeDef sClockSourceConfig;
25
26 htim3.Instance = TIM3;
27 htim3.Init.Prescaler = 0;
28 htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
29 htim3.Init.Period = 16383;
30 htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
31 htim3.Init.RepetitionCounter = 0;
32 HAL_TIM_Base_Init(&htim3);
33
34 sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_ETRMODE2;
35 sClockSourceConfig.ClockPolarity = TIM_CLOCKPOLARITY_NONINVERTED;
36 sClockSourceConfig.ClockPrescaler = TIM_CLOCKPRESCALER_DIV1;
37 sClockSourceConfig.ClockFilter = 0;
38 HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig);
39
40 HAL_NVIC_SetPriority(TIM3_IRQn, 0, 0);
41 HAL_NVIC_EnableIRQ(TIM3_IRQn);
}42
43
44 void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* htim_base) {
45 GPIO_InitTypeDef GPIO_InitStruct;
46 if(htim_base->Instance==TIM3) {
47 /* Peripheral clock enable */ __
48 HAL_RCC_TIM3_CLK_ENABLE(); __
49HAL_RCC_GPIOD_CLK_ENABLE();
50
51 /**TIM3 GPIO Configuration 52 PD2 ------> TIM3_ETR
53 */
54 GPIO_InitStruct.Pin = GPIO_PIN_2;
55 GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
56 GPIO_InitStruct.Pull = GPIO_NOPULL;
57 GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
58 HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
59 }
60 {
خطوط [27:33] تایمر TIM3 را پیکربندی میکنند و دوره را روی 19999 تنظیم میکنند. خطوط [34:38] منبع کلاک خارجی را برای TIM3 پیکربندی میکنند. از آنجایی که اسیلاتور LSE در فرکانس 32.768 کیلوهرتز کار می کند، با استفاده از معادله زیر می توانیم فرکانس UEV را محاسبه کنیم که برابر است با:
UpdateEvent = 32:768/(1)(0 + 1)(16383 + 1)(0 + 1) = 2Hz = 0.5s
در نهایت، خطوط [48:58] ، TIM3 را فعال کرده و پین PD2 (که مربوط به پایه TIM3_ETR2 است) را به عنوان منبع ورودی پیکربندی میکنند.
نکته: پورت GPIO D باید با استفاده از ماکرو __GPIOD_CLK_ENABLE () ، قبل از اینکه از آن به عنوان منبع کلاک برای TIM3 استفاده کنیم، فعال شود. همین امر حتی در مورد TIM3 نیز صدق میکند، که با استفاده از __TIM3_CLK_ENABLE() فعال می شود.
External Clock Mode 1
تایمرهای general purpose همه منظوره و advanced پیشرفته STM32 را می توان برای کار در حالت master یا slave پیکربندی کرد. زمانی که یک تایمر بهعنوان slave پیکربندی شده باشد، میتواند توسط خطوط داخلی ITR0، ITR1، ITR2 و ITR3، یک کلاک خارجی متصل به پین ETR1 یا از منابع کلاک دیگر متصل به منابع TI1FP1 و TI2FP2، که مطابق با پین های 1 و 2 کانال است تغذیه (کلاک) شود. این حالت کاری External Clock Mode 1 نامیده می شود .
حالت کلاک خارجی 1 و 2 برای همه مبتدیان در میکروهای STM32 گیج کننده است. هر دو حالت راهی برای کلاک کردن یک تایمر با استفاده از یک منبع کلاک خارجی هستند، اما حالت اول با پیکربندی تایمر در حالت slave ایجاد میشود (در واقع نوعی ” triggering ” است)، در حالی که حالت دوم با انتخاب ساده منابع کلاک مختلف به دست میآید.
نکته: ورودیهای TI1FP1 و TI2FP2 چیزی بیش از کانالهای ورودی TI1 و TI2 تایمر پس از اعمال فیلتر در ورودی نیستند.
برای پیکربندی تایمر در حالت slave از تابع HAL_TIM_SlaveConfigSynchronization() و نمونه ای از ساختار TIM_SlaveConfigTypeDef استفاده می کنیم که به صورت زیر تعریف می شود:
typedef struct {
uint32_t ClockSource; /* TIM clock sources */
uint32_t ClockPolarity; /* TIM clock polarity */
uint32_t ClockPrescaler; /* TIM clock prescaler */
uint32_t ClockFilter; /* TIM clock filter */
{ TIM_ClockConfigTypeDef;
- SlaveMode: هنگامی که یک تایمر در حالت slave پیکربندی می شود، می تواند توسط چندین منبع مختلف کلاک/راه اندازی شود. این فیلد می تواند مقادیری از جدول زیر را در بر گیرد. این پاراگراف در مورد حالت TIM_SLAVEMODE_EXTERNAL1 است.
• InputTrigger: منبعی که تایمر پیکربندی شده در حالت برده را فعال/کلاک می کند، مشخص می کند. این فیلد می تواند مقادیری از جدول زیر را در بر گیرد.
- TriggerPolarity: قطبیت منبع تریگر/ساعت را نشان می دهد. می تواند مقادیری از جدول زیر را در بر گیرد.
- TriggerPrescaler: این بخش prescaler را برای منبع کلاک خارجی مشخص می کند. می تواند مقادیری از جدول زیر را در بر گیرد. به طور پیش فرض، مقدار TIM_TRIGGERPRESCALER_DIV1 انتخاب شده است.
استفاده از CubeMX برای پیکربندی منبع کلاک تایمر General Purpose
پیکربندی منبع کلاک تایمر general purpose می تواند برای افراد تازه کار یک کابوس باشد . CubeMX توانسته این فرآیند را ساده کند.
برای پیکربندی تایمر در حالت External Clock Mode 2 کافی است ETR2 را به عنوان منبع کلاک از نمای Pinout انتخاب کنید:
پس از انتخاب منبع کلاک، می توان فیلتر کلاک خارجی، polarity و prescaler را از کادر dialog پیکربندی تایمر تنظیم کرد.
برای پیکربندی تایمر در حالت External Clock Mode 1 ، باید این حالت را از ورودی Slave انتخاب کنیم و سپس منبع تریگر (که در این مورد منبع کلاک ، برای تایمر است) را انتخاب کنیم.
حالات همگام سازی Master/Slave
هنگامی که یک تایمر در حالت master کار می کند، می تواند تایمر دیگری را که در حالت Slave پیکربندی شده است از طریق یک خط خروجی اختصاصی، به نام خروجی Trigger (TRGO)، متصل به خطوط اختصاصی داخلی به نام ITR0، ITR1، ITR2 و ITR3 تغذیه (کلاک) کند. تایمر master هم می تواند به عنوان منبع کلاک عمل کند (به عنوان یک prescaler مرتبه اول) یا تایمر slave را تحریک trigger کند.
این خطوط Trigger داخلی (ITR) (ITR0، ITR1، ITR2 و ITR3) دقیقاً داخل تراشه هستند و هر خط بین دو تایمر مشخص ، متصل می شود. به عنوان مثال، در میکروکنترلر STM32F030، خط TIM1 TRGO به خط ITR0 تایمر TIM2 متصل است، همانطور که در شکل زیر نشان داده شده است.
یک تایمر slave میتواند بهطور همزمان بهعنوان master برای تایمر دیگر نیز عمل کند و شبکه پیچیده ای از تایمر ها را به وجود آورد. به عنوان مثال، شکل زیر نشان می دهد که چگونه تایمرها را می توان به صورت آبشاری متصل کرد:
اما شکل زیر نشان می دهد که چگونه تایمرها می توانند ساختارهای سلسله مراتبی را با استفاده از ترکیب حالت های master/slave تشکیل دهند. توجه داشته باشید که TIM1، TIM2 و TIM3 به صورت داخلی از طریق همان خط ITR0 به هم متصل هستند. این امر باعث می شود تا چندین تایمر در یک رویداد مشابه (reset, enable, update و غیره) همگام سازی شوند.
برای پیکربندی تایمر در حالت master ، از تابع HAL_TIMEx_MasterConfigSynchronization() و نمونهای از ساختار TIM_MasterConfigTypeDef استفاده میکنیم که به شکل زیر تعریف میشود.
typedef struct {
uint32_t MasterOutputTrigger; /* Trigger output (TRGO) selection */
uint32_t MasterSlaveMode; /* Master/slave mode selection */
} TIM_MasterConfigTypeDef;
- MasterOutputTrigger: رفتار خروجی TRGO را مشخص می کند و می تواند مقادیری از جدول زیر را در بر گیرد.
- MasterSlaveMode: برای فعال/غیرفعال کردن حالت master/slave یک تایمر استفاده می شود. می تواند مقادیر TIM_MASTERSLAVEMODE_ENABLE یا TIM_MASTERSLAVEMODE_DISABLE را در بر گیرد.
در ادامه نحوه پیکربندی TIM1 و TIM3 را در حالت آبشاری با تنظیم TIM1 به عنوان master برای تایمر TIM3 نشان میدهیم. TIM1 به عنوان منبع کلاک برای TIM3 از طریق خط ITR0 استفاده می شود. علاوه بر این، TIM1 به گونه ای پیکربندی شده است که روی یک رویداد خارجی در خط TI1FP1 شروع به شمارش می کند، که در Nucleo-F030 مربوط به پین PA8 است: زمانی که پایه PA8 بالا می رود، TIM1 شروع به شمارش می کند و سپس تایمر TIM3 را از طریق خط ITR0. تغذیه (کلاک) می کند.
12 int main(void) {
13 HAL_Init();
14 15 Nucleo_BSP_Init();
16 MX_TIM1_Init();
17 MX_TIM3_Init();
18 19 HAL_TIM_Base_Start_IT(&htim3);
20 21 while (1);
22 }
23
24 void MX_TIM1_Init(void) {
25 TIM_ClockConfigTypeDef sClockSourceConfig;
26 TIM_MasterConfigTypeDef sMasterConfig;
27 TIM_SlaveConfigTypeDef sSlaveConfig;
28
29 htim1.Instance = TIM1;
30 htim1.Init.Prescaler = 47999;
31 htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
32 htim1.Init.Period = 249;
33 htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
34 htim1.Init.RepetitionCounter = 0;
35 HAL_TIM_Base_Init(&htim1);
36
37 sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
38 HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig);
39
40 sSlaveConfig.SlaveMode = TIM_SLAVEMODE_TRIGGER;
41 sSlaveConfig.InputTrigger = TIM_TS_TI1FP1;
42 sSlaveConfig.TriggerPolarity = TIM_TRIGGERPOLARITY_RISING;
43 sSlaveConfig.TriggerFilter = 15;
44 HAL_TIM_SlaveConfigSynchronization(&htim1, &sSlaveConfig);
45
46 sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
47 sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_ENABLE;
48 HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig);
49 }
50
51 void MX_TIM3_Init(void) {
52 TIM_SlaveConfigTypeDef sSlaveConfig;
53
54 htim3.Instance = TIM3;
55 htim3.Init.Prescaler = 0;
56 htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
57 htim3.Init.Period = 1;
58 htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
59 HAL_TIM_Base_Init(&htim3);
60
61 sSlaveConfig.SlaveMode = TIM_SLAVEMODE_EXTERNAL1;
62 sSlaveConfig.InputTrigger = TIM_TS_ITR0;
63 HAL_TIM_SlaveConfigSynchronization(&htim3, &sSlaveConfig);
64
65 HAL_NVIC_SetPriority(TIM3_IRQn, 0, 0);
66 HAL_NVIC_EnableIRQ(TIM3_IRQn);
67 }
68
69 void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* htim_base) {
70 GPIO_InitTypeDef GPIO_InitStruct;
71 if(htim_base->Instance==TIM3) {
72 __HAL_RCC_TIM3_CLK_ENABLE();
73 }
74
75 if(htim_base->Instance==TIM1) {
76 __HAL_RCC_TIM1_CLK_ENABLE();
77
78 GPIO_InitStruct.Pin = GPIO_PIN_8;
79 GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
80 GPIO_InitStruct.Pull = GPIO_PULLDOWN;
81 GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
82 GPIO_InitStruct.Alternate = GPIO_AF2_TIM1;
83 HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
84 }
85 }
خطوط [29:38] TIM1 را به گونه ای پیکربندی می کنند که از گذرگاه APB1 داخلی کلاک شود. و خطوط [40:44] TIM1 را در حالت slave پیکربندی می کنند، به طوری که وقتی خط TI1FP1 بالا رفت (یعنی فعال می شود) شروع به شمارش می کند. PA8 GPIO بر این اساس در خطوط [74:79] پیکربندی شده است (به عنوان GPIO_AF2_TIM1 پیکربندی شده است). توجه داشته باشید که مقاومت PULL UP داخلی در خط 76 فعال می شود: این امر مانع از این می شود که ورودی به طور تصادفی تایمر را فعال کند. به همین دلیل، TriggerFilter در خط 43 روی حداکثر سطح تنظیم شده است (اگر سعی کنید آن را روی صفر تنظیم کنید، متوجه خواهید شد که راه اندازی تصادفی تایمر حتی با لمس سیم متصل به پین PA8 بسیار آسان است،).
خطوط [46:48] TIM1 را نیز در حالت master پیکربندی میکنند. تایمر خط داخلی خود را (که به خط ITR0 TIM3 متصل است) هر بار که رویداد بهروزرسانی update event ایجاد میشود، راهاندازی میکند. در نهایت، خطوط [61:63] TIM3 را در حالت update event تنظیم میکنند و خط ITR0 را به عنوان منبع کلاک انتخاب میکنند.
توجه داشته باشید برای اینکه LED LD2 هر 500 میلیثانیه (2 هرتز) چشمک بزند، دوره TIM1 روی 249 تنظیم شده است که باعث میشود فرکانس بهروزرسانی TIM1 عدد 4 هرتز باشد با استفاده از معادله زیر داریم که:
UpdateEvent = 4Hz/(0 + 1)(1 + 1)(0 + 1) = 2Hz = 0.5s
توجه داشته باشید که قسمت Period را نمی توان صفر قرار داد.
برای فعال کردن TIM1 باید پین PA8 را به منبع +3V3 وصل کنید. شکل زیر نحوه اتصال آن را در Nucleo-F030 نشان می دهد.
فعال سازی وقفه های مرتبط با Trigger
هنگامی که یک تایمر در حالت slave کار می کند، IRQ تایمر , در صورت فعال بودن، هر بار که رویداد trigger مشخص شده فعال میشود ، رخ می دهد. به عنوان مثال، هنگامی که کلاک master به دلیل یک رویداد بهروزرسانی فعال میشود، IRQ تایمر slave روشن میشود و میتوان با تعریف callback از این موضوع مطلع شد:
void HAL_TIM_TriggerCallback(TIM_HandleTypeDef *htim)
{
...
}
به طور پیش فرض، HAL_TIM_Base_Start_IT() این نوع وقفه را فعال نمی کند. ما باید از تابع HAL_TIM_SlaveConfigSynchronization_IT()، به جای تابع HAL_TIM_SlaveConfigSynchronization() استفاده کنیم. بدیهی است که ISR مربوطه باید تعریف شده و تابع HAL_TIM_IRQHandler() فراخوانی شود.
استفاده از CubeMX برای تنظیم همگام سازی Master/Slave
برای پیکربندی یک تایمر در حالت slave با استفاده از CubeMX، کافی است حالت trigger مورد نظر (Reset Mode, Gated Mode, Trigger Mode) را از IP Pane tree (Slave mode combo-box انتخاب کنید و سپس منبع trigger را انتخاب کنید.به خاطر داشته باشید تایمر پیکربندی شده در حالت slave ، که در حالت External Clock Mode 1 کار نمی کند، باید از کلاک داخلی یا منبع ETR2 کلاک شود.
ایجاد رویدادهای مرتبط با تایمر توسط نرم افزار
تایمرها معمولاً در صورت برآورده شدن یک شرط معین رویدادها را تولید می کنند. برای مثال، زمانی که رجیستر شمارنده (CNT) با مقدار Period مطابقت داشته باشد، رویداد Update Event (UEV) را تولید می کنند.همچنین میتوانیم تایمر را مجبور کنیم تا یک رویداد خاص را توسط نرمافزار تولید کند. هر تایمر یک رجیستر اختصاصی به نام Event Generator (EGR) ارائه می دهد. برخی از بیتهای این رجیستر برای اجرای یک رویداد مربوط به تایمر استفاده میشوند. به عنوان مثال، اولین بیت، به نام Update Generator (UG)، اجازه می دهد تا یک رویداد UEV در هنگام 1 شدن ایجاد شود. این بیت پس از ایجاد رویداد به طور خودکار پاک می شود.
برای تولید رویدادها توسط نرم افزار، HAL تابع زیر را ارائه می دهد:
HAL_StatusTypeDef HAL_TIM_GenerateEvent(TIM_HandleTypeDef *htim, uint32_t EventSource);
TIM_EVENTSOURCE_UPDATE دو نقش مهم دارد. اولین مورد مربوط به نحوه به روز رسانی رجیستر Period (یعنی رجیستر TIMx->ARR) هنگامی که تایمر در حال اجراست. بهطور پیشفرض، محتوای رجیستر ARR هنگام ایجاد رویداد TIM_EVENTSOURCE_UPDATE به رجیستر سایه داخلی منتقل میشود، مگر اینکه تایمر بهطور متفاوتی پیکربندی شده باشد.
رویداد TIM_EVENTSOURCE_UPDATE همچنین زمانی مفید است که خروجی TRGO یک تایمر پیکربندی شده به عنوان master در حالت TIM_TRGO_RESET تنظیم شود: در این حالت، تایمر slave تنها در صورتی فعال trigger می شود که از رجیستر TIMx->EGR برای تولید رویداد TIM_EVENTSOURCE_UPDATE استفاده شود (یعنی ، بیت UG یک شده است).
کد زیر نحوه تولید رویداد نرم افزاری را نشان می دهد (مثال بر اساس میکروکنترلر STM32F401RE است). TIM3 و TIM4 دو تایمر هستند که به ترتیب در حالت master و slave پیکربندی شده اند. TIM4 برای کار در حالت ETR1 تنظیم شده است (یعنی توسط تایمر master کلاک می شود). TIM3 به گونه ای پیکربندی شده است که وقتی بیت UG رجیستر TIM3->EGR یک می شود، خروجی TRGO (که به صورت داخلی به خط ITR2 متصل است) trigg شود. در نهایت، رویداد UEV را به صورت دستی هر 200 میلیثانیه از روتین main() تولید میکنیم.
int main(void) {
... while (1) {
HAL_TIM_GenerateEvent(&htim3, TIM_EVENTSOURCE_UPDATE);
HAL_Delay(200);
}
...
}
void MX_TIM3_Init(void){
TIM_ClockConfigTypeDef sClockSourceConfig;
TIM_MasterConfigTypeDef sMasterConfig;
htim3.Instance = TIM3;
htim3.Init.Prescaler = 65535;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 120;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_Base_Init(&htim3);
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig);
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_ENABLE; HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig);
}
void MX_TIM4_Init(void) {
TIM_SlaveConfigTypeDef sSlaveConfig;
htim4.Instance = TIM4;
htim4.Init.Prescaler = 0;
htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
htim4.Init.Period = 1;
htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_Base_Init(&htim4);
sSlaveConfig.SlaveMode = TIM_SLAVEMODE_EXTERNAL1;
sSlaveConfig.InputTrigger = TIM_TS_ITR2;
HAL_TIM_SlaveConfigSynchronization_IT(&htim4, &sSlaveConfig);
}
مدهای کاری کانتر
در ابتدای این فصل دیدیم که یک تایمر پایه basic از صفر تا یک مقدار Period دوره معین را می شمارد. تایمرهای عمومی General purpose و پیشرفته advanced به روش های مختلف دیگری نیز می توانند شمارش کنند. شکل زیر سه حالت اصلی شمارش را نشان می دهد.
وقتی یک تایمر در حالت TIM_COUNTERMODE_DOWN شمارش میکند، از مقدار Period شروع میشود و تا صفر به صورت معکوس شمارش میکند: وقتی شمارنده به پایان رسید، IRQ تایمر رخ داده و فلگ UIF یک میشود (یعنی رویداد بهروزرسانی ایجاد میشود و HAL_TIM_PeriodElapsedCallback () توسط HAL فراخوانی می شود.)
در عوض، وقتی یک تایمر در حالت TIM_COUNTERMODE_CENTERALIGNED شمارش میکند، از صفر تا مقدار Period شروع به شمارش میکند: این امر باعث میشود که IRQ رخ داده و فلگ UIF یک شود ، (یعنی رویداد بهروزرسانی ایجاد شده و HAL_TIM_PeriodElapsedCallback توسط HAL فراخوانی میشود). سپس تایمر شروع به شمارش معکوس تا صفر می کند و یک رویداد به روز رسانی دیگر (و همچنین IRQ مربوطه) ایجاد می شود.
شکل 16 ساختار کانال های ورودی را در یک تایمر با هدف کلی25 نشان می دهد. همانطور که می بینید، هر ورودی به یک آشکارساز لبه متصل است، که همچنین مجهز به فیلتری است که برای “debounce” سیگنال ورودی استفاده می شود. خروجی آشکارساز لبه به یک مالتی پلکسر منبع (IC1، IC2 و غیره) می رود. در صورتی که ورودی/خروجی معینی به دستگاه جانبی دیگری اختصاص داده شود، این امکان را می دهد که کانال های ورودی را مجدداً ترسیم کنید. در نهایت، یک پیش مقیاسکننده اختصاصی اجازه میدهد تا فرکانس سیگنال ورودی را کاهش دهد تا در صورتی که فرکانس کارکرد تایمر قابل کاهش نباشد، همانطور که در مدتی دیگر خواهیم دید.
حالت Input Capture
تایمرهای عمومی General purpose برای تولید پایه زمانی طراحی نشده اند. حتی اگر استفاده از آنها برای انجام این کار کاملاً امکان پذیر باشد، می توان از تایمرهای دیگر مانند تایمرهای اولیه basic و تایمر SysTick برای انجام این کار استفاده کرد. تایمرهای عمومی General purpose قابلیت های بسیار پیشرفته تری دارند که می توان از آنها برای سایر فعالیت های مهم مرتبط با زمان استفاده کرد.
شکل زیر ساختار کانال های ورودی را در یک تایمر General purpose نشان می دهد. همانطور که می بینید، هر ورودی به یک آشکارساز لبه متصل است و مجهز به فیلتری است که برای “debounce” سیگنال ورودی استفاده می شود. خروجی آشکارساز لبه به یک مالتی پلکسر (IC1، IC2 و غیره) وصل می شود که این امکان را فراهم می سازد تا در صورتی که I/O معینی به واحد جانبی دیگری اختصاص داده شود، ورودی کانال های تایمر ، مجدداً ترسیم شود. در نهایت، یک prescaler اختصاصی باعث کاهش فرکانس سیگنال ورودی میشود تا در صورتی که فرکانس کاری تایمر قابل کاهش نباشد، تناسب فرکانسی فراهم شود.
حالت Input capture توسط تایمرهای پیشرفته advanced و همه منظوره general purpose ارائه می شود .این حالت امکان محاسبه فرکانس سیگنال های خارجی اعمال شده به هر یک از 4 کانالی که این تایمرها ارائه می دهند را فراهم می سازد و عمل capture به طور مستقل برای هر کانال انجام می شود.
شکل زیر نحوه عملکرد فرآیند capture را نشان می دهد. TIMx تایمری است که برای کار در فرکانس کلاک TIMx_CLK معین پیکربندی شده است. به این معنی که رجیستر TIMx_CNT را تا مقدار Period هر 1/ TIMx_CLK ثانیه افزایش می دهد. فرض کنید سیگنال موج مربعی را به یکی از کانالهای تایمر اعمال میکنیم و تایمر را به گونهای تنظیم میکنیم که در هر لبه بالا رونده سیگنال ورودی، تحریک trigg شود. میبینیم که رجیستر TIMx_CCRx با محتوای رجیستر TIMx_CNT در هر انتقال شناسایی شده (شمارش) بهروزرسانی می شود. هنگامی که این اتفاق می افتد، تایمر متناظرا یک وقفه یا یک درخواست DMA ایجاد می کند که اجازه می دهد تا مقدار شمارنده را دنبال کند.
برای محاسبه period سیگنال خارجی، دو capture متوالی مورد نیاز است. دوره period با کم کردن دو مقدار CNT0 (مقدار 4 در شکل زیر) و CNT1 (مقدار 20 در شکل زیر) و با استفاده از فرمول زیر محاسبه می شود:
Capture = CNT1 – CNT0 if CNT0 < CNT1
Capture = (T IMx_P eriod – CNT0) + CNT1 if CNT0 > CNT1
CHP rescaler یک prescaler دیگری است که به کانال ورودی اعمال می شود و PolarityIndex اگر کانال حساس به لبه بالارونده یا پایین رونده سیگنال ورودی باشد، برابر با 1 است و اگر حساس به هر دو لبه باشد برابر با 2 است.
شرایط دیگر این است که فرکانس UEV باید کمتر از فرکانس نمونه برداری از سیگنال باشد. دلیل اهمیت این موضوع واضح است: اگر تایمر سریعتر از سیگنال نمونه برداری شده کار کند، قبل از اینکه بتواند از لبه های سیگنال نمونه برداری کند، سرریز می شود (یعنی Period شمارنده تمام می شود ، شکل زیر را ببینید). به همین دلیل، معمولاً بهتر است مقدار Period روی حداکثر قرار گرفته و ضریب Prescaler برای کاهش فرکانس شمارش افزایش پیدا کند.
استفاده از CubeMX برای پیکربندی حالت Input Capture
به لطف CubeMX، پیکربندی کانال های ورودی تایمر عمومی general purpose در حالت input capture واقعا آسان است. برای اتصال یک کانال به ورودی مربوطه (یعنی IC1 به TI1)، باید حالت Input capture direct را برای کانال مورد نظر انتخاب کنیم، همانطور که در شکل زیر نشان داده شده است.
حالت Output Compare
تاکنون از چند تکنیک برای کنترل شکل موج خروجی استفاده کردهایم، که یکی با استفاده از وقفه و دیگری با DMA بوده است. هر دوی آنها از رویداد UEV تولید شده برای تغییر وضعیت GPIO ای که به عنوان پین خروجی پیکربندی شده ، استفاده میکنند. output compare حالتی است که توسط تایمرهای همه منظوره general purpose و پیشرفته advanced ، زمانی که رجیستر مقایسه کانال (TIMx_CCRx) با رجیستر شمارشگر تایمر (TIMx_CNT) مطابقت دارد و امکان کنترل وضعیت کانال های خروجی به وجود می آید ، ارائه می شود.
شش حالت output compare برای برنامه نویسان در دسترس است:
- Output compare timing: مقایسه بین رجیستر مقایسه خروجی (CCRx) و شمارنده (CNT) هیچ تأثیری روی خروجی ندارد. این حالت برای ایجاد یک timing base پایه زمان استفاده می شود.
- Output compare active: هنگامی که شمارنده (CNT) با رجیستر capture/compare (CCRx) مطابقت داشته باشد، خروجی کانال یک می رود.
- Output compare inactive: هنگامی که شمارنده (CNT) با رجیستر capture/compare (CCRx) مطابقت داشته باشد، خروجی کانال صفر می شود.
- Output compare toggle: هنگامی که شمارنده (CNT) با با رجیستر capture/compare (CCRx) مطابقت داشته باشد، خروجی کانال تغییر وضعیت می دهد.
- Output compare forced active/inactive: خروجی کانال مستقل از مقدار شمارنده به صورت اجباری یک (active mode) یا صفر (inactive mode) می شود.
هر کانال تایمر در حالت Output compare با استفاده از تابع HAL_TIM_OC_ConfigChannel() و نمونه ای از ساختار TIM_OC_InitTypeDef پیکربندی و به صورت زیر تعریف می شود:
typedef struct {
uint32_t OCMode; /* Specifies the TIM mode. */
uint32_t Pulse; /* Specifies the pulse value to be loaded into the Capture Compare Register. */
uint32_t OCPolarity; /* Specifies the output polarity. */
uint32_t OCNPolarity; /* Specifies the complementary output polarity.*/
uint32_t OCFastMode; /* Specifies the Fast mode state. */
uint32_t OCIdleState; /* Specifies the TIM Output Compare pin state during Idle state.*/
uint32_t OCNIdleState; /* Specifies the complementary TIM Output Compare pin state during Idle state. */
} TIM_OC_InitTypeDef;
• OCMode: حالت Output compare را مشخص می کند و می تواند مقادیری از جدول زیر را در بر گیرد.
- Pulse: محتوای این فیلد در رجیستر CCRx ذخیره میشود و تعیین میکند چه زمانی خروجی trigg شود.
OCPolarity: سطح لاجیک کانال خروجی را زمانی که رجیستر CCRx با CNT مطابقت دارد، مشخص می کند و می تواند مقادیری از جدول زیر را در بر گیرد.
OCNPolarity: سطح لاجیک خروجی مکمل را تعریف می کند. این حالت فقط در تایمرهای پیشرفته TIM1 و TIM8 موجود است که امکان تولید سیگنال های اضافی را در کانال های اختصاصی اضافی فراهم می کند (یعنی وقتی CH1 یک است CH1N صفر بوده و بالعکس). این ویژگی برای کاربردهای کنترل موتور طراحی شده و می تواند مقادیری از جدول زیر را در بر گیرد.
- OCFastMode: وضعیت حالت fast mode را مشخص می کند. این پارامتر فقط در حالت PWM1 و PWM2 معتبر است و می تواند مقادیر TIM_OCFAST_DISABLE و TIM_OCFAST_ENABLE را در بر گیرد.
- OCIdleState: وضعیت پین خروجی کانال را در حالت idle تایمر مشخص می کند و می تواند مقادیر TIM_OCIDLESTATE_SET و TIM_OCIDLESTATE_RESET را در بر گیرد. این پارامتر فقط در تایمرهای پیشرفته TIM1 و TIM8 موجود است.
- OCNIdleState: وضعیت پین خروجی مکمل کانال را در حالت idle تایمر مشخص می کند. می تواند مقادیر TIM_OCNIDLESTATE_SET و TIM_OCNIDLESTATE_RESET را در بر گیرد. این پارامتر فقط در تایمرهای پیشرفته TIM1 و TIM8 موجود است.
هنگامی که کانال در حالت output compare mode پیکربندی میشود و رجیستر CCRx با شمارنده تایمر CNT مطابقت پیدا کند ، یک وقفه خاص ایجاد میشود (اگر فعال باشد). این امر باعث می شود تا فرکانس سوئیچینگ هر کانال به طور مستقل کنترل و در نهایت تغییر فاز بین کانال ها انجام شود. فرکانس هر کانال را می توان با استفاده از فرمول زیر محاسبه کرد:
CHx_Update = T IMx_CLK/CCRx
TIMx_CLK فرکانس اجرایی تایمر و CCRx مقدار Pulse در ساختار TIM_OnePulse_InitTypeDef است که برای پیکربندی کانال استفاده می شود. در نتیجه می توانیم مقدار Pulse را با توجه به فرکانس کانال به روش زیر محاسبه کنیم:
Pulse =T IMx_CLK/CHx_Update
Pulse =T IMx_CLK/CHx_Update
واضح است که فرکانس تایمر باید طوری تنظیم شود تا مقدار Pulse محاسبه شده در فرمول بالا کمتر از مقدار Period تایمر باشد (مقدار CCRx نمی تواند بیشتر از مقدار TIM->ARR که مطابق با دوره تایمر است باشد).
مثال زیر نحوه تولید دو سیگنال موج مربعی را نشان می دهد که یکی با فرکانس 50 کیلوهرتز و دیگری با فرکانس 100 کیلوهرتز کار می کند.در این مثال از کانال 1 و 2 (OC1 و OC2) تایمر TIM3 استفاده و بر روی Nucleo-F030R8 طراحی شده است.
17 volatile uint16_t CH1_FREQ = 0;
18 volatile uint16_t CH2_FREQ = 0;
19
20 int main(void) {
21 HAL_Init();
22
23 Nucleo_BSP_Init();
24 MX_TIM3_Init();
25
26 HAL_TIM_OC_Start_IT(&htim3, TIM_CHANNEL_1);
27 HAL_TIM_OC_Start_IT(&htim3, TIM_CHANNEL_2);
28
29 while (1);
30 }
31
32 /* TIM3 init function */
33 void MX_TIM3_Init(void) {
34 TIM_OC_InitTypeDef sConfigOC;
35
36 htim3.Instance = TIM3;
37 htim3.Init.Prescaler = 2;
38 htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
39 htim3.Init.Period = 65535;
40 htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
41 HAL_TIM_OC_Init(&htim3);
42
43 CH1_FREQ = computePulse(&htim3, 50000);
44 CH2_FREQ = computePulse(&htim3, 100000);
45
46 sConfigOC.OCMode = TIM_OCMODE_TOGGLE;
47 sConfigOC.Pulse = CH1_FREQ;
48 sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
49 sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
50 HAL_TIM_OC_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1);
51
52 sConfigOC.Pulse = CH2_FREQ;
53 HAL_TIM_OC_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_2);
54 }
خطوط [46:53] کانال 1 و 2 را به عنوان کانال های output compare پیکربندی می کنند. هر دو در حالت toggle mode تنظیم شدهاند (یعنی هر بار که رجیستر CCRx با رجیستر تایمر CNT مطابقت میکند،وضعیت GPIO تغییر میکند). TIM3 برای کار در فرکانس 16 مگاهرتز درنظر گرفته شده است و از این رو تابع computePulse() که از معادله بالا استفاده می کند، مقادیر 320 و 160 را برای داشتن فرکانس سوئیچینگ در هر کانال به ترتیب برابر با 50 کیلوهرتز و 100 کیلوهرتز برمی گرداند. با این حال، کد بالا هنوز برای درایو GPIO در آن فرکانس کافی نیست. در اینجا ما کانال ها را طوری پیکربندی می کنیم که هر بار که رجیستر CNT تایمر برابر با 320 برای کانال 1 و 160 برای کانال 2 شد، خروجی آنها تغییر پیدا کند. یعنی فرکانس سوئیچینگ برابر است با:
16000000/65535 + 1= 244Hz
و همانطور که در شکل زیر نشان داده شده است، فقط یک جابجایی 10 µs بین دو کانال داریم. مقدار 65535 نیز مربوط به Period تایمر است، در واقع حداکثر مقداری است که توسط رجیستر CNT تایمر شمارش می شود.
برای رسیدن به فرکانس سوئیچینگ مورد نظر ، باید خروجی را در هر 320 و 160 شمارش رجیستر TIM3 CNT تغییر دهیم. برای انجام این کار، می توانیم تابع callback زیر را تعریف کنیم:
62 uint16_t pulse;
63
64 /* TIM2_CH1 toggling with frequency = 50KHz */
65 if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
66 {
67 pulse = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
68 /* Set the Capture Compare Register value */
69 __HAL_TIM_SET_COMPARE(htim, TIM_CHANNEL_1, (pulse + CH1_FREQ));
70 }
71
72 /* TIM2_CH2 toggling with frequency = 100KHz */
73 if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2)
74 {
75 pulse = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2);
76 /* Set the Capture Compare Register value */
77 __HAL_TIM_SET_COMPARE(htim, TIM_CHANNEL_2, (pulse + CH2_FREQ));
78 }
79 }
()HAL_TIM_OC_DelayElapsedCallback به طور خودکار توسط HAL هر بار که رجیستر CCRx کانال با شمارنده تایمر منطبق شود، فراخوانی می شود. بنابراین می توانیم Pulse (یعنی رجیستر CCRx) را برای کانال 1 تا 320 و برای کانال 2 تا 160 افزایش دهیم. با این کار کانال مربوطه در فرکانس مورد نظر ، همانطور که در شکل زیر نشان داده شده است ، تغییر کند.
همین نتیجه را میتوان با استفاده از حالت DMA و یک بردار از قبل راهاندازیشده به دست آورد، که در نهایت با استفاده از متغیر نوع const در حافظه فلش ذخیره میشود:
const uint16_t ch1IV[] = {320, 640, 960, ...};
...
HAL_TIM_OC_Start_DMA(&htim3, TIM_CHANNEL_1, (uint32_t)ch1IV, sizeof(ch1IV));
استفاده از CubeMX برای پیکربندی Output Compare Mode
فرآیند پیکربندی حالت output compare در CubeMX با حالت input capture یکسان است. مرحله اول انتخاب حالت output compare CHx برای کانال مورد نظر است. سپس، از نمای پیکربندی TIMx،می توان سایر پارامترهای output compare را تنظیم کرد (output mode, channel polarity و غیره).
Pulse-Width Generation
امواج مربعی ایجاد شده تا کنون همه یک ویژگی مشترک دارند: آنها یک دوره زمانی به نام TON برابر با دوره زمانی TOFF دارند. به همین دلیل گفته می شود که duty cycle چرخه کاری 50 درصدی نیز دارند. چرخه وظیفه duty cycle درصدی از یک دوره زمانی (مثلاً 1 ثانیه) است که در آن سیگنال فعال است. به عنوان فرمول، چرخه وظیفه duty cycle به صورت زیر بیان می شود:
D = TON/Period × 100%
که در آن D چرخه کاری duty cycle و TON زمان فعال بودن سیگنال است. بنابراین، چرخه کاری 50٪ به این معنی است که سیگنال در 50٪ مواقع روشن و در 50٪ مواقع خاموش است. duty cycle چیزی در مورد مدت زمان ماندگاری آن نمی گوید. بسته به طول دوره زمانی period ، “زمان روشن بودن” برای چرخه کاری 50٪ می تواند کسری از ثانیه، یک روز یا حتی یک هفته باشد. عرض پالس مدت زمان TON دریک period واقعی و معین است. به عنوان مثال، با فرض یک دوره 1 ثانیه، یک چرخه کاری 20 درصد عرض پالسی برابر با 200 میلی ثانیه دارد. شکل زیر سه چرخه کاری مختلف 50%، 20% و 80%. را نشان می دهد:
مدولاسیون عرض پالس Pulse-width modulation (PWM) تکنیکی است که برای تولید چندین پالس با چرخه های کاری مختلف duty cycle در یک دوره زمانی یا در یک فرکانس معین ، استفاده می شود. PWM کاربردهای زیادی در الکترونیک دیجیتال دارد که می توان در دو دسته اصلی دسته بندی کرد:
- کنترل ولتاژ خروجی ( در نتیجه جریان).
- رمزگذاری (یعنی مدوله کردن) یک پیام (یعنی یک سری بایت در الکترونیک دیجیتال) روی یک موج حامل (که در یک فرکانس مشخص اجرا می شود).
این دو دسته را می توان در چندین کاربرد عملی PWM گسترش داد. برای کنترل ولتاژ خروجی، کاربردهای زیر را داریم:
- تولید ولتاژ خروجی از 0 ولت تا VDD (یعنی حداکثر ولتاژ مجاز برای I/O که در STM32 3.3 ولت است).
– کنترل نور LED ها (دیمر)
– کنترل موتور؛
– تبدیل توان؛
- تولید یک موج خروجی که در یک فرکانس معین اجرا می شود (موج سینوسی، مثلثی، مربعی، و غیره).
- خروجی صدا.
با استفاده از فیلتر خروجی مناسب، که معمولا از فیلتر پایین گذر استفاده می شود، PWM می تواند مانند یک DAC رفتار کند. با تغییر چرخه کاری duty cycle پین خروجی می توان ولتاژ خروجی را به طور مناسب تنظیم کرد. همچنین یک تقویت کننده می تواند محدوده ولتاژ را در صورت نیاز افزایش یا کاهش دهد یا با استفاده از ترانزیستورهای قدرت و توان بالا ، انواع جریان و بار را کنترل کرد.
کانال تایمر در حالت PWM با استفاده از تابع HAL_TIM_PWM_ConfigChannel() و نمونه ای از ساختار TIM_OC_InitTypeDef پیکربندی می شود. فیلد TIM_OC_InitTypeDef.Pulse چرخه کاری duty cycle را تعریف می کند و از 0 تا Period تایمر متغیر است. هر چه Period طولانی تر باشد، duty cycle را در محدوده بیشتری می توان تنظیم کرد. در واقع می توانیم ولتاژ خروجی را بهتر تنظیم کنیم.
انتخاب Period دوره، که فرکانس سیگنال خروجی را با کلاک تایمر (داخلی، خارجی و غیره) تعیین می کند، شانسی انتخاب نمی شود. بستگی به زمینه کاری دارد و می تواند تأثیر شدیدی بر انتشار کلی EMI داشته باشد.
علاوه بر این، برخی از دستگاههایی که با تکنیک PWM کنترل میشوند ممکن است در فرکانسهای معین، صداهایی را از خود ساطع کنند. این موضوع در مورد موتورهای الکتریکی است که هنگام کنترل در فرکانس های محدوده شنوایی، می توانند صدای وزوز ناخواسته را ایجاد کنند. مثال مشابه دیگر ، صدای منتشر شده توسط سلف های قدرت در منابع تغذیه سوئیچینگ است که از مفهوم پایه PWM برای تنظیم ولتاژ خروجی و در نتیجه جریان استفاده می کنند. گاهی اوقات رهایی از صدای خروجی اجتناب ناپذیر است و برای کاهش مشکل باید از محصولات خاصی استفاده کرد. در موارد دیگر، فرکانس مناسب از «محدودیتهای طبیعی» ناشی میشود: کاهش نور LED در فرکانس نزدیک به 100 هرتز معمولاً برای جلوگیری از سوسو زدن نورکافی است.
دو حالت PWM وجود دارد: حالت PWM 1 و 2. هر دوی آنها از طریق فیلد TIM_OC_InitTypeDef.OCMode با استفاده از مقادیر TIM_OCMODE_PWM1 و TIM_OCMODE_PWM2 قابل تنظیم هستند:
- حالت PWM 1: در هنگام شمارش رو به بالا، کانال تا زمانی فعال بوده که Period <Pulse، وگرنه غیر فعال می شود. در هنگام شمارش رو به پایین، کانال تا زمانی غیر فعال بوده که Period > Pulse ، وگرنه فعال است.
- حالت PWM 2: در هنگام شمارش رو به بالا، کانال 1 تا زمانی غیر فعال بوده که Period <Pulse، وگرنه فعال می شود. در هنگام شمارش رو به پایین، کانال 1 تا زمانی فعال بوده که Period > Pulse ، وگرنه غیر فعال است.
مثال زیر کاربرد تکنیک PWM برای دیمر در LED را نشان می دهد. این مثال بر روی برد Nucleo-F401RE طراحی شده است و نور LED LD2 را کاهش و افزایش می دهد.
11 int main(void) {
12 HAL_Init();
13
14 Nucleo_BSP_Init();
15 MX_TIM2_Init();
16
17 HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
18
19 uint16_t dutyCycle = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_1);
20
21 while(1) {
22 while(dutyCycle 0) {
28 __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, --dutyCycle);
29 HAL_Delay(1);
30 }
31 }
32 }
33
34 /* TIM3 init function */
35 void MX_TIM2_Init(void) {
36 TIM_OC_InitTypeDef sConfigOC;
37
38 htim2.Instance = TIM2;
39 htim2.Init.Prescaler = 499;
40 htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
41 htim2.Init.Period = 999;
42 htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
43 HAL_TIM_PWM_Init(&htim2);
44
45 sConfigOC.OCMode = TIM_OCMODE_PWM1;
46 sConfigOC.Pulse = 0;
47 sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
48 sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
49 HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1);
50 }
خطوط [45:49] اولین کانال تایمر TIM2 را برای کار در حالت PWM 1 پیکربندی میکنند. چرخه کاری duty cycle از 0 تا 999، متغیر خواهد بود ، که با مقدار Period مطابقت دارد. یعنی اگر فیلتر خوبی در خروجی داشته باشیم ( PCB به خوبی طراحی شده باشد) می توانیم ولتاژ خروجی را با دقت ∼0,0033V ولت تنظیم کنیم که مانند یک DAC 10 بیتی عمل می کند. در خطوط [21:32] حالت دیمر انجام می شود. حلقه اول مقدار Pulse را (که مربوط به رجیستر Capture Compare (CCR1) است) تا مقدار Period (که مربوط به رجیستر مجدد Auto Reload (ARR) است) هر 1 میلی ثانیه افزایش می دهد. یعنی در کمتر از 1 ثانیه LED کاملاً روشن می شود. حلقه دوم، به همین ترتیب، مقدار Pulse را کاهش می دهد تا به صفر برسد.
فرکانس به روز رسانی تایمر روی 84MHz/(499+1)(999+1)=168Hz تنظیم شده است. همین فرکانس را می توان با تنظیم Prescaler روی 249 و Period روی 1999 به دست آورد. اما عملکرد دیمرتغییر می کند.
تولید موج سینوسی با استفاده از PWM
موج مربعی تولید شده با PWM را بعد از عبور از یک فیلتر در مسیر ، می توان برای تولید یک سیگنال آنالوگ صاف که دارای ولتاژ پیک تا پیک کاهش یافته (Vpp) است، تولید کرد. فیلتر پایین گذر مقاومت-خازن (RC) (شکل زیر) می تواند تمام سیگنال های AC که فرکانس بالاتری از یک آستانه معین دارند را قطع کند. قانون کلی فیلترهای پایین گذر RC این است که هر چه فرکانس قطع کمتر باشد Vpp نیز کمتر است. فیلتر پایین گذر RC از یکی از ویژگی های مهم خازن ها استفاده می کند: توانایی مسدود کردن جریان های DC در حالی که اجازه عبور جریان های AC را می دهد: با توجه به ثابت زمانی R/C که توسط شبکه مقاومت-خازن تشکیل می شود، فیلتر ، سیگنال های AC ای را که فرکانسی بالاتر از ثابت زمانی R/C دارند ،به زمین مدار هدایت می کند و به بخش DC سیگنال و ولتاژهای AC با فرکانس کم تر از ثابت زمانی R/C ، اجازه عبور دهد.
در حالی که این مدار بسیار ساده است، انتخاب مقادیر مناسب برای R (مقاومت) و C (خازن) به برخی از تصمیمات طراحی مربوط می شود: اینکه چقدر از ریپل امواج قابل تحمل است و فیلتر چقدر سریع باید پاسخ دهد. این دو پارامتر متقابل هستند. در اکثر فیلترها، دوست داریم فیلتری عالی داشته باشیم – فیلتری که تمام فرکانسهای کمتر را از فرکانس قطع را بدون ریپل سیگنال عبور دهد. متأسفانه همچین فیلتر ایدهآلی وجود ندارد: برای کاهش ریپل به صفر، باید فیلتر بسیار بزرگی را انتخاب کنیم، که باعث شده زمان زیادی طول بکشد تا خروجی پایدار شود. در حالی که این موضوع می تواند برای یک ولتاژهای پیوسته و ثابت قابل قبول باشد، اما اگر بخواهیم یک شکل موج پیچیده از سیگنال PWM تولید کنیم، تاثیر شدیدی بر کیفیت سیگنال خروجی دارد.
فرکانس قطع (fc) یک فیلتر پایین گذر RC مرتبه اول با فرمول زیر بیان می شود:
fc =1/2πRC
شکل زیر اثر یک فیلتر پایین گذر بر روی سیگنال PWM با فرکانس 100 هرتز را نشان می دهد. در اینجا یک مقاومت 1K و یک خازن 10μF را انتخاب کرده ایم. در نتیجه فرکانس قطع برابر است با:
fc = 1 /2π103 × 10–5 ≈ 15.9Hz
شکل زیر اثر فیلتر پایین گذر را با یک مقاومت 4300K و یک خازن 10μF نشان می دهد. در نتیجه فرکانس قطع برابر است با:
fc =1/2π(4.3 × 103) × 10–5 ≈ 3.7Hz
با تغییر ولتاژ خروجی (به این معنی که چرخه کار duty cycle را تغییر می دهیم) می توانیم یک شکل موج دلخواه در خروجی ایجاد کنیم که فرکانس آن کسری از دوره PWM است. ایده اصلی این است که شکل موج مورد نظر را (به عنوان مثال یک موج سینوسی) به تعداد «x» بخش تقسیم کنیم و برای هر بخش یک چرخه PWM داریم. زمان TON (یعنی چرخه کاری duty cycle) مستقیماً با دامنه شکل موج در آن بخش مطابقت دارد که با استفاده از تابع sin() محاسبه می شود.
نمودار نشان داده شده در شکل بالا را در نظر بگیرید. در اینجا موج سینوسی به 10 بخش تقسیم شده است. بنابراین در اینجا نیاز به 10 پالس مختلف PWM داریم که به صورت سینوسی افزایش/کاهش داده میشوند. یک پالس PWM با چرخه کاری 0٪، دامنه حداقل (0 ولت) و پالس با چرخه کاری 100٪، حداکثر دامنه (3.3 ولت) را نشان می دهد. از آنجایی که خروجی PWM دارای نوسان ولتاژ بین 0 ولت تا 3.3 ولت است، موج سینوسی ما نیز بین 0 ولت تا 3.3 ولت نوسان می کند.
360 درجه طول می کشد تا موج سینوسی یک سیکل را کامل کند. بنابراین برای 10 بخش باید زاویه را در هر مرحله 36 درجه افزایش دهیم. به این نرخ گام زاویه یا وضوح زاویه (Angle Step Rate or Angle Resolution) می گویند. می توانیم تعداد تقسیمات را افزایش دهیم تا شکل موج دقیق تری داشته باشیم. اما با افزایش تقسیمات، باید وضوح را نیز افزایش دهیم، در واقع باید فرکانس تایمر مورد استفاده برای تولید سیگنال PWM را افزایش دهیم (هر چه تایمر سریعتر اجرا شود دوره کوچکتر است).
معمولاً 200 بخش تقریب خوبی برای موج خروجی است. بدان معنا که اگر بخواهیم یک موج سینوسی 50 هرتز تولید کنیم، باید تایمر را با فرکانس 50Hz*200 = 10kHz کیلوهرتز اجرا کنیم. دوره پالس برابر با 200 خواهد بود (تعداد مراحل – یعنی ولتاژ خروجی را هر 3.3V/200=0.016V تغییر می دهیم)، بنابراین مقدار Prescaler برابر خواهد بود (با فرض اینکه میکروکنترلر STM32F030 در فرکانس 48 مگاهرتز کار می کند):
Prescaler = 48MHz /50Hz × 200divisions × 200Pulse = 24
مثال زیر نحوه تولید یک موج سینوسی خالص 50 هرتز در میکروکنترلر STM32F030 که با فرکانس 48 مگاهرتز کار می کند را نشان می دهد :
14 #define PI 3.14159
15 #define ASR 1.8 //360 / 200 = 1.8
16
17 int main(void) {
18 uint16_t IV[200];
19 float angle;
20
21 HAL_Init();
22
23 Nucleo_BSP_Init();
24 MX_TIM3_Init();
25
26 for (uint8_t i = 0; i < 200; i++) {
27 angle = ASR*(float)i;
28 IV[i] = (uint16_t) rint(100 + 99*sinf(angle*(PI/180)));
29 }
30
31 HAL_TIM_PWM_Start_DMA(&htim3, TIM_CHANNEL_1, (uint32_t *)IV, 200);
32
33 while (1);
34 }
35
36 /* TIM3 init function */
37 void MX_TIM3_Init(void) {
38 TIM_OC_InitTypeDef sConfigOC;
39
40 htim3.Instance = TIM3;
41 htim3.Init.Prescaler = 23;
42 htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
43 htim3.Init.Period = 199;
44 htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV4;
45 HAL_TIM_PWM_Init(&htim3);
46
47 sConfigOC.OCMode = TIM_OCMODE_PWM1;
48 sConfigOC.Pulse = 0;
49 sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
50 sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
51 HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1);
52
53 hdma_tim3_ch1_trig.Instance = DMA1_Channel4;
54 hdma_tim3_ch1_trig.Init.Direction = DMA_MEMORY_TO_PERIPH;
55 hdma_tim3_ch1_trig.Init.PeriphInc = DMA_PINC_DISABLE;
56 hdma_tim3_ch1_trig.Init.MemInc = DMA_MINC_ENABLE;
57 hdma_tim3_ch1_trig.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
58 hdma_tim3_ch1_trig.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
59 hdma_tim3_ch1_trig.Init.Mode = DMA_CIRCULAR;
60 hdma_tim3_ch1_trig.Init.Priority = DMA_PRIORITY_LOW;
61 HAL_DMA_Init(&hdma_tim3_ch1_trig);
62
63 /* Several peripheral DMA handle pointers point to the same DMA handle.
64 Be aware that there is only one channel to perform all the requested DMAs. */
65 __HAL_LINKDMA(&htim3, hdma[TIM_DMA_ID_CC1], hdma_tim3_ch1_trig);
66 __HAL_LINKDMA(&htim3, hdma[TIM_DMA_ID_TRIGGER], hdma_tim3_ch1_trig);
خطوط [26:29] کد ، برای تولید بردار Initialization Vector (IV) استفاده می شود، یعنی بردار حاوی مقادیرPulse پالس مورد استفاده برای تولید موج سینوسی (مطابق با سطوح ولتاژ خروجی) است. تابع sinf() سینوس زاویه داده شده را که بر حسب رادیان بیان شده است برمی گرداند. بنابراین باید زاویه را با استفاده از فرمول زیر از درجه به رادیان تبدیل کنیم:
Radians = π /180° × Degrees
در اینجا ما هر سیکل از موج سینوسی را به 200 مرحله تقسیم کرده ایم (یعنی محیط را به 200 بخش تقسیم کرده ایم)، بنابراین باید مقدار رادیان هر مرحله را محاسبه کنیم. اما از آنجایی که مقادیر سینوس برای زاویه بین 180 تا 360 درجه منفی می باشد (شکل زیر )، باید آن را مقیاس بندی کنیم، زیرا مقادیر PWM در خروجی نمی تواند منفی باشد.
هنگامی که بردار IV تولید شد، PWM را در حالت DMA شروع می کنیم. DMA1_Channel4 برای کار در حالت دایره ای circular پیکربندی شده است، که به طور خودکار مقدار رجیستر TIMx_CCRx را مطابق مقادیر پالس موجود در بردار IV پر می کند. استفاده از تایمر در حالت DMA بهترین راه برای جلوگیری از ایجاد تأخیر و تأثیر بر هسته Cortex-M است.
استفاده از CubeMX برای پیکربندی حالت PWM
فرآیند پیکربندی حالت PWM در CubeMX پس از تسلط بر مفاهیم اساسی PWM ساده است. مرحله اول انتخاب حالت PWM Generation CHx برای کانال مورد نظر و در مرحله بعد، از نمای پیکربندی TIMx ، امکان تنظیم سایر ویژگی های PWM وجود دارد. ( PWM mode 1 or 2, channel polarity,….. ).
حالت One Pulse Mode
حالت یک پالس One Pulse Mode (OPM) ترکیبی از حالت های input capture و output compare است که توسط تایمرهای عمومی general purpose و پیشرفته advanced ارائه می شود. این حالت کاری اجازه می دهد تا شمارنده در پاسخ به یک محرک راه اندازی شود و پس از یک تاخیر قابل برنامه ریزی، یک پالس با مدت زمان قابل برنامه ریزی (PWM) تولید کند.
OPM حالتی است که منحصراً برای کانال های 1 و 2 تایمر طراحی شده است. با استفاده از تابع زیر می توانیم تصمیم بگیریم کدام یک از این دو کانال خروجی و کدام ورودی باشد:
HAL_TIM_OnePulse_ConfigChannel(TIM_HandleTypeDef
*htim, TIM_OnePulse_InitTypeDef* sConfig,
uint32_t OutputChannel, uint32_t InputChannel);
هر دو کانال با یک نمونه از ساختار TIM_OnePulse_InitTypeDef پیکربندی و به شکل زیر تعریف میشود:
typedef struct {
uint32_t Pulse; /* Specifies the pulse value to be loaded into the CCRx register.*/
/* Output channel configuration */
uint32_t OCMode; /* Specifies the TIM mode. */
uint32_t OCPolarity; /* Specifies the output polarity. */
uint32_t OCNPolarity; /* Specifies the complementary output polarity. */
uint32_t OCIdleState; /* Specifies the TIM Output Compare pin state during Idle state.*/
uint32_t OCNIdleState; /* Specifies the TIM Output Compare pin state during Idle state.*/
/* Input channel configuration */
uint32_t ICPolarity; /* Specifies the active edge of the input signal. */
uint32_t ICSelection; /* Specifies the input. */
uint32_t ICFilter; /* Specifies the input capture filter. */
} TIM_OnePulse_InitTypeDef;
console.log( 'Code is Poetry' );
ساختار به طور منطقی به دو بخش تقسیم می شود: یکی مربوط به پیکربندی کانال ورودی و دیگری مربوط به کانال خروجی است. وارد جزئیات فیلدهای این ساختار نخواهیم شد، چون آنها شبیه به آنچه که تا به حال در مورد حالت های input capture و output compare صحبت کردیم، هستند.
مورد مهم درک نحوه محاسبه زمان تأخیر و مدت زمان پالس توسط تایمر است. تاخیر طبق فرمول زیر محاسبه می شود:
Delay = Pulse/(T IMx_CLK/ Prescaler+1)
و مدت زمان (یعنی چرخه کاری duty cycle) پالس نیز با این فرمول محاسبه می شود:
Duration = (Period – Pulse)/(T IMx_CLK/ Prescaler+1)
در واقع هنگامی که کانال ورودی رویداد محرک (Trigg) را شناسایی کرد، تایمر شروع به شمارش می کند و هنگامی که رجیستر CNT با رجیستر CCRx (Pulse) برابر شد، سیگنال خروجی را تولید می کند و تا زمانی که رجیستر CNT به رجیستر ARR (Period) برسد، طول می کشد(ادامه می یابد). (Period – Pulse)
OPM را می توان به صورت تک shot یا در حالت تکراری repetitive تنظیم کرد:
HAL_TIM_OnePulse_Init(TIM_HandleTypeDef *htim, uint32_t OnePulseMode);
که TIM_OPMODE_SINGLE را برای پیکربندی OPM به صورت تک shot یا TIM_OPMODE_REPETITIVE را برای فعال کردن تکراری repetitive می پذیرد.
مثال زیر نحوه پیکربندی TIM3 را در حالت OPM در میکروکنترلر STM32F030 نشان می دهد.
12 int main(void) {
13 HAL_Init();
14
15 Nucleo_BSP_Init();
16 MX_TIM3_Init();
17
18 HAL_TIM_OnePulse_Start(&htim3, TIM_CHANNEL_1);
19
20 while (1);
21 }
22
23 /* TIM3 init function */
24 void MX_TIM3_Init(void) {
25 TIM_OnePulse_InitTypeDef sConfig;
26
27 htim3.Instance = TIM3;
28 htim3.Init.Prescaler = 47;
29 htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
30 htim3.Init.Period = 65535;
31 HAL_TIM_OnePulse_Init(&htim3, TIM_OPMODE_SINGLE);
32
33 /* Configure the Channel 1 */
34 sConfig.OCMode = TIM_OCMODE_PWM1;
35 sConfig.OCPolarity = TIM_OCPOLARITY_LOW;
36 sConfig.Pulse = 19999;
37
38 /* Configure the Channel 2 */
39 sConfig.ICPolarity = TIM_ICPOLARITY_RISING;
40 sConfig.ICSelection = TIM_ICSELECTION_DIRECTTI;
41 sConfig.ICFilter = 0;
42
43 HAL_TIM_OnePulse_ConfigChannel(&htim3, &sConfig, TIM_CHANNEL_1, TIM_CHANNEL_2);}
خطوط [34:36] کانال خروجی را در حالت PWM 1 ، خطوط [39:41] کانال ورودی را پیکربندی می کنند.تابع HAL_TIM_OnePulse_ConfigChannel()، در خط 43، کانال 1 را به عنوان خروجی و کانال 2 را به عنوان ورودی تنظیم می کند. در نهایت HAL_TIM_OnePulse_Start() (که در خط 18 فراخوانی می شود) تایمر را در حالت OPM راه اندازی می کند. با اعمال ولتاژ به پین PA7 در Nucleo-F030R8، تایمر پس از 20 میلیثانیه تأخیر راهاندازی میشود و همانطور که در شکل زیر نشان داده شده است، یک PWM در حدود 45 میلیثانیه تولید میکند.
کانال خروجی یک تایمر در حالت کاری One Pulse را می توان حتی در حالت های دیگر غیر از حالت PWM پیکربندی کرد.
استفاده از CubeMX برای پیکربندی حالت OPM
برای فعال کردن حالت OPM با استفاده از CubeMX، اولین گام پیکربندی دو کانال 1 و 2 به طور مستقل می باشد و سپس همانطور که در شکل زیر نشان داده شده است، گزینه حالت One Pulse را انتخاب کنید. سپس، از نمای پیکربندی TIMx ، می توان تنظیمات دیگر هر یک از کانال ها را انجام داد.
نکته مهم : در زمان نوشتن این بخش، کد تولید شده توسط CubeMX چندان خوب نیست. کد تولید شده از HAL_TIM_OnePulse_ConfigChannel() استفاده نمی کند، و هر کانال را به گونه ای پیکربندی می کند که می تواند به طور مستقل استفاده شود. این امر منجر به پیچیده تر شدن کد می شود. با این حال، ممکن است زمانی که این بخش را میخوانید، ST آن را اصلاح کرده باشد.
حالت Encoder Mode
انکودرهای چرخشی دستگاه هایی هستند که طیف وسیعی از کاربردها را دارند. از آنها برای اندازه گیری سرعت و همچنین موقعیت زاویه ای اجسام در حال چرخش ، برای اندازه گیری RPM ، جهت موتور، کنترل سروو موتورها و همچنین استپر موتورها و غیره استفاده می کنند. انواع مختلفی از انکودرهای چرخشی وجود دارد: نوری، مکانیکی، مغناطیسی.
انکودرهای افزایشی نوعی از انکودرهای چرخشی هستند که هنگام تشخیص حرکت ، در خروجی یک چرخش ایجاد می کند. نوع مکانیکی نیاز به انحراف دارد و معمولاً به عنوان “پتانسیومتر دیجیتال” استفاده می شود. اکثر استریوهای مدرن از انکودرهای چرخشی مکانیکی برای کنترل صدا استفاده می کنند. انکودرهای چرخشی افزایشی به دلیل هزینه کم و توانایی آن در ارائه سیگنال هایی که به راحتی می توانند برای ارائه اطلاعات مربوط به حرکت مانند سرعت تفسیر شوند، بیشترین استفاده را در بین تمام انکودرهای چرخشی دارند.
آنها از دو خروجی به نامهای A و B استفاده میکنند که خروجیهای مربعی نامیده میشوند، زیرا 90 درجه اختلاف فاز دارند.( همانطور که در شکل بالا نشان داده شده است.) جهت موتور بستگی دارد که فاز A به فاز B ،یا فاز B به فاز A حرکت می کند .
یک کانال سوم اختیاری، index pulse ، یک بار در هر دور رخ می دهد و به عنوان مرجع برای اندازه گیری موقعیت مطلق استفاده می شود. راه های مختلفی برای تشخیص جهت و موقعیت انکودرهای چرخشی وجود دارد. با اتصال پین های A و B به دو ورودی/خروجی MCU، می توان تشخیص داد چه زمان سیگنال HIGH and LOW می شود. این کار را می توان هم به صورت دستی انجام داد (با استفاده از وقفه ها برای ثبت زمانی که کانال تغییر وضعیت می دهد) یا با استفاده از یک تایمر: کانال های آن را می توان در حالت input capture پیکربندی کرد و مقادیر دریافتی برای محاسبه جهت و سرعت انکودر مقایسه می شوند.
تایمرهای عمومی general purpose در STM32 روشی مناسبی برای خواندن انکودر های چرخشی ارائه می دهند: این حالت encoder mode نامیده می شود و فرآیند ثبت داده را بسیار ساده می کند. هنگامی که یک تایمر در حالت encoder mode پیکربندی می شود، رجیستر شمارنده تایمر (TIMx_CNT) در لبه کانال های ورودی افزایش/کاهش می یابد.
دو حالت ثبت داده موجود است: X2 و X4. در حالت X2، رجیستر CNT فقط در هر لبه از یک کانال (T1 یا T2) افزایش/کاهش مییابد. در حالت X4، رجیستر CNT در هر لبه از دو کانال به روز می شود. این حالت باعث دو برابر شدن فرکانس ثبت داده می شود. جهت حرکت به طور خودکار محاسبه شده و در رجیستر TIMx_DIR در اختیار برنامه نویس قرار می گیرد. با توجه به تعداد پالس هایی که انکودر در هر دور ارائه می دهد و با مقایسه منظم مقدار رجیستر شمارنده ، می توان تعداد RPM محاسبه کرد.
انکودر های مکانیکی افزایشی معمولاً به دلیل نویز در خروجی نیاز به بررسی دارند. معمولاً از یک مقایسه کننده به عنوان قسمت فیلتر این دستگاه ها استفاده می شود، به خصوص اگر از آنها برای اتصال موتورها و سایر دستگاه های نویز بالا استفاده شوند. تحت شرایط خاص، بخش فیلتر ورودی تایمر میکروکنترلر STM32 را می توان برای فیلتر کردن کانال های A و B استفاده کرد و تعداد قطعات BOM را کاهش داد.
حالت encoder mode فقط در کانال های TI1 و TI2 موجود است و با استفاده از تابع HAL_TIM_Encoder_Init() و نمونه ای از ساختار TIM_Encoder_InitTypeDef فعال می شود که به شکل زیر تعریف شده است.
typedef struct {
/* T1 channel */
uint32_t EncoderMode; /* Specifies the active edge of the input signal. */
uint32_t IC1Polarity; /* Specifies the active edge of the input signal. */
uint32_t IC1Selection; /* Specifies the input. */
uint32_t IC1Prescaler; /* Specifies the Input capture prescaler. */
uint32_t IC1Filter; /* Specifies the input capture filter. */
/* T2 channel */
uint32_t IC2Polarity; /* Specifies the active edge of the input signal. */
uint32_t IC2Selection;/* Specifies the input. */
uint32_t IC2Prescaler; /* Specifies the Input capture prescaler. */
uint32_t IC2Filter; /* Specifies the input capture filter. */
} TIM_Encoder_InitTypeDef;
با اکثر فیلدهای TIM_Encoder_InitTypeDef در بخش های قبلی آشنا شده ایم. تنها مورد قابل توجه EncoderMode است که می تواند مقادیر TIM_ENCODERMODE_TI1 یا TIM_ENCODERMODE_TI2 را برای انتخاب حالت انکودر X2 در یکی از دو کانال و مقدار TIM_ENCODERMODE_TI12 را برای تنظیم حالت X4 به طوری که رجیستر TIMx_CNT در هر لبه از کانال های TI و TI2 به روز شود ، در نظر بگیرد.
مثال زیر که بر روی برد Nucleo-F030R8 طراحی شده است، یک انکودر افزایشی را با استفاده از TIM1 در حالت output compare شبیه سازی می کند. کانالهای OC1 و OC2 (PA8, PA9) در TIM1 با استفاده از کانکتور به کانالهای TI1 و TI2 (PA6, PA7) در TIM3 متصل میشوند و به گونهای پیکربندی شدهاند که دو سیگنال موج مربعی با دوره مشابه اما در فاز متفاوت تولید می کنند. سپس TIM3 در حالت EncoderMode پیکربندی می شود. تایمر SysTick برای تولید پایه زمانی timebase استفاده می شود یعنی در هر 1 ثانیه، تعداد پالس ها به همراه جهت انکودر محاسبه می شود. سپس تعداد دور در دقیقه، با فرض انکودری که 4 پالس برای هر دور تولید می کند، محاسبه می شود. در نهایت، با فشار دادن دکمه USER میتوان فاز بین کانال های A و B را تغییر داد: (چرخش انکودر برعکس می شود.)
22 #define PULSES_PER_REVOLUTION 4
23
24 int main(void) {
25 HAL_Init();
26
27 Nucleo_BSP_Init();
28 MX_TIM1_Init();
29 MX_TIM3_Init();
30
31 HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL);
32 HAL_TIM_OC_Start(&htim1, TIM_CHANNEL_1);
33 HAL_TIM_OC_Start(&htim1, TIM_CHANNEL_2);
34
35 cnt1 = __HAL_TIM_GET_COUNTER(&htim3);
36 tick = HAL_GetTick();37 38 while (1) {
39 if (HAL_GetTick() - tick > 1000L) {
40 cnt2 = __HAL_TIM_GET_COUNTER(&htim3);
41 if (__HAL_TIM_IS_TIM_COUNTING_DOWN(&htim3)) {
42 if (cnt2 cnt1) /* Check for counter overflow */
48 diff = cnt2 - cnt1;
49 else
50 diff = (65535 - cnt1) + cnt2;
51 }
52
53 sprintf(msg, "Difference: %d\r\n", diff);
54 HAL_UART_Transmit(&huart2, (uint8_t*) msg, strlen(msg), HAL_MAX_DELAY);
55
56 speed = ((diff / PULSES_PER_REVOLUTION) / 60);
57
58 /* If the first three bits of SMCR register are set to 0x3
59 * then the timer is set in X4 mode (TIM_ENCODERMODE_TI12)
60 * and we need to divide the pulses counter by two, because
61 * they include the pulses for both the channels */
62 if ((TIM3->SMCR & 0x3) == 0x3)
63 speed /= 2;
64
65 sprintf(msg, "Speed: %d RPM\r\n", speed);
66 HAL_UART_Transmit(&huart2, (uint8_t*) msg, strlen(msg), HAL_MAX_DELAY);
67
68 dir = __HAL_TIM_IS_TIM_COUNTING_DOWN(&htim3);
69 sprintf(msg, "Direction: %d\r\n", dir);
70 HAL_UART_Transmit(&huart2, (uint8_t*) msg, strlen(msg), HAL_MAX_DELAY);
71
72 tick = HAL_GetTick();
73 cnt1 = __HAL_TIM_GET_COUNTER(&htim3);
74 }
75
76 if (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) == GPIO_PIN_RESET) {
77 /* Invert rotation by swapping CH1 and CH2 CCR value */
78 tim1_ch1_pulse = __HAL_TIM_GET_COMPARE(&htim1, TIM_CHANNEL_1);
79 tim1_ch2_pulse = __HAL_TIM_GET_COMPARE(&htim1, TIM_CHANNEL_2);
80
81 __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, tim1_ch2_pulse);
82 __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, tim1_ch1_pulse);
83 }
84 }
85 }
86
87 /* TIM1 init function */
88 void MX_TIM1_Init(void) {
89 TIM_OC_InitTypeDef sConfigOC;
90
91 htim1.Instance = TIM1;
92 htim1.Init.Prescaler = 9;
93 htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
94 htim1.Init.Period = 999;
95 HAL_TIM_Base_Init(&htim1);
96
97 sConfigOC.OCMode = TIM_OCMODE_TOGGLE;
98 sConfigOC.Pulse = 499;
99 sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
100 sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
101 sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
102 sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
103 sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
104 HAL_TIM_OC_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1);
105
106 sConfigOC.Pulse = 999; /* Phase B is shifted by 90° */
107 HAL_TIM_OC_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_2);
108 }
109
110 /* TIM3 init function */
111 void MX_TIM3_Init(void) {
112 TIM_Encoder_InitTypeDef sEncoderConfig;
113
114 htim3.Instance = TIM3;
115 htim3.Init.Prescaler = 0;
116 htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
117 htim3.Init.Period = 65535;
118
119 sEncoderConfig.EncoderMode = TIM_ENCODERMODE_TI12;
120
121 sEncoderConfig.IC1Polarity = TIM_ICPOLARITY_RISING;
122 sEncoderConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI;
123 sEncoderConfig.IC1Prescaler = TIM_ICPSC_DIV1;
124 sEncoderConfig.IC1Filter = 0;
125
126 sEncoderConfig.IC2Polarity = TIM_ICPOLARITY_RISING;
127 sEncoderConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI;
128 sEncoderConfig.IC2Prescaler = TIM_ICPSC_DIV1;
129 sEncoderConfig.IC2Filter = 0;
130
131 HAL_TIM_Encoder_Init(&htim3, &sEncoderConfig);
132 }
تابع MX_TIM1_Init تایمر TIM1 را برای کانالهای OC1 و OC2 در حالت output compare پیکربندی میکند که هر 20μs خروجی خود را فعال می کنند. این دو خروجی با تنظیم دو مقدار مختلف متغیر Pulse (خطوط 84 و 92) در فاز جابجا می شوند. تابع MX_TIM3_Init() TIM3 را در حالت X4 انکودر (TIM_ENCODERMODE_TI12) ، پیکربندی می کند.
تابع main() طوری طراحی شده است که در هر 1000 تیک SysTimer (برای تولید یک تیک در هر 1 میلی ثانیه پیکربندی شده است) محتوای فعلی رجیستر شمارنده (cnt2) را با مقدار ذخیره شده (cnt1) مقایسه می کند: مطابق جهت انکودر (بالا یا پایین)، اختلاف محاسبه شده و سرعت بدست می آید. کد برنامه همچنین باید overflow/underflow نهایی شمارنده را تشخیص دهد و اختلاف را بر این اساس محاسبه کند.
توجه داشته باشید چون ما در هر ثانیه یک مقایسه را انجام می دهیم، TIM1 باید طوری تنظیم شود که مجموع پالس های تولید شده توسط کانال های A و B کمتر از 65535 در ثانیه باشد. به همین منظور، برای کاهش سرعت TIM1 ، Prescaler آن را برابر با 9 قرار میدهیم.
در نهایت، خطوط [76:83] با فشار دادن دکمه کاربر برد Nucleo، فاز بین A و B (یعنی کانال های OC1 و OC2 تایمر TIM1) را معکوس می کنند.