در دنیای دیجیتال امکان خرابی داده هنگام انتقال به سیستمی دیگر یا ذخیره بر روی حافظه وجود دارد. به همین دلیل، ما از مدتها پیش نیاز داشتیم تا راهی برای تشخیص این موضوع پیدا کنیم.
هنگامی که خطا در دادههای دریافتی شناسایی شود، میتوان دوباره از فرستنده درخواست ارسال مجدد کرد. در مواردی حتا آن را تصحیح کرد و یا حداقل از اتکا بر آن داده و بروز یک فاجعه جلوگیری کنیم.
به طور کلی روشی که در این الگوریتمها به کار میبرند این است که به دادهی اصلی یک دادهی اضافی برای چک کردن خطا اضافه میکنند. هرگاه که میخواهیم صحت اطلاعات را چک کنیم دوباره طبق الگوریتم این دادهی اضافی را محاسبه میکنیم. اگر دادهی محاسبه شده با چیزی که از قبل داشتیم یکی باشد، به این معنی است که مشکلی در اطلاعات دریافتی وجود ندارد.
حالا در اینجا میخواهیم سراغ یکی از الگوریتمهای رایج خطایابی به نام Cyclic Redundancy Check یا به اختصار CRC برویم. در نظر داشته باشید که CRC تنها امکان تشخیص خطا را دارد و نمیتواند داده را تصحیح کند.
ردپای این الگوریتم از اترنت و وایرلس گرفته تا فشرده سازیهایی چون PNG و GZIP دیده میشود. در دنیای امبدد سیستم هم میتوانیم هنگام ارسال داده با مثلاً پروتکل UART یا ذخیره تنظیمات دستگاه در EEPROM از آن استفاده کنیم.
این الگوریتم بر پایهی تقسیم چند جملهایها در ریاضیات کار میکند و فهمیدن ریاضیات آن نیاز به کمی تلاش دارد. در این پست قصد ندارم وارد جزئیات پیادهسازی این الگوریم شوم و اگر نیاز دارید که خودتان آن را پیاده سازی کنید یا بیشتر در موردش مطالعه کنید به این لینکها مراجعه کنید.(+ + +)
خوشبختانه در بسیاری از میکروکنترلرهای STM32 ما واحد سخت افزاری برای CRC داریم که علاوه بر راحتتر کردن کار، به ما این امکان را میدهد که بهینهتر و سریعتر به نتیجه نهایی برسیم.
پریفرال CRC در میکروکنترلر STM32:
اگرچه این واحد نباید تفاوت چندانی در سریهای مختلف داشته باشد، اما جهت اطلاع بدانید که کد مطرح شده برای سری STM32G4 است.
همانطور که جلوتر اشاره کردم CRC بر پایهی تقسیم چندجملهایها کار میکند. دادهی ورودی میشود ضرایب چندجملهای مقسوم که بر مقسومعلیه انتخاب شده توسط ما تقسیم میشود. باقیماندهی این تقسیم هم میشود جواب CRC مدنظر ما که به دنبالش هستیم.
انتخاب مقسومعلیه مناسب کار راحتی نیست. چراکه بر امکان خطایابی میتواند تاثیر بگذارد. هرچه احتمال اینکه ما در محاسبهی CRC دو دادهی مختلف به عددی یکسان برسیم کمتر باشد، یعنی امکان خطایابی بالاتری داریم.
در پریفرال CRC میکروکنترلر STM32 به طور پیشفرض از عدد 0x04C11DB7 به عنوان مقسومعلیه استفاده شده. مشابه مقسوم علیهای که در بسیاری از پروتکلها مثل اترنت میبینیم. در ادامه مقداردهی اولیه این پریفرال را مشاهده میکنید، که مقادیر پیش فرض برای پارامترها در نظر گرفته شده.
CRC_HandleTypeDef hcrc; void MX_CRC_Init(void) { hcrc.Instance = CRC; hcrc.Init.DefaultPolynomialUse = DEFAULT_POLYNOMIAL_ENABLE; hcrc.Init.DefaultInitValueUse = DEFAULT_INIT_VALUE_ENABLE; hcrc.Init.InputDataInversionMode = CRC_INPUTDATA_INVERSION_NONE; hcrc.Init.OutputDataInversionMode = CRC_OUTPUTDATA_INVERSION_DISABLE; hcrc.InputDataFormat = CRC_INPUTDATA_FORMAT_BYTES; if (HAL_CRC_Init(&hcrc) != HAL_OK) { Error_Handler(); } }
توجه کنید که InputDataFormat را باید با توجه به نوع دادههایتان انتخاب کنید. مثلا اگر قرار است CRC بر روی آرایهای از جنس uint32_t اجرا شود باید به آن مقدار CRC_INPUTDATA_FORMAT_WORDS و اگر قرار است از جنس uint8_t باشد مقدار CRC_INPUTDATA_FORMAT_BYTES بدهید.
اما در نهایت برای محاسبه CRC باید آدرس را به صورت pointerای از جنس (uint32_t *) برای پارامتر pBuffer از تابع HAL_CRC_Calculate ارسال کنید. این تابع در بدنهاش بر اساس مقدار InputDataFormat تشخیص میدهد که چگونه با این پوینتر رفتار کند.
برای پارامتر BufferLength هم تعداد خانههای آرایه (نه الزاماً تعداد بایتها) را ارسال کنید.
#define LEN 5U Uint8_t buf[LEN] = {0x01, 0x02, 0x03, 0x04, 0x05}; HAL_CRC_Calculate(&hcrc, (uint32_t*)buf, LEN);
همانطور که دیدید از تابع HAL_CRC_Calculate برای محاسبهی یکبارهی CRC بر روی آرایهای از دیتا میتوان استفاده کرد. اما امکان دسترسی یکباره به دیتاها را نداشتید و میخواستید طی چند مرحله محاسبهی CRC را انجام دهید، باید سراغ تابع HAL_CRC_Accumulate بروید.
در انتها اگر خواستید صحت نتایج بدست آمده را بسنجید، میتوانید از وبسایت crccalc استفاده کنید. اگر از مقادیر پیش فرض استفاده کرده باشید، باید به نتیجهای مشابه با ردیف CRC-32/MPEG-2 رسیده باشید.