STM32のUART/DMAで不定長を送受信

## 基本動作

`RTOS_threads`名前空間の`UART_DMA_TX<uint16_t buffer_length>`および`UART_DMA_RX<uint16_t buffer_length>`を使うことでUARTのDMA送信およびDMA受信を行う。

今回のコードはエラー対応は行っていない。GPSモジュールとかを受信するのに使おうとすると起動時に確率的なエラーが発生したりする。

今回のコードはデータの転送にQueueを使うので、大量のデータの転送には向かない。

## 使い方

あらかじめCubeMX等で初期化コードを作成し、ペリフェラル等の初期化を行っておく。TXとRXのDMAを作成し、RXはCircularモードに設定しておく。

適当に宣言を行う。

```c++
extern "C"
{
    extern UART_HandleTypeDef hlpuart1;
    extern osMessageQueueId_t stdin_queue;
    extern osMessageQueueId_t stdout_queue;
}

namespace
{
    using LPUART1_DMA_TX = RTOS_threads::UART_DMA_TX<128>;
    using LPUART1_DMA_RX = RTOS_threads::UART_DMA_RX<128>;
    LPUART1_DMA_TX *lpuart1_tx = nullptr;
    LPUART1_DMA_RX *lpuart1_rx = nullptr;
}
```

任意のタイミングでスレッドを起動する。

```c++
lpuart1_tx = LPUART1_DMA_TX::start("LPUART1 TX", osPriorityLow1, hlpuart1, stdout_queue);
lpuart1_rx = LPUART1_DMA_RX::start("LPUART1 RX", osPriorityLow1, hlpuart1, stdin_queue);
```

割り込みを転送する。

```c++
extern "C" void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *const huart, const uint16_t Size)
{
    if (lpuart1_rx)
    {
        lpuart1_rx->RxEventCallback(huart, Size);
    }
}

extern "C" void HAL_UART_TxCpltCallback(UART_HandleTypeDef *const huart)
{
    if (lpuart1_tx)
    {
        lpuart1_tx->TxCpltCallback(huart);
    }
}
```

これだけ!!

起動時に渡したキュー(`stdin_queue`、`stdout_queue`)を経由してUART転送を行うので、_writeで書いたり_readで読んだりすれば、printfやscanfをUARTで行える。
バッファの長さは少なくとも割り込み周期が数ミリ秒以上になるのを目安に設定しておく(長い分にはある程度好きに設定すればいい)。例えば115.2kbaudなら128バイトにしておけば最小の割り込み間隔は5ミリ秒くらいになる。

## 送信ライブラリ

```c++
#ifndef RTOS_threads_UART_DMA_TX_H
#define RTOS_threads_UART_DMA_TX_H

#include <stm32f4xx.h>
#include <cmsis_os.h>
#include <new>

namespace RTOS_threads
{
    template <uint16_t buffer_length>
    class UART_DMA_TX
    {
    public:
        inline void TxCpltCallback(const UART_HandleTypeDef *const huart)
        {
            if (huart->Instance == this->huart->Instance)
            {
                osThreadFlagsSet(thread_id, 0x1);
            }
        }

        static UART_DMA_TX *start(const char *const thread_name,
                              const osPriority_t priority,
                              UART_HandleTypeDef &huart,
                              const osMessageQueueId_t queue)
        {
            UART_DMA_TX *const instance = new (std::nothrow) UART_DMA_TX(huart, queue);

            if (!instance)
            {
                return nullptr;
            }

            const osThreadAttr_t attr = {
                thread_name,
                0,              // attribute
                nullptr, 0,     // control block
                nullptr, 0,     // stack
                priority, 0, 0, // prioritiy, tz_module, reserved
            };

            if (nullptr == osThreadNew(reinterpret_cast<osThreadFunc_t>(entry), instance, &attr))
            {
                delete instance;
                return nullptr;
            }

            return instance;
        }

    protected:
    private:
        static constexpr uint32_t dequeue_timeout = 1;

        UART_HandleTypeDef *const huart;
        const osMessageQueueId_t queue;
        uint8_t buff[buffer_length];
        osThreadId thread_id = nullptr;

        void task(void)
        {
            thread_id = osThreadGetId();

            for (;;)
            {
                uint16_t size = 0;
                for (size = 0; size < buffer_length; size++)
                {
                    if (osOK != osMessageQueueGet(queue, buff + size, nullptr,
                                                  size ? dequeue_timeout : osWaitForever))
                    {
                        break;
                    }
                }

                if (size)
                {
                    HAL_UART_Transmit_DMA(huart, buff, size);
                    osThreadFlagsWait(0x1, osFlagsWaitAny, osWaitForever);
                }
            }
        }

        UART_DMA_TX(UART_HandleTypeDef &huart, const osMessageQueueId_t queue)
            : huart(&huart), queue(queue), buff()
        {
        }

        ~UART_DMA_TX() {}

        UART_DMA_TX(const UART_DMA_TX &);
        UART_DMA_TX &operator=(const UART_DMA_TX &);

        // start以外からの呼び出しは禁止(shall not)
        static void entry(UART_DMA_TX *const instance)
        {
            if (instance)
            {
                instance->task();
                delete instance;
            }

            osThreadExit();
        }
    };
}

#endif
```

## 受信ライブラリ

```c++
#ifndef RTOS_threads_UART_DMA_RX_H
#define RTOS_threads_UART_DMA_RX_H

#include <stm32f4xx.h>
#include <cmsis_os.h>
#include <new>

namespace RTOS_threads
{
    template <uint16_t buffer_length>
    class UART_DMA_RX
    {
    public:
        inline void RxEventCallback(const UART_HandleTypeDef *const huart, const uint16_t Size)
        {
            if (huart->Instance == this->huart->Instance)
            {
                osMessageQueuePut(event_queue, &Size, 0, 0);
            }
        }

        static UART_DMA_RX *start(const char *const thread_name,
                              const osPriority_t priority,
                              UART_HandleTypeDef &huart,
                              const osMessageQueueId_t queue)
        {
            UART_DMA_RX *const instance = new (std::nothrow) UART_DMA_RX(huart, queue);

            if (!instance)
            {
                return nullptr;
            }

            const osThreadAttr_t attr = {
                thread_name,
                0,              // attribute
                nullptr, 0,     // control block
                nullptr, 0,     // stack
                priority, 0, 0, // priority, tz_module, reserved
            };

            if (nullptr == osThreadNew(reinterpret_cast<osThreadFunc_t>(entry), instance, &attr))
            {
                delete instance;
                return nullptr;
            }

            return instance;
        }

    protected:
    private:
        static constexpr uint16_t event_queue_size = 4;

        UART_HandleTypeDef *const huart;
        const osMessageQueueId_t queue;
        const osMessageQueueId_t event_queue;
        uint8_t buff[buffer_length];

        void task(void)
        {
            if (!event_queue)
            {
                return;
            }

            HAL_UARTEx_ReceiveToIdle_DMA(huart, buff, buffer_length);

            uint16_t prev_pos = 0;

            for (;;)
            {
                uint16_t pos = 0;
                osMessageQueueGet(event_queue, &pos, nullptr, osWaitForever);

                // 1バイトずつバッファからキューへ転送
                for (; prev_pos < pos; prev_pos++)
                {
                    osMessageQueuePut(queue, &buff[prev_pos], 0, 1);
                }

                // N<=prev_posの際にprev_posからNを引く
                // 必ずprev_pos<=Nになるので==で比較して0代入でも問題ないが、念のために<=で比較して-=で減算する。
                if (buffer_length <= prev_pos)
                {
                    prev_pos -= buffer_length;
                }
            }
        }

        UART_DMA_RX(UART_HandleTypeDef &huart, const osMessageQueueId_t queue)
            : huart(&huart), queue(queue),
              event_queue(osMessageQueueNew(event_queue_size, sizeof(uint16_t), nullptr)), buff()
        {
        }

        ~UART_DMA_RX()
        {
            osMessageQueueDelete(event_queue);
        }

        UART_DMA_RX(const UART_DMA_RX &);
        UART_DMA_RX &operator=(const UART_DMA_RX &);

        // start以外からの呼び出しは禁止(shall not)
        static void entry(UART_DMA_RX *const instance)
        {
            if (instance)
            {
                instance->task();
                delete instance;
            }

            osThreadExit();
        }
    };
}

#endif
```