ARM, STM32, آموزش, آموزش STM32 با توابع LL, توصیه شده, مقاله های سیسوگ

دریافت دیتا در پروتکل UART در STM32 | قسمت نهم آموزش STM32 با توابع LL

STM32

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

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

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

 

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

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

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

شاید برای شما مفید باشد: راه اندازی ماژول nrf24l01

راه‌حل‌های دریافت دیتا در پروتکل 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) صحبت خواهیم کرد.

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

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

25 دیدگاه در “دریافت دیتا در پروتکل UART در STM32 | قسمت نهم آموزش STM32 با توابع LL

  1. Avatar for اکبر اکبر گفت:

    سلام
    کد رو از کجا میشه دانلود کرد؟

  2. Avatar for زهرا حسین ابادی زهرا حسین ابادی گفت:

    سلام من برای نوشتن این برنامه با ارور
    ‘Value_RX’ undeclared مواجه میشم و نمی دونم چطور value_rx باید فراخوانی بشه

    1. Avatar for Zeus ‌ Zeus ‌ گفت:

      سلام دوست عزیز
      پروژه توی گیت های سیسوگ هست
      اونو کلون کنید و استفاده کنید.

  3. Avatar for علی رضا علی رضا گفت:

    سلام
    مرسی از توضیحات عالیتون
    در مورده متن زیر سوال داشتم
    “برای تشخیص رویداد دریافت، در کد بالا و درون تابع USART1_IRQHandler با استفاده از شرط if بیت‌های RXNE و RXNEIE را بررسی کردیم”
    اگر در تابع وقفه این دو بیت را چک نکنیم، چه مشکلی پیش میاد؟
    فرض من این هسش که همین که روال اجرای برنامه تا اینجا اومده یعنی خود به خود هر دو شرط صادق بوده دیگه!

    مرسی

    1. Avatar for Zeus ‌ Zeus ‌ گفت:

      سلام دوست عزیز
      اتفاق خاصی نمی افته 🙂

  4. Avatar for مصطفی مصطفی گفت:

    سلام وقت بخیر
    ببخشید من همین کد رو میزنم ولی در فایلstm32f1xx_it.c با ارور undefined refrence to ”Value_RX مواجه میشم
    Value_RXهم در main تعرف کردم و در فایلstm32f1xx_it.c با حالت extern دوباره آوردمش.

  5. Avatar for محمد محمد گفت:

    سلام، اینجا چطور میشه برای رشته‌ی ارسالی طول تعیین نکرد؟
    مثلاً میخوایم از طریق کامپیوتر کلمات مختلف بنویسیم با طول های مختلف و بعد ارسال کنیم. منظورم اینه چطور باید به صورت پارامتری(ماژولار) بنویسیم؟ مرسی.

    1. Avatar for Zeus ‌ Zeus ‌ گفت:

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

      1. Avatar for محمد محمد گفت:

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

        1. Avatar for Zeus ‌ Zeus ‌ گفت:

          خواهش میکنم دوست عزیز 🙂
          نظر لطف شماست

  6. Avatar for داستایوسکی داستایوسکی گفت:

    سلام

    یه مقدار در رابطه با اینکه چطور از دیتای دریافتی استفاده کنیم هم میگید؟
    البته چون تجربه زیادی دارید راهنمایی کلی میخوام که در کل برای گرفتن دیتا از کامپیوتر(مثلا تنظیم سرعت موتور) چطور و بیشتر به چه نحوی این کار رو انجام میدن؟ چه پروتکلی مرسوم تر هست؟
    مثلا من قصد دارم یک عددی وارد کنم که موتور با اون سرعت بچرخه و به کمک آموزش بخش قبل، سرعت واقعی موتور رو هم به کامپیوتر ارسال کنم.(مثال ساده شده عرض کردم)
    متشکرم. ?

    1. Avatar for Zeus ‌ Zeus ‌ گفت:

      سلام دوست عزیز
      در این خصوص عملا شما وارد دنیای پروتکل ها میشد که اتفاقا پروتکل های ارتباطی زیادی هم هست و این بستگی به خود شما داره بسته به نیاز یکی از آنها را انتخاب کنید
      برای انتخاب شما پارامترهایی مثل این که چه چیزی قراره از پروتکل استفاده کنه مهم میشه مثلا اگر قراره پروتکل مورد استفاده افراد باشه پس تباید پروتکل باینری باشه و بهتری که اسکی بیس باشه و اگر قراره بین دستگاه های الکترونیکی استفاده بشه هر کدوم از این موارد میتونه مورد استفاده باشه
      برای ارتباط هم پروتکل هایی مثل at بیس هست درست مثل چیزی که برای مودم ها استفاده میشه یا stx,etx و … که با یه سرچ ساده می تونید لیستش رو توی ویکی پیدا کنید.
      حتی می تونیدبر اساس نیاز پروتکل خودتون رو طراحی کنید.

  7. Avatar for هادی هادی گفت:

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

    1. Avatar for کامین جلیلی کامین جلیلی گفت:

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

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

  8. Avatar for رضا رضا گفت:

    سلام

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

    1. Avatar for کامین جلیلی کامین جلیلی گفت:

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

  9. Avatar for پرنیا پرنیا گفت:

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

    1. Avatar for کامین جلیلی کامین جلیلی گفت:

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

      1. Avatar for پرنیا پرنیا گفت:

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

        1. Avatar for کامین جلیلی کامین جلیلی گفت:

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

    2. Avatar for اصغر سجادی اصغر سجادی گفت:

      بسیار عالی آموزش هاتون و تک فقط یک سوالی داشتم من همه این عملیات هوایی که گفتید رو انجام دادم و نوشتم ولی مشکلم اینه که متغییر ها رو گلوبال و خروجی هم تعریف کردم ولی متغییر که که تو تابع وقفه مقدار دهی میشود در تابع whileکد برنامه کار نمیکند و هیچ ری اکشنی به شرط ها نشان نمیدهد

  10. Avatar for نیما نیما گفت:

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

    1. Avatar for کامین جلیلی کامین جلیلی گفت:

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

      1. Avatar for نیما نیما گفت:

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

        1. Avatar for کامین جلیلی کامین جلیلی گفت:

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

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

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