...

воскресенье, 15 ноября 2015 г.

Переходим с STM32 на российский микроконтроллер К1986ВЕ92QI. Практическое применение: Опрашиваем клавиши, генерируем ШИМ. Сравнение кода на CMSIS и SPL (PWM+TIM+PORT). Часть вторая

Вступление.

В предыдущей статье мы с вами повторили общую структуру таймера и детально рассмотрели ручной способ настройки ШИМ канала с использованием CMSIS. Но многим не нравится «копаться в регистрах» и они предпочитают принципиально другой уровень абстракции, позволяющий, как им кажется, упростить задачу. В этой статье я попытаюсь показать вам все плюсы и минусы данного подхода.

Изменение в подаче материала.

Во всех предыдущих статьях я описывал все знания по какой-либо задаче в одном последовательно сформированном тексте, стараясь не упустить всех тонкостей и деталей, получая при этом достаточно объемную, но исчерпывающую любые вопросы, статью. В результате, мнения о моих статьях разделились. Кому-то нравилось, что в статьях нет отсылок к литературе и вся необходимая информация для понимания статьи находится в самой статье или в ее предшественниках. А кому-то наоборот было не интересно читать про «элементарные вещи» (такие, как синтаксис языка, стандартная организация блоков, и т.д.) и люди закрывали статью не прочтенной. Так как я не люблю отсылать людей к литературе, но при этом не хочу, чтобы что-либо в статье было непонятно, то материал теперь будет излагаться следующим образом: основной текст — текст для людей, разбирающихся в том, о чем читают и имеющих некоторый опыт работы по данной тематике. Для тех, кто что-либо не знает или не до конца понимает — под спойлерами с пометками «Пояснения к....» — будет собрана вся необходимая для понимания статьи информация.

Задача.

Наша задача решить ту же задачу, что мы решали в предыдущей статье, но с использованием только лишь возможностей SPL. По итогу мы сможем сказать, какой подход более нагляден, быстр и меньше весит. Как следствие — мы создадим столько же функций, сколько было в предыдущей реализации с такими же именами, за исключением того, что добавим «SPL», чтобы можно было их отличить и сравнить влияние каждой функции на вес и производительность кода (Заменяя функцию ручной инициализации на функцию с автоматической инициализацией средствами SPL).

Настройка портов ввода-вывода средствами SPL (PORT).

Начать предлагаю с самого простого. С портов ввода-вывода для вручную управляемого светодиода. Раньше эта функция называлась initPinPortCForLed. Теперь будет initPinPortCForLedSPL. Имена последующих функций будут иметь такой же принцип именования. Как мы помним, для того, чтобы порт запустился и мы смогла зажечь светодиод — нужно:
  1. Подать сигнал тактирования на порт.
  2. Инициализировать сам порт.
  3. Выставить значение в регистр RXTX.
Так было, когда мы работали с CMSIS напрямую. С SPL все немного иначе. Для настройки любого периферийного модуля нужно заполнить структуру. Иногда — не одну. И потом ее передать в функцию, которая сама все настроит.
Пояснение к сказанному.

Можно провести аналогию с постройкой дома: вы делаете чертеж по всем требованием, а потом передаете его людям, которые сами строят дом. Вас не касается, как будет построен дом. Вы подразумеваете, что в точности — как на вашем чертеже. Здесь «чертеж» — это настроенная вами структура. А «люди, строящие дом» — функция SPL. Для каждого блока периферии существует своя структура. Узнать, какая требуется структура, можно заглянув в файл в папке spl (в дереве проекта) с именем MDR32F9Qx_имя_периферии.

Для начала нам нужно подать сигнал тактирования на порт. Для этого нам нужно обратиться к файлу MDR32F9Qx_rst_clk.h. В нем, в самом конце, есть функции, которые нам может предоставить SPL. Из всего многообразия функций, нас интересует лишь RST_CLK_PCLKcmd. С ее помощью мы можем подать сигнал тактирования на любой блок периферии.
void RST_CLK_PCLKcmd(uint32_t RST_CLK_PCLK, FunctionalState NewState);
У функции есть два параметра:
  • RST_CLK_PCLK — имя блока периферии, на который нужно подать или с которого нужно снять тактовый сигнал (сигнал тактирования). Возможные имена можно найти поиском по этому же .h файлу, набрав RST_CLK_PCLK в качестве искомого.
    Возможные значения параметра RST_CLK_PCLK
    #ifdef USE_MDR1986VE9x /* For cortex M3 */
    
            #define RST_CLK_PCLK_CAN1           PCLK_BIT(MDR_CAN1_BASE)
            #define RST_CLK_PCLK_CAN2           PCLK_BIT(MDR_CAN2_BASE)
            #define RST_CLK_PCLK_USB            PCLK_BIT(MDR_USB_BASE)
            #define RST_CLK_PCLK_EEPROM         PCLK_BIT(MDR_EEPROM_BASE)
            #define RST_CLK_PCLK_RST_CLK        PCLK_BIT(MDR_RST_CLK_BASE)
            #define RST_CLK_PCLK_DMA            PCLK_BIT(MDR_DMA_BASE)
            #define RST_CLK_PCLK_UART1          PCLK_BIT(MDR_UART1_BASE)
            #define RST_CLK_PCLK_UART2          PCLK_BIT(MDR_UART2_BASE)
            #define RST_CLK_PCLK_SSP1           PCLK_BIT(MDR_SSP1_BASE)
            #define RST_CLK_PCLK_09             PCLK_BIT(0x40048000)
            #define RST_CLK_PCLK_I2C            PCLK_BIT(MDR_I2C_BASE)
            #define RST_CLK_PCLK_POWER          PCLK_BIT(MDR_POWER_BASE)
            #define RST_CLK_PCLK_WWDG           PCLK_BIT(MDR_WWDG_BASE)
            #define RST_CLK_PCLK_IWDG           PCLK_BIT(MDR_IWDG_BASE)
            #define RST_CLK_PCLK_TIMER1         PCLK_BIT(MDR_TIMER1_BASE)
            #define RST_CLK_PCLK_TIMER2         PCLK_BIT(MDR_TIMER2_BASE)
            #define RST_CLK_PCLK_TIMER3         PCLK_BIT(MDR_TIMER3_BASE)
            #define RST_CLK_PCLK_ADC            PCLK_BIT(MDR_ADC_BASE)
            #define RST_CLK_PCLK_DAC            PCLK_BIT(MDR_DAC_BASE)
            #define RST_CLK_PCLK_COMP           PCLK_BIT(MDR_COMP_BASE)
            #define RST_CLK_PCLK_SSP2           PCLK_BIT(MDR_SSP2_BASE)
            #define RST_CLK_PCLK_PORTA          PCLK_BIT(MDR_PORTA_BASE)
            #define RST_CLK_PCLK_PORTB          PCLK_BIT(MDR_PORTB_BASE)
            #define RST_CLK_PCLK_PORTC          PCLK_BIT(MDR_PORTC_BASE)
            #define RST_CLK_PCLK_PORTD          PCLK_BIT(MDR_PORTD_BASE)
            #define RST_CLK_PCLK_PORTE          PCLK_BIT(MDR_PORTE_BASE)
            #define RST_CLK_PCLK_26             PCLK_BIT(0x400D0000)
            #define RST_CLK_PCLK_BKP            PCLK_BIT(MDR_BKP_BASE)
            #define RST_CLK_PCLK_28             PCLK_BIT(0x400E0000)
            #define RST_CLK_PCLK_PORTF          PCLK_BIT(MDR_PORTF_BASE)
            #define RST_CLK_PCLK_EBC            PCLK_BIT(MDR_EBC_BASE)
            #define RST_CLK_PCLK_31             PCLK_BIT(0x400F8000)
    
            #define IS_RST_CLK_PCLK(PCLK)       ((((PCLK) & RST_CLK_PCLK_09) == 0x00) && \
                                                                                     (((PCLK) & RST_CLK_PCLK_26) == 0x00) && \
                                                                                     (((PCLK) & RST_CLK_PCLK_28) == 0x00) && \
                                                                                     (((PCLK) & RST_CLK_PCLK_31) == 0x00))
    #endif // #ifdef USE_MDR1986VE9x /* For cortex M3 */
    
    Прошу обратить внимание, что для каждой серии микроконтроллеров этот список свой. В большинстве пунктов списки идентичны, но некоторые индивидуальные для каждой серии строки могут отличатся.
  • NewState — состояние, в которое нужно перевести сигнал тактирования. Либо DISABLE — отключить тактирование, либо ENABLE — включить тактирование.
Вспомним, что наш светодиод подключен к PC1. Не сложно догадаться, что в нашем случае функция будет выглядеть так.
RST_CLK_PCLKcmd(RST_CLK_PCLK_PORTC, ENABLE);       // Включаем тактирование порта C.
Теперь на нужный нам порт ввода-вывода подан тактовый сигнал и мы можем начать его настраивать. Для начала нам необходимо найти функцию, которая настраивает порт. Она находится в файле MDR32F9Qx_port.h. Называется PORT_Init и имеет следующий вид.
void PORT_Init(MDR_PORT_TypeDef* PORTx, const PORT_InitTypeDef* PORT_InitStruct);
Как мы видим, у этой функции так же 2 параметра:
  1. MDR_PORT_TypeDef — имя порта, который мы настраиваем. В формате MDR_PORTX, где вместо X — буква нашего порта (A, B, C...). В нашем случае будет MDR_PORTC.
  2. Второй параметр — это структура вида PORT_InitTypeDef. Ее описание находится в том же файле (MDR32F9Qx_port.h). К сожалению, описание SPL полностью на английском. Как следствие, человеку, не знающему английского языка и незнакомому с устройством периферии на уровне регистров будет довольно тяжко. Да и иногда об истинном значении комментариев к функциям приходится по началу только гадать. Понимание их назначения приходит лишь после тщательного изучения блок-схемы того или иного периферийного модуля.
    Описание структуры PORT_InitTypeDef
    typedef struct
    {
      uint16_t PORT_Pin;                     /*!< Specifies PORT pins to be configured.
                                                  This parameter is a mask of @ref PORT_pins_define values. */
      PORT_OE_TypeDef PORT_OE;               /*!< Specifies in/out mode for the selected pins.
                                                  This parameter is one of @ref PORT_OE_TypeDef values. */
      PORT_PULL_UP_TypeDef PORT_PULL_UP;     /*!< Specifies pull up state for the selected pins.
                                                  This parameter is one of @ref PORT_PULL_UP_TypeDef values. */
      PORT_PULL_DOWN_TypeDef PORT_PULL_DOWN; /*!< Specifies pull down state for the selected pins.
                                                  This parameter is one of @ref PORT_PULL_DOWN_TypeDef values. */
      PORT_PD_SHM_TypeDef PORT_PD_SHM;       /*!< Specifies SHM state for the selected pins.
                                                  This parameter is one of @ref PORT_PD_SHM_TypeDef values. */
      PORT_PD_TypeDef PORT_PD;               /*!< Specifies PD state for the selected pins.
                                                  This parameter is one of @ref PORT_PD_TypeDef values. */
      PORT_GFEN_TypeDef PORT_GFEN;           /*!< Specifies GFEN state for the selected pins.
                                                  This parameter is one of @ref PORT_GFEN_TypeDef values. */
      PORT_FUNC_TypeDef PORT_FUNC;           /*!< Specifies operating function for the selected pins.
                                                  This parameter is one of @ref PORT_FUNC_TypeDef values. */
      PORT_SPEED_TypeDef PORT_SPEED;         /*!< Specifies the speed for the selected pins.
                                                  This parameter is one of @ref PORT_SPEED_TypeDef values. */
      PORT_MODE_TypeDef PORT_MODE;           /*!< Specifies the operating mode for the selected pins.
                                                  This parameter is one of @ref PORT_MODE_TypeDef values. */
    }PORT_InitTypeDef;
    
    Пояснение: что такое структура, как ее заполнять, откуда брать значения?
    Структура, по сути, представляет из себя массив, каждая фиксированная (имеет свое место в массиве) ячейка которого содержит какой-то параметр. В отличии от массива, каждый параметр структуры может иметь свой тип. Как и массив, перед заполнением, структуру необходимо создать.
    PORT_InitTypeDef Led0PortC_structInit;         // На порту C.
    
    Здесь PORT_InitTypeDef — это тип. Иначе говоря — просто шаблон, на основании которого происходит «разметка» памяти. Led0PortC_structInit — имя конкретной структуры, придуманное нами. Как создавая переменную типа uint32_t мы задавали ее имя, к примеру Loop, так и тут мы создаем структуру типа PORT_InitTypeDef с именем Led0PortC_structInit. Важно отметить, что объявление структуры должно быть сделано в функции до первой команды. Иначе проект не соберется. После создания структуры необходимо ее заполнить. Заполнение идет следующим образом.
    имя_структуры.ее_параметр = какое-то значение;
    
    И так — для каждого параметра из описания. В описании к каждой ячейке есть пояснение, какие значения можно в нее записывать. Как правило, если значением является не какое-то произвольное число из какого-либо диапазона, то в описании есть слово ref. С помощью слова, стоящего после него, можно найти в файле все доступные значения для данной ячейки. Возьмем в пример первую ячейку.
    uint16_t PORT_Pin;                     /*!< Specifies PORT pins to be configured.
                                                  This parameter is a mask of @ref PORT_pins_define values. */
    
    Используя поиск, находим PORT_pins_define.
    Видим следующее.
    #define PORT_Pin_0                  0x0001U  /*!< Pin 0 selected */
    #define PORT_Pin_1                  0x0002U  /*!< Pin 1 selected */
    #define PORT_Pin_2                  0x0004U  /*!< Pin 2 selected */
    #define PORT_Pin_3                  0x0008U  /*!< Pin 3 selected */
    #define PORT_Pin_4                  0x0010U  /*!< Pin 4 selected */
    #define PORT_Pin_5                  0x0020U  /*!< Pin 5 selected */
    #define PORT_Pin_6                  0x0040U  /*!< Pin 6 selected */
    #define PORT_Pin_7                  0x0080U  /*!< Pin 7 selected */
    #define PORT_Pin_8                  0x0100U  /*!< Pin 8 selected */
    #define PORT_Pin_9                  0x0200U  /*!< Pin 9 selected */
    #define PORT_Pin_10                 0x0400U  /*!< Pin 10 selected */
    #define PORT_Pin_11                 0x0800U  /*!< Pin 11 selected */
    #define PORT_Pin_12                 0x1000U  /*!< Pin 12 selected */
    #define PORT_Pin_13                 0x2000U  /*!< Pin 13 selected */
    #define PORT_Pin_14                 0x4000U  /*!< Pin 14 selected */
    #define PORT_Pin_15                 0x8000U  /*!< Pin 15 selected */
    #define PORT_Pin_All                0xFFFFU  /*!< All pins selected */
    
    У нас PORTC вывод 1. По идеи, мы можем написать
    Led0PortC_structInit.PORT_Pin                          = PORT_Pin_1;
    
    Но у нас еще со времен прошлой статьи остался такой define.
    // Подключение светодиодов.        
    #define LED0                                            (1<<0)                    // PORTC.
    #define LED1                                            (1<<1)                    // PORTC.
    
    Это такая же маска порта, что и в описании, только с другим именем. Но она дает более ясное представление о том, что мы подключаем, так что я запишу ее.
    Led0PortC_structInit.PORT_Pin                              = LED1; // Пин нашего светодиода.
    
Заполнив структуру останется лишь указать ее в качестве параметра функции SPL PORT_Init, не забывая про "&" перед именем структуры (передаем указатель на структуру).
Получим готовую функцию вида.
void initPinPortCForLedSPL (void)
{
        PORT_InitTypeDef Led0PortC_structInit; // На порту C.
        RST_CLK_PCLKcmd(RST_CLK_PCLK_PORTC, ENABLE); // Включаем тактирование порта C.
        
        Led0PortC_structInit.PORT_Pin = LED1; // Пин нашего светодиода.
        Led0PortC_structInit.PORT_FUNC = PORT_FUNC_PORT; // Вывод работают в режиме обычного порта.
        Led0PortC_structInit.PORT_GFEN = PORT_GFEN_OFF; // Входной фильтр отключен.
        Led0PortC_structInit.PORT_MODE = PORT_MODE_DIGITAL; // Вывод цифровой.
        Led0PortC_structInit.PORT_OE = PORT_OE_OUT; // Вывод работает на выход.
        Led0PortC_structInit.PORT_PD = PORT_PD_DRIVER; // Управляемый драйвер.
        Led0PortC_structInit.PORT_PD_SHM = PORT_PD_SHM_OFF; // Триггер Шмитта выключен.
        Led0PortC_structInit.PORT_PULL_DOWN = PORT_PULL_DOWN_OFF; // Подтяжка в 0 отключена.
        Led0PortC_structInit.PORT_PULL_UP = PORT_PULL_UP_OFF; // Подтяжка в 1 отключена. 
        Led0PortC_structInit.PORT_SPEED = PORT_SPEED_MAXFAST;    // Работа вывода с максимальной скоростью.

        PORT_Init(MDR_PORTC, &Led0PortC_structInit); // Инициализируем порт.
}
Функция инициализации клавиш проходит аналогичным образом. Разница лишь в том, что мы указываем режим вывода вместо выхода — вход (PORT_OE_IN), а так же включаем входной фильтр (PORT_GFEN_ON).
Функция инициализации выводов, подключенных к кнопкам.
void initPinForButtonSPL (void)
{
        // Генерируем структуры инициализации портов.
        PORT_InitTypeDef buttonPortB_structInit; // Структура для иницализации входов кнопоки на порту C.
        PORT_InitTypeDef buttonPortC_structInit; // Выходы на порту C.
        PORT_InitTypeDef buttonPortE_structInit; // Не порту E.
        
        // Включаем тактирование портов.
        RST_CLK_PCLKcmd(RST_CLK_PCLK_PORTB, ENABLE); // Включаем тактирование порта B.
        RST_CLK_PCLKcmd(RST_CLK_PCLK_PORTC, ENABLE); // Включаем тактирование порта C.
        RST_CLK_PCLKcmd(RST_CLK_PCLK_PORTE, ENABLE); // Включаем тактирование порта E.
        
        // Заполняем стркутуры портов.
        buttonPortB_structInit.PORT_FUNC = PORT_FUNC_PORT; // Выводы работают в режиме обычного порта.
        buttonPortB_structInit.PORT_GFEN = PORT_GFEN_ON; // Входной фильтр отключен на обоих выводах.
        buttonPortB_structInit.PORT_MODE = PORT_MODE_DIGITAL; // Оба вывода цифровые.
        buttonPortB_structInit.PORT_OE = PORT_OE_IN; // Выводы работают на вход.
        buttonPortB_structInit.PORT_PD = PORT_PD_DRIVER; // Управляемый драйвер.
        buttonPortB_structInit.PORT_PD_SHM = PORT_PD_SHM_OFF; // Триггер Шмитта выключен.
        buttonPortB_structInit.PORT_Pin = UP_MSK|RIGHT_MSK; // Все вышеупомянутые настройки только для двух выводов.
        buttonPortB_structInit.PORT_PULL_DOWN = PORT_PULL_DOWN_OFF; // Подтяжка в 0 отключена.
        buttonPortB_structInit.PORT_PULL_UP = PORT_PULL_UP_OFF; // Подтяжка в 1 отключена. 
        buttonPortB_structInit.PORT_SPEED = PORT_SPEED_MAXFAST; // Работа выводов с максимальной скоростью.
        
        buttonPortC_structInit.PORT_FUNC = PORT_FUNC_PORT; // PORTC.
        buttonPortC_structInit.PORT_GFEN = PORT_GFEN_ON;                        
        buttonPortC_structInit.PORT_MODE = PORT_MODE_DIGITAL;   
        buttonPortC_structInit.PORT_OE = PORT_OE_IN;                                            
        buttonPortC_structInit.PORT_PD = PORT_PD_DRIVER;                        
        buttonPortC_structInit.PORT_PD_SHM = PORT_PD_SHM_OFF;           
        buttonPortC_structInit.PORT_Pin = SELECT_MSK;                                           
        buttonPortC_structInit.PORT_PULL_DOWN = PORT_PULL_DOWN_OFF;             
        buttonPortC_structInit.PORT_PULL_UP     = PORT_PULL_UP_OFF;                     
        buttonPortC_structInit.PORT_SPEED = PORT_SPEED_MAXFAST;         

        buttonPortE_structInit.PORT_FUNC = PORT_FUNC_PORT; // PORTE.
        buttonPortE_structInit.PORT_GFEN = PORT_GFEN_ON;                                                        
        buttonPortE_structInit.PORT_MODE = PORT_MODE_DIGITAL;           
        buttonPortE_structInit.PORT_OE = PORT_OE_IN;                                    
        buttonPortE_structInit.PORT_PD = PORT_PD_DRIVER;                                
        buttonPortE_structInit.PORT_PD_SHM = PORT_PD_SHM_OFF;                   
        buttonPortE_structInit.PORT_Pin = DOWN_MSK|LEFT_MSK;                            
        buttonPortE_structInit.PORT_PULL_DOWN = PORT_PULL_DOWN_OFF;             
        buttonPortE_structInit.PORT_PULL_UP = PORT_PULL_UP_OFF;                 
        buttonPortE_structInit.PORT_SPEED = PORT_SPEED_MAXFAST;         

        // Инициализируем порты.
        PORT_Init(MDR_PORTB, &buttonPortB_structInit);
        PORT_Init(MDR_PORTC, &buttonPortC_structInit);
        PORT_Init(MDR_PORTE, &buttonPortE_structInit);
}

Настройка таймера для генерации ШИМ (PWM).

Прежде чем настраивать сам таймер — настроим вывод порта ввода-вывода, на который будем выводить ШИМ в режим альтернативной функции. Помним, что в прошлой статье мы использовали порт PORTA и вывод 1.
Выйдет следующее.
RST_CLK_PCLKcmd(RST_CLK_PCLK_PORTA, ENABLE); // Включаем тактирование порта A.
        
        PWMPortA_structInit.PORT_FUNC = PORT_FUNC_ALTER; // Вывод работают в режиме альтернативной функции.
        PWMPortA_structInit.PORT_GFEN = PORT_GFEN_OFF; // Входной фильтр отключен.
        PWMPortA_structInit.PORT_MODE = PORT_MODE_DIGITAL; // Вывод цифровой.
        PWMPortA_structInit.PORT_OE = PORT_OE_OUT; // Вывод работает на выход.
        PWMPortA_structInit.PORT_PD = PORT_PD_DRIVER; // Управляемый драйвер.
        PWMPortA_structInit.PORT_PD_SHM = PORT_PD_SHM_OFF; // Триггер Шмитта выключен.
        PWMPortA_structInit.PORT_Pin = PORT_Pin_1; // Пин нашего светодиода.
        PWMPortA_structInit.PORT_PULL_DOWN = PORT_PULL_DOWN_OFF; // Подтяжка в 0 отключена.
        PWMPortA_structInit.PORT_PULL_UP = PORT_PULL_UP_OFF; // Подтяжка в 1 отключена. 
        PWMPortA_structInit.PORT_SPEED = PORT_SPEED_MAXFAST;     // Работа вывода с максимальной скоростью.

        PORT_Init(MDR_PORTA, &PWMPortA_structInit); // Инициализируем порт.
Теперь мы можем приступить к настройке самого таймера. Прежде всего, нам нужно подать тактирование на TIMER1. Сделать это можно так же с помощью функции RST_CLK_PCLKcmd, рассмотренной ранее.
RST_CLK_PCLKcmd(RST_CLK_PCLK_TIMER1, ENABLE); // Включаем тактирование таймера 1.
Далее стоит ясно обозначить задачу. Нам нужно:
  1. Настроить основной таймер.
  2. Настроить канал таймера.
  3. Настроить вывод таймера.
  4. Настроить тактовую частоту для работы всего таймера.
  5. Включить таймер.
Для каждого пункта в SPL есть своя функция, а для первых трех пунктов существуют еще и свои структуры. Все функции и их параметры находятся в файле MDR32F9Qx_timer.h. Начнем с первого пункта. Для инициализации основного таймера существует функция TIMER_CntInit.
void TIMER_CntInit(MDR_TIMER_TypeDef* TIMERx, const TIMER_CntInitTypeDef* TIMER_CntInitStruct);
Здесь два параметра.
  • TIMERx — выбранный для инициализации таймер. Указывается он в формате MDR_TIMERX, где X — номер нужного таймера. В нашем случае — MDR_TIMER1.
  • Структура типа TIMER_CntInitTypeDef — в данной структуре имеется перечисление всех возможных параметров основного таймера.
    Вот ее описание.
    typedef struct {

    #if defined(USE_MDR1986VE9x) /* For Cortex M3 */
    uint16_t TIMER_IniCounter; /*!< Specifies the initial counter value.
    This parameter can be a number between 0x0000 and 0xFFFF. */
    #elif ((defined (USE_MDR1986VE3)) || (defined (USE_MDR1986VE1T)))
    uint32_t TIMER_IniCounter; /*!< Specifies the initial counter value.
    This parameter can be a number between 0x0000 and 0xFFFFFFFF. */
    #endif // #elif ((defined (USE_MDR1986VE3)) || (defined (USE_MDR1986VE1T)))

    uint16_t TIMER_Prescaler; /*!< Specifies the prescaler value used to divide the TIMER clock.
    This parameter can be a number between 0x0000 and 0xFFFF.
    CLK = TIMER_CLK/(TIMER_Prescaler + 1) */

    #if defined(USE_MDR1986VE9x) /* For Cortex M3 */
    uint16_t TIMER_Period; /*!< Specifies the period value to be loaded into the
    Auto-Reload Register (ARR) at the next update event.
    This parameter must be a number between 0x0000 and 0xFFFF. */
    #elif ((defined (USE_MDR1986VE3)) || (defined (USE_MDR1986VE1T))) /* For Cortex M1 */
    uint32_t TIMER_Period; /*!< Specifies the period value to be loaded into the
    Auto-Reload Register (ARR) at the next update event.
    This parameter must be a number between 0x0000 and 0xFFFFFFFF. */
    #endif // #elif ((defined (USE_MDR1986VE3)) || (defined (USE_MDR1986VE1T))) /* For Cortex M1 */

    uint16_t TIMER_CounterMode; /*!< Specifies the counter mode.
    This parameter can be a value of ref TIMER_Counter_Mode */

    uint16_t TIMER_CounterDirection; /*!< Specifies the counter direction.
    This parameter can be a value of ref TIMER_Counter_Direction */

    uint16_t TIMER_EventSource; /*!< Specifies the Counter Event source.
    This parameter can be a value of ref TIMER_Event_Source */

    uint16_t TIMER_FilterSampling; /*!< Specifies the filter sampling clock (FDTS).
    This parameter can be a value of ref TIMER_Filter_Sampling */

    uint16_t TIMER_ARR_UpdateMode; /*!< Specifies the Auto-Reload Register (ARR) updating mode.
    This parameter can be a value of ref TIMER_ARR_Update_Mode */

    uint16_t TIMER_ETR_FilterConf; /*!< Specifies the ETR Filter configuration.
    This parameter can be a value of ref TIMER_FilterConfiguration */

    uint16_t TIMER_ETR_Prescaler; /*!< Specifies the ETR Prescaler configuration.
    This parameter can be a value of ref TIMER_ETR_Prescaler */

    uint16_t TIMER_ETR_Polarity; /*!< Specifies the ETR Polarity configuration.
    This parameter can be a value of ref TIMER_ETR_Polarity */

    uint16_t TIMER_BRK_Polarity; /*!< Specifies the BRK Polarity configuration.
    This parameter can be a value of ref TIMER_BRK_Polarity */
    } TIMER_CntInitTypeDef;

Заполнив все поля и инициализировав таймер получим следующее.

TIMER_CntInitTypeDef timerPWM_structInit; // Структура для настройки основного таймера (без каналов).
// Заполняем структуру таймера.
timerPWM_structInit.TIMER_ARR_UpdateMode = TIMER_ARR_Update_Immediately; // Регистр ARR можно обновлять в любое время.
timerPWM_structInit.TIMER_BRK_Polarity = TIMER_BRKPolarity_NonInverted; // BRK сигнал не инвертируется (нас этот параметр не касается).
timerPWM_structInit.TIMER_CounterDirection = TIMER_CntDir_Up; // Считаем "вверх". CNT инкрементируется (CNT++). 
timerPWM_structInit.TIMER_CounterMode = TIMER_CntMode_ClkFixedDir; // Считаем в одну сторону, вверх.
timerPWM_structInit.TIMER_ETR_FilterConf = TIMER_Filter_1FF_at_TIMER_CLK; // Сигнал зафиксирован в 1-м триггере на частоте TIM_CLK (В нашем случае оставляем по-умолчанию).
timerPWM_structInit.TIMER_ETR_Polarity = TIMER_ETRPolarity_NonInverted; // ETR на входе не инвертируется (мы его и не используем).
timerPWM_structInit.TIMER_ETR_Prescaler = TIMER_ETR_Prescaler_None; // Частота ETR на входе не делится (ETR не используем.).
timerPWM_structInit.TIMER_EventSource = TIMER_EvSrc_None; // Таймер не вызывает прерываний.
timerPWM_structInit.TIMER_FilterSampling = TIMER_FDTS_TIMER_CLK_div_1; // FDTS = TIMER_CLK. (Так не используем.).
timerPWM_structInit.TIMER_IniCounter = 0; // Считаем с 0. Начальное значение счетчика. (CNT = 0.).
timerPWM_structInit.TIMER_Period = PWM_speed; // Считаем до указанного в параметрах функции значения (ARR = PWM_speed).
timerPWM_structInit.TIMER_Prescaler = 32000 - 1;// Делитель входного сигнала. PSG регистр. 
        
TIMER_CntInit(MDR_TIMER1, &timerPWM_structInit); // Инициализируем основной таймер.
Как можно видеть из комментариев к коду заполнения структуры — большинство пунктов остаются по-умолчанию (отключенными). Но не смотря на это, из все равно нужно указать.
Далее нужно инициализировать канал таймера. В нашем случае — первый. За инициализицию каналов отвечает функция TIMER_ChnInit.
void TIMER_ChnInit(MDR_TIMER_TypeDef* TIMERx, const TIMER_ChnInitTypeDef* TIMER_ChnInitStruct)
Первым параметром идет имя инициализируемого таймера. Оно остается тем же, что и у функции инициализации основного таймера. А вот структура уже типа TIMER_ChnInitTypeDef.
Вот ее описание.
typedef struct
{
uint16_t TIMER_CH_Number; /*!
This parameter can be a value of ref TIMER_CH_Number */

uint16_t TIMER_CH_Mode; /*!< Specifies the TIMER Channel mode.
This parameter can be a value of ref TIMER_CH_Mode */

uint16_t TIMER_CH_ETR_Ena; /*!< Enables or disables ETR.
This parameter can be a value of FunctionalState */

uint16_t TIMER_CH_ETR_Reset; /*!< Enables or disables ETR Reset.
This parameter can be a value of ref TIMER_CH_ETR_Reset */

uint16_t TIMER_CH_BRK_Reset; /*!< Enables or disables BRK Reset.
This parameter can be a value of ref TIMER_CH_BRK_Reset */

uint16_t TIMER_CH_REF_Format; /*!< Specifies the REF signal format.
This parameter can be a value of ref TIMER_CH_REF_Format */

uint16_t TIMER_CH_Prescaler; /*!< Specifies the TIMER Channel Prescaler configuration.
This parameter can be a value of ref TIMER_CH_Prescaler */

uint16_t TIMER_CH_EventSource; /*!< Specifies the Channel Event source.
This parameter can be a value of ref TIMER_CH_EventSource */

uint16_t TIMER_CH_FilterConf; /*!< Specifies the TIMER Channel Filter configuration.
This parameter can be a value of ref TIMER_FilterConfiguration */

uint16_t TIMER_CH_CCR_UpdateMode; /*!< Specifies the TIMER CCR, CCR1 update mode.
This parameter can be a value of ref TIMER_CH_CCR_Update_Mode */

uint16_t TIMER_CH_CCR1_Ena; /*!< Enables or disables the CCR1 register.
This parameter can be a value of FunctionalState */

uint16_t TIMER_CH_CCR1_EventSource; /*!< Specifies the Channel CCR1 Event source.
This parameter can be a value of ref TIMER_CH_CCR1_EventSource */
}TIMER_ChnInitTypeDef;

Так же заполняем и инициализируем.
TIMER_ChnInitTypeDef timerPWM_channelStructInit; // Структура канала ШИМ.
// Заполняем структуру PWM канала.
timerPWM_channelStructInit.TIMER_CH_BRK_Reset = TIMER_CH_BRK_RESET_Disable; // Сброс канала BRK не производится (BRK не используем).
timerPWM_channelStructInit.TIMER_CH_CCR1_Ena =  DISABLE; // CCR1 не используем.
timerPWM_channelStructInit.TIMER_CH_CCR1_EventSource =  TIMER_CH_CCR1EvSrc_PE; // Выбор события по входному каналу для CAP1: положительный фронт по Chi. (По умолчанию, мы не используем).
timerPWM_channelStructInit.TIMER_CH_CCR_UpdateMode = TIMER_CH_CCR_Update_Immediately; // Регистр CCR можно обновлять в любое время (CCR не используем).
timerPWM_channelStructInit.TIMER_CH_ETR_Ena = DISABLE; // ETR не используется.
timerPWM_channelStructInit.TIMER_CH_ETR_Reset = TIMER_CH_ETR_RESET_Disable; // Сброс ETR не производится.
timerPWM_channelStructInit.TIMER_CH_EventSource = TIMER_CH_EvSrc_PE;                                                            // Выбор события по входному каналу: положительный фронт. (Так же не используется).
timerPWM_channelStructInit.TIMER_CH_FilterConf = TIMER_Filter_1FF_at_TIMER_CLK; // Входной сигнал от TIMER_CLK фиксируется одним триггером.
timerPWM_channelStructInit.TIMER_CH_Mode = TIMER_CH_MODE_PWM; // Канал в ШИМ режиме.
timerPWM_channelStructInit.TIMER_CH_Number = TIMER_CHANNEL1; // Первый канал. 
timerPWM_channelStructInit.TIMER_CH_Prescaler = TIMER_CH_Prescaler_None; // В канале частота не делится.
timerPWM_channelStructInit.TIMER_CH_REF_Format = TIMER_CH_REF_Format3; // Сигнал REF меняется при CNT == ARR.
        
TIMER_ChnInit(MDR_TIMER1, &timerPWM_channelStructInit); // Инициализируем канал.
Замечу, что именно в этой функции мы формируем сигнал на REF для ШИМ.
Далее нужно настроить канал таймера на выход. Для этого есть функция TIMER_ChnOutInit.
void TIMER_ChnOutInit(MDR_TIMER_TypeDef* TIMERx, const TIMER_ChnOutInitTypeDef* TIMER_ChnOutInitStruct);
Первым параметром так же идет имя нашего таймера. Второй — структура TIMER_ChnOutInitStruct.
Ее описание.
typedef struct
{
uint16_t TIMER_CH_Number; /*!
This parameter can be a value of ref TIMER_CH_Number */

uint16_t TIMER_CH_DirOut_Polarity; /*!< Specifies the TIMER CHx output polarity.
This parameter can be a value of ref TIMER_CH_OUT_Polarity */

uint16_t TIMER_CH_DirOut_Source; /*!< Specifies the TIMER CHx output source.
This parameter can be a value of ref TIMER_CH_OUT_Source */

uint16_t TIMER_CH_DirOut_Mode; /*!< Specifies the TIMER CHx output enable source.
This parameter can be a value of ref TIMER_CH_OUT_Mode */

uint16_t TIMER_CH_NegOut_Polarity; /*!< Enables or disables the TIMER CHxN output inversion.
This parameter can be a value of ref TIMER_CH_OUT_Polarity */

uint16_t TIMER_CH_NegOut_Source; /*!< Specifies the TIMER CHxN output source.
This parameter can be a value of ref TIMER_CH_OUT_Source */

uint16_t TIMER_CH_NegOut_Mode; /*!< Specifies the TIMER CHxN output enable source.
This parameter can be a value of ref TIMER_CH_OUT_Mode */

uint16_t TIMER_CH_DTG_MainPrescaler; /*!< Specifies the main prescaler of TIMER DTG.
This parameter can be a number between 0x0000 and 0x00FF.
Delay DTGdel = TIMER_CH_DTG_MainPrescaler*(TIMER_CH_DTG_AuxPrescaler + 1) clocks. */

uint16_t TIMER_CH_DTG_AuxPrescaler; /*!< Specifies the auxiliary prescaler of TIMER DTG.
This parameter can be a number between 0x0000 and 0x000F.
Delay DTGdel = TIMER_CH_DTG_MainPrescaler*(TIMER_CH_DTG_AuxPrescaler + 1) clocks. */

uint16_t TIMER_CH_DTG_ClockSource; /*!< Specifies the TIMER DTG clock source.
This parameter can be a value of ref TIMER_CH_DTG_Clock_Source */
}TIMER_ChnOutInitTypeDef;

После заполнения структуры и инициализации будем наблюдать следующий код.
TIMER_ChnOutInitTypeDef timerPWM_channelOUTPWMStructInit; // Структура настройки выхода канала ШИМ.
// Параметры выхода.
timerPWM_channelOUTPWMStructInit.TIMER_CH_DirOut_Mode = TIMER_CH_OutMode_Output; // Всегда выход.
timerPWM_channelOUTPWMStructInit.TIMER_CH_DirOut_Polarity = TIMER_CHOPolarity_NonInverted; // Неинвертированный.
timerPWM_channelOUTPWMStructInit.TIMER_CH_DirOut_Source = TIMER_CH_OutSrc_REF; // На выход REF сигнал.
timerPWM_channelOUTPWMStructInit.TIMER_CH_DTG_AuxPrescaler = 0; // Делителя не стоит.
timerPWM_channelOUTPWMStructInit.TIMER_CH_DTG_ClockSource = TIMER_CH_DTG_ClkSrc_TIMER_CLK; // Источник тактового сигнала для DTG - TIMER_CLK. Но DTG мы все равно не используем.
timerPWM_channelOUTPWMStructInit.TIMER_CH_DTG_MainPrescaler = 0; // Делитель сигнала на DTG.
timerPWM_channelOUTPWMStructInit.TIMER_CH_NegOut_Mode = TIMER_CH_OutMode_Input; // Инвертный канал на вход. Все остальные его параметр берем по умолчанию, т.к. они не важны.
timerPWM_channelOUTPWMStructInit.TIMER_CH_NegOut_Polarity = TIMER_CHOPolarity_NonInverted; // Без инвертирования инвертированного канала.
timerPWM_channelOUTPWMStructInit.TIMER_CH_NegOut_Source = TIMER_CH_DTG_ClkSrc_TIMER_CLK; // Источник тактового сигнала для DTG - TIMER_CLK.
timerPWM_channelOUTPWMStructInit.TIMER_CH_Number = TIMER_CHANNEL1; // Первый канал.
        
TIMER_ChnOutInit(MDR_TIMER1, &timerPWM_channelOUTPWMStructInit); // Настраиваем канал на выход.
Теперь осталось только подать тактирование на таймер и запустить его. Для подачи тактового сигнала (именно тот, на основе которого таймер ведет счет) есть функция TIMER_BRGInit.
void TIMER_BRGInit(MDR_TIMER_TypeDef* TIMERx, uint32_t TIMER_BRG);
Первый параметр, как обычно, имя таймера, второй — делитель. Делитель рассчитывается так же, как и для регистра PSG в предыдущей статье (по сути эта функция лишь пишет наш делитель в PSG...). Так же отмечу, что эта же функция и разрешает подачу сигнала тактирования на таймер по-умолчанию. Ну а за включение отвечает функция TIMER_Cmd.
void TIMER_Cmd(MDR_TIMER_TypeDef* TIMERx, FunctionalState NewState)
Параметры — имя таймера и его состояние ENABLE/DISABLE.
По итогу всей настройки получаем следующее.
// Инициализация таймера в режиме ШИМ для работы со светодиодом в режиме SPL.
void initTimerPWMledSPL (uint32_t PWM_speed)
{
PORT_InitTypeDef PWMPortA_structInit; // Структура для инициализации вывода таймера в режиме ШИМ.
TIMER_CntInitTypeDef timerPWM_structInit; // Структура для настройки основного таймера (без каналов).
TIMER_ChnInitTypeDef timerPWM_channelStructInit; // Структура канала ШИМ.
TIMER_ChnOutInitTypeDef timerPWM_channelOUTPWMStructInit; // Структура настройки выхода канала ШИМ.

RST_CLK_PCLKcmd(RST_CLK_PCLK_PORTA, ENABLE); // Включаем тактирование порта A.
RST_CLK_PCLKcmd(RST_CLK_PCLK_TIMER1, ENABLE); // Включаем тактирование таймера 1.

PWMPortA_structInit.PORT_FUNC = PORT_FUNC_ALTER; // Вывод работают в режиме альтернативной функции.
PWMPortA_structInit.PORT_GFEN = PORT_GFEN_OFF; // Входной фильтр отключен.
PWMPortA_structInit.PORT_MODE = PORT_MODE_DIGITAL; // Вывод цифровой.
PWMPortA_structInit.PORT_OE = PORT_OE_OUT; // Вывод работает на выход.
PWMPortA_structInit.PORT_PD = PORT_PD_DRIVER; // Управляемый драйвер.
PWMPortA_structInit.PORT_PD_SHM = PORT_PD_SHM_OFF; // Триггер Шмитта выключен.
PWMPortA_structInit.PORT_Pin = PORT_Pin_1; // Пин нашего светодиода.
PWMPortA_structInit.PORT_PULL_DOWN = PORT_PULL_DOWN_OFF; // Подтяжка в 0 отключена.
PWMPortA_structInit.PORT_PULL_UP = PORT_PULL_UP_OFF; // Подтяжка в 1 отключена.
PWMPortA_structInit.PORT_SPEED = PORT_SPEED_MAXFAST; // Работа вывода с максимальной скоростью.

PORT_Init(MDR_PORTA, &PWMPortA_structInit); // Инициализируем порт.

// Заполняем структуру таймера.
timerPWM_structInit.TIMER_ARR_UpdateMode = TIMER_ARR_Update_Immediately; // Регистр ARR можно обновлять в любое время.
timerPWM_structInit.TIMER_BRK_Polarity = TIMER_BRKPolarity_NonInverted; // BRK сигнал не инвертируется (нас этот параметр не касается).
timerPWM_structInit.TIMER_CounterDirection = TIMER_CntDir_Up; // Считаем «вверх». CNT инкрементируется (CNT++).
timerPWM_structInit.TIMER_CounterMode = TIMER_CntMode_ClkFixedDir; // Считаем в одну сторону, вверх.
timerPWM_structInit.TIMER_ETR_FilterConf = TIMER_Filter_1FF_at_TIMER_CLK; // Сигнал зафиксирован в 1-м триггере на частоте TIM_CLK (В нашем случае оставляем по-умолчанию).
timerPWM_structInit.TIMER_ETR_Polarity = TIMER_ETRPolarity_NonInverted; // ETR на входе не инвертируется (мы его и не используем).
timerPWM_structInit.TIMER_ETR_Prescaler = TIMER_ETR_Prescaler_None; // Частота ETR на входе не делится (ETR не используем.).
timerPWM_structInit.TIMER_EventSource = TIMER_EvSrc_None; // Таймер не вызывает прерываний.
timerPWM_structInit.TIMER_FilterSampling = TIMER_FDTS_TIMER_CLK_div_1; // FDTS = TIMER_CLK. (Так не используем.).
timerPWM_structInit.TIMER_IniCounter = 0; // Считаем с 0. Начальное значение счетчика. (CNT = 0.).
timerPWM_structInit.TIMER_Period = PWM_speed; // Считаем до указанного в параметрах функции значения (ARR = PWM_speed).
timerPWM_structInit.TIMER_Prescaler = 32000 — 1; // Делитель входного сигнала. PSG регистр.

TIMER_CntInit(MDR_TIMER1, &timerPWM_structInit); // Инициализируем основной таймер.

// Заполняем структуру PWM канала.
timerPWM_channelStructInit.TIMER_CH_BRK_Reset = TIMER_CH_BRK_RESET_Disable; // Сброс канала BRK не производится (BRK не используем).
timerPWM_channelStructInit.TIMER_CH_CCR1_Ena = DISABLE; // CCR1 не используем.
timerPWM_channelStructInit.TIMER_CH_CCR1_EventSource = TIMER_CH_CCR1EvSrc_PE; // Выбор события по входному каналу для CAP1: положительный фронт по Chi. (По умолчанию, мы не используем).
timerPWM_channelStructInit.TIMER_CH_CCR_UpdateMode = TIMER_CH_CCR_Update_Immediately; // Регистр CCR можно обновлять в любое время (CCR не используем).
timerPWM_channelStructInit.TIMER_CH_ETR_Ena = DISABLE; // ETR не используется.
timerPWM_channelStructInit.TIMER_CH_ETR_Reset = TIMER_CH_ETR_RESET_Disable; // Сброс ETR не производится.
timerPWM_channelStructInit.TIMER_CH_EventSource = TIMER_CH_EvSrc_PE; // Выбор события по входному каналу: положительный фронт. (Так же не используется).
timerPWM_channelStructInit.TIMER_CH_FilterConf = TIMER_Filter_1FF_at_TIMER_CLK; // Входной сигнал от TIMER_CLK фиксируется одним триггером.
timerPWM_channelStructInit.TIMER_CH_Mode = TIMER_CH_MODE_PWM; // Канал в ШИМ режиме.
timerPWM_channelStructInit.TIMER_CH_Number = TIMER_CHANNEL1; // Первый канал.
timerPWM_channelStructInit.TIMER_CH_Prescaler = TIMER_CH_Prescaler_None; // В канале частота не делится.
timerPWM_channelStructInit.TIMER_CH_REF_Format = TIMER_CH_REF_Format3; // Сигнал REF меняется при CNT == ARR.

TIMER_ChnInit(MDR_TIMER1, &timerPWM_channelStructInit); // Инициализируем канал.

// Параметры выхода.
timerPWM_channelOUTPWMStructInit.TIMER_CH_DirOut_Mode = TIMER_CH_OutMode_Output; // Всегда выход.
timerPWM_channelOUTPWMStructInit.TIMER_CH_DirOut_Polarity = TIMER_CHOPolarity_NonInverted; // Не инвертированный.
timerPWM_channelOUTPWMStructInit.TIMER_CH_DirOut_Source = TIMER_CH_OutSrc_REF; // На выход REF сигнал.
timerPWM_channelOUTPWMStructInit.TIMER_CH_DTG_AuxPrescaler = 0; // Делителя не стоит.
timerPWM_channelOUTPWMStructInit.TIMER_CH_DTG_ClockSource = TIMER_CH_DTG_ClkSrc_TIMER_CLK; // Источник тактового сигнала для DTG — TIMER_CLK. Но DTG мы все равно не используем.
timerPWM_channelOUTPWMStructInit.TIMER_CH_DTG_MainPrescaler = 0; // Делитель сигнала на DTG.
timerPWM_channelOUTPWMStructInit.TIMER_CH_NegOut_Mode = TIMER_CH_OutMode_Input; // Инвертный канал на вход. Все остальные его параметр берем по умолчанию, т.к. они не важны.
timerPWM_channelOUTPWMStructInit.TIMER_CH_NegOut_Polarity = TIMER_CHOPolarity_NonInverted;// Без инвертирования инвертированного канала.
timerPWM_channelOUTPWMStructInit.TIMER_CH_NegOut_Source = TIMER_CH_DTG_ClkSrc_TIMER_CLK; // Источник тактового сигнала для DTG — TIMER_CLK.
timerPWM_channelOUTPWMStructInit.TIMER_CH_Number = TIMER_CHANNEL1; // Первый канал.

TIMER_ChnOutInit(MDR_TIMER1, &timerPWM_channelOUTPWMStructInit); // Настраиваем канал на выход.

TIMER_BRGInit(MDR_TIMER1, TIMER_HCLKdiv1); // Подаем источник сигнала для счета (частота процессора без предделителя). // В этой функции выбор делителя (у нас «1») и включение подачи такта.
TIMER_Cmd(MDR_TIMER1, ENABLE); // Включаем таймер.
}


Настройка таймера для вызова прерываний (IRQ)

Далее нам нужно настроить таймер, генерирующий прерывания для опроса клавиш. Здесь нам нужно будет настроить таймер лишь по первой структуре. Так как каналы и выходы мы не используем. Инициализация таймера будет выглядеть так же, как у предыдущего, за исключением ячейки TIMER_EventSource. В ней мы должны указать, по какому событию у нас происходит прерывание. В прошлой статье мы использовали CNT == ARR. Его и используем.
А вообще, возможны следующие варианты.
#define TIMER_EvSrc_None                      (((uint32_t)0x0) << TIMER_CNTRL_EVENT_SEL_Pos)  /*!< No events. */
#define TIMER_EvSrc_TM1                       (((uint32_t)0x1) << TIMER_CNTRL_EVENT_SEL_Pos)  /*!< Selects TIMER1 (CNT == ARR) event. */
#define TIMER_EvSrc_TM2                       (((uint32_t)0x2) << TIMER_CNTRL_EVENT_SEL_Pos)  /*!< Selects TIMER2 (CNT == ARR) event. */
#define TIMER_EvSrc_TM3                       (((uint32_t)0x3) << TIMER_CNTRL_EVENT_SEL_Pos)  /*!< Selects TIMER3 (CNT == ARR) event. */
#define TIMER_EvSrc_CH1                       (((uint32_t)0x4) << TIMER_CNTRL_EVENT_SEL_Pos)  /*!< Selects Channel 1 event. */
#define TIMER_EvSrc_CH2                       (((uint32_t)0x5) << TIMER_CNTRL_EVENT_SEL_Pos)  /*!< Selects Channel 2 event. */
#define TIMER_EvSrc_CH3                       (((uint32_t)0x6) << TIMER_CNTRL_EVENT_SEL_Pos)  /*!< Selects Channel 3 event. */
#define TIMER_EvSrc_CH4                       (((uint32_t)0x7) << TIMER_CNTRL_EVENT_SEL_Pos)  /*!< Selects Channel 4 event. */
#define TIMER_EvSrc_ETR                       (((uint32_t)0x8) << TIMER_CNTRL_EVENT_SEL_Pos)  /*!< Selects ETR event. */
Так же не забудем про включение тактирования таймера и подачу на него тактового сигнала для счета.
Вот так мы инициализировали таймер.
TIMER_CntInitTypeDef timerButtonCheck_structInit; // Структура для настройки основного таймера вызова прерывания для опроса клавиш.
RST_CLK_PCLKcmd(RST_CLK_PCLK_TIMER2, ENABLE); // Включаем тактирование таймера 1.
TIMER_BRGInit(MDR_TIMER2, TIMER_HCLKdiv1); // Подаем источник сигнала для счета (частота процессора без предделителя).

// Заполняем структуру основного таймера.
timerButtonCheck_structInit.TIMER_ARR_UpdateMode = TIMER_ARR_Update_Immediately; // Регистр ARR можно обновлять в любое время.
timerButtonCheck_structInit.TIMER_BRK_Polarity = TIMER_BRKPolarity_NonInverted; // BRK сигнал не инвертируется (нас этот параметр не касается).
timerButtonCheck_structInit.TIMER_CounterDirection = TIMER_CntDir_Up; // Считаем «вверх». CNT инкрементируется (CNT++).
timerButtonCheck_structInit.TIMER_CounterMode = TIMER_CntMode_ClkFixedDir; // Считаем в одну сторону, вверх.
timerButtonCheck_structInit.TIMER_ETR_FilterConf = TIMER_Filter_1FF_at_TIMER_CLK; // Сигнал зафиксирован в 1-м триггере на частоте TIM_CLK (В нашем случае оставляем по-умолчанию).
timerButtonCheck_structInit.TIMER_ETR_Polarity = TIMER_ETRPolarity_NonInverted; // ETR на входе не инвертируется (мы его и не используем).
timerButtonCheck_structInit.TIMER_ETR_Prescaler = TIMER_ETR_Prescaler_None; // Частота ETR на входе не делится (ETR не используем.).
timerButtonCheck_structInit.TIMER_EventSource = TIMER_EvSrc_TM2; // Таймер вызывает прерывание при CNT = ARR.
timerButtonCheck_structInit.TIMER_FilterSampling = TIMER_FDTS_TIMER_CLK_div_1; // FDTS = TIMER_CLK. (Так не используем.).
timerButtonCheck_structInit.TIMER_IniCounter = 0; // Считаем с 0. Начальное значение счетчика. (CNT = 0.).
timerButtonCheck_structInit.TIMER_Period = 250/25; // Считаем до указанного в параметрах функции значения (ARR = PWM_speed).
timerButtonCheck_structInit.TIMER_Prescaler = 32000 — 1; // Делитель входного сигнала. PSG регистр.

TIMER_CntInit(MDR_TIMER2, &timerButtonCheck_structInit); // Инициализируем основной таймер.

Далее воспользуемся встроенной в CMSIS функцией для разрешения прерывания от всего таймера (ее мы разобрали в предыдущей статье) и включим таймер.
Полноценная функция инициализации.
// Настройка таймера для генерации прерываний 25 раз в секунду при помощи SPL.
void initTimerButtonCheckSPL (void) 
{ 
 TIMER_CntInitTypeDef timerButtonCheck_structInit; // Структура для настройки основного таймера вызова прерывания для опроса клавиш.
 RST_CLK_PCLKcmd(RST_CLK_PCLK_TIMER2, ENABLE); // Включаем тактирование таймера 1.
 TIMER_BRGInit(MDR_TIMER2, TIMER_HCLKdiv1); // Подаем источник сигнала для счета (частота процессора без предделителя).
        
 // Заполняем структуру основного таймера.
 timerButtonCheck_structInit.TIMER_ARR_UpdateMode = TIMER_ARR_Update_Immediately; // Регистр ARR можно обновлять в любое время.
 timerButtonCheck_structInit.TIMER_BRK_Polarity = TIMER_BRKPolarity_NonInverted; // BRK сигнал не инвертируется (нас этот параметр не касается).
 timerButtonCheck_structInit.TIMER_CounterDirection = TIMER_CntDir_Up; // Считаем "вверх". CNT инкрементируется (CNT++). 
 timerButtonCheck_structInit.TIMER_CounterMode = TIMER_CntMode_ClkFixedDir; // Считаем в одну сторону, вверх.
 timerButtonCheck_structInit.TIMER_ETR_FilterConf = TIMER_Filter_1FF_at_TIMER_CLK; // Сигнал зафиксирован в 1-м триггере на частоте TIM_CLK (В нашем случае оставляем по-умолчанию).
 timerButtonCheck_structInit.TIMER_ETR_Polarity = TIMER_ETRPolarity_NonInverted; // ETR на входе не инвертируется (мы его и не используем).
 timerButtonCheck_structInit.TIMER_ETR_Prescaler = TIMER_ETR_Prescaler_None; // Частота ETR на входе не делится (ETR не используем.).
 timerButtonCheck_structInit.TIMER_EventSource = TIMER_EvSrc_TM2; // Таймер вызывает прерывание при CNT = ARR.
 timerButtonCheck_structInit.TIMER_FilterSampling = TIMER_FDTS_TIMER_CLK_div_1; // FDTS = TIMER_CLK. (Так не используем.).
 timerButtonCheck_structInit.TIMER_IniCounter = 0; // Считаем с 0. Начальное значение счетчика. (CNT = 0.).
 timerButtonCheck_structInit.TIMER_Period = 250/25;                                                                                                                             // Считаем до указанного в параметрах функции значения (ARR = PWM_speed).
 timerButtonCheck_structInit.TIMER_Prescaler = 32000 - 1; // Делитель входного сигнала. PSG регистр. 

 TIMER_CntInit(MDR_TIMER2, &timerButtonCheck_structInit); // Инициализируем основной таймер.
        
 TIMER_ITConfig(MDR_TIMER2, TIMER_STATUS_CNT_ARR, ENABLE); // Разрешаем прерывание по CNT = ARR.        
 NVIC_EnableIRQ(Timer2_IRQn); // Разрешаем прерывание от таймера в целом.
        
 TIMER_Cmd(MDR_TIMER2, ENABLE); // Включаем таймер.
}

Переводим прерывание на SPL.

Последним шагом будет перевод на SPL функций в прерывании. Прерывание имеет все то же стандартное имя, указанное в стартап файле. Помним, что при входе в прерывание нам нужно сбросить флаг статуса таймера. Для этого служит функция TIMER_ClearFlag.
void TIMER_ClearFlag(MDR_TIMER_TypeDef* TIMERx, uint32_t Flags)
В качестве параметров требуется указать имя порта и флага прерывания. В нашем случае будет:
TIMER_ClearFlag(MDR_TIMER2, TIMER_STATUS_CNT_ARR); // Сбрасываем флаг. Обязательно первой коммандой.
После этого мы инвертировали состояние светодиода, показывая, что прерывание сработало. В SPL нет функции инвертирования бита, зато есть функция чтения и записи единичных бит. Ими и воспользуемся.
uint8_t PORT_ReadInputDataBit(MDR_PORT_TypeDef* PORTx, uint32_t PORT_Pin);
void PORT_WriteBit(MDR_PORT_TypeDef* PORTx, uint32_t PORT_Pin, BitAction BitVal);
У обеих первым параметром идет имя порта, далее у функции чтения нужно указать имя пина. Указывается именно маска. У функции записи, после имени порта следует указать бит, который записывается (так же маской) и значение бита (0 или 1). Функцию чтения можно использовать как параметр функции записи. Тогда мы получим:
PORT_WriteBit(MDR_PORTC, LED1, !PORT_ReadInputDataBit(MDR_PORTC, LED1)); // Записываем инвертированное значение бита.
После этого нам нужно считывать данные с кнопок. Причем по одной кнопке. Для этого воспользуемся функцией PORT_ReadInputDataBit, разобранной выше.
Опрос кнопок будет выглядеть так.
if (PORT_ReadInputDataBit(MDR_PORTB, UP_MSK) == 0) PWM_speed--; // Проверяем, нажата ли какая-нибудь клавиша. Если нажата - что-то делаем с частотой.
                else if (PORT_ReadInputDataBit(MDR_PORTE, DOWN_MSK) == 0) PWM_speed++;                  
                else if (PORT_ReadInputDataBit(MDR_PORTE, LEFT_MSK) == 0) PWM_speed--;
                else if (PORT_ReadInputDataBit(MDR_PORTB, RIGHT_MSK)== 0) PWM_speed++;
Осталось только сменить частоту по окончании опроса кнопок. Для этого есть функция TIMER_SetCntAutoreload.
void TIMER_SetCntAutoreload(MDR_TIMER_TypeDef* TIMERx, uint16_t Autoreload)
Нам нужно лишь указать таймер с PWM и новую частоту.
По итогу имеем прерывание вида.
void Timer2_IRQHandler (void)
{
        TIMER_ClearFlag(MDR_TIMER2, TIMER_STATUS_CNT_ARR); // Сбрасываем флаг. Обязательно первой коммандой.                                                                                                                                                                                                            
        PORT_WriteBit(MDR_PORTC, LED1, !PORT_ReadInputDataBit(MDR_PORTC, LED1)); // Записываем инвертированное значение бита.           
        if (PORT_ReadInputDataBit(MDR_PORTB, UP_MSK) == 0) PWM_speed--; // Проверяем, нажата ли какая-нибудь клавиша. Если нажата - что-то делаем с частотой.
                else if (PORT_ReadInputDataBit(MDR_PORTE, DOWN_MSK) == 0) PWM_speed++;                  
                else if (PORT_ReadInputDataBit(MDR_PORTE, LEFT_MSK) == 0) PWM_speed--;
                else if (PORT_ReadInputDataBit(MDR_PORTB, RIGHT_MSK)== 0) PWM_speed++;
        
        // Проверяем, чтобы частота не вышла за пределы диапазона от 250 Гц до 0.5 Гц.
        if (PWM_speed < 1) PWM_speed = 1;                                                                    
                else if (PWM_speed > 500) PWM_speed = 500;
        
        TIMER_SetCntAutoreload(MDR_TIMER1, PWM_speed); // Меняем частоту.                                       
}

Подведение итогов.


Как мы могли убедиться, с использованием SPL код стал выглядеть намного обьёмнее. Но давайте сравним вес полученного кода. В код, написанный лишь с использованием CMSIS со всеми видами оптимизации занимает столько.

Наш же код с оптимизацией -O0 весит столько.

С оптимизацией -O3.

Код с использованием SPL весит в 4-5 раз больше, чем написанный вручную. Про скорость выполнения я вообще молчу. Это тема отдельной статьи, коих не мало. Теперь подведем итоги.
Плюсы использования SPL
  1. Написанный с помощью SPL код воспринимается однозначно, при наличии соответствующего описания к самой SPL (чего пока, к сожалению, нет).
  2. При правильном заполнении структур — все будет сконфигурировано верно. Отсутствие ошибок. Мне пока не удалось обнаружить ни единой ошибки в SPL. Даже несмотря на то, что это B-версия.

Минусы использования SPL
  1. Размер кода поражает воображение.
  2. Скорость выполнения так же ниже.
  3. На данный момент с SPL сложнее разобраться, чем с CMSIS.
  4. Не всегда можно угадать логику человека, который писал библиотеку. Порой неработоспособность объясняется тем, что то, что должно было быть включено в функцию — вынесено из нее в другу.
  5. Огромное количество места строк кода для инициализации структур.

Вывод

SPL имеет право на жизнь, но лишь после того, как будут ясно описаны все его возможности. Причем использовать его возможно лишь в задачах, где не требуется много вычислительной мощности и объема памяти. Лично мне оказалось гораздо проще настроить всю описанную периферию, читая документацию и пропуская не нужные регистры.

Файл проекта.

Список предыдущих статей.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Комментариев нет:

Отправить комментарий