در قسمت قبل از سری آموزش STM32 با توابع HAL، راجع به نحوه اتصال تایمرها به هم و ساخت تایمر بزرگتر و همچنین کاربرد نمونهای از آن، صحبت شد. در این قسمت، حالت Input capture و حالت Output compare در تایمرها را بررسی میکنیم. با سیسوگ همراه باشید.
ساز و کار و کاربرد حالت Input capture
در قسمت یازدهم این سری آموزش، در مورد تایمرها و حالتهای مختلف کاری آنها توضیح دادیم. یکی از حالتهای پرکاربرد تایمرها، حالت Input capture است. در این حالت، کلاک تایمر از منبع داخلی تأمین میشود و هنگام وقوع یک رخداد خارجی در پین مربوطه، مقدار شمارنده تایمر، ثبت و در رجیستر Input capture ذخیره میشود. همانطور که قبلاً اشاره شد، در هر تایمر چندین کانال Input capture وجود دارد. در شکل زیر نحوه کارکرد تایمر در این حالت، به تصویر کشیده شده است:
همانطور که در تصویر نشان دادهشده است، در زمان وقوع یک لبه (در اینجا بالا رونده) مقدار فعلی شمارنده، یعنی عدد 4، ثبتشده است. سپس در زمان وقوع لبه (بالارونده) دوم، مجدداً مقدار شمارنده، یعنی عدد 20، ذخیرهشده است. با مقایسه این دو عدد میتوان به فرکانس سیگنال خارجی پی برد.
نحوه کارکرد و استفاده حالت Output compare
میتوان گفت که کارکرد حالت Output compare برعکس حالت Input capture است. بدینصورت که در این حالت، هنگام رسیدن شمارنده تایمر، به یک مقدار مشخص (مقدار ذخیرهشده در Capture/Compare register)، اعمال زیر قابل انجام هستند:
- میتوان پایه خروجی کانال output compare را برای صفر، یک و یا Toggle شدن تنظیم کرد. این حالت برای تولید موجهای مربعی، با فرکانسهای مختلف، کاربرد دارد.
- تولید شکل موج PWM.
- تولید درخواست وقفه و فراخوانی روال مربوطه.
سختافزار و ساختار Capture/Compare
سختافزار ورودی و خروجی در تایمر.
اجزای تشکلی دهنده هر کانال Capture/Compare، عبارتاند از یک رجیستر capture/compare، یک shadow register، مدار ورودی برای عمل capture (شامل فیلتر دیجیتال، multiplexing و prescaler) و مدار خروجی (شامل مقایسه کننده و output control). در شکلهای زیر این مدارهای ورودی و خروجی با جزییات نشان دادهشدهاند:
مدار ورودی.
مدار خروجی.
استفاده از حالت Input capture
همانطور که اشاره شد، در حالت Input capture، رجیسترهای capture/compare (TIMx_CCRx)، در زمان وقوع یک لبه در سیگنال ICx مربوطه، مقدار شمارنده را در خود ذخیره میکنند. هنگام رخداد یک capture، پرچم CCXIF مربوطه (در رجیستر TIMx_SR) فعال میشود و همچنین ممکن است یک درخواست وقفه یا DMA، تولید شود. درصورتیکه در زمان فعال بودن پرچم CCxIF، یک capture روی دهد، پرچم over-capture، یعنی CCxOF (در رجیستر TIMx_SR)، فعال خواهد شد. راههای پاک (صفر) کردن بیت CCxIF، عبارتاند از نوشتن صفر در این بیت یا خواندن دیتای capture شده و ذخیرهشده در رجیستر TIMx_CCRx. بیت مربوط به CCxOF نیز از طریق نوشتن صفر در آن، پاک میشود.
در این بخش میخواهیم با به کاربردن تایمر در حالت Input capture، یک فرکانس متر بسازیم. پس ابتدا منطق کار توضیح میدهیم و سپس به ایجاد و توسعه پروژه میپردازیم.
برای این پروژه باید مراحل زیر، طی شوند:
- تنظیم یک تایمر همهمنظوره (بهعنوانمثال TIM2) برای استفاده از منبع کلاک داخلی و تعیین کانال 1 بهعنوان Input capture و دریافت لبههای بالارونده.
- خواندن رجیستر CCR1 در هنگام وقوع اولین لبه و ذخیره آن در متغیر T1 و سپس خواندن این رجیستر در زمان وقوع لبه دوم و ذخیره آن در متغیر T2. اکنون، دوره تناوب سیگنال از T2 – T1 محاسبه میشود و فرکانس سیگنال نیز از معکوس کردن دوره تناوب بهدست خواهد آمد.
- USART1 نیز مانند پروژههای قبل، برای نمایش پیام موردنظر (در اینجا فرکانس سیگنال)، تنظیم و استفاده خواهد شد.
خلاصه مراحل توضیح داده شده.
نکته مهمی که باید به آن توجه داشت این است که، در صورت overflow شدن تایمر قبل از وقوع لبه دوم، محاسبه فرکانس دچار خطا خواهد شد. این اتفاق بهخصوص در زمان اندازهگیری فرکانسهای پایین رخ خواهد داد، زیرا دوره تناوب چنین سیگنالهایی، طولانی خواهد بود. بنابراین برای اینکه امکان اندازهگیری فرکانسهای پایین را داشته باشیم، باید وقفه مربوط به overflow تایمر را نیز فعال کنیم و تعداد overflow هایی که قبل بین وقوع دو لبه سیگنال، رخ میدهند را شمارش کنیم. سپس به ازای هر overflow، مقدار 65536 را به T2 اضافه کنیم.
ایجاد پروژه
در این پروژه تمامی بخشها ازجمله کلاک، دیباگ و USART را مانند پروژه قبل انجام میدهیم و سپس به تنظیم تایمر TIM2 میپردازیم؛
تنظیم TIM2 در حالت Input Capture.
اکنون پروژه را ایجاد میکنیم و به سراغ نوشتن کد میرویم.
نوشتن کد پروژه
مثل پروژههای اخیر، در فایل main.c فایل مربوط به ریدایرکت stdio را اضافه میکنیم و سپس به تعریف ثابتهای موردنیاز میپردازیم:
1 | #include "stdio_usart.h" |
1 2 3 4 | #define AwaitingCapture 0 #define FirstCapture 1 #define SecondCapture 2 #define F_CLK 72000000UL |
سپس باید متغیرهای گلوبال موردنیاز را تعریف کنیم:
1 2 3 4 | volatile uint8_t captureState = AwaitingCapture; volatile uint16_t TIM2_overFlowCnt = 0; volatile uint32_t T1 = 0; volatile uint32_t T2 = 0; |
اکنون درون بدنه تابع int main و قبل از حلقه while(1)، کد زیر را مینویسیم:
1 2 3 4 5 6 7 8 9 10 | /* USER CODE BEGIN 2 */ uint32_t Ticks = 0; uint32_t Frequency = 0; RetargetInit(&huart1); HAL_TIM_Base_Start_IT(&htim2); HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1); printf("begin! \r\n"); /* USER CODE END 2 */ |
سپس باید دو تابع مربوط به روال وقفه Capture و Update event را بنویسیم. این دو تابع را به شکل زیر تعریف میکنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | /* USER CODE BEGIN 4 */ void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef* htim) { if(captureState == AwaitingCapture) { TIM2_overFlowCnt = 0; T1 = TIM2->CCR1; captureState = FirstCapture; } else if(captureState == FirstCapture) { T2 = TIM2->CCR1; captureState = SecondCapture; __HAL_TIM_SetCounter(&htim2, 0); } } void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim) { TIM2_overFlowCnt++; } /* USER CODE END 4 */ |
درنهایت، بدنه حلقه while(1) را به شکل زیر مینویسیم تا فرکانس سیگنال را محاسبه و چاپ کنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | /* USER CODE BEGIN WHILE */ while (1) { // compute and print the input signal's frquency, whenever 2 captures occur if(captureState == SecondCapture) { Ticks = (T2 + (TIM2_overFlowCnt * 65536)) - T1; Frequency = (uint32_t)(F_CLK/Ticks); if(Frequency) { printf("Frequency = %lu Hz Overflow count = %u\n\r", Frequency,TIM2_overFlowCnt); } captureState = AwaitingCapture; } /* USER CODE END WHILE */ |
نتیجه اجرای این کد، در ترمینال سریال بهصورت زیر است:
نتیجه اندازهگیری فرکانس در ترمینال سریال.
استفاده از حالت Output Compare
در این بخش میخواهیم با استفاده از حالت Output compare، در 4 کانال مختلف TIM3، 4 شکل موج مربعی با فرکانسهای مختلف تولید کنیم.
پیکربندی پروژه
مجدداً وارد بخش پیکربندی پروژه میشویم و هر 4 کانال تایمر 3 را در حالت Output Compare تنظیم میکنیم. تنظیم هر 4 کانال را مشابه هم انجام میدهیم؛
تنظیم بخش Time-base تایمر 3.
تنظیم کانالهای Output compare تایمر 3.
نوشتن کد
برای شروع بهکار تایمر 3، باید در تابع int main، آن را مانند تایمر 2 فعال کنیم. بدین منظور و همچنین برای فعالسازی هر چهار کانال TIM3، کد زیر را قبل از حلقه while(1) مینویسیم:
1 2 3 4 5 | HAL_TIM_Base_Start_IT(&htim3); HAL_TIM_OC_Start_IT(&htim3, TIM_CHANNEL_1); HAL_TIM_OC_Start_IT(&htim3, TIM_CHANNEL_2); HAL_TIM_OC_Start_IT(&htim3, TIM_CHANNEL_3); HAL_TIM_OC_Start_IT(&htim3, TIM_CHANNEL_4); |
مانند حالت Input capture، برای حالت Output compare نیز روال وقفه تعریف میشود. برای کنترل فرکانس کانالهای خروجی، باید روال وقفه مربوط به 4 کانال تایمر 3 را بهصورت زیر تعریف کنیم:
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 | /******************** TIM3 (Output Compare) ********************/ void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef* htim) { static uint8_t i1 = 1, i2 = 1, i3 = 1, i4 = 1; if(__HAL_TIM_GET_IT_SOURCE(&htim3, TIM_IT_CC1) != RESET) { TIM3->CCR1 = ((i1*((__HAL_TIM_GetAutoreload(&htim3) - 2) / 5)) + 2); if(i1 == 5) i1 = 0; else i1++; } if(__HAL_TIM_GET_IT_SOURCE(&htim3, TIM_IT_CC2) != RESET) { TIM3->CCR2 = ((i2*((__HAL_TIM_GetAutoreload(&htim3) - 2) / 25)) + 2); if(i2 == 25) i2 = 0; else i2++; } if(__HAL_TIM_GET_IT_SOURCE(&htim3, TIM_IT_CC3) != RESET) { TIM3->CCR3 = ((i3*((__HAL_TIM_GetAutoreload(&htim3) - 2) / 50)) + 2); if(i3 == 50) i3 = 0; else i3++; } if(__HAL_TIM_GET_IT_SOURCE(&htim3, TIM_IT_CC4) != RESET) { TIM3->CCR4 = ((i4*((__HAL_TIM_GetAutoreload(&htim3) - 2) / 100)) + 2); if(i4 == 100) i4 = 0; else i4++; } } /* USER CODE END 4 */ |
بهاینترتیب، 4 کانال خروجی تایمر 3، در فرکانسهای مختلف نوسان خواهند کرد.
در این قسمت از سری آموزش STM32 با توابع HAL، حالت Input capture و حالت Output compare در تایمرها و نمونهای از کاربرد هر یک را بررسی کردیم. در قسمت بعد، PWM را معرفی خواهیم کرد. با ما همراه باشید.
اگر فاصله لبه بالا رونده و لبه پایین رونده رو بخوایم اندازه بگیریم چطور؟
سلام، من یک مشکل با توابع HAL دارم بعضی از توابع کار نمیکنند!! کامپایل بدون مشکل انجام میشه و بدون هیچ خطا و مشکلی فایل hex ساخته میشه اما زمانی که فایل رو به Proteus وارد میکنم شبیه سازی بدون خطا انجام میشه ولی نتیجه ی مطلوب با توجه به کد رو نمیگیرم برای مثال:
if( ( HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) ) == GPIO_PIN_SET){
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);
}
else HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);
در دستورات بالا مطلوب هست که اگر PortA_Pin0 با یک برابر شد PortA_Pin1 برابر با یک شود در غیر این صورت PortA_Pin1 برابر با صفر شود، اما نتیجه شبیه سازی اشتباه میشود همین حالت برای دستورات ADC نیز تکرار میشود اما در راه اندازی انتراپت و بعضی موارد مشکلی ندارم.
من از Proteus 8.13 و STM32CubeMX 6.5.0 و Keil MDK 5.37.0.0 استفاده میکنم.
ممنون میشه اگه من رو راهنمایی کنید.
سلام دوست عزیز
خیلی به پروتئوس اعتماد نکنید ، روی مدار واقعی تست کنید
اخیرا که نه ولی خیلی وقت پیش این پروتئوس خیلی سرم کارم گذاشت و مداری رو که اصلا کار نمیکرد رو در واقعیت بستم و کار کرد.