Messing With Stm32 Discovery Led Flash

Note:

This is a bunch of old blog posts i recovered from old website. Some of the information in there might not be longer correct.

After long time of not doing anything related to microcontrollers I decided that I want to come back to this awesome hobby. I didn't have to look for long to find the perfect (for me ;) development platform. The Discovery dev board is perfect for small and big projects. The board that I bought is the stm32f401 with the gyro and accelerometer devices. Nice little board with cool peripherals. Just like with all programming projects like that its good to start from something simple, like the incredibly boring "Hello World!" example. However nothing is boring when it comes to microcontrollers. You could get the UART working and send the "Hello World!" string send over to the laptop/PC but this might actually be a bit too much for the first time so its better to start with a simple LED flash. There is no point for me to talk too much about it so lets just set the stuff up. One thing that I will mention though is that even though ST has a library of drivers called STM32CubeMX available I wont be using that. At least not initially. The reason for it is that I want to learn the microcontroller and its operation and for me the best way is to go down and dirty into its silicon guts ;). You will still need to download the STM32Cube library to get the basic CMSIS header files that contain the registry mapping and struct definitions. This stuff is essential to even start with the board. When you download the library from ST website you can have a look at the peripheral drivers code. But for this tutorial we will need the CMSIS headers, the linker script and the initial system config file. So....my directory setup is like that:

|-- build
|-- linker
| |-- stm32f4_linker.ld
|-- Makefile
|-- src
| |-- gpio.c
| |-- gpio.h
| |-- main.c
| |-- stm32f401xc.h
| |-- stm32f4xx_it.c
| |-- stm32f4xx_it.h
| |-- system_stm32f4xx.c
|-- startup
|-- startup_stm32f401xc.s
  • stm32f4_linker.ld - this is a linker script, you will find it in the example projects in the "Templates" folder
  • startup_stm32f401xc.s - this is a very important file with assembly code that performs the memory initialization and configures the interrupt vector table.
  • stm32f4xx_it.c - contains definitions of the interrupt routines. Notice that the interrupt routine names correspond exactly to the names in the "startup_stm32f401xc.s". If interrupt is used just put the handler code in the appropriate interrupt function.
  • stm32f4xx_it.h - just a header file for the above.
  • system_stm32f4xx.c - this one contains the code to initialize the stm32f401 clock. Just to get the initial example working we will borrow the file from the STM32Cube numerous examples ;)

There is no point in showing the content of the above files in here. Just download the Cube library and have a look for the files. So what is interesting for us here is the content of the main.c and gpio.c/gpio.h and maybe the Makefile that will allow us to build the project. I believe that the comments in the code are sufficient enough and anybody will be able to understand it. The gpio.c:


#include <stm32f401xc.h>
#include "gpio.h"
	
#define LED_GREEN GPIO_PIN_12
#define LED_ORANGE GPIO_PIN_13
#define LED_RED GPIO_PIN_14
#define LED_BLUE GPIO_PIN_15

void qc_gpio_init() {
	
	/* GPIO D Periph clock enable */
	RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN;
	/* set GPIOD pins 12, 13, 14 and 15 as output */
	GPIOD->MODER |= (GPIO_MODER_MODER12_0 | GPIO_MODER_MODER13_0 | GPIO_MODER_MODER14_0 | GPIO_MODER_MODER15_0);
	/* push-pull output */
	GPIOD->OTYPER &= ~(GPIO_OTYPER_OT_12 | GPIO_OTYPER_OT_13 | GPIO_OTYPER_OT_14 | GPIO_OTYPER_OT_15);
	/* max speed */
	GPIOD->OSPEEDR |= (GPIO_OSPEEDER_OSPEEDR12 | GPIO_OSPEEDER_OSPEEDR13 | GPIO_OSPEEDER_OSPEEDR14 | GPIO_OSPEEDER_OSPEEDR15);
	/* outputs so disable pull_xxx */
	GPIOD->PUPDR &= ~(GPIO_PUPDR_PUPDR12 | GPIO_PUPDR_PUPDR13 | GPIO_PUPDR_PUPDR14 | GPIO_PUPDR_PUPDR15);
}
	
void qc_gpio_pin_set(GPIO_TypeDef *GPIOPort, uint16_t pin){
	/* set pin in the lower 16bit*/
	GPIOPort->BSRR |= pin;
}
	
void qc_gpio_pin_clear(GPIO_TypeDef *GPIOPort, uint16_t pin){
	/* set in the higher 16bit */
	GPIOPort->BSRR |= (pin << 16);
}

void qc_gpio_basic_flash(uint32_t d){
	qc_gpio_pin_clear(GPIOD, LED_GREEN);
	qc_gpio_pin_set(GPIOD, LED_ORANGE);
	qc_gpio_pin_clear(GPIOD, LED_RED);
	qc_gpio_pin_set(GPIOD, LED_BLUE);
	
	qc_delay(d);
	qc_gpio_pin_set(GPIOD, LED_GREEN);
	qc_gpio_pin_clear(GPIOD, LED_ORANGE);
	qc_gpio_pin_set(GPIOD, LED_RED);
	qc_gpio_pin_clear(GPIOD, LED_BLUE);
	qc_delay(d);
}

The delay routine is just something embarrassingly simple at the moment but it does the job.

void qc_delay(uint32_t d){
	volatile int i,j;
	for (i=0; i < d; i++){
		j++;
	}
}

You can stick it into a gpio.c file or a separate utils.c file and then include its header in gpio.c, its up to you. We will look into configuring the ARM to use Systick peripheral to measure the time to create precise delay routines in future. The main.c is very simple as well:

#include "gpio.h"
int main(){
	qc_gpio_init();
	/* just keep flashing, just keep flashing...*/
	while(1){
		qc_gpio_basic_flash(500000);
	}
	return 0;
}

And the Makefile to build the whole project looks as follows:

TOOL_PFX = arm-none-eabi
CC = $(TOOL_PFX)-gcc
AS = $(TOOL_PFX)-as
OBJCOPY = $(TOOL_PFX)-objcopy
OBJDUMP = $(TOOL_PFX)-objdump
GDB = $(TOOL_PFX)-gdb
SIZE = $(TOOL_PFX)-size
HAL_INC = /usr/local/lib/arm/hal/include
CMSIS = /usr/local/lib/arm/CMSIS

BIN = main.hex
ELF = main.elf
LINKER = linker
LK = $(LINKER)/stm32f4_linker.ld
STARTUP = startup
SRC=src
BUILD=build

CFLAGS = -Wall -g -std=c99
CFLAGS += -mthumb -mcpu=cortex-m4
CFLAGS += -I$(HAL_INC) -I$(CMSIS)

LFLAGS = -T$(LK) -Wall -mthumb -mcpu=cortex-m4 -lgcc -lc -lrdimon
S = $(wildcard $(SRC)/*.c)
S += $(wildcard $(STARTUP)/*.s)
H = $(wildcard $(SRC)/*.h)
O = $(patsubst $(SRC)/%.c, $(BUILD)/%.o, $(patsubst $(STARTUP)/%.s, $(BUILD)/%.o, $(S)))
all: $(BIN)

$(BIN): $(ELF)
	@$(OBJCOPY) -Oihex $(ELF) $(BIN)
	@echo "elf is ready"
	
$(BUILD)/%.o: $(SRC)/%.s
	@$(AS) -o $@ $<
	@echo "Assembly of $< finished"
$(BUILD)/%.o: $(SRC)/%.c
	@$(CC) $(CFLAGS) -c -o $@ $<
	@echo "Compilation of $< finished"
	
$(BUILD)/%.o: $(STARTUP)/%.s
	@$(CC) $(CFLAGS) -c -o $@ $<
	@echo "Compilation of $< finished"
	
$(ELF): $(O)
	@echo "Linking..."
	@$(CC) $(LFLAGS) $(O) -o $(ELF)
	@echo "Linking done"
	
PHONY: clean

clean:
	@echo "Deleting *.o files"
	@$(RM) $(BUILD)/*.o
	@$(RM) $(BIN) $(ELF)

This should get you started. CMSIS and HAL_INC are directories on my machine where I copied all the CMSIS header files (and HAL in case I need them). You can do the same. I'm assuming that you have all the cross-compiler tools installed (if not I'll publish some tutorials on how to install them soon). In the next tutorial we will try to configure the system clock ourselves to use the external crystal and we will try to set the system clock to our chosen frequency.