Samstag, 4. Juni 2016

STM32 microcontroller #3: HAL & better makefile

Introduction

In my last post, I changed the clock speed of my STM32 controller by reading the datasheet/manual and setting some registers.
For bigger projects, it is just too much work to read the Reference Manual and find all registers settings to use a peripheral of a STM32 controller. For that reason, ST-Microelectronics created a software library that hides direct register access behind a layer of higher level C-functions. This is called "harware abstraction layer", or HAL. I will show an example that uses the HAL library to change the clock speed and flashes a LED. I will provide a makefile that should be good enough for future projects.

A simple HAL program

The HAL comes as part of the STMCube software package. For my STM32F103RBT6 controller, all files are in the directory STM32Cube_FW_F1_V1.3.0/Drivers/STM32F1xx_HAL_Driver. It contains one file (stm32f1xx_hal_conf_template.h) which has to be copied into any new project that uses the HAL library and has to be renamed to stm32f1xx_hal_conf.h.

Before starting to write code, documentation is needed. There is a document with the title "Description of STM32F1xx HAL drivers" which you can find by searching for UM1850 on the ST-Microelectronics webpage. I think that is best resource of information for the HAL, but if I found it not as good as the "Reference Manual" in terms of quality and completeness. But that might be related to my limited experience in using HAL. I would even say, that getting started with the HAL programming (without any prior knowledge) is just as difficult as starting to use the bare registers. The good thing is: I found lots of example code written using the HAL inside the STM32Cube package, so it is possible to get a working program in short time by copy/paste from these examples and a bit of adaptation. On the one hand that is nice, on the other hand it often prevents deeper understanding of what is going on behind the scenes.

I created a new directory with the following content:
  • makfile (see below) 
  • src/ 
    • main.c 
    • stm32f1xx_hal_conf.h (copied from the stm32f1xx_hal_conf_template.h) 
The content of main.c was mainly taken from the GPIO example project for the STM32F103RB-Nucleo develompent board. This can be found in the STMCube package in the directory STM32Cube_FW_F1_V1.3.0/Projects/STM32F103RB-Nucleo/Examples/GPIO/.

Many parts of the HAL depend on a time base which is incremented inside the SysTick_Handler interrupt routine. So, this routine must be defined and it must call the HAL_IncTick() function. There is a function HAL_Init(), that should be called before using any other HAL function. It mainly initializes the time base counter.

The steps for setting the clock frequency are identical to my previous post, only that no single register is accessed directly. All settings are done by setting values in a C-structures of type RCC_OscInitTypeDef, and of type RCC_ClkInitTypeDef, which are then passed to functions HAL_RCC_OscConfig and HAL_RCC_ClockConfig, respectively.

Enabling a GPIO pin also works by filling values to a structure of type GPIO_InitTypeDef and passing it to the function HAL_GPIO_Init(). Toggling a pin works by calling HAL_GPIO_TogglePin(). Because of the integrated timebase, the HAL_Delay() function provides precise delays in units of miliseconds.

Here is the complete code of main.c:

/**
******************************************************************************
* based on: 
*   Projects/STM32F103RB-Nucleo/Examples/GPIO/GPIO_IOToggle/Src/main.c
*   from the STM32Cube_FW_F1_V1.3.0 software package package
******************************************************************************
* @attention
*
* <h2><center>&copy; COPYRIGHT(c) 2015 STMicroelectronics</center></h2>
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright notice,
*    this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
*    this list of conditions and the following disclaimer in the documentation
*    and/or other materials provided with the distribution.
* 3. Neither the name of STMicroelectronics nor the names of its contributors
*    may be used to endorse or promote products derived from this software
*    without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
******************************************************************************
*/

#include "stm32f1xx.h"
#include "stm32f1xx_hal.h"

void SysTick_Handler(void)
{
 HAL_IncTick();
}

void SystemClock_Config(void)
{
 // Configure PLL with HSE_VALUE = 16000000UL = 16 MHz
 // PLL configuration: 
 //   PLLCLK = (HSE / 2) * PLLMUL = (16 / 2) * 9 = 72 MHz 
 RCC_OscInitTypeDef oscinitstruct = {0};
 oscinitstruct.OscillatorType      = RCC_OSCILLATORTYPE_HSE;
 oscinitstruct.HSEState            = RCC_HSE_ON;
 oscinitstruct.LSEState            = RCC_LSE_OFF;
 oscinitstruct.HSIState            = RCC_HSI_OFF;
 oscinitstruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
 oscinitstruct.HSEPredivValue      = RCC_HSE_PREDIV_DIV2;
 oscinitstruct.PLL.PLLState        = RCC_PLL_ON;
 oscinitstruct.PLL.PLLSource       = RCC_PLLSOURCE_HSE;
 oscinitstruct.PLL.PLLMUL          = RCC_PLL_MUL9;
 HAL_RCC_OscConfig(&oscinitstruct);
 
 // Select PLL as system clock source and configure 
 // the HCLK, PCLK1 and PCLK2 clocks dividers 
 RCC_ClkInitTypeDef clkinitstruct = {0};
 clkinitstruct.ClockType = (RCC_CLOCKTYPE_SYSCLK 
                             | RCC_CLOCKTYPE_HCLK 
                             | RCC_CLOCKTYPE_PCLK1 
                             | RCC_CLOCKTYPE_PCLK2);
 clkinitstruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
 clkinitstruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
 clkinitstruct.APB2CLKDivider = RCC_HCLK_DIV1;
 clkinitstruct.APB1CLKDivider = RCC_HCLK_DIV2;  
 HAL_RCC_ClockConfig(&clkinitstruct, FLASH_LATENCY_2);
}

int main(void)
{
 //  STM32F103xB HAL library initialization:
 // - Configure the Flash prefetch
 // - Systick timer is configured by default as source of time base, but user 
 //  can eventually implement his proper time base source (a general purpose 
 //  timer for example or other time source), keeping in mind that Time base 
 //  duration should be kept 1ms since PPP_TIMEOUT_VALUEs are defined and 
 //  handled in milliseconds basis.
 // - Set NVIC Group Priority to 4
 // - Low Level Initialization
 HAL_Init();
 
 // Configure the system clock to 72 MHz 
 SystemClock_Config();
 
 // -1- Enable GPIO Clock (to be able to program the configuration registers) 
 __HAL_RCC_GPIOC_CLK_ENABLE();
 
 // -2- Configure IO in output push-pull mode to drive external LED
 GPIO_InitTypeDef  GPIO_InitStruct;
 GPIO_InitStruct.Mode  = GPIO_MODE_OUTPUT_PP;
 GPIO_InitStruct.Pull  = GPIO_PULLUP;
 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
 GPIO_InitStruct.Pin   = GPIO_PIN_4;
 HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
 
 // -3- Toggle IO in an infinite loop 
 while (1)
 {
  HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_4);
  HAL_Delay(500); // 500 ms = 1/2 s
 }
}

The new makefile

I have written a new makefile that requires a minimum amount of manual work to start a new project. The only file that needs to be copied from the STM32Cube package is stm32f1xx_hal_conf_template.h that should be renamed into stm32f1xx_hal_conf.h. Here, individual parts of the HAL can be switched off if they are not needed. All other files are directly compiled from the STM32Cube package. At the top of the makefile, the root directory of the STM32Cube package has to be specified, as well as some information about the device.
With minor modifications, this makefile should also work for another SMT32FXXX family, but I did not test that yet.

########################################################################
# Makefile for STM32F3 MCU using arm-none-eabi-gcc toolchain
#  for compiling, and openocd with st-link-v2 for uploading.
# 
# For project setup, copy linker script into same directory as this
# makefile and create a src/ subdirectory that contains the application
# code and stm32f1xx_hal_conf.h 
#
# Use this at your own risk.
#
# Note: using this makefile for other families might require changes.
########################################################################

# root path of STM32Cube package
STM32CUBE_PATH = ../STM32Cube_FW_F1_V1.3.0

# specify device type by family, type and number: 
#  (all letters in capitals)
#   e.g. for STM32 F1 03 RBT6
#      FAMILY = F1
#      TYPE = 03
#      NUMBER = X6    (X replaces RBT or C8T)

# device family
FAMILY = F1
# device type
TYPE = 03
# device number
NUMBER = X6
# the CPU
CPU = cortex-m3

# external quarz frequency
HSE_VALUE = 16000000UL

# program name
PROGRAM = main

# list of additional program modules (.o files)
OBJS = \
 
########################################################################
# Changes below this line should not be necessary.
########################################################################

# device name
lc = $(subst A,a,$(subst B,b,$(subst C,c,$(subst D,d,$(subst E,e,$(subst F,f,$(subst G,g,$(subst H,h,$(subst I,i,$(subst J,j,$(subst K,k,$(subst L,l,$(subst M,m,$(subst N,n,$(subst O,o,$(subst P,p,$(subst Q,q,$(subst R,r,$(subst S,s,$(subst T,t,$(subst U,u,$(subst V,v,$(subst W,w,$(subst X,x,$(subst Y,y,$(subst Z,z,$1))))))))))))))))))))))))))
Device = STM32$(FAMILY)$(TYPE)$(call lc,$(NUMBER))
DEVICE = STM32$(FAMILY)$(TYPE)$(NUMBER)
device = $(call lc,$(DEVICE))
family = $(call lc,$(FAMILY))


vpath %.c $(STM32CUBE_PATH)/Drivers/STM32$(FAMILY)xx_HAL_Driver/Src \
          $(STM32CUBE_PATH)/Drivers/CMSIS/Device/ST/STM32$(FAMILY)xx/Source/Templates/ \
          src
          
vpath %.h $(STM32CUBE_PATH)/Drivers/STM32$(FAMILY)xx_HAL_Driver/Inc  \
          $(STM32CUBE_PATH)/Middlewares/Third_Party/FreeRTOS/Source/include/ \
          $(STM32CUBE_PATH)/Drivers/CMSIS/Device/ST/STM32$(FAMILY)xx/Include/ \
          src
          
vpath %.s $(STM32CUBE_PATH)/Drivers/CMSIS/Device/ST/STM32$(FAMILY)xx/Source/Templates/gcc/

vpath %.o obj

CFLAGS = -I $(STM32CUBE_PATH)/Drivers/CMSIS/Device/ST/STM32$(FAMILY)xx/Include \
         -I $(STM32CUBE_PATH)/Drivers/CMSIS/Include \
   -I $(STM32CUBE_PATH)/Drivers/STM32$(FAMILY)xx_HAL_Driver/Inc/ \
   -I src \
         -Wall -Os -std=c99 -mcpu=$(CPU) -mlittle-endian -mthumb  -D$(Device) -DHSE_VALUE=$(HSE_VALUE) 
         
# the stm32 hardware abstraction library
STM_HAL_OBJS = $(shell ls $(STM32CUBE_PATH)/Drivers/STM32$(FAMILY)xx_HAL_Driver/Src | sed  's/\.c/\.o/g')

# startup and system initialization code
STM_BASIC_OBJS = \
 system_stm32$(family)xx.o        \
 startup_$(device).o              \
 
# generic rules
%.o: %.c %.h
 arm-none-eabi-gcc $(CFLAGS)  -c $< -o $@

%.o: %.c 
 arm-none-eabi-gcc $(CFLAGS)  -c $< -o $@

%.o: %.s
 arm-none-eabi-gcc $(CFLAGS)  -c $< -o $@

# main target
all: $(PROGRAM).hex

$(PROGRAM).hex: $(PROGRAM).elf
 arm-none-eabi-objcopy -Oihex $(PROGRAM).elf  $(PROGRAM).hex

$(PROGRAM).elf: $(PROGRAM).o $(OBJS) $(STM_HAL_OBJS) $(STM_BASIC_OBJS) $(DEVICE)_FLASH.ld
 arm-none-eabi-gcc $(CFLAGS)  -T $(DEVICE)_FLASH.ld \
 -Wl,--gc-sections  $(PROGRAM).o $(OBJS) $(STM_HAL_OBJS) $(STM_BASIC_OBJS) -o $(PROGRAM).elf

# make a local copy of the linkcer script that has no '0' characters 
#   (why are there lines starting with '0' ?)
LINKER_SCRIPT = $(STM32CUBE_PATH)/Drivers/CMSIS/Device/ST/STM32F1xx/Source/Templates/gcc/linker/$(DEVICE)_FLASH.ld
$(DEVICE)_FLASH.ld: $(LINKER_SCRIPT)
 sed 's/^0/ /g' $(LINKER_SCRIPT) > $(DEVICE)_FLASH.ld

flash: $(PROGRAM).hex
 sudo openocd -f /usr/share/openocd/scripts/interface/stlink-v2.cfg     \
              -f /usr/share/openocd/scripts/target/stm32$(family)x.cfg  \
              -c "program $(PROGRAM).hex verify reset exit"

clean:
 rm -f *.o $(PROGRAM).elf *.ld
 
.PHONY: all flash clean






Keine Kommentare:

Kommentar veröffentlichen