چگونه هنگام کارهای دیگر، دادهٔ سریال را دریافت کنیم؟
فرض کنید رایانه چند فرمان میفرستد، اما حلقهٔ اصلی میکروکنترلر مشغول کار دیگری است. اگر فقط وقتی به تابع دریافت میرسیم ورودی را بخوانیم، دادههای رسیده در فاصلهٔ میان 2 دریافت ممکن است از دست بروند. در این درس، وقفه بایت را تحویل میگیرد، آن را در صف RAM میگذارد و حلقهٔ اصلی بعداً فرمان را پردازش میکند.
پیشنیازها درس 10، NVIC و اولویت وقفه و درس 12، ارتباط UART هستند. هدف، جداسازی دریافت از پردازش و تشخیص افت داده است؛ هیچ بافر محدودی برای هر نرخ و هر توقفی تضمین دریافت بدون افت نمیدهد.
از STM32F303CBT6 روی GB100ST V2.0، پروگرامر SWD و مبدل USB به UART با منطق 3.3V استفاده میکنیم. TX مبدل به PA10/RX، RX مبدل به PA9/TX و GNDها مشترک وصل میشوند؛ رابط RS-232 مستقیم یا خروجی 5V را جای منطق 3.3V نگذارید. تغذیه و جهت پایهها را با شماتیک تطبیق دهید.
وضعیت: هر 2 نمونهٔ تکبایتی و ReceiveToIdle_IT بدون هشدار کامپایل شدهاند؛ آزمون برد، پورت سریال و تزریق خطا هنوز انجام نشده است.

برای خواندن یک بایت چقدر وقت داریم؟
USART1 این پروژه روی PA9/PA10 با AF7، سرعت 115200، قالب 8N1 و Oversampling برابر 16 است. 8N1 یعنی 8 بیت داده، بدون parity و 1 بیت پایان؛ همراه بیت شروع، هر نویسه 10 بیت روی خط میگیرد. منبع USART1 صریحاً PCLK2 برابر 72 MHz است؛ کلاک پایهٔ دوره HSE برابر 8 MHz، PLL با ضریب 9، AHB بر 1 و APB1/APB2 بهترتیب بر 2 و 1، با Flash برابر 2 WS باقی میماند.
Character time = 10 / 115200 ≈ 86.806 µs
Ideal byte rate = 115200 / 10 = 11520 byte/s
بیتها ابتدا در ثبات شیفت جمع و دادهٔ کامل در RDR آماده میشود؛ RXNE آمادهبودن داده برای خواندن را نشان میدهد. اگر پیش از خواندن دادهٔ قبلی، دادهٔ بعدی آماده شود، ORE یا Overrun رخ میدهد. در جریان پیوستهٔ همین قالب، فاصلهٔ اسمی پایان 2 نویسه حدود 86.806 µs است؛ این عدد مهلت تضمینشدهٔ نرمافزار از هر نقطهٔ دلخواه نیست.
طولانیشدن کار اصلی بهتنهایی ORE را قطعی نمیکند: باید داده برسد و دریافت سختافزاری بهموقع سرویس نشود. مشکل دریافت مسدودکننده، فاصلهٔ میان فراخوانیهاست؛ در دریافت وقفهای این سرویس به ISR سپرده میشود، ولی تأخیر IRQ، ماسککردن وقفه و فاصلهٔ آمادهسازی مجدد هنوز محدودیتاند.
وقفه بایت را میگیرد؛ حلقهٔ اصلی معنا را میفهمد
HAL_UART_Receive_IT درخواست دریافت تعداد معینی بایت را آغاز میکند. برای نمونهٔ نخست، تعداد 1 است. هندلر تولیدشدهٔ USART1_IRQHandler تابع HAL_UART_IRQHandler را صدا میزند و پس از کاملشدن درخواست، HAL به HAL_UART_RxCpltCallback میرسد. پایان این درخواست، دریافت نامحدود نیست؛ باید درخواست بعدی آماده شود.
در Core/Src/main.c، بلوک موجود USER CODE 4 این تابع واقعی قرار دارد. ماکروی LESSON13_IDLE_IT در نمونهٔ نخست 0 و در نمونهٔ دوم 1 است:
static HAL_StatusTypeDef Start_Rx(void)
{
#if LESSON13_IDLE_IT
return HAL_UARTEx_ReceiveToIdle_IT(&huart1, idle_chunk, sizeof(idle_chunk));
#else
return HAL_UART_Receive_IT(&huart1, &rx_byte, 1U);
#endif
}
در بلوک USER CODE 2، فراخوانی نخست Start_Rx بررسی میشود و شکست آن به Error_Handler میرود. IRQ مربوط USART1 در CubeMX فعال و تقدم آن در GROUP4 برابر 2 است؛ این تنظیم به NVIC_IPR میرود. Baud/قالب به USART_BRR و USART_CR1/CR2، و شروع دریافت HAL به وضعیت handle و بیتهای وقفهٔ USART مرتبط است. حلقهٔ اصلی در USER CODE 3 بهترتیب Service_Recovery، برداشتن بایت از صف و Feed_Byte را اجرا میکند؛ تابع تولیدی را دوباره تعریف نکنید.
کالبک زیر عین همان بلوک USER CODE 4 است. نخست خطا بررسی، سپس بایت در صف گذاشته و وضعیت آمادهسازی مجدد کنترل میشود:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1)
{
uint32_t errors = HAL_UART_GetError(huart);
if (errors != HAL_UART_ERROR_NONE)
{ uart_error_flags |= errors; recovery_pending = 1U; return; }
rx_bytes++;
Ring_Push(rx_byte);
if ((recovery_pending == 0U) && (Start_Rx() != HAL_OK))
{
rearm_failures++;
recovery_pending = 1U;
}
}
}
این گزیدهها مستقل نیستند؛ اعلانهای PD/PV/PFP، راهاندازی و سایر تابعها در پروژهٔ کامل همراه وجود دارند. در ISR چاپ، ارسال مسدودکننده و تجزیهٔ خط نداریم. rx_bytes شمار بایتهای پردازششده در کالبک است، حتی اگر صف پر باشد؛ آن را شمار بایتهای تحویلدادهشده به فرمانخوان ندانید.

بافر حلقوی: یک تولیدکننده، یک مصرفکننده
صف حلقوی آرایهای است که پس از خانهٔ آخر به اول برمیگردد. ISR تنها تولیدکننده است و head را جلو میبرد؛ حلقهٔ اصلی تنها مصرفکننده است و tail را جلو میبرد. نام این الگو SPSC است. آرایهٔ 256 بایتی یک خانه را خالی میگذارد تا حالت پر از خالی قابل تشخیص باشد؛ ظرفیت مفید 255 بایت است.
Next head = (head + 1) & 255
Full: next head == tail
Initially empty backlog = 255 / 11520 ≈ 22.135 ms
ماسک 255 برای همین اندازهٔ توان 2 درست است. دو تابع زیر عین کد کامپایلشده در USER CODE 4 هستند:
static void Ring_Push(uint8_t byte)
{
uint32_t next = (rx_head + 1U) & (RX_RING_SIZE - 1U);
if (next == rx_tail) { ring_drops++; loss_epoch++; return; }
rx_ring[rx_head] = byte;
__DMB();
rx_head = next;
}
static int Ring_Pop(uint8_t *byte)
{
uint32_t tail = rx_tail;
if (tail == rx_head) { return 0; }
__DMB();
*byte = rx_ring[tail];
__DMB();
rx_tail = (tail + 1U) & (RX_RING_SIZE - 1U);
return 1;
}
شاخصها uint32_t همتراز و volatile هستند. در این الگوی مشخص تکهستهای Cortex-M4 با CMSIS/GCC، DMB ترتیب دسترسیهای حافظه را حفظ میکند: داده پیش از انتشار head نوشته، و خواندن پیش از آزادکردن خانه با tail کامل میشود. این طراحی را به صف چندنویسنده یا تضمین عمومی همگامسازی C11 تعمیم ندهید؛ volatile بهتنهایی قفل یا اتمیککنندهٔ عملیات مرکب نیست.
22.135 ms فقط حجم انباشت اسمی 255 نویسه با صف ابتدا خالی و ورود پیوسته است؛ «زمان مجاز تضمینشدهٔ توقف main» نیست. اشغال قبلی صف، زمان ISR، آمادهسازی مجدد، پردازش و رفتار فرستنده اثر دارند. ارسال پاسخ در main با timeout تنظیمشدهٔ 100 ms انجام میشود؛ این زمان میتواند از بودجهٔ صف بیشتر باشد. پس برنامه برای هر burst دلخواه بدون افت نیست.
وقتی صف پر باشد، Ring_Push بایت تازه را کنار میگذارد و ring_drops و loss_epoch را افزایش میدهد. main تغییر epoch را میبیند، دادهٔ صف و خط ناقص را دور میریزد و تا پایان خط دوباره همتراز میشود. discarded_on_resync شمار دادههای اضافی دورریختهشده است. این بازیابی عمداً داده از دست میدهد؛ با ORE سختافزاری یکی نیست.

ReceiveToIdle_IT چه تفاوتی دارد؟
در نمونهٔ gebrabit_lesson13_idle_it_v2، همان Start_Rx درخواست HAL_UARTEx_ReceiveToIdle_IT را برای آرایهٔ 32 بایتی idle_chunk آغاز میکند. IDLE به سکوت خط پس از دریافت مربوط است؛ در قالب این درس، زمان یک فریم حدود 86.806 µs است. این رویداد، قرارداد پایان بستهٔ کاربردی نیست؛ فرستنده میتواند وسط یک فرمان هم مکث کند.
پس از رویداد IDLE یا کاملشدن 32 بایت، کالبک HAL_UARTEx_RxEventCallback تعداد Size را میگیرد. کد واقعی آن ابتدا خطا و محدودهٔ Size از 1 تا 32 را بررسی، همان تعداد بایت را به صف منتقل و درخواست بعدی را دوباره آغاز میکند. هر رویداد لزوماً یک فرمان کامل نیست؛ تجزیهٔ فرمان در main میماند.
این API در HAL ثابت همین F3 وجود دارد و با وقفه کار میکند، نه DMA. رسیدگی به بایتها هنوز کار CPU است و میان پایان درخواست و آمادهشدن درخواست بعدی فاصله باقی میماند. دستهبندی 32 بایتی، اثبات حذف همهٔ افتها یا مستقلشدن دریافت از CPU نیست.

خطا را ثبت کنیم، سپس در main بازیابی کنیم
| پرچم | معنا و محدودیت |
|---|---|
| ORE | دادهٔ قبلی پیش از آمادهشدن دادهٔ بعدی خوانده نشده؛ خطای دریافت سختافزاری |
| FE | بیت پایان معتبر تشخیص داده نشده؛ ناهماهنگی قالب/نرخ یا اختلال از علتهای ممکناند |
| NE | تشخیص نویز در نمونهبرداری دریافت |
| PE | عدم تطابق parity؛ در 8N1 این پروژه parity خاموش است و آزمایش PE انجام نشده |
پرچمها به USART_ISR و پاککردن آنها به USART_ICR مربوطاند. رفتار HAL را از stm32f3xx_hal_uart.c همان نسخه میخوانیم: ORE در دریافت IT میتواند انتقال جاری را متوقف کند؛ خطاهایی مانند FE/NE/PE در بعضی مسیرهای غیرمسدودکننده فقط گزارش میشوند. برنامهٔ این درس برای همهٔ خطاهای گزارششده محافظهکارانه بازیابی را به main واگذار میکند.
چرا در کالبک تکمیل، خطا را قبل از Start_Rx بررسی کردیم؟ مسیر IRQ خطادار HAL ممکن است RxISR و کالبک تکمیل را پیش از تصمیم نهایی خطای مسدودکننده اجرا کند. آغاز دریافت تازه ErrorCode را پاک میکند. guard موجود مانع پوشاندن شواهد خطای جاری با rearm زودهنگام میشود؛ نمونهٔ IDLE نیز همین guard را دارد.
این کالبک واقعی USER CODE 4 فقط شمارنده و پرچم را ثبت میکند:
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1)
{
uart_errors++;
uart_error_flags |= HAL_UART_GetError(huart);
recovery_pending = 1U;
}
}
uart_errors تعداد اجرای کالبک خطاست و uart_error_flags مجموعهٔ بیتهای دیدهشده؛ هیچکدام شمار دقیق بایتهای خراب نیستند. rearm ناموفق نیز rearm_failures را زیاد و recovery_pending را تنظیم میکند. پرچم pending درخواست کار است و مقدار آن شمار همهٔ خطاها نیست.
تابع Service_Recovery در main، PRIMASK را ذخیره و IRQهای قابل ماسک را موقتاً میبندد. در صورت درخواست بازیابی، HAL_UART_AbortReceive در مسیر بدون DMA همین پروژه دریافت را لغو، پرچمهای ICR را پاک و دادهٔ ماندهٔ دریافت را با RQR flush میکند. سپس صف و خط ناقص کنار گذاشته، دریافت بازآغاز و PRIMASK قبلی بازیابی میشود. شاخهٔ بررسی خطای Abort در سورس دفاعی است؛ شاهدی نیست که مسیر بدون DMA حاضر حتماً HAL_ERROR تولید میکند.
این توقف کوتاه برای حفاظت از بازنشانی دادههای مشترک است؛ در آن printf، انتظار وقفه یا HAL_Delay نداریم. بازیابی، بازهمترازشدن پس از ازدسترفتن داده است و ممکن است چند فرمان را کنار بگذارد؛ پروتکل ارسال مجدد یا تضمین تحویل نیست.

فرمان خطی را بیرون از ISR بخوانیم
Feed_Byte در main بایتهای صف را به خط تبدیل میکند. آرایهٔ 96 بایتی حداکثر 95 نویسه بهعلاوهٔ NUL میگیرد. CR، LF و CRLF پایان خطاند و CRLF یک خط میسازد. ورودی غیرچاپی یا بیشازحد بلند تا پایان خط کنار گذاشته میشود تا رشتهٔ ناقص بهعنوان فرمان اجرا نشود.
sum8 = sum(ASCII prefix before the final comma) modulo 256
ACK = A,<sequence>,<sum8> + CRLF
شمارهٔ توالی دهدهی بدون علامت 32 بیتی است؛ payload حداکثر 64 رقم hex برای 32 بایت دارد. بیشترین خط معتبر پروتکل 80 نویسه بدون EOL و 82 بایت با CRLF است. حد ذخیرهسازی 95 نویسه و حد طول پروتکل دو محدودیت متفاوتاند. ابزار میزبان checksum درست را میسازد؛ مقدار حدسی وارد نکنید.
Process_Line محدودهٔ عدد، شکل hex و sum8 را بررسی میکند؛ خط معتبر accepted_lines را زیاد، PB6 را Toggle و ACK را در main ارسال میکند. مجموع 8 بیتی برخورد دارد؛ CRC، امنیت یا تشخیص همهٔ خرابیها نیست. sequence_discontinuities ناپیوستگی انتقالهای معتبر را میشمارد، نه تعداد دقیق بایتهای گمشده. ACK نیز بهتنهایی تحویل همهٔ دادههای ورودی را ثابت نمیکند.
آزمایش را از نرخ کنترلشده آغاز کنید
- روی رایانه مدل و self-test ابزار میزبان را طبق README اجرا کنید. این کار پورت سریال باز نمیکند و آزمون UART نیست.
- پیش از پروگرام، نام نمونه، 115200/8N1، ظرفیت 255 و شمار بسته/seed/window را در کاربرگ ثبت کنید. ابتدا window برابر 1 باشد تا میزبان برای پاسخ هر فرمان منتظر بماند.
- پروژهٔ انتخابی را بسازید و پروگرام کنید؛ مبدل 3.3V با TX/RX متقاطع و زمین مشترک را وصل کنید. برای آزمون واقعی، وابستگی pyserial و پورت درست را در محیط خود تعیین کنید.
- ابزار میزبان را برای 100 فرمان اجرا و ACKهای گمشده، تکراری یا نامعتبر را با شمارندههای firmware تطبیق دهید. نتیجهٔ موفق فقط به شرایط ثبتشدهٔ همین آزمون محدود است.
- برای burst، window و الگوی ارسال را صریح افزایش دهید و جدا ثبت کنید. سرریز صف، rearm failure و خطای سختافزاری را از هم جدا نگه دارید؛ «سریعبودن main» بهتنهایی اثبات عدم افت نیست.
ORE را با پرشدن صف اشتباه نگیریم
برای آزمایش پیشنهادی ORE، با روش کنترلشدهٔ دیباگر فقط IRQ مربوط USART1 را هنگام ورود داده بیش از ظرفیت سختافزاری، موقتاً ماسک و سپس وضعیت قبلی آن را بازگردانید. SysTick را متوقف نکنید تا timeoutهای HAL امکان پیشروی داشته باشند. مقدار uart_error_flags، uart_errors، recoveries و پذیرش فرمان معتبر بعدی را ثبت کنید؛ این آزمایش در تحویل حاضر انجام نشده و کد C تازهٔ آزمایشنشده به پروژه اضافه نمیکنیم.
برای پرشدن صف نرمافزاری، ISR میتواند RDR را بهموقع بخواند ولی main مصرف نکند؛ این حالت الزاماً ORE نیست. توقف 30 ms همراه فقط 200 بایتِ ورودی، اگر صف ابتدا خالی باشد، برای پرکردن ظرفیت 255 کافی نیست. نتیجه را از تعداد واقعی ورودی و اشغال اولیه پیشبینی کنید، نه فقط طول توقف.

اگر آزمون مطابق انتظار نبود
| نشانه | مسیر واقعی بررسی |
|---|---|
| فقط نخستین بایت میرسد | Start_Rx نخست، USART1_IRQHandler و HAL_UART_IRQHandler، guard خطا و rearm_failures را بررسی کنید؛ انتقال یکبایتی خودکار نامحدود نیست |
| ring_drops زیاد میشود | صف پر شده و Ring_Push بایت تازه را کنار گذاشته؛ TX اصلی با timeout برابر 100 ms و مصرف main را بررسی کنید، نه فقط baud |
| uart_error_flags دارای ORE است | منبع سختافزاری و تأخیر سرویس IRQ را بررسی کنید؛ Service_Recovery عمداً صف و خط را flush میکند |
| recoveries زیاد ولی ACK کم است | خطا/rearm باعث بازیابی میشود و تا EOL داده کنار گذاشته میشود؛ زمان و شکل خطوط پس از خطا مهم است |
| invalid_lines یا line_overflows افزایش دارد | محدودهٔ طول، چاپیبودن ورودی، hex،sum8 و پایان خط را با ابزار میزبان تطبیق دهید |
| tx_failures بالا میرود | snprintf نامعتبر/بیشازحد یا HAL_UART_Transmit ناموفق این شمارنده را افزایش میدهد؛ شمارنده بهتنهایی علت بازگشتی HAL را ثبت نمیکند |
خودآزمایی
پرسش 1: چرا صف 256 بایتی فقط 255 بایت جا دارد؟
پاسخ: یک خانه خالی میماند تا head برابر tail فقط معنای خالی بدهد. شرط پر، برابرشدن head بعدی با tail است.
پرسش 2: ORE با ring_drops چه تفاوتی دارد؟
پاسخ: ORE از نخواندن بهموقع دریافت سختافزاری میآید؛ ring_drops یعنی ISR بایت را تحویل گرفته اما صف RAM پر بوده است.
پرسش 3: چرا ISR فقط صف را پر و rearm میکند؟
پاسخ: کار طولانی زمان سرویس درخواستهای بعدی را زیاد میکند. چاپ، delay و parser در ISR این طرح نیستند؛ افت پیامد قطعی هر بار نیست ولی خطر و محدودیت زمانی را افزایش میدهند.
پرسش 4: آیا IDLE پایان فرمان است یا DMA دارد؟
پاسخ: هیچکدام الزاماً درست نیست. سکوت میتواند وسط فرمان باشد و این نمونه از API وقفهای IT استفاده میکند؛ جداکنندهٔ خط را parser تشخیص میدهد.
پرسش 5: چرا خطا را پیش از آمادهسازی مجدد میخوانیم؟
پاسخ: آغاز دریافت جدید ErrorCode را پاک میکند. guard اجازه نمیدهد مسیر خطای HAL با rearm زودهنگام پوشانده شود؛ بازیابی در main انجام میشود.
پرسش 6: آیا sum8 و شمارهٔ توالی دریافت بدون افت را اثبات میکنند؟
پاسخ: خیر. sum8 برخورد دارد و شمارندهٔ توالی فقط بعضی ناپیوستگیها را نشان میدهد. شرایط فرستنده، بافر، IRQ و آزمون واقعی باید ثبت شوند؛ بازیابی حاضر نیز داده را کنار میگذارد.
تمرینها
تمرین 1، پایه: از قالب 8N1، زمان نویسه و ظرفیت انباشت 255 بایتی در 115200 را دوباره حساب کنید. پاسخ: هر نویسه 10 بیت، زمان حدود 86.806 µs و انباشت اسمی صف ابتدا خالی حدود 22.135 ms است. با calculate_budget.py تطبیق دهید؛ این محاسبه ضمانت زمانبندی نیست.
تمرین 2، میانی: main مصرف را متوقف کرده ولی ISR سالم است. اگر صف ابتدا خالی و فقط 200 بایت برسد، آیا صرف توقف 30 ms پرشدن صف را ثابت میکند؟ پاسخ: خیر؛ با فرض صف ابتدا خالی و سرویس بهموقع IRQ، 200 کمتر از 255 است. زمان اسمی ارسال پیوستهٔ 200 بایت در 115200/8N1 حدود 17.361 ms است؛ مکث میزبان یا تأخیر ارسال میتواند زمان واقعی را بیشتر کند. برای ورودی پیوستهٔ کافی، ظرفیت محدود است؛ سرریزِ آن با ORE فرق دارد. مدل ظرفیت و wrap را اجرا و فرضهای تعداد ورودی و اشغال اولیه را ثبت کنید.
تمرین 3، پیشرفته: دو نمونه را مستقل با 100 فرمان و window1 مقایسه کنید؛ سپس آزمایش کنترلشدهٔ ORE را با بازگرداندن وضعیت IRQ و فرمان سالم بعدی انجام دهید. معیار پایان: ثبت ابزار، firmware، شمار ACK/خطا/بازیابی و نتیجهٔ واقعی یا علت انجامنشدن آزمایش. دادهٔ کنارگذاشتهشده را مخفی نکنید و نتیجهٔ یک window را به همهٔ burstها تعمیم ندهید.
شاهد ساخت و منابع همراه
هر 2 پروژهٔ تازه در 2026-11-02 با CubeMX 6.14.0، پایگاه 6.0.140 و CubeF3 V1.11.6 تولید و با GCC 13.2.1، CMake Debug و -O0 -g3 -Wall -Werror پاک و بدون هشدار ساخته شدند. Flash ناحیهٔ لینک تکبایتی 21948 و نمونهٔ IDLE برابر 21972 بایت است؛ text+data بهترتیب 21892 و 21916 بایت است و با اندازهٔ ناحیهٔ لینک یکی نیست. RAM لینک هر دو 2600 بایت شامل رزرو heap/stack است، نه مصرف زندهٔ اندازهگیریشده.
مدل Python با 12 گروه بررسی، محاسبهٔ بودجه با 3 assertion، self-test ابزار میزبان و بررسی آفلاین 2 ELF انجام شدهاند. اینها C یا زمانبندی IRQ را روی تراشه اجرا نمیکنند. سورس/IOC، ELF/MAP واقعی، گزارش ساخت، مجوزها، مدلها، ابزار میزبان و کاربرگ تکمیلنشده همراهاند. برد، ترمینال، آزمون ORE، اندازهگیری نرخ واقعی و GUI نرمافزار آزمایش نشدهاند.
مراجع سختافزاری: DS9118 Rev 14، RM0316 و شماتیک GB100ST V2.0. مرجع رفتار API: HAL ثابت 953955a، فایلهای stm32f3xx_hal_uart.c برای IRQ/خطا/Abort و stm32f3xx_hal_uart_ex.c برای ReceiveToIdle_IT. بستهٔ CubeF3 در commit 663d2baae3604244f90ed80d0d757df17e36ada9 ثابت است.
نمونهٔ رسمی مرتبط UART_TwoBoards_ComIT در STM32F3-Discovery بررسی شده است؛ آن نمونه USART2 روی PD5/PD6 با 9600 و آرایهٔ ثابت دارد. صف، parser و پروتکل محدود این درس کپی مستقیم همان مثال یا آزمون رسمی ST روی این برد نیستند.
دانلود فایلهای همراه درس 13
پروژهٔ واقعی شامل IOC و سورس، خروجیهای ELF/MAP، گزارش ساخت، مدل و کاربرگ، مجوزها و 6 تصویر همراه اسکریپت قابل ویرایش در بسته قرار دارند. راهنمای پروژه وضعیت واقعی ساخت و آزمون را توضیح میدهد. ابتدا ZIP را استخراج و README را بخوانید.
پیوند دانلود به نسخهٔ مشخص و بازبینیشدهٔ همراه مقاله متصل است.