diff --git a/README.md b/README.md index b8eb5f8..82fc189 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ templates/ | [`c/eeprom-ft24c256`](c/eeprom-ft24c256) | EEPROM 24Cxx по I²C с нарезкой записи по страницам | `stdint.h` | две I²C-транзакции, задержка | | [`c/can-sensor`](c/can-sensor) | однокадровые SETCAN SETTINGS для 64-битных ROM | ядро: `stdint.h`; порт F1: CMSIS | callbacks либо готовый bxCAN STM32F1 | | [`c/ds18b20`](c/ds18b20) | термометры DS18B20 поверх программной 1-Wire | `stdint.h` | Init, DelayUs, Reset, WriteBit, ReadBit — **порты STM32F103, STM32G431 и STM32G474 в комплекте** | +| [`c/ds18b20-ds2480`](c/ds18b20-ds2480) | DS18B20 через DS2480B: поиск ROM, температура, EEPROM, паразитное питание | C99 | UART 9600 8N1, сброс моста, задержка | | [`c/set-protocol`](c/set-protocol) | единое ядро SETProtocol: SET v2, совместимые ProtoCAN/GUI v1, GAS, телеметрия, firmware flow и стабильный host ABI | C99 | COM/SLCAN/SocketCAN/USB/Ethernet или callbacks — **Windows, Android и STM32F4-порты в комплекте** | | [`c/set-protocol/ports/stm32f407-devboard-v1`](c/set-protocol/ports/stm32f407-devboard-v1) | доступ SETGUI к Modbus-регистрам F407 через CAN485 DevBoard_V1 | `pcan_modbus_server`, STM32 HAL CAN | bxCAN FIFO0 и callbacks карты регистров | | [`c/set-protocol/ports/stm32-bxcan`](c/set-protocol/ports/stm32-bxcan) | порт прикладного ProtoCAN для STM32, бывший SETCAN; сохранён API `PROTOCAN_*` | STM32 HAL CAN/RTC/TIM + общее ядро `pcan_id` | classic bxCAN; настройки платы предоставляет прошивка | diff --git a/c/ds18b20-ds2480/CMakeLists.txt b/c/ds18b20-ds2480/CMakeLists.txt new file mode 100644 index 0000000..40698a2 --- /dev/null +++ b/c/ds18b20-ds2480/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.13) +project(ds18b20_ds2480 C) +add_library(ds18b20_ds2480 STATIC ds2480.c ds18b20_ds2480.c) +target_include_directories(ds18b20_ds2480 PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_compile_features(ds18b20_ds2480 PUBLIC c_std_99) +if(MSVC) + target_compile_options(ds18b20_ds2480 PRIVATE /W4) +else() + target_compile_options(ds18b20_ds2480 PRIVATE -Wall -Wextra -Wpedantic) +endif() +option(DS18B20_DS2480_BUILD_TESTS "Build host tests" ON) +if(DS18B20_DS2480_BUILD_TESTS) + enable_testing() + add_executable(test_ds18b20_ds2480 tests/test_ds18b20_ds2480.c) + target_link_libraries(test_ds18b20_ds2480 PRIVATE ds18b20_ds2480) + add_test(NAME ds18b20_ds2480 COMMAND test_ds18b20_ds2480) +endif() diff --git a/c/ds18b20-ds2480/README.md b/c/ds18b20-ds2480/README.md new file mode 100644 index 0000000..524d2b6 --- /dev/null +++ b/c/ds18b20-ds2480/README.md @@ -0,0 +1,145 @@ +# DS18B20 через DS2480B + +Переносимая библиотека C99: поиск DS18B20 на общей шине, температура, +разрешение 9–12 бит, TH/TL и сохранение в EEPROM через UART-мост DS2480B. +Поддерживаются внешнее и паразитное питание: преобразование и запись EEPROM +используют strong pullup, включаемый мостом сразу после последнего бита команды. + +Ядро не зависит от HAL, CMSIS, ОС или существующей GPIO-библиотеки `ds18b20`. +Динамической памяти и глобального состояния нет. Каждый UART-мост имеет свой +`ds2480`, каждый обход — свой `ds2480_search`. + +```text +Приложение → ds18b20_ds2480 → ds2480 → callbacks UART/задержки → платформа +``` + +## Файлы + +| Файл | Назначение | Зависимости | +|---|---|---| +| `ds2480.h`, `ds2480.c` | Калибровка, reset, обмен битами/байтами, поиск ROM, CRC, strong pullup | `stdint.h`, callbacks | +| `ds18b20_ds2480.h`, `ds18b20_ds2480.c` | Команды термометра, проверка scratchpad, знаковая температура | `ds2480`, `string.h` | +| `examples/read_first.c` | Полный цикл для первого найденного DS18B20 | ядро, порт приложения | +| `tests/test_ds18b20_ds2480.c` | Модель UART-моста и нескольких устройств 1-Wire | ядро, стандартная библиотека C | + +## Контракт порта + +```c +int prepare(void *user); +int write(void *user, const uint8_t *data, uint32_t size, uint32_t timeout_ms); +int read(void *user, uint8_t *data, uint32_t size, uint32_t timeout_ms); +void delay_ms(void *user, uint32_t ms); +``` + +Первые три callbacks возвращают `0` при успехе, иначе ошибку. +`prepare` аппаратно сбрасывает DS2480B или формирует UART BREAK не короче 2 мс, +настраивает **9600 бод, 8N1**, выдерживает минимум 2 мс после сброса и очищает RX +и ошибки UART. Эта функция должна иметь собственный конечный таймаут. +Нельзя просто посылать `C1` работающему мосту вместо сброса. + +`write` передаёт ровно `size` байтов и ждёт окончания передачи; `read` получает +ровно `size` байтов. Обе операции ограничены `timeout_ms`; при частичном обмене +возвращается ошибка. Приём должен работать уже во время передачи: ответ может +появиться до вызова `read`. `delay_ms` не должна возвращаться раньше заданного +времени (учтите округление системного тика). + +Управлять одним UART извне одновременно с библиотекой нельзя. В RTOS блокировка +нужна на всю операцию верхнего уровня, включая поиск и ожидание преобразования. +Тайминги 1-Wire формирует мост, запрещать прерывания на время слотов не требуется. + +## Быстрый старт + +Функции `prepare`, `write`, `read`, `delay_ms` и `uart_context` предоставляет плата. +Следующий код располагается внутри функции приложения: + +```c +ds2480 bus; +ds2480_search search = {{0}, 0, 0}; +ds2480_port port = {uart_context, prepare, write, read, delay_ms}; +int16_t raw; +ds2480_status status = ds2480_init(&bus, &port, 20); +if (status != DS2480_OK) return; +status = ds18b20_ds2480_next(&bus, &search); +if (status != DS2480_OK) return; +status = ds18b20_ds2480_convert(&bus, search.rom); +if (status != DS2480_OK) return; +status = ds18b20_ds2480_temperature(&bus, search.rom, &raw); +if (status != DS2480_OK) return; +float celsius = raw / 16.0f; +(void)celsius; +``` + +Вызывайте `next` повторно с тем же курсором до `DS2480_DONE`, сохраняя каждый +найденный ROM в памяти приложения. Лимита количества датчиков в ядре нет. +Пустая шина возвращает `DS2480_NO_PRESENCE`; обрыв поиска и ошибки CRC не +маскируются под успешное завершение. Для нового обхода обнулите курсор. + +`convert(bus, NULL)` одновременно запускает все устройства — допустимо только +на шине исключительно с DS18B20. Затем читайте каждое по ROM. Для паразитного +питания суммарный ток должен укладываться в возможности моста; при необходимости +преобразуйте по одному датчику. Преобразование блокирует вызов на 750 мс плюс +UART-обмен. EEPROM использует выдержку 12 мс, её содержимое после перезапуска +этой функцией не проверяется. Чтение температуры само преобразование не запускает. +Значение 85 °C после включения нельзя отличить от настоящих 85 °C без завершённого +преобразования; не считайте его заведомой ошибкой. + +## Ошибки и ограничения + +- `IO` — таймаут/ошибка порта, `PROTOCOL` — неожиданный ответ DS2480B. + После них экземпляр не готов к работе: повторите `ds2480_init`. +- `SHORT` — короткое замыкание, `NO_PRESENCE` — нет presence. +- `CRC` — неверная контрольная сумма, `DATA` — недопустимые данные или + неподтверждённая запись. Выход температуры/scratchpad при ошибке не меняется. +- При ошибке во время strong pullup ядро вызывает `prepare`, чтобы прекратить + импульс, и остаётся неготовым. При отказе самого порта снятие питания гарантировать + невозможно; восстановление UART/моста остаётся задачей приложения. + +Используется стандартная скорость 1-Wire и только Command Mode: байт передаётся +восьмью командами Single Bit. Поэтому любые значения, включая `E3`, передаются +без экранирования. Это простая реализация для опроса температуры; повышенные +скорости UART, Overdrive и Search Accelerator пока не реализованы. Параметры +таймингов длинной линии остаются заводскими. Аппаратная проверка обязательна +для выбранной топологии/нагрузки. Протокол проверен по документации **DS2480B**; +старые ревизии DS2480 без суффикса B отдельно не проверялись. + +## Подключение и тесты + +Добавьте `ds2480.c`, `ds18b20_ds2480.c` и путь к заголовкам в сборку прошивки. +Номер UART, GPIO и библиотеку платформы выбирает приложение. Готовый порт +для конкретной платы пока не входит в библиотеку. + +```cmake +set(DS18B20_DS2480_BUILD_TESTS OFF CACHE BOOL "" FORCE) +add_subdirectory(third_party/templates/c/ds18b20-ds2480) +target_link_libraries(firmware PRIVATE ds18b20_ds2480) +``` + +Отдельная сборка хостовых тестов: + +```sh +cmake -S . -B build +cmake --build build --config Debug +ctest --test-dir build -C Debug --output-on-failure +``` + +Без CMake, из каталога библиотеки: + +```sh +clang -std=c99 -Wall -Wextra -Wpedantic -Werror -I . ds2480.c ds18b20_ds2480.c tests/test_ds18b20_ds2480.c -o build/test.exe +./build/test.exe +``` + +Тесты моделируют калибровку без ответа, несколько ROM с развилками, другие +семейства, адресацию, все разрешения, отрицательную температуру, CRC, пустую +и замкнутую шину, ошибки транспорта, strong pullup и восстановление после ошибок. +Это программная модель; испытания на физическом DS2480B пока не проводились. + +## Использование + +Добавлена в сабмодуль `templates` проекта `john103C6T6NewVer`, ветка `ds2480`. +В рабочую прошивку ещё не включена: для этого требуется порт выбранного UART. + +## Источники + +- [DS2480B datasheet, таблицы команд и ответов](https://www.analog.com/media/en/technical-documentation/data-sheets/ds2480b.pdf) +- [DS18B20 datasheet, команды, питание и scratchpad](https://www.analog.com/media/en/technical-documentation/data-sheets/ds18b20.pdf) diff --git a/c/ds18b20-ds2480/ds18b20_ds2480.c b/c/ds18b20-ds2480/ds18b20_ds2480.c new file mode 100644 index 0000000..b1c46a4 --- /dev/null +++ b/c/ds18b20-ds2480/ds18b20_ds2480.c @@ -0,0 +1,108 @@ +#include "ds18b20_ds2480.h" +#include + +static ds2480_status write_byte(ds2480 *bus, uint8_t value) +{ + uint8_t echoed; + ds2480_status status = ds2480_byte(bus, value, &echoed); + if (status != DS2480_OK) return status; + return echoed == value ? DS2480_OK : DS2480_DATA; +} + +static ds2480_status select_rom(ds2480 *bus, const uint8_t *rom) +{ + uint8_t i; + ds2480_status status; + if (rom && (rom[0] != 0x28 || ds2480_crc8(rom, 8))) return DS2480_ARGUMENT; + status = ds2480_reset(bus); + if (status != DS2480_OK) return status; + status = write_byte(bus, rom ? 0x55 : 0xCC); + if (status != DS2480_OK || !rom) return status; + for (i = 0; i < 8; ++i) { + status = write_byte(bus, rom[i]); + if (status != DS2480_OK) return status; + } + return DS2480_OK; +} + +ds2480_status ds18b20_ds2480_next(ds2480 *bus, ds2480_search *search) +{ + ds2480_status status; + do { + status = ds2480_search_next(bus, search); + if (status != DS2480_OK) return status; + } while (search->rom[0] != 0x28); + return DS2480_OK; +} + +ds2480_status ds18b20_ds2480_convert(ds2480 *bus, const uint8_t rom[8]) +{ + ds2480_status status = select_rom(bus, rom); + if (status != DS2480_OK) return status; + return ds2480_power_byte(bus, 0x44, 750); +} + +ds2480_status ds18b20_ds2480_read(ds2480 *bus, const uint8_t rom[8], uint8_t scratchpad[9]) +{ + uint8_t data[9], i; + ds2480_status status; + if (!rom || !scratchpad) return DS2480_ARGUMENT; + status = select_rom(bus, rom); + if (status != DS2480_OK) return status; + status = write_byte(bus, 0xBE); + if (status != DS2480_OK) return status; + for (i = 0; i < 9; ++i) { + status = ds2480_byte(bus, 0xFF, &data[i]); + if (status != DS2480_OK) return status; + } + if (ds2480_crc8(data, 9)) return DS2480_CRC; + /* All-zero data has valid CRC but cannot be a DS18B20 scratchpad. */ + if ((data[4] & 0x9F) != 0x1F) return DS2480_DATA; + memcpy(scratchpad, data, sizeof data); + return DS2480_OK; +} + +ds2480_status ds18b20_ds2480_temperature(ds2480 *bus, const uint8_t rom[8], int16_t *raw) +{ + uint8_t data[9], undefined; + uint16_t value; + int32_t signed_value; + ds2480_status status; + if (!raw) return DS2480_ARGUMENT; + status = ds18b20_ds2480_read(bus, rom, data); + if (status != DS2480_OK) return status; + undefined = (uint8_t)(3 - ((data[4] >> 5) & 3)); + value = (uint16_t)((uint16_t)data[0] | ((uint16_t)data[1] << 8)); + value &= (uint16_t)~((1U << undefined) - 1U); + signed_value = value & 0x8000 ? (int32_t)value - 65536 : (int32_t)value; + if (signed_value < -55 * 16 || signed_value > 125 * 16) return DS2480_DATA; + *raw = (int16_t)signed_value; + return DS2480_OK; +} + +ds2480_status ds18b20_ds2480_configure(ds2480 *bus, const uint8_t rom[8], + int8_t th, int8_t tl, uint8_t resolution, uint8_t save) +{ + uint8_t data[9], config; + ds2480_status status; + if (!rom || resolution < 9 || resolution > 12) return DS2480_ARGUMENT; + config = (uint8_t)(0x1F | ((resolution - 9) << 5)); + status = select_rom(bus, rom); + if (status != DS2480_OK) return status; + status = write_byte(bus, 0x4E); + if (status != DS2480_OK) return status; + status = write_byte(bus, (uint8_t)th); + if (status != DS2480_OK) return status; + status = write_byte(bus, (uint8_t)tl); + if (status != DS2480_OK) return status; + status = write_byte(bus, config); + if (status != DS2480_OK) return status; + status = ds18b20_ds2480_read(bus, rom, data); + if (status != DS2480_OK) return status; + if (data[2] != (uint8_t)th || data[3] != (uint8_t)tl || data[4] != config) + return DS2480_DATA; + if (!save) return DS2480_OK; + status = select_rom(bus, rom); + if (status != DS2480_OK) return status; + return ds2480_power_byte(bus, 0x48, 12); +} diff --git a/c/ds18b20-ds2480/ds18b20_ds2480.h b/c/ds18b20-ds2480/ds18b20_ds2480.h new file mode 100644 index 0000000..fc20cc9 --- /dev/null +++ b/c/ds18b20-ds2480/ds18b20_ds2480.h @@ -0,0 +1,32 @@ +#ifndef DS18B20_DS2480_H +#define DS18B20_DS2480_H +#include "ds2480.h" +#ifdef __cplusplus +extern "C" { +#endif + +/** Find next DS18B20, skipping other families; same cursor rules as bridge. */ +ds2480_status ds18b20_ds2480_next(ds2480 *bus, ds2480_search *search); +/** Start conversion and hold strong pullup for 750 ms (all resolutions). + * rom=NULL broadcasts: use only on a bus containing exclusively DS18B20. + * Supports external and parasite power within the bridge's current budget. + */ +ds2480_status ds18b20_ds2480_convert(ds2480 *bus, const uint8_t rom[8]); +/** Read and validate all 9 scratchpad bytes. Output is unchanged on error. + * rom is mandatory, must have family 0x28 and valid CRC. + */ +ds2480_status ds18b20_ds2480_read(ds2480 *bus, const uint8_t rom[8], uint8_t scratchpad[9]); +/** Read last conversion in signed 1/16 Celsius units; masks undefined bits + * for 9/10/11-bit resolution. Call convert first: power-on 85 C is ambiguous. + * Output is unchanged on error. Does not start a conversion itself. + */ +ds2480_status ds18b20_ds2480_temperature(ds2480 *bus, const uint8_t rom[8], int16_t *raw); +/** Set TH/TL and resolution (9..12); verify by reading back. + * save!=0 copies to EEPROM with 12 ms strong pullup; avoid frequent writes. + */ +ds2480_status ds18b20_ds2480_configure(ds2480 *bus, const uint8_t rom[8], + int8_t th, int8_t tl, uint8_t resolution, uint8_t save); +#ifdef __cplusplus +} +#endif +#endif diff --git a/c/ds18b20-ds2480/ds2480.c b/c/ds18b20-ds2480/ds2480.c new file mode 100644 index 0000000..8026083 --- /dev/null +++ b/c/ds18b20-ds2480/ds2480.c @@ -0,0 +1,164 @@ +#include "ds2480.h" + +static ds2480_status fault(ds2480 *bus, ds2480_status status) +{ + bus->ready = 0; + return status; +} + +static ds2480_status exchange(ds2480 *bus, uint8_t command, uint8_t *reply) +{ + if (!bus || !reply) return DS2480_ARGUMENT; + if (!bus->ready) return DS2480_NOT_READY; + if (bus->port.write(bus->port.user, &command, 1, bus->timeout_ms) || + bus->port.read(bus->port.user, reply, 1, bus->timeout_ms)) + return fault(bus, DS2480_IO); + return DS2480_OK; +} + +ds2480_status ds2480_init(ds2480 *bus, const ds2480_port *port, uint32_t timeout_ms) +{ + uint8_t calibration = 0xC1, reply; + ds2480_status status; + ds2480_port copy; + if (!bus) return DS2480_ARGUMENT; + if (!port || !port->prepare || !port->write || !port->read || + !port->delay_ms || !timeout_ms) { + bus->ready = 0; + return DS2480_ARGUMENT; + } + copy = *port; /* Also permit reinitialization with &bus->port. */ + bus->ready = 0; + bus->port = copy; + bus->timeout_ms = timeout_ms; + if (copy.prepare(copy.user) || + copy.write(copy.user, &calibration, 1, timeout_ms)) return DS2480_IO; + /* First C1 calibrates only: there is NO response byte. */ + copy.delay_ms(copy.user, 2); + bus->ready = 1; + /* Strong pullup duration = infinite, command-mode operations only. */ + status = exchange(bus, 0x3F, &reply); + if (status != DS2480_OK) return status; + if (reply != 0x3E) return fault(bus, DS2480_PROTOCOL); + return DS2480_OK; +} + +ds2480_status ds2480_reset(ds2480 *bus) +{ + uint8_t reply; + ds2480_status status = exchange(bus, 0xC1, &reply); + if (status != DS2480_OK) return status; + /* DS2480B revision pattern from table 2; bit 5 is unspecified. */ + if ((reply & 0xDC) != 0xCC) return fault(bus, DS2480_PROTOCOL); + switch (reply & 3) { + case 0: return DS2480_SHORT; + case 3: return DS2480_NO_PRESENCE; + default: return DS2480_OK; /* Ordinary or alarming presence. */ + } +} + +static ds2480_status slot(ds2480 *bus, uint8_t bit, uint8_t power, uint8_t *received) +{ + uint8_t reply, command = (uint8_t)(0x81 | (bit ? 0x10 : 0) | (power ? 2 : 0)); + ds2480_status status = exchange(bus, command, &reply); + if (status != DS2480_OK) return status; + if ((reply & 0xFC) != (command & 0xFC) || + ((reply & 3) != 0 && (reply & 3) != 3)) + return fault(bus, DS2480_PROTOCOL); + *received = reply & 1; + return DS2480_OK; +} + +ds2480_status ds2480_bit(ds2480 *bus, uint8_t bit, uint8_t *received) +{ + if (!received) return DS2480_ARGUMENT; + return slot(bus, bit, 0, received); +} + +ds2480_status ds2480_byte(ds2480 *bus, uint8_t value, uint8_t *received) +{ + uint8_t i, bit, result = 0; + ds2480_status status; + if (!received) return DS2480_ARGUMENT; + for (i = 0; i < 8; ++i) { + status = ds2480_bit(bus, (uint8_t)((value >> i) & 1), &bit); + if (status != DS2480_OK) return status; + result |= (uint8_t)(bit << i); + } + *received = result; + return DS2480_OK; +} + +ds2480_status ds2480_power_byte(ds2480 *bus, uint8_t value, uint32_t hold_ms) +{ + uint8_t i, bit, reply, echoed = 0; + ds2480_status status; + if (!bus || !hold_ms || hold_ms > 1000) return DS2480_ARGUMENT; + for (i = 0; i < 8; ++i) { + status = slot(bus, (uint8_t)((value >> i) & 1), (uint8_t)(i == 7), &bit); + if (status != DS2480_OK) { + /* A lost reply may leave an infinite pulse active: reset hardware. */ + if (i == 7 && !bus->ready) (void)bus->port.prepare(bus->port.user); + return status; + } + echoed |= (uint8_t)(bit << i); + } + bus->port.delay_ms(bus->port.user, hold_ms); + status = exchange(bus, 0xF1, &reply); + if (status == DS2480_OK && reply != 0xEC && reply != 0xEF) + status = fault(bus, DS2480_PROTOCOL); + if (status != DS2480_OK) (void)bus->port.prepare(bus->port.user); + if (status == DS2480_OK && echoed != value) return DS2480_DATA; + return status; +} + +uint8_t ds2480_crc8(const uint8_t *data, uint32_t size) +{ + uint8_t crc = 0, bit; + uint32_t i; + for (i = 0; i < size; ++i) { + crc ^= data[i]; + for (bit = 0; bit < 8; ++bit) + crc = (uint8_t)((crc >> 1) ^ ((crc & 1) ? 0x8C : 0)); + } + return crc; +} + +ds2480_status ds2480_search_next(ds2480 *bus, ds2480_search *search) +{ + ds2480_search next; + ds2480_status status; + uint8_t pos, a, b, direction, ignored, last_zero = 0; + if (!bus || !search || search->discrepancy > 64) return DS2480_ARGUMENT; + if (search->done) return DS2480_DONE; + next = *search; + status = ds2480_reset(bus); + if (status != DS2480_OK) return status; + status = ds2480_byte(bus, 0xF0, &ignored); + if (status != DS2480_OK) return status; + for (pos = 1; pos <= 64; ++pos) { + uint8_t index = (uint8_t)((pos - 1) / 8); + uint8_t mask = (uint8_t)(1U << ((pos - 1) % 8)); + status = ds2480_bit(bus, 1, &a); + if (status != DS2480_OK) return status; + status = ds2480_bit(bus, 1, &b); + if (status != DS2480_OK) return status; + if (a && b) return DS2480_DATA; /* Devices disappeared mid-search. */ + if (a != b) direction = a; + else { + direction = pos < search->discrepancy ? (uint8_t)!!(search->rom[index] & mask) + : (uint8_t)(pos == search->discrepancy); + if (!direction) last_zero = pos; + } + if (direction) next.rom[index] |= mask; + else next.rom[index] &= (uint8_t)~mask; + status = ds2480_bit(bus, direction, &ignored); + if (status != DS2480_OK) return status; + } + if (!next.rom[0]) return DS2480_DATA; + if (ds2480_crc8(next.rom, 8)) return DS2480_CRC; + next.discrepancy = last_zero; + next.done = (uint8_t)(last_zero == 0); + *search = next; + return DS2480_OK; +} diff --git a/c/ds18b20-ds2480/ds2480.h b/c/ds18b20-ds2480/ds2480.h new file mode 100644 index 0000000..f686049 --- /dev/null +++ b/c/ds18b20-ds2480/ds2480.h @@ -0,0 +1,69 @@ +#ifndef DS2480_H +#define DS2480_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + DS2480_OK = 0, DS2480_DONE, DS2480_NO_PRESENCE, DS2480_SHORT, + DS2480_IO, DS2480_PROTOCOL, DS2480_CRC, DS2480_ARGUMENT, + DS2480_NOT_READY, DS2480_DATA +} ds2480_status; + +/** Blocking UART callbacks: 0 = success, nonzero = error/timeout. + * prepare resets the bridge (BREAK >= 2 ms or hardware reset), sets UART + * to 9600 8N1, waits >= 2 ms after reset, clears RX/errors before returning. + * write waits for physical TX completion; read receives exactly size bytes. + * Both enforce timeout_ms. RX must remain enabled while transmitting. + * delay_ms waits AT LEAST the requested duration. No callback may reenter. + */ +typedef struct { + void *user; + int (*prepare)(void *user); + int (*write)(void *user, const uint8_t *data, uint32_t size, uint32_t timeout_ms); + int (*read)(void *user, uint8_t *data, uint32_t size, uint32_t timeout_ms); + void (*delay_ms)(void *user, uint32_t ms); +} ds2480_port; + +/** One instance per UART/bridge; serialize access for the whole operation. */ +typedef struct { + ds2480_port port; + uint32_t timeout_ms; + uint8_t ready; +} ds2480; + +/** Independent ROM search cursor. Zero-initialize before each enumeration. */ +typedef struct { + uint8_t rom[8]; + uint8_t discrepancy; + uint8_t done; +} ds2480_search; + +/** Reset/calibrate DS2480B and verify configuration. Empty bus is allowed. + * On IO/protocol errors call init again before further transactions. + */ +ds2480_status ds2480_init(ds2480 *bus, const ds2480_port *port, uint32_t timeout_ms); +/** Standard-speed reset; distinguish short, empty bus and transport failure. */ +ds2480_status ds2480_reset(ds2480 *bus); +/** Exchange one slot (write 1 to read); received must be non-NULL. */ +ds2480_status ds2480_bit(ds2480 *bus, uint8_t bit, uint8_t *received); +/** Exchange a byte, LSB first, in command mode (eight UART transactions). */ +ds2480_status ds2480_byte(ds2480 *bus, uint8_t value, uint8_t *received); +/** Write a byte and start strong pullup immediately after its last slot. + * Blocks for hold_ms (1..1000), then terminates the pulse and consumes reply. + */ +ds2480_status ds2480_power_byte(ds2480 *bus, uint8_t value, uint32_t hold_ms); +/** CRC8 Dallas/Maxim. data must address size bytes (NULL allowed for size=0). */ +uint8_t ds2480_crc8(const uint8_t *data, uint32_t size); +/** Search ALL families. OK yields ROM with valid CRC; DONE ends enumeration. + * Cursor changes only on success. After an error retry or zero it to restart. + */ +ds2480_status ds2480_search_next(ds2480 *bus, ds2480_search *search); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/c/ds18b20-ds2480/examples/read_first.c b/c/ds18b20-ds2480/examples/read_first.c new file mode 100644 index 0000000..032174a --- /dev/null +++ b/c/ds18b20-ds2480/examples/read_first.c @@ -0,0 +1,19 @@ +#include "ds18b20_ds2480.h" + +/* board_port supplies the four callbacks documented in ds2480.h. + * Blocking example: includes bridge reset, scan and 750 ms conversion. + */ +ds2480_status ds18b20_example_read_first(const ds2480_port *board_port, int16_t *raw) +{ + ds2480 bus; + ds2480_search search = {{0}, 0, 0}; + ds2480_status status; + if (!raw) return DS2480_ARGUMENT; + status = ds2480_init(&bus, board_port, 20); + if (status != DS2480_OK) return status; + status = ds18b20_ds2480_next(&bus, &search); + if (status != DS2480_OK) return status; + status = ds18b20_ds2480_convert(&bus, search.rom); + if (status != DS2480_OK) return status; + return ds18b20_ds2480_temperature(&bus, search.rom, raw); +} diff --git a/c/ds18b20-ds2480/tests/test_ds18b20_ds2480.c b/c/ds18b20-ds2480/tests/test_ds18b20_ds2480.c new file mode 100644 index 0000000..84b1234 --- /dev/null +++ b/c/ds18b20-ds2480/tests/test_ds18b20_ds2480.c @@ -0,0 +1,228 @@ +#include "ds18b20_ds2480.h" +#include +#include +#include + +#define CHECK(x) do { if (!(x)) { fprintf(stderr, "line %d: %s\n", __LINE__, #x); exit(1); } } while (0) + +enum { ROM_COMMAND, MATCH, FUNCTION, SEARCH, READ, CONFIG }; +typedef struct { + uint8_t rom[3][8], scratch[9]; + unsigned count, active, state, phase, position, bits, value, match_index; + unsigned prepared, calibrated, pending, reply, pulse, hold, convert, copy; + unsigned fail_read, fail_write, fail_prepare, corrupt_reply, reset_reply; + unsigned fail_power, fail_stop, reject_config; +} fake; + +static void crc_scratch(fake *f) { f->scratch[8] = ds2480_crc8(f->scratch, 8); } + +static void setup(fake *f) +{ + unsigned i; + static const uint8_t scratch[9] = {0x91, 0x01, 0x4B, 0x46, 0x7F, 0xFF, 0x0C, 0x10, 0x70}; + memset(f, 0, sizeof *f); + f->count = 3; + f->reset_reply = 0xCD; + memcpy(f->scratch, scratch, 9); + for (i = 0; i < 3; ++i) { + f->rom[i][0] = i == 2 ? 0x10 : 0x28; + f->rom[i][1] = (uint8_t)(i + 1); + f->rom[i][7] = ds2480_crc8(f->rom[i], 7); + } +} + +static void byte_in(fake *f, uint8_t byte) +{ + unsigned i; + if (f->state == ROM_COMMAND) { + if (byte == 0xF0) { f->state = SEARCH; f->position = f->phase = 0; } + else if (byte == 0x55) { f->state = MATCH; f->match_index = 0; } + else { CHECK(byte == 0xCC); f->state = FUNCTION; } + } else if (f->state == MATCH) { + for (i = 0; i < f->count; ++i) + if (f->rom[i][f->match_index] != byte) f->active &= ~(1U << i); + if (++f->match_index == 8) f->state = FUNCTION; + } else if (f->state == FUNCTION) { + CHECK(f->active != 0); + switch (byte) { + case 0xBE: f->state = READ; f->position = 0; break; + case 0x4E: f->state = CONFIG; f->position = 2; break; + case 0x44: ++f->convert; break; + case 0x48: ++f->copy; break; + default: CHECK(0); + } + } else { + CHECK(f->state == CONFIG); + if (!f->reject_config) f->scratch[f->position] = byte; + if (++f->position == 5) { crc_scratch(f); f->state = FUNCTION; } + } +} + +static uint8_t wire_bit(fake *f, uint8_t bit) +{ + unsigned i, result = 1; + if (f->state == SEARCH) { + for (i = 0; i < f->count; ++i) { + unsigned v = (f->rom[i][f->position / 8] >> (f->position % 8)) & 1; + if (!(f->active & (1U << i))) continue; + if (f->phase < 2) result &= f->phase ? !v : v; + else if (v != bit) f->active &= ~(1U << i); + } + if (++f->phase == 3) { f->phase = 0; ++f->position; } + return (uint8_t)result; + } + if (f->state == READ) { + CHECK(f->position < 72 && bit == 1); + result = (f->scratch[f->position / 8] >> (f->position % 8)) & 1; + ++f->position; + return (uint8_t)result; + } + f->value |= (unsigned)bit << f->bits; + if (++f->bits == 8) { + uint8_t byte = (uint8_t)f->value; + f->bits = f->value = 0; + byte_in(f, byte); + } + return bit; +} + +static int prepare(void *user) +{ + fake *f = user; + ++f->prepared; + f->pending = f->pulse = f->calibrated = 0; + return (int)f->fail_prepare; +} + +static int transmit(void *user, const uint8_t *data, uint32_t size, uint32_t timeout) +{ + fake *f = user; + uint8_t command = *data, bit; + CHECK(size == 1 && timeout == 20 && !f->pending); + if (f->fail_write) return -1; + if (!f->calibrated) { CHECK(command == 0xC1); f->calibrated = 1; return 0; } + CHECK(!f->pulse || command == 0xF1); + if (command == 0x3F) f->reply = 0x3E; + else if (command == 0xC1) { + f->reply = f->count ? f->reset_reply : 0xCF; + f->state = ROM_COMMAND; f->bits = f->value = 0; + f->active = (1U << f->count) - 1; + } else if (command == 0xF1) { + CHECK(f->pulse && (f->hold == 750 || f->hold == 12)); + f->pulse = 0; f->reply = 0xEC; + if (f->fail_stop) f->fail_read = 1; + } else { + CHECK(command == 0x81 || command == 0x91 || command == 0x83 || command == 0x93); + bit = wire_bit(f, (uint8_t)((command >> 4) & 1)); + f->reply = (command & 0xFC) | (bit ? 3U : 0U); + if (command & 2) { + CHECK(f->bits == 0 && (f->convert || f->copy)); + f->pulse = 1; f->hold = 0; + if (f->fail_power) f->fail_read = 1; + } + } + f->pending = 1; + return 0; +} + +static int receive(void *user, uint8_t *data, uint32_t size, uint32_t timeout) +{ + fake *f = user; + CHECK(size == 1 && timeout == 20 && f->pending); + if (f->fail_read) return -1; + *data = (uint8_t)(f->corrupt_reply ? 0 : f->reply); + f->pending = 0; + return 0; +} + +static void delay(void *user, uint32_t ms) +{ + fake *f = user; + if (f->pulse) f->hold += ms; + else CHECK(ms == 2); +} + +static void init(fake *f, ds2480 *bus) +{ + ds2480_port port = { f, prepare, transmit, receive, delay }; + CHECK(ds2480_init(bus, &port, 20) == DS2480_OK); + CHECK(!f->pending); +} + +int main(void) +{ + fake f, other; + ds2480 bus, second; + ds2480_search search = {{0}, 0, 0}, saved; + uint8_t bit, scratch[9], rom[8]; + int16_t raw = 999; + unsigned found = 0, i; + setup(&f); init(&f, &bus); + CHECK(ds2480_crc8(f.scratch, 9) == 0); /* Datasheet vector, not generated. */ + while (ds18b20_ds2480_next(&bus, &search) == DS2480_OK) { + if (!memcmp(search.rom, f.rom[0], 8)) found |= 1; + else if (!memcmp(search.rom, f.rom[1], 8)) found |= 2; + else CHECK(0); + } + CHECK(found == 3 && search.done); + CHECK(ds18b20_ds2480_next(&bus, &search) == DS2480_DONE); + CHECK(ds18b20_ds2480_convert(&bus, NULL) == DS2480_OK); + CHECK(f.convert == 1 && f.hold == 750 && !f.pulse && !f.pending); + CHECK(ds18b20_ds2480_temperature(&bus, f.rom[0], &raw) == DS2480_OK && raw == 401); + CHECK(ds18b20_ds2480_convert(&bus, f.rom[1]) == DS2480_OK && f.active == 2); + for (i = 9; i <= 12; ++i) { + CHECK(ds18b20_ds2480_configure(&bus, f.rom[0], -5, -20, (uint8_t)i, 1) == DS2480_OK); + CHECK(f.hold == 12 && !f.pulse && !f.pending); + f.scratch[0] = 0x5F; f.scratch[1] = 0xFF; crc_scratch(&f); + CHECK(ds18b20_ds2480_temperature(&bus, f.rom[0], &raw) == DS2480_OK); + CHECK(raw == (i == 9 ? -168 : i == 10 ? -164 : i == 11 ? -162 : -161)); + } + CHECK(f.copy == 4); + f.reject_config = 1; + CHECK(ds18b20_ds2480_configure(&bus, f.rom[0], 1, 2, 9, 1) == DS2480_DATA && f.copy == 4); + f.scratch[8] ^= 1; raw = 999; + CHECK(ds18b20_ds2480_temperature(&bus, f.rom[0], &raw) == DS2480_CRC && raw == 999); + memset(f.scratch, 0, 9); + CHECK(ds18b20_ds2480_read(&bus, f.rom[0], scratch) == DS2480_DATA); + memset(f.scratch, 0xFF, 9); + CHECK(ds18b20_ds2480_temperature(&bus, f.rom[0], &raw) == DS2480_CRC); + memcpy(rom, f.rom[0], 8); rom[7] ^= 1; + CHECK(ds18b20_ds2480_convert(&bus, rom) == DS2480_ARGUMENT); + CHECK(ds18b20_ds2480_read(&bus, NULL, scratch) == DS2480_ARGUMENT); + CHECK(ds18b20_ds2480_configure(&bus, f.rom[0], 0, 0, 8, 0) == DS2480_ARGUMENT); + CHECK(ds2480_bit(&bus, 1, NULL) == DS2480_ARGUMENT); + f.reset_reply = 0xEC; CHECK(ds2480_reset(&bus) == DS2480_SHORT); + f.reset_reply = 0xEE; CHECK(ds2480_reset(&bus) == DS2480_OK); + f.count = 0; CHECK(ds2480_reset(&bus) == DS2480_NO_PRESENCE); + memset(&search, 0, sizeof search); + CHECK(ds2480_search_next(&bus, &search) == DS2480_NO_PRESENCE); + setup(&f); init(&f, &bus); f.count = 1; f.rom[0][7] ^= 1; + saved = search; + CHECK(ds2480_search_next(&bus, &search) == DS2480_CRC); + CHECK(!memcmp(&search, &saved, sizeof search)); + f.fail_read = 1; CHECK(ds2480_reset(&bus) == DS2480_IO); + CHECK(ds2480_reset(&bus) == DS2480_NOT_READY); + setup(&f); init(&f, &bus); f.corrupt_reply = 1; + CHECK(ds2480_reset(&bus) == DS2480_PROTOCOL && !bus.ready); + setup(&f); init(&f, &bus); CHECK(ds2480_reset(&bus) == DS2480_OK); f.corrupt_reply = 1; + CHECK(ds2480_bit(&bus, 1, &bit) == DS2480_PROTOCOL); + setup(&f); init(&f, &bus); f.fail_write = 1; + CHECK(ds2480_reset(&bus) == DS2480_IO && !bus.ready); + setup(&f); init(&f, &bus); f.fail_power = 1; + CHECK(ds18b20_ds2480_convert(&bus, NULL) == DS2480_IO); + CHECK(!f.pulse && !bus.ready && f.prepared == 2); + setup(&f); init(&f, &bus); f.fail_stop = 1; + CHECK(ds18b20_ds2480_convert(&bus, NULL) == DS2480_IO); + CHECK(!f.pulse && !bus.ready && f.prepared == 2); + setup(&f); init(&f, &bus); + setup(&other); init(&other, &second); + CHECK(ds18b20_ds2480_convert(&second, other.rom[1]) == DS2480_OK); + CHECK(f.convert == 0 && other.convert == 1); + CHECK(ds18b20_ds2480_temperature(&bus, f.rom[0], &raw) == DS2480_OK && raw == 401); + CHECK(ds2480_init(&bus, &bus.port, 20) == DS2480_OK); + f.fail_prepare = 1; + CHECK(ds2480_init(&bus, &bus.port, 20) == DS2480_IO && !bus.ready); + CHECK(ds2480_init(&bus, NULL, 20) == DS2480_ARGUMENT); + puts("DS18B20/DS2480: all host tests passed"); + return 0; +}