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

35
c/ds18b20/CMakeLists.txt Normal file
View File

@@ -0,0 +1,35 @@
cmake_minimum_required(VERSION 3.13)
project(ds18b20 C)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)
# Переносимое ядро: ни регистров, ни задержек, ни stm32f10x.h.
add_library(ds18b20 STATIC
src/onewire.c
src/ds18b20.c
)
target_include_directories(ds18b20 PUBLIC include)
if(MSVC)
target_compile_options(ds18b20 PRIVATE /W4)
else()
target_compile_options(ds18b20 PRIVATE -Wall -Wextra -Wpedantic)
endif()
# Порты (ports/) сюда не входят: им нужен CMSIS-заголовок целевого МК,
# они подключаются напрямую в проект прошивки.
option(DS18B20_BUILD_TESTS "Собирать тесты на симуляторе шины" ON)
if(DS18B20_BUILD_TESTS)
enable_testing()
add_executable(test_ds18b20
tests/test_ds18b20.c
tests/fake_onewire.c
)
target_link_libraries(test_ds18b20 PRIVATE ds18b20)
target_include_directories(test_ds18b20 PRIVATE tests)
add_test(NAME ds18b20 COMMAND test_ds18b20)
endif()

127
c/ds18b20/README.md Normal file
View File

@@ -0,0 +1,127 @@
# ds18b20
Драйвер термометров **DS18B20** поверх программной шины **1-Wire**.
Вынесен из проекта `KONOR_ds18b20` и подключается в него сабмодулем.
Ядро на C99: ни динамической памяти, ни ОС, ни `stm32f10x.h`. Всё
аппаратно-зависимое собрано в порте — на новую платформу переносятся
пять функций, остальное собирается как есть.
```
приложение
ds18b20.c поиск датчиков, чтение и запись scratchpad
onewire.c побайтовый обмен, CRC8, SEARCH ROM, MATCH/SKIP ROM
ports/<платформа> Init, DelayUs, Reset, WriteBit, ReadBit
```
## Состав
| Файл | Что делает | Зависимости |
|---|---|---|
| `include/ds18b20.h`, `src/ds18b20.c` | перечень датчиков, измерения, запись TH/TL/разрешения | `onewire.h`, `stdint.h` |
| `include/onewire.h`, `src/onewire.c` | байты и блоки, CRC8 Dallas/Maxim, обход SEARCH ROM | `stdint.h` |
| `ports/stm32f1/` | тайминги Maxim, вывод с открытым стоком, задержки по DWT | CMSIS `stm32f10x.h` |
| `tests/` | симулятор шины с виртуальными датчиками и host-тесты | — |
## Порт: что нужно от платформы
```c
void OneWire_Init(void); /* настроить вывод и источник задержек */
void OneWire_DelayUs(uint32_t us);
uint8_t OneWire_Reset(void); /* сброс и проверка presence */
void OneWire_WriteBit(uint8_t bit);
uint8_t OneWire_ReadBit(void);
```
Всё остальное — `OneWire_WriteByte`, `OneWire_ReadByte`, `OneWire_Crc8`,
`OneWire_SearchNext`, `OneWire_SelectRom`, `OneWire_SkipRom` и весь
`ds18b20.c` — уже написано и от платформы не зависит.
### Готовый порт STM32F1
Вывод настроен как выход с открытым стоком; чтение идёт через `IDR`,
поэтому режим не переключается. Прерывания запрещаются только на
длительность одного тайм-слота, чтобы не терять байты USART. Источник
микросекундных задержек — счётчик тактов ядра `DWT->CYCCNT`.
Привязку вывода порт берёт из `onewire_config.h`, который предоставляет
приложение — шаблон лежит рядом:
```c
#include "stm32f10x.h"
#define ONEWIRE_GPIO GPIOA
#define ONEWIRE_PIN 1U
#define ONEWIRE_GPIO_CLOCK RCC_APB2ENR_IOPAEN
```
Требуется внешняя подтяжка 4.7 кОм к питанию.
## Использование
```c
DS18B20_Bus bus;
OneWire_Init();
DS18B20_Scan(&bus); /* найти датчики на шине */
DS18B20_StartConversion(); /* SKIP ROM + CONVERT T */
/* выдержать DS18B20_ConversionTimeMs(config) */
DS18B20_ReadAll(&bus); /* обновить измерения */
for (uint8_t i = 0; i < bus.count; i++) {
if (bus.sensors[i].status & DS18B20_STATUS_VALID) {
float celsius = bus.sensors[i].raw / 16.0f;
}
}
```
Ошибка одного датчика не прерывает опрос остальных — она видна в поле
`status` соответствующей записи.
## Тесты
Симулятор шины реализует те же пять функций, что и настоящий порт, но
работает не с выводом, а с моделью: виртуальные датчики отвечают побитно,
включая полный алгоритм `SEARCH ROM`. Так на хосте проверяется именно то,
что иначе проверялось бы только осциллографом.
```bash
cmake -B build && cmake --build build && ctest --test-dir build
```
```bash
clang -std=c99 -Wall -Wextra -Iinclude -Itests \
tests/test_ds18b20.c tests/fake_onewire.c src/onewire.c src/ds18b20.c -o test && ./test
```
Покрыто: CRC8, обход шины с несколькими датчиками и пустой шины, отсев
чужих семейств по коду ROM, паразитное питание, битый scratchpad,
пропажа датчика, подтверждение и отказ записи, сохранение в EEPROM
датчика, пределы разрешения 9..12 бит.
## Особенности, которые стоит знать
* **Порядок датчиков задаёт шина, а не код.** `SEARCH ROM` обходит их в
порядке битов идентификатора, поэтому индекс в `bus.sensors[]` не
совпадает с порядком подключения. Искать датчик нужно по ROM через
`DS18B20_Find()`.
* **Пропажу датчика на живой шине не видно по presence.** Импульс
подтверждают соседи, и `MATCH ROM` к снятому датчику проходит без
признаков ошибки — она проявится как `DS18B20_STATUS_CRC_ERROR`.
`DS18B20_STATUS_NO_PRESENCE` означает, что молчит вся шина.
* **`DS18B20_MAX_SENSORS` равен 8** — столько датчиков помещается в один
кадр `SENSOR_DATA` протокола SETGUI. Для другого применения значение
можно поднять.
* **Обратное чтение после записи обязательно.** Датчик может ответить и
оставить регистры прежними; такой случай отличается от ошибки шины
кодом `DS18B20_WRITE_REJECTED`.
## Лицензия и происхождение
Код вынесен из `KONOR_ds18b20` (модуль термометрии на STM32F103C8T6)
без изменения поведения: разделение на ядро и порт сделано переносом
функций, тела не правились.

125
c/ds18b20/include/ds18b20.h Normal file
View File

@@ -0,0 +1,125 @@
/**
* @file ds18b20.h
* @brief Драйвер термометров DS18B20 поверх шины 1-Wire.
*
* Модуль хранит перечень найденных датчиков и последние измерения, поэтому
* ответ на запрос GUI формируется без обращения к шине.
*/
#ifndef DS18B20_H
#define DS18B20_H
#include <stdint.h>
#include "onewire.h"
/** Максимум датчиков, помещающихся в один кадр SENSOR_DATA. */
#define DS18B20_MAX_SENSORS 8U
/** Код семейства DS18B20 в первом байте ROM. */
#define DS18B20_FAMILY_CODE 0x28U
/** Размер scratchpad вместе с байтом CRC8. */
#define DS18B20_SCRATCHPAD_SIZE 9U
#define DS18B20_STATUS_VALID 0x01U /**< Измерение достоверно. */
#define DS18B20_STATUS_CRC_ERROR 0x02U /**< Не сошёлся CRC8 scratchpad. */
#define DS18B20_STATUS_NO_PRESENCE 0x04U /**< Датчик не ответил на сброс. */
#define DS18B20_STATUS_PARASITE 0x08U /**< Датчик питается паразитно. */
#define DS18B20_WRITE_OK 0U /**< Запись подтверждена обратным чтением. */
#define DS18B20_WRITE_BUS_ERROR 1U /**< Нет presence или не сошёлся CRC8. */
#define DS18B20_WRITE_REJECTED 2U /**< Датчик ответил, но значение не изменил. */
/**
* @brief Состояние одного датчика на шине.
*/
typedef struct {
uint8_t rom[ONEWIRE_ROM_SIZE]; /**< Уникальный 64-битный идентификатор. */
int16_t raw; /**< Код температуры из scratchpad. */
uint8_t user_byte1; /**< Пользовательский байт 1 (регистр TH). */
uint8_t user_byte2; /**< Пользовательский байт 2 (регистр TL). */
uint8_t config; /**< Байт конфигурации с разрешением. */
uint8_t status; /**< Битовая маска DS18B20_STATUS_*. */
} DS18B20_Sensor;
/**
* @brief Перечень датчиков шины и общее состояние питания.
*/
typedef struct {
DS18B20_Sensor sensors[DS18B20_MAX_SENSORS]; /**< Найденные датчики. */
uint8_t count; /**< Число заполненных элементов массива. */
uint8_t parasite; /**< 1, если хотя бы один датчик питается паразитно. */
} DS18B20_Bus;
/**
* @brief Ищет датчики DS18B20 и запоминает их идентификаторы.
*
* Устройства других семейств пропускаются; ранее накопленные измерения
* сбрасываются.
*
* @param bus Перечень датчиков, принадлежащий приложению.
* @return Количество найденных датчиков.
*/
uint8_t DS18B20_Scan(DS18B20_Bus *bus);
/**
* @brief Запускает преобразование во всех датчиках командой SKIP ROM.
*
* @return 1 при наличии presence-импульса, иначе 0.
*/
uint8_t DS18B20_StartConversion(void);
/**
* @brief Читает scratchpad всех известных датчиков и обновляет измерения.
*
* Ошибка отдельного датчика не прерывает опрос остальных: она отражается в
* поле status соответствующего элемента.
*
* @param bus Перечень датчиков, заполненный DS18B20_Scan.
* @return Количество датчиков с достоверным измерением.
*/
uint8_t DS18B20_ReadAll(DS18B20_Bus *bus);
/**
* @brief Возвращает время преобразования для заданного разрешения.
*
* @param config Байт конфигурации scratchpad.
* @return Время преобразования в миллисекундах.
*/
uint16_t DS18B20_ConversionTimeMs(uint8_t config);
/**
* @brief Собирает байт конфигурации из разрешения в битах.
*
* @param bits Разрешение 9..12 бит.
* @return Байт конфигурации scratchpad.
*/
uint8_t DS18B20_ConfigFromResolution(uint8_t bits);
/**
* @brief Ищет датчик по идентификатору в накопленном перечне.
*
* @param bus Перечень датчиков.
* @param rom Искомый идентификатор.
* @return Указатель на элемент перечня либо 0, если ROM не найден.
*/
DS18B20_Sensor *DS18B20_Find(DS18B20_Bus *bus, const uint8_t *rom);
/**
* @brief Записывает три байта scratchpad выбранного датчика.
*
* @param sensor Датчик из перечня; кэш обновляется при успешной записи.
* @param user_byte1 Новое значение пользовательского байта 1 (TH).
* @param user_byte2 Новое значение пользовательского байта 2 (TL).
* @param config Байт конфигурации с требуемым разрешением.
* @param save_to_eeprom Признак копирования scratchpad в EEPROM датчика.
* @return DS18B20_WRITE_OK при подтверждённой записи, DS18B20_WRITE_BUS_ERROR
* при молчании датчика или сбое CRC8, DS18B20_WRITE_REJECTED если
* датчик ответил, но оставил прежние значения.
*/
uint8_t DS18B20_WriteScratchpad(DS18B20_Sensor *sensor, uint8_t user_byte1,
uint8_t user_byte2, uint8_t config,
uint8_t save_to_eeprom);
#endif /* DS18B20_H */

149
c/ds18b20/include/onewire.h Normal file
View File

@@ -0,0 +1,149 @@
/**
* @file onewire.h
* @brief Программная шина 1-Wire на выводе с открытым стоком.
*
* Драйвер не зависит от типа датчика: он выдаёт тайминги, ищет ROM и считает
* CRC8. Работа с DS18B20 вынесена в ds18b20.c.
*
* Реализация разделена надвое:
* src/onewire.c переносимая часть - побайтовый обмен, CRC8, поиск ROM,
* MATCH ROM и SKIP ROM; ни регистров, ни задержек;
* ports/<платформа> пять функций, зависящих от железа: OneWire_Init,
* OneWire_DelayUs, OneWire_Reset, OneWire_WriteBit
* и OneWire_ReadBit.
*
* Чтобы перенести библиотеку на другой микроконтроллер, достаточно написать
* эти пять функций - остальное собирается как есть.
*/
#ifndef ONEWIRE_H
#define ONEWIRE_H
#include <stdint.h>
/** Размер идентификатора устройства 1-Wire в байтах. */
#define ONEWIRE_ROM_SIZE 8U
/**
* @brief Состояние обхода шины алгоритмом SEARCH ROM.
*
* Структура принадлежит вызывающему коду; параллельные обходы должны
* использовать разные экземпляры.
*/
typedef struct {
uint8_t rom[ONEWIRE_ROM_SIZE]; /**< Текущий найденный идентификатор. */
uint8_t last_discrepancy; /**< Позиция последней развилки, 1..64. */
uint8_t last_family_discrepancy; /**< Развилка внутри кода семейства. */
uint8_t last_device; /**< Признак завершённого обхода. */
} OneWire_Search;
/**
* @brief Настраивает вывод шины и счётчик циклов для микросекундных задержек.
*
* Вызывается один раз после инициализации тактирования системы.
*/
void OneWire_Init(void);
/**
* @brief Формирует импульс сброса и проверяет ответ устройств.
*
* @note На время выборки presence прерывания запрещаются примерно на 75 мкс;
* длительность самого импульса сброса на точность не влияет.
*
* @return 1, если хотя бы одно устройство выдало presence-импульс, иначе 0.
*/
uint8_t OneWire_Reset(void);
/**
* @brief Передаёт один бит в тайм-слоте записи.
*
* @param bit Передаваемое значение, отличное от нуля означает единицу.
*/
void OneWire_WriteBit(uint8_t bit);
/**
* @brief Считывает один бит в тайм-слоте чтения.
*
* @return Принятое значение бита: 0 или 1.
*/
uint8_t OneWire_ReadBit(void);
/**
* @brief Передаёт байт младшим битом вперёд.
*
* @param value Передаваемый байт.
*/
void OneWire_WriteByte(uint8_t value);
/**
* @brief Принимает байт младшим битом вперёд.
*
* @return Принятый байт.
*/
uint8_t OneWire_ReadByte(void);
/**
* @brief Передаёт блок байтов подряд без промежуточных сбросов.
*
* @param data Указатель на передаваемые байты.
* @param size Количество байтов.
*/
void OneWire_WriteBytes(const uint8_t *data, uint32_t size);
/**
* @brief Принимает блок байтов подряд.
*
* @param data Буфер приёмника.
* @param size Количество байтов.
*/
void OneWire_ReadBytes(uint8_t *data, uint32_t size);
/**
* @brief Считает CRC8 Dallas/Maxim с полиномом 0x8C.
*
* @param data Проверяемые байты.
* @param size Количество байтов.
* @return Значение CRC8; для блока вместе с контрольным байтом равно нулю.
*/
uint8_t OneWire_Crc8(const uint8_t *data, uint32_t size);
/**
* @brief Подготавливает состояние к новому полному обходу шины.
*
* @param search Состояние обхода, принадлежащее вызывающему коду.
*/
void OneWire_SearchInit(OneWire_Search *search);
/**
* @brief Находит следующий идентификатор на шине.
*
* @param search Состояние обхода, ранее подготовленное OneWire_SearchInit.
* @return 1, если поле rom содержит новый проверенный ROM, иначе 0.
*/
uint8_t OneWire_SearchNext(OneWire_Search *search);
/**
* @brief Адресует конкретное устройство командой MATCH ROM.
*
* @param rom Восьмибайтовый идентификатор устройства.
* @return 1 при наличии presence-импульса, иначе 0.
*/
uint8_t OneWire_SelectRom(const uint8_t *rom);
/**
* @brief Адресует все устройства шины командой SKIP ROM.
*
* @return 1 при наличии presence-импульса, иначе 0.
*/
uint8_t OneWire_SkipRom(void);
/**
* @brief Задерживает выполнение на заданное число микросекунд.
*
* Используется драйверами шины и модулем DS18B20 при ожидании EEPROM.
*
* @param microseconds Длительность задержки.
*/
void OneWire_DelayUs(uint32_t microseconds);
#endif /* ONEWIRE_H */

View File

@@ -0,0 +1,26 @@
/**
* @file onewire_config.template.h
* @brief Шаблон привязки шины 1-Wire для порта STM32F1.
*
* Скопируйте в своё приложение под именем onewire_config.h и положите на
* путь поиска заголовков. Библиотека сама этот файл не подставляет:
* привязка выводов принадлежит плате, а не драйверу.
*
* Требуется внешняя подтяжка 4.7 кОм к питанию.
*/
#ifndef ONEWIRE_CONFIG_H
#define ONEWIRE_CONFIG_H
#include "stm32f10x.h"
/** Порт вывода шины. */
#define ONEWIRE_GPIO GPIOA
/** Номер вывода в порту, 0..15. */
#define ONEWIRE_PIN 1U
/** Бит включения тактирования порта в RCC->APB2ENR. */
#define ONEWIRE_GPIO_CLOCK RCC_APB2ENR_IOPAEN
#endif /* ONEWIRE_CONFIG_H */

View File

@@ -0,0 +1,158 @@
/**
* @file onewire_stm32f1.c
* @brief Порт шины 1-Wire на STM32F1: тайминги, вывод и задержки.
*
* Вывод постоянно настроен как выход с открытым стоком: чтение уровня
* выполняется через регистр IDR, поэтому переключать режим не требуется.
* Прерывания запрещаются только на длительность одного тайм-слота, чтобы не
* терять байты USART во время обмена.
*
* Привязка вывода приходит из onewire_config.h, который предоставляет
* приложение. Шаблон - onewire_config.template.h рядом с этим файлом.
*/
#include "onewire.h"
#include "onewire_config.h"
#if !defined(ONEWIRE_GPIO) || !defined(ONEWIRE_PIN) || !defined(ONEWIRE_GPIO_CLOCK)
#error "onewire_config.h должен задать ONEWIRE_GPIO, ONEWIRE_PIN и ONEWIRE_GPIO_CLOCK"
#endif
/** Длительности тайм-слотов в микросекундах согласно рекомендациям Maxim. */
#define OW_RESET_LOW_US 480U
#define OW_PRESENCE_WAIT_US 70U
#define OW_RESET_TAIL_US 410U
#define OW_WRITE1_LOW_US 6U
#define OW_WRITE1_TAIL_US 64U
#define OW_WRITE0_LOW_US 60U
#define OW_WRITE0_TAIL_US 10U
#define OW_READ_LOW_US 6U
#define OW_READ_SAMPLE_US 9U
#define OW_READ_TAIL_US 55U
/** Число тактов ядра на микросекунду; заполняется в OneWire_Init. */
static uint32_t g_cycles_per_us = 72U;
/**
* @brief Переводит вывод шины в активный низкий уровень.
*/
static void ow_drive_low(void)
{
ONEWIRE_GPIO->BRR = (uint32_t)(1UL << ONEWIRE_PIN);
}
/**
* @brief Отпускает шину; уровень поднимает внешний резистор подтяжки.
*/
static void ow_release(void)
{
ONEWIRE_GPIO->BSRR = (uint32_t)(1UL << ONEWIRE_PIN);
}
/**
* @brief Читает текущий уровень на выводе шины.
*
* @return 1 для высокого уровня, 0 для низкого.
*/
static uint8_t ow_read_pin(void)
{
return (uint8_t)((ONEWIRE_GPIO->IDR >> ONEWIRE_PIN) & 1U);
}
void OneWire_DelayUs(uint32_t microseconds)
{
const uint32_t start = DWT->CYCCNT;
const uint32_t ticks = microseconds * g_cycles_per_us;
while ((DWT->CYCCNT - start) < ticks) {
__NOP();
}
}
void OneWire_Init(void)
{
uint32_t config;
g_cycles_per_us = SystemCoreClock / 1000000U;
if (g_cycles_per_us == 0U) {
g_cycles_per_us = 1U;
}
/* Счётчик тактов ядра — единственный источник микросекундных задержек. */
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CYCCNT = 0U;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
RCC->APB2ENR |= ONEWIRE_GPIO_CLOCK;
/* Выход с открытым стоком, 50 МГц: CNF = 01, MODE = 11. */
if (ONEWIRE_PIN < 8U) {
config = ONEWIRE_GPIO->CRL;
config &= ~(0xFUL << (ONEWIRE_PIN * 4U));
config |= (0x7UL << (ONEWIRE_PIN * 4U));
ONEWIRE_GPIO->CRL = config;
} else {
config = ONEWIRE_GPIO->CRH;
config &= ~(0xFUL << ((ONEWIRE_PIN - 8U) * 4U));
config |= (0x7UL << ((ONEWIRE_PIN - 8U) * 4U));
ONEWIRE_GPIO->CRH = config;
}
ow_release();
OneWire_DelayUs(OW_RESET_LOW_US);
}
uint8_t OneWire_Reset(void)
{
uint32_t primask;
uint8_t presence;
/* Удлинение импульса сброса безопасно, поэтому прерывания не запрещаем. */
ow_drive_low();
OneWire_DelayUs(OW_RESET_LOW_US);
primask = __get_PRIMASK();
__disable_irq();
ow_release();
OneWire_DelayUs(OW_PRESENCE_WAIT_US);
presence = (uint8_t)(ow_read_pin() == 0U);
__set_PRIMASK(primask);
OneWire_DelayUs(OW_RESET_TAIL_US);
return (uint8_t)(presence && (ow_read_pin() != 0U));
}
void OneWire_WriteBit(uint8_t bit)
{
const uint32_t primask = __get_PRIMASK();
__disable_irq();
ow_drive_low();
if (bit != 0U) {
OneWire_DelayUs(OW_WRITE1_LOW_US);
ow_release();
OneWire_DelayUs(OW_WRITE1_TAIL_US);
} else {
OneWire_DelayUs(OW_WRITE0_LOW_US);
ow_release();
OneWire_DelayUs(OW_WRITE0_TAIL_US);
}
__set_PRIMASK(primask);
}
uint8_t OneWire_ReadBit(void)
{
const uint32_t primask = __get_PRIMASK();
uint8_t bit;
__disable_irq();
ow_drive_low();
OneWire_DelayUs(OW_READ_LOW_US);
ow_release();
OneWire_DelayUs(OW_READ_SAMPLE_US);
bit = ow_read_pin();
__set_PRIMASK(primask);
OneWire_DelayUs(OW_READ_TAIL_US);
return bit;
}

202
c/ds18b20/src/ds18b20.c Normal file
View File

@@ -0,0 +1,202 @@
/** test
* @file ds18b20.c
* @brief Реализация драйвера DS18B20: поиск, чтение и запись scratchpad.
*/
#include "ds18b20.h"
#define DS_CMD_CONVERT_T 0x44U
#define DS_CMD_WRITE_SCRATCHPAD 0x4EU
#define DS_CMD_READ_SCRATCHPAD 0xBEU
#define DS_CMD_COPY_SCRATCHPAD 0x48U
#define DS_CMD_READ_POWER 0xB4U
/** Задержка записи EEPROM датчика по datasheet, мкс. */
#define DS_EEPROM_WRITE_US 12000U
/**
* @brief Считывает scratchpad адресованного датчика с проверкой CRC8.
*
* @param rom Идентификатор датчика.
* @param scratchpad Буфер на DS18B20_SCRATCHPAD_SIZE байт.
* @return Битовая маска ошибок DS18B20_STATUS_* без бита VALID.
*/
static uint8_t ds_read_scratchpad(const uint8_t *rom, uint8_t *scratchpad)
{
if (OneWire_SelectRom(rom) == 0U) {
return DS18B20_STATUS_NO_PRESENCE;
}
OneWire_WriteByte(DS_CMD_READ_SCRATCHPAD);
OneWire_ReadBytes(scratchpad, DS18B20_SCRATCHPAD_SIZE);
if (OneWire_Crc8(scratchpad, DS18B20_SCRATCHPAD_SIZE) != 0U) {
return DS18B20_STATUS_CRC_ERROR;
}
return 0U;
}
/**
* @brief Опрашивает тип питания датчика командой READ POWER SUPPLY.
*
* @param rom Идентификатор датчика.
* @return 1 для паразитного питания, иначе 0.
*/
static uint8_t ds_read_parasite(const uint8_t *rom)
{
if (OneWire_SelectRom(rom) == 0U) {
return 0U;
}
OneWire_WriteByte(DS_CMD_READ_POWER);
/* Датчик с паразитным питанием прижимает шину к нулю. */
return (uint8_t)(OneWire_ReadBit() == 0U);
}
uint8_t DS18B20_ConfigFromResolution(uint8_t bits)
{
if (bits < 9U) {
bits = 9U;
} else if (bits > 12U) {
bits = 12U;
}
return (uint8_t)(0x1FU | ((bits - 9U) << 5U));
}
uint16_t DS18B20_ConversionTimeMs(uint8_t config)
{
switch ((config >> 5U) & 0x03U) {
case 0U:
return 94U;
case 1U:
return 188U;
case 2U:
return 375U;
default:
return 750U;
}
}
uint8_t DS18B20_Scan(DS18B20_Bus *bus)
{
OneWire_Search search;
uint8_t index;
bus->count = 0U;
bus->parasite = 0U;
OneWire_SearchInit(&search);
while ((bus->count < DS18B20_MAX_SENSORS) && (OneWire_SearchNext(&search) != 0U)) {
if (search.rom[0] != DS18B20_FAMILY_CODE) {
continue;
}
DS18B20_Sensor *sensor = &bus->sensors[bus->count];
for (index = 0U; index < ONEWIRE_ROM_SIZE; index++) {
sensor->rom[index] = search.rom[index];
}
sensor->raw = 0;
sensor->user_byte1 = 0U;
sensor->user_byte2 = 0U;
sensor->config = DS18B20_ConfigFromResolution(12U);
sensor->status = 0U;
if (ds_read_parasite(sensor->rom) != 0U) {
sensor->status |= DS18B20_STATUS_PARASITE;
bus->parasite = 1U;
}
bus->count++;
}
return bus->count;
}
uint8_t DS18B20_StartConversion(void)
{
if (OneWire_SkipRom() == 0U) {
return 0U;
}
OneWire_WriteByte(DS_CMD_CONVERT_T);
return 1U;
}
uint8_t DS18B20_ReadAll(DS18B20_Bus *bus)
{
uint8_t scratchpad[DS18B20_SCRATCHPAD_SIZE];
uint8_t valid = 0U;
uint8_t index;
for (index = 0U; index < bus->count; index++) {
DS18B20_Sensor *sensor = &bus->sensors[index];
const uint8_t parasite = (uint8_t)(sensor->status & DS18B20_STATUS_PARASITE);
const uint8_t error = ds_read_scratchpad(sensor->rom, scratchpad);
if (error != 0U) {
sensor->status = (uint8_t)(parasite | error);
continue;
}
sensor->raw = (int16_t)((uint16_t)scratchpad[0] | ((uint16_t)scratchpad[1] << 8U));
sensor->user_byte1 = scratchpad[2];
sensor->user_byte2 = scratchpad[3];
sensor->config = scratchpad[4];
sensor->status = (uint8_t)(parasite | DS18B20_STATUS_VALID);
valid++;
}
return valid;
}
DS18B20_Sensor *DS18B20_Find(DS18B20_Bus *bus, const uint8_t *rom)
{
uint8_t index;
uint8_t byte;
for (index = 0U; index < bus->count; index++) {
for (byte = 0U; byte < ONEWIRE_ROM_SIZE; byte++) {
if (bus->sensors[index].rom[byte] != rom[byte]) {
break;
}
}
if (byte == ONEWIRE_ROM_SIZE) {
return &bus->sensors[index];
}
}
return 0;
}
uint8_t DS18B20_WriteScratchpad(DS18B20_Sensor *sensor, uint8_t user_byte1,
uint8_t user_byte2, uint8_t config,
uint8_t save_to_eeprom)
{
uint8_t scratchpad[DS18B20_SCRATCHPAD_SIZE];
if (OneWire_SelectRom(sensor->rom) == 0U) {
sensor->status = (uint8_t)((sensor->status & DS18B20_STATUS_PARASITE)
| DS18B20_STATUS_NO_PRESENCE);
return DS18B20_WRITE_BUS_ERROR;
}
OneWire_WriteByte(DS_CMD_WRITE_SCRATCHPAD);
OneWire_WriteByte(user_byte1);
OneWire_WriteByte(user_byte2);
OneWire_WriteByte(config);
/* Обратное чтение подтверждает, что датчик принял все три байта. */
if (ds_read_scratchpad(sensor->rom, scratchpad) != 0U) {
return DS18B20_WRITE_BUS_ERROR;
}
if ((scratchpad[2] != user_byte1) || (scratchpad[3] != user_byte2)
|| (scratchpad[4] != config)) {
/* Обмен состоялся, но регистры остались прежними: значение отвергнуто. */
sensor->user_byte1 = scratchpad[2];
sensor->user_byte2 = scratchpad[3];
sensor->config = scratchpad[4];
return DS18B20_WRITE_REJECTED;
}
sensor->user_byte1 = scratchpad[2];
sensor->user_byte2 = scratchpad[3];
sensor->config = scratchpad[4];
if (save_to_eeprom != 0U) {
if (OneWire_SelectRom(sensor->rom) == 0U) {
return DS18B20_WRITE_BUS_ERROR;
}
OneWire_WriteByte(DS_CMD_COPY_SCRATCHPAD);
/* Датчик с внешним питанием тоже допускает выдержку по времени. */
OneWire_DelayUs(DS_EEPROM_WRITE_US);
}
return DS18B20_WRITE_OK;
}

177
c/ds18b20/src/onewire.c Normal file
View File

@@ -0,0 +1,177 @@
/**
* @file onewire.c
* @brief Переносимая часть шины 1-Wire: побайтовый обмен, CRC8 и поиск ROM.
*
* Здесь нет ни регистров, ни задержек, ни запрета прерываний: всё это живёт
* в порте (ports/), а этот файл собирается любым C99-компилятором и
* проверяется host-тестами на симуляторе шины.
*
* От порта требуются пять функций: OneWire_Init, OneWire_DelayUs,
* OneWire_Reset, OneWire_WriteBit и OneWire_ReadBit.
*/
#include "onewire.h"
#define OW_CMD_SEARCH_ROM 0xF0U
#define OW_CMD_MATCH_ROM 0x55U
#define OW_CMD_SKIP_ROM 0xCCU
void OneWire_WriteByte(uint8_t value)
{
uint8_t index;
for (index = 0U; index < 8U; index++) {
OneWire_WriteBit((uint8_t)(value & 1U));
value = (uint8_t)(value >> 1U);
}
}
uint8_t OneWire_ReadByte(void)
{
uint8_t value = 0U;
uint8_t index;
for (index = 0U; index < 8U; index++) {
value = (uint8_t)(value >> 1U);
if (OneWire_ReadBit() != 0U) {
value |= 0x80U;
}
}
return value;
}
void OneWire_WriteBytes(const uint8_t *data, uint32_t size)
{
uint32_t index;
for (index = 0U; index < size; index++) {
OneWire_WriteByte(data[index]);
}
}
void OneWire_ReadBytes(uint8_t *data, uint32_t size)
{
uint32_t index;
for (index = 0U; index < size; index++) {
data[index] = OneWire_ReadByte();
}
}
uint8_t OneWire_Crc8(const uint8_t *data, uint32_t size)
{
uint8_t crc = 0U;
uint32_t index;
uint8_t bit;
for (index = 0U; index < size; index++) {
crc ^= data[index];
for (bit = 0U; bit < 8U; bit++) {
crc = (uint8_t)((crc & 1U) != 0U ? ((crc >> 1U) ^ 0x8CU) : (crc >> 1U));
}
}
return crc;
}
void OneWire_SearchInit(OneWire_Search *search)
{
uint32_t index;
for (index = 0U; index < ONEWIRE_ROM_SIZE; index++) {
search->rom[index] = 0U;
}
search->last_discrepancy = 0U;
search->last_family_discrepancy = 0U;
search->last_device = 0U;
}
uint8_t OneWire_SearchNext(OneWire_Search *search)
{
uint8_t id_bit_number = 1U;
uint8_t last_zero = 0U;
uint8_t rom_byte_number = 0U;
uint8_t rom_byte_mask = 1U;
uint8_t search_direction;
uint8_t id_bit;
uint8_t cmp_id_bit;
if (search->last_device != 0U) {
OneWire_SearchInit(search);
return 0U;
}
if (OneWire_Reset() == 0U) {
OneWire_SearchInit(search);
return 0U;
}
OneWire_WriteByte(OW_CMD_SEARCH_ROM);
do {
id_bit = OneWire_ReadBit();
cmp_id_bit = OneWire_ReadBit();
if ((id_bit != 0U) && (cmp_id_bit != 0U)) {
/* Ни одно устройство не ответило: обход прерван. */
OneWire_SearchInit(search);
return 0U;
}
if (id_bit != cmp_id_bit) {
search_direction = id_bit;
} else if (id_bit_number < search->last_discrepancy) {
search_direction =
(uint8_t)((search->rom[rom_byte_number] & rom_byte_mask) != 0U);
} else {
search_direction = (uint8_t)(id_bit_number == search->last_discrepancy);
}
if ((id_bit == cmp_id_bit) && (search_direction == 0U)) {
last_zero = id_bit_number;
if (last_zero < 9U) {
search->last_family_discrepancy = last_zero;
}
}
if (search_direction != 0U) {
search->rom[rom_byte_number] |= rom_byte_mask;
} else {
search->rom[rom_byte_number] &= (uint8_t)~rom_byte_mask;
}
OneWire_WriteBit(search_direction);
id_bit_number++;
rom_byte_mask = (uint8_t)(rom_byte_mask << 1U);
if (rom_byte_mask == 0U) {
rom_byte_number++;
rom_byte_mask = 1U;
}
} while (rom_byte_number < ONEWIRE_ROM_SIZE);
if (OneWire_Crc8(search->rom, ONEWIRE_ROM_SIZE) != 0U) {
OneWire_SearchInit(search);
return 0U;
}
search->last_discrepancy = last_zero;
if (search->last_discrepancy == 0U) {
search->last_device = 1U;
}
return 1U;
}
uint8_t OneWire_SelectRom(const uint8_t *rom)
{
if (OneWire_Reset() == 0U) {
return 0U;
}
OneWire_WriteByte(OW_CMD_MATCH_ROM);
OneWire_WriteBytes(rom, ONEWIRE_ROM_SIZE);
return 1U;
}
uint8_t OneWire_SkipRom(void)
{
if (OneWire_Reset() == 0U) {
return 0U;
}
OneWire_WriteByte(OW_CMD_SKIP_ROM);
return 1U;
}

View 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;
}

View 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 */

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;
}