ARM? Cortex? M4 Cookbook
上QQ阅读APP看书,第一时间看更新

Configuring the hardware abstraction layer

The method we deployed in Chapter 1, A Practical Introduction to ARM® CORTEX® used Startup.c to provide a very basic Run Time Environment (RTE), and although this is sufficient to get started blinking LEDs, we need to define a more advanced RTE to take advantage of the other peripherals we'll meet in future recipes. The Application Programmers Interface (API) that STMicroelectronics (STMicro) provide for their microcontrollers is called a hardware abstraction layer (HAL), and CMSIS v2.0 compliant programs must configure this before initializing their peripherals. The RTE manager offers two routes named Classic and STM32CubeMX to configure the HAL. Selecting STM32CubeMX invokes a graphical tool developed by STMicro (freely available at www.st.com) that creates the RTE (that is, generates RTE.h and imports the associated libraries). We describe this process in Chapter 9, Embedded Toolchain. Since we're already familiar with the Classic API, we'll continue to use this, and simply add a few lines of code to configure the HAL.

How to do it…

For configuring the HAL follow the steps outlined:

  1. Make a copy of the folder helloBlinky_c1v1 which we created in Chapter 1, A Practical Introduction to ARM® CORTEX®, Understanding the simple use of GPIO and name it helloBlinky_c2v0.
    Tip

    Copying a folder and renaming it is a quick way to extend an existing project. Future recipes refer to this process as cloning the project.

  2. Open the project, and using the RTE manager, expand the CMSISRTOS (API) software component. Check the KeilRTX option. Click on Resolve, and exit using OK.
  3. Add #include "cmsis_os.h"
  4. Add a function prototype declaration, that is, void SystemClock_Config(void) in the file helloBlinky.c.
  5. Add the following lines of code (copy and paste from the example project CMSIS-RTOS Blinky):
    #ifdef __RTX
    extern uint32_t os_time;
    
    uint32_t HAL_GetTick(void) {
      return os_time; 
    }
    #endif 
  6. Copy the function named SystemClock_Config ( ) from the example project CMSIS-RTOS Blinky, and paste this into the file helloBlinky.c.
  7. Add calls to HAL_Init ( ) and SystemClock_Config ( ) at the beginning of main(). Our source code file helloBlinky.c should now appear as follows:
    #include "stm32f4xx_hal.h"        
    #include "Board_LED.h"
    #include "cmsis_os.h"
    
    /* Function Prototype */
    void SystemClock_Config(void);
    
    #ifdef __RTX
    extern uint32_t os_time;
    
    uint32_t HAL_GetTick(void) {
      return os_time; 
    }
    #endif
    
    /**
      * System Clock Configuration
      */
    void SystemClock_Config(void) {
      RCC_OscInitTypeDef RCC_OscInitStruct;
      RCC_ClkInitTypeDef RCC_ClkInitStruct;
    
      /* Enable Power Control clock */
      __HAL_RCC_PWR_CLK_ENABLE();
    
      /* The voltage scaling allows optimizing the power 
         consumption when the device is clocked below the
         maximum system frequency (see datasheet). */
    
      __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
    
      /* Enable HSE Oscillator and activate PLL 
          with HSE as source */
      RCC_OscInitStruct.OscillatorType =RCC_OSCILLATORTYPE_HSE;
      RCC_OscInitStruct.HSEState = RCC_HSE_ON;
      RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
      RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
      RCC_OscInitStruct.PLL.PLLM = 25;
      RCC_OscInitStruct.PLL.PLLN = 336;
      RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
      RCC_OscInitStruct.PLL.PLLQ = 7;
      HAL_RCC_OscConfig(&RCC_OscInitStruct);
    
      /* Select PLL as system clock source and configure 
         the HCLK, PCLK1 and PCLK2 clocks piders */
      RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_SYSCLK |
                                    RCC_CLOCKTYPE_PCLK1 |
                                    RCC_CLOCKTYPE_PCLK2;
      RCC_ClkInitStruct.SYSCLKSource = 
                                RCC_SYSCLKSOURCE_PLLCLK;
      RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
      RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
      RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
      HAL_RCC_ClockConfig(&RCC_ClkInitStruct,
                                      FLASH_LATENCY_5);
    }
    
    /**
      * Main function
      */
    int main (void) {
      const unsigned int Off_Code = 0x0000;
      const unsigned int On_Code = 0x00FF;
      unsigned int i;
      
      HAL_Init ( );   /* Init Hardware Abstraction Layer */
      SystemClock_Config ( );           /* Config Clocks */        
      LED_Initialize ( );                    /* LED Init */
      // etc...
    }
  8. Build and run the program.
Tip

Notice that the code executes about 10 times faster than the recipe of Chapter 1, A Practical Introduction to ARM® CORTEX®. Try commenting out the call SystemClock_Config ( ) in main ( ) by placing // immediately before the statement. Rebuild and run. Compare the execution speed of the two versions.

How it works…

The function SystemClock_Config ( ) comprehensively configures the clock tree shown in Figure 16 of STMicro's reference manual RM0090 (www.st.com). It selects the Phase Locked Loop (PLL) clock derived from the 25 MHz crystal controlled HSE clock as the System Clock, and configures the multiplier N = 336 and piders P = 2 and M = 25. The system clock frequency is given by:

The configuration values are held in two data structures (structs) called RCC_OscInitStruct and RCC_ClkInitStruct.

As we will see later in the chapter, functions may be declared implicitly by the function definition or explicitly by a function prototype. Function prototypes are considered to be preferable, and these are often declared in header files (for example, see Board_LED.h). So, in case we've given a prototype declaration first,

Structs just identify the arrangements of data in memory. We will discuss structs later once we've dealt with more basic data types such as integers.

Finally, the following section of code:

#include "cmsis_os.h"

#ifdef __RTX
extern uint32_t os_time;

uint32_t HAL_GetTick(void) {
  return os_time; 
}
#endif

It isn't strictly necessary for a program that only uses GPIO, but subsequent recipes using other peripherals need it. So, to avoid illustrating the configuration each time, we'll assume this boilerplate is included in all future recipes.

Lastly, we've called our source code file helloBlinky.c. This is the same name we gave the project. By convention, this indicates that this source code file contains the main() function.