PWMインプットキャプチャプログラミング
PWMキャプチャ
目的は、特定のピンに入力される PWM 波の周波数とデューティ サイクルを測定することです。
以下は PWM 部分の回路図です:
PWM は XL555 チップによって生成され、スライディング レオスタット R40 によって PA15 に接続されます。スライディング レオスタットの異なる抵抗値は、PWM 波の異なる周波数に対応します。同じ原則が次のものにも当てはまります。
ボード上の PA15 ピンの機能は TIM2_CH1 と TIM8_CH1 であることがわかります。ボード上の TIM_CH1 を使用して PWM をキャプチャします。PB4 ピンは、PWM をキャプチャするために TIM3_CH1 を使用します。(どちらも基本タイマーを使用します)
2 つの XL555 によって生成された波形をオシロスコープで観察すると、生成できる波形の周波数範囲はおよそ 700HZ ~ 23KHZ であることがわかります。デューティ サイクルは約 50 パーセントです。
シングルチャンネル PWM キャプチャ プログラミング
プログラミングのアイデア:
ピンを立ち上がりエッジ割り込みに設定します。つまり、方形波の立ち上がりエッジに遭遇するたびに割り込みが生成されます。
割り込みが発生すると、割り込み処理関数に入り、コールバック関数に入ります。
コールバック関数では何をすればよいでしょうか? CNT値を取得します。CNT は実際には時計の移動時間です。前の立ち上がりエッジでこの時間を取得し、それをクリアして、次の立ち上がりエッジでこの時間を取得します。これは 2 つの立ち上がりエッジ間の時間の長さです。したがって、CNT 値は PWM 波の周期であり、その周波数は周期を通じて取得できます。
周期の計算を容易にするために、CNT を 1 マイクロ秒プラス 1 に設定できます。次に、CNT の値は何マイクロ秒を表し、何 HZ 周波数に対応します。
ステップ:
- [テンプレート] STM32CUBEMX 生成コードのプロジェクトとして。
- TIM2 の PA15 を TIM2 CH1 入力キャプチャとして設定します。
- 要件に応じて、TIM2_CH1 の分周値を設定します (1us ごとに 1 回カウントするように設定することをお勧めします)。
- tim.c と tim.h を [プログラミング プロジェクト] に移植する
4.1 main.c に #include" tim.h" が含まれる
4.2 プロジェクトに tim.c と stm32g4 HAL ライブラリ関数を追加する;
4.3 stm32g4xx hal_conf.h で TIM モジュールを開始する;
4.4 stm32g4xx it.c で、TIM2_IRQHandler 割り込みサービス関数を移植します;
4.5 メイン関数で、MX TIM2 lnit) タイマー初期化関数と HAL_TIM_IC_Start_IT&htim2, TIM_CHANNEL 1) タイマー 2 キャプチャ関数を開始します; 4.6 HAL_TIM_IC_CaptureCallback コールバック関数で
、 CNT値からPWMの周波数を計算!
ピンを次のように構成します。
次に、プリスケーラ値を設定します。
周波数が 1MHZ になるようにプリスケーラ値を設定したい場合は、まず TIMER2 の周波数を知る必要があります。
STM32G431 のデータシートには次の情報があります。
TIMER2 は APB1 バスの下にマウントされたタイマーであることがわかります。
次に、CUBE のクロック ツリーで次のことがわかります。
APB1 バスの下のタイマーの周波数はすべて 80MHZ です。
そこで、プリスケーラ値を 79 に設定します。これは、1 マイクロ秒である 1MHZ に分割できます。
カウンタ期間: これはタイミング期間であり、CNT がオーバーフローして自動的にゼロに戻るまで増加する時間を決定します。
そのため、CNT が自動的にオーバーフローしてゼロに戻るのを防ぐために、この期間をできるだけ長く設定するようにしています。32 ビットなので、最大値は 0xffffffff なので、0xffffffff として設定できます。
最後に、コードを生成します (TIMER2 はバス上にマウントされているため、そのクロックは以前に初期化されているため、初期化する必要はありません)。
まず、main 関数で pwm 入力キャプチャ割り込みを有効にします。
HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1); //开启pwm输入捕获中断
テンプレート プロジェクトに割り込みコールバック関数を作成します。
//PWM捕获中断的回调函数
u32 tim2_cnt1;
u32 f40=0;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
//获取CNT的值
tim2_cnt1=__HAL_TIM_GetCounter(&htim2);
__HAL_TIM_SetCounter(&htim2,0);//获取cnt值之后就把他清零
f40=1000000/tim2_cnt1;//周期的倒数就是频率
HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1); //开启pwm输入捕获中断
}
LCD 画面に F40 値を表示して、方形波周波数の変化を観察します。
デュアルPWMキャプチャプログラミング
別の PWM キャプチャを設定するには
、PB4 を設定する必要があります。
基本的な設定は同じです。唯一注意する必要があるのは、PB4 のカウンタ周期が 16 ビットで、最大数が 65535 であることです。 0xffff。上記と同じではありません。同じであれば設定が間違っています。他に注意する必要はありません。コードを直接生成してください。
次に、コードの移行を実行し、テンプレート プロジェクトのコードをプログラミング プロジェクトに移植します。
main 関数に timer3 の初期化関数を追加し、timer3channel1 の pwm 入力キャプチャ割り込みをオンにした後、
コールバック関数を書き換えます。
//PWM捕获中断的回调函数
u32 tim2_cnt1,tim3_cnt1;
u32 f40=0,f30;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
//获取CNT的值
if(htim==&htim2)
{
tim2_cnt1=__HAL_TIM_GetCounter(&htim2);
__HAL_TIM_SetCounter(&htim2,0);//获取cnt值之后就把他清零
f40=1000000/tim2_cnt1;//周期的倒数就是频率
HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1); //开启pwm输入捕获中断
}
if(htim==&htim3)
{
tim3_cnt1=__HAL_TIM_GetCounter(&htim3);
__HAL_TIM_SetCounter(&htim3,0);//获取cnt值之后就把他清零
f30=1000000/tim3_cnt1;//周期的倒数就是频率
HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1); //开启pwm输入捕获中断
}
}
単一の PWM の周波数とデューティ サイクルを測定します
測定周波数は実際には測定期間です。
デューティ サイクルの測定は、サイクル全体でハイ レベルがどのくらい続くかを測定することです。
プログラミングのアイデア:
- 最初の立ち上がりエッジ割り込みはタイミングを開始し (カウンタをクリアし)、立ち下がりエッジ割り込みに変わります。
- 立ち下がりエッジ割り込みでは、カウンタのCNT値T1を取得し、立ち上がりエッジ割り込みに変更します。
- 2 番目の立ち上がりエッジ割り込みはカウンタの CNT 値 T2 を取得し、PWM 周波数は T2 を通じて取得できます。PWM のデューティ サイクルは T1/T2 を通じて取得できます。
したがって、一連の操作の後、実際には 2 つの時間が得られます。1 つは高レベルの時間、もう 1 つはサイクル全体の時間です。
デューティ サイクルを計算するにはこれで十分です。
したがって、現時点でのプログラミングの難しさは、割り込みの極性をどのように変更するかということです。
彼の tim 初期化関数で次のことがわかります。
void MX_TIM3_Init(void)
{
TIM_MasterConfigTypeDef sMasterConfig = {
0};
TIM_IC_InitTypeDef sConfigIC = {
0};
htim3.Instance = TIM3;
htim3.Init.Prescaler = 79;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 65535;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_IC_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
sConfigIC.ICFilter = 0;
if (HAL_TIM_IC_ConfigChannel(&htim3, &sConfigIC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
}
tim を初期化する構造体が定義されています。TIM_IC_InitTypeDef sConfigIC = {0};
この構造体の定義をクリックすると、以下が表示されます。
typedef struct
{
uint32_t ICPolarity; /*!< Specifies the active edge of the input signal.
This parameter can be a value of @ref TIM_Input_Capture_Polarity */
uint32_t ICSelection; /*!< Specifies the input.
This parameter can be a value of @ref TIM_Input_Capture_Selection */
uint32_t ICPrescaler; /*!< Specifies the Input Capture Prescaler.
This parameter can be a value of @ref TIM_Input_Capture_Prescaler */
uint32_t ICFilter; /*!< Specifies the input capture filter.
This parameter can be a number between Min_Data = 0x0 and Max_Data = 0xF */
} TIM_IC_InitTypeDef;
この構造の最初のものは割り込みの極性、2 つ目は入力チャネル、最後の 2 つはそれぞれ周波数分割とフィルタリングです。
初期化関数の後、関数を使用してHAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig)
この構造体を参照し、tim を構成します。それでは、この関数の定義を見てみましょう。
HAL_StatusTypeDef HAL_TIM_IC_ConfigChannel(TIM_HandleTypeDef *htim, TIM_IC_InitTypeDef *sConfig, uint32_t Channel)
{
/* Check the parameters */
assert_param(IS_TIM_CC1_INSTANCE(htim->Instance));
assert_param(IS_TIM_IC_POLARITY(sConfig->ICPolarity));
assert_param(IS_TIM_IC_SELECTION(sConfig->ICSelection));
assert_param(IS_TIM_IC_PRESCALER(sConfig->ICPrescaler));
assert_param(IS_TIM_IC_FILTER(sConfig->ICFilter));
/* Process Locked */
__HAL_LOCK(htim);
if (Channel == TIM_CHANNEL_1)
{
/* TI1 Configuration */
TIM_TI1_SetConfig(htim->Instance,
sConfig->ICPolarity,
sConfig->ICSelection,
sConfig->ICFilter);
/* Reset the IC1PSC Bits */
htim->Instance->CCMR1 &= ~TIM_CCMR1_IC1PSC;
/* Set the IC1PSC value */
htim->Instance->CCMR1 |= sConfig->ICPrescaler;
}
else if (Channel == TIM_CHANNEL_2)
{
/* TI2 Configuration */
assert_param(IS_TIM_CC2_INSTANCE(htim->Instance));
TIM_TI2_SetConfig(htim->Instance,
sConfig->ICPolarity,
sConfig->ICSelection,
sConfig->ICFilter);
/* Reset the IC2PSC Bits */
htim->Instance->CCMR1 &= ~TIM_CCMR1_IC2PSC;
/* Set the IC2PSC value */
htim->Instance->CCMR1 |= (sConfig->ICPrescaler << 8U);
}
else if (Channel == TIM_CHANNEL_3)
{
/* TI3 Configuration */
assert_param(IS_TIM_CC3_INSTANCE(htim->Instance));
TIM_TI3_SetConfig(htim->Instance,
sConfig->ICPolarity,
sConfig->ICSelection,
sConfig->ICFilter);
/* Reset the IC3PSC Bits */
htim->Instance->CCMR2 &= ~TIM_CCMR2_IC3PSC;
/* Set the IC3PSC value */
htim->Instance->CCMR2 |= sConfig->ICPrescaler;
}
else
{
/* TI4 Configuration */
assert_param(IS_TIM_CC4_INSTANCE(htim->Instance));
TIM_TI4_SetConfig(htim->Instance,
sConfig->ICPolarity,
sConfig->ICSelection,
sConfig->ICFilter);
/* Reset the IC4PSC Bits */
htim->Instance->CCMR2 &= ~TIM_CCMR2_IC4PSC;
/* Set the IC4PSC value */
htim->Instance->CCMR2 |= (sConfig->ICPrescaler << 8U);
}
__HAL_UNLOCK(htim);
return HAL_OK;
}
チャンネル1の場合は以下を実行すると書いてあります。
if (Channel == TIM_CHANNEL_1)
{
/* TI1 Configuration */
TIM_TI1_SetConfig(htim->Instance,
sConfig->ICPolarity,
sConfig->ICSelection,
sConfig->ICFilter);
/* Reset the IC1PSC Bits */
htim->Instance->CCMR1 &= ~TIM_CCMR1_IC1PSC;
/* Set the IC1PSC value */
htim->Instance->CCMR1 |= sConfig->ICPrescaler;
}
チャンネル2またはチャンネル3の場合は、以下のプログラムを実行します。
チャンネル 1 を見てみましょう。
ここで関数が呼び出されTIM_TI1_SetConfig
、sConfigIC
構造体のさまざまなパラメーターが渡されます。それでは、関数が内部でどのように動作するか
を見てから、構造体のパラメーターを渡してみましょう。TIM_TI1_SetConfig
sConfigIC
void TIM_TI1_SetConfig(TIM_TypeDef *TIMx, uint32_t TIM_ICPolarity, uint32_t TIM_ICSelection,
uint32_t TIM_ICFilter)
{
uint32_t tmpccmr1;
uint32_t tmpccer;
/* Disable the Channel 1: Reset the CC1E Bit */
TIMx->CCER &= ~TIM_CCER_CC1E;
tmpccmr1 = TIMx->CCMR1;
tmpccer = TIMx->CCER;
/* Select the Input */
if (IS_TIM_CC2_INSTANCE(TIMx) != RESET)
{
tmpccmr1 &= ~TIM_CCMR1_CC1S;
tmpccmr1 |= TIM_ICSelection;
}
else
{
tmpccmr1 |= TIM_CCMR1_CC1S_0;
}
/* Set the filter */
tmpccmr1 &= ~TIM_CCMR1_IC1F;
tmpccmr1 |= ((TIM_ICFilter << 4U) & TIM_CCMR1_IC1F);
/* Select the Polarity and set the CC1E Bit */
tmpccer &= ~(TIM_CCER_CC1P | TIM_CCER_CC1NP);
tmpccer |= (TIM_ICPolarity & (TIM_CCER_CC1P | TIM_CCER_CC1NP));
/* Write to TIMx CCMR1 and CCER registers */
TIMx->CCMR1 = tmpccmr1;
TIMx->CCER = tmpccer;
}
この中で各種レジスタが動作していることがわかります。次の行では、割り込み極性レジスタのビットが設定されています。
tmpccer |= (TIM_ICPolarity & (TIM_CCER_CC1P | TIM_CCER_CC1NP));
TIMx->CCER = tmpccer;
TIM_ICPolarity
極性を遮断するだけです。この文はtmpccer
この変数を操作し、最終的にtmpccer
この変数をこのレジスタに割り当てますTIMx->CCER
。
したがって、TIMx->CCER
このレジスタを操作するだけで割り込み極性を操作できます。
TIMx->CCER
クリックして手順を表示します。
__IO uint32_t CCER; /*!< TIM capture/compare enable register, Address offset: 0x20 */
公式コメントではCCERはインプットキャプチャイネーブルレジスタであることが分かります。
マニュアルを参照したところ、
CCER は 16 ビットのレジスタであることがわかりました (STM32 は 32 ビットですが、メモリを節約するために一部のレジスタは 16 ビットに設定される場合もあります)。
どちらを設定する必要がありますか?
マニュアルには、最後から 2 番目のビットである bit1 が割り込み極性 (Polarity) を設定すると記載されています。
つまり、CC1NP が 0 の場合は PWM 入力モードであり、入力モードで CC1P が 0 の場合は立ち上がりエッジ割り込みとなります。CC1P が 1 の場合、それは立ち下がりエッジ割り込みです。
したがって、CC1P ビットを変更するだけで済みます (PWM 入力モードを使用しているため、CC1NP は初期化中にすでに 0 に初期化されています)。
プログラミングを開始します。
コールバック関数の TIM2 は次のように記述できます。
if(htim==&htim2)
{
if(tim2_state==0)//第一个上升沿产生,开始计时
{
__HAL_TIM_SetCounter(&htim2,0);//把CNT清零
TIM2->CCER|=0x02; //下降沿中断,就是要把CC1P置为1
tim2_state=1;//等待下降沿产生
}
else if(tim2_state==1)//第一个下降沿产生,获取T1的值,就是高电平的时长
{
tim2_cnt1=__HAL_TIM_GetCounter(&htim2);//获取CNT的值
TIM2->CCER &=~0x02;
tim2_state=2;
}
else if(tim2_state==2)//第二个上升沿产生,获取T2的值,就是整个周期
{
tim2_cnt2=__HAL_TIM_GetCounter(&htim2);//获取CNT的值
f40=1000000/tim2_cnt2;//周期的倒数就是频率
d40=tim2_cnt1*100.0f/tim2_cnt2;//计算占空比
tim2_state=0;
}
HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1); //开启pwm输入捕获中断
}
2 つの PWM チャネルの周波数とデューティ サイクルを測定します
上記のコードを TIMER3 に適用して、再度記述するだけです。
//PWM捕获中断的回调函数
u32 tim2_cnt1,tim3_cnt1,tim2_cnt2,tim3_cnt2;
u32 f40=0,f30=0;
float d40=0,d30=0;
u8 tim2_state=0,tim3_state=0;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
//获取CNT的值
if(htim==&htim2)
{
if(tim2_state==0)//第一个上升沿产生,开始计时
{
__HAL_TIM_SetCounter(&htim2,0);//把CNT清零
TIM2->CCER|=0x02; //下降沿中断,就是要把CC1P置为1
tim2_state=1;//等待下降沿产生
}
else if(tim2_state==1)//第一个下降沿产生,获取T1的值,就是高电平的时长
{
tim2_cnt1=__HAL_TIM_GetCounter(&htim2);//获取CNT的值
TIM2->CCER &=~0x02;
tim2_state=2;
}
else if(tim2_state==2)//第二个上升沿产生,获取T2的值,就是整个周期
{
tim2_cnt2=__HAL_TIM_GetCounter(&htim2);//获取CNT的值
f40=1000000/tim2_cnt2;//周期的倒数就是频率
d40=tim2_cnt1*100.0f/tim2_cnt2;
tim2_state=0;
}
HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1); //开启pwm输入捕获中断
}
if(htim==&htim3)
{
if(tim3_state==0)//第一个上升沿产生,开始计时
{
__HAL_TIM_SetCounter(&htim3,0);//把CNT清零
TIM3->CCER|=0x02; //下降沿中断,就是要把CC1P置为1
tim3_state=1;//等待下降沿产生
}
else if(tim3_state==1)//第一个下降沿产生,获取T1的值,就是高电平的时长
{
tim3_cnt1=__HAL_TIM_GetCounter(&htim3);//获取CNT的值
TIM3->CCER &=~0x02;
tim3_state=2;
}
else if(tim3_state==2)//第二个上升沿产生,获取T2的值,就是整个周期
{
tim3_cnt2=__HAL_TIM_GetCounter(&htim3);//获取CNT的值
f30=1000000/tim3_cnt2;//周期的倒数就是频率
d30=tim3_cnt1*100.0f/tim3_cnt2;
tim3_state=0;
}
HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_1); //开启pwm输入捕获中断
}
}