Files
templates/c/ds18b20/tests/test_ds18b20.c
Andrey Kruchinkin 873ac438f3 feat(ds18b20): термометры DS18B20 поверх программной 1-Wire
Перенесён из репозитория ds18b20, который подключался сабмодулем в
KONOR_ds18b20. История библиотеки осталась там; сюда пришло состояние
на момент переезда.

Ядро на C99 без stm32f10x.h и динамической памяти. Порт — пять функций
(Init, DelayUs, Reset, WriteBit, ReadBit); реализация для STM32F1 идёт
в комплекте в ports/stm32f1. Хостовые тесты на модели шины с виртуальными
датчиками проходят: tests/test_ds18b20.c.
2026-08-23 01:15:34 +03:00

357 lines
13 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Тесты драйвера 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 <stdio.h>
#include <string.h>
#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;
}