2016年1月9日土曜日

STM32F1のUSARTをDMAで受信

USARTを受信するにはポーリングでは不都合。とりあえずタイマ割り込みを使うのが手っ取り早いが、タイマ割り込みではデータが来るたびに割り込み処理を行う必要がある。DMAを使うとソフトウェアではほとんど処理する必要が無いため、気分的に早くなってる気がする。

1) 変数を用意する

#define USART1_RX_Buff_Size (256)
uint8_t USART1_RX_Buff[USART1_RX_Buff_Size];
uint16_t USART1_Rx_Buff_GetCounter = USART1_RX_Buff_Size;

今回はバッファサイズを256としたが、USART1_RX_Buff_SizeをGCCのオプションで設定すればmakefileとかで変更することもできると思う。バッファサイズを小さくし過ぎると取りこぼすデータが発生するかもしれない。上限は数十kbyteあたりだが、実用上は数百バイトもあれば十分だろう。

2) USARTその他を初期化する

void USART1_Init(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    DMA_InitTypeDef DMA_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(GPIOA, &GPIO_InitStructure);


    USART_InitStructure.USART_BaudRate = 115200;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_Init(USART1, &USART_InitStructure);


    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&USART1->DR);
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)(USART1_RX_Buff);
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    DMA_InitStructure.DMA_BufferSize = sizeof(USART1_RX_Buff) / sizeof(USART1_RX_Buff[0]);
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

    DMA_DeInit(DMA1_Channel5);
    DMA_Init(DMA1_Channel5, &DMA_InitStructure);
    DMA_Cmd(DMA1_Channel5, ENABLE);

    USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);


    USART_Cmd(USART1, ENABLE);
}

まずGPIOとUSARTを初期化する。これはポーリングでの受信や割り込みでの受信と全く同じ。TXのPin9はAF_PP、RXのPin10はPullUpで初期化する。
ペリフェラルを初期化したらDMAの初期化を行う。DMAの初期化も特に変なところはないが、ModeをCircularに設定する。優先度はMediumとしたが、例えばSPIを最大速度でVeryHighにした場合はUSARTを取りこぼすかもしれない。USARTは非同期で送信されるため、可能なかぎり優先度を上げておくほうがいい。
それと大事なことだが、DMAを初期化する前にDMAのクロックを有効にしておく必要がある(RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);)。

3) 受信関数

int USART1_getc(void) {
    int counter = DMA_GetCurrDataCounter(DMA1_Channel5);

    if (counter == USART1_Rx_Buff_GetCounter) {
        return(-1);
    }

    uint8_t ch = USART1_RX_Buff[USART1_RX_Buff_Size - USART1_Rx_Buff_GetCounter--];

    if (USART1_Rx_Buff_GetCounter == 0) {
        USART1_Rx_Buff_GetCounter = USART1_RX_Buff_Size;
    }

    return(ch);
}

この関数ではDMAを確認し、データがない場合は-1を返す。データが有る場合は最初に受信したデータを返す。この関数はバッファからデータを読む機能しかない。1行を受信したい場合は上位の関数で改行文字を検出したりする必要がある。



今回はUSARTの受信をDMAで行った。割り込みを使う場合と比べてちょっとくらい早くなった気がする。
USARTの受信はそんなに大変じゃないけど、送信は結構面倒。ただ送信をバッファリングできれば数msecとか数十msec単位で高速化できたりするので試してみる価値はある。

0 件のコメント:

コメントを投稿