در دنیای دیجیتال امروزی، فناوری USB (Universal Serial Bus) به یکی از اساسیترین استانداردهای ارتباطی تبدیل شده است. تصور دنیای فناوری بدون USB تقریباً غیرممکن است – از شارژ گوشیهای هوشمند گرفته تا اتصال پرینترها، صفحهکلیدها، حافظههای جانبی و هزاران دستگاه دیگر استفاده شده است.
قبل از ظهور USB در اواسط دهه ۱۹۹۰ کاربران با انواع مختلف پورتها مواجه بودند:
هر یک از این پورتها محدودیتهای خاص خود از جمله نصب درایورهای پیچیده، سرعت پایین، عدم قابلیت اتصال گرم (Hot-Plug) و سردرگمی کاربران را داشتند که به همین دلیل در سال ۱۹۹۶، گروهی از شرکتهای پیشرو در صنعت فناوری شامل اینتل، مایکروسافت، آیبیام و کامپک استاندارد USB 1.0 را معرفی کردند. اهداف اصلی این استاندارد عبارت بودند از:
| نسخه | سال معرفی | سرعت | ویژگیها |
|---|---|---|---|
| USB 1.0 | 1996 | 1.5 Mbps (Low-Speed) | اولین نسخه |
| USB 1.1 | 1998 | 12 Mbps (Full-Speed) | رفع اشکالات نسخه 1.0 |
| USB 2.0 | 2000 | 480 Mbps (High-Speed) | سازگاری معکوس |
| USB 3.0 | 2008 | 5 Gbps (SuperSpeed) | افزایش پهنای باند |
| USB 3.1 | 2013 | 10 Gbps (SuperSpeed+) | بهبود سرعت |
| USB 3.2 | 2017 | 20 Gbps | چندلاینی |
| USB 4 | 2019 | 40 Gbps | مبتنی بر Thunderbolt 3 |
| نقش | وظایف اصلی | نمونههای متداول |
|---|---|---|
| USB Host (میزبان) | مدیریت باس USB- کنترل تایمینگ و همگامسازی- تأمین توان الکتریکی- شناسایی و پیکربندی دستگاهها | کامپیوتر، لپتاپ، سرور |
| USB Device (دستگاه) | دریافت توان و فرمان از Host- اجرای عملکرد خاص (ذخیرهسازی، ورودی، چندرسانهای) | فلش، ماوس، کیبورد، وبکم |
| USB OTG (دوحالته) | ایفای نقش Host یا Device بسته به شرایط | گوشی هوشمند، تبلت |
زمانیکه هر دستگاه USB (مثلاً فلش، مودم، یا موس) به یک میزبان (Host) مثل کامپیوتر یا میکروکنترلر وصل میشود، یک مراحل مختلفی انجام میشود تا آن دستگاه توسط سیستم شناسایی شده و آمادهی استفاده شود که در ادامه شرح داده شده.
هدف این مرحله تشخیص حضور دستگاه جدید و شروع ارتباط است که توسط کنترلر USB انجام میشود. کنترلر USB به طور مداوم وضعیت ولتاژ خطوط داده (D+ و D−) را بررسی میکند و در صورتی که تغییری در سطح ولتاژ مشاهده شود، میزبان متوجه میشود که یک دستگاه جدید به پورت متصل شده است و در همین لحظه نوع سرعت دستگاه (Low Speed، Full Speed، High Speed یا SuperSpeed) نیز از روی خطوط تشخیص داده میشود.
در استانداردی که میکروکنترلرها از آن پشتیبانی میکنند تشخیص سرعت با بررسی سطح ولتاژ خطوط داده انجام میگیرد. در استاندارد USB3 و بالاتر تشخیص سرعت از طریق سیگنالهای اضافی و ارتباط out-of- band انجام میشود.
هدف این مرحله، قرار دادن دستگاه در وضعیت اولیه و همزمانسازی آن با میزبان است. پس از تشخیص اتصال، میزبان یک سیگنال ریست روی خطوط داده ارسال میکند و دستگاه با دریافت این سیگنال، تمام تنظیمات موقت خود را پاک کرده و به حالت پیشفرض بازمیگردد. در این حالت هنوز هیچ آدرس مشخصی به دستگاه داده نشده و آدرس آن صفر است. این سیگنال برای مدت حداقل 10 میلیثانیه خطوط داده را در حالت LOW نگه میدارد.
هدف این مرحله ایجاد یک شناسه (ID) یکتا برای هر دستگاه متصل است تا میزبان بتواند چندین دستگاه را همزمان مدیریت کند. میزبان از طریق دستور استاندارد SET_ADDRESS یک عدد بین ۱ تا ۱۲۷ را به دستگاه اختصاص میدهد و از این لحظه، تمام دستورات و ارتباطات بین میزبان و آن دستگاه با استفاده از همین آدرس انجام میشود. تا قبل از اختصاص این آدرس جدید، عدد صفر به عنوان آدرس برای تبادل اطلاعات استفاده میشود.
هدف این مرحله، جمعآوری اطلاعات توصیفی دستگاه برای شناخت نوع، قابلیتها و نیازهای آن است. میزبان با ارسال درخواستهای استاندارد، مجموعهای از دادهها به نام Descriptor از دستگاه دریافت میکند.
این اطلاعات شامل موارد زیر است:
Device Descriptor: شامل شناسه سازنده (Vendor ID)، شناسه محصول (Product ID)، نسخه USB و کلاس دستگاه.
Configuration Descriptor: مشخصات پیکربندیها، تعداد رابطها (Interfaces) و مسیرهای داده (Endpoints).
String Descriptor: اطلاعات متنی مانند نام سازنده، شماره سریال و توضیحات دستگاه.
با دریافت این دادهها، میزبان تشخیص میدهد که دستگاه از چه نوعی است (مثلاً ذخیرهسازی، شبکه، صوت، یا ورودی).
هدف این مرحله، فعالسازی نرمافزار کنترلی (درایور) برای برقراری ارتباط سطح بالا با دستگاه است. سیستمعامل یا نرمافزار میزبان با بررسی Vendor ID و Product ID دستگاه، نوع آن را شناسایی کرده و درایور سازگار را پیدا میکند. درایور واسطی است که دادههای سطح کاربر (User Space) را به دستورات سطح سختافزار (Kernel Space) تبدیل میکند. پس از بارگذاری درایور، میزبان قادر است بهصورت کامل با دستگاه کار کند.
در این مرحله میزبان درخواستهای کلاسمحور (Class-specific) ارسال میکند به این صورت که اگر دستگاه CDC (مثل مودم یا Ethernet over USB) باشد میزبان دستورات کنترل شبکه ارسال میکند و اگر دستگاه Mass Storage باشد میزبان دستورات SCSI over Bulk ارسال میکند.
هدف این مرحله، قرار دادن دستگاه در حالت عملیاتی و فعالسازی مسیرهای داده است. میزبان با ارسال دستور SET_CONFIGURATION دستگاه را از حالت آمادهبهکار به حالت فعال منتقل میکند. میزبان یکی از پیکربندیهای موجود در Descriptor را انتخاب کرده و دستور SET_CONFIGURATION میفرستد سپس دستگاه مقدار Configuration ID را در رجیستر خود ذخیره کرده و Endpointهای مربوطه را فعال میکند. در این وضعیت، Endpointها (مسیرهای ارتباطی داده) فعال میشوند و دستگاه آماده ارسال و دریافت داده است. از این نقطه به بعد، دستگاه بهطور کامل قابل استفاده توسط کاربر یا نرمافزار است.
در ارتباط USB، Host (میزبان) همیشه کنترل انتقال داده را در دست دارد. برای نظم دادن به ارسال داده بین چندین دستگاه، زمان به واحدهایی به نام Frame تقسیم میشود. در USB 1.1 و USB 2.0، هر Frame معادل ۱ میلیثانیه (1 ms) است. در USB 2.0 (High Speed)، این فریم به واحدهای کوچکتر به نام Microframe با طول ۱۲۵ میکروثانیه (125 µs) تقسیم میشود. هر Frame شامل یک سری Packet (بستههای داده) است.
میزبان (Host) در ابتدای هر Frame، یک بستهی خاص به نام Start of Frame (SOF) میفرستد که بقیه دستگاهها را همگامسازی میکند.
ساختار این بسته تقریباً چنین است:
| فیلد | اندازه (bit) | توضیح |
| Sync | 8 بیت | الگوی همگامسازی فریم |
| PID | 8 بیت | شناسه بسته (برای SOF مقدار خاص دارد) |
| Frame Number | 11 بیت | شماره فریم (از 0 تا 2047، سپس دوباره 0 میشود) |
| CRC5 | 5 بیت | کد بررسی خطا (برای اطمینان از صحت شماره فریم) |
| EOP | — | علامت پایان بسته (End Of Packet) |
هر ۱ میلیثانیه، میزبان یک بستهی SOF میفرستد تا دستگاهها بدانند که در چه فریم زمانی قرار داریم.
هر نوع انتقال در USB (مثلاً Control, Bulk, Interrupt, یا Isochronous) درون این فریمها زمانبندی میشود:
| نوع انتقال داده | وابستگی به فریم | Endpoint | جهت | توضیح |
| Control Transfer | نهچندان دقیق | EP0 | IN/OUT | برای تنظیمات (مثل enumeration) |
| Interrupt Transfer | در فریمهای خاص | EP1 | IN | میزبان بهصورت دورهای داده را درخواست میکند |
| Bulk Transfer | آزاد | EP2 | OUT | فقط وقتی فضا خالی در فریم باشد |
| Isochronous Transfer | دقیقاً در هر فریم | EP3 | IN | برای صدا/تصویر؛ نیاز به زمانبندی ثابت دارد |
| EOP | — | — | — | علامت پایان بسته (End Of Packet) |
تمام دادههایی که بین میزبان (Host) و دستگاه (Device) ردوبدل میشود، در قالب یکی از این ۴ نوع انتقال انجام میگیرد — که هرکدام هدف، رفتار زمانی و اولویت خاص خود را دارند. هر نوع داده از یک Endpoint استفاده میکند. Endpoint به معنی «درگاه ارتباطی منطقی» است که میتوانید آن را مانند یک خط ارتباط در یک دستگاه USB درنظر بگیرید. هر endpoint یک شمارهی ۴ بیتی (0–15) و یک جهت (IN یا OUT) دارد. از سمت دستگاه به میزبان با (IN) و از سمت میزبان به دستگاه با (OUT) مشخص میشود.
این نوع انتقال در تمام دستگاهها وجود دارد و همهی دستگاهها حداقل یک endpoint 0 برای کنترل دارند. وقتی دستگاه وصل میشود، میزبان از EP0 استفاده میکند تا اطلاعات پایه (descriptorها) را بگیرد و دستگاه را شناسایی کند.
در سه مرحله انجام میشود:
|
1 2 3 4 5 |
Host: SETUP: GET_DESCRIPTOR(Device) Device: DATA: <Device Descriptor> STATUS: OK |
دادههای زیاد و بدون محدودیت زمانی. اگر فریم فضای خالی داشته باشد، Bulk هم اجازه ارسال دارد. این نوع داده تضمین تحویل (دارای CRC و retry) دارد اما تضمینی درمورد تاخیر زمانی ندارد. به عنوان مثال فلش مموریها از این نوع انتقال داده استفاده میکنند. به عنوان مثال:
|
1 2 3 |
Frame 12: Bulk OUT (data chunk 1) Frame 13: Bulk OUT (data chunk 2) ... |
برخلاف نامش، “interrupt” در USB واقعا وقفه سختافزاری نیست بلکه میزبان بهصورت دورهای (polling) در فریمهای خاص داده را میگیرد. به این صورت که میزبان هر چند میلی ثانیه درخواست داده جدید میکند. این نوع تبادل بیشتر در ماوس و دستگاههای مشابه انجام میشود.
|
1 2 3 |
Frame 1000: IN (dx=3, dy=1) Frame 1010: IN (dx=0, dy=-2) Frame 1020: IN (dx=1, dy=0) |
تبادل همزمان (Isochronous Transfer) در پروتکل USB، بهعنوان مهمترین نوع انتقال برای دادههای زنده (Real-time) طراحی شده است. هدف اصلی این روش، تضمین تحویل دادهها با تأخیر یکنواخت و کم (Low Latency) است، حتی اگر این به قیمت از دست دادن تصحیح خطا تمام شود. در این روش، از پهنای باند کانال USB یک ظرفیت ثابت و تضمینشده در هر فریم (USB Frame) رزرو میشود. این بدان معناست که دستگاه میداند در هر بازه زمانی مشخص، بخشی از پهنای باند در اختیار آن است. این روش بدون تکرار ارسال و اگر داده دقیقا در زمان مشخص به دستگاه نرسد در نظر گرفته نمیشود. به طور مثال کارت صدا با استفاده از این مدل تبادل داده کار میکند.
|
1 2 3 |
Frame 2000: Iso OUT (48 samples) Frame 2001: Iso OUT (48 samples) Frame 2002: Iso OUT (48 samples) |
جدا از انواع انتقال داده، کلاسهای مختلفی هم وجود دارد که نوع انتقال داده را مشخص میکند. مثلا مشخص میکند که برای انتقال صوت باید از چه نوع انتقال دادهای استفاده کنیم. در ادامه لیست این کلاسها را مشاهده میکنید.
کلاس USB (USB Class) یک استاندارد تعریفشده توسط انجمن توسعهدهندگان USB (USB-IF) است که مشخص میکند یک دستگاه USB چگونه باید با سیستم میزبان ارتباط برقرار کند. هر کلاس، پروتکلها، endpointها و نوع دادهای را که دستگاه پشتیبانی میکند، تعریف مینماید. این استانداردسازی باعث میشود که درایورهای عمومی (generic drivers) برای انواع مختلف دستگاهها وجود داشته باشد و نیازی به درایور اختصاصی برای هر دستگاه نباشد.
|
کدکلاس |
نام کلاس |
توضیحات |
نمونه دستگاهها |
|
00 |
Device |
برای هر دستگاهی |
دستگاههای سفارشی (Custom) |
|
01 |
Audio |
انتقال صوت و کنترلهای صوتی |
هدفون، میکروفون، اسپیکر |
|
02 |
CDC Control |
ارتباطات (مودم، شبکه، …) |
مودم، روتر، آداپتور شبکه |
|
03 |
HID (Human Interface Device) |
دستگاههای رابط انسانی |
کیبورد، ماوس، جویاستیک |
|
05 |
Physical |
دستگاههای فیزیکی (Force Feedback) |
جویاستیک فیدبکدار |
|
06 |
Image |
دستگاههای تصویربرداری |
وبکم، اسکنر |
|
07 |
Printer |
چاپگرها |
پرینتر |
|
08 |
Mass Storage |
ذخیرهسازی انبوه |
فلش، هارد اکسترنال |
|
09 |
Hub |
هاب USB |
هاب USB |
|
0A |
CDC Data |
دادههای CDC |
همراه با کلاس 02h |
|
0B |
Smart Card |
کارتهای هوشمند |
کارت خوان |
|
0D |
Content Security |
امنیت محتوا |
دستگاههای حفاظتی |
|
0E |
Video |
انتقال ویدئو |
وبکم، دوربین |
|
0F |
Personal Healthcare |
سلامت شخصی |
دستگاههای پزشکی |
|
10 |
Audio/Video Devices |
دستگاههای صوتی/تصویری |
دوربین دیجیتال |
|
11 |
Billboard Device |
دستگاههای تبلیغاتی |
دستگاههای نمایش Type-C |
|
12 |
USB Type-C Bridge |
پل Type-C |
کابلهای مبدل |
|
DC |
Diagnostic Device |
دستگاههای تشخیصی |
دیباگر USB |
|
E0 |
Wireless Controller |
کنترلر بیسیم |
آداپتور بلوتوث |
|
EF |
Miscellaneous |
متفرقه |
دستگاههای چندکاره |
|
FE |
Application Specific |
کاربردهای خاص |
دستگاههای سفارشی |
|
FF |
Vendor Specific |
اختصاصی فروشنده |
دستگاههای با درایور خاص |
بجز کلاسهایی که ذکر شد تعدادی زیرکلاس (Subclass USB) وجود دارد که سطح دومی از طبقهبندی در استاندارد USB است. هر کلاس دارای تعدادی زیرکلاس است که درون کلاسهای اصلی قرار میگیرند. در حالی که کلاس (Class) نوع کلی دستگاه را مشخص میکند (مثل HID یا Audio)، زیرکلاسها جزئیات بیشتر و نوع خاص تری از عملکرد را درون آن کلاس تعریف مینماید. این تقسیمبندی دقیقتر به سیستمعامل کمک میکند تا درایور مناسبتری را برای دستگاه انتخاب کند. در اینجا برای جلوگیری از طولانی شدن مطلب به عنوان مثال زیرکلاسهای مربوط به یکی از پرکاربردترین کلاسها یعنی کلاس CDC را بررسی میکنیم.
| Subclass کد | نام Subclass | توضیحات | پروتکلها | ||
| 00h | Reserved | رزرو شده | – | ||
| 01h | Direct Line Control | کنترل خط مستقیم | مودمها | ||
| 02h | Abstract Control | کنترل انتزاعی | مودم V.25ter | ||
| 03h | Telephone Control | کنترل تلفن | دستگاههای تلفنی | ||
| 04h | Multi-Channel | چند کاناله | مودمهای چندخط | ||
| 05h | CAPI Control | کنترل CAPI | – | ||
| 06h | Ethernet Networking | شبکه اترنت | آداپتور شبکه | ||
| 07h | ATM Networking | شبکه ATM | – | ||
| 08h-0Dh | Wireless | بیسیم | بلوتوث، Wi-Fi | ||
| 0Eh-7Fh | Reserved | رزرو شده | – | ||
| 80h-FFh | Vendor Specific | اختصاصی فروشنده | دستگاههای خاص | ||
تا اینجا با نحوه کار USB آشنا شدید و به اندازهی کافی اطلاعات دارید تا بتوانید دادههای Description را مطالعه کنید. حالا با هم سراغ کد نویسی میرویم. در اینجا از کتابخانه محبوب TinyUSB استفاده میکنیم که در ادامه با آن آشنا میشوید.
TinyUSB یک کتابخانهی سبک و چندپلتفرمی است که برای افزودن قابلیت USB Device/Host به میکروکنترلرهایی طراحی شده که کنترل مستقیم بر USB Core خود دارند از جمله:
TinyUSB به شما اجازه میدهد که رفتار USB را خودتان تعریف کنید و مثلاً دستگاه را به شکل کیبورد، کارت صدا، MIDI، یا رابط شبکه معرفی کنید. در USB Device، هر دستگاه خودش را با مجموعهای از Descriptorها به میزبان معرفی میکند. این اطلاعات شامل نوع دستگاه، تعداد رابط ها، Endpointها و رشتههای توضیحی است.
TinyUSB اینها را از قبل نوشته و در مثالهایی قرار داده و شما میتوانید در کتابخانه مربوط به هر مثال آنها را تنظیم کنید. اما در اینجا ما قصد داریم از مود Host استفاده کنیم به همین دلیل با (Descriptorها) کاری نداریم و نیاز است (tusb_config.h) را ادیت کنیم تا USB را به صورت Host تنظیم کنیم که در ادامه خواهید دید. در حالت Host کتابخانه تعدادی callback دارد تا اتصال/جداسازی را نشان دهد و همین طور ارسال و دریافت پیام را مدیریت کند. برای استفاده از توابع ارسال و دریافت پیام نیاز است که قبل از هرچیز description از دستگاه متصل دریافت شود.
خوشبختانه در گیت TinyUSB مثالهای مختلفی وجود دارد که چطور باید از این توابع استفاده کنید و ما موفق شدیم میکروکنترلر (RP2040) را با USB به ماژول (Quectel EC200) متصل کنیم که نتیجهی این کار در تصویر پایین مشخص است.

برنامه به این صورت کار میکند که شما هردستوری روی UART ارسال کنید از طریق USB به ماژول فرستاده شده و جواب به شما نمایش داده میشود. برنامه به این دلیل به این صورت نوشته شد که بتوانید از ارتباط صحیح ماژول با میکروکنترلر خودتان مطمئن شوید. چنانچه لازم باشد میتوانید به راحتی کد را تغییر دهید تا بدون UART کار کند. کد برنامه از این لینک قابل دانلود است. کد زیر مربوط به قسمت تنظیمات کتابخانه TinyUSB است.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
#ifndef TUSB_CONFIG_H_ #define TUSB_CONFIG_H_ #ifdef __cplusplus extern "C" { #endif //-------------------------------------------------------------------- // Common Configuration //-------------------------------------------------------------------- #define CFG_TUSB_DEBUG 1 // defined by compiler flags for flexibility #ifndef CFG_TUSB_MCU #error CFG_TUSB_MCU must be defined #endif #ifndef CFG_TUSB_OS #define CFG_TUSB_OS OPT_OS_NONE #endif #ifndef CFG_TUSB_DEBUG #define CFG_TUSB_DEBUG 0 #endif /* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. * Tinyusb use follows macros to declare transferring memory so that they can be put * into those specific section. * e.g * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) */ #ifndef CFG_TUH_MEM_SECTION #define CFG_TUH_MEM_SECTION #endif #ifndef CFG_TUH_MEM_ALIGN #define CFG_TUH_MEM_ALIGN __attribute__ ((aligned(4))) #endif //-------------------------------------------------------------------- // Host Configuration //-------------------------------------------------------------------- // Enable Host stack #define CFG_TUH_ENABLED 1 // #define CFG_TUH_MAX3421 1 // use max3421 as host controller #if CFG_TUSB_MCU == OPT_MCU_RP2040 // #define CFG_TUH_RPI_PIO_USB 1 // use pio-usb as host controller // host roothub port is 1 if using either pio-usb or max3421 #if (defined(CFG_TUH_RPI_PIO_USB) && CFG_TUH_RPI_PIO_USB) || (defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421) #define BOARD_TUH_RHPORT 1 #endif #endif // Default is max speed that hardware controller could support with on-chip PHY #define CFG_TUH_MAX_SPEED BOARD_TUH_MAX_SPEED //------------------------- Board Specific -------------------------- // RHPort number used for host can be defined by board.mk, default to port 0 #ifndef BOARD_TUH_RHPORT #define BOARD_TUH_RHPORT 0 #endif // RHPort max operational speed can defined by board.mk #ifndef BOARD_TUH_MAX_SPEED #define BOARD_TUH_MAX_SPEED OPT_MODE_DEFAULT_SPEED #endif //-------------------------------------------------------------------- // Driver Configuration //-------------------------------------------------------------------- // Size of buffer to hold descriptors and other data used for enumeration #define CFG_TUH_ENUMERATION_BUFSIZE 256 // only hub class is enabled #define CFG_TUH_HUB 1 // max device support (excluding hub device) // 1 hub typically has 4 ports #define CFG_TUH_DEVICE_MAX (3*CFG_TUH_HUB + 1) // Max endpoint per device #define CFG_TUH_ENDPOINT_MAX 8 // Enable tuh_edpt_xfer() API #define CFG_TUH_API_EDPT_XFER 1 #ifdef __cplusplus } #endif |
توابعی که در کد زیر مشاهده میکنید برای گرفتن Description از ماژول هستند.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
static void parseEc200Interface(uint8_t daddr, uint8_t *cfg) { uint16_t totalLen = cfg[2] | (cfg[3] << 8); uint8_t *p = cfg; uint8_t *end = cfg + totalLen; bool foundVendorIf = false; gDev = 0; gEpIn = 0; gEpOut = 0; atReady = false; while (p < end && p[0] > 0) { uint8_t len = p[0]; uint8_t type = p[1]; if (type == TUSB_DESC_INTERFACE && len >= sizeof(tusb_desc_interface_t)) { tusb_desc_interface_t *itf = (tusb_desc_interface_t *)p; foundVendorIf = (itf->bInterfaceClass == 0xFF); // vendor-specific if (foundVendorIf) { printf("Found EC200 vendor AT interface (itf=%u)\n", itf->bInterfaceNumber); } } else if (type == TUSB_DESC_ENDPOINT && foundVendorIf && len >= sizeof(tusb_desc_endpoint_t)) { tusb_desc_endpoint_t *ep = (tusb_desc_endpoint_t *)p; if (ep->bmAttributes.xfer == TUSB_XFER_BULK) { if (tu_edpt_dir(ep->bEndpointAddress) == TUSB_DIR_IN) gEpIn = ep->bEndpointAddress; else gEpOut = ep->bEndpointAddress; } } p += len; } if (gEpIn && gEpOut) { gDev = daddr; printf("AT EP IN = 0x%02X\n", gEpIn); printf("AT EP OUT = 0x%02X\n", gEpOut); atReady = true; } } void tuh_mount_cb(uint8_t daddr) { printf("Device mounted: addr=%u\n", daddr); uint8_t cfg[512]; if (tuh_descriptor_get_configuration_sync(daddr, 0, cfg, sizeof(cfg)) != XFER_RESULT_SUCCESS) { printf("Failed to read config\n"); return; } parseEc200Interface(daddr, cfg); if (!atReady) return; // Open endpoints safely if (!openEndpointSafe(daddr, gEpOut)) return; if (!openEndpointSafe(daddr, gEpIn)) return; // Prepare and start RX transfer atRxXfer.daddr = daddr; atRxXfer.ep_addr = gEpIn; atRxXfer.buffer = atRxBuf; atRxXfer.buflen = sizeof(atRxBuf); atRxXfer.complete_cb = atRxCompleteCb; if (!tuh_edpt_xfer(&atRxXfer)) { printf("ERROR: failed to start initial IN transfer\n"); return; } printf("AT interface ready.\n"); } |
Description که توابع استخراج میکند به صورت زیر است.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
Device 1: ID 2c7c:6005 Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 0200 bDeviceClass 239 bDeviceSubClass 2 bDeviceProtocol 1 bMaxPacketSize0 64 idVendor 0x2c7c idProduct 0x6005 bcdDevice 0318 iManufacturer 1 Android iProduct 2 Android iSerialNumber 3 0000 bNumConfigurations 1 |
پس از استخراج دادههای مورد نیاز از ماژول حالا نوبت ارسال و دریافت داده است که از این توابع استفاده میکنیم.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
uint8_t sendAt(const char *cmd) { if (!atReady || gDev == 0 || gEpOut == 0) return 0; if (cmd == NULL) return 0; if (txBusy) { // transmitter busy — caller can retry later or we can implement a queue printf("TX busy, skipping send\n"); return 0; } // prepare txBuf: copy cmd and ensure CRLF termination size_t cmdLen = strnlen(cmd, AT_TX_BUF_SIZE - 1); if (cmdLen == 0) return 0; // check space: +2 for CRLF if (cmdLen + 2 >= sizeof(txBuf)) { printf("ERROR: command too long\n"); return 0; } memcpy(txBuf, cmd, cmdLen); // add CRLF if last char not CR or LF if (cmdLen >= 1 && (txBuf[cmdLen - 1] == '\r' || txBuf[cmdLen - 1] == '\n')) { // if user already added CR or LF, we ensure there is LF after CR if (txBuf[cmdLen - 1] == '\r') { txBuf[cmdLen++] = '\n'; } } else { txBuf[cmdLen++] = '\r'; txBuf[cmdLen++] = '\n'; } // Fill transfer object txXfer.daddr = gDev; txXfer.ep_addr = gEpOut; txXfer.buffer = txBuf; txXfer.buflen = (uint32_t)cmdLen; txXfer.complete_cb = txCompleteCb; // Mark busy BEFORE calling tuh_edpt_xfer to avoid races. txBusy = true; if (!tuh_edpt_xfer(&txXfer)) { // failed to start transfer txBusy = false; printf("ERROR: tuh_edpt_xfer failed for OUT\n"); return 0; } return 1; } void atRxCompleteCb(tuh_xfer_t* xfer) { // Basic validation if (xfer == NULL) return; if (xfer->daddr != gDev) return; if (xfer->ep_addr != gEpIn) return; // Null-terminate (be careful: actual_len <= buf len) size_t n = xfer->actual_len; if (n >= AT_RX_BUF_SIZE) n = AT_RX_BUF_SIZE - 1; atRxBuf[n] = '\0'; // Print received chunk printf("[AT RX] %s", (char*)atRxBuf); // Re-arm the IN transfer for next incoming data atRxXfer.daddr = gDev; atRxXfer.ep_addr = gEpIn; atRxXfer.buffer = atRxBuf; atRxXfer.buflen = sizeof(atRxBuf); atRxXfer.complete_cb = atRxCompleteCb; if (!tuh_edpt_xfer(&atRxXfer)) { printf("WARN: failed to restart at IN transfer\n"); } } |
وبسایت: http://www.arvidtek.com
www.arvidtek.com | گروه مهندسی آرویدتک | فعال حوزه الکترونیک و مخابرات | فروشگاه تخصصی قطعات الکترونیک
سیسوگ با افتخار فضایی برای اشتراک گذاری دانش شماست. برای ما مقاله بنویسید.