STM32G4のDACをTIMで駆動してDMAで転送して2chの波形を出す


NUCLEO-G474REボードを使っています。

CubeMXの設定

DAC4の設定

今回はDAC4を使うことにしました(ピンアサインの競合の関係で)。

今回はテストデータとして正弦波を使うので、Signed FormatをEnableに設定しています。またTriggerにTimer 7 Trigger Out eventを設定しています。

DAC4のDMAの設定

Data WidthのPeripheral側は必ずWordに設定してください。Memory側はフォーマットに応じて設定することになりますが、今回は8bitデータが2ch分でHal Wordを設定しました。12bit幅をフルに使う場合はMemory側もWordになります。
あとはModeをCircularに設定し、必要に応じてPriorityも設定しておきます。

OPAMP4/5の設定

OPAMP4のModeをFollower-DAC4_OUT1-INPに設定します。それ以外はデフォルトです。
OPAMP5も同様に、Follower-DAC4_OUT2-INPに設定します。

TIM7の設定

TIM7を適当に設定しておきます。
今回はコアクロックが170MHzなので、周期を170-1として、1MHzのトリガを発生させます。
Trigger Event SelectionにUpdate Eventを設定しておきます。

DACの起動

適当な正弦波を作成し、DACを開始します。OPAMPも有効にして、最後にタイマをスタートします。
今回は1MHzトリガなのでポイント数を1000とし、1kHzの正弦波が出るようにしました。また、デュアルチャンネルなので、cosとsinで90度位相をずらした信号を設定しました。
sin/cosは-1から+1の範囲ですが、DACには8bitのデータを渡すので、127倍しています。DACは符号有りモードに設定しているので、オフセットは不要です。

constexpr uint16_t N = 1000;
static struct
{
    int8_t ch1;
    int8_t ch2;
} data[N] = {};

for (uint32_t i = 0; i < N; i++)
{
    constexpr float pi = 3.1415926535897932384626433832795f;
    data[i].ch1 = static_cast<int8_t>(cosf(i * pi * 2 / N) * 127);
    data[i].ch2 = static_cast<int8_t>(sinf(i * pi * 2 / N) * 127);
}

HAL_DACEx_DualStart_DMA(&hdac4, DAC_CHANNEL_1, reinterpret_cast<uint32_t *>(data), N, DAC_ALIGN_8B_R);
HAL_OPAMP_Start(&hopamp4);
HAL_OPAMP_Start(&hopamp5);
HAL_TIM_Base_Start(&htim7);

綺麗な波形が出ていますね。

雑記

今回、なぜかDMAが動かなくて、数時間溶かしました。結局、DMAのPeripheral側の幅をWordにしないとダメだった、という。リファレンスマニュアルを読めば「32bitでアクセスしろ」と書いてあるので、ちゃんと仕様書を読め、という話なんですが。。。
以前はDMAの幅は気にせずに動いていたような気がします。前にQiitaに投稿したF3でDACをDMAで転送するサンプルもHalfWordで転送しているようですし。もしかしたらG4はそのあたりが厳しくなったのかもしれません。F3でもF4でも同様に「32bitでアクセスしろ」と書いてあるので、仕様外なのにたまたま動いていただけかもしれませんが。

今回の教訓
「公式のドキュメントは隅から隅まで読み尽くせ」