Вступление.
В предыдущей статье мы с вами повторили общую структуру таймера и детально рассмотрели ручной способ настройки ШИМ канала с использованием CMSIS. Но многим не нравится «копаться в регистрах» и они предпочитают принципиально другой уровень абстракции, позволяющий, как им кажется, упростить задачу. В этой статье я попытаюсь показать вам все плюсы и минусы данного подхода.Изменение в подаче материала.
Во всех предыдущих статьях я описывал все знания по какой-либо задаче в одном последовательно сформированном тексте, стараясь не упустить всех тонкостей и деталей, получая при этом достаточно объемную, но исчерпывающую любые вопросы, статью. В результате, мнения о моих статьях разделились. Кому-то нравилось, что в статьях нет отсылок к литературе и вся необходимая информация для понимания статьи находится в самой статье или в ее предшественниках. А кому-то наоборот было не интересно читать про «элементарные вещи» (такие, как синтаксис языка, стандартная организация блоков, и т.д.) и люди закрывали статью не прочтенной. Так как я не люблю отсылать людей к литературе, но при этом не хочу, чтобы что-либо в статье было непонятно, то материал теперь будет излагаться следующим образом: основной текст — текст для людей, разбирающихся в том, о чем читают и имеющих некоторый опыт работы по данной тематике. Для тех, кто что-либо не знает или не до конца понимает — под спойлерами с пометками «Пояснения к....» — будет собрана вся необходимая для понимания статьи информация.Задача.
Наша задача решить ту же задачу, что мы решали в предыдущей статье, но с использованием только лишь возможностей SPL. По итогу мы сможем сказать, какой подход более нагляден, быстр и меньше весит. Как следствие — мы создадим столько же функций, сколько было в предыдущей реализации с такими же именами, за исключением того, что добавим «SPL», чтобы можно было их отличить и сравнить влияние каждой функции на вес и производительность кода (Заменяя функцию ручной инициализации на функцию с автоматической инициализацией средствами SPL).Настройка портов ввода-вывода средствами SPL (PORT).
Начать предлагаю с самого простого. С портов ввода-вывода для вручную управляемого светодиода. Раньше эта функция называлась initPinPortCForLed. Теперь будет initPinPortCForLedSPL. Имена последующих функций будут иметь такой же принцип именования. Как мы помним, для того, чтобы порт запустился и мы смогла зажечь светодиод — нужно:- Подать сигнал тактирования на порт.
- Инициализировать сам порт.
- Выставить значение в регистр RXTX.
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; /*!< Specifies the TIMER Channel number to be configured.
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; /*!< Specifies the TIMER Channel number to be configured.
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 весит в 2-2.5 раза больше, чем написанный вручную. Результат не плохой, но все же уступает ручному написанию. Скорость выполнения, конечно, много меньше. Это тема отдельной статьи, коих не мало. Теперь подведем итоги.
Плюсы использования SPL
- Написанный с помощью SPL код воспринимается однозначно, при наличии соответствующего описания к самой SPL (чего пока, к сожалению, нет).
- При правильном заполнении структур — все будет сконфигурировано верно. Отсутствие ошибок. Мне пока не удалось обнаружить ни единой ошибки в SPL. Даже несмотря на то, что это B-версия.
Минусы использования SPL
- Размер кода больше, чем при ручной настройке.
- Скорость выполнения так же ниже.
- На данный момент с SPL сложнее разобраться, чем с CMSIS.
- Не всегда можно угадать логику человека, который писал библиотеку. Порой неработоспособность объясняется тем, что то, что должно было быть включено в функцию — вынесено из нее в другу.
- Огромное количество места строк кода для инициализации структур.
Вывод
SPL имеет право на жизнь, но лишь после того, как будут ясно описаны все его возможности. Причем использовать его возможно лишь в задачах, где не требуется много вычислительной мощности и объема памяти. Лично мне оказалось гораздо проще настроить всю описанную периферию, читая документацию и пропуская не нужные регистры.Файл проекта.
P.S. Спасибо Amomum за подсказку с --feedback unused. Благодаря этому код стал весить действительно много меньше, в связи с этим подправил статью.
- 1. Переходим с STM32F103 на К1986ВЕ92QI. Или первое знакомство с российским микроконтроллером.
- 2. Переходим с STM32 на российский микроконтроллер К1986ВЕ92QI. Настройка проекта в keil и мигание светодиодом.
- 3. Переходим с STM32 на российский микроконтроллер К1986ВЕ92QI. Системный таймер (SysTick).
- 4. Переходим с STM32 на российский микроконтроллер К1986ВЕ92QI. Настройка тактовой частоты.
- 5. Переходим с STM32 на российский микроконтроллер К1986ВЕ92QI. Практическое применение: Генерируем и воспроизводим звук. Часть первая: генерируем прямоугольный и синусоидальный сигнал. Освоение ЦАП (DAC).
- 6. Переходим с STM32 на российский микроконтроллер К1986ВЕ92QI. Практическое применение: Генерируем и воспроизводим звук. Часть вторая: генерируем синусоидальный сигнал. Освоение DMA.
- 7. Переходим с STM32 на российский микроконтроллер К1986ВЕ92QI. Практическое применение: Генерируем и воспроизводим звук. Часть третья: генерируем синусоидальный сигнал. Простой взгляд на DMA + первое знакомство с таймерами.
- 8. Переходим с STM32 на российский микроконтроллер К1986ВЕ92QI. Практическое применение: Генерируем и воспроизводим звук. Часть четвертая: создаем цифровую часть одноголосной и многоголосой музыкальной открытки.
- 9, Переходим с STM32 на российский микроконтроллер К1986ВЕ92QI. Практическое применение: Опрашиваем клавиши, генерируем ШИМ. Сравнение кода на CMSIS и SPL (PWM+TIM+PORT). Часть первая.
Комментарии (15)
VioletGiraffe
14.11.2015 23:28+2А он такой же российский, как в своё время серия 580 была?
Vadimatorikda
15.11.2015 04:41Разработка — да, но на основе Cortex-m3 купленного. Спор на эту тему был в самой первой статье. Можете изучить при желании.
Carry
15.11.2015 15:40Вся проблема перехода с STM32 на К1986ВЕ92QI заключается в отсутствии последнего а не в программировании банальной периферии.
Я даже на аналог AVR никак не могу перейти т.к. не поставляется. Зато в железном корпусе.
Может, через год микросхема придет, запаяю, пощупаю.Vadimatorikda
15.11.2015 15:51Периферия не всегда банальная. Взять тот же DMA… (Есть в предыдущих статьях). А так — да. Но никто не мешает позвонить в Миландр и договориться о покупке нескольких чипов. Вам еще их и доставят в любую точку страны.
Carry
16.11.2015 00:37Когда что-то работает совсем не так как описано, или описано неявно — это да, вызывает трудности. Тут и у именитых производителей проблемы бывают. В этом плане меня TI периодически напрягает.
А вот наш чип хоть какой-нибудь пощупать пока не удалось.
Плату не покупают т.к. этот космолет уже куплен, а пощупать нельзя т.к. не я один.
Микросхема заказана, печать есть, в ней работает китайский дублер, ждем нашего (с китайским кристаллом?).
Carry
16.11.2015 00:52Если микросхемы такие дорогие и тяжело производимые — можно сделать отладочные платы в виде законченного устройства с электрически защищенными внешними интерфейсами с сдавать их в аренду за недорого.
Vadimatorikda
16.11.2015 04:35Я вот думаю на эту тему. Вроде бы даже есть группы в контакте, которые продают готовые платки с этими контроллерами. Стоят значительно дешевле моей.
Amomum
15.11.2015 22:11+1Попробуйте включить ключ компилятору и линкеру '--feedback unused', он должен выкинуть все неиспользуемые функции после двух компиляций. Как правило, размер бинарника уменьшается заметно.
Vadimatorikda
16.11.2015 04:31Огромное спасибо! Сейчас заменю картинки и пересчитаю сравнение, для предоставления более точных результатов.
grossws
16.11.2015 18:34А
-flto
в их линкере поддерживается? Если да, то тоже способ чуток заоптимизировать.Amomum
16.11.2015 18:58Судя по тому, что я нагуглил, аналогом является галка «cross module optimization». На моем первом попавшемся под руку проекте никакого результата она не дала (но это, конечно, ни о чем не говорит).
Тоже надо запомнить, спасибо за идею.
Amomum
16.11.2015 12:35По поводу скорости выполнения — достаточно спорный вопрос. Как правило, SPL используется в основном на этапе инициализации периферии, т.е. один раз после включения, соответственно какого-то заметного эффекта на скорость работы прошивки это не оказывает.
В каких-то критичных по времени кусках — в прерываниях, например — можно спокойно использовать только CMSIS (или ассемблер).Vadimatorikda
16.11.2015 12:40Полностью согласен. Но ради интереса в одной из следующих статей постараюсь использовать в прерывании сначала CMSIS, а потом SPL. В каком-нибудь очень критичном ко времени примере.
Phizio
А как же КДПВ? или не провёл ещё никто для наших импортозамещенниц няшные фотосессии?
Vadimatorikda
КДПВ была целым видео к предыдущей статье (в первой части). Это, можно сказать, продолжение. Что не влезло в предыдущую (было бы сложно читать их вместе...). Так что КДПВ для обеих в первой.