در قسمت هفتم از آموزش STM32 با توابع LL، ابتدا در رابطه با کلیت و ذات وقفه صحبت کردیم و گفتیم که به چه دلایلی وقفه مفید است و باید در سیستم وجود داشته باشد، در ادامه در رابطه با وقفه در میکروکنترلرهای STM32 سری F1 صحبت کردیم و طرز کار واحد NVIC رو به طور کامل شرح دادیم. در نهایت هم نگاهمان را معطوف به وقفههای خارجی کردیم و یک وقفهی خارجی که بر روی پینهای میکروکنترلر جانمایی شده است را راهاندازی کردیم. در این قسمت میخواهیم در رابطه با پروتکل UART در میکروکنترلرهای STM32 صحبت کنیم. ابتدا تئوری مربوط به پروتکل UART را با جزئیات کامل شرح میدهیم و سپس این پروتکل ارتباطی را در میکروکنترلر STM32 راهاندازی میکنیم.
UART(universal asynchronous receiver-transmitter)
قبل از اینکه در رابطه با این پروتکل صحبت کنم این نکته را یادآور شوم که UART برگرفته از USART است که حرف S درون عبارت USART به معنای Synchronous است. اما چون اغلب از بخش سنکرون این پروتکل استفاده نمیشود، این پروتکل بیشتر با نام UART شناخته میشود و ما هم در این مقاله قصد داریم به UART بپردازیم.
حال شاید از خودتان بپرسید سنکرون (Synchronous) یا آسنکرون (Asynchronous) به چه معناست، در مدارات الکترونیک دیجیتال هر موقع اسم سنکرون را به کار میبریم به قطع یقین در جایی از مدارمان یک سیگنال، به اسم سیگنال کلاک وجود دارد.
به عنوان یادآوری این را بگویم که مدارات الکترونیک دیجیتال به دو دسته کلی ترکیبی و ترتیبی تقسیم میشوند. و مدارات ترتیبی خود به دو دستهی ترتیبی سنکرون و ترتیبی آسنکرون تقسیم میشوند. در ترتیبی سنکرون، مدار همیشه با عاملی به اسم کلاک کار میکند و هماهنگ است. اما در ترتیبی آسنکرون، هیچ کلاکی وجود ندارد و عملکرد مدار بر اساس پارامتر دیگری که در ادامه توضیح میدهم، کار میکند و هماهنگ است.
پس تا اینجا فهمیدیم که UART در دستهی مدارات آسنکرون و USART در دستهی مدارات سنکرون قرار میگیرد.
UART یک پروتکل ارتباطی سریال متشکل از یک فرستنده و یک گیرنده است که در هر لحظه هم میتواند دیتایی را دریافت و هم دیتایی را به نقطهی دیگری ارسال کند که اصطلاحا به این قابلیت Full duplex گفته میشود.
پینهای RX و TX در پروتکل UART
تصویر بالا دو تراشه که از پروتکل UART پشتیبانی میکنند را نشان میدهد. همانطور که از این تصویر مشخص است، در پروتکل UART علاوه بر GND که در همه جا و همهی پروتکلها وجود دارد و معیاری برای سنجش سایر سیگنالها است، دو پین اصلی دیگر به اسم RX و TX نیز وجود دارد که RX به معنای گیرنده و TX به معنای فرستنده است
اگر به خوبی به این تصویر دقت کنید میبینید که TX دیوایس اول به RX دیوایس دوم، و TX دیوایس دوم به RX دیوایس اول متصل است و دلیل این موضوع هم این است که وقتی دیوایس اول دیتا را بر روی TX میفرستد، در سمت دیگر، دیوایس دوم باید همان دیتا را بر روی RX دریافت کند.
نحوهی انتقال دیتا در پروتکل UART
یکی از مهمترین عوامل در شناخت و اشراف به یک پروتکل، آشنایی با نحوهی انتقال دیتا در آن پروتکل است.
در هر پروتکل یک سری قوانین توسط سازندگان آن پروتکل تدوین شده است که این قوانین نحوهی انتقال دیتا را شرح میدهند.
در پروتکل UART هم یک سری قوانین برای انتقال دیتا وضع شده است که در ادامه این قوانین را بررسی میکنیم.
ابتدا به تصویر زیر دقت کنید:
در تصویر بالا یک پکت دیتا نشان داده شده است که این پکت دارای جزئیاتی به شرح زیر است:
Start bit: این بیت از پکت که مقدار آن صفر منطقی است نشاندهندهی شروع ارسال پکت است و به گیرنده خبر میدهد که ارسال پکت شروع شده است.
Data Frame: پس از اینکه Start bit ارسال شد نوبت این است که دیتای موردنظر ما به سمت گیرنده ارسال شود. دیتا میتواند متشکل از 5 تا 9 بیت باشد اما بیشتر از 8 یا 9 بیت استفاده میشود.
Parity bit: بیت پریتی یا معادل آن در زبان فارسی توازن، معیاری برای سنجش خطا است. این بیت اختیاری است، یعنی پکت هم میتواند شامل بیت پریتی باشد و هم نه. مقدار بیت پریتی در فرستنده و گیرنده محاسبه و با هم مقایسه میشوند، اگر مقدار این بیت در سمت فرستنده و گیرنده برابر نبودند یعنی اینکه خطایی رخ داده است و دیتای دریافتی صحیح نمیباشد.
یک توضیح مختصر در رابطه با بیت پریتی میدهم برای جزئیات بیشتر میتوانید به مراجع مربوطه مراجعه کنید. دو نوع پریتی یعنی پریتی زوج و پریتی فرد وجود دارد.
در پریتی زوج همهی بیتهای Data Frame با هم XOR میشوند تا اگر تعداد 1های بیتهای Data Frame فرد بود، بیت پریتی یک منطقی شود و اگر تعداد 1های بیتهای Data Frame زوج بود، بیت پریتی صفر منطقی شود تا تعداد 1های مجموع بیتهای Data Frame و بیت پریتی زوج باشد.
در پریتی فرد همهی بیتهای Data Frame با هم XNOR میشوند تا اگر تعداد 1های بیتهای Data Frame فرد بود، بیت پریتی صفر منطقی شود و اگر تعداد 1های بیتهای Data Frame زوج بود، بیت پریتی یک منطقی شود تا تعداد 1های مجموع بیتهای Data Frame و بیت پریتی فرد باشد.
Stop bit: این بیت از پکت که مقدار آن یک منطقی است نشاندهندهی پایان ارسال پکت است و به گیرنده خبر میدهد که ارسال پکت به پایان رسیده است. البته همانطور که از تصویر مشخص است بیت پایان میتواند از 1 به 2 بیت نیز افزایش یابد.
خب ما تا اینجا جزئیات یک پکت در پروتکل UART را بررسی کردیم و گفتیم که هر جز از پکت چه کاربردی دارد. اما هنوز یک عامل مهم در این پروتکل را بررسی نکردیم و آن عامل هم چیزی نیست جز Baud rate.
Baud rate در واقع مشخص میکند که در یک ثانیه چند بیت دیتا منتقل میشود. اگر به خاطر داشته باشیم در ابتدای مقاله گفتیم عاملی که باعث کار و هماهنگی مدارات سنکرون میشود کلاک است ولی در مدارت آسنکرون که کلاکی وجود ندارد یک پارامتر دیگر این کار را انجام میدهد که آن پارامتر همین Baud rate است.
در فریم دیتا وقتی ما یک بیت را ارسال میکنیم بسیار مهم است که ارسال این بیت چه مقدار زمانی طول میکشد. در واقع Baud rate است که این مقدار زمان را مشخص میکند.
مقدار Baud rate های متفاوت و زیادی در این پروتکل بنا به سرعت و کاربرد مورد نیاز ما وجود دارد اما معمولا از دو سرعت 9600 و 115200 استفاده میشود.
پروتکل UART در میکروکنترلرهای STM32
در میکروکنترلرهای STM32 علاوه بر اینکه تمامی مواردی که در رابطه با پروتکل UART بررسی کردیم، پشتیبانی میشود، موارد جانبی دیگری نیز به همراه این پروتکل ارائه شده است که در داکیومنتهای شرکت ST این موارد جانبی را جز قابلیتهای مهم این دسته از میکروکنترلرها به حساب آورده است.
اما هر کدام از این موارد جانبی باید به صورت جداگانه توضیح داده شوند و راهاندازی شوند و از حوصلهی این مقاله خارج است. پس ما در این مقاله قصد داریم که فقط پروتکل UART را در حالت ارسال دیتا راهاندازی کنیم.
خب اجازه بدهید که به نرمافزار STM32CubeMX برویم تا UART را در حالت ارسال داده راهاندازی کنیم.
ابتدا کلاک و دیباگ را مانند گذشته تنظیم میکنیم و سپس از بخش Connectivity که مربوط به پروتکلهای پشتیبانی شده توسط میکروکنترلر است، USART1 را فعال میکنیم.
پس از فعال کردن USART1 میبینم که دو پین مربوط به USART1 به شکل زیر در آمده اند:
اکنون باید مشخص کنیم که میخواهیم از کدام یک از حالتهای سنکرون یا آسنکرون USART1 استفاده کنیم. اگر حالت سنکرون را انتخاب کنیم که معادل همان USART و اگر حالت آسنکرون را انتخای کنیم معادل همان UART میشود.
اما همانطور که گفتم ما میخواهیم از حالت آسنکرون یعنی از UART استفاده کنیم. پس مانند شکل زیر حالت آسنکرون را انخاب میکنیم:
با توجه به پکت مربوط به پروتکل UART، پارامترهای Parity ،Word length ،Baud rate و Stop bit پارامترهای متغیری بودند و ما باید مقدار آنها را از بین چند مقدار موجود تعیین میکردیم.
ابتدا به تصویر زیر دقت کنید:
همانطور که از تصویر بالا مشاهده میکنید مقدار Baud rate را ما 115200 بیت بر ثانیه تنظیم کردیم. این نوع میکروکنترلر Baud rate بین 245 بیت بر ثانیه تا 1000 کیلو بیت بر ثانیه را پشتیبانی میکند.
طول دیتا در این میکروکنترلر از دو مقدار 8 و 9 بیت پشتیبانی میکند که ما مقدار 8 بیت را انتخاب کردیم.
طبق توضیحاتی که دادیم بیت پریتی هم میتواند در پکت ارسالی وجود نداشته باشد و هم میتواند وجود داشته باشد و یکی از حالتهای زوج یا فرد باشد. ما در اینجا بیت پریتی را روی حالت None تنظیم کردیم یعنی اینکه میخواهیم بیت پریتی در پکت ارسالی نباشد.
و در نهایت Stop bit هم باید 1 یا 2 بیت باشد که ما در اینجا 1 بیت را انتخاب کردیم.
همچنین چون ما میخواهیم فقط دیتا ارسال کنیم، پس Data direction را بر روی Transmit only تنظیم میکنیم.
پس از انجام تنظیمات بالا از پروژه خروجی میگیریم و وارد محیط برنامهنویسی Keil میشویم.
ما ابتدا یک ثابت (const) آرایهای به اسم Name، با نوع char و به طول 6 تعریف میکنیم و رشتهی “Kamin” را درون این ثابت قرار میدهیم.
همچنین یک متغیر 8 بیتی به نام i و با نوع uint8_t نیز برای شمارنده تعریف میکنیم.
روند کار ما اینگونه است که میخواهیم آرایه Name که رشته “Kamin” در آن قرار دارد را با استفاده از UART میکروکنترلر به کامپیوتر ارسال کنیم و نتیجه را کامپیوتر مشاهده کنیم.
ابتدا به کد زیر که درون حلقه while نوشتیم دقت کنید:
1 2 3 4 5 6 7 8 | LL_USART_TransmitData8(USART1, Name[i++]); while(!LL_USART_IsActiveFlag_TXE(USART1)); if(i == 6) { i = 0; } |
ما ابتدا با استفاده از تابع LL_USART_TransmitData8 کارکتر اولِ Name را با UART1 برای کامپیوتر ارسال میکنیم. برای اینکه بتوانیم دومین کارکتر و به همین ترتیب تا آخرین کارکتر را هم برای کامپیوتر ارسال کنیم، باید بررسی کنیم ببینیم که آیا رجیستر مربوط به دیتای ارسالی خالی است که ما کارکتر بعدی را بفرستیم یا نه.
اجازه بدهید قبل از اینکه توضیح بدهم که چگونه باید بررسی کنیم که آیا رجیستر دیتای ارسالی خالی است یا نه، پشتپردهی انتقال دیتا با استفاده از UART در میکروکنترلر را توضیح بدهم.
زمانی که ما با استفاده از تابع LL_USART_TransmitData8 قصد داریم دیتایی را با استفاده از UART انتقال بدهیم، دیتای ما درون رجیستر Transmit Data Register (TDR) قرار میگیرد. پس از قرار گرفتن دیتا درون این رجیستر، شیفت رجیستری به نام Transmit Shift Register وظیفه دارد که دیتای درون رجیستر Transmit Data را بر روی پین TX میکروکنترلر قرار دهد.
پس از اینکه دیتای درون Transmit Data Register (TDR) به طور کامل درون شیفت رجیستر قرار گرفت و ارسال دیتا استارت خورد، بیت هشتم از رجیستر Status register به نام TXE مقدارش 1 میشود و نشاندهندهی این است که ما میتوانیم دیتای بعدی را درون رجیستر Transmit Data Register (TDR) قرار بدهیم بدون اینکه هیچ گونه خطایی رخ بدهد.
دقت کنید که بیت TXE از رجیستر Status register فقط خواندنی است و مقدار آن به صورت سختافزاری تغییر میکنید و ما از طریق برنامه یا نرمافزار نمیتوانیم مقدار این بیت را تغییر بدهیم، بلکه فقط میتوانیم مقدار این بیت را بخوانیم.
پس به طور خلاصه زمانی که ما دیتایی را درون رجیستر Transmit Data Register (TDR) مینویسیم بیت TXE به صورت سختافزاری 0 و زمانی که همین دیتا به طور کامل درون شیفت رجیستر قرار گرفت و ارسال دیتا استارت خورد این بیت به صورت سختافزاری 1 میشود.
با توجه به توضیحات بالا ما درون برنامه با استفاده از تابع LL_USART_IsActiveFlag_TXE بررسی کردیم که چه موقع بیت TXE مقدارش 1 میشود و تا زمانی که 1 نشد برنامه منتظر بماند تا دیتا ارسال شود و هر زمانی هم که 1 شد دیتای بعدی درون رجیستر Transmit Data Register (TDR) قرار بگیرد.
همچنین با استفاده از ساختار شرطی if بررسی کردیم که اگر آخرین کاراکتر از رشته ارسال شد، دوباره به اول رشته برگردد. در واقع ما به صورت متوالی رشتهی “Kamin” را با استفاده از UART برای کامپیوتر میفرستیم.
پس از اینکه برنامه را اجرا و مبدل USB به TTL را بین میکروکنترلر و کامپیوتر متصل کردیم، رشته “Kamin” به صورت متوالی مانند تصویر زیر به پورت سریال کامپیوتر فرستاده میشود.
در این قسمت، پروتکل UART و همچنین فرستادن دیتا با استفاده این پروتکل را بررسی کردیم، در قسمت نهم در رابطه با دریافت دیتا با استفاده از پروتکل UART صحبت خواهیم کرد.
سلام و خسته نباشید
برای اتصال TTL و دریافت و ارسال دیتا با UART حتما باید مبدل TTL به USB داشته باشیم یا با پروگرامر STLINK-V2 هم امکان پذیر است؟
سلام. مبدل سریال نیاز هست
خیلی ممنونم از آموزش خوبتون. چطور میتونم رشته مد نظرم رو فقط یک بار ارسال کنم؟ یعنی یک بار از میکرو به سیستم من ارسال بشه نه مدام و پشت سر هم.
فقط کافیه دستور ارسال رو از حلقه while فانکشن main خارج کنید.
سلام و وقت بخیر
تشکر بابت آموزشها
من یه مشکل برخورد کردم که دلیلش رو پیدا نمیکنم
پورت سریال USART1 انگار فعال نمیشه
من قبلا با HAL راهش انداخته بودم و بوردم رو تست کرده بودم
ولی الان که پروژه رو پورت میکنم روی LL اصلا انگار نه انگار، هیچ ارسالی اتفاق نمیافته
USART3 کاملا صحیح عمل میکنه، ولی USART1 کلا کار نمیکنه
پروژه جدید هم درست کردم، چه با HAL، چه با LL، روی یه میکرو و هدربورد هم امتحان کردم، اصلا خروجی نداشت
دوستان نظری دارید ؟
متشکرم
ُسلام دوست عزیز، مرسی وقت شما هم بخیر. خوشحالیم که آموزشهای مارو دنبال میکنید.
قبل از هر چیزی چک کنید که پروژه رو به شکل صحیح ایجاد میکنید، یعنی اگر از Cube MX استفاده میکنید، USART1 رو فعال کرده باشید. توی کد تولید شده هم میتونید چک کنید و ببینید سیگنال کلاک برای USART1 فعال شده یا نه (توی قسمت کدهای تولید شده برای USART Init به وسیله Cube MX). نکته دیگر اینکه توجه کنید که حتما بعد از کدهای مربوط به Initialization (مثلا بعد از USER CODE BEGIN 2) کدهای مربوط به USART را بنویسید، حالا اگر از HAL استفاده میکنید، فقط یه تابع برای استفاده از HAL نیازه، پس در صورتی که توی مراحل قبلی، و اتصال سخت افزاری مشکلی وجود نداشته باشه باید بتونید اطلاعات رو بدون هیچ مشکلی ارسال یا دریافت کنید. در صورتی هم که از LL استفاده میکنید به وسیله توابع LL، هر بایت از داده را پس از چک کردن پرچم ارسال داده، به خروجی ارسال کنید.
سلام و احترام
متشکرم از پاسختون
پروژه های مختلفی با Hal و LL ساختم و هیچکدوم کار نکردند، انگار که اصلا سریال، کلاک نداشت، در حالی که تمام رجیسترها رو با دیباگر چک میکردم
آخر سر به پیشنهاد یکی از دوستان، به جای USART، مود اصلی GPIO رو چک کردم، که متوجه شدم کلا پین ترنسمیتر کلا Pull up شده و اصلا قسمت push-pull کار نمیکنه و مطمئن شدم که این پین آسیب دیده، با تعویض میکرو مشکل حل شد
باز هم از وقت و زحمات و راهنماییتون تشکر میکنم
روز خوبی داشته باشید
سلام مجدد خدمت شما دوست عزیز.
خواهش میکنم.
تا جاییکه من اطلاع داشتم احتمال رخ دادن ایراد سخت افزاری خیلی کمتر از موارد دیگهای بود که بهشون اشاره کردم.
مرسی که جزییات مسئله رو با ما در میون گذاشتین.
روز شما هم خوش.
سلام
چطور میشه مقدار یک متغیر، مثل همین i رو از طریق uart منتقل کرد و روی لپ تاپ دید؟ دستورش رو چطور باید نوشت؟
سلام دوست عزیز
برای این کار دو راهکار وجود داره یکی ارسال به شکل باینری و دیگری ارسال به شکل ascii ، حالت دوم ساده تر و قابل فهم تر برای کاربر هست
برای این کار مقدار متغییر i رو با استفاده از دستور sprintf به شکل یک آرایه از بایت ها تبدیل کنید و بعد حاصل را در طرق پورت سریال ارسال کنید
int i=55
char tmp[15] = {0};
sprintf(tmp,"%d",i);
uart_send(tmp);
عالی، متشکرم. خیلی خوب در یک خط گفتین.
کاش بشه یه آموزک(آموزشهای ریز ریز) با محتواهای اینچنینی که در عمل کاربرد دارن بذارید. برای شما که اکسپرت هستید اینا عادیه ولی برای تازه کارا کلی طول میشه و روند کلی آموزش رو کند میکنه.
سلام
پیشنهاد خیلی خوبی هست اتفاقا، در موردش فکر میکنیم این که چطور میشه اجراییش کرد و چطور میشه موارد این چنینی رو دید
ممنونم
بیشترش در کامنت ها موجود هست، چون دوستان پرسیدن، ولی ممکنه یک سوال در آموزش با ریجیستری باشه یکی در HAL یکی در LL که همه به یک مورد اشاره میکنن!
این سوالات چیزایی هست که توی اغلب آموزش ها به طور مستقیم نمیگن(همون تجربه شما عزیزان)
گاهی سر چیزای خیلی ریز شاید ساعتها و بعضاً روزها درگیر بشم، به نظرم میتونه خیلی مفید باشه:)
تشکر فراوان از سیسوگی های عزیز.
ممنونم
داریم به این مساله فکر میکنم
و البته الان راه حل براش دارم، امیدوارم به زودی بتونیم لانچش کنیم
سلام.
در این بخش اگر بخوایم مقدار یک متغیر، مثلا خروجی یک سنسور رو بفرستیم روی UART باید چه کنیم؟
فرض کنید یک اینتراپت خارجی گذاشتیم که با فشردن دکمه مقدار یک سنسور خوانده میشه و میخوایم همون لحظه این مقدار را روی سریال مانیتور ببینیم.
ممنون از زحمات و وقتی که گذاشتید ?❤
سلام دوست عزیز
میتونید به کمک دستور sprintf مقدار عددی دستور به رشته کارکتر تبدیل کنید و بعد ارسال کنید روی پورت سریال 🙂
مرسی از توضیحات خوب
من امروز خواستم این مدار رو عملی تست کنم و وقتی به انتهای مطلب رسیدم و پروگرم کردم، بعدش موندم که چی کار کنم.
ما یکی از پایه های میکرو را به عنوان فرستنده در نظر گرفتیم. حالا این پایه رو چطور به لپ تاپ وصل کنیم؟ باید مبدلی استفاده کنیم ؟ TTLبه USB مثلا
خواهش میکنم پرنیا جان. بله درسته باید از یک مبدل USB به TTL استفاده بکنید.
فراموش کردم این را بنویسم. ممنون که یادآوری کردید، الان آخر مقاله اضافهاش میکنم.
سلام
اگر flow control رو توی cube فعال کرده باشیم، پایههای rts و cts رو خودمون دستی باید بررسی و تنظیم کنیم؟
چون توی hal خودش انجام میده
سلام دوست عزیز. نه، مانند همون توابع HAL اگر در Cube فعال کرده باشید، در کد جنریت شده هم فعال میشود.