در قسمت هشتم از آموزش STM32 با توابع LL، ابتدا مقدمات پروتکل UART را بررسی کردیم و گفتیم که یک پکت دیتا در این پروتکل شامل چه بخشهایی میشود و این دیتا به چه صورت و با چه سرعتهایی میتواند منتقل بشود.
در نهایت هم پروتکل UART را در میکروکنترلرهای STM32 بررسی کردیم و بخش Transmit یا همان ارسال دیتا را به صورت عملی بر روی برد راهاندازی کردیم.
در این قسمت میخواهیم بخش UART-Receive یا همان دریافت دیتای پروتکل UART را راهاندازی کنیم.
چالش دریافت دیتا در پروتکل UART
اگر از قسمت قبل به یاد داشته باشید، هر وقت که میخواستیم دیتایی را ارسال کنیم آن دیتا را درون بافر مربوط به ارسال دیتا (Transmit Data Register (TDR)) قرار میدادیم و پس از اینکه دیتا به صورت کامل درون شیفت رجیستر قرار گرفت و ارسال دیتا استارت خورد با استفاده از بیت TXE به ما خبر داده میشد که دیتای بعد را میتوانیم درون بافر مربوط به ارسال دیتا قرار بدهیم، اما در مورد خواندن قضیه کمی متفاوت است.
این متفاوت بودن قضیه از آنجا ناشی میشود که ما میدانیم که قرار است چه زمانی دیتا را ارسال کنیم، اما نمیدانیم که چه زمانی قرار است دیتا به سمت ما فرستاده شود. همین که نمیدانیم چه زمانی قرار است دیتا به سمت ما فرستاده شود خود یک مشکل ایجاد میکند.
شاید برای شما مفید باشد: راه اندازی ماژول nrf24l01
راهحلهای دریافت دیتا در پروتکل 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 بنویسیم:
1 | LL_USART_EnableIT_RXNE(USART1); |
ابتدا به کد زیر که تابع وقفه است دقت کنید:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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 مینویسیم:
1 2 3 4 5 6 7 8 9 10 11 12 | 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) صحبت خواهیم کرد.
سلام
کد رو از کجا میشه دانلود کرد؟
سلام من برای نوشتن این برنامه با ارور
‘Value_RX’ undeclared مواجه میشم و نمی دونم چطور value_rx باید فراخوانی بشه
سلام دوست عزیز
پروژه توی گیت های سیسوگ هست
اونو کلون کنید و استفاده کنید.
سلام
مرسی از توضیحات عالیتون
در مورده متن زیر سوال داشتم
“برای تشخیص رویداد دریافت، در کد بالا و درون تابع USART1_IRQHandler با استفاده از شرط if بیتهای RXNE و RXNEIE را بررسی کردیم”
اگر در تابع وقفه این دو بیت را چک نکنیم، چه مشکلی پیش میاد؟
فرض من این هسش که همین که روال اجرای برنامه تا اینجا اومده یعنی خود به خود هر دو شرط صادق بوده دیگه!
مرسی
سلام دوست عزیز
اتفاق خاصی نمی افته 🙂
سلام وقت بخیر
ببخشید من همین کد رو میزنم ولی در فایلstm32f1xx_it.c با ارور undefined refrence to ”Value_RX مواجه میشم
Value_RXهم در main تعرف کردم و در فایلstm32f1xx_it.c با حالت extern دوباره آوردمش.
سلام، اینجا چطور میشه برای رشتهی ارسالی طول تعیین نکرد؟
مثلاً میخوایم از طریق کامپیوتر کلمات مختلف بنویسیم با طول های مختلف و بعد ارسال کنیم. منظورم اینه چطور باید به صورت پارامتری(ماژولار) بنویسیم؟ مرسی.
سلام دوست عزیز
خوب در واقع توی این قسمت یه بیسیک اولیه از دریافت کارکتر نوشته شده برای دریافت رشته های پیچیده با طول دلخواه بهتره که شما از fifo استفاده کنید
fifo یه بافر چرخشی هست که یکی از کاربردهاش دقیقا همین مساله است، کارکتر های دریافت شده رو توی بافر میذاره و شما دونه دونه میتونید آنها رو بخوانید و پردازش کنید بدون این که نگران از دست رفتن کارکترها باشید.
خیلی ممنون از سر نخ خوبی که دادین.
شما باعث کلی صرفه جویی در وقت میشید با این سایتی که دارید. تشکر.
خواهش میکنم دوست عزیز 🙂
نظر لطف شماست
سلام
یه مقدار در رابطه با اینکه چطور از دیتای دریافتی استفاده کنیم هم میگید؟
البته چون تجربه زیادی دارید راهنمایی کلی میخوام که در کل برای گرفتن دیتا از کامپیوتر(مثلا تنظیم سرعت موتور) چطور و بیشتر به چه نحوی این کار رو انجام میدن؟ چه پروتکلی مرسوم تر هست؟
مثلا من قصد دارم یک عددی وارد کنم که موتور با اون سرعت بچرخه و به کمک آموزش بخش قبل، سرعت واقعی موتور رو هم به کامپیوتر ارسال کنم.(مثال ساده شده عرض کردم)
متشکرم. ?
سلام دوست عزیز
در این خصوص عملا شما وارد دنیای پروتکل ها میشد که اتفاقا پروتکل های ارتباطی زیادی هم هست و این بستگی به خود شما داره بسته به نیاز یکی از آنها را انتخاب کنید
برای انتخاب شما پارامترهایی مثل این که چه چیزی قراره از پروتکل استفاده کنه مهم میشه مثلا اگر قراره پروتکل مورد استفاده افراد باشه پس تباید پروتکل باینری باشه و بهتری که اسکی بیس باشه و اگر قراره بین دستگاه های الکترونیکی استفاده بشه هر کدوم از این موارد میتونه مورد استفاده باشه
برای ارتباط هم پروتکل هایی مثل at بیس هست درست مثل چیزی که برای مودم ها استفاده میشه یا stx,etx و … که با یه سرچ ساده می تونید لیستش رو توی ویکی پیدا کنید.
حتی می تونیدبر اساس نیاز پروتکل خودتون رو طراحی کنید.
سلام هنگام کار با این کد یک مشکل برخوردم و آن تعریف متغیرهای ابتدای برنامه بود . که بعد از یک ساعت جستحو در اینترنت راه حل را پیدا کردم
در فایل stm32f1 متغییرها راتعریف می کنیم و درفایل main بیرون از تابع main متغیر ها را با کلمه extern قبل از آن متغییر تعریف می کنیم
مثال
;int x
;extern int x
سلام هادی جان. بله درسته، تو کد این مورد را لحاظ کردم ولی فکر کنم در مقاله یادم رفت که این نکته را بگویم.
البته بهتر است که ابتدا متغیر را در فایل main تعریف کنید، سپس در فایلهای دیگر آن را از با کلاس extern تعریف کنید.
سلام
با تشکر از آموزشهاتون
من همچنان منتظر قسمتهای بعدی هستم
سلام رضا جان. فردا حتما قسمت دهم منتشر میشه. منتظر باشید.
آموزش دیگه ادامه نداره؟
من 1-2 روز دیگه این بخش رو هم تمرین میکنم و تموم میشه. امیدوارم ادامه دار باشه. مثلاً مباحث پیاده سازی روش های کنترلی و … مثال بزنید.(کنترلر دیجیتال) مثلا کنترل دمای (هیتر=لامپ، سنسور دما هم که LM35)
تا جایی که من رسیدم که عالی بوده، تشکر کامین عزیز ?
پرنیا جان، خوشحالم از اینکه این مقالات برایتان مفید بوده است. همین هفته قسمت دهم منتشر خواهد شد. بله آموزش ادامهدار خواهد بود، اما در ابتدای کار در یک آموزش اصولی سعی میشود بر روی امکانات خود میکروکنترلر و نحوهی به کارگیری آنها تمرکز شود. در قسمتهای پایانی حتما چندین پروژه انجام خواهیم داد.
متشکرم.
در ضمن اگر یه دوره پیش نیاز هم برای این بزارید و یه سری مقدمات رو بگید عالیه. مثلا داخل کد ها 0<<1 هست. با سرچ میشه فهمید ولی یه مقدار روند کلی خواندن رو کند میکنه.
امیدوارم شاد و موفق باشید
خب پیش نیاز این دوره زبان C هست، یعنی این مقالات با این پیش فرض نوشته شدهاند که شما با زبان C آشنایی کافی دارید.
بسیار عالی آموزش هاتون و تک فقط یک سوالی داشتم من همه این عملیات هوایی که گفتید رو انجام دادم و نوشتم ولی مشکلم اینه که متغییر ها رو گلوبال و خروجی هم تعریف کردم ولی متغییر که که تو تابع وقفه مقدار دهی میشود در تابع whileکد برنامه کار نمیکند و هیچ ری اکشنی به شرط ها نشان نمیدهد
باسلام و تشکر
در این روش اگر یک رشته ی مثلا ۲۰ کاراکتری رو یکجا ارسال کنیم برنامه جا نمیمونه و دیتا از دست نمیدیم؟
سلام نیما جان. چون هدف آشنایی با خود پریفرال UART هست، کد را خیلی ساده نوشتم و موردی که شما میفرمائید در کد در نظر گرفته نشده است.
ممنون میشم اگر برای پیاده سازی یه ارتباط سریال کامل برای کنترل یه دستگاه توضیح یا نمونه ای معرفی کنید
منظورتون این هست که یه دیوایسی که با این پروتکل کار میکنه رو راهاندازی کنیم؟