کتابخانه 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 را خودمان انجام دهیم.