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