/** * @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 /** Длина идентификатора датчика (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 */