2016年1月9日土曜日

STM32F1のUSARTを割り込みで送信

USARTの送信をポーリングで行う場合、115.2kbaudで32文字送った場合、2.8msec程度かかる。わずかといえばわずかだが、この時間はほとんど無駄にループさせているため、CPUのリソースが浪費されている。一旦データをバッファにコピーし、送信処理を終了してから、割り込みなどを利用してデータを送ればこの時間を200usec未満にすることができる。

1) 変数を用意する

#define USART1_TX_Buff_Size (128)
uint8_t USART1_TX_Buff[USART1_TX_Buff_Size];
uint16_t USART1_TX_Buff_SetCounter = 0;
volatile uint16_t USART1_TX_Buff_GetCounter = 0; // 割り込みで更新するのでvolatileをつける 

USART1_TX_Buff_Sizeでバッファサイズを設定する。その後USART1_TX_Buffでバッファを用意し、SetCounterとGetCounterを用意する。Setは挿入位置、Getは取り出し位置を記録する。GetCounterは割り込みの中で変更されるため、コンパイラの最適化によってwhileによる監視等が削除される場合がある。そのためGetにはvolatileをつけて最適化を回避する。

2) 下位関数を用意する

uint16_t USART1_TX_Buff_SetCounter_Inc(void) {
    uint16_t counter = USART1_TX_Buff_SetCounter;

    counter++;

    if (counter == USART1_TX_Buff_Size) {
        counter = 0;
    }

    return(counter);
}

uint16_t USART1_TX_Buff_GetCounter_Inc(void) {
    uint16_t counter = USART1_TX_Buff_GetCounter;

    counter++;

    if (counter == USART1_TX_Buff_Size) {
        counter = 0;
    }

    return(counter);
}

SetCounter_IncとGetCounter_Incはその名の通りSetとGetをインクリメントする。ただし関数内では変数に反映せず、戻り値として値を返すので関数を呼び出したところで責任をもって変数に反映する必要がある。

3) 割り込みを有効にする

    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 5;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    USART_ITConfig(USART1, USART_IT_TC, ENABLE);

USARTを初期化する際にNVICも初期化する。USART自体の初期化は前回のエントリを参照。

4) 割り込みハンドラを用意する

void USART1_IRQHandler(void)
{
    if (USART_GetITStatus(USART1, USART_IT_TC))
    {
        if (USART1_TX_Buff_SetCounter == USART1_TX_Buff_GetCounter) {
            USART_ITConfig(USART1, USART_IT_TC, DISABLE);
        } else {
            USART_SendData(USART1, USART1_TX_Buff[USART1_TX_Buff_GetCounter]);

            USART1_TX_Buff_GetCounter = USART1_TX_Buff_GetCounter_Inc();

            USART_ClearITPendingBit(USART1, USART_IT_TC);
        }
    }
}

内部処理としてはSetとGetの差が0なら割り込みを停止し、割り込み自体はクリアせずに終了する。データが存在する場合はUSART_SendDataで送信を行い、Getをインクリメントする。その後割り込みをクリアして終了する。

5) 送信用の関数を用意する

void USART1_putc(unsigned char ch) {
    uint16_t inc;
    
    do {
        inc = USART1_TX_Buff_SetCounter_Inc();
    } while (inc == USART1_TX_Buff_GetCounter);

    USART1_TX_Buff[USART1_TX_Buff_SetCounter] = ch;

    USART1_TX_Buff_SetCounter = inc;

    USART_ITConfig(USART1, USART_IT_TC, ENABLE);
}

putcの中ではバッファへのコピーのみを行う。ただし未送信のデータを破壊しないようにdo whileで待機する。その後にバッファへコピーを行い、SetCounterを更新する。最後に割り込みを有効にする。


割り込みはデータの流れが面倒になるのと、ソフトウェアもそれなりに複雑になる。毎度のことだがほんとうに必要かどうかを考えてから実装しよう。

0 件のコメント:

コメントを投稿