From 64353965efd491541e20e1eedc20d79d442ca959 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Wed, 24 Jun 2026 23:41:22 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A1=D0=B3=D0=B5=D0=BD=D0=B5=D1=80=D0=B8?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=BE=20=D0=98=D0=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Inc/DS_SN65HVD230.h | 40 ++++++++++ README.md | 174 ++++++++++++++++++++++++++++++++++++++++++++ Src/DS_SN65HVD230.c | 92 +++++++++++++++++++++++ 3 files changed, 306 insertions(+) create mode 100644 Inc/DS_SN65HVD230.h create mode 100644 Src/DS_SN65HVD230.c diff --git a/Inc/DS_SN65HVD230.h b/Inc/DS_SN65HVD230.h new file mode 100644 index 0000000..71f8e8a --- /dev/null +++ b/Inc/DS_SN65HVD230.h @@ -0,0 +1,40 @@ +#ifndef DS_SN65HVD230_H +#define DS_SN65HVD230_H + +#include + +#ifdef STM32G030xx +#include "stm32g0xx_hal.h" +#endif + +#ifdef STM32G0B1xx +#include "stm32g0xx_hal.h" +#endif + + +/* Управляющий дескриптор для STM32G0 */ +typedef struct { + FDCAN_HandleTypeDef *hfdcan; // Указатель на аппаратный FDCAN в STM32G0 +} SN65HVD230_t; + +/* Унифицированная структура кадра (Классический CAN-режим на FDCAN) */ +typedef struct { + uint32_t Identifier; // ID пакета (11 или 29 бит) + uint32_t IdType; // Тип: FDCAN_STANDARD_ID или FDCAN_EXTENDED_ID + uint32_t TxFrameType; // Тип кадра: FDCAN_DATA_FRAME или FDCAN_REMOTE_FRAME + uint32_t DataLength; // Количество байт данных: от FDCAN_DLC_BYTES_0 до FDCAN_DLC_BYTES_8 + uint8_t Data[8]; // Буфер данных (макс 8 байт для классического CAN) +} CAN_Message_t; + +/* Прототипы функций управления */ +HAL_StatusTypeDef SN65HVD230_Init(SN65HVD230_t *node, FDCAN_HandleTypeDef *hfdcan_handle); +HAL_StatusTypeDef SN65HVD230_Start(SN65HVD230_t *node); +HAL_StatusTypeDef SN65HVD230_Stop(SN65HVD230_t *node); + +/* Функции отправки и приема */ +HAL_StatusTypeDef SN65HVD230_Transmit(SN65HVD230_t *node, CAN_Message_t *msg, uint32_t timeout_ms); +HAL_StatusTypeDef SN65HVD230_Receive(SN65HVD230_t *node, CAN_Message_t *msg, uint32_t timeout_ms); + + + +#endif // DS_SN65HVD230_H \ No newline at end of file diff --git a/README.md b/README.md index 64917a9..e9f7a3a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,176 @@ # SN65HVD230 +### Драйвер трансивера SN65HVD230 для STM32G0 (FDCAN) + +Легковесная, объектно-ориентированная библиотека на Си для работы с CAN-трансивером **SN65HVD230** (на базе плат типа MCU-230 / CJMCU-230) через аппаратный модуль **FDCAN** микроконтроллеров семейства **STM32G0**. + +Библиотека оптимизирована для работы в режиме классического CAN (Classic CAN) на максимальной скорости, так как на используемом модуле пин управления энергосбережением (`Rs`) аппаратно подтянут к GND. + +* * * + +### 📌 Особенности реализации + +* **Чистый ООП-стиль на Си**: вся работа идет через дескриптор устройства `SN65HVD230_t`. +* **Специализировано под STM32G0**: код очищен от лишних макросов препроцессора и завязан строго на архитектуру модуля FDCAN. +* **Безопасность памяти**: полное отсутствие динамического выделения памяти (`malloc`). +* **Контроль таймаутов**: функции отправки и приема снабжены блокирующими тайм-аутами для предотвращения зависания контроллера при сбоях на физическом уровне шины. + +* * * + +### 🛠 Настройка периферии в STM32CubeMX + +Для корректной работы библиотеки настройте модуль FDCAN в графическом конфигураторе: + +1. В разделе **Connectivity** выберите **FDCAN1**. +2. Установите режим **Operating Mode** в значение **Architecture**. +3. **Настройка тактирования (Bit Timings)**: + * Настройте параметры `Nominal Prescaler`, `Time Seg1` и `Time Seg2` под требуемую скорость вашей сети (например, 250 или 500 кбит/с). + * Режим `Frame Format` установите в **Classic CAN**. +4. Вкладка **NVIC Settings**: + * Включите прерывание **FDCAN1 interrupt 0** (необходимо для работы системы уведомлений и прерываний). + +* * * + +### 🔌 Схема подключения (MCU-230 <-> STM32G0) + +Контакт модуля MCU-230 + +Контакт STM32G0 + +Описание + +**3V3** + +3.3V + +Питание модуля (от шины МК) + +**GND** + +GND + +Общая земля + +**CTX** + +FDCAN1\_TX + +Линия передачи данных (настраивается в CubeMX) + +**CRX** + +FDCAN1\_RX + +Линия приема данных (настраивается в CubeMX) + +**CANH** + +Линия CAN\_H + +Прямой дифференциальный сигнал шины + +**CANL** + +Линия CAN\_L + +Инверсный дифференциальный сигнал шины + +⚠️ **Важное замечание по терминатору 120 Ом:** +На плате MCU-230 уже распаян SMD-резистор `121` (120 Ом). + +* Если в вашей тестовой сети всего **два устройства** — оставьте его. +* Если вы подключаете этот модуль к **готовой существующей сети**, где на концах уже установлены терминаторы — обязательно **выпаяйте резистор 120 Ом** с платы модуля, чтобы не просаживать импеданс шины. + +* * * + +### 🚀 Быстрый старт (Пример использования) + +Добавьте файлы `sn65hvd230_g0.h` и `sn65hvd230_g0.c` в свой проект. Скопируйте следующую логику в файл `main.c`: + +c + + #include "sn65hvd230_g0.h" + + /* Хэндлер FDCAN, сгенерированный CubeMX */ + extern FDCAN_HandleTypeDef hfdcan1; + + /* Создаем глобальный дескриптор нашего CAN-узла */ + SN65HVD230_t can_node; + + void main(void) { + // ... Автоматическая инициализация HAL, SystemClock, GPIO ... + // MX_FDCAN1_Init(); // Инициализация FDCAN из CubeMX + + /* 1. Аппаратный сброс фильтров (Разрешаем прием ВСЕХ пакетов для теста) */ + FDCAN_FilterTypeDef filter; + filter.IdType = FDCAN_STANDARD_ID; + filter.FilterIndex = 0; + filter.FilterType = FDCAN_FILTER_RANGE; + filter.FilterConfig = FDCAN_FILTER_TO_RXFIFO0; + filter.FilterID1 = 0x000; + filter.FilterID2 = 0x7FF; + HAL_FDCAN_ConfigFilter(&hfdcan1, &filter); + + /* 2. Инициализация структуры трансивера */ + SN65HVD230_Init(&can_node, &hfdcan1); + + /* 3. Запуск FDCAN шины */ + SN65HVD230_Start(&can_node); + + /* 4. Подготовка пакета для отправки */ + CAN_Message_t tx_msg; + tx_msg.Identifier = 0x123; // ID сообщения + tx_msg.IdType = FDCAN_STANDARD_ID; // Стандартный 11-битный формат + tx_msg.TxFrameType = FDCAN_DATA_FRAME; // Кадр данных + tx_msg.DataLength = FDCAN_DLC_BYTES_4; // Будем отправлять 4 байта + tx_msg.Data[0] = 0xDE; + tx_msg.Data[1] = 0xAD; + tx_msg.Data[2] = 0xBE; + tx_msg.Data[3] = 0xEF; + + while (1) { + /* Отправляем сообщение раз в секунду с таймаутом ожидания 50 мс */ + SN65HVD230_Transmit(&can_node, &tx_msg, 50); + HAL_Delay(1000); + + /* Неблокирующий опрос входящих сообщений (timeout = 0) */ + CAN_Message_t rx_msg; + if (SN65HVD230_Receive(&can_node, &rx_msg, 0) == HAL_OK) { + // Если пришел пакет с ожидаемым ID, обрабатываем буфер rx_msg.Data + if (rx_msg.Identifier == 0x500) { + // Ваша логика обработки данных здесь... + } + } + } + } + + +Используйте код с осторожностью. + +* * * + +### 📝 API Справочник функций + +### `HAL_StatusTypeDef SN65HVD230_Init(SN65HVD230_t *node, FDCAN_HandleTypeDef *hfdcan_handle)` + +Связывает структуру дескриптора со сгенерированным в CubeMX аппаратным хэндлером FDCAN. + +* **Возвращает**: `HAL_OK` при успехе, `HAL_ERROR` если переданы пустые указатели (`NULL`). + +### `HAL_StatusTypeDef SN65HVD230_Start(SN65HVD230_t *node)` + +Включает прерывания по получению новых данных в FIFO 0 и переводит модуль FDCAN из режима инициализации в активный режим работы на шине. + +### `HAL_StatusTypeDef SN65HVD230_Stop(SN65HVD230_t *node)` + +Переводит модуль FDCAN в режим инициализации, безопасно отключая узел от CAN-сети. + +### `HAL_StatusTypeDef SN65HVD230_Transmit(SN65HVD230_t *node, CAN_Message_t *msg, uint32_t timeout_ms)` + +Отправляет классический CAN-кадр в шину. Если TX FIFO заполнено, функция будет ожидать освобождения ячейки в течение времени `timeout_ms`. + +### `HAL_StatusTypeDef SN65HVD230_Receive(SN65HVD230_t *node, CAN_Message_t *msg, uint32_t timeout_ms)` + +Вычитывает входящий CAN-кадр из буфера FIFO 0 периферии. Если буфер пуст, функция ожидает появления сообщения в течение `timeout_ms`. + +* Для неблокирующего опроса шины вызывайте функцию с аргументом `timeout_ms = 0`. \ No newline at end of file diff --git a/Src/DS_SN65HVD230.c b/Src/DS_SN65HVD230.c new file mode 100644 index 0000000..c8b8861 --- /dev/null +++ b/Src/DS_SN65HVD230.c @@ -0,0 +1,92 @@ +#include "sn65hvd230_g0.h" + +/** + * @brief Инициализация структуры и привязка к FDCAN в STM32G0 + */ +HAL_StatusTypeDef SN65HVD230_Init(SN65HVD230_t *node, FDCAN_HandleTypeDef *hfdcan_handle) { + if (node == NULL || hfdcan_handle == NULL) { + return HAL_ERROR; + } + + node->hfdcan = hfdcan_handle; + return HAL_OK; +} + +/** + * @brief Запуск периферийного модуля FDCAN + */ +HAL_StatusTypeDef SN65HVD230_Start(SN65HVD230_t *node) { + if (node == NULL || node->hfdcan == NULL) return HAL_ERROR; + + // Активируем прерывание по заполнению FIFO 0 (потребуется, если включите IT-режим в CubeMX) + HAL_FDCAN_ActivateNotification(node->hfdcan, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0); + + return HAL_FDCAN_Start(node->hfdcan); +} + +/** + * @brief Остановка модуля FDCAN + */ +HAL_StatusTypeDef SN65HVD230_Stop(SN65HVD230_t *node) { + if (node == NULL || node->hfdcan == NULL) return HAL_ERROR; + return HAL_FDCAN_Stop(node->hfdcan); +} + +/** + * @brief Отправка пакета данных в шину (Классический CAN-кадр через FDCAN) + */ +HAL_StatusTypeDef SN65HVD230_Transmit(SN65HVD230_t *node, CAN_Message_t *msg, uint32_t timeout_ms) { + if (node == NULL || node->hfdcan == NULL || msg == NULL) return HAL_ERROR; + + uint32_t tickstart = HAL_GetTick(); + FDCAN_TxHeaderTypeDef TxHeader; + + // Конфигурация заголовка кадра в стиле G0 FDCAN + TxHeader.Identifier = msg->Identifier; + TxHeader.IdType = msg->IdType; + TxHeader.TxFrameType = msg->TxFrameType; + TxHeader.DataLength = msg->DataLength; // Константы типа FDCAN_DLC_BYTES_8 + TxHeader.ErrorStateIndicator = FDCAN_ESI_ACTIVE; + TxHeader.BitRateSwitch = FDCAN_BRS_OFF; // Выключаем ускорение данных (для классического CAN) + TxHeader.FDFormat = FDCAN_CLASSIC_CAN; // Работаем в режиме совместимости с обычным CAN + TxHeader.TxEventFifoControl = FDCAN_NO_TX_EVENTS; + TxHeader.MessageMarker = 0; + + // Ожидаем, пока в TX FIFO освободится хотя бы одна ячейка + while (HAL_FDCAN_GetTxFifoFreeLevel(node->hfdcan) == 0) { + if ((HAL_GetTick() - tickstart) > timeout_ms) { + return HAL_TIMEOUT; + } + } + + // Помещаем сообщение в очередь отправки FDCAN + return HAL_FDCAN_AddMessageToTxFifoQ(node->hfdcan, &TxHeader, msg->Data); +} + +/** + * @brief Опрашивающий (блокирующий) прием кадра из FIFO 0 + */ +HAL_StatusTypeDef SN65HVD230_Receive(SN65HVD230_t *node, CAN_Message_t *msg, uint32_t timeout_ms) { + if (node == NULL || node->hfdcan == NULL || msg == NULL) return HAL_ERROR; + + uint32_t tickstart = HAL_GetTick(); + FDCAN_RxHeaderTypeDef RxHeader; + + // Ожидаем появления нового сообщения в FIFO 0 + while (HAL_FDCAN_GetRxFifoFillLevel(node->hfdcan, FDCAN_RX_FIFO0) == 0) { + if ((HAL_GetTick() - tickstart) > timeout_ms) { + return HAL_TIMEOUT; + } + } + + // Вычитываем данные из FIFO 0 + if (HAL_FDCAN_GetRxMessage(node->hfdcan, FDCAN_RX_FIFO0, &RxHeader, msg->Data) == HAL_OK) { + msg->Identifier = RxHeader.Identifier; + msg->IdType = RxHeader.IdType; + msg->TxFrameType = RxHeader.RxFrameType; + msg->DataLength = RxHeader.DataLength; + return HAL_OK; + } + + return HAL_ERROR; +}