فرض کنید میخواهید یک دستگاه پایش وضعیت طراحی کنید که دادههای ولتاژ یا متغیرهای حسگرها را روی بستر شبکهٔ سیمی محلی ارسال کند یا با ارائهٔ یک صفحهٔ وب متنی، امکان مشاهدهٔ مقادیر را از طریق مرورگر فراهم سازد. میکروکنترلر STM32F303CBT6 فاقد کنترلر دسترسی به رسانه (Ethernet MAC) داخلی است و اجرای پشتههای نرمافزاری کامل مانند LwIP روی آن، به دلیل نیاز به قطعهٔ واسط شبکه و مصرف قابل توجه حافظه در میکروکنترلری با 32 KiB رم اصلی (و 8 KiB حافظهٔ دسترسی سریع CCM که مجموعاً 40 KiB را تشکیل میدهند)، چالشهای فنی فراوانی به همراه دارد. در این درس، با بهرهگیری از تراشهٔ شبکهٔ W5500 که پشتهٔ کامل پروتکلهای IPv4، TCP، UDP، لایهٔ MAC و PHY ده/صد مگابیت را روی مدارات سیلیکونی خود پیادهسازی کرده و 32 KiB بافر میانی اختصاصی دارد، یک ارتباط شبکهٔ مطمئن روی گذرگاه SPI ایجاد میکنیم. در این مسیر، الزامات زمانبندی کلاک و اتصالات الکتریکی را بررسی میکنیم، کتابخانهٔ استاندارد WIZnet ioLibrary v3.1.3 را با توابع بازخوانی به لایهٔ HAL متصل میسازیم، تفاوت پیکربندی آدرس ایستا با پروتکل DHCP را تحلیل مینماییم و سه سرویس شامل سرور پژواک TCP روی پورت 5000، وبسرور تککلاینت روی پورت 8080 برای قرائت وضعیت کانال ADC و سوکت پژواک UDP روی پورت 5001 را راهاندازی میکنیم.
وضعیت اعتبارسنجی: کامپایلشده؛ پروژهٔ همراه با گزینههای -Wall -Werror بدون خطا و هشدار ساخته شده است. اجرای روی برد، آزمون رابط گرافیکی و اندازهگیری فیزیکی انجام نشدهاند. شکلهای مفهومی، مدلهای محاسباتی و مقادیر مورد انتظار، نتیجهٔ اندازهگیری نیستند.
پیشنیاز: درس 27. سختافزار مبنا: STM32F303CBT6 در بستهٔ LQFP48 روی GB100ST V2.0. قطعات جانبی، تغذیه و روش اتصال در متن همین درس توضیح داده شدهاند.
شبکهسازی در سامانههای نهفته و معماری کنترلر اترنت سختافزاری W5500
در طراحی سامانههای نهفته، شبکهٔ سیمی اترنت به دلیل قابلیت اطمینان، نرخ تبادل مشخص و سهولت اتصال به زیرساختهای موجود، نقشی محوری دارد. با این حال، اگر به مشخصات میکروکنترلر STM32F303CBT6 رجوع کنیم، درمییابیم که این تراشه بر خلاف برخی مدلهای خانوادههای دیگر (نظیر مدلهای خاصی از STM32F4)، فاقد کنترلر درونی MAC یا رابطهای سختافزاری MII و RMII است. بنابراین برای اتصال به کابل شبکه، طراح ناگزیر است از یک تراشهٔ کمکی خارجی بهره گیرد.
تفاوت بار پردازشی و حافظه: پشتهٔ نرمافزاری در برابر پشتهٔ سختافزاری W5500
در ساختار سنتی که از پشتههای نرمافزاری مانند LwIP استفاده میشود، پردازندهٔ اصلی میکروکنترلر باید تکتک لایههای پروتکل شامل کشف آدرس فیزیکی (ARP)، بستهبندی IP، کنترل جریان TCP، زمانبندی ارسال مجدد و محاسبهٔ جمعوارسی (Checksum) را با کدهای نرمافزاری پردازش کند. این فرآیند نیازمند تخصیص بخش بزرگی از حافظهٔ موقت برای توصیفگرها و صف بستهها است. میکروکنترلر STM32F303CB تنها دارای 32 KiB حافظهٔ رم اصلی (SRAM) و 8 KiB حافظهٔ اختصاصی هسته (CCM SRAM) است که مجموعاً 40 KiB رم در اختیار میگذارد. اگرچه حافظهٔ CCM برای متغیرهای پردازنده و بافرهای نرمافزاری در دسترس است، اما به ماتریس دسترسی مستقیم به حافظه (DMA) متصل نیست و نمیتوان از آن برای انتقال مستقیم دادههای ارتباطی استفاده کرد.
تراشهٔ کمکی W5500 با ارائهٔ پشتهٔ سختافزاری (Hardwired TCP/IP)، این لایههای زیرین را بر عهده میگیرد. در این معماری، لایهٔ فیزیکی (10/100 Ethernet PHY)، کنترل دسترسی به رسانه (MAC) و ماشینهای حالت پردازش بستههای IPv4، TCP و UDP همگی بر روی سیلیکون داخلی W5500 اجرا میشوند. البته آفلود سختافزاری به این معنی نیست که بار پردازشی شبکه در میکروکنترلر به صفر میرسد؛ هستهٔ Cortex-M4 همچنان وظیفهٔ تبادل بایتها روی گذرگاه SPI، اجرای توابع راهانداز ioLibrary، مدیریت وضعیت کلاینت DHCP، فرمتبندی درخواستها و پاسخهای سطح کاربرد (مانند سرآیندهای HTTP) و نگهداری بافرهای پردازشی برنامه را بر دوش دارد.
ساختار داخلی W5500: موتور پردازش پروتکلهای TCP/IP و 32 KiB حافظه بافر
مطابق با دیتاشیت رسمی W5500 نسخه 1.1.0، این کنترلر دارای 32 KiB حافظهٔ رم داخلی است که با بودجهٔ جهتدار ثابت 16 KiB برای ارسال (TX) و 16 KiB برای دریافت (RX) طراحی شده است. این تراشه میتواند تا 8 سوکت مستقل سختافزاری را همزمان مدیریت کند. در پیکربندی متوازن این پروژه، به هر یک از 8 سوکت مقدار 2 KiB بافر ارسال و 2 KiB بافر دریافت اختصاص داده شده است. وجود این حافظهٔ میانی به جذب نوسانات ترافیک در خط شبکه کمک میکند؛ اما باید توجه داشت که این ظرفیت محدود است. در صورت تأخیر برنامه در خواندن دادهها از بافر دریافت یا ارسال قطعات حجیم به ویژه در بسترهای بدون اتصال مانند UDP، سرریز بافر و افت بسته همچنان رخ میدهد؛ از این رو اعمال کنترل جریان و تخلیهٔ به موقع دادهها در لایهٔ کاربرد ضروری است. همچنین باید توجه داشت که W5500 از پروتکل IPv6، خردسازی و اتصال مجدد بستههای بزرگ IP (Fragmentation) یا لایهٔ امنیتی TLS به صورت سختافزاری پشتیبانی نمیکند.


رابط فیزیکی SPI و قیدهای فرکانسی: تفاوت توانایی W5500 با محدودیت STM32F303
ارتباط میان میکروکنترلر STM32 و تراشهٔ W5500 از طریق گذرگاه سریال سنکرون SPI برقرار میشود؛ در این ارتباط میکروکنترلر به عنوان Master و کنترلر شبکه در نقش Slave عمل میکند. تنظیم فرکانس کاری این خط نیازمند تحلیل توامان قابلیتهای تراشهٔ مقصد، ساختار ساعت میکروکنترلر و شرایط فیزیکی سیمبندی است.
تحلیل ادعای فرکانس 80 MHz در دیتاشیت و واقعیت عملکردی
در کاتالوگ W5500 حداکثر فرکانس تئوری کلاک رابط کاربری تا 80 MHz ذکر شده است. اما با بررسی جدول مشخصات زمانی در صفحهٔ 61 دیتاشیت نسخه 1.1.0، پاورقی زمانبندی تصریح میکند که مقادیر تاخیر تضمینشدهٔ تراشه بر مبنای فرکانس 33.3 MHz تعریف شدهاند؛ بنابراین 33.3 MHz یک مرجع تضمین پارامترهای زمانی است، نه حداقل فرکانس مورد نیاز برای کارکرد. از سوی دیگر، در بردهای آزمایشی و هنگام استفاده از کابلها یا سیمهای جامپر، ظرفیت خازنی پارازیتی خطوط و تضعیف لبههای پالس، دستیابی به سرعتهای بالا را ناممکن میسازد و فرکانسهای کاری باید با در نظر گرفتن ملاحظات تداخل الکترومغناطیسی انتخاب شوند.
محاسبهٔ فرکانس کاری در پروژه: گذرگاه APB2 و پیکربندی SPI1 با تقسیمکنندهٔ 16
در درخت ساعت پروژهٔ پیادهسازیشده، نوسانساز خارجی HSE با فرکانس 8 MHz از طریق حلقهٔ قفل فاز (PLL) به فرکانس هستهٔ 72 MHz تبدیل میشود. واحد SPI1 بر روی گذرگاه APB2 با فرکانس 72 MHz تغذیه میگردد. تقسیمکنندههای پیشتقسیم کلاک واحد SPI در STM32 توانهای صحیح از عدد 2 هستند (/2, /4, /8, /16, …). بنابراین بیشترین کلاک محاسباتی با مقسم 2 برابر 36 MHz خواهد بود. در پروژهٔ همراه، برای انتخاب کلاکی پایینتر و مناسبتر برای بررسی اولیهٔ سیمبندی کارگاهی، مقسم کلاک روی 16 تنظیم شده است:
$$f_{SCK} = \frac{72\text{ MHz}}{16} = 4.5\text{ MHz}$$
فرکانس 4.5 MHz مقدار اسمی پیکربندیشده است؛ پایداری شکل موج و صحت تبادل روی سختافزار این مثال اندازهگیری نشدهاند. کاهش کلاک جای بررسی تغذیه، اتصالات و کیفیت سیگنال را نمیگیرد.
سیمبندی و اتصالات فیزیکی: گذرگاه SPI1، پایهٔ ریست و پایههای کنترلی
اتصالات فیزیکی میان ماژول W5500 و میکروکنترلر STM32F303CBT6 بر روی برد GB100ST V2.0 مطابق با کد کامپایلشده به شرح زیر است:
- پایه PA5: خط کلاک سریال SPI1_SCK (در مود AF5).
- پایه PA6: خط دادهٔ ورودی میکروکنترلر SPI1_MISO (در مود AF5).
- پایه PA7: خط دادهٔ خروجی میکروکنترلر SPI1_MOSI (در مود AF5).
- پایه PA4: خط انتخاب تراشه (Active-Low SCSn) که در فایل پیکربندی از برچسب SENSOR_CS استفاده مجدد کرده و با کد برنامه کنترل میشود.
- پایه PB0: خط ریست سختافزاری فعال-پایین (RSTn) برای بازنشانی ماژول.
- پایه PA1: ورودی کانال 2 واحد آنالوگ به دیجیتال ADC1_IN2 جهت پایش ولتاژ ورودی.
- پایه PB6: خروجی فرمان الایدی وضعیت روی برد اصلی.
پایهٔ وقفهٔ INTn ماژول در این مثال متصل نشده است؛ زیرا معماری نرمافزار به صورت پایش دورهای (Polling) در حلقهٔ اصلی نوشته شده و نیازی به تحریک وقفهٔ خارجی ندارد.
الزامات الکتریکی، جریان تغذیهٔ 3.3V و اثر ظرفیت خازنی سیمها
مدار فرستنده و گیرندهٔ فیزیکی شبکه (Ethernet PHY) و ترانسفورماتور ایزولاسیون جک RJ45 در هنگام ارسال فریمها و پالسهای تشخیص لینک، جریان مصرفی لحظهای بالایی طلب میکنند. به همین دلیل، تغذیهٔ ماژول W5500 تحت هیچ شرایطی نباید از پایههای خروجی GPIO میکروکنترلر تامین شود. ماژول باید مستقیماً به یک منبع 3.3 ولت رگولهشده با توان خروجی کافی وصل گردد (یا اگر ماژول دارای رگولاتور داخلی است، به خط 5 ولت ماژول با منطق 3.3V وصل شود). مشترک بودن زمین (GND) میکروکنترلر و ماژول و کوتاه نگه داشتن طول سیمهای رابط جهت پیشگیری از اعوجاج سیگنال در فرکانس 4.5 MHz کاملاً ضروری است.


راهاندازی کتابخانهٔ ioLibrary و پیادهسازی توابع بازخوانی (Callbacks)
برای تعامل استاندارد با ریجسترهای W5500، از کتابخانهٔ رسمی WIZnet ioLibrary_Driver نگارش v3.1.3 (برچسب با هش کامیت 069a81350e8ea0da2d210ab01db97895bba20399) با تعریف ماکروی سراسری _WIZCHIP_=5500 استفاده شده است. این نسخه با اعمال سه وصلهٔ مستند محلی در پوشهٔ پروژه همراه شده تا خطاهای سختگیرانهٔ کامپایلر GCC تحت پرچمهای -Wall -Werror (شامل آکولادگذاری مقادیر اولیه در wizchip_conf.c، پرانتزگذاری بیتهای کنترلی در socket.c و محدودسازی طول بستهٔ DHCP به 548 بایت همراه با بررسی کوکی تبادل) را برطرف سازد.
معماری لایهای درایور WIZnet ioLibrary و مفهوم توابع Callback
کتابخانهٔ WIZnet برای کار بر روی انواع میکروکنترلرها طراحی شده و وابسته به یک سختافزار خاص نیست. به همین جهت، این درایور از توابع بازخوانی (Callbacks) استفاده میکند. لایهٔ کاربرد موظف است چهار تابع به درایور معرفی کند: دو تابع برای فعال و غیرفعال کردن خط انتخاب تراشه (CS) و دو تابع برای خواندن و نوشتن تکبایت بر روی گذرگاه SPI.
در فایل Core/Src/spi.c، تابع مقداردهی اولیه وضعیت رابط SPI1 را روی مود 0 (سطح کلاک در حالت بیباری پایین و نمونهبرداری در لبهٔ اول) قرار میدهد:
/* Authored override of the real lesson27 hardware baseline; external CS stays high. */
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16;
if (HAL_SPI_Init(&hspi1) != HAL_OK) Error_Handler();
پیادهسازی توابع انتخاب تراشه (CS) و تبادل بایت با لایهٔ HAL
توابع Select و Deselect با تغییر سطح منطقی پایهٔ PA4 به صفر و یک، قاب ارتباطی SPI را باز و بسته میکنند. توابع SpiRead و SpiWrite نیز با استفاده از تابع HAL_SPI_TransmitReceive ارسال و دریافت همزمان بایت را انجام میدهند:
static void Select(void) { HAL_GPIO_WritePin(SENSOR_CS_GPIO_Port, SENSOR_CS_Pin, GPIO_PIN_RESET); }
static void Deselect(void) { HAL_GPIO_WritePin(SENSOR_CS_GPIO_Port, SENSOR_CS_Pin, GPIO_PIN_SET); }
static uint8_t SpiRead(void)
{
uint8_t tx = 0xFF, rx = 0;
/* Guard the vendor library's hardware-register polling loops as well. */
if ((uint32_t)(HAL_GetTick() - api_started) > 5000U) Fail(2);
if (HAL_SPI_TransmitReceive(&hspi1, &tx, &rx, 1, 20) != HAL_OK) Fail(1);
return rx;
}
static void SpiWrite(uint8_t value)
{
uint8_t rx;
if ((uint32_t)(HAL_GetTick() - api_started) > 5000U) Fail(2);
if (HAL_SPI_TransmitReceive(&hspi1, &value, &rx, 1, 20) != HAL_OK) Fail(1);
}
مکانیزم نگهبان زمانی (API Guard) برای جلوگیری از قفل شدن پردازنده
چنانچه کابل رابط قطع شود یا تراشهٔ W5500 پاسخ ندهد، برخی توابع داخلی درایور ممکن است در حلقههای while برای خواندن رجیسترهای وضعیت گرفتار شوند. در این پروژه، متغیر api_started در ابتدای اجرای تابع App_Init و همچنین در ابتدای هر تکرار از حلقهٔ App_Poll مقداردهی زمانی میشود (نه قبل از تکتک فراخوانیهای کوچک API). در داخل توابع SpiRead و SpiWrite، مجموع زمان سپریشده در طول آن تکرار با سقف 5000 میلیثانیه مقایسه میشود و تابع HAL نیز مهلت زمانی 20 میلیثانیه به ازای هر بایت دارد. اگر زمان از سقف مجاز فراتر رود یا تبادل HAL با خطا مواجه شود، تابع Fail فراخوانی میگردد. این تابع خط CS را غیرفعال کرده (یک منطقی) و سیستم را در حلقهٔ بینهایتی متوقف میکند تا وضعیت معیوب مشخص شود؛ این مکانیزم توقف قطعی (Fail-stop) است و امکان بازیابی خودکار در آن وجود ندارد.
مقداردهی اولیهٔ بافرهای 8 گانه، بررسی شناسهٔ سیلیکون و اعمال مکآدرس
در تابع App_Init، ابتدا پایهٔ PB0 به مدت 2 میلیثانیه در سطح پایین نگه داشته شده و سپس به مدت 100 میلیثانیه در سطح بالا قرار میگیرد تا ریست سختافزاری اعمال شود (دیتاشیت حداقل عرض پالس 500 میکروثانیه را معین کرده است). سپس توابع بازخوانی ثبت شده و رجیستر شناسهٔ نسخه خوانده میشود. اگر مقدار خواندهشده برابر 0x04 (شناسهٔ ثابت سیلیکون W5500) نباشد، برنامه با خطای Fail(4) متوقف میگردد:
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
pin.Pin = GPIO_PIN_0; pin.Mode = GPIO_MODE_OUTPUT_PP; pin.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &pin);
HAL_Delay(2); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET); HAL_Delay(100);
api_started = HAL_GetTick();
reg_wizchip_cs_cbfunc(Select, Deselect);
reg_wizchip_spi_cbfunc(SpiRead, SpiWrite);
/* Single main-context owner: no SPI/socket calls from interrupts or tasks. */
if (getVERSIONR() != 0x04) Fail(4);
uint8_t memory[8] = {2,2,2,2,2,2,2,2};
if (wizchip_init(memory, memory) != 0) Fail(5);
wiz_NetTimeout timeout = {3, 2000}; /* RCR3; RTR200ms, NOT an app request deadline. */
wizchip_settimeout(&timeout);
if (HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED) != HAL_OK) Fail(6);
tick_enabled = 1;
در این پیکربندی، ساختار timeout مقدار RCR را روی 3 و RTR را روی 2000 واحد 100 میکروثانیهای، معادل 200 میلیثانیه، قرار میدهد. این تنظیمات در زمان انتظار و تلاش مجدد سختافزاری عملیاتهایی مانند ARP و TCP نقش دارند؛ آنها مهلت مستقل 10 ثانیهای بستن کلاینت در برنامه را تعیین نمیکنند.

تنظیمات شبکه: آدرسدهی ایستا (Static IP) در برابر پروتکل خودکار DHCP
هر گره در شبکهٔ محلی برای برقراری ارتباط نیازمند آدرس پروتکل اینترنت (IP)، ماسک زیرشبکه (Subnet Mask) و آدرس درگاه پیشفرض (Gateway) است. این مقادیر را میتوان به شکل ایستا در حافظه تعریف کرد یا از طریق پروتکل تخصیص پویای میزبان (DHCP) به دست آورد.
ساختار wiz_NetInfo و تنظیمات آدرسدهی ثابت (Static IP)
در فایل app.c ساختار g_network برای حالت ایستا تعریف شده است. آدرس فیزیکی (MAC) انتخابی از دستهٔ آدرسهای محلی مدیریتشده (Locally Administered) با الگوی 02:00:00:00:49:01 تعیین شده که در شبکهٔ عملیاتی باید مقداری یکتا باشد:
/* Locally administered EXAMPLE MAC; choose a unique one per real device. */
wiz_NetInfo g_network = {
{0x02,0x00,0x00,0x00,0x49,0x01}, {192,168,50,50},
{255,255,255,0}, {192,168,50,1}, {192,168,50,1}, NETINFO_STATIC
};
هنگامی که ماکروی APP_USE_DHCP برابر 0 باشد، تابع wizchip_setnetinfo(&g_network) این مقادیر را در رجیسترهای تراشه ذخیره کرده و متغیر g_net_ready = 1 میشود. توجه داشته باشید که یک شدن g_net_ready صرفاً به معنای اعمال تنظیمات در رجیسترهای W5500 است و دلیلی بر اتصال فیزیکی کابل (که توسط g_link_up بررسی میشود) یا دسترسیپذیری سرورهای راه دور نیست. برای آزمایش در یک زیرشبکهٔ مشترک محلی با نشانی عددی، تنظیم درگاه و DNS الزامی نخواهد بود.
سازوکار کلاینت DHCP و چرخهٔ چهارمرحلهای دریافت آدرس
با تعریف APP_USE_DHCP برابر 1، سوکت شماره 7 به پروتکل خودکار اختصاص یافته و از پروتکل دیتاگرام کاربر (UDP روی پورتهای 67 و 68) با بافر 548 بایتی استفاده میکند. کلاینت چرخهٔ ارسال پیام کشف (Discover)، دریافت پیشنهاد (Offer)، درخواست انتساب (Request) و تاییدیه (ACK) را طی میکند. با انتساب موفق آدرس، تابع بازخوانی LeaseAssigned فراخوانی شده و پارامترها در تراشه بهروز میشوند.
قید حیاتی زمانسنجی: تفکیک وقفهٔ SysTick از پردازش شبکه و جلوگیری از بنبست
پروتکل DHCP دارای مفهوم مهلت اجارهٔ زمانی (Lease Time) است. تابع DHCP_time_handler() در کتابخانه باید هر یک ثانیه فراخوانی شود. چنانچه در فرآیند دریافت آدرس، تداخل IP (Conflict) رخ دهد، کتابخانهٔ ioLibrary وارد یک حلقهٔ انتظار فعال (Busy-wait) دوثانیهای میشود. اگر تابع زمانسنج در حلقهٔ اصلی فراخوانی میشد، این تاخیر مانع از پیشروی شمارندهٔ زمان شده و ماشین حالت در یک حلقهٔ بسته متوقف میگردید. به همین جهت، زمانسنجی در وقفهٔ یک میلیثانیهای SysTick (یا هر تایمر سختافزاری مستقل دیگر) تعبیه شده است:
void App_Tick1ms(void)
{
static uint16_t divider;
if (tick_enabled != 0U && ++divider == 1000U) {
divider = 0;
DHCP_time_handler(); /* ONLY time counter; no SPI or socket work in ISR. */
}
}
نکتهٔ اساسی این است که در روال وقفه صرفاً یک متغیر زمانی افزایش مییابد و هیچگونه تبادل SPI یا مدیریت سوکت درون وقفه انجام نمیپذیرد.
تفاوت رفتار در شرایط خطا: توقف قطعی در برابر عدم بازگشت خودکار به IP ایستا
در پیادهسازی حاضر، در صورتی که پاسخ DHCP دریافت نشود و متغیر DHCP_run() مقدار DHCP_FAILED را برگرداند، یا پس از سپری شدن 45 ثانیه اولیه هیچ آدرسی اختصاص نیابد، سرویس متوقف شده و dhcp_stopped = 1 میشود. در این طراحی، سامانه عمداً به آدرس ایستا سوئیچ نمیکند؛ چرا که در شبکههای صنعتی و اداری، تغییر خودسرانهٔ آدرس به یک IP پیشفرض ممکن است موجب تداخل ناخواسته با سایر گرهها شود. راهاندازی مجدد نیازمند ریست سختافزاری کل سیستم خواهد بود.
برنامهنویسی سوکت و پیادهسازی سرویسهای TCP ،HTTP و UDP
تراشهٔ W5500 به برنامهنویس امکان میدهد با فراخوانی توابع سوکت، دادهها را ارسال و دریافت کند. سوکتهای کاربردی در این پروژه با پرچم SF_IO_NONBLOCK باز میشوند تا عملیات خواندن و نوشتن منتظر نماند و وضعیت SOCK_BUSY به چرخههای بعدی موکول شود؛ هرچند برخی روالهای جانبی مانند ارسال بستههای DHCP یا حلقههای بررسی رجیسترها در HAL SPI کماکان تاخیرهای مسدودکنندهٔ مقطعی دارند.
ماشین حالت سوکت TCP و مدیریت ارتباطات بدون مسدودسازی (Non-blocking)
سوکت TCP ابتدا در وضعیت SOCK_CLOSED قرار دارد. برنامه با دستور socket سوکت را به حالت SOCK_INIT برده و سپس با فراخوانی listen آن را آمادهٔ اتصال کلاینت میسازد. پس از دستتکانی سهمرحلهای، وضعیت سوکت به SOCK_ESTABLISHED تغییر مییابد. در صورت عدم تبادل داده بیش از 10 ثانیه یا وقوع وقفهٔ Sn_IR_TIMEOUT، سوکت با تابع ResetClient بسته و بافر آن پاک میشود.
پیادهسازی سرور پژواک (TCP Echo Server) روی پورت 5000
سوکت شماره 0 برای سرور پژواک پورت 5000 اختصاص یافته است. متغیر offset مقدار بایتهای ارسالشده در هر مرحله را دنبال میکند. متغیر g_tcp_echo_bytes تعداد کل بایتهای پذیرفتهشده توسط بافر فرستندهٔ تراشه را میشمارد و به منزلهٔ اثبات دریافت داده توسط برنامهٔ کلاینت نیست:
uint16_t available = getSn_RX_RSR(sn);
if (available == 0U) {
if (state == SOCK_CLOSE_WAIT) ResetClient(sn);
return;
}
if (sn == 0) {
uint16_t request = available > sizeof(client->bytes) ? sizeof(client->bytes) : available;
int32_t received = recv(sn, client->bytes, request);
g_last_socket_result = received;
if (received > 0) { client->length = (uint16_t)received; client->offset = 0; client->last_activity = now; }
else if (received < 0) ResetClient(sn);
}
وبسرور سبک (HTTP Port 8080) و نمایش دادههای آنالوگ ADC
سوکت شماره 1 نقش وبسرور را بر عهده دارد. بایتهای رسیده تا 767 بایت جمعآوری شده و در صورت یافتن الگوی پایان سرآیند (\r\n\r\n) و تطابق با دستور GET / ، تابع BuildHttp فراخوانی میشود. این وبسرور سبک طبق طراحی خود پس از ارسال صفحه اتصال را قطع میکند (Connection: close) که یک انتخاب برای وبسرورهای بسیار ساده است و نه الزام کلی پروتکل HTTP (زیرا HTTP/1.1 از اتصالات پایدار نیز پشتیبانی میکند):
static void BuildHttp(Connection *client)
{
char body[256];
int size = snprintf(body, sizeof(body),
"<!doctype html><html><body><h1>STM32 ADC</h1><p>raw=%lu valid=%lu</p></body></html>\n",
(unsigned long)g_adc_raw, (unsigned long)g_adc_valid);
if (size < 0 || (unsigned)size >= sizeof(body)) Fail(3);
int length = snprintf((char *)client->bytes, sizeof(client->bytes),
"HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: %u\r\nConnection: close\r\n\r\n%s",
(unsigned)size, body);
if (length < 0 || (unsigned)length >= sizeof(client->bytes)) Fail(3);
client->length = (uint16_t)length; client->offset = 0; client->responded = 1;
++g_http_responses;
}
پیش از صدور دستور disconnect(1)، برنامه منتظر فعال شدن پرچم Sn_IR_SENDOK میماند تا مطمئن شود پشتهٔ TCP بایتها را تایید کرده است. این بیت نباید به صورت دستی پاک شود، زیرا تابع send در کتابخانه وضعیت آن را مدیریت میکند. شمارندهٔ g_http_responses صرفاً تعداد دفعات ساخته شدن پاسخ HTTP را نشان میدهد و گواهی بر نمایش آن در مرورگر کلاینت نیست. دادهٔ ADC قرائتشده از پایهٔ PA1 بر اساس فلگ g_adc_valid سنجیده میشود؛ در صورتی که نمونهبرداری هنوز انجام نشده یا خطایی رخ دهد، مقدار این فلگ 0 خواهد بود و مقدار خام ممکن است نامعتبر یا قدیمی باشد.
سوکت UDP روی پورت 5001 و سازوکار مدیریت بستههای فراتر از بافر
سوکت شماره 2 برای آزمایش تبادل بستههای دیتاگرام کاربر (UDP) روی پورت 5001 استفاده میشود. در پروتکل UDP تضمین ارسال مجدد یا حفظ توالی بستهها وجود ندارد. تابع recvfrom دادهها و مشخصات فرستنده را دریافت میکند. در صورتی که بستهای با طول بیش از 256 بایت ارسال شود، تابع getsockopt(2, SO_REMAINSIZE, &remaining) وجود دادههای مازاد را آشکار میکند. برای جلوگیری از ارسال پیامهای ناقص و مخدوش، کل دیتاگرام دور ریخته شده، متغیر g_udp_oversize_drops افزایش یافته و سوکت ریست میگردد.
عیبیابی، آزمونهای عملی و وضعیت اعتبارسنجی پروژه
راهاندازی شبکه در سطوح نهفته به دلیل تداخل لایههای سختافزار، فیزیکی، زمانسنجی و منطقی، مستلزم بررسی گامبهگام کدهای وضعیت سیستم است.
جدول جامع عیبیابی بر اساس کدهای خطای پروژه و وضعیت سختافزار
جدول زیر نشانهها، متغیرهای ثبت خطا و اقدامات لازم را بر پایهٔ سورس پروژه خلاصه میکند:
| کد خطا یا نشانه | محل ثبت در کد | علت احتمالی | اقدام اصلاحی و بررسی |
|---|---|---|---|
g_net_error = 1 |
توابع SpiRead و SpiWrite |
عدم بازگشت HAL_OK در تبادل بایت SPI | بررسی اتصالات PA5 (SCK)، PA6 (MISO)، PA7 (MOSI) و اطمینان از اشتراک زمین (GND). |
g_net_error = 2 |
مکانیزم API Guard در تبادل SPI | تجاوز زمان کل اجرای تکرار جاری از 5000 میلیثانیه | پاسخ ندادن تراشهٔ W5500 در حلقههای داخلی رجیسترها؛ بررسی نوسانات تغذیه و کلاک. |
g_net_error = 3 |
تابع BuildHttp |
خطا در اندازهٔ تولید رشته یا سرریز بافرهای 256 یا 768 بایتی | تطبیق طول بدنهٔ متنی با بافر تخصیصیافته و اطمینان از صحت خروجی snprintf. |
g_net_error = 4 |
بررسی رجیستر در App_Init |
ناهمخوانی مقدار رجیستر VERSIONR با 0x04 |
عدم اتصال ماژول، قطعی خط PA4 (CS) یا فعال نشدن پالس ریست روی PB0. |
g_net_error = 5 |
تابع wizchip_init |
شکست در تنظیم مقادیر اولیهٔ بافرهای 8 گانه | بررسی تخصیص آرایههای حافظه و عدم تجاوز از سقف 16 KiB برای هر جهت. |
g_net_error = 6 |
تابع HAL_ADCEx_Calibration_Start |
عدم موفقیت در کالیبراسیون واحد ADC1 | بررسی ولتاژ پایهٔ مرجع VDDA بر روی میکروکنترلر و پایداری خط تغذیه. |
g_link_up == 0 |
بررسی رجیستر PHYCFGR در App_Poll |
نبود پالس اتصال فیزیکی روی لایهٔ PHY | کابل شبکه متصل نیست، پورت سوئیچ خاموش است یا جک شبکه آسیب دیده است. |
g_dhcp_status == DHCP_FAILED |
ماشین حالت در App_Poll |
پاسخ ندادن سرور DHCP یا سررسید مهلت 45 ثانیه | بررسی اتصال به روتر دارای سرور DHCP، مسدود نبودن پورتهای 67/68 یا تداخل مکآدرس. |
g_udp_oversize_drops > 0 |
تابع ServiceUdp |
ارسال بستهٔ دیتای UDP با طول بیش از 256 بایت | کلاینت باید طول پیامها را به 256 بایت محدود کند تا بستهها دور ریخته نشوند. |
سناریوی آزمون عملی روی شبکهٔ محلی ایزوله و مشاهدات مورد انتظار
برای آزمون برنامه در یک شبکهٔ آزمایشی ایزوله، مراحل مهندسی زیر پیشنهاد میشود:
- بررسی لینک فیزیکی: کابل اترنت را میان ماژول و سوئیچ شبکه وصل کنید. چراغهای روی جک RJ45 باید روشن شوند و متغیر
g_link_upدر حافظه مقدار 1 را نشان دهد. - آزمون اتصال اولیه (Ping): در حالت آدرس ایستا، کارت شبکهٔ کامپیوتر را روی رنج مشترک (مثلاً
192.168.50.10) قرار دهید و دستورping 192.168.50.50را اجرا کنید. پشتهٔ سختافزاری W5500 به بستههای اکوی ICMP پاسخ میدهد و دریافت پاسخ تاییدی بر صحت اتصال لایهٔ شبکه است. - آزمون سرور پژواک TCP: با ابزار
netcatبا دستورnc 192.168.50.50 5000به سرور وصل شوید. با ارسال رشتههای متنی، محتوا عینا بازگردانده شده و متغیرg_tcp_echo_bytesافزایش مییابد. - آزمون وبسرور: با مرورگر وب نشانی
http://192.168.50.50:8080/را باز کنید. صفحهٔ متنی حاوی وضعیت قرائت ولتاژ (مثلاًraw=... valid=1) نمایش داده شده و شمارندهٔg_http_responsesافزوده میشود. همواره دقت کنید ولتاژ متصل به PA1 بین 0 تا 3.3 ولت باشد و از اتصال ولتاژ 5V پرهیز شود. - آزمون سوکت UDP: یک دیتاگرام با طول کمتر از 256 بایت به پورت 5001 ارسال کنید؛ محتوا بازگردانده شده و
g_udp_packetsافزایش مییابد. ارسال بستههای حجیمتر باعث افزایشg_udp_oversize_dropsخواهد شد.
گزارش وضعیت اعتبارسنجی: تفکیک دقیق کامپایل بدون خطا از آزمون سختافزاری
کدهای ارائهشده در این درس حاصل کامپایل کامل و بدون هشدار پروژه با ابزار CMake و کامپایلر arm-none-eabi-gcc 13.2.1 تحت پرچمهای -O0 -g3 -Wall -Werror هستند (حجم تصویر فلش در حالت Debug برابر 27644 بایت و رم رزروشده 4168 بایت، و در حالت DHCP فلش 33984 بایت و رم 4800 بایت است). تاکید میگردد که اعتبارسنجی حاضر صرفاً بر مبنای صحت ساخت و شواهد قطعی کامپایل نرمافزار بوده و آزمونهای سختافزاری روی بورد فیزیکی، سنجشهای سیگنالی با اسیلوسکوپ یا تبادل ترافیک واقعی با شبکه در این گزارش انجام نگرفته است.

پرسشهای خودآزمایی
1. چرا در میکروکنترلر STM32F303 به جای پشتهٔ نرمافزاری LwIP از تراشهٔ سختافزاری W5500 استفاده شد؟
میکروکنترلر STM32F303CBT6 فاقد کنترلر اترنت داخلی (MAC) و رابطهای MII/RMII است. پیادهسازی پشتهٔ نرمافزاری LwIP نیازمند اتصال یک کنترلر MAC خارجی مجزا و تخصیص حجم بالایی از حافظه برای صف بستهها و بافرهای پردازشی است که روی میکروکنترلری با 32 KiB رم اصلی و 8 KiB حافظهٔ CCM (که به کنترلر DMA نیز متصل نیست) محدودیت جدی ایجاد میکند. تراشهٔ W5500 لایههای فیزیکی، MAC و پشتهٔ کامل پروتکلهای IPv4، TCP و UDP را به همراه بافرهای اختصاصی درون سیلیکون خود اجرا میکند و میکروکنترلر تنها تبادل دادههای کاربردی را روی گذرگاه SPI مدیریت مینماید.
2. چرا با وجود ادعای فرکانس 80 MHz در مشخصات W5500، فرکانس کلاک SPI در پروژه روی 4.5 MHz تنظیم شده است؟
فرکانس 80 MHz یک حداکثر فرکانس اسمی رابط تراشه است. در پاورقی جدول مشخصات زمانی دیتاشیت v1.1.0 قید شده که ارقام تاخیر زمانی تضمینشدهٔ قطعه بر مبنای فرکانس 33.3 MHz اندازهگیری شدهاند. از طرف دیگر، در STM32F303 فرکانس گذرگاه APB2 برابر 72 MHz است و کمترین ضریب تقسیم کلاک SPI برابر /2 است که بیشینهٔ تئوری 36 MHz به دست میدهد. با این حال، دستیابی به این سرعتهای بالا نیازمند طراحی دقیق امپدانس روی برد مدار چاپی است. برای جلوگیری از اعوجاج شکل موج و بروز خطای بیتی روی سیمهای جامپر در محیطهای کارگاهی، مقسم کلاک روی 16 قرار گرفته تا فرکانس اسمی 4.5 MHz حاصل شود؛ صحت تبادل و شکل موج همچنان باید روی مدار واقعی بررسی شوند.
3. چرا تابع DHCP_time_handler باید در یک تایمر مستقل (مانند وقفهٔ SysTick) اجرا شود و نباید در حلقهٔ اصلی main قرار گیرد؟
کتابخانهٔ ioLibrary در صورت تشخیص تداخل آدرس شبکه (IP Conflict)، وارد یک تاخیر فعال (Busy-wait) به مدت 2 ثانیه میشود. اگر تابع زمانسنجی در حلقهٔ اصلی main فراخوانی میشد، در هنگام وقوع این تاخیر، شمارندهٔ داخلی DHCP جلو نمیرفت؛ هرچند تیک میلیثانیهای HAL میتوانست همچنان ادامه داشته باشد که این وضعیت به بنبست قطعی در ماشین حالت DHCP منجر میگردید. در این پروژه، وقفهٔ SysTick هر 1 میلیثانیه اجرا میشود و App_Tick1ms پس از هر 1000 تیک، DHCP_time_handler را فراخوانی میکند. یک تایمر مستقل مناسب نیز میتواند همین شمارش 1 ثانیهای را تأمین کند؛ پیشروی زمان به فعال ماندن وقفه و کلاک مربوط وابسته است؛ ضمن آنکه در وقفه فقط شمارنده افزوده شده و هیچ عملیات ارتباطی با SPI انجام نمیشود.
4. بیت وقفهٔ Sn_IR_SENDOK چه نقشی در ارسال داده دارد و چرا نباید آن را به صورت دستی در کد کاربر پاک کرد؟
بیت Sn_IR_SENDOK نشان میدهد که قطعهٔ دادهٔ ارسالی TCP با موفقیت توسط طرف مقابل در سطح لایهٔ انتقال تایید (ACK) شده است. وبسرور پیش از فراخوانی تابع disconnect برای بستن اتصال، منتظر این بیت میماند تا مطمئن شود بستهها در مسیر شبکه نماندهاند. این بیت نباید به صورت دستی در کد برنامه پاک شود؛ زیرا تابع داخلی send() در کتابخانهٔ ioLibrary در فراخوانیهای بعدی خود وضعیت این پرچم را بررسی و مقدار آن را مدیریت میکند و پاک کردن دستی آن باعث به هم خوردن وضعیت داخلی درایور میشود.
5. در صورتی که یک کلاینت UDP بستهای بزرگتر از بافر 256 بایتی ارسال کند، برنامه چه واکنشی نشان میدهد؟
برنامه پس از فراخوانی recvfrom، با استفاده از تابع getsockopt و گزینهٔ SO_REMAINSIZE وضعیت بافر دریافت را بررسی میکند. از آنجا که پروتکل UDP یک پروتکل مبتنی بر بسته (Datagram) است و خرد کردن بستهها به چند بخش ساختار پیام را مخدوش میسازد، در صورت باقی ماندن دادهٔ مازاد، برنامه کل بسته را دور ریخته، شمارندهٔ g_udp_oversize_drops را افزایش میدهد و سوکت را میبندد و مجدداً باز میکند تا بافر دریافت کاملاً پاکسازی شود.
6. رفتار سیستم در صورت ناتوانی کلاینت DHCP در دریافت آدرس چیست و چرا بازگشت خودکار به آدرس ایستا انجام نمیشود؟
اگر کلاینت DHCP در فرآیند مذاکره با خطای عدم تخصیص مواجه شود و تابع DHCP_run وضعیت DHCP_FAILED را برگرداند، یا پس از گذشت 45 ثانیه هیچ آدرسی ثبت نگردد، تابع DHCP_stop اجرا شده و سرویسهای شبکه با صفر شدن g_net_ready غیرفعال میمانند. سیستم عمداً به آدرس ایستا سوئیچ نمیکند؛ زیرا در محیطهای شبکهٔ سازمانی یا صنعتی، تخصیص ناگهانی و بدون هماهنگی یک آدرس پیشفرض ممکن است موجب تداخل مخرب با سایر تجهیزات حاضر در زیرشبکه شود. تلاش مجدد نیازمند ریست سختافزاری سیستم خواهد بود.
تمرینها
تمرینهای تغییر کد، پیشنهاد آموزشیاند؛ نسخهٔ تغییریافته باید دوباره کامپایل و جداگانه روی برد بررسی شود.
1. تمرین 1: افزودن پیشوند به دادههای دریافتی در سرور پژواک TCP
با توجه به این که پروتکل TCP مبتنی بر جریان پیوستهٔ بایت (Byte-stream) است و مرز مشخصی برای پیامها حفظ نمیکند، منطق سرور پژواک روی پورت 5000 را به گونهای اصلاح کنید که به ازای هر قطعهٔ دادهٔ دریافتی (یا با جستجوی کاراکتر انتهای سطر n\)، پیشوند متنی '[ECHO]: ' را اضافه کرده و سپس آن را بازگرداند. مراقبت کنید که طول نهایی داده از سقف بافر فرستنده (768 بایت) فراتر نرود و در صورت رخداد ارسال ناقص (Partial Send)، آفست داده به درستی حفظ شود.
معیار بررسی: پروژه را کامپایل کرده و با ابزار netcat با پورت 5000 ارتباط برقرار کنید. بررسی کنید که با ارسال رشتههای آزمایشی، پیشوند به درستی افزوده شده و متغیرهای offset و g_tcp_echo_bytes بدون سرریز بافر بهروزرسانی شوند.
2. تمرین 2: افزودن وضعیت الایدی برد به پاسخ وبسرور
تابع BuildHttp را به گونهای گسترش دهید که علاوه بر مقادیر خام و اعتبارسنجی سنسور آنالوگ، وضعیت جاری الایدی روی برد (پایه PB6) را به صورت یک برچسب متنی (نظیر LED=ON یا LED=OFF) در بدنهٔ HTML تولید کند. محاسبات اندازه را به گونهای مدیریت نمایید که خروجی تابع snprintf از طول بافر body (256 بایت) و بافر نهایی کلاینت (768 بایت) فراتر نرود و سرآیند Content-Length دقیقاً برابر با طول رشتهٔ بدنه تنظیم شود.
معیار بررسی: صحت مقادیر تولیدی snprintf و عدم فعال شدن شرط خطای Fail(3) را در کد بازبینی کنید. اطمینان حاصل کنید که طول مشخصشده در Content-Length با تعداد بایتهای واقعی متن HTML تطابق دارد.
3. تمرین 3: مدیریت پیشرفتهٔ قطع کابل و بازیابی ماژول W5500
یک روال بازیابی پیشرفته طراحی کنید که اگر متغیر g_link_up برای مدت بیش از 30 ثانیه مداوم در وضعیت صفر (قطع فیزیکی کابل) باقی ماند، فرآیند راهاندازی مجدد را مدیریت کند. توجه داشته باشید که فراخوانی مستقیم App_Init بدون بستن سوکتهای قبلی و بدون ریست متغیرهای DHCP اشتباه است؛ برنامه باید ابتدا کلیهٔ سوکتهای باز را ببندد (ResetClient برای سوکتهای 0 و 1 و close برای سوکت 2)، پرچمهای آماده بودن شبکه و متغیرهای کنترلی DHCP را پاک کند، پالس ریست PB0 را اعمال نموده، بافرها و توابع بازخوانی را مجدداً مقداردهی کند و تایمر خطای لینک را ریست نماید تا سیستم در چرخهٔ ریست مداوم گرفتار نشود.
معیار بررسی: کد اصلاحشده را در هر دو پیکربندی Debug (آدرس ایستا) و DHCP کامپایل نمایید. بررسی کنید که ساختار ریست از باقی ماندن سوکتهای معلق یا استفاده از آدرس اجارهٔ منقضیشده پیشگیری کرده و متغیرهای زمانی پیش از راهاندازی مجدد بازنشانی شوند.
منابع و مبنای بررسی
مبنای فنی این درس، اسناد رسمی و نسخهٔ مشخص کتابخانهها و پروژهٔ قابل دریافت آن است. نسخهها و محدودیتها در راهنمای فایلهای همراه ثبت شدهاند.
دانلود پروژه و فایلهای همراه
دانلود بستهٔ کامل پروژه، خروجی ساخت، اسکریپتها و 6 تصویر آموزشی · راهنمای اجرا · پوشهٔ همین نسخه در گیتهاب
حجم بسته: 3,954,646 بایت. شناسهٔ SHA256: c3ec8e57686273d2c33ab74e93fb21c7831314b0fa0da4b142129433c0f3f3bc. گزارش ساخت و برگهٔ خالی ثبت آزمایش در بسته قرار دارد.