کنترلر صنعتی IoT روی STM32F103RET6 با embOS، مودم Quectel M66 (MQTT/FOTA)، RS-485 و دو سنسور SHT30. این سند خروجی ممیزی خطبهخط اپلیکیشن v1.8.0 و بوتلودر v1.7.0، تحلیل استاتیک، دادهی مصرف استک کامپایلر و تستهای زنده روی شبیهساز Renode است، و یک برنامهی سهمرحلهای برای بستن ریسکهای میدانی میدهد.
| موضوع | وضعیت فعلی | پیامد |
|---|---|---|
| بیلد Release | -O0، کتابخانهی embOS نسخهی Debug+Profiling، فقط -Wall | سرریزهای بافر پنهان میمانند؛ اپ ۱۱۵ کیلوبایت بهجای ۶۰ تا ۸۰ |
| تسکها | ۴ تسک، همه اولویت ۲ (round-robin)، استک ۲/۲/۴/۲ کیلوبایت | کار بلاککنندهی هر تسک زمان بقیه را میخورد؛ VLA روی استک ۲ کیلوبایتی |
| واچداگ | ۳ ثانیه، refresh از حدود ۶۰ نقطه (حتی داخل uart_debug_print)؛ بوتلودر واچداگ ندارد | تسک گیرکرده دیده نمیشود؛ HardFault در بوتلودر یعنی توقف دائمی |
| پیکربندی در فلش | رشتهی CSV بدون CRC، ۳۰ فراخوانی strtok بدون بررسی NULL، بازنویسی در هر بوت | رکورد ناقص = HardFault؛ فرسایش صفحه در چند روز |
| FOTA | CRC فقط برای هر فایل؛ بدون CRC کل ایمیج، بدون rollback؛ پرش کور بوتلودر | در حالتهای رایج نصب نمیشود یا اپ پاکشده میماند |
| حافظه | حدود ۴۰ از ۶۴ کیلوبایت RAM؛ heap و استک اصلی مشترک | حاشیهی کم برای بافرهای بزرگ و پیامهای بلند |
بازتولید شد. با دو IOX پیکربندیشده، یک FOTA کامل اجرا شد: ۲۱۷ فایل از FTP دانلود و در فلش staging نوشته شد، اپ {Installing...} فرستاد، ریست کرد و به سرور FOTA >> Successfully done گزارش داد. بوتلودر اما flash_fota_flag: 0 خواند، نوشت No New Program to write! و نسخهی قبلی را بالا آورد. سرور فکر میکند دستگاه آپدیت شده؛ نشده است.
| # | مشکل | محل | چطور رخ میدهد | اصلاح | زمان |
|---|---|---|---|---|---|
| A1 | پارس متادیتا بدون بررسی NULL؛ فقط ۸ بایت اول اعتبارسنجی میشود؛ فرمت نویسنده و خواننده ناسازگار | flash.c:439-675 boot main.c:415-645 | قطع برق حین نوشتن در هر بوت؛ رکورد بلندتر از ۲۰۰ بایت (SET_LOG و SET_MSV)؛ iox_num ۱ یا ۲ (بدون شناسه) یا بیشتر از ۳ (بدون شاخه)؛ در بوتلودر while(1) بدون واچداگ | ساختار باینری نسخهدار + CRC32، دو صفحهی A/B، fallback به پیشفرض، یک فایل مشترک اپ/بوتلودر | ۲ روز |
| A2 | سرریز بافر استک در مسیر بوت | flash.c:167-183, 506, 660 boot 482, 631 | bf[32] در هر بوت ۴۳+ بایت میگیرد؛ bf[50] با recipe تا ۱۰۰؛ received_data[130] با حلقهی بیحد؛ فقط با چیدمان -O0 زنده است | snprintf/strlcat، حلقههای کراندار، terminator صریح، -Wstringop-overflow | ۱ روز |
| A3 | لیست سنسور قبل از اعتبارسنجی در فلش نوشته میشود | modbus.c:150-166 flash.c:170-186 | پیام ["05","A1","A2"] از سرور = strncpy(…,NULL) در بوت بعدی = بوتلوپ تا فلش مجدد؛ نوشتن بیرون از پیام داخل ring buffer صف | پارس به ساختار RAM با حد، اعتبارسنجی، سپس ذخیره با CRC | ۰٫۵ روز |
| A4 | فرسایش فلش صفحهی پیکربندی | proc.c:37, boot 1159 io.c:598, quectel.c:3656 | پاک/نوشتن در هر بوت و هر فرمان؛ بدون سیمکارت هر ۵ تا ۱۰ ثانیه یک سیکل → ۱۰ هزار سیکل (حداقل دیتاشیت) در حدود یک روز | نوشتن فقط در صورت تغییر؛ شمارندهها در رجیستر BKP یا .noinit؛ دو صفحه با شمارهی توالی؛ عدم ذخیرهی toggle سیمکارت | ۱ روز |
| A5 | بوتلودر بدون اعتبارسنجی میپرد و واچداگ ندارد | boot main.c:1166-1209 | شکست کپی FOTA: اپ پاک شده و پرچم پاک میشود → پرش به 0xFFFFFFFF؛ SCB->VTOR = مقدار SP؛ __disable_irq تا اپ میماند و timeoutهای HAL نمیگذرند | بررسی SP/reset vector، IWDG در بوتلودر، VTOR درست، __enable_irq، CRC ایمیج قبل از پاک کردن، حفظ پرچم تا معتبر شدن اپ | ۲ روز |
| A6 | اشارهگرهای آویزان اعتبار FTP | proc.c:1012-1024 quectel.c:3123-3245 | اشاره به داخل ring buffer صفی که پاک میشود؛ رشتهی بدون پایان با strcat → خرابی استک Task_Quectel | strlcpy به بافر استاتیک | ۱ ساعت |
| A7 | VLA روی استک ۲ کیلوبایتی Task_Operator | proc.c:569, 689 quectel.c:610, 1339 | با بیش از ۲۰ سنسور char bf[len] از ۲۰۴۸ میگذرد → OS_Error → IWDG → بوتلوپ؛ payload_buffer[1024] و fota_buff[975] از پیام ۱۵۰۰ بایتی | حذف VLA؛ ساخت پیام در بافر استاتیک؛ اندازهی استک از .su + ۲۵٪ | ۰٫۵ روز |
| A8 | پاک کردن ۱۰۰ صفحه در یک فراخوانی در برابر واچداگ ۳ ثانیه | flash.c:46-82 quectel.c:3819-3823 | ۲ تا ۴ ثانیه توقف هسته؛ ریست وسط FOTA؛ بازنویسی بوتلودر از داخل اپ بدون fallback = بریک با قطع برق | پاک کردن صفحهبهصفحه با refresh؛ ممنوعیت آپدیت بوتلودر از اپ تا طراحی دومرحلهای | ۰٫۵ روز |
| A9 | کپی پاسخ RS-485 بدون حد در اسلات ۲۲ بایتی | modbus.c:101-136 io.c:413 | نویز باس modbus_num را خراب میکند → حلقه روی ۲۵۵ سنسور → سرریز all_buffer[1800]؛ strstr()+1 روی NULL | کران کپی، اعتبارسنجی LRC/CRC پاسخ، بررسی NULL | ۰٫۵ روز |
| # | مشکل | محل | اصلاح |
|---|---|---|---|
| B1 | پارس JSON سرور با آفست ثابت (msg[40]، msg+26، &msg[20])؛ io_set_out بدون حد روی Out_Array[32] | proc.c:421-455, io.c:533, modbus.c:153 | توکنایزر jsmn + جدول فرمان با فیلدهای تایپدار و کراندار |
| B2 | ۶ پارسر پاسخ مودم و {SET_LOG} روی NULL دیرفرنس میکنند → HardFault → ۱ تا ۲ دقیقه آفلاین | quectel.c:961, 1020, 1807, 1852; proc.c:921 | بررسی NULL، خروج ایمن |
| B3 | data_index در FOTA هرگز ریست نمیشود؛ FOTA دوم بدون ریبوت در آفست غلط مینویسد | proc.c:16, quectel.c:3756 | ریست در qctl_ftp_reset؛ کران fota_data_bytes |
| B4 | دریافت UART با DMA یکبایتی و خواندن دوبارهی DR؛ حین پاک/نوشتن فلش ۱۰۰ تا ۲۳۰ بایت گم میشود؛ تایمر ۱۰ میلیثانیهای بدون critical section | main.c:532-569, 139-151 | ring buffer دایرهای ۲ کیلوبایت + وقفهی IDLE، جداسازی فریم در تسک |
| B5 | ماشینهای حالت MQTT و FTP بدون timeout؛ تنها فرار تایمر ۱۸۵ ثانیهی «هیچ بایتی» است که هر URC آن را زنده نگه میدارد؛ FOTA گیرکرده ذخیرهی پیکربندی را در ۱۲ نقطه غیرفعال میکند | quectel.c:2744-3618 | deadline هر درخواست + نردبان retry → CFUN → PWRKEY → reset |
| B6 | یکپارچگی FOTA فقط XOR16 + CRC16 هر فایل؛ بدون هدر، CRC32، بررسی اندازه/مقصد/نسخه، rollback؛ staging (۲۰۴ کیلوبایت) کوچکتر از اسلات اپ (۲۲۵) بدون بررسی | proc.c:1004-1008 | هدر ایمیج + CRC32 + boot-confirm/rollback (مرحلهی سوم) |
| B7 | هر ERROR یا +CME ERROR ناشناخته = AT+CFUN=1,1 و ۳۰ تا ۶۰ ثانیه قطعی | quectel.c:2003, 2034 | جفتکردن درخواست/پاسخ در لایهی AT |
| B8 | بعد از هر ریست خروجیها و رلهها صفر میشوند تا سرور دوباره SET_OUT بفرستد | defines.c:201, io.c:166 | تصمیم محصولی: حفظ در BKP یا مستندسازی fail-safe-off |
| B9 | درایور فلش از سه تسک بدون mutex؛ مقدار بازگشتی نادیده گرفته میشود → لیست سنسور نیمهکاره → A3 در بوت بعدی | modbus.c:165, proc.c:873 | mutex فلش؛ تغییرات پیکربندی از طریق تسک مالک |
| B10 | واچداگ نمیتواند تسک گیرکرده را ببیند (refresh داخل توابع کتابخانهای)؛ led_blink حلقهی بینهایت HAL_Delay | quectel.c:2266 | supervisor با بیت زندهبودن هر تسک |
| B11 | چاپ دیباگ بلاککننده (۱۷ میلیثانیه بهازای ۱۰۰ کاراکتر) داخل callback تایمرهای embOS و ۴۰۰ نقطهی دیگر؛ چرخههای «۳۰ ثانیهای» در شبیهساز ۳۸ تا ۵۵ ثانیه شدند | defines.c:593, main.c:142-210 | لاگر غیربلاککننده با DMA و سطح لاگ |
| B12 | مسیر merge-recipe: strncpy تا ۱۷۰۰ کاراکتر در بافر ۵۰۰ بایتی؛ malloc از heap مشترک با استک اصلی | microchipMonitoring.c:860 | حذف مسیر آزمایشی یا کراندار کردن |
| B13 | I2C بدون بازیابی باس؛ timeout ۵۰۰ و ۱۰۰۰ میلیثانیه؛ HAL_Delay(20) داخل تسک | sht3x.c:69-105 | bus recovery با کلاک SCL؛ timeout کوتاه؛ OS_TASK_Delay |
| B14 | درخواست Modbus بدون LRC/CRC؛ پاسخ اعتبارسنجی نمیشود؛ پنجرهی ۱۰۰ میلیثانیه پاسخ دیر را به سنسور بعدی نسبت میدهد؛ منطق terminator در RTU مرده | modbus.c:29-136, proc.c:825 | لایهی Modbus با checksum و timeout هر اسلیو و تطبیق آدرس+تابع |
| B15 | موارد کوچک: qctl_check_crc با len<4؛ bf[128] سرریز؛ تقسیم بر صفر در cm_get_chip_temp؛ df_hex_hexstring(7) غلط | quectel.c:564, proc.c:712, microchipMonitoring.c:444, defines.c:485 | رفع نقطهای |
-O0؛ آبجکتهای stale (e2.*)؛ بدون -Wextra -Wshadow -Wvla -Wformat=2؛ سورس تحت git نیست؛ نسخهی اپ و بوتلودر ناهمخوان.io_write_io_expanders، ۵ بلوک انتشار MQTT؛ لاگ روی فایل، SMS، cJSON آزمایشی و my_malloc کامپایل میشوند.extern؛ include دایرهای؛ دو فرمت واگرای متادیتا؛ رمزها در سورس (defines.h:193, 237)؛ SSL موجود ولی خاموش.یافتههای تحلیل استاتیک (cppcheck): modbus.c:130 شرط همیشه غلط؛ defines.c:485 مقدار برگشتی غلط؛ microchipMonitoring.c:74 malloc بدون بررسی؛ سایهاندازی متغیرها در io.c:405 و quectel.c:578, 1812, 3837 (شامل former_percent که گزارش پیشرفت را عوض میکند)؛ کد غیرقابلدسترس بعد از NVIC_SystemReset. برای اجرا: -D__GNUC__=10 -D__arm__ لازم است وگرنه cppcheck در cmsis_compiler.h متوقف میشود.
هر ماژول مالک وضعیت خودش است، API کوچک دارد و بین تسکها فقط با mailbox/queue متعلق به مصرفکننده صحبت میشود. اولویت تسکها Sep > Quectel > Operator > Io بهجای round-robin. فقط supervisor واچداگ را میزند. هیچ I/O بلاککنندهای در callback تایمر.
| # | تغییر | میبندد | زمان | تأیید در شبیهساز |
|---|---|---|---|---|
| 1 | پارسرهای متادیتا/سنسور: بررسی NULL هر توکن، کران هر کپی، خواندن به اندازهی طول واقعی، terminator صریح؛ عیناً در بوتلودر؛ رفع ناسازگاری فیلدهای IOX/FOTA | A1 A2 A3 | ۱٫۵ روز | بوت با متادیتای بریده، iox_num=1/2/4، SET_SEN با تعداد غلط، FOTA با ۲ IOX |
| 2 | بوتلودر: اعتبارسنجی وکتور تیبل قبل از پرش، فعال کردن IWDG، اصلاح VTOR و PRIMASK، حفظ پرچم FOTA تا معتبر شدن اسلات، بررسی اندازه قبل از پاک کردن | A5 | ۱ روز | کپی FOTA با ایمیج بزرگتر از اسلات؛ قطع برق (pause+reset) وسط کپی |
| 3 | کپی اعتبار FTP به بافر استاتیک؛ ریست data_index؛ کران fota_data_bytes | A6 B3 | ۰٫۲۵ روز | دو FOTA پشتسرهم بدون ریبوت |
| 4 | نوشتن متادیتا فقط در صورت تغییر؛ حذف نوشتنهای زمان بوت؛ عدم ذخیرهی toggle سیمکارت؛ rate-limit برای CPIN NOT INSERTED | A4 | ۰٫۵ روز | یک ساعت بدون سیمکارت، شمارش سیکلهای پاک کردن |
| 5 | حذف VLA؛ اندازهی استاتیک بافرها؛ کران کپیهای payload_buffer/fota_buff/پاسخ Modbus؛ بررسی NULL در ۶ پارسر URC و {SET_LOG} | A7 A9 B2 B12 | ۱ روز | ۳۲ سنسور؛ payload بدفرم روی هر topic؛ نویز RS-485 |
| 6 | پاک کردن صفحهبهصفحه با refresh واچداگ؛ غیرفعال کردن آپدیت بوتلودر از داخل اپ | A8 | ۰٫۲۵ روز | FOTA کامل با IWDG فعال |
| 7 | بیلد: -Os، -Wextra -Wshadow -Wvla -Wformat=2 -Wstringop-overflow، لینک embOS نسخهی Release، پاک کردن آبجکتهای stale، git، baseline cppcheck | C1 | ۰٫۵ روز | بوت و ۳۵ سناریوی موجود با بیلد جدید |
| 8 | سناریوهای شبیهساز برای موارد ۱ تا ۶ در cm_sim/tests | تأیید | ۱ روز | — |
پارسرها بعد از جداسازی توابع خالصاند: cfg، proto، framer خط AT، framing Modbus، هدر/CRC FOTA، دیکد hex در io_set_out. با Unity/Ceedling یا CMake+ctest و mock کوچک HAL_*. قاعده: هر تابعی که بایت سرور یا مودم را لمس میکند تستهای خالی/بریده/بزرگ/تعداد غلط دارد.
-Werror و هشدارهای کامل؛ cppcheck با --error-exitcode=1؛ clang-tidy bugprone-*,cert-*.su از بودجهی تسک بیشتر شد یا dynamic داشت بیلد را شکست میدهدcm_sim| سناریو | پوشش |
|---|---|
| بوت با هر گونهی متادیتا (پیشفرض، ۱/۲/۳/۴ IOX، recipe بلند، ۳۲ سنسور، صفحهی بریده) | A1 A2 A3 A7 |
| قطع MQTT، خروج سیمکارت، بدون سیمکارت یک ساعت با شمارش سیکل پاک کردن فلش | A4 B5 B7 |
| FOTA: مسیر موفق، chunk خراب، توقف FTP، قطع برق در هر فاز | A5 A8 B3 B6 |
| payload بزرگ و بدفرم روی هر topic | B1 B2 |
| نویز RS-485 و پاسخ دیر | A9 B14 |
| soak ۲۴ ساعته: pace، شمارندهی واچداگ، high-water حافظه، دبی UART5 | B10 B11 |
شبیهساز الان ELF بدون تغییر را با سرعت واقعی اجرا میکند، مودم به بروکر واقعی وصل است، RS-485 و SHT30 و IWDG مدل شدهاند و ۳۵ سناریو بهعلاوهی FOTA سرتاسری آماده است.
تصمیم محصولی باز: رفتار خروجیها و رلهها بعد از ریست (B8): حفظ آخرین وضعیت یا fail-safe-off. این تصمیم باید قبل از مرحلهی دوم گرفته شود.