مقدمه
ارتباط سریال زوج سیم یا UART یکی از ابتدایی ترین پروتکلهای ارتباطی برای تبادل داده آسنکرون به بین دو قطعه یا دستگاه الکترونیکی است. از نقطه نظر پیچیدگی، پیاده سازی یک ارتباط سریال زوج سیم در FPGA چندان چالش برانگیز نیست. سلیقه طراح و نیازمندیهای هر پروژه دو عامل تأثیرگذار در نوع کدنویسی و نحوه پیاده سازی ماژول UART در FPGA هستند. به همین دلیل است که بی نهایت پیاده سازی متفاوت که همگی عملکردی صحیحی دارند، برای UART وجود دارد.
طی سالهای اخیر مقالات متعددی در رابطه با این پروتکل و پیاده سازی آن به زبان فارسی منتشر شده است، از این رو ما قصد نداریم چرخ را مجدداً اختراع کنیم و آموزشهای متعددی را که در رابطه با UART در اختیار همگان هست، مجدداً باز نشر دهیم. این نوشتار از پایگاه دانش هگزالینکس به تشریح عملکرد و نحوه استفاده از بهینه ترین پیاده سازی UART، برای تراشههای شرکت Xilinx اختصاص دارد. استفاده از واژه بهینه ترین به هیچ وجه اغراق نیست، زیرا طراحی آن توسط مهندسان Xilinx صورت گرفته و شما با مطالعه کدها به سادگی موضوع را درک خواهید کرد.
پیش زمینه
برای سالیان طولانی UART مترادف با RS232 بود. در حقیقت RS232 یک UART با سیگنال 12V± است که با یک کانکتور DB-9 در پشت کامپیوترها شناخته میشد. با آمدن USB و فراگیر شدن استفاده از آن، این پورت به مرور از کامپیوترهای شخصی حذف شد و اینگونه بنظر میرسید که مدتی بعد UART و استفاده از آن محو میشود و به تاریخ میپیوندد، اما برخلاف انتظار این واسط ارتباطی در بیشتر میزهای اداری و همچنین در صنعت بسیار زنده و پرکاربرد باقی ماند و میتوان گفت میراث RS232 تقریباً در همه جا وجود دارد. مطمئناً بخش اعظم این ابزارها و تجهیزات نیز برای سالهای متمادی در میدان باقی خواهد ماند. علاوه بر این، هنوز هم اپلیکیشنهای نرم افزاری بسیاری برای ارتباط با قطعات و تجهیزات الکترونیک نیازمند اتصال به پورت COM هستند. برخی شرکتها خیلی سریع تشخیص دادند که جایگزین کردن یک کامپیوتر با پورت قدیمی به این زودیها غیرممکن است و شروع به ارائه قطعات ساده مبدل USB به UART کردند که با اتصال به USB و ایجاد یک پورت COM مجازی، میتوان قطعات UART را وصل کرد. نه فقط این، که سهولت استفاده از این مبدلها باعث شده است تا همگان آینده روشن تری برای UART متصور باشند. دلیل تداوم استفاده از UART با وجود مزایای زیاد USB میتواند این باشد که پیچیدگیهای پروتکل USB زیاد است. در صورت نیاز به ارتباطات با نرخ بالا و قابلیت سریع اتصال و اجرا (Plug and Play)، تا حدی منطقی است که افزایش پیچیدگی در طراحی سخت افزار و نرم افزار را بپذیرید. اما با این وجود، هنوز اپلیکیشنهای متعددی وجود دارند که فقط به یک ارتباط با پهنای باند نسبتاً کم نیاز دارند. در ادامه عملکرد ماژولهای فرستنده و گیرنده UART ارائه شده توسط شرکت Xilinx شرح داده میشود.
برای دریافت کدها روی دکمه زیر کلیک کنید
در کنار کدهای فرستنده و گیرنده UART یک کد برای تجمیع و استفاده از این دو ماژول نیز ارائه گردیده است
کلیات
یک ارتباط ساده بین دو قطعه سخت افزاری معمولاً شامل ارسال و دریافت داده میشود. در ارتباط UART نیز این چنین است و ماژولهای فرستنده و گیرنده بطور مجزا طراحی شدهاند. این شکل از طراحی این قابلیت را فراهم میکند تا در مواردی که ارتباط یکطرفه است، از یکی از ماژولها استفاده شود و در مصرف منابع داخلی FPGA صرفه جویی شود. هر چند منابع مصرفی در این دو ماژول بسیار کم و حتی ناچیز است. جالب اینجاست که هر ماژول فرستنده و یا گیرنده تنها شامل یک بافر FIFO با عمق ۱۶ بایت است و در عین حال هر یک از این ماژولها فقط ۵ اسلایس (Slice) از تراشههای Spartan-6 یا Virtex-6 را اشغال میکنند. فضای مصرفی ترکیب هر دو آنها نیز برابر ۱۰ اسلایس، که معادل 1.7% از کوچکترین تراشه Spartan-6 و 0.01% از بزرگترین تراشه Virtex-6 است. هر دو ماژول فرستنده و گیرنده دارای تنظیمات ارتباطی ثابت و از پیش تعریف شده هستند. به این صورت که دارای ۸ بیت به داده، یک بیت آغاز (start bit)، یک بیت توقف (stop bit) و بدون بیت پریتی (parity bit) هستند. تقریباً برای طیف وسیعی از کاربردها همین تنظیمات عمومی در ارتباط UART کفایت میکند. در این ماژولها، نرخ باوود (Baud Rate)، متناسب با استفاده شما بوسیله تولید پالسهای فعال سازی و با توجه به فرکانس کلاک سیستم محاسبه و تعریف میشود.
در حالی که بسیاری از اپلیکیشنها به یک نرخ باود استاندارد مانند ۹۶۰۰ یا ۱۱۵۲۰۰ احتیاج دارند، این ماژولها میتوانند هر سرعت دادهای تا حداکثر یک شانزدهم فرکانس کلاک شما را فراهم کنند، این بدان معنی است که میتوانید سرعتهای بیش از 5mbps را نیز بدست آورید. همچنین با توجه به وجود بافرهای FIFO این ماژولها میتوانند هندشیکهای لازم را برای تسهیل ارسال و دریافت داده برای شما فراهم کنند. دادهها بطور موازی در بافرهای ۱۶ بایتی نوشته یا از آنها خوانده میشوند و این بافرها مجموعهای از پرچمهای وضعیت را برای استفاده در اختیار کاربر قرار میدهند.
در هر دو ماژول فرستنده و گیرنده پورت ورودی “en_16_x_baud” برای تعیین نرخ باود ارتباط سریال با توجه به فرکانس کلاک استفاده میشود. به این ترتیب که پالسهای فعال سازی تولید شده که در بالا به آن اشاره شد، به این ورودی اعمال میشود. نحوه محاسبه و تولید این پالسها در ادامه تشریح میشود.
همانطور که گفته شد، میزان سرعت این ماژولها میتواند حداکثر CLK/16 باشد. به عنوان مثال، با یک کلاک ۱۰۰ مگاهرتز، اگر ورودی en_16_x_baud به طور دائم ‘1’ منطقی باشد، میتوان نرخ انتقال داده حداکثر ۶۲۵ کیلو بایت در ثانیه را بدست آورد. البته به شرط آنکه برای در هر دو انتهای هر لینک در سیستم، نرخ بیت یکسانی را تعریف کنید.
هیچ کس نمیتواند شما را به تطابق با یک نرخ استاندارد مجبور کند. با وجود هر یک از ماژولهای UART که تنها ۱۰ اسلایس را اشغال میکنند، پیاده سازی چندین لینک UART بصورت موازی نیز یک راه مقرون به صرفه برای افزایش پهنای باند است.
به غیر از ورودی سریال به گیرنده، که به دلیل ماهیت ارتباط UART غیر همزمان (آسنکرون) است، همه ورودیها و خروجیهای هر دو ماژول همزمان با کلاک هستند و باید در طراحی مدنظر قرار بگیرد.
ارسال داده با ماژول فرستنده UART
ماژول فرستنده UART شامل دو بخش فرستنده و بافر FIFO است. پورتهای ورودی و خروجی و معماری داخلی این ماژول در شکل زیر ارائه شده است.
برای انتقال داده کافی است مقادیر ۸ بیتی را در بافر FIFO بنویسید. فرستنده UART به طور خودکار دادههای موجود در بافر را با نرخ باود تعریف شده توسط en_16_x_baud تا زمانی که بافر FIFO خالی شود، منتقل میکند. بافر FIFO کاملاً همزمان با کلاک است. دادههای ۸ بیتی که بر روی پورت ورودی “data_in” قرار داده میشوند، در صورتی که پورت کنترل ورودی “buffer_write” برابر یک منطقی باشد، در لبه بالا رونده کلاک در FIFO نوشته خواهند شد. به این ترتیب امکان نوشتن دادهها بصورت یکی یکی یا پشت سرهم ایجاد میشود.
در شکل بالا کدهای اسکی رشته متن “Time” با صورت ترکیبی نوشته و ارسال شدهاند. یعنی عملیات ارسال با نوشتن یک کاراکتر منفرد و به دنبال آن نوشتن پشت سر هم ۳ کاراکتر دیگر در FIFO انجام شده است.
برای کنترل مناسب نوشتن در بافر، سیگنالها یا پرچمهای وضعیتی از طرف بافر در اختیار کاربر قرار داده شده است که بسته به کاربرد، هر یک از آنها میتوانند مورد استفاده قرار بگیرند.
سیگنال Buffer_full
در حالی که ممکن است در هر لبه کلاک یک داده در بافر FIFO نوشته شود، اما فرستنده UART همیشه تعداد کلاکهای بیشتری را برای انتقال سریال هر کاراکتر یا داده نیاز دارد. به عنوان مثال ماژول UART ارسال یک بایت را در ۸۶۸۰ لبه کلاک در نرخ ۱۱۵۲۰۰ با استفاده از کلاک ۱۰۰ مگاهرتز انجام میدهد. بنابراین هرچند پر کردن بافر FIFO با ۱۶ بایت داده قبل از ارسالِ اولین کاراکتر کاری بسیار آسان است، ولی در ادامه نوشتن در بافر باعث بازنویسی داده برروی دادههای قبلی و در نتیجه از دست دادن دادهها میشود. بنابراین بسیار مهم است که اگر پرچم “buffer_full” فعال است، هیچ تلاشی برای نوشتن دادههای بیشتر در FIFO انجام نشود. متداول ترین تکنیکی که در یک اپلیکیشن مورد استفاده قرار میگیرد، این است که قبل از نوشتن هر یک داده در FIFO فرستنده، ابتدا بررسی شود که آیا پرچم buffer_full صفر منطقی است یا خیر؟ اگر پرچم فعال باشد، اپلیکیشن باید برای نوشتن داده دیگر و بررسی دوباره پرچم buffer_full منتظر غیر فعال شدن آن باشد.
سیگنال Buffer_half_full
پرچم “buffer_half_full” نیز یک خروجی فعال بالا است و زمانی ‘1’ خواهد شد که بافر FIFO هشت (۸) عدد یا بیشتر داده در انتظار انتقال، در درون خود داشته باشد. این سیگنال میتواند برای بعضی از کاربردهایی که ترجیح میدهند تعداد کوچکی داده پشت سرهم در FIFO بنویسند به جای اینکه یک کاراکتر یک کاراکتر این کار را انجام دهند، مفید باشد. به این صورت که ابتدا با بررسی اینکه پرچم buffer_half_full غیرفعال است، برنامه متوجه میشود که تا ۸ بایت اطلاعات را میتواند پشت سر هم بدون نیاز به بررسی پرچم buffer_full در هر بار نوشتن آنها، در بافر FIFO بنویسد.
سیگنال Buffer_reset
این سیگنال همانطور که از نامش پیداست یک ریست فعال بالا برای بافر است. با یک شدن ورودی “buffer_reset” بافر FIFO به طور سنکرون در لبه بالا رونده کلاک ریست میشود. باید توجه داشت در این حالت هر گونه داده در بافر از بین میرود و همه پرچمها پاک میشوند. این کار به طور معمول فقط هنگام راه اندازی مجدد سیستم یا متعاقب سرریز بافر (که به هر حال بهتر است از آن جلوگیری شود) لازم است. توجه داشته باشید که اگر ریست بافر در حالی که دادهها منتقل میشوند، اعمال شود، در این صورت دادهای که در آن زمان منتقل میشود احتمالاً خراب و نادرست ارسال میشود.
سیگنال Buffer_data_present
پرچم “buffer_data_present” در زمانی که یک یا چند داده در بافر FIFO برای ارسال موجود باشد، با مقدار یک منطقی فعال میشود. به بیان دیگر میتوان گفت زمانی که بافر خالی باشد، این پرچم ‘0’ است. در بسیاری از موارد اکثر برنامهها این پرچم را نادیده میگیرند، با این حال این سیگنال میتواند به دو طریق مفید باشد:
- اگر مشخص شود که FIFO خالی است، میتوان بدون نیاز به بررسی پرچم buffer_full در هر بار نوشتن، تا ۱۶ بایت داده را پشت سر هم بی خطر در بافر قرار داد.
- نمودار زیر چگونگی عملکرد پرچم buffer_data_present را هنگام نوشته شدن اولین داده در بافر نشان میدهد. در صورت فراهم نشدن دادههای دیگری برای ارسال در بافر، پرچم پس از ارسال آخرین بیت داده مجدداً صفر میشود. این موضوع میتواند توسط اپلیکیشن مورد نظر برای پیاده سازی یک شمای کنترل جریان سخت افزاری یا نرم افزاری (XON/XOFF) استفاده شود و تنها با نوشتن یک داده در بافر، سیگنال clear to send(CTS) برای گیرنده در سوی دیگر ارتباط سریال فراهم کرد. هر چن این مسأله باعث هدر رفت ظرفیت ذخیره سازی بافر FIFO و برخی از قابلیتهای دیگر آن میشود، اما در مواردی که نیاز به متوقف کردن ارسال داده به ازای هر کاراکتر داشته باشیم، میتواند مفید فایده باشد.
نکته اول: در برخی از برنامهها میتوان از قبل پیش بینی کرد که بافر FIFO هرگز پر نمیشود و میتوان همه پرچمها را نادیده گرفت. این تکنیک میتواند در صورتی کاربرد داشته باشد که بستههای اطلاعاتی که در FIFO فرستنده نوشته میشوند همیشه کمتر از ۱۶ داده داشته باشند و این بستهها به گونه ای باشند که تضمین شود UART زمان کافی برای انتقال هر بسته داشته باشد به گونهای که FIFO در هنگام نوشته شدن بسته بعدی خالی شده باشد. به عنوان مثال، یک اپلیکیشن میتواند زمان یک شبانه روز را با استفاده از یک رشته متن با فرمت ‘hh:mm:ss’ منتقل کند. این بسته اطلاعات حاوی ۸ کاراکتر است، و در صورت انتقال در فواصل یک ثانیهای کاملاً تضمین میکند که اگر نرخ ارسال بیش از 80bps باشد، بافر هیچگاه کامل پر نخواهد شد.
نکته دوم: در صورت نیاز میتوان با قرار دادن FIFO اضافی بین برنامه اصلی و فرستنده UART عمق بافر FIFO را افزایش داد. به عنوان مثال، یک بافر FIFO که از حافظه بلوکی (BRAM) استفاده میکند، میتواند عمق بافر را به میزان قابل توجهی افزایش دهد. روشکار احتمالاً به این صورت خواهد بود که برنامه اصلی پرچمهای وضعیت FIFO اضافی را بررسی میکند و اطلاعات مربوط را متناسب با آنها در بافر FIFO مینویسد. سپس یک ماشین حالت ساده فرایند خواندن دادهها از این بافر FIFO و نوشتن در بافر FIFO فرستنده UART در صورتی که buffer_full برابر با ‘0’ باشدِ، بصورت اتوماتیک انجام میدهد.
دریافت داده با ماژول گیرنده UART
ماژول گیرنده UART نیز شامل دو بخش گیرنده و بافر FIFO است. پورتهای ورودی و خروجی و دیاگرام کلی این ماژول در شکل زیر نمایش داده شده است.
دادهها بطور خودکار با نرخ باود تعریف شده روی پورت en_16_x_baud دریافت میشوند و سپس در بافر FIFO گیرنده ذخیره میشوند. بافر FIFO کاملاً سنکرون با کلاک است. هنگامی که پرچم “buffer_data_present” با مقدار منطقی یک ‘1’ فعال باشد بیان کننده این است که حداقل یک کاراکتر برای خواندن، در بافر وجود دارد که میتواند از طریق پورت “data_out” خوانده شود. اپلیکیشن گیرنده باید این داده را برداشته و سپس پورت ورودی کنترلی “buffer_read” را برای یک لبه بالا رونده کلاک، یک نگه دارد. در این صورت FIFO گیرنده، داده بعدی را در صورت وجود برای خواندن روی پورت data_out قرار میدهد و یا در صورتی که دادهای برای خواندن وجود نداشته باشد، پرچم buffer_data_present را صفر میکند تا نشان دهد اکنون FIFO خالی است. به این ترتیب بوسیله این سیگنال کنترلی ورودی، و پرچم خروجی امکان خواندن دادهها بصورت یکی یکی یا پشت سرهم امکان پذیر میشود.
در شکل بالا کد اسکی حرف “T” که توسط ماژول گیرنده UART دریافت شده است به طور خودکار در بافر FIFO که قبلاً خالی بوده، ذخیره میشود، مشاهده میکنید که پرچم buffer_data_present از صفر به یک تغییر وضعیت میدهد. پس از چند سیکل کلاک، اپلیکیشن گیرنده به پرچم buffer_data_present پاسخ میدهد و دادههای دریافتی را از FIFO میخواند و سپس با یک کردن ورودی کنترلی buffer_read به اندازه یک پریود کلاک یک بار دیگر خالی شدن بافر را گزارش میکند.
نکته اول: به عبارت دقیق تر سیگنال کنترلی buffer_read میتواند به عنوان سیگنالی با محتوای “من شما را خوانده ام” توصیف شود. برخلاف ورودی کنترلی buffer_write در فرستنده UART که یک دستورالعمل لازم برای نوشتن دادهها در FIFO فرستنده در لبه بالارونده کلاک بود، دادههای آماده روی پورت خروجی data_out گیرنده FIFO میتوانند در هر زمان که معتبر باشند، خوانده بشوند (به عنوان مثال کاراکتر “T” نشان داده شده در شکل فوق میتواند در هر نقطه از منطقه سبز رنگ خوانده شود) و سیگنال buffer_read صرفاً نشانهای است تا به FIFO اطلاع بدهد که اکنون میتواند از آن داده به داده بعدی منتقل شود و یا پرچمهای وضعیت خروجی را به روز کند.
سیگنال buffer_read تنها باید زمانی که پرچم buffer_data_present یک است، فعال شود. این حالت نشان دهنده این است که دادههای معتبری برای خواندن از بافر وجود دارد. شکل زیر خواندن پیاپی هم دادههای “i” ، “m” و “e” را نشان میدهد که در نهایت باعث خالی شدن بافر میشود.
بد نیست کمی دقت کنید و ببینید که چگونه از سیگنال buffer_data_present برای کنترل buffer_read و جلوگیری از خواندن غیرمجاز دادهها استفاده میشود. مقدار دادن به buffer_read هنگامی که buffer_data_present صفر است، به خودی خود منجر به عملکرد نادرست FIFO نمیشود، اما ممکن است که اپلیکیشن گیرنده مقدار پورت data_out را در زمانی که داده خروجی آن صحیح نیست، بخواند و مشکل ایجاد شود. همچنین این خطر وجود دارد که ورودی buffer_read دقیقاً همزمان با لحظهای که کاراکتر دریافت شده از ورودی سریال در بافر FIFO نوشته میشود، فعال شود. در این صورت داده دریافت شده توسط اپلیکیشن از دست میرود و درست خوانده نمیشود.
برای کنترل مناسب خواندن از بافر، علاوه بر پرچم buffer_data_present سیگنالهایی مشابه فرستنده از طرف بافر در اختیار کاربر قرار داده شده است که بسته به کاربرد هر یک از آنها میتوانند، استفاده بشوند.
سیگنال Buffer_full
بافر گیرنده FIFO میتواند حداکثر تا ۱۶ کاراکتر یا داده را در خود جای دهد. طبیعتاً این بدان معنی است که شما میتوانید قبل از اینکه اپلیکیشن نیاز به خواندن هر یک از کاراکترها از درون بافر داشته باشد، ۱۶ کاراکتر از ورودی سریال دریافت کنید. پرچم “buffer_full” به محض دریافت داده شانزدهم از زمان شروع به کار یا آخرین بار خوانده شدن دادهها، فعال خواهد شد (این خروجی هم فعال بالاست). اگر این وضعیت در یک اپلیکیشن در نظر گرفته شود، باید با آن بصورت یک فوریت برخورد شود که حداقل یک داده از بافر را قبل از دریافت داده هفدهم خوانده بشود تا سریز بافر اتفاق نیافتد. به یاد داشته باشید که مدت زمان نسبتاً طولانی (به عنوان مثال ۸۶۸۰ کلاک با نرخ داده سریال ۱۱۵۲۰۰ با استفاده از کلاک ۱۰۰ مگاهرتز) برای دریافت داده بعدی طی میشود، بنابراین یک پاسخ نسبتاً سریع به پرچم buffer_full میتواند از بروز مشکل و از دست رفتن دادهها جلوگیری کند.
برای خواندن دادهها بصورت پشت سر هم نیز این چنین در نظر بگیرید که اگر FIFO پر باشد، برنامه میتواند بدون نیاز به بررسی وضعیت پرچم buffer_data_present، شانزده داده را پشت سرهم بخواند.
سیگنال Buffer_half_full
مشابه پرچم buffer_full، پرچم خروجی “buffer_half_full” نیز یک سیگنال فعال بالا است. هرگاه FIFO گیرنده حاوی حداقل ۸ داده (یا بیشتر) باشد که در صف انتظار خوانده شدن توسط برنامه هستند، این سیگنال فعال میشود. بررسی و استفاده مناسب از این پرچم به چند طریق میتواند ارزشمند باشد:
- اگر از پرچم buffer_half_full برای هشدار دادن به اپلیکیشن و الزام آن به خواندن از بافر استفاده شود، درحالی که هنوز نیمی از بافر خالی است (که برای دریافت ۸ کاراکتر دیگر کافی است) آنگاه اپلیکیشن زمان کافی جهت برنامه ریزی عملیات خواندن بافر در آینده نزدیک، بدون نیاز به متوقف کردن کار فعلی را خواهد داشت. در صورت استفاده از این رویکرد در طراحی، هرگز پرچم buffer_full فعال نمیشود، اما اگر چنین اتفاقی بیافتد، میتوان مطمئن بود که “سرریز” یا “خطای ارتباطی” رخ داده است.
- پرچم buffer_half_full برای پیاده سازی یک شمای کنترل جریان سخت افزاری یا نرم افزاری (XON/XOFF) مناسب است. هنگامی که این پرچم فعال میشود، اپلیکیشن گیرنده میتواند به فرستنده در سر دیگر ارتباط گزارش بدهد که باید کار انتقال متوقف بشود. ناگفته پیداست که با توجه به طبیعت تنظیمات UART تقریباً غیرممکن است که کار انتقال داده را مستقیماً و بلافاصله متوقف کنید، اما از آنجایی که فضای کافی در بافر گیرنده FIFO برای ذخیره ۸ کاراکتر دیگر وجود دارد، برای توقف ارسال قبل از وقوع سرریز، استفاده از پرچم buffer_half_full یک حاشیه امن مناسب فراهم میکند.
- برخی از اپلیکیشنها وظایف مهم تری نسبت به بررسی مدارم داده های درون بافر FIFO دارند و برای آنها آگاهی دائم از این مسأله که آیا چیزی در انتظار خواندن از FIFO گیرنده است یا خیر، چندان مهم نیست. علاوه بر این، مدیریت هر کاراکتر به محض دریافت در شرایطی که سرعت دریافت کاراکترها در مقایسه با سایر کارهای در حال اجرای اپلیکیشن به شکل محسوسی کمتر است، بیشتر از این که کارگشا باشد باعث بروز اختلال در روند اجرای کارهای مهم تر میشود. در این شرایط، پرچم buffer_half_full میتواند به عنوان هشداری برای مطلع کردن اپلیکیشن (مثل وقفه) استفاده شود که حداقل ۸ داده در FIFO در انتظار خواندن هستند. نه تنها این اطلاعات ممکن است برای پردازش کافی باشد بلکه اپلیکیشن میتواند بدون نیاز به بررسی وضعیت پرچم buffer_data_present بین هر عملیات خواندن، تا ۸ کاراکتر را پشت سرهم از بافر بخواند.
سیگنال buffer_reset
قرار گرفتن مقدار یک منطقی ‘1’ روی پورت ورودی “buffer_reset” بطور سنکرون با کلاک، بافر FIFO را در لبه بالا رونده کلاک ریست میکند. به محض اعمال ریست تمام دادهها در بافر از بین میروند و همه پرچمهای وضعیت نیز ریست میشوند. به طور معمول ریست فقط هنگام راه اندازی مجدد سیستم یا در صورت وقوع سرریز بافر (که به هر حال بهتر است از آن جلوگیری شود) مورد نیاز است. توجه داشته باشید که اگر هنگام دریافت داده ریست اعمال شود، در این صورت داده دریافت شده به شرطی که buffer_reset در هنگام دریافت بیت توقف، صفر (غیر فعال) باشد، داده در بافر گیرنده نوشته میشود و در غیر این صورت از دست میرود.
تعیین نرخ ارسال و دریافت داده (BUAD Rate)
مهمترین پارامتری که در ارتباط سریال باید مشخص شود و از ویژگیهای این ارتباط است، تعریف نرخ باود (Buad Rate) است. نرخ باود به تعداد بیتی که طی یک ثانیه ارسال و یا دریافت میشود، اطلاق میگردد. بطور معمول این نرخ برای ارتباط با کامپیوتر مقادیر استانداردی مانند ۹۶۰۰ یا ۱۱۵۲۰۰ کیلو بیت بر ثانیه است. ولی این نرخ میتواند هر مقدار دلخواهی که برای دو طرف لینک ارتباطی تنظیم شده است، باشد. هر بسته داده ۱۰ بیتی شامل یک بیت آغاز و ۸ بیت اطلاعات و یک بیت توقف است. به این ترتیب نرخ ارسال داده اصلی برابر 8/10
نرخ باود است.
بنابراین اگر نیاز به ارسال داده با نرخ مثلاً ۸۰ کیلو بیت بر ثانیه (80Kbps) داشته باشید، باید نرخ باود برای ارتباط UART را برابر 100Kbps تنظیم کنید. همچنین اگر نرخ باود برابر مقدار استاندارد 115200Kbps باشد نرخ ارسال داده اصلی برابر 92160Kbps خواهد بود.
سیگنال en_16_x_baud
این سیگنال یک ورودی به ماژولهای فرستنده و گیرنده است که نرخ باود را با استفاده از پالسهای فعال بالا با سرعت ۱۶ برابر نرخ بیت سریال، تعیین میکند. این پالسها از کلاک سیستم مشتق میشوند که دارای یک فرکانس مشخص است. دو نکته در مورد سیگنال “en_16_x_baud” باید در نظر گرفته شود.
- اول اینکه پالسهای ورودی به این پین سنکرون با کلاک هستند و
- دوم اینکه پهنای پالسهای ورودی به اندازه یک پریود کلاک است. بنابراین نباید با سیگنال کلاک با فرکانس ۱۶ برابر نرخ باود اشتباه شود.
در اکثر مواقع برای تولید سیگنال en_16_x_baud از یک شمارنده ساده استفاده میشود. به این ترتیب که شمارنده با رسیدن به مقداری که از تقسیم فرکانس کلاک بر شانزده برابر نرخ باود بدست میآید، به اندازه یک پریود کلاک، یک پالس فعال بالا تولید میکند. برای درک بهتر، در ادامه این موضوع با یک مثال توضیح داده میشود.
اگر کلاک اصلی FPGA برابر ۱۰۰ مگاهرتز باشد و ارتباط سریال با نرخ باود ۱۱۵۲۰۰ داشته باشیم، نرخ پالسهایی که به en_16_x_baud داده میشود برابر 1843200 = 115200*16
پالس بر ثانیه خواهد بود. با توجه به کلاک ۱۰۰ مگاهرتز به ازای هر 1E8/1843200 = 54.253
دوره تناوب کلاک، یک پالس فعال میشود. با رند نمودن عدد به نزدیکترین عدد صحیح عدد ۵۴ برای شمارنده در نظر گرفته میشود. این رند کردن باعث انحراف کم نرخ باود میشود که باید مقدار انحراف بررسی شود. از تقسیم فرکانس کلاک بر عدد رند شده و سپس تقسیم حاصل بر عدد ۱۶ نرخ باود جدید برابر با ۱۱۵۷۴۰.۷۴۱ بدست می آید، که خطای آن نسبت به حال تئوریک حدوداً 0.5%
میباشد.
(1E8/54)/16 = 115740.741
در حالت کلی در صورتی که نرخ باود خطایی کمتر از 5%
بین فرستنده و گیرنده در دو طرف لینک ارتباطی سریال داشته باشد، در دریافت و ارسال داده اخلالی ایجاد نمیشود. بنابراین میتوان با خطای بدست آمده که کمتر از این مقدار است، مدار تولید نرخ باود را به کمک شمارنده پیاده سازی کرد.
واضح هست که حداکثر نرخ باود قابل دستیابی در یک سیستم با فرکانس کلاک مشخص برابر فرکانس کلاک تقسیم بر ۱۶ است. پس در مثال بالا حداکثر نرخ باود برابر است با
100,000,000/16 = 6,250,000 bps
در بیشتر مواقع استفاده از روش بالا برای ساخت پالسهای باود و ایجاد ارتباط مطمئن کفایت میکند و خطای زیر پنج درصد در نرخ باود باعث دریافت اطلاعات بدون خطا خواهد میشود. اما باید در نظر گرفت که این خطا برای هر دو طرف لینک است و در نظر گرفتن تمام آن برای یک طرف و ایدهآل در نظر گرفتن طرف دیگر منطقی نیست. بنابراین حد مجاز برای خطای نرخ باود برای هر طرف باید کمتر از 2.5%
در نظر گرفته شود. حالا باید در نظر گرفت، زمانی که نرخ باود نسبت به کلاک بالا برود خطای باود نیز به مقدار قابل توجهی افزایش مییاید. مگر در حالتی که نسبت نرخ باود به کلاک عدد صحیحی باشد. به عنوان مثال همانطور که در بالا مشاهده شد با کلاک ۱۰۰ مگاهرتز برای ساختن باود ۱۱۵۲۰۰ با خطای حدود 0.5%
مواجه بودیم ولی اگر کلاک برابر ۲۵ مگاهرتز باشد، محاسبات ما بصورت زیر خواهد بود:
16*115200 =1843200
25000000/1843200 = 13.56 => rounding ≈ 14
(25E6/14)/16 = 111607.14
خطای حاصل حدوداً 3.1%
میشود، که مسلماً از حاشیه امن برای یک ارتباط بدون خطا تجاوز نموده است. برای حل این مشکل یک راه کاهش نرخ باود یا افزایش فرکانس کلاک است که در بسیاری از مواقع این امکان وجود ندارد. راه دیگر تنظیم شمارنده بصورتی است که مقدار غیر صحیح را بطور تقریبی معادل سازی کند. بر اساس محاسباتی که انجام دادیم، در حالت ایده آل نیاز داریم در هر ۱۳.۵۶ پریود کلاک یک پالس سیگنال en_16_x_baud فعال شود. اما برای پیاده سازی تقریبی باید از میانگین تقسیم فرکانس بر مقادیر مختلف استفاده شود. در این مورد با جابجایی شمارنده تقسیم کلاک بین مقادیر ۱۳ و ۱۴ به میانگین ۱۳.۵ میرسیم که به مقدار مطلوب نزدیک است. بنابراین در هر ۲۷ پریود کلاک دو پالس تولید میشود یکی در مقدار ۱۳ و دیگری در مقدار ۲۷=۱۴+۱۳ سپس شمارنده مجدداً ریست میشود.
در این حالت نرخ باود برابر با ۱۱۵۷۴۱ بیت بر ثانیه خواهد بود.
(25E6*(2/27))/16 = 115741
این مقدار از نرخ مطلوب ۱۱۵۲۰۰ به اندازه ۵۴۱ بیت بر ثانیه سریعتر است. با این تکنیک خطای حاصل کمتر از 0.5%
میشود. به این نکته نیز اشاره شود که مقادیر شمارنده در شکل بدلیل شروع از صفر شمارنده یک واحد کمتر است.
جمع بندی
در این آموزش طولانی بهترین پیاده سازی ماژول UART در FPGA را که توسط مهندسان Xilinx انجام پذیرفته است، بررسی کردیم. پروتکل UART یکی از پروتکلهای قدیمی اما در عین حال، پرکاربرد در صنعت است. از این پروتکل میتوان برای انتقال اطلاعات با سرعت کم و در فواصل متوسط استفاده کرد. پروتکل UART یکی از ساده ترین روشهایی است که میتواند برای برقراری ارتباط بین FPGA با دنیای خارج از آن مورد استفاده قرار بگیرد. با استفاده از آن به سادگی امکان ارسال یک فرمان از کامپیوتر به FPGA و بالعکس فراهم میشود. با توجه به سادگی این پروتکل و عملکرد مناسب آن از لحاظ انتقال سالم اطلاعات، هنوز هم در بسیاری از کاربردهای صنعتی از پروتکل RS232 استفاده میشود.
منبع: github
7 در مورد “پیاده سازی ماژول UART در FPGA”
بله. اشتباه از من بود. کد به درستی کار می کند.
فقط ی سوالی داشتم. توی این کد پورت هایی که به وسیله آن ها بتوانیم داده ها را در فرستنده یوآرت بفرستیم وجود ندارد؟ خود دیتایی را که میخواهیم بفرستیم میگیرد اما چجوریه که پورتی براش مشخص نشده که کجا دیتا را بفرستیم؟
هییچی درست شد.
سلام و روز بخیر خدمت شما، خوشحالم که در نهایت موفق شدید.
سلام. کد تجمیع فرستنده و گیرنده را خودتان نوشته اید یا مال میکروبلیزهست؟
درود بر شما مجتبی عزیز امیدوارم سرحال و پر انرژی باشید.
کدها توسط همکاران در مجموعه هگزالینکس نوشته شده است و به میکروبلیز و پیکوبلیز (حدس میزنم منظور شما پیکوبلیز بوده) وابسته نیست.
سلام. کدی که برای تجمیع گیرنده و فرستنده قرار دادید خطا داره.
سلام آقا مجتبی عزیز
کدها در محیط توسعه ISE و Vivado روی تراشههای Spartan و Artix7 تست شدهاند. لطفاً پروژه و تراشه مورد استفاده را مجدداً بررسی کنید.
البته میتوانید خودتان یک کد تجمیع مطابق با سلیقه خودتان طراحی و استفاده کنید.