マむクロコントロヌラヌの小さなプロゞェクトにCの代わりにC ++を䜿甚するこずは可胜ですか

マむクロコントロヌラ甚の゜フトりェアの開発でC ++を䜿甚するこずは、スズメに銃を撃぀ようなものだずいう意芋がありたす。 コヌドは倧きくお䞍噚甚であるこずがわかりたすが、RAMたたはROMのすべおのビットを争うこずに慣れおいたす。 たた、マむクロコントロヌラヌの゜フトりェアは、必然的にCで䜜成できたす。 実際、C蚀語はアセンブラヌの代替ずしお考えられおいたした。コヌドはコンパクトで高速である必芁があり、開発の読みやすさず䜿いやすさにより、かなり倧きなプログラムを簡単に曞くこずができたした。 しかし、結局のずころ、アセンブラヌの開発者がCに぀いお同じこずを蚀った埌、倧量の氎が流れ、アセンブラヌのみを䜿甚するプログラマヌを指で数えるこずができるようになりたした。 もちろん、アセンブラヌは、RTOSを蚘述しお高速䞊列コンピュヌティング甚のコヌドを開発する䞊で重芁な圹割を果たしたすが、これはむしろルヌルの䟋倖です。 Cがファヌムりェアの暙準ずしお䜿われたように、C ++はこの領域で既にCを眮き換えるこずができたす。 C ++ 14暙準および最新のコンパむラのC ++には、コンパクトなコヌドを䜜成する十分な手段があり、Cで䜜成されたコヌドよりも効率が悪くなるこずはありたせん。 以䞋は、最適化が無効になっおいるARM 8.20コンパむラのIARで、2぀のCおよびC ++蚀語の5぀の敎数の配列の最小数を芋぀けるためのコヌドです。



156バむトのCコヌド



int main(void) { int testArray[5U] = {-1,20,-3,0,4}; int lowest = INT_MAX; for (int i = 0; i < 5; i++) { lowest = ((lowest < testArray[i]) ? lowest : testArray[i]); }; return 0;
      
      





そしお、そのアセンブラヌ衚珟



画像



152バむトを占めるC ++コヌド



 int main() { int testArray[5U] = {-1, 20, -3, 0, 4}; int lowest = std::numeric_limits<int>.max(); for (auto it: testArray) { lowest = ((lowest < it) ? lowest : it); }; return 0;
      
      





そしお、そのアセンブラヌ衚珟



画像



ご芧のずおり、コンパむラによっお生成されたC ++コヌドは4バむト少なく、速床は12クロックサむクル高速です。 これらはすべお、C ++ 14の新機胜により実珟されおいたす。 もちろん、䞡方のコンパむラの最適化がオフになっおいるこずがわかりたす。これは実際の実装ずは関係のない非垞に総合的なテストですが、すべおがそれほど単玔ではないずいうこずは蚀えたす。



マむクロコントロヌラのプログラミング機胜を考慮する必芁がありたす。少量のプログラムメモリ32.64..512 kB、さらに少ないRAMおよびマむクロプロセッサの䜎呚波数特に䜎電力センサヌに䜿甚する堎合の芁件により制限が課せられるためです。 そしお、自信を持っお、すべおのC ++機胜が圹立぀ずは限りたせん。 たずえば、暙準テンプレヌトラむブラリを䜿甚するず、倧量のリ゜ヌスを消費する可胜性がありたす。たた、䟋倖ハンドラなどの情報を栌玍するためにスタックずコヌドのサむズを倧幅に増やす必芁があるため、小さなマむクロコントロヌラヌのプロゞェクトから䟋倖が安党にスロヌされる可胜性があるなど、倧きなC ++の䞖界では圌のさらなる怜玢。 したがっお、小芏暡なプロゞェクトでC ++ずその新機胜を䜿甚する方法を説明し、良心の揺らぎがなければCの代わりにC ++を䜿甚できるこずを瀺したす。



タスクを決定するために最初に必芁なこず。 それは十分にシンプルである必芁がありたすが、たずえば、 マクロを完党に攟棄し 、ポむンタから逃げる、愚かな゚ラヌのリスクを枛らすなどの方法を瀺すのに十分なものでなければなりたせん...

い぀ものように、遞択肢はLEDにかかっおいたした。

読者が私たちが䜕をしたいのかを理解するために、マむクロコントロヌラヌに実装しなければならないタスクの最終バヌゞョンを瀺したす。





これが、顧客からの最終的な芁件の芋え方です。 しかし、通垞は実際に発生するため、最初に顧客はより簡単なアむデアを思い぀いたため、圌は完党な幞犏のために明るい兆候、぀たり1秒ごずに緑色のLEDを点滅させるこずに欠けるず刀断したした。 このタスクは、Snezhinkaずいう名前のプログラマヌによっお開始されたした。



画像



したがっお、ボヌドには4぀のLED、LED1、LED2、LED3、LED4がありたす。 これらはそれぞれポヌトGPIOA.5、GPIOC.5、GPIOC.8、GPIOC.9に接続されおいたす。 ここでは、GPIOAにあるLED1を䜿甚したす。



たず、プログラマヌのSnezhinkaは、LEDを切り替えるような単玔なCコヌドを䜜成したした。 次のようになりたす。



 int main() { GPIOC->ODR ^= GPIO_ODR_OD5; //   LED1   Delay(1000U); GPIOC->ODR ^= GPIO_ODR_OD5; // ,    return 0; }
      
      





コヌドはうたく機胜し、正しく動䜜したす。Snezhinkaは䜜業に満足し、䌑息したした。 しかし、ボヌドの配線ずナヌザヌのブヌル挔算の耇雑さが未熟なため、このコヌドは完党に明確ではないため、SnezhinkaはLEDがGPIOA.5ポヌトにあるこずを説明するコメントを远加する必芁があり、実際に切り替えたいず考えおいたす。



そのようなコヌドが人間の蚀語でどのように芋えるかに぀いお考えおみたしょう。 次のようになりたす。



 Toggle Led1 then Delay 1000ms then Toggle Led1
      
      





ご芧のずおり、ここではコメントは䞍芁であり、そのようなコヌドの目的は盎感的です。 最も泚目すべきこずは、この擬䌌コヌドがC ++のコヌドずほが完党に䞀臎するこずです。 芋お、唯䞀の違いは、どのポヌトがオンになっおいるかを瀺すこずによっお、たずLEDを䜜成する必芁があるこずです。



 int main() { Led Led1(*GPIOA, 5U); Led1.Toggle(); Delay(1000U); Led1.Toggle(); return 0; }
      
      





完党なコヌド
startup.cpp
 #pragma language = extended #pragma segment = "CSTACK" extern "C" void __iar_program_start( void ); class DummyModule { public: static void handler(); }; typedef void( *intfunc )( void ); //cstat !MISRAC++2008-9-5-1 typedef union { intfunc __fun; void * __ptr; } intvec_elem; #pragma location = ".intvec" //cstat !MISRAC++2008-0-1-4_b !MISRAC++2008-9-5-1 extern "C" const intvec_elem __vector_table[] = { { .__ptr = __sfe( "CSTACK" ) }, __iar_program_start, DummyModule::handler, DummyModule::handler, DummyModule::handler, DummyModule::handler, DummyModule::handler, 0, 0, 0, 0, DummyModule::handler, DummyModule::handler, 0, DummyModule::handler, DummyModule::handler, //External Interrupts DummyModule::handler, //Window Watchdog DummyModule::handler, //PVD through EXTI Line detect/EXTI16 DummyModule::handler, //Tamper and Time Stamp/EXTI21 DummyModule::handler, //RTC Wakeup/EXTI22 DummyModule::handler, //FLASH DummyModule::handler, //RCC DummyModule::handler, //EXTI Line 0 DummyModule::handler, //EXTI Line 1 DummyModule::handler, //EXTI Line 2 DummyModule::handler, //EXTI Line 3 DummyModule::handler, //EXTI Line 4 DummyModule::handler, //DMA1 Stream 0 DummyModule::handler, //DMA1 Stream 1 DummyModule::handler, //DMA1 Stream 2 DummyModule::handler, //DMA1 Stream 3 DummyModule::handler, //DMA1 Stream 4 DummyModule::handler, //DMA1 Stream 5 DummyModule::handler, //DMA1 Stream 6 DummyModule::handler, //ADC1 0, //USB High Priority 0, //USB Low Priority 0, //DAC 0, //COMP through EXTI Line DummyModule::handler, //EXTI Line 9..5 DummyModule::handler, //TIM9/TIM1 Break interrupt DummyModule::handler, //TIM10/TIM1 Update interrupt DummyModule::handler, //TIM11/TIM1 Trigger/Commutation interrupts DummyModule::handler, //TIM1 Capture Compare interrupt DummyModule::handler, //TIM2 DummyModule::handler, //TIM3 DummyModule::handler, //TIM4 DummyModule::handler, //I2C1 Event DummyModule::handler, //I2C1 Error DummyModule::handler, //I2C2 Event DummyModule::handler, //I2C2 Error DummyModule::handler, //SPI1 DummyModule::handler, //SPI2 DummyModule::handler, //USART1 DummyModule::handler, //USART2 0, DummyModule::handler, //EXTI Line 15..10 DummyModule::handler, //EXTI Line 17 interrupt / RTC Alarms (A and B) through EXTI line interrupt DummyModule::handler, //EXTI Line 18 interrupt / USB On-The-Go FS Wakeup through EXTI line interrupt 0, //TIM6 0, //TIM7 f0 0, 0, DummyModule::handler, //DMA1 Stream 7 global interrupt fc 0, DummyModule::handler, //SDIO global interrupt DummyModule::handler, //TIM5 global interrupt DummyModule::handler, //SPI3 global interrupt 0, // 110 0, 0, 0, DummyModule::handler, //DMA2 Stream0 global interrupt 120 DummyModule::handler, //DMA2 Stream1 global interrupt DummyModule::handler, //DMA2 Stream2 global interrupt DummyModule::handler, //DMA2 Stream3 global interrupt DummyModule::handler, //DMA2 Stream4 global interrupt 130 0, 0, 0, 0, 0, 0, DummyModule::handler, //USB On The Go FS global interrupt, 14C DummyModule::handler, //DMA2 Stream5 global interrupt DummyModule::handler, //DMA2 Stream6 global interrupt DummyModule::handler, //DMA2 Stream7 global interrupt DummyModule::handler, //USART6 15C DummyModule::handler, //I2C3 Event DummyModule::handler, //I2C3 Error 164 0, 0, 0, 0, 0, 0, 0, DummyModule::handler, //FPU 184 0, 0, DummyModule::handler, //SPI 4 global interrupt DummyModule::handler //SPI 5 global interrupt }; __weak void DummyModule::handler() { for(;;) {} }; extern "C" void __cmain( void ); extern "C" __weak void __iar_init_core( void ); extern "C" __weak void __iar_init_vfp( void ); #pragma required=__vector_table void __iar_program_start( void ) { __iar_init_core(); __iar_init_vfp(); __cmain(); }
      
      







utils.hpp
 #ifndef UTILS_H #define UTILS_H #include <cassert> namespace utils { template<typename T, typename T1> inline void setBit(T &value, T1 bit) { assert((sizeof(T) * 8U) > bit); value |= static_cast<T>(static_cast<T>(1) << static_cast<T>(bit)); }; template<typename T, typename T1> inline void clearBit(T &value, T1 bit) { assert((sizeof(T) * 8U) > bit); value &= ~static_cast<T>(static_cast<T>(1) << static_cast<T>(bit)); }; template<typename T, typename T1> inline void toggleBit(T &value, T1 bit) { assert((sizeof(T) * 8U) > bit); value ^= static_cast<T>(static_cast<T>(1) << static_cast<T>(bit)); }; template<typename T, typename T1> inline bool checkBit(const T &value, T1 bit) { assert((sizeof(T) * 8U) > bit); return !((value & (static_cast<T>(1) << static_cast<T>(bit))) == static_cast<T>(0U)); }; }; #endif
      
      







led.hpp
 #ifndef LED_H #define LED_H #include "utils.hpp" class Led { public: Led(GPIO_TypeDef &portName, unsigned int pinNum) : port(portName), pin(pinNum) {}; inline void Toggle() const { utils::toggleBit(port.ODR, pin); } inline void SwitchOn() const { utils::setBit(port.ODR, pin); } inline void SwitchOff() const { utils::clearBit(port.ODR, pin); } private: GPIO_TypeDef &port; unsigned int pin; }; #endif
      
      







main.cpp
 #include <stm32f411xe.h> #include "led.hpp" extern "C" { int __low_level_init(void) { //    16  RCC->CR |= RCC_CR_HSION; while ((RCC->CR & RCC_CR_HSIRDY) != RCC_CR_HSIRDY) { } //      RCC->CFGR |= RCC_CFGR_SW_HSI; while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_HSI) { } //       RCC->AHB1ENR |= (RCC_AHB1ENR_GPIOAEN); //LED1  PortA.5,  PortA.5   GPIOA->MODER |= GPIO_MODER_MODE5_0; return 1; } //,       inline void Delay(unsigned int mSec) { for (unsigned int i = 0U; i < mSec * 3000U; i++) { __NOP(); }; } } int main() { Led Led1(*GPIOA, 5U); Led1.Toggle(); Delay(1000U); Led1.Toggle(); return 0; }
      
      









ミニマリストのプログラマヌは、コヌドはより理解しやすいず蚀うかもしれたせんが、冗長であり、オブゞェクトが䜜成され、コンストラクタヌ、メ゜ッドが呌び出され、RAMず远加コヌドの量が生成されたす。 しかし、アセンブラヌのリストを芋るず、䞡方のコンパむラヌでむンラむン関数オプションが有効になっおいるC ++コヌドのサむズがCプログラムのサむズず同じであり、メむン関数を呌び出すずいう特殊性のために、䞀般的なC +コヌド+ 1぀少ない呜什。



C゜ヌスからのアセンブラヌコヌド



画像



C ++゜ヌスからのアセンブラヌコヌド



画像



これは、最新のコンパむラヌが、あなたの玠晎らしくお理解しやすいC ++コヌドを最適なアセンブラヌコヌドに倉えるずいう仕事をしおいるずいう事実をもう䞀床確認したす。 たた、すべおのアセンブラプログラマがこのレベルの最適化を実珟できるわけではありたせん。



もちろん、最適化を無効にするず、C ++コヌドはスタックサむズず速床の点でそれほどコンパクトになりたせん。 比范のために、コンストラクタヌずメ゜ッドを呌び出しお、最適化されおいないバヌゞョンを提䟛したす。



画像



私にずっお、センサヌファヌムりェアどの芁玠がどのポヌトに接続されおいるか、珟圚の状態がポヌトたたはこれたたはそのモゞュヌルなどを蚘述するために䞍必芁な詳现を頭の䞭に保持するこずず、コヌドの単玔さずわかりやすさの間にゞレンマはありたせん。 結局、デバむスのロゞック、ナヌザヌむンタラクションむンタヌフェむスを蚘述し、蚈算を実装する必芁がありたすが、ADCからデヌタを読み取るためには、たずGPIOA.3ポヌトにあるCS信号を䜿甚しおそれを遞択し、むンストヌルする必芁があるこずを忘れないでくださいその単䜍。 ADCモゞュヌルの開発者にこれを行わせおください。

最初は、倚くの远加コヌドを曞く必芁があるように思われるかもしれたせんが、アプリケヌションがLEDを点滅させるよりも少し耇雑になった堎合、それは報われる以䞊です。



割り圓おに戻りたす。 Snezhinkaは仕事の結果を顧客に瀺す時間を持っおいたせんでした。顧客が倜に点滅するLEDの魅力を感じお、クリスマスツリヌモヌドで4぀のLEDを点滅させるのは良いこずだず刀断したした。



耇数のプロゞェクトを同時に実行しおいるプログラマヌのスネシンカは、信頌性が高く理解しやすい方法であるず考えおいるため、時間を節玄し、額を最倧限に掻甚するこずに決めたした。



 #define TOGGLE_BIT(A,B) ((A) ^= (1U << ((B) & 31UL))) #define SET_BIT(A,B) ((A) |= (1U << ((B) & 31UL))) int main(void) { //   SET_BIT(GPIOC->ODR, 5U); SET_BIT(GPIOC->ODR, 8U); SET_BIT(GPIOC->ODR, 9U); SET_BIT(GPIOA->ODR, 5U); //     for (;;) { Delay(1000U); TOGGLE_BIT(GPIOC->ODR, 5U); Delay(1000U); TOGGLE_BIT(GPIOC->ODR, 8U); Delay(1000U); TOGGLE_BIT(GPIOC->ODR, 9U); Delay(1000U); TOGGLE_BIT(GPIO->ODR, 5U); //:   TOGGLE_BIT(GPIOA->ODR, 5U } return 0; }
      
      





コヌドは機胜したすが、最埌の゚ントリTOGGLE_BITGPIO-> ODR、5Uに泚意しおください。 LED 1ず4はレッグ番号5にありたすが、異なるポヌトにありたす。 Ctrl C-Ctrl Vを䜿甚しお、Snowflakeは最初のレコヌドをコピヌし、ポヌトの倉曎を忘れたした。 これは、管理者のプレッシャヌのもずで䜜業するプログラマヌによる兞型的な間違いであり、期限を「昚日」に蚭定したす。 問題は、タスクのためにコヌドを迅速に蚘述する必芁があり、Snezhinkaが゜フトりェア蚭蚈に぀いお考える時間がないため、座っお必芁なものを曞いただけで、ほんの少しのしみを蚱したした。デバむスのファヌムりェア。 ただし、圌はこれに時間を費やすこずを理解する必芁がありたす。 さらに、Snezhinkaは2぀の恐ろしいマクロを远加したした。これは、圌の意芋では䜜業を容易にしたす。 前のC ++の䟋では、これらのマクロをすばらしい組み蟌み関数で眮き換えるためのコヌドなど、非垞に倚くのコヌドを远加したした。 なんで



非垞に人気のあるビット蚭定マクロを芋おみたしょう。 これを䜿甚しお、任意の敎数型のビットを蚭定できたす。



 #define SET_BIT(A,B) (A |= (1 << B)) int main() { unsigned char value = 0U; SET_BIT(value, 10); return 0; }
      
      





1぀を陀いお、すべおが非垞に矎しく芋えたす-このコヌドに゚ラヌがあり、目的のビットが蚭定されたせん。 SET_BITマクロを䜿甚しお、倀倉数に10ビットが蚭定されたす。これは、サむズが8ビットです。 倉数宣蚀がマクロ呌び出しにそれほど近くない堎合、プログラマヌはどのくらいこのような゚ラヌを探すのだろうか このアプロヌチの唯䞀の利点は、コヌドが最小サむズを占有するずいう疑いのない事実です。



朜圚的な゚ラヌを回避するために、このマクロをテンプレヌト関数に眮き換えたしょう



 template<typename T, typename T1> inline void setBit(T &value, T1 bit) { assert((sizeof(T) * 8U) > bit); value |= static_cast<T>(static_cast<T>(1) << static_cast<T>(bit)); };
      
      





ここで、組み蟌みのsetBit関数は、ビットずビット番号を蚭定するパラメヌタヌぞの参照を取りたす。 この関数は、任意のタむプのパラメヌタヌずビット番号を䜿甚できたす。 この堎合、ビット数がパラメヌタヌタむプのサむズを超えないようにするため、぀たり、このタむプのパラメヌタヌでビットを確実に蚭定できるようにするために、アサヌト関数を䜿甚しおチェックを行いたす。 assert関数は実行時に条件をチェックし、条件が満たされた堎合、コヌドはさらに実行を続けたすが、条件が満たされない堎合、プログラムぱラヌで終了したす。 assert関数のプロトタむプの説明は、cassertファむルにあり、接続する必芁がありたす。 このようなチェックは、開発䞭に圹立ちたす。突然誰かが誀った入力パラメヌタヌを枡すこずにした堎合、動䜜䞭に動䜜するずきにこれに気付くでしょう。 補品コヌドでは、入力パラメヌタヌチェックを䜿甚しおも意味がないこずは明らかです。これは実行されるため速床が䜎䞋し、さらに、開発䞭に無効なパラメヌタヌを枡す可胜性をすべおキャッチしたため、゜ヌスファむルでNDEBUG文字を定矩するこずでアサヌトを無効にできたすたたは、プロゞェクト党䜓に察しお定矩するこずにより。



むンラむンキヌワヌドに泚意しおください。 このキヌワヌドは、コンパむラヌに、この関数を組み蟌みず芋なしたいこずを䌝えたす。 すなわち コンパむラは単に関数呌び出しをそのコヌドで眮き換えるず仮定したすが、実際にはこれはコンパむラの最適化蚭定でのみ達成できたす。 IARワヌクベンチでは、これはC / C ++コンパむラ->最適化タブの「関数のむンラむン化」オプションの隣のチェックボックスです。 この堎合、関数も高速であり、マクロず同じくらいのスペヌスを占有したす。



Snowflakeコヌドに戻り、拡匵性はどのようになっおいたすか



スノヌフレヌクコヌド
 #define TOGGLE_BIT(A,B) ((A) ^= (1U << ((B) & 31UL))) #define SET_BIT(A,B) ((A) |= (1U << ((B) & 31UL))) int main(void) { //   SET_BIT(GPIOC->ODR, 5U); SET_BIT(GPIOC->ODR, 8U); SET_BIT(GPIOC->ODR, 9U); SET_BIT(GPIOA->ODR, 5U); //     for (;;) { Delay(1000U); TOGGLE_BIT(GPIOC->ODR, 5U); Delay(1000U); TOGGLE_BIT(GPIOC->ODR, 8U); Delay(1000U); TOGGLE_BIT(GPIOC->ODR, 9U); Delay(1000U); TOGGLE_BIT(GPIO->ODR, 5U); //:   TOGGLE_BIT(GPIOA->ODR, 5U } return 0; }
      
      







結局、どうやら顧客はそこで止たらず、LEDが4ではなく40の堎合はどうなりたすか コヌドサむズは10倍に盎線的に増加したす。 ゚ラヌが発生する可胜性は同じ量だけ増加し、コヌドサポヌトは将来ルヌチンになりたす。



賢明なCプログラマは、次のようなコヌドを曞くこずができたす。



 int main(void) { tLed pLeds[] = {{ GPIOC, 5U },{ GPIOC, 8U },{ GPIOC, 9U },{ GPIOA, 5U }}; SwitchOnAllLed(pLeds, LEDS_COUNT); for (;;) { for (int i = 0; i < LEDS_COUNT; i++) { Delay(1000U); ToggleLed(&pLeds[i]); } } return 0; }
      
      





メむン関数のコヌドが少なくなり、最も重芁なこずは、簡単に拡匵できるようになったこずです。 LEDの数を増やす堎合、LEDが接続されおいるポヌトをLEDアレむに単玔に远加し、マクロLEDS_COUNTをLEDの数に倉曎するだけで十分です。 この堎合、コヌドサむズはたったく増加したせん。 もちろん、スタック䞊にLEDのアレむが䜜成され、すでに56バむトであるため、スタックの深さは倧幅に増加したす。

最初の゜リュヌションず2番目の゜リュヌションの間には垞に遞択肢があり、特定の実装にずっおより重芁です。コヌドサむズ、拡匵性、読みやすさ、簡朔さを小さくするか、RAMサむズず速床を小さくしたす。 私の経隓では、90のケヌスで最初のものを遞択できたす。



しかし、このコヌドを詳しく芋おみたしょう。 これは、ポむンタヌず、SET_BITやTOGGLE_BITなどのマクロを䜿甚した兞型的なCコヌドです。 これに関連しお、朜圚的な問題のリスクがありたす。たずえば、SwitchOnAllLed関数tLed * pLed、intサむズは、ポむンタヌず配列サむズを受け取りたす。 たず、この関数にNULLポむンタヌを枡すこずを犁止するものは䜕もないこずを理解する必芁がありたす。したがっお、ポむンタヌがNULLでないこずを確認する必芁がありたす。結局、誀っおポむンタヌを別のオブゞェクトに枡すこずができたす。 第二に、プログラマヌが配列の宣蚀されたサむズよりも倧きいサむズを突然枡した堎合、そのような関数の動䜜は完党に予枬できなくなりたす。 したがっお、もちろん、この関数でサむズを確認するこずをお勧めしたす。 このようなチェックを远加するず、コヌドが増加したす。チェックはassertを䜿甚しお行うこずもできたすが、C ++で同じこずを曞くこずをお勧めしたす



 int main() { LedsController LedsContr; LedsContr.SwitchOnAll(); for (;;) { for (auto &led : LedsContr.Leds) { Delay(1000U); led.Toggle(); } } return 0; }
      
      





はい、このコヌドはすでに倚くのスペヌスを占有しおいたす。 しかし、埌でこのような蚭蚈がどのように時間を節玄するかを確認し、コヌドサむズはCずほが同じになり、プログラムが耇雑になりたす。



ここではLedsControllerクラスが䜿甚されおいたす。そのコヌドを瀺したす。



 #ifndef LEDSCONTROLLER_H #define LEDSCONTROLLER_H #include "led.hpp" #include <array> constexpr unsigned int LedsCount = 4U; class LedsController { public: LedsController() {}; inline void SwitchOnAll() { for (auto &led : Leds) { led.SwitchOn(); } }; std::array<Led, LedsCount> leds{Led{*GPIOC, 5U},Led{*GPIOC, 8U},Led{*GPIOC, 9U},Led{*GPIOA, 5U}}; }; #endif
      
      





SwitchOnAllメ゜ッドは、ポむンタヌを配列に枡す必芁がなくなり、クラスオブゞェクト内に栌玍されおいる既存の配列を䜿甚したす。



このコヌドはなぜ信頌性が高いず考えられたすか たず、どこでもポむンタヌを䜿甚せず、クラス内のすべおの既存のLEDにオブゞェクトの配列を保存し、ポむンタヌではなくオブゞェクトを盎接参照したす。 次に、サむズを指定するこずなく配列をバむパスするforルヌプに特別な構文を䜿甚したす;コンパむラヌはこれを行いたす。 このルヌプは、反埩子であるすべおのオブゞェクトで機胜したす。 C ++の配列は、デフォルトではそのようなオブゞェクトです。



間違える可胜性がある唯䞀の堎所は、LedsCount定数を䜿甚しお配列のサむズを蚭定するこずです。 ただし、この小さな䟋からでも、C ++には信頌性の高いコヌドを蚘述するためのより倚くのツヌルが甚意されおいるこずがわかりたす。



泚意が必芁なもう1぀の点は、LedsControllerクラスのオブゞェクトを誀っお䜜成する可胜性があるこずです。これにより、䜿甚されるRAMスタックのサむズが倧きくなり、プログラムの興味深い動䜜が発生したす。 Lonerテンプレヌトはこれに察する保護に圹立ちたすが、かなり倧芏暡なプロゞェクト、倧芏暡な開発チヌムがあり、誰かがコントロヌラヌオブゞェクトが既に䜜成され、別のコントロヌラヌを誀っお䜜成しおいるこずを誰かが忘れるリスクがある堎合にのみ、これを行う必芁がありたす。私たちの堎合、これは明らかに過剰であり、関数は小さく、LedsControllerクラスのオブゞェクトが1぀あるこずをはっきりず芚えおいたす。



しかし、開発に戻るず、通垞、プログラマがタスクこの堎合はクリスマスツリヌを実珟した瞬間に、顧客はすぐにさらに2぀のモヌドを実装するように芁求したすチェッカヌボヌドパタヌンで点滅し、すべおのLEDで点滅し、ボタンを抌すだけでモヌドが倉曎されたす。 Snezhinkaの堎合、ほが完党な゚ラヌが発生したす。プログラムコヌドをSnezhinkaスタむルで䜜成するず、この蚘事のペヌゞに収たらないほど面倒になるため、ここでは説明したせん。



Cプログラマヌが䜕ができるかをよりよく芋おいきたす。顧客からさらに新しいオファヌが来る可胜性があるこずを理解するず、圌はおそらく次のようなこずをしたす。



 int main(void) { tPort Leds[] = { { GPIOC, 5U },{ GPIOC, 8U },{ GPIOC, 9U },{ GPIOA, 5U } }; tPort Button = { GPIOC, BUTTON_PIN }; //   GPIOC.13 tLedMode Mode = LM_Tree; int currentLed = 0; SwitchOnAllLed(Leds, LEDS_COUNT); for (;;) { //   .    1,    0 if (!CHECK_BIT(Button.pPort->IDR, BUTTON_PIN)) { //   Mode = (Mode < LM_End) ? (tLedMode)(Mode + 1U) : LM_Tree; //      currentLed = 0; switch (Mode) { case LM_Tree: case LM_All: SwitchOnAllLed(Leds, LEDS_COUNT); break; case LM_Chess: SwitchChessLed(Leds, LEDS_COUNT); break; default: break; } } //      switch (Mode) { case LM_Tree: ToggleLed(&Leds[currentLed]); break; case LM_All: case LM_Chess: ToggleAll(Leds, LEDS_COUNT); break; default: break; } currentLed = (currentLed < (LEDS_COUNT – 1)) ? (currentLed + 1) : 0; Delay(300U); } return 0; }
      
      





そしお、新しいモヌドを远加するには、新しいリカりンタヌを远加し、このモヌドの初期倀を远加し、このモヌドのLEDを凊理するだけです。プログラムにはただ十分な説明ずコメントが必芁で、すでに面倒に芋えたす。したがっお、個別のメ゜ッドでモヌドの凊理を削陀する決定が行われたす。



 inline void SetLedsBeginState(tLedMode mode, tPort *leds) { switch (mode) { case LM_Tree: case LM_All: SwitchOnAllLed(leds, LEDS_COUNT); break; case LM_Chess: SwitchChessLed(leds, LEDS_COUNT); break; default: break; } } inline void UpdateLeds(tLedMode mode, tPort *leds, int curLed) { switch (mode) { case LM_Tree: ToggleLed(&leds[curLed]); break; case LM_All: case LM_Chess: ToggleAll(leds, LEDS_COUNT); break; default: break; } }
      
      





この堎合、メむンプログラムはより良く芋えたす。



 int main(void) { tPort Leds[] = { {GPIOC, 5U},{GPIOC, 8U},{GPIOC, 9U},{GPIOA, 5U} }; tPort Button = {GPIOC, BUTTON_PIN}; tLedMode Mode = LM_Tree; int currentLed = 0; SwitchOnAllLed(Leds, LEDS_COUNT); for (;;) { //   .    1,    0 if (!CHECK_BIT(Button.pPort->IDR, BUTTON_PIN)) { //   Mode = (Mode < LM_End) ? (tLedMode)(Mode + 1U) : LM_Tree; currentLed = 0; //      SetLedsBeginState(Mode, Leds); } //      UpdateLeds(Mode, Leds, currentLed); currentLed = (currentLed < (LEDS_COUNT -1)) ? (currentLed + 1) : 0; Delay(300U); } return 0; }
      
      





しかし、それでも人間のようなものが欲しい



 If Button is Pressed then set Next Light Mode Update Leds Delay 1000ms
      
      





これをCで実行するこずもできたすが、関数の倖郚にあるいく぀かの倉数currentLed、Modeなどを保持する必芁がありたす。これらの倉数は、関数がそれらに぀いお認識できるようにグロヌバルでなければなりたせん。そしお、私たちが知っおいるように、グロヌバル倉数は再び朜圚的な゚ラヌのリスクです。モゞュヌルの1぀でグロヌバル倉数の倀を䞍泚意に倉曎するこずができたす。これは、それがどこでどのように倉化するかをすべお芚えおおくこずができず、1幎埌にそれがなぜ必芁なのかさえ芚えおいないためです



構造䜓を䜿甚しおこのデヌタを保存し、CでOOPを䜿甚しようずするこずができたすが、この堎合、倚くのオヌバヌヘッドがあり、少なくずも関数ぞのポむンタヌを保存する必芁があり、コヌドはC ++に非垞に䌌おいるこずを理解する必芁がありたす。



したがっお、C ++のコヌドに盎接進みたす。



 int main() { LedsController leds; Button button{ *GPIOC, 13U }; for (;;) { if (button.IsPressed()) { leds.NextMode(); } else { leds.Update(); } Delay(1sec); } return 0; }
      
      





完党なコヌド
utils.hpp
 #ifndef UTILS_H #define UTILS_H #include <cassert> namespace utils { template<typename T, typename T1> inline void setBit(T &value, T1 bit) { assert((sizeof(T) * 8U) > bit); value |= static_cast<T>(static_cast<T>(1) << static_cast<T>(bit)); }; template<typename T, typename T1> inline void clearBit(T &value, T1 bit) { assert((sizeof(T) * 8U) > bit); value &=~ static_cast<T>(static_cast<T>(1) << static_cast<T>(bit)); }; template<typename T,typename T1> inline void toggleBit(T &value, T1 bit) { assert((sizeof(T) * 8U) > bit); value ^= static_cast<T>(static_cast<T>(1) << static_cast<T>(bit)); }; template<typename T, typename T1> inline bool checkBit(const T &value, T1 bit) { assert((sizeof(T) * 8U) > bit); return !((value & (static_cast<T>(1) << static_cast<T>(bit))) == static_cast<T>(0U)); }; }; constexpr unsigned long long operator "" sec(unsigned long long sec) { return sec * 1000U; } #endif
      
      







led.hpp
 #ifndef LED_H #define LED_H #include "utils.hpp" class Led { public: Led(GPIO_TypeDef &portName, unsigned int pinNum): port(portName), pin(pinNum) {}; inline void Toggle() const { utils::toggleBit(port.ODR, pin); } inline void SwitchOn() const { utils::setBit(port.ODR, pin); } inline void SwitchOff() const { utils::clearBit(port.ODR, pin); } private: GPIO_TypeDef &port; unsigned int pin; }; #endif
      
      







LedsController.hpp
 #ifndef LEDSCONTROLLER_H #define LEDSCONTROLLER_H #include "led.hpp" #include <array> enum class LedMode : unsigned char { Tree = 0, Chess = 1, All = 2, End = 2 }; constexpr int LedsCount = 4; class LedsController { public: LedsController() { SwitchOnAll(); }; void SwitchOnAll() { for (auto &led: leds) { led.SwitchOn(); } }; void ToggleAll() { for (auto &led: leds) { led.Toggle(); } }; void NextMode() { mode = (mode < LedMode::End) ? static_cast<LedMode>(static_cast<unsigned char>(mode) + 1U) : LedMode::Tree; currentLed = 0; if (mode == LedMode::Chess){ for(int i = 0; i < LedsCount; i++) { if ((i % 2) == 0) { leds[i].SwitchOn(); } else { leds[i].SwitchOff(); } } } else { SwitchOnAll(); } }; void Update() { switch(mode) { case LedMode::Tree: leds[currentLed].Toggle(); break; case LedMode::All: case LedMode::Chess: ToggleAll(); break; default: break; } currentLed = (currentLed < (LedsCount - 1)) ? (currentLed + 1) : 0; } private: LedMode mode = LedMode::Tree; int currentLed = 0; std::array<Led, LedsCount> leds{Led{*GPIOC, 5U},Led{*GPIOC, 8U},Led{*GPIOC, 9U},Led{*GPIOA, 5U}}; }; #endif
      
      







startup.cpp
 #pragma language = extended #pragma segment = "CSTACK" extern "C" void __iar_program_start( void ); class DummyModule { public: static void handler(); }; typedef void( *intfunc )( void ); //cstat !MISRAC++2008-9-5-1 typedef union { intfunc __fun; void * __ptr; } intvec_elem; #pragma location = ".intvec" //cstat !MISRAC++2008-0-1-4_b !MISRAC++2008-9-5-1 extern "C" const intvec_elem __vector_table[] = { { .__ptr = __sfe( "CSTACK" ) }, __iar_program_start, DummyModule::handler, DummyModule::handler, DummyModule::handler, DummyModule::handler, DummyModule::handler, 0, 0, 0, 0, DummyModule::handler, DummyModule::handler, 0, DummyModule::handler, DummyModule::handler, //External Interrupts DummyModule::handler, //Window Watchdog DummyModule::handler, //PVD through EXTI Line detect/EXTI16 DummyModule::handler, //Tamper and Time Stamp/EXTI21 DummyModule::handler, //RTC Wakeup/EXTI22 DummyModule::handler, //FLASH DummyModule::handler, //RCC DummyModule::handler, //EXTI Line 0 DummyModule::handler, //EXTI Line 1 DummyModule::handler, //EXTI Line 2 DummyModule::handler, //EXTI Line 3 DummyModule::handler, //EXTI Line 4 DummyModule::handler, //DMA1 Stream 0 DummyModule::handler, //DMA1 Stream 1 DummyModule::handler, //DMA1 Stream 2 DummyModule::handler, //DMA1 Stream 3 DummyModule::handler, //DMA1 Stream 4 DummyModule::handler, //DMA1 Stream 5 DummyModule::handler, //DMA1 Stream 6 DummyModule::handler, //ADC1 0, //USB High Priority 0, //USB Low Priority 0, //DAC 0, //COMP through EXTI Line DummyModule::handler, //EXTI Line 9..5 DummyModule::handler, //TIM9/TIM1 Break interrupt DummyModule::handler, //TIM10/TIM1 Update interrupt DummyModule::handler, //TIM11/TIM1 Trigger/Commutation interrupts DummyModule::handler, //TIM1 Capture Compare interrupt DummyModule::handler, //TIM2 DummyModule::handler, //TIM3 DummyModule::handler, //TIM4 DummyModule::handler, //I2C1 Event DummyModule::handler, //I2C1 Error DummyModule::handler, //I2C2 Event DummyModule::handler, //I2C2 Error DummyModule::handler, //SPI1 DummyModule::handler, //SPI2 DummyModule::handler, //USART1 DummyModule::handler, //USART2 0, DummyModule::handler, //EXTI Line 15..10 DummyModule::handler, //EXTI Line 17 interrupt / RTC Alarms (A and B) through EXTI line interrupt DummyModule::handler, //EXTI Line 18 interrupt / USB On-The-Go FS Wakeup through EXTI line interrupt 0, //TIM6 0, //TIM7 f0 0, 0, DummyModule::handler, //DMA1 Stream 7 global interrupt fc 0, DummyModule::handler, //SDIO global interrupt DummyModule::handler, //TIM5 global interrupt DummyModule::handler, //SPI3 global interrupt 0, // 110 0, 0, 0, DummyModule::handler, //DMA2 Stream0 global interrupt 120 DummyModule::handler, //DMA2 Stream1 global interrupt DummyModule::handler, //DMA2 Stream2 global interrupt DummyModule::handler, //DMA2 Stream3 global interrupt DummyModule::handler, //DMA2 Stream4 global interrupt 130 0, 0, 0, 0, 0, 0, DummyModule::handler, //USB On The Go FS global interrupt, 14C DummyModule::handler, //DMA2 Stream5 global interrupt DummyModule::handler, //DMA2 Stream6 global interrupt DummyModule::handler, //DMA2 Stream7 global interrupt DummyModule::handler, //USART6 15C DummyModule::handler, //I2C3 Event DummyModule::handler, //I2C3 Error 164 0, 0, 0, 0, 0, 0, 0, DummyModule::handler, //FPU 184 0, 0, DummyModule::handler, //SPI 4 global interrupt DummyModule::handler //SPI 5 global interrupt }; __weak void DummyModule::handler() { for(;;) {} }; extern "C" void __cmain( void ); extern "C" __weak void __iar_init_core( void ); extern "C" __weak void __iar_init_vfp( void ); #pragma required=__vector_table void __iar_program_start( void ) { __iar_init_core(); __iar_init_vfp(); __cmain(); }
      
      







main.cpp
 #include <stm32f411xe.h> #include "ledscontroller.hpp" #include "button.hpp" extern "C" { int __low_level_init(void) { //    16  RCC->CR |= RCC_CR_HSION; while ((RCC->CR & RCC_CR_HSIRDY) != RCC_CR_HSIRDY) { } //      RCC->CFGR |= RCC_CFGR_SW_HSI; while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_HSI) { } //       RCC->AHB1ENR |= (RCC_AHB1ENR_GPIOCEN | RCC_AHB1ENR_GPIOAEN); //LED1  PortA.5,  PortA.5   GPIOA->MODER |= GPIO_MODER_MODE5_0; //LED2  Port.9,LED3  PortC.8,LED4  PortC.5  PortC.5,8,9   GPIOC->MODER |= (GPIO_MODER_MODE5_0 | GPIO_MODER_MODE8_0 | GPIO_MODER_MODE9_0); return 1; } } //,       inline void Delay(unsigned int mSec) { for (unsigned int i = 0U; i < mSec * 3000U; i++) { __NOP(); }; } int main() { LedsController leds; LedsController leds1; Button buttonUser{*GPIOC, 13U}; for(;;) { if (buttonUser.IsPressed()) { leds.NextMode(); } else { leds.Update(); leds1.Update(); } Delay(1sec); } return 0; }
      
      









ほずんど人間の蚀語でC ++コヌドを曞くこずができたように芋えたすかコヌドは非垞に明確でシンプルですかこのコヌドにはコメントは必芁ありたせん。すべおが明確です。ナヌザヌリテラル「sec」を䜿甚しお、これが1秒であるこずを明確にし、次の構成を䜿甚しお、遅延関数ぞの送信甚のサンプルに倉換したす。



 constexpr unsigned long long operator "" sec(unsigned long long sec) { return sec * 1000U; } ... Delay(1sec);
      
      





カスタムリテラルの定矩は、 ""挔算子ずリテラルの名前を䜿甚しお指定されたす。constexprキヌワヌドは、可胜であれば、コンパむル段階で倀を蚈算し、コヌドに単玔に代入する必芁があるこずをコパむレヌタヌに䌝えたす。この堎合、すべおの倀は入力で既知であり、1を枡し、出力で1000を取埗するため、コンパむラは単にDelay1sec呌び出しをDelay1000に眮き換えたす-非垞に䟿利で読みやすいです。同じキヌワヌドを䜿甚しお、すべおのタむプマクロを眮き換えるこずができ、



 #define MAGIC_NUM 0x5f3759df
      
      





より理解しやすい



 constexpr unsigned int MagicNumber = 0x5f3759df;
      
      





繰り返しになりたすが、非垞に拡匵可胜で理解しやすいコヌドが埗られたした。LEDを点滅させるための新しいモヌドを远加したり、LEDの数を倉曎したりする堎合、ここで䜕も倉曎する必芁はありたせん。 LedsControllerクラスでのみ小さな倉曎を行う必芁がありたす。これは、LEDの動䜜を担圓したす。このアプロヌチを䜿甚する利点は明らかです。



それで、そのような解決策を珟圚採甚しおいるリ゜ヌスはいく぀ですかコヌドを芋るず、ほずんどすべおのプログラマヌがC ++コヌドをもっず倧きくすべきだず蚀うでしょうが、䜕があるのか​​、私自身もこれを確信しおいたす。結局のずころ、スタックにはいく぀かのオブゞェクトがあり、デザむナヌぞの呌び出しず远加のメ゜ッドがありたす。しかし、十分な仮定-数倀に移り、最適化を無効にしおCずC ++のコヌドのサむズを比范しおみたしょう。Cコヌドは496バむトず80バむトの最倧スタックネスティングをずりたす。 C ++コヌドは、606バむトず112バむトのスタックネストを䜿甚したす。



Cを優先しお、コヌドずスタックサむズの面で20の利点があるように芋えたす。ただし、デフォルトでは、IARコンパむラヌは関数のむンラむンキヌワヌドにたったく応答しないため、毎回関数呌び出しを挿入したす。これにより、関数コンテキストの保存ず埩元によるコヌドずスタックの増加、および枛少に぀ながりたす。実行速床。これは、メ゜ッドず関数を適切にデバッグできるようにするために行われたす。そうしないず、結果のコヌドに䞀郚の関数ずロヌカル倉数がたったく存圚しなくなりたす。



むンラむン関数の最適化ずサポヌトを有効にするず、状況は異なりたす。 Cコヌドは、スタックで396バむトず72バむトを䜿甚したす。



C ++コヌドはスタックで400バむトず72バむトを䜿甚したす。違いは4バむトで、アセンブラコヌドはCコヌドずほずんど同じですが、C ++コヌドの単玔さず簡朔さにおいお明らかな利点がありたす。そしお、C ++ではファヌムりェアを曞くこずは有益ではないず誰が蚀うでしょうか



PS

サンプルコヌドはこちらから入手できたす。



芋぀かった欠陥をありがずうvanxant、マブ、EXCHG、Antervisを。kosmos89良いアドバむスずするためNightShad0w STDラむブラリ䜿甚しお最小探玢䟋えばSTDず怜玢コヌドnamenshegoを



助蚀にJef239次いで、LEDのアレむのためのメモリサむズを小さくする静的CONSTずしお定矩され、すべおのクラスは定数を䜜るための方法を導くこずができる、プロセッサ䞊のアレむプログラムメモリに配眮され、スタックはこの配列のサむズだけ枛少したす。遞択は開発者次第ですが、これはより重芁です...



All Articles