در قسمت قبل از سری آموزش STM32 با توابع HAL، در مورد تایمرها و کاربردهای و نمونهای از آنها صحبت شد. در این قسمت، نحوه اتصال تایمرها به هم و ساخت تایمر بزرگتر را بررسی میکنیم. همچنین یک کاربرد نمونه چنین تایمری را با توسعه یک پروژه ساده نشان میدهیم.
همهی تایمرهایی که معرفی کردیم و در میکروهای مورداستفاده در این سری آموزشی وجود دارند (بهجز SysTick)، 16 بیتی هستند. اما در این قسمت، میخواهیم نحوه ساخت تایمرهای 32 بیتی و 64 بیتی را بررسی کنیم. همانطور که احتمالاً میدانید، در میکروکنترلرهای مورداستفاده در این آموزش، همه تایمرها (بهجز SysTick) 16 بیتی هستند. که البته ممکن است این مسئله مشکلساز نباشد. اما گاهی پیش میآید که برای اندازهگیری طول یک پالس با دقت بالا، نیاز پیدا کنیم که تایمری با تعداد بیتهای بیشتری در اختیار داشته باشیم. یا اینکه اگر قصد شمارش تعداد پالسها یا لبههای یک سیگنال خارجی را داشته باشیم که تعداد آنها از ۶۵۵۳۶ بیشتر باشد، تایمرهای 16 بیتی کارساز نیستند. پس در این موقعیتها، راهحل چه خواهد بود؟ آیا لازم است که از خانوادهای از میکروکنترلر استفاده کرد که تایمرهایی با دقت بالا دارند؟ یا میشود به شکل دیگری این مسئله را حل کرد؟ البته باید گفت که راههای نرمافزاری برای حل این مشکلها وجود دارد. اما در این قسمت یک راهحل ساده سختافزاری برای عبور کردن از این محدودیت، معرفی خواهیم کرد.
ساخت تایمر 64 بیتی
همانطور که میدانید، در میکروکنترلر STM32F103C8T6 که در بورد Blue Pill بهکاررفته است، تنها تایمرهای 16 بیتی وجود دارد. اگر برای کاربرد خاصی نیاز به تایمر بزرگتری داشته باشیم (مثلاً برای اندازهگیری فرکانسهای بالا یا شمارشهای طولانی یک واقعه یا گذر زمان)، باید با استفاده از این تایمرهای 16 بیتی، و اتصال آنها بهصورت سختافزاری، عمل موردنظرمان را انجام دهیم. به عبارت دقیقتر اگر تایمرها را جوری متصل کنیم که هر بار سرریز تایمر1 بهعنوان کلاک تایمر 2 عمل کند، بدین گونه یک تایمر 32 بیتی ساختهایم. به همین ترتیب اگر 3 یا 4 تایمر را به هم متصل کنیم، تایمرهای 48 و 64 بیتی در اختیار خواهیم داشت.
برای اینکه مسئله موردنظر را بهتر درک کنیم، میتوانیم به یک حالت سادهتر این موضوع نگاه کنیم؛ زمانی که میخواهیم بهوسیله اتصال چند شمارنده، یک شمارنده بزرگتر بسازیم، میتوانیم با استفاده از پایه سرریز یا پایه پرارزش یک شمارنده، کلاک یک شمارنده پرارزشتر را کنترل کنیم. بدینوسیله هرگاه شمارش به حداکثر مقدار خود در شمارنده اول میرسد، شمارنده پرارزشتر یک واحد شمارش میکند. به همین ترتیب شمارنده دوم نیز میتواند کلاک شمارنده سوم را کنترل کند و این روند ادامه پیدا کند. همچنین میدانیم که تایمرهای میکروکنترلرها نیز درواقع شمارنده هستند. پس احتمالاً به روش مشابه میتوان تایمرها را برای ساخت تایمر بزرگتر نیز، به یکدیگر متصل کرد.
قبلاً گفته شد که برای هر تایمر، منبع کلاک میتواند بهصورت داخلی یا خارجی تنظیم میشود. همچنین، برای هر تایمر یک منبع تریگر میتوان تعریف کرد. این تریگر میتواند از منبعهای مختلفی فرستاده شود. در حالتی که تایمر روی حالت Slave Mode و External Clock Mode1 تنظیمشده باشد، منبع تریگر بهعنوان منبع کلاک در نظر گرفته میشود. بنابراین با این روش میتوان کلاک یک تایمر را توسط یک تایمر دیگر تأمین نمود. در ادامه برای ساخت تایمر بزرگتر (32 و 64 بیتی) یک پروژه ایجاد میکنیم.
ایجاد پروژه
روال معمول ایجاد پروژه ساخت تایمر بزرگتر را تا مرحله تنظیم کلاک و دیباگ طی میکنیم و سپس تایمرها را بهصورت زیر تنظیم مینماییم:
همانطور که در تصویر بالا دیده میشود، منبع کلاک تایمر1، در حالت ETR2 و از طریق سیگنال خارجی و پایه PA12 تأمین میگردد. همچنین Trigger Event برای این تایمر در حالت Update Event انتخابشده است. در ادامه، TIM2، TIM3 و TIM4 مشابه هم و بهصورت زیر تنظیم میشوند:
بدین ترتیب هر تایمر با update event یا سرریز کردن یک تایمر دیگر، شمارش میکند و عملاً هر 4 تایمر را به هم متصل کردهایم. کلاک تایمر اول نیز از منبع خارجی تأمین میشود تا بتوانیم تعداد پالس یک سیگنال خاص را بشماریم.
دستورات مورد نیاز
برای استفاده از تایمرهای متصل شده (برای ساخت تایمر بزرگتر ) بهعنوان شمارنده یک سیگنال خارجی، میتوان بهسادگی هر چهار تایمر را فعال کرده و سپس مقدار شمارنده آنها را چاپ میکنیم. بدیهی است که قبل از آن باید کدهای مربوط به ریدایرکت stdio را نیز به این پروژه اضافه کنیم.
بنابراین کد زیر را در بدنه تابع int main و قبل از حلقه while(1) مینویسیم:
1 2 3 4 5 6 7 8 | /* USER CODE BEGIN 2 */ RetargetInit(&huart1); // initializing the stdio retargetting HAL_TIM_Base_Start(&htim1); // start TIM1 HAL_TIM_Base_Start(&htim2); // start TIM2 HAL_TIM_Base_Start(&htim3); // start TIM3 HAL_TIM_Base_Start(&htim4); // start TIM4 /* USER CODE END 2 */ |
سپس مقدار شمارندهها را در حلقه while(1)، چاپ میکنیم:
1 2 3 4 5 6 7 8 9 10 | /* USER CODE BEGIN WHILE */ while (1) { HAL_Delay(500); printf("TIM4: %lu \r\n", (__HAL_TIM_GetCounter(&htim4))); printf("TIM3: %lu \r\n", (__HAL_TIM_GetCounter(&htim3))); printf("TIM2: %lu \r\n", (__HAL_TIM_GetCounter(&htim2))); printf("TIM1: %lu \r\n\r\n", (__HAL_TIM_GetCounter(&htim1))); /* USER CODE END WHILE */ |
برای اینکه نتیجه شمارش و اتصال تایمرها را بتوانیم بهسرعت بررسی کنیم، مقدار سرریز یا همان Counter Period (Auto-Reload Register) را روی عدد کوچکی مثل 2 تنظیم میکنیم.
اندازهگیری فرکانس با تایمر 32 بیتی
بهعنوان یک کاربرد نمونه برای تایمر 32 بیتی، میخواهیم فرکانس یک سیگنال ورودی (که دوره تناوب کوچک و فرکانس بالایی دارد) را اندازهگیری کنیم. بدین منظور تایمرهای 1 و 2 را طبق شکلهای بالا (مربوط به ساخت تایمر 64 بیتی)، تنظیم میکنیم و پارامتر Counter Period را برای آنها روی عدد 65535 قرار میدهیم. سپس تنظیم تایمر 3 را برای اندازهگیری زمان 1 ثانیه، طبق شکل زیر انجام میدهیم:
اکنون مجدداً وارد فایل کد میشویم و در بدنه int main کد مربوط به فعالسازی تایمرها را به شکل زیر تغییر میدهیم:
1 2 3 | HAL_TIM_Base_Start(&htim1); // start TIM1 HAL_TIM_Base_Start(&htim2); // start TIM2 HAL_TIM_Base_Start_IT(&htim3); // start TIM3 |
سپس متغیرهای زیر را تعریف میکنیم:
1 2 3 | float frequency; uint32_t counterLS; uint32_t counterMS; |
در این مرحله، تابع HAL_TIM_PeriodElapsedCallback که مربوط به روال وقفه Update event است و در پروژه قبل نیز استفاده شد را بهصورت زیر مینویسیم:
1 2 3 4 5 6 | /* USER CODE BEGIN 4 */ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim) { read_flag = 1; // set the read flag signal } /* USER CODE END 4 */ |
در مرحله آخر، کد قبلی نوشتهشده در حلقه while(1) را کامنت میکنیم و کد زیر را در بدنه این حلقه مینویسیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | if(read_flag) { /* Clear the read flag */ read_flag = 0; counterLS = __HAL_TIM_GetCounter(&htim1); //Get the counter value of Timer1 counterMS = __HAL_TIM_GetCounter(&htim2); //Get the counter value of Timer2 frequency = (float)((counterMS * __HAL_TIM_GetAutoreload(&htim1)) + counterLS) / 1000000; //Calculate the frequency of the input signal __HAL_TIM_SetCounter(&htim1, 0); //Reset the counter value of Timer1 __HAL_TIM_SetCounter(&htim2, 0); //Reset the counter value of Timer2 printf("frequency is %2.6f MHz\r\n", frequency); //Print the frequency /* print the number of pulses counted */ printf("n. of pulses is %lu \r\n\r\n", (counterMS * __HAL_TIM_GetAutoreload(&htim1)) + counterLS); } |
در این کد، میبینیم که ابتدا پرچم read_flag صفر شده است تا اجرای کد بهدرستی و در هر وقفه یک ثانیه یک بار اتفاق بیافتد. سپس بخش کمارزش و پرارزش شمارش، که به ترتیب توسط تایمرهای TIM1 و TIM2 شمارششده است، خوانده میشود. همانطور که میدانید هر تایمر/شمارنده 16 بیتی، قابلیت شمارش تا عدد 65535 را دارد. حال اگر بخواهیم فرکانس سیگنالی را اندازه بگیریم که در هر ثانیه بیش از 65535 نوسان میکند (فرکانس آن بیش از 65535Hz باشد)، یک تایمر 16 بیتی کارساز نخواهد بود و باید عمل شمارش را با یک تایمر 32 بیتی یا 2 تایمر 16 بیتی متصلبههم انجام دهیم.
پس از نوشتن کدهای گفتهشده، پروژه را Build و روی میکرو دانلود میکنیم. سپس یک سیگنال با فرکانس حدود 10 مگاهرتز را با Signal Generator تولید میکنیم و به پایه PA12 میکروکنترلر میدهیم. نتیجه اجرا و اندازهگیری فرکانس سیگنال را میتوانیم در ترمینال سریال ببینیم؛
در این قسمت از سری آموزش STM32 با توابع HAL، نحوه اتصال تایمرها به هم و ساخت تایمر بزرگتر و کاربرد نمونهای از آن را بررسی کردیم. در قسمت بعدی در مورد حالتهای Input capture و Output compare در تایمرها صحبت خواهیم کرد. با ما همراه باشید.
ممنون نمیخاد توضیح بدین خودم فهمیدم 🙂
ممنون از مطلب عالی ای که منتشر کردین
میشه لطفا راجع این فرمول کمی بیشتر توضیح دهید
frequency = (float)((counterMS * __HAL_TIM_GetAutoreload(&htim1)) + counterLS) / 1000000;