Добавить переносимый планировщик PULSE
This commit is contained in:
22
c/can-sensor/CMakeLists.txt
Normal file
22
c/can-sensor/CMakeLists.txt
Normal 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()
|
||||||
@@ -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 и не занимает прерывания. Скопируйте
|
||||||
|
|||||||
@@ -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--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 */
|
||||||
|
|||||||
79
c/can-sensor/tests/test_can_sensor.c
Normal file
79
c/can-sensor/tests/test_can_sensor.c
Normal 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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user