feat(ds18b20): термометры DS18B20 поверх программной 1-Wire

Перенесён из репозитория ds18b20, который подключался сабмодулем в
KONOR_ds18b20. История библиотеки осталась там; сюда пришло состояние
на момент переезда.

Ядро на C99 без stm32f10x.h и динамической памяти. Порт — пять функций
(Init, DelayUs, Reset, WriteBit, ReadBit); реализация для STM32F1 идёт
в комплекте в ports/stm32f1. Хостовые тесты на модели шины с виртуальными
датчиками проходят: tests/test_ds18b20.c.
This commit is contained in:
2026-08-23 01:15:34 +03:00
parent 13f2c8fe64
commit 873ac438f3
11 changed files with 1701 additions and 0 deletions

View File

@@ -0,0 +1,356 @@
/**
* Тесты драйвера 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;
}