Добавить переносимый планировщик PULSE

This commit is contained in:
2026-09-01 20:54:58 +03:00
parent e9ec078b38
commit af82687e42
5 changed files with 179 additions and 1 deletions

View File

@@ -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()

View File

@@ -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` | | `ports/stm32f1/` | опросный порт CAN1: GPIO, BTR, фильтр, mailbox/FIFO, тайм-аут ACK | CMSIS `stm32f10x.h` |
`ZZ` — номер сборки, `YY` — позиция. Нулевой ROM с DLC=8 очищает локацию. `ZZ` — номер сборки, `YY` — позиция. Нулевой ROM с DLC=8 очищает локацию.
@@ -40,14 +40,20 @@ CanSensor link;
CanSensor_Io io = { .send = bxcan_send, .receive = bxcan_receive, .context = &board }; CanSensor_Io io = { .send = bxcan_send, .receive = bxcan_receive, .context = &board };
CanSensor_Config config; CanSensor_Config config;
CanSensor_ConfigDefault(&config); /* TX 0x1FFE0000, RX 0x17FE0000 */ CanSensor_ConfigDefault(&config); /* TX 0x1FFE0000, RX 0x17FE0000 */
config.pulse_period_ms = 1000U; /* 0 — PULSE выключен */
CanSensor_Init(&link, &io, &config); CanSensor_Init(&link, &io, &config);
CanSensor_SendId(&link, position, assembly_serial, rom); CanSensor_SendId(&link, position, assembly_serial, rom);
CanSensor_Task(&link, now_ms);
CanSensor_Message message; CanSensor_Message message;
if (CanSensor_Poll(&link, &message)) { /* принят идентификатор */ } if (CanSensor_Poll(&link, &message)) { /* принят идентификатор */ }
``` ```
Период можно менять во время работы через `CanSensor_SetPulsePeriod()`. Значение
`0` именно выключает heartbeat: кадры PULSE больше не ставятся в очередь. При
повторном включении первый кадр отправляется через полный заданный период.
## Порт STM32F1 ## Порт STM32F1
Порт не использует STM32 HAL и не занимает прерывания. Скопируйте Порт не использует STM32 HAL и не занимает прерывания. Скопируйте

View File

@@ -40,6 +40,7 @@ void CanSensor_ConfigDefault(CanSensor_Config *config)
} }
config->tx_id = CAN_SENSOR_DEFAULT_TX_ID; config->tx_id = CAN_SENSOR_DEFAULT_TX_ID;
config->rx_id = CAN_SENSOR_DEFAULT_RX_ID; config->rx_id = CAN_SENSOR_DEFAULT_RX_ID;
config->pulse_period_ms = 0U;
config->extended = 1U; config->extended = 1U;
config->retries = CAN_SENSOR_DEFAULT_RETRIES; 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->send_errors = 0U;
link->received_messages = 0U; link->received_messages = 0U;
link->dropped_frames = 0U; link->dropped_frames = 0U;
link->pulse_last_ms = 0U;
link->pulse_counter = 0U;
link->pulse_armed = 0U;
return 1U; return 1U;
} }
@@ -201,3 +205,51 @@ uint8_t CanSensor_Poll(CanSensor *link, CanSensor_Message *out)
} }
return 0U; 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--;
}
}

View File

@@ -27,9 +27,11 @@
/** Назначенный проекту тип сообщения SETCAN SETTINGS. */ /** Назначенный проекту тип сообщения SETCAN SETTINGS. */
#define CAN_SENSOR_MSGTYPE_SETTINGS 0xEU #define CAN_SENSOR_MSGTYPE_SETTINGS 0xEU
#define CAN_SENSOR_MSGTYPE_PULSE 0xFU
/** Маска полей SETCAN от Priority до MsgType; Body под маской не находится. */ /** Маска полей SETCAN от Priority до MsgType; Body под маской не находится. */
#define CAN_SENSOR_HEADER_MASK 0x1FFF0000UL #define CAN_SENSOR_HEADER_MASK 0x1FFF0000UL
#define CAN_SENSOR_ADDRESS_MASK 0x1FF00000UL
#define CAN_SENSOR_BODY_MASK 0x0000FFFFUL #define CAN_SENSOR_BODY_MASK 0x0000FFFFUL
/** Базовые ID SETTINGS для выбранных DeviceType=0x7 и DeviceID=0xF. */ /** Базовые ID SETTINGS для выбранных DeviceType=0x7 и DeviceID=0xF. */
@@ -88,6 +90,7 @@ typedef struct {
uint32_t rx_id; /**< База запроса 0x17FE0000, Body должен быть нулевым. */ uint32_t rx_id; /**< База запроса 0x17FE0000, Body должен быть нулевым. */
uint8_t extended; /**< 1 — Extended CAN ID. */ uint8_t extended; /**< 1 — Extended CAN ID. */
uint8_t retries; /**< Число попыток передачи. */ uint8_t retries; /**< Число попыток передачи. */
uint32_t pulse_period_ms; /**< Период PULSE; 0 полностью отключает PULSE. */
} CanSensor_Config; } CanSensor_Config;
/** Состояние транспорта и диагностические счётчики. */ /** Состояние транспорта и диагностические счётчики. */
@@ -98,6 +101,9 @@ typedef struct {
uint32_t send_errors; uint32_t send_errors;
uint32_t received_messages; uint32_t received_messages;
uint32_t dropped_frames; uint32_t dropped_frames;
uint32_t pulse_last_ms;
uint8_t pulse_counter;
uint8_t pulse_armed;
} CanSensor; } CanSensor;
void CanSensor_ConfigDefault(CanSensor_Config *config); 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); 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 */ #endif /* CAN_SENSOR_H */

View File

@@ -0,0 +1,79 @@
#include "can_sensor.h"
#include <stdio.h>
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;
}