一連のサンプルプログラムはSTMライブラリ付属のサンプルプログラムをわたしがいじったものです.
不覚にもこのサンプルプログラムには感銘を覚えました.
なぜかというと、TIM3にはカウンタは16bitのが1本しかないわけです.
それなのに、4種類の任意の周波数を発生させられるというのがこのサンプルプログラムです.
そんなことできるわけがない、英文だから何を言いたいのかよくわからん!と思っていたのですが、source codeを眺めたら、本当に4種類の周波数を発生できます.
原理はこうです
・16bitカウンタは何も考えずに0~FFFFをグルグルと回り続けます.カウンタ値をcntとしCPUが必要に応じてそれを読みます.
・比較回路出力はFFを反転させてパルスを生成します.
・比較回路出力は同時にCPUに割り込みます.
・CPUの割り込みルーチンは、比較値レジスタを変更します.
ref[15:0] = cnt[15:0] + const
constは、出力パルス半周期のカウント数です.
・割り込みがかかる度に、比較値レジスタをconstだけ先に進めた値にupdateすることでconst回毎の割り込みを実現しています.
・下図の赤い囲みの回路が4つ内蔵されてますので、4種類の周波数を発生できます.
おおっ、なんてミラクル・ワンダフルなんだ!!
外部仕様
TIM3の出力ピンであるTIM3_CHxをremapしてPC6,PC7,PC8,PC9(37,38,39,40pin)にアサインします.
PC8とPC9にはLEDが接続されていますので、2つのLEDを異なる周波数で点滅させます.
PC6とPC7はオシロで観測するしかありませんが、異なる周波数のパルスを出力します.
内部仕様
タイマはTIM3を使います.
比較回路出力で割り込みします.(TIM_IT_CC1,TIM_IT_CC2,TIM_IT_CC3,TIM_IT_CC4)
CPUは、24MHzの最大clockで動かします.
APBバスのclockは、24MHz/8/8=375kHz.TIM3のclockはこの2倍で750kHzが供給される.
LEDは目に見えるくらいの10Hzぐらいの周波数で点滅させる.具体的には下記.
750kHz / 6678 / 2 = 56.15Hz (TIM3_CH1, PC6)
750kHz / 17891 / 2 = 20.96Hz (TIM3_CH2, PC7)
750kHz / 42556 / 2 = 8.81Hz (TIM3_CH3, PC8) LED
750kHz / 61238 / 2 = 6.12Hz (TIM3_CH4, PC9) LED
サンプルプログラム
ページ末尾からproject folderをDLできます.eclipseへの組み込み方はこちらを参照してください.
main.c
#include "stm32f10x.h" CPUのハードウエアによって決まる割り込み番号やレジスタアドレスを定義しています
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIMxを設定するためだけに必要な構造体です
TIM_OCInitTypeDef TIM_OCInitStructure; TIMxを設定するためだけに必要な構造体です
↓タイマカウンタと比較する値です.
この比較値からTIM3_CH4出力周波数を計算する式は、いまのところのわたしの知識ではこうなります.下図がclock系統図です.
水晶周波数 = 8MHz
PLL出力 = 水晶 x3 = 24MHz = SYSCLK (PLLの仕様はまだ読んでません)
HCLK = SYSCLK / prescaler = 24MHz / 8 = 3MHz
PCLK1 = HCLK / 8 = 375kHz
TIM3CLK = PCLK * 2 = 750kHz (この仕様は謎ですね.なんでx2するの?)
TIM3内部にもプリスケがありますが、それは1にします.なので750kHzのまま.
出力周波数 = 750kHz / 61238 / 2 = 6.12Hz (61238は半周期につき÷2)
__IO uint16_t CCR1_Val = 6677; // 24MHz / 8 / 8 * 2 / 1 / 6678 / 2 = 56.15Hz (TIM3_CH1, PC6)
__IO uint16_t CCR2_Val = 17890; // 24MHz / 8 / 8 * 2 / 1 / 17891 / 2 = 20.96Hz (TIM3_CH2, PC7)
__IO uint16_t CCR3_Val = 42555; // 24MHz / 8 / 8 * 2 / 1 / 42556 / 2 = 8.81Hz (TIM3_CH3, PC8)
__IO uint16_t CCR4_Val = 61237; // 24MHz / 8 / 8 * 2 / 1 / 61238 / 2 = 6.12Hz (TIM3_CH4, PC9)
void RCC_Configuration(void); resetとclockの初期設定のための関数です
void GPIO_Configuration(void); GPIOの初期設定のための関数です
void NVIC_Configuration(void); 割り込みの初期設定の関数です
int main(void)
{
RCC_Configuration(); clockの初期設定をしてます.プリスケも設定しています.下の方にcodeがあります.
NVIC_Configuration(); NVICの初期設定をしてます.下の方にcodeがあります.
GPIO_Configuration(); GPIOの初期設定をしてます.下の方にcodeがあります.
↓ここからがいよいよTIM3の設定です.
RCC_Configuration()によってプリスケが設定された結果、TIM3のclockは24MHz/8/8*2=750kHzです.
/* Time base configuration */
TIM_TimeBaseStructure.TIM_Period = 65535; 16bitカウンタなので65535が最大値です
TIM_TimeBaseStructure.TIM_Prescaler = 0; プリスケ=1です
TIM_TimeBaseStructure.TIM_ClockDivision = 0; 外部信号をサンプルする周期fDTSを次のように決めます.外部信号を受信するわけではないので0でOKです.
0: fDTS = 24MHz
1: fDTS = 12MHz
2: fDTS = 6MHz
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; 次の選択肢があります.
TIM_CounterMode_Up カウントアップモード
TIM_CounterMode_Down カウントダウンモード
TIM_CounterMode_CenterAligned1 カウントアップダウンモード(割り込みは谷で発行する)
TIM_CounterMode_CenterAligned2 カウントアップダウンモード(割り込みは山で発行する)
TIM_CounterMode_CenterAligned3 カウントアップダウンモード(割り込みは山谷両方で発行する)
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); 構造体の情報をTIM3に設定します.
↓TIM3_CH1の比較値の設定などをしています.
/* Output Compare Toggle Mode configuration: Channel1 */
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle; TIM_OCModeは次の選択肢があります.
TIM_OCMode_PWM1 PWMモード
TIM_OCMode_PWM2 PWMモード(逆極性)
TIM_OCMode_Active カウンタ=しきい値になった瞬間、TIM3_CH1にHIGHパルスを出すらしい
TIM_OCMode_Inactive カウンタ=しきい値になった瞬間、TIM3_CH1にLOWパルスを出すらしい
TIM_OCMode_Timing 比較動作はしないでカウンタだけがクルクル回るらしいがよくわからない
TIM_OCMode_Toggle カウンタ=しきい値になったら、TIM3_CH1を反転させる
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OutputState_DisableまたはTIM_OutputState_Enableです.
TIM_OCInitStructure.TIM_Pulse = CCR1_Val; 冒頭で値を決めた比較値.
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; 出力極性.TIM_OCPolarity_HighまたはTIM_OCPolarity_Lowです.
TIM_OC1Init(TIM3, &TIM_OCInitStructure); 構造体の情報をTIM3に設定します.
TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Disable); いまいちこの意味を理解できていません
↓TIM3_CH2の比較値の設定などをしています.
/* Output Compare Toggle Mode configuration: Channel2 */
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = CCR2_Val;
TIM_OC2Init(TIM3, &TIM_OCInitStructure);
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Disable);
↓TIM3_CH3の比較値の設定などをしています.
/* Output Compare Toggle Mode configuration: Channel3 */
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = CCR3_Val;
TIM_OC3Init(TIM3, &TIM_OCInitStructure);
TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Disable);
↓TIM3_CH4の比較値の設定などをしています.
/* Output Compare Toggle Mode configuration: Channel4 */
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = CCR4_Val;
TIM_OC4Init(TIM3, &TIM_OCInitStructure);
TIM_OC4PreloadConfig(TIM3, TIM_OCPreload_Disable);
/* TIM enable counter */
TIM_Cmd(TIM3, ENABLE); TIM3よ動け!
/* TIM IT enable */ 割り込みenable
TIM_ITConfig(TIM3, TIM_IT_CC1 | TIM_IT_CC2 | TIM_IT_CC3 | TIM_IT_CC4, ENABLE);
タイマ割り込みにはこういう種類があります.
TIM_IT_Update: TIM update Interrupt source カウンタがドンついたら割り込み
TIM_IT_CC1: TIM Capture Compare 1 Interrupt source カウンタが比較値を一致したら割り込み
TIM_IT_CC2: TIM Capture Compare 2 Interrupt source カウンタが比較値を一致したら割り込み
TIM_IT_CC3: TIM Capture Compare 3 Interrupt source カウンタが比較値を一致したら割り込み
TIM_IT_CC4: TIM Capture Compare 4 Interrupt source カウンタが比較値を一致したら割り込み
TIM_IT_COM: TIM Commutation Interrupt source 勉強不足でわかりません
TIM_IT_Trigger: TIM Trigger Interrupt source 勉強不足でわかりません
TIM_IT_Break: TIM Break Interrupt source 勉強不足でわかりません
while(1) {}
}
void RCC_Configuration(void)
{
↓上で出力周波数の計算式を書きましたが、そのうち①と②のプリスケ設定をここでやっています.
PLL出力 = 水晶 x3 = 24MHz = SYSCLK (PLLの仕様はまだ読んでません)
①HCLK = SYSCLK / prescaler = 24MHz / 8 = 3MHz
②PCLK1 = HCLK / 8 = 375kHz
TIM3CLK = PCLK * 2 = 750kHz (この仕様は謎ですね.なんでx2するの?)
TIM3内部にもプリスケがありますが、それは1にします.なので750kHzのまま.
出力周波数 = 750kHz / 61238 / 2 = 6.12Hz (61238は半周期につき÷2)
①RCC_HCLKConfig(RCC_SYSCLK_Div8); // HCLK=24MHz/8=3MHz
②RCC_PCLK1Config(RCC_HCLK_Div8); // PCLK1=3MHz/8=375kHz
↓APB1にぶら下がったTIM3のclockをenableしています.
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
↓APB2にぶら下がったGPIOとAFIOをenableしています.
AFIOはAlternate Functionのことで、TIMx_CHxのようなperipheral出力がAFIOです.
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB|
RCC_APB2Periph_GPIOC | RCC_APB2Periph_AFIO, ENABLE);
}
↓GPIO_Configuration()は、GPIOの初期設定をしています.
ここでGPIOを設定するとは、TIM3の出力すなわちTIM3_CH1,TIM3_CH2,TIM3_CH3,TIM3_CH4をSTM32のどこかのピンに出力するように設定することに他なりません.
しかしTIM3_CHxをSTM32の任意のピンに設定できるわけではありません.あらかじめどこそものピンと決まっています.
どこのピンかを知るにはどの資料を参照すればいいのでしょうか?
それが下記の資料です.24ページのtable4を見て下さい.
http://www.st.com/internet/com/TECHNICAL_RESOURCES/TECHNICAL_LITERATURE/DATASHEET/CD00251732.pdf
TIM3_CHxが登場する場所を抜粋しました.
table4の見方を説明します.
・STM32-DISCOVERYに載っているのはLQFP64ですのでPins列はLQFP64の列に着目します.
・Pin name列にはPC6などの名前が書かれています.これはGPIOC-bit6の意味です.
・Main functionの列は、起動後に何も手を下さなければこの機能がアサインされます.
・Default列は、peripheralのIOとしてアサインしたときの機能が書かれています.22,23,26,27pinにTIM3_CHxがアサインされていることがわかります.
・Remap列も、peripheralのIOとしてアサインしたときの機能が書かれています.Remapすると63,64,65,66pinにTIM3_CHxのアサインが移動します.
void GPIO_Configuration(void) GPIO初期設定
{
GPIO_InitTypeDef GPIO_InitStructure; GPIO設定用構造体
↓以下のコメントアウトされている部分は、TIM3_CHxをAlternate Functionのdefaultアサインする手続きです.つまり22,23,26,27pinにTIM3_CHxがアサイン.
/*
// TIM3_CH12 output ports are PA6,PA7 (default)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// TIM3_CH34 output ports are PB0,PB1 (default)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_Init(GPIOB, &GPIO_InitStructure);
*/
GPIO_Pinの選択肢は、GPIO_Pin_0~GPIO_Pin_15およびGPIO_Pin_Allです.
GPIO_Modeの選択肢はつぎです.
GPIO_Mode_AIN アナログ入力
GPIO_Mode_IN_FLOATING floating入力(pull-upもpull-downもしない)
GPIO_Mode_IPD pull-down入力
GPIO_Mode_IPU pull-up入力
GPIO_Mode_Out_OD open-drain出力
GPIO_Mode_Out_PP push-pull出力 (フツーの出力)
GPIO_Mode_AF_OD Altanate Function open-drain出力 (peripheralのIOピンとしてアサイン)
GPIO_Mode_AF_PP Altanate Function push-pull (peripheralのIOピンとしてアサイン)
GPIO_Speedの選択肢は、
GPIO_Speed_2MHz
GPIO_Speed_10MHz
GPIO_Speed_50MHz
GPIO_Init()は2回登場してます.
GPIOA-6(31)とGPIOA-7(32)にAFのdefaultをアサインさせとります.
GPIOB-0(26)とGPIOB-1(27)にAFのdefaultをアサインさせとります.
↓以下の部分は、TIM3_CHxをAlternate Functionのremapアサインする手続きです.つまり37,38,39,40pinにTIM3_CHxがアサイン.
// TIM3_CH1234 output ports are PC6,PC7,PC8,PC9 (Remap)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_PinRemapConfig(GPIO_FullRemap_TIM3, ENABLE);
構造体の設定とGPIO_Init()は既出なので説明は割愛します.
キモはGPIO_PinRemapConfig()です.こやつがremapにしろ!と命じています.
第1引数の選択肢は、たくさんあるので末尾に記します.
}
↓割り込みコントローラNVICを設定しています.何を設定しているのかは勉強不足でわかりません.
void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* Enable the TIM3 global Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
↓debug用の関数です.とりあえず使ってないので無視しときます.
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t* file, uint32_t line)
{
while (1) {}
}
#endif
↓これが割り込みルーチンです.超重要.
ところで、このTIM3_IRQHandler()という名前が、どこでTIM3割り込みにアサインされているのかと疑問を感じませんか?
こたえは、この奥深いところにあるファイルに記載されています.
C:\eclipse\workspace\stdperiph_02_TIM_TOGGLE\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\gcc_ride7\startup_stm32f10x_md_vl.s
一部を抜粋するとこのように割り込みハンドラ名がリストされています.
この割り込みハンドラ名を知らないと、割り込みプログラムを書けないのでこのファイルはさりげなく重要です.
.word RCC_IRQHandler
.word EXTI0_IRQHandler
.word DMA1_Channel1_IRQHandler
.word DMA1_Channel7_IRQHandler
.word ADC1_IRQHandler
.word EXTI9_5_IRQHandler
.word TIM1_BRK_TIM15_IRQHandler
.word TIM1_UP_TIM16_IRQHandler
.word TIM1_TRG_COM_TIM17_IRQHandler
.word TIM1_CC_IRQHandler
.word TIM2_IRQHandler
.word TIM3_IRQHandler
.word TIM4_IRQHandler
.word I2C1_EV_IRQHandler
.word SPI1_IRQHandler
.word USART1_IRQHandler
void TIM3_IRQHandler(void)
{
uint16_t cnt, ref;
↓やっていることは、次回のパルスエッジ時刻で割り込みをかけてもらうために比較値を変更することです.
if (TIM_GetITStatus(TIM3, TIM_IT_CC1) != RESET) { TIM3_CH1の割り込みか?
TIM_ClearITPendingBit(TIM3, TIM_IT_CC1 ); 割り込み受付したので、次回の割り込みを許可する
cnt = TIM_GetCapture1(TIM3); カウンタ値を採取
ref = cnt + CCR1_Val; 比較値=カウンタ現在値+半周期
TIM_SetCompare1(TIM3, ref ); 比較値をTIM3_CH1にセットする
}
if (TIM_GetITStatus(TIM3, TIM_IT_CC2) != RESET) { TIM3_CH2の割り込みか?
TIM_ClearITPendingBit(TIM3, TIM_IT_CC2);
cnt = TIM_GetCapture2(TIM3);
ref = cnt + CCR2_Val;
TIM_SetCompare2(TIM3, ref );
}
if (TIM_GetITStatus(TIM3, TIM_IT_CC3) != RESET) { TIM3_CH3の割り込みか?
TIM_ClearITPendingBit(TIM3, TIM_IT_CC3);
cnt = TIM_GetCapture3(TIM3);
ref = cnt + CCR3_Val;
TIM_SetCompare3(TIM3, ref );
}
if (TIM_GetITStatus(TIM3, TIM_IT_CC4) != RESET) { TIM3_CH4の割り込みか?
TIM_ClearITPendingBit(TIM3, TIM_IT_CC4);
cnt = TIM_GetCapture4(TIM3);
ref = cnt + CCR4_Val;
TIM_SetCompare4(TIM3, ref );
}
}
GPIO_PinRemapConfig()の第1引数の選択肢リスト.
下記の選択肢を使ったらなにが起きるのかは試してません.
いまは名前から推測するってことでおしまいにつき.
GPIO_Remap_SPI1 : SPI1 Alternate Function mapping
GPIO_Remap_I2C1 : I2C1 Alternate Function mapping
GPIO_Remap_USART1 : USART1 Alternate Function mapping
GPIO_Remap_USART2 : USART2 Alternate Function mapping
GPIO_PartialRemap_USART3 : USART3 Partial Alternate Function mapping
GPIO_FullRemap_USART3 : USART3 Full Alternate Function mapping
GPIO_PartialRemap_TIM1 : TIM1 Partial Alternate Function mapping
GPIO_FullRemap_TIM1 : TIM1 Full Alternate Function mapping
GPIO_PartialRemap1_TIM2 : TIM2 Partial1 Alternate Function mapping
GPIO_PartialRemap2_TIM2 : TIM2 Partial2 Alternate Function mapping
GPIO_FullRemap_TIM2 : TIM2 Full Alternate Function mapping
GPIO_PartialRemap_TIM3 : TIM3 Partial Alternate Function mapping
GPIO_FullRemap_TIM3 : TIM3 Full Alternate Function mapping
GPIO_Remap_TIM4 : TIM4 Alternate Function mapping
GPIO_Remap_PD01 : PD01 Alternate Function mapping
GPIO_Remap_TIM5CH4_LSI : LSI connected to TIM5 Channel4 input capture for calibration
GPIO_Remap_ADC1_ETRGINJ : ADC1 External Trigger Injected Conversion remapping
GPIO_Remap_ADC1_ETRGREG : ADC1 External Trigger Regular Conversion remapping
GPIO_Remap_SWJ_NoJTRST : Full SWJ Enabled (JTAG-DP + SW-DP) but without JTRST
GPIO_Remap_SWJ_JTAGDisable : JTAG-DP Disabled and SW-DP Enabled
GPIO_Remap_SWJ_Disable : Full SWJ Disabled (JTAG-DP + SW-DP)
GPIO_Remap_TIM15 : TIM15 Alternate Function mapping (only for Value line devices)
GPIO_Remap_TIM16 : TIM16 Alternate Function mapping (only for Value line devices)
GPIO_Remap_TIM17 : TIM17 Alternate Function mapping (only for Value line devices)
GPIO_Remap_CEC : CEC Alternate Function mapping (only for Value line devices)
GPIO_Remap_TIM1_DMA : TIM1 DMA requests mapping (only for Value line devices)