31 DACへ直接データ出力 sin/cos波発生 (NVIC,割り込み優先度,GPIO,LED)(割り込みあり)

30番ではSTM32のDACが持つ三角波発生機能を利用して、プログラムがなにもしないでも三角波あるいはノイズを発生できるようにしました.

今回は、割り込みルーチンでDACにいちいちサンプルデータを出力するやりかたで、sin波cos波を発生させてみます.

sin/cosの関数は使わずにsin/cosを発生させます.テーブルを持つわけでもありません.あまり知られていない方式だと思うので試してみました.

これは高校で習った微分公式です.

d sin(t) / dt = cos(t)

d cos(t) / dt = -sin(t)

ゆえに、

d sin(t) = cos(t) dt

d cos(t) = - sin(t) dt

なので、

sin(t+1) - sin(t) = cos(t) dt

cos(t+1) - cos(t) = - sin(t) dt

この関係を元にすると、sin/cos関数を使わずともsin/cos波を作れます.dt→Δと変更して、

sin(t+1) = sin(t) + Δcos(t)

cos(t+1) = cos(t) - Δsin(t)

今抱えているsin/cosを元に次のsin/cosを生成できるというわけです.Δの大きさを変えれば周波数を変えられます.

FPGAでテキトーな正弦波を作りたいときにわたしはちょくちょくこの手を使います.

FPGAではΔを1/256にしたり1/1024にしたりすれば回路規模を増やさずにピーとかポーとか音を出せます.

C言語ではfloatを使うので、Δは何でもよいでしょう.

DAC使い方QA

STM社のデータシートにもライブラリ説明書にもサンプルプログラムにもいまいち判りやすく書かれてないように思うので、DACに任意データを出力するやりかたをQ&A形式でまとめます.

Q: DACに出力するデータをどのレジスタに書けばいいの?

A: そういうレジスタがあります.しかもたくさんある.実際にはレジスタを直接叩くのではなく、ライブラリ関数をcallしますので不要な知識ですけど、、、、、

●DAC_DHR12R1[11:0] DAC1 12bitがデータ

●DAC_DHR12L1[15:0] DAC1 左詰の[15:4]が12bitデータ

●DAC_DHR8R1[7:0] DAC1 8bitがデータ →フルスケール電圧は同じで分解能が256になる

●DAC_DHR12R211:0] DAC2 12bitがデータ

●DAC_DHR12L2[15:0] DAC2 左詰の[15:4]が12bitデータ

●DAC_DHR8R2[7:0] DAC2 8bitがデータ →フルスケール電圧は同じで分解能が256になる

●DAC_DHR12RD[32:0] [27:16]がDAC2の12bitデータ [11:0]がDAC1の12bitデータ

●DAC_DHR12LD[32:0] [31:20]がDAC2の12bitデータ [15:4]がDAC1の12bitデータ

●DAC_DHR8RD[15:0] [15:8]がDAC2の8bitデータ [7:0]がDAC1の8bitデータ

●DAC_DOR1[11:0] DAC1 12bitデータ ※

●DAC_DOR2[11:0] DAC2 12bitデータ ※

なんでこんなに分かれているのかというと、それはたぶんDMAとの親和性のためだと思います.メモリに積んだデータのアサインにこのような柔軟性を与えておくとDMAが幸福になるんでしょう.

※末尾の2つだけ位置づけが異なります.このレジスタに書くと、問答無用でDACへ出力されます.それ以外のレジスタは一時置き場なのでもう一手間かけないとDACへ出力されません.

Q: DACに出力するライブラリ関数は?

A: これまたいくつか関数がありますけど、とりあえずこれを知っていればいいんじゃないでしょうか?

DAC_SetDualChannelData (DAC_Align_12b_R, dac2, dac1);

Q: DMAでDACを動かすには?

A: これはここでは扱いません.まだトライしてないし.

Q: DAC_SetDualChannelData() をコールしさえすれば、DACに出力されるんですか?

A: いいえ.次の関数をコールすればDAC出力に反映されます.これが一手間です.

DAC_SoftwareTriggerCmd (DAC_Channel_1, ENABLE );

DAC_SoftwareTriggerCmd (DAC_Channel_2, ENABLE );

この関数は、DAC_DHRxxxxに一時置きされたデータをDAC_DORxへ転送するフラグであるSWTRIG2, SWTRIG1をアサートする機能です.SWTRIG2, SWTRIG1はデータをDACに転送したら自動的にクリアされます.したがって、プログラムが明示的にクリアする必要はありません.また、毎サンプルにこの関数をコールする必要があります.

サンプルプログラムでやること

外部仕様

●LEDをチカチカさせる

●sin波とcos波を出力する

内部仕様

●TIM3割り込みでLEDチカチカさせる

●TIM2割り込みでsin/cosデータをDACへ出力する.周波数は高くても数100Hzぐらい.

●sin/cos発生アルゴリズムは冒頭で述べた

●DAC出力pinはPA4とPA5.なんでこのpinなのかは過去記事を参照してください.

●LED出力pinはPC8とPC9.STM32-DISCOVERYの回路がそうなっていますので.交互に点滅させる.

サンプルプログラム解説

ページ末尾からprojectフォルダをDLできます.eclipseへのprojectの組み込み方はこちらを参照してください.

main.c

#include "stm32f10x.h" CPUハードウエアに根ざしたいろいろな定義をします

↓各種初期設定に必要な構造体

NVIC_InitTypeDef NVIC_InitStructure; 割り込みコントローラ

GPIO_InitTypeDef GPIO_InitStructure; GPIO

DAC_InitTypeDef DAC_InitStructure; DAC

TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; タイマ

int main(void)

{

↓clockを各peripheralに供給します.APB1バスとAPB2バスがあって、各peripheralはそのどちらかにぶら下がっています.

RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE); DAC

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); TIM2 DACのサンプル周期

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); TIM3 LEDチカチカタイマ

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIOA DACのport

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); GPIOC LEDのport

第一引数の選択肢は下記です.

RCC_APB1PeriphClockCmd()

RCC_APB1Periph_TIM2, RCC_APB1Periph_TIM3, RCC_APB1Periph_TIM4, RCC_APB1Periph_TIM6, RCC_APB1Periph_TIM7, RCC_APB1Periph_WWDG, RCC_APB1Periph_SPI2, RCC_APB1Periph_USART2, RCC_APB1Periph_USART3, RCC_APB1Periph_I2C1, RCC_APB1Periph_I2C2, RCC_APB1Periph_PWR, RCC_APB1Periph_DAC, RCC_APB1Periph_CEC

RCC_APB2PeriphClockCmd()

RCC_APB2Periph_AFIO, RCC_APB2Periph_GPIOA, RCC_APB2Periph_GPIOB, RCC_APB2Periph_GPIOC, RCC_APB2Periph_GPIOD, RCC_APB2Periph_GPIOE, RCC_APB2Periph_GPIOF, RCC_APB2Periph_GPIOG, RCC_APB2Periph_ADC1, RCC_APB2Periph_TIM1, RCC_APB2Periph_SPI1, RCC_APB2Periph_USART1, RCC_APB2Periph_TIM15, RCC_APB2Periph_TIM16, RCC_APB2Periph_TIM17

↓NVICは割り込み優先度をコントロールする機能ですがあまり詳しくは理解できていません、

NVIC割り込みコントローラは4bitの優先度を持っていて、つまり最大で16個の割り込み優先度設定が出来るようです、

割り込み優先度には、PreemptionPriority とSubPriority にカテゴライズされています.

前者が大分類、後者が小分類というような階層になっています.

大分類に3bitを割り当て、小分類に1bitを割り当てるような階層作りができます.

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);

それをやるのがこの↑関数です.引数によって、大分類と小分類のbitアサインを決めます.

| PreemptionPriority | SubPriority |

---------------------------------------------------------

NVIC_PriorityGroup_0 | 0 (0bit) | 0-15 (4bit) |

NVIC_PriorityGroup_1 | 0-1 (1bit) | 0-7 (3bit) |

NVIC_PriorityGroup_2 | 0-3 (2bit) | 0-3 (2bit) |

NVIC_PriorityGroup_3 | 0-7 (3bit) | 0-1 (1bit) |

NVIC_PriorityGroup_4 | 0-15(4bit) | 0 (0bit) |

ここでは、TIM2割り込みとTIM3割り込みの2つしか使いませんので、1~4のどれでも良いわけです.

↓TIM2割り込み優先度の設定

①NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; // DAC timer

②NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;

③NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;

④NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

⑤NVIC_Init(&NVIC_InitStructure);

①はどの割り込みかを指定します.選択肢はたくさんあるので末尾に掲載します

②はTIM2の割り込み優先度を下げています.DACのサンプル周波数という大切な割り込みですが優先度を下げる必要がありました.理由は後で.

③小分類は最高優先度すなわち0にしておきます

④enable

⑤構造体をNVICに書く

↓TIM3割り込み優先度の設定

①NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; // LED flash timer

②NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;

③NVIC_Init(&NVIC_InitStructure);

①どの割り込みかを指定します.

②TIM3の割り込み優先度をなんと0番の最高にしています.たかがLEDチカチカになんで最高優先度を奢るのかは後で説明します.

③構造体をNVICに書く

↓DACの出力であるPA4,PA5のport設定.

①GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5; // DAC output port

②GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;

③GPIO_Init(GPIOA, &GPIO_InitStructure);

①PA4,PA5がDAC出力である理由は過去記事を参照のこと.

②DAC出力ですが、digital出力と衝突しないようにアナログ入力portに設定します.これは注意点です.

③構造体をGPIOAに書く

↓LED portであるPC8,PC9を出力ポートに設定

①GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9; // LED port

②GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;

③GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;

④GPIO_Init(GPIOC, &GPIO_InitStructure);

⑤GPIO_SetBits(GPIOC, GPIO_Pin_8);

⑥GPIO_ResetBits(GPIOC, GPIO_Pin_9);

②出力スピードは遅くてもいいので2MHzの低速にしました

③push-pull出力

④GPIOCに構造体を書く

⑤⑥2つのLEDを交互にチカチカさせるために初期設定として片方を点灯させておく

↓TIM2のDACサンプル周期で割り込み

①TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);

②TIM_TimeBaseStructure.TIM_Period = 100; // 24MHz / 100 = 240kHz

③TIM_TimeBaseStructure.TIM_Prescaler = 0;

④TIM_TimeBaseStructure.TIM_ClockDivision = 0x0;

⑤TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;

⑥TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);

①構造体を初期化

②③カウンタmax=100、プリスケ=1に設定.結果として240kHzで割り込み発生

④ここではどうでもいいので割愛

⑤カウンタupでつかう

⑥構造体をTIM2に書く

↓TIM3のLEDチカチカ割り込み

①TIM_TimeBaseStructure.TIM_Period = 24000; // 24MHz / 24000 / 100 = 10Hz

②TIM_TimeBaseStructure.TIM_Prescaler = 100;

③TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);

①②で10Hz割り込みになります

③構造体をTIM2に書く

ここで、TIM2とTIM3の割り込み優先度の話をします.

普通に考えたら重要なタイムベースであるTIM2を最高優先度にするでしょう.

ですがどうしなかったのはなぜか?という解説です.-----しょーもない話です-----

当初、割り込み優先度をTIM2>TIM3にしたら、LEDがチカチカしなくなってしまいました.

その理由は、TIM2の割り込みが高優先度で高頻度にかかるので、TIM3がちっとも割り込まれなくなっていたからでした.

そもそも、なるべく高い周波数のsin/cosを発生させたかったのでTIM2割り込みを240kHzなどというおよそ処理できないくらいの高頻度で発生させました.

そういう無茶なことをしても、処理速度的に間に合わなかったTIM2割り込みがロストするだけなので、STM32の処理速度の都合が折り合うどこかでバランスしてsin/cosは生成されます.

ところが、TIM2の処理のために全身全霊を傾けてくれているSTM32はもはやTIM3につきあってられなくなってTIM3割り込みが100%ロストするというトラブルでした.

そこで、TIM3を最高優先度にして、TIM3にもつきあってやれよとSTM32に指示してLEDチカチカが動いたわけです.

もちろん、TIM2割り込みを適度にまばらにするのが妥当な対処であることは言うまでもありません.

トラブルシュート事例として書きたかったのであえてそのままにしました.

↓DACの設定です

①DAC_InitStructure.DAC_Trigger = DAC_Trigger_Software;

②DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;

③DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Disable;

④DAC_Init(DAC_Channel_1, &DAC_InitStructure);

⑤DAC_Init(DAC_Channel_2, &DAC_InitStructure);

①ここではソフトウエアでDACデータを更新しますのでDAC_Trigger_Softwareにします.実例30の三角波発生はTIM2出力でDACデータを更新しました.大きな違いです.

②実例30ではhardwareで三角波を発生させましたが、ここではhardwareではなにも発生させませんので DAC_WaveGeneration_None です.

③DAC出力バッファの強弱設定.弱でよいので DAC_OutputBuffer_Disable

④⑤構造体をDAC1,DAC2に書く

DAC_Cmd(DAC_Channel_1, ENABLE); DAC1オン

DAC_Cmd(DAC_Channel_2, ENABLE); DAC2オン

TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); TIM2カウンタドンつき割り込みオン

TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE); TIM3カウンタドンつき割り込みオン

TIM_Cmd(TIM2, ENABLE); TIM2オン

TIM_Cmd(TIM3, ENABLE); TIM3オン

while (1) { }

}

↓debug機能なのでとりあえず無視

#ifdef USE_FULL_ASSERT

void assert_failed(uint8_t* file, uint32_t line){ while(1) {} }

#endif

↓TIM2割り込みで、sin/cos計算とDAC設定をする.

上で述べた sin(t+1) = sin(t) + Δcos(t) , cos(t+1) = cos(t) - Δsin(t) の計算をやってます.

uint16_t dac1, dac2;

float sine_r=0, cos_r=1;

float Sine, Cos;

float div=10; // 10:338Hz 100:33.8Hz 1000:3.38Hz

①void TIM2_IRQHandler(void) // sine/cos wave generation

{

TIM_ClearITPendingBit(TIM2, TIM_IT_Update ); 割り込みフラグをクリアして次の割り込みを許可する

Sine = sine_r + cos_r/div; sin計算

Cos = cos_r - Sine/div; cos計算

sine_r = Sine; sin計算

cos_r = Cos; cos計算

dac1=(Sine+1)*2046; DAC1出力value

dac2=(Cos+1)*2046; DAC2出力value

DAC_SetDualChannelData (DAC_Align_12b_R, dac2, dac1); DAC valueを一時置き

DAC_SoftwareTriggerCmd (DAC_Channel_1, ENABLE ); DAC1出力

DAC_SoftwareTriggerCmd (DAC_Channel_2, ENABLE ); DAC2出力

}

①の関数名は、ひょんなところに明示されています.

project_top\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\gcc_ride7\startup_stm32f10x_md_vl.s

もしも他の割り込みハンドラを書きたいときにはこのファイルを参照しましょう.

↓出力portを論理反転する

void GPIO_ReverseBit(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)

{

if(GPIO_ReadInputDataBit(GPIOx, GPIO_Pin)==0) GPIO_SetBits(GPIOx, GPIO_Pin);

else GPIO_ResetBits(GPIOx, GPIO_Pin);

return;

}

↓10HzのTIM3割り込み.PC8とPC9のLED portをトグルします.

void TIM3_IRQHandler(void) // LED flash

{

TIM_ClearITPendingBit(TIM3, TIM_IT_Update );

GPIO_ReverseBit (GPIOC, GPIO_Pin_8);

GPIO_ReverseBit (GPIOC, GPIO_Pin_9);

}

以上でソースはおしまい

NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; のところの割り込み機能指定の選択肢リスト.

NonMaskableInt_IRQn Non Maskable Interrupt

MemoryManagement_IRQn Cortex-M3 Memory Management Interrupt

BusFault_IRQn Cortex-M3 Bus Fault Interrupt

UsageFault_IRQn Cortex-M3 Usage Fault Interrupt

SVCall_IRQn Cortex-M3 SV Call Interrupt

DebugMonitor_IRQn Cortex-M3 Debug Monitor Interrupt

PendSV_IRQn Cortex-M3 Pend SV Interrupt

SysTick_IRQn Cortex-M3 System Tick Interrupt

WWDG_IRQn Window WatchDog Interrupt

PVD_IRQn PVD through EXTI Line detection Interrupt

TAMPER_IRQn Tamper Interrupt

RTC_IRQn RTC global Interrupt

FLASH_IRQn FLASH global Interrupt

RCC_IRQn RCC global Interrupt

EXTI0_IRQn EXTI Line0 Interrupt

EXTI1_IRQn EXTI Line1 Interrupt

EXTI2_IRQn EXTI Line2 Interrupt

EXTI3_IRQn EXTI Line3 Interrupt

EXTI4_IRQn EXTI Line4 Interrupt

EXTI9_5_IRQn External Line[9:5] Interrupts

EXTI15_10_IRQn External Line[15:10] Interrupts

DMA1_Channel1_IRQn DMA1 Channel 1 global Interrupt

DMA1_Channel2_IRQn DMA1 Channel 2 global Interrupt

DMA1_Channel3_IRQn DMA1 Channel 3 global Interrupt

DMA1_Channel4_IRQn DMA1 Channel 4 global Interrupt

DMA1_Channel5_IRQn DMA1 Channel 5 global Interrupt

DMA1_Channel6_IRQn DMA1 Channel 6 global Interrupt

DMA1_Channel7_IRQn DMA1 Channel 7 global Interrupt

ADC1_IRQn ADC1 global Interrupt

TIM1_BRK_TIM15_IRQn TIM1 Break and TIM15 Interrupts

TIM1_UP_TIM16_IRQn TIM1 Update and TIM16 Interrupts

TIM1_TRG_COM_TIM17_IRQn TIM1 Trigger and Commutation and TIM17 Interrupt

TIM1_CC_IRQn TIM1 Capture Compare Interrupt

TIM2_IRQn TIM2 global Interrupt

TIM3_IRQn TIM3 global Interrupt

TIM4_IRQn TIM4 global Interrupt

TIM6_DAC_IRQn TIM6 and DAC underrun Interrupt

TIM7_IRQn TIM7 Interrupt

I2C1_EV_IRQn I2C1 Event Interrupt

I2C1_ER_IRQn I2C1 Error Interrupt

I2C2_EV_IRQn I2C2 Event Interrupt

I2C2_ER_IRQn I2C2 Error Interrupt

SPI1_IRQn SPI1 global Interrupt

SPI2_IRQn SPI2 global Interrupt

USART1_IRQn USART1 global Interrupt

USART2_IRQn USART2 global Interrupt

USART3_IRQn USART3 global Interrupt

RTCAlarm_IRQn RTC Alarm through EXTI Line Interrupt

CEC_IRQn HDMI-CEC Interrupt