📜 ⬆️ ⬇️

We are switching from STM32 to the Russian K1986BE92QI microcontroller. Practical application: We interrogate keys, we generate PWM. Comparison of CMSIS and SPL code (PWM + TIM + PORT). Part two

Introduction.

In the previous article, we repeated the general structure of the timer and examined in detail the manual way to configure the PWM channel using CMSIS. But many do not like “digging into registers” and they prefer a fundamentally different level of abstraction, which allows, as it seems to them, to simplify the task. In this article I will try to show you all the pros and cons of this approach.

The change in the supply of material.

In all previous articles, I described all the knowledge on a task in one consistently formed text, trying not to miss all the subtleties and details, while receiving a rather lengthy, but exhaustive, any questions article. As a result, opinions about my articles are divided. Someone liked that the articles did not contain references to literature and all the necessary information for understanding the article is in the article itself or in its predecessors. But on the contrary, it was not interesting for someone to read about “elementary things” (such as language syntax, standard block organization, etc.) and people closed the article that was not read. Since I do not like to refer people to literature, but at the same time I don’t want anything in the article to be clear, the material will now be presented as follows: the main text is the text for people who understand what they read and have experience on this topic. For those who do not know something or do not fully understand it - under the spoilers marked "Explanations to ...." - all the information necessary for understanding the article will be collected.

Task.

Our task is to solve the same problem that we solved in the previous article, but using only SPL capabilities. As a result, we will be able to say which approach is more visual, fast and weighs less. As a consequence, we will create as many functions as there were in the previous implementation with the same names, except that we add “SPL” to distinguish them and compare the effect of each function on the weight and performance of the code (Replacing the manual initialization function with function with automatic initialization by means of SPL).
')

Configuring I / O ports using SPL (PORT).

I propose to start with the simplest. With I / O ports for a manually controlled LED. Previously, this function was called initPinPortCForLed. Now there will be initPinPortCForLedSPL. Names of subsequent functions will have the same naming convention. As we remember, in order for the port to start and we could light the LED - you need:
  1. Send a clock signal to the port.
  2. Initialize the port itself.
  3. Set the value to the register RXTX.
So it was when we worked with CMSIS directly. With SPL, it's a little different. To configure any peripheral module you need to fill the structure. Sometimes - not one. And then transfer it to a function that sets everything up.
Explanation of what was said.
You can draw an analogy with the construction of the house: you make a drawing for all the requirements, and then pass it on to the people who are building the house themselves. You do not care how the house will be built. You mean exactly as in your drawing. Here the “drawing” is the structure you have configured. And “people building a house” is a function of SPL. For each block of the periphery there is its own structure. You can find out what structure is required by looking into a file in the spl folder (in the project tree) with the name MDR32F9Qx_peripheral_name.
First we need to send a clock signal to the port. To do this, we need to refer to the file MDR32F9Qx_rst_clk.h . In it, at the very end, there are functions that SPL can provide us. Of all the variety of functions, we are only interested in RST_CLK_PCLKcmd . With its help, we can send a clocking signal to any peripheral block.
void RST_CLK_PCLKcmd(uint32_t RST_CLK_PCLK, FunctionalState NewState); 
The function has two parameters:
Recall that our LED is connected to PC1. It is not difficult to guess that in our case the function will look like this.
 RST_CLK_PCLKcmd(RST_CLK_PCLK_PORTC, ENABLE); //    C. 
Now we have a clock signal on the I / O port we need and we can begin to configure it. First we need to find the function that configures the port. It is in the file MDR32F9Qx_port.h . Called PORT_Init and has the following form.
 void PORT_Init(MDR_PORT_TypeDef* PORTx, const PORT_InitTypeDef* PORT_InitStruct); 
As we can see, this function also has 2 parameters:
  1. MDR_PORT_TypeDef is the name of the port we are setting up. In the format MDR_PORTX, where instead of X - the letter of our port (A, B, C ...). In our case there will be MDR_PORTC .
  2. The second parameter is a structure of the form PORT_InitTypeDef . Its description is in the same file ( MDR32F9Qx_port.h ). Unfortunately, the SPL description is entirely in English. As a result, a person who does not know English and is unfamiliar with the device of the periphery at the register level will be quite hard. Yes, and sometimes on the true meaning of the comments to the functions have at first just guess. An understanding of their purpose comes only after a careful study of the block diagram of a peripheral module.
    Structure Description 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; 
    Explanation: what is a structure, how to fill it, where to get the values?
    The structure, in fact, is an array, each fixed (has its place in the array) whose cell contains some parameter. Unlike an array, each structure parameter can have its own type. Like an array, before filling, the structure must be created.
     PORT_InitTypeDef Led0PortC_structInit; //   C. 
    Here PORT_InitTypeDef is a type . In other words - just a template, on the basis of which the “markup” of memory occurs. Led0PortC_structInit - the name of a specific structure, invented by us. Like creating a variable of type uint32_t, we set its name, for example Loop, and here we create a structure of type PORT_InitTypeDef with the name Led0PortC_structInit. It is important to note that the declaration of the structure must be done in the function before the first command. Otherwise the project will not meet. After creating the structure you need to fill it. Filling is as follows.
     _._ = - ; 
    And so - for each parameter from the description. In the description of each cell there is an explanation of what values ​​you can write to it. As a rule, if the value is not some arbitrary number from any range, then the description contains the word ref . With the help of the word after it, you can find in the file all the available values ​​for this cell. Take the first cell as an example.
     uint16_t PORT_Pin; /*!< Specifies PORT pins to be configured. This parameter is a mask of @ref PORT_pins_define values. */ 
    Using the search, we find PORT_pins_define .
    We see the following.
     #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 */ 
    We have PORTC output 1. According to the idea, we can write
     Led0PortC_structInit.PORT_Pin = PORT_Pin_1; 
    But we still have such a define since the last article.
     //  . #define LED0 (1<<0) // PORTC. #define LED1 (1<<1) // PORTC. 
    This is the same port mask as in the description, but with a different name. But it gives a clearer idea of ​​what we are connecting, so I will write it down.
     Led0PortC_structInit.PORT_Pin = LED1; //   . 
Having filled out the structure, it will only be necessary to specify it as a parameter of the SPL PORT_Init function, not forgetting the "&" before the name of the structure (passing a pointer to the structure).
We will receive ready function of a type.
 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); //  . } 
The key initialization function is similar. The only difference is that we specify the output mode instead of the output - the input ( PORT_OE_IN ), and also turn on the input filter ( PORT_GFEN_ON ).
The function of initializing pins connected to the buttons.
 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); } 

Setting the timer to generate PWM (PWM).

Before setting the timer itself, we will configure the output of the I / O port, to which we will output the PWM to alternative function mode. Remember that in the last article we used the PORTA port and output 1.
The following will be released.
 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); //  . 
Now we can proceed to setting up the timer itself. First of all, we need to apply clocking to TIMER1. This can also be done using the RST_CLK_PCLKcmd function discussed earlier.
 RST_CLK_PCLKcmd(RST_CLK_PCLK_TIMER1, ENABLE); //    1. 
Next is to clearly identify the task. We need:
  1. Set the main timer.
  2. Configure channel timer.
  3. Set the timer output.
  4. Set the clock frequency for the entire timer.
  5. Enable timer.
For each item in the SPL has its own function, and for the first three points there are also their own structures. All functions and their parameters are in the file MDR32F9Qx_timer.h . Let's start with the first item. To initialize the main timer there is a function TIMER_CntInit .
 void TIMER_CntInit(MDR_TIMER_TypeDef* TIMERx, const TIMER_CntInitTypeDef* TIMER_CntInitStruct); 
Here are two parameters.
Filling in all the fields and initializing the timer we get the following.
 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); //   . 
As you can see from the comments to the code for filling the structure - most of the items remain by default (disabled). But despite this, you still need to specify.
Next, you need to initialize the timer channel. In our case - the first. The function TIMER_ChnInit is responsible for channel initialization.
 void TIMER_ChnInit(MDR_TIMER_TypeDef* TIMERx, const TIMER_ChnInitTypeDef* TIMER_ChnInitStruct) 
The first parameter is the name of the timer being initialized. It remains the same as that of the initial timer initialization function. But the structure is of type TIMER_ChnInitTypeDef .
Here is her description.
typedef struct
{
uint16_t TIMER_CH_Number; / *! <Specifies the 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;
Also fill and initialize.
 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); //  . 
I note that it is in this function that we form a signal at REF for PWM.
Next you need to configure the channel timer to exit. For this there is a function TIMER_ChnOutInit .
 void TIMER_ChnOutInit(MDR_TIMER_TypeDef* TIMERx, const TIMER_ChnOutInitTypeDef* TIMER_ChnOutInitStruct); 
The first parameter is the name of our timer. The second is the TIMER_ChnOutInitStruct structure.
Her description.
typedef struct
{
uint16_t TIMER_CH_Number; / *! <Specifies the 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;
After filling in the structure and initialization, we will observe the following code.
 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); //    . 
Now it only remains to apply a clock to the timer and start it. To supply a clock signal (the one on the basis of which the timer counts) is the function TIMER_BRGInit .
 void TIMER_BRGInit(MDR_TIMER_TypeDef* TIMERx, uint32_t TIMER_BRG); 
The first parameter, as usual, is the name of the timer, the second is the divisor. The divider is calculated in the same way as for the PSG register in the previous article (in fact, this function is only written by our divider in the PSG ...). I also note that the same function also allows the clock signal to be sent to the default timer. Well, for the inclusion of the function responsible TIMER_Cmd .
 void TIMER_Cmd(MDR_TIMER_TypeDef* TIMERx, FunctionalState NewState) 
Parameters - the name of the timer and its state ENABLE / DISABLE .
According to the results of the whole setup, we get the following.
// Initialization of the timer in the PWM mode for working with the LED in the SPL mode.
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); // .
}

Timer setting for interrupt call (IRQ)

Next, we need to configure a timer that generates interrupts for polling keys. Here we will need to set the timer only for the first structure. Since the channels and outputs we do not use. The initialization of the timer will look the same as the previous one, with the exception of the TIMER_EventSource cell . In it, we must indicate on which event we have an interruption. In the last article, we used CNT == ARR. His and use.
In general, the following options are possible.
 #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. */ 
Just do not forget about the inclusion of a timer clocking and the filing of a clock signal for the account.
This is how we initialized the timer.
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); // .
Next, we use the built-in function in CMSIS to enable interrupts from the entire timer (we analyzed it in the previous article) and turn on the timer.
Full initialization function.
 //      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); //  . } 

We translate interruption on SPL.

The final step is to translate the SPL functions in the interrupt. An interrupt has all the same standard name specified in the startup file. Remember that when entering an interrupt, we need to reset the timer status flag. To do this, use the TIMER_ClearFlag function .
 void TIMER_ClearFlag(MDR_TIMER_TypeDef* TIMERx, uint32_t Flags) 
As parameters, you must specify the name of the port and the interrupt flag. In our case will be:
 TIMER_ClearFlag(MDR_TIMER2, TIMER_STATUS_CNT_ARR); //  .   . 
After that, we inverted the LED state, indicating that the interrupt worked. In the SPL there is no function to invert a bit, but there is a function to read and write single bits. And we will use them.
 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); 
For both, the first parameter is the name of the port, then the reading function needs to specify the pin name. Specified exactly mask . In the recording function, after the port name you should specify the bit that is written (also masked) and the value of the bit (0 or 1). The read function can be used as a write function parameter. Then we get:
 PORT_WriteBit(MDR_PORTC, LED1, !PORT_ReadInputDataBit(MDR_PORTC, LED1)); //    . 
After that we need to read data from the buttons. And one button. To do this, we use the PORT_ReadInputDataBit function , which has been parsed above.
Poll buttons will look like this.
 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++; 
It remains only to change the frequency at the end of the survey buttons. For this there is a function TIMER_SetCntAutoreload .
 void TIMER_SetCntAutoreload(MDR_TIMER_TypeDef* TIMERx, uint16_t Autoreload) 
We only need to specify a timer with PWM and a new frequency.
As a result, we have an interrupt view.
 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); //  . } 

Summarizing.


As we could see, using SPL, the code began to look much larger. But let's compare the weight of the resulting code. In the code written only using CMSIS with all kinds of optimization takes so much.

Our code with optimization -O0 weighs so much.

With -O3 optimization.

Code using SPL weighs 2-2.5 times more than written by hand. The result is not bad, but still inferior to manual writing. The execution speed is, of course, much less. This is a topic for a separate article, of which there are plenty. Now let's summarize.
Benefits of using SPL
  1. The code written with the help of SPL is perceived unambiguously, if there is an appropriate description for the SPL itself (which so far, unfortunately, is not).
  2. — . . SPL. , B-.

SPL
  1. , .
  2. .
  3. SPL , CMSIS.
  4. , . , , — .
  5. .

Conclusion

SPL has the right to life, but only after all its possibilities are clearly described. And it is possible to use it only in tasks where a lot of computing power and memory size is not required. Personally, it turned out to be much easier for me to configure all the peripherals described, reading the documentation and skipping unnecessary registers.

Project file

PS Thank you Amomum for hint with --feedback unused. Due to this, the code began to weigh really much less, in connection with this, I corrected the article.

Source: https://habr.com/ru/post/270863/


All Articles