はじめに
この短いメモには、以下の問題に対する明らかな解決策が含まれていないため、眠れぬ夜を何度か行かなければなりませんでした。
タスク:マイクロコントローラーの周辺ユニットの動作に関するユーザー指定のデータに基づいて、リアルタイムで使用できるレジスターの構成マスクの配列をコンパイル段階で取得します。 同時に、コンパイル段階ですべてのパラメーターが正しく設定されていることを確認する必要があります(マイクロコントローラーの周辺機器が正しく構成されます)。
これをどうやって行うかに興味がある人は、猫の下でお願いします。
目次:
- C ++ 14.の主な問題と制限。
- ユーザーが決して間違えられないという事実に基づくソリューション。
- ユーザーエラーの説明。
- クラスを拡張します。
- 略称エントリについて。
- 結果。
C ++ 14の主な問題と制限
はじめに説明したタスクに出会ったとたんに、C ++言語指定子constexprをすぐに思い出しました。 この問題を解決する必要性は、ライブラリの設計段階でも特定されたため、C ++ 11標準のすべての制限が考慮されました。これは、もともとライブラリの構築が計画されていました(constexprメソッド内でループを使用できないなど)、およびライブラリ言語としてC ++ 14標準が選択され、C ++ 11の制限の多くが削除されました(C ++ 17は、ライブラリに設定されたタスクの観点から、C ++ 14とあまり変わらなかったため、意図的に選択されませんでした)それがメインとしても選ばれたGCCからの彼のサポートです 執筆時点では、ライブラリコンパイラは安定していません)。
しかし、C ++ 14のconstexpr関数はリアルタイム関数とほぼ同じように柔軟になったという事実にも関わらず、大部分の利点-デバッグの欠如-を取り消す太いマイナス記号が1つ残っています。 それについては、 こちらの Habréに書かれています 。 この記事から、私は主なアイデアを得ました:
しかし、問題はそれだけでは終わりません。 後でよく使用するconstexpr関数を作成する場合、読み取り可能なエラーを返すと便利です。 ここでは、static_assertがこれにちょうど適切であると誤って想定される可能性があります。 ただし、関数パラメーターはconstexprにできないため、static_assertは使用できません。これは、コンパイル時にパラメーター値が認識されることが保証されない理由です。constexprメソッドでは例外がサポートされていないため、constexprでthrowを使用することは不可能であるというエラーが表示されます。 ループ内で何かを処理した場合、どの要素が例外になったかを確認する際に、どの要素に落ちたかを見つけることはできません。
エラーを表示する方法は? 私が見つけた多かれ少なかれ通常の方法は、例外をスローすることです:
状況は次のとおりです。static_assertは不可能、throwは不可、printfと割り込みコンパイルは不可です。
ユーザーが決して間違えられないという事実に基づくソリューション
これらのすべての制限とデバッグできないことはどうですか? そもそも、 ユーザーは決してミスをしないと仮定しましょう(はい、未来的ですが、今のところこれを公理と考えます)。 それからconstexprは、原則として、入力パラメーターが正しくないという例外をデバッグおよびスローする必要がないことがわかりました。
例として、誰かによって最初にオンにされた(クロック信号が周辺装置に送信された)マイクロコントローラーポート端末出力制御オブジェクトのクラスと、出力が目的の速度で出力で構成された(LEDを点滅させる)ことを考えます。 実際、クラスのオブジェクトには、出力状態を1から0に、またはその逆に切り替えるメソッドが必要です。 私たちの施設からそれ以上は必要ありません。
ただし、エッセンスを生成しないために、1つの出力全体の構成を記述する構造があると仮定します。 この構造は、オブジェクトのクラスだけでなく、他のクラス(たとえば、事前にモジュールを初期化し、出力を目的の状態に構成したクラス)でも使用されます。 この構造は次のようになります。
/* * . */ struct __attribute__( ( packed ) ) pin_config_t { EC_PORT_NAME port; // // ( : EC_PORT_NAME::A ). EC_PORT_PIN_NAME pin_name; // // ( : EC_PORT_PIN_NAME::PIN_0 ). EC_PIN_MODE mode; // // ( : EC_PIN_MODE::OUTPUT ). EC_PIN_OUTPUT_CFG output_config; // // ( : EC_PIN_OUTPUT_CFG::NOT_USE ). EC_PIN_SPEED speed; // // ( : EC_PIN_SPEED::MEDIUM ). EC_PIN_PULL pull; // // ( : EC_PIN_PULL::NO ). EC_PIN_AF af; // // ( : EC_PIN_AF::NOT_USE ). EC_LOCKED locked; // // // global_port // ( EC_LOCKED::NOT_LOCKED ). EC_PIN_STATE_AFTER_INIT state_after_init; // // ( , ). // ( EC_PIN_STATE_AFTER_INIT::NO_USE). };
この構造は、次の列挙クラスを使用します。
/********************************************************************** * enum class-. **********************************************************************/ /* * . */ enum class EC_PORT_PIN_NAME { PIN_0 = 0, PIN_1 = 1, PIN_2 = 2, PIN_3 = 3, PIN_4 = 4, PIN_5 = 5, PIN_6 = 6, PIN_7 = 7, PIN_8 = 8, PIN_9 = 9, PIN_10 = 10, PIN_11 = 11, PIN_12 = 12, PIN_13 = 13, PIN_14 = 14, PIN_15 = 15 }; /* * . */ enum class EC_PIN_MODE { INPUT = 0, // . OUTPUT = 1, // . AF = 2, // . ANALOG = 3 // . }; /* * . */ enum class EC_PIN_OUTPUT_CFG { NO_USE = 0, // . PUSH_PULL = 0, // "-". OPEN_DRAIN = 1 // " ". }; /* * . */ enum class EC_PIN_SPEED { LOW = 0, // . MEDIUM = 1, // . FAST = 2, // . HIGH = 3 // }; /* * */ enum class EC_PIN_PULL { NO_USE = 0, // . UP = 1, // . DOWN = 2 // . }; /* * , . */ enum class EC_PIN_AF { AF_0 = 0, NO_USE = AF_0, SYS = AF_0, AF_1 = 1, TIM1 = AF_1, TIM2 = AF_1, AF_2 = 2, TIM3 = AF_2, TIM4 = AF_2, TIM5 = AF_2, AF_3 = 3, TIM8 = AF_3, TIM9 = AF_3, TIM10 = AF_3, TIM11 = AF_3, AF_4 = 4, I2C1 = AF_4, I2C2 = AF_4, I2C3 = AF_4, AF_5 = 5, SPI1 = AF_5, SPI2 = AF_5, I2S2 = AF_5, AF_6 = 6, SPI3 = AF_6, I2S3 = AF_6, AF_7 = 7, USART1 = AF_7, USART2 = AF_7, USART3 = AF_7, AF_8 = 8, UART4 = AF_8, UART5 = AF_8, USART6 = AF_8, AF_9 = 9, CAN1 = AF_9, CAN2 = AF_9, TIM12 = AF_9, TIM13 = AF_9, TIM14 = AF_9, AF_10 = 10, OTG_FS = AF_10, AF_11 = 11, ETH = AF_11, AF_12 = 12, FSMC = AF_12, SDIO = AF_12, AF_13 = 13, DCMI = AF_13, AF_14 = 14, AF_15 = 15, EVENTOUT = AF_15 }; /* * set_locked_key_port * set_locked_keys_all_port global_port. * ! global_port. * - . * - . */ enum class EC_LOCKED { NOT_LOCKED = 0, // . LOCKED = 1 // . }; /* * * ( , ). */ enum class EC_PIN_STATE_AFTER_INIT { NO_USE = 0, RESET = 0, SET = 1 };
前述のように、クラスのオブジェクトは、出力の出力(レッグ)の状態のみを変更する必要があります。 ライブラリはstm32f2(および唯一)で記述されているため、この目的のために各ポートのGPIO物理ブロックでBSRレジスタを使用するのが合理的です。これにより、ビット0-15にユニット(1)を書き込み、対応するビットを設定できます(ユニット(1)を書き込む) 0番目のビットはポート0の出力状態を1に設定し、ユニット(1)を16-31に書き込むことにより、対応するビットを16にリセットします(ユニット(1)を31番目のビットに書き込むと、31-16 = 15番目のポートピンがリセットされます) 0)。
ご覧のとおり、目的の出力を1に設定するタスクは、BSRレジスタ1 << output_numberに書き込み 、レコード1 << output_number + 16にリセットすることになります。
これらの目的のために、ユーザー構造構造からportおよびpin_nameフィールドを取得するだけで十分です。 他のすべてのフィールドは必要ありません。
オブジェクトのクラスの一般的なビューを示します。
class pin { public: constexpr pin ( const pin_config_t* const pin_cfg_array ); void set ( void ) const; void reset ( void ) const; void set ( uint8_t state ) const; void set ( bool state ) const; void set ( int state ) const; private: constexpr uint32_t p_bsr_get ( const pin_config_t* const pin_cfg_array ); constexpr uint32_t set_msk_get ( const pin_config_t* const pin_cfg_array ); constexpr uint32_t reset_msk_get ( const pin_config_t* const pin_cfg_array ); const uint32_t p_bsr; const uint32_t bsr_set_msk, bsr_reset_msk; };
ご覧のとおり、このクラスには次のメソッドがあります。
- パラメータなしで設定-出力の出力の状態を1に設定します。
- reset-出力の出力の状態を0にリセットします。
- さまざまなタイプのパラメーターで設定します-実際には、上記の関数を使用して出力で特定の状態を設定するのは1つの関数です(後で説明します)。
これらの方法はすべて、ユーザーがリアルタイムで使用します。 それらを考慮してください。
/* * <<1>>, * . */ void pin::set ( void ) const { *M_U32_TO_P(this->p_bsr) = this->bsr_set_msk; } /* * <<0>>, * . */ void pin::reset ( void ) const { *M_U32_TO_P(this->p_bsr) = this->bsr_reset_msk; } /* * , * . */ void pin::set ( uint8_t state ) const { if ( state ) { this->set(); } else { this->reset(); } } void pin::set ( bool state ) const { this->set( static_cast< uint8_t >( state ) ); } void pin::set ( int state ) const { this->set( static_cast< uint8_t >( state ) ); }
setおよびresetメソッドは、以下の定義を使用して、uint32_t変数の値をuint32_t変数へのポインターに明示的に変換します。
// uint32_t uint32_t. // . #define M_U32_TO_P(point) ((uint32_t *)(point))
現時点では、オブジェクトのメソッドが既製のマスクでどのように機能するかを理解しました。最も重要なことは、それらを準備するために残っています(この記事の目的)。
クラスには3つのメソッドがあります。
- set_msk_get-uint32_t変数の値を返します。これは、ユーザー定義の出力を<< 1 >>に設定するためのBSRレジスタのマスクです。
- reset_msk_get-変数のuint32_t値を返します。これは、ユーザー定義の出力を<< 0 >>にリセットするためのBSRレジスタのマスクです。
- p_bsr_get-マイクロコントローラの物理メモリカード上のBSRレジスタのアドレスを含むuint32_t変数の値を返します。
構造パラメーターを指定する際にユーザーが間違いなく間違いなかったことを知って、次のコードを書くことができます。
/********************************************************************** * constexpr . **********************************************************************/ /* * "1" BSR. */ constexpr uint32_t pin::set_msk_get ( const pin_config_t* const pin_cfg_array ) { return 1 << M_EC_TO_U8(pin_cfg_array->pin_name); } /* * "0" BSR. */ constexpr uint32_t pin::reset_msk_get ( const pin_config_t* const pin_cfg_array ) { return 1 << M_EC_TO_U8( pin_cfg_array->pin_name ) + 16; } /* * BSR, . */ constexpr uint32_t pin::p_bsr_get( const pin_config_t* const pin_cfg_array ) { uint32_t p_port = p_base_port_address_get( pin_cfg_array->port ); return p_port + 0x18; }
これらの関数は、定義を使用して、enumクラスの値をuint8_t変数に変換します。
// enum class uint8_t. #define M_EC_TO_U8(ENUM_VALUE) ((uint8_t)ENUM_VALUE)
p_bsr_getメソッドは、特定のクラスに属さないp_base_port_address_getメソッドも使用します。 このメソッドは、EC_PORT_NAMEクラスの列挙値(ポート名)を取得し、マイクロコントローラーの物理カード上のこのポートのレジスターの位置の物理アドレスを返します。 次のようになります。
一般的なメソッドp_base_port_address_get
リセット/セットマスクの定数とレジスタアドレスを入力するクラスコンストラクターは次のとおりです。
/* * - * . */ constexpr uint32_t p_base_port_address_get( EC_PORT_NAME port_name ) { switch( port_name ) { #ifdef PORTA case EC_PORT_NAME::A: return 0x40020000; #endif #ifdef PORTB case EC_PORT_NAME::B: return 0x40020400; #endif #ifdef PORTC case EC_PORT_NAME::C: return 0x40020800; #endif #ifdef PORTD case EC_PORT_NAME::D: return 0x40020C00; #endif #ifdef PORTE case EC_PORT_NAME::E: return 0x40021000; #endif #ifdef PORTF case EC_PORT_NAME::F: return 0x40021400; #endif #ifdef PORTG case EC_PORT_NAME::G: return 0x40021800; #endif #ifdef PORTH case EC_PORT_NAME::H: return 0x40021C00; #endif #ifdef PORTI case EC_PORT_NAME::I: return 0x40022000; #endif } }
/********************************************************************** * constexpr . **********************************************************************/ constexpr pin::pin ( const pin_config_t* const pin_cfg_array ): p_bsr ( this->p_bsr_get( pin_cfg_array ) ), bsr_set_msk ( this->set_msk_get( pin_cfg_array ) ), bsr_reset_msk ( this->reset_msk_get( pin_cfg_array ) ) {};
技術的には、このクラスを使用できるようになりましたが、ユーザーが常にすべての構造パラメーターを正しく入力できるわけではないことを覚えています...
ユーザーエラーの説明。
作業中のデバッグされたクラスができたので、入力構造の検証を完了するだけで、クラスのオブジェクトを安全に使用できます。 しかし、前述のように、constexprでこれを行うのは快適です-不可能です。 しかし、解決策があります-テンプレート。 ユーザーコード内のすべてのオブジェクトをグローバルに設定する必要があるため(これはライブラリを使用するための主な条件であり、(ライブラリ)の説明のドキュメント、記事の最後に記載するリンクで読むことができます)、テンプレートの使用が最も合理的であるようです。 実際、テンプレートではstatic_assertが許可されています。 また、あらゆる種類のコードを使用して、より複雑なチェックを実行できます。 そして、最も重要なこと:
構造体から継承されたテンプレートクラスを作成し、このクラスのコンストラクターで必要なすべてのチェックを実行し、ユーザーコードでこのテンプレートクラスのオブジェクトをグローバルに宣言すると、コンパイラーは、クラスが継承されたまさにその構造体に暗黙的な型変換を適用できるようになります。
この機会は使用できません!
/********************************************************************** * template . **********************************************************************/ template < EC_PORT_NAME PORT, EC_PORT_PIN_NAME PIN_NAME, EC_PIN_MODE MODE, EC_PIN_OUTPUT_CFG OUTPUT_CONFIG, EC_PIN_SPEED SPEED, EC_PIN_PULL PULL, EC_PIN_AF AF, EC_LOCKED LOCKED, EC_PIN_STATE_AFTER_INIT STATE_AFTER_INIT > class pin_config_check_param : public pin_config_t { public: constexpr pin_config_check_param(): pin_config_t( { .port = PORT, .pin_name = PIN_NAME, .mode = MODE, .output_config = OUTPUT_CONFIG, .speed = SPEED, .pull = PULL, .af = AF, .locked = LOCKED, .state_after_init = STATE_AFTER_INIT } ) { /* * . */ #if defined(STM32F205RB)|defined(STM32F205RC)|defined(STM32F205RE) \ |defined(STM32F205RF)|defined(STM32F205RG) static_assert( PORT >= EC_PORT_NAME::A && PORT <= EC_PORT_NAME::H, "Invalid port name. The port name must be A..H." ); #endif static_assert( PIN_NAME >= EC_PORT_PIN_NAME::PIN_0 && PIN_NAME <= EC_PORT_PIN_NAME::PIN_15, "Invalid output name. An output with this name does not" "exist in any port. The output can have a name PIN_0..PIN_15." ); static_assert( MODE >= EC_PIN_MODE::INPUT && MODE <= EC_PIN_MODE::ANALOG, "The selected mode does not exist. " "The output can be set to mode: INPUT, OUTPUT, AF or ANALOG." ); static_assert( OUTPUT_CONFIG == EC_PIN_OUTPUT_CFG::PUSH_PULL || OUTPUT_CONFIG == EC_PIN_OUTPUT_CFG::OPEN_DRAIN, "A non-existent output mode is selected. " "The output can be in the mode: PUSH_PULL, OPEN_DRAIN." ); static_assert( SPEED >= EC_PIN_SPEED::LOW && SPEED <= EC_PIN_SPEED::HIGH, "A non-existent mode of port speed is selected. " "Possible modes: LOW, MEDIUM, FAST or HIGH." ); static_assert( PULL >= EC_PIN_PULL::NO_USE && PULL <= EC_PIN_PULL::DOWN, "A non-existent brace mode is selected." "The options are: NO_USE, UP or DOWN." ); static_assert( AF >= EC_PIN_AF::AF_0 && AF <= EC_PIN_AF::AF_15, "A non-existent mode of the alternative port function is selected." ); static_assert( LOCKED == EC_LOCKED::NOT_LOCKED || LOCKED == EC_LOCKED::LOCKED, "Invalid port lock mode selected." ); static_assert( STATE_AFTER_INIT == EC_PIN_STATE_AFTER_INIT::NO_USE || STATE_AFTER_INIT == EC_PIN_STATE_AFTER_INIT::SET, "The wrong state of the output is selected." "The status can be: NO_USE, UP or DOWN." ); }; };
これで、ユーザーコードでこのクラスのオブジェクトを宣言できます。
const pin_config_check_param< EC_PORT_NAME::C, EC_PORT_PIN_NAME::PIN_4, EC_PIN_MODE::OUTPUT, EC_PIN_OUTPUT_CFG::PUSH_PULL, EC_PIN_SPEED::MEDIUM, EC_PIN_PULL::NO_USE, EC_PIN_AF::NO_USE, EC_LOCKED::LOCKED, EC_PIN_STATE_AFTER_INIT::SET > lcd_res;
その後、クラスpinのオブジェクトを作成するときは、通常の構造として、それを参照しないでください。
const constexpr pin pin_lcd_res( &lcd_res );
次に、ユーザーコードで、このオブジェクトのメソッドを使用できます。
void port_test ( void ) { pin_lcd_res.reset(); pin_lcd_res.set(); }
クラス機能を展開
終了モードで足を制御するのに非常に適したクラスを取得しました。 ただし、エントリ用に別のクラスを作成しないでください? 出力と入力用に構成された出力の両方で使用できるように、クラスを少し変更します。 また、出力で状態を反転する方法を追加します。
2つの定数でクラスを補完します。
- p_bb_odr_read-ここには、ODRレジスタのオブジェクト出力ビットへのポインターがあります(出力が出力に使用される場合、出力出力でユーザーが設定した位置)。 ビットバンディング領域を使用。
- p_bb_idr_read-IDRレジスタにオブジェクト出力ビットへのポインタがあります(このレジスタには、出力の構成方法に関係なく、出力入力での実際の状態が含まれます)。 ビットバンディング領域を使用。
特定の出力に対してこれらの定数の値を返すメソッドを作成します。
/* * bit_banding * , . */ constexpr uint32_t pin::bb_p_idr_read_get ( const pin_config_t* const pin_cfg_array ) { uint32_t p_port = p_base_port_address_get( pin_cfg_array->port ); uint32_t p_idr = p_port + 0x10; return M_GET_BB_P_PER(p_idr, M_EC_TO_U8(pin_cfg_array->pin_name)); } /* * bit banding , * . */ constexpr uint32_t pin::odr_bit_read_bb_p_get ( const pin_config_t* const pin_cfg_array ) { uint32_t p_port = p_base_port_address_get( pin_cfg_array->port ); uint32_t p_reg_odr = p_port + 0x14; return M_GET_BB_P_PER(p_reg_odr, M_EC_TO_U8(pin_cfg_array->pin_name)); }
ここでは、M_GET_BB_P_PERを定義します。これは、uint32_tにより、周辺領域のレジスタアドレスの値とポートビット番号のuint32_tによって、このビットのビットバンドアドレスを返します。
M_GET_BB_P_PERを定義する
//********************************************************************* // , . //********************************************************************* #define BIT_BAND_SRAM_REF 0x20000000 #define BIT_BAND_SRAM_BASE 0x22000000 // RAM Bit Banding . #define MACRO_GET_BB_P_SRAM(reg, bit) \ ((BIT_BAND_SRAM_BASE + (reg - BIT_BAND_SRAM_REF)*32 + (bit * 4))) #define BIT_BAND_PER_REF ((uint32_t)0x40000000) #define BIT_BAND_PER_BASE ((uint32_t)0x42000000) // Bit Banding . #define M_GET_BB_P_PER(ADDRESS,BIT) \ ((BIT_BAND_PER_BASE + (ADDRESS - BIT_BAND_PER_REF)*32 + (BIT * 4)))
これらのコマンドの初期化をコンストラクターに追加しましょう。
/********************************************************************** * constexpr . **********************************************************************/ constexpr pin::pin ( const pin_config_t* const pin_cfg_array ): p_bsr ( this->p_bsr_get( pin_cfg_array ) ), p_bb_odr_read ( this->odr_bit_read_bb_p_get( pin_cfg_array ) ), bsr_set_msk ( this->set_msk_get( pin_cfg_array ) ), bsr_reset_msk ( this->reset_msk_get( pin_cfg_array ) ), p_bb_idr_read ( this->bb_p_idr_read_get( pin_cfg_array ) ) {};
さて、これらの定数で動作するリアルタイム関数を追加しましょう。
/* * , * . */ void pin::invert( void ) const { if (*M_U32_TO_P_CONST(p_bb_odr_read)) { // 1, 0. this->reset(); } else { this->set(); } } /* * . */ int pin::read() const { return *M_U32_TO_P_CONST(p_bb_idr_read); }
ここでは、別の1定義が使用され(M_U32_TO_P_CONST)、uint32_t変数に格納されている値をuint32_t書き込み保護変数へのポインターに変換します。
// uint32_t uint32_t. // , ( ). #define M_U32_TO_P_CONST(point) ((const uint32_t *const)(point))
最終的に、クラスは次の形式を取りました。
class pin { public: constexpr pin ( const pin_config_t* const pin_cfg_array ); void set ( void ) const; void reset ( void ) const; void set ( uint8_t state ) const; void set ( bool state ) const; void set ( int state ) const; void invert ( void ) const; int read ( void ) const; private: constexpr uint32_t p_bsr_get ( const pin_config_t* const pin_cfg_array ); constexpr uint32_t set_msk_get ( const pin_config_t* const pin_cfg_array ); constexpr uint32_t reset_msk_get ( const pin_config_t* const pin_cfg_array ); constexpr uint32_t odr_bit_read_bb_p_get ( const pin_config_t* const pin_cfg_array ); constexpr uint32_t bb_p_idr_read_get ( const pin_config_t* const pin_cfg_array ); const uint32_t p_bsr; const uint32_t bsr_set_msk, bsr_reset_msk; const uint32_t p_bb_odr_read, p_bb_idr_read; };
略称エントリについて。
多くの場合、特定のタスク(ADC入力など)のオブジェクト構成構造を作成する必要があります。 そのような結論が多数ある場合、毎回すべてのパラメーターを記述するのは面倒です。 これを行うには、テンプレートクラスを使用できます。テンプレートクラスはテンプレートクラスを使用します。 ADCの場合、次のようになります。
template < EC_PORT_NAME PORT, EC_PORT_PIN_NAME PIN_NAME > class pin_config_adc_check_param : public pin_config_check_param< PORT, PIN_NAME, EC_PIN_MODE::INPUT, EC_PIN_OUTPUT_CFG::NO_USE, EC_PIN_SPEED::LOW, EC_PIN_PULL::UP, EC_PIN_AF::NO_USE, EC_LOCKED::LOCKED, EC_PIN_STATE_AFTER_INIT::NO_USE > { public: constexpr pin_config_adc_check_param() {}; };
コード内の広告に必要なスペースは何倍も少なくなります。
const pin_config_adc_check_param< EC_PORT_NAME::B, EC_PORT_PIN_NAME::PIN_1 > adc_left;
まとめ
したがって、出力では、コンパイル段階でRAMを一切使用せず、コンストラクターをリアルタイムで呼び出す必要のないオブジェクトを作成する機会がありました。 この場合、それらが正しく初期化されていることを確認します。
この特定のケースでは、間違いを犯すことは困難です、私は同意します。 ここでのチェックは不要かもしれませんが、たとえば、PLLコンフィギュレーション設定の作成に関しては、すでに複雑になっています。 何かを考慮することはできません。 たとえば、一部の除数を値に設定することはできませんが、入力するフィールドでそれを受け入れることはできますが、設定された除数は、推奨値の境界に到達しない、またはその逆の頻度を受け取ります。 そのような場合、コンパイル段階でチェックする機能は非常に役立ちます。
また、クラスピンのオブジェクトを初期化するために作成されたグローバル構造がメインファームウェアファイルに移動しないことにも注意してください。 未使用としてリンカーによって破棄されます。 ユーザープログラムで実際に呼び出されたコンストラクターとメソッドによって入力されたuint32_t変数のみがフラッシュに移動します。
この記事に記載されているコードは、 このライブラリの一部です。 ライブラリはまだ開発の初期段階にあります。 アルファ版はどのようになりますか-このトピックに関する別の記事があります。
アイデアを思いついたトースターに対応してくれたmadcomakerに感謝します。