ARM, STM, STM32, STM32_LL, آموزش, توصیه شده, مقاله

آموزش STM32 با توابع LL قسمت نهم: UART-Receive

STM32

در قسمت هشتم از آموزش STM32 با توابع LL، ابتدا مقدمات پروتکل UART را بررسی کردیم و گفتیم که یک پکت دیتا در این پروتکل شامل چه بخش‌هایی می‌شود و این دیتا به چه صورت و با چه سرعت‌هایی می‌تواند منتقل بشود.

در نهایت هم پروتکل UART را در میکروکنترلرهای STM32 بررسی کردیم و بخش Transmit یا همان ارسال دیتا را به صورت عملی بر روی برد راه‌اندازی کردیم.

در این قسمت می‌خواهیم بخش UART-Receive یا همان دریافت دیتای پروتکل UART را راه‌اندازی کنیم.

 

چالش دریافت دیتا در پروتکل UART

اگر از قسمت قبل به یاد داشته باشید، هر وقت که می‌خواستیم دیتایی را ارسال کنیم آن دیتا را درون بافر مربوط به ارسال دیتا (Transmit Data Register (TDR)) قرار می‌دادیم و پس از اینکه دیتا به صورت کامل درون شیفت رجیستر قرار گرفت و ارسال دیتا استارت خورد با استفاده از بیت TXE به ما خبر داده می‌شد که دیتای بعد را می‌توانیم درون بافر مربوط به ارسال دیتا قرار بدهیم، اما در مورد خواندن قضیه کمی متفاوت است.

این متفاوت بودن قضیه از آنجا ناشی می‌شود که ما می‌دانیم که قرار است چه زمانی دیتا را ارسال کنیم، اما نمی‌دانیم که چه زمانی قرار است دیتا به سمت ما فرستاده شود. همین که نمی‌دانیم چه زمانی قرار است دیتا به سمت ما فرستاده شود خود یک مشکل ایجاد می‌کند.

 

راه‌حل‌های دریافت دیتا در پروتکل UART

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

حداقل توان پردازشی که باعث می‌شود هیچ دیتایی از بین نرود، توانی است که بر سرعت جایگزینی دیتای جدید با دیتای قبلی در بافر، غلبه کند!

خب اجازه بدهید راه‌حل دوم را بررسی بکنیم. اما پیشنهاد می‌کنم قبل از اینکه بدانید راه‌حل دوم چیست، یک بار دیگر قسمت هفتم که مربوط به Interrupt یا همان وقفه است را مرور بکنید.

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

پس هر وقت که از طریق وقفه UART، به ما خبر داده شد که دیتا درون بافر دریافت قرار گرفت ما باید درون تابع وقفه، دیتا را ذخیره کنیم.

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

 

پروتکل UART

 

زمانی که محتوای درون شیفت رجیستر RDR، به رجیستر USART_DR منتقل شد، بیت ششم از رجیستر Status register به نام RXNE مقدارش 1 می‌شود و نشان‌دهنده‌ی این است که ما می‌توانیم دیتای درون رجیستر USART_DR را بخوانیم. و هر موقع دیتای درون رجیستر USART_DR قرائت شد، بیت RXNE مقدارش 0 می‌شود.

ما اگر پس از 1 شدن بیت RXNE، دیتای درون رجیستر USART_DR را بخوانیم، در واقع از راه‌حل اول که گفتیم مناسب نیست استفاده کردیم، و باید تمام توان پردازشی میکروکنترلر را به این کار اختصاص بدهیم.

اما راه‌حل مدنظر ما، استفاده از وقفه بود. برای استفاده از وقفه پس از اینکه بیت RXNE مقدارش 1 شد، یک مرحله دیگر باید طی بشود تا ما برای دریافت دیتا بتوانیم از طریق وقفه UART اقدام بکنیم.

زمانی که بیت RXNE مقدارش 1 می‌شود اگر بیت RXNEIE که مربوط به وقفه UART است نیز فعال باشد، آنگاه وقفه UART تولید یا ایجاد خواهد شد.

 

وقفه UART

به شکل زیر که نحوه‌ی ایجاد وقفه UART را نشان می‌دهد دقت کنید:

 

وقفه UART

 

در ادامه با توجه به تصویر بالا، موضوع مهمی را توضیح خواهم داد که شما باید به خوبی به این موضوع توجه کنید تا بعدا دلیل کدی که درون تابع وقفه می‌نویسیم را بدانید.

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

همانطور که تصویر بالا مشخص است چندین رویداد مختلف مربوط به UART مثل ارسال و دریافت دیتا وجود دارد. تمام رویدادهای موجود در این تصویر روندی مشابه به هم دارند، پس ما تنها یکی از این رویدادها که همین رویداد مربوط به دریافت دیتا است را بررسی می‌کنیم.

ما اگر بخواهیم زمانی که بیت RXNE مقدارش 1 شد، همزمان یک وقفه هم رخ بدهد باید بیت RXNEIE را نیز فعال کنیم یا مقدار 1 را درون این بیت بنویسیم.

حال اگر بیت RXNEIE فعال باشد یا مقدارش 1 باشد، هر زمانی که بیت RXNE مقدارش از 0 به 1 تغییر کرد و نشان داد که دیتا آماده‌ی خواندن است، وقفه UART ایجاد خواهد شد.

اما این وقفه‌ی ایجاد شده تنها مختص به دریافت دیتا نیست، بلکه همه‌ی رویدادهای UART از جمله ارسال و دریافت دیتا در نهایت همین یک وقفه را ایجاد خواهند کرد.

پس اگر همین یک وقفه وجود دارد، زمان رویدادهای مختلف، ما چگونه متوجه بشویم که این وقفه مربوط به رویداد ارسال است یا دریافت یا یک رویداد دیگر؟

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

در تصویر بالا دو بیت RXNE و RXNEIE با استفاده از یک گیت AND با هم AND شده‌اند. و حاصل AND شدن این دو بیت به همراه AND شدن چندین بیت دیگر با همدیگر OR شده‌اند و در نهایت پس از عبور از یک گیت OR دیگر، به USART interrup ختم خواهند شد و وقفه UART را ایجاد می‌کنند.

پس دلیل اینکه همه رویدادها تنها یک وقفه را ایجاد می‌کنند این است که همه‌ی این رویدادها در نهایت با هم OR شده‌اند.

راه تشخیص اینکه بررسی کنیم وقفه مربوط به کدام رویداد است، این است که همزمان با بررسی وقفه، بیت‌های مربوط به رویداد را نیز بررسی کنیم. مثلا برای دریافت دیتا باید در تابع وقفه بیت‌های RXNE و RXNEIE را بررسی کنیم. هنگام برنامه نویسی و زمانی که کد درون تابع وقفه را می‌نویسیم بهتر متوجه این موضوع خواهید شد.

هر آن چیزی که نیاز بود از جزئیات سخت‌افزار، رویدادها و وقفه UART بدانید را در بالا شرح دادم. خب اجازه بدهید که به نرم‌افزار STM32CubeMX برویم تا UART را در حالت دریافت دیتا راه‌اندازی بکنیم.

 

تنظیمات UART-Receive در نرم‌افزار STM32CubeMX 

ابتدا کلاک و دیباگ را مانند گذشته تنظیم می‌کنیم و سپس از بخش Connectivity که مربوط به پروتکل‌های پشتیبانی شده توسط میکروکنترلر است، USART1 را فعال می‌کنیم.

همه‌ی بخش‌ها به جز Data Direction که باید در حالت Receive and Transmit باشد را مانند قسمت هشتم تنظیم می‌کنیم.

 

پروتکل UART

 

پروتکل UART

 

پروتکل UART

 

همچنین USART1 global interrupt را فعال می‌کنیم تا وقفه UART فعال شود.

 

پروتکل UART

 

پس از انجام تنظیمات بالا از پروژه خروجی می‌گیریم و وارد محیط برنامه‌نویسی Keil می‌شویم.

برنامه‌ای که می‌خواهیم در ادامه بنویسیم اینگونه است که ابتدا در حالت UART-Receive، با استفاده از وقفه، چندین کارکتر را دریافت می‌کنیم و این کارکترها را کنار هم قرار می‌دهیم تا یک رشته ساخته شود، سپس همین رشته ساخته شده را به کارکترهای تشکیل‌دهنده‌اش تجزیه می‌کنیم و در حالت UART-Transmit بر روی پورت UART می‌فرستیم و در پورت سریال کامپیوتر این رشته را مشاهده خواهیم کرد.

در برنامه ابتدا یک آرایه به اسم Value_RX، با نوع char و به طول 5 تعریف می‌کنیم تا کاراکترهای دریافتی را درون این آرایه ذخیره کنیم. همچنین سه متغیر 8 بیتی با نوع uint8_t با نام‌های i و j را برای شمارنده و x را برای تشخیص مُد دریافت یا ارسال تعریف می‌کنیم.

برای فعال کردن رویداد دریافت کافی است که فقط یک خط کد زیر را درون main برنامه و قبل از حلقه while بنویسیم:

 

ابتدا به کد زیر که تابع وقفه است دقت کنید: 

خب همانطور که قبلا هم گفتیم همه‌ی رویدادهای UART تنها یک وقفه دارند و با رخ دادن هر رویداد فقط همین یک وقفه USART1 global interrupt یا تابع USART1_IRQHandler فعال خواهد شد. و ما برای اینکه متوجه بشویم که کدام رویداد رخ داده است باید درون تابع USART1_IRQHandler بیت‌های مربوط به رویداد را بررسی بکنیم.

ما برای تشخیص رویداد دریافت، در کد بالا و درون تابع USART1_IRQHandler با استفاده از شرط if بیت‌های RXNE و RXNEIE را بررسی کردیم و اگر مقدار این بیت‌ها 1 بود، آنگاه کارکترهای دریافتی از پورت UART درون آرایه Value_RX قرار بگیرند. و هر موقع هم آرایه Value_RX به صورت کامل پر شد مقدار متغیر i که شمارنده است را 0 و مقدار متغیر x که نشان‌دهنده‌ی مُد دریافت یا ارسال است را 1 می‌کنیم تا وارد مُد ارسال بشویم.

پس از اینکه در فایل stm32f1xx_it.c کد درون تابع USART1_IRQHandler را تکمیل کردیم به main برنامه برمی‌گردیم و کد زیر را درون حلقه while می‌نویسیم:

چون ما درون تابع وقفه پس از اینکه کارکترها را به صورت کامل دریافت کردیم، با تغییر متغیر x به عدد 1، مُد برنامه را به حالت ارسال تغییر دادیم، پس در کد بالا شرط if برقرار است و همان رشته‌ی دریافتی به کارکترهای تشکیل‌دهنده‌اش تجزیه و بر روی پورت UART فرستاده خواهد شد. پس از اینکه رشته به صورت کامل ارسال شد، مقدار متغیر j که شمارنده است را 0 و همینطور مقدار متغیر x را برای اینکه دوباره وارد مُد دریافت بشویم هم 0 می‌کنیم.

عملکرد برنامه اینگونه است که یک رشته را از شما دریافت می‌کند و سپس همان رشته را بر روی پورت UART برای شما می‌فرستد و دوباره منتظر گرفتن رشته جدید می‌شود. دقت کنید برای سادگی برنامه، طول رشته را 5 حرف در نظر گرفتیم، شما هم اگر خواستید خودتان این برنامه را تست کنید، طول رشته‌ای که ارسال می‌کنید را 5 حرف در نظر بگیرید.

برای تست برنامه، نرم‌افزار RealTerm را بر روی کامپیوتر اجرا می‌کنیم تا نتیجه را مشاهده کنیم.

ما پس از ارسال رشته “Kamin” و “STM32” نتیجه زیر را بر روی کامپیوتر مشاهده خواهیم کرد:

 

پروتکل UART

 

در قسمت دهم در رابطه با مبدل آنالوگ به دیجیتال (ADC) صحبت خواهیم کرد.

انتشار مطالب با ذکر نام و آدرس وب سایت سیسوگ، بلامانع است.

شما نیز میتوانید یکی از نویسندگان سیسوگ باشید.   همکاری با سیسوگ

نوشته های مشابه

دیدگاهتان را بنویسید

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

12 دیدگاه در “آموزش STM32 با توابع LL قسمت نهم: UART-Receive

  1. هادی گفت:

    سلام هنگام کار با این کد یک مشکل برخوردم و آن تعریف متغیرهای ابتدای برنامه بود . که بعد از یک ساعت جستحو در اینترنت راه حل را پیدا کردم
    در فایل stm32f1 متغییرها راتعریف می کنیم و درفایل main بیرون از تابع main متغیر ها را با کلمه extern قبل از آن متغییر تعریف می کنیم
    مثال
    ;int x
    ;extern int x

    1. گفت:

      سلام هادی جان. بله درسته، تو کد این مورد را لحاظ کردم ولی فکر کنم در مقاله یادم رفت که این نکته را بگویم.

      البته بهتر است که ابتدا متغیر را در فایل main تعریف کنید، سپس در فایل‌های دیگر آن را از با کلاس extern تعریف کنید.

  2. رضا گفت:

    سلام

    با تشکر از آموزشهاتون
    من همچنان منتظر قسمت‌های بعدی هستم

    1. گفت:

      سلام رضا جان. فردا حتما قسمت دهم منتشر می‌شه. منتظر باشید.

  3. پرنیا گفت:

    آموزش دیگه ادامه نداره؟
    من 1-2 روز دیگه این بخش رو هم تمرین میکنم و تموم میشه. امیدوارم ادامه دار باشه. مثلاً مباحث پیاده سازی روش های کنترلی و … مثال بزنید.(کنترلر دیجیتال) مثلا کنترل دمای (هیتر=لامپ، سنسور دما هم که LM35)
    تا جایی که من رسیدم که عالی بوده، تشکر کامین عزیز 🌹

    1. گفت:

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

      1. پرنیا گفت:

        متشکرم.
        در ضمن اگر یه دوره پیش نیاز هم برای این بزارید و یه سری مقدمات رو بگید عالیه. مثلا داخل کد ها 0<<1 هست. با سرچ میشه فهمید ولی یه مقدار روند کلی خواندن رو کند میکنه.
        امیدوارم شاد و موفق باشید

        1. گفت:

          خب پیش نیاز این دوره زبان C هست، یعنی این مقالات با این پیش فرض نوشته شده‌اند که شما با زبان C آشنایی کافی دارید.

  4. نیما گفت:

    باسلام و تشکر
    در این روش اگر یک رشته ی مثلا ۲۰ کاراکتری رو یکجا ارسال کنیم برنامه جا نمیمونه و دیتا از دست نمیدیم؟

    1. گفت:

      سلام نیما جان. چون هدف آشنایی با خود پریفرال UART هست، کد را خیلی ساده نوشتم و موردی که شما می‌فرمائید در کد در نظر گرفته نشده است.

      1. نیما گفت:

        ممنون میشم اگر برای پیاده سازی یه ارتباط سریال کامل برای کنترل یه دستگاه توضیح یا نمونه ای معرفی کنید

        1. گفت:

          منظورتون این هست که یه دیوایسی که با این پروتکل کار می‌کنه رو راه‌اندازی کنیم؟