Страница 1 из 1

STM32 + DMA + RS485, буфер считывает эхо

Добавлено: Вт июл 22, 2025 22:34:36
radiokayro13
Всем привет! В программировании МК новичок, пытаюсь заставить работать UART + DMA + RS485 на STM32L433cct6.

Проблема в том, что в буффер попадает отправленная команда, связывал с тем, что не успевает опуститься DE пин и МК считывает свою же команду с линии, победить смог только добавив адресный байт и фильтруя по нему.
Вторым странным фактом является то, что без HAL_Delay в SendCommand сообщение не отправляется.
Ну и вишенкой на торте: на другой МК сообщение отправляется поврежденное, первые 4 байта как и должны, а остальные какой-то шум.

Подозреваю, что проблемы именно в коде, кажется, я тут много лишнего накрутил, помогите найти ошибки, чтобы исправить и улучшить свои навыки)

Код для uart_dma.hpp

Код: Выделить всё

    class DMA final {
    public:
        explicit DMA(UART_HandleTypeDef* huart, CRC_HandleTypeDef* hcrc);
        ~DMA();
        DMA(const DMA&) = delete;
        DMA& operator=(const DMA&) = delete;

        // Initialization
        void Initialize();

        // Commands
        void RegisterCommand(uint8_t cmd, BKUCommand::Command* handler);
        void SendCommand(uint8_t id, uint16_t data);

        // Transport operations
        void HandleBuffer();
        void HandleTxComplete();
        void HandleError();
        void HandleRxComplete();
        void HandleTimComplete();

    private:
        // Hardware handles
        UART_HandleTypeDef* _huart;
        CRC_HandleTypeDef* _hcrc;

        // Commands
        static constexpr uint8_t START_BYTE = 0xA;
        static constexpr size_t FRAME_SIZE = 7;
        static constexpr size_t MAX_COMMANDS = 8;

        struct CommandHandler {
            uint8_t cmd;
            BKUCommand::Command* handler;
        };
        CommandHandler _handlers[MAX_COMMANDS];
        uint8_t _handler_count = 0;

        // DMA
        static constexpr size_t DMA_BUFFER_SIZE = 16;
        uint8_t _dma_buffer[DMA_BUFFER_SIZE];
        uint32_t _dma_pos = 0;
        uint32_t _last_sent_time = 0;


        // CRC
        uint16_t calculateCRC(const uint8_t* data) const;

        // Frame
        void processFrame(const uint8_t* frame);
        bool validateFrame(const uint8_t* frame) const;

        // Tx
        volatile bool _is_sending = false;

        // Helper functions
        BKUCommand::Command* findHandler(uint8_t cmd);

    };
Код для uart_dma.cpp

Код: Выделить всё

#include "uart_dma.hpp"
#include <string.h>

#define ADDRESS 0xF

namespace UART {

    DMA::DMA(UART_HandleTypeDef* huart, CRC_HandleTypeDef* hcrc) :
        _huart(huart),
        _hcrc(hcrc),
        _handler_count(0) {
        memset(_dma_buffer, 0, DMA_BUFFER_SIZE);
    }

    DMA::~DMA() {
        HAL_UART_DMAStop(_huart);
    }

    void DMA::Initialize() {
        HAL_CRC_Init(_hcrc);
        HAL_UART_Receive_DMA(_huart, _dma_buffer, DMA_BUFFER_SIZE);
    }

    void DMA::RegisterCommand(uint8_t cmd, BKUCommand::Command* handler) {
        if (_handler_count < MAX_COMMANDS) {
            _handlers[_handler_count].cmd = cmd;
            _handlers[_handler_count].handler = handler;
            _handler_count++;
        }
    }

    void DMA::SendCommand(uint8_t cmd, uint16_t data) {
    	if (__HAL_UART_GET_FLAG(_huart, UART_FLAG_BUSY)) {
			return; // Ждем завершения предыдущей передачи
		}

		_is_sending = true;
		uint8_t frame[FRAME_SIZE] = {
			START_BYTE,
			BKU_ADDRESS,
			cmd,
			static_cast<uint8_t>(data >> 8),
			static_cast<uint8_t>(data & 0xFF)
		};

		uint16_t crc = calculateCRC(frame);
		frame[5] = crc >> 8;
		frame[6] = crc & 0xFF;

		BKUCommand::Command* handler = findHandler(cmd);
		if (handler != nullptr) {
			handler->SetRequestTime(HAL_GetTick());
		}

		HAL_Delay(30);

		HAL_UART_Transmit_DMA(_huart, frame, FRAME_SIZE);
    }


    BKUCommand::Command* DMA::findHandler(uint8_t cmd) {
        for (uint8_t i = 0; i < _handler_count; i++) {
            if (_handlers[i].cmd == cmd) {
                return _handlers[i].handler;
            }
        }
        return nullptr;
    }

    void DMA::HandleTxComplete() {
        _is_sending = false;
    }

    void DMA::HandleRxComplete() {}

    void DMA::HandleError() {
    	_is_sending = false;

		_dma_pos = 0;
		HAL_UART_DMAStop(_huart);
		HAL_UART_Receive_DMA(_huart, _dma_buffer, DMA_BUFFER_SIZE);
		__HAL_UART_CLEAR_FLAG(_huart, UART_FLAG_ORE | UART_FLAG_NE | UART_FLAG_FE);
    }

    void DMA::HandleBuffer() {
		uint32_t current_pos = DMA_BUFFER_SIZE - __HAL_DMA_GET_COUNTER(_huart->hdmarx);
		uint32_t bytes_to_process = (current_pos - _dma_pos) % DMA_BUFFER_SIZE;
		if (bytes_to_process < FRAME_SIZE) {
			return;
		}

		// Защита от переполнения буфера
		if ((current_pos - _dma_pos) % DMA_BUFFER_SIZE > DMA_BUFFER_SIZE/2) {
			_dma_pos = current_pos;
			return;
		}

		while (_dma_pos != current_pos) {
			// Если найден стартовый байт и достаточно данных для проверки
			if (_dma_buffer[_dma_pos] == START_BYTE) {
				uint8_t frame[FRAME_SIZE];
				bool frame_complete = true;

				// Проверяем, есть ли полный фрейм в буфере
				for (int i = 0; i < FRAME_SIZE; i++) {
					uint32_t pos = (_dma_pos + i) % DMA_BUFFER_SIZE;
					if (pos == current_pos) {  // Достигли текущей позиции DMA
						frame_complete = false;
						break;
					}
					frame[i] = _dma_buffer[pos];
				}

                if (validateFrame(frame)) {
                    processFrame(frame);
                    _dma_pos = (_dma_pos + FRAME_SIZE) % DMA_BUFFER_SIZE;
                    continue;  // Пропускаем инкремент позиции
                } else {
                    // Невалидный фрейм - пропускаем только стартовый байт
                    _dma_pos = (_dma_pos + 1) % DMA_BUFFER_SIZE;
                    continue;
                }
            }
			// Переходим к следующему байту
			_dma_pos = (_dma_pos + 1) % DMA_BUFFER_SIZE;
		}
    }

    void DMA::processFrame(const uint8_t* frame) {
        uint8_t cmd = frame[2];
        uint16_t data = (frame[3] << 8) | frame[4];

        BKUCommand::Command* handler = findHandler(cmd);
        if (handler != nullptr) {
            handler->ProcessPayload(data);
        }
    }

    bool DMA::validateFrame(const uint8_t* frame) const {
		if (frame[0] != START_BYTE) {
			return false;
		}
		if (frame[1] != ADDRESS) {
			return false;
		}

		uint16_t received_crc = (frame[5] << 8) | frame[6]; // Байты CRC в фрейме
		uint16_t calculated_crc = calculateCRC(frame);

		return true;
    }

    uint16_t DMA::calculateCRC(const uint8_t* data) const {
        // Temporary buffer for alignment
        uint32_t aligned_data[2] = {0};
        memcpy(aligned_data, data, 5);

        // Hardware CRC calculation
        uint32_t crc = HAL_CRC_Calculate(_hcrc, aligned_data, 1); // 4 bytes = 1 word
        return ~crc & 0xFFFF; // Inversion for CRC-16 standard compliance
    }

}


Re: STM32 + DMA + RS485, буфер считывает эхо

Добавлено: Ср июл 23, 2025 09:28:19
HardWareMan
[uquote="radiokayro13",url="/forum/viewtopic.php?p=4733203#p4733203"]Проблема в том, что в буффер попадает отправленная команда, связывал с тем, что не успевает опуститься DE пин и МК считывает свою же команду с линии, победить смог только добавив адресный байт и фильтруя по нему.[/uquote]
Если не нужно эхо, то приёмник надо отключать до первого байта отправки. С другой стороны, эхо у полудуплексных линий используется для контроля отсутствия коллизий в "эфире".

Re: STM32 + DMA + RS485, буфер считывает эхо

Добавлено: Ср июл 23, 2025 11:25:08
tonyk
radiokayro13 писал(а):Подозреваю, что проблемы именно в коде
Зачем аппаратный CRC, когда используете программный? Оставьте его для USB и Ethernet, в которых он реально нужен.
Где управление трансивером? Или он у вас с автодектом? Или UART им управляет?
Приём через DMA не_требует защиты от переполнения. Нужен только один дополнительный байт для проверки правильной длины посылки.

Странно у вас там всё. Вам нужно обработать два прерывания: первое от DMA по окончании отправки всего блока, которое разрешит прерывание от UART, и второе от UART, когда в линию уйдёт последний байт посылки. Прерывании от UART означает, что посылка ушла в линию и можно сигнализировать об окончании передачи и переключать трансивер на приём.

Добавлено after 32 minutes 32 seconds:
Настройку UART не вижу.