/** * Тесты драйвера DS18B20 и переносимой части 1-Wire на симуляторе шины. * * clang -std=c99 -Wall -Wextra -Iinclude -Itests \ * tests/test_ds18b20.c tests/fake_onewire.c src/onewire.c src/ds18b20.c -o test */ #include #include #include "ds18b20.h" #include "fake_onewire.h" #include "onewire.h" static int g_fail; #define CHECK(cond) \ do { \ if (!(cond)) { \ printf("FAIL %s:%d %s\n", __FILE__, __LINE__, #cond); \ g_fail++; \ } \ } while (0) /* Три датчика с ROM, которые расходятся в разных битах: обход должен пройти по всем ветвям дерева SEARCH ROM. */ static const uint8_t ROM_A[7] = { 0x28, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66 }; static const uint8_t ROM_B[7] = { 0x28, 0x11, 0x22, 0x33, 0x44, 0x55, 0x67 }; static const uint8_t ROM_C[7] = { 0x28, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x01 }; /* Датчик чужого семейства: DS18B20_Scan обязан его пропустить. */ static const uint8_t ROM_OTHER[7] = { 0x10, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }; static fake_bus_t g_bus; /** * @brief Находит запись по ROM виртуального датчика. * * Порядок обхода SEARCH ROM задаётся битами идентификаторов, а не порядком * массива, поэтому опираться на индекс в тестах нельзя. */ static DS18B20_Sensor *by_device(DS18B20_Bus *bus, uint8_t device_index) { return DS18B20_Find(bus, g_bus.devices[device_index].rom); } static void setup(uint8_t count) { memset(&g_bus, 0, sizeof g_bus); g_bus.count = count; fake_bus_attach(&g_bus); } /* -- CRC8 ------------------------------------------------------------------ */ static void test_crc8(void) { /* Контрольный ROM из datasheet Maxim: CRC блока вместе с байтом = 0. */ static const uint8_t rom[8] = { 0x28, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x00 }; uint8_t crc = OneWire_Crc8(rom, 7U); uint8_t full[8]; memcpy(full, rom, 8U); full[7] = crc; CHECK(OneWire_Crc8(full, 8U) == 0U); /* Симулятор и библиотека считают CRC8 одинаково. */ CHECK(fake_bus_crc8(rom, 7U) == crc); } /* -- Поиск ----------------------------------------------------------------- */ static void test_search_finds_all(void) { setup(3U); fake_device_init(&g_bus.devices[0], ROM_A, 0, 0U); fake_device_init(&g_bus.devices[1], ROM_B, 0, 0U); fake_device_init(&g_bus.devices[2], ROM_C, 0, 0U); OneWire_Search search; uint8_t found[3][8]; uint8_t count = 0U; OneWire_SearchInit(&search); while ((count < 3U) && (OneWire_SearchNext(&search) != 0U)) { memcpy(found[count], search.rom, 8U); count++; } CHECK(count == 3U); /* Обход завершён: следующий вызов не должен выдавать четвёртый ROM. */ CHECK(OneWire_SearchNext(&search) == 0U); /* Каждый ROM найден ровно один раз. */ for (uint8_t d = 0U; d < 3U; d++) { uint8_t hits = 0U; for (uint8_t f = 0U; f < count; f++) { if (memcmp(found[f], g_bus.devices[d].rom, 8U) == 0) { hits++; } } CHECK(hits == 1U); } } static void test_search_on_empty_bus(void) { setup(0U); OneWire_Search search; OneWire_SearchInit(&search); CHECK(OneWire_SearchNext(&search) == 0U); } static void test_search_single_device(void) { setup(1U); fake_device_init(&g_bus.devices[0], ROM_C, 0, 0U); OneWire_Search search; OneWire_SearchInit(&search); CHECK(OneWire_SearchNext(&search) == 1U); CHECK(memcmp(search.rom, g_bus.devices[0].rom, 8U) == 0); CHECK(search.last_device == 1U); CHECK(OneWire_SearchNext(&search) == 0U); } /* -- Сканирование и чтение ------------------------------------------------- */ static void test_scan_filters_family(void) { setup(3U); fake_device_init(&g_bus.devices[0], ROM_A, 0, 0U); fake_device_init(&g_bus.devices[1], ROM_OTHER, 0, 0U); fake_device_init(&g_bus.devices[2], ROM_C, 0, 1U); DS18B20_Bus bus; CHECK(DS18B20_Scan(&bus) == 2U); CHECK(bus.count == 2U); CHECK(bus.sensors[0].rom[0] == DS18B20_FAMILY_CODE); CHECK(bus.sensors[1].rom[0] == DS18B20_FAMILY_CODE); /* Паразитное питание одного датчика поднимает флаг на всю шину. */ CHECK(bus.parasite == 1U); CHECK((by_device(&bus, 2U)->status & DS18B20_STATUS_PARASITE) != 0U); CHECK((by_device(&bus, 0U)->status & DS18B20_STATUS_PARASITE) == 0U); /* Чужое семейство в перечень не попало. */ CHECK(DS18B20_Find(&bus, g_bus.devices[1].rom) == NULL); } static void test_read_all(void) { setup(2U); /* +25.0625 C и -10.125 C в формате DS18B20. */ fake_device_init(&g_bus.devices[0], ROM_A, 0x0191, 0U); fake_device_init(&g_bus.devices[1], ROM_C, (int16_t)0xFF5E, 0U); DS18B20_Bus bus; CHECK(DS18B20_Scan(&bus) == 2U); CHECK(DS18B20_StartConversion() == 1U); CHECK(g_bus.convert_count == 1U); CHECK(DS18B20_ReadAll(&bus) == 2U); DS18B20_Sensor *warm = by_device(&bus, 0U); DS18B20_Sensor *cold = by_device(&bus, 1U); CHECK(warm != NULL && cold != NULL); CHECK(warm->raw == 0x0191); CHECK(cold->raw == (int16_t)0xFF5E); CHECK((warm->status & DS18B20_STATUS_VALID) != 0U); CHECK(warm->user_byte1 == 0x4BU); CHECK(warm->user_byte2 == 0x46U); CHECK(warm->config == 0x7FU); } static void test_crc_error_does_not_stop_the_poll(void) { setup(2U); fake_device_init(&g_bus.devices[0], ROM_A, 0x0100, 0U); fake_device_init(&g_bus.devices[1], ROM_C, 0x0200, 0U); DS18B20_Bus bus; CHECK(DS18B20_Scan(&bus) == 2U); g_bus.devices[0].corrupt_crc = 1U; /* Первый датчик отдаёт битый scratchpad, второй обязан быть опрошен. */ CHECK(DS18B20_ReadAll(&bus) == 1U); DS18B20_Sensor *broken = by_device(&bus, 0U); DS18B20_Sensor *good = by_device(&bus, 1U); CHECK((broken->status & DS18B20_STATUS_CRC_ERROR) != 0U); CHECK((broken->status & DS18B20_STATUS_VALID) == 0U); CHECK((good->status & DS18B20_STATUS_VALID) != 0U); CHECK(good->raw == 0x0200); } static void test_missing_device_is_reported(void) { setup(2U); fake_device_init(&g_bus.devices[0], ROM_A, 0x0100, 0U); fake_device_init(&g_bus.devices[1], ROM_C, 0x0200, 0U); DS18B20_Bus bus; CHECK(DS18B20_Scan(&bus) == 2U); /* Датчик сняли с шины между сканированием и опросом. */ DS18B20_Sensor *gone = by_device(&bus, 0U); DS18B20_Sensor *alive = by_device(&bus, 1U); g_bus.devices[0].present = 0U; CHECK(DS18B20_ReadAll(&bus) == 1U); /* NO_PRESENCE здесь не появится: presence-импульс выдаёт оставшийся датчик, и MATCH ROM к пропавшему проходит без признаков ошибки. Пропажа видна только по CRC8 пустого scratchpad - так устроена шина. */ CHECK((gone->status & DS18B20_STATUS_CRC_ERROR) != 0U); CHECK((gone->status & DS18B20_STATUS_VALID) == 0U); /* Пропажа соседа не должна мешать опросу оставшихся. */ CHECK((alive->status & DS18B20_STATUS_VALID) != 0U); /* А вот когда молчит вся шина, драйвер обязан сказать NO_PRESENCE. */ g_bus.devices[1].present = 0U; CHECK(DS18B20_ReadAll(&bus) == 0U); CHECK((gone->status & DS18B20_STATUS_NO_PRESENCE) != 0U); CHECK((alive->status & DS18B20_STATUS_NO_PRESENCE) != 0U); CHECK(DS18B20_StartConversion() == 0U); } /* -- Поиск по ROM и запись -------------------------------------------------- */ static void test_find(void) { setup(2U); fake_device_init(&g_bus.devices[0], ROM_A, 0, 0U); fake_device_init(&g_bus.devices[1], ROM_C, 0, 0U); DS18B20_Bus bus; CHECK(DS18B20_Scan(&bus) == 2U); DS18B20_Sensor *found = DS18B20_Find(&bus, g_bus.devices[1].rom); CHECK(found != NULL); CHECK(memcmp(found->rom, g_bus.devices[1].rom, 8U) == 0); static const uint8_t unknown[8] = { 0x28, 0, 0, 0, 0, 0, 0, 0 }; CHECK(DS18B20_Find(&bus, unknown) == NULL); } static void test_write_scratchpad(void) { setup(1U); fake_device_init(&g_bus.devices[0], ROM_A, 0x0100, 0U); DS18B20_Bus bus; CHECK(DS18B20_Scan(&bus) == 1U); DS18B20_Sensor *sensor = by_device(&bus, 0U); uint8_t config = DS18B20_ConfigFromResolution(10U); CHECK(DS18B20_WriteScratchpad(sensor, 0x11U, 0x22U, config, 0U) == DS18B20_WRITE_OK); CHECK(sensor->user_byte1 == 0x11U); CHECK(sensor->user_byte2 == 0x22U); CHECK(sensor->config == config); CHECK(g_bus.devices[0].scratchpad[2] == 0x11U); CHECK(g_bus.copy_count == 0U); /* Сохранение в EEPROM датчика: COPY SCRATCHPAD плюс выдержка. */ uint32_t before = g_bus.delay_us_total; CHECK(DS18B20_WriteScratchpad(sensor, 0x33U, 0x44U, config, 1U) == DS18B20_WRITE_OK); CHECK(g_bus.copy_count == 1U); CHECK((g_bus.delay_us_total - before) >= 12000U); } static void test_write_rejected(void) { setup(1U); fake_device_init(&g_bus.devices[0], ROM_A, 0x0100, 0U); DS18B20_Bus bus; CHECK(DS18B20_Scan(&bus) == 1U); /* Датчик отвечает, но регистры не меняет: обмен состоялся, значение нет. */ g_bus.devices[0].accept_writes = 0U; DS18B20_Sensor *sensor = by_device(&bus, 0U); CHECK(DS18B20_WriteScratchpad(sensor, 0x77U, 0x88U, 0x3FU, 0U) == DS18B20_WRITE_REJECTED); /* Кэш обновлён тем, что реально лежит в датчике. */ CHECK(sensor->user_byte1 == 0x4BU); CHECK(sensor->config == 0x7FU); } static void test_write_bus_error(void) { setup(1U); fake_device_init(&g_bus.devices[0], ROM_A, 0x0100, 0U); DS18B20_Bus bus; CHECK(DS18B20_Scan(&bus) == 1U); DS18B20_Sensor *sensor = by_device(&bus, 0U); g_bus.devices[0].present = 0U; CHECK(DS18B20_WriteScratchpad(sensor, 0x11U, 0x22U, 0x7FU, 0U) == DS18B20_WRITE_BUS_ERROR); CHECK((sensor->status & DS18B20_STATUS_NO_PRESENCE) != 0U); } /* -- Разрешение ------------------------------------------------------------ */ static void test_resolution(void) { CHECK(DS18B20_ConfigFromResolution(9U) == 0x1FU); CHECK(DS18B20_ConfigFromResolution(10U) == 0x3FU); CHECK(DS18B20_ConfigFromResolution(11U) == 0x5FU); CHECK(DS18B20_ConfigFromResolution(12U) == 0x7FU); /* Выход за диапазон зажимается, а не заворачивается. */ CHECK(DS18B20_ConfigFromResolution(0U) == 0x1FU); CHECK(DS18B20_ConfigFromResolution(200U) == 0x7FU); CHECK(DS18B20_ConversionTimeMs(0x1FU) == 94U); CHECK(DS18B20_ConversionTimeMs(0x3FU) == 188U); CHECK(DS18B20_ConversionTimeMs(0x5FU) == 375U); CHECK(DS18B20_ConversionTimeMs(0x7FU) == 750U); } static void test_sensor_limit(void) { /* На шине больше датчиков, чем помещается в кадр SENSOR_DATA. */ setup(FAKE_MAX_DEVICES); for (uint8_t i = 0U; i < FAKE_MAX_DEVICES; i++) { uint8_t rom[7] = { 0x28, 0x10, 0x20, 0x30, 0x40, 0x50, 0x00 }; rom[6] = (uint8_t)(0xA0U + i); fake_device_init(&g_bus.devices[i], rom, (int16_t)(0x100 + i), 0U); } DS18B20_Bus bus; uint8_t count = DS18B20_Scan(&bus); CHECK(count <= DS18B20_MAX_SENSORS); CHECK(count == DS18B20_MAX_SENSORS); CHECK(DS18B20_ReadAll(&bus) == DS18B20_MAX_SENSORS); } int main(void) { test_crc8(); test_search_finds_all(); test_search_on_empty_bus(); test_search_single_device(); test_scan_filters_family(); test_read_all(); test_crc_error_does_not_stop_the_poll(); test_missing_device_is_reported(); test_find(); test_write_scratchpad(); test_write_rejected(); test_write_bus_error(); test_resolution(); test_sensor_limit(); if (g_fail == 0) { printf("all ds18b20 tests passed\n"); return 0; } printf("%d check(s) failed\n", g_fail); return 1; }