stm32h743搭載のボードを作ったらDMAが動作しなくて悩んだこと
※この記事はstm32CubeIDEを使うことを前提にして書いてあります
stm32h743はクロック480MHz、内蔵Flash 2MByte、内蔵SRAM 864KByte maxでキャッシュメモリ、FPUを備えていて
アナログインターフェースとして16bit A/D 、OPAMP、コンパレータを内蔵しTFT-LCDコントローラのLTDCも搭載した組み込み用高性能マイクロコントローラです。
超高性能&機能てんこ盛りなのは嬉しいかわりに内部バスアーキテクチャが複雑になっているためstm32CubeIDEまかせではDMAアクセスがうまくいかず、解決までに暫く悩みました。
結局は「リファレンスマニュアルをちゃんと読めよ。」ということに尽きるわけですが、stm32CubeMXのGUI画面でパラメータ設定をすれば初期化プログラムが簡単に出来てしまうという楽さに慣れてしまった身としては久しぶりにリファレンスマニュアルを睨みながら苦労していた昔のことを懐かしく思い出すことになりました。
stm32h7xシリーズでDMAが動かない理由
1.キャッシュメモリのせいでDMAがRAMに書いた値がプログラムのメモリアクセスには反映されない。(ADC等、DMA→RAMの場合)
プログラムはキャッシュメモリを介してRAMをアクセスするため、プログラムが関知しないDMAによるRAMへの書き込みではRAMの状態をキャッシュメモリに反映する特別な命令が必要となります。
2.DMAによって使えるRAMのブロックが決まっている。
下図のようにstm32h743ではUART用のDMA1,DMA2はD2ドメインのRAM、ADC用のBDMAはD3ドメインのRAMに接続されているため各DMA用のバッファはそれぞれ別のRAM領域に割り当てる必要があります。
stm32h743でDMAを使うための手順
1.キャッシュメモリの設定
詳細はねむいさんのブログを参照して下さい。
stm32CubeIDEで次のように設定します
2.リンカスクリプトファイルの修正
stm32CubeIDEが自動的に作るリンカスクリプト *_FLASH.ldではITCMRAMを標準の変数、スタックエリアとして使っていてD1,D2,D3ドメインの割り当て用ラベルが無いため下記のようにRAM_D1、RAM_D2、RAM_D3セクションを追加します。
リンカスクリプトはstm32cubeMXでコードジェネレートしても変更されないので一度だけ追加しておけばOKです。
/* User_heap_stack section, used to check that there is enough RAM left */ ._user_heap_stack : { . = ALIGN(8); PROVIDE ( end = . ); PROVIDE ( _end = . ); . = . + _Min_Heap_Size; . = . + _Min_Stack_Size; . = ALIGN(8); } >RAM .RAM_D1 : { . = ALIGN(32); } >RAM_D1 .RAM_D2 : { . = ALIGN(32); } >RAM_D2 .RAM_D3 : { . = ALIGN(32); } >RAM_D3 /* Remove information from the standard libraries */ /DISCARD/ : { libc.a ( * ) libm.a ( * ) libgcc.a ( * ) }
3.DMA用バッファをD1,D2,D3ドメインに割り当てる
ヘッダファイルなどにマクロを設定して
#define ALIGN_32BYTES_D1(buf) buf __attribute__ ((section(".RAM_D1"))) __attribute__ ((aligned (32))) #define ALIGN_32BYTES_D2(buf) buf __attribute__ ((section(".RAM_D2"))) __attribute__ ((aligned (32))) #define ALIGN_32BYTES_D3(buf) buf __attribute__ ((section(".RAM_D3"))) __attribute__ ((aligned (32)))
プログラムの変数宣言にこのマクロをつかいます
ALIGN_32BYTES_D3(uint16_t adbuf[5]); ALIGN_32BYTES_D2(UART_RXBUF uart_rx1);
stm32h743でDMAでADCを読み出すための設定
stm32cubeIDEで下記のように設定してプログラムの最初でDMA読出しをスタートすれば
HAL_ADC_Start_DMA(&hadc3, (uint32_t *)adbuf, 5);
バッファを読み出すだけで最新のAD結果を得ることが出来ます。
DMAはバックで自動的にADを読み出すのでSampling timeを短くしすぎるとプログラムの実行速度に影響があるので注意してください。