Samstag, 19. März 2016

STM32 microcontroller #1: toolchain & LED blink

Introduction

I wanted to start playing around with an ARM microcontroller. After some research I decided to buy the STM32F103RBT6 from STMicroelectronics. It is fast and has lots of peripherals, but is still small enough to be put an adapter PCB for use on a bread board. A popular first step in using a new microcontroller is to get a blinking LED. I'll show the essential steps to achieve this on a STM32F103RBT6 with a free and simple Linux toolchain. It is also important to me to program a bare chip (with no bootloader).

A lot of information about the device is available on the STMicroelectronics web page. I'll refer here to the Reference Manual and the application note AN2586 on that page.
In addition I got valuable information and hints from the following sources:

I'm thankful to all people, providing all that helpful content. In this post, I try to summarize the most useful information (based on my personal judgement).


Toolchain Installation

The toolchain is the set of programs that is needed to compile the program code into an executable file that can be copied on the target chip. On my Arch Linux I installed the following packages in order to get the gcc compiler for ARM processors:
  • arm-none-eabi-binutils
  • arm-none-eabi-gcc
  • arm-none-eabi-gdb
  • arm-none-eabi-newlib
That gives me a compiler and all related tools. But I need more. A chip like the STM32F103RBT6 is rather complex. It needs some startup code before the main() function is entered. And in order to control the peripherals (ADC, GPIO, ...), one needs all the addresses of the registers to control them. In addition a linker script is needed to put the code together into a program that will run on the target device. All these things are provided on the STMicroelectronics website. The package is called STM32Cube. It is available for different STM32 devices (F1, F2, F3,...). I need the package STM32Cube F1 (link to .zip file is on the bottom of that page), because I use STM32F103RBT6. 

I also need a device that can be plugged into the USB-port of my PC and copies the program on the chip. I bought a ST-Link V2 (a cheap, ebay clone from China) for that purpose. That device can be controlled with a program called Open On-Chip Debugger. On Arch Linux, I got this program after installing the package
  • openocd

The PCB

Because I bought a bare chip, I need a PCB to put it on.
A thing of beauty
I want a PCB that can be plugged onto a bread board (as I saw here), but I wanted to add the decoupling capacitors on the PCB, a programming socket and a mini-USB connector in case I later want to play with the USB interface of the controller.
During the board design I followed the application note AN2586. I used KiCAD to draw schematic and board layout, and produced it with the toner transfer method.
Front side of the board with a blinking LED. It is not fully populated yet (missing quarz and USB) but works already

Back side of the board with all the decoupling capacitors.

Blinking LED Program

I created a project directory and copied the following files from the STM32Cube package:
  • STM32Cube_FW_F1_V1.3.0/Drivers/CMSIS/Device/ST/STM32F1xx/Source/Templates/gcc/startup_stm32f103x6.s (startup code written in assembly language. I need that file, because I have STM32F103RBT6)
  • STM32Cube_FW_F1_V1.3.0/Middlewares/Third_Party/FreeRTOS/Source/include/stdint.readme -> stdint.h (rename the file to stdint.h)
  • STM32Cube_FW_F1_V1.3.0/Drivers/CMSIS/Device/ST/STM32F1xx/Source/Templates/gcc/linker/STM32F103X6_FLASH.ld (the linker script, There are '0' characters in some lines that I needed to remove)
  • STM32Cube_FW_F1_V1.3.0/Drivers/CMSIS/Device/ST/STM32F1xx/Source/Templates/system_stm32f1xx.c (more startup code written in C, which is called from the assembly startup code)
  • STM32Cube_FW_F1_V1.3.0/Drivers/CMSIS/Device/ST/STM32F1xx/Include/system_stm32f1xx.h (header file for the startup code)
Then I created a main.c file that contains the program code. Basically, one has to set a few bits in the correct registers to make the chip do what I want. For the blinking LED, I need to drive the general purpose input output (GPIO) pins of the device. The relevant information for the GPIO is found on pages 111-113 (important parts are marked in red)
and page 170 of the Reference Manual:
The program code is using the names for the registers and the numbers, such as GPIO_CRL_MODE4 instead of the corresponding hex number 0x00030000. They are provided after including the fille stm32f1xx.h

#include "stm32f1xx.h"

void wait(int delta)
{
 volatile int i;
 for (i = delta; i; --i);
}

int main()
{
 // Enbale GPIOC
 RCC->APB2ENR   |= RCC_APB2ENR_IOPCEN;
 
 // Configure GPIOC pin 4 by setting bits in the lower half 
 // of the port configuration register CRL
 // (reset value of GPIOC->CRL = 0x44444444)
 GPIOC->CRL |=   GPIO_CRL_MODE4; // MODE4 = 11 (50 MHz = fast mode)
 GPIOC->CRL &= ~(GPIO_CRL_CNF4); // CNF4  = 00 (Push-Pull mode)
 
 for (;;)
 {
  GPIOC->BSRR |= GPIO_BSRR_BS4;  // set pin4 (LED on)
  wait(200000);
  GPIOC->BRR |= GPIO_BRR_BR4;    // reset pin4 (LED off)
  wait(200000);
 }
}

Finally, a makefile is needed to compile everything. The CFLAGS contain include paths to the STM32Cube package. In case you have put it somewhere else, that path has to be changed accordingly. The rest of the CFLAGS variable specifies the processor core (cortex-m3) and the device type (STM32F103x6). With -mthumb the compiler uses a reduced ARM instruction set to save memory space, more info here.
Note, that this makefile is written in a simple way in order to make it easy to see the individual commands (no use of default rules). It compiles the main program and the startup (C code and assembly code), then combines everything into main.elf using the linker script provided in the STM32Cube package. Finally, it converts the program to Intel hex format.


CFLAGS = -I ../STM32Cube_FW_F1_V1.3.0/Drivers/CMSIS/Device/ST/STM32F1xx/Include \
         -I ../STM32Cube_FW_F1_V1.3.0/Drivers/CMSIS/Include \
         -Wall -mcpu=cortex-m3 -mlittle-endian -mthumb  -DSTM32F103x6 -Os 
 
main.hex: main.elf
 arm-none-eabi-objcopy -Oihex main.elf main.hex

main.elf: system.o main.o startup.o
 arm-none-eabi-gcc $(CFLAGS)  -T STM32F103X6_FLASH.ld \
 -Wl,--gc-sections system.o main.o startup.o -o main.elf

startup.o: startup_stm32f103x6.s
 arm-none-eabi-gcc $(CFLAGS)  -c startup_stm32f103x6.s -o startup.o

system.o: system_stm32f1xx.c
 arm-none-eabi-gcc $(CFLAGS)  -c system_stm32f1xx.c -o system.o
 
main.o: main.c
 arm-none-eabi-gcc $(CFLAGS)  -Os -c main.c -o main.o

flash: main.hex
 openocd -f /usr/share/openocd/scripts/interface/stlink-v2.cfg \
         -f /usr/share/openocd/scripts/target/stm32f1x.cfg     \
         -c "program main.hex verify reset exit"

clean:
 rm *.o main.elf

Writing to Flash

In order to copy the program (main.hex) to the flash memory on the chip, I use the ST-Link V2 mentioned above. The ST-Link is controlled by the openocd program called with the correct command line arguments. These arguments are two configuration file for the programmer and for the target device, and a list of commands to execute: program verify reset exit. The correct call can be seen in the makefile under the flash: target. Note that you might change the path to the interface and target configuration files. These files are installed together with the openocd program. The paths I've used here work for my Linux distribution.
The ST-Link device has to be connected with 5 cables to the chip
pin_on_STLink -> pin_on_STM32:
  • 2 cables with supply voltage: GND -> GND and 3.3V -> VDD
  • 2 cables for data transfer: SWDIO -> PA13  and SWCLK -> PA14
  • 1 cable for reset: RST ->NRST

After typing make flash in the terminal, the LED (connected to pin PC4 and through a resistor to GND) starts blinking. 

Next Part

Part 2, where I will set a higher clock speed