Вступление.
В предыдущей статье мы с вами повторили общую структуру таймера и детально рассмотрели ручной способ настройки ШИМ канала с использованием CMSIS. Но многим не нравится «копаться в регистрах» и они предпочитают принципиально другой уровень абстракции, позволяющий, как им кажется, упростить задачу. В этой статье я попытаюсь показать вам все плюсы и минусы данного подхода.Изменение в подаче материала.
Во всех предыдущих статьях я описывал все знания по какой-либо задаче в одном последовательно сформированном тексте, стараясь не упустить всех тонкостей и деталей, получая при этом достаточно объемную, но исчерпывающую любые вопросы, статью. В результате, мнения о моих статьях разделились. Кому-то нравилось, что в статьях нет отсылок к литературе и вся необходимая информация для понимания статьи находится в самой статье или в ее предшественниках. А кому-то наоборот было не интересно читать про «элементарные вещи» (такие, как синтаксис языка, стандартная организация блоков, и т.д.) и люди закрывали статью не прочтенной. Так как я не люблю отсылать людей к литературе, но при этом не хочу, чтобы что-либо в статье было непонятно, то материал теперь будет излагаться следующим образом: основной текст — текст для людей, разбирающихся в том, о чем читают и имеющих некоторый опыт работы по данной тематике. Для тех, кто что-либо не знает или не до конца понимает — под спойлерами с пометками «Пояснения к....» — будет собрана вся необходимая для понимания статьи информация.Задача.
Наша задача решить ту же задачу, что мы решали в предыдущей статье, но с использованием только лишь возможностей SPL. По итогу мы сможем сказать, какой подход более нагляден, быстр и меньше весит. Как следствие — мы создадим столько же функций, сколько было в предыдущей реализации с такими же именами, за исключением того, что добавим «SPL», чтобы можно было их отличить и сравнить влияние каждой функции на вес и производительность кода (Заменяя функцию ручной инициализации на функцию с автоматической инициализацией средствами SPL).Настройка портов ввода-вывода средствами SPL (PORT).
Начать предлагаю с самого простого. С портов ввода-вывода для вручную управляемого светодиода. Раньше эта функция называлась initPinPortCForLed. Теперь будет initPinPortCForLedSPL. Имена последующих функций будут иметь такой же принцип именования. Как мы помним, для того, чтобы порт запустился и мы смогла зажечь светодиод — нужно:- Подать сигнал тактирования на порт.
- Инициализировать сам порт.
- Выставить значение в регистр RXTX.
Можно провести аналогию с постройкой дома: вы делаете чертеж по всем требованием, а потом передаете его людям, которые сами строят дом. Вас не касается, как будет построен дом. Вы подразумеваете, что в точности — как на вашем чертеже. Здесь «чертеж» — это настроенная вами структура. А «люди, строящие дом» — функция SPL. Для каждого блока периферии существует своя структура. Узнать, какая требуется структура, можно заглянув в файл в папке spl (в дереве проекта) с именем MDR32F9Qx_имя_периферии.
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 — включить тактирование.
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 параметра:
- MDR_PORT_TypeDef — имя порта, который мы настраиваем. В формате MDR_PORTX, где вместо X — буква нашего порта (A, B, C...). В нашем случае будет MDR_PORTC.
- Второй параметр — это структура вида 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 — имя конкретной структуры, придуманное нами. Как создавая переменную типа uint32_t мы задавали ее имя, к примеру Loop, так и тут мы создаем структуру типа PORT_InitTypeDef с именем Led0PortC_structInit. Важно отметить, что объявление структуры должно быть сделано в функции до первой команды. Иначе проект не соберется. После создания структуры необходимо ее заполнить. Заполнение идет следующим образом.PORT_InitTypeDef Led0PortC_structInit; // На порту C.
И так — для каждого параметра из описания. В описании к каждой ячейке есть пояснение, какие значения можно в нее записывать. Как правило, если значением является не какое-то произвольное число из какого-либо диапазона, то в описании есть слово ref. С помощью слова, стоящего после него, можно найти в файле все доступные значения для данной ячейки. Возьмем в пример первую ячейку.имя_структуры.ее_параметр = какое-то значение;
Используя поиск, находим PORT_pins_define.uint16_t PORT_Pin; /*!< Specifies PORT pins to be configured. This parameter is a mask of @ref PORT_pins_define values. */
Видим следующее.У нас PORTC вывод 1. По идеи, мы можем написать#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 */
Но у нас еще со времен прошлой статьи остался такой define.Led0PortC_structInit.PORT_Pin = PORT_Pin_1;
Это такая же маска порта, что и в описании, только с другим именем. Но она дает более ясное представление о том, что мы подключаем, так что я запишу ее.// Подключение светодиодов. #define LED0 (1<<0) // PORTC. #define LED1 (1<<1) // PORTC.
Led0PortC_structInit.PORT_Pin = LED1; // Пин нашего светодиода.
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); // Инициализируем порт.
}
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); // Инициализируем порт.
RST_CLK_PCLKcmd(RST_CLK_PCLK_TIMER1, ENABLE); // Включаем тактирование таймера 1.
Далее стоит ясно обозначить задачу. Нам нужно:
- Настроить основной таймер.
- Настроить канал таймера.
- Настроить вывод таймера.
- Настроить тактовую частоту для работы всего таймера.
- Включить таймер.
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.
{
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); // Инициализируем канал.
Далее нужно настроить канал таймера на выход. Для этого есть функция TIMER_ChnOutInit.
void TIMER_ChnOutInit(MDR_TIMER_TypeDef* TIMERx, const TIMER_ChnOutInitTypeDef* TIMER_ChnOutInitStruct);
Первым параметром так же идет имя нашего таймера. Второй — структура TIMER_ChnOutInitStruct.
{
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); // Настраиваем канал на выход.
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.
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. */
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); // Инициализируем основной таймер.
// Настройка таймера для генерации прерываний 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++;
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
- Написанный с помощью SPL код воспринимается однозначно, при наличии соответствующего описания к самой SPL (чего пока, к сожалению, нет).
- При правильном заполнении структур — все будет сконфигурировано верно. Отсутствие ошибок. Мне пока не удалось обнаружить ни единой ошибки в SPL. Даже несмотря на то, что это B-версия.
Минусы использования SPL
- Размер кода поражает воображение.
- Скорость выполнения так же ниже.
- На данный момент с SPL сложнее разобраться, чем с CMSIS.
- Не всегда можно угадать логику человека, который писал библиотеку. Порой неработоспособность объясняется тем, что то, что должно было быть включено в функцию — вынесено из нее в другу.
- Огромное количество места строк кода для инициализации структур.
Вывод
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.
Комментариев нет:
Отправить комментарий