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:
35
c/ds18b20/CMakeLists.txt
Normal file
35
c/ds18b20/CMakeLists.txt
Normal 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
127
c/ds18b20/README.md
Normal 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
125
c/ds18b20/include/ds18b20.h
Normal 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
149
c/ds18b20/include/onewire.h
Normal 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 */
|
||||
26
c/ds18b20/ports/stm32f1/onewire_config.template.h
Normal file
26
c/ds18b20/ports/stm32f1/onewire_config.template.h
Normal 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 */
|
||||
158
c/ds18b20/ports/stm32f1/onewire_stm32f1.c
Normal file
158
c/ds18b20/ports/stm32f1/onewire_stm32f1.c
Normal 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
202
c/ds18b20/src/ds18b20.c
Normal 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
177
c/ds18b20/src/onewire.c
Normal 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;
|
||||
}
|
||||
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