2016年7月8日金曜日

TIM-DMA-DAC

STM32F1のDACから正弦波を出しました。





若干高調波がありますが、かなり綺麗な波形です。DACが2chあるので、時間方向に正弦波の強さを変えつつ、逆位相を出力したりできます。なのでAM変調波とかも出力できます。ま、100Hz当たりが限界で、送信出力も極めて弱いので普通のラジオ程度だと受信できないですけど。


TIM_TimeBaseInitTypeDef TIM_InitStructure;
DMA_InitTypeDef DMA_InitStructure;

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);

TIM_InitStructure.TIM_Period = 72 - 1;
TIM_InitStructure.TIM_Prescaler = 40 - 1;
TIM_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_InitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_InitStructure.TIM_RepetitionCounter = 0;

TIM_TimeBaseInit(TIM4, &TIM_InitStructure);


DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&DAC->DHR12RD);
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)(DAC_Buffers);
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStructure.DMA_BufferSize = sizeof(DAC_Buffers) / sizeof(DAC_Buffers[0]);
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

DMA_DeInit(DMA1_Channel7);
DMA_Init(DMA1_Channel7, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel7, ENABLE);
TIM_DMACmd(TIM4, TIM_DMA_Update, ENABLE);

//TIM_Cmd(TIM4, ENABLE);

とりあえずTIM4とDMA1-7を使いました。予めuint32_tでバッファを宣言しておく必要があります。今回は500サンプルのバッファを作りました。500サンプルで50Hzになるように、TIM4を初期化します。TIM4はタイミングを作るだけなのでNVICなどは必要ありません。
次にDMAの初期化ですが、これも他の用途と全く同じです。ただし、普通はSTM32でペリフェラルとDMA転送する場合はByteが多く、たまにHalfWordを使う程度ですが、今回は珍しくWord転送です。これはDACに2chを一気に渡すために32bitのレジスタを使うためです。
とりあえずこの時点でDMA自体は有効にしておき、TIMのDMAトリガ出力も有効にしますが、TIM自体は後ほど有効化します(データを作ってから動かします)。

int i, l = sizeof(DAC_Buffers) / sizeof(DAC_Buffers[0]);
const float pi = 3.14159265f;
for (i = 0; i < l; i++) {
    float f = (float)i / l;
    f = sinf(f * pi * 2);
    
    uint16_t da1 = (uint16_t)(2047 + (2047 * +f));
    uint16_t da2 = (uint16_t)(2047 + (2047 * -f));
    DAC_Buffers[i] = da1 | da2 << 16;
}

TIM_Cmd(TIM4, ENABLE);

正弦波を作るに当たり、今回はsinf関数を使用しました。そのためmath.hをインクルードしておく必要があります。マイコン内で浮動小数点を扱えなかったり、扱いたくない場合はExcel等でテーブルを作ってやることもできますが、コピペしたりデータの管理が面倒なので、今回はマイコン内で作りました。どうせ起動時に1回実行するだけですし、動作確認用に動かすだけですから、ちょっと時間食うくらいは許容します。DAC1とDAC2では位相を逆にしています。
DACのDHR12RDは右詰め12bitで、下位16bitがDAC1、上位16bitがDAC2に出力されています。ということで32bitのデータバッファにビットシフトして与えています。
この辺りは趣味でやる以上はある程度個人の好みに実装して下さい。例えば16bitの2次元配列でDAC1とDAC2を分離するといったやり方もアリだと思います。

最後にTIM_Cmdでタイマを走らせて終了です。


STM32F1のペリフェラルは結構いろいろな組み合わせ方ができて、特にTIMはタイミングを作ったり、他にもいろいろな機能があります。場当たり的に必要な機能を実装していくとあっという間に使い切るので、うまく使いまわせるところは共通で使っていく必要があります。

TIMの機能
・タイマ割り込み
・PWM出力
・パルス幅計測
・DMAタイミング生成
・他のタイマの開始/停止
・その他

タイマの開始/停止は、例えばTIM2でPWMを作り、PWMがHighの時だけTIM3を走らせる、みたいな事ができます。5kHzのPWMを正確に50パルスだけ出したい、というような使い方ができます。
期限とか気にせずに考えてる時は楽しいんですけどね。いざ使おうとすると大変です。

0 件のコメント:

コメントを投稿