web-dev-qa-db-ja.com

STM32F4 UART HALドライバー

この新しいHALドライバーの使用方法を理解しようとしています。 HAL_UART_Receive_IT()を使用してデータを受信します。これは、データを受信したときに割り込み機能を実行するようにデバイスを設定します。

問題は、割り込みがトリガーされる前に読み取るデータの長さを指定する必要があることです。可変長のコマンドのようなコンソールを送信する予定なので、固定長にすることはできません。これを行う唯一の方法は、一度に1つの文字を読み取り、別の文字列を作成することだと思います。

HALドライバーには、x個の文字を受信するようにHAL_UART_Receive_IT()を設定し、x個を超える文字を送信しようとすると問題が発生するようです。エラー。

現在、私はそれを正しい方法で行っているかどうかはわかりません、何かアイデアはありますか?

18
HammerFet

DMAを使用して受信を動作させることにしました。送信機のシリアル端末で入力されたデータを処理するために1バイトの循環バッファーを使用しています。下部の送信部分の詳細、送信に関する詳細情報)。

いくつかの定義と変数:

#define BAUDRATE              9600
#define TXPIN                 GPIO_PIN_6
#define RXPIN                 GPIO_PIN_7
#define DATAPORT              GPIOB
#define UART_PRIORITY         6
#define UART_RX_SUBPRIORITY   0
#define MAXCLISTRING          100 // Biggest string the user will type

uint8_t rxBuffer = '\000'; // where we store that one character that just came in
uint8_t rxString[MAXCLISTRING]; // where we build our string from characters coming in
int rxindex = 0; // index for going though rxString

IOをセットアップします。

__GPIOB_CLK_ENABLE();
__USART1_CLK_ENABLE();
__DMA2_CLK_ENABLE();

GPIO_InitTypeDef GPIO_InitStruct;

GPIO_InitStruct.Pin = TXPIN | RXPIN;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
HAL_GPIO_Init(DATAPORT, &GPIO_InitStruct);

UARTをセットアップします。

UART_HandleTypeDef huart1;
DMA_HandleTypeDef hdma_usart1_rx;

huart1.Instance = USART1;
huart1.Init.BaudRate = BAUDRATE;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
HAL_UART_Init(&huart1);

DMAをセットアップします。

extern DMA_HandleTypeDef hdma_usart1_rx; // assuming this is in a different file

hdma_usart1_rx.Instance = DMA2_Stream2;
hdma_usart1_rx.Init.Channel = DMA_CHANNEL_4;
hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_usart1_rx.Init.MemInc = DMA_MINC_DISABLE;
hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_usart1_rx.Init.Mode = DMA_CIRCULAR;
hdma_usart1_rx.Init.Priority = DMA_PRIORITY_LOW;
hdma_usart1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
HAL_DMA_Init(&hdma_usart1_rx);

__HAL_LINKDMA(huart, hdmarx, hdma_usart1_rx);

HAL_NVIC_SetPriority(DMA2_Stream2_IRQn, UART_PRIORITY, UART_RX_SUBPRIORITY);
HAL_NVIC_EnableIRQ(DMA2_Stream2_IRQn);

セットアップDMA割り込み:

extern DMA_HandleTypeDef hdma_usart1_rx;

void DMA2_Stream2_IRQHandler(void)
{
    HAL_NVIC_ClearPendingIRQ(DMA2_Stream2_IRQn);
    HAL_DMA_IRQHandler(&hdma_usart1_rx);
}

DMAを開始します。

__HAL_UART_FLUSH_DRREGISTER(&huart1);
HAL_UART_Receive_DMA(&huart1, &rxBuffer, 1);

DMA受信コールバック:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    __HAL_UART_FLUSH_DRREGISTER(&huart1); // Clear the buffer to prevent overrun

    int i = 0;

    print(&rxBuffer); // Echo the character that caused this callback so the user can see what they are typing

    if (rxBuffer == 8 || rxBuffer == 127) // If Backspace or del
    {
        print(" \b"); // "\b space \b" clears the terminal character. Remember we just echoced a \b so don't need another one here, just space and \b
        rxindex--; 
        if (rxindex < 0) rxindex = 0;
    }

    else if (rxBuffer == '\n' || rxBuffer == '\r') // If Enter
    {
        executeSerialCommand(rxString);
        rxString[rxindex] = 0;
        rxindex = 0;
        for (i = 0; i < MAXCLISTRING; i++) rxString[i] = 0; // Clear the string buffer
    }

    else
    {
        rxString[rxindex] = rxBuffer; // Add that character to the string
        rxindex++;
        if (rxindex > MAXCLISTRING) // User typing too much, we can't have commands that big
        {
            rxindex = 0;
            for (i = 0; i < MAXCLISTRING; i++) rxString[i] = 0; // Clear the string buffer
            print("\r\nConsole> ");
        }
    }
}

したがって、文字を受け取り、ユーザーが入力した内容を示す文字列(char配列)を作成するコードはほぼすべてです。ユーザーがバックスペースまたはdelを押すと、配列の最後の文字が上書きされ、enterを押すと、その配列は別の関数に送信され、コマンドとして処理されます。

コマンドの解析およびコードの送信方法を確認するには、私のプロジェクトを参照してください Here

@Flipと@Dormenの提案に感謝します!

15
HammerFet

データレジスタ(DR)がいっぱいのときにデータを受信すると、オーバーランエラーが発生します。問題は、関数UART_Receive_IT(UART_HandleTypeDef*)が十分なデータを受信するとDRレジスタの読み取りを停止することです。新しいデータがあると、オーバーランエラーが発生します。

私がやったのは、むしろ循環DMA受信構造を使用することでした。その後、currentPosInBuffer - uart->hdmarx->Instance->NDTRまだ処理していない受信データの量を判断します。

DMAは循環バッファリング自体を行いますが、バッファの終わりを超えた場合、ループバックを手動で最初に実装する必要があるため、少し複雑です。

また、コントローラがデータを転送したと言うグリッチ(つまり、NDTRが減少した)が見つかりましたが、データはまだバッファにありません。 DMA /バスアクセスの競合の問題の可能性がありますが、迷惑です。

6
Flip

STM32 UARTドライバーは少し不安定です。箱から出して動作する唯一の方法は、受け取る文字の正確な数を知っている場合です。不特定の数を受け取りたい場合文字の私が出会って試したいくつかの解決策があります:

  1. 受信する文字数を1に設定し、別の文字列を作成します。これは機能しますが、ドライバーがrxBufferを読み取るたびに割り込みが無効になるため、一部の文字が失われる可能性があるため、データを非常に高速で受信するときに問題が発生します。

  2. 受信する文字数を最大のメッセージサイズに設定し、タイムアウトを実装してから、メッセージ全体を読み取ります。

  3. 独自のUART_Receive_IT関数を作成して、循環バッファーに直接書き込みます。これはより多くの作業ですが、最終的には最もうまくいくことがわかりました。ただし、halドライバーの一部を変更する必要があるため、コードの移植性は低くなります。

別の方法は、@ Flipが推奨するようにDMAを使用することです。

3
Domen Kern

別のアプローチでパッチを適用するファイル「stm32l0xx_it.c」(または必要に応じてl4xx)の「void USART2_IRQHandler(void)」。文字が受信されるたびに、この割り込みが呼び出されます。 CubeMXコードジェネレーターで更新するときに変更されないユーザーコードを挿入するスペースがあります。パッチ:

void USART2_IRQHandler(void)
{
  /* USER CODE BEGIN USART2_IRQn 0 */

  /* USER CODE END USART2_IRQn 0 */
  HAL_UART_IRQHandler(&huart2);
  /* USER CODE BEGIN USART2_IRQn 1 */
  usart_irqHandler_callback( &huart2 ); // patch: call to my function 
  /* USER CODE END USART2_IRQn 1 */
}

小さな文字バッファーを提供し、受信IT機能を開始します。最大115200ボーでは、1バイトを超えて消費することはなく、残りのバッファーは未使用のままになります。

st = HAL_UART_Receive_IT( &huart2, (uint8_t*)rx2BufIT, RX_BUF_IT_SIZE );

バイトを受け取ったら、それをキャプチャして自分のリングバッファに入れ、文字ポインタと-counterを設定し直します:

// placed in my own source-code module:
void usart_irqHandler_callback( UART_HandleTypeDef* huart ) {
  HAL_UART_StateTypeDef  st;
  uint8_t c;
  if(huart->Instance==USART2) {
    if( huart->RxXferCount >= RX_BUF_IT_SIZE ) {
      rx2rb.err = 2;           // error: IT buffer overflow
    }
    else {
      huart->pRxBuffPtr--;     // point back to just received char
      c = (uint8_t) *huart->pRxBuffPtr; // newly received char
      ringbuf_in( &rx2rb, c ); // put c in rx ring-buffer
      huart2.RxXferCount++;    // increment xfer-counter avoids end of rx
    }
  }
}

この方法はかなり高速であることが証明されました。 ITまたはDMAを使用して1バイトのみを受信すると、常に初期化が解除され、受信プロセスを再度初期化する必要があります。これは遅すぎることが判明しました。上記のコードはフレームです。ここでは、リングバッファから完了した行をいつでも読み取ることができるステータス構造で、受信した文字やその他のイベントが割り込みを引き起こしたかどうかをチェックする必要があります。
編集:
この方法は、DMAではサポートされず、代わりにITを使用します。循環モードで1バイトでDMAを使用します。 HALライブラリでCubeMXジェネレーターを使用する場合は、より短く簡単に実装できます。

0
peets

私のプロジェクトでも同じ問題に直面しなければなりませんでした。私がしたことは、ペリフェラルの初期化の直後にHAL_USART_Receive_IT()で1バイトの読み取りを開始することです。

次に、転送完了時にコールバックを作成し、バイトをバッファーに入れ、コマンドが完了したらフラグを設定し、別のバイトに対してHAL_USART_Receive_IT()を再度呼び出します。

USARTを介してコマンドを受信するため、最初のバイトからコマンドがあと何バイト長くなるかがわかります。たぶんそれもあなたのために働くことができます!

0
LoriB

通常、私は自分でUART循環バッファの実装を書きました。前述のように、STM32 HALライブラリのUART割り込み関数は少し奇妙です。独自の循環バッファを書くことができます。 UART割り込みフラグを使用する2つの配列とポインターのみ。

0
ctasdemir