آموزش ESP32-IDF قسمت سوم

آموزش ESP32-IDF قسمت سوم -پورت سریال
سلام به آموزش ESP32-IDF قسمت سوم خوش آمدید.
در قسمت سوم این آموزش ESP32-IDF، پورت های ارتباطی ESP32 و به طور خاص پورت سریال را بررسی می کنیم. در مرحله اول ، ما در مورد برخی از کتابخانه های مهم درایور UART که در ESP-IDF ارائه شده است ، بحث خواهیم کرد و سپس پروژه های نمونه را برای انتقال و دریافت داده ها از طریق UART بررسی میکنیم. با استفاده از چند مثال، نحوه پیکربندی تنظیمات UART و نصب درایور UART برای خواندن/نوشتن با استفاده از رابط پورت سریال را خواهیم آموخت. از پورت سریال (UART) همچنین برای اشکال زدایی و برنامه ریزی ESP32 با استفاده از پورت USB نیز می توان استفاده کرد.
پورت سریال :
ارتباط UART که به نام انتقال دریافت ناهمزمان جهانی یا ارتباط سریال نیز شناخته می شود یکی از ساده ترین و رایج ترین پروتکل های ارتباطی است که بین دو دستگاه استفاده می شود. انتقال داده بین دو دستگاه از طریق خطوط انتقال و دریافت که به دو دستگاه متصل هستند، می باشد. داده ها به صورت بیت از یک دستگاه به دستگاه دیگر، بیت به بیت منتقل می شوند. یکی از مزایای کلیدی استفاده از این پروتکل ارتباطی این است که دو دستگاهی که در حال ارتباط هستند نیازی به یک کلاک مشترک ندارند. با این حال، لازم است نرخ باودریت یکسانی برای هر دو دستگاه تنظیم شود تا دستورالعمل ها برای آنها قابل درک باشد.
ماژولهای توسعه و پردازنده های ESP32 شامل سه پورت UART است که معمولا به نامهای UART0، UART1 و UART2 شناخته میشوند. این سه پورت سریال با سطح ولتاژ TTL 3.3V کار می کنند. هر یک از واحد های پورت سریال دارای 4 پین هستند: RX، TX، RTS و CTS.
توابع پورت سریال ESP32 :
خب حالا وقتشه که کتابخانه درایور UART و عملکردهای آن را که توسط ESP-IDF برای برقراری ارتباط بین دستگاههای UART مانند ESP32 و مبدل USB-TTL ارائه شده است، مورد بحث قرار دهیم. ESP-IDF کتابخانه driver/uart.h را که برای ارتباط UART مورد نیاز است برای شما فراهم کرده است. در ادامه مراحل استفاده از API ارائه شده توسط این کتابخانه را برای راه اندازی موفقیت آمیز یک ارتباط UART به شما نشان خواهم داد. اولین کاری که برای داشتن یک ارتباط سریال لازم است اضافه کرد هدر فایل ارتباط UART است:
اگر با پورت سریال در پردازنده های دیگر کار کرده باشید میدانید که برای داشتن یک ارتباط موفق باید یکسری تنظیمات بر روی پورت سریال خود اعمال کنیم.این تنظیمات باید برای هر دو سمت ارتباط یکسان باشد.
این تنظیمات شامل :
- باودریت
- بیتهای دیتا
- بیت کنترل Parity
- تعداد بیتهای توقف
- مد کنترل جریان داده
- مد ارتباط
به طور کلی دو روش برای تنظیم این پارامترهای ارتباطی وجود دارد.
در روش اول که بیشتر مورد استفاده قرار می گیرد ، ما تمام پارامترها را در یک مرحله واحد پیکربندی می کنیم. این کار ابتدا با ایجاد یک ساختار از نوع uart_config_t انجام می شود که شامل تمام تعاریف پارامترهای مورد نیاز است. و سپس فراخوانی تابع ()UART_PARAM_CONFIG که دو متغیر را به عنوان ورودی دریافت میکند.
پارامتر اول شماره پورت UART مورد استفاده و پارامتر دو استراکت تنظیم شده که محتوی تنظیمات مورد نیاز پورت سریال میباشد.
روش دوم شامل فراخوانی توابع جداگانه برای پیکربندی هر یک از پارامترها است. جدول زیر پارامترها و توابع پیکربندی مربوط به آنها را نشان می دهد.
uart_set_baudrate(uart_port_t uart_num, uint32_t baudrate)
از این تابع برای تنظیم باودریت پورت سریال استفاده میشود. که دارای دو ورودی میباشد ورودی اول شماره پورت سریال که میخواهیم تنظیمات روی آن اعمال شود و پارامتر دو باودریت موردنظر.
uart_set_word_length(uart_port_t uart_num, uart_word_length_t data_bit)
این تابع برای تنظیم طول کاراکتر ارسالی توسط پورت سریال استفاده میشود.که دو پارامتر به عنوان ورودی دریافت میکند پارامتر اول مثل حالت قبل شماره پورت سریال مورد استفاده و پارامتر دو تعداد بیتهای داده ارسالی پورت سریال.
uart_set_parity(uart_port_t uart_num, uart_parity_t parity_mode)
تابع uart_set_parity برای اعمال تنظیمات حالت بیت parity بر روی پورت سریال مورد استفاده قرار میگیرد.شماره پورت سریال و مد مورد نظر پارامترهای ورودی این تابع هستند. این توابع پس از اجرا در صورت موفق بودن یا ناموفق بودم مقادیر ESP_OK و ESP_FAIL را برمیگردانند که میتوان از آنها برای چک کردن صحت اجرای تنظیمات استفاده نمود.
uart_set_stop_bits(uart_port_t uart_num, uart_stop_bits_t stop_bits)
تابع بعدی که نیاز داریم با اون آشنا بشیم تابع ست کردن تعداد بیتهای توقف میباشد.پارامتر اصلی این تابع پارامتر دوم میباشد که بیتهای توقف رو تنظیم میکند.
uart_set_hw_flow_ctrl(uart_port_t uart_num, uart_hw_flowcontrol_t flow_ctrl, uint8_t rx_thresh)
()set_hw_flow_ctrl تابعی است که برای تنظیم کنترل جریان داده در پورت سریال استفاده میشود.ساده ترین استفاده آن تنظیم پینهای RTS و CTS و نحوه رفتار آنهاست.
uart_set_mode(uart_port_t uart_num, uart_mode_t mode)
تابع آخر که در این بخش بررسی میکنیم وظیفه تنظیم عملکرد پورت سریال را دارا میباشد.پارامتر اول شماره پورت سریال و پارامتر دو مد عملکرد پورت سریال است.پارامتر دوم میتواند مدهای پورت سریال که شامل UART, RS485,IRDA و غیره است را تنظیم کند.
تنظیم پین های ارتباطی :
در این مرحله، نیاز است پینهای فیزیکی را که میخواهیم برای ارتباط از طریق UART از آنها استفاده کنیم را تعین نماییم.
تابع ()UART_SET_PIN برای تنظیم پین های TX ، RX ، RTS و CTS که ما به عنوان پارامتر آنها را به درون تابع میفرستیم، فراخوانی می شود.
این تابع دارای پنج پارامتر است. اولین پارامتر شماره پورت سریال است. پارامتر دوم پین GPIO است که می خواهیم به عنوان پین TX تنظیم کنیم. پارامتر سوم پین GPIO است که می خواهیم به عنوان پین RX تنظیم کنیم. به همین ترتیب ، پارامتر چهارم پین GPIO است که می خواهیم به عنوان پین RTS تنظیم کنیم. سرانجام ، آخرین پارامتر پین GPIO است که آنرا به عنوان پین CTS تنظیم می کنیم. کتابخانه پورت سریال پین های GPIO را که ما به سیگنال های مربوط به TX ، RX ، RTS و CTS مربوط می کنیم ، مسیریابی و تنظیمات لازم را اعمال می کند.
در تابع زیر، GPIO4 را به عنوان TX و GPIO5 را به عنوان RX پین GPIO18 را به عنوان RTS و GPIO19 را به عنوان CTS برای UART2 تنظیم می کنیم.
نصب درایور پورت سریال:
در مرحله بعد، منابع ESP32 را برای درایور UART اختصاص می دهیم. این مرحله نشان دهنده تکمیل پیکربندی UART است. ما از تابع ()uart_driver_install برای اختصاص منابع داخلی لازم برای درایور UART استفاده می کنیم. برای این کار نیاز است که پنج متغیر ورودی این تابع را مقدار دهی نماییم. اولین پارامتر شماره پورت UART است. بقیه پارامترها به ترتیب در زیر فهرست شده اند:
rx_buffer_size
این پارامتر سایز بافر دریافت پورت سریال را تعیین میکند.
tx_buffer_size
با استفاده از این متغیر سایز بافر ارسال پورت سریال ESP32 را مشخص میکنیم.
queue_size
توسط این متغیر می توانیم عمق یا سایز صف رویدادهای واحد UART را تعیین نماییم.
uart_queue
پارامتر چهارم درواقع یک متغیر از نوع صف میباشد که رویدادهای مربوط به پورت سریال در آن صف شده و میتوان برای آگاهی از رویدادهای اتفاق افتاده از آن استفاده کرد.اگر در این متغیر مقدار NULL قرار دهید درایور پورت سریال از صف استفاده نخواهد کرد.
intr_alloc_flags
با استفاده از این متغییر میتوان وقفه های مربوط به پورت سریال را فعال کرد.
خب بیاید بررسی کنیم که چطور باید از این تابع استفاده نمود.
در این مثال ما میخواهیم از صف و بافر برای ارسال و دریافت داده های و مدیریت رویدادها استفاده کنیم.
سپس تابع نصب درایور را فراخوانی کرده و باقی پارامترها را نیز به شکل زیر مقدار دهی میکنیم.
ارسال و دریافت داده :
این مرحله نشان دهنده شروع عملیات UART است. کنترل کننده UART دارای یک ماشین حالت محدود است که معمولاً به عنوان FSM شناخته می شود که ارتباطات UART را کنترل می کند. FSM از تمام فرآیندهایی که در آن برنامه فقط داده ها را در یک بافر خاص می خواند و می نویسد مراقبت می کند. بیایید هر یک از این مراحل را با هم بررسی کنیم.
ارسال داده :
برای ارسال سریال، ابتدا داده ها را در بافر TX FIFO می نویسیم. سپس FSM این داده ها را سریالیزه کرده و آن را ارسال می کند. ما از تابع ()uart_write_bytes برای نوشتن داده ها در بافر حلقه TX استفاده می کنیم(بافرهای پورت سریال در ESP-IDF از نوع Ring buffer هستند). هنگامی که فضای کافی در بافر TX FIFO موجود باشد، این داده ها از بافر حلقه به بافر FIFO منتقل می شوند. این فرآیند در پشت صحنه از طریق روتین سرویس وقفه رخ می دهد و کاربر دخالتی در آن ندارد.
تابع ()uart_write_bytes دارای سه پارامتر است. اولین پارامتر شماره پورت UART است. پارامتر دوم آدرس بافر داده و پارامتر سوم طول داده ای است که باید ارسال شود.
در زیر می توانید کدی را مشاهده کنید که نحوه استفاده از این تابع برای انتقال رشته “Welcome to dspworld.ir” را نشان می دهد.
دریافت داده:
به همین ترتیب، برای دریافت داده ها به صورت سریال، FSM ابتدا داده های دریافتی را پردازش کرده و آنها را موازی می کند. سپس داده ها را در بافر RX FIFO می نویسد. در نهایت، این داده ها از بافر RX FIFO خوانده می شود. برای خواندن دادههای موجود در بافر RX FIFO، از تابع ()uart_read_bytes استفاده میکنیم. این تابع دارای چهار پارامتر است. اولین پارامتر شماره پورت UART است. پارامتر دوم اشاره گر به بافر است. سومین پارامتر طول داده و آخرین پارامتر مقدار تام اوت در واحد تیک های RTOS است.
مقدار برگشتی این تابع اگر 1- باشد نشان دهنده خطا و اگر مقداری بزرگتر از صفر باشد نشان دهنده تعداد داده ورودی به TX FIFO میباشد.
بررسی مثال UART Echo :
خب بیاید با هم دیگه چندتا مثال رو بررسی کنیم.
در مثال اول، پروژه ای را با استفاده از درایور UART که قبلا توضیح داده شد، می سازیم و تست می کنیم. برای اینمنظور از مثال UART Echo ارائه شده توسط کتابخانه ESP-IDF استفاده خواهیم کرد. از طریق این مثال به شما نشان خواهم داد که چگونه از پورت سریال استفاده کنید.
در این مثال ساده ما میخواهیم داده های دریافتی از پورت سریال را مجددا برای فرستنده ارسال نماییم.
ادیتور VsCode را باز کنید و به مسیر View > Command Palette بروید.
ESP-IDF: New Project را در نوار جستجو تایپ کرده و enter را فشار دهید.
نام پروژه و دایرکتوری را مشخص کنید. من نام پروژه خودم را «ESP_IDF_UART_ECHO» گذاشتهام شما میتوانید هر نام دلخواهی را انتخاب نمایید. برای انتخاب برد من ESP32_S3 chip (via ESP USB Bridge)، را انتخاب کردم شما این بخش را براساس برد یا ماژول خود انتخاب نمایید.
برای ادامه کار روی دکمه « Choose Template» کلیک کنید.
در بخش Extension گزینه ESP-IDF را انتخاب نمایید.
از ستون سمت راست و در قسمت uart بر روی uart_echo کلیک کرده و در نهایت بر روی “Create project using template uart_echo”
کلیک میکنیم.
یک اعلان مبنی بر ایجاد پروژه دریافت خواهید کرد. برای باز کردن پروژه در یک پنجره جدید، روی “بله” کلیک کنید.
پروژه ESP_IDF_UART_ECHO که ایجاد کردهایم، در داخل برگه EXPLORER در سمت چپ IDE باز میشود. چندین پوشه در داخل پوشه پروژه ما وجود دارد. این ساختار برای هر پروژه ای که از طریق ESP-IDF Explorer ایجاد می کنید یکسان است و اگر شما تصمیم دارید از پایه خودتان پروژه را ایجاد کنید بهتر است مطابق همین ساختار باشد.
ویرایش تنظیمات SDK ESP-IDF :
خب ابتدا به بخش تنظیمات IDF میرویم. برای این منظور روی نماد نشان داده شده در زیر کلیک کنید. ESP-IDF SDK Configuration Editor باز میشود.
به پایین بروید و Echo Example Configuration را پیدا کنید. در این بخش می توانید پارامتر پیکربندی UART را بر اساس نیاز خود تنظیم کنید. این تنظیمات شامل شماره پورت UART، سرعت ارتباط UART (باودریت)، پین UART RX، پایه UART TX و اندازه پشته برای تسک ها است.
می ببینید که به طور پیش فرض، ESP-IDF از پورت UART شماره 2 (UART2)، نرخ باودریت 115200، پین RX به عنوان GPIO5، پین TX به عنوان GPIO4 و اندازه پشته تسک به مقدار 2048 استفاده می کند.
می توانید این پارامترها را تغییر دهید و سپس بر روی دکمه ذخیره در بالا کلیک نمایید.ما در این مثال تنظیمات پیکربندی را به صورت پیش فرض رها میکنیم.
بررسی کد:
خب حالا وقتشه که کد رو باهم بررسی کنیم.
به پوشه main که حاوی سورس کد برنامه است می رویم و فایل uart_echo_example_main.c را باز میکنیم.
کد زیر باز می شود که در زیر مشاهده میکنید. این کد مثال UART Echo را نشان می دهد.
در این مثال قرار است پیام دریافتی در UART2 به فرستنده باز گردانده شود.
نحوه عمکرد کد :
در مرحله اول ، ما باید کتابخانه های مورد نیازمان را در برنامه وارد کنید.
کتابخانه های Freertos برای تولید تاخیر و ایجاد تسکها، کتابخانه Driver/gpio.h برای تعین پایه های ورودی و اختصاص آنها به پورت سریال ،و کتابخانه driver/uart.h برای پیکربندی و استفاده از پورت سریال در برنامه.
سپس چندین متغیر از جمله پینهای RX، TX، RTS و CTS، نرخ باودریت، شماره پورت UART و اندازه پشته تسک را تعریف میکنیم.
این متغیرها به مقادیری که قبلاً در menuconfig تنظیم کرده بودیم دسترسی خواهند داشت تا مقادیر از قبل تعریف شده را بارگذاری نمایند.
علاوه بر این، اندازه بافر را در این کد تعریف خواهیم کرد. 1024 بایت یا 1 کیلوبایت است.
تابع ()echo_task :
در مرحله بعد تابع () echo_task را داریم. در داخل این تابع یک ساختار از نوع uart_config_t داریم که وظیفه آن نگهداری تمام پارامترهای لازم برای ارتباط UART است.
این ساختار زمانی که پارامترهای ارتباطی UART را پیکربندی میکنیم، به عنوان یک پارامتر در تابع ()uart_param_confg ارسال میشود. پیکربندی مورد استفاده 8 بیت داده، بدون بیت توازن و 1 بیت توقف (8-N-1) با نرخ باودریت 115200 است. علاوه بر این، ساعت منبع UART روی APB تنظیم شده است.
نصب درایور :
در این قسمت تابع ()uart_driver_install را برای نصب درایور UART فراخوانی می کنیم. این تابع نیاز به شش پارامتر به شرح زیر دارد:
uart_port_t
این پارامتر شماره پورت UART است که در menuconfig تنظیم کردیم. چون من از تنظیمات پیش فرض استفاده می کنم، بنابراین مقدار این پارامتر UART2 است.
int rx_buffer_size
پارامتر دوم اندازه بافر است که من مقدار آنرا ۱۰۲۴ بایت ضرب در ۲ تعریف کردم.
int tx_buffer_size
متغیر بعدی اندازه بافر TX است که در مثال ما روی 0 تنظیم شده است. این بدان معنی است که تا زمانی که تمام داده ها ارسال نشود، درایور از این بافر استفاده نخواهد کرد.
int queue_size
پارامتر بعدی اندازه صف رویداد UART است که روی 0 تنظیم شده است زیرا در این مثال از هیچ صفی استفاده نمی شود.
QueueHandle_t *uart_queue
اینجا باید هندلر صف رویداد UART را مشخص بکنیم و چون در این مثال هیچ صفی وجود ندارد من مقدار، NULL را وارد میکنم.
int intr_alloc_flags
و آخرین پارامتر پرچمی است که به یک وقفه اختصاص داده شده است. و از آنجایی که در این مثال از وقفه استفاده نمی کنم، بنابراین به صورت NULL تنظیم شده است.
تنظیم پارامترهای UART :
در مرحله بعد ، ما با استفاده از فراخوانی تابع ()UART_PARAM_CONFIG پارامترهای پیکربندی UART را اعمال می کنیم. این تابع نیاز به دو پارامتر ورودی دارد. پارامتر اول شماره پورت UART و پارامتر دوم تنظیمات پارامتر UART است که در تابع ()echo_task با استفاده از یک ساختار مقدار دهی نمودیم.
تنظیم پین های ارتباطی:
برای تنظیم پین های ارتباطی UART (TX، RX، RTS و CTS)، از تابع ()uart_set_pin استفاده می کنیم و GPIO ها را به عنوان پارامترهای ورودی آن تعریف می کنیم.
در مثال ما، درایور سیگنالهای TX، RX، RTS و CTS را به ترتیب به پینهای GPIO مربوطه که ما تنظیم کردهایم اختصاص میدهد.
اکنون با تخصیص حافظه پویا یک بافر موقت برای داده های دریافتی ایجاد خواهیم کرد.
اجرای ارسال و دریافت با پورت سریال :
در داخل حلقه while، با فراخوانی تابع ()uart_read_bytes، دادهها را از UART2 میخوانیم و مقدار بازگشتی تابع را همانطور که قبلا گفتم برابر با تعداد داده های دریافتیست در متغیر از نوع int با نام len ذخیره میکنیم.
سپس دادهها را که در بافر موقت data ذخیره شده است و مقدار آن برابر با len میباشد را با استفاده از () uart_write_bytes مجددا در UART مینویسیم و ارسال میکنیم.
بررسی تابع ()app_main :
در داخل تابع main() تسک را ایجاد می کنیم. برای ایجاد یک تسک، از تابع ()xTaskCreate استفاده کنید. این تابع چندین آرگومان می گیرد.
-
- اولین آرگومان نام تابع است. در مثال ما، نام آنرا echo_task قرار دادیم.
- آرگومان دوم نام وظیفه برای اهداف اشکال زدایی میباشد که هر نامی میتواند داشه باشد. در این مثال ما، آرگومان دوم را به عنوان “uart_echo_task” تعریف کردیم.
- آرگومان سوم اندازه پشته تسک را مشخص می کند. این پارامتر نشان دهنده مقدار حافظه ای است که می خواهیم برای یک تسک خاص ذخیره کنیم. من اندازه پشته را روی “2048” تنظیم کرده ام که مقدار پیش فرض است.
- آرگومان چهارم پارامترهای ورودی به تسک میباشد. این اشاره گر به عنوان پارامتر به تسک ایجاد شده ارسال می شود. با استفاده از آن می توان مقادیر و متغیرهای دلخواه را به تسک فرستاد. در مثال ما، این آرگومان را به صورت NULL تنظیم می کنیم که نشان می دهد ما از این ویژگی استفاده نمی کنیم.
- پارامتر پنجم اولویت تسک است. ما آن را روی 10 تنظیم کرده ایم.
- آخرین آرگومان یک هندلر است که برای تغییر عملکرد تسک استفاده می شود.به عنوان مثال تعلیق، حذف، از سرگیری، دریافت یا تنظیم یک پیکربندی از تسک. این پارامتر از نوع اشاره گر است بنابراین نماد آمپرساند همراه آن استفاده می شود. این پارامتر اختیاری است بنابراین می توانیم آن را روی NULL نیز تنظیم کنیم. در مثال ما هم آن را روی NULL تنظیم می کنیم.
