タイミングシステムの一般的な理解
前回の記事では、簡単なSysTickタイマーを使用して安定した遅延を作成する方法を学び、割り込みメカニズムにも少し突入しました。 次に、HCLKソースからタイマーをクロックすることにより、8 MHzが得られると仮定しました。 今こそ、これらの数字がどこから来たのかを理解する時です。
ドキュメントを開き、「クロック信号MDR_RST_CLK」に移行すると、このようなテーブルが表示されます。
ビルトインRC HSIジェネレーター
HSIジェネレーターは、8 MHzのクロック周波数を生成します。 UCCの電源が入ると、発電機が自動的に起動します。
したがって、スイッチをオンにした後、コントローラーはHSIからクロックされます。 ブロック図では、各ブロックの周波数の変化を示すシーケンスを青で強調表示しました。 デフォルトでは、周波数ソースセレクター(MUX)はHSIから受信するように構成されています。 周波数には何も起こらず、HCLK(SysTickタイマーと組み合わされたクロックライン)を介して、変更なしでCPU_CLKに送られます。 ただし、内部RSジェネレーターを正確なデバイスと呼ぶことはできません。 その周波数は非常に不安定です。 これらの目的のために、外部8 MHz水晶振動子がボードに取り付けられています。 しかし、それは何と呼ばれていますか?
外部HSEジェネレーター
HSEジェネレーターは、外部共振器を使用して2..16 MHzのクロック周波数を生成するように設計されています。 UCCが生成されると、ジェネレーターが起動します...
ここで、コントローラーをクロックからHSIからHSEに切り替える方法を理解する必要があります。 もう一度、ブロック図を見てください。 信号が外部水晶振動子からコアクロックラインとHCLKにどのように通過するかを論理的に赤でマークしました。
クロックレジスタのセットアップ。
クロックの動きを比representation的に表現したので、クロックレジスタを整理します。 各ブロックの隣の図には、特定のレジスタのビットがあり、「移動」とクロック周波数を変更できるレジスタを変更します。 これらのビットはレジスタMDR_RST_CLK-> CPU_CLOCKにあります。 変更する必要があるビットを赤でマークしました。 青はそのままにしておくことができます。 それらはすでに正しい位置にインストールされています。
次に、値を変更するビットのdefineを記述する必要があります。
#define HCLK_SEL(CPU_C3) (1<<8) #define CPU_C1_SEL(HSE) (1<<1)
他のすべてのビットはゼロに等しいため、古いデータを消去することを恐れずに、レジスタに直接書き込むことができます。 その結果、次のことが明らかになります。
RST_CLK->CPU_CLOCK = CPU_C1_SEL(HSE)|HCLK_SEL(CPU_C3);
それはすべてのようです。 しかし、私たちがそれを縫うと、それが得られます。
その後のデバッグの不可能性。 私はすぐに、エラーの説明を含むドキュメントに進みました。 しかし、それはありませんでした。 判明したように、重要な特定の詳細が1つ欠落していました。 HSEの説明によると:
HSEジェネレーターは、外部共振器を使用して2..16 MHzのクロック周波数を生成するように設計されています。 ジェネレータは、UCC電源とHSEONイネーブル信号がHS_CONTROLレジスタに現れると起動します。
このレジスタを見てみましょう。
別の1を定義し、有効化ビットを有効にします。
#define HS_CONTROL(HSE_ON) (1<<0) RST_CLK->HS_CONTROL = HS_CONTROL(HSE_ON);
小さな余談。
エラーを検索すると、周辺クロックページ(前のSysTickに関する記事で説明)に戻り、RST_CLKが見つかりました。 デフォルトではオンになっていますが、水晶振動子を使用したすべての操作の前に、その包含を規定しています。
すべての追加の後、関数は次のようになりました。
#define HCLK_SEL(CPU_C3) (1<<8) #define CPU_C1_SEL(HSE) (1<<1) #define PCLK_EN(RST_CLK) (1<<4) #define HS_CONTROL(HSE_ON) (1<<0) void HSE_Init (void) { RST_CLK->PER_CLOK |= PCLK_EN(RST_CLK); // ( ). RST_CLK->HS_CONTROL = HS_CONTROL(HSE_ON) // HSE . RST_CLK->CPU_CLOCK = CPU_C1_SEL(HSE)|HCLK_SEL(CPU_C3); // "" HSE . }
最初の「松葉杖」。
その瞬間、エラーのリストでHSEを開始するエラーを探していたときに、次のグリッチに遭遇しました。
外部の水晶振動子からクロックをオンにした直後に内部ジェネレーターをオフにすることを計画していたので、覚えておくしかありませんでした。 このエラーは、マイクロコントローラーのすべてのリビジョンに存在します。 彼女にとっては松葉杖を彫る必要があります。 すぐに言います。 私の場合、HSIを無効にすることはできませんでした。 推奨事項に記載されているとおりにすべてを行ったという事実にもかかわらず。
問題と「解決策」の方法をより詳細に研究します。
ここに記載されているレジスタがあります。
必要なビットとRTCクロックの定義を記述します。
#define REG_0F(HSI_ON) ~(1<<22) #define RTC_CS(ALRF) (1<<2) #define PCLK(BKP) (1<<27)
それでは、実際に実行してみましょう。
#define HCLK_SEL(CPU_C3) (1<<8) #define CPU_C1_SEL(HSE) (1<<1) #define PCLK_EN(RST_CLK) (1<<4) #define HS_CONTROL(HSE_ON) (1<<0) #define REG_0F(HSI_ON) ~(1<<22) #define RTC_CS(ALRF) (1<<2) #define PCLK(BKP) (1<<27) #define CPU_C2_SEL(CPU_C2_SEL) (1<<2) void HSE_Init (void) { RST_CLK->PER_CLOK |= PCLK_EN(RST_CLK); // ( ). RST_CLK->HS_CONTROL = HS_CONTROL(HSE_ON) // HSE . RST_CLK->CPU_CLOCK = CPU_C1_SEL(HSE)|HCLK_SEL(CPU_C3); // "" HSE . RST_CLK->PER_CLOK |= PCLK(BKP); // ( ). BKP->RTC_CS |= RTC_CS(ALRF); // HSI. BKP->REG_0F = BKP->REG_0F&(REG_0F(HSI_ON)); // HSI. }
プログラムの実装を監視します。 これは、ビットをクリアしようとする前のレジスタの状態です。
そして、これは後です。
最初は、オフセットと間違えられ、キビはALRFビットをオンにしなかったが、右側のメニュー(ダイレクトレジスタ設定)でそれをクリックして、何も受信しなかったと思いました。 適切なタイミングを選択しなかったと思いましたが、他のいくつかのセルをクリックすることで応答がありました。 どうやら、このメソッドはこの問題を解決しません。 ALRFとHSI_ONを連続して押してジェネレーターをオフにしようとすると、ジェネレーターをオフにするというアイデアに失望します。 じゃあ これは、初期段階ではあまり干渉しません。 しかし、将来的には問題になるでしょう。 特にハンドヘルドデバイスを作成する場合。
16 MHzを取得します。
コントローラを外部水晶振動子からクロックさせることができたため、より正確な時間遅延を得ることができました。 ここで、周波数逓倍器の使用方法を学習します。 コントローラーの説明には、最大80 MHzの周波数でクロックできると記載されています。 クロック周波数を2倍にできる関数を作成してみましょう。 ブロック図をもう一度見てみましょう。
ここで、周波数がCPU_C1ラインに沿ってCPU_C2に「下がる」前に、CPU PLLを通過します。 そこで、ある値が「乗算」されます。 そのレジスタをさらに詳しく考えてみましょう。
PLLでは、クロックビットを含める必要はありません。 したがって、すぐにレジスタの設定を開始できます。 繰り返しますが、必要な定義を記述します。
#define PLL_CONTROL_PLL_CPU_ON (1<<2) //PLL .
さて、包含関数自体。
#define PLL_CONTROL_PLL_CPU_ON (1<<2) //PLL . void HSE_16Mhz_Init (void) // " 2 " . { RST_CLK->PLL_CONTROL = PLL_CONTROL_PLL_CPU_ON|(1<<8); // PLL, 2 . RST_CLK->HS_CONTROL = HS_CONTROL(HSE_ON); // HSE . RST_CLK->CPU_CLOCK = CPU_C1_SEL(HSE)|HCLK_SEL(CPU_C3)|CPU_C2_SEL(CPU_C2_SEL) ; // " " HSE . }
これで、プロジェクトに関数を追加できます。
int main (void) { Init_SysTick(); // . Led_init(); // 0 C . PORTC->RXTX |= 1; Delay_ms (1000); PORTC->RXTX = 0; Delay_ms (1000); HSE_16Mhz_Init(); while (1) { PORTC->RXTX |= 1; Delay_ms (1000); PORTC->RXTX = 0; Delay_ms (1000); } }
ちらつくLEDを1サイクルだけ残したわけではないことに注意してください。 これは、いわゆる「レスキュープログラム」です。 実験中に問題が発生した場合、RESETを押した後、更新されたファームウェアでMKをフラッシュするのに1秒かかります。
点滅後、LEDは内部のクォーツから2回点滅し、外部から2回速く点滅します。
次に、コードを最適化します。 3行の関数を使用するのは愚かなことです。 さらに、2つ目は2番目の関数で繰り返されます。 したがって、それらを定義することを提案します。 このフォームでは、最初の関数が表示されます。
#define RST_CLK_ON_Clock() RST_CLK->PER_CLOCK |= PCLK_EN(RST_CLK) // ( ). #define HSE_Clock_ON() RST_CLK->HS_CONTROL = HS_CONTROL(HSE_ON) // HSE . #define HSE_Clock_OffPLL() RST_CLK->CPU_CLOCK = CPU_C1_SEL(HSE)|HCLK_SEL(CPU_C3);// "" HSE .
そして2番目から、私はユニバーサル周波数スイッチング機能を作ることを提案します。 これを行うには、前の関数からHSE設定を破棄し、PLL_CONTROLレジスタに別のPLLリスタートビットを追加します。 したがって、新しい値を受け取ったら、すぐにその値を記録し始めます。 関数は次のようになり始めます。
#define PLL_CONTROL_PLL_CPU_ON (1<<2) //PLL . #define PLL_CONTROL_PLL_CPU_PLD (1<<3) // PLL. void HSE_PLL (uint8_t PLL_multiply) // " 2 " . { RST_CLK->PLL_CONTROL = RST_CLK->PLL_CONTROL&(~(0xF<<8)); // . RST_CLK->PLL_CONTROL |= PLL_CONTROL_PLL_CPU_ON|((PLL_multiply-1)<<8)|PLL_CONTROL_PLL_CPU_PLD; // PLL X , PLL. RST_CLK->CPU_CLOCK |= HCLK_SEL(CPU_C3)|CPU_C2_SEL(CPU_C2_SEL)|CPU_C1_SEL(HSE); // "" PLL HSE. }
メインプログラムに追加します。 これも整理されています。
void Block (void) // (). { PORTC->RXTX |= 1; Delay_ms (1000); PORTC->RXTX = 0; Delay_ms (1000); } int main (void) { Init_SysTick(); // . Led_init(); // 0 C . Block(); // (). HSE_Clock_ON(); // HSE . HSE_PLL(2); // 2 while (1) { PORTC->RXTX |= 1; Delay_ms (1000); PORTC->RXTX = 0; Delay_ms (1000); } }
結論として、各サイクル後に乗算係数を変更して、速度を最大10まで上げて80 MHzにするプログラムを作成します。
void Block (void) // (). { PORTC->RXTX |= 1; Delay_ms (1000); PORTC->RXTX = 0; Delay_ms (1000); } int main (void) { Init_SysTick(); // . Led_init(); // 0 C . Block(); // (). HSE_Clock_ON(); // HSE . HSE_PLL(2); // 2 uint8_t PLL_Data = 1; // . while (1) { PORTC->RXTX |= 1; Delay_ms (1000); PORTC->RXTX = 0; Delay_ms (1000); if (PLL_Data<10) PLL_Data++; else PLL_Data=1; // - . - . HSE_PLL(PLL_Data); }
プログラムのビデオ。
ボードの回復。
クロック周波数の設定で最初に失敗した後、ボードは応答しなくなりました。 私は保護プログラムを提供しなかったので(それを不必要と考えています)、私はそれを実装する方法を探し始めました。 以前の記事へのコメントの中で、 vertu77は、ポート設定が正しくない場合にボードを復元する3つの方法を提案しました。
それでもJTAGポートが機能しない場合-対処方法:
1.次に、別のJTAGポートが役立つ場合があります。 ボードに配線されていない場合は、別のポートのSWの脚にのみはんだ付けできます(面倒が少ない)
2. UART(標準ブートローダー)経由でファームウェアを注ぎます
3.有線プログラムの機能を使用します。最初に点滅する前に電源を入れた後、新しい塗りつぶしをフラッシュする時間があります。 著者のバージョンでは、これはほとんど不可能です。 実際のプログラムでは、多くの場合、プログラムは内部ジェネレーターから起動され、外部クオーツが初期化されます。 無限の待機サイクルが使用される場合、クォーツのはんだ付けを解除し、最初の点滅の前にフラッシュすることができます。
しかし、私の場合、両方のJTAGは反応せず(MKをプログラムしようとしたときに揺らいでいた)、保護プログラムはなく、USARTブートローダーは非常に遠くにありました。 はい、回復にあまり時間をかけたくありませんでした。 そこで、回復の4番目の方法が考案されました。 BOOTスイッチをEXT_ROM / JTAG_Bモードに切り替え、JTAG_Bに接続し、保護プログラムを提供するファームウェアでコードをフラッシュする必要があります。 私の場合、水晶振動子を調整する前に1秒の待機サイクルを追加しました。 そのため、失敗した各エクスペリエンスの後、[リセット]をクリックして、デバッグモードを再度管理するだけで十分でした。
以前の記事のリスト。
githubのプロジェクト 。