236 lines
13 KiB
Markdown
236 lines
13 KiB
Markdown
# DS18B20: перенос на другую платформу
|
||
|
||
## Что переносится без изменений
|
||
|
||
`Inc/ds18b20.h`, `Inc/ds18b20_config.h` и `Src/ds18b20.c` — переносимое
|
||
C-ядро. Оно использует `stdint.h`, `stddef.h`, `string.h` и аппаратные
|
||
callback. Не добавляйте в core HAL, RTOS, Modbus или глобальные дескрипторы
|
||
конкретного проекта.
|
||
|
||
## Шаг 1. Добавить файлы в сборку
|
||
|
||
Добавьте `Libraries/DS18B20/Inc` в include path и
|
||
`Libraries/DS18B20/Src/ds18b20.c` в список исходников. Создайте отдельные
|
||
каталоги `Port/<PLATFORM>/Inc` и `Port/<PLATFORM>/Src`.
|
||
|
||
Application adapter держите вне библиотеки либо в отдельном `Adapter`:
|
||
он планирует операции, переводит ошибки и связывает результат с остальным
|
||
проектом.
|
||
|
||
## Шаг 2. Создать platform context
|
||
|
||
Context содержит только ресурсы одного физического 1-Wire master:
|
||
|
||
```c
|
||
typedef struct {
|
||
gpio_handle_t gpio;
|
||
timer_handle_t timer;
|
||
uint32_t timer_ticks_per_us;
|
||
irq_state_t saved_irq_state;
|
||
} ds18b20_my_mcu_t;
|
||
```
|
||
|
||
Не используйте скрытый изменяемый singleton. Для двух шин создаются два context,
|
||
два `ds18b20_t` и два массива ROM.
|
||
|
||
## Шаг 3. Реализовать callbacks
|
||
|
||
```c
|
||
static void drive_low(void *context);
|
||
static void release_line(void *context);
|
||
static uint8_t read_line(void *context);
|
||
static void delay_us(void *context, uint32_t us);
|
||
static uint32_t tick_ms(void *context);
|
||
static void critical_enter(void *context);
|
||
static void critical_exit(void *context);
|
||
|
||
const ds18b20_onewire_ops_t ds18b20_my_mcu_ops = {
|
||
drive_low, release_line, read_line, delay_us, tick_ms,
|
||
critical_enter, critical_exit
|
||
};
|
||
```
|
||
|
||
### GPIO
|
||
|
||
- Линия 1-Wire работает только как open-drain: порт либо тянет её к 0, либо
|
||
переходит в высокоимпедансное состояние.
|
||
- Запрещён push-pull высокий уровень.
|
||
- `release_line` не должна ждать; `read_line` читает реальный pin level.
|
||
- Номинал внешнего pull-up и допустимая длина/ёмкость шины выбираются по
|
||
электрическим условиям конкретной платы.
|
||
|
||
STM32F4 port оставляет pin в `GPIO_MODE_OUTPUT_OD`, отпускает линию записью
|
||
единицы в BSRR и читает IDR без переключения MODER.
|
||
|
||
### Микросекундная задержка
|
||
|
||
`delay_us` обязана быть монотонной и достаточно точной для 1-Wire standard
|
||
speed. Не используйте scheduler sleep с миллисекундной гранулярностью. Если
|
||
задержка основана на hardware timer:
|
||
|
||
- таймер запускается до инициализации библиотеки;
|
||
- он считает непрерывно во всех вызывающих контекстах;
|
||
- учитывается переполнение счётчика;
|
||
- произведение `us * timer_ticks_per_us` не должно переполняться в диапазоне
|
||
используемых библиотекой задержек;
|
||
- частота таймера и `timer_ticks_per_us` должны совпадать.
|
||
|
||
### Tick и критическая секция
|
||
|
||
`tick_ms` нужен только для `ds18b20_wait()`; state-machine adapter может не
|
||
использовать блокирующий wait. Вычитание tick выполняется как `uint32_t` и
|
||
допускает wrap.
|
||
|
||
Critical callbacks должны сохранять и восстанавливать предыдущее состояние
|
||
прерываний, а не безусловно включать их. Если платформа гарантирует timing иначе,
|
||
оба callback можно оставить `NULL`.
|
||
|
||
## Шаг 4. Инициализировать экземпляр
|
||
|
||
```c
|
||
#define DS_CAPACITY 8U
|
||
|
||
static ds18b20_my_mcu_t port_context;
|
||
static ds18b20_t bus;
|
||
static uint8_t rom_storage[DS_CAPACITY][DS18B20_ROM_SIZE];
|
||
|
||
platform_gpio_timer_init(&port_context);
|
||
|
||
ds18b20_status_t status =
|
||
ds18b20_init(&bus, &ds18b20_my_mcu_ops, &port_context,
|
||
rom_storage, DS_CAPACITY);
|
||
```
|
||
|
||
Все три объекта должны жить столько же, сколько используется `bus`. Стековый
|
||
context или ROM storage нельзя передавать экземпляру, переживающему функцию.
|
||
|
||
## Шаг 5. Создать application adapter
|
||
|
||
Adapter должен:
|
||
|
||
1. выполнить явный поиск или восстановить/проверить известные ROM;
|
||
2. сериализовать Search ROM, Convert T, scratchpad и EEPROM-команды;
|
||
3. запустить Convert T и вернуть управление;
|
||
4. опрашивать ready с общим deadline;
|
||
5. читать каждый ROM и публиковать значение только после CRC;
|
||
6. восстановить state machine после disconnect, CRC error и timeout;
|
||
7. не запускать поиск/EEPROM-запись из обычного temperature polling;
|
||
8. синхронизировать доступ задач RTOS mutex-ом на уровне экземпляра.
|
||
|
||
Для циклического приложения вызывайте `ds18b20_search_step()` только когда
|
||
шина не занята преобразованием температуры или User Byte. Храните timeout,
|
||
отмену и sequence в адаптере приложения: portable core не зависит от HAL,
|
||
Modbus, GUI и глобального `hdallas`.
|
||
|
||
Не используйте critical callbacks библиотеки как mutex: они защищают короткий
|
||
1-Wire slot и могут запрещать прерывания.
|
||
|
||
## Память и выравнивание
|
||
|
||
- Core не использует heap.
|
||
- На каждую шину требуется `sizeof(ds18b20_t)` плюс
|
||
`rom_capacity * DS18B20_ROM_SIZE` байт ROM storage.
|
||
- Scratchpad — 9 байт у вызывающей стороны.
|
||
- Специального DMA-выравнивания core не требует; соблюдайте обычное выравнивание
|
||
C-типов для `ds18b20_t` и context.
|
||
- `rom_capacity` измеряется в элементах `uint8_t[8]`.
|
||
|
||
## Timing и питание
|
||
|
||
DS18B20 с внешним питанием может сигнализировать готовность через read slot.
|
||
Для parasite power требуется strong pull-up на всё время Convert T и
|
||
Copy Scratchpad. Контракт предоставляет optional callback
|
||
`strong_pullup(context, enable)`. Для поддержки режима порт обязан:
|
||
|
||
1. включить активный high не позднее 10 мкс после команды Copy Scratchpad;
|
||
2. удерживать его не менее 10 ms без другой активности 1-Wire;
|
||
3. безопасно отключать strong pull-up при success, timeout и error;
|
||
4. возвращать линию в open-drain idle перед Recall/readback;
|
||
5. проверить обычное и parasite-powered подключение на реальной плате.
|
||
|
||
## Перенос STM32F4 HAL port
|
||
|
||
Перед `ds18b20_stm32f4_hal_init()` заполните:
|
||
|
||
| Поле | Требование |
|
||
| --- | --- |
|
||
| `port` | Валидный `GPIO_TypeDef *` с включённым clock |
|
||
| `pin` | Одна ненулевая GPIO mask |
|
||
| `timer` | Запущенный свободно работающий `TIM_TypeDef *` |
|
||
| `timer_ticks_per_us` | Ненулевое число timer ticks за 1 мкс |
|
||
|
||
Порт использует `HAL_GetTick()` и CMSIS PRIMASK. При переносе на другую STM32
|
||
семью проверьте HAL-заголовок, разрядность/частоту timer, GPIO BSRR/IDR и способ
|
||
сохранения interrupt state.
|
||
|
||
## Проверки
|
||
|
||
Host/mock-проверки выполняются без платы:
|
||
|
||
```powershell
|
||
python -m unittest Libraries.PortableTests.test_portable_models
|
||
git diff --check
|
||
```
|
||
|
||
Для нового порта добавьте тесты reset/presence, write/read slots, timing bounds,
|
||
CRC error, нескольких экземпляров, timeout и восстановления после ошибки.
|
||
Затем выполните целевую сборку без ошибок/предупреждений.
|
||
|
||
## Перенос User Byte mailbox adapter
|
||
|
||
Core-модуль не зависит от Modbus. При переносе STM32 adapter сохраните
|
||
`selector + contractVersion=2` как одну транзакцию и APPLY как отдельную
|
||
последнюю запись. Input обязан возвращать selector/version echo. Legacy version
|
||
`0` можно принимать только для TH; TL без version `2` должен завершаться
|
||
`invalid`, а не значением TH.
|
||
|
||
Host-проверки adapter:
|
||
|
||
```powershell
|
||
powershell -ExecutionPolicy Bypass -File Modules/UserByte/Adapter/STM32_Modbus/Tests/run_host_tests.ps1
|
||
```
|
||
|
||
## Checklist
|
||
|
||
- [ ] Core собирается без HAL/RTOS/Modbus include.
|
||
- [ ] У каждой шины отдельные instance, context и ROM storage.
|
||
- [ ] GPIO физически open-drain и никогда не выдаёт push-pull high.
|
||
- [ ] Есть корректный внешний pull-up и общий GND.
|
||
- [ ] Таймер запущен, частота и overflow проверены.
|
||
- [ ] Critical section восстанавливает предыдущее состояние.
|
||
- [ ] Поиск, conversion, scratchpad и EEPROM сериализованы.
|
||
- [ ] У state machine есть deadline и восстановление после ошибок.
|
||
- [ ] CRC ROM и scratchpad проверяется до публикации данных.
|
||
- [ ] Host/mock-тесты и `git diff --check` проходят.
|
||
- [ ] Целевая сборка даёт 0 ошибок и 0 предупреждений.
|
||
- [ ] Работа проверена на реальной шине с 0, 1 и несколькими датчиками.
|
||
- [ ] Parasite power отмечен неподдерживаемым либо проверен со strong pull-up.
|
||
# Strong pull-up
|
||
|
||
Если порт поддерживает parasite power, добавьте callback
|
||
`strong_pullup(context, enable)`: включение должно немедленно активно держать
|
||
high после Copy Scratchpad, выключение — вернуть open-drain idle. Без безопасной
|
||
аппаратной реализации не разрешайте parasite mode вызывающему приложению.
|
||
|
||
### Импорт известных адресов
|
||
|
||
После ds18b20_init передайте каждый сохранённый ROM в
|
||
ds18b20_add_known_rom. Проверяйте возвращаемый статус и лимит ёмкости.
|
||
Хранилище ROM принадлежит вызывающему коду и живёт всё время работы шины.
|
||
Импорт не требует GPIO-транзакций и не заменяет проверку CRC температуры.
|
||
Сохраняйте список перед search_begin (он очищает результаты); по завершении
|
||
или отмене добавляйте прежние ID обратно вне активной конверсии.
|
||
Для нескольких шин храните снимки и происхождение ID отдельно для каждой.
|
||
|
||
### Перенос поиска с повторами
|
||
|
||
Пересоберите всех потребителей: ds18b20_t расширен состоянием поиска и
|
||
диагностикой. Рабочие буферы остаются caller-owned, HAL и Flash в ядре не нужны.
|
||
Только BUSY означает продолжение; остальные результаты терминальные.
|
||
Держите одного владельца шины до завершения/отмены поиска. Адаптер при отмене
|
||
сбрасывает search_active перед адресным опросом. Search begin нельзя запускать
|
||
во время strong pull-up. Пауза recovery действует только при search_active.
|
||
Новые callbacks не требуются; delay_us должен поддерживать добавочные 20 мкс.
|
||
Проверьте пределы config и внешний deadline. Для Modbus переносите два поля
|
||
диагностики лишь в свободные регистры, не сдвигая последующие окна.
|