diff --git a/c/can-sensor/README.md b/c/can-sensor/README.md new file mode 100644 index 0000000..e218d38 --- /dev/null +++ b/c/can-sensor/README.md @@ -0,0 +1,53 @@ +# can-sensor + +Передача 64-битных идентификаторов датчиков (ROM 1-Wire) по шине CAN. + +Ядро на C99: не включает заголовки периферии, не обращается к регистрам, +не пользуется прерываниями. Обмен идёт через таблицу `CanSensor_Io`, +которую заполняет порт платы. + +Сообщение состоит из двух классических CAN-кадров — 64-битный идентификатор +и преамбула вместе не помещаются в восемь байтов поля данных: + +``` + кадр 1 — преамбула, DLC = 2 | команда | позиция | + кадр 2 — идентификатор, DLC = 8| ROM 8 байт | +``` + +## Состав + +| Файл | Что делает | Зависимости | +|---|---|---| +| `can_sensor.h`, `can_sensor.c` | сборка и разбор пары кадров, повторы передачи, счётчики обмена | `stdint.h` | + +Приём собирает сообщение сам: кадр ROM без преамбулы и кадр с неверной +длиной отбрасываются и учитываются в `dropped_frames`. + +## Что нужно от платформы + +```c +uint8_t send(void *ctx, const CanSensor_Frame *frame); /* 1 — кадр принят */ +uint8_t receive(void *ctx, CanSensor_Frame *frame); /* 1 — кадр получен */ +``` + +`receive` необязателен: с нулевым полем узел работает только на передачу. + +## Быстрый старт + +```c +CanSensor link; +CanSensor_Io io = { .send = bxcan_send, .receive = bxcan_receive, .context = &board }; +CanSensor_Config config; +CanSensor_ConfigDefault(&config); /* tx_id 0x200, кадр ROM 0x201, 3 попытки */ + +CanSensor_Init(&link, &io, &config); +CanSensor_SendId(&link, position, rom); + +CanSensor_Message message; +if (CanSensor_Poll(&link, &message)) { /* принят идентификатор */ } +``` + +## Проверено в проектах + +`KONOR_ds18b20` — bxCAN на STM32F103C8T6. Естественная пара — [`ds18b20`](../ds18b20): +ROM-коды, найденные `DS18B20_Search()`, уходят в шину как есть. diff --git a/c/can-sensor/can_sensor.c b/c/can-sensor/can_sensor.c new file mode 100644 index 0000000..348c8e9 --- /dev/null +++ b/c/can-sensor/can_sensor.c @@ -0,0 +1,246 @@ +/** + * @file can_sensor.c + * @brief Сборка, передача и разбор сообщений «преамбула + идентификатор». + * + * Реализация не хранит очередей и не пользуется временем: состояние приёма + * ограничено признаком принятой преамбулы, поэтому библиотека одинаково + * работает и в главном цикле, и в обработчике прерывания порта. + */ + +#include "can_sensor.h" + +/** + * @brief Возвращает идентификатор кадра данных для заданных настроек. + * + * @param config Настройки узла. + * @return Идентификатор кадра ROM при передаче. + */ +static uint32_t can_sensor_tx_data_id(const CanSensor_Config *config) +{ + if (config->tx_data_id != 0U) { + return config->tx_data_id; + } + return config->tx_id + CAN_SENSOR_DATA_ID_OFFSET; +} + +/** + * @brief Возвращает ожидаемый идентификатор преамбулы при приёме. + * + * @param config Настройки узла. + * @return Идентификатор кадра преамбулы. + */ +static uint32_t can_sensor_rx_id(const CanSensor_Config *config) +{ + if (config->rx_id != 0U) { + return config->rx_id; + } + return config->tx_id; +} + +/** + * @brief Возвращает ожидаемый идентификатор кадра ROM при приёме. + * + * @param config Настройки узла. + * @return Идентификатор кадра идентификатора датчика. + */ +static uint32_t can_sensor_rx_data_id(const CanSensor_Config *config) +{ + if (config->rx_data_id != 0U) { + return config->rx_data_id; + } + return can_sensor_rx_id(config) + CAN_SENSOR_DATA_ID_OFFSET; +} + +/** + * @brief Передаёт один кадр с повторами при отказе контроллера. + * + * @param link Состояние узла. + * @param frame Передаваемый кадр. + * @return 1, если кадр принят контроллером, иначе 0. + */ +static uint8_t can_sensor_send_frame(CanSensor *link, const CanSensor_Frame *frame) +{ + uint8_t attempt; + + for (attempt = 0U; attempt < link->config.retries; attempt++) { + if (link->io.send(link->io.context, frame) != 0U) { + return 1U; + } + } + return 0U; +} + +void CanSensor_ConfigDefault(CanSensor_Config *config) +{ + if (config == 0) { + return; + } + config->tx_id = CAN_SENSOR_DEFAULT_TX_ID; + config->tx_data_id = CAN_SENSOR_DEFAULT_TX_ID + CAN_SENSOR_DATA_ID_OFFSET; + config->rx_id = CAN_SENSOR_DEFAULT_TX_ID; + config->rx_data_id = CAN_SENSOR_DEFAULT_TX_ID + CAN_SENSOR_DATA_ID_OFFSET; + config->extended = 0U; + config->retries = CAN_SENSOR_DEFAULT_RETRIES; +} + +uint8_t CanSensor_Init(CanSensor *link, const CanSensor_Io *io, + const CanSensor_Config *config) +{ + uint8_t index; + + if ((link == 0) || (io == 0) || (io->send == 0)) { + return 0U; + } + + link->io = *io; + if (config != 0) { + link->config = *config; + } else { + CanSensor_ConfigDefault(&link->config); + } + if (link->config.tx_id == 0U) { + link->config.tx_id = CAN_SENSOR_DEFAULT_TX_ID; + } + if (link->config.retries == 0U) { + link->config.retries = CAN_SENSOR_DEFAULT_RETRIES; + } + + link->rx.command = 0U; + link->rx.position = 0U; + for (index = 0U; index < CAN_SENSOR_ID_SIZE; index++) { + link->rx.id[index] = 0U; + } + link->rx_preamble = 0U; + link->sent_messages = 0U; + link->send_errors = 0U; + link->received_messages = 0U; + link->dropped_frames = 0U; + return 1U; +} + +uint8_t CanSensor_BuildPreamble(const CanSensor_Config *config, CanSensor_Frame *frame, + uint8_t command, uint16_t position) +{ + uint8_t index; + + if ((config == 0) || (frame == 0)) { + return 0U; + } + frame->id = config->tx_id; + frame->extended = config->extended; + frame->length = CAN_SENSOR_PREAMBLE_SIZE; + frame->data[0] = command; + /* Позиция занимает два байта в порядке от старшего к младшему. */ + frame->data[1] = (uint8_t)((position >> 8U) & 0xFFU); + frame->data[2] = (uint8_t)(position & 0xFFU); + for (index = CAN_SENSOR_PREAMBLE_SIZE; index < CAN_SENSOR_MAX_DATA; index++) { + frame->data[index] = 0U; + } + return 1U; +} + +uint8_t CanSensor_BuildIdFrame(const CanSensor_Config *config, CanSensor_Frame *frame, + const uint8_t *id) +{ + uint8_t index; + + if ((config == 0) || (frame == 0)) { + return 0U; + } + frame->id = can_sensor_tx_data_id(config); + frame->extended = config->extended; + frame->length = CAN_SENSOR_ID_SIZE; + for (index = 0U; index < CAN_SENSOR_ID_SIZE; index++) { + frame->data[index] = (id != 0) ? id[index] : 0U; + } + return 1U; +} + +uint8_t CanSensor_Send(CanSensor *link, uint8_t command, uint16_t position, + const uint8_t *id) +{ + CanSensor_Frame frame; + + if ((link == 0) || (link->io.send == 0)) { + return 0U; + } + (void)CanSensor_BuildPreamble(&link->config, &frame, command, position); + if (can_sensor_send_frame(link, &frame) == 0U) { + link->send_errors++; + return 0U; + } + (void)CanSensor_BuildIdFrame(&link->config, &frame, id); + if (can_sensor_send_frame(link, &frame) == 0U) { + /* Одиночная преамбула приёмником отбрасывается, позиция не изменится. */ + link->send_errors++; + return 0U; + } + link->sent_messages++; + return 1U; +} + +uint8_t CanSensor_SendId(CanSensor *link, uint16_t position, const uint8_t *id) +{ + if (id == 0) { + return 0U; + } + return CanSensor_Send(link, CAN_SENSOR_CMD_WRITE_POSITION, position, id); +} + +uint8_t CanSensor_HandleFrame(CanSensor *link, const CanSensor_Frame *frame, + CanSensor_Message *out) +{ + uint8_t index; + + if ((link == 0) || (frame == 0)) { + return 0U; + } + + if ((frame->id == can_sensor_rx_id(&link->config)) + && (frame->extended == link->config.extended)) { + if (frame->length < CAN_SENSOR_PREAMBLE_SIZE) { + link->dropped_frames++; + return 0U; + } + link->rx.command = frame->data[0]; + link->rx.position = (uint16_t)(((uint16_t)frame->data[1] << 8U) + | frame->data[2]); + link->rx_preamble = 1U; + return 0U; + } + + if ((frame->id != can_sensor_rx_data_id(&link->config)) + || (frame->extended != link->config.extended)) { + return 0U; + } + if ((link->rx_preamble == 0U) || (frame->length < CAN_SENSOR_ID_SIZE)) { + /* Идентификатор без преамбулы не говорит, в какую позицию его писать. */ + link->rx_preamble = 0U; + link->dropped_frames++; + return 0U; + } + for (index = 0U; index < CAN_SENSOR_ID_SIZE; index++) { + link->rx.id[index] = frame->data[index]; + } + link->rx_preamble = 0U; + link->received_messages++; + if (out != 0) { + *out = link->rx; + } + return 1U; +} + +uint8_t CanSensor_Poll(CanSensor *link, CanSensor_Message *out) +{ + CanSensor_Frame frame; + + if ((link == 0) || (link->io.receive == 0)) { + return 0U; + } + while (link->io.receive(link->io.context, &frame) != 0U) { + if (CanSensor_HandleFrame(link, &frame, out) != 0U) { + return 1U; + } + } + return 0U; +} diff --git a/c/can-sensor/can_sensor.h b/c/can-sensor/can_sensor.h new file mode 100644 index 0000000..eb10d15 --- /dev/null +++ b/c/can-sensor/can_sensor.h @@ -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 + +/** Длина идентификатора датчика (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 */