در قسمت دوازدهم از آموزش STM32 با توابع LL، در رابطه با تایمرها و نحوهی عملکرد آنها صحبت کردیم و درنهایت واحد تایمر در میکروکنترلرهای STM32 را در حالت TimeBase راهاندازی کردیم و زمان 1 ثانیه را اندازهگیری کردیم. در این قسمت همچنان میخواهیم در رابطه با تایمرها صحبت کنیم و با استفاده از حالت Input capture در میکروکنترلرهای STM32 فرکانس یک سیگنال ورودی را اندازهگیری کنیم.
شاید برای شما مفید باشد: آموزش FPGA
ابتدا توضیحاتی در رابطه بااینکه Input capture چیست و چگونه عمل میکند ارائه میدهیم، سپس با استفاده از Input capture در میکروکنترلرهای STM32 فرکانس یک سیگنال ورودی را اندازهگیری خواهیم کرد.
Input capture
معنی لغوی عبارت Input capture معادل با ضبط یا ثبت ورودی است. آن چیزی هم که ما میخواهیم به آن بپردازیم تقریباً مرتبط با همین معنی لغوی این عبارت است. برای توضیحات مربوط به Input capture ابتدا به تصویر زیر توجه کنید:
در شکل بالا ما یک سیگنال ورودی (Inpu Signal) داریم که میخواهیم با استفاده از قابلیت Input capture میکروکنترلر فرکانس این سیگنال ورودی را اندازه بگیریم. برای اندازهگیری سیگنال ورودی ابتدا باید شمارندهی واحد تایمر را با فرکانس مشخص، بهصورت بالا شمار (یا پایین شمار) راهاندازی کنیم (مرحله 1 در تصویر.)
سپس سیگنالی که میخواهیم فرکانس آن را اندازهگیری کنیم را به یکی از کانالهای تایمر که بر روی پین میکروکنترلر قرار دارد، متصل میکنیم و وقفهی این کانال را فعال و حساس به لبهی بالارونده (یا پایینرونده) قرار میدهیم. در این صورت در هر لبهی بالارونده، یک وقفه رخ میدهد (مرحله 2 در تصویر.) پسازاینکه وقفهی مربوط به لبهی بالارونده رخ داد، مقدار شمارنده در لحظه وقوع وقفه، در رجیستر Capture ذخیره میشود (مرحله 3 در تصویر.)
شمارنده همچنان به شمارش خود ادامه خواهد داد. در حالی که شمارنده به شمارش خود ادامه میدهد، بر روی دومین لبهی بالارونده سیگنال یک وقفهی دیگر رخ میدهد (مرحله 4 در تصویر.)
پسازاینکه وقفهی مربوط به دومین لبهی بالارونده رخ داد، مقدار شمارنده در لحظه وقوع وقفه، دوباره در رجیستر Capture ذخیره میشود (مرحله 5 در تصویر.) اکنون ما یک سری اطلاعات داریم که باید با استفاده از این اطلاعات، مقدار فرکانس سیگنال ورودی را محاسبه کنیم. خب ما اطلاعات مقدار شمارنده در لحظات وقوع وقفهها که در رجیستر Capture ثبتشده است و همچنین فرکانس کلاک شمارنده را در اختیارداریم. حال اگر ما مقدار فرکانس کلاک شمارنده، یعنی 1KHz را بر اختلاف دو مقدار 61 و 54 تقسیم کنیم، فرکانس سیگنال ورودی را محاسبه کردهایم. برای محاسبه فرکانس در زمانهای مختلف، مراحل بالا را بهصورت مستمر تکرار میکنیم.
کلیت محاسبه فرکانس سیگنال ورودی، مستقل از اینکه از چه نوع ابزاری (میکروکنترلر، FPGA یا هر ابزار دیگری) برای این محاسبه استفاده میکنیم، روشی است که در بالا ذکر کردیم. اکنونکه با این روش محاسبه آشنا شدید، میخواهیم بهصورت عملی فرکانس یک سیگنال را با استفاده از Input capture در میکروکنترلرهای STM32 محاسبه بکنیم.
Input capture در میکروکنترلرهای STM32
برای توضیح حالت Input capture در میکروکنترلرهای STM32 ابتدا به تصویر زیر دقت کنید:
برای اینکه از حالت Input capture در میکروکنترلرهای STM32 استفاده کنیم و فرکانس یک سیگنال ورودی را اندازه بگیریم، ابتدا باید این سیگنال را به یکی از کانالهای ورودی واحد تایمر متصل کنیم. همانطور که از تصویر بالا مشاهده میکنید، در مسیر این سیگنال بلوکهای فیلتر دیجیتال، تشخیص لبه و Prescaler قرار دارد. برای سادگی کار، ما از فیلتر دیجیتال و Prescaler استفاده نخواهیم کرد اما تشخیص لبه یا همان Edge detector را در حالت لبهی بالارونده قرار میدهیم تا با هر لبهی بالارونده یک وقفه رخ بدهد.
با اعمال تنظیمات بالا، در هر لبهی بالاروندهی سیگنال ورودی یک وقفه رخ خواهد داد و محتوای رجیستر شمارنده به رجیستر Capture منتقل خواهد شد. نکتهای که بسیار مهم است و باید به آن توجه ویژه کرد، نقش فرکانس کلاک شمارنده و همچنین مقدار Auto-reload register است. درواقع با توجه به محدودهی فرکانس سیگنال ورودی، باید فرکانس کلاک شمارنده و مقدار Auto-reload register را به نحوی انتخاب کنیم که از صحت فرکانس محاسبهشده مطمئن باشیم.
اجازه بدهید کمی بیشتر در این رابطه توضیح بدهم تا مسئله از حالت گنگ بودن خارج شود.
خب همانطور که گفتیم برای محاسبهی فرکانس سیگنال ورودی، نیاز است که مقدار شمارنده در دو لبهی بالاروندهی متوالی را داشته باشیم. از جهتی شمارندهی تایمر در میکروکنترلرهای STM32 با توجه به تنظیماتی که ما اعمال کردیم از 0 تا مقدار Auto-reload register شروع به شمارش میکند که بیشترین مقدار این رجیستر با توجه به 16 بیتی بودن آن برابر با 65535 است. پسازاینکه شمارنده به مقدار Auto-reload register رسید یک سرریز رخ میدهد و دوباره از 0 شروع به شمارش میکند. با توجه به بازه 0 تا 65535 رجیستر Auto-reload، در زمان شمارش ممکن است چندین حالت مختلف رخ بدهد که در ادامه به تفصیل هر حالت را بررسی خواهیم کرد.
حالت اول:
اگر دو وقفهی مربوط به دو لبهی بالاروندهی متوالی، زمانی رخ بدهد که هنوز هیچ سرریزی اتفاق نیفتاده باشد، هیچ مشکلی وجود ندارد و ما میتوانیم بهدرستی و بدون هیچ خطایی مقدار فرکانس سیگنال ورودی را محاسبه کنیم. مثلاً فرض کنید وقفهی اول زمانی رخ بدهد که مقدار شمارنده 500 و وقفهی دوم زمانی رخ بدهد که مقدار شمارنده 1000 است.
حالت دوم:
اگر وقفهی اول قبل از سرریز، و وقفهی دوم بعد از سرریز رخ بدهد. در این حالت هم میتوان با یک تکنیک ساده که در ادامه، هنگام نوشتن کد خواهیم گفت، به درستی و بدون هیچ خطایی اختلاف بین دو مقدار و به دنبال آن مقدار فرکانس سیگنال ورودی را محاسبه کنیم. مثلا فرض کنید وقفهی اول زمانی رخ بدهد که مقدار شمارنده 40000 و وقفهی دوم زمانی رخ بدهد که شمارنده سرریز کرده است و به مقدار 2000 رسیده است.
البته در این حالت اگر وقفهی دوم بعد از سرریز رخ بدهد و همچنین مقدار شمارنده در لحظهی وقوع این وقفه، از مقدار شمارنده در لحظهی وقوع وقفهی اول بیشتر باشد، میتوان گفت که این حالت یک حالت جدید است. اما ما این حالت را یک زیر حالت از حالت دوم در نظر گرفتیم.
تنها فرقی که بین این دو زیر حالت وجود دارد، این است که در زیر حالت اول، عدد پس از سرریز از عدد قبل سرریز کوچکتر، اما در زیر حالت دوم، عدد پس از سرریز از عدد قبل سرریز بزرگتر است. همین مثال بالا را در نظر بگیرید که شمارنده ابتدا 40000 بود و پس از سرریز به مقدار 2000 رسید. در زیر حالت دوم فرض کنید بهجای 2000 به عدد 50000 برسد. کدی که ما در ادامه خواهیم نوشت زیر حالت اول را پوشش خواهد داد، بهعنوان یک چالش نوشتن کدی که زیر حالت دوم را پوشش بدهد به خودتان واگذار میشود.
حالت سوم:
حالتی که برای ما مشکل ایجاد میکند و باعث میشود که نتوانیم اختلاف بین دو مقدار را بهدرستی محاسبه کنیم، حالتی است که بیش از یک بار سرریز رخ بدهد. مثلاً فرض کنید وقفهی اول زمانی رخ بدهد که مقدار شمارنده 6000 و وقفهی دوم زمانی رخ بدهد که شمارنده دو بار یا بیشتر سرریز کرده است و شمارنده به مقدار 3000 رسیده است. البته در این حالت هم با یک راهکار بسیار ساده میتوانید فرکانس سیگنال ورودی را بهدرستی محاسبه کنید. این مورد هم بهعنوان چالش دوم به خودتان واگذار میشود.
در بالا تمامی حالات مختلف Input capture در میکروکنترلرهای STM32 بیان شد و سعی کردم که این موضوع را بسیار ساده و روان شرح بدهم. اما اگر هنوز جایی برایتان گنگ است و ممکن توضیحات کافی نباشد در زیر همین پست سوالاتهای خود را بپرسید.
از میان تمامی حالات بالا، احتمال اینکه در طول زمان، فقط حالت اول رخ بدهد بسیار کم است (توجه کنید که میگوییم احتمال اینکه فقط حالت یک رخ بدهد کم است، نه اینکه حالت یک اصلا ندهد.) و اگر با توجه به محدودهی فرکانس سیگنال ورودی، فرکانس کلاک شمارنده و مقدار Auto-reload را به نحوی تنظیم کنیم که فقط یک بار سرریز رخ بدهد، حالت سوم هم اتفاق نمیافتد.
پس با این تفاسیر نیاز است که ما کدی بنویسیم که حالت اول و دوم را پوشش بدهد. ما در ادامه میخواهیم یک سیگنال با فرکانس 976Hz را به کانال 1 متصل کنیم و فرکانس آن را اندازه بگیریم. اجازه بدهید به نرمافزار برویم و ادامه توضیحات را آنجا بیان کنیم.
ابتدا تنظیمات را مانند تصویر زیر انجام میدهیم:
همانطور که از تصویر مشخص است ما منبع کلاک تایمر 1 را بر روی کلاک داخلی و همچنین کانال 1 این تایمر را بر روی Input capture قرار میدهیم. در تنظیمات دیگر هم کانتر را بر روی حالت بالا شمار و مقدار Auto-reload را در بیشترین مقدار آن، یعنی 65535 قرار میدهیم. تنظیمات کانال 1 که مربوط به Input capture است را هم فقط بر روی لبهی بالارونده تنظیم میکنیم.
Prescaler و سایر تنظیمات را هم در همان حالت پیشفرض خودشان قرار میدهیم تا کلاک واحد تایمر که به شمارنده متصل است و سیگنال ورودی بدون هیچ تقسیم فرکانسی به مق صد خود برسند. همچنین در قسمت NVIC Setting وقفه مربوط به Capture را نیز فعال میکنیم:
اما یک مسئله مهم کلاک واحد تایمر است، مقدار مجاز کلاک این واحد چقدر است؟
با توجه به فرکانس سیگنال ورودی و مقدار Auto-reload که مقدارش را بر روی عدد 65535 قراردادیم، فقط فرکانس 72MHz که ماکسیمم کلاک قابلاعمال به میکروکنترلر ما یعنی STM32F103C8T6 است، غیرمجاز است. چون اگر فرکانس واحد تایمر را 72MHz قرار بدهیم شمارنده بیش از یک بار سرریز رخ خواهد کرد.
توجه کنید با توجه به محدودیتی که در میکروکنترلرهای ST وجود دارد ما کلاک باسها و پریفرالها را نمیتوانیم بر روی هر عددی قرار بدهیم و تنها یک سری عدد خاص است که میتوانیم از آنها استفاده کنیم. کلاک 72MHz که گفتیم غیرمجاز است و بزرگترین عدد مجازی که با توجه به همین محدودیت میکروکنترلرهای ST میتوانیم برای کلاک قرار بدهیم، عدد 64MHz است.
پس با این تفاسیر، قسمت کلاک را به نحوی تنظیم میکنیم که کلاک 64MHz که بالاترین کلاک مجاز است به واحد تایمر اعمال شود.
برای اینکه مقدار فرکانس را بر روی کامپیوتر مشاهده کنیم، پریفرال UART را هم مانند قسمت هشتم فعال کرده و برای ادامهی کار به نرمافزار Keil میرویم.
در فایل main، ابتدا متغیرهای زیر را تعریف میکنیم:
1 2 3 | volatile uint32_t Frequency = 0; char str[30]; uint8_t i = 0; |
برای فعال کردن وقفه، کانال ورودی و همچنین شمارنده هم کد زیر را در main برنامه مینویسیم:
1 2 3 | LL_TIM_EnableIT_CC1(TIM1); LL_TIM_CC_EnableChannel(TIM1, LL_TIM_CHANNEL_CH1); LL_TIM_EnableCounter(TIM1); |
اکنون باید در فایل stm32f1xx_it.c و در وقفه مربوط به Capture، مقدار شمارنده را با هر وقفه بخوانیم و با استفاده از هر دو وقفهی متوالی و فرکانس کلاک شمارنده، فرکانس سیگنال ورودی را محاسبه کنیم.
برای این کار ابتدا در فایل stm32f1xx_it.c متغیر Frequency را با کلاس extern تعریف میکنیم تا در این فایل هم به متغیر Frequency دسترسی داشته باشیم. همچنین مقدار فرکانس واحد تایمر که برابر با 64MHz بود را با define تعریف میکنیم تا در فرمول محاسبه فرکانس سیگنال ورودی از آن استفاده کنیم.
1 2 | #define TIM1Clock 64000000 extern volatile uint32_t Frequency; |
کل کد مربوط به محاسبهی فرکانس سیگنال ورودی را در تابع TIM1_CC_IRQHandler مینویسیم.
ابتدا به کد دقت کنید:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | void TIM1_CC_IRQHandler(void) { /* USER CODE BEGIN TIM1_CC_IRQn 0 */ if(LL_TIM_IsActiveFlag_CC1(TIM1) == 1) { /* Clear the update interrupt flag*/ LL_TIM_ClearFlag_CC1(TIM1); static uint8_t CaptureIndex = 0; static uint32_t ICValue1 = 0; static uint32_t ICValue2 = 0; static uint32_t DiffCapture = 0; if(CaptureIndex == 0) { ICValue1 = LL_TIM_IC_GetCaptureCH1(TIM1); CaptureIndex = 1; } else if(CaptureIndex == 1) { ICValue2 = LL_TIM_IC_GetCaptureCH1(TIM1); if (ICValue2 > ICValue1) { DiffCapture = (ICValue2 - ICValue1); } else if (ICValue2 < ICValue1) { DiffCapture = ((TIM1->ARR - ICValue1) + ICValue2) + 1; } Frequency = TIM1Clock / DiffCapture; CaptureIndex = 0; } } /* USER CODE END TIM1_CC_IRQn 0 */ /* USER CODE BEGIN TIM1_CC_IRQn 1 */ /* USER CODE END TIM1_CC_IRQn 1 */ } |
در کد بالا ابتدا با وقوع هر وقفه flag مربوط به وقفه را پاک میکنیم.
سپس اگر متغیر CaptureIndex برابر با 0 بود، یعنی منتظر وقفهی اول هستیم و با وقوع وقفهی اول مقدار شمارنده را در متغیر ICValue1 ذخیره میکنیم و متغیر CaptureIndex را برابر با 1 قرار میدهیم تا برنامه وارد حالت انتظار برای وقفهی دوم بماند.
با وقوع وقفهی دوم مقدار شمارنده را در متغیر ICValue2 ذخیره میکنیم. حال با توجه به اینکه سرریز رخ داده است یا نه، با استفاده از کد زیر اختلاف دو مقدار متوالی شمارنده را محاسبه میکنیم:
1 2 3 4 5 6 7 8 | if (ICValue2 > ICValue1) { DiffCapture = (ICValue2 - ICValue1); } else if (ICValue2 < ICValue1) { DiffCapture = ((TIM1->ARR - ICValue1) + ICValue2) + 1; } |
درنهایت هم با استفاده از فرمول Frequency = TIM1Clock / DiffCapture، فرکانس سیگنال ورودی را محاسبه میکنیم و دوباره متغیر CaptureIndex را برابر با 0 قرار میدهیم تا همین روند را بهصورت متوالی تکرار و فرکانس سیگنال ورودی را محاسبه کنیم.
برای اینکه نتیجه را مشاهده کنیم کد زیر که مربوط به UART است را در فایل main و درون حلقه while مینویسیم تا مقدار فرکانس سیگنال ورودی بهصورت مستمر به کامپیوتر فرستاده شود:
1 2 3 4 5 6 7 8 9 10 11 12 13 | while (1) { /* USER CODE END WHILE */ int len = sprintf(str, "Frequency is: %dHz\n\r", Frequency); for (i; i<len; i++) { LL_USART_TransmitData8(USART1, str[i]); while(!LL_USART_IsActiveFlag_TXE(USART1)); } i = 0; /* USER CODE BEGIN 3 */ } |
پس از اجرای برنامه، نتیجهی زیر بر روی کامپیوتر قابل مشاهده است:
همانطور که انتظار داشتیم فرکانس اندازهگیری شده توسط Input capture، برابر با عدد 976Hz بود که از قبل میدانستیم.
در قسمت چهاردهم در رابطه با retarget کردن stdio صحبت میکنیم.
با سلام و خسته نباشید ،
برخی دوستان پرسیده بودن که این روش به مقدار زیادی CPU رو درگیر میکنه ، که درست هم گفته بودن ، مخصوصا در اندازه گیری فرکانس های بالا.
روش دیگری که هست اینه که از تایمر در حالت Slave و « شمارش کلاک خارجی » استفاده بشه.
در این حالت ، ( بسته به تنظیم کاربر ) به ازای هر لبه بالارونده / پایین رونده ، مقدار رجیستر Counter به شکل خودکار یک واحد افزایش پیدا میکنه ؛ در نتیجه نیازی به وقفه برای تشخیص لبه نداریم و فقط برای شمارش سرریز ها نیازمند وقفه هستیم.
برای به دست آوردن فرکانس هم طی فاصله زمانی مشخص ( مثلا هر ۱ ثانیه ) مقدار سرریز ها رو در ۶۵۵۳۶ ضرب میکنیم و با مقدار رجیستر Counter جمع میکنیم ، بعد از دریافت نتیجه هم مقدار رجیستر Counter و مقدار شمارشگر سرریز رو صفر میکنیم.
به همین سادگی.
شاید بزرگترین ایراد این روش این باشه که تمام تایمر های درون میکروکنترلر STM دارای حالت Slave نیستن و در انتخاب پین ها محدودیت داریم.
ممنون که همراه ما هستی، سلامت و موفق باشی! 🙏😊
سلام. ضمن تشکر، عارضم خدمتتون که لینک قسمت 14 ام خرابه. صفحه رو باز نمیکنه، صفحه اصلی رو باز میکنه به اشتباه.
سلام ممنون از اطلاع رسانیتون، لینک درست شد.
سلام
ممنون از ارائه این مطلب و به طور کل سایر مطالب ارزنده سایت
اما به نظرتون این روش محاسبه با تعداد زیاد وقفه ها در ثانیه و اجرای تعداد زیادی کد در روتین وقفه وقت زیادی از cpu نمیگیره؟
و در سایر امور جاری اون اختلال ایجاد نمیکنه؟
و اینکه در نهایت یک میکرو ۷۲مگاهرتزی با این روش(اجرای اون همه کد به ازای فرکانس چند کیلو هرتزی چندین هزار بار در ثانیه) نهایتا تا چه فرکانسی رو قادر به پاسخگویی روتین وقفه است؟
سلام و درود بر شما دوست عزیز
متشکر برای نظر و دقت شما ، البته که راه های بهتری برای انحام این محاسبات هست ولی برای مطلب آموزشی از نمایش کاربرد یک قابلیت حتی داکومنت های خود st از همین روش استفاده کرده اند و من فکر میکنم به دلیل حفظ سادگی است
متشکرم
سلام آقا سپهر چند وقت پیش داشتم کتاب مدرس مرجع رو میخوندم الان هم که مقالات شما را میخونم خیلی مطابقت داشت با شما. حتی از خود ST هم بهتر توضیح دادید این موضوع را ممنون از مقالات بسیار عااااالی شما
خیلی استفاده کردم از مطالبتون
امیدوارم همیشه پایدار و موفق باشید .
خوشحالم از اینکه این مجموعه مقالات برای شما مفید بوده و توانستید بهرهی کافی را ببرید. همینطور شما داود جان، هر جای این کره خاکی که هستید موفق و پیروز باشید.
سلام مثل همیشه بسیار عالی و ممنون .
فقط اگه بخاییم این مطالب رو پرینت بگیریم چکار باید کرد ؟
خسته نباشید
سلام محسن جان، شما لطف دارید. یا صفحه رو ذخیره کنید و بعد پرینت بگیرید، و یا اینکه کلیک راست کنید روی صفحه و گزینه Print را انتخاب کنید.