|
В ESP-IDF вычисление CRC32 выполняется с помощью аппаратно-ускоренных функций, находящихся в ПЗУ (ROM) чипа. Основной способ — использовать функции из компонента esp_rom_crc.h, которые поддерживают различные стандарты CRC, включая CRC32 в little-endian и big-endian вариантах [1].
[Основные функции CRC32 в ESP-IDF]
Самые важные функции для расчёта CRC32 объявлены в заголовочном файле esp_rom_crc.h:
esp_rom_crc32_le(uint32_t crc, const uint8_t *buf, uint32_t len) — для little-endian режима. esp_rom_crc32_be(uint32_t crc, const uint8_t *buf, uint32_t len) — для big-endian режима.
Обе функции используют стандартный для ESP32 полином CRC-32: 0x04c11db7.
Особенности использования. Библиотека спроектирована так, чтобы поддерживать вычисление CRC для данных, разделённых на несколько буферов. В начале и в конце расчётов необходимо инвертировать начальное и конечное значения (с помощью оператора `~`). Это связано с тем, как функции реализованы в ПЗУ и описано в официальной документации.
[Примеры расчёта CRC32]
Ниже приведены примеры для нескольких распространённых стандартов CRC32. Полином для всех примеров — 0x04c11db7.
Пример 1: CRC-32 (ISO/HDLC). Этот стандарт используется, например, в протоколах Ethernet, PKZIP, GZip.
#include "esp_rom_crc.h"
uint32_t calculate_crc32_iso_hdlc(const uint8_t *data, size_t len) { // Параметры: init=0xffffffff, refin=true, refout=true, xorout=0xffffffff uint32_t crc = esp_rom_crc32_le(UINT32_MAX, data, len); // Для данного стандарта входное и выходное значения инвертируются return crc;
}
Пояснение: UINT32_MAX (или ~0UL) — это инвертированное начальное значение 0x00000000. Функция crc32_le используется, так как требуется отражение (refin/refout=true). Конечный результат инвертируется.
Пример 2: CRC-32/BZIP2. Используется в архиваторах BZIP2.
#include "esp_rom_crc.h"
uint32_t calculate_crc32_bzip2(const uint8_t *data, size_t len) { // Параметры: init=0xffffffff, refin=false, refout=false, xorout=0xffffffff uint32_t crc = esp_rom_crc32_be(UINT32_MAX, data, len); return crc;
}
Пояснение: Так как отражение не требуется (refin/refout=false), используется crc32_be. Начальное значение также инвертируется.
Пример 3: CRC-32/MPEG-2. Применяется в транспортных потоках MPEG-2.
#include "esp_rom_crc.h"
uint32_t calculate_crc32_mpeg2(const uint8_t *data, size_t len) { // Параметры: init=0xffffffff, refin=false, refout=false, xorout=0x00000000 uint32_t crc = esp_rom_crc32_be(UINT32_MAX, data, len); // В этом стандарте итоговое значение не инвертируется, а инвертируется результат функции return ~crc;
}
Пояснение: особенность этого стандарта в том, что конечный результат не инвертируется (xorout=0), поэтому мы инвертируем то, что возвращает функция crc32_be.
Пример 4: расчёт по частям (для больших данных). Если данные поступают не целиком, расчёт можно выполнять итеративно.
uint32_t calculate_crc32_iso_hdlc_parts(const uint8_t *part1, size_t len1, const uint8_t *part2, size_t len2)
{ // Параметры: init=0xffffffff, refin=true, refout=true, xorout=0xffffffff
// Шаг 1: Инвертируем начальное значение uint32_t crc = ~0UL; // или ~0x00000000
// Шаг 2: Расчёт для первого буфера crc = esp_rom_crc32_le(crc, part1, len1);
// Шаг 3: Расчёт для второго буфера crc = esp_rom_crc32_le(crc, part2, len2);
// Шаг 4: Инвертируем итоговый результат crc = ~crc; return crc;
}
[Альтернативный метод: mz_crc32]
В ПЗУ также доступна функция mz_crc32 из библиотеки сжатия Miniz. Она предоставляет интерфейс, совместимый с библиотекой zlib, и может быть удобна в некоторых случаях. Её наличие зависит от конкретной модели чипа.
mz_crc32 — это функция из библиотеки сжатия Miniz, встроенной в ПЗУ многих чипов Espressif (ESP32, ESP32-S2/S3, ESP32-C3/C2/C6 и др.). Она предоставляет интерфейс, совместимый с популярной библиотекой zlib, и вычисляет CRC-32 с теми же параметрами, что и crc32_le (полином 0x04c11db7, с отражением входных/выходных данных).
Проверка наличия: убедиться, что функция доступна для вашего чипа, можно по наличию макроса ESP_ROM_HAS_MZ_CRC32 (обычно определён в esp_rom_caps.h) .
#if ESP_ROM_HAS_MZ_CRC32 // mz_crc32 доступна #endif
Функция объявлена в заголовочном файле rom/miniz.h и имеет следующий прототип:
mz_ulong mz_crc32(mz_ulong crc, const unsigned char *ptr, size_t buf_len);
Она идеально подходит для расчёта CRC по частям, так как принимает промежуточное значение crc.
Пример 1: базовый расчёт CRC для одного блока данных. В этом примере показан стандартный способ вычисления CRC-32, совместимый с большинством утилит (ZIP, GZip и др.).
#include < stdio.h> #include < string.h> #include "rom/miniz.h" // Подключаем заголовочный файл Miniz
void app_main(void)
{ const char *data = "Hello, ESP-IDF with mz_crc32!"; size_t len = strlen(data);
// Шаг 1: Инициализация CRC начальным значением mz_crc32(0, NULL, 0) // Согласно документации, вызов mz_crc32(0, NULL, 0) возвращает начальное значение 0. // Однако стандарт требует начальной инициализации как ~0L (0xffffffff). // Функция mz_crc32 внутри сама управляет этим процессом при передаче данных. // Правильный способ инициализации для первого вызова с данными - передать 0. uint32_t crc = mz_crc32(0, (const unsigned char*)data, len);
// Шаг 2: Финальное преобразование (XOROUT) // Для стандарта ISO/HDLC (PKZIP) необходимо инвертировать финальный результат. crc = ~crc; printf("CRC32 (mz_crc32) для \"%s\": 0x%08lX\n", data, (unsigned long)crc);
}
Пример 2: расчёт CRC по частям (для больших данных). Это основной способ использования mz_crc32 — обновлять контрольную сумму по мере поступления данных.
#include < stdio.h> #include < string.h> #include "rom/miniz.h"
uint32_t calculate_crc32_parts(const uint8_t *part1, size_t len1, const uint8_t *part2, size_t len2) { // Шаг 1: Начальное значение для mz_crc32 при работе с данными - 0 uint32_t crc = 0;
// Шаг 2: Обработка первой части crc = mz_crc32(crc, part1, len1);
// Шаг 3: Обработка второй части (передаём промежуточный crc) crc = mz_crc32(crc, part2, len2); // Шаг 4: Финальное преобразование (инвертирование) crc = ~crc; return crc;
}
void app_main(void)
{ const uint8_t *data = (const uint8_t*)"Hello, world!"; // Разобьём данные на две части uint8_t part1[5] = {0}; // "Hello" uint8_t part2[10] = {0}; // ", world!"
memcpy(part1, data, 5); memcpy(part2, data + 5, 10); // Остаток строки
uint32_t crc_full = calculate_crc32_parts(part1, 5, part2, 10); printf("CRC32 частей: 0x%08lX\n", (unsigned long)crc_full);
// Проверка: вычислим CRC для всего блока сразу uint32_t crc_single = ~mz_crc32(0, data, strlen((const char*)data)); printf("CRC32 целиком: 0x%08lX\n", (unsigned long)crc_single);
}
Сравнение с esp_rom_crc32_le. Главное отличие mz_crc32 от esp_rom_crc32_le — в инициализации и финальной обработке. Функция mz_crc32 имитирует поведение zlib, где:
- Начальное значение для первого вызова должно быть `0` (а не `~0`, как требуется стандартом). - Внутри себя функция начинает с `~0` и сама управляет отражением бит. - После обработки всех данных результат необходимо инвертировать (`~crc`), чтобы получить стандартное значение CRC-32.
Таким образом, обе функции дают один и тот же результат для стандарта CRC-32 (ISO/HDLC), но требуют разной подготовки входных данных.
[Дополнительные сведения]
● CRC в NVS (Non-Volatile Storage): система энергонезависимого хранения ESP-IDF (NVS) [2] использует CRC32 для проверки целостности своих записей. При чтении данных из NVS их контрольная сумма автоматически проверяется, и если она не совпадает, запись считается недействительной.
● Расширение функциональности: в сообществе ESP-IDF обсуждается возможность добавления более гибкого и стандартизированного API для различных алгоритмов CRC (CRC-8, CRC-16/Modbus и др.), что может упростить жизнь разработчикам в будущем.
[Ссылки]
1. Порядок следования байт (endianness). 2. ESP32: библиотека энергонезависимого хранилища данных. |