Compare commits
51 Commits
874d2e1dfc
...
codex/ump-
| Author | SHA1 | Date | |
|---|---|---|---|
| 79411dbc26 | |||
| 5260186c16 | |||
| 10bfa587c7 | |||
| ac3025ee32 | |||
| 37508f058b | |||
| 3334f4cddd | |||
| aedd5fc082 | |||
| 9ba3742b29 | |||
| 4f23f48896 | |||
| 8e9fd144ca | |||
| 67cca68fcc | |||
| c0c20f6dc2 | |||
| 11f16f618b | |||
| 3a1e8d469a | |||
| daaca0d94b | |||
| 49c65ea78d | |||
| 34f728cfaa | |||
| 72205216f5 | |||
| 5d48ddf0b0 | |||
| 245d486a63 | |||
| e262b0acdd | |||
| ed92ec346e | |||
| 3d374e7777 | |||
| d03e039cb4 | |||
| d002b14e4e | |||
| 11007b0704 | |||
| 821202b655 | |||
| 8596918bf7 | |||
| bada3bbe8d | |||
| 87f2f2f56b | |||
| 5523f049ec | |||
| b08ea02a5c | |||
| 1b3a6318eb | |||
| 57622ccd66 | |||
| ba529f579c | |||
| 4f788d39d6 | |||
| 1799b00167 | |||
| 940d9ada76 | |||
| 6eaa47d3a8 | |||
| 1f49ba49bb | |||
| b7f00acb24 | |||
| 70b2ee3cad | |||
| 45582cda4a | |||
| 2bb9fea90a | |||
| 6392d36741 | |||
| 0c635ce1be | |||
| 5caca07ad4 | |||
| 1f1006bf2a | |||
| 31a111ee46 | |||
| 20f63bc389 | |||
| 1296773346 |
@@ -35,6 +35,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 и TMS320F2812-порты в комплекте** |
|
||||
| [`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; настройки платы предоставляет прошивка |
|
||||
@@ -49,6 +50,9 @@ templates/
|
||||
|
||||
| Модуль | Что делает | Зависимости |
|
||||
|---|---|---|
|
||||
| [`python/setprotocol/firmware_database.py`](python/setprotocol/firmware_database.py) | база прошивок: HTTPS-каталог, скачивание с SHA-256 и кэшем, публикация в Gitea без SETGUI; [подключение и CLI](tools/firmware-publish/DATABASE.md) | stdlib, Python 3.10+ |
|
||||
| [`python/setprotocol/firmware_publish.py`](python/setprotocol/firmware_publish.py) | общие метаданные публикации, SHA-256, release tag и обновление каталога прошивок; [подключение](tools/firmware-publish/PORTING.md) | stdlib; сетевой адаптер в SETGUI |
|
||||
| [`python/setprotocol/firmware_catalog.py`](python/setprotocol/firmware_catalog.py) | модель и parser каталога `firmware.releases` | stdlib |
|
||||
| [`python/protocan`](python/protocan) | разбор ProtoCAN, транспортный кадр моста, кадр SETGUI, кодеки каталога | stdlib, Python 3.9+ |
|
||||
| [`python/protocan/trends.py`](python/protocan/trends.py) | общие настройки графиков, ограниченная история, ctypes-декодер GAS/raw CAN | stdlib, опционально SETProtocol DLL/SO |
|
||||
|
||||
|
||||
22
c/ds18b20-ds2480/CMakeLists.txt
Normal file
22
c/ds18b20-ds2480/CMakeLists.txt
Normal file
@@ -0,0 +1,22 @@
|
||||
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)
|
||||
add_executable(test_ds2480_stm32f4
|
||||
ports/stm32f4/ds2480_stm32f4_hal.c ports/stm32f4/tests/test_port.c)
|
||||
target_include_directories(test_ds2480_stm32f4 PRIVATE
|
||||
. ports/stm32f4 ports/stm32f4/tests)
|
||||
add_test(NAME ds2480_stm32f4 COMMAND test_ds2480_stm32f4)
|
||||
endif()
|
||||
145
c/ds18b20-ds2480/README.md
Normal file
145
c/ds18b20-ds2480/README.md
Normal file
@@ -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, UART 9600 бод. Байты передаются в
|
||||
Data Mode с экранированием E3, reset/поиск/strong pullup — в Command Mode.
|
||||
Переключения режима выполняет библиотека. Повышенные скорости UART,
|
||||
Overdrive и Search Accelerator пока не реализованы. Параметры
|
||||
таймингов длинной линии остаются заводскими. Аппаратная проверка обязательна
|
||||
для выбранной топологии/нагрузки. Протокол проверен по документации **DS2480B**;
|
||||
старые ревизии DS2480 без суффикса B отдельно не проверялись.
|
||||
|
||||
## Подключение и тесты
|
||||
|
||||
Добавьте `ds2480.c`, `ds18b20_ds2480.c` и путь к заголовкам в сборку прошивки.
|
||||
Номер UART, GPIO и библиотеку платформы выбирает приложение. Порт [STM32F407 / STM32F4 HAL](ports/stm32f4/README.md) входит в библиотеку.
|
||||
|
||||
```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`.
|
||||
Подключена к опросу climate через USART6 PC6/PC7; администратор выбирает GPIO или DS2480.
|
||||
Ожидание преобразования в climate неблокирующее: `ds2480_power_begin/end`.
|
||||
|
||||
## Источники
|
||||
|
||||
- [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)
|
||||
108
c/ds18b20-ds2480/ds18b20_ds2480.c
Normal file
108
c/ds18b20-ds2480/ds18b20_ds2480.c
Normal file
@@ -0,0 +1,108 @@
|
||||
#include "ds18b20_ds2480.h"
|
||||
#include <string.h>
|
||||
|
||||
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);
|
||||
}
|
||||
32
c/ds18b20-ds2480/ds18b20_ds2480.h
Normal file
32
c/ds18b20-ds2480/ds18b20_ds2480.h
Normal file
@@ -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
|
||||
204
c/ds18b20-ds2480/ds2480.c
Normal file
204
c/ds18b20-ds2480/ds2480.c
Normal file
@@ -0,0 +1,204 @@
|
||||
#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)
|
||||
{
|
||||
uint8_t mode = 0xE3;
|
||||
if (!bus || !reply) return DS2480_ARGUMENT;
|
||||
if (!bus->ready) return DS2480_NOT_READY;
|
||||
if (bus->power_active && command != 0xF1) return DS2480_BUSY;
|
||||
if (bus->data_mode) {
|
||||
if (bus->port.write(bus->port.user, &mode, 1, bus->timeout_ms))
|
||||
return fault(bus, DS2480_IO);
|
||||
bus->data_mode = 0;
|
||||
}
|
||||
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->power_active = 0;
|
||||
bus->data_mode = 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 mode = 0xE1, reply;
|
||||
if (!bus || !received) return DS2480_ARGUMENT;
|
||||
if (!bus->ready) return DS2480_NOT_READY;
|
||||
if (bus->power_active) return DS2480_BUSY;
|
||||
if (!bus->data_mode) {
|
||||
if (bus->port.write(bus->port.user, &mode, 1, bus->timeout_ms))
|
||||
return fault(bus, DS2480_IO);
|
||||
bus->data_mode = 1;
|
||||
}
|
||||
if (bus->port.write(bus->port.user, &value, 1, bus->timeout_ms))
|
||||
return fault(bus, DS2480_IO);
|
||||
if (value == 0xE3 && bus->port.write(bus->port.user, &value, 1, bus->timeout_ms))
|
||||
return fault(bus, DS2480_IO);
|
||||
if (bus->port.read(bus->port.user, &reply, 1, bus->timeout_ms))
|
||||
return fault(bus, DS2480_IO);
|
||||
*received = reply;
|
||||
return DS2480_OK;
|
||||
}
|
||||
|
||||
ds2480_status ds2480_power_begin(ds2480 *bus, uint8_t value)
|
||||
{
|
||||
uint8_t i, bit, echoed = 0;
|
||||
ds2480_status status;
|
||||
if (!bus) 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->power_active = 1;
|
||||
if (echoed != value) {
|
||||
status = ds2480_power_end(bus);
|
||||
return status == DS2480_OK ? DS2480_DATA : status;
|
||||
}
|
||||
return DS2480_OK;
|
||||
}
|
||||
|
||||
ds2480_status ds2480_power_end(ds2480 *bus)
|
||||
{
|
||||
uint8_t reply;
|
||||
ds2480_status status;
|
||||
if (!bus) return DS2480_ARGUMENT;
|
||||
if (!bus->ready) return DS2480_NOT_READY;
|
||||
if (!bus->power_active) return DS2480_OK;
|
||||
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);
|
||||
bus->power_active = 0;
|
||||
return status;
|
||||
}
|
||||
|
||||
ds2480_status ds2480_power_byte(ds2480 *bus, uint8_t value, uint32_t hold_ms)
|
||||
{
|
||||
ds2480_status status;
|
||||
if (!hold_ms || hold_ms > 1000) return DS2480_ARGUMENT;
|
||||
status = ds2480_power_begin(bus, value);
|
||||
if (status != DS2480_OK) return status;
|
||||
bus->port.delay_ms(bus->port.user, hold_ms);
|
||||
return ds2480_power_end(bus);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
79
c/ds18b20-ds2480/ds2480.h
Normal file
79
c/ds18b20-ds2480/ds2480.h
Normal file
@@ -0,0 +1,79 @@
|
||||
#ifndef DS2480_H
|
||||
#define DS2480_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#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_BUSY
|
||||
} 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;
|
||||
uint8_t power_active;
|
||||
uint8_t data_mode;
|
||||
} 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 in Data Mode; escapes E3 and preserves one reply per byte. */
|
||||
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);
|
||||
/** Start indefinite strong pullup after the final bit; returns immediately
|
||||
* after UART exchange. Until power_end, other bus operations return BUSY.
|
||||
* Application MUST call power_end after the sensor's required hold time,
|
||||
* or init to cancel/recover. No internal timer/interrupt releases the pulse.
|
||||
*/
|
||||
ds2480_status ds2480_power_begin(ds2480 *bus, uint8_t value);
|
||||
/** Release strong pullup, consume its reply. Idempotent when ready/idle. */
|
||||
ds2480_status ds2480_power_end(ds2480 *bus);
|
||||
/** 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
|
||||
19
c/ds18b20-ds2480/examples/read_first.c
Normal file
19
c/ds18b20-ds2480/examples/read_first.c
Normal file
@@ -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);
|
||||
}
|
||||
52
c/ds18b20-ds2480/ports/stm32f4/README.md
Normal file
52
c/ds18b20-ds2480/ports/stm32f4/README.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# STM32F407 / STM32F4 HAL
|
||||
|
||||
Порт UART для `ds2480.c`. Платформа настраивает тактирование и GPIO, порт
|
||||
формирует BREAK через TX GPIO, восстанавливает UART 9600 8N1 и проверяет
|
||||
ошибки приёма. DMA и обработчики UART-прерываний не нужны. HAL tick должен
|
||||
работать; вызывать из прерывания или при запрещённых прерываниях нельзя.
|
||||
|
||||
```c
|
||||
ds2480 bridge;
|
||||
ds2480_port io;
|
||||
ds2480_stm32f4_hal platform = {&huart6, GPIOC, GPIO_PIN_6, GPIO_AF8_USART6};
|
||||
/* Before this point: enable GPIOC/USART6 clocks, configure PC6/PC7 as AF8. */
|
||||
if (ds2480_stm32f4_hal_bind(&platform, &io) != DS2480_OK) return;
|
||||
if (ds2480_init(&bridge, &io, 20) != DS2480_OK) return;
|
||||
```
|
||||
|
||||
В сборку добавляются `ds2480_stm32f4_hal.c`, ядро библиотеки, путь к этому
|
||||
каталогу и STM32F4 HAL/CMSIS. UART выделяется только для моста. Порт принимает
|
||||
по одному байту за операцию: ответ сохраняется в DR во время завершения TX,
|
||||
затем читается без сброса RX. FE/NE/ORE/PE означают ошибку обмена даже при RXNE.
|
||||
|
||||
## Подключение в climate F407VET6
|
||||
|
||||
| STM32 / питание | DS2480B |
|
||||
|---|---|
|
||||
| PC6, USART6_TX | TXD, вывод 7 (вход моста) |
|
||||
| PC7, USART6_RX | RXD, вывод 8 (выход моста) |
|
||||
| Общая земля | GND, вывод 1 |
|
||||
| +5 В | VDD, вывод 4; VPP, вывод 5; POL, вывод 6 |
|
||||
| DQ датчиков DS18B20 | 1-W, вывод 2 |
|
||||
|
||||
DS2480B работает от 5 В. Проверьте согласование логических уровней по
|
||||
электрическим характеристикам конкретной платы/модуля; не считайте питание
|
||||
DS2480B от 3,3 В допустимым. Для внешнего питания DS18B20 подключите VDD;
|
||||
при паразитном питании VDD датчика соединяется с GND. В обоих случаях общий GND.
|
||||
|
||||
PC6/PC7 выбраны в `climate` (AF8). UART1/2 используются Modbus, SDIO использует
|
||||
4-битную шину PC8..PC12/PD2. Пины для другой платы задаются её приложением.
|
||||
|
||||
## Неблокирующее питание датчиков
|
||||
|
||||
`ds2480_power_begin` посылает команду датчика с strong pullup на последнем
|
||||
слоте. Приложение возвращается в главный цикл, отсчитывает 750 мс для
|
||||
преобразования либо минимум 10 мс для EEPROM, затем вызывает
|
||||
`ds2480_power_end`. До завершения импульса остальной обмен возвращает BUSY.
|
||||
При отмене вызывайте `power_end`, при потере синхронизации — `ds2480_init`.
|
||||
|
||||
Используется в `climate_control_f407vet6_f4`: `ds2480_app.c` связывает порт
|
||||
из сабмодуля с существующим каталогом датчиков и диагностикой Modbus.
|
||||
|
||||
Источники: [STM32F407, таблица alternate functions](https://www.st.com/resource/en/datasheet/stm32f407ve.pdf),
|
||||
[DS2480B, выводы и UART](https://www.analog.com/media/en/technical-documentation/data-sheets/ds2480b.pdf).
|
||||
85
c/ds18b20-ds2480/ports/stm32f4/ds2480_stm32f4_hal.c
Normal file
85
c/ds18b20-ds2480/ports/stm32f4/ds2480_stm32f4_hal.c
Normal file
@@ -0,0 +1,85 @@
|
||||
#include "ds2480_stm32f4_hal.h"
|
||||
|
||||
#define RX_ERRORS (USART_SR_ORE | USART_SR_NE | USART_SR_FE | USART_SR_PE)
|
||||
|
||||
static int prepare(void *user)
|
||||
{
|
||||
ds2480_stm32f4_hal *p = (ds2480_stm32f4_hal *)user;
|
||||
GPIO_InitTypeDef gpio = {0};
|
||||
/* Abort resets HAL states after timeouts and disables UART IRQ/DMA. */
|
||||
if (HAL_UART_Abort(p->uart) != HAL_OK) return -1;
|
||||
__HAL_UART_DISABLE(p->uart);
|
||||
HAL_GPIO_WritePin(p->tx_port, p->tx_pin, GPIO_PIN_RESET);
|
||||
gpio.Pin = p->tx_pin;
|
||||
gpio.Mode = GPIO_MODE_OUTPUT_PP;
|
||||
gpio.Pull = GPIO_NOPULL;
|
||||
gpio.Speed = GPIO_SPEED_FREQ_HIGH;
|
||||
HAL_GPIO_Init(p->tx_port, &gpio);
|
||||
HAL_Delay(2);
|
||||
HAL_GPIO_WritePin(p->tx_port, p->tx_pin, GPIO_PIN_SET);
|
||||
HAL_Delay(2);
|
||||
gpio.Mode = GPIO_MODE_AF_PP;
|
||||
gpio.Alternate = p->tx_alternate;
|
||||
HAL_GPIO_Init(p->tx_port, &gpio);
|
||||
p->uart->Init.BaudRate = 9600;
|
||||
p->uart->Init.WordLength = UART_WORDLENGTH_8B;
|
||||
p->uart->Init.StopBits = UART_STOPBITS_1;
|
||||
p->uart->Init.Parity = UART_PARITY_NONE;
|
||||
p->uart->Init.Mode = UART_MODE_TX_RX;
|
||||
p->uart->Init.HwFlowCtl = UART_HWCONTROL_NONE;
|
||||
p->uart->Init.OverSampling = UART_OVERSAMPLING_16;
|
||||
if (HAL_UART_Init(p->uart) != HAL_OK) return -1;
|
||||
/* SR then DR: discard BREAK echo/stale RX and clear FE/NE/ORE/PE. */
|
||||
__HAL_UART_CLEAR_OREFLAG(p->uart);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int write_byte(void *user, const uint8_t *data, uint32_t size, uint32_t timeout)
|
||||
{
|
||||
ds2480_stm32f4_hal *p = (ds2480_stm32f4_hal *)user;
|
||||
if (!data || size != 1 || !timeout || timeout == HAL_MAX_DELAY) return -1;
|
||||
/* One reply fits in DR while HAL waits for TX complete. Never flush RX
|
||||
* here: a reply may already be present when HAL_UART_Transmit returns. */
|
||||
if (p->uart->Instance->SR & RX_ERRORS) return -1;
|
||||
return HAL_UART_Transmit(p->uart, data, 1, timeout) == HAL_OK ? 0 : -1;
|
||||
}
|
||||
|
||||
static int read_byte(void *user, uint8_t *data, uint32_t size, uint32_t timeout)
|
||||
{
|
||||
ds2480_stm32f4_hal *p = (ds2480_stm32f4_hal *)user;
|
||||
uint32_t start, flags;
|
||||
if (!data || size != 1 || !timeout || timeout == HAL_MAX_DELAY) return -1;
|
||||
start = HAL_GetTick();
|
||||
for (;;) {
|
||||
flags = p->uart->Instance->SR;
|
||||
/* Test errors BEFORE reading DR, including when RXNE is already set. */
|
||||
if (flags & RX_ERRORS) {
|
||||
__HAL_UART_CLEAR_OREFLAG(p->uart);
|
||||
return -1;
|
||||
}
|
||||
if (flags & USART_SR_RXNE) {
|
||||
*data = (uint8_t)p->uart->Instance->DR;
|
||||
return 0;
|
||||
}
|
||||
if ((uint32_t)(HAL_GetTick() - start) >= timeout) return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static void delay_ms(void *user, uint32_t ms)
|
||||
{
|
||||
(void)user;
|
||||
HAL_Delay(ms);
|
||||
}
|
||||
|
||||
ds2480_status ds2480_stm32f4_hal_bind(ds2480_stm32f4_hal *p, ds2480_port *port)
|
||||
{
|
||||
if (!p || !port || !p->uart || !p->uart->Instance || !p->tx_port ||
|
||||
!p->tx_pin || (p->tx_pin & (p->tx_pin - 1U)) || p->tx_alternate > 15U)
|
||||
return DS2480_ARGUMENT;
|
||||
port->user = p;
|
||||
port->prepare = prepare;
|
||||
port->write = write_byte;
|
||||
port->read = read_byte;
|
||||
port->delay_ms = delay_ms;
|
||||
return DS2480_OK;
|
||||
}
|
||||
32
c/ds18b20-ds2480/ports/stm32f4/ds2480_stm32f4_hal.h
Normal file
32
c/ds18b20-ds2480/ports/stm32f4/ds2480_stm32f4_hal.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#ifndef DS2480_STM32F4_HAL_H
|
||||
#define DS2480_STM32F4_HAL_H
|
||||
|
||||
#include "ds2480.h"
|
||||
#include "stm32f4xx_hal.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Dedicated UART initialized by the board (clock, RX/TX AF, no IRQ/DMA).
|
||||
* tx_port/pin/alternate let prepare generate a real >=2 ms BREAK, including
|
||||
* recovery from a lost strong-pullup response. No reset pin is required.
|
||||
* GPIO clock must stay enabled. UART must not be used by any other service.
|
||||
*/
|
||||
typedef struct {
|
||||
UART_HandleTypeDef *uart;
|
||||
GPIO_TypeDef *tx_port;
|
||||
uint16_t tx_pin;
|
||||
uint32_t tx_alternate;
|
||||
} ds2480_stm32f4_hal;
|
||||
|
||||
/** Populate callbacks only; call ds2480_init afterwards to reset/calibrate.
|
||||
* Thread/main-loop use only, with running HAL tick and interrupts enabled.
|
||||
* UART baud rate is fixed at 9600 8N1; all transfers are single-byte.
|
||||
*/
|
||||
ds2480_status ds2480_stm32f4_hal_bind(ds2480_stm32f4_hal *context, ds2480_port *port);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
39
c/ds18b20-ds2480/ports/stm32f4/tests/stm32f4xx_hal.h
Normal file
39
c/ds18b20-ds2480/ports/stm32f4/tests/stm32f4xx_hal.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/* Host-only HAL model, never add this directory to a firmware include path. */
|
||||
#ifndef TEST_STM32F4_HAL_H
|
||||
#define TEST_STM32F4_HAL_H
|
||||
#include <stdint.h>
|
||||
typedef struct { uint32_t SR, DR, CR1; } USART_TypeDef;
|
||||
typedef struct { uint32_t pin, level, mode; } GPIO_TypeDef;
|
||||
typedef struct { uint32_t BaudRate,WordLength,StopBits,Parity,Mode,HwFlowCtl,OverSampling; } UART_InitTypeDef;
|
||||
typedef struct { USART_TypeDef *Instance; UART_InitTypeDef Init; } UART_HandleTypeDef;
|
||||
typedef struct { uint32_t Pin, Mode, Pull, Speed, Alternate; } GPIO_InitTypeDef;
|
||||
typedef enum { HAL_OK, HAL_ERROR } HAL_StatusTypeDef;
|
||||
#define USART_SR_ORE 8U
|
||||
#define USART_SR_NE 4U
|
||||
#define USART_SR_FE 2U
|
||||
#define USART_SR_PE 1U
|
||||
#define USART_SR_RXNE 32U
|
||||
#define GPIO_PIN_RESET 0U
|
||||
#define GPIO_PIN_SET 1U
|
||||
#define GPIO_MODE_OUTPUT_PP 1U
|
||||
#define GPIO_MODE_AF_PP 2U
|
||||
#define GPIO_NOPULL 0U
|
||||
#define GPIO_SPEED_FREQ_HIGH 3U
|
||||
#define UART_WORDLENGTH_8B 0U
|
||||
#define UART_STOPBITS_1 0U
|
||||
#define UART_PARITY_NONE 0U
|
||||
#define UART_MODE_TX_RX 12U
|
||||
#define UART_HWCONTROL_NONE 0U
|
||||
#define UART_OVERSAMPLING_16 0U
|
||||
#define HAL_MAX_DELAY UINT32_MAX
|
||||
#define __HAL_UART_DISABLE(u) ((u)->Instance->CR1=0)
|
||||
void test_clear(UART_HandleTypeDef *u);
|
||||
#define __HAL_UART_CLEAR_OREFLAG(u) test_clear(u)
|
||||
HAL_StatusTypeDef HAL_UART_Abort(UART_HandleTypeDef *u);
|
||||
HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *u);
|
||||
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *u,const uint8_t *p,uint16_t n,uint32_t timeout);
|
||||
void HAL_GPIO_WritePin(GPIO_TypeDef *g,uint16_t pin,uint32_t level);
|
||||
void HAL_GPIO_Init(GPIO_TypeDef *g,const GPIO_InitTypeDef *i);
|
||||
void HAL_Delay(uint32_t ms);
|
||||
uint32_t HAL_GetTick(void);
|
||||
#endif
|
||||
41
c/ds18b20-ds2480/ports/stm32f4/tests/test_port.c
Normal file
41
c/ds18b20-ds2480/ports/stm32f4/tests/test_port.c
Normal file
@@ -0,0 +1,41 @@
|
||||
#include "ds2480_stm32f4_hal.h"
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
static uint32_t now, low_ms, high_ms, clears;
|
||||
static GPIO_TypeDef gpio;
|
||||
static unsigned fail_init, fail_tx;
|
||||
void test_clear(UART_HandleTypeDef *u){u->Instance->SR=0;++clears;}
|
||||
HAL_StatusTypeDef HAL_UART_Abort(UART_HandleTypeDef *u){(void)u;return HAL_OK;}
|
||||
HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *u)
|
||||
{assert(gpio.mode==GPIO_MODE_AF_PP && gpio.level==1);assert(u->Init.BaudRate==9600);return fail_init?HAL_ERROR:HAL_OK;}
|
||||
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *u,const uint8_t *p,uint16_t n,uint32_t timeout)
|
||||
{assert(n==1 && timeout==20);u->Instance->DR=*p;u->Instance->SR=USART_SR_RXNE;return fail_tx?HAL_ERROR:HAL_OK;}
|
||||
void HAL_GPIO_WritePin(GPIO_TypeDef *g,uint16_t pin,uint32_t level){g->pin=pin;g->level=level;}
|
||||
void HAL_GPIO_Init(GPIO_TypeDef *g,const GPIO_InitTypeDef *i){assert(i->Pin==64);g->mode=i->Mode;}
|
||||
void HAL_Delay(uint32_t ms){now+=ms;if(gpio.level)high_ms+=ms;else low_ms+=ms;}
|
||||
uint32_t HAL_GetTick(void){return now++;}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
USART_TypeDef regs={0};UART_HandleTypeDef uart={0};
|
||||
ds2480_stm32f4_hal ctx={&uart,&gpio,64,8};ds2480_port port;
|
||||
uint8_t byte=0xCD,rx=0;
|
||||
uart.Instance=®s;
|
||||
assert(ds2480_stm32f4_hal_bind(&ctx,&port)==DS2480_OK);
|
||||
assert(port.prepare(port.user)==0 && low_ms>=2 && high_ms>=2 && clears==1);
|
||||
assert(port.write(port.user,&byte,1,20)==0);
|
||||
assert(regs.SR & USART_SR_RXNE); /* Early reply must survive TX completion. */
|
||||
assert(port.read(port.user,&rx,1,20)==0 && rx==byte && clears==1);
|
||||
regs.SR=USART_SR_RXNE|USART_SR_FE;rx=0xA5;
|
||||
assert(port.read(port.user,&rx,1,20)!=0 && rx==0xA5 && clears==2);
|
||||
regs.SR=USART_SR_ORE;assert(port.write(port.user,&byte,1,20)!=0);
|
||||
regs.SR=0;now=UINT32_MAX-5;
|
||||
assert(port.read(port.user,&rx,1,20)!=0); /* Wrap-safe bounded timeout. */
|
||||
assert(port.write(port.user,&byte,2,20)!=0);
|
||||
assert(port.read(port.user,&rx,1,HAL_MAX_DELAY)!=0);
|
||||
fail_tx=1;assert(port.write(port.user,&byte,1,20)!=0);
|
||||
fail_init=1;assert(port.prepare(port.user)!=0 && gpio.level==1);
|
||||
ctx.tx_pin=3;assert(ds2480_stm32f4_hal_bind(&ctx,&port)==DS2480_ARGUMENT);
|
||||
puts("STM32F4 UART port: OK");return 0;
|
||||
}
|
||||
253
c/ds18b20-ds2480/tests/test_ds18b20_ds2480.c
Normal file
253
c/ds18b20-ds2480/tests/test_ds18b20_ds2480.c
Normal file
@@ -0,0 +1,253 @@
|
||||
#include "ds18b20_ds2480.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#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;
|
||||
unsigned data_mode, escape;
|
||||
} 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;
|
||||
f->data_mode = f->escape = 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; }
|
||||
if (f->data_mode) {
|
||||
unsigned i, reply = 0;
|
||||
if (command == 0xE3 && !f->escape) { f->escape=1; return 0; }
|
||||
if (!f->escape || command == 0xE3) {
|
||||
f->escape=0;
|
||||
for (i=0; i<8; ++i) reply |= (unsigned)wire_bit(f,(uint8_t)((command>>i)&1)) << i;
|
||||
f->reply=reply; f->pending=1; return 0;
|
||||
}
|
||||
f->escape=0; f->data_mode=0;
|
||||
}
|
||||
if (command == 0xE1) { f->data_mode=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(ds2480_reset(&bus) == DS2480_OK);
|
||||
CHECK(ds2480_byte(&bus, 0xCC, &bit) == DS2480_OK);
|
||||
CHECK(ds2480_power_begin(&bus, 0x44) == DS2480_OK);
|
||||
CHECK(f.hold == 0 && f.pulse && bus.power_active);
|
||||
CHECK(ds2480_reset(&bus) == DS2480_BUSY);
|
||||
CHECK(ds2480_byte(&bus, 0xFF, &bit) == DS2480_BUSY);
|
||||
CHECK(ds2480_power_begin(&bus, 0x44) == DS2480_BUSY);
|
||||
delay(&f, 750);
|
||||
CHECK(ds2480_power_end(&bus) == DS2480_OK && !bus.power_active);
|
||||
CHECK(ds2480_power_end(&bus) == DS2480_OK);
|
||||
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);
|
||||
CHECK(ds18b20_ds2480_configure(&bus, f.rom[0], (int8_t)-29, 2, 12, 0) == DS2480_OK);
|
||||
CHECK(f.scratch[2] == 0xE3); /* Escaped byte followed by an ordinary byte. */
|
||||
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;
|
||||
}
|
||||
@@ -1,11 +1,144 @@
|
||||
# PORTING
|
||||
# Портирование firmware-info
|
||||
|
||||
Аппаратно-зависимый контракт ограничен `firmware_info_config.h`:
|
||||
`FIRMWARE_VERSION_MAJOR`, `FIRMWARE_VERSION_MINOR`, `FIRMWARE_VERSION_PATCH` и
|
||||
необязательный `FIRMWARE_BUILD_ID`. Выберите порт по семейству МК или создайте
|
||||
его копию. Публикация — ответственность транспорта: 12 `uint16_t` для Modbus
|
||||
либо 24 little-endian байта для SETGUI.
|
||||
## Назначение и границы
|
||||
|
||||
При переносе проверьте: поддержку `__DATE__`/`__TIME__`, наличие generated в
|
||||
include path, запуск генератора до компиляции и декодирование build ID как двух
|
||||
ASCII-байтов в каждом логическом слове.
|
||||
Это C-библиотека описания работающего образа: SemVer, дата/время компиляции,
|
||||
8 символов build ID и сериализация контракта v1. Она не обращается к сети,
|
||||
не читает версию из сервера и не записывает Flash. HAL, RTOS, UART и CAN
|
||||
ядру не нужны. Отправку результата выполняет приложение.
|
||||
|
||||
Для размещения `.hex/.bin` в каталоге SETGUI уже существуют
|
||||
[`setprotocol.firmware_publish`](../../python/setprotocol/firmware_publish.py)
|
||||
и [`tools/firmware-publish`](../../tools/firmware-publish/README.md).
|
||||
Связь компонентов и перенос публикации описаны в
|
||||
[`tools/firmware-publish/PORTING.md`](../../tools/firmware-publish/PORTING.md).
|
||||
|
||||
## 1. Файлы и конфигурация
|
||||
|
||||
Пример ниже предполагает `lib/templates` внутри нового проекта:
|
||||
|
||||
```text
|
||||
project/
|
||||
inc/firmware_info_config.h
|
||||
generated/firmware_build_id.h # создаётся до сборки
|
||||
lib/templates/c/firmware-info/
|
||||
src/
|
||||
```
|
||||
|
||||
Добавьте в сборку оба файла:
|
||||
|
||||
```text
|
||||
lib/templates/c/firmware-info/src/firmware_info.c
|
||||
lib/templates/c/firmware-info/src/firmware_info_port.c
|
||||
```
|
||||
|
||||
В include path добавьте `inc`, `generated` и
|
||||
`lib/templates/c/firmware-info/include`. Встроенный `CMakeLists.txt` собирает
|
||||
только ядро и host-тест; `firmware_info_port.c` и каталоги конфигурации нужно
|
||||
добавлять к целевому firmware target самостоятельно.
|
||||
|
||||
Скопируйте `ports/<mcu>/firmware_info_config.template.h` как
|
||||
`inc/firmware_info_config.h`. Есть варианты STM32F1/F4/G4 и К1921ВК028;
|
||||
они не содержат регистров МК. Для другого МК с обычными 8-битными байтами
|
||||
достаточно такого же config:
|
||||
|
||||
```c
|
||||
#ifndef FIRMWARE_INFO_CONFIG_H
|
||||
#define FIRMWARE_INFO_CONFIG_H
|
||||
#define FIRMWARE_VERSION_MAJOR 1U
|
||||
#define FIRMWARE_VERSION_MINOR 2U
|
||||
#define FIRMWARE_VERSION_PATCH 3U
|
||||
#include "firmware_build_id.h"
|
||||
#endif
|
||||
```
|
||||
|
||||
Обязательный include в этом примере позволяет обнаружить пропущенный pre-build.
|
||||
В готовых шаблонах include опциональный через `__has_include`; если заголовок
|
||||
не подключён, порт использует `LOCALDEV`. Для компилятора без `__has_include`
|
||||
используйте явный include. Можно связать три версии с существующими макросами
|
||||
проекта, как это сделано в `KONOR_ds18b20/inc/firmware_info_config.h`.
|
||||
|
||||
## 2. Build ID
|
||||
|
||||
Из корня нового проекта перед компиляцией запустите:
|
||||
|
||||
```powershell
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File "lib/templates/c/firmware-info/tools/make_build_id.ps1" -Repository "." -Output "generated/firmware_build_id.h"
|
||||
```
|
||||
|
||||
Указывайте `-Repository` и `-Output` явно: расположение сабмодуля и рабочий
|
||||
каталог IDE различаются между проектами. `-Repository` должен указывать на
|
||||
исходники прошивки, а не на репозиторий `templates`. Для Keil из каталога `mdk`
|
||||
соответственно используйте `..\lib\templates\...`, `-Repository ".."` и
|
||||
`-Output ".\Generated\firmware_build_id.h"`.
|
||||
|
||||
При доступном Git чистый checkout получает 8 знаков commit, изменения tracked
|
||||
файлов — 7 знаков и `+`. Если commit определить нельзя, используется `NOGIT000`.
|
||||
Новые untracked-файлы текущий генератор при определении dirty не учитывает.
|
||||
Дата/время берутся из `__DATE__`/`__TIME__` при компиляции `firmware_info_port.c`;
|
||||
для выпуска выполняйте полный Rebuild, чтобы не оставить старый объектный файл.
|
||||
|
||||
## 3. Обработчик запроса версии
|
||||
|
||||
Пример функции подготовки полезной нагрузки, вызываемой вашим обработчиком:
|
||||
|
||||
```c
|
||||
#include "firmware_info_port.h"
|
||||
|
||||
firmware_info_status_t app_make_firmware_info(uint8_t *payload, size_t capacity)
|
||||
{
|
||||
firmware_info_t info;
|
||||
firmware_info_status_t status = firmware_info_port_describe(&info);
|
||||
if (status != FIRMWARE_INFO_OK) return status;
|
||||
return firmware_info_to_le_bytes(&info, payload, capacity);
|
||||
}
|
||||
```
|
||||
|
||||
При успехе передайте ровно `FIRMWARE_INFO_PAYLOAD_SIZE` (24) байта в собственный
|
||||
транспорт. При ошибке верните ошибку протокола, а не содержимое буфера.
|
||||
Не отправляйте `sizeof(firmware_info_t)`: структура не является wire format.
|
||||
|
||||
В KONOR обработчик `app_send_firmware_info()` отвечает на
|
||||
`PROTO_MSG_FIRMWARE_INFO = 0x03`, сохраняя sequence запроса. Старый 32-байтовый
|
||||
`DEVICE_INFO` остаётся отдельным сообщением. Для нового протокола сначала
|
||||
согласуйте команду с клиентом: подключение библиотеки само по себе не добавит
|
||||
декодер и отображение версии в GUI.
|
||||
|
||||
Для Modbus вызовите `firmware_info_to_registers()` с массивом из 12 `uint16_t`
|
||||
и разместите его в выбранной карте регистров. Адреса и Modbus-порядок байтов
|
||||
обеспечивает ваш Modbus-стек; LE-буфер в Modbus напрямую не копируйте.
|
||||
|
||||
## 4. Контракт v1
|
||||
|
||||
| Индекс слова | Содержимое |
|
||||
|---|---|
|
||||
| 0 | Версия контракта: 1 |
|
||||
| 1, 2, 3 | major, minor, patch |
|
||||
| 4 | Год |
|
||||
| 5 | `(month << 8) \| day` |
|
||||
| 6 | `(hour << 8) \| minute` |
|
||||
| 7 | Секунды |
|
||||
| 8–11 | По два ASCII-символа build ID: первый в старшем байте слова |
|
||||
|
||||
В LE-представлении младший байт каждого слова идёт первым. Например, build ID
|
||||
`ab` в начале строки даёт слово `0x6162`, но байты `62 61`; декодируйте сначала
|
||||
слово, затем символы из старшего/младшего байта. NUL-терминатор не передаётся.
|
||||
|
||||
Текущая проверка допускает major/minor до 255 и patch до 999. Если в каталоге
|
||||
используется `(major << 16) | (minor << 8) | patch`, ограничьте **все три** части
|
||||
до 255, иначе значения пересекутся. Версия каталога автоматически из C-config
|
||||
не извлекается.
|
||||
|
||||
Для C2000 с 16-битным `char` нельзя считать готовым байтовый порт: отдельно
|
||||
проверьте наличие `uint8_t` и представление октетов в транспорте. Публикация
|
||||
TMS SCI8-файла с ПК поддерживается независимо от переноса этой C-библиотеки.
|
||||
|
||||
## 5. Проверка переноса
|
||||
|
||||
1. Соберите host-тест из `tests/test_firmware_info.c` вместе с ядром либо
|
||||
используйте CMake/CTest. Он проверяет дату, регистры и порядок байтов build ID.
|
||||
2. Соберите целевую прошивку с обоими `.c` и сгенерированным заголовком.
|
||||
3. Запросите версию у устройства: сравните SemVer, build ID, время и 24-байтовый
|
||||
ответ с конкретной сборкой. Проверьте, что прежний `DEVICE_INFO` не изменился.
|
||||
4. Подключите выпуск по инструкции публикатора; сравните версию в config и
|
||||
`firmware-release.cmd` перед Rebuild и `--preflight`.
|
||||
|
||||
@@ -25,3 +25,7 @@ config, а не дублирования ядра.
|
||||
|
||||
KONOR публикует эти 24 байта ответом `FIRMWARE_INFO (0x03)`, сохраняя старый
|
||||
32-байтовый `DEVICE_INFO` без изменений.
|
||||
|
||||
Подробная инструкция с кодом обработчика, форматом ответа и проверкой переноса:
|
||||
[`PORTING.md`](PORTING.md). Для размещения файла прошивки в каталоге SETGUI
|
||||
используется отдельный [общий публикатор](../../tools/firmware-publish/PORTING.md).
|
||||
|
||||
93
c/set-protocol/ALTERA_LOGIC.md
Normal file
93
c/set-protocol/ALTERA_LOGIC.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# Altera Logic: общее ядро и порты
|
||||
|
||||
Потоковый SETCAN/GAS-профиль и общие порты описаны отдельно:
|
||||
[Altera Logic ONLINE](../../doc/setcan/ALTERA_LOGIC_STREAM.md).
|
||||
Ниже — совместимый прежний буферный протокол UART `A5 CMD`.
|
||||
|
||||
Клиент UART-протокола FPGA-анализатора Cyclone IV: 16 каналов, 4096 выборок.
|
||||
Работает с существующим RTL `altera_loganalisator`, версия протокола 1.
|
||||
Ядро C99 не зависит от Qt, ОС, системных часов и динамической памяти.
|
||||
|
||||
Слои: SETGUI → Python ctypes → C99 → Python Qt-порт → COM / USB–UART.
|
||||
GUI передаёт числовые настройки и получает готовый массив; формат пакетов,
|
||||
XOR, endian, последовательность команд, парсер и состояния тайм-аутов находятся в C.
|
||||
|
||||
| Файл | Назначение и зависимости |
|
||||
|---|---|
|
||||
| `include/altera_logic.h` | Публичный ABI, макрос экспорта из `pcan_abi.h` |
|
||||
| `src/altera_logic.c` | Кодек и автомат обмена; только стандартная библиотека C |
|
||||
| `tests/test_altera_logic.c` | Эталонные пакеты RTL, полный захват, ошибки |
|
||||
| `../../python/altera_logic/native.py` | ctypes-модели, загрузка DLL, экспорт CSV; stdlib |
|
||||
| `../../python/altera_logic/qt_port.py` | Асинхронный COM-порт; PySide6 QtSerialPort |
|
||||
|
||||
## Сборка
|
||||
|
||||
Из корня `templates`:
|
||||
|
||||
```powershell
|
||||
python c/set-protocol/tools/build_host.py --output python/altera_logic/native/setprotocol.dll
|
||||
```
|
||||
|
||||
На Linux выходной файл — `python/altera_logic/native/libsetprotocol.so`.
|
||||
Также поддерживается основная CMake-сборка `c/set-protocol`; путь к результату
|
||||
задаётся через `ALTERA_LOGIC_LIBRARY`. Библиотека без символов `la_*`
|
||||
не поддерживается: тихого Python-fallback нет.
|
||||
|
||||
## Контракт порта
|
||||
|
||||
Выделить выровненную память размером `la_context_size()`; все обращения к
|
||||
одному контексту выполняются в одном потоке.
|
||||
|
||||
- `la_init(ctx)` — начать с INFO.
|
||||
- `la_next(ctx, out, capacity)` — получить следующий запрос, либо 0, если отправлять нечего.
|
||||
- `la_feed(ctx, bytes, size)` — передать принятые байты, включая частичные ответы.
|
||||
- `la_tick(ctx, elapsed_ms)` — сообщить прошедшее время; тайм-аут ответа 1000 мс.
|
||||
- `la_start(ctx, divider, mask, value, edge_mask, edge_value)` — настройка и новый захват.
|
||||
- `la_get(ctx, field)` — состояние и прогресс; `la_samples` — завершённая запись.
|
||||
- `la_fail(ctx, LA_IO_ERROR)` — сообщить ошибку транспорта.
|
||||
|
||||
COM: 921600 бод, 8N1, без управления потоком. Одновременно ожидается только
|
||||
один ответ. После ARM STATUS опрашивается каждые 25 мс; ожидание самого
|
||||
триггера не ограничено. После done читаются 64 блока по 64 выборки.
|
||||
Ошибки закрывают сеанс без автоматического повтора ARM. Неверный размер,
|
||||
код команды или XOR прекращает сеанс. При рассинхронизации запроса на FPGA
|
||||
может потребоваться аппаратный RESET_N; переподключение не гарантирует её сброс.
|
||||
|
||||
Клиент намеренно принимает только профиль 16 × 4096 версии 1.
|
||||
Поддержка внешней SDRAM и расширенных адресов сюда пока не входит.
|
||||
Прерывание в GUI закрывает порт ПК: STOP в протоколе FPGA отсутствует.
|
||||
|
||||
## Быстрый старт без платы
|
||||
|
||||
Добавить `templates/python` в PYTHONPATH:
|
||||
|
||||
```python
|
||||
from altera_logic import NativeAnalyzer
|
||||
|
||||
client = NativeAnalyzer()
|
||||
client.reset(demo=True)
|
||||
client.start(divider=49)
|
||||
capture = client.capture()
|
||||
print(len(capture.samples)) # 4096
|
||||
print(capture.sample_rate) # 1_000_000
|
||||
print(capture.trigger_index) # 2048
|
||||
capture.save_csv("demo.csv")
|
||||
```
|
||||
|
||||
Демо создаёт синтетическую запись в C; не моделирует заданные условия триггера.
|
||||
CSV содержит явный признак demo, время относительно триггера и состояния D0…D15.
|
||||
|
||||
## Потребители и проверка
|
||||
|
||||
Используется вкладкой SETGUI «Altera Logic». Для Android/JNI новый потребитель
|
||||
может использовать тот же ABI; интерфейс Android в этой задаче не изменяется.
|
||||
Существующий ABI SETProtocol не изменён, добавлены только символы `la_*`.
|
||||
|
||||
```powershell
|
||||
$env:PYTHONPATH = "$PWD/python"
|
||||
python -m unittest discover -s python/tests -p test_altera_logic.py
|
||||
```
|
||||
|
||||
CMake включает `test_altera_logic` и CTest `altera_logic`.
|
||||
На плате требуется отдельно проверить разводку пинов, RAM-модуль и прошивку;
|
||||
программные проверки не заменяют аппаратную проверку.
|
||||
@@ -6,9 +6,12 @@ set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
|
||||
# SET protocol v2: управление, телеметрия, CAN segmentation и firmware flow.
|
||||
set(SETPROTOCOL_V2_SOURCES
|
||||
src/altera_logic.c
|
||||
src/altera_stream.c
|
||||
src/set_protocol.c
|
||||
src/set_can.c
|
||||
src/set_firmware.c
|
||||
src/set_boot.c
|
||||
src/set_telemetry.c
|
||||
src/set_plot.c
|
||||
src/set_trends.c
|
||||
@@ -34,6 +37,12 @@ set(SETPROTOCOL_LEGACY_SOURCES
|
||||
)
|
||||
|
||||
set(SETPROTOCOL_SOURCES
|
||||
src/set_regmap.c
|
||||
src/set_emu_ump.c
|
||||
src/set_emu_tms.c
|
||||
src/set_emu_igbt.c
|
||||
src/set_emu_server.c
|
||||
src/set_wavegen.c
|
||||
${SETPROTOCOL_V2_SOURCES}
|
||||
${SETPROTOCOL_LEGACY_SOURCES}
|
||||
)
|
||||
@@ -79,6 +88,20 @@ endif()
|
||||
option(SETP_BUILD_TESTS "Build host tests" ON)
|
||||
if(SETP_BUILD_TESTS)
|
||||
enable_testing()
|
||||
add_executable(test_emulator_services tests/test_emulator_services.c ports/example-emulator/service.c)
|
||||
target_link_libraries(test_emulator_services PRIVATE setprotocol_static)
|
||||
add_test(NAME emulator_services COMMAND test_emulator_services)
|
||||
if(MSVC)
|
||||
target_compile_options(test_emulator_services PRIVATE /UNDEBUG)
|
||||
else()
|
||||
target_compile_options(test_emulator_services PRIVATE -UNDEBUG)
|
||||
endif()
|
||||
add_executable(test_altera_logic tests/test_altera_logic.c)
|
||||
target_link_libraries(test_altera_logic PRIVATE setprotocol_static)
|
||||
add_test(NAME altera_logic COMMAND test_altera_logic)
|
||||
add_executable(test_altera_stream tests/test_altera_stream.c)
|
||||
target_link_libraries(test_altera_stream PRIVATE setprotocol_static)
|
||||
add_test(NAME altera_stream COMMAND test_altera_stream)
|
||||
|
||||
add_executable(test_plot tests/test_plot.c)
|
||||
target_link_libraries(test_plot PRIVATE setprotocol_static)
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
# SETProtocol
|
||||
|
||||
Универсальные сервисы эмуляторов: [адресное пространство, API и порт новой платы](docs/EMULATOR_SERVICES.md).
|
||||
`set_regmap` объединяет УМП, 2812, IGBT и DAC за общим API для RTU, CAN и других оболочек.
|
||||
|
||||
Единое переносимое протокольное ядро SET для `SETGUI`, Android GUI, устройств
|
||||
и сервисных утилит. В одном C99-проекте собраны:
|
||||
|
||||
|
||||
228
c/set-protocol/docs/EMULATOR_SERVICES.md
Normal file
228
c/set-protocol/docs/EMULATOR_SERVICES.md
Normal file
@@ -0,0 +1,228 @@
|
||||
# Универсальное адресное пространство эмуляторов
|
||||
|
||||
Общий код находится в `templates/c/set-protocol`. Прошивка платы задаёт память,
|
||||
GPIO и аппаратные callbacks; протокол и модели не включают STM32/HAL/USB/ОС,
|
||||
не выделяют heap и не читают системное время. Все вызовы одного экземпляра
|
||||
сериализуются главным циклом или блокировкой вызывающего.
|
||||
|
||||
## Слои и точка расширения
|
||||
|
||||
- `set_regmap.h/.c`: таблица непересекающихся регионов и общий вызов
|
||||
`set_regmap_request(map, function, address, value, out, capacity, &count)`.
|
||||
Function 3 читает `value` слов, function 6 пишет одно слово.
|
||||
- `set_emu_ump`, `set_emu_tms`, `set_emu_igbt`, `set_wavegen`:
|
||||
независимые экземпляры моделей. Callbacks получают **относительный offset**,
|
||||
а не внешний адрес. Один модуль можно разместить несколько раз по разным адресам.
|
||||
- `set_emu_server`: готовая композиция для текущего стенда, режим УМП/2812,
|
||||
подготовка настроек с применением через 500 тиков.
|
||||
- `set_regmap_rtu` и `set_regmap_can`: готовые транспортные кодеки над одной картой.
|
||||
`set_emu_server_rtu/can` дополнительно сохраняют legacy поведение стенда.
|
||||
USB CDC, UART, TCP или иной поток только доставляет полный RTU-кадр.
|
||||
Другой протокол разбирает собственную оболочку и вызывает `set_regmap_request`.
|
||||
|
||||
Вызов с неизвестной функцией возвращает 1; неизвестный адрес, RO-запись,
|
||||
чтение через границу региона — 2; неверное значение или размер буфера — 3;
|
||||
ошибка аппаратного порта — 4; занятый модуль — 6. При ошибке `count=0`.
|
||||
Длина успешного чтения равна запрошенной, запись возвращает адрес/значение.
|
||||
Максимум — 124 слова. RTU-обёртка требует буфер 253 байта до выполнения команды;
|
||||
CAN — 124 слова. Недостаточный буфер не может выполнить запись без ответа.
|
||||
При ошибке содержимое выходного буфера не используется.
|
||||
|
||||
`set_regmap_init` проверяет границы 16-битного пространства, пересечения,
|
||||
права и callbacks. Таблица и состояния принадлежат вызывающему и должны жить
|
||||
дольше карты. После регистрации таблица неизменна; для изменения карты
|
||||
инициализируйте её заново в момент, когда нет обслуживаемых запросов.
|
||||
|
||||
## Каталог и стандартная карта
|
||||
|
||||
Каталог доступен на адресе 0 через любой транспорт.
|
||||
|
||||
| Слово заголовка | Значение |
|
||||
|---|---|
|
||||
| 0 | 0x5345, сигнатура SE |
|
||||
| 1 | версия каталога 1 |
|
||||
| 2 | количество регионов |
|
||||
| 3 | начало дескрипторов: 8 |
|
||||
| 4 | длина дескриптора: 8 |
|
||||
| 5 | максимум слов в одном чтении: 124 |
|
||||
| 6, 7 | резерв: 0 |
|
||||
|
||||
Дескриптор: `kind, version, instance, base, words, flags, 0, 0`.
|
||||
Flags: bit0 чтение, bit1 запись. Права описывают возможности региона;
|
||||
отдельные регистры внутри могут быть только для чтения. Пропуски внутри
|
||||
региона возвращают ошибку, если модуль их не поддерживает.
|
||||
|
||||
| Kind | Стандартный адрес | Размер | Модуль |
|
||||
|---|---|---|---|
|
||||
| 1 | 0x1000 | 0x17C | УМП: статус, окно, онлайн |
|
||||
| 2 | 0x1200 | 36 | управление, конфигурация и indexed capability |
|
||||
| 3 | 0x1300 | 8 | генератор DAC |
|
||||
| 4 | 0x1400 | 112 | IGBT, версия 3 |
|
||||
| 5 | 0x1500 | 80 | 2812 через регистры |
|
||||
| 6 | 0x2000 | capacity × 40 | архив/банк УМП |
|
||||
| 7 | 0xA000 | wave capacity | таблица DAC |
|
||||
|
||||
Эта карта — готовая композиция, а не требование к новым платам.
|
||||
`set_regmap` позволяет выбирать адреса, подмножество и несколько экземпляров
|
||||
модуля; каталог сообщает фактическую карту. Настройка UART и выбор УМП/2812
|
||||
принадлежат только готовому `set_emu_server`. Максимальная ёмкость УМП
|
||||
в этой карте — 819 записей на банк; текущая STM32 использует 800.
|
||||
|
||||
В режиме 2812 прямое обращение к регистрам УМП возвращает busy, legacy
|
||||
RTU/CAN к этим диапазонам игнорируются для совместимости. Остальные сервисы
|
||||
и каталог доступны. Регистры управления 2812 принимают запись только в режиме 2812.
|
||||
Чтение его состояния не запускает модель.
|
||||
|
||||
## IGBT
|
||||
|
||||
Предыдущий локальный диапазон IGBT 0x1300 конфликтовал с DAC. Его alias
|
||||
сохранить нельзя: он обращается к генератору. Используйте каталог kind=4 или
|
||||
новый стандартный адрес 0x1400.
|
||||
|
||||
| Offset | Назначение |
|
||||
|---|---|
|
||||
| 0, 1 | 0x4947, версия 3 |
|
||||
| 2 | enable 0/1; выключение сразу очищает состояние RX и ожидаемые ACK |
|
||||
| 3 | чтение последнего TX; запись подготовленной TX-маски |
|
||||
| 4 | RX-маска |
|
||||
| 5 | резерв 0 |
|
||||
| 6 | FAULT-маска 0…255 |
|
||||
| 7 | задержка ACK 0…1000 мс |
|
||||
| 8 | ширина ACK 1…255 мс |
|
||||
| 9 | счётчик фронтов modulo 65536; запись 0 сбрасывает |
|
||||
| 10 | число каналов 8 |
|
||||
| 11 | один шаг модели = 1 мс |
|
||||
| 12 | источник TX: 0 переданные GPIO, 1 подготовленная маска |
|
||||
| 13 | подготовленная TX-маска, чтение/запись |
|
||||
| 14 | запись маски запуска одиночного TX; чтение 0 |
|
||||
| 15 | длина записи канала: 12 слов |
|
||||
|
||||
Версия 3 сохраняет первые 14 регистров. Общие delay/width при записи
|
||||
устанавливают значения для всех каналов; при чтении показывают последнее
|
||||
общее задание, а не поканальные значения. Полное состояние: FC03 0x1400 / 112 слов.
|
||||
Запуск TX требует enabled=1, source=1, разрешённого TX канала и низкого TX;
|
||||
повторный запуск высокого TX отвергается кодом 6. Длительность отсчитывает
|
||||
сама модель, не клиент COM. Ручная запись TX отменяет все текущие TX-импульсы.
|
||||
Выключение эмуляции отменяет импульсы и сбрасывает их TX; статическое задание
|
||||
TX сохраняется. PB остаются входами: тестовый TX — внутренняя подстановка,
|
||||
физическая обратная связь RX выводится на PC0…PC7.
|
||||
|
||||
Поканальные регистры: `0x1410 + 12 * channel`, channel=0…7.
|
||||
|
||||
| Offset канала | Значение |
|
||||
|---|---|
|
||||
| 0 | flags: bit0 разрешение TX, bit1 разрешение RX, bit2 ACK active-low |
|
||||
| 1 | RX mode: 0 ACK, 1 физический 0, 2 физическая 1, 3 пропуск ACK |
|
||||
| 2, 3 | задержка ACK ON 0…1000 мс, ширина 1…1000 мс |
|
||||
| 4, 5 | задержка ACK OFF 0…1000 мс, ширина 1…1000 мс |
|
||||
| 6 | одиночный TX: 1…60000 мс |
|
||||
| 7 | маска фронтов для ACK: bit0 ON, bit1 OFF |
|
||||
| 8 | ширина FAULT 1…60000 мс; 0 — удержание до снятия FAULT |
|
||||
| 9 | RO счётчик фронтов канала, modulo 65536 |
|
||||
| 10, 11 | RO оставшаяся задержка / длительность ACK в мс |
|
||||
|
||||
Изменение flags/mode отменяет ожидаемый и активный ACK канала.
|
||||
Отключённый RX и выключенная модель всегда дают физический 0.
|
||||
Инверсия применяется к автоматическому ответу и idle при пропуске ACK;
|
||||
ручные уровни 0/1 не инвертируются. Сброс общего счётчика сбрасывает и канальные.
|
||||
FAULT моделирует длинный/удерживаемый ответ после фронта; это не модель Vce,
|
||||
Vge, DESAT, внутренней защиты или блокировки конкретного силового драйвера.
|
||||
|
||||
`set_igbt_tick(state, pins)` получает маску входов от порта платы.
|
||||
Фронт на тике T запускает ACK на T+delay. Повторный фронт до начала ACK
|
||||
перезапускает задержку; это определённая модельная обработка слишком частых команд.
|
||||
Каждый из восьми каналов независим. FAULT удерживает RX после его активации;
|
||||
снятие FAULT освобождает только ранее аварийные каналы и сохраняет обычные ACK.
|
||||
Чтобы TX через регистры не перезаписывался GPIO, выберите source=1.
|
||||
|
||||
**Текущая IGBT-модель имеет шаг 1 мс.** Она проверяет логическую последовательность
|
||||
Vin/Vstat, но не воспроизводит ACK 250/380 нс и ширины профилей 1SP/1SD в DSLogic.
|
||||
Такую аппаратную точность нельзя получить переносом модели или вызовом из SysTick:
|
||||
для неё требуется отдельный быстрый аппаратный порт и соответствующая модель времени.
|
||||
|
||||
## 2812
|
||||
|
||||
Состояние едино для legacy CMD_STD и адресного API:
|
||||
|
||||
| Offset | Назначение |
|
||||
|---|---|
|
||||
| 0, 1 | 0x544D, версия 1 |
|
||||
| 2, 3, 4 | running, частота и задание (int16 ×100 Гц) |
|
||||
| 8…11 | четыре подготовленных analog int16 |
|
||||
| 12…17 | шесть подготовленных управляющих байт (значение 0…255) |
|
||||
| 18 | записать 1: атомарно применить подготовленную команду |
|
||||
| 32…45 | 28 байт дискретов; младший байт слова — первый байт |
|
||||
| 46…79 | 34 аналоговых int16 |
|
||||
| остальные | чтение 0 |
|
||||
|
||||
Модель остаётся упрощённой BALZAM_7: Go, выбор remote, Setspeed,
|
||||
разгон 10 Гц/с и синтетическая телеметрия. Legacy CMD_STD имеет отдельный
|
||||
адрес 2812, запрос 18 и ответ 101 байт, с проверкой CRC до изменения состояния.
|
||||
Буфер ответа менее 101 байта запрещает выполнение команды.
|
||||
|
||||
## УМП и DAC
|
||||
|
||||
УМП сохраняет прежние записи по 40 слов, банк/архив, фронтовой триггер,
|
||||
постинтервал 1000 мс и indexed-чтение без изменения курсора. Два разных
|
||||
буфера по `capacity*40` слов предоставляет плата. Размещение SRAM/CCM
|
||||
не является частью библиотеки.
|
||||
|
||||
DAC использует прежний `set_wave_port` с `start/stop`, транзакционную загрузку,
|
||||
commit, чтение таблицы и запрет изменений при работе. Регистровые callbacks
|
||||
и прежний `set_wave_rtu` вызывают одну реализацию. До повторного init вызывающий
|
||||
останавливает аппаратный вывод; библиотека не угадывает текущее состояние DMA.
|
||||
|
||||
## Обёртки и новая плата
|
||||
|
||||
Пример: [ports/example-emulator/service.c](../ports/example-emulator/service.c).
|
||||
Он регистрирует два экземпляра IGBT и показывает прямой, RTU и CAN вызов.
|
||||
Порт новой платы должен:
|
||||
|
||||
1. Создать состояния и таблицу регионов, вызвать init и проверить результат.
|
||||
2. Предоставить буферы и аппаратный DAC-порт, если нужен генератор.
|
||||
3. Передавать реальные тики и входы; читать результат модели и обновлять GPIO.
|
||||
4. Доставлять запросы через выбранную оболочку в одну карту, сериализуя доступ.
|
||||
|
||||
CAN-envelope совместим с UMP: BE address/value/0x1F00/token/function.
|
||||
Идентификаторы CAN и маршрутизацию задаёт порт. Ответ делится по два слова,
|
||||
нечётное чтение дополняется нулём до чётного числа; клиент отбрасывает последнее
|
||||
слово по длине исходного запроса. Токен 1…255; один незавершённый ответ на соединение.
|
||||
Проверенная копия слов хранится до отправки всех фрагментов.
|
||||
|
||||
`set_emu_stream` — только сборщик известных запросов FC03/FC06 и CMD_STD,
|
||||
по одному экземпляру на поток. Он принимает монотонные uint32 миллисекунды,
|
||||
сохраняет кадр между USB-пакетами, сбрасывает незавершённый после паузы >1000 мс
|
||||
и восстанавливается после плохого CRC. Это не универсальный Modbus-парсер
|
||||
FC16/FC23: для другого формата нужна другая тонкая оболочка.
|
||||
|
||||
## Сборка и проверки
|
||||
|
||||
C99-ядро подключается через `setprotocol_static`/DLL, определения публичного
|
||||
API экспортируются через PCAN_ABI_API. Включить `set_regmap.h` или
|
||||
`set_emu_server.h`. Для иной архитектуры проверить наличие uint8/16/32_t;
|
||||
существующий 16-битный C28x octet-порт UMP остаётся отдельным, этот новый
|
||||
байтовый API пока не проверен TI-компилятором.
|
||||
|
||||
`test_emulator_services.c` проверяет каталог, пересечения, границы и короткие
|
||||
буферы, два экземпляра, переназначение адресов, RTU/CAN/stream, совместное
|
||||
состояние 2812 и DAC, длинные задержки IGBT и освобождение FAULT.
|
||||
Существующие тесты прошивки дополнительно проверяют архив УМП, USB-фрагментацию,
|
||||
смену режима/адресов и совместимость с декодерами SETGUI.
|
||||
|
||||
## Выбор единственного эмулятора
|
||||
|
||||
В SETGUI: «Эмулятор → Настройки STM → Тип эмулятора», затем
|
||||
«Применить в STM и проверить» и «Открыть управление эмулятором».
|
||||
Конфигурация версии 3: режим 0 — УМП, 1 — 2812, 2 — IGBT, 3 — ЦАП.
|
||||
Регистр 0x1219 подготавливает режим; 0x1218 = 0xA55A применяет его через
|
||||
500 мс после подтверждения. Статус FC03 0x1210 / 10 слов содержит версию 3,
|
||||
действующий режим в слове 8 и подготовленный в слове 9.
|
||||
|
||||
При смене режима останавливаются TIM6/DMA/DAC, ЦАП устанавливается в ноль,
|
||||
IGBT отключается и его выходы обнуляются, 2812 сбрасывается, УМП останавливается.
|
||||
Новый режим не запускается автоматически. Для IGBT нужно явно включить
|
||||
эмуляцию; для ЦАП заново загрузить таблицу и дать команду пуска.
|
||||
Повторное применение того же режима не прерывает текущую работу.
|
||||
Записи невыбранным IGBT/ЦАП/2812 отклоняются с кодом 6; чтение статуса
|
||||
и явные команды остановки IGBT/ЦАП доступны. Настройки связи доступны всегда.
|
||||
После сброса выбран УМП, IGBT выключен, ЦАП не запущен.
|
||||
126
c/set-protocol/docs/SIGNAL_GENERATOR.md
Normal file
126
c/set-protocol/docs/SIGNAL_GENERATOR.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# Генератор произвольного сигнала WG v1
|
||||
|
||||
Общее C99-ядро строит кривые по точкам, преобразует напряжения в коды ЦАП и
|
||||
принимает таблицы через RTU поверх USB CDC/COM. Оно не зависит от Qt, HAL или ОС.
|
||||
Память предоставляет вызывающий; скрытого heap и глобального состояния нет.
|
||||
|
||||
```text
|
||||
SETGUI: точки -> Python/ctypes -> set_signal.c -> график / CSV / C
|
||||
-> set_wavegen.c -> USB CDC
|
||||
MCU: USB stream -> set_wave_rtu -> set_wave_port -> TIM6/DMA/DAC
|
||||
```
|
||||
|
||||
| Файл | Зависимости и назначение |
|
||||
|---|---|
|
||||
| `include/set_signal.h`, `src/set_signal.c` | C99/math: МНК 1…5, linear, PCHIP, natural cubic spline, 12-bit DAC |
|
||||
| `include/set_wavegen.h`, `src/set_wavegen.c` | C99 + `set_crc.c`: транзакционная загрузка и wire codec |
|
||||
| `ports/stm32f407-wavegen` | CMSIS F407: PA4/DAC1, TIM6, DMA1 Stream5 Channel7 |
|
||||
| `ports/stm32g474-wavegen` | STM32G4 HAL: готовые DAC/TIM/DMA handles из CubeMX |
|
||||
| `python/set_devices/signal_reconstruction.py`, `waveform.py` | Модели, JSON/экспорт, ctypes; алгоритмов на Python нет |
|
||||
| `python/set_devices/wavegen_protocol.py`, `qt_ports/wavegen.py` | ctypes-кодек и Qt COM worker с проверкой чтением обратно |
|
||||
|
||||
## Интерполяция и таблица
|
||||
|
||||
`set_signal_reconstruct` принимает раздельные X/Y и workspace `14*N+128`
|
||||
элементов double. Сортировка, усреднение одинаковых времён, нормализация,
|
||||
МНК через QR и интерполяция выполняются в C. Максимум 100000 входных/10000
|
||||
выходных точек. Переданный workspace может повторно использоваться.
|
||||
Большие расчёты предназначены для хоста; на MCU можно строить малые таблицы
|
||||
или принимать готовую таблицу без затрат на интерполяцию.
|
||||
|
||||
`endpoint=1` включает последний X (анализ логов); `endpoint=0` строит один
|
||||
период `[0,T)` без дублированного отсчёта стыка. Частота Fs и период T задают
|
||||
целое `N=Fs*T`; для WG максимум N=4096, Fs=1…50000 Гц, один канал, циклический
|
||||
выход. При разных значениях первой и последней точки на стыке будет скачок.
|
||||
Напряжения 0…Vref округляются к ближайшему коду 0…4095; выход за диапазон
|
||||
отклоняется целиком. Это относится и к выбросам сплайна. Vref должен
|
||||
соответствовать фактическому VDDA/VREF+ платы; это не программируемое питание.
|
||||
|
||||
Пример хоста (DLL передаётся через `SETPROTOCOL_LIBRARY`):
|
||||
|
||||
```python
|
||||
from set_devices.waveform import generate, c_header
|
||||
points = [(0, 0), (10, 3.3), (20, 0)]
|
||||
wave = generate(points, sample_rate=10000, vref=3.3, method="linear")
|
||||
assert len(wave.codes) == 200
|
||||
with open("waveform.h", "w", encoding="utf-8") as output:
|
||||
output.write(c_header(wave))
|
||||
```
|
||||
|
||||
## Контракт порта
|
||||
|
||||
`start(context, samples, count, sample_rate)` возвращает 0 при успехе;
|
||||
`stop(context)` синхронно прекращает DMA и устанавливает нулевой выход.
|
||||
Память таблицы принадлежит `set_wave_state`, её нельзя менять до stop.
|
||||
Вызывайте RTU и обслуживание состояния из одного основного потока, не из IRQ.
|
||||
IRQ USB помещает байты в очередь; USB-пакеты не являются границами RTU.
|
||||
|
||||
```c
|
||||
static uint16_t samples[SET_WAVE_MAX]; /* DMA-accessible SRAM, not CCM */
|
||||
static set_wave_state wave;
|
||||
static set_wave_f407 hardware = {72000000};
|
||||
void application_init(void) {
|
||||
set_wave_port port = set_wave_f407_port(&hardware);
|
||||
set_wave_init(&wave, samples, SET_WAVE_MAX, &port);
|
||||
}
|
||||
size_t on_rtu(const uint8_t *frame, size_t n, uint8_t reply[256]) {
|
||||
return set_wave_rtu(&wave, 16, frame, n, reply, 256);
|
||||
}
|
||||
```
|
||||
|
||||
Для F407 DAC_CH1 — PA4, TIM6 TRGO_UPDATE, DMA1 Stream5/Channel7. Порт использует
|
||||
только нижние 16 бит DAC_CR и не меняет канал 2. Таблица лежит в SRAM1/2
|
||||
`0x20000000…0x2001FFFF`, не в CCM. Перед первым отсчётом есть один такт
|
||||
предзагрузки последнего значения предыдущего периода. Для гарантированной
|
||||
целой частоты порт принимает только делители входной частоты TIM6; при
|
||||
72 МГц подходят, например, 100, 1000, 5000, 10000, 20000 Гц.
|
||||
Частоту таймера передавайте с учётом удвоения при делителе APB1 > 1.
|
||||
Ошибку DMA/underrun проверяйте через `set_wave_f407_fault()` и останавливайте
|
||||
состояние. На F407-проекте этот вызов включён в главный цикл.
|
||||
|
||||
Для G474 настройте CubeMX: DAC1 channel1 (PA4), output buffer enabled,
|
||||
TIM6 TRGO_UPDATE, DMA memory-to-peripheral, circular, halfword/halfword,
|
||||
memory increment, запрос `DMA_REQUEST_DAC1_CHANNEL1`; подключите HAL IRQ
|
||||
для выбранного канала DMA и DAC. Передайте handles и timer_hz в
|
||||
`set_wave_g474`. Обработчики ошибок DMA/DAC в приложении должны вызвать stop
|
||||
и сбросить `wave.running/ready`. Порт проверен компиляцией с CubeG4 1.6.1;
|
||||
конкретная плата G474 здесь не прошита.
|
||||
|
||||
## USB / Modbus RTU
|
||||
|
||||
Адрес по умолчанию 16, FC03/FC06, CRC16 Modbus. Максимальный запрос — 8 байт,
|
||||
ответ — 21 байт; существующий потоковый USB RTU parser F407 подходит без
|
||||
изменения формата. Все поля строит/проверяет C-кодек, GUI не пакует байты.
|
||||
|
||||
| Регистр | Значение |
|
||||
|---|---|
|
||||
| `0x1300`, FC03, 8 слов | `0x5747`, версия 1, running, Fs, count, received, ready, capacity |
|
||||
| `0x1302`, FC06 | 0: stop и нулевой выход; 1: start только после commit |
|
||||
| `0x1303`, FC06 | Частота 1…50000; сбрасывает ready |
|
||||
| `0x1304`, FC06 | Начать загрузку, число отсчётов 2…4096; сброс received/ready |
|
||||
| `0xA000+i`, FC06 | Последовательная загрузка отсчёта 0…4095; повтор того же значения допустим |
|
||||
| `0xA000+i`, FC03, 1 слово | Чтение загруженного отсчёта для проверки |
|
||||
| `0x1306`, FC06 | `0xA55A`: commit, только если все отсчёты загружены |
|
||||
|
||||
Во время running разрешены чтение и stop; запись/перезагрузка дают исключение
|
||||
6. Неполная таблица не запускается. Повреждённый CRC и чужой адрес не меняют
|
||||
состояние. При reset ready/running=0, таблица не сохраняется во Flash.
|
||||
Профиль и регистры генератора не являются регистратором УМП или протоколом 2812.
|
||||
|
||||
Хост выполняет probe → stop → Fs → count → samples → readback каждого
|
||||
отсчёта → commit → status. Пуск — отдельная пользовательская команда.
|
||||
После потери USB уже запущенный DAC продолжает автономное воспроизведение;
|
||||
при таймауте состояние неизвестно до повторного status/stop. Одновременно
|
||||
используйте одного клиента, не открывайте этот же COM во вкладке подключения.
|
||||
|
||||
## Потребители и проверки
|
||||
|
||||
- SETGUI: «Логи и графики → Генератор», JSON, CSV и C header.
|
||||
- `home/407vet6_emul_TMS_Periph`: USB CDC и UART, штатный адрес настройки STM,
|
||||
отдельный PA4 DAC выход; HAL/CMSIS и настройки платы остаются в проекте.
|
||||
- G474: переносимый HAL-порт; board init остаётся в целевой прошивке.
|
||||
- `tests/test_signal_wave.c`: C-векторы интерполяции, DAC, CRC, неполной
|
||||
загрузки, readback и блокировки running. Общие байты проверены Python
|
||||
ctypes и фактическим USB stream parser эмулятора.
|
||||
|
||||
Аппаратная проверка амплитуды, периода и формы осциллографом не выполнена.
|
||||
35
c/set-protocol/include/altera_logic.h
Normal file
35
c/set-protocol/include/altera_logic.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/** Altera logic analyzer UART client. C99, caller-owned memory, no OS/heap.
|
||||
* One request at a time; no automatic retries (ARM is not idempotent).
|
||||
* Initialize aligned storage of la_context_size() bytes, then call la_next,
|
||||
* la_feed and la_tick from one thread. tick receives elapsed milliseconds.
|
||||
*/
|
||||
#ifndef ALTERA_LOGIC_H
|
||||
#define ALTERA_LOGIC_H
|
||||
#include "pcan_abi.h"
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
enum { LA_CONNECTING, LA_READY, LA_CAPTURING, LA_DONE, LA_ERROR };
|
||||
enum { LA_OK, LA_BAD_FRAME, LA_CHECKSUM, LA_DEVICE_ERROR, LA_TIMEOUT,
|
||||
LA_UNSUPPORTED, LA_ARGUMENT, LA_IO_ERROR };
|
||||
enum { LA_STATE, LA_ERROR_CODE, LA_CHANNELS, LA_DEPTH, LA_CLOCK_HZ,
|
||||
LA_FLAGS, LA_TRIGGER_INDEX, LA_READ_COUNT, LA_DIVIDER };
|
||||
PCAN_ABI_API size_t la_context_size(void);
|
||||
PCAN_ABI_API void la_init(void *ctx);
|
||||
/** Return 0 on success. Configuration is immutable throughout this capture. */
|
||||
PCAN_ABI_API int la_start(void *ctx, uint32_t divider, uint32_t mask,
|
||||
uint32_t value, uint32_t edge_mask, uint32_t edge_value);
|
||||
/** Returns 6 when a request is ready, otherwise 0. Capacity must be >=6. */
|
||||
PCAN_ABI_API size_t la_next(void *ctx, uint8_t *out, size_t capacity);
|
||||
PCAN_ABI_API void la_feed(void *ctx, const uint8_t *data, size_t size);
|
||||
PCAN_ABI_API void la_tick(void *ctx, uint32_t elapsed_ms);
|
||||
PCAN_ABI_API void la_fail(void *ctx, uint32_t code);
|
||||
PCAN_ABI_API uint32_t la_get(const void *ctx, uint32_t field);
|
||||
PCAN_ABI_API size_t la_samples(const void *ctx, uint16_t *out, size_t capacity);
|
||||
/** Offline fixture, never communicates with hardware or claims a real trigger. */
|
||||
PCAN_ABI_API void la_demo_init(void *ctx);
|
||||
PCAN_ABI_API int la_demo_capture(void *ctx, uint32_t divider);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
46
c/set-protocol/include/altera_stream.h
Normal file
46
c/set-protocol/include/altera_stream.h
Normal file
@@ -0,0 +1,46 @@
|
||||
/** SETCAN/GAS Altera Logic online stream, shared by CAN and UART.
|
||||
* Caller-owned context; one serialized caller. No heap, OS or Qt dependencies.
|
||||
*/
|
||||
#ifndef ALTERA_STREAM_H
|
||||
#define ALTERA_STREAM_H
|
||||
#include "pcan_abi.h"
|
||||
#include "pcan_frame.h"
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#define LAS_DEVICE_TYPE 6U
|
||||
#define LAS_DEVICE_ID 14U
|
||||
#define LAS_GAS_META 0xFE00U
|
||||
#define LAS_GAS_DATA 0xFF00U
|
||||
#define LAS_CAPACITY 8192U
|
||||
#define LAS_TAG 0x4C01U
|
||||
enum { LAS_COUNT, LAS_PERIOD_NS, LAS_SESSION, LAS_RECEIVED, LAS_MISSING,
|
||||
LAS_DUPLICATES, LAS_INVALID, LAS_IGNORED, LAS_CRC_ERRORS, LAS_HAS_META };
|
||||
PCAN_ABI_API size_t las_context_size(void);
|
||||
PCAN_ABI_API int las_init(void *ctx, uint32_t device_id);
|
||||
PCAN_ABI_API void las_can(void *ctx, uint32_t id, const uint8_t *data, size_t size,
|
||||
uint32_t extended, uint32_t remote);
|
||||
PCAN_ABI_API void las_uart(void *ctx, const uint8_t *data, size_t size);
|
||||
PCAN_ABI_API uint32_t las_get(const void *ctx, uint32_t field);
|
||||
/** Chronological bounded history. breaks[i]=1 marks a discontinuity before i. */
|
||||
PCAN_ABI_API size_t las_snapshot(const void *ctx, uint64_t *indices, uint16_t *samples,
|
||||
uint8_t *breaks, size_t capacity);
|
||||
/** Sparse step trace for rendering; capacity >= 2*LAS_COUNT, move=pen-up. */
|
||||
PCAN_ABI_API size_t las_trace(const void *ctx, uint32_t channel, uint64_t *indices,
|
||||
uint8_t *levels, uint8_t *moves, size_t capacity);
|
||||
PCAN_ABI_API uint32_t las_device_type(void);
|
||||
PCAN_ABI_API uint32_t las_device_id(void);
|
||||
PCAN_ABI_API const char *las_device_name(void);
|
||||
PCAN_ABI_API void las_demo_step(void *ctx, uint32_t count);
|
||||
/** Device-side packet builders; return zero for invalid arguments.
|
||||
* uart=0: 8-byte CAN data + *id; uart=1: complete AA55 transport + *id.
|
||||
* Same builders are used by firmware ports, tests and the demo producer.
|
||||
*/
|
||||
PCAN_ABI_API size_t las_metadata(uint32_t device, uint32_t session, uint32_t period_ns,
|
||||
uint32_t uart, uint32_t *id, uint8_t *out, size_t capacity);
|
||||
PCAN_ABI_API size_t las_data(uint32_t device, uint32_t session, uint32_t index,
|
||||
uint32_t sample, uint32_t uart, uint32_t *id, uint8_t *out, size_t capacity);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
26
c/set-protocol/include/set_boot.h
Normal file
26
c/set-protocol/include/set_boot.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#ifndef SET_BOOT_H
|
||||
#define SET_BOOT_H
|
||||
#include "set_firmware.h"
|
||||
|
||||
/* Transport-independent, single-slot updater. invalidate MUST become durable
|
||||
* before erase; commit is called only after read-back verification. */
|
||||
typedef struct {
|
||||
bool (*invalidate)(void *user);
|
||||
bool (*erase)(void *user, uint32_t size);
|
||||
bool (*write)(void *user, uint32_t offset, const uint8_t *data, uint16_t size);
|
||||
bool (*verify)(void *user, uint32_t size, uint32_t crc);
|
||||
bool (*commit)(void *user, uint32_t size, uint32_t crc);
|
||||
void (*activate)(void *user);
|
||||
} setp_boot_ops;
|
||||
typedef struct {
|
||||
setp_boot_ops ops;
|
||||
void *user;
|
||||
uint32_t base, capacity, crc;
|
||||
uint16_t block_size;
|
||||
setp_fw_status_t status;
|
||||
} setp_boot;
|
||||
void setp_boot_init(setp_boot *b, const setp_boot_ops *ops, void *user,
|
||||
uint32_t base, uint32_t capacity);
|
||||
/* Returns SET status; FW_STATUS body is encoded by the caller. */
|
||||
uint16_t setp_boot_command(setp_boot *b, uint16_t type, const uint8_t *p, uint16_t n);
|
||||
#endif
|
||||
32
c/set-protocol/include/set_emu_igbt.h
Normal file
32
c/set-protocol/include/set_emu_igbt.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#ifndef SET_EMU_IGBT_H
|
||||
#define SET_EMU_IGBT_H
|
||||
#include "set_regmap.h"
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#define SET_IGBT_CHANNELS 8u
|
||||
#define SET_IGBT_WORDS 112u
|
||||
#define SET_IGBT_CHANNEL_WORDS 12u
|
||||
typedef struct {
|
||||
uint16_t flags, mode, on_delay, on_width, off_delay, off_width;
|
||||
uint16_t tx_width, edges, fault_width, count;
|
||||
} set_igbt_channel;
|
||||
typedef struct {
|
||||
uint8_t enabled, tx, previous_tx, rx, fault, injected_tx, input_source;
|
||||
uint16_t pending[SET_IGBT_CHANNELS], active[SET_IGBT_CHANNELS];
|
||||
uint16_t delay_ms, width_ms, event_count;
|
||||
uint8_t logical_rx;
|
||||
uint16_t pulse[SET_IGBT_CHANNELS], response_width[SET_IGBT_CHANNELS];
|
||||
set_igbt_channel channel[SET_IGBT_CHANNELS];
|
||||
} set_igbt_emu;
|
||||
PCAN_ABI_API void set_igbt_init(set_igbt_emu *);
|
||||
/* One model tick = 1 ms, no system clock. This coarse logical model does NOT
|
||||
* reproduce the sub-microsecond timing of 1SP/1SD DSLogic hardware profiles.
|
||||
* input_source=0 takes the supplied pins, 1 uses register-injected TX. */
|
||||
PCAN_ABI_API void set_igbt_tick(set_igbt_emu *, uint8_t pins);
|
||||
PCAN_ABI_API unsigned set_igbt_read(void *, uint16_t offset, uint16_t count, uint16_t *);
|
||||
PCAN_ABI_API unsigned set_igbt_write(void *, uint16_t offset, uint16_t value);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
48
c/set-protocol/include/set_emu_server.h
Normal file
48
c/set-protocol/include/set_emu_server.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/* Reference service composition for emulator boards; use set_regmap directly
|
||||
* for a different map, subset, or multiple instances of any module. */
|
||||
#ifndef SET_EMU_SERVER_H
|
||||
#define SET_EMU_SERVER_H
|
||||
#include "set_emu_ump.h"
|
||||
#include "set_emu_tms.h"
|
||||
#include "set_emu_igbt.h"
|
||||
#include "set_wavegen.h"
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#define SET_EMU_CONTROL_BASE 0x1200u
|
||||
#define SET_EMU_IGBT_BASE 0x1400u
|
||||
#define SET_EMU_TMS_BASE 0x1500u
|
||||
#define SET_EMU_INDEXED_BASE 0x2000u
|
||||
enum { SET_EMU_KIND_UMP=1,SET_EMU_KIND_CONTROL=2,SET_EMU_KIND_WAVE=3,
|
||||
SET_EMU_KIND_IGBT=4,SET_EMU_KIND_TMS=5,SET_EMU_KIND_UMP_DATA=6,
|
||||
SET_EMU_KIND_WAVE_DATA=7 };
|
||||
typedef struct {
|
||||
set_regmap map;
|
||||
set_reg_region regions[7];
|
||||
set_ump_emu ump;
|
||||
set_tms_emu tms;
|
||||
set_igbt_emu igbt;
|
||||
set_wave_state *wave;
|
||||
/* config[3]: 0=UMP, 1=TMS2812, 2=IGBT, 3=DAC. Configuration version 3. */
|
||||
uint16_t config[4],staged[4],apply_delay;
|
||||
} set_emu_server;
|
||||
/* bank_capacity in records, each bank has capacity*40 words.
|
||||
* Wave may be NULL. UART rate index 0..4. No pins, heap or global instances.
|
||||
* Init is for a quiescent instance: caller stops hardware before reinitializing. */
|
||||
PCAN_ABI_API unsigned set_emu_server_init(set_emu_server *,uint16_t *bank0,
|
||||
uint16_t *bank1,uint16_t bank_capacity,set_wave_state *,unsigned address,
|
||||
unsigned tms_address,unsigned baud_index,unsigned can_mode,unsigned enabled);
|
||||
PCAN_ABI_API void set_emu_server_tick(set_emu_server *,uint8_t igbt_pins);
|
||||
PCAN_ABI_API uint32_t set_emu_server_baud(const set_emu_server *);
|
||||
PCAN_ABI_API size_t set_emu_server_rtu(set_emu_server *,const uint8_t *,size_t,uint8_t *,size_t);
|
||||
PCAN_ABI_API size_t set_emu_server_can(set_emu_server *,const uint8_t *,size_t,
|
||||
uint16_t *,size_t,unsigned *,unsigned *);
|
||||
/* Stream adapter for FC03/06 (8 octets) and TMS CMD_STD (18 octets). One
|
||||
* context per connection. Transport passes monotonic milliseconds. */
|
||||
typedef struct { uint8_t data[18],used;uint32_t last_byte; } set_emu_stream;
|
||||
PCAN_ABI_API size_t set_emu_stream_feed(set_emu_stream *,set_emu_server *,
|
||||
uint8_t byte,uint32_t now_ms,uint8_t *reply,size_t capacity);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
22
c/set-protocol/include/set_emu_tms.h
Normal file
22
c/set-protocol/include/set_emu_tms.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef SET_EMU_TMS_H
|
||||
#define SET_EMU_TMS_H
|
||||
#include "set_regmap.h"
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#define SET_TMS_WORDS 80u
|
||||
typedef struct {
|
||||
uint8_t controls[6], running;
|
||||
uint16_t commands[10], divider;
|
||||
int32_t frequency, target;
|
||||
} set_tms_emu;
|
||||
PCAN_ABI_API void set_tms_init(set_tms_emu *);
|
||||
PCAN_ABI_API void set_tms_tick(set_tms_emu *);
|
||||
PCAN_ABI_API unsigned set_tms_read(void *,uint16_t,uint16_t,uint16_t *);
|
||||
PCAN_ABI_API unsigned set_tms_write(void *,uint16_t,uint16_t);
|
||||
PCAN_ABI_API size_t set_tms_rtu(set_tms_emu *,unsigned device,const uint8_t *,
|
||||
size_t,uint8_t *,size_t);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
25
c/set-protocol/include/set_emu_ump.h
Normal file
25
c/set-protocol/include/set_emu_ump.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#ifndef SET_EMU_UMP_H
|
||||
#define SET_EMU_UMP_H
|
||||
#include "set_regmap.h"
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#define SET_UMP_WORDS 40u
|
||||
typedef struct {
|
||||
uint16_t (*banks[2])[SET_UMP_WORDS];
|
||||
uint16_t capacity,can_mode;
|
||||
uint8_t enabled;
|
||||
uint16_t state,used,next,bank,archive_bank,archive_count,archive_next,period,divider,cursor,source,generation,post,dropped,prev_command,prev_input,prev_output;
|
||||
uint32_t elapsed,sequence,scenario;
|
||||
} set_ump_emu;
|
||||
/* Two distinct caller-owned arrays, capacity*40 words EACH. No heap/MCU layout. */
|
||||
PCAN_ABI_API unsigned set_ump_init(set_ump_emu *,uint16_t *,uint16_t *,uint16_t capacity,
|
||||
uint16_t can_mode,uint8_t enabled);
|
||||
PCAN_ABI_API void set_ump_tick(set_ump_emu *); /* one millisecond */
|
||||
PCAN_ABI_API unsigned set_ump_read(void *,uint16_t,uint16_t,uint16_t *);
|
||||
PCAN_ABI_API unsigned set_ump_indexed_read(void *,uint16_t,uint16_t,uint16_t *);
|
||||
PCAN_ABI_API unsigned set_ump_write(void *,uint16_t,uint16_t);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
48
c/set-protocol/include/set_regmap.h
Normal file
48
c/set-protocol/include/set_regmap.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/* Transport-independent 16-bit word address space. No heap, clocks or board headers. */
|
||||
#ifndef SET_REGMAP_H
|
||||
#define SET_REGMAP_H
|
||||
#include "pcan_abi.h"
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#define SET_REG_MAX_WORDS 124u
|
||||
#define SET_REG_READ 1u
|
||||
#define SET_REG_WRITE 2u
|
||||
enum { SET_REG_OK=0, SET_REG_FUNCTION=1, SET_REG_ADDRESS=2,
|
||||
SET_REG_VALUE=3, SET_REG_FAILURE=4, SET_REG_BUSY=6 };
|
||||
typedef unsigned (*set_reg_read_fn)(void *, uint16_t, uint16_t, uint16_t *);
|
||||
typedef unsigned (*set_reg_write_fn)(void *, uint16_t, uint16_t);
|
||||
typedef struct {
|
||||
uint16_t base, words, kind, version, instance, flags;
|
||||
void *context;
|
||||
set_reg_read_fn read;
|
||||
set_reg_write_fn write;
|
||||
} set_reg_region;
|
||||
typedef struct {
|
||||
const set_reg_region *regions;
|
||||
size_t count;
|
||||
} set_regmap;
|
||||
/* Directory at 0: 8-word header, then 8 words per region. Reject overlaps,
|
||||
* wraparound, invalid callbacks and overlap with the directory itself.
|
||||
* Regions and contexts must outlive the map and remain unchanged. */
|
||||
PCAN_ABI_API unsigned set_regmap_init(set_regmap *, const set_reg_region *, size_t);
|
||||
/* One region per request. Preflight all bounds/capacity before calling a module.
|
||||
* Writes return absolute address/value echo. Reads must be side-effect free.
|
||||
* A module validates the entire write before mutation. Caller serializes access. */
|
||||
PCAN_ABI_API unsigned set_regmap_request(const set_regmap *, unsigned function,
|
||||
uint16_t address, uint16_t value, uint16_t *out, size_t capacity, size_t *count);
|
||||
/* Complete Modbus RTU FC03/06 frame; no broadcast. Capacity >=253 bytes.
|
||||
* CRC/address/capacity validation precedes dispatch. */
|
||||
PCAN_ABI_API size_t set_regmap_rtu(const set_regmap *, unsigned device,
|
||||
const uint8_t *, size_t, uint8_t *, size_t);
|
||||
/* Legacy UMP CAN envelope, usable by any region: BE address/value/1F00/token/fn.
|
||||
* Returns padded EVEN words, last padding word=0. Caller retains original
|
||||
* requested count; token/error accompany fragments. Output capacity >=124. */
|
||||
PCAN_ABI_API size_t set_regmap_can(const set_regmap *, const uint8_t *, size_t,
|
||||
uint16_t *, size_t, unsigned *token, unsigned *error);
|
||||
PCAN_ABI_API size_t set_regmap_can_fragment(const uint16_t *, size_t count,
|
||||
size_t index, unsigned token, unsigned error, uint8_t *, size_t capacity);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
59
c/set-protocol/include/set_wavegen.h
Normal file
59
c/set-protocol/include/set_wavegen.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/** @file set_wavegen.h Transactional table upload and cyclic DAC playback. */
|
||||
#ifndef SET_WAVEGEN_H
|
||||
#define SET_WAVEGEN_H
|
||||
#include "pcan_abi.h"
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#define SET_WAVE_MAX 4096u
|
||||
#define SET_WAVE_MAX_RATE 1000000u
|
||||
#define SET_WAVE_BLOCK_MAX 120u
|
||||
#define SET_WAVE_BASE 0x1300u
|
||||
#define SET_WAVE_DATA 0xA000u
|
||||
typedef struct {
|
||||
void *context;
|
||||
/* Return 0 on success. Samples remain owned by state until stop returns.
|
||||
* start must arrange periodic output, typically DAC + timer + DMA.
|
||||
* stop must quiesce DMA before returning and set output to zero. */
|
||||
int (*start)(void *, const uint16_t *, size_t, uint32_t);
|
||||
void (*stop)(void *);
|
||||
} set_wave_port;
|
||||
typedef struct {
|
||||
uint16_t *samples;
|
||||
size_t capacity;
|
||||
uint16_t count, received, ready, running, rate_high;
|
||||
uint32_t rate;
|
||||
set_wave_port port;
|
||||
} set_wave_state;
|
||||
void set_wave_init(set_wave_state *, uint16_t *, size_t, const set_wave_port *);
|
||||
/* Atomic sample-block transfer. values=NULL builds a read; otherwise a write.
|
||||
* At most 120 words: request <=249 bytes, response <=245 bytes. */
|
||||
PCAN_ABI_API size_t set_wave_block_request(unsigned device,unsigned index,
|
||||
const uint16_t *values,size_t count,uint8_t *out,size_t capacity);
|
||||
PCAN_ABI_API int set_wave_block_response(const uint8_t *request,size_t request_size,
|
||||
const uint8_t *reply,size_t reply_size,uint16_t *words,size_t capacity);
|
||||
size_t set_wave_block_rtu(set_wave_state *,unsigned device,const uint8_t *,size_t,
|
||||
uint8_t *,size_t);
|
||||
/* Relative register callbacks for set_regmap; status=8 words, samples=4096.
|
||||
* These are the same state/validation used by the legacy RTU wrapper. */
|
||||
PCAN_ABI_API unsigned set_wave_read(void *,uint16_t,uint16_t,uint16_t *);
|
||||
PCAN_ABI_API unsigned set_wave_write(void *,uint16_t,uint16_t);
|
||||
PCAN_ABI_API unsigned set_wave_samples_read(void *,uint16_t,uint16_t,uint16_t *);
|
||||
PCAN_ABI_API unsigned set_wave_samples_write(void *,uint16_t,uint16_t);
|
||||
/* Caller serializes calls in the main loop. Returns complete reply size,
|
||||
* 0 for unrelated/invalid frames. CRC verified before all state changes. */
|
||||
size_t set_wave_rtu(set_wave_state *, unsigned device, const uint8_t *, size_t,
|
||||
uint8_t *, size_t);
|
||||
/* Thin host transport ABI. Operations: 0=status, 1=stop, 2=sample rate,
|
||||
* 3=begin upload(count), 4=sample(index,value), 5=commit, 6=start.
|
||||
* 7=read a sample(index) for verification. Return 8 or 0. */
|
||||
PCAN_ABI_API size_t set_wave_request(unsigned device,unsigned operation,unsigned index,
|
||||
unsigned value,uint8_t *out,size_t capacity);
|
||||
/* Return 0=need more bytes, positive=word count, negative=invalid/exception.
|
||||
* Validates exact echo/read shape, CRC, address and signature on status. */
|
||||
PCAN_ABI_API int set_wave_response(const uint8_t *request,const uint8_t *reply,
|
||||
size_t size,uint16_t *words,size_t capacity);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -4,6 +4,8 @@
|
||||
*/
|
||||
#ifndef SETPROTOCOL_H
|
||||
#define SETPROTOCOL_H
|
||||
#include "set_regmap.h"
|
||||
#include "set_emu_server.h"
|
||||
|
||||
#include "balsam_can.h"
|
||||
#include "periph28335.h"
|
||||
|
||||
55
c/set-protocol/include/ump_protocol.h
Normal file
55
c/set-protocol/include/ump_protocol.h
Normal file
@@ -0,0 +1,55 @@
|
||||
/* UMP logger v2 (PM35), independent of PM67 and SET protocol v2.
|
||||
* Octets use 16-bit storage: C28x has CHAR_BIT=16 and no uint8_t.
|
||||
* All lengths below count wire octets or register words, never sizeof bytes.
|
||||
*/
|
||||
#ifndef UMP_PROTOCOL_H
|
||||
#define UMP_PROTOCOL_H
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#define UMP_BASE 0x1000U
|
||||
#define UMP_DATA 0x1010U
|
||||
#define UMP_LIVE 0x1100U
|
||||
#define UMP_MARKER 0x1F00U
|
||||
#define UMP_MAX_WORDS 124U
|
||||
#define UMP_RTU_CAPACITY 253U
|
||||
typedef uint16_t ump_octet;
|
||||
/* Called under caller's snapshot lock. write returns 1 on success, 0 otherwise. */
|
||||
typedef struct {
|
||||
void *context;
|
||||
uint16_t (*read)(void *context, uint16_t address);
|
||||
int (*write)(void *context, uint16_t address, uint16_t value);
|
||||
} ump_backend;
|
||||
/* Returns Modbus exception 0/1/2/3. CAN has stricter whole-block reads. */
|
||||
unsigned ump_validate(unsigned function, uint16_t address, uint16_t value, int can);
|
||||
/* reply capacity is >=124 words. Errors and writes return address/value echo. */
|
||||
unsigned ump_prepare(const ump_backend *backend, unsigned function,
|
||||
uint16_t address, uint16_t value, int can, uint16_t *reply, unsigned *error);
|
||||
uint16_t ump_crc(const ump_octet *data, size_t count);
|
||||
/* Mode 1..16; kind 0=request, 1=normal telemetry, 2=logger reply; invalid ->0. */
|
||||
uint32_t ump_can_id(unsigned mode, unsigned kind);
|
||||
/* Returns 0 for non-service frames, malformed size, or zero token. */
|
||||
unsigned ump_can_request(const ump_backend *backend, const ump_octet *request,
|
||||
size_t size, uint16_t *reply, unsigned *token, unsigned *error);
|
||||
/* count must be an even number 2..124; index counts pairs. Returns 8 or 0. */
|
||||
unsigned ump_can_fragment(const uint16_t *words, unsigned count, unsigned index,
|
||||
unsigned token, unsigned error, ump_octet *reply);
|
||||
/* Complete RTU frame only, after UART framing. Ignores bad CRC/foreign address.
|
||||
* Caller provides >=253 octets; no writes occur on insufficient capacity.
|
||||
* Device address 1..247; no broadcasts or legacy padding in this extension.
|
||||
*/
|
||||
size_t ump_rtu(const ump_backend *backend, unsigned device,
|
||||
const ump_octet *request, size_t size, ump_octet *reply, size_t capacity);
|
||||
/* Response for an already CRC-checked/framed request (legacy UART receiver). */
|
||||
size_t ump_rtu_response(const ump_backend *backend, unsigned device, unsigned function,
|
||||
uint16_t address, uint16_t value, ump_octet *reply, size_t capacity);
|
||||
/* Serialize a captured reply after releasing the snapshot lock. Returns 0 on
|
||||
* invalid count/capacity; error replies need no words. */
|
||||
size_t ump_rtu_encode(unsigned device, unsigned function, unsigned error,
|
||||
const uint16_t *words, unsigned count, ump_octet *reply, size_t capacity);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
41
c/set-protocol/ports/example-emulator/service.c
Normal file
41
c/set-protocol/ports/example-emulator/service.c
Normal file
@@ -0,0 +1,41 @@
|
||||
/* Minimal board-independent composition. This compiles without any MCU headers.
|
||||
* Adapt these entry points to the selected board's network/USB/UART/CAN port.
|
||||
* Run every call in one task (or protect the entire call with the same lock). */
|
||||
#include "set_emu_igbt.h"
|
||||
static set_igbt_emu drivers[2];
|
||||
static set_reg_region regions[2];
|
||||
static set_regmap map;
|
||||
unsigned example_service_init(void)
|
||||
{
|
||||
unsigned i;
|
||||
for(i=0;i<2;++i) {
|
||||
set_reg_region *r=®ions[i];
|
||||
set_igbt_init(&drivers[i]);
|
||||
r->base=(uint16_t)(0x300+i*0x100);r->words=SET_IGBT_WORDS;
|
||||
r->kind=4;r->version=2;r->instance=(uint16_t)i;r->flags=3;
|
||||
r->context=&drivers[i];r->read=set_igbt_read;r->write=set_igbt_write;
|
||||
}
|
||||
return set_regmap_init(&map,regions,2);
|
||||
}
|
||||
void example_service_tick(uint8_t inputs0,uint8_t inputs1,uint8_t outputs[2])
|
||||
{
|
||||
set_igbt_tick(&drivers[0],inputs0);set_igbt_tick(&drivers[1],inputs1);
|
||||
outputs[0]=drivers[0].rx;outputs[1]=drivers[1].rx;
|
||||
}
|
||||
unsigned example_custom_protocol(unsigned function,uint16_t address,uint16_t value,
|
||||
uint16_t *words,size_t capacity,size_t *count)
|
||||
{
|
||||
/* Caller has already decoded/authenticated its own transport envelope. */
|
||||
return set_regmap_request(&map,function,address,value,words,capacity,count);
|
||||
}
|
||||
size_t example_uart_or_usb(const uint8_t *frame,size_t length,uint8_t *reply,size_t capacity)
|
||||
{
|
||||
return set_regmap_rtu(&map,16,frame,length,reply,capacity);
|
||||
}
|
||||
size_t example_can(const uint8_t frame[8],uint16_t *snapshot,size_t capacity,
|
||||
unsigned *token,unsigned *error)
|
||||
{
|
||||
/* Select the CAN request ID outside the model; send the returned snapshot
|
||||
* using set_regmap_can_fragment and the board's chosen response CAN ID. */
|
||||
return set_regmap_can(&map,frame,8,snapshot,capacity,token,error);
|
||||
}
|
||||
54
c/set-protocol/ports/stm32-usb-boot/boot_usb_protocol.inc
Normal file
54
c/set-protocol/ports/stm32-usb-boot/boot_usb_protocol.inc
Normal file
@@ -0,0 +1,54 @@
|
||||
/* Shared SET USB boot dispatcher. The MCU wrapper supplies BOOT_* macros. */
|
||||
#include "usb_setp_stream.h"
|
||||
#include <string.h>
|
||||
static setp_boot boot;
|
||||
static bool reset_pending;
|
||||
static uint32_t reset_at;
|
||||
typedef struct { uint8_t *out; uint16_t size; uint32_t now; } reply_context;
|
||||
void boot_protocol_init(void) { setp_boot_init(&boot,&BOOT_OPS,0,BOOT_APP_BASE,BOOT_APP_SIZE); }
|
||||
void boot_protocol_poll(uint32_t now) {
|
||||
if(reset_pending && (int32_t)(now-reset_at)>=0) BOOT_RESET();
|
||||
}
|
||||
static void dispatch(const setp_frame_t *r,void *user) {
|
||||
reply_context *c=user;
|
||||
uint8_t body[128]; uint16_t status=SETP_STATUS_OK; size_t n=0;
|
||||
setp_frame_t response={0};
|
||||
if((r->destination!=0 && r->destination!=BOOT_NODE) ||
|
||||
(r->flags&(SETP_FLAG_RESPONSE|SETP_FLAG_EVENT))) return;
|
||||
if(r->flags&(SETP_FLAG_ERROR|SETP_FLAG_MORE)) status=SETP_STATUS_INVALID_ARGUMENT;
|
||||
else if(r->message_type>=SETP_MSG_FW_BEGIN && r->message_type<=SETP_MSG_FW_ACTIVATE) {
|
||||
status=setp_boot_command(&boot,r->message_type,r->payload,r->payload_length);
|
||||
if(!status && r->message_type==SETP_MSG_FW_STATUS)
|
||||
n=setp_fw_status_encode(&boot.status,body+2,sizeof(body)-2);
|
||||
if(!status && r->message_type==SETP_MSG_FW_ACTIVATE) {
|
||||
reset_pending=true; reset_at=c->now+500;
|
||||
}
|
||||
} else if(r->payload_length) status=SETP_STATUS_INVALID_LENGTH;
|
||||
else switch(r->message_type) {
|
||||
case SETP_MSG_PING: setp_put_u32(body+2,c->now); n=4; break;
|
||||
case SETP_MSG_DEVICE_INFO: {
|
||||
static const uint8_t model[]=BOOT_MODEL;
|
||||
const setp_device_info_t info={1,0,0,0x10000,3,0,sizeof(model)-1,model};
|
||||
n=setp_device_info_encode(&info,body+2,sizeof(body)-2); break;
|
||||
}
|
||||
case SETP_MSG_CAPABILITIES: {
|
||||
const setp_capabilities_t caps={1,SETP_MAX_PAYLOAD,
|
||||
SETP_IFACE_MASK(SETP_IFACE_USB_CDC),SETP_FEATURE_FIRMWARE,0,0,0,0};
|
||||
n=setp_capabilities_encode(&caps,body+2,sizeof(body)-2); break;
|
||||
}
|
||||
default: status=SETP_STATUS_UNSUPPORTED; break;
|
||||
}
|
||||
setp_put_u16(body,status); if(status) n=0;
|
||||
response.flags=SETP_FLAG_RESPONSE|(status ? SETP_FLAG_ERROR : 0);
|
||||
response.message_type=r->message_type; response.sequence=r->sequence;
|
||||
response.source=BOOT_NODE; response.destination=r->source;
|
||||
response.payload=body; response.payload_length=(uint16_t)(n+2);
|
||||
c->size=(uint16_t)setp_frame_encode(&response,c->out,SETP_FRAME_MAX);
|
||||
}
|
||||
void usb_setp_reset(usb_setp_stream *s) { memset(s,0,sizeof(*s)); }
|
||||
uint16_t usb_setp_feed(usb_setp_stream *s,uint8_t byte,uint32_t now,uint8_t *out) {
|
||||
reply_context c={out,0,now};
|
||||
if(now-s->last_byte>1000) setp_parser_reset(&s->parser);
|
||||
s->last_byte=now; (void)setp_parser_feed(&s->parser,&byte,1,dispatch,&c);
|
||||
return c.size;
|
||||
}
|
||||
13
c/set-protocol/ports/stm32f407-periph/README.md
Normal file
13
c/set-protocol/ports/stm32f407-periph/README.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Порт STM32F407 / эмулятор периферии
|
||||
|
||||
Добавьте `src/ump_protocol.c` и `ump_stm32f407.c` в сборку.
|
||||
Порт получает backend с read/write и переводит 8-битные буферы STM32 в
|
||||
октеты ядра. Приём UART, CRC framing, CAN ID/DLC/RTR-фильтры и очередь
|
||||
передачи находятся в проекте; ядро проверяет CRC полного RTU кадра.
|
||||
Вызывать из главного цикла. В эмуляторе SysTick лишь накапливает тики,
|
||||
поэтому read/write и генератор не выполняются одновременно.
|
||||
Ответ RTU требует 253 байта; CAN snapshot — 124 uint16_t. Не передавайте
|
||||
неполный snapshot в fragment, count должен быть фактическим числом слов.
|
||||
Подключено в `home/407vet6_emul_TMS_Periph/Src/shared_ump.c`.
|
||||
|
||||
[Контракт, пример и проверки](../../docs/UMP_PROTOCOL.md).
|
||||
30
c/set-protocol/ports/stm32f407-periph/ump_stm32f407.c
Normal file
30
c/set-protocol/ports/stm32f407-periph/ump_stm32f407.c
Normal file
@@ -0,0 +1,30 @@
|
||||
#include "ump_stm32f407.h"
|
||||
size_t ump_stm32f407_rtu(const ump_backend *b, unsigned device,
|
||||
const uint8_t *request, size_t length, uint8_t *reply, size_t capacity)
|
||||
{
|
||||
ump_octet r[8],out[UMP_RTU_CAPACITY];
|
||||
size_t i,n;
|
||||
if (!request || !reply || length!=8 || capacity<UMP_RTU_CAPACITY) return 0;
|
||||
for (i=0;i<8;i++) r[i]=request[i];
|
||||
n=ump_rtu(b,device,r,8,out,UMP_RTU_CAPACITY);
|
||||
for (i=0;i<n;i++) reply[i]=(uint8_t)out[i];
|
||||
return n;
|
||||
}
|
||||
unsigned ump_stm32f407_can(const ump_backend *b, const uint8_t *request,
|
||||
size_t length, uint16_t *words, unsigned *token, unsigned *error)
|
||||
{
|
||||
ump_octet r[8];
|
||||
unsigned i;
|
||||
if (!request || length!=8) return 0;
|
||||
for (i=0;i<8;i++) r[i]=request[i];
|
||||
return ump_can_request(b,r,8,words,token,error);
|
||||
}
|
||||
unsigned ump_stm32f407_fragment(const uint16_t *words, unsigned count,
|
||||
unsigned index, unsigned token, unsigned error, uint8_t reply[8])
|
||||
{
|
||||
ump_octet out[8];
|
||||
unsigned i;
|
||||
if (!reply || !ump_can_fragment(words,count,index,token,error,out)) return 0;
|
||||
for (i=0;i<8;i++) reply[i]=(uint8_t)out[i];
|
||||
return 8;
|
||||
}
|
||||
11
c/set-protocol/ports/stm32f407-periph/ump_stm32f407.h
Normal file
11
c/set-protocol/ports/stm32f407-periph/ump_stm32f407.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#ifndef UMP_STM32F407_H
|
||||
#define UMP_STM32F407_H
|
||||
#include "../../include/ump_protocol.h"
|
||||
/* Application owns framing, ID filtering, snapshot lock, and tx buffers. */
|
||||
size_t ump_stm32f407_rtu(const ump_backend *backend, unsigned device,
|
||||
const uint8_t *request, size_t length, uint8_t *reply, size_t capacity);
|
||||
unsigned ump_stm32f407_can(const ump_backend *backend, const uint8_t *request,
|
||||
size_t length, uint16_t *words, unsigned *token, unsigned *error);
|
||||
unsigned ump_stm32f407_fragment(const uint16_t *words, unsigned count,
|
||||
unsigned index, unsigned token, unsigned error, uint8_t reply[8]);
|
||||
#endif
|
||||
68
c/set-protocol/ports/stm32f407-usb-boot/README.md
Normal file
68
c/set-protocol/ports/stm32f407-usb-boot/README.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# STM32F407VET6 USB boot port
|
||||
|
||||
SET v2 single-slot updater: `include/set_boot.h`, `src/set_boot.c` and the existing
|
||||
`set_firmware` codec. Byte transport is supplied by the application, independently
|
||||
of the Flash callbacks. The emulator project supplies the ST USB CDC stream.
|
||||
|
||||
## Integration in another F407 project
|
||||
|
||||
1. Copy `boot_config.template.h` to the project's include directory as
|
||||
`boot_config.h`: set crystal frequency, SET node and product model. Match
|
||||
the crystal frequency with HAL `HSE_VALUE`.
|
||||
2. Compile `src/set_protocol.c`, `src/set_firmware.c`, `src/set_boot.c` and this
|
||||
port's `boot_main.c`, `boot_flash.c`, `boot_request.c`, `boot_usb_stream.c`.
|
||||
3. Provide STM32F4 CMSIS/HAL, startup, the ST USB device CDC middleware and the
|
||||
board USB transport (`usb_cdc.h`, `usb_setp_stream.h` and their implementation).
|
||||
The transport calls `usb_setp_feed`/`usb_setp_reset` from this port instead of
|
||||
an application command dispatcher. Enable SET v2 for the bootloader build.
|
||||
4. Link boot code below `0x0800C000`; link the application at `0x08010000`.
|
||||
Add `boot_request.c` to the application and periodically call
|
||||
`f407_reset_poll`; its update command calls `f407_request_boot`.
|
||||
|
||||
The emulator's `Bootloader/Src/*.c` files are include-only build wrappers. Neither
|
||||
the portable updater nor this port depends on the emulator models or its
|
||||
`board_config.h`. The USB transport and HAL configuration remain board supplied.
|
||||
|
||||
Flash layout (512 KiB device): boot sectors 0..2 at `0x08000000` (48 KiB),
|
||||
commit record in sector 3 at `0x0800C000`, application sectors 4..7 at
|
||||
`0x08010000` (448 KiB). All addresses and sector operations are in this port.
|
||||
The sector programming/jump approach is adapted from climate's
|
||||
`climate_control_f407vet6_f4/Bootloader/bootloader_f407.c`.
|
||||
|
||||
BEGIN requires slot 0, base `0x08010000`, size 8..458752, block size 1..256,
|
||||
ERASE_SLOT flag only, zero SHA/key/signature. DATA must be sequential; its CRC,
|
||||
length and bounds are checked. END requires complete receipt and matching size
|
||||
and CRC. The port verifies the actual Flash CRC and vector table before writing
|
||||
the commit marker last. SHA, signatures, automatic activation and resume are
|
||||
not supported and are rejected explicitly. CRC is an integrity check.
|
||||
|
||||
STATUS returns the standard 16-byte SET firmware status. ACTIVATE accepts an
|
||||
empty payload after END; the transport adapter defers reset to let the USB
|
||||
reply finish. ABORT clears the RAM session; it does not restore erased firmware.
|
||||
On reset, SP and reset PC are checked like in climate. USB metadata is not
|
||||
required, so Keil/SWD installs and alternating SWD/USB updates work even with
|
||||
absent or stale USB metadata. Preserve the bootloader using sector erase rather
|
||||
than full-chip erase. Reflash the updated bootloader once to adopt this behavior.
|
||||
USB END/ACTIVATE still require complete receipt and CRC verification.
|
||||
|
||||
Like climate, the reset vector check cannot detect incomplete images once valid
|
||||
vectors are written: after an interrupted update and reset such an image may run.
|
||||
To recover an unresponsive app, erase sector 4 at `0x08010000` via SWD, reset, and
|
||||
upload the full image again. Erasing metadata sector 3 no longer forces bootloader
|
||||
entry. An interrupted update requires a fresh BEGIN and full retransmission.
|
||||
`test_boot_image.c` is a host-only regression test; do not add it to firmware targets.
|
||||
|
||||
`boot_request.c` is shared by the application and bootloader. Product command
|
||||
`0x1003` (empty payload) in the application sets RTC backup register 0 and
|
||||
schedules reset after 500 ms. The bootloader consumes that flag before deciding
|
||||
whether to jump. Reserve BKP0R for this port. The jump must run early after reset,
|
||||
before enabling peripheral interrupts, USB or DMA. Both images must set VTOR
|
||||
to their own link address in SystemInit.
|
||||
|
||||
Board: PA11 D-/PA12 D+, existing self-powered OTG_FS CDC port, 25 MHz HSE;
|
||||
no VBUS host drive, PA9/PA10 are not used. Flash erase uses x32 at nominal 3.3 V,
|
||||
programming uses bytes to support unaligned protocol blocks. Hardware acceptance
|
||||
must cover disconnect/power loss during erase, programming and commit.
|
||||
|
||||
The SET USB command dispatcher is shared with G474 in
|
||||
`../stm32-usb-boot/boot_usb_protocol.inc`; keep that directory when importing this port.
|
||||
@@ -0,0 +1,9 @@
|
||||
#ifndef F407_BOOT_CONFIG_H
|
||||
#define F407_BOOT_CONFIG_H
|
||||
/* Copy to the board include directory as boot_config.h.
|
||||
* HSE must be an integer MHz frequency; PLL input is 1 MHz.
|
||||
* The Flash layout in boot_port.h targets the 512 KiB STM32F407VE. */
|
||||
#define F407_BOOT_HSE_HZ 25000000UL
|
||||
#define F407_BOOT_NODE 16U
|
||||
#define F407_BOOT_MODEL "STM32F407-USB-BOOT"
|
||||
#endif
|
||||
88
c/set-protocol/ports/stm32f407-usb-boot/boot_flash.c
Normal file
88
c/set-protocol/ports/stm32f407-usb-boot/boot_flash.c
Normal file
@@ -0,0 +1,88 @@
|
||||
/* Flash geometry and jump sequence derived from climate's F407 bootloader.
|
||||
* Sector 3 is a durable commit record; sectors 4..7 hold the application.
|
||||
* Reset checks vectors like climate; USB END always verifies Flash. */
|
||||
#include "boot_port.h"
|
||||
#include "boot_image.h"
|
||||
#include "stm32f4xx_hal.h"
|
||||
#include <string.h>
|
||||
#define META 0x0800C000UL
|
||||
#define COMMIT 0x53455442UL
|
||||
#define ERRORS (FLASH_SR_PGSERR|FLASH_SR_PGPERR|FLASH_SR_PGAERR|FLASH_SR_WRPERR|FLASH_SR_SOP)
|
||||
static void unlock(void) {
|
||||
if(FLASH->CR & FLASH_CR_LOCK) { FLASH->KEYR=0x45670123; FLASH->KEYR=0xCDEF89AB; }
|
||||
FLASH->SR=ERRORS;
|
||||
}
|
||||
static bool finish(void) {
|
||||
while(FLASH->SR & FLASH_SR_BSY) {}
|
||||
return !(FLASH->SR & ERRORS);
|
||||
}
|
||||
static void cache_reset(void) {
|
||||
FLASH->ACR &= ~(FLASH_ACR_ICEN|FLASH_ACR_DCEN);
|
||||
FLASH->ACR |= FLASH_ACR_ICRST|FLASH_ACR_DCRST;
|
||||
FLASH->ACR &= ~(FLASH_ACR_ICRST|FLASH_ACR_DCRST);
|
||||
FLASH->ACR |= FLASH_ACR_ICEN|FLASH_ACR_DCEN;
|
||||
__DSB(); __ISB();
|
||||
}
|
||||
static bool erase_sector(unsigned sector) {
|
||||
bool ok; unlock();
|
||||
FLASH->CR=(FLASH->CR & ~(FLASH_CR_SNB|FLASH_CR_PSIZE)) |
|
||||
FLASH_CR_SER | (2UL<<FLASH_CR_PSIZE_Pos) | (sector<<FLASH_CR_SNB_Pos);
|
||||
FLASH->CR|=FLASH_CR_STRT; ok=finish();
|
||||
FLASH->CR &= ~FLASH_CR_SER; FLASH->CR |= FLASH_CR_LOCK;
|
||||
cache_reset(); return ok;
|
||||
}
|
||||
static bool program(uint32_t address, const uint8_t *p, uint32_t n) {
|
||||
bool ok=true; unlock();
|
||||
/* Byte programming supports arbitrary SET block boundaries and final tails. */
|
||||
FLASH->CR=(FLASH->CR & ~FLASH_CR_PSIZE)|FLASH_CR_PG;
|
||||
while(n--) {
|
||||
*(volatile uint8_t *)address=*p;
|
||||
if(!finish()) { ok=false; break; }
|
||||
++address; ++p;
|
||||
}
|
||||
FLASH->CR &= ~FLASH_CR_PG; FLASH->CR |= FLASH_CR_LOCK;
|
||||
cache_reset(); return ok;
|
||||
}
|
||||
static bool invalidate(void *u) { (void)u; return erase_sector(3); }
|
||||
static bool erase(void *u, uint32_t size) {
|
||||
unsigned s; (void)u;
|
||||
if(size<8 || size>F407_APP_SIZE) return false;
|
||||
for(s=4;s<=7;++s) {
|
||||
uint32_t start=s==4 ? F407_APP_BASE : 0x08020000UL+(s-5)*0x20000UL;
|
||||
if(start>=F407_APP_BASE+size) break;
|
||||
if(!erase_sector(s)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static bool write(void *u,uint32_t off,const uint8_t *p,uint16_t n) {
|
||||
(void)u;
|
||||
if(!n || off>F407_APP_SIZE || n>F407_APP_SIZE-off) return false;
|
||||
return program(F407_APP_BASE+off,p,n) && !memcmp((const void *)(F407_APP_BASE+off),p,n);
|
||||
}
|
||||
static bool verify(void *u,uint32_t size,uint32_t crc) {
|
||||
(void)u;
|
||||
return f407_vectors_valid((const uint8_t *)F407_APP_BASE,size) &&
|
||||
setp_crc32((const uint8_t *)F407_APP_BASE,size)==crc;
|
||||
}
|
||||
static bool commit(void *u,uint32_t size,uint32_t crc) {
|
||||
uint32_t record[4]={size,crc,~size,COMMIT}; (void)u;
|
||||
return program(META,(const uint8_t *)record,sizeof(record)) &&
|
||||
!memcmp((const void *)META,record,sizeof(record));
|
||||
}
|
||||
bool f407_app_valid(void) {
|
||||
/* Like climate: direct Keil/SWD writes need no USB completion record. */
|
||||
return f407_vectors_valid((const uint8_t *)F407_APP_BASE,F407_APP_SIZE);
|
||||
}
|
||||
static void activate(void *u) { (void)u; /* main waits for USB response before reset */ }
|
||||
const setp_boot_ops f407_boot_ops={invalidate,erase,write,verify,commit,activate};
|
||||
__attribute__((naked,noreturn)) static void enter(uint32_t sp,uint32_t pc) {
|
||||
__asm volatile("msr msp, r0\nmovs r2, #0\nmsr control, r2\ndsb\nisb\ncpsie i\nbx r1\n");
|
||||
}
|
||||
void f407_jump_app(void) {
|
||||
unsigned i; uint32_t sp=*(const uint32_t *)F407_APP_BASE;
|
||||
uint32_t pc=*(const uint32_t *)(F407_APP_BASE+4);
|
||||
__disable_irq(); SysTick->CTRL=0;
|
||||
for(i=0;i<8;++i) { NVIC->ICER[i]=0xffffffff; NVIC->ICPR[i]=0xffffffff; }
|
||||
SCB->ICSR=SCB_ICSR_PENDSTCLR_Msk|SCB_ICSR_PENDSVCLR_Msk;
|
||||
SCB->VTOR=F407_APP_BASE; enter(sp,pc);
|
||||
}
|
||||
19
c/set-protocol/ports/stm32f407-usb-boot/boot_image.h
Normal file
19
c/set-protocol/ports/stm32f407-usb-boot/boot_image.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef F407_BOOT_IMAGE_H
|
||||
#define F407_BOOT_IMAGE_H
|
||||
#include "boot_port.h"
|
||||
#include <string.h>
|
||||
|
||||
static inline bool f407_vectors_valid(const uint8_t *image, uint32_t size)
|
||||
{
|
||||
uint32_t sp, pc;
|
||||
if (size < 8 || size > F407_APP_SIZE) return false;
|
||||
memcpy(&sp, image, 4);
|
||||
memcpy(&pc, image + 4, 4);
|
||||
return !(sp & 7) &&
|
||||
((sp > 0x20000000UL && sp <= 0x20020000UL) ||
|
||||
(sp > 0x10000000UL && sp <= 0x10010000UL)) &&
|
||||
(pc & 1) && (pc & ~1UL) >= F407_APP_BASE &&
|
||||
(pc & ~1UL) < F407_APP_BASE + size;
|
||||
}
|
||||
|
||||
#endif
|
||||
63
c/set-protocol/ports/stm32f407-usb-boot/boot_main.c
Normal file
63
c/set-protocol/ports/stm32f407-usb-boot/boot_main.c
Normal file
@@ -0,0 +1,63 @@
|
||||
#include "stm32f4xx_hal.h"
|
||||
#include "boot_config.h"
|
||||
#include "usb_cdc.h"
|
||||
#include "boot_port.h"
|
||||
#if F407_BOOT_HSE_HZ != HSE_VALUE
|
||||
#error boot_config.h and HAL HSE_VALUE must describe the same crystal
|
||||
#endif
|
||||
#if (F407_BOOT_HSE_HZ % 1000000UL) || F407_BOOT_HSE_HZ < 2000000UL || F407_BOOT_HSE_HZ > 50000000UL
|
||||
#error HSE must provide a valid integer PLLM divider (2..50 MHz)
|
||||
#endif
|
||||
uint32_t SystemCoreClock=16000000;
|
||||
const uint8_t AHBPrescTable[16]={0,0,0,0,0,0,0,0,1,2,3,4,6,7,8,9};
|
||||
const uint8_t APBPrescTable[8]={0,0,0,0,1,2,3,4};
|
||||
static uint8_t usb_clock_ready;
|
||||
static volatile uint32_t clock_fault;
|
||||
void SystemInit(void) { SCB->CPACR|=0xFu<<20; SCB->VTOR=0x08000000; }
|
||||
void SysTick_Handler(void) { HAL_IncTick(); }
|
||||
static void board_fatal(uint32_t code) { clock_fault=code; for(;;) {} }
|
||||
static void peripheral_clock(void)
|
||||
{
|
||||
RCC_OscInitTypeDef osc={0};
|
||||
RCC_ClkInitTypeDef clk={0};
|
||||
/* Разрешаем доступ к PWR и выбираем диапазон питания ядра перед повышением частоты. */
|
||||
__HAL_RCC_PWR_CLK_ENABLE();
|
||||
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
|
||||
osc.OscillatorType=RCC_OSCILLATORTYPE_HSE;
|
||||
osc.HSEState=RCC_HSE_ON;
|
||||
/* Готовность HSE проверяется HAL с ограниченным временем ожидания, а не бесконечным циклом. */
|
||||
if (HAL_RCC_OscConfig(&osc)==HAL_OK) usb_clock_ready=1;
|
||||
else {
|
||||
clock_fault=1;
|
||||
osc.HSEState=RCC_HSE_OFF;
|
||||
if (HAL_RCC_OscConfig(&osc)!=HAL_OK) board_fatal(3);
|
||||
}
|
||||
/* CPU 72 MHz, APB1 36 MHz, USB 48 MHz; USB requires the HSE crystal. */
|
||||
osc.OscillatorType=RCC_OSCILLATORTYPE_HSI;
|
||||
osc.HSIState=RCC_HSI_ON;
|
||||
osc.HSICalibrationValue=RCC_HSICALIBRATION_DEFAULT;
|
||||
osc.PLL.PLLState=RCC_PLL_ON;
|
||||
osc.PLL.PLLSource=usb_clock_ready ? RCC_PLLSOURCE_HSE : RCC_PLLSOURCE_HSI;
|
||||
osc.PLL.PLLM=usb_clock_ready ? F407_BOOT_HSE_HZ/1000000UL : 16;
|
||||
osc.PLL.PLLN=144; osc.PLL.PLLP=RCC_PLLP_DIV2; osc.PLL.PLLQ=3;
|
||||
if (HAL_RCC_OscConfig(&osc)!=HAL_OK) board_fatal(3);
|
||||
clk.ClockType=RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
|
||||
clk.SYSCLKSource=RCC_SYSCLKSOURCE_PLLCLK;
|
||||
clk.AHBCLKDivider=RCC_SYSCLK_DIV1;
|
||||
clk.APB1CLKDivider=RCC_HCLK_DIV2; clk.APB2CLKDivider=RCC_HCLK_DIV1;
|
||||
/* HAL задаёт два такта ожидания Flash и перестраивает SysTick для новой частоты CPU. */
|
||||
if (HAL_RCC_ClockConfig(&clk,FLASH_LATENCY_2)!=HAL_OK) board_fatal(3);
|
||||
}
|
||||
|
||||
|
||||
void boot_protocol_init(void);
|
||||
void boot_protocol_poll(uint32_t now);
|
||||
int main(void) {
|
||||
/* Jump from reset, before initializing USB, DMA, clocks or interrupts. */
|
||||
bool requested=f407_boot_requested();
|
||||
if(!requested && f407_app_valid()) f407_jump_app();
|
||||
if(HAL_Init()!=HAL_OK) board_fatal(3);
|
||||
peripheral_clock(); boot_protocol_init();
|
||||
if(usb_clock_ready) usb_cdc_init();
|
||||
for(;;) { uint32_t now=HAL_GetTick(); usb_cdc_poll(now); boot_protocol_poll(now); }
|
||||
}
|
||||
13
c/set-protocol/ports/stm32f407-usb-boot/boot_port.h
Normal file
13
c/set-protocol/ports/stm32f407-usb-boot/boot_port.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#ifndef F407_BOOT_PORT_H
|
||||
#define F407_BOOT_PORT_H
|
||||
#include "set_boot.h"
|
||||
#define F407_APP_BASE 0x08010000UL
|
||||
#define F407_APP_SIZE 0x00070000UL
|
||||
#define F407_BOOT_REQUEST 0x1003U
|
||||
extern const setp_boot_ops f407_boot_ops;
|
||||
bool f407_app_valid(void);
|
||||
bool f407_boot_requested(void);
|
||||
void f407_request_boot(uint32_t now);
|
||||
void f407_reset_poll(uint32_t now);
|
||||
void f407_jump_app(void);
|
||||
#endif
|
||||
26
c/set-protocol/ports/stm32f407-usb-boot/boot_request.c
Normal file
26
c/set-protocol/ports/stm32f407-usb-boot/boot_request.c
Normal file
@@ -0,0 +1,26 @@
|
||||
#include "boot_port.h"
|
||||
#include "stm32f4xx_hal.h"
|
||||
#define REQUEST_MAGIC 0x42555342UL
|
||||
static uint32_t reset_at;
|
||||
static bool reset_pending;
|
||||
static void backup_access(void)
|
||||
{
|
||||
__HAL_RCC_PWR_CLK_ENABLE();
|
||||
PWR->CR |= PWR_CR_DBP;
|
||||
__HAL_RCC_RTC_ENABLE();
|
||||
}
|
||||
bool f407_boot_requested(void)
|
||||
{
|
||||
bool requested;
|
||||
backup_access(); requested=RTC->BKP0R==REQUEST_MAGIC;
|
||||
RTC->BKP0R=0; return requested;
|
||||
}
|
||||
void f407_request_boot(uint32_t now)
|
||||
{
|
||||
backup_access(); RTC->BKP0R=REQUEST_MAGIC;
|
||||
reset_at=now+500; reset_pending=true;
|
||||
}
|
||||
void f407_reset_poll(uint32_t now)
|
||||
{
|
||||
if(reset_pending && (int32_t)(now-reset_at)>=0) NVIC_SystemReset();
|
||||
}
|
||||
10
c/set-protocol/ports/stm32f407-usb-boot/boot_usb_stream.c
Normal file
10
c/set-protocol/ports/stm32f407-usb-boot/boot_usb_stream.c
Normal file
@@ -0,0 +1,10 @@
|
||||
#include "stm32f4xx_hal.h"
|
||||
#include "boot_port.h"
|
||||
#include "boot_config.h"
|
||||
#define BOOT_OPS f407_boot_ops
|
||||
#define BOOT_APP_BASE F407_APP_BASE
|
||||
#define BOOT_APP_SIZE F407_APP_SIZE
|
||||
#define BOOT_NODE F407_BOOT_NODE
|
||||
#define BOOT_MODEL F407_BOOT_MODEL
|
||||
#define BOOT_RESET() NVIC_SystemReset()
|
||||
#include "../stm32-usb-boot/boot_usb_protocol.inc"
|
||||
34
c/set-protocol/ports/stm32f407-usb-boot/test_boot_image.c
Normal file
34
c/set-protocol/ports/stm32f407-usb-boot/test_boot_image.c
Normal file
@@ -0,0 +1,34 @@
|
||||
#include "boot_image.h"
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
uint32_t words[32] = {0x20020000, F407_APP_BASE + 9};
|
||||
uint8_t *image = (uint8_t *)words;
|
||||
/* A Keil image needs no metadata. Replacing its body must still boot. */
|
||||
assert(f407_vectors_valid(image, F407_APP_SIZE));
|
||||
words[10] ^= 1;
|
||||
assert(f407_vectors_valid(image, F407_APP_SIZE));
|
||||
words[0] = words[1] = 0xffffffff;
|
||||
assert(!f407_vectors_valid(image, F407_APP_SIZE));
|
||||
words[0] = 0x20020000; words[1] = 0x08000101;
|
||||
assert(!f407_vectors_valid(image, F407_APP_SIZE));
|
||||
words[1] = F407_APP_BASE + 8;
|
||||
assert(!f407_vectors_valid(image, F407_APP_SIZE));
|
||||
words[1] = F407_APP_BASE + 9; words[0] = 0x20020001;
|
||||
assert(!f407_vectors_valid(image, F407_APP_SIZE));
|
||||
words[0] = 0x10010000;
|
||||
assert(f407_vectors_valid(image, F407_APP_SIZE));
|
||||
assert(!f407_vectors_valid(image, 8));
|
||||
assert(!f407_vectors_valid(image, 7));
|
||||
assert(!f407_vectors_valid(image, F407_APP_SIZE + 1));
|
||||
words[1] = F407_APP_BASE + F407_APP_SIZE + 1;
|
||||
assert(!f407_vectors_valid(image, F407_APP_SIZE));
|
||||
words[1] = F407_APP_BASE + 9;
|
||||
/* Like climate, vectors alone cannot establish image completeness. */
|
||||
memset(image + 8, 0xff, sizeof(words) - 8);
|
||||
assert(f407_vectors_valid(image, F407_APP_SIZE));
|
||||
puts("F407 boot: Keil image replacement and vector checks passed");
|
||||
return 0;
|
||||
}
|
||||
53
c/set-protocol/ports/stm32f407-wavegen/set_wave_f407.c
Normal file
53
c/set-protocol/ports/stm32f407-wavegen/set_wave_f407.c
Normal file
@@ -0,0 +1,53 @@
|
||||
#include "set_wave_f407.h"
|
||||
#include "stm32f4xx.h"
|
||||
static void stop(void *context) {
|
||||
unsigned limit=100000; (void)context;
|
||||
RCC->AHB1ENR|=RCC_AHB1ENR_GPIOAEN|RCC_AHB1ENR_DMA1EN;
|
||||
RCC->APB1ENR|=RCC_APB1ENR_DACEN|RCC_APB1ENR_TIM6EN;
|
||||
(void)RCC->APB1ENR;
|
||||
TIM6->CR1=0;
|
||||
DAC->CR&=~(DAC_CR_DMAEN1|DAC_CR_TEN1);
|
||||
DMA1_Stream5->CR&=~DMA_SxCR_EN;
|
||||
while((DMA1_Stream5->CR&DMA_SxCR_EN)&&--limit) {}
|
||||
/* Leave other DAC channel untouched. Channel 1 outputs zero on stop. */
|
||||
DAC->DHR12R1=0;
|
||||
DMA1->HIFCR=0xF40u;
|
||||
DAC->SR=DAC_SR_DMAUDR1;
|
||||
}
|
||||
static int start(void *context,const uint16_t *samples,size_t count,uint32_t rate) {
|
||||
set_wave_f407 *c=(set_wave_f407 *)context;
|
||||
uint32_t ticks,psc,arr; uintptr_t address=(uintptr_t)samples;
|
||||
if(!c||!rate||rate>SET_WAVE_MAX_RATE||count<2||count>SET_WAVE_MAX||c->timer_hz%rate
|
||||
||address<0x20000000u||address+count*2>0x20020000u) return 1;
|
||||
ticks=c->timer_hz/rate;
|
||||
for(psc=1;psc<=65536;++psc) if(ticks%psc==0&&ticks/psc<=65536) break;
|
||||
if(psc>65536||ticks<psc) return 1;
|
||||
arr=ticks/psc;
|
||||
stop(context);
|
||||
if(DMA1_Stream5->CR&DMA_SxCR_EN) return 1;
|
||||
GPIOA->MODER|=3u<<8;
|
||||
GPIOA->PUPDR&=~(3u<<8);
|
||||
TIM6->PSC=psc-1;TIM6->ARR=arr-1;TIM6->CNT=0;
|
||||
TIM6->CR2=0;TIM6->EGR=TIM_EGR_UG;TIM6->SR=0;
|
||||
TIM6->CR2=TIM_CR2_MMS_1; /* update -> TRGO */
|
||||
DMA1_Stream5->PAR=(uint32_t)&DAC->DHR12R1;
|
||||
DMA1_Stream5->M0AR=(uint32_t)samples;DMA1_Stream5->NDTR=(uint32_t)count;
|
||||
DMA1_Stream5->FCR=0;
|
||||
DMA1_Stream5->CR=(7u<<25)|DMA_SxCR_DIR_0|DMA_SxCR_MINC|DMA_SxCR_CIRC
|
||||
|DMA_SxCR_PSIZE_0|DMA_SxCR_MSIZE_0|DMA_SxCR_PL_1;
|
||||
/* Prime the previous period's last sample; DMA supplies the next value
|
||||
* after each trigger. Cyclic order is last,0,1,... with one priming tick. */
|
||||
DAC->CR&=~0xFFFFu;
|
||||
DAC->DHR12R1=samples[count-1];
|
||||
DAC->CR|=DAC_CR_EN1|DAC_CR_TEN1|DAC_CR_DMAEN1; /* TSEL1=0: TIM6 */
|
||||
DMA1_Stream5->CR|=DMA_SxCR_EN;
|
||||
TIM6->CR1=TIM_CR1_CEN;
|
||||
return 0;
|
||||
}
|
||||
set_wave_port set_wave_f407_port(set_wave_f407 *context) {
|
||||
set_wave_port port={context,start,stop};return port;
|
||||
}
|
||||
int set_wave_f407_fault(void) {
|
||||
return (DMA1->HISR&(DMA_HISR_TEIF5|DMA_HISR_DMEIF5|DMA_HISR_FEIF5))
|
||||
||(DAC->SR&DAC_SR_DMAUDR1);
|
||||
}
|
||||
12
c/set-protocol/ports/stm32f407-wavegen/set_wave_f407.h
Normal file
12
c/set-protocol/ports/stm32f407-wavegen/set_wave_f407.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#ifndef SET_WAVE_F407_H
|
||||
#define SET_WAVE_F407_H
|
||||
#include "../../include/set_wavegen.h"
|
||||
/* Owns PA4, DAC channel 1, TIM6 and DMA1 stream 5/channel 7.
|
||||
* timer_hz is TIM6 input clock (twice APB1 when APB1 prescaler !=1).
|
||||
* Rate: 1..SET_WAVE_MAX_RATE (1 MS/s), exact divisors of timer_hz only.
|
||||
* Context and sample buffer must outlive playback; buffer must be in SRAM. */
|
||||
typedef struct { uint32_t timer_hz; } set_wave_f407;
|
||||
set_wave_port set_wave_f407_port(set_wave_f407 *context);
|
||||
/* Nonzero on DMA error or DAC underrun; caller stops state on failure. */
|
||||
int set_wave_f407_fault(void);
|
||||
#endif
|
||||
34
c/set-protocol/ports/stm32g474-periph/README.md
Normal file
34
c/set-protocol/ports/stm32g474-periph/README.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# STM32G474RB emulator peripheral port
|
||||
|
||||
Board adapters for the common UMP/TMS/IGBT server and waveform generator.
|
||||
All peripheral source files have detailed Russian comments matching the F407 port.
|
||||
The shared algorithms, codecs and register map stay in `../../src`.
|
||||
|
||||
- `board.c`: application VTOR 0x08010000; HSI16/PLL 160 MHz, APB1 80 MHz;
|
||||
HSI48/CRS USB; board GPIO and 1 ms model ticks.
|
||||
- `transport.c`: USART2 PA2/PA3 RTU, optional RS485 DE PB15;
|
||||
FDCAN1 PB8/PB9, Classic CAN, extended IDs, bounded polling and TX timeout.
|
||||
- `wave_hal.c`: PA4 DAC1_CH1, TIM6, DMA1_Channel1 with DMAMUX DAC1 request;
|
||||
2..4096 samples in SRAM1/2, exact timer division, maximum 1 MS/s.
|
||||
- `main.c`: service ordering and deferred reboot into the G474 USB bootloader.
|
||||
|
||||
IGBT input bits 0..7 use PB0/PB1/PB2/PB10/PB11/PB12/PB13/PB14;
|
||||
output bits use PC0..PC7. SWD, SWO and USB pins are preserved.
|
||||
Clock, addresses and RS485 mode come from the board-supplied `board_config.h`.
|
||||
The default internal clock does not guarantee CAN or waveform accuracy over
|
||||
all temperature conditions; use a verified 24 MHz HSE source when required.
|
||||
|
||||
The consumer supplies `board.h`, `transport.h`, `wave_hal.h`, `emulator.h`,
|
||||
G4 HAL configuration and the common model composition. The reference project
|
||||
is `Nucleo_G474RB` in the emulator repository. Compile `EMU_CAPACITY=400`:
|
||||
each 32000-byte archive bank fits the MCU's 32 KiB CCM/96 KiB SRAM layout.
|
||||
DMA samples stay in SRAM1/2. No F407 peripheral implementation is compiled.
|
||||
|
||||
Link at 0x08010000 with a 64 KiB Flash limit; install the separate G474RB
|
||||
USB bootloader first. Require 128 KiB silicon, DBANK=0, BFB2=0 and no bank swap.
|
||||
The firmware rejects mismatched geometry before peripheral initialization.
|
||||
Reuse `../stm32g474-usb-boot/boot_usb_port.c` and `boot_request.c` with the
|
||||
shared USB CDC transport. Device model: STM32G474RB-EMU-EXPERIMENT.
|
||||
|
||||
Host tests validate the shared software; peripheral timing and electrical
|
||||
behavior have not yet been validated on a physical board.
|
||||
109
c/set-protocol/ports/stm32g474-periph/board.c
Normal file
109
c/set-protocol/ports/stm32g474-periph/board.c
Normal file
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Аппаратный порт STM32G474RB: векторы приложения, тактирование и GPIO.
|
||||
* Алгоритмы UMP/TMS/IGBT находятся в общем C-ядре templates; здесь только пины.
|
||||
*/
|
||||
#include "board.h"
|
||||
#include "board_config.h"
|
||||
#include "transport.h"
|
||||
#include "emulator.h"
|
||||
#include "igbt_emulator.h"
|
||||
#include "stm32g4xx_hal.h"
|
||||
volatile uint32_t clock_fault;
|
||||
uint32_t SystemCoreClock=16000000u;
|
||||
const uint8_t AHBPrescTable[16]={0,0,0,0,0,0,0,0,1,2,3,4,6,7,8,9};
|
||||
const uint8_t APBPrescTable[8]={0,0,0,0,1,2,3,4};
|
||||
static uint8_t usb_clock_ready;
|
||||
/* Восемь входов IGBT: PB0,PB1,PB2,PB10..PB14. PB3/SWO и PB8/PB9 CAN свободны. */
|
||||
static const uint16_t input_pins[8]={GPIO_PIN_0,GPIO_PIN_1,GPIO_PIN_2,
|
||||
GPIO_PIN_10,GPIO_PIN_11,GPIO_PIN_12,GPIO_PIN_13,GPIO_PIN_14};
|
||||
/* До C runtime разрешены только регистровые операции; глобальные данные ещё не готовы. */
|
||||
void SystemInit(void) { SCB->CPACR|=0xFu<<20; SCB->VTOR=0x08010000u; }
|
||||
void SystemCoreClockUpdate(void) {
|
||||
RCC_ClkInitTypeDef config; uint32_t latency;
|
||||
HAL_RCC_GetClockConfig(&config,&latency);
|
||||
SystemCoreClock=HAL_RCC_GetSysClockFreq() >> AHBPrescTable[config.AHBCLKDivider >> RCC_CFGR_HPRE_Pos];
|
||||
}
|
||||
/* ISR только считает миллисекунды: протоколы и модели обслуживаются главным циклом. */
|
||||
void SysTick_Handler(void) { HAL_IncTick(); }
|
||||
void board_fatal(uint32_t code) { clock_fault=code; for (;;) {} }
|
||||
static void peripheral_clock(void)
|
||||
{
|
||||
RCC_OscInitTypeDef osc={0}; RCC_ClkInitTypeDef clk={0};
|
||||
RCC_PeriphCLKInitTypeDef peripheral={0}; RCC_CRSInitTypeDef crs={0};
|
||||
__HAL_RCC_PWR_CLK_ENABLE();
|
||||
/* Для 160 МГц нужен Range1 Boost; Flash 4 wait states по таблице ST. */
|
||||
if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1_BOOST)!=HAL_OK) board_fatal(3);
|
||||
osc.OscillatorType=RCC_OSCILLATORTYPE_HSI|RCC_OSCILLATORTYPE_HSI48;
|
||||
osc.HSIState=RCC_HSI_ON; osc.HSICalibrationValue=RCC_HSICALIBRATION_DEFAULT;
|
||||
osc.HSI48State=RCC_HSI48_ON;
|
||||
osc.PLL.PLLState=RCC_PLL_ON; osc.PLL.PLLSource=RCC_PLLSOURCE_HSI;
|
||||
/* HSI16 / 4 * 80 / 2 = 160 МГц; PLLQ также 160 МГц, FDCAN берёт PCLK1. */
|
||||
osc.PLL.PLLM=RCC_PLLM_DIV4;
|
||||
#if EMU_USE_HSE
|
||||
#if EMU_HSE_HZ != 24000000u
|
||||
#error This board profile supports 24 MHz HSE or internal HSI16
|
||||
#endif
|
||||
osc.OscillatorType|=RCC_OSCILLATORTYPE_HSE;
|
||||
osc.HSEState=EMU_HSE_BYPASS ? RCC_HSE_BYPASS : RCC_HSE_ON;
|
||||
osc.PLL.PLLSource=RCC_PLLSOURCE_HSE; osc.PLL.PLLM=RCC_PLLM_DIV6;
|
||||
#endif
|
||||
osc.PLL.PLLN=80; osc.PLL.PLLP=RCC_PLLP_DIV2;
|
||||
osc.PLL.PLLQ=RCC_PLLQ_DIV2; osc.PLL.PLLR=RCC_PLLR_DIV2;
|
||||
if (HAL_RCC_OscConfig(&osc)!=HAL_OK) board_fatal(3);
|
||||
clk.ClockType=RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
|
||||
clk.SYSCLKSource=RCC_SYSCLKSOURCE_PLLCLK; clk.AHBCLKDivider=RCC_SYSCLK_DIV1;
|
||||
clk.APB1CLKDivider=RCC_HCLK_DIV2; clk.APB2CLKDivider=RCC_HCLK_DIV1;
|
||||
if (HAL_RCC_ClockConfig(&clk,FLASH_LATENCY_4)!=HAL_OK) board_fatal(3);
|
||||
/* USB не зависит от PLL/HSE. USART2 и FDCAN получают 80 МГц PCLK1. */
|
||||
peripheral.PeriphClockSelection=RCC_PERIPHCLK_USB|RCC_PERIPHCLK_FDCAN|RCC_PERIPHCLK_USART2;
|
||||
peripheral.UsbClockSelection=RCC_USBCLKSOURCE_HSI48;
|
||||
peripheral.FdcanClockSelection=RCC_FDCANCLKSOURCE_PCLK1;
|
||||
peripheral.Usart2ClockSelection=RCC_USART2CLKSOURCE_PCLK1;
|
||||
if (HAL_RCCEx_PeriphCLKConfig(&peripheral)!=HAL_OK) board_fatal(3);
|
||||
__HAL_RCC_CRS_CLK_ENABLE();
|
||||
crs.Prescaler=RCC_CRS_SYNC_DIV1; crs.Source=RCC_CRS_SYNC_SOURCE_USB;
|
||||
crs.Polarity=RCC_CRS_SYNC_POLARITY_RISING;
|
||||
crs.ReloadValue=__HAL_RCC_CRS_RELOADVALUE_CALCULATE(48000000u,1000u);
|
||||
crs.ErrorLimitValue=RCC_CRS_ERRORLIMIT_DEFAULT;
|
||||
crs.HSI48CalibrationValue=RCC_CRS_HSI48CALIBRATION_DEFAULT;
|
||||
HAL_RCCEx_CRSConfig(&crs); usb_clock_ready=1;
|
||||
}
|
||||
void board_init(void)
|
||||
{
|
||||
GPIO_InitTypeDef gpio={0};
|
||||
/* Этот образ связан с RB-загрузчиком: 128 КБ Flash, непрерывная карта DBANK=0. */
|
||||
__HAL_RCC_SYSCFG_CLK_ENABLE();
|
||||
if (*(const uint16_t *)FLASHSIZE_BASE!=128u ||
|
||||
(FLASH->OPTR&(FLASH_OPTR_DBANK|FLASH_OPTR_BFB2)) ||
|
||||
(SYSCFG->MEMRMP&SYSCFG_MEMRMP_FB_MODE)) board_fatal(5);
|
||||
peripheral_clock();
|
||||
__HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_GPIOC_CLK_ENABLE();
|
||||
__HAL_RCC_USART2_CLK_ENABLE(); __HAL_RCC_FDCAN_CLK_ENABLE();
|
||||
/* ST-LINK VCP физически подключён к PA2/PA3; выбираем USART2 AF7, формат 8N1. */
|
||||
gpio.Pin=GPIO_PIN_2|GPIO_PIN_3; gpio.Mode=GPIO_MODE_AF_PP;
|
||||
gpio.Speed=GPIO_SPEED_FREQ_VERY_HIGH; gpio.Pull=GPIO_PULLUP;
|
||||
gpio.Alternate=GPIO_AF7_USART2; HAL_GPIO_Init(GPIOA,&gpio);
|
||||
gpio.Pin=GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14;
|
||||
gpio.Mode=GPIO_MODE_INPUT; gpio.Pull=GPIO_PULLDOWN; HAL_GPIO_Init(GPIOB,&gpio);
|
||||
/* PC0..PC7 — восемь ответов IGBT. Устанавливаем нули до включения выходов. */
|
||||
gpio.Pin=0xff; gpio.Mode=GPIO_MODE_OUTPUT_PP; gpio.Pull=GPIO_NOPULL;
|
||||
HAL_GPIO_WritePin(GPIOC,0xff,GPIO_PIN_RESET); HAL_GPIO_Init(GPIOC,&gpio);
|
||||
#if EMU_RS485_DE
|
||||
gpio.Pin=GPIO_PIN_15; HAL_GPIO_WritePin(GPIOB,GPIO_PIN_15,GPIO_PIN_RESET);
|
||||
HAL_GPIO_Init(GPIOB,&gpio);
|
||||
#endif
|
||||
/* FDCAN1 Classic CAN: PB8 RX, PB9 TX. PA11/PA12 остаются для USB FS. */
|
||||
gpio.Pin=GPIO_PIN_8|GPIO_PIN_9; gpio.Mode=GPIO_MODE_AF_PP;
|
||||
gpio.Pull=GPIO_PULLUP; gpio.Alternate=GPIO_AF9_FDCAN1; HAL_GPIO_Init(GPIOB,&gpio);
|
||||
transport_init();
|
||||
}
|
||||
uint8_t board_usb_ready(void) { return usb_clock_ready; }
|
||||
/* Один шаг 1 мс. Бит n соответствует элементу input_pins[n], выход — PCn. */
|
||||
void board_tick(void)
|
||||
{
|
||||
uint8_t inputs=0,outputs; unsigned i;
|
||||
for(i=0;i<8;++i) if(HAL_GPIO_ReadPin(GPIOB,input_pins[i])==GPIO_PIN_SET) inputs|=(uint8_t)(1u<<i);
|
||||
igbt_emu_set_tx(inputs); emu_tick(); outputs=igbt_emu_get_rx();
|
||||
HAL_GPIO_WritePin(GPIOC,(uint16_t)(~outputs&0xffu),GPIO_PIN_RESET);
|
||||
HAL_GPIO_WritePin(GPIOC,outputs,GPIO_PIN_SET);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user