# Cyphal CAN

Когда устройство становится достаточно сложным, появляется потребность в коммуникации между контроллерами, или между контроллером и компьютером (например, Raspberry Pi). Для этого в микроконтроллерах STM32G4 встроен модуль FDCAN. Этот протокол является популярным и достаточно гибким способом построения коммуникационных сетей, однако он не предоставляет никаких абстракций над пакетами байтов, которыми оперирует.&#x20;

Для целей общения между устройствами, нами был выбран прокол [Cyphal](https://opencyphal.org/), создающий слой абстракции над FDCAN без потерь в скорости и дающий возможность передавать структурированные сообщения произвольной длины, а не просто байты.

{% hint style="info" %}
У этого протокола есть и множество других достоинств, описание всего протокола выходит за рамки этой докуменации, подробнее о нем можно прочитать [тут](https://opencyphal.org/specification/Cyphal_Specification.pdf).
{% endhint %}

***

## libcxxcanard

Нами была разработана и проверена на множестве устройств универсальная C++ библиотека для работы с cyphal (на основе *libcanard*) - [libcxxcanard](https://github.com/VBCores/libcxxcanard). Далее будет дан обзор возможностей и типичных примеров использования данной библиотеки.

{% hint style="info" %}
Подробная документация - [read-the-docs](https://libcxxcanard.readthedocs.io/en/latest/)

Примеры -  [libcyphal-docs](https://github.com/VBCores/libcyphal-docs/tree/master)
{% endhint %}

<details>

<summary>Короткое сравнение с libcanard</summary>

libcxxcanard основана на libcanard и призвана упростить ее использование. Далее несколько наглядных сравнений "на пальцах" (на картинки лучше нажать, чтоб рассмотреть подробнее):

![](/files/VpeA1V9kySFyUbK3PQm7)

![](/files/yU27BwW0D8Uoyug4aQWe)

</details>

Дальше будут параллельно разбираться примеры для linux (raspberry pi) и stm32g4. Считается, что сделаны следующие инклюды:

```cpp
#include <cyphal/cyphal.h>
#include <cyphal/allocators/o1/o1_allocator.h>
#include <cyphal/subscriptions/subscription.h>
```

### Инициализация

Код инициализации короткий и кроссплатформенный, надо только выбрать нужный провайдер:

{% tabs %}
{% tab title="linux" %}

```cpp
#include <cyphal/providers/LinuxCAN.h>

std::shared_ptr<CyphalInterface> cyphal_interface;

void main() {
    cyphal_interface = std::shared_ptr<CyphalInterface>(CyphalInterface::create_heap<LinuxCAN, O1Allocator>(
        NODE_ID,
        "can0",         // SocketCAN
        200,            // длина очереди
        DEFAULT_CONFIG  // дефолтные настройки для линукса, объявлены в библиотеке
    ));
}
```

{% endtab %}

{% tab title="stm32" %}

```cpp
#include <cyphal/providers/G4CAN.h>

static std::shared_ptr<CyphalInterface> cyphal_interface;

// Необходимо определить для репорта ошибок - просто вызовем дефолтный хэндлер
// Но можно делать и что-то более разумное - например:
// https://github.com/voltbro/gyrobro_foc/blob/feature/mech-angle/App/communication.cpp#L47
void error_handler() {
    Error_Handler();
}
// micros_64 - надо объявить отдельно, с помощью таймера
// или для ленивых HAL_GetTick()*1000
UtilityConfig utilities(micros_64, cyphal_error_handler);

void main() {
    cyphal_interface = std::shared_ptr<CyphalInterface>(CyphalInterface::create_heap<G4CAN, O1Allocator>(
        NODE_ID,
        &hfdcan1,  // считаем что настроен, например через CubeMX
        200,       // длина очереди
        utilities  // объявлен выше
    ));
}
```

{% endtab %}
{% endtabs %}

### Подписки и отправка сообщений

По сравнению с libcanard, работа с отправкой/приемом сообщений гораздо проще. Есть единственное неудобство: из-за того что libcxxcanard работает поверх сишного libcanard, для любого используемого типа сообщений нужно (**единожды**) использовать макрос `TYPE_ALIAS` - `TYPE_ALIAS(УдобноеНовоеНазваниеТипа, сгенерированный_cyhal_тип_сообщения)`.

Минимальный пример, echo-сервиса:

{% tabs %}
{% tab title="linux" %}

```cpp
#include <voltbro/echo/echo_servoce_1_0.h>

TYPE_ALIAS(EchoRequest, voltbro_echo_echo_service_Request_1_0)
TYPE_ALIAS(EchoResponse, voltbro_echo_echo_service_Response_1_0)

// Подписки наследуются от шаблонного класса AbstractSubscription
// 1000 - port_id для сервиса
class EchoSub: public AbstractSubscription<EchoRequest> {
public:
    EchoSub(InterfacePtr interface):
        AbstractSubscription<EchoRequest>(interface, 1000) 
        {};
    void handler(const EchoRequest::Type& request, CanardRxTransfer* transfer) override {
        EchoResponse::Type response = {.pong = request.ping};
        interface->send_response<EchoResponse>(&response, transfer);
    }
};

std::shared_ptr<EchoSub> echo_sub;

void main() {
    // ... сначала тут описанная выше инициализация cyphal
    echo_sub = std::make_shared<EchoSub>(cyphal_interface);
}
```

{% endtab %}

{% tab title="stm32" %}

```cpp
#include <voltbro/echo/echo_servoce_1_0.h>

TYPE_ALIAS(EchoRequest, voltbro_echo_echo_service_Request_1_0)
TYPE_ALIAS(EchoResponse, voltbro_echo_echo_service_Response_1_0)

// Подписки наследуются от шаблонного класса AbstractSubscription
// 1000 - port_id для сервиса
class EchoSub: public AbstractSubscription<EchoRequest> {
public:
    EchoSub(InterfacePtr interface):
        AbstractSubscription<EchoRequest>(interface, 1000) 
        {};
    void handler(const EchoRequest::Type& request, CanardRxTransfer* transfer) override {
        EchoResponse::Type response = {.pong = request.ping};
        interface->send_response<EchoResponse>(&response, transfer);
    }
};

std::shared_ptr<EchoSub> echo_sub;

void main() {
    // ... сначала тут описанная выше инициализация cyphal
    echo_sub = std::make_shared<EchoSub>(cyphal_interface);
}

// Пример настройки хардварных фильтров для подписки, актуально только для STM32
void setup_filters() {
    HAL_FDCAN_ConfigGlobalFilter(
        &hfdcan1,
        FDCAN_REJECT,
        FDCAN_REJECT,
        FDCAN_REJECT_REMOTE,
        FDCAN_REJECT_REMOTE
    );

    static FDCAN_FilterTypeDef sFilterConfig;

    // Хардварный фильтр для пордписки. apply_filter - из libcxxcanard
    apply_filter(&hfdcan1, &sFilterConfig,echo_sub->make_filter(NODE_ID))
}
```

{% endtab %}
{% endtabs %}

Развернутый пример подписчика на обычные сообщения, связанного с ROS (для linux):

```cpp
#include <voltbro/battery/state_1_0.h>
#include <sensor_msgs/msg/battery_state.hpp>

TYPE_ALIAS(BatteryState, voltbro_battery_state_1_0)

class BatteryService : public AbstractSubscription<BatteryState> {
private:
    rclcpp::Publisher<sensor_msgs::msg::BatteryState>::SharedPtr publisher;
    rclcpp::Logger logger;
public:
    BatteryService(rclcpp::Node* node, const std::shared_ptr<CyphalInterface> interface)
        : AbstractSubscription<BatteryState>(interface, 7993),
          logger(node->get_logger().get_child("battery")) {
        std::string topic_name = "/bat";
        publisher = node->create_publisher<sensor_msgs::msg::BatteryState>(topic_name, 5);
        RCLCPP_INFO(logger, "Publishing topic <%s>", topic_name.c_str());
    }
    void handler(
        const BatteryState::Type& bat_info,
        CanardRxTransfer* transfer
    ) {
        sensor_msgs::msg::BatteryState battery;
        
        // Копируем поля
        battery.voltage = bat_info.voltage.volt;
        // ... и т.д.
        
        publisher->publish(battery);
    }
};
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://voltbro.gitbook.io/vbcores/tutorials/cyphal-can.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
