ESP-IDF: вычисление CRC32 Печать
Добавил(а) microsin   

В 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: библиотека энергонезависимого хранилища данных.