آموزش STM32 با توابع LL قسمت سیزدهم: Timer-Input capture

آموزش ARM

در قسمت دوازدهم از آموزش STM32 با توابع LL، در رابطه با تایمرها و نحوه‌ی عملکرد آن‌ها صحبت کردیم و در نهایت واحد تایمر در میکروکنترلرهای STM32 را در حالت TimeBase راه‌امدازی کردیم و زمان 1 ثانیه را اندازه‌گیری کردیم. در این قسمت همچنان می‌خواهیم در رابطه با تایمرها صحبت کنیم و با استفاده از حالت Input capture در میکروکنترلرهای STM32 فرکانس یک سیگنال ورودی را اندازه‌گیری کنیم.

ابتدا توضیحاتی در رابطه با اینکه Input capture چیست و چگونه عمل می‌کند ارائه می‌دهیم، سپس با استفاده از Input capture در میکروکنترلرهای STM32 فرکانس یک سیگنال ورودی را اندازه‌گیری خواهیم کرد.

Input capture

معنی لغوی عبارت Input capture معادل با ضبط یا ثبت ورودی است. آن چیزی هم که ما می‌خواهیم به آن بپردازیم تقریبا مرتبط با همین معنی لغوی این عبارت است.

برای توضیحات مربوط به 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
قابلیت 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 متصل کنیم و فرکانس آن را اندازه بگیریم. اجازه بدهید به نرم‌افزار برویم و ادامه توضیحات را آن‌جا بیان کنیم.

ابتدا تنظیمات را مانند تصویر زیر انجام می‌دهیم:

تنظیمات Timer در نرم‌افزار STM32CubeMX
تنظیمات Timer در نرم‌افزار STM32CubeMX

همانطور که از تصویر مشخص است ما منبع کلاک تایمر 1 را بر روی کلاک داخلی و همچنین کانال 1 این تایمر را بر روی Input capture قرار می‌دهیم. در تنظیمات دیگر هم کانتر را بر روی حالت بالا شمار و مقدار Auto-reload را در بیشترین مقدار آن، یعنی 65535 قرار می‌دهیم. تنظیمات کانال 1 که مربوط به Input capture است را هم فقط بر روی لبه‌ی بالارونده تنظیم می‌کنیم.

Prescaler و سایر تنظیمات را هم در همان حالت پیش‌فرض خودشان قرار می‌دهیم تا کلاک واحد تایمر که به شمارنده متصل است و سیگنال ورودی بدون هیچ تقسیم فرکانسی به مق صد خود برسند.

همچنین در قسمت NVIC Setting وقفه مربوط به Capture را نیز فعال می‌کنیم:

فعال کردن وقفه Timer در نرم‌افزار STM32CubeMX
فعال کردن وقفه Timer در نرم‌افزار STM32CubeMX

اما یک مسئله مهم کلاک واحد تایمر است، مقدار مجاز کلاک این واحد چقدر است؟

با توجه به فرکانس سیگنال ورودی و مقدار Auto-reload که مقدارش را بر روی عدد 65535 قرار دادیم، فقط فرکانس 72MHz که ماکسیمم کلاک قابل اعمال به میکروکنترلر ما یعنی STM32F103C8T6 است، غیر مجاز است. چون اگر فرکانس واحد تایمر را 72MHz قرار بدهیم شمارنده بیش از یک بار سرریز رخ خواهد کرد.

توجه کنید با توجه به محدودیتی که در میکروکنترلرهای ST وجود دارد ما کلاک باس‌ها و پریفرال‌ها را نمی‌توانیم بر روی هر عددی قرار بدهیم و تنها یک سری عدد خاص است که می‌توانیم از آن‌ها استفاده کنیم. کلاک 72MHz که گفتیم غیر مجاز است و بزرگ‌ترین عدد مجازی که با توجه به همین محدودیت میکروکنترلرهای ST می‌توانیم برای کلاک قرار بدهیم، عدد 64MHz است.

پس با این تفاسیر، قسمت کلاک را به نحوی تنظیم می‌کنیم که کلاک 64MHz که بالاترین کلاک مجاز است به واحد تایمر اعمال شود.

تنظیم کلاک Timer در نرم‌افزار STM32CubeMX

برای اینکه مقدار فرکانس را بر روی کامپیوتر مشاهده کنیم، پریفرال UART را هم مانند قسمت هشتم فعال کرده و برای ادامه‌ی کار به نرم‌افزار Keil می‌رویم.

در فایل main، ابتدا متغیرهای زیر را تعریف می‌کنیم:

volatile uint32_t Frequency = 0;
char str[30];
uint8_t i = 0;

برای فعال کردن وقفه، کانال ورودی و همچنین شمارنده هم کد زیر را در main برنامه می‌نویسیم:

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 تعریف می‌کنیم تا در فرمول محاسبه فرکانس سیگنال ورودی از آن استفاده کنیم.

#define TIM1Clock 64000000
extern volatile uint32_t Frequency;

کل کد مربوط به محاسبه‌ی فرکانس سیگنال ورودی را در تابع TIM1_CC_IRQHandler می‌نویسیم.

ابتدا به کد دقت کنید:

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 ذخیره می‌کنیم. حال با توجه به اینکه سرریز رخ داده است یا نه، با استفاده از کد زیر اختلاف دو مقدار متوالی شمارنده را محاسبه می‌کنیم:

if (ICValue2 > ICValue1)
{
	DiffCapture = (ICValue2 - ICValue1); 
}
else if (ICValue2 < ICValue1)
{
	DiffCapture = ((TIM1->ARR - ICValue1) + ICValue2) + 1;
}

در نهایت هم با استفاده از فرمول Frequency = TIM1Clock / DiffCapture، فرکانس سیگنال ورودی را محاسبه می‌کنیم و دوباره متغیر CaptureIndex را برابر با 0 قرار می‌دهیم تا همین روند را به صورت متوالی تکرار و فرکانس سیگنال ورودی را محاسبه کنیم.

برای اینکه نتیجه را مشاهده کنیم کد زیر که مربوط به UART است را در فایل main و درون حلقه while می‌نویسیم تا مقدار فرکانس سیگنال ورودی به صورت مستمر به کامپیوتر فرستاده شود:

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

همانطور که انتظار داشتیم فرکانس اندازه‌گیری شده توسط Input capture، برابر با عدد 976Hz بود که از قبل می‌دانستیم.

در قسمت چهاردهم در رابطه با PWM صحبت می‌کنیم.

کانال تلگرام آرملینکس

برای دسترسی به کانال تلگرام و دانلود فایل‌های پروژه و ویدئو، بر روی دکمه زیر کلیک کنید:

10 دیدگاه دربارهٔ «آموزش STM32 با توابع LL قسمت سیزدهم: Timer-Input capture»

  1. سلام با تشکر از مطالب مفیدتون . لطفا جلسات بعدی رو هم ادامه دهید . و اینکه از dma هم استفاده کنید ممنون

    1. کامین جلیلی

      سلام حسین، سپاس از نظر مثبت‌تان. کمی درگیر کارها هستم، حتما این مجموعه را کامل می‌کنم. DMA را هم در یک قسمت به صورت کامل شرح خواهم داد.

    1. کامین جلیلی

      سلام مجید، سپاس از نظر مثبت‌تون. دوست داشتم هرچه سریع‌تر این مجموعه را کامل کنم، اما درگیر یک پروژه خیلی سنگین شدم، متاسفانه در حال حاضر وقت خالی نیست. سعی‌ام را می‌کنم زودتر بیام و تمامش کنم.

دیدگاه‌ خود را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

اسکرول به بالا