stm32FのDMA Circular mode を使ったUART受信

stm32FのUARTでシリアルデータを受信するのにDMAのcircularモードを使うとDMAだけでデータをリングバッファに読み込むことが出来ます。
リングバッファからの読み出し関数は、DMAのポインタ(huart_cobs->hdmarx->Instance->CNDTR)をリングバッファへの書き込みポインタとして、読み出しポインタで書き込みポインタを追いかけるようにするだけです。

この手法は割り込みン数を使わずにデータをリングバッファに取り込めるのでとても簡単で効率が良いのですがSTM32CubeMXのHALライブラリを使っているとエラーが起きた時に勝手に送受信を止められてしまうという困った現象が起きます。

解決法を探していると同じことをやっている人がいて、「受信エラーはCRCチェックで対処するからHALライブラリのエラーハンドリングは邪魔なのでこうやってやめさせたよ。」という回答をみつけました :-P (下のトピック)。

Topic: ‘Best’ way to load UART data to ring buffer with STM32/HAL
このトピックの投稿者が「この方法がとてもクールだと思わないか?」と言ってるのに対して、別の人が「自分もそう思う、だけどエラーハンドリングには注意した方がいいよ。」とアドバイスを書いています。

受信部のソースコード : スマートにまとめられているので参考に転載しておきます。

/*
* The STM32 makes receiving chars into a large circular buffer simple
* and requires no CPU time. The UART receiver DMA must be setup as CIRCULAR.
*/
#define CIRC_BUF_SZ       64  /* must be power of two */
static uint8_t rx_dma_circ_buf[CIRC_BUF_SZ];
static UART_HandleTypeDef *huart_cobs;
static uint32_t rd_ptr;

#define DMA_WRITE_PTR ( (CIRC_BUF_SZ - huart_cobs->hdmarx->Instance->CNDTR) & (CIRC_BUF_SZ - 1) )

void msgrx_init(UART_HandleTypeDef *huart)
{
    huart_cobs = huart;
    HAL_UART_Receive_DMA(huart_cobs, rx_dma_circ_buf, CIRC_BUF_SZ);
    rd_ptr = 0;
}

static bool msgrx_circ_buf_is_empty(void) {
    if(rd_ptr == DMA_WRITE_PTR) {
        return true;
    }
    return false;
}

static uint8_t msgrx_circ_buf_get(void) {
    uint8_t c = 0;
    if(rd_ptr != DMA_WRITE_PTR) {
        c = rx_dma_circ_buf[rd_ptr++];
        rd_ptr &= (CIRC_BUF_SZ - 1);
    }
    return c;
}

 

エラーへの対処 : HALの余計なお世話を無効にする方法、これが私の探していた答えです。

/* These uart interrupts halt any ongoing transfer if an error occurs, disable them */
/* Disable the UART Parity Error Interrupt */
__HAL_UART_DISABLE_IT(&huart1, UART_IT_PE);
/* Disable the UART Error Interrupt: (Frame error, noise error, overrun error) */
__HAL_UART_DISABLE_IT(&huart1, UART_IT_ERR);

HALライブラリを使ったstm32f0のフラッシュ書き込み

stm32CubeMXが生成してくれるHALライブラリを使ってstm32F051のFlashにデータを書き込む

stMicro提供のライブラリを使うとstm32FシリーズCPU内蔵のFlashROMへデータを書き込むのはとても簡単なのだが
以前のstdperipheralライブラリからHALに乗り換えてちょっと躓いたのでメモ。

FLASHに書き込む時には書き込み対象ページのデータを書き込み前に消去しておかないとならない。
stm32F051ではライブラリの関数を使って1KByteのページ単位でデータを消去することになる。
書き込みはstm32f0xx_hal_flash.cにある関数HAL_FLASH_Programが以前のライブラリと殆ど同じ方法で使える。
サンプルプログラムを見ると消去の方は構造体を使ったパラメータ渡しのHAL_FLASHEx_Eraseという関数を使ってあるが使い方がややこしそうでしかも同じソースファイルには含まれていない。
で、同じソースにある簡単そうな関数FLASH_PageErase(uint32_t PageAddress)を使ってみたのだがこれが間違いの元で
暫く悩んだ挙句サンプルに従ってstm32f0xx_hal_flash_ex.cに含まれているHAL_FLASHEx_Erase関数を使ったらすんなりと書き込めてしまった。

(教訓)
HALライブラリの関数を使うときはHAL_という文字で始まる関数だけを使わないとトラブルの基になる。

// フラッシュROMへデータを書き込む
void WriteToFlash(uint32_t addr, uint8_t* dat, uint16_t size)
{
  int i;
  FLASH_EraseInitTypeDef EraseInitStruct;
  uint32_t PageError = 0;
  HAL_StatusTypeDef r;

  r = HAL_FLASH_Unlock();
  if( r != HAL_OK ){
#ifdef _DEBUG_FLASH_HAL
    xprintf("WriteParamToFlash:FLASH_Unlock error r=%x\r\n",r);
#endif
    return;
  }
  EraseInitStruct.TypeErase = TYPEERASE_PAGES;
  EraseInitStruct.PageAddress = addr;
  EraseInitStruct.NbPages = 1;
  r = HAL_FLASHEx_Erase(&EraseInitStruct, &PageError);
  if ( r != HAL_OK )
  {
#ifdef _DEBUG_FLASH_HAL
    xprintf("WriteParamToFlash:FLASH_Erase error r=%x, pageerr=%x\r\n",r,PageError);
#endif
    return;
  }
  for(i=0; i<size; i+=2){
    r = HAL_FLASH_Program(TYPEPROGRAM_HALFWORD, addr+i, *(uint16_t*)(dat+i));
#ifdef _DEBUG_FLASH_HAL
    xprintf("WriteParamToFlash i=%d, r=%x\r\n",i,r);
#endif
    if( r != HAL_OK ){
#ifdef _DEBUG_FLASH_HAL
    xprintf("WriteParamToFlash:FLASH_Program error i=%d, r=%x\r\n",i,r);
#endif
      return;
    }
  }
  r = HAL_FLASH_Lock();
  if( r != HAL_OK ){
#ifdef _DEBUG_FLASH_HAL
    xprintf("WriteParamToFlash:FLASH_Lock error r=%x\r\n",r);
#endif
  }
}

DMAを使ったUART送受信

stm32CubeMXでstm32F051のプログラムを作るのに、まずデバッグに必要なUARTの送受信から手をつけた。
今までは受信には割り込みを使い送信は直接送信を使っていたがstm32CubeMXが生成してくれるドライバHALを使うと今まで敬遠していたDMAも簡単に使えそうな気がしてトライしてみた。

思ったほど簡単ではなかったが何とか使い物になるUARTのライブラリが出来上がった。
DMA通信テストのためにJTW32にループバックテスト用画面を追加してテストした結果が次の画面。

Loopbacktest

左のウィンドウにある文字をタイマーで連続してstm32F051ボードに送信し帰ってきた文字と照合して長時間安定して通信出来ることを確認した。
設定した通信速度は1.5Mbpsなので計算上では100KB/秒以上の転送速度が可能なのだが10KB/Sec以上に上げるとエラーが発生する。
ボードから一定のパターンを繰り返し送信してそのパターンをチェックする連続受信テストでは27.8KB/Secでエラーなしに連続してデータを遅れることが確認できたのでこれがCPUの処理能力の上限で、エラーなしに連続送信が可能という結果だった。
実際にUSART通信を使うのは出来るだけ高速でボード内部の状態を送信してグラフで見たいという場合が殆どなのでとりあえずは使える送受信ライブラリが出来たことになる。

DMAの使い方だがデータを送る時はいったん送信バッファにデータを書き込みDMAがビジーでない時にDMAを起動、DMA完了割り込みで送信バッファにデータがあれば再度DMAを起動してデータを送るという仕組みにした。
受信側も最初はDMA完了割り込みを使ったりして苦労したが結局DMAをサーキュラーモードに設定し、受信チェックでCNDTRを監視してデータの操作を行うという方式に落ち着いた。
引っかかったのは送受信ともDMAを使う場合受信のDMAが途中で止まってしまうことで、これはDMAのチャンネルを送信の割り込みグループに属さないチャンネルに変更することで回避できた。