STM32Cube Library – Part 2 Hello World

This is part 2 in a multi-part series. Read part 1 here: The STM32Cube library – part 1 Toolchain

We left off by generating a project in the STM32CubeMX application. Today we will modify the automatically generated code to do our bidding; namely blink several LEDs. This will also provide a tour of usage of the STM32Cube HAL library.

I will be using a stm32f4Discovery board for this tutorial, but it should work on most any other stm32 development board, just be sure to use the correct pins.

Navigate to the place that code we generated last time (you did save it, right?), there should be a Src, Inc, and Drivers directory. We will only modify the code in the Src and Inc directories, but we will take a look at the code in the Drivers file as well (This is where the libraries are stored).

 Directories in the STM32 HAL library

First an overview of the directory structure of Drivers. The folder is split into two subdirectories, CMSIS (the basic ARM Cortex stuff) and the HAL library. The HAL library directory also has a Src and Inc directory where its source files are kept. Whenever we want to use functions from the HAL library these files will need to be included into our project (actually the code generator does this for us).

The CMSIS folder is more interesting, it contains the register layout for the microcontroller, core processor definitions, and startup code. The ARM code (basic register layout, math libraries, etc) are in the top-level directories (Include, DSP_Lib, RTOS). I have not used these at all but they are there if you need them (some of the low-level code is used by the HAL library). However, we need most of the files from the Device folder so that is where we will focus. In that folder there are several folders with just one file ST and STM32F4xx (will be whatever device line you are using). In that folder there is a Source and Include directory. The Include directory has quite a few files in it, but the one that we directly include in our code is stm32f4xx.h (again based on what device line you are using). This file in turn includes the correct file for the exact device in use that defines the register layout for the device.

The Source directory contains the files necessary for starting up the microcontroller (initializing the internal clocks and the interrupt table) The two necessary files (for our purposes) are in Source/Templates/system_stm32f4xx.c (this holds the clock initialization code) and Source/Templates/gcc/startup_stm32f407xx.s this contains the interrupt table (set of functions for interrupt handlers) used by the microcontroller.

Library Documentation

Also, before we actually begin, I recommend having a look at the library documentation. This is available in the directory where the STM32CubeMX application downloaded a copy of the library code. On my computer, that is a directory called STM32Cube in my home folder. Inside you will find a folder called Repositories where the library code is kept for safekeeping. In this case, since we are using the STM32F4Discovery board we will look at the F4 directory. The Documentation folder has a pdf document that describes the directory structure and basic information about the library. However, what we are really after is the API documentation for the libraries. For the HAL library this is in Drivers/STM32F4xx_HAL_Driver/. In this folder there are several .chm documents for each line of f4 microcontrollers I use the xCHM viewer to open it. For the CMSIS code (lots of math stuff!) it is under the CHMIS/index.html.

Okay, enough of that! Lets get coding!

Writing the program

Lets take a look at Src/main.c (from the top-level directory of the project).

//STM Copyright here (omitted for space)
#include "stm32f4xx_hal.h" 

/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private variables ------------------------------------------*/
SPI_HandleTypeDef hspi1;

/* USER CODE BEGIN PV */
/* Private variables ------------------------------------------*/

/* USER CODE END PV */

/* Private function prototypes --------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_SPI1_Init(void);

/* USER CODE BEGIN PFP */
/* Private function prototypes --------------------------------*/

/* USER CODE END PFP */

/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration ----------------------------------------*/

  /*
  * Reset of all peripherals, Initializes the Flash interface
  * and the Systick.
  */
  HAL_Init();

  /* Configure the system clock */
  SystemClock_Config();

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_SPI1_Init();

  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/* SPI1 init function */ 
void MX_SPI1_Init(void) 
{ 
  hspi1.Instance = SPI1; 
  hspi1.Init.Mode = SPI_MODE_MASTER; 
  hspi1.Init.Direction = SPI_DIRECTION_2LINES; 
  hspi1.Init.DataSize = SPI_DATASIZE_8BIT; 
  hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; 
  hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; 
  hspi1.Init.NSS = SPI_NSS_SOFT; 
  hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2; 
  hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; 
  hspi1.Init.TIMode = SPI_TIMODE_DISABLE; 
  hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; 
  hspi1.Init.CRCPolynomial = 10; 
  HAL_SPI_Init(&hspi1); 
}
/*
* Function Definitions of SystemClock_Config() and MX_GPIO_Init()
* omitted for space.
*/

/* USER CODE BEGIN 4 */ 
 
/* USER CODE END 4 */

Lets break this down.

Here is the first section

#include "stm32f4xx_hal.h" 

/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private variables ------------------------------------------*/
SPI_HandleTypeDef hspi1;

/* USER CODE BEGIN PV */
/* Private variables ------------------------------------------*/

/* USER CODE END PV */

/* Private function prototypes --------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_SPI1_Init(void);

/* USER CODE BEGIN PFP */
/* Private function prototypes --------------------------------*/

/* USER CODE END PFP */

/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

Each of the /* USER CODE BEGIN… */ /*USER CODE END…*/ sections brackets off a section of code that you, the user, can write code in (what a revelation). Be sure not to remove these comments as they indicate to the code generator where to leave code alone.

You can also see the hspi1 variable generated by the STM32CubeMX program, this is the variable that the HAL library stores state information for the SPI peripheral. Each time we initialize a peripheral (other than GPIO pins) one of these state holding variables will be made they are initialized by the various, automatically generated MX_xxx_Init() functions.

/* SPI1 init function */ 
void MX_SPI1_Init(void) { 
  hspi1.Instance = SPI1; 
  hspi1.Init.Mode = SPI_MODE_MASTER; 
  hspi1.Init.Direction = SPI_DIRECTION_2LINES; 
  hspi1.Init.DataSize = SPI_DATASIZE_8BIT; 
  hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; 
  hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; 
  hspi1.Init.NSS = SPI_NSS_SOFT; 
  hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2; 
  hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; 
  hspi1.Init.TIMode = SPI_TIMODE_DISABLE; 
  hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; 
  hspi1.Init.CRCPolynomial = 10; 

  HAL_SPI_Init(&hspi1); //set up the peripheral 
}

This is one of the generated functions, it initializes the hspi1 variable to the correct state for setting up the SPI bus. Even though sequentially this function comes after the main code, I am putting it first to show you what is going on.

This function has a lot of constants and parameters that are SPI specific, but the idea is that you enter parameters into a struct, then pass that structure to the library which does the dirty work in the registers for you. Most of the settings seen here can be tweaked in the STM32CubeMX program by clicking on the peripheral you want to configure.

All the HAL structures follow a similar structure to the one seen above. There is an Instance variable that points to the hardware which should be configured. This is useful if there is more than one of that peripheral (aka 3 SPI buses).

This structure is now passed to the HAL_SPI_Init() function, this will call some more automatically generated code that will initilize the requred IO pins (this is in the Src/stm32f4xx_hal_msp.c file)

void HAL_SPI_MspInit(SPI_HandleTypeDef* hspi) { 
  GPIO_InitTypeDef GPIO_InitStruct; 
  if(hspi->Instance==SPI1) { 
    /* USER CODE BEGIN SPI1_MspInit 0 */ 
 
    /* USER CODE END SPI1_MspInit 0 */ 
    /* Peripheral clock enable */ 
    __HAL_RCC_SPI1_CLK_ENABLE(); 
   
    /**SPI1 GPIO Configuration     
    PA5     ------> SPI1_SCK 
    PA6     ------> SPI1_MISO 
    PA7     ------> SPI1_MOSI  
    */ 
    GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7; 
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; 
    GPIO_InitStruct.Pull = GPIO_NOPULL; 
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; 
    GPIO_InitStruct.Alternate = GPIO_AF5_SPI1; 
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); 
 
    /* USER CODE BEGIN SPI1_MspInit 1 */ 
 
    /* USER CODE END SPI1_MspInit 1 */ 
  } 
}

The HAL_xxx_Init functions call the MspInit functions because unlike 8-bit AVR microcontrollers, ARM microcontrollers usually allow peripherals to be mapped to several different IO pins. When you choose which a pin for an alternate function in the STM32CubeMX software, it generates the MspInit function for that peripheral.

Keen eyed readers will have noticed that there is also some pins which were selected for alternate functions in the MX_GPIO_Init function. This is because although these pins were selected for an alternate function, the peripheral that they were configured for was not enabled, therefore the initialization code was placed along with the other GPIO pins.

int main(void) {
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration ----------------------------------------*/

  /*
  * Reset of all peripherals, Initializes the Flash interface
  * and the Systick.
  */
  HAL_Init();

  /* Configure the system clock */
  SystemClock_Config();

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_SPI1_Init();

  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1){
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

Finally we get to the main code, this is the piece that we are going to edit. You can see from the comments that HAL_Init() (provided by the HAL library) resets the peripherals, configures the SysTick timer (used for precise delays) and initializes the flash interface.

This code also calls an the automatically generated functions that configure the system and various bus clocks in the microcontroller along with the various peripheral configuration functions. Then the code enters an infinite loop which we will populate.

So, now what? We want to blink the LEDs, from the HAL documentation we can see that in the GPIO driver documentation that there is a function called HAL_GPIO_WritePin() which takes a GPIO port, a pin number, and a pin state as arguments.

HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_pin,
      GPIO_PinState PinState)

Great!, but how to fill those arguments? Well its a good thing the STM32CubeMX software produces some “nice” output for us in the Inc/mxconstants.h file.

//lots of stuff above
#define LD4_Pin GPIO_PIN_12 
#define LD4_GPIO_Port GPIOD 
#define LD3_Pin GPIO_PIN_13 
#define LD3_GPIO_Port GPIOD 
#define LD5_Pin GPIO_PIN_14 
#define LD5_GPIO_Port GPIOD 
#define LD6_Pin GPIO_PIN_15 
#define LD6_GPIO_Port GPIOD
//lots of stuff below

(these are the pins for the LEDs, the rest of the file is definitions for the other pins)

That helps a lot! That tells us how to fill the first two arguments. The third argument is easy, from the library documents for GPIO_PinState we can see that its definition is as follows

enum GPIO_PinState { 
  GPIO_PIN_RESET = 0, 
  GPIO_PIN_SET
}

So to turn a pin on you would use 1, or GPIO_PIN_SET, and to turn it off use GPIO_PIN_RESET.

Therefore, in order to enable LED4 you would write

HAL_GPIO_WritePin(LD4_GPIO_PORT, LD4_Pin, GPIO_PIN_SET)

and to turn it off

HAL_GPIO_WritePin(LD4_GPIO_PORT, LD4_Pin, GPIO_PIN_RESET)

There is also a nifty trick using bitwise operations to write multiple pins (on the same port) at the same time.

HAL_GPIO_WritePin(LD4_GPIO_PORT, LD4_Pin | LD6_Pin, GPIO_PIN_SET)

Use the same process for turning pins off.

Finally we want to be able to see the lights blink, so we want a delay. Luckily, there is a function for that! The HAL library provides us a convenient way to delay for a number of milliseconds with HAL_Delay() which unsurprisingly takes as an argument, the number of milliseconds to delay.

So, to sum it all up we will blink the LEDs on the development board in order and delay for  a bit in between.

/* USER CODE BEGIN WHILE */
  while (1){
    //clear the previous pin
    HAL_GPIO_Write(LD6_GPIO_PORT, LD6_Pin, GPIO_PIN_RESET);
    //set the first pin
    HAL_GPIO_Write(LD3_GPIO_PORT, LD3_Pin, GPIO_PIN_SET);
    HAL_Delay(250);

    HAL_GPIO_Write(LD3_GPIO_PORT, LD3_Pin, GPIO_PIN_RESET);
    HAL_GPIO_Write(LD4_GPIO_PORT, LD4_Pin, GPIO_PIN_SET);
    HAL_Delay(250);

    HAL_GPIO_Write(LD4_GPIO_PORT, LD4_Pin, GPIO_PIN_RESET);
    HAL_GPIO_Write(LD5_GPIO_PORT, LD5_Pin, GPIO_PIN_SET);
    HAL_Delay(250);

    HAL_GPIO_Write(LD5_GPIO_PORT, LD5_Pin, GPIO_PIN_RESET);
    HAL_GPIO_Write(LD6_GPIO_PORT, LD6_Pin, GPIO_PIN_SET);
    HAL_Delay(250);
    /* USER CODE END WHILE */ 

    /* USER CODE BEGIN 3 */ 
  } 
  /* USER CODE END 3 *

Well, there it is the stereotypical microcontroller getting started example. I realize that I have not broken any new ground with this tutorial, but hopefully it will help someone using the new STM32 libraries.

Next time I will cover compiling the code and flashing to the microcontroller.

Soli Deo gloria

Advertisements

One thought on “STM32Cube Library – Part 2 Hello World

  1. Pingback: STM32Cube Library – Part 3 Compiling – Nebk Electronics

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s