feat(can-sensor): передача ROM датчиков парой CAN-кадров

Перенесён из KONOR_ds18b20/lib/can.

64-битный идентификатор датчика и преамбула не помещаются в восемь байт
поля данных, поэтому сообщение идёт двумя кадрами. Сборку на приёме
библиотека делает сама: кадр ROM без преамбулы и кадр неверной длины
отбрасываются и учитываются в dropped_frames.
This commit is contained in:
2026-08-23 01:15:12 +03:00
parent 11bf8b6c11
commit 13f2c8fe64
3 changed files with 516 additions and 0 deletions

217
c/can-sensor/can_sensor.h Normal file
View File

@@ -0,0 +1,217 @@
/**
* @file can_sensor.h
* @brief Портируемая передача идентификаторов датчиков по шине CAN.
*
* Библиотека не привязана к микроконтроллеру: она не включает заголовки
* периферии, не обращается к регистрам и не пользуется прерываниями. Обмен
* идёт через таблицу обратных вызовов CanSensor_Io, которую заполняет порт
* платы (для этой сборки — @c src/can.c поверх bxCAN STM32F103C8T6).
*
* Сообщение состоит из двух кадров классического CAN, потому что 64-битный
* идентификатор датчика и преамбула вместе не помещаются в восемь байтов
* поля данных:
*
* @code
* кадр 1 — преамбула, DLC = 2 +---------+----------+
* | команда | позиция |
* +---------+----------+
* кадр 2 — идентификатор, DLC=8 +----------------------------+
* | ROM датчика, байты 0..7 |
* +----------------------------+
* @endcode
*
* Преамбула несёт команду записи датчика в позицию: первый байт — код
* команды (@ref CAN_SENSOR_CMD_WRITE_POSITION), второй — номер позиции в
* таблице узла-приёмника. Кадр идентификатора передаётся сразу за преамбулой
* и без неё считается недействительным, поэтому приёмник не запишет ROM в
* позицию, которая ему не была назначена.
*/
#ifndef CAN_SENSOR_H
#define CAN_SENSOR_H
#include <stdint.h>
/** Длина идентификатора датчика (ROM 1-Wire) в байтах. */
#define CAN_SENSOR_ID_SIZE 8U
/** Длина преамбулы: код команды (1 байт) и номер позиции (2 байта, u16 BE). */
#define CAN_SENSOR_PREAMBLE_SIZE 3U
/** Предельная длина поля данных классического кадра CAN. */
#define CAN_SENSOR_MAX_DATA 8U
/** Код команды «записать идентификатор датчика в позицию». */
#define CAN_SENSOR_CMD_WRITE_POSITION 0xA1U
/** Код команды «очистить позицию»; кадр идентификатора передаётся нулевым. */
#define CAN_SENSOR_CMD_CLEAR_POSITION 0xA2U
/** Смещение идентификатора кадра данных относительно кадра преамбулы. */
#define CAN_SENSOR_DATA_ID_OFFSET 1U
/** Число попыток передачи одного кадра по умолчанию. */
#define CAN_SENSOR_DEFAULT_RETRIES 3U
/** Идентификатор кадра преамбулы по умолчанию (стандартный, 11 бит). */
#define CAN_SENSOR_DEFAULT_TX_ID 0x200U
/**
* @brief Кадр шины CAN в форме, не зависящей от контроллера.
*/
typedef struct {
uint32_t id; /**< Идентификатор кадра, 11 или 29 бит. */
uint8_t extended; /**< 1 — расширенный идентификатор. */
uint8_t length; /**< Число значащих байтов поля данных. */
uint8_t data[CAN_SENSOR_MAX_DATA]; /**< Поле данных кадра. */
} CanSensor_Frame;
/**
* @brief Принятое или собранное сообщение: преамбула и идентификатор датчика.
*/
typedef struct {
uint8_t command; /**< Код команды из преамбулы. */
uint16_t position; /**< Позиция записи из преамбулы. */
uint8_t id[CAN_SENSOR_ID_SIZE]; /**< Идентификатор датчика. */
} CanSensor_Message;
/**
* @brief Доступ библиотеки к контроллеру CAN.
*
* Обязателен только @c send; при нулевом @c receive функция CanSensor_Poll()
* ничего не делает и узел работает только на передачу.
*/
typedef struct {
/** Ставит кадр в очередь передачи; 1 — кадр принят контроллером. */
uint8_t (*send)(void *context, const CanSensor_Frame *frame);
/** Забирает принятый кадр; 1 — кадр получен, 0 — очередь пуста. */
uint8_t (*receive)(void *context, CanSensor_Frame *frame);
void *context; /**< Контекст порта, передаётся вызовам без изменений. */
} CanSensor_Io;
/**
* @brief Идентификаторы кадров и режим передачи.
*
* Нулевые поля заменяются значениями по умолчанию вызовом
* CanSensor_ConfigDefault() или самой CanSensor_Init().
*/
typedef struct {
uint32_t tx_id; /**< Идентификатор кадра преамбулы при передаче. */
uint32_t tx_data_id; /**< Идентификатор кадра ROM; 0 — @c tx_id + 1. */
uint32_t rx_id; /**< Ожидаемая преамбула при приёме; 0 — @c tx_id. */
uint32_t rx_data_id; /**< Ожидаемый кадр ROM; 0 — @c rx_id + 1. */
uint8_t extended; /**< 1 — расширенные идентификаторы 29 бит. */
uint8_t retries; /**< Попыток передачи кадра; 0 — значение по умолчанию. */
} CanSensor_Config;
/**
* @brief Состояние узла: настройки, счётчики и сборка принимаемого сообщения.
*/
typedef struct {
CanSensor_Io io; /**< Обратные вызовы порта. */
CanSensor_Config config; /**< Идентификаторы кадров и режим передачи. */
CanSensor_Message rx; /**< Собираемое сообщение приёма. */
uint8_t rx_preamble; /**< 1 — преамбула принята, ожидается кадр ROM. */
uint32_t sent_messages; /**< Полностью переданные сообщения. */
uint32_t send_errors; /**< Сообщения, не ушедшие в шину. */
uint32_t received_messages; /**< Полностью принятые сообщения. */
uint32_t dropped_frames; /**< Кадры без преамбулы или с неверной длиной. */
} CanSensor;
/**
* @brief Заполняет настройки значениями по умолчанию.
*
* Идентификатор преамбулы — @ref CAN_SENSOR_DEFAULT_TX_ID, кадр ROM идёт
* следующим идентификатором, приём настроен на те же значения.
*
* @param config Настройки, принадлежащие вызывающему коду.
*/
void CanSensor_ConfigDefault(CanSensor_Config *config);
/**
* @brief Готовит узел к работе.
*
* @param link Состояние узла, принадлежащее вызывающему коду.
* @param io Обратные вызовы порта; копируются внутрь состояния.
* @param config Настройки либо 0 для значений по умолчанию.
* @return 1 при успешной настройке, 0 при неполных аргументах.
*/
uint8_t CanSensor_Init(CanSensor *link, const CanSensor_Io *io,
const CanSensor_Config *config);
/**
* @brief Собирает кадр преамбулы.
*
* @param config Настройки узла.
* @param frame Кадр приёмника.
* @param command Код команды, например @ref CAN_SENSOR_CMD_WRITE_POSITION.
* @param position Номер позиции записи датчика.
* @return 1 при успешной сборке, иначе 0.
*/
uint8_t CanSensor_BuildPreamble(const CanSensor_Config *config, CanSensor_Frame *frame,
uint8_t command, uint16_t position);
/**
* @brief Собирает кадр идентификатора датчика.
*
* @param config Настройки узла.
* @param frame Кадр приёмника.
* @param id Идентификатор датчика длиной @ref CAN_SENSOR_ID_SIZE либо 0 для
* нулевого кадра команды очистки.
* @return 1 при успешной сборке, иначе 0.
*/
uint8_t CanSensor_BuildIdFrame(const CanSensor_Config *config, CanSensor_Frame *frame,
const uint8_t *id);
/**
* @brief Передаёт преамбулу и идентификатор датчика.
*
* Кадры уходят подряд; при отказе контроллера передача повторяется
* @c retries раз. Если преамбула ушла, а кадр ROM — нет, сообщение считается
* несостоявшимся: приёмник отбросит одиночную преамбулу.
*
* @param link Состояние узла.
* @param command Код команды преамбулы.
* @param position Номер позиции записи датчика.
* @param id Идентификатор датчика либо 0 для команды очистки позиции.
* @return 1, если оба кадра приняты контроллером, иначе 0.
*/
uint8_t CanSensor_Send(CanSensor *link, uint8_t command, uint16_t position,
const uint8_t *id);
/**
* @brief Передаёт команду записи датчика в позицию.
*
* Краткая форма CanSensor_Send() с кодом @ref CAN_SENSOR_CMD_WRITE_POSITION.
*
* @param link Состояние узла.
* @param position Номер позиции записи датчика.
* @param id Идентификатор датчика длиной @ref CAN_SENSOR_ID_SIZE.
* @return 1, если оба кадра приняты контроллером, иначе 0.
*/
uint8_t CanSensor_SendId(CanSensor *link, uint16_t position, const uint8_t *id);
/**
* @brief Разбирает принятый кадр и собирает из пары кадров сообщение.
*
* Кадр идентификатора без предшествующей преамбулы отбрасывается, а новая
* преамбула заменяет незавершённую: сборка не требует таймера.
*
* @param link Состояние узла.
* @param frame Принятый кадр.
* @param out Приёмник готового сообщения либо 0.
* @return 1, если сообщение собрано полностью, иначе 0.
*/
uint8_t CanSensor_HandleFrame(CanSensor *link, const CanSensor_Frame *frame,
CanSensor_Message *out);
/**
* @brief Забирает кадры у порта и возвращает первое собранное сообщение.
*
* @param link Состояние узла.
* @param out Приёмник сообщения либо 0.
* @return 1, если сообщение собрано, иначе 0.
*/
uint8_t CanSensor_Poll(CanSensor *link, CanSensor_Message *out);
#endif /* CAN_SENSOR_H */