وضعیت اعتبارسنجی: 2 پروژهٔ واقعی با CubeMX 6.14.0، DB 6.0.140 و CubeF3 1.11.6 تولید و با GCC 13.2.1 در ساخت پاک Debug با -O0 -g3 -Wall -Werror بدون خطا و هشدار کامپایل شده است. 3 بررسی مدل یا فایل اجرایی انجام شده است. پروگرام، اجرای برد، محیط گرافیکی CubeIDE و اندازه‌گیری واقعی انجام نشده‌اند؛ 6 شکل این مقاله نمودار تألیفی یا مدل‌اند و شاهد آزمایش سخت‌افزاری نیستند.

پیش‌نیاز: درس 9، درس 10، درس 17. ترتیب این آموزش مطابق سند سفارش دوره است.

چرا وقفهٔ تایمر؟

اگر main بعد از تغییر LED داخل HAL_Delay منتظر بماند، کار بعدی همان حلقه تا پایان انتظار عقب می‌افتد. وقفه‌های مجاز همچنان می‌توانند اجرا شوند؛ پس HAL_Delay کل CPU را خاموش نمی‌کند. این درس شمارش زمان را به TIM6 می‌سپارد، در وقفه فقط یک خروجی و شمارندهٔ سبک را به‌روز می‌کند و تعداد رخدادهای سرویس‌شده را برای main نگه می‌دارد.

دوره‌ای اجراشدن با «پس از پایان کار، کمی صبرکردن» فرق دارد. تایمر طبق کلاک و تنظیم خودش Update می‌سازد؛ زمان رسیدن CPU به callback به وقفه‌ها، ماسک‌ها و هزینهٔ نرم‌افزار وابسته است. حتی اگر دورهٔ سخت‌افزار ثابت بماند، لبهٔ GPIO در callback می‌تواند jitter داشته باشد. هیچ تضمین بدون اندازه‌گیری برای زمان دقیق ورود به ISR داده نمی‌شود.

گیت UIF و UIE تا وقفهٔ مشترک TIM6
نمودار تألیفی مسیر Update تا فلگ UIF و مجوز UIE و NVIC؛ TIM6_DAC_IRQn برابر IRQ خارجی 54 و اندیس جدول بردار 70 است. UIF یک بیت است و چند رخداد عقب‌افتاده را نمی‌شمارد؛ فعال‌بودن NVIC و ماسک‌ها و اولویت نیز مؤثرند. برای دیدن جزئیات، تصویر را باز کنید.

UEV، UIF و UIE سه مفهوم متفاوت‌اند

Update یا UEV یک رخداد داخلی تایمر است. در شرایط تنظیم این پروژه، پرچم UIF در SR وقوع Update را اعلام می‌کند. UIE در DIER مجوز درخواست وقفهٔ Update است. برای اجرای handler، فعال‌بودن بردار NVIC و اجازهٔ ورود از نظر اولویت و ماسک CPU هم لازم است. UIF حتی با UIE خاموش ممکن است یک شود؛ روشن‌کردن NVIC به‌تنهایی تایمر را شروع نمی‌کند.

محاسبهٔ دوره از درخت کلاک

HSE اسمی 8MHz با PREDIV=/1 و PLL×9، SYSCLK/HCLK اسمی 72MHz می‌سازد. APB1=/2 یعنی PCLK1=36MHz، اما کلاک TIM6 روی مسیر تایمر دو برابر و برابر 72MHz است. APB2=/1 برابر 72MHz و Flash=2WS است. این محاسبه برای تنظیم واقعی پروژه است، نه همهٔ انتخاب‌های ممکن کلاک STM32.

T_update = (PSC + 1) × (ARR + 1) / f_TIM
T_1ms = 72 × 1000 / 72000000 = 0.001s
T_500ms = 7200 × 5000 / 72000000 = 0.5s

رابطه برای شمارش صعودی ساده با ARR غیرصفر است. در TIM6 هر دو PSC و ARR شانزده‌بیتی‌اند؛ مقدارهای ذخیره‌شده یک واحد کمتر از ضریب یا تعداد تیک‌اند. PSC=71 تقسیم 72 و ARR=999 تعداد 1000 تیک است. مثال 500ms در جدول فقط محاسبه است؛ دو پروژهٔ کامپایل‌شده هر دو دورهٔ1ms دارند.

وقتی تقسیم دقیق نیست

برای هر PSC مجاز، تعداد تیک مناسب را از زمان مطلوب و f_tick حساب و عدد صحیح نزدیک را انتخاب کنید؛ محدودهٔ ARR و ممنوعیت صفر در این کاربرد را رعایت کنید. اسکریپت همراه، تمام 65536 مقدار PSC و دو همسایهٔ عدد صحیحِ تعداد تیک را بررسی می‌کند، کمترین خطای مطلق دوره را برمی‌گزیند و در تساوی، PSC کوچک‌تر را ترجیح می‌دهد. بهینه‌بودن خطای دوره به معنی کمترین jitter، مناسب‌ترین مصرف یا بهترین انتخاب برای تمام کاربردها نیست.

محاسبهٔ دورهٔ 1ms با PSC و ARR
نمودار محاسباتی تألیفی برای TIM6 با کلاک 72MHz، PSC=71 و ARR=999؛ تیک CNT برابر 1us و 1000 تیک برابر Update اسمی 1ms است. این عدد فرکانس تنظیمات است و نمودار اندازه‌گیری خروجی نیست. برای دیدن جزئیات، تصویر را باز کنید.
نمونه PSC ARR دورهٔ Update فرکانس موج Toggle
پروژه‌های ساخته‌شده 71 999 1ms 500Hz اسمی
مثال محاسباتی جدا 7199 4999 500ms 1Hz اسمی

verify_interrupt_periods.py مثال‌ها و رد ARR=0 در مدل را بررسی می‌کند. برای نرخ 1234Hz، جست‌وجوی دوره با کلاک اسمی 72MHz جفت PSC=0 و ARR=58346 را می‌دهد: دوره 0.000810375s و خطای نسبی دوره 2.75ppm. این خطای گردکردن است؛ تلرانس کریستال، jitter ISR و خطای ابزار در آن محاسبه نشده‌اند.

انتخاب TIM6 و جدایی از Timebase اصلی HAL

TIM6 و TIM7 تایمرهای پایهٔ16 بیتی با شمارش صعودی‌اند و کانال Capture/Compare یا خروجی PWM روی پایه ندارند. برای مبنای زمان ساده مناسب‌اند و کانال تایمرهای دیگر را اشغال نمی‌کنند؛ اما یک منبع محدود سیستم‌اند و ممکن است کار دیگری، مانند تریگر DAC یا timebase HAL، قبلاً از آن‌ها استفاده کند.

در این پروژه HAL همچنان از SysTick استفاده می‌کند و TIM6 فقط تایمر کاربر است. اگر در پروژه‌ای HAL timebase به TIM6 تغییر کرده باشد، دستکاری دوره یا callback آن می‌تواند زمان HAL و timeoutها را خراب کند؛ انتخاب تایمر مستقل یا ادغام آگاهانه لازم است. صرف نام TIM6 تضمین نبود تداخل نیست.

دو پروژهٔ واقعی gebrabit_lesson18_irq و gebrabit_lesson18_slowmain برای STM32F303CBT6/GB100ST V2.0 آماده‌اند. هر دو TIM6 با PSC=71، ARR=999، Up و preload فعال دارند؛ PB6 خروجی USER_LED و PB7 خروجی ISR_MARKER است. تفاوت نسخهٔ دوم فقط SLOW_MAIN_DELAY_MS=500 در main است؛ نسخهٔ اول 0 دارد. مبنای HAL همان SysTick است. فایل همراه CMake است؛ مسیر CubeIDE از تولید IOC در CubeMX مستقل و import پروژهٔ خروجی در پوشه‌ای جدا می‌گذرد و اینجا اجرا نشده است.

تنظیم CubeMX و اولویت NVIC

در CubeMX مستقل، TIM6 را برای تایمر پایه فعال کنید؛ Prescaler=71، Counter Period=999 و Auto-reload preload=Enable باشد. تولید فایل جدا برای هر پریفرال و نگهداری USER CODE فعال بماند. SYS Debug=Serial Wire و پایه‌های PA13/PA14 حفظ شوند. PB6 و PB7 با نام‌های USER_LED و ISR_MARKER خروجی Push-pull بدون Pull تنظیم شده‌اند؛ سطح اولیهٔ هر دو Low است.

در NVIC بردار TIM6_DAC_IRQn فعال است. GROUP4 روی این تراشه 4 بیت اولویت پیش‌دستی و 0 بیت زیراولویت می‌دهد؛ priority=2 و subpriority=0 برای TIM6 انتخاب شده است. عدد کمتر اولویت بالاتر دارد. SysTick با priority=15 پایین‌تر است و در حالت عادی نمی‌تواند وسط TIM6 اجرا شود. اولویت، تأخیر ورود را صفر نمی‌کند و وقفهٔ ماسک‌شده را آزاد نمی‌کند.

مسیر کامل وقفه در HAL همین نسخه

Update در تایمر و UIF/UIE مجاز، درخواست را به NVIC می‌رساند؛ بردار startup به TIM6_DAC_IRQHandler در Core/Src/stm32f3xx_it.c متصل است. handler ابتدا منبع TIM6 را بررسی می‌کند، سپس HAL_TIM_IRQHandler را فراخوانی می‌کند. HAL در شاخهٔ Update، پرچم و مجوز را بررسی و UIF را پیش از اجرای HAL_TIM_PeriodElapsedCallback پاک می‌کند. callback در main.c فقط Instance=TIM6 را می‌پذیرد.

نام TIM6_DAC یعنی بردار مشترک است. در پروژهٔ حاضر DAC و وقفهٔ underrun آن فعال نیستند. فیلتر واقعی handler با UIF و UIE از اجرای marker برای منبع نامرتبط پرهیز می‌کند؛ اگر بعداً DAC اضافه شود، باید منبع DAC نیز در همین بردار درست dispatch و acknowledge شود. return بدون پاک‌کردن علتِ درخواست DAC، راه‌حل کامل آن پروژه نیست.

مسیر واقعی HAL و محدودهٔ نشانگر PB7
نمودار توالی تألیفی مطابق ISR پروژه: پس از شرط UIF و UIE، PB7 بالا می‌رود، HAL فلگ را پاک و callback را اجرا می‌کند و PB7 پایین می‌آید. نشانگر شامل entry و return استثنا یا شرط پیش از نشانگر نیست و عرض آن کل تأخیر ورود به وقفه را نشان نمی‌دهد. برای دیدن جزئیات، تصویر را باز کنید.

شروع با _IT و اثر UG راه‌اندازی

در حالت پایهٔ این پروژه، HAL_TIM_Base_Start شمارش را شروع می‌کند ولی UIE را فعال نمی‌کند؛ پروژه از HAL_TIM_Base_Start_IT استفاده می‌کند که UIE و شروع شمارش را در مسیر مناسب فعال می‌کند. مقدار برگشتی HAL_OK بررسی می‌شود. Start_IT هم جای تنظیم NVIC را نمی‌گیرد.

HAL_TIM_Base_Init در مسیر TIM_Base_SetConfig مقدارهای PSC/ARR را می‌نویسد و با UG اعمال می‌کند. در تنظیم این پروژه ممکن است UIF همان‌جا یک شود؛ Start_IT آن را از پیش پاک نمی‌کند. USER CODE2 در حالت توقف، CNT را 0، UIF را پاک و pending NVIC را پاک می‌کند و سپس Start_IT را صدا می‌زند. این ترتیب از سرویس فوری پرچم باقی‌مانده جلوگیری می‌کند؛ زمان رسیدن به callback اول همچنان شامل تأخیر ورود نرم‌افزار است.

ماکروی CLEAR_FLAG مکمل ماسک را به SR می‌نویسد تا در بیت UIF صفر و در بیت‌های دیگر 1 بنویسد؛ این پرچم W0C است. نوشتن UG یا دستکاری شمارنده در حین اجرا بر فاز اثر دارد؛ نمونهٔ حاضر چنین تغییر زنده‌ای انجام نمی‌دهد.

پاک‌کردن فلگ Update حاصل از راه‌اندازی اولیه
نمودار تألیفی Init، آماده‌سازی CNT و UIF و NVICpending و Start_IT؛ پاک‌کردن UIF قدیمی از callback فوری ناشی از فلگ قدیمی جلوگیری می‌کند، اما رسیدن نرم‌افزار به callback دقیقاً پس از 1ms یا بی‌Jitterبودن تضمین نمی‌شود. برای دیدن جزئیات، تصویر را باز کنید.

کد واقعی: ISR کوتاه، پردازش شمارنده در main

callback، PB6 را Toggle و pending_ticks و total_irq_count را یک واحد زیاد می‌کند. این شمارنده‌ها تعداد callbackهای سرویس‌شده را می‌شمارند، نه لزوماً تمام Updateهای فیزیکی. در main، TakePendingTicks تعداد منتظر را برمی‌دارد و صفر می‌کند؛ last_batch، processed_ticks و maximum_batch برای مشاهدهٔ رفتار نگه داشته شده‌اند. این مثال هنوز زمان‌بند کارهای کاربردی یا صف timestamp نیست.

کدهای زیر قطعه‌های منتخب از پروژهٔ واقعاً کامپایل‌شده‌اند. مسیر فایل و بلوک USER CODE همراه هر قطعه آمده است. قطعه‌های منتخب برنامهٔ مستقل نیستند؛ برای بازسازی، پروژهٔ کامل و فایل .ioc داخل ZIP را استفاده کنید. توابع تولیدشده را دوباره تعریف نکنید. اگر بلوک 3 نشان داده شده، آکولاد انتهای حلقهٔ واقعی نیز در آن قرار دارد.

Core/Src/main.c — USER CODE PD

/* USER CODE BEGIN PD */
#define SLOW_MAIN_DELAY_MS 0U

/* USER CODE END PD */

Core/Src/main.c — USER CODE PV

/* USER CODE BEGIN PV */
volatile uint32_t pending_ticks = 0U;
volatile uint32_t total_irq_count = 0U;
volatile uint32_t processed_ticks = 0U;
volatile uint32_t last_batch = 0U;
volatile uint32_t maximum_batch = 0U;

/* USER CODE END PV */

Core/Src/main.c — USER CODE 0

/* USER CODE BEGIN 0 */
static uint32_t TakePendingTicks(void)
{
  uint32_t saved_primask = __get_PRIMASK();
  __disable_irq();
  uint32_t ticks = pending_ticks;
  pending_ticks = 0U;
  __DMB();
  __set_PRIMASK(saved_primask);
  return ticks;
}

/* USER CODE END 0 */

Core/Src/main.c — USER CODE 2

/* USER CODE BEGIN 2 */
__HAL_DBGMCU_FREEZE_TIM6();
  __HAL_TIM_SET_COUNTER(&htim6, 0U);
  __HAL_TIM_CLEAR_FLAG(&htim6, TIM_FLAG_UPDATE);
  HAL_NVIC_ClearPendingIRQ(TIM6_DAC_IRQn);
  if (HAL_TIM_Base_Start_IT(&htim6) != HAL_OK) { Error_Handler(); }

  /* USER CODE END 2 */

Core/Src/main.c — USER CODE 3

/* USER CODE BEGIN 3 */
if (SLOW_MAIN_DELAY_MS != 0U) { HAL_Delay(SLOW_MAIN_DELAY_MS); }
    last_batch = TakePendingTicks();
    processed_ticks += last_batch;
    if (last_batch > maximum_batch) { maximum_batch = last_batch; }
  }
  /* USER CODE END 3 */

Core/Src/main.c — USER CODE 4

/* USER CODE BEGIN 4 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if (htim->Instance == TIM6)
  {
    HAL_GPIO_TogglePin(USER_LED_GPIO_Port, USER_LED_Pin);
    pending_ticks++;
    total_irq_count++;
  }
}

/* USER CODE END 4 */

بردار مشترک واقعی TIM6 و DAC

Core/Src/stm32f3xx_it.c — تابع تولیدشدهٔ TIM6_DAC_IRQHandler فقط برای خواندن نشان داده شده است. شرط و marker داخل USER CODEهای 0 و 1 همان بردارند؛ HAL_TIM_IRQHandler وسط تابع، تولیدشده باقی می‌ماند. تابع را جای دیگری دوباره تعریف نکنید.

void TIM6_DAC_IRQHandler(void)
{
  /* USER CODE BEGIN TIM6_DAC_IRQn 0 */
  /* DAC interrupts are not enabled in this project. A future DAC user must
     dispatch and acknowledge its own source on this shared IRQ. */
  if ((__HAL_TIM_GET_FLAG(&htim6, TIM_FLAG_UPDATE) == RESET) ||
      (__HAL_TIM_GET_IT_SOURCE(&htim6, TIM_IT_UPDATE) == RESET))
  {
    return;
  }
  HAL_GPIO_WritePin(ISR_MARKER_GPIO_Port, ISR_MARKER_Pin, GPIO_PIN_SET);

  /* USER CODE END TIM6_DAC_IRQn 0 */
  HAL_TIM_IRQHandler(&htim6);
  /* USER CODE BEGIN TIM6_DAC_IRQn 1 */
  HAL_GPIO_WritePin(ISR_MARKER_GPIO_Port, ISR_MARKER_Pin, GPIO_PIN_RESET);

  /* USER CODE END TIM6_DAC_IRQn 1 */
}

چرا volatile کافی نیست؟

volatile مانع کنارگذاشتن دسترسی‌های لازم توسط کامپایلر می‌شود؛ اتمیک‌بودن عملیات مرکب را تضمین نمی‌کند. افزایش یک شمارنده و به‌خصوص «خواندن و سپس صفرکردن» main، چند عمل منطقی دارند. اگر ISR میان خواندن و صفرکردن وارد شود، رخداد تازه ممکن است پاک و گم شود. تعداد دقیق دستورهای اسمبلی به ساخت وابسته است؛ کلیدواژه را قفل هم‌روندی ندانید.

TakePendingTicks مقدار قبلی PRIMASK را ذخیره، IRQهای قابل ماسک را کوتاه غیرفعال، مقدار pending را کپی و صفر، DMB را اجرا و همان PRIMASK قبلی را برمی‌گرداند. اینجا NMI و HardFault به این متغیر دست نمی‌زنند. بازکردن بی‌قید وقفه در پایان، به جای حفظ وضعیت قبلی، برای یک تابع عمومی درست نیست. بخش بحرانی باید کوتاه بماند؛ هر ماسک می‌تواند زمان ورود ISR را جابه‌جا کند.

اگر چند Update پیش از سرویس روی UIF=1 جمع شوند، آن بیت تعدادشان را ذخیره نمی‌کند؛ یک callback تعداد واقعی ازدست‌رفته را بازسازی نمی‌کند. اگر ISR کوتاه و قابل سرویس باشد، شمارندهٔ نرم‌افزاری فرصت بیشتری برای حفظ callbackها می‌دهد، ولی ظرفیتش 32 بیتی است. شمارنده‌ای که در نرخ 1kHz بدون تخلیه بالا می‌رود حدود 49.71 روز یک دور می‌زند؛ این زمان سقف تضمین‌شدهٔ قابل‌قبول برای تأخیر کار نیست.

ناحیهٔ بحرانی برای خواندن و صفرکردن شمارندهٔ مشترک
نمودار تألیفی مثال گم‌شدن increment میان خواندن و صفرکردن main و راه‌حل واقعی ذخیرهٔ PRIMASK، غیرفعال‌سازی کوتاه، کپی و صفر و DMB و بازیابی PRIMASK قبلی؛ volatile عملیات مرکب را اتمیک نمی‌کند. توقف طولانی وقفه می‌تواند رخدادهای تک‌بیتی UIF را ادغام کند. برای دیدن جزئیات، تصویر را باز کنید.

زمان رخداد، تأخیر ورود و هزینهٔ HAL

زمان Update را سخت‌افزار تایمر مشخص می‌کند. از درخواست تا اجرای نخستین دستور، بررسی اولویت، ماسک، تکمیل دستورها، وضعیت پشته/حافظه و وقفه‌های دیگر دخیل‌اند. سپس handler و HAL و callback اجرا می‌شوند. ذکر یک عدد ایده‌آل ورود هسته بدون شرایط حافظه و پشته، اندازه‌گیری این پروژه را جایگزین نمی‌کند؛ اینجا نه تأخیر ورود و نه هزینهٔ HAL اندازه‌گیری شده‌اند.

برای تعیین نرخ وقفهٔ معقول، باید بدترین زمان سرویس و وقفه‌های مزاحم و مهلت کارهای main را اندازه گرفت. نسبت زمان سرویس به دوره، بخشی از بودجهٔ CPU را نشان می‌دهد؛ حاشیه و اولویت سایر کارها هم لازم است. سقف عمومی مانند 50kHz یا 100kHz برای این برنامه ادعا نمی‌کنیم. با زیادشدن نرخ، انتخاب Compare سخت‌افزاری، DMA یا طرح دیگر باید بر اساس نیاز و قابلیت واقعی بررسی شود، نه صرفاً جایگزینی نام HAL با LL.

آزمایش 1: هر Toggle یک نیم‌دوره است

پیش از پروگرام، پیش‌بینی کنید: با یک Toggle در هر 1ms، فاصلهٔ لبه‌ها 1ms و یک دور کامل موج 2ms است؛ پس فرکانس اسمی 500Hz است، نه 1000Hz. نرخ Update اسمی 1000Hz است. این نتیجه مدل است؛ دقت کریستال، تأخیر ISR و ابزار روی مشاهده اثر دارند و عبارت «دقیقاً500Hz روی اسیلوسکوپ دیده شد» مجاز نیست.

فرکانس 500Hz خروجی با Toggle در هر 1ms
شکل موج مدل و تألیفی با محور ms؛ معکوس‌کردن PB6 در هر وقفهٔ اسمی 1ms دورهٔ کامل 2ms و فرکانس اسمی 500Hz می‌سازد. پالس‌های PB7 فقط پوشش مفهومی HAL و callback هستند و عرض ساختگی آن‌ها اندازه‌گیری تأخیر یا زمان اجرا نیست. برای دیدن جزئیات، تصویر را باز کنید.

ST-LINK را با PA13/PA14، زمین مشترک، مرجع 3.3V و NRST مطابق شماتیکV2.0 وصل کنید. اسیلوسکوپ ورودی پرامپدانس سازگار با 3.3V را به PB6 و زمین مشترک متصل کنید. نام منطقی PB6 به پایهٔ فیزیکی 42 و PB7 به پایهٔ فیزیکی 43 بستهٔ LQFP48 مربوط است؛ شمارهٔ پایهٔ بدنه را با شمارهٔ هدر برد یکی نگیرید. LED کاربر PB6 از ترانزیستور راه‌اندازی می‌شود؛ محل پروب را از شماتیک تعیین کنید. ورودی 5V فقط برای ورودی مجاز برد است، نه 3V3 یا پایهٔ MCU.

پروژهٔ IRQ را پروگرام و در اجرای آزاد دوره، فرکانس و شرایط ابزار را در worksheet ثبت کنید. freeze تایمر 6 هنگام halt دیباگر در کد فعال است؛ توقف/گام‌به‌گام برای قضاوت دربارهٔ دوره معتبر نیست. همهٔ ردیف‌های نتیجهٔ سخت‌افزاری اکنون pending هستند. معیار پذیرش خواننده، سازگاری دوره با مدل و تلرانس کلاک/ابزار و بررسی نبود کار مسدودکننده در ISR است.

آزمایش 2: main کند می‌شود، رخدادها منتظر می‌مانند

پروژهٔ slowmain واقعاً با همان تایمر 1ms و فقط SLOW_MAIN_DELAY_MS=500 ساخته شده است. در آن، main پیش از تخلیهٔ pending داخل HAL_Delay می‌ماند. تا وقتی وقفه‌ها مجاز و مسیر TIM6 قابل سرویس است و تنظیم تایمر تغییر نکرده، انتظار داریم درخواست‌های TIM6 در این فاصله ادامه یابند و last_batch حدود 500 شود؛ این مقدار تضمین دقیق نیست. فرکانس موج PB6 را پیش و پس از تغییر ثبت کنید و ادعای ثبات قطعی را به جای اندازه‌گیری ننویسید.

processed_ticks تعداد سرویس‌های تحویل‌شده را جمع می‌کند ولی کار کاربردی طی انتظار به موقع انجام نشده است. شمار batch، timestamp هر Update را ندارد. برای کارهایی که مهلت سخت دارند، جمع‌کردن رخدادهای عقب‌افتاده جای اجرای به‌موقع را نمی‌گیرد. متغیرهای دیباگر نیز چند snapshot هم‌زمان تضمین‌شده نیستند.

عیب‌یابی با مسیر واقعی کد

  • اگر CNT می‌شمارد ولی callback اجرا نمی‌شود، UIE، NVIC و اتصال startup به handler را بررسی کنید؛ در مسیر همین برنامه Start_IT لازم است. با برداشتن _IT از آزمایش، Start عادی مجوز Update را فعال نمی‌کند.
  • اگر بلافاصله پس از شروع callback دیده می‌شود، UIF ناشی از UG یا pending باقی‌مانده را بررسی کنید. پاک‌کردن UIF در حالی که منبع دیگری فعال است، جای تشخیص منبع بردار مشترک را نمی‌گیرد.
  • اگر ARR=0 تنظیم شده، مستند F3 LL_TIM_SetAutoReload توقف شمارنده در مقدار autoreload صفر را ذکر می‌کند؛ این خطا را با «وقفهٔ بسیار سریع با دورهٔ یک تیک» تفسیر نکنید. مقدارهای پروژهٔ همراه 999 هستند.
  • اگر انتظار دوره بر مبنای 36MHz با واقعیت فرق دارد، مسیر دوبرابرشدن کلاک تایمر APB1 را بررسی کنید. محاسبه با 36MHz و اجرای 72MHz، زمان واقعی را نصف انتظار آن محاسبه می‌کند.
  • اگر ISR گیر می‌کند، داخل callback همین نسخه HAL_Delay یا printf وجود ندارد؛ کد افزوده‌شده را بررسی کنید. HAL_Delay در TIM6 با priority2 می‌تواند منتظر SysTick پایین‌تر بماند که وسط آن پیشرفت نمی‌کند. هر printf هم الزاماً یک خطای مشخص مانند سرریز پشته ایجاد نمی‌کند؛ رفتار وابسته به پیاده‌سازی آن است.
  • اگر pending کم‌تر از تعداد Update مورد انتظار است، طول ماسک یا ISR و UIF تک‌بیتی و دورزدن شمارنده را بررسی کنید. volatile به‌تنهایی دادهٔ ازدست‌رفته یا مهلت ازدست‌رفته را برنمی‌گرداند.

پرسش‌های خودآزمایی و پاسخ

  1. در APB1=36MHz با مقسم 2، TIM6 چه کلاکی دارد؟ پاسخ: مسیر انتخاب‌شدهٔ تایمر 72MHz است.
  2. چرا 71 و 999 دوره 1ms می‌دهند؟ پاسخ:72×1000 تیک ورودی از 72MHz، برابر 0.001s است.
  3. چرا UIF پیش از Start_IT پاک می‌شود؟ پاسخ: UG راه‌اندازی می‌تواند آن را یک کرده باشد؛ آن رخداد به معنی گذشت نخستین دوره نیست.
  4. آیا pending_ticks تعداد همهٔ Updateهای فیزیکی است؟ پاسخ: خیر؛ تعداد callbackهای سرویس‌شده است و ادغام رخدادها در UIF قابل بازیابی نیست.
  5. چرا با Toggle در 1ms، فرکانس موج 500Hz می‌شود؟ پاسخ: دو Toggle یک دورهٔ کامل 2ms می‌سازند.
  6. چرا main باید خواندن و صفرکردن شمارنده را حفاظت کند؟ پاسخ: ISR ممکن است بین آن‌ها مقدار تازه‌ای اضافه کند؛ volatile این عملیات مرکب را اتمیک نمی‌کند.

تمرین

  1. با یک مبنای 1ms، دو کار 10ms و 250ms را برای main طراحی کنید. اگر last_batch بیش از 1 شد، سیاست جبران، پرش یا ثبت مهلت ازدست‌رفته را صریح انتخاب کنید؛ کار سنگین را در ISR نگذارید. کد تمرین را پیش از گزارش نتیجه جداگانه بسازید و بیازمایید؛ پروژهٔ حاضر فقط شمار رخدادها را تحویل می‌دهد.
  2. از PB7 همراه همین پروژه برای اندازه‌گیری پوش فراخوانی HAL_TIM_IRQHandler استفاده کنید. marker پس از فیلتر اولیه بالا و بعد از HAL پایین می‌رود؛ بنابراین هزینهٔ ورود/خروج استثنا و شرط اولیه در پهنای آن نیست، اما HAL و callback و اثر ابزارگذاری حضور دارند. آن را «زمان خالص callback» یا «تأخیر کامل ورود ISR» ننامید. شرایط پروب و کمینه/بیشینه را ثبت کنید.

منابع و فایل‌های همراه

ادعا مرجع
قابلیت تایمر و پایه‌ها DS9118 Rev14، جدول 5 و جدول 13
UG، شروع _IT، پاک‌کردن پرچم و callback HAL همان نسخه؛ TIM_Base_SetConfig، HAL_TIM_Base_Start_IT و HAL_TIM_IRQHandler
ماکروی W0C و ARR=0 HAL TIM header و F3 LL_TIM_SetAutoReload
NVIC، اولویت و PRIMASK PM0214، مدل استثنا و NVIC و CMSIS و فایل‌های واقعی startup/it/tim همراه پروژه
نمونهٔ رسمی نزدیک CubeF3 v1.11.6، TIM_TimeBase؛ برد دیگر/TIM3
اتصال برد شماتیک GB100ST V2.0

گزارش‌ها، IOC، منابع و ELF/MAP هر دو ساخت، مدل محاسباتی و جدول آزمایش همراه‌اند. همهٔ شکل‌ها نمودار تألیفی یا مدل‌اند؛ تصویر واقعی اسیلوسکوپ و شاهد اجرای برد ارائه نشده است.

دانلود فایل‌های همراه این آموزش

پروژهٔ واقعی با فایل .ioc، منبع کامل، خروجی ELF/MAP، گزارش ساخت بدون هشدار، اسکریپت بررسی، کاربرگ آزمایش و 6 نمودار آموزشی با فایل ترسیم. محتوا داخل ZIP قرار دارد؛ اجرای برد و اندازه‌گیری واقعی انجام نشده‌اند.

دانلود به نسخهٔ مشخص همراه مقاله متصل است. ابتدا ZIP را استخراج و راهنما را مطالعه کنید.

با نظرات خود به تیم جبرا در بهبود کیفیت کمک کنید

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

سبد خرید
پیمایش به بالا