エピローグ:真の組み込みプロフェッショナルを目指す
Lチカを卒業した技術者へ、10年保証プログラムの設計論、在宅勤務でのオシロスコープ・ロジックアナライザ活用秘法
1. エピローグ
海外のSTM32 シリーズの解説書を含めて,「Lチカアプリ」で入門し,STM32 の評価ボードのサンプルプログラムを卒業した以降に必要とされる,本格的なSTM32 の解説本が見当たりません。STM32 の解説やヒント集は,インターネットやブログでの解説も多く見受けますが,特定の課題を解決する方法を提供する解説が多く,深い理解に到達するのが難しいと思います。
1. 対象とする読者
本書では,STM32 シリーズを使用する「真の組み込みプロフェッショナル」を目指すエンジニアが必要とするノウハウを提供したいと考えています。本書では,エンベデッド開発の底流に流れる,思想(考え方)を理解できるように執筆していきます。新しい技術に基づくファームウェアやミドルウェアが追加されても,底流に流れる技術的な手法は,過去の技術の上に立脚した技術です。読者が開発行為でつまづいたときに,本書に掲載されている事例から,類推して問題解決ができるように心がけていきます。
本書は,STM32 シリーズ,STM32CubeMX 入門書ではありません。目的は,STM32 シリーズのSOC プロセッサの様々な機能をうまく使いこなすために,STM32 シリーズの内部構造を理解し,STM32CubeMX を道具としてどう活用するかが主題です。対象となる読者は,STM32CubeMX を使用し,C/C++言語で独自のソリューションを作成されているエンジニアの方々をターゲットにしています。STM32 以外のエンベデッドプロセッサから乗り換えを考えているエンジニアの方々にも有用な書籍となるでしょう。
2. 執筆で心がけたこと
できるだけ、章の順番もストレートに通読できるように心がけました。後の章の解説を読まないと先に進まないとか、未定義用語がいきなり出てくることがないようにしてあります。新出用語等については、英文字略記の後にかならず正式名称を表記するように心がけています。例:SoC(System on a chip)当初は、ハードウェアの章と、ソフトウェアの章を独立させて記述しようと思っていましたが、STM32 シリーズのハードウエアは、ソフトウェアでの設定や操作によってハードウェアの概観が決まる関係上、ハードウェアの解説を行った後にソフトウェアの解説を行っています。
STM32 シリーズのユーザマニュアル(UM)は、ハードウェアの説明書なので、HAL API との関連の記載はありません。一方、HAL API のDoxygen で生成されたマニュアルには、ハードウェアとの関連性の説明が不足しています。ハードウェアとHAL API の関連を補足し、同時に理解することができるように配慮しています。また、読者が、プログラムを作成するうえで理解を深めてほしい箇所には、注意:記載内容を理解しないと、予期せぬ動作を招く事柄を記載しています。
重要:システム全体に景況する記載内容です。ヒント:プログラムを作成するときのヒントやチップスを提供しますなどを記載してあります。
3. 10 年保証のプログラムを書く
STM32 シリーズは,10 年の製造保証を約束している製品が多くあります。同様に,私たちのようなエンベデッドエンジニアも,10 年間稼働可能で,長期メンテナンスができるソリューションを提供することができる技量が要求されていくと思います。これからは,持続可能な社会に貢献するためにも,ソリューションが長期使用されることを考えることが「真の組み込みプロフェッショナル」に要求されていくでしょう。STM32 シリーズのファームウェアアップデート機能を生かして最新の機能をユーザに提供しつつ,ハードウエア資源とソフトウェア資源の寿命を長くする事が求められる時代です。
10年前に作った自分のプログラムを,修正や追加が必要となったときに苦労なくプログラムのバージョンアップができるかどうか,古いプログラムを見ながら,考えてみるのもいいでしょう。時代の要求により,新しい技術パラダイムとしてIoT(internet of things),AI(Artificial Intelligence 人工知能),グラフィカルUI(User Interface)など,多くの新しい技術がどんどん導入されていきます。これらの最新技術をホットに取り入れながら,かつ,その前提となる堅牢なシステムを提供するのが,「真の組み込みプロフェッショナル」の役割だと考えます。
STM32シリーズ システムアーキテクチャのおさらい
ARM Cortex-Mコアとバス・マトリクス、SoC内部構造の腑分け
2. STM32 シリーズシステムアーキテクチャ
1. Soc(System on a chip)とは全部入りラーメンのようなもの
SoC(System on a chip)とは、簡単にいえば、一個の半導体チップを用意すれば、えさ(電源)を与えるだけで、システムに必要な殆どの機能が自己完結的に動作するということです。つまり、全部入りラーメンのようなものですね?!あとは、好みによって、トッピングする具を追加する(LED、スイッチ、センサーやアクチュエータなど)だけで、シビアなグルメ(使い手)の満足できるおいしいラーメン(製品)が出来上がります。
1.1. おいしいラーメン(Soc)は基本のスープと具が大切
SoC は、おいしいラーメンと同じで、味の基本であるスープ(電源回路、クロック機能、CPU とバス構造、割り込み機能、DMA(Direct Memory Access)機能、IO 機能など)と、チャーシューやシナチクの定番の具(マルチメディア機能や標準的通信ペリフェラルなど)が絶品で、スープと具が絶妙にコラボレーション(相互接続)していないと、定番ラーメン(ヒット製品)にはなりません。
2. 知っておかないと困るSTM32 の基本ハードウェア
STM32 シリーズのリファレンスマニュアルやチュートリアルでは、それぞれの部分についての詳しい解説がありますが、全体を把握できるように記載されていないのが課題だと思います。Figure 1 は,STM32 シリーズのほぼ標準的なシステムアーキテクチャを示したものです。STM32 シリーズSoC チップ内には,システムを管理するためのシステムモジュールや,固有の機能を提供するペリフェラルやIP などが多数存在しています。最初は,もっと単純で簡略的なブロック図を用意しようと思っていましたが、図1で提示しているブロック図では、STM32 シリーズの基本動作を理解する上での必要十分な情報を提供する目的として、①機能ブロックの詳しい内部構造、②システムの入出力信号,③機能ブロックの相互接続,④機能ブロックを網羅して、相互依存関係が一望できる内容に書き改めました。図1で記載されている、各機能ブロックは、STM32 シリーズのSoc に搭載される個々のペリフェラルを自由に使いこなすために最低限必要な要素です。これらの要素のが理解されて、個別のペリフェラルの①電源制御、②クロック供給、③割り込み処理、④DMA データ転送、⑤エラー管理が可能となります。
つづく章で、詳しく説明しますので、頑張って理解を進めてください。
初めにHAL (Hardware Abstraction Layer) ありき
HAL APIの設計思想、weak関数によるコールバック機構、HAL_Init() / HAL_DeInit() の挙動
3. 初めにHAL(Hardware abstraction layer)ありき
STM32CubeMX(以下、CubeMX と略記します)が開発される以前から,STMicro 社はハードウェア抽象化レイヤーHAL(Hardware Abstraction Layer)を提供しています。HAL は,STM32 シリーズに内蔵される各種ペリフェラルを取り扱うために用意された抽象化されたドライバ関数群です。CubeMX は、カスタマイズしたシステム構成を生成するときにHAL API などを使用して基本的なコードを生成します。
1. HAL の抽象化コンセプト
STMicro 社が提供するSTM32 HAL(Hardware Abstraction Layer)は、抽象的で体系だったC言語仕様に基づくAPI(Application Programing Interface)です。HAL API では、STM32 シリーズSoc に内蔵されるハードウェアのリソース(レジスタ等)を直接操作しません。このことで、STM32 シリーズの種々の製品間で、ユーザのアプリケーションが容易に移植可能なポータブルAPI を提供しています。
2. STM32 シリーズ間の互換性
STM32 ファミリーには多くの機種が存在し,内蔵されるペリフェラルも異なります。同じ名称のペリフェラルであっても,搭載されている機能や制御レジスタ構成がバージョンによって異なる場合もあります。STM32 に搭載されているペリフェラルのほとんどは、下位互換性を保ちつつ、様々な機能追加や改良をへて新しいバージョンに継承されています。注意:しかし、あるSTM32 から他のSTM32 に移植しようとした時に、ペリフェラル内のIP 仕様(回路構成)とインプリメンテーションの違いがあるため、個々のペリフェラルの初期設定とRCC(リセットとクロック)設定が異る場合があります。
3. ハードウェアエンジニアでもHAL API の理解が必要
STM32 シリーズのハードウェアについて学ぶ前に、HAL API の話をするには訳があります。CubeMX でプロジェクトを新規作成し、コードを生成させると、ほぼ95%以上のコードはHAL API を呼び出して処理を行います。STM32 シリーズを使用するハードウェアエンジニアも、STM32 シリーズの特定機種に内蔵される、内蔵電源動作、システムクロックの選定、ペリフェラルの機能の選択をCubeMX を使用して決定しなくてはなりません。そして、STM32 のピン設定が決まることによって、はじめてSTM32 を含む最終製品の回路構成が決まります。
したがって、ハードウェアエンジニアであっても、CubeMX の吐き出すHAL API に依存したコードをある程度、理解しなければなりません。
4. HAL API とweak 関数(重要)
4.1. weak 関数とは
__attribute__((weak));またはマクロ定義の __weak を使った関数をweak 関数と呼びます。weak 関数属性があると、関数宣言の結果によるシンボルは、オブジェクト・ファ イルの中に、グローバル・シンボルとしてではなく、弱いシンボルとして現れます。このC 言語機能は、HAL のソースコード内の関数宣言と、ユーザが作成した関数が、重複関数名エラーを起こすことなく、オーバーライド(再定義)するための機能です。
HAL のソースコードを確認すると、数多くのweak 関数が存在していることがわかります。
4.2. HAL API 内のweak 関数
HAL API のうち、初期化関数HAL_xxx_Init()/HAL_xxxDeInit()非初期化関数内で呼ばれるSTM32 シリーズの固有の実装依存部の初期化、割り込みハンドラー関数などが、HAL ライブラリのソースコードには「weak 関数」として実装されています。これらのHAL のソースコード内のweak 関数は、イベント(割り込みやDMA)完了後のアプリケーション側で処理が行われる可能性のある関数(Call back 関数)や、HAL の初期化関数内で特定のSTM32 ペリフェラルの機能やピン設定を活性化させる呼び出しHAL_Msp 関数などです。この部分は、ユーザプログラムコード内で処理を記述しなかったときに、リンクエラーが起こらないように「何もしない関数」として呼ばれるようになっています。
5. HAL API の戻り値 HAL_StatusTypeDef
HAL API 一部の関数をのぞき、typedef enum で定義されたHAL_StatusTypeDef の値を返します。
HAL_OK:HAL API は正常に実行されました。
HAL_ERROR:HAL API のAPI パラメータまたは実行エラーが発生しました。
HAL_BUSY:HAL API に関係するペリフェラルが動作中でAPI は実行されなかった
HAL_TIMEOUT:HAL API が実行中にタイムアウトしました。重要:HAL API はリエントラント(再入可能)ではありません。ヒント:ポーリング、割り込み、DMA によるデータ転送が処理が行われるHAL API やマクロが呼ばれれた後は、動作が完了するまでは間は、BUSY 状態になります。ビジー状態を解除するにはAbort 関数を使用して、転送状態を終了する必要があります。
6. HAL のグローバルAPI
HAL グローバル関数は、HAL の基本関数です。HAL 初期化関数様々なHAL API 内で呼ばれる時計測関数などが含まれています。
6.1. HAL_GetHalVersion()
使用しているHAL のレビジョン番号をを戻します。戻り値は、32bit で、レビジョン番号がパックされています。ビット位置:[31:24] main version [23:16] sub1 version [15:8] sub2 version [7:0] release candidate
6.2. HAL_Init()
① main の最初の関数として呼び出す必要があります。② Cortex M CPU のデータ/命令キャッシュとプリフェッチキューを初期設定します。③ 割り込み優先度が最高となる1ms の割り込み発生のタイムベースを設定する。systick(デフォルトのシステムタイマ)か他のタイマを代替えして使用可能です。④ NVIC の4 プリエンプションビットへのプライオリティグループをセットします。⑤ MCU システムレベル初期設定を行います。ユーザー関数HAL_MspInit()を呼びます。
HAL_MspInit()は、HAL コード内で weak 関数として定義されています。6.3. HAL_DeInit()
① すべてのペリフェラルを初期化(デフォルト状態に)もどします。② システムレベルを完全に初期化するユーザーコール関数HAL_MspDeInit()を呼びます。
HAL_MspDeInit()は、HAL コード内で weak 関数として定義されています。6.4. HAL のシステムタイマ関数
システムタイマー関数は、1mSec の単位でシステム時間を計測します。システムタイマーは最も高い割り込み優先度で動作するので、割り込みを禁止しない限り、ペリフェラル割り込み内などでも有効です。
6.4.1. HAL_GetTick()
現在のSystick カウンターの値を戻しますタイムベース割込みにおいてインクリメントされます。タイムアウト管理用としてペリフェラルドライバによって使われます。
6.4.2. HAL_Delay()
ms 単位の遅延時間を挿入します。
6.4.3. __weak HAL_InitTick()
タイムベースのソースを設定します。Cortex M に内蔵されるsystick がデフォルトですが、weak 関数なのでユーザが再定義可能です。(RTOS などでsystick を使用する場合など)
6.4.4. HAL_IncTick()
アプリケーション時間ベースとして使われたグローバル変数「uwTick」をインクリメントする。タイムベース割り込みサービスルーチンISR (systick, timer, …)からコールされなければならない。
6.4.5. HAL_SuspendTick() / HAL_ResumeTick()
HAL_SuspendTick()Tick インクリメントを中断します。
HAL_ResumeTick()Tick インクリメントを再開します。
基本ハードウェア:メモリ空間、Cortex-Mコア、NVIC、システムバス
アドレスマッピング、パイプライン、NVIC割り込み優先順位、マルチAHBバス・マトリクス、クロスバースイッチ
4. STM32 シリーズの基本ハードウェアとHAL API
1. この章で学ぶことは?
STM32 シリーズのSoC に内蔵される、基本機能モジュールの動作と相互接続、依存関係を理解しましょう。この章では,次の内容にスポットを当てます。また、基本機能モジュールに関連するSTM32 シリーズの共通HAL API の概観についても併せて理解を進めていきましょう。① HAL(Hardware Abstraction Layer)の基本コンセプト② STM32 のメモリー空間: メモリアドレスの領域と割り当て③ ARM Cortex M シリーズの内部構造: プロセッサ構造,割り込み方式など④ NVIC(Nested Vectored Interrupt Controller) 割り込みコントローラの構造⑤ PWR(Power Controller)電源制御:内部電源供給と電源電圧監視⑥ RCC(Reset & Clocl Controller) :リセットとクロック発生源選択、クロックの供給⑦ GPIO(General purpose Input output):物理ポート・ピンの割り当て・物理特性の設定,内蔵するペリフェラル信号の接続⑧ EXTI(Extended Interrupt Controller): 拡張割り込み制御(割り込み制御の拡張機能)⑨ DMA(Direct Memory Access Controller):CPU を使用しないデータ転送機能⑩ 内蔵Peripheral の基本的な構造と構成(制御レジスタ部分と固有機能提供部分)これらを正確に理解すれば,STM32 シリーズのSoC の基本的な枠組みを自由自在に扱うことができるようになります。
皆さんが作成するアプリケーションのシステム初期化や動的システム制御プログラムは,システム全体のパフォーマンスや電力消費を抑えることも含め,現在のシステムの置かれた状況に合わせて,必要な個別のペリフェラル機能を活性化させたり,休止させたり,SoC 内部の特定機能部分のクロック周波数を変更したりすることが可能でなければなりません。これらを不具合なく具現化するために,どのように、STM32 シリーズのSoc 内部は,必要な機能モジュールが合理的に分離され,相互接続されているか理解しましょう。
1. System HAL API と基本モジュール
STM32 シリーズのファミリー(STM32 G0/F4/L4/F7/H7)や機種(H733/H743)によって、内部構造は異なりますが、ファミリごとに用意されたHAL API を使用することによって、ほぼ共通のプログラムでハードウェア機能を使用することができます。STM32 シリーズのファミリーや機種固有のハードウェアを制御する細かな制御レジスタを直接操作しないで、HAL API を使用することによってSTM32 シリーズ間での移植性がたかまります。
この章では、図1 に記載された、STM32 シリーズでほぼ共通する基本モジュールを操作するHAL API との関連もあわせて、理解していきましょう。個別の機能を提供するペリフェラル操作については章を改めて解説します。
2. STM32 シリーズのメモリ空間と領域の割り当て
| メモリ用途 | アドレス範囲 | 先頭アドレス | 概要・機能 |
|---|---|---|---|
| Boot Image | 0x0000-0000 〜 0x07FF-FFFF | 0x0000-0000 | ブート領域(Flash/システムメモリ/SRAMエイリアス) |
| Flash Memory | 0x0800-0000 〜 0x1FFF-FFFF | 0x0800-0000 | 内蔵メインFlashメモリ(Bank 1 / Bank 2)、システムメモリ、OTP領域 |
| RAM Area | 0x2000-0000 〜 0x3FFF-FFFF | 0x2000-0000 | 内蔵SRAM(SRAM1, SRAM2, SRAM3, AXI SRAM, DTCM-RAM) |
| Peripheral Reg 1 | 0x4000-0000 〜 0x5FFF-FFFF | 0x4000-0000 | 内蔵ペリフェラルレジスタ(APB1, APB2, AHB1, AHB2, AHB3 等) |
| External Memory 1 | 0x6000-0000 〜 0x9FFF-FFFF | 0x6000-0000 | 外部メモリスロット(FMC Bank1〜3: NOR/PSRAM/NAND, Quad-SPI等) |
| Arm Cortex-M 内部 | 0xE000-0000 〜 0xFFFF-FFFF | 0xE000-0000 | CPU内部ペリフェラル(SysTick, NVIC, MPU, SCB, デバッグレジスタ) |
Table 1 は、STM32 シリーズの典型的なメモリーマップです。
機種多少の違いはありますが、基本的なメモリーマップ構成は同様です。メモリアドレス空間は4G バイト(32bit)で、大きく8領域に区分されています。
2.1. Boot Image 領域
0x0000-0000 ~ 0x07FF-FFFF起動時のBOOT0 ピンの設定やFLASH 内のOB(Option Byte)等の設定で、この領域でアクセスできるメモリ領域が変化します。この領域は、指定されたメモリ領域をイメージ(エリアス)として見えるようになります。FLASH(Bank 1/ 2)、RAM、外部メモリ(External Memory)、ブートローダのメモリーイメージがこのメモリエリアからアクセスできるようになります。
一般に起動時にBOOT0 ピンがLOW の場合は、Main FLASH 領域から起動します。BOOT0 ピンがHIGH の場合は、FLASH の System Memory 領域に書き込まれている Boot loader 起動しします。
2.2. Flash Memory 領域
0x0800-0000 ~ 0x1FFF-FFFF先頭の0x0800-0000 番地には、プログラムやデータを格納するMain Flash が配置されます。System Memory:部分は、ブートプログラムなどの工場出荷時に書き込まれる部分です。Option Byte:ブート時の初期化条件やReset 条件、スタートアドレスなどが配置されます。OTP (One Time Programing)は、製品出荷時に一度だけ書き込むことができる領域です。
プログラムを格納するFLASH Memory の先頭アドレスは、必ず0x0800-0000 です。ヒント:STMicro 社が提供する評価ボード(Necleo シリーズ、Discovery キット、Evaluationキット)のST-LINK デバッグUSB ポートをPC に接続するとブラウザ画面が開き、そこはBINファイルをドラッグアンドドロップすると簡単にプログラムをFLASH に書き込めるのはこのためです。
2.3. RAM 領域
0x2000-0000 ~ 0x3FFF-0000 STM32 に内蔵されるRAM 領域です。RAM は機種によってRAM1,RAM2 等に分かれている場合があります。DTCM-RAM はSTM32F7/H7 のCore 部分に内蔵された高速メモリーです。
2.4. Peripheral Register 領域
0x4000-0000 ~ 0x5FFF-FFFF / 0xA000-0000 ~ 0xBFFF-FFFF STM32 に内蔵されるペリフェラルの制御レジスタ領域です、AHB は高速バス、APB は低速ペリフェラルバスに接続されます。ペリフェラルの種類によって、AHB1,AHB2,APB1,APB1 などバスが区分されている場合があります。
2.5. External Memory 領域
0x6000-0000 ~ 0x9FFF-FFFF / 0xC000-000 ~ 0xDFFF-0000 FMC(Flexible Memory Controller)を介してSTM32 外部に接続されたメモリ(RAM / PSRAM /DRAM / NOR FLASH / NAND FLASH 等)や、QUADSPI(シリアルフラッシュメモリ)用のメモリー空間です。外部に接続されたこれらのメモリを内部メモリと同様にSTM32 のメモリー空間から間接的にアクセスするための領域です。
注意:接続するメモリはメーカや種類によって物理的な仕様や通信方法が異なるため、FMC やSUQDSPI の制御レジスタをメモリ仕様に合わせて正しく設定する必要があります。
2.6. ARM Cortex M 内部ペリフェラルレジスタ空間
0xE000-0000 ~ 0xFFFF-FFFF STM32 のCPU 部分(ARM Cortex M)の内部で蜜結合している、MCU(Micro Controller Unit) 、FPU(Floation point unit)、NVIC(Nested Vectored Interrupt Controller)などに関連したペリフェラルの制御レジスタが配置されています。STM32 シリーズの心臓部 ARM Cortex M プロセッサFigure 2 は、CPU 部分の基本となる機能モジュールの全体像を示します。STM32 シリーズが採用しているプロセッサ部分は,STM32MP1 を除いて,ARM Cortex M シリーズです。(プロセッサによってMCU,ALU,NVIC を除き,追加されたり省略される機能もあります)
2.7.
MCU(Micro Controller Unit)命令をメモリーから読み取り,解読し実行する唯一の主体です。それ以外の部分は,MCU が活用する受動的な機能モジュールであることを理解しましょう。MCU 内部には,計算結果を一時的に保持する複数の汎用レジスタ(R0~R12) スタックポインタ(SP[R13]),リンクレジスタ(LR[R14]),カウンタ(PC[R15]),システム状態レジスタなどを含みます。これらのレジスタは,Cortex M0~M7 まで共通で,MCU のマシン語レベルでの互換性があります。
2.8. Cortex M シリーズの命令セット
Cortex M0~M7 は,RISC(Reduced Instruction Set Computer) ベースのプロセッサですが,プログラムsize の減少と割り込みでの高速なレジスタ退避などの目的で,CISC(Complex Instruction Set Computer)的な命令を実装しています。CISC 的な命令は,RISC プロセッサをMPU 内のマイクロコードで実行させる方式を採用しています。
2.8.1. ハーバード・アーキテクチャ
図2は,STM32 G4 シリーズ(Cortex M4 Core)の構造を示しています。STM32 G0 シリーズ(Cortex M0 Core)では,シンプルでバスラインは1つしかありません(System Bus のみ)。一方,STM32 F4/G4シリーズ(Cortex M4 Core)等では,のバスラインは,I-Bus(ICODE Bus),D-Bus(DCODE-Bus),S-Bus(System Bus)の3本建てです。Cortex M4 Core のバスラインがI-Bus とD-Bus に分かれているのは,プログラムコードを読み込む(Instruction Bus)とデータを読み書きする(Data Bus)を分けることで,プログラム計算処理とデータ転送を平行に実行できるHarvard Architecture を採用しているためです。S-Bus は,ペリフェラルにアクセスするためのもので,外部デバイスとのアクセス競合を受けずに,I-Bus とD-Bus を使ってプログラムを実行可能です。
2.9. ALU(Arithmetic Logic Unit)
汎用レジスタに対して整数型算術演算を行います。論理演算、加減演算、乗算除算、アドレス計算などの演算処理を行います。
2.10. ARM Cortex M4/M7 で追加された機能
2.10.1. FPU(Floating Point Unit)
浮動小数演算用レジスタに対して浮動小数点型算術演算を行います。浮動小数点の掛け算などはクロック1サイクルで計算できます。(M4 は単精度,M7 は倍精度)浮動小数点用の命令セットとレジスターS0~S31 が追加されます。
2.10.2. DSP(Digital Signal Processing)
信号処理計算に欠かせない不飽和演算(SIMD 命令)や積和計算(MAC 命令)を高速で実行します。DSP 演算用の命令セットとMAC レジスター等が追加されます。特にアナログ変換やオーディオ信号処理でのDSP 信号処理で多用されます。
2.11. MPU (Memory Protection Unit)
MPU は,CPU が定義されていないアドレスを指定したり,特定のアドレスで読み出し,書き込みおよびプログラムの実行を許可するかしないかを設定するメモリ保護機能です。(実装されていないSTM32 シリーズもあります)
2.12. Debug Interface (JTAG or SWD)
オンラインデバッグや,フラッシュのプログラミング,チップ内のレジスタの読み書きなどを行うインターフェイスです。STMicro の提供する,ST-LINL-V3 やST-LINK-V2 を使用して,デバッグやプログラミングが可能です。インターフェースは、ARM CoreSight 規格を採用してるので、ARM CoreSight 準拠のプログラマやデバッグツールも使用可能です。
2.12.1. デバッグポートの外部ピン
STM32 シリーズのデバッグポートは、同じポートとピンを使用します。Full JTAG モードと2本のポートでデバッグ可能な SWD の2種類のインターフェイスが可能です。Debug Ports PA13 PA14 PA15 PB3 PB4 Full JTAG JTMS JTCK JDDI JTDO NJRST Full JTAG without nJRST JTMS JTCK JDDI JTDO SWD(serial wire debug)SWDIO SWCLK(SWO)デバッグポートは、GPIO 設定で一般のI/O ポートとして設定可能ですが、リセット時は上記の機能が割り当てられています。
さらにTPIU(Trace Port Interface Unit)を使用した、高速のトレース機能などをデバッグで使用する場合は、SWO を使用するか、別に用意されるのデバッグトレース用のピン割り当てを使用することが可能です。FIGURE 4 STM32 シリーズのデバッグ機能(G4 の例)STMICRO STM32 G4 TRAINING 資料より転載
3. NVIC (Nested Vectored Interrupt Controller)
ARM-Cortex M Core 専用の多重割り込みコントローラです。割り込み要因番号と割り込み処理ルーチンISR(Interrupt Service Routine)のとび先アドレステーブル(Vector Table)が対になっています。また,割り込み要因ごとに優先順位を指定することができます。MPU と蜜結合されているので,高速な割り込み応答が可能です。外部からのイベント信号や,障害検知,内部機能モジュールで発生したイベントによる割り込み処理を一元的に管理します。
NVIC は,重要なプロセッサ機能なので,少し詳しく説明します。
3.1.1. 割り込み番号と割り込みベクターテーブル
3.1.1.1. 割り込み処理と割り込み番号IRQ[n]
割り込み処理(例外処理とも呼ばれます):①現在実行しているプログラムよりも優先して処理を実行しなければならない割り込み要求IRQ(Interrupt Request)が発生した場合に,現在のプログラムをいったん中断します。②現在のプログラムで使用しているMPU のレジスターをスタックメモリ領域に退避させたのちに,優先される処理を実行します。③この処理プログラムのことをISR(Interrupt service routine)またはInterrupt Handler と呼びます。
④ISR 処理が終了すると,スタックメモリ領域に退避させたMPU のレジスターを復帰させて割り込み処理発生前の処理を継続させます。⑤割り込み処理中にさらに優先順位が高いイベントが発生した場合を多重割り込みと呼び,割り込みが入れ子(Nest)することも可能です。NVIC では,システム割り込みやペリフェラル個別の割り込みは,それぞれの割り込み信号線で通知され,それぞれの割り込み信号に割り込み番号が割り当てられます。NVIC の構成上,1 番から255 番までの割り込み番号IRQ[1~255]が使用できます。
3.1.1.2. 割り込みベクターテーブル Interrupt vector table
割り込みベクターテーブルは,メモリ上に割り込み番号に対応したISR エントリアドレスが割り込み番号順に配置されたテーブルです。テーブルが配置されるアドレスは,NVIC 内のVTOR(Vector table offset register)に格納されたアドレスで指定できます(VTOR の初期値は0x0000-0000 です。この表は,Cortex M シリーズの共通した割り込みテーブルです。1つのテーブルは32bits(ワードsize)で構成されます。
①割り込みベクターテーブルは,IRQ[0]~IRQ[15]は,システム例外処理用に使用されます。②IRQ[16]以降に,Soc に依存したペリフェラル等のISR エントリアドレスが配置されます。No Handler Note Reset stack Addressリセット時のシステムスタックアドレス1 Reset_Handlerリセット時のプログラムアドレス2 NMI_Handler Non maskable interrupt handler 3 HardFault_Handler Hard Fault handler 4 MemManage_Handler Memory manage handler 5 BusFault_Handler Bus fault handler(Prefetch fault, memory access fault)6 UsageFault_Handler Usage fault Handler 7 Reserved 8 Reserved 9 Reserved 10 Reserved 1 SVC_Handler Super visor call handler (System service call via SWI instruction)12 DebugMon_Handler Debug monitor handler 13 Reserved 14 PendSV_Handler Pending Service handler (Pendable request for system service)15 SysTick_Handler System timer Interrupt handler 16 SoC 固有の割り込みハンドラ IRQ[16]… …IRQ[n]n SoC 固有の割り込みハンドラ IRQ[n+1]
3.2. 割り込みの活性化と優先順位
NVIC 内には,割り込みの活性化するレジスタと優先順位を決定する割り込み優先順位レジスタがあります。①ISER(Interrupt set-enable registers) / ICER(Interrupt clear-enable registers)割り込み信号線からの割り込みを有効(Enable)/無効(Clear)にします。②ISPR(nterrupt set-pending registers) / ICPR(Interrupt clear-pending registers)割り込みを状態を強制的に保留中にすることを有効(Enable)/無効(Clear)にします。保留中とは,優先度の高い他の割り込みサービスが実行中のため割り込み処理が保留されている状態です。
ヒント:ISPR を使用して保留状態にセットすると,ソフトウェアで疑似的に割り込みが発生したかのよう振る舞うことができます。③IPR(Interrupt priority registers)割り込みの優先順位を設定します。割り込み優先順位は0~15 までで,数の小さいほうが優先順位は高くなります。同じ優先順位の割り込みが発生した場合は,輪番制(Round robin)で処理が行われます。
3.3. NVIC に関連するHAL API
HAL_NVIC 関数の引数IRQn_Type IRQn は、NVIC の割り込みベクター番号を表します。
void HAL_NVIC_SetPriorityGrouping(uint32_t PriorityGroup)必要なロック解除シーケンスを使用して、優先度のグループ化フィールド(プリエンプションの優先度とサブ優先度)を設定します。
void HAL_NVIC_SetPriority(IRQn_Type IRQn,uint32_t PreemptPriority, uint32_t SubPriority)割り込みの優先度を設定します。(0~15 0 が最も優先順位が高くなります)
void HAL_NVIC_EnableIRQ(IRQn_Type IRQn)NVIC 割り込みコントローラーでデバイス固有の割り込みを有効にします。
void HAL_NVIC_DisableIRQ(IRQn_Type IRQn)NVIC 割り込みコントローラーでデバイス固有の割り込みを無効にします。
void HAL_NVIC_SystemReset(void)システムリセット要求を開始して、MCU をリセットスタートします。
uint32_t HAL_NVIC_GetPriorityGrouping(void)NVIC 割り込みコントローラから優先順位グループ化フィールドを取得します。
void HAL_NVIC_GetPriority(IRQ_Type IRQn,uint32_t PriorityGroup,uint32_t *pPreemptPriority,uint32_t * pSubPriority)IRQn で指定した割り込み番号の割り込み優先度を取得します。
void HAL_NVIC_SetPendingIRQ(IRQn_Type IRQn)IRQn で指定した割り込み番号の外部割り込みの保留ビットをセットします。
uint32_t HAL_NVIC_GetPendingIRQ(IRQn_Type IRQn)IRQn で指定した割り込み番号の現在の保留ビットの状態を取得します
void HAL_NVIC_ClearPendingIRQ(IRQn_Type IRQn)IRQn で指定した割り込み番号の外部割り込みの保留ビットをクリアします。
uint32_t HAL_NVIC_GetActive(IRQn_Type IRQn)IRQn で指定した割り込み番号のアクティブ割り込みを取得します(NVIC のアクティブレジスタを読み取り、アクティブビットを返します)。
4. システムバスの構成
4.1. Bus Master とBus Slave そして Bus Matrix
4.1.1. Bus Master
能動的に,アドレスバスで特定のアドレスを指定することで,データバスに接続されている,メモリまたはレジスタに対して読み込み,書き込み操作ができる機能モジュールをBus Master と呼びます。Bus Master は,逐次処理を行うCPU(Central Processor)とDMA(Direct memory access)の2種類のBus Master があります。それ以外にも,データ転送の高速性を要求される,USB コントローラやETH(Ethernet)コントローラなどは,DMA 機能を搭載したペリフェラルも Bus Master に含まれます。
4.1.2. Bus Slave
受動的に,Bus Master の操作でデータ交換をする機能モジュールを Bus Slave と呼びます。メモリー,ペリフェラル,特殊な内部処理を行うIP などがこれに該当します。Figure 5 の表記で,DMA Controller や Other Bus Master の左側に APB Slave と書いてあることに注意したください。なぜなら,CPU 以外のBus Master は,CPU からの内部レジスタの操作によってはじめてBus Master 機能が有効になるからです。CPU からの指示を受け入れる Bus Slave 機能が存在することを理解しておいてください。CPU からの指示でBus Master が活性化すると,外部からのイベントや関連付けられたペリフェラルの要求(Request)によってBus Master 機能が自発的に実行されます。
4.1.3. Bus Matrix とは
複数の Bus Master が存在すると,同時に同じデバイスを複数の Bus Master がアクセスを開始すると,バスのとりあいが起こります(競合状態)。この場合は,競合の調停を行わねばなりません。そこで,Bus Master の複数のバスに対して,Bus Slave 側のバスも複数のバスを設けてバスラインの競合が起こりにくいように工夫することで,バスの競合を避け,並列処理が可能にすることで,バスの並行動作を行い,パフォーマンスを向上させます。
STM32 シリーズでは,外部メモリアクセス機能である,FMC(Flexible static memory controller)やQUADSPI(Quad-SPI interface)なども専用の高速のAHB バスを配置したり,RAM 領域やペリフェラルが接続されるバスを2つに分けるなどの工夫がなされています。
4.1.3.1. バス調停(Bus Arbitration)とは:
ある瞬間にバスの使用権を持つことが許されるのは1 つのMaster だけです。したがって,複数のMaster が同時にバスを使用することはできません。そこで,バスに複数のMaster が存在する場合には,何らかの方法でそれぞれのMaster に使用権を割り当てます。Bus Slaves Bus Masters S-Matrix SRAM2 backup SRAM SRAM1 SRAM SRAM2 Bkup SRAM SRAM1 HI-Speed External Memory Controller Statas / Control Reg External Memory I/F Low Speed Peripheral or IP Status / Control Reg Unique IP / Logic High Speed Peripheral or IP Status / Control Reg Unique IP / Logic AHB Slaves AHB ICode AHB DCode AHB Slave AHB Slave AHB DMA1 AHB DMA2 AHB ICODE AHB DCODE AHB SYSTEM FLASH INFO BLK BANK1 BANK2 ART Accelerator AHB APB Bridge APB Cortex-M Core EVENT Controller CPUCore MCU/ALU SIMD/FPU NVIC Nested vectored Interrupt Controller Debug Access Port JTAG or SWD MPU Memory Protection Unit SYSTEM AHB Base Regs R[0..15]SYS -REG DCODE AHB ICODE AHB Ext Regs MAC S[0..31]DMA Controller Status Control Reg DMAMUX DMA1 AHB DMA2 AHB APB Slave DMALogic GPIO IF GPIO IF GPIO IF SysTick Timer AHB BUS Other Bus Master Status Control Reg DMA1 AHB APB Slave Datexfer Logic FIGURE 5 STM32 シリーズシステムバス構成 (右:実例 STM32G4 ユーザマニュアルより転載)
4.1.3.2. 複数のバスを持たせて並行処理を可能にする
複数のBus Master と 複数のBus Slave のとのに格子状のバスを配置して,スイッチすることで,バスの競合を回避することが可能となります。この方式は,クロスバースイッチと呼ばれるものです。Figure 6 クロスバースイッチ(Wikipedia より引用)クロスバースイッチは,縦方向に並行した複数の通信路と横方向の同様な通信路の交点にスイッチを設け,これらのスイッチ群を制御することで,対向する通信路との専有経路を動的に構築する構造である。クロスバー電話交換機で金属棒が交差している状況から「クロスバー」スイッチと呼ばれるようになった。
4.1.4. Bus Master と Bus Slave をつなぐ Bus の構成(AHB,APB とは)
STM32 シリーズのシステムバスは,ARM Cortex-M シリーズで使用される,ARM コアのバスアーキテクチャAMBA(Advanced Microcontroller Bus Architecture)を使用しており,このうちの代表的なものにAHB(Advanced High-performance Bus)とAPB(Advanced Peripheral Bus)があります。いずれのバスも,アドレス32 本とデータ32 本,制御信号がセットとなった形式です。AHB-Liteという記述は,AHB の制御線を簡略化したものです。Bus Master とBus Slave のバスにおいて,メモリや高速性を要求されるペリフェラルは,AHB に接続されます。
これらのデバイスに供給されるクロックがHCLK です。これに比較して,低速なペリフェラルはAPB に接続されます。これらのデバイスに供給されるクロックがPCLK です。
4.1.4.1. AHB(高速バス)
高速転送用のバスで,アドレス・フェーズとデータ・フェーズがずれた,いわゆるパイプライン型のバスです。コア側クロック(HCLK)の1 サイクル・ピッチで転送できます。
4.1.4.2. APB(低速ペリフェラルバス)
周辺モジュールを接続する低速バスで,アドレスとデータは同一フェーズに現れるます。周辺側クロック(PCLK)の2 サイクル・ピッチで転送します。APB は,AHB バスから「AHB-APB ブリッジ」を介して接続されます。
4.1.5. その他の広帯域BUS
STM32 で採用されているCPU であるARM Cortex-M シリーズは,アドレス32bits とデータ32bits が基本です。ただし,1回のアドレス空間へのアクセスに対して,より多くのデータを転送できるように,Cache(バッファメモリ)を組み合わせてデータ幅を64bits でアクセスできるように工夫したバスもあります。
4.1.5.1. AXI インターコネクト
STM32F7/H7 で採用されているARM Cortex-M7 Core は内部では64bits 幅のAXI (Advanced eXtensible Interface) bus を採用しています。さらに,内部FLASH,内部メモリ(SRAM)や外部メモリ(FMC,OctoSPI)などの高速アクセスが要求されるメモリーデバイスはAXI bus 上に接続されます)それ以外のペリフェラルは,AXI―AHB ブリッジを介して,AHB 経由で接続されます。
4.1.5.2. ART Accelerator
一般に,FLASH メモリではCPU アクセス遅延が生じます。ART Accelerator は,FALASH メモリとAHB 間に配置され,FLASH データ幅64bits と,内蔵する命令Cache とプリフェッチ機能等を組み合わせて,FLASH アクセスを高速CPU アクセスに適したレベルに引き上げます。STM32F7/H7 の場合は,内蔵フラッシュだけでなく,FMC に接続された外部メモリやQuadSPI に対しても有効です。
基本ハードウェア:電源制御(PWR)、クロック(RCC)、ペリフェラルIF、GPIO
POR/PDR/PVD、低消費電力モード、PLLクロックツリー、クロックドメイン分離、GPIO内部回路
5. 電源制御PWR とリセット回路
STM32 シリーズのSoc が電源投入され,リセット条件を検知し内部リセット後に様々なハードウェアによる初期化が行われ,MCU(Micro Control Unit)が動作を開始するまでの流れを追うと,STM32 シーリーズの全体像が浮かんできます。
5.1. PWR(Power Controller)のしくみ
5.1.1. 電源入力源(DVDD DVBAT AVDD ) と内部コア電源VCORE
外部ピンからの基本電源入力は,デジタル主電源DVDD,バッテリー電源DVBAT,アナログ主電源AVDD 等があります。また,外部入出力I/O ピンと関連しない内部コアロジックの電源はVCORE(約
5V)から供給されます。VCORE は,DVDD から安定化電源回路(Main Regulator)を介して供給され
ます。STM32 の安定化電源回路はLDO(ロードロップシリーズ電源)が標準装備されていますが,より電力効率の良いSMPS(スイッチング電源回路)が追加されているものもあります。バッテリ電源入力VBAT は,主電源DVDD が供給されていない場合でも内部時計機能RTC (Real Time Clock) やバックアップメモリなどが動作できるようにする電源です。内部バックアップ電源VBACK は,主電源DVDD が供給されている場合はDVDD が供給元になります。
5.1.2. 電源監視機能(Power supply supervisor)
電源電圧を監視するSupervisor は常にDVDD 電圧を監視していて,DVDD 電圧がリセット解除条件[POR(Power on Reset) / BOR(Brown out Reset)等]を満たさない場合は,システムリセット信号nreset が解除されない状態が継続します。デジタル主電源DVDD 電圧がリセット解除条件を満たすと,リセットが解除されます(ただし,バッテリー電源VBAT でバックアップされている機能モジュールはリセットされません。)
5.1.3. NRST 端子とRCC(Reset Controller 部)の関係
NRST 端子は,外部リセット端子ですが,外部からのリセット入力だけでなく,内部で発生したリセット要因を外部デバイスに通知できる双方向端子であることに注意しましょう。図2で示されるRCC 内部の(Reset Controller 部)は,電力制御PWR からのリセット条件[POR/BOR 等]や,ウオッチドックタイマー(WDG/IWDG)のタイムアウト,ソフトウェアによるリセット(rcc_sft_rst),その他リセット要因信号など,プロセッサ内部で発生するリセット状態をNRST 端子を介して外部デバイスに通知します。
STM32 内部のシステムリセット信号reset /sys_rst は,外部リセット入力とプロセッサ内部で発生するリセット状態の2つを一元的に通知します。
6. STM32 の電源モード(Power Mode)(重要)を理解しよう
| 電源モード | メインレギュレータ | CPU | FLASH | SRAM | 有効クロック源 | 有効ペリフェラル・ピン |
|---|---|---|---|---|---|---|
| RUN | ON | 動作中(Active) | ON | ON | すべて(ALL) | 全ペリフェラル / 全ピン |
| SLEEP | ON | 停止(Stop) | ON | ON / OFF | すべて(ALL) | 全ペリフェラル(割り込み・イベント復帰可) |
| STOP | ON(低電力) | 停止(Stop) | 停止(Off) | ON(保持) | LSE / LSI | リセットピン、全GPIO、PWR、RTC、IWDG、低消費電力ペリフェラル |
| STANDBY | OFF | 電源断(Down) | 停止(Off) | バックアップRAMのみ | LSE / LSI | リセットピン、WKUPピン、PWR、RTC、IWDG |
STM32 シリーズの電源制御システムは、現在のシステムが必要とする部分以外は、電源を供給しない、またはクロックを停止させて、「その部分を眠らせる」という考えに基づいています。これは、CPU 部分もその例外ではありません。Table 2 の横方向は、STM32 シリーズの基本的部分の電源動作モードを示したものです。STM32 シリーズによっては、複数のメイン電源回路(Main Regulator)を搭載して、電源モードにローパワーモード(LP_RUN,LP_SLEEP)を追加したものもあります。この場合は、一部の省電力に対応したペリフェラルだけがアクティブになります。
6.1. 電源制御モード RUN/SLEEP/STOP/STNDBY
通常の動作(RUN モード)以外の電源制御モードを省電力モードと呼びます。
6.1.1. RUN Mode (通常の走行モード)
CPU,FLASH,RAM,すべてのペリフェラルを動作させることが可能です。NRST Pin(リセットピン)をLOW にすると、他の電源モードからRUN モードに復帰します。
6.1.2. SLEEP Mode(ペリフェラルからのイベント待ち状態)
RUN モードと異なるのは、CPU だけが停止しているということです。活性化されているペリフェラルは動作を続けており、ペリフェラルからの割り込みやDMA 割り込み、そのたのイベントが発生すると、RUN モードに復帰し、CPU は命令を実行を再開します。割り込みを検出した場合は、割り込み処理(ISR)が呼び出されます。
6.1.3. STOP Mode
システムの主要な処理を必要としない場合で、ローパワー対応のペリフェラルやスイッチでRUN モードに復帰させる場合に使用されます。このモードでは、CPU とFLASH が停止しますがRAM は動作しています。内部のLSI と外部のLSE がクロックソースとして、基本機能モジュールとローパワーペリフェラルに供給されます。STOP モードからRUN モードに復帰するためには、事前にローパワー対応のペリフェラルやスイッチ(GPIO pin / WKUP pin)のイベント設定を行います。イベントが発生すると、RUN モードに復帰し、CPU は命令を実行を再開します。
6.1.4. STANDBY Mode
STOP モードに加えて、バックアップRAM 以外のRAM が停止します。VBAT 電源から電源供給が行われるRTC (Real time clock)の時計機能、バックアップRAM も動作し続けます。注意:RUN モードに復帰した場合は、CPU はリセット状態で復帰します。
6.2. 電源制御モードの状態遷移
HAL_PWR_EnterSLEEPMode()SLEEP モードに遷移します。
HAL_PWR_EnterSTOPMode()STOP モードに遷移します。
HAL_PWR_EnterSTANDBYMode()STANDBY モードに遷移します。
6.2.1. 電源モードを移行する前には、RUN モード復帰させる種をまいておく
SLEEP、STOP モードでは、CPU が停止しています。また STANDBY では、CPUが完全に無効となります。RUN モード以外では、CPU が目覚める(RUN モードに復帰する)ための要因を仕掛けておかないと、RESET 以外にCPU を目ざます手段がありません。省電力モードに移行以前に次のような目覚めの種をまいておきましょう。① 遷移したモード中で有効なペリフェラルの割り込み等を有効にする。
② 特定のGPIO ピンの割り込みやイベントを有効ににする。③ 特定のWKUP ピン(PA0 など)のイベントを有効ににする。④ 省エネモードで常に活性なRTC などの割り込みやイベントを有効にする。
6.2.2. SLEEP モードからの復帰
CPU が停止しているだけなので、ペリフェラルからの割り込みやイベントを有効にして、RUN モードに復帰させるようにします。CPU 復帰は最小の遅延時間で行われるため、処理のリアルタイム性が損なわれません。SLEEP モードに遷移させます。イベントが発生すると、RUN モードに復帰します。応用製品が通常動作している状態でSLEEP モードを使用して消費電力を抑えます。
6.2.3. STOP モードからの復帰
SLEEP モードでのシステムのクロック供給は、LSE とLSI に限られるので、HSE やHSI、PLL 等を使用していた場合は、クロックの切り替えや再設定を事前に行う必要があります。FLASH も停止するので、CPU復帰はそれほど早くありません。
6.2.4. STANDBY モードからの復帰
VBAT 電源から電源供給が行なわれている機能モジュールとシステムコア部分以外は、ほとんど停止状態となります。CPU 電源も無効なので、RUN モードに復帰した場合、リセットシーケンスが起動します。ヒント:電源制御モードの中で消費電力が最小限に抑えられるため、WKUP ピンに接続された電源スイッチ等による、電源OFF 状態として使用されます。(RTC(時計)や、バックアップメモリは動作し続けます。STANDBY モードからWAKEUP ボタンで復帰できるようにするためには、まずPA0 等をWAKEUP として利用する旨を、PWR_WakeUpPinCmd 関数を使用して宣言します。GPIO ピン自体の初期化を行う必要はありません。他の周辺回路とは異なり、WAKEUP ピンの初期化はPWR_WakeUpPinCmd 関数だけで行うことができます。
6.3. Cortex M のWFI 命令とWFE 命令について
低消費電力モードに移行させる命令にはWFI 命令とWFE 命令があります。①WFI はWait For Interrupt(割り込み待ち)の略です。 C 言語関数:__WFI()低消費電力モードから復帰する際に、ベクタ割込みコントローラ(NVIC)によって、割込みが認識され、割込み処理が始まります。②WFE は、Wait For Event(イベント待ち)の略です。C 言語関数:__WFE()低消費電力モードから復帰する際に、割込み処理が行われずに、低消費電力モードからの復帰だけが行われます。つまり、WFE の次の命令を実行します。
HAL_PWR 関数には、SLEEP モード、STOP モード、STANDBY モードに移行させる、
HAL_PWR_EnterSLEEPMode(),HAL_PWR_EnterSTOPMode(),HAL_PWR_EnterSTANDBYMode()のHAL_PWR 関数があります。これらの関数の引数設定で、現在の設定に適切なWFI 命令またはWFE 命令を呼ぶことで、省電力モードに移行します。
7. RCC によるクロッククロック制御のしくみ
7.1. はじめに心臓の鼓動「SYSCLK」ありき
STM32 シリーズSoC の基本システムクロックは,SYSCLK です。このクロックを分周して,システムバスのAHB クロックが生成され,主にプロセッサに供給されるクロックFCLK とコアブロックに供給されるクロックHCLK に供給されます。さらにAHB クロックを分周して,主にペリフェラルのバスとレジスタ部分に供給されるAPB クロックが生成されますペリフェラル用クロックPCLK とタイマー用のTPCLK に供給されます。
STM32 シリーズの機種によって,複数のAHB クロック(HCLK1,HCLK2)やAPB クロック(PCLK1,PCLK2)が追加されるものもあります。この場合は分周器も複数用意されます。
7.2. SYSCLK と内部クロック(HSI,LSI) 外部クロック(HSE,LSE)とPLLCLK
STM32 はSYSCLK の発生源として,STM32 内部クロック発生器として,①HSI RC(High Speed Internal RC oscillator 16MHz)と,②LSI RC(Low Speed Internal RC oscillator 32kHz)があります。これらはRC(抵抗・コンデンサ)回路方式のクロック発生器です。それぞれ,2つの内部クロックHSI クロックとLSI クロックを生成します。外部クロック2種類③HSE(High Speed External Clock),④LSE(Low Speed External Clock)は,STM32 のオシレータピンに接続された水晶振動子やセラミック振動子などの駆動回路から供給されます。⑤PLLCLK は,HSI16 またはHSE を基本周波数を分周(÷PLLM)したのちPLL による逓倍(×PLLN)分周(÷PLLR)する機能により自由度のあるSYSCLK を生成することができます。
ヒント:STM32H7 の場合は、PLL 設定で、小数点以下も指定することが可能です(Flaction 機能)。
7.3. SYSCLK の供給源を選択するRCC(System Clock Selector)回路
図9 のRCC モジュール内のSystem Clock Selector 回路は,5種類あるクロック供給源(①HSI,②LSI,③HSE,④LSE,⑤ PLLCLK)からSYSCLK の供給源を選択します。リセット後は,System Clock Selector の初期状態では,HSI クロックが指定され,HSI プリスケラー(HSI Div)とAHB プリスケラー(AHBCLK Diviser)が無効となっているため,SYSCLK はHSIクロックの周波数である16MHz となります。
7.4. その他の内部クロック供給源 MSI,HSI48,I2SPLL など
STM32 シリーズの種類によっては,周波数変更可能なMSI(Middle Speed Internal Clock)が追加されているものもあります。省電力対応のSTM32L4+などに追加されており,HSI よりも低い周波数のクロックを供給します。設定可能な周波数レンジは100kHz~48MHz です。USB デバイスが内蔵されている場合は,USB 回路の基本クロックである48MHz を供給するHSI48 オシレータが追加されます。HSI48 が追加されたデバイスの場合 HSI は HSI16 と記載されます。オーディオ機能が追加されている場合は、追加のPLL(I2SPLL)も用意されます。
7.4.1. クロックの精度をたかめるしくみ
HSI48 は,RC 方式の比較的低精度のオシレータで駆動されます。USB 規格のクロック精度を高めるため,高精度の外付けされる水晶クロックに追従させるCRS(Clock Recovery System)が追加されています。
7.4.2. クロックの設定を確かめるなら MCO(Master clock out)出力ピンを使おう
内部クロック(HSI LSI PLLCLK など)は,内部出力信号なので周波数などを直接測定できませんが,MCO出力ピン(PA8,PA9)のクロック供給源を選択することで,チップ外部で観測することができます。RCC Clock Controller~HSI 16 RC HSI HSE LSI PLL÷PLLM×PLLN÷PLLR÷PLLQ÷PLLP LSE÷ AHB CLK Divider SYSCLK Source System Clock Selector PLL Source PLL Clock S l t÷ APBn CLK Divider÷ HSI Div× APBn CLK Multiplier PLL HSI16 HSE HSE_OSC PLLR VBACK LSE LSE_OSC LSI LSI_RC SYSCLK PCLKn HCLK FCLK TPCLKn PLLCLK PLLQCLK PLLPCLK lse_ck lsi_ck hse_ck VDD hsi_ck Status / Control Reg~LSI RC 32kHz HSE OSC 4-48 MHz LSE OSC
768kHz
7.5. RCC に関連するHAL_RCC API
RCC に関連する主なHAL API を列挙します。詳細は記載しませんが、どんな関数があるのか一望しましょう。
HAL_StatusTypeDef HAL_RCC_DeInit(void)RCC クロック設定をデフォルトのリセット状態にリセットします。__weak HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef *RCC_OscInitStruct)RCC_OscInitTypeDef で指定されたパラメーターに従って、RCC オシレーターを初期化します。LSI,LSE,HSE,SHI,PLL などのクロックソースを初期化します。
HAL_StatusTypeDef HAL_RCC_ClockConfig(RCC_ClkInitTypeDef * RCC_ClkInitStruct、uint32_tFLatency)RCC_ClkInitStruct で指定されたパラメーターに従って、CPU、AHB、およびAPBバスクロックを初期化します。SYSCLK,HCLK,PCLK などのクロックルーティングを設定します。Void HAL_RCC_MCOConfig(uint32_t RCC_MCOx、uint32_t RCC_MCOSource、uint32_t RCC_MCODiv)MCO1 ピン(PA8)またはMCO2 ピン(PC9)に出力するクロックソースを選択します。
uint32_t HAL_RCC_GetSysClockFreq(void)現在のSYSCLK 周波数を返します。
uint32_t HAL_RCC_GetHCLKFreq(void)現在のHCLK 周波数を返します。
uint32_t HAL_RCC_GetPCLKFreq(void)現在のPCLK 周波数を返します。
uint32_t HAL_RCC_GetPCLKnFreq(void)現在のPCLKn 周波数を返します。
void HAL_RCC_GetOscConfig(RCC_OscInitTypeDef * RCC_OscInitStruct)内部RCC 構成レジスタに従ってRCC_OscInitStruct を構成します。
HAL_RCC_OscConfig()で構成されたオシレータの設定状態を取得します。
void HAL_RCC_GetClockConfig(RCC_ClkInitTypeDef * RCC_ClkInitStruct、uint32_t *pFLatency)内部RCC 構成レジスタに従ってRCC_ClkInitStruct を構成します。
HAL_RCC_ClockConfig()で構成されたSYSCLK などのクロックの設定状態を取得します。
void HAL_RCC_NMI_IRQHandler(void)この関数は、RCC-CSS 割り込み要求を処理します。
8. RCC(reset & clock controller)の個別ペリフェラル制御部
Peripheral Independent Controller には,1 つのペリフェラルごとに,5つのレジスタ内のビットフィールドが定義されています。このレジスタは,常時変更可能です。必要なペリフェラルを動作中に停止・動作させたり,供給されるクロックを変更したりすることができます。① pppRST:指定したペリフェラルをリセットします。ppp_reset に反映します。② pppSMEN:SLEEP/STOP モード移行時にppp_pclk を供給するためのフラグです。
③ pppEN:RUN モード動作時にppp_pclk を供給するためのフラグです。④ pppSEL[n]:pppSEL[n]で選択したクロックをppp_ker_ck に供給します。注意:pppSEL[n]で選択できるクロックは,ペリフェラルによって異なります。SAI やI2S を備えたSTM32 の場合は,追加のPLL からのクロック供給が可能なものもあります。
8.1. 個別ペリフェラル制御部のHAL_RCCEx 関数
個別ペリフェラル制御部はSTM32 シリーズの機種に依存するので、STM32 シリーズの共通
HAL_RCC 関数ではなく拡張HAL API 群で個別のペリフェラル制御の設定を行います。
HAL_StatusTypeDef HAL_RCCEx_PeriphCLKConfig(RCC_PeriphCLKInitTypeDef * PeriphClkInit)RCC_PeriphCLKInitTypeDef で指定されたパラメータに従って、RCC 拡張ペリフェラルクロックを初期化します(クロックソース、プリスケーラ値など)。
void HAL_RCCEx_GetPeriphCLKConfig(RCC_PeriphCLKInitTypeDef * PeriphClkInit)現在のRCC 内部レジスタに基づきRCC_ClkInitStruct 構造体で設定状態を取得します。(HAL_RCCEx_PeriphCLKConfig の逆関数です。)
uint32_t HAL_RCCEx_GetPeriphCLKFreq(uint32_t PeriphClk)特定のペリフェラルのペリフェラルクロック周波数を返します。
void HAL_RCCEx_GetPLLxClockFreq(PLLx_ClocksTypeDef * PLLx_Clocks)現在のPLLx クロックに設定されている出力周波数を計算して返します:PLLx_P_Frequency、PLLx_Q_Frequency およびPLLx_R_Frequency。PLLx の部分は、個別のSTM32 に内蔵されている、PLL の名称です。RCC Peripheral Independent Controller SYSCLK PCLKn lse_ck hse_ck PLLQCLK PLLRCLK Other _CLK pppSEL [n]pppRST pppEN pppSMEN ppp_reset ppp_ker_ck ppp_pclk PCLKn/HCLK pppreset pppclock enable pppclock enable in /Sleep/Stop Independent clock config RCC_AxB_R STRn RCC_AxBE NRn RCC_A xB_SM ENRn RCC_A xB_CC IPRn AHB Slave FIGURE 10 RCC 個別ペリフェラル制御ブロック
9. STM32 の内蔵ペリフェラルのシステムインターフェイス
この節で説明するペリフェラルとSTM32 の基本システムとの相互接続を完全に理解することがとても重要です。STM32 に内蔵されるペリフェラルは、ほぼ同じ構造となっています。Figure 11 は,STM32 シリーズに内蔵されるペリフェラルを一般化したブロック図です。図の表記でppp の部分は,i2c1,uart2,rc,aes などの固有名称に読み替えてください。ペリフェラルは機能によって,高速バスのAHB または低速バスAPB のどちらかのSlave バスに接続されています。ペリフェラルには,ppp_pclk クロックが供給される部分とppp_ker_ck クロックが供給される部分2つのパートに分かれています。
外部インターフェイスがない場合は,ppp_ker_ck クロック部分がない場合もあります (CRC, AES など)。ppp_pclk,ppp_ker_ck,ppp_reset は,あとで説明する,RCC(reset & clock controller)の個別ペリフェラル制御部(Peripheral Independent Controller)の制御レジスタから,ペリフェラル単位で供給されます。
初期状態では,ppp_pclk,ppp_ker_ck の2つのクロックが停止状態なので,ペリフェラルは無効です。ppp_reset 信号は,ペリフェラル全体を初期状態に戻します。
9.1. Peripheral Clock Domain (ppp_pclk Domain)
このブロックには,ペリフェラルの制御レジスタとステータスレジスタがあります。ppp_pclk が活性化すると,AHB またはAPB バスから,制御レジスタとステータスレジスタにアクセスが可能です。
9.2. Peripheral Kernel Clock Domain (ppp_ker_ck Domain)
このブロックには,ペリフェラル固有のロジック回路やI/O 回路が含まれています。例外として、外部インターフェースがないペリフェラルの場合は,ペリフェラル固有のロジック回路にもppp_pclk が供給されます(CRC,AES など)。このブロックは,消費電流が比較的大きいので,ppp_pclk Domain だけを設定しておいて,ペリフェラルを使用するときにppp_ker_ck を供給するという使い方ができます。
9.3. ペリフェラルの出力制御信号
ペリフェラルが動作中にイベントが発生した場合に,STM32 の基本機能(割り込み,DMA 要求,Wake-UP 要求)と相互接続するための3 つの出力信号があります。ペリフェラルが固有の処理(受信・送信・計算の開始/完了/エラー発生など)の結果を通知します。
9.3.1. Peripheral Wakeup Request 出力信号 (ppp_wakeup)
ペリフェラルで通知事項(イベント)が発生した場合に,システムをWakeup させるための信号です。
9.3.2. Peripheral Interrupt Request 出力信号(ppp_int)
ペリフェラルで外部へのイベントが発生した場合に,割り込みコントローラのNVIC(Nested vectored Interrupt Controller)に通知します。この通知により,ベクター割り込みが発生します。ベクター割り込みが定義されていないイベントが発生した場合は,EXTI の拡張割り込み経由で割り込みを起動する場合もあります。
9.3.3. Peripheral DMA Request 出力信号(ppp_dma)
ペリフェラルでイベントが発生した場合に,DMA コントローラに転送指示を通知します。この通知により,DMA コントローラがデータ転送を開始します。ペリフェラルによっては,複数の独立したDMA要求信号が実装される場合があります。(例えばUART の受信完了、送信要求などの場合)
9.4. この節のまとめ
① ペリフェラルは、クロックppp_pclk とppp_ker_ck を供給しないと動かない。② ペリフェラルに供給されるクロックは2種類あるPeripheral Clock (ppp_pclk):ペリフェラル制御レジスタを動作させるクロック入力Peripheral Kernel Clock (ppp_ker_ck):ペリフェラル機能部分を動作させるクロック入力③ ペリフェラルで発生したイベントは3つの出力信号で通知されます。
Peripheral Wakeup (ppp_wakeup):STANDBY/STOP 状態から目覚めさせる(RUN)信号Peripheral Interrupt (ppp_int):NVIC に割り込みを要求する信号Peripheral DMA request (ppp_dma):DMA コントローラにデータ転送を依頼する信号
10. チップの外とのやり取りは必ずGPIO を経由する
STM32 シリーズのSoC チップの特徴として、電源に関係するピン、リセットピン、BOOT0 ピン(起動時のプログラムのスタート先を指定)以外は例外なく、GPIO(General purpose Input / output)機能ブロックを経由して、チップ外部に接続されるデバイスとのやり取りを行います。最新のSTM35G0/G4 などでは、リセットピン、BOOT0 ピンもGPIO経由となります。したがって、端子からSoC の外部の世界とやり取りをするためにはかならずGPIO のお世話にならなければならないことに注意してください。まさに、GPIOは社会の窓といえるでしょう。Figure 12 はすべてのGPIO ピンごとに用意されている機能です。GPIO を活性状態にするには、RCC(Peripheral Independent Controller)のGPIOxCEN を設定してgpio_[a..g]_pclk を供給する必要があります。__HAL_RCC_GPIOC_CLK_ENABLE();
10.1. 入力データレジスタGPIOx_IDR、と出力データレジスタ GPIOx_DOR
GPIOx_IDR I/O ポートの入力値が格納されています。GPIOx_ODR I/O ポートの出力値が格納されています。参考:GPIOx_ODR は、不可分なビット単位のセット/リセットのために、GPIOx_BSRR レジスタによって、ODR ビットを個々にセット/リセットすることができます。これらのレジスターは、Alternate 機能で、ピンがペリフェラルにアサインされていても読み書きが可能です。
10.2. GPIO ピンの物理的特性を設定する
GPIO のピンの物理的特性を設定するレジスタについておさらいしましょう。GPIOx_MODER(GPIO Port Mode Register)ポートのモードを指定します。MODER[y][1:0]:ピン y(y = 15~0)00:入力モード / 01:出力モード / 10:オルタネート機能モード /11:アナログモードオルタネート機能モードは、ペリフェラルからのI/O を行うモードです。GPIOx_OTYPER(GPIO Port Output Type Register)ポートの出力FET のドライブ方法を設定します。OTYPER [y][ 0]:ピン y(y = 15~0)0:出力プッシュプル(リセット状態) / 1:出力オープンドレイン(N-FET のみ)注意:オープンドレインでは、GPIO 出力レジスタODR のビットに0 を書きむとVSS 側のN-FETがオンします。1 の場合はハイインピーダンスになります。
GPIOx_PUPDR(GPIO Port Pull up/Pull down Register)I/O のプルアップまたはプルダウンを設定しますPUPDR[y][1:0]:ピン y(y = 15~0)00:プルアップ/プルダウンなし 01:プルアップ 10:プルダウン 11:予約済みGPIOx_OSPEEDR(GPIO Output Port Speed Register)I/O の出力スピードを設定をします。OSPEEDR[y][1:0]ピン y(y = 15~0)00:ロースピード 01:ミディアムスピード 10:ハイスピード 11:超ハイスピードヒント:速度のパラメータは、STM32 機種個別のデータシート(DS)を参照してください。
GPIO_AFRn(GPIO Alternate Function Register)ピンに関連するペリフェラルの入出力信号に切り替える I/O Alternate 機能です。STM32 シリーズに代表される組み込みプロセッサの物理的な端子(Pin)は、使用方法固定された電源端子やリセット端子などを除くと、単純なロジック入出力、内蔵されるペリフェラルの入出力、内部クロック出力、アナログ入出力などをGPIO_AFRn レジスタを操作することで、ピンの機能を切り替えて使用することが可能です。
10.3. GPIO 用 HAL API
GPIO 操作用のHAL_GPIO の関数は、操作するGPIO のインスタンス(実アドレス)の 構造を記述する、以下のGPIO_TypeDef 構造体へのポインタを使用してアクセスします。
typedef struct
{
__IO uint32_t MODER; /*!< GPIO port mode register,Address offset: 0x00 */__IO uint32_t OTYPER; /*!< GPIO port output type register,Address offset: 0x04 */__IO uint32_t OSPEEDR; /*!< GPIO port output speed register,Address offset: 0x08 */__IO uint32_t PUPDR; /*!< GPIO port pull-up/pull-down register,Address offset: 0x0C */__IO uint32_t IDR; /*!< GPIO port input data register,Address offset: 0x10 */__IO uint32_t ODR; /*!< GPIO port output data register,Address offset: 0x14 */__IO uint32_t BSRR; /*!< GPIO port bit set/reset,Address offset: 0x18 */__IO uint32_t LCKR; /*!< GPIO port configuration lock register,Address offset: 0x1C */__IO uint32_t AFR[2]; /*!< GPIO alternate function registers,Address offset: 0x20-0x24 */} GPIO_TypeDef;GPIO_TypeDef 構造体を眺めると、ポートごとのGPIO 制御レジスタのアドレスの並びがわかると思います。図12 と見比べると、GPIO 制御レジスタの具体的な配置がわかります。GPIO 操作用の
HAL_GPIO 関数は複雑なことをしないで、直接GPIO 制御レジスタを操作します。つまり、HAL_GPIO関数は、いつ何時でも呼ぶことができるということです。GPIO 操作用のHAL_GPIO の初期化関数は、
GPIO_InitTypeDef 構造体を使用します。GPIO_InitTypeDef の具体的な構造を示しておきます。
typedef struct
{
uint32_t Pin;/*!< 設定するGPIOピンを指定 */
uint32_t Mode;/*!< 選択したピンの動作モードを指定 */
uint32_t Pull;/*!< プルアップまたはプルダウンの設定を指定 */
uint32_t Speed;/*!< 選択したピンの駆動速度を指定 */
uint32_t Alternate;/*!< 選択したピンに接続するペリフェラル機能(オルタネート機能)を指定 */} GPIO_InitTypeDef;GPIO のインスタンス構造体で定義された個々のレジスタの設定を簡素化して、抽象的に設定できるように工夫しています。以下、HAL_GPIO 関数を列挙してポイントを説明します。
void HAL_GPIO_Init(GPIO_TypeDef * GPIOx、GPIO_InitTypeDef * GPIO_Init)
GPIO_Init で指定されたパラメーターで、GPIOx ペリフェラルを初期化します。
void HAL_GPIO_DeInit(GPIO_TypeDef * GPIOx、uint32_t GPIO_Pin)GPIOx ペリフェラルレジスタをデフォルトのリセット値に戻します。GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef * GPIOx、uint16_t GPIO_Pin)指定した入力ポート(GPIOx)ピン(GPIO_Pin)の現在の状態を読み取ります。ヒント:Alternate 機能が有効でペリフェラルが使用していてもポートを読めます。
void HAL_GPIO_WritePin(GPIO_TypeDef * GPIOx、uint16_t GPIO_Pin、GPIO_PinState PinState)選択したデータポートレジスタをPinState の値に設定します。ヒント:Alternate 機能が有効でペリフェラルが使用していてもポートを書けます。
void HAL_GPIO_TogglePin(GPIO_TypeDef * GPIOx、uint16_t GPIO_Pin)指定されたGPIO ピンの値をトグルします。
HAL_StatusTypeDef HAL_GPIO_LockPin(GPIO_TypeDef * GPIOx、uint16_t GPIO_Pin)GPIO ピン構成レジスタをロックします。一度ロックされると、リセットされるまで
HAL_GPIO_Init で設定されたを変更することができません。(設定保護機能)
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)NVIC のEXTI 割り込み要求を処理します。__weak HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)何もしないEXTI の割り込み処理コールバック関数です。ユーザ側で再定義します。
10.3.1. GPIO_InitStruct.Mode と.Alternate に着目しよう
前ページで復習したとおり、GPIO_InitTypeDef 構造体は、HAL_GPIO_Init()で使用するGPIO初期化構造体です。構造体に含まれる値についておさらいします。重要:GPIO は、単独での割り込み処理機能を持っていません。HAL_GPIO_Init()関数のMode 引数で、割り込みを必要とする設定を行った場合は、関数内部でEXTI(Extended Interrupt & event Controller)やSYSCFG(Sysetem Configuration)機能と連携していることを理解しましょう
typedef struct
{
uint32_t Pin;
uint32_t Mode; uint32_t Pull; uint32_t Speed; uint32_t Alternate;
} GPIO_InitTypeDef;Pin: 初期化したいピンの選択初期化したいピンを論理和で列挙します。GPIO_PIN_0~GPIO_PIN_15 で指定します。GPIO_PIN_0|GPIO_PIN_15 と指定すれば pin0 とpin15 だけが初期化されます。Pull:プルアップ/プルダウンの設定GPIO_NOPULLプルアップダウン機能を使用しません 。GPIO_PULLUP内部抵抗を使用して、信号ピンをVDDIO にプルアップ接続します。
GPIO_PULLDOWN内部抵抗を使用して、信号ピンをVSS にプルダウン接続します。Speed:出力モードの最大周波数を設定しますGPIO_SPEED_FREQ_LOW低速GPIO_SPEED_FREQ_MEDIUM中速GPIO_SPEED_FREQ_HIGH高速GPIO_SPEED_FREQ_VERY_HIGH最高速Pull および Speed は、GIOP 内部のレジスタを操作します。Mode:ピンをどのモードで使用するのかを選定します。
GPIO_MODE_INPUT:デジタル入力モードに設定します。GPIO の出力機能は無効ですGPIO_MODE_OUTPUT_PP: デジタル出力モードに設定します。ドライバFET はpush/pull モードGPIO_MODE_OUTPUT_OD: デジタル出力モードに設定します。ドライバFET はopen drain モードこの3つのモードは、シンプルでGIOP 内部のレジスタを操作するだけです。
GPIO_MODE_AF_PP:代替え機能を有効にし、push/pull デジタル出力モードに設定します。GPIO_MODE_AF_OD:代替え機能を有効にし、open drain デジタル出力モードに設定します。この2つのモードは、GIOP 内部のレジスタを操作し、Alternate(代替え)機能を有効にします。GPIO_MODE_ANALOG:アナログモードに設定します。GPIO の出力機能は無効です。
GPIO_MODE_ANALOG_ADC_CONTROL: アナログモードで接続されたADC 制御を可能にします。この2つのモードは、GIOP 内部のレジスタを操作し、アナログ機能を有効にします。デジタル機能は無効GPIO_MODE_IT_RISING: EXTI の割り込み機能を使用して、入力信号の立ち上がりエッジを検出しますGPIO_MODE_IT_FALLING: EXTI の割り込み機能を使用して、入力信号の立ち下がりエッジを検出しますGPIO_MODE_IT_RISING_FALLING: EXTI の割り込み機能を使用して、入力信号の両エッジを検出しますこの3つのモードは、EXTI のエッジ検出機能を有効にして、EXTI の割り込みが発生するようにEXTI 制御レジスタを設定します。また、SYSCFG 機能の内部レジスタI/O Port Matrix を設定し、NVIC に接続されたexti_int[pin]への割り込み要求とピンを関連付けます。
注意:同じピン番号(例えば、PA0、PB0…PG0)は同時にEXTI 割り込みを発生させることはできません。GPIO_MODE_EVT_RISING:EXTI のイベント機能を使用して、入力信号の立ち上がりエッジを検出しますGPIO_MODE_EVT_FALLING: EXTI のイベント機能を使用して、入力信号の立ち下がりエッジを検出しますGPIO_MODE_EVT_RISING_FALLING: EXTI のイベント機能を使用して、入力信号の両エッジを検出しますこの3つのモードは、EXTI のエッジ検出機能を有効にして、EXTI のイベントが発生するようにEXTI 制御レジスタを設定します。
Alternate:ペリフェラル入出力への代替え機能(Alternate function)の設定Mode 値がGPIO_MODE_AF_PP またはGPIO_MODE_AF_OD に設定されると、ピンは、最大16 種(GPIO_AF0~GPIO_AF15)類のピン固有のペリフェラルへの入出力信号に接続されます。注意:接続できるペリフェラルは、STM32 シリーズ内の機種のピンごとに異なります。Alternate の定数値は、HAL API のインクルードファイル”stm32xxxxx_hal_gpio_ex.h”の定数定義(#define)を使用します。GPIO_AF4_I2C1、GPIO_AF7_USART1 のような明示的な定数が定義されています。
基本ハードウェア:EXTI、SYSCFG、DMA Controller、DMAMUX
非同期イベント管理、SYSCFGピンリマッピング、DMA FIFOバッファ、DMAMUX動的リクエストマッピング
11. EXTI 拡張割り込み・イベントコントローラ
11.1. 「ボタン割り込みでのL チカアプリ」でEXTI の本質を理解する
課題設定:PA0 にボタンスイッチの端子を接続し、もう一方の端子をVSS(GND)に接続する。PA1 にLED(-側)を接続し1KΩの抵抗を介してVDD(電源)に接続する。課題要求:EXTI 処理を利用してPA0 ボタンを押すごとに、PA1 のLED が点灯・消灯を繰り返すこと。
11.1.1. ボタン割り込みでのL チカアプリの実装
① PA0 とPA1 の物理的設定とPA0 割り込みをHAL 関数を使用して設定します。この初期化関数は、CubeMX で生成したコードを編集したものです。
static void MX_GPIO_Init(void)
{
__HAL_RCC_GPIOA_CLK_ENABLE();/* GPIO Ports Clock Enable */
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_SET); /* Initialize PA for Open drain */
GPIO_InitTypeDef GPIO_InitStruct = {0};/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);/*Configure GPIO pin : PA0 */
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);/*Configure GPIO pin : PA1 */
GPIO_InitStruct.Pin = GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);/* EXTI interrupt init*/
HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(EXTI0_IRQn);
}② PA0 に接続されたボタンの割り込みハンドラーを記述します。
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
If(GPIO_Pin == GPIO_PIN_1){
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_1);
}
}「こんなの、誰でも知ってるよ!」と思い込んではせん、HAL_GPIO 関数の中では、結構複雑なこと(SYSCFG とEXTI とのディープな連携)が行われているのです。
11.2. ボタン割り込みでのL チカアプリの詳細
スイッチが押されるたびに、LED がトグル点灯するまでのプログラムの流れを詳しく追ってみましょう。① 初期設定MX_GPIO_Init(void)PORTA(PA)のクロックを有効にします: __HAL_RCC_GPIOA_CLK_ENABLE();PA0 の設定:PA0 はPull=GPIO_PULLUP と指定されることで、内部抵抗通じてVDDIO に接続されます。PA0 に接続されたボタンが押下されていないときには、論理値は1 になります。
ボタンが押されると、PA0 は、VSS に接続されるので論理値は0 になります。PA0 は Mode= GPIO_MODE_IT_FALLING と指定されることで、次の機能が有効になります。SYSCFG のI/O Port Matrix 機能を用いて、PA0 信号をEXTI 内部に16 個のピンごとに存在する、EXTI 処理回路のエッジ検出(Edge Detect)回路の入力信号に接続されます。FALLING(立下り)が指定されれいるので、エッジ検出回路を立下り検出を有効にします。
割り込みが発生すると、NVIC に対してEXTI0_IRQn ベクター割り込み要求を行います。PA1 の設定:PA1 はPull=GPIO_NO と指定されることで、内部抵抗によりプルアップダウンは無効です。PA1 は Mode= GPIO_MODE_OUTPUT_OD 指定されることで、次の機能が有効になります。PA1 は出力モードとなります。GPIO の出力レジスタGPIO0_ODR[0]の値が出力されます。
OUTPUT_OD(Open Drain)指定により出力駆動FET のN-Chan 側だけが有効になります。出力レジスタGPIO0_ODR[0]を0 に指定すると、N-Chan 側が通電状態(ON)になります。N-Chan 側が通電状態(ON)になると、LED が点灯しますNVIC の設定:EXTI0_IRQn の優先度を設定します。HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0);EXTI0_IRQn の割り込み要求を有効にします。HAL_NVIC_EnableIRQ(EXTI0_IRQn);② 割り込みハンドラー HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)割り込みが発生すると最終的に、このコールバック関数が割り込みコンテキスト内で呼ばれます。HAL_GPIO_EXTI_Callback 関数の引数GPIO_Pin (GPIO_PIN_0~GPIO_PIN_15)で、どのピンに関連する割り込みが発生したかを通知します。どのピンに起因する割り込みか判定して、PA1 の出力信号をトグルします。
If(GPIO_Pin == GPIO_PIN_1){HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_1);}ヒント:HAL_GPIO_EXTI_Callback 関数は、HAL_GPIO 内で予約されている関数です。ユーザ側で記述しないと、HAL_GPIO コード内にある何もしない week 関数が呼ばれます。
11.3. ボタン割り込みでのL チカアプリの詳細動作を検証しよう
① PA0 のボタンが押されると…ボタンが押された瞬間、PA0 入力信号の論理値は1 から0 に変化します。EXTI のエッジ検出回路がPA0 の立下りエッジ(1→0 の変化)を検出すると、EXTI の割り込み保留フラグIPF(Interrupt pending flag)が論理値1 になります。NVIC への割り込み要求信号int_exti0も論理値1 となります。② NVIC が割り込みを検出すると…(1)
NVIC がint_exti0 を検出すると、int_exti0 の優先度を判定し、現在のMCU がint_exti0 よりも高い優先度の割り込み処理を行っていない場合は、割り込み処理を受理します。それ以外は高い優先度の割り込み処理が継続されます。その間は、int_exti0 割り込みは保留(pending)されます。(2)NVIC がint_exti0 を受理すると、MCU は、現在の内部レジスタをスタック(SP)上に退避させ、HAL_GPIO の内部にあるEXTI0_IRQHandler を呼び出します。
(3)EXTIn_IRQHandler は、ピン番号を引数として、HAL_GPIO の内部関数である
HAL_GPIO_IRQHandler(GPIO_Pin)を呼び出します。(4)
HAL_GPIO_IRQHandler は、EXTI 内部の割り込みフラグIPF をクリアし、ユーザ記述した関数のHAL_GPIO_EXTI_Callback(GPIO_Pin)を呼び出します。(5)
HAL_GPIO_EXTI_Callback 関数内でアプリケーションは割り込みの事後処理(PA1出力をトグル)を行い、HAL_GPIO_IRQHandler に戻ります。(6)
HAL_GPIO_IRQHandler から復帰すると、退避したスタック(SP)上のMCU の内部レジスタを復元し、以前のNCU 走行コンテキストに復帰します。
11.4. EXTI 拡張割り込み・イベントコントローラの詳細
EXTI(Extended Interrupt & Event Controller)は、STM32 Soc 外部および内部で非同期的に発生するイベント/割込みを管理します。EXTI から割り込みやイベントを発生させる元となるEXTI 入力信号(exit_lineN)をSTM32G4 シリーズの例で、具体的に説明します。
11.4.1. EXTI の入力信号Configurable Line とDirect Line
EXTI 側は、エッジ信号はEXTI 内部でラッチ記憶され、論理値レベル信号1に変換されます。EXTI 側で間接的に割り込みイベントの条件を生成するイベント入力を「設定可能な信号線イベント」(Configurable Line Event)と呼びます。② ダイレクト信号(Direct Line)とは:ペリフェラルがNVIC に直接接続された割り込み要求出力信号(ppp_int 割り込み信号)を持っている場合のペリフェラルは、イベント要求出力信号(ppp_event イベント信号)を持っています。イベント発生時の出力は、論理値レベル信号1となります。
ペリフェラル側で割り込み要因が発生すると、ppp_int をアクティブすることで、NVIC に接続されているppp_int に対応するIRQ[n]の割り込みベクターによるISR(interrupt service routine)を呼び出します。この割り込み処理については、EXTI は、関与しません。STOP モード中は、CPU は停止し、PWR(Power Controller)はSTM32 の基本部分と一部のペリフェラル以外の電源供給が行われていない状態です。この状態から処理を継続できるRUN モードに移行するためには、いくつかの前準備が必要となります。
①PWR(Power Controller)にexti_wkup イベントを送り電源供給状態を復帰させる。②CPU(RxEV 信号)にcpu_event パルスを送りRUN モードに復帰させる。注意:ダイレクトライン割り込みをもつペリフェラルであっても、EXTI のイベント通知機能を使用して、STOP モードからRUN モードに復帰させなければなりません。ペリフェラル側でppp_event イベント信号を生成し、EXTI 側で間接的に管理するイベント入力信号を「ダイレクト信号イベント」(Direct Line Event)と呼びます。
11.4.2. EXTI の制御レジスタ
各EXTI 制御レジスタはEXTI[n]に関連する制御をビットフィールド単位で設定します。nは、EXTI の入力信号(line)番号です。0~15 は、GPIO ポートのpin 番号です。16~127 はそれ以外の拡張割り込み入力信号のイベント番号です。EXTI_RTSRx (EXTI Rising Trigger Selection Register).RTnエッジトリガ検出回路(Edge Trigger Detector)で「立ち上がりエッジ」を検出可能にします。
EXTI_FTSRx (EXTI Falling Trigger Selection Register).FTnエッジトリガ検出回路(Edge Trigger Detector)で「立ち下がり」エッジを検出可能にします。EXTI_SWIERx (EXTI Software Interrupt Event Register).SWIERnエソフトウェア割込みを設定します。このビットフィールドは、読出し時に必ず 0 を返します。
0:0 を書き込んでも、ビットの値は変化しません。1:このビットに 1 を書き込むと、イベントをトリガを発生させます。このビットは、ハードウェアによって自動的にクリアされます。EXTI_IMRx (EXTI Interrupt Mask Register).IMn割込みマスクフラグ0:割込みリクエストはマスクされます。1:割込みリクエストはマスクされません。EXTI_EMRx (EXTI Evant Mask Register).EMnイベントマスクフラグ0:イベントリクエストはマスクされます。1:イベントリクエストはマスクされません。
EXTI_PRx (EXTI Pending Register).PIFnペンディング割込みフラグ0:トリガリクエストは発生していません。1:トリガリクエストが発生しました。このビットは、エッジイベントが割込みラインに到着したときにセットされます。このビットは、ビットに“1”を書き込むことによってクリアされます。ヒント:EXTI やSYSCFG は、影の役者で、EXTI やSYSCFG を直接制御するHAL API はありません。
たとえば、ダイレクト信号23 を使用して(I2C1)のイベント処理を場合は、HAL_I2CEx API を使用して、間接的にEXTI のイベント処理レジスタを設定します。
HAL_I2CEx_EnableWakeUp(&hI2c1);// I2C1 のHAL ハンドルから自動的にEXTI[23]が選択されて、WakeUp イベントが生成されます。
11.4.3. Direct Line Event を使用したコードの例
ここでは、i2c1_int 信号とi2c1_event 信号をもっている、I2C1 の実装例を説明しましょう。初期化コードとエラー処理を割愛します。内容:I2C1 を使用して割り込み受信を行い、STOP モード中にデータ受信を繰り返す。実装例:Int main(){
HAL_Init();/* Configure the system clock to 16 MHz */SystemClock_Config();while(1){① HAL_I2CEx_EnableWakeUp(&I2cHandle);② HAL_I2C_Slave_Receive_IT(&I2cHandle, (uint8_t *)aRxBuffer, RXBUFFERSIZE);③ HAL_RCCEx_WakeUpStopCLKConfig(RCC_STOP_WAKEUPCLOCK_HSI);④ __HAL_RCC_PWR_CLK_ENABLE();⑤ HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);⑥ /* ... STOP mode ... */⑦ while (HAL_I2C_GetState(&I2cHandle) != HAL_I2C_STATE_READY);⑧ UserI2c1Process();}
void HAL_I2C_SlaveTxCpltCallback(I2C_HandleTypeDef *I2cHandle)
{/* Restore config: clock, GPIO... */SystemClock_Config();/* Wake Up from Stop Mode */}
12. SYSCFG(System Configuration Controller)
STM32 のシステム動作に関係する基本的な設定を行います。①メモリ領域の再配置②GPIO 外部割込みの管理③ 「堅牢性」機能の管理④ SRAM2 保護機能⑤ FPU 割込み⑥ I2C 高速モードプラスの設定⑦ 独立CPU 割込みマスク
13. DMA (Direct Memory Access) Controller
13.1. DMA の基本動作
ヒント:Peripheral AHB Master はメモリメモリ間転送を行えるようにメモリにも接続されています。STM32 には、2つの標準DMA コントローラ(DMA1,DMA2)があり,1つのDMA コントローラで独立した8 チャンネルまでのDMA 転送を同時に行うことができます。STM32 DMA のDMA 転送の基本単位は Stream チャンネル(Stream 0~7)と呼ばれます。Stream 単位ごとに同じDMA 制御レジスタが存在します。たくさんの制御レジスタがあって様々な動作が可能ですが、基本となる動作は同じです。
DMA は、Stream チャンネル単位で完全に独立しています。同一のDMA 内で複数のStreamを同時に動作させた場合は、Stream ごとに設定された優先順位に基づき、DMA Bus Arbiuterがバスの競合を調停します。同じ優先順位の場合は持ち回り方式 (Round Robin)となります。
13.2. 一つのDMA ストリームを考察しよう
13.2.1. DMA Stream の動作要求dma_strX と、結果を割り込みで伝えるdma_itX
DMA の初期設定が行われると、次のDMA 転送サイクルが繰り返されます。⑧ペリフェラルのDMA 転送要求信号 ppp_dma 信号が、DMA 接続経路(DMAMUX 等)からdma_strX に導かれ DMA Stream X にDMA 転送要求が受理されます。⑨DMA Stream X は、Bus Arbiter で転送元のBus の使用権が確保できるまで待ちます。⑩転送元ペリフェラルまたはメモリ上のデータをDMA 内のデータレジスタに受信します。
⑪DMA Stream X は、Bus Arbiter で転送先のBus の使用権が確保できるまで待ちます。⑫DMA 内のデータレジスタから転送先のペリフェラルまたはメモリに送信します。⑬指定された転送回数の半分に達したもしくは転送完了した場合、または転送エラーが発生した場合は、DMA Stream ごとに関連付けられた割り込み信号dma_itX がアサートされます。それ以外の場合は、①に戻って、次のDMA 転送サイクルを実行します。
dma_itX は、NVIC を起動し関連づけられたISR(interrupt service routine)を呼びだします。ppp_dma 信号は、ペリフェラルの処理速度に応じて、受信完了、送信完了などが断続的に発生するので、1回のDMA 転送サイクル内で何回も発生することに注意してください。
13.3. FIFO を使用しないDMA 転送 (Direct mode)方法
13.3.1. DMA を停止、開始、再開させる DMA_SxR 内EN フラグ
DMA の初期設定または、DMA 転送を停止させるには、DMA_SxR 内のEN(Enable)を0 にします。この状態でDMA の各種設定が可能です。設定が終わったら、EN を1 にするとDMA を活性化されます。活性化中EN はいつでも0に設定して停止することができます。DMA 転送が完了またはエラーが発生するとEN は0 にクリアされます。活性化という言葉を使ったのは、DMA 開始の要因になるイベントが通知されるとDMA 転送が始められる状態にするという意味です。
13.3.2. DMA の転送方向、転送バス幅、転送ごとのアドレス増加数等を設定する
DMA ストリームの基本動作はDMA_SxCR(Stream x Control Register)のレジスターで決定されます。①DMA_SxCR DIR[1:0] (Direction):データの転送方向を設定します。00:ペリフェラルからメモリ、01:メモリからペリフェラル、10:メモリ間②DMA_SxCR PINC,MINC:ペリフェラル/メモリアドレスインクリメントモード0:アドレスは固定 1:アドレスは、各データ転送後にインクリメント③DMA_SxCR PSIZE[1:0] MSIZE[1:0]: ペリフェラル/メモリデータサイズ00:バイト(8 bit)、01:ハーフワード(16 bit)、10:ワード(32 bit)通常は、両レジスタともペリフェラル側のデータレジスタの幅に設定します。
13.3.3. 転送アドレス(SxPAR,SxM0AR)、転送サイズ(SxNDTR)を指定する
①DMA_SxPAR (Stream x Peripheral Address Register)Peripheral AHB Master に接続されるペリフェラルの先頭アドレスを指定します。②DMA_SxM0R (Stream x Memory 0 Address Register)Memory AHB Master に接続されるメモリーアドレスの先頭を指定します(その0)③DMA_SxNDTR (Stream x Number of Data Transfer Register)DMA 転送数を指定します(DMA 動作中は、転送すべき残りのデータ数を保持します)DMA_SxCR.DIR [1:0]DMA 転送方向転送元アドレス転送先アドレス00ペリフェラルからメモリDMA_SxPAR DMA_SxM0AR 01メモリからペリフェラルDMA_SxM0AR DMA_SxPAR 10メモリ間転送DMA_SxPAR DMA_SxM0AR 11予約
13.3.4. DMA 完了割り込みの設定
DMA 転送中に、DMA 動作の状況を割り込みで通知することができます。
13.4. DMA Circular (循環モード)
通常は、1回のDMA 転送が終了した場合、DMA_SxCR のEN ビットが0 とDMA 転送完了となりますが、DMA_SxCR のCIRC ビットを立てると、1回のDMA 転送が終了した場合に、DMA_SxM0AR とDMA_SNDTR が自動的に初期設定したアドレス値に戻され、次のDMA 転送を待機します。この場合は、DMA_SxCR のEN ビットは1のままになります。一般的なペリフェラル・メモリDMA 転送での循環モードの処理手順は、次の通りです。
①DMA_SxCR のEN ビットを0 にする。②方向(DIR)、バス幅(PSIZE,MSIZE)、アドレス増加値(PINC)等を設定する。③転送アドレス(SxPAR,SxM0AR)、転送サイズ(SxNDTR)を指定する。④DMA_SxCR のEN ビットを1にして、DMA 転送開始を予約する。⑤DMA 転送イベントが有効になると、DMA 転送が開始される。⑥DMA 転送数が1/2 になると、DMA_L/HSIR.HTIF がセットされ、割り込みが発生。
割り込みハンドラー内で前半転送完了後の処理を行います。⑦DMA 転送数が完了すると、SxM0AR、SxNDTR が③で設定した値に戻ります。DMA_L/HSIR.TCIF がセットされ、割り込みが発生します。割り込みハンドラー内で後半転送完了後の処理を行います。⑧シーケンス⑤に戻ります。⑨DMA サーキュラーモード終了の場合は、DMA_SxCR のEN ビットを0 にします。
13.5. DMA ダブルバッファモード
詳細は、割愛しますが、このほか、受信メモリーバッファを2つ用意して、DMA を転送するDual buffer モードがあります。2つ目のメモリアドレス指定にDMA_SxM1R を使用します。転送完了時にDMA_SxM0R とDMA_SxM1R が使用されるのを除き、DMA サーキュラーモードとほぼ同じシーケンスとなります。
13.6. FIFO モードデータのパッキングとアンパッキング
FIFO を使用すると、バス幅の異なるペリフェラルとメモリの間で、データの集約(パッキング)とデータの分離(アンパッキング)を行うことができます。詳細は、STM32 シリーズのリファレンスマニュアル(RM)のDMA の章をご覧ください。割り込み要因割り込みフラグ設定割り込み原因フラグ割り込み原因クリア転送完了DMA_SxCR.TCIE DMA_L/HSIR.TCIF DMA_L/HIFCR.CTCIF 1/2 転送完了DMA_SxCR.HTIE DMA_L/HSIR.HTIF DMA_L/HIFCR.CHTIF転送エラーDMA_SxCR.TEIE DMA_L/HSIR.TEIF DMA_L/HIFCR.CTEIFダイレクトモードエラーDMA_SxCR.DMEIE DMA_L/HSIR.DMEIF DMA_L/HIFCR.CCMEIF FIFO エラーDMA_SxFCR.FEIF DMA_L/HSIR.FEIF DMA_L/HIFCR.CFEIF
14. DMAMUX:ペリフェラルとDMA Stream への関連付け
14.1. DMAMUX 未搭載の場合
DMAMUX が搭載される以前のSTM32 シリーズ(F4、L4 シリーズなど)では、それぞれのDMA Stream チャンネルごとに、8種類までの固定されたペリフェラルからのDMA 要求(ppp_dma)のいずれかを選択できるようになっていました。図12 を参照DMA1/2_CSELR(DMAx Channel Select Register)で、各チャンネルに接続されるDMA 要求のどれを使用するか設定する方式です。
14.2. DMAMUX(DMA Request Multiplexer)
14.2.1. DMAMUX(DMA Request Multiplexer)とDMAREQ_ID
DMAMUX1_CxCR 内のDMAREQ_ID[6:0]フィールドで、どのペリフェラルからのDMA 要求を入力するのかを決めることができます。DMAREQ_ID は、STM32 の機種ごとに異なります。図14 は、STM32G0 のDMAREQ_ID と搭載されているペリフェラルの関係を示しています。DMAREQ_ID は、最大127 個までのペリフェラルを関連付けることができます。DMAREQ_ID=0 の場合は、ペリフェラルは未設定です。
STM32CubeMX と起動シーケンス(電源投入からmain()まで)
Reset Handler、スタックポインタ設定、Cランタイム初期化(.data/.bss)、SystemInit()、HALペリフェラル初期化
5. STM32 シリーズそしてSTM32CubeMX
1. STM32CubeMX を使いこなすこと
CubeMX は,STM32 シリーズのSOC プロセッサに搭載されている様々な機能を統合的に設計するため必須な道具箱です。この章では、つぎの事柄に焦点を当てていきます。① エンジニアである読者の方々が作成するプログラムと,CubeMX が生成するプロジェクトフレームワークが相互に協調してうまく動作させるにはどうすればいいのか?② CubeMX でつまずくことがないようにするにはどうすればいいのか?③ STM32 の様々な機能を,CubeMX を使用して一つ一つ吟味しながら,Step By Step でアプリケーションに反映させるための近道はなにか?などなどを,読者のみなさんと考えていきましょう。STM32 シリーズを組込みシステムで使いこなすためには,各種のペリフェラルやIP 機能セクションの土台となっているクロック供給(RCC),割り込み制御 (NVIC),DMA 転送,ARM-Cortex バスアーキテクチャを理解しておく必要があります。また,STM32CubeMX で生成されるコードの理解を深めるには,前章で説明したSTM32 シリーズのコア機能の知識が必要となります。
1.1. STM32CubeMX を繰り返し使ってプロジェクトを進化させよう!
CubeMX は,初期コードの単なる生成ツールとしてでなく,アプリケーションで必要となるペリフェラルやIP 機能,ミドルウェアを追加しながらステップバイステップで巨大なプロジェクトを構築できるように設計されています。しかしながら,多くのCubeMX を導入する開発現場で,初期コード生成だけCubeMX を使用するだけで,あらたな機能追加をする場合,新たにCubeMX で新プロジェクトを生成し,プログラムを手動で移植し直している事例を多く見受けます。
CubeMX でハードウェアリソースとミドルウェアの選定と構築を行い,ソフトウェア開発環境(STM32CubeIDE など)で独自のコードの作成を行い,CubeMX とソフトウェア開発環境との間を行き来しながら,プログラム作りを進める方法を理解する事が大切です。
1.2. STM32CubeMX の提供する機能はたくさんあります。
①ソリューションのハードウエア構成に必要とされる端子機能を設定する。②内部のクロックのルーティングや使用されるペリフェラルを決定する。③初期化のコンフィグレーションコードを生成する。④HAL・LL 関数や,ARM Cotex-M シリーズのCMSIS 関数群のコードをインポートする。⑤ミドルウェア(RTOS,FATFS,USB クラスなど)を構成しコードをインポートする。⑥CubeMX 拡張パッケージ(BLE,AI,IoT,Graphics など)を構成しコードをインポートする。
⑦以上の内容を含む統合開発ツール(STM32CubeIDE,IAR,KEIL)用プロジェクトを生成する。⑧新たにCubeMX 上で追加/削除された機能をプロジェクトのコードに反映させる。重要:一部のペリフェラル(I2C,SDRAM,FMC 内蔵FLASH など)では,ペリフェラルのレジスタに直接設定される値ではなく,CubeMX で設定される複数のパラメータから自動計算された結果のパラメータも生成されます。例えば,I2C1 の場合は,I2C の物理的なインターフェイスのタイミングを複数のパラメータから自動計算してその結果をパラメータに反映するという具合です。
1. .ioc ファイルにはCubeMX の全ての要素が詰まっている
CubeMX で作成された設定の全ては,.ioc ファイルに吐き出されます。このファイルの内容だけで,ゼロから開発環境に必要とされるプロジェクトが生成されます。プログラム作成者は,CubeMX で生成されたコードファイルに,ユーザが追記できるプログラム記述場所 ( /* USER CODE BEGIN XXX */ と /* USER CODE END XXX */の間の行)にプログラムを追加するという掟(ルール)を守っている限り,CubeMX を使用して設定変更を行なっても,原則的にユーザによる追加プログラム部分は保護されます。また,CubeMX が管理しない追加したソフトなどもプロジェクトフォールダ内の別のフォールダに配置することで,問題なく再ビルドが行えます。
従って,CubeMX を生成されるコードがどのような考え方で生成され,実行時にどのように振る舞うのか正しく理解することが,堅牢なシステムを構成するための必要条件となります。HAL API,CMSIS などの振る舞いや内部基本構造を理解すること,STM32 チップに含まれる割り込みコントローラ(NVIC)やDMA コントローラとペリフェラルの関係,ARM バスアーキテクチャとHAL API,CMSIS の関係も理解をふかめましょう。
2. CubeMX で生成されるコード
Windows,Linux などの環境では,CPU が使用するペリフェラルや様々なシステムコントローラの初期化が完了していますので,実質的に,main()関数が呼ばれる前に何が行われているのか気にする必要はほとんどありません。エンベデッドシステムでは,おなじみのprintf(“Hello World!¥n”);でさえ,長い道のり(Soc チップの様々なレベルの初期化)を経ないと表示されることはありません。
それなので,一番簡単なI/O ポートを通じて,LED を点滅するプログラム(俗にいう「L チカプログラム」)が,エンベデッドシステムでのprintf(“Hello World!¥n”);に該当する入門プログラムになっています。安定したプロレベルのシステムアプリケーションを皆さんが作成したいと思うなら,main()が呼ばれる以前に何が行われているのか,CubeMX が生成するいくつかの初期化関数コールのなかで,いったい何が起こっているのか知っておく必要があります。
「機能を追加すると,忽然とプログラムが動かなくなる」,「コード修正やCubeMX での設定変更を行った後,何かのひょうしでHard fault が発生してお手上げ状態となってしまった」などの原因の解明には,これらの初期化処理の中で何が行われているのかしっかり理解しておくことが,エンベデッドシステムのエキスパートになる一歩です。
3. CubeMX Main()が呼ばれる前に何が起こっているのか?
この図は、電源が投入されて、MPU が動き出し、皆さんがなじみのあるmain()が呼び出されるまでの道のりをフォローチャートで記述したものです。備考:ファイル名のxxxx の部分は,STM32 ごとの固有のプロセッサ名称で置き換えてください。system_stmxxxx.c staertup_stmxxxx.s PWR & NVIC Hardware Logics Power On SysReset= true NRST = LOW BOR,POR Ok?PWR(VDD Supervisor)Check:VDD > VBOR VDD > VPOR VBOR : Brown Out Reset Level VPOR : Power On Reset Level SysReset= false NRST = HIGH NVIC Read Interrupt Vector Table@ 0x0000-0000h (@stertup_stmxxxx.s)MPU: SP(R[13] = _ estack@ IRQ[0]MPU: PC(R[15] = Reset_Handler @ IRQ[1]MAIN Regulator Activate V CORE RUN MPU: @ PC = @ Reset_Handler Call SystemInit () @system_stm32xx.c Initialize Static Value (Copy)FLASH @ _ sidata→ SRAM @_sdata ~ @_edata Initialize bss Value (fill Zero)0x00000000→SRAM @_sbss ~ @_ebss Call Static Constructors Call __ libc_init_array() @ libc.a Call main() @ main.c
Reset_Handler()@ stertup_stmxxxx.s Infinite_Loop
SystemInit()3.4. main()内の初期化処理
main 関数の先頭でHAL_Init()が呼ばれます。
3.4.1. HAL 層の初期化 HAL_Init()
①MCU にData/Instruction キャッシュ機能があればこれらのキャッシュ機能を無効にします。(Cortex M7 など)②NVIC の割り込みグループの初期設定を行います。③SysTick タイマが初期化されます。SysTick は,HAL_Delay(),HAL_GerTick()で使用されるシステムタイマーです。注意:SysTick タイマは,Cortex M の内部レジスタです。
④SysTick タイマ割り込みを有効にすることで1m 秒ごとに割り込みが発生し,タイマー値が更新されるようになります。参考:RTOS などのリアルタイムOS を使用すると,SysTick タイマはタスク切り替えのタイマとして使用されるので,チップ内のペリフェラルである汎用タイマが使用されます。注意:HAL 層でタイムアウト機能のある関数はSysTick タイマが動作していることを前提としているので,HAL_Init()が実行される前に呼び出すと動作しない可能性があることに注意してください。
3.4.2. システムクロックの初期化 SystemClock_Config()
SystemClock_Config()関数が呼び出されると,RCC(Clock Controller)部内のレジスタを設定します。CubeMX のClock Config 画面で設定されたSYSCLK に初期化され,MCU や各ペリフェラルのクロックの分周比が設定されます。PLL がSYSCLK の入力である場合は,PLL 関連のレジスタが設定されます。注意:ペリフェラルで使用されていないPLL やクロックルーティングは,リセット時の初期値のままで,SystemClock_Config()では使用されているクロック以外の設定は行いません。
3.4.3. ペリフェラルクロックの初期化 PeriphCommonClock_Config()
特定のペリフェラルで,追加されたPLL を使用した場合は,追加されたPLL の初期化特定のペリフェラルへのクロックの供給と,クロックの分周比が設定されます。典型的な例として,SAI やI2S ペリフェラルのオーディオクロック生成などで使用されます。参考:バージョンの古いCubeMX では,システムクロックの初期化とペリフェラルクロックの初期化をSystemClockConfig()関数のみでまとめて初期化するコードを生成する事があります。
3.4.4. 個別のペリフェラル初期化 MX_XXXX_Init()
CubeMX で特定のペリフェラルやIP を有効にすると,使用される個別のペリフェラルごとに,初期化関数であるMX_XXX_Init()関数が生成されます。例えば,I2C1 を有効にすると,MX_I2C1_Init()が生成されます。MX_XXX_Init()の詳細は,次の節で説明します。
3.4.5. エンベデッドOS の初期化とユーザアプリケーションの初期化
以上のシステム初期化プロセスが終了すると,システムを通常動作させるうえで必要とされるすべてのシステムコントローラとペリフェラルのセットアップが完了してます。
3.4.5.1. エンベデッドOS を使用しない場合(ベアメタルアプリ)
アプリケーションレベルの初期化を行ったり,必要とされるペリフェラルのイベントを有効にする処理を行います。
3.4.5.2. エンベデッドOS を使用する場合
エンベデッドOS を初期化し,タスク,セマフォ,メールボックス,イベントフラグなどをエンベデッドOS に登録します。必要とされるペリフェラルのイベントを有効にする処理は,登録されたタスク内で行います。最後に,エンベデッドOS を起動し,登録されたタスクを起動します。エンベデッドシステムの場合,基本として,ペリフェラルの初期化で割り込み処理や,DMA を使用したデータ転送の起動条件を設定してあります。ペリフェラルに関連するイベントが発生すると,割り込み処理やDMA 転送が自発的に開始されるので,以降,main プログラムで自発的に何かを処理することはありません。
3.4.6. 無限アイドルループでバックグラウンド処理を行う
3.4.6.1. エンベデッドOS を使用しない場合(ベアメタルアプリ)
割り込みハンドラー内で,割り込みやDMA 転送の終了割り込み時点で,重い処理を行う必要がある場合は,CPU の時間の占有を避けるため,グローバルなフラグを介して,無限ループ内で事後処理を待ち受けて処理する手法が一般的です。例題:オーディオデータ処理で,DMA が受け取ったMEMS マイクのPDM データをPCM データに変換する場合,CPU によるPDM/PCM 変換が重いので,DMA 完了時にグローバルなイベント完了変数を用意して,main()内の無限ループでフラグをポーリングして事後処理のPDM/PCM 変換を行うという方法です。SAI(Serial Audio Interface)のサンプルプログラムを参考にしてください。
ヒント:__WFI()を使うアイドルループでひたすらフラグをポーリングするのは,CPU パワーの無駄使いです。__WFI()マクロ(Wait for interrupt)は,次の割り込みが発生するまでCPU を停止させる機能です。
__WFI()を発行して,CPU を停止させます。
割り込みが発生するとCPU が再び動き出し,割り込みイベント処理を処理してISR(interrupt service routine)を終了します。
CPU は動作しているので,アイドルループに戻ってきます。
そこで,イベントフラグを検査して,割り込みからの復帰後の処理が必要であれば,事後処理プログラムを呼び出します。
①に戻ります。(無限ループ)
3.4.6.2. エンベデッドOS を使用する場合
① エンベデッドOS をスタートさせると,それぞれのタスクを起動したのち,タスクイベントがない場合は,アイドルタスクの無限ループが実行されます。② ペリフェラルに関連するイベントに関連するタスクは割り込みハンドラーからセマフォなどを利用してタスクイベントを受け取り,アプリケーションレベルの処理を行います。
3.5. ペリフェラル初期化 MX_XXXX_Init()のなかみ
ペリフェラル単位の初期化の流れを理解する事が,大切です。MX_I2C1_Init()を例に説明しましょう。ペリフェラルやIP をHAL API で使用するためには,ペリフェラルやIP ごとに現在の設定内容を保存するHAL ハンドルを用意し,間接的に個別のペリフェラルを制御します。例えば,CubeMXでI2C1 を有効にすると,hi2c1 というハンドルがグローバル変数として定義されます。
3.5.1. HAL ペリフェラル初期化関数を実行する
HAL_XXXX_Init(&handle)関数を呼ぶことで,ペリフェラルの初期化が行われます。I2C1 の場合は,HAL_I2C_Init(&hi2c1)という具合です。
HAL_I2C_Init()関数が個別ペリフェラルの実態であるI2C1 をHAL ハンドルを仲介して,初期化します。
3.5.2. HAL_XXXX_Init がHAL_XXXX_MspInit(&handle)を呼び出す(重要)
CubeMX で生成される,stm32xxxx_Hal_Msp.c には,ロジック部分を活性化する
HAL_XXXX_MspInit()が生成されます。
HAL_XXXX_MspInit(&handle)関数は,HAL_XXXX_Init (&handle)から呼び出されるため,handleを仲介して,以下の初期化を行います。
ペリフェラル固有の入出力GPIO,機能ロジック部分のクロック供給CubeMX で設定したペリフェラル固有の入出力ポートを活性化する。具体的には,__HAL_RCC_GPIOX_CLK_ENABLE();関数を使用して,GPIO ポートにクロックを供給します。
ペリフェラル機能ロジック部分へのクロックを有効にする具体的には,HAL_RCC_PeriphCLKConfig()またはHAL_RCCEx_PeriphCLKConfig()関数を使用してRCC からのクロックを有効にします。
ペリフェラル固有の入出力GPIO 機能の設定ペリフェラル固有の入出力っとGPIO ピンを関連付けるため,AF(Alternate Function)レジスタを設定します。ペリフェラル固有の入出力ポートの方向,ドライブ方法,プルアップダウンなどを
HAL_GPIO_Init()関数を使用して初期化します。ペリフェラルとDMA を関連付けるパラメータを元に,HAL 初期化関数が初期化に必要とされるパラメータをハンドルに含まれるペリフェラル固有のInit 構造体のパラメータ変数にセットされています。I2C1 の場合は,hi2c1.Init.Timing = 0x1234567; …という具合です。
3.6. ペリフェラル用HAL API のHandle とInstance(実ハードウェア)
UART などのペリフェラルをHAL API を使用して操作する場合は、ペリフェラル固有のHALハンドル(構造体)を使用して、間接的にSTM32 シリーズSoC に内蔵される固有のペリフェラル(HAL API ではInstance と呼んでいます)にアクセスします。Handle 構造体は,ペリフェラルの種類ごとに内容は異なりますが,以下の構造は同じです。① Ppp_HandleDef がHandle 構造体名称です。
Ppp の部分は,UART,I2C,TIM などの共通機能を持ったペリフェラル名称に置き換えててください。② Handle 構造体には,必ずInstance ポインタ変数があります。Instance ポインタ変数は,操作対象となる実ペリフェラルのレジスタ群の先頭アドレスとレジスタを定義します。Instance に設定される名称は,UART1,UART2,I2C1,I2C2,TIM1,TIM2 などの個別ペリフェラル名です。
Instance 実際は,PPP_typeDef で定義された実体(Instance)の構造体へのポインタです。③ Handle 構造体には,必ずInit 構造体があります。Init 構造体には,初期化に必要なペリフェラル固有のパラメータが格納されます。④ HAL API の第一引数は,Handle 構造体へのポインタです。Handle 構造体には,ペリフェラルを操作するための状態変数,送受信を行うためのデータポインタ,データ長などのブックキーピングのための変数が格納されています。
3.6.1. ペリフェラル用HAL の共通関数
①
HAL_PPP_Init(PPP_HandleTypeDef * handle)Handle 構造体内のInit パラメータを用いて,Instance で指定される操作対象となる実ペリフェラルを初期化します。②
HAL_PPP_DeInit(PPP_HandleTypeDef * handle)Handle 構造体内のInstance で指定される操作対象となる実ペリフェラルを非活性化します。注意:Handle 構造体変数は,HAL_ppp_Init()関数で初期化された以後か,
HAL_ppp_DeInit()関数でハンドルが解放されるまで,固定したメモリ上に存在している必要があります。3.6.2. ストリームデータを扱わないペリフェラル用HAL の共通関数
①
HAL_PPP_Start(PPP_HandleTypeDef * handle)ペリフェラルのデータ変換や計算を開始します。②
HAL_PPP_Stop(PPP_HandleTypeDef * handle)データ変換やデータ計算停止します。ペリフェラルを無効にします。③
HAL_PPP_Start_IT(PP_HandleTypeDef * handle)ペリフェラルの割り込みによるデータ変換やデータ計算を開始します。
HAL_PPP_Stop_IT(PPP_HandleTypeDef * handle)データ変換やデータ計算を停止します。割り込み通知とペリフェラルを無効にします。④
HAL_PPP_Start_DMA(PPP_HandleTypeDef * handle,uint32_t * pData, uint32_t length)ペリフェラルのデータ変換やデータ計算を開始し,DMA を介して結果を転送します。⑤
HAL_ADC_Stop_DMA(ADC_HandleTypeDef * hadc)データ変換やデータ計算を停止し,ADC DMA 転送とペリフェラルを無効にします。
3.6.3. ストリームデータを伴うペリフェラル用HAL の共通関数
①
HAL_PPP_Transmit(PPP_HandleTypeDef * handle,uint8_t * pData,uint16_t Size,uint32_t Timeout)ブロッキングモードで大量のデータを送信します。②
HAL_PPP_Receive(PPP_HandleTypeDef * handle,uint8_t * pData,uint16_t Size,uint32_t Timeout)ブロッキングモードで大量のデータを受信します。③
HAL_PPP_Transmit_IT(PPP_HandleTypeDef * handle,uint8_t * pData,uint16_t size)割り込みモードで大量のデータを送信します。④
HAL_PPP_Receive_IT(PPP_HandleTypeDef * handle,uint8_t * pData,uint16_t size)割り込みモードで大量のデータを受信します。⑤
HAL_PPP_Transmit_DMA(PPP_HandleTypeDef * handle,uint8_t * pData,uint16_t size)DMA モードで大量のデータを送信します。⑥
HAL_PPP_Receive_DMA(PPP_HandleTypeDef * handle,uint8_t * pData,uint16_t size)DMA モードで大量のデータを受信します。⑦
HAL_PPP_DMAPause(PPP_HandleTypeDef * handle)DMA 転送を一時停止します。⑧
HAL_PPP_DMAResume(PPP_HandleTypeDef * handle)DMA 転送を再開します。⑨
HAL_PPP_DMAStop(PPP_HandleTypeDef * handle)DMA 転送を停止します。⑩
HAL_PPP_Abort(PPP_HandleTypeDef * handle)進行中の転送を中止します(ブロッキングモード)。⑪
HAL_PPP_AbortTransmit(PPP_HandleTypeDef * handle)進行中の送信転送を中止します(ブロッキングモード)。⑫
HAL_PPP_AbortReceive(PPP_HandleTypeDef * handle)進行中の受信転送を中止します(ブロッキングモード)。⑬
HAL_PPP_Abort_IT(PPP_HandleTypeDef * handle)進行中の転送を中止します(割り込みモード)。⑭
HAL_PPP_AbortTransmit_IT(PPP_HandleTypeDef * handle)進行中の送信転送を中止します(割り込みモード)。⑮
HAL_PPP_AbortReceive_IT(PPP_HandleTypeDef * handle)3.6.4. CMSIS:Cortex Microcontroller Software Interface Standard
STM32 シリーズのコアプロセッサであるCortex プロセッサの機能をコントロールするための抽象化された関数群です。NVIC(Nested Vectored Interrupt Controller)による割り込み機能や,MPU(Memory Protection Unit) の設定,リアルタイムOS 用プリミティブ,STM32 シリーズでDSP 機能や浮動小数点処理が内蔵されているARM-Cortex M4,M7 等でのDSP ライブラリが提供されます。