در قسمت هشتم از آموزش STM32 با توابع LL، ابتدا مقدمات پروتکل UART را بررسی کردیم و گفتیم که یک پکت دیتا در این پروتکل شامل چه بخشهایی میشود و این دیتا به چه صورت و با چه سرعتهایی میتواند منتقل بشود.
در نهایت هم پروتکل UART را در میکروکنترلرهای STM32 بررسی کردیم و بخش Transmit یا همان ارسال دیتا را به صورت عملی بر روی برد راهاندازی کردیم.
در این قسمت میخواهیم بخش UART-Receive یا همان دریافت دیتای پروتکل UART را راهاندازی کنیم.
چالش دریافت دیتا در پروتکل UART
اگر از قسمت قبل به یاد داشته باشید، هر وقت که میخواستیم دیتایی را ارسال کنیم آن دیتا را درون بافر مربوط به ارسال دیتا (Transmit Data Register (TDR)) قرار میدادیم و پس از اینکه دیتا به صورت کامل درون شیفت رجیستر قرار گرفت و ارسال دیتا استارت خورد با استفاده از بیت TXE به ما خبر داده میشد که دیتای بعد را میتوانیم درون بافر مربوط به ارسال دیتا قرار بدهیم، اما در مورد خواندن قضیه کمی متفاوت است.
این متفاوت بودن قضیه از آنجا ناشی میشود که ما میدانیم که قرار است چه زمانی دیتا را ارسال کنیم، اما نمیدانیم که چه زمانی قرار است دیتا به سمت ما فرستاده شود. همین که نمیدانیم چه زمانی قرار است دیتا به سمت ما فرستاده شود خود یک مشکل ایجاد میکند.
راهحلهای دریافت دیتا در پروتکل UART
برای رفع این مشکل دو راهحل وجود دارد. یکی از راهحلها این است که منتظر بمانیم و هر وقت دیتا، درون بافر مربوط به دریافت دیتا قرار داده شد، آن دیتا را برداریم. خب این راهحل اصلا منطقی نیست چون که باید تقریبا تمام توان پردازشی میکروکنترلر را برای همین کار قرار بدهیم و اگر هم بخواهیم فقط بخشی از توان پردازشی را برای این کار قرار بدهیم ممکن است دیتایی lost شود یا از دست برود.
حداقل توان پردازشی که باعث میشود هیچ دیتایی از بین نرود، توانی است که بر سرعت جایگزینی دیتای جدید با دیتای قبلی در بافر، غلبه کند!
خب اجازه بدهید راهحل دوم را بررسی بکنیم. اما پیشنهاد میکنم قبل از اینکه بدانید راهحل دوم چیست، یک بار دیگر قسمت هفتم که مربوط به Interrupt یا همان وقفه است را مرور بکنید.
در واقع راهحل دوم مربوط به کاربرد وقفهها میشود. نحوهی کار به این صورت است که هر وقت دیتایی از طریق پین RX به بافر دریافت دیتا رسید، وقفهی مربوطه فعال میشود و ما از طریق این وقفه متوجه خواهیم شد که باید به دیتای دریافتی رسیدگی کنیم.
پس هر وقت که از طریق وقفه 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 مثل ارسال و دریافت دیتا وجود دارد. تمام رویدادهای موجود در این تصویر روندی مشابه به هم دارند، پس ما تنها یکی از این رویدادها که همین رویداد مربوط به دریافت دیتا است را بررسی میکنیم.
ما اگر بخواهیم زمانی که بیت 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 باشد را مانند قسمت هشتم تنظیم میکنیم.
همچنین USART1 global interrupt را فعال میکنیم تا وقفه UART فعال شود.
پس از انجام تنظیمات بالا از پروژه خروجی میگیریم و وارد محیط برنامهنویسی Keil میشویم.
برنامهای که میخواهیم در ادامه بنویسیم اینگونه است که ابتدا در حالت UART-Receive، با استفاده از وقفه، چندین کارکتر را دریافت میکنیم و این کارکترها را کنار هم قرار میدهیم تا یک رشته ساخته شود، سپس همین رشته ساخته شده را به کارکترهای تشکیلدهندهاش تجزیه میکنیم و در حالت UART-Transmit بر روی پورت UART میفرستیم و در پورت سریال کامپیوتر این رشته را مشاهده خواهیم کرد.
در برنامه ابتدا یک آرایه به اسم Value_RX، با نوع char و به طول 5 تعریف میکنیم تا کاراکترهای دریافتی را درون این آرایه ذخیره کنیم. همچنین سه متغیر 8 بیتی با نوع uint8_t با نامهای i و j را برای شمارنده و x را برای تشخیص مُد دریافت یا ارسال تعریف میکنیم.
برای فعال کردن رویداد دریافت کافی است که فقط یک خط کد زیر را درون main برنامه و قبل از حلقه while بنویسیم:
LL_USART_EnableIT_RXNE(USART1);
ابتدا به کد زیر که تابع وقفه است دقت کنید:
void USART1_IRQHandler(void) { /* USER CODE BEGIN USART1_IRQn 0 */ if(LL_USART_IsActiveFlag_RXNE(USART1) && LL_USART_IsEnabledIT_RXNE(USART1)) { Value_RX[i++] = LL_USART_ReceiveData8(USART1); if (i > 4) { i = 0; x = 1; } } /* USER CODE END USART1_IRQn 0 */ /* USER CODE BEGIN USART1_IRQn 1 */ /* USER CODE END USART1_IRQn 1 */ }
خب همانطور که قبلا هم گفتیم همهی رویدادهای 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 مینویسیم:
if (j < 5 && x == 1) { LL_USART_TransmitData8(USART1, (uint8_t)Value_RX[j++]); while(!LL_USART_IsActiveFlag_TXE(USART1)); } if (j == 5) { j = 0; x = 0; }
چون ما درون تابع وقفه پس از اینکه کارکترها را به صورت کامل دریافت کردیم، با تغییر متغیر x به عدد 1، مُد برنامه را به حالت ارسال تغییر دادیم، پس در کد بالا شرط if برقرار است و همان رشتهی دریافتی به کارکترهای تشکیلدهندهاش تجزیه و بر روی پورت UART فرستاده خواهد شد. پس از اینکه رشته به صورت کامل ارسال شد، مقدار متغیر j که شمارنده است را 0 و همینطور مقدار متغیر x را برای اینکه دوباره وارد مُد دریافت بشویم هم 0 میکنیم.
عملکرد برنامه اینگونه است که یک رشته را از شما دریافت میکند و سپس همان رشته را بر روی پورت UART برای شما میفرستد و دوباره منتظر گرفتن رشته جدید میشود. دقت کنید برای سادگی برنامه، طول رشته را 5 حرف در نظر گرفتیم، شما هم اگر خواستید خودتان این برنامه را تست کنید، طول رشتهای که ارسال میکنید را 5 حرف در نظر بگیرید.
برای تست برنامه، نرمافزار RealTerm را بر روی کامپیوتر اجرا میکنیم تا نتیجه را مشاهده کنیم.
ما پس از ارسال رشته “Kamin” و “STM32” نتیجه زیر را بر روی کامپیوتر مشاهده خواهیم کرد:
در قسمت دهم در رابطه با مبدل آنالوگ به دیجیتال (ADC) صحبت خواهیم کرد.
منبع:سیسوگ