Newer
Older
stm32 / common / molelord / MoleUsart.c
/**
 * @file MoleUsart.c
 * @brief USARTを使ってprintf()するための実装
 * @details
 * 使用許諾は The MIT License です。
 * 参照 https://opensource.org/licenses/mit-license.php
 * @copyright
 * Copyright (c) 2020 molelord
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

#include "main.h"
#include <stdlib.h>   // abort()
#include "molelord/MoleUsart.h"
#include "FreeRTOS.h"
#include "queue.h"    // xQueueCreate()

//! MoleUsart型を最大いくつ扱うか
#define NUM_OF_MOLEUSART 2

#define TXQ_LENGTH  8
#define RXQ_LENGTH 16
#define QITEM_SIZE sizeof(char)

/*!
 * MoleUsart_IRQHandler()において、LLドライバのUSARTインスタンスの
 * ポインタ値をキーにしてMoleUsartインスタンスを特定するために、
 * 配列にMoleUsartインスタンスへのポインタを記録しておく。
 */
static MoleUsart_td *l_inst[NUM_OF_MOLEUSART];
static int32_t      l_count = 0;

void
MoleUsart_construct(MoleUsart_td *inst, USART_TypeDef *usart)
{
    assert_param(0 <= l_count && l_count < NUM_OF_MOLEUSART);

    inst->usart = usart;
    inst->txQ   = xQueueCreate(TXQ_LENGTH, QITEM_SIZE);
    if (inst->txQ == NULL) abort();
    inst->rxQ   = xQueueCreate(RXQ_LENGTH, QITEM_SIZE);
    if (inst->rxQ == NULL) abort();

    int32_t i;
    for (i = 0; i < NUM_OF_MOLEUSART; i++)
    {
        if (l_inst[i] == NULL)
        {
            l_inst[i] = inst;
            goto FOUND;
        }
    }
    abort(); //! l_instの中に空きが見つからなかった

FOUND:
    l_count++;
}

void
MoleUsart_enableIT(MoleUsart_td *inst)
{
    LL_USART_EnableIT_RXNE(inst->usart);
}

void
MoleUsart_destruct(MoleUsart_td *inst)
{
    assert_param(1 <= l_count && l_count <= NUM_OF_MOLEUSART);

    LL_USART_DisableIT_RXNE(inst->usart);
    LL_USART_DisableIT_TXE(inst->usart);

    int32_t i;
    for (i = 0; i < NUM_OF_MOLEUSART; i++)
    {
        if (l_inst[i] == inst)
        {
            vQueueDelete(l_inst[i]->rxQ);
            l_inst[i]->rxQ = NULL;
            vQueueDelete(l_inst[i]->txQ);
            l_inst[i]->txQ = NULL;
            l_inst[i]      = NULL;
            goto FOUND;
        }
    }
    abort(); //! l_instの中に破棄したいものが見つからなかった

FOUND:
    l_count--;
}

/*!
 * @note FIFOがいっぱいの時はブロッキングする
 */
void
MoleUsart_putchar(MoleUsart_td *inst, int ch)
{
    const BaseType_t rc = xQueueSend(inst->txQ, &ch, portMAX_DELAY);
    if (rc != pdTRUE) abort();

    /* xQueueSend()を呼び出してから戻ってくるまでの間に、すでに
     * キューはカラになっている可能性がある。
     * キューの残り要素数を取得してから何かするまでの間にISRが呼ばれて
     * 要素数が変化するのを防ぐために、まずTXE割り込みを禁止し、
     * 残り要素数を調べて0ならTXE割り込み禁止のままリターンし、
     * そうでないならTXE割り込みを許可する。
     */
    LL_USART_DisableIT_TXE(inst->usart);
    const UBaseType_t remain = uxQueueMessagesWaiting(inst->txQ);
    if (remain == 0) return;
    LL_USART_EnableIT_TXE(inst->usart);
}

/*!
 * @note FIFOがカラの時はブロッキングする
 */
int
MoleUsart_getchar(MoleUsart_td *inst)
{
    char ch = '\0';
    const BaseType_t rc = xQueueReceive(inst->rxQ, &ch, portMAX_DELAY);
    if (rc != pdTRUE) abort();
    return ch;
}

/*!
 * @details
 * stm32f?xx_it.cから呼び出させる処理。
 * stm32f?xx_it.c側で「複数あるUSARTのうちどれが割り込み要因になったか」
 * を特定できるので、それを引数に与える。
 */
void
MoleUsart_IRQHandler(USART_TypeDef *usart)
{
    //! まず、usartの値をキーとしてMoleUsartインスタンスを特定する
    int32_t i;
    for (i = 0; i < NUM_OF_MOLEUSART; i++)
    {
        if (l_inst[i] != NULL)
        {
            if (l_inst[i]->usart == usart)
            {
                goto FOUND;
            }
        }
    }
    return; // 既知のUSARTでない割り込みだった

FOUND:
    ;
    MoleUsart_td * const inst = l_inst[i];
    BaseType_t higherPriorityTaskWoken = pdFALSE;

    // RX Not Empty
    if (LL_USART_IsEnabledIT_RXNE(usart) &&
        LL_USART_IsActiveFlag_RXNE(usart))
    {
        const char ch = LL_USART_ReceiveData8(usart);
        const BaseType_t rc = xQueueSendFromISR(
            inst->rxQ, &ch, &higherPriorityTaskWoken);
        if (rc == errQUEUE_FULL)
        {
            /*!
             * 受信キューがFULLであるということは取りこぼすという
             * ことなので、設計的にまずい。
             * ここでabort()するようなら設計に見直しが必要。
             */
            abort();
        }
    }

    // TX Empty
    if (LL_USART_IsEnabledIT_TXE(usart) &&
        LL_USART_IsActiveFlag_TXE(usart))
    {
        char ch = '\0';
        BaseType_t rc = xQueueReceiveFromISR(
            inst->txQ, &ch, &higherPriorityTaskWoken);

        /* TXE割り込みが許可されているならば
         * キューには1つ以上溜まっているはず
         */
        if (rc != pdTRUE) abort();

        LL_USART_TransmitData8(usart, (uint8_t)ch);

        // キューがカラならTXE割り込みを禁止する
        rc = xQueueIsQueueEmptyFromISR(inst->txQ);
        if (rc == pdTRUE)
        {
            LL_USART_DisableIT_TXE(inst->usart);
        }
    }

    // Transmit Complete
    if (LL_USART_IsEnabledIT_TC(usart) &&
        LL_USART_IsActiveFlag_TC(usart))
    {
        // TC割り込みをクリアするのみ
        LL_USART_ClearFlag_TC(usart);
    }

    portYIELD_FROM_ISR(higherPriorityTaskWoken);
}

// vim: tabstop=4 shiftwidth=4 expandtab