Очередная статья: STM32 для начинающих

    Всех приветствую!

    Это моя первая статья на Хабре, поэтому прошу не кидаться тяжелыми предметами. Заранее спасибо.

    Начнем с предыстории. Когда-то мне пришлось перейти на микроконтроллеры ARM фирмы ST. Это было связано с тем, что PIC и AVR уже не хватало и хотелось новых приключений. Из доступного в хлебобулочных магазинах и большого количества статей о «быстром старте» выбор пал именно на STM32F100.

    Я привык работать в IAR. Да, есть другие IDE, но мне хватает возможности IAR: относительно удобный редактор, не плохой отладчик и достаточно удобно работать с регистрами во время отладки.

    Когда я попытался сделать первый проект меня ждало разочарование — CMSIS! Кому как, но для меня это было (и остается) ужасом: много буков, длинные и для меня не понятные структуры. Вникать во все это было не интересно. Попытался скомпилировать пару примеров и понял — это не наш метод.

    Неужели нет других вариантов? Есть. Тот, встроенный в IAR: iostm32f10xx4.h и подобные инклудники. Вполне не плохо:

    RCC_APB2ENR_bit.ADC1EN = 1; // включить тактирование ADC

    Оставалось это запихнуть в классы и пользоваться. Так и сделал. Через какое-то время потребовалось сделать код для STM32f4xx. И тут снова засада — нет инклудиков. Что делать? — писать самому. Проанализировал имеющиеся самописные библиотеки решил немного сделать по другому. Вот об этом и будет рассказ.

    Начало


    Про установку IAR и драйверов для отладчика рассказывать не буду, т.к. здесь ничего нового. У меня стоит IAR 8 с ограниченем кода в 32кБ. Для работы выбран контроллер STM32F103, установленный на плате plue pill.

    Запускаем IAR, создаем проект c++, выбираем нужный контроллер
    image
    Следующий шаг — изучение документации. Нас будет интересовать Reference manual RM0008. Там главное внимательно читать.

    Вообще, когда я обучал своих работников программированию контроллеров, я давал задание — включить светодиод (подключенный к ножке контроллера), использую дебагер, редактирую регистры и читая документацию.

    Модуль RCC. Такирование


    Про этот модуль обычно забывают. Вспоминают только тогда, когда не получается мигнуть светодиодом.

    Запомните! Что бы включить какую-либо периферию, на нее надо подать тактовые импульсы! Без этого никак.

    Порты ввода-вывода сидят на шине APB2. Находим в документации регист для упрвления тактированием этой шины, это RCC_APB2ENR:



    Чтобы включить тактирование порта C (светодиод как раз припаян к PC13), требуется записать в бит IOPCEN единичку.

    Теперь найдем адрес регистра RCC_APB2ENR. Смещение у него 0x18, базовый адрес для регистров RCC 0x40021000.

    Чтобы удобно было работать с битами, создадим структуру:

    typedef struct
    {
      uint32_t  AFIOEN         : 1;
      uint32_t                 : 1;
      uint32_t  IOPAEN         : 1;
      uint32_t  IOPBEN         : 1;
      uint32_t  IOPCEN         : 1;
      uint32_t  IOPDEN         : 1;
      uint32_t  IOPEEN         : 1;
      uint32_t                 : 2;
      uint32_t  ADC1EN         : 1;
      uint32_t  ADC2EN         : 1;
      uint32_t  TIM1EN         : 1;
      uint32_t  SPI1EN         : 1;
      uint32_t                 : 1;
      uint32_t  USART1EN       : 1;
      uint32_t                 :17;
    } RCC_APB2ENR_b;
    

    Чтобы потом не мучаться, сразу перечислим все адреса регистров:

    enum AddrRCC
    {
      RCC_CR          = 0x40021000,
      RCC_CFGR        = 0x40021004,
      RCC_CIR         = 0x40021008,
      RCC_APB2RSTR    = 0x4002100C,
      RCC_APB1RSTR    = 0x40021010,
      RCC_AHBENR      = 0x40021014,
      RCC_APB2ENR     = 0x40021018,
      RCC_APB1ENR     = 0x4002101C,
      RCC_BDCR        = 0x40021020,
      RCC_CSR         = 0x40021024
    };
    

    теперь остается написать код для включения периферии:

    static void EnablePort(uint8_t port_name)
    {
      volatile RCC_APB2ENR_b* apb2enr = reinterpret_cast<RCC_APB2ENR_b*>(RCC_APB2ENR);
      switch (port_name)
      {
        case 'A': apb2enr->IOPAEN = 1; break;
        case 'a': apb2enr->IOPAEN = 1; break;
        case 'B': apb2enr->IOPBEN = 1; break;
        case 'b': apb2enr->IOPBEN = 1; break;
        case 'C': apb2enr->IOPCEN = 1; break;
        case 'c': apb2enr->IOPCEN = 1; break;
        case 'D': apb2enr->IOPDEN = 1; break;
        case 'd': apb2enr->IOPDEN = 1; break;
        case 'E': apb2enr->IOPEEN = 1; break;
        case 'e': apb2enr->IOPEEN = 1; break;
      }
    }
    

    При работе с регистрами не забываем про volatile, иначе после оптимизации компилятором долго будем искать ошибки и ругать разработчиков компилятора.

    Тоже самое делаем для включения тактирвания другой периферии.

    В итоге получился такой класс (не все перечислено):

    STM32F1xx_RCC.h
    
    #pragma once
    #include "stdint.h"
    namespace STM32F1xx
    {
      class RCC
      {
      protected:
        enum AddrRCC
        {
          RCC_CR          = 0x40021000,
          RCC_CFGR        = 0x40021004,
          RCC_CIR         = 0x40021008,
          RCC_APB2RSTR    = 0x4002100C,
          RCC_APB1RSTR    = 0x40021010,
          RCC_AHBENR      = 0x40021014,
          RCC_APB2ENR     = 0x40021018,
          RCC_APB1ENR     = 0x4002101C,
          RCC_BDCR        = 0x40021020,
          RCC_CSR         = 0x40021024
        };
        
        typedef struct {
          uint32_t  HSION          : 1;
          uint32_t  HSIRDY         : 1;
          uint32_t                 : 1;
          uint32_t  HSI_TRIM       : 5;
          uint32_t  HSI_CAL        : 8;
          uint32_t  HSEON          : 1;
          uint32_t  HSERDY         : 1;
          uint32_t  HSEBYP         : 1;
          uint32_t  CSSON          : 1;
          uint32_t                 : 4;
          uint32_t  PLLON          : 1;
          uint32_t  PLLRDY         : 1;
          uint32_t                 : 6;
        } RCC_CR_b;
    		
        typedef struct {
          uint32_t  SW             : 2;
          uint32_t  SWS            : 2;
          uint32_t  HPRE           : 4;
          uint32_t  PPRE1          : 3;
          uint32_t  PPRE2          : 3;
          uint32_t  ADC_PRE        : 2;
          uint32_t  PLLSRC         : 1;
          uint32_t  PLLXTPRE       : 1;
          uint32_t  PLLMUL         : 4;
          uint32_t  USBPRE         : 1;
          uint32_t                 : 1;
          uint32_t  MCO            : 3;
          uint32_t                 : 5;
        } RCC_CFGR_b;
    
        typedef struct
        {
          uint32_t  TIM2EN         : 1;
          uint32_t  TIM3EN         : 1;
          uint32_t  TIM4EN         : 1;
          uint32_t                 : 8;
          uint32_t  WWDGEN         : 1;
          uint32_t                 : 2;
          uint32_t  SPI2EN         : 1;
          uint32_t                 : 2;
          uint32_t  USART2EN       : 1;
          uint32_t  USART3EN       : 1;
          uint32_t                 : 2;
          uint32_t  I2C1EN         : 1;
          uint32_t  I2C2EN         : 1;
          uint32_t  USBEN          : 1;
          uint32_t                 : 1;
          uint32_t  CANEN          : 1;
          uint32_t                 : 1;
          uint32_t  BKPEN          : 1;
          uint32_t  PWREN          : 1;
          uint32_t                 : 3;
        } RCC_APB1ENR_b;
    		
        typedef struct
        {
          uint32_t  AFIOEN         : 1;
          uint32_t                 : 1;
          uint32_t  IOPAEN         : 1;
          uint32_t  IOPBEN         : 1;
          uint32_t  IOPCEN         : 1;
          uint32_t  IOPDEN         : 1;
          uint32_t  IOPEEN         : 1;
          uint32_t                 : 2;
          uint32_t  ADC1EN         : 1;
          uint32_t  ADC2EN         : 1;
          uint32_t  TIM1EN         : 1;
          uint32_t  SPI1EN         : 1;
          uint32_t                 : 1;
          uint32_t  USART1EN       : 1;
          uint32_t                 :17;
        } RCC_APB2ENR_b;
    
        typedef struct {
          uint32_t  DMAEN          : 1;
          uint32_t                 : 1;
          uint32_t  SRAMEN         : 1;
          uint32_t                 : 1;
          uint32_t  FLITFEN        : 1;
          uint32_t                 : 1;
          uint32_t  CRCEN          : 1;
          uint32_t                 :25;
        } RCC_AHBENR_r;
        
      public:
        static void EnablePort(uint8_t port_name)
        {
          volatile RCC_APB2ENR_b* apb2enr = reinterpret_cast<RCC_APB2ENR_b*>(RCC_APB2ENR);
          switch (port_name)
          {
            case 'A': apb2enr->IOPAEN = 1; break;
            case 'a': apb2enr->IOPAEN = 1; break;
            case 'B': apb2enr->IOPBEN = 1; break;
            case 'b': apb2enr->IOPBEN = 1; break;
            case 'C': apb2enr->IOPCEN = 1; break;
            case 'c': apb2enr->IOPCEN = 1; break;
            case 'D': apb2enr->IOPDEN = 1; break;
            case 'd': apb2enr->IOPDEN = 1; break;
            case 'E': apb2enr->IOPEEN = 1; break;
            case 'e': apb2enr->IOPEEN = 1; break;
          }
        }
    
        static void DisablePort(char port_name)
        {
          volatile RCC_APB2ENR_b* apb2enr = reinterpret_cast<RCC_APB2ENR_b*>(RCC_APB2ENR);
          switch (port_name)
          {
            case 'A': apb2enr->IOPAEN = 0; break;
            case 'a': apb2enr->IOPAEN = 0; break;
            case 'B': apb2enr->IOPBEN = 0; break;
            case 'b': apb2enr->IOPBEN = 0; break;
            case 'C': apb2enr->IOPCEN = 0; break;
            case 'c': apb2enr->IOPCEN = 0; break;
            case 'D': apb2enr->IOPDEN = 0; break;
            case 'd': apb2enr->IOPDEN = 0; break;
            case 'E': apb2enr->IOPEEN = 0; break;
            case 'e': apb2enr->IOPEEN = 0; break;
          }
        }
    
        static void EnableAFIO()
        {
          volatile RCC_APB2ENR_b* apb2enr = reinterpret_cast<RCC_APB2ENR_b*>(RCC_APB2ENR);
          apb2enr->AFIOEN = 1;
        }
    
        static void DisableAFIO()
        {
          volatile RCC_APB2ENR_b* apb2enr = reinterpret_cast<RCC_APB2ENR_b*>(RCC_APB2ENR);
          apb2enr->AFIOEN = 0;
        }
        
        static void EnableI2C(int PortNumber)
        {
          switch (PortNumber)
          {
            case 1:
            {
              volatile RCC_APB1ENR_b* apb1enr = reinterpret_cast<RCC_APB1ENR_b*>(RCC_APB1ENR);
              apb1enr->I2C1EN = 1;
              break;
            }
            case 2:
            {
              volatile RCC_APB1ENR_b* apb1enr = reinterpret_cast<RCC_APB1ENR_b*>(RCC_APB1ENR);
              apb1enr->I2C2EN = 1;
              break;
            }
    
          }
        }
    
        static void EnableUART(int PortNumber)
        {
          switch (PortNumber)
          {
            case 1:
            {
              volatile RCC_APB2ENR_b* apb2enr = reinterpret_cast<RCC_APB2ENR_b*>(RCC_APB2ENR);
              apb2enr->USART1EN = 1;
              break;
            }
            case 2:
            {
              volatile RCC_APB1ENR_b* apb1enr = reinterpret_cast<RCC_APB1ENR_b*>(RCC_APB1ENR);
              apb1enr->USART2EN = 1;
              break;
            }
            case 3:
            {
              volatile RCC_APB1ENR_b* apb1enr = reinterpret_cast<RCC_APB1ENR_b*>(RCC_APB1ENR);
              apb1enr->USART3EN = 1;
              break;
            }
    
          }
        }
        
        static void DisableUART(int PortNumber)
        {
          switch (PortNumber)
          {
            case 1:
            {
              volatile RCC_APB2ENR_b* apb2enr = reinterpret_cast<RCC_APB2ENR_b*>(RCC_APB2ENR);
              apb2enr->USART1EN = 0;
              break;
            }
            case 2:
            {
              volatile RCC_APB1ENR_b* apb1enr = reinterpret_cast<RCC_APB1ENR_b*>(RCC_APB1ENR);
              apb1enr->USART2EN = 0;
              break;
            }
            case 3:
            {
              volatile RCC_APB1ENR_b* apb1enr = reinterpret_cast<RCC_APB1ENR_b*>(RCC_APB1ENR);
              apb1enr->USART3EN = 0;
              break;
            }
          }
        }
        
        static void EnableSPI(int PortNumber)
        {
          switch (PortNumber)
          {
            case 1:
            {
              volatile RCC_APB2ENR_b* apb2enr = reinterpret_cast<RCC_APB2ENR_b*>(RCC_APB2ENR);
              apb2enr->SPI1EN = 1;
              break;
            }
            case 2:
            {
              volatile RCC_APB1ENR_b* apb1enr = reinterpret_cast<RCC_APB1ENR_b*>(RCC_APB1ENR);
              apb1enr->SPI2EN = 1;
              break;
            }
          }
        }
    
        static void DisableSPI(int PortNumber)
        {
          switch (PortNumber)
          {
            case 1:
            {
              volatile RCC_APB2ENR_b* apb2enr = reinterpret_cast<RCC_APB2ENR_b*>(RCC_APB2ENR);
              apb2enr->SPI1EN = 0;
              break;
            }
            case 2:
            {
              volatile RCC_APB1ENR_b* apb1enr = reinterpret_cast<RCC_APB1ENR_b*>(RCC_APB1ENR);
              apb1enr->SPI2EN = 0;
              break;
            }
          }
        }
        
        static void EnableDMA()
        {
          volatile RCC_AHBENR_r* ahbenr = reinterpret_cast<RCC_AHBENR_r*>(RCC_AHBENR);
          ahbenr->DMAEN = 1;
        }
        
        static void DisableDMA()
        {
          volatile RCC_AHBENR_r* ahbenr = reinterpret_cast<RCC_AHBENR_r*>(RCC_AHBENR);
          ahbenr->DMAEN = 0;
        }
      };
    }
    


    Теперь можно в main.cpp присоединить файл и пользоваться:

    #include "STM32F1xx_RCC.h"
    
    using namespace STM32F1xx;
    
    int main()
    {
      RCC::EnablePort('c');
      return 0;
    }
    

    Теперь можно и с портами поработать. GPIO


    Открываем в документации раздел General-purpose and alternate-function I/Os. Находим Port bit configuration table:



    Битами CNF[1:0] задается режим работы порта (аналоговый вход, цифровой вход, выход), биты MODE[1:0] отвечат за скорость работы порта в режиме выход.

    Взглянем на регистры GPIOx_CRL и GPIOx_CRH (x=A, B, C,...)



    видно, что биты идут последовательно:

    CNF[1:0], MODE[1:0]

    тогда создадим константы с режимами работы портов

    enum mode_e
    {
      ANALOGINPUT             = 0,
      INPUT                   = 4,
      INPUTPULLED             = 8,
    
      OUTPUT_10MHZ            = 1,
      OUTPUT_OD_10MHZ         = 5,
      ALT_OUTPUT_10MHZ        = 9,
      ALT_OUTPUT_OD_10MHZ     = 13,
    
      OUTPUT_50MHZ            = 3,
      OUTPUT_OD_50MHZ         = 7,
      ALT_OUTPUT_50MHZ        = 11,
      ALT_OUTPUT_OD_50MHZ     = 15,
    
      OUTPUT_2MHZ             = 2,
      OUTPUT_OD_2MHZ          = 6,
      ALT_OUTPUT_2MHZ         = 10,
      ALT_OUTPUT_OD_2MHZ      = 14,
    
      OUTPUT                  = 3,
      OUTPUT_OD               = 7,
      ALT_OUTPUT              = 11,
      ALT_OUTPUT_OD           = 15
    };

    тогда метод для конфигурации будет выглядеть так:

    // pin_number - номер порта
    void Mode(mode_e mode)
    {
      uint32_t* addr;
      if(pin_number > 7)
        addr = reinterpret_cast<uint32_t*>(GPIOA_CRH);
      else
        addr = reinterpret_cast<uint32_t*>(GPIOA_CRL);
      
      int bit_offset;
      if(pin_number > 7)
        bit_offset = (pin_number - 8) * 4;
      else
        bit_offset = pin_number * 4;
    
      uint32_t mask = ~(15 << bit_offset);
      *addr &= mask;
      *addr |= ((int)mode) << bit_offset;
    }

    теперь можно сделать более удобные методы для выбора режима:

        void ModeInput()              { Mode(INPUT);         }
        void ModeAnalogInput()        { Mode(ANALOGINPUT);   }
        void ModeInputPulled()        { Mode(INPUTPULLED);   }
        void ModeOutput()             { Mode(OUTPUT);        }
        void ModeOutputOpenDrain()    { Mode(OUTPUT_OD);     }
        void ModeAlternate()          { Mode(ALT_OUTPUT);    }
        void ModeAlternateOpenDrain() { Mode(ALT_OUTPUT_OD); }

    В документации находим адреса управляющих регистров для портов и перечислим:

    enum AddrGPIO
    {
      PortA           = 0x40010800,
      GPIOA_CRL       = 0x40010800,
      GPIOA_CRH       = 0x40010804,
      GPIOA_IDR       = 0x40010808,
      GPIOA_ODR       = 0x4001080C,
      GPIOA_BSRR      = 0x40010810,
      GPIOA_BRR       = 0x40010814,
      GPIOA_LCKR      = 0x40010818,
      PortB           = 0x40010C00,
      PortC           = 0x40011000,
      PortD           = 0x40011400,
      PortE           = 0x40011800,
      PortF           = 0x40011C00,
      PortG           = 0x40012000
    };

    Долго думал использовать базовый адрес и смещения или абсолютные адреса. В итоге остановился на последнем. Это добавляет некоторые издержки, но в процессе отладки удобней находить в памяти.

    Модернизируем метод:

    if(pin_number > 7)
      addr = reinterpret_cast<uint32_t*>(GPIOA_CRH - PortA + PortAddr);
    else
      addr = reinterpret_cast<uint32_t*>(GPIOA_CRL - PortA + PortAddr);

    Возможно, у кого-то будет глаз дергаться, но красивее пока не придумал.

    Чтобы перевести ножку в нужное логическое состояние, достаточно записать соответствующий бит в регистре ODRx. Например, так:

    void Set(bool st)
    {
      uint32_t* addr;
      addr = reinterpret_cast<uint32_t*>(GPIOA_ODR - PortA + PortAddr);
    
      if(st)
        *addr |= 1 << pin_number;
      else
      {
        int mask = ~(1 << pin_number);
        *addr &= mask;
      } 
    }

    Также для управления состоянием можно воспользоваться регистрами GPIOx_BSRR.
    По аналогии делаем методы для считывания состояния порта, методы для конфигурации и инициализации (не забываем включить тактирование). В итоге получился такой класс для работы с портами:

    STM32F1xx_Pin.h
    #pragma once
    #include <stdint.h>
    #include "STM32F1xx_RCC.h"
    
    namespace STM32F1xx
    {
      class Pin
      {
      public:
        enum mode_e
        {
          ANALOGINPUT             = 0,
          INPUT                   = 4,
          INPUTPULLED             = 8,
    
          OUTPUT_10MHZ            = 1,
          OUTPUT_OD_10MHZ         = 5,
          ALT_OUTPUT_10MHZ        = 9,
          ALT_OUTPUT_OD_10MHZ     = 13,
    
          OUTPUT_50MHZ            = 3,
          OUTPUT_OD_50MHZ         = 7,
          ALT_OUTPUT_50MHZ        = 11,
          ALT_OUTPUT_OD_50MHZ     = 15,
    
          OUTPUT_2MHZ             = 2,
          OUTPUT_OD_2MHZ          = 6,
          ALT_OUTPUT_2MHZ         = 10,
          ALT_OUTPUT_OD_2MHZ      = 14,
    
          OUTPUT                  = 3,
          OUTPUT_OD               = 7,
          ALT_OUTPUT              = 11,
          ALT_OUTPUT_OD           = 15
        };
        
      private:
        enum AddrGPIO
        {
          PortA           = 0x40010800,
          GPIOA_CRL       = 0x40010800,
          GPIOA_CRH       = 0x40010804,
          GPIOA_IDR       = 0x40010808,
          GPIOA_ODR       = 0x4001080C,
          GPIOA_BSRR      = 0x40010810,
          GPIOA_BRR       = 0x40010814,
          GPIOA_LCKR      = 0x40010818,
          PortB           = 0x40010C00,
          PortC           = 0x40011000,
          PortD           = 0x40011400,
          PortE           = 0x40011800,
          PortF           = 0x40011C00,
          PortG           = 0x40012000
        };
        
      private:
        int   pin_number;
        int   PortAddr;
        
      public:
        Pin()                               { }
        Pin(char port_name, int pin_number) { Init(port_name, pin_number); }
        ~Pin()
        {
          Off();
          ModeAnalogInput();
        }
      public:
        void Init(char port_name, int pin_number)
        {
          this->pin_number = pin_number;
          RCC::EnablePort(port_name);
          switch (port_name)
          {
            case 'A': PortAddr = PortA; break;
            case 'a': PortAddr = PortA; break;
            case 'B': PortAddr = PortB; break;
            case 'b': PortAddr = PortB; break;
            case 'C': PortAddr = PortC; break;
            case 'c': PortAddr = PortC; break;
            case 'D': PortAddr = PortD; break;
            case 'd': PortAddr = PortD; break;
            case 'E': PortAddr = PortE; break;
            case 'e': PortAddr = PortE; break;
          }
        }
    
        void ModeInput()              { Mode(INPUT);         }
        void ModeAnalogInput()        { Mode(ANALOGINPUT);   }
        void ModeInputPulled()        { Mode(INPUTPULLED);   }
        void ModeOutput()             { Mode(OUTPUT);        }
        void ModeOutputOpenDrain()    { Mode(OUTPUT_OD);     }
        void ModeAlternate()          { Mode(ALT_OUTPUT);    }
        void ModeAlternateOpenDrain() { Mode(ALT_OUTPUT_OD); }
    
        void NoPullUpDown()
        {
          uint32_t* addr;
          if(pin_number > 7)
            addr = reinterpret_cast<uint32_t*>(GPIOA_CRH - PortA + PortAddr);
          else
            addr = reinterpret_cast<uint32_t*>(GPIOA_CRL - PortA + PortAddr);
    
          int bit_offset;
          if(pin_number > 7)
            bit_offset = (pin_number - 8) * 4;
          else
             bit_offset = pin_number * 4;
    
          int mask = ~((1 << 3) << bit_offset);
          *addr &= mask;
        }
        
        void Mode(mode_e mode)
        {
          uint32_t* addr;
          if(pin_number > 7)
            addr = reinterpret_cast<uint32_t*>(GPIOA_CRH - PortA + PortAddr);
          else
            addr = reinterpret_cast<uint32_t*>(GPIOA_CRL - PortA + PortAddr);
          
          int bit_offset;
          if(pin_number > 7)
            bit_offset = (pin_number - 8) * 4;
          else
            bit_offset = pin_number * 4;
    
          uint32_t mask = ~(15 << bit_offset);
          *addr &= mask;
          *addr |= ((int)mode) << bit_offset;
        }
    
        void Set(bool st)
        {
          uint32_t* addr;
          addr = reinterpret_cast<uint32_t*>(GPIOA_ODR - PortA + PortAddr);
    
          if(st)
            *addr |= 1 << pin_number;
          else
          {
            int mask = ~(1 << pin_number);
            *addr &= mask;
          } 
        }
    
        void On()
        {
          uint32_t* addr;
          addr = reinterpret_cast<uint32_t*>(GPIOA_ODR - PortA + PortAddr);
          int bit_offset = pin_number;
          *addr |= 1 << bit_offset;
        }
    
        void Off()
        {
          uint32_t* addr;
          addr = reinterpret_cast<uint32_t*>(GPIOA_ODR - PortA + PortAddr);
          int bit_offset = pin_number;
          int mask = ~(1 << bit_offset);
          *addr &= mask;
        }
    
        bool Get()
        {
          uint32_t* addr = reinterpret_cast<uint32_t*>(GPIOA_IDR - PortA + PortAddr);
          int bit_offset = pin_number;
          int mask = (1 << bit_offset);
          bool ret_val = (*addr & mask);
          return ret_val;
        }
      };
    };
    


    Ну что, опробуем:
    #include "STM32F1xx_Pin.h"
    
    using namespace STM32F1xx;
    
    Pin led('c', 13);
    
    int main()
    {
      led.ModeOutput();
      led.On();
      led.Off();
      return 0;
    }
    

    Проходим дебагером и убеждаемся, что светодиод сначала загорается (после led.ModeOutput();), потом гаснет (led.On();) и снова загорается (led.Off();). Это связано с тем, что светодиод подключен к ножке через линию питания. Поэтому, когда на выводе низкий уровень, светодиод загорается.

    Не большие итоги


    В данной статье я попытался (надеюсь, получилось) показать как можно немного упростить себе жизнь, сделать код более читаемым. Или наоборот — как нельзя делать. Каждый решит сам.
    Можно было просто написать враперы для CMSIS, но это не интересно.

    Спасибо за уделенное время. Если интересно продолжение — дайте знать.
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 43

      +5
      Мне кажется, что stm32 вполне заслуживает более интересной работы, чем мигание светодиодом. Это конечно уважаемая, исторически сложившаяся, традиционная так, сказать работа для микроконтроллеров, но хотелось бы чего-нибудь посвежее.
      А так, да добро пожаловать в клуб любителей STM -ок на ХАБРе.
        0
        Спасибо, это только начало.
          +4
          даже на младших STM32 которые запитаны от часовой батарейки можно сделать плавный классный интерфейс с анимированными свайпами, да разрешение будет не VGA и цветов очень мало но он будет летать!
          а на старшие STM32 вполне успевают обрабатывать видео налету и принимать решения.
          топовые же STM32 тянут полновесные CNN сетки типа MobileNetV2 и говорить можно не о секундах на кадр а о FPS, естественно всё в реалтайме.
          Но дальше мигания светодиода, почему то почти никто не рассказывает.
          Обидно. Мне кажется что у многих «аллергия на мигание светодиодом» просыпается.

          Я бы для стартового проекта поднял бы уарт. Почему? Любой язык начинается с Hello world а для этого нужен printf, но для printf нужен uart. Если есть хотя-бы он то всё остальное сделать в разы проще. Я всегда начинаю с уарта и printf/scanf, будь то FPGA, MCU или малинка в baremethal. Более того простой printf/scanf можно вполне впихнуть в пару тыщь байт или LUT/reg ячеек. Т.е. он влезет КУДА УГОДНО и ещё останется чтоб поднять и наладить любую другую периферию. А подымать и настраивать периферию удобнее если ты можешь в реалтайме выводить события, состояния регистров в бинарном хекс и десятичном значении и видишь всю динамику такой какой она есть. Да и поднять уарт в разы быстрее чем довести до ума отладчик и тд. Uart это логи! Это удобные текстовые команды устройству в реалтайме без перекомпиляции (для FPGA это часы и сутки!), это не только экономия времени и нервов а ещё и видиние реальной работы устройства не прерывая и не останавливая переферию дебаггером!
          Не смотрите через замочную скважину светодиода на мир, откройте чёртову дверь UARTa!
          Не забывайте про уарт, uart хороший, uart ваш друг!

          Добавлено: Sdima1357 Надеюсь моя uart «доктрина» заслуживает STM32 больше чем мигание светодиодом?
            0

            Есть классы для uart, spi, can. К примеру, для urat сделан modbus rtu, wake.

              0
              Мой отдельный плюсик за CAN, буду очень рад
              0
              «добавлено для sdima»
              Ну наверное. Я например на нем Синклер — Спектрум эмулятор habr.com/ru/post/412325 писал для начала.
              То есть светодиод ту меня был конечно, но я решил не делиться таким результатом.
              Тут много неплохих статей на про stm на хабре
            +1

            Отличная статья, продолжай в том же духе!

              0
              Ок. Буду готовить следующую.
                0
                Только просьба — проверяйте правописание, «оно хорошее, но почему-то хромает».
              +8
              Не совсем вас понял.
              В начале вы пишите:
              Когда я попытался сделать первый проект меня ждало разочарование — CMSIS! Кому как, но для меня это было (и остается) ужасом: много буков, длинные и для меня не понятные структуры. Вникать во все это было не интересно.

              А ближе к концу углубляетесь еще «ниже» и пишете свои структуры, причем оперируйте адресами регистров, которые в CMSIS уже прописаны.
              В ваши структуры тоже нужно будет другим вникать.
              Смысл?
                0

                Одно дело пользоваться этими структурами в пользовательский части кода, другое дело, когда они спрятаны внутри классов.

                  +1
                  Отвечу за автора — CMSIS для разраба, избалованного Python'ом или JS выглядит как шестиглавая медуза, у которой при каждом побежденном методе отрастает десять регистров, пять адресов в памяти со смещением и volatile. И, главное — ничерта не понятно, зачем оно все нужно и как работает.
                  +4
                  Не знаю, в каком году ты начинал с ним работать, но уже несколько лет есть генератор проектов CubeMX и таким велосипедостроением заниматься уже просто неприлично.
                    +3
                    В целом я с Вами согласен, но иногда он (cube) генерит такую индусскую муть, что просто страшно…
                      –1
                      А зачем на нее смотреть, если все работает как нужно?
                        +2
                        Не всегда, не всегда. Это пока светодиодик, то оно конечно отлично, а вот что посложнее…
                        Навскидку USB host,Ethernet,sd card, не работают как положено, и полно багов.
                        Вот тогда и приходится ковыряться
                          +1
                          Когда не работает, ковыряться придется в любом случае.
                          Только в первом случае, придется ковыряться в исходниках компании разработчика, имея возможность найти решение проблемы в интернете. Если с ним кто-то уже сталкивался.

                          И совсем другое дело, когда проблемы возникают с посторонней библиотекой, которую кроме автора мало кто использует.
                            +1
                            Это да конечно, но похоже что на софте там пару человек и те из Индии.
                            Железячники у них отличные, а программисты так себе или просто очень сильно перегружены
                            0
                            Ну пока светодиодик, можно и ручками писать, а вот когда нужно cделать что то приличное, вот смотрите как это делали мы в Embox
                              0
                              Ну да вполне приличная ОС и статьи интересные
                              +1
                              У ST есть рабочие примеры подо всю периферию. Там смотри.

                              www.st.com/en/embedded-software/stsw-stm32068.html

                              Семейство нужное выбирай и качай.
                                +1
                                Это не так. Оно там далеко не все работает.там часто код например под IAR работает а под GCC нет. Например забудут сегмент cache free определить, и ты неделю другую не понимаешь почему иногда приходят испорченные пакеты в Ethernet. это как правило незаметно, но иногда полсекунды задержка в передаче
                            +2
                            Работает? Обновляется? Переносится на другие контроллеры семейства? Сколько самодельным библиотекам до этого?
                            +1

                            Поддержу, Sdima1357 Cube генерит столько кода… чтобы поморгать светодиодом. Это универсальная библиотека для быстрого прототипирования, она не годится для нормальных продуктов, связанных с надежностью.
                            Быстро что-то проверить -самое то, но писать продуктовый код на ней не лучшее решение (ИМХО).

                              –1
                              Это универсальная библиотека для быстрого прототипирования, она не годится для нормальных продуктов, связанных с надежностью.

                              Пример «нормальных продуктов» в студию. Обычно такие аргументы у диванных теоретиков.
                                +1

                                Да любой промышленныйи датчик. Ну вот, например, https://www.emerson.ru/ru-ru/catalog/metran-150-ru-ru
                                Вы ни одного сертификата безопасности не получите с авто-сгенерировангой ерундой из Cube. Либо придется всю эту лапшу проверять юнит тестами… Вы как себе это представляете?

                                  –3
                                  Датчик давления. Ясно. Я уж думал что-нибудь вроде контроллера в газовой котельной будет.

                                  Вы ни одного сертификата безопасности не получите с авто-сгенерировангой ерундой из Cube.

                                  Ну открой сертификаты твоего же датчика и найди там упоминание госта на говнокод. Его серьифицируют по точности измерений, рабочим температурам, электромагнитным помехам и прочим параметрам, измеряемым снаружи. В код к нему никто не лазит.

                                  Либо придется всю эту лапшу проверять юнит тестами… Вы как себе это представляете?

                                  Советуешь без тестов обходиться?
                                    +2
                                    Датчик давления. Ясно. Я уж думал что-нибудь вроде контроллера в газовой котельной будет.

                                    А в чем отличие? Контроллеры тоже есть и другие устройства… Вы просили пример, я дал, могу еще....


                                    Ну открой сертификаты твоего же датчика и найди там упоминание госта на говнокод

                                    Я знаю как их сертифицируют. А вы знаете? Есть много сертификаций. Вот например на соответствие этому ГОСТу:
                                    ГОСТ Р МЭК 61508-2012, на самом деле просто перевод IEC 61508
                                    А я вам скажу, лезут в код, подписывают NDA и лезут везде, смотрят покрытие всех требований к функциональной безопасности в архитектуре, юнит тестах, коде, смотрят отчеты линта и аргументы по бану некоторых сообщений. Вся методика там в ГОСТе и описана. А приведенный в пример датчик имеет такой сертификат.


                                    Сертификация соответствия требованиям международного стандарта функциональной безопасности IEC 61508 с уровнем полноты безопасности SIL 2 (SIL 3 при соблюдении требований) с предоставлением отчета анализа отказов, их последствий и диагностики (FMEDA)

                                    И если вы думаете это фигня, то имейте ввиду, что датчиков на этой платформе уже выпущено 1 млн. Мало того, вам после того как вы сертификат получили, код, связанный с функциональной безопасностью менять нельзя. Иначе снова показывать код, аудит и подтверждение сертификата.


                                    У STM есть специальная библиотека для тестирования ядер микроконтроллеров, так как аппаратная часть микроконтроллера относится к сефети критикал параметрам. Вот она имеет сертификат, ее можно в нормальных продуктах использовать, а не поделках. Но там нет ничего связанного с генерацией кода, эта библиотека пришита гвоздями (SHA256) при сертификации и вы не можете ничего там менять. А вот и сам сертификат https://www.st.com/resource/en/certification_document/x-cube-stl_certification_tuv_rheinland.pdf Да и там нет ни HAL ни макросов, в основном все на ассемблере. Прочитать тут можете:
                                    https://www.st.com/content/st_com/en/functionalsafety.html
                                    И тут
                                    https://www.st.com/content/st_com/en/products/embedded-software/mcu-mpu-embedded-software/stm32-embedded-software/stm32cube-expansion-packages/x-cube-stl.html
                                    Поэтому Cube CMSIS и вся автогенерация, это все для быстрого прототипирования, поделок и ширпотреба.


                                    Советуешь без тестов обходиться?

                                    Нет, ну просто представляю, сколько надо времени, чтобы HAL покрыть, да даже просто I2C, там же черт ногу сломит с бранчами. Она универсальная, а как любая универсальная вещь избыточна. Там для реального проекта нужно может только 2 простых метода, а придется всю лапшу покрывать тестами, иначе покрытия не будет и не зачтется вам эта библиотека и сертификат вы не получите.


                                    А такая же фигня еще и с RTOS творится. FreeRTOS никто в нормальных продуктах тоже использовать не сможет, зато ее клон SafeRTOS, пожалуйста.
                                    https://www.highintegritysystems.com/safertos/


                                    В общем, не для промышленных целей она сделана была, а для упрощения вхождения в разработку на микроконтроллере, быстрого прототипирования, проверки идей.

                              +1

                              А кубик поддерживает миландровские контроллеры?

                                –1
                                Их даже миландр нормально не поддерживает. У них должна отличаться периферия и поэтому драйверы не будут совместимы.
                              +2
                              Ну вот опять, кто-то пытается победить CMSIS новым методом. А метод оказывается старым, уже не актуальным на данный момент.
                              Оборачивание регистров в битовые структуры — не уменьшает количество Си кода, и значительно увеличивает размер бинарного кода. Метод от самой ST — тоже болеет этой фигнёй. Идеального решения не существует, и никакие библиотеки с новыми методами тут не помогут. Если в регистре есть 32 отдельных поля для записи — то все 32 поля придётся описать. Описание должно быть в одном месте, без размазывания по всему проекту. Хотя данное требование не относится ко всему что есть в мк.

                              Откройте для себя макросы _VAL2FLD и _FLD2VAL, это позволит частично сократить чтение документации, и значительно сократить время поиска ошибок. Основной бонус этих макросов — отсутствие дополнительного слоя абстракции, CMSIS используется как есть — в собственном соку.
                                +2
                                Очень хорошая статья на тему, как делать не нужно. На самом деле очень даже опасная — подобные статьи читают в основном новички, начинают делать также, а потом их приходится бить по рукам.

                                1) Вообще, после фразы «Когда я попытался сделать первый проект меня ждало разочарование — CMSIS! Кому как, но для меня это было (и остается) ужасом: много буков, длинные и для меня не понятные структуры. » программиста можно выгонять с собеседования, так как это явно не эмбеддер, язык С он точно не знает, как следствие и С++ тоже.
                                Ну да:
                                RCC_APB2ENR_bit.ADC1EN = 1; 
                                RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;


                                Эти записи такие разные, явно стоит вендорлока, ещё и свой аналог для описания регистров нагородим. Вы же его автоматически генерируете? И тесты у вас есть?

                                2) IAR — компилятор для профессионалов(нет). Только в заголовке будет запись типа:
                                #error This file should only be compiled by ARM IAR compiler and assembler
                                IAR платный, а вы не оставляете мне возможности собрать проект другим компилятором. Может быть мне его купить за 2-3к$? А у вас он куплен?
                                Под линукс он не поставляется, наверное, придется ещё и винду покупать. Ну да ладно: «Я привык работать в IAR. Да, есть другие IDE, но мне хватает возможности IAR».

                                3) Как правило, начинающие разработчики изучают С++ со стороны ООП и это вполне себе оправданно, но это не серебряная пуля. C++ — мультипарадигмальный язык программирования, наиболее мощными возможностями которого являются метапрограммирование и программирование в пространстве типов, позволяющие сгенерировать оптимальный код, который так важен в эмбеддед.
                                Все расчеты в коде, который вы привели, можно выполнить на этапе компиляции, сведя все к записям в регистры GPIO, у вас это все считается в рантайме. А если у вас по ТЗ время перехода МК в некое состояние 1мс, при этом настроить нужно 200 ног?
                                Посчитать на этапе компиляции маски можно вот так

                                Использование будет примерно таким:
                                struct LedTrait final: mpp::gpio::LedTrait
                                {
                                    constexpr static mpp::gpio::Inversion kInversion = mpp::gpio::Inversion::Off;
                                };
                                //...
                                using LedBlue   = mpp::gpio::Gpio < mpp::gpio::PD15, LedTrait >;
                                //...
                                using Leds = mpp::gpio::IoGroup < LedBlue, LedRed, LedOrange, LedGreen >;


                                А помигать можно как-то так :
                                Leds::Toggle();


                                Представленный код прошу рассматривать только с академической точки зрения как пример возможностей современнего С++.

                                Возможно, вам стоит изучить язык более досконально, С++20 рановато брать, а вот С++17 давно мейнстрим. Уже потом учить работников и писать статьи.
                                  0
                                  Очень хорошая статья на тему, как делать не нужно

                                  Напишите как нужно. Будем учиться по Вашим статьям. Только не отсылайте читать других авторов, ведь интересен Ваш подход и консультироваться с Вами.
                                  начинают делать также, а потом их приходится бить по рукам.

                                  Не повезло Вашим подчиненными, если таковые имеются.
                                  программиста можно выгонять с собеседования, так как это явно не эмбеддер, язык С он точно не знает, как следствие и С++ тоже.

                                  да, хорошо чувствовать превосходство. Только когда появляется на собеседовании человек, который покажет, что Вы плохо знаете, то его тоже отсеете?
                                  Эти записи такие разные, явно стоит вендорлока, ещё и свой аналог для описания регистров нагородим. Вы же его автоматически генерируете? И тесты у вас есть?

                                  Вы о чем?
                                  и да, CMSIS тестируют на пользователях, не просто так вссплывают опечатки и баги.
                                  2) IAR — компилятор для профессионалов(нет). Только в заголовке будет запись типа:
                                  #error This file should only be compiled by ARM IAR compiler and assembler
                                  IAR платный, а вы не оставляете мне возможности собрать проект другим компилятором. Может быть мне его купить за 2-3к$? А у вас он куплен?
                                  Под линукс он не поставляется, наверное, придется ещё и винду покупать. Ну да ладно: «Я привык работать в IAR. Да, есть другие IDE, но мне хватает возможности IAR».

                                  где Вы в коде нашли не совместимость с другими компиляторами? я порекомендовал IAR, т.к. мне он показался удобней. но это не означает, что все поголовно должны перейти на него.
                                  у меня iar не куплен, лицензия (как написано в тексте) ограничение кода. для дома и семьи хватает.
                                  За лицензию платит заказчик, когда я отдаю исходники. И вообще, почему хороший инструмент должен быть бесплатным?
                                  3) Как правило, начинающие разработчики изучают С++ со стороны ООП и это вполне себе оправданно, но это не серебряная пуля. C++ — мультипарадигмальный язык программирования, наиболее мощными возможностями которого являются метапрограммирование и программирование в пространстве типов, позволяющие сгенерировать оптимальный код, который так важен в эмбеддед.

                                  так и расскажите об этом новичкам!
                                  Все расчеты в коде, который вы привели, можно выполнить на этапе компиляции, сведя все к записям в регистры GPIO, у вас это все считается в рантайме.

                                  можно. каждый выбирает свой путь
                                  А если у вас по ТЗ время перехода МК в некое состояние 1мс, при этом настроить нужно 200 ног?

                                  не попадалось. не уверен, что на все случаи жизни можно сделать что-то универсальное.
                                  А помигать можно как-то так :

                                  посмотрел Ваш стиль написания. Красиво. Молодец.
                                  void board::Init()
                                  {
                                    <b>RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN;</b>
                                  	
                                    board::Systick::Init();
                                    board::ClockCounter::Init();
                                    board::Leds::Init();
                                  	
                                    return;
                                  }

                                  вот только зачем в области пользователя доступ к регистрам?
                                  или если отвечать Вашим стилем: за это надо мордой об экран
                                  Представленный код прошу рассматривать только с академической точки зрения как пример возможностей современнего С++.

                                  можно даже пользоваться, ничего криминального в коде нет
                                  Возможно, вам стоит изучить язык более досконально, С++20 рановато брать, а вот С++17 давно мейнстрим.

                                  буду стараться
                                  Уже потом учить работников и писать статьи.

                                  а кто будет учить работников? ждать Вас? мне нужны специалисты сейчас.
                                  А вот от Вас ждем интересный статей. Я серьезно, поделитесь опытом.
                                    +1
                                    Напишите как нужно. Будем учиться по Вашим статьям. Только не отсылайте читать других авторов, ведь интересен Ваш подход и консультироваться с Вами.

                                    В коде вы используете битовые поля, порядок размещения бит в которых не регламентируется стандартом, т.е. является implementation-defined.

                                    Отсюда, работа с регистрами, требующими строгий порядок бит, через битовый поля выливается в undefined behavior.

                                    Это распространенная ошибка. Ещё чаще используют пакованые структуры, которых в стандарте нет и никогда не будет, для «сериализации» пакетов данных,. Это опять приводит к undefined behavior, привязывающий код к конкретной версии компилятора, на котором он хоть как-то «работает».
                                    Эти подходы нельзя использовать категорически. Вместо битовых полей необходимо использовать маски, а вместо пакованых структур писать полноценные сериализаторы.

                                    Так как IAR использует методику работы с регистрами через битовые поля у себя в библиотеках, то работоспособность подхода гарантируется разработчиками компилятора. Однако другие компиляторы такой гарантии не дают, в этом смысл моих слов о вендор локе, качестве вашего кода и статьи в целом.

                                    Мы эмбеддеры, наша задача — писать надежный код, строго соответствующий стандарту. Давайте оставим право на говнокод ардуинщикам.

                                    и да, CMSIS тестируют на пользователях, не просто так вссплывают опечатки и баги.

                                    CMSIS и svd генерируются автоматически по verilog-модели. С 2013г. я встречал ошибку только один раз в заголовочнике для stm32f411хе.

                                    вот только зачем в области пользователя доступ к регистрам?

                                    Я пока думаю, как это оформить: через профили питания или просто передавать в трейте конфигуратору PLL.
                                    Во всяком случае, код в файлах board платформозависимый, в нем допускается использовать подобный код.
                                      0

                                      Про битовые поля везде пишут, что не стандартизовано, но при этом компиляторы, которыми я пользовался, вполне предсказуемо компилировали код. Это IAR, Keil, GCC, Visual Studio, code composer studio (TI). Даже интерфейсы с битовыми полями между SystemC и systemVerilog не вызывали проблем.
                                      Для сериализации — да, могут быть нюансы. Но опять же надо понимать, что требуется от компилятора.
                                      Использовать define можно для битовых масок и операций, но читаемость кода исчезает. Да и вообще, моё мнение — define в коде это зло.
                                      В CMSIS были неувязочки с битами, которые отличались от документации. Это замечал и в библиотеках IAR (вроде, DR <>DRR в usart, точно не помню, давно было).
                                      Да и вообще, если открыть community.arm.com там раньше люди писали об ошибках в CMSIS. Правда, давненько туда не заглядывал.


                                      Про ардуинщиков да, много интересного (забавного) можно увидеть в коде. Но с другой стороны — есть много проектов, которые просто работают. Пока я терял время на подготовку своего кода, у ардуинщиков уже все было готово. Ну и пусть они потом 40 раз исправляют косяки, но клиент уже у них.


                                      Иногда ознакамливаюсь с библиотеками для ардуино, когда надо быстро разобраться с каким-нибудь девайсом или дисплеем со встроенным контроллером. Жесть, но взять последовательность цифр для инициализации полезно, хотя-бы на начальном этапе.


                                      И да, я не эмбеддер, я электроник. Программировать пришлось из-за того, что с программистами (которые мне попадались) тяжко договориться. Например, при ошибке программист считает, что программа должна остановиться или вывести модальное окно с ошибкой. А мне в это время надо отладить железку. И вот я руками держу щупы осциллографа, ногой включаю питание, а прога вывела ошибку и остановилась. И как настраивать?..
                                      Да, абстрагироваться до последних версий c++ у меня уже мозга не хватает, но некоторые моменты освоил. Решил поделиться в статье.
                                      Скоро будет продолжение. Рад буду конструктивно обсудить что можно улучшить или упростить.

                                    +1
                                    Под линукс он не поставляется, наверное, придется ещё и винду покупать

                                    Уже вот вот будет… :) Вроде даже уже есть, но пока мне еще не дали.


                                    Посчитать на этапе компиляции маски можно вот так

                                    Там у вас тоже странности в коде есть


                                    inline constexpr static void Init() noexcept(true) {
                                              GPIO_TypeDef* regs { reinterpret_cast<GPIO_TypeDef*>(kPort) };

                                    Не будет это работать на этапе компиляции, можно убирать constexpr
                                    Но прикольно, только если это все руками писать, то накладно… если бы генерилка была какая.

                                      0
                                      Там у вас тоже странности в коде есть

                                      Ревью небыло, так что может что-то подобное всплыть. К сожалению, на текущий момент ключ --pedantic с этим не поможет.

                                      Уже вот вот будет… :) Вроде даже уже есть, но пока мне еще не дали.

                                      Учитывая наличие пакетов типа arm-none-eabi-gcc или аналогичных во всех дистрибутивах, необходимость стремится к минимуму.
                                      Единственное, для некоторых работ может быть нужен сертифицированный компилятор, в таком случае можно рассмотреть IAR или GreenHills. Но это будет одна единственная лицензия для билд-сервера.

                                      Но прикольно, только если это все руками писать, то накладно… если бы генерилка была какая.

                                      Этот момент можно рассмотерть:
                                      1. Многие вендоры делают различные конфигураторы для периферии, например, тот же cubemx от ST. В коробке с этими конфигураторами идут файлы с описанием периферии, настоятельно рекомендую заглянуть в папку /db/mcu/IP/
                                        Эти файлы применими как минимум для получения ограничителей/constraints библиотечным модулем или генератором. Именно их я использую для генерации списка пинов.
                                      2. Не для всякой периферии такая генерация необходима. Вендоры часто консервативны и, как минимум, в пределах серии сохраняют один и тот же набор доступных польхователю регистров. В этом случае затраты для написания шаблона под генератор будут идентичны затратам на написание библиотечного модуля.
                                      3. Есть уникальная периферия, например PLL, вот здесь генератор может пригодится. Но шаблон для генератора придется писать., что для какого-нибудь stm32h7 задача крайне не тривиальная.


                                      Генераторы для PLL я точно писать буду, но несколько позже. В первую очередь я планирую доделать модули и примеры, не зависящие от железа:
                                      • инжекция конрольных сумм прошивкки в саму прошивку (уже готово)
                                      • Бинарный логгер, заточенный специально под эмбеддед. Есть интересная идея, как организовать печать строк с форматированием, но при этом не хранить их в прошике и не заниматься форматированием значений в рантайме.
                                    –2

                                    Открываю статью, вижу IAR — закрываю статью.


                                    Почему? Потому что IAR и Keil — пропиаритарны.
                                    Многие возразят: есть же free, с ограничением по размеру кода… или условиям использования…
                                    Отвечаю: есть, но я не хочу приучаться к системе, которая в случае разрастания кода или не дай бог комерциализации моего проекта может потребовать несколько тысяч долларов — сразу.


                                    Я много раз пытался завести STM на Qt+GCC ARM под винды, но ниразу у меня не вышло. (под линух смысла нет, т. к. основная ОС — для моих любительских и учебных проектов — виндовс) Счас мне напихают — Qt тоже пропиоритарен. Отвечу на сколько последний раз сталкивался: Community подразумевает требование публикации исходных кодов даже для коммерческих продуктов. Меня такой подход — на данном этапе возможной комерциализации моих проектов — устраивает.


                                    Последний раз я дрюкался с какой то IDE от самих STM… Вроде работает… Но в STM так и не вступил, и не только из-за геморности с IDE, но и отсутствием подходящего проекта… Простите, но моргать диодиком я уже на AVRкак в доврдуиновскую эпоху наморгался.


                                    Кто нибудь! Напишите нормальную статью именно по старту, для тупых магистров/аспирантов. А то не дай бог, честное слово! самому прийдется начинать писать!

                                      +1
                                      Открываю статью, вижу IAR — закрываю статью...

                                      захожу в магазин, вижу Windows на ноутбуке — выхожу из магазина…
                                      Если Вы учитесь — производители предлагают бесплатно попользоваться, если Вы зарабатыаете деньги, почему другие не должны это делать.
                                      Если Вы обучаете студентов — обратитесь к разработчикам, возможно, есть лицензия для ВУЗов.
                                      А то что бесплатно — придется вручную прикручивать. Тут ничего не поделать.
                                        0

                                        IAR использует Clang, поэтому вполне себе нормальный он. Основное преимущество, что поддержка есть со стороны производителя и сертификаты и это дефакто стандарт для промышленных компаний (по крайней мере на западе). У него есть 30 кБайтная версия для обучения.
                                        Вообще без разницы на чем делать, код написанный на С++17 должен на любом компиляторе собираться. Там есть тонкости с прагмами и заданием сегментов и встроенных функций, но они минимальны. Все отличия можно в одном файле держать.
                                        Поэтому насчет компиляторов, тут без разницы — для встроенщиков осталось всего два вида Clang и GCC и работать такой код должен везде. А IDE можно любую к ним прикрутить....

                                          0

                                          В том то и проблема с "быстрым стартом", либо IDE в комплекте с компилятором — платная, либо нужно с бубном попрыгать, что-бы "helloworld" запустить.
                                          И это в случае с STM/ARM. С AVRовским семейством от микрочипа — таких проблем нет.
                                          Я уж не говорю про миландровские "клоны STM f103" — эти только на IARe у нас в конторе смогли запустить.

                                            0

                                            У них библиотека для дебагера отличается. Вроде, для keil тоже есть, не помню.
                                            Делал на миландр проект, как раз в iar. Жаль, что для stlink ничего не сделали, только j-link. Сначала чип понравился, заработало почти все и сразу. Но потом повылезали нюансы, связанные с особенностью чипа. Errata достаточно объёмная (хорошо или плохо), не все проблемы можно решить. С can там танцы с бубном. Недавно коллеги по несчастью выяснели — частота уходит, будем на неделе изучать феномен.

                                            0

                                            Самое интересное, что 30 КБ это ограничение на код. На константы, хранимые во flash (например, картинки) я не заметил.

                                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                        Самое читаемое