ES1 PRJ2, PRJV & PRJD: STM32F05x microcontroller

Lesson 3: Standard Peripheral Driver Library

By Hugo Arends

© 2018 HAN University of Applied Sciences, version 1.0


Drivers

In the previous lesson we have developed software for the USART. This is called a driver: an abstraction layer between hardware and the main application. We did this using the Cortex Microcontroller Software Interface Standard (CMSIS).

In the first lesson we used the GPIO. This was done by directly reading/writing the peripheral registers through the CMSIS compliant interface. This is depicted in the following programming model:

Blinky with Standard Peripheral Drivers Library

The STM32F051x microcontroller has a lot of features. This means we would have to develop drivers for all of them, so they can be reused in different projects. Luckily, STMicroelectronics has already done this for us. They call this the Standard Peripheral Driver Library and it can be downloaded for free from their website:

http://www.st.com/web/en/catalog/tools/PF257886

Let’s see how we include this library in a Keil MDK-ARM project and how we can use the GPIO- and USART drivers. Download the ZIP file associated with this lesson and let's have a look at the directory structure:

The ‘stm32f0discovery projects’-folder contains three subfolders:

The ‘projects’ folder contains a folder called ‘lesson3’’. Open this folder and double click the file lesson3.uvprojx. This opens the μVision IDE and shows the following project structure:

The first thing to notice is that the project structure is different from the structure on disk. The second thing to notice is that there are four folders:

From a programmer's point of view, this means we now have the following programming model:

Make sure the target ‘Blinky’ is selected from the dropdown box:

The file main1.c implements a simple blinking LED program. The blink frequency is approximately 1 Hz. When the blue USER button is pressed, the frequency is 10 Hz.

If we want to use the ‘STM32F0Discovery LED & PB functions’, we must first include the header file:

#include “stm32f0_discover.h”

Then the GPIO peripherals must be initialized. ‘STM32F0Discovery LED & PB functions’ provides a function to do this:

STM_EVAL_LEDInit(LED3);

Lets see how this function is implemented. Right click on the function name and select ‘Go To Definition Of ‘STM_EVAL_LEDInit’. This brings you to the implementation in the file stm32f0_discovery.c, which is part of the ‘STM32F0Discovery LED & PB functions’. The implementation looks like this:

/**

  * @brief  Configures LED GPIO.

  * @param  Led: Specifies the Led to be configured.

  *         This parameter can be one of following parameters:

  *         @arg LED3

  *         @arg LED4

  * @retval None

  */

void STM_EVAL_LEDInit(Led_TypeDef Led)

{

  GPIO_InitTypeDef  GPIO_InitStructure;

 

  /* Enable the GPIO_LED Clock */

  RCC_AHBPeriphClockCmd(GPIO_CLK[Led], ENABLE);

  /* Configure the GPIO_LED pin */

  GPIO_InitStructure.GPIO_Pin = GPIO_PIN[Led];

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;

  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;

  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;

  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

  GPIO_Init(GPIO_PORT[Led], &GPIO_InitStructure);

}

The function uses a data type from the GPIO peripheral driver library: GPIO_InitTypedef. This is a structure that must be set to initialize the GPIO pin. In the function we see that:

Then the function GPIO_Init() from the peripheral driver library is called with this initialization struct as a parameter. But before this can be done, the GPIO clock of the corresponding GPIO port must be enabled. This is done with the peripheral driver library function RCC_AHBPeriphClockCmd().

This function shows how all layers from the programming model are used. When you go deeper into the device driver library, for instance the GPIO_Init(), then you’ll find the code to ‘translate’ the initialisation struct to CMSIS compliant instructions.

The main routine in the example project associated with this lesson uses the STM_EVAL_ … functions and implements the following code:

int main(void)

{

  // Configure LED3 and LED4 on STM32F0-Discovery

  STM_EVAL_LEDInit(LED3);

  STM_EVAL_LEDInit(LED4);

 

  // Initialize User_Button on STM32F0-Discovery

  STM_EVAL_PBInit(BUTTON_USER, BUTTON_MODE_GPIO);

 

  while(1)

  {

    // Blink LED's

    STM_EVAL_LEDOn(LED3);

    STM_EVAL_LEDOff(LED4);

    // Button pressed?

    if(STM_EVAL_PBGetState(BUTTON_USER) == Bit_SET)

    {

      // Delay 0.1 sec.

      delay(SystemCoreClock/8/10);

    }

    else

    {

      // Delay 1 sec.

      delay(SystemCoreClock/8);

    }

   

    // Blink LED's

    STM_EVAL_LEDOff(LED3);

    STM_EVAL_LEDOn(LED4);

    // Button pressed?

    if(STM_EVAL_PBGetState(BUTTON_USER) == Bit_SET)

    {

      // Delay 0.1 sec.

      delay(SystemCoreClock/8/10);

    }

    else

    {

      // Delay 1 sec.

      delay(SystemCoreClock/8);      

    }    

  }

}

This example simply blinks the LEDs with different frequencies if the blue Push Button is pressed. With the STM_EVAL_ … functions the user can focus more on the application instead of details in setting each bit in all the registers.

Assignments 1

STM_EVAL_LEDInit(LED_RED);

STM_EVAL_LEDOn(LED_RED);

STM_EVAL_LEDOff(LED_RED);

STM_EVAL_LEDToggle(LED_RED);

USART with Standard Peripheral Drivers Library

Although we have developed some driver functions for the USART in the previous lesson, there is also a USART peripheral driver library available. The target USART uses this driver:

In the main loop in the file main2.c we need to initialize the GPIO pins and the USART. First the GPIO pins. These must be initialized, because the alternate functions AF1 must be set. We use the GPIO_InitTypeDef structure from the GPIO peripheral driver library to declare a new variable called GPIO_InitStructure:

GPIO_InitTypeDef GPIO_InitStructure;

Before setting it’s fields we need to, as always, enable the GPIOA peripheral clock:

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA,ENABLE);

Then, the fields in the GPIO_InitStructure are set:

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

Next, the Alternate Functions of the two pins are set:

GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_1);

GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_1);

Finally, the GPIO pins are initialized by calling the function GPIO_Init with a pointer to this structure as a parameter:

GPIO_Init(GPIOA, &GPIO_InitStructure);

The GPIO pins have been initialized, now USART1. First a variable called USART_InitStructure is declared with a datatype from the USART peripheral driver library:

USART_InitTypeDef USART_InitStructure;

This struct can be filled with the default values from the USART1 by calling the function USART_StructInit():

USART_StructInit(&USART_InitStructure);

Now we only need to change the parameters that must be different from the default setup, for instance the baud rate:

USART_InitStructure.USART_BaudRate = 115200;

Then USART1 is initialized with the function USART_Init() with a pointer to the initialization struct as a parameter:

USART_Init(USART1, &USART_InitStructure);

Finally, USART1 is enabled:

USART_Cmd(USART1, ENABLE);

Before transmitting data, we need to check if the Transmit Data Register is empty:

while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET){;}

USART_SendData(USART1, (uint16_t)'P');

Notice that the data to be send is typecasted to uint16_t. The maximum word length is 9 bits, therefore a 16 bits variable must be used.

Receiving data is done by waiting as long as there is no data in the Receive Data Register:

while(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET){;}

c = USART_ReceiveData(USART1);

The complete main program is implemented like this:

int main(void)

{

  GPIO_InitTypeDef  GPIO_InitStructure;

  USART_InitTypeDef USART_InitStructure;

 

  uint16_t c;

  // --------------------------------------------------------------------

  // Initialize USART1

  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);

 

  // Setup Tx- and Rx pin

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;

  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

 

  GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_1);

  GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_1);

 

  GPIO_Init(GPIOA, &GPIO_InitStructure);

 

  USART_StructInit(&USART_InitStructure);

  USART_InitStructure.USART_BaudRate = 115200;

  USART_Init(USART1, &USART_InitStructure);

  USART_Cmd(USART1, ENABLE);

 

  while(1)

  {

    // Transmit information: "Press a key: "

    while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET){;}

    USART_SendData(USART1, (uint16_t)'P');

   

    // Etc. ...

    while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET){;}

    USART_SendData(USART1, (uint16_t)' ');

    // Receive data

    while(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET){;}

    c = USART_ReceiveData(USART1);

   

    // Make uppercase

    if(c >= (uint16_t)'a' && c <= (uint16_t)'z')

    {

      c -= 32;

    }

   

    // Transmit uppercase and goto next line

    while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET){;}

    USART_SendData(USART1, c);

    while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET){;}

    USART_SendData(USART1, (uint16_t)'\n');

    while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET){;}

    USART_SendData(USART1, (uint16_t)'\r');    

  }

}

Learning about the possibilities of a driver is done by studying the header and source file. With the μVision right-mouse-click options ‘Go To Definition ...’ and ‘Go To Reference ...’ this is made easier for programmers.

Assignments 2

Code size

Although there are advantages of using the peripheral driver library, there is also a disadvantage. Let’s compare the code size of the blinky projects from this lesson and from lesson 1:

Lesson1 Program Size: Code= 632 RO-data=224 RW-data=20 ZI-data=1028  

Lesson3 Program Size: Code=2784 RO-data=252 RW-data=48 ZI-data=1024  

Code: Program flash memory

RO-data: Read Only data (typically located in ROM space)

RW-data: Read/Write data, variables (typically located in RAM space)

ZI-data: Zero Initialized data (typically located in RAM space)

The program code size is more than four times larger! This means more flash memory will be necessary. Also code execution will be slower. The peripheral driver library implements a lot of function calls and parameter checking that take significant time. So, the peripheral driver library is very useful for rapidly making a main application. But afterwards, programmers should implement their own drivers as necessary.

Fully optimized C-code examples

If your project requires you to write fully optimized C-code, STM provides basic examples for all STM32F0xx peripherals. These examples only use register direct access as defined by CMSIS for the Cortex-M0 based microcontrollers. These are called ‘code snippets’ and can be downloaded for free from here:

www.st.com/stm32snippetsf0-pr

hugo.arends@han.nl More lessons? Click here!