STM32 + LL でUSARTのDMAを用いた送受信を行う
こんにちは。今回は、LLAPIを用いてUSARTのDMAを用いた送受信を行っていきます。受信はDMAをcircular modeにしてリングバッファで行います。
データシートを確認したのちに実装をかけていきます。今回使用するマイコンはSTM32F405RGTです。
データシートの確認
USARTのDMAを使用した送信・受信の設定方法などを確認していきます。
初めに、送信から確認をします。
- DMA制御レジスタにメモリアドレスの書き込み、転送先を設定する
- 転送バイト数の設定
- 必要に応じてチャネル優先順位を設定
- 割り込みの設定を行う
- SRレジスタのTCビットに0にクリアする
- DMAレジスタのチャネルを有効にする
- DMAの送信要求を設定
DMAのTCフラグの管理とDMAへ転送先設定を行えばペリフェラルのフラグ管理はDMAがやってくれるみたいです。
続いて、DMAの受信を確認していきます。
これもほとんど送信と同じ順序で設定をすれば大丈夫そうです。
- DMA制御レジスタにレジスタアドレスの書き込み、受信先を設定する
- 受信のバイト数の設定
- 必要に応じてチャネル優先順位を設定
- 必要に応じて割り込みの設定を行う
- DMAの受信設定を要求
USART(UART)の使い方によって割り込みなどの設定を随時行う必要があります。
STM32CubeMXの設定
今回は、USART1を使用します。
使用するUSART1を有効化して、Baud RateとDMAの設定をしました。TXはNomal Mode、RXはCircular Modeに設定をします。
その後、Project Managerで設定をしていきます。
使用するライブラリはもちのろんLLです。HALから変えましょう。
プログラムの実装
STM32CubeMXで出力された初期設定のコードを確認したところ、だいたいの設定はしてくれているのでデータシートの確認で書いてある通りにプログラムを書いていきます。
usart.cのUSER CODE BEGIN 1内に以下のコードを書いていきます。
TX周りの実装(string.h,stdio.hをインクルードしてください)
#define USART1_TX_SIZE 128
// usart1 tx buffer
uint8_t usart1_tx_buffer[USART1_TX_SIZE];
// usart1 tx status
uint8_t is_tx_status;
void USART1_TX_DMA_Init(void) {// DMAの終了時の割り込みフラグを有効化
LL_DMA_EnableIT_TC(DMA2, LL_DMA_STREAM_7);
is_tx_status = 0;
}
void USART1_TX_DMA_Transfer(char *ch_data) {
// 送信するデータの長さを取得
uint8_t length = strlen(ch_data);
// 送信用バッファにデーtあ
memcpy(usart1_tx_buffer, ch_data, length);
// 送信完了していない場合はまつ
while (is_tx_status == 1)
;
is_tx_status = 1;
// DMAのストリームを無効化
LL_DMA_DisableStream(DMA2, LL_DMA_STREAM_7);
// DMAのメモリとペリフェラルのポインタを設定
LL_DMA_ConfigAddresses(DMA2, LL_DMA_STREAM_7, (uint32_t)&usart1_tx_buffer,
LL_USART_DMA_GetRegAddr(USART1),LL_DMA_DIRECTION_MEMORY_TO_PERIPH);
// 送信データの長さを設定
LL_DMA_SetDataLength(DMA2, LL_DMA_STREAM_7, length);
// DMAストリームを有効化
LL_DMA_EnableStream(DMA2, LL_DMA_STREAM_7);
// DMAを使用したUSARTの送信をリクエスト
LL_USART_EnableDMAReq_TX(USART1);
}
void USART1_TX_DMA_CallBack(void) {
// 送信が完全に終了している場合フラグをクリア
if (LL_DMA_IsActiveFlag_TC7(DMA2) == 1) {
// 送信完了フラグをクリア
LL_DMA_ClearFlag_TC7(DMA2);
is_tx_status = 0;
}
}
void USART1_Printf(const char *ch_data, ...) {
char ch_buff[128];
sprintf(ch_buff, ch_data);
USART1_TX_DMA_Transfer(ch_buff);
}
stm32f4xx_it.cのDMA2_Stream7_IRQHandlerでUSART1_TX_DMA_CallBackを呼んでおいてください。二回目以降の送信ができなくなります。
RX周りの実装
#define USART1_RX_SIZE 64
// usart1 rx buffer
uint8_t usart1_rx_buffer[USART1_RX_SIZE];
// usart1 rx pointer
uint8_t usart1_rx_pointer;
void USART1_RX_DMA_Init(void) {
// 変数の初期化
memset(usart1_rx_buffer, 0, sizeof(usart1_rx_buffer));
usart1_rx_pointer = 0;
// DMAストリームの無効化
LL_DMA_DisableStream(DMA2, LL_DMA_STREAM_2);
// DMAアドレスの設定
LL_DMA_ConfigAddresses(DMA2, LL_DMA_STREAM_2, LL_USART_DMA_GetRegAddr(USART1),
(uint32_t)&usart1_rx_buffer,LL_DMA_DIRECTION_PERIPH_TO_MEMORY);
// データアドレスの長さを設定
LL_DMA_SetDataLength(DMA2, LL_DMA_STREAM_2, USART1_RX_SIZE);
// DMAストリームの有効化
LL_DMA_EnableStream(DMA2, LL_DMA_STREAM_2);
LL_USART_EnableDMAReq_RX(USART1);
}
uint32_t USART1_DMA2_GetWritePointer(void) {
return (USART1_RX_SIZE - DMA2_Stream2->NDTR) & (USART1_RX_SIZE - 1);
}
uint8_t USART1_DMA_IsCircleBufferEmpty(void) {
if (usart1_rx_pointer == USART1_DMA2_GetWritePointer())
return 1;
else
return 0;
}
uint8_t USART1_DMA_GetCircleBuffer(void) {
uint8_t ch = 0;
if (usart1_rx_pointer != USART1_DMA2_GetWritePointer()) {
ch = usart1_rx_buffer[usart1_rx_pointer++];
usart1_rx_pointer &= (USART1_RX_SIZE - 1);
}
return ch;
}
main.cに動作確認用プログラムを書いていきます(string.h,stdio.hをインクルードしてください)
setbuf(stdout, NULL);
char ch_buff[128];
uint8_t ch_pointer = 0;
USART1_Printf("Hello World\r\n");
while(1){
USART1_Printf("Insert String\r\n");
ch_pointer = 0;
if (USART1_DMA_IsCircleBufferEmpty() == 0) {
while (USART1_DMA_IsCircleBufferEmpty() == 0) {
ch_buff[ch_pointer++] = USART1_DMA_GetCircleBuffer();
}
USART1_Printf(ch_buff);
}
LL_mDelay(5000);
memset(ch_buff, 0, sizeof(ch_buff));
}
5秒の間に文字入力があった場合は、入力された文字を返すようなプログラムです(適当)。
各自のやりたいことにあったように改造をしていただければと思います。’\n’が入力されたタイミングで表示するとかいろいろと方法はあると思います。
さいごに
今回のプログラムには、コメントを多くつけてみました。いかがだったでしょうか?参考になれば幸いです。以前にDMAとUSARTで上手く動かず諦めましたが、そのプログラムを確認したところ些細なミスで頭を抱えました。