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:
305
c/ds18b20/tests/fake_onewire.c
Normal file
305
c/ds18b20/tests/fake_onewire.c
Normal file
@@ -0,0 +1,305 @@
|
||||
/**
|
||||
* @file fake_onewire.c
|
||||
* @brief Симулятор шины 1-Wire с виртуальными датчиками DS18B20.
|
||||
*
|
||||
* Реализует те же пять функций, что и настоящий порт, но работает не с
|
||||
* выводом, а с моделью шины. Этого достаточно, чтобы прогнать на хосте
|
||||
* весь алгоритм SEARCH ROM и обмен с датчиком побитно - именно там
|
||||
* прячутся ошибки, которых не видно в обзоре кода.
|
||||
*/
|
||||
|
||||
#include "fake_onewire.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "onewire.h"
|
||||
|
||||
/** Состояние разбора команд шины. */
|
||||
typedef enum {
|
||||
FAKE_IDLE = 0, /**< После сброса, ждём код команды ROM. */
|
||||
FAKE_SEARCH, /**< Идёт SEARCH ROM. */
|
||||
FAKE_MATCH, /**< Принимаем 8 байт адреса. */
|
||||
FAKE_FUNCTION, /**< ROM-команда отработала, ждём функцию. */
|
||||
FAKE_READ_SCRATCH, /**< Отдаём scratchpad. */
|
||||
FAKE_WRITE_SCRATCH, /**< Принимаем TH, TL, config. */
|
||||
FAKE_READ_POWER /**< Отдаём один бит типа питания. */
|
||||
} fake_state_t;
|
||||
|
||||
static fake_bus_t *g_bus;
|
||||
static fake_state_t g_state;
|
||||
|
||||
/* Сборка входящих и исходящих байтов по битам. */
|
||||
static uint8_t g_in_byte;
|
||||
static uint8_t g_in_bits;
|
||||
static uint8_t g_out_byte;
|
||||
static uint8_t g_out_bits;
|
||||
static uint8_t g_out_index;
|
||||
|
||||
/* Состояние SEARCH ROM. */
|
||||
static uint8_t g_search_bit; /**< Номер бита ROM, 0..63. */
|
||||
static uint8_t g_search_phase; /**< 0 - прямой, 1 - обратный, 2 - выбор. */
|
||||
static uint8_t g_match_index;
|
||||
static uint8_t g_write_index;
|
||||
|
||||
/** Активен ли датчик в текущей транзакции. */
|
||||
static uint8_t g_selected[FAKE_MAX_DEVICES];
|
||||
|
||||
void fake_bus_attach(fake_bus_t *bus)
|
||||
{
|
||||
g_bus = bus;
|
||||
g_state = FAKE_IDLE;
|
||||
g_in_byte = 0U;
|
||||
g_in_bits = 0U;
|
||||
g_out_bits = 0U;
|
||||
g_out_index = 0U;
|
||||
bus->reset_count = 0U;
|
||||
bus->convert_count = 0U;
|
||||
bus->copy_count = 0U;
|
||||
bus->delay_us_total = 0U;
|
||||
}
|
||||
|
||||
uint8_t fake_bus_crc8(const uint8_t *data, uint32_t size)
|
||||
{
|
||||
uint8_t crc = 0U;
|
||||
for (uint32_t i = 0U; i < size; i++) {
|
||||
crc ^= data[i];
|
||||
for (uint8_t bit = 0U; bit < 8U; bit++) {
|
||||
crc = (uint8_t)((crc & 1U) ? ((crc >> 1) ^ 0x8CU) : (crc >> 1));
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
void fake_device_init(fake_device_t *device, const uint8_t rom7[7],
|
||||
int16_t raw, uint8_t parasite)
|
||||
{
|
||||
memcpy(device->rom, rom7, 7U);
|
||||
device->rom[7] = fake_bus_crc8(device->rom, 7U);
|
||||
device->present = 1U;
|
||||
device->parasite = parasite;
|
||||
device->accept_writes = 1U;
|
||||
device->corrupt_crc = 0U;
|
||||
|
||||
device->scratchpad[0] = (uint8_t)((uint16_t)raw & 0xFFU);
|
||||
device->scratchpad[1] = (uint8_t)(((uint16_t)raw >> 8) & 0xFFU);
|
||||
device->scratchpad[2] = 0x4BU; /* TH по умолчанию */
|
||||
device->scratchpad[3] = 0x46U; /* TL по умолчанию */
|
||||
device->scratchpad[4] = 0x7FU; /* 12 бит */
|
||||
device->scratchpad[5] = 0xFFU;
|
||||
device->scratchpad[6] = 0x0CU;
|
||||
device->scratchpad[7] = 0x10U;
|
||||
device->scratchpad[8] = fake_bus_crc8(device->scratchpad, 8U);
|
||||
}
|
||||
|
||||
static void refresh_crc(fake_device_t *device)
|
||||
{
|
||||
device->scratchpad[8] = fake_bus_crc8(device->scratchpad, 8U);
|
||||
if (device->corrupt_crc != 0U) {
|
||||
device->scratchpad[8] ^= 0xFFU;
|
||||
}
|
||||
}
|
||||
|
||||
/** Единственный выбранный датчик либо NULL, если их несколько или ноль. */
|
||||
static fake_device_t *sole_selected(void)
|
||||
{
|
||||
fake_device_t *found = NULL;
|
||||
for (uint8_t i = 0U; i < g_bus->count; i++) {
|
||||
if ((g_selected[i] != 0U) && (g_bus->devices[i].present != 0U)) {
|
||||
if (found != NULL) {
|
||||
return NULL;
|
||||
}
|
||||
found = &g_bus->devices[i];
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
/* --- Порт: пять функций, которых ждёт переносимая часть ------------------- */
|
||||
|
||||
void OneWire_Init(void)
|
||||
{
|
||||
g_state = FAKE_IDLE;
|
||||
}
|
||||
|
||||
void OneWire_DelayUs(uint32_t microseconds)
|
||||
{
|
||||
if (g_bus != NULL) {
|
||||
g_bus->delay_us_total += microseconds;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t OneWire_Reset(void)
|
||||
{
|
||||
uint8_t presence = 0U;
|
||||
|
||||
g_state = FAKE_IDLE;
|
||||
g_in_byte = 0U;
|
||||
g_in_bits = 0U;
|
||||
g_out_bits = 0U;
|
||||
g_out_index = 0U;
|
||||
g_search_bit = 0U;
|
||||
g_search_phase = 0U;
|
||||
|
||||
for (uint8_t i = 0U; i < g_bus->count; i++) {
|
||||
g_selected[i] = g_bus->devices[i].present;
|
||||
if (g_bus->devices[i].present != 0U) {
|
||||
presence = 1U;
|
||||
}
|
||||
}
|
||||
g_bus->reset_count++;
|
||||
return presence;
|
||||
}
|
||||
|
||||
/** Обрабатывает собранный байт команды. */
|
||||
static void dispatch_byte(uint8_t value)
|
||||
{
|
||||
switch (g_state) {
|
||||
case FAKE_IDLE:
|
||||
if (value == 0xF0U) { /* SEARCH ROM */
|
||||
g_state = FAKE_SEARCH;
|
||||
g_search_bit = 0U;
|
||||
g_search_phase = 0U;
|
||||
} else if (value == 0x55U) { /* MATCH ROM */
|
||||
g_state = FAKE_MATCH;
|
||||
g_match_index = 0U;
|
||||
} else if (value == 0xCCU) { /* SKIP ROM */
|
||||
g_state = FAKE_FUNCTION;
|
||||
}
|
||||
break;
|
||||
|
||||
case FAKE_MATCH:
|
||||
for (uint8_t i = 0U; i < g_bus->count; i++) {
|
||||
if (g_bus->devices[i].rom[g_match_index] != value) {
|
||||
g_selected[i] = 0U;
|
||||
}
|
||||
}
|
||||
g_match_index++;
|
||||
if (g_match_index >= 8U) {
|
||||
g_state = FAKE_FUNCTION;
|
||||
}
|
||||
break;
|
||||
|
||||
case FAKE_FUNCTION: {
|
||||
fake_device_t *device = sole_selected();
|
||||
if (value == 0x44U) { /* CONVERT T */
|
||||
g_bus->convert_count++;
|
||||
} else if (value == 0xBEU) { /* READ SCRATCHPAD */
|
||||
if (device != NULL) {
|
||||
refresh_crc(device);
|
||||
g_state = FAKE_READ_SCRATCH;
|
||||
g_out_index = 0U;
|
||||
g_out_bits = 0U;
|
||||
}
|
||||
} else if (value == 0x4EU) { /* WRITE SCRATCHPAD */
|
||||
g_state = FAKE_WRITE_SCRATCH;
|
||||
g_write_index = 0U;
|
||||
} else if (value == 0x48U) { /* COPY SCRATCHPAD */
|
||||
g_bus->copy_count++;
|
||||
} else if (value == 0xB4U) { /* READ POWER SUPPLY */
|
||||
g_state = FAKE_READ_POWER;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case FAKE_WRITE_SCRATCH: {
|
||||
fake_device_t *device = sole_selected();
|
||||
if ((device != NULL) && (device->accept_writes != 0U)) {
|
||||
device->scratchpad[2U + g_write_index] = value;
|
||||
}
|
||||
g_write_index++;
|
||||
if (g_write_index >= 3U) {
|
||||
if (device != NULL) {
|
||||
refresh_crc(device);
|
||||
}
|
||||
g_state = FAKE_FUNCTION;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void OneWire_WriteBit(uint8_t bit)
|
||||
{
|
||||
if (g_state == FAKE_SEARCH) {
|
||||
/* Третья фаза SEARCH ROM: мастер сообщает выбранную ветвь. */
|
||||
for (uint8_t i = 0U; i < g_bus->count; i++) {
|
||||
if (g_selected[i] == 0U) {
|
||||
continue;
|
||||
}
|
||||
uint8_t rom_bit = (uint8_t)((g_bus->devices[i].rom[g_search_bit / 8U]
|
||||
>> (g_search_bit % 8U)) & 1U);
|
||||
if (rom_bit != (bit != 0U)) {
|
||||
g_selected[i] = 0U;
|
||||
}
|
||||
}
|
||||
g_search_bit++;
|
||||
g_search_phase = 0U;
|
||||
if (g_search_bit >= 64U) {
|
||||
g_state = FAKE_FUNCTION;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
g_in_byte = (uint8_t)((g_in_byte >> 1) | ((bit != 0U) ? 0x80U : 0U));
|
||||
g_in_bits++;
|
||||
if (g_in_bits >= 8U) {
|
||||
uint8_t value = g_in_byte;
|
||||
g_in_byte = 0U;
|
||||
g_in_bits = 0U;
|
||||
dispatch_byte(value);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t OneWire_ReadBit(void)
|
||||
{
|
||||
if (g_state == FAKE_SEARCH) {
|
||||
uint8_t ones = 0U;
|
||||
uint8_t zeros = 0U;
|
||||
for (uint8_t i = 0U; i < g_bus->count; i++) {
|
||||
if (g_selected[i] == 0U) {
|
||||
continue;
|
||||
}
|
||||
uint8_t rom_bit = (uint8_t)((g_bus->devices[i].rom[g_search_bit / 8U]
|
||||
>> (g_search_bit % 8U)) & 1U);
|
||||
if (rom_bit != 0U) {
|
||||
ones = 1U;
|
||||
} else {
|
||||
zeros = 1U;
|
||||
}
|
||||
}
|
||||
if ((ones == 0U) && (zeros == 0U)) {
|
||||
return 1U; /* никто не отвечает - обе фазы единичны */
|
||||
}
|
||||
uint8_t result = (g_search_phase == 0U) ? (uint8_t)(zeros == 0U)
|
||||
: (uint8_t)(ones == 0U);
|
||||
g_search_phase = (uint8_t)(g_search_phase + 1U);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (g_state == FAKE_READ_POWER) {
|
||||
fake_device_t *device = sole_selected();
|
||||
/* Паразитное питание прижимает шину к нулю. */
|
||||
return (uint8_t)((device != NULL) && (device->parasite != 0U) ? 0U : 1U);
|
||||
}
|
||||
|
||||
if (g_state == FAKE_READ_SCRATCH) {
|
||||
fake_device_t *device = sole_selected();
|
||||
if (device == NULL) {
|
||||
return 1U;
|
||||
}
|
||||
if (g_out_bits == 0U) {
|
||||
g_out_byte = (g_out_index < 9U) ? device->scratchpad[g_out_index] : 0xFFU;
|
||||
}
|
||||
uint8_t bit = (uint8_t)((g_out_byte >> g_out_bits) & 1U);
|
||||
g_out_bits++;
|
||||
if (g_out_bits >= 8U) {
|
||||
g_out_bits = 0U;
|
||||
g_out_index++;
|
||||
}
|
||||
return bit;
|
||||
}
|
||||
return 1U;
|
||||
}
|
||||
41
c/ds18b20/tests/fake_onewire.h
Normal file
41
c/ds18b20/tests/fake_onewire.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* @file fake_onewire.h
|
||||
* @brief Модель шины 1-Wire с виртуальными датчиками для host-тестов.
|
||||
*/
|
||||
|
||||
#ifndef FAKE_ONEWIRE_H
|
||||
#define FAKE_ONEWIRE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define FAKE_MAX_DEVICES 8U
|
||||
|
||||
/** Виртуальный датчик: ROM, scratchpad и управляемые отказы. */
|
||||
typedef struct {
|
||||
uint8_t rom[8];
|
||||
uint8_t scratchpad[9];
|
||||
uint8_t present; /**< 0 - датчик снят с шины. */
|
||||
uint8_t parasite; /**< 1 - отвечает как паразитно питаемый. */
|
||||
uint8_t accept_writes; /**< 0 - молча игнорирует запись. */
|
||||
uint8_t corrupt_crc; /**< 1 - портит CRC8 scratchpad. */
|
||||
} fake_device_t;
|
||||
|
||||
typedef struct {
|
||||
fake_device_t devices[FAKE_MAX_DEVICES];
|
||||
uint8_t count;
|
||||
uint32_t reset_count;
|
||||
uint32_t convert_count;
|
||||
uint32_t copy_count;
|
||||
uint32_t delay_us_total;
|
||||
} fake_bus_t;
|
||||
|
||||
/** Подключает модель к порту; после вызова работают функции OneWire_*. */
|
||||
void fake_bus_attach(fake_bus_t *bus);
|
||||
|
||||
/** Заполняет датчик: семь байт ROM без CRC, температура и тип питания. */
|
||||
void fake_device_init(fake_device_t *device, const uint8_t rom7[7],
|
||||
int16_t raw, uint8_t parasite);
|
||||
|
||||
uint8_t fake_bus_crc8(const uint8_t *data, uint32_t size);
|
||||
|
||||
#endif /* FAKE_ONEWIRE_H */
|
||||
356
c/ds18b20/tests/test_ds18b20.c
Normal file
356
c/ds18b20/tests/test_ds18b20.c
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user