diff --git a/c/can-sensor/CMakeLists.txt b/c/can-sensor/CMakeLists.txt new file mode 100644 index 0000000..9171761 --- /dev/null +++ b/c/can-sensor/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.13) +project(can_sensor C) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) + +add_library(can_sensor STATIC can_sensor.c) +target_include_directories(can_sensor PUBLIC .) + +if(MSVC) + target_compile_options(can_sensor PRIVATE /W4) +else() + target_compile_options(can_sensor PRIVATE -Wall -Wextra -Wpedantic) +endif() + +option(CAN_SENSOR_BUILD_TESTS "Собирать тесты транспорта CAN sensor" ON) +if(CAN_SENSOR_BUILD_TESTS) + enable_testing() + add_executable(test_can_sensor tests/test_can_sensor.c) + target_link_libraries(test_can_sensor PRIVATE can_sensor) + add_test(NAME can_sensor COMMAND test_can_sensor) +endif() diff --git a/c/can-sensor/README.md b/c/can-sensor/README.md index 4fbbe07..59c109e 100644 --- a/c/can-sensor/README.md +++ b/c/can-sensor/README.md @@ -19,7 +19,7 @@ | Файл | Что делает | Зависимости | |---|---|---| -| `can_sensor.h`, `can_sensor.c` | сборка и разбор SETTINGS, повторы передачи, счётчики обмена | `stdint.h` | +| `can_sensor.h`, `can_sensor.c` | сборка и разбор SETTINGS, планировщик PULSE, повторы передачи, счётчики обмена | `stdint.h` | | `ports/stm32f1/` | опросный порт CAN1: GPIO, BTR, фильтр, mailbox/FIFO, тайм-аут ACK | CMSIS `stm32f10x.h` | `ZZ` — номер сборки, `YY` — позиция. Нулевой ROM с DLC=8 очищает локацию. @@ -40,14 +40,20 @@ CanSensor link; CanSensor_Io io = { .send = bxcan_send, .receive = bxcan_receive, .context = &board }; CanSensor_Config config; CanSensor_ConfigDefault(&config); /* TX 0x1FFE0000, RX 0x17FE0000 */ +config.pulse_period_ms = 1000U; /* 0 — PULSE выключен */ CanSensor_Init(&link, &io, &config); CanSensor_SendId(&link, position, assembly_serial, rom); +CanSensor_Task(&link, now_ms); CanSensor_Message message; if (CanSensor_Poll(&link, &message)) { /* принят идентификатор */ } ``` +Период можно менять во время работы через `CanSensor_SetPulsePeriod()`. Значение +`0` именно выключает heartbeat: кадры PULSE больше не ставятся в очередь. При +повторном включении первый кадр отправляется через полный заданный период. + ## Порт STM32F1 Порт не использует STM32 HAL и не занимает прерывания. Скопируйте diff --git a/c/can-sensor/can_sensor.c b/c/can-sensor/can_sensor.c index e1b6fd4..681b811 100644 --- a/c/can-sensor/can_sensor.c +++ b/c/can-sensor/can_sensor.c @@ -40,6 +40,7 @@ void CanSensor_ConfigDefault(CanSensor_Config *config) } config->tx_id = CAN_SENSOR_DEFAULT_TX_ID; config->rx_id = CAN_SENSOR_DEFAULT_RX_ID; + config->pulse_period_ms = 0U; config->extended = 1U; config->retries = CAN_SENSOR_DEFAULT_RETRIES; } @@ -72,6 +73,9 @@ uint8_t CanSensor_Init(CanSensor *link, const CanSensor_Io *io, link->send_errors = 0U; link->received_messages = 0U; link->dropped_frames = 0U; + link->pulse_last_ms = 0U; + link->pulse_counter = 0U; + link->pulse_armed = 0U; return 1U; } @@ -201,3 +205,51 @@ uint8_t CanSensor_Poll(CanSensor *link, CanSensor_Message *out) } return 0U; } + +void CanSensor_SetPulsePeriod(CanSensor *link, uint32_t period_ms) +{ + if (link == 0) { + return; + } + if (link->config.pulse_period_ms != period_ms) { + link->config.pulse_period_ms = period_ms; + /* После включения или смены периода отсчитываем полный новый период. */ + link->pulse_armed = 0U; + } +} + +uint32_t CanSensor_PulsePeriod(const CanSensor *link) +{ + return (link != 0) ? link->config.pulse_period_ms : 0U; +} + +void CanSensor_Task(CanSensor *link, uint32_t now_ms) +{ + CanSensor_Frame frame; + const uint32_t period_ms = (link != 0) ? link->config.pulse_period_ms : 0U; + + if ((link == 0) || (link->io.send == 0) || (period_ms == 0U)) { + return; + } + if (link->pulse_armed == 0U) { + link->pulse_last_ms = now_ms; + link->pulse_armed = 1U; + return; + } + if ((now_ms - link->pulse_last_ms) < period_ms) { + return; + } + + frame.id = (link->config.tx_id & CAN_SENSOR_ADDRESS_MASK) + | ((uint32_t)CAN_SENSOR_MSGTYPE_PULSE << 16U); + frame.extended = 1U; + frame.length = 1U; + link->pulse_counter++; + frame.data[0] = link->pulse_counter; + if (can_sensor_send_frame(link, &frame) != 0U) { + link->pulse_last_ms = now_ms; + } else { + /* Счётчик меняется только для реально поставленных в очередь кадров. */ + link->pulse_counter--; + } +} diff --git a/c/can-sensor/can_sensor.h b/c/can-sensor/can_sensor.h index d6a5ebf..06f4f46 100644 --- a/c/can-sensor/can_sensor.h +++ b/c/can-sensor/can_sensor.h @@ -27,9 +27,11 @@ /** Назначенный проекту тип сообщения SETCAN SETTINGS. */ #define CAN_SENSOR_MSGTYPE_SETTINGS 0xEU +#define CAN_SENSOR_MSGTYPE_PULSE 0xFU /** Маска полей SETCAN от Priority до MsgType; Body под маской не находится. */ #define CAN_SENSOR_HEADER_MASK 0x1FFF0000UL +#define CAN_SENSOR_ADDRESS_MASK 0x1FF00000UL #define CAN_SENSOR_BODY_MASK 0x0000FFFFUL /** Базовые ID SETTINGS для выбранных DeviceType=0x7 и DeviceID=0xF. */ @@ -88,6 +90,7 @@ typedef struct { uint32_t rx_id; /**< База запроса 0x17FE0000, Body должен быть нулевым. */ uint8_t extended; /**< 1 — Extended CAN ID. */ uint8_t retries; /**< Число попыток передачи. */ + uint32_t pulse_period_ms; /**< Период PULSE; 0 полностью отключает PULSE. */ } CanSensor_Config; /** Состояние транспорта и диагностические счётчики. */ @@ -98,6 +101,9 @@ typedef struct { uint32_t send_errors; uint32_t received_messages; uint32_t dropped_frames; + uint32_t pulse_last_ms; + uint8_t pulse_counter; + uint8_t pulse_armed; } CanSensor; void CanSensor_ConfigDefault(CanSensor_Config *config); @@ -124,4 +130,17 @@ uint8_t CanSensor_HandleFrame(CanSensor *link, const CanSensor_Frame *frame, uint8_t CanSensor_Poll(CanSensor *link, CanSensor_Message *out); +/** + * @brief Меняет период PULSE во время работы. + * + * @param period_ms Период в миллисекундах; 0 выключает PULSE. + */ +void CanSensor_SetPulsePeriod(CanSensor *link, uint32_t period_ms); + +/** Возвращает действующий период PULSE; 0 означает «выключено». */ +uint32_t CanSensor_PulsePeriod(const CanSensor *link); + +/** Вызывает планировщик PULSE из главного цикла. */ +void CanSensor_Task(CanSensor *link, uint32_t now_ms); + #endif /* CAN_SENSOR_H */ diff --git a/c/can-sensor/tests/test_can_sensor.c b/c/can-sensor/tests/test_can_sensor.c new file mode 100644 index 0000000..5d0a010 --- /dev/null +++ b/c/can-sensor/tests/test_can_sensor.c @@ -0,0 +1,79 @@ +#include "can_sensor.h" + +#include + +typedef struct { + CanSensor_Frame last_frame; + uint32_t send_count; + uint8_t accept; +} FakeCan; + +static uint8_t fake_send(void *context, const CanSensor_Frame *frame) +{ + FakeCan *fake = (FakeCan *)context; + + fake->send_count++; + fake->last_frame = *frame; + return fake->accept; +} + +#define CHECK(condition) \ + do { \ + if (!(condition)) { \ + fprintf(stderr, "check failed at line %d: %s\n", __LINE__, \ + #condition); \ + return 1; \ + } \ + } while (0) + +int main(void) +{ + CanSensor link; + CanSensor_Config config; + CanSensor_Io io; + FakeCan fake = {0}; + + fake.accept = 1U; + io.send = fake_send; + io.receive = 0; + io.context = &fake; + CanSensor_ConfigDefault(&config); + + CHECK(config.pulse_period_ms == 0U); + CHECK(CanSensor_Init(&link, &io, &config) != 0U); + + /* Нулевой период означает выключенный PULSE при любых метках времени. */ + CanSensor_Task(&link, 0U); + CanSensor_Task(&link, 0xFFFFFFFFUL); + CHECK(fake.send_count == 0U); + + CanSensor_SetPulsePeriod(&link, 1000U); + CHECK(CanSensor_PulsePeriod(&link) == 1000U); + CanSensor_Task(&link, 500U); + CanSensor_Task(&link, 1499U); + CHECK(fake.send_count == 0U); + CanSensor_Task(&link, 1500U); + CHECK(fake.send_count == 1U); + CHECK(fake.last_frame.id == 0x1FFF0000UL); + CHECK(fake.last_frame.extended == 1U); + CHECK(fake.last_frame.length == 1U); + CHECK(fake.last_frame.data[0] == 1U); + + /* После period=0 дальнейшие вызовы не отправляют heartbeat. */ + CanSensor_SetPulsePeriod(&link, 0U); + CHECK(CanSensor_PulsePeriod(&link) == 0U); + CanSensor_Task(&link, 0xFFFFFFFFUL); + CHECK(fake.send_count == 1U); + + /* Повторное включение начинает новый полный период. */ + CanSensor_SetPulsePeriod(&link, 10U); + CanSensor_Task(&link, 2000U); + CanSensor_Task(&link, 2009U); + CHECK(fake.send_count == 1U); + CanSensor_Task(&link, 2010U); + CHECK(fake.send_count == 2U); + CHECK(fake.last_frame.data[0] == 2U); + + puts("can_sensor pulse tests passed"); + return 0; +}