I want to make sure I have set up my first embedded software project up correctly and therefore am trying to blink an LED on my NUCLEO-F411RE board (STM32F411RE micro controller). I am not using any IDEs as I want to do everything from scratch. My project structure is as follows.
├── build
│ ├── Buggy.bin
│ ├── Buggy.dis
│ ├── Buggy.elf
│ ├── Buggy.hex
│ ├── Buggy.map
│ ├── main.o
│ ├── startup_stm32f411xe.o
│ ├── stm32f4xx_it.o
│ └── system_stm32f4xx.o
├── lib
│ ├── cmsis
│ │ ├── include
│ │ │ ├── arm_common_tables.h
│ │ │ ├── arm_const_structs.h
│ │ │ ├── arm_math.h
│ │ │ ├── core_cm0.h
│ │ │ ├── core_cm0plus.h
│ │ │ ├── core_cm3.h
│ │ │ ├── core_cm4.h
│ │ │ ├── core_cm7.h
│ │ │ ├── core_cmFunc.h
│ │ │ ├── core_cmInstr.h
│ │ │ ├── core_cmSimd.h
│ │ │ ├── core_sc000.h
│ │ │ └── core_sc300.h
│ │ └── stm32f4xx
│ │ ├── stm32f4xx.h
│ │ └── system_stm32f4xx.h
│ ├── Makefile
│ └── STM32F4xx_StdPeriph_Driver
│ ├── include
│ │ ├── misc.h
│ │ ├── stm32f4xx_adc.h
│ │ ├── stm32f4xx_crc.h
│ │ ├── stm32f4xx_dbgmcu.h
│ │ ├── stm32f4xx_dma.h
│ │ ├── stm32f4xx_exti.h
│ │ ├── stm32f4xx_flash.h
│ │ ├── stm32f4xx_flash_ramfunc.h
│ │ ├── stm32f4xx_gpio.h
│ │ ├── stm32f4xx_i2c.h
│ │ ├── stm32f4xx_iwdg.h
│ │ ├── stm32f4xx_pwr.h
│ │ ├── stm32f4xx_rcc.h
│ │ ├── stm32f4xx_rtc.h
│ │ ├── stm32f4xx_sdio.h
│ │ ├── stm32f4xx_spi.h
│ │ ├── stm32f4xx_syscfg.h
│ │ ├── stm32f4xx_tim.h
│ │ ├── stm32f4xx_usart.h
│ │ └── stm32f4xx_wwdg.h
│ ├── libstdperiph.a
│ ├── Makefile
│ └── src
│ ├── misc.c
│ ├── misc.o
│ ├── stm32f4xx_adc.c
│ ├── stm32f4xx_adc.o
│ ├── stm32f4xx_crc.c
│ ├── stm32f4xx_crc.o
│ ├── stm32f4xx_dbgmcu.c
│ ├── stm32f4xx_dbgmcu.o
│ ├── stm32f4xx_dma.c
│ ├── stm32f4xx_dma.o
│ ├── stm32f4xx_exti.c
│ ├── stm32f4xx_exti.o
│ ├── stm32f4xx_flash.c
│ ├── stm32f4xx_flash.o
│ ├── stm32f4xx_flash_ramfunc.c
│ ├── stm32f4xx_flash_ramfunc.o
│ ├── stm32f4xx_gpio.c
│ ├── stm32f4xx_gpio.o
│ ├── stm32f4xx_i2c.c
│ ├── stm32f4xx_i2c.o
│ ├── stm32f4xx_iwdg.c
│ ├── stm32f4xx_iwdg.o
│ ├── stm32f4xx_pwr.c
│ ├── stm32f4xx_pwr.o
│ ├── stm32f4xx_rcc.c
│ ├── stm32f4xx_rcc.o
│ ├── stm32f4xx_rtc.c
│ ├── stm32f4xx_rtc.o
│ ├── stm32f4xx_sdio.c
│ ├── stm32f4xx_sdio.o
│ ├── stm32f4xx_spi.c
│ ├── stm32f4xx_spi.o
│ ├── stm32f4xx_syscfg.c
│ ├── stm32f4xx_syscfg.o
│ ├── stm32f4xx_tim.c
│ ├── stm32f4xx_tim.o
│ ├── stm32f4xx_usart.c
│ ├── stm32f4xx_usart.o
│ ├── stm32f4xx_wwdg.c
│ └── stm32f4xx_wwdg.o
├── main.c
├── main.h
├── Makefile
├── startup_stm32f411xe.s
├── stm32f4xx_conf.h
├── stm32f4xx_flash.ld
├── stm32f4xx_it.c
├── stm32f4xx_it.h
└── system_stm32f4xx.c
I am using the STD_Periph library to handle interfacing with the actual hardware and CMSIS for hardware abstraction of the actual processor core and peripherals (I think).
The main.c source file should contain the code to blink an LED on the board every second.
#include "stm32f4xx.h"
void TimingDelay_Decrement(void);
static __IO uint32_t uwTimingDelay;
static void Delay(__IO uint32_t nTime);
int main(void) {
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
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_2MHz;
GPIO_Init(GPIOD, &GPIO_InitStructure);
while (1) {
GPIO_ToggleBits(GPIOD, GPIO_Pin_13);
Delay(1000);
}
}
void Delay(__IO uint32_t nTime)
{
uwTimingDelay = nTime;
while(uwTimingDelay != 0x00) {
uwTimingDelay--;
}
}
void TimingDelay_Decrement(void)
{
if (uwTimingDelay != 0x00)
{
uwTimingDelay--;
}
}
When I build my project using the Makefile, with the make
command, everything works successfully. I then run make flash, which also completes successfully. However, none of the LEDs are blinking every second. I don't have the knowledge to know what the problem is, especially as there is no error message, so it is hard for me to debug. I have a feeling its related to the Makefile or linker script so I will include them below.
Makefile:
# STM32F4-Discovery Makefile
C_SRC=$(wildcard *.c) \
$(wildcard src/*.c)
# Add assembly source files here or use $(wildcard *.s) for all .s files
S_SRC = $(wildcard *.s)
# Project name
PROJ_NAME = Buggy
OUTPATH = build
BINPATH = /usr/bin/
OUTPATH := $(abspath $(OUTPATH))
BASEDIR := $(abspath ./)
MKDIR_P = mkdir -p
###################################################
# Check for valid float argument
# NOTE that you have to run make clean after
# changing these as hardfloat and softfloat are not
# binary compatible
ifneq ($(FLOAT_TYPE), hard)
ifneq ($(FLOAT_TYPE), soft)
#override FLOAT_TYPE = hard
override FLOAT_TYPE = soft
endif
endif
###################################################
AS=$(BINPATH)arm-none-eabi-as
CC=$(BINPATH)arm-none-eabi-gcc
LD=$(BINPATH)arm-none-eabi-gcc
OBJCOPY=$(BINPATH)arm-none-eabi-objcopy
OBJDUMP=$(BINPATH)arm-none-eabi-objdump
SIZE=$(BINPATH)arm-none-eabi-size
LINKER_SCRIPT = stm32f4xx_flash.ld
CPU = -mcpu=cortex-m4 -mthumb
CFLAGS = $(CPU) -c -std=gnu99 -g -O2 -Wall
LDFLAGS = $(CPU) -mlittle-endian -mthumb-interwork -Wl,--gc-sections,-Map=$(OUTPATH)/$(PROJ_NAME).map,--cref --specs=nano.specs
ifeq ($(FLOAT_TYPE), hard)
CFLAGS += -fsingle-precision-constant -Wdouble-promotion
CFLAGS += -mfpu=fpv4-sp-d16 -mfloat-abi=hard
else
CFLAGS += -msoft-float
endif
# Default to STM32F411xE if no device is passed
ifeq ($(DEVICE_DEF), )
DEVICE_DEF = STM32F411xE
endif
CFLAGS += -D$(DEVICE_DEF)
vpath %.a lib
# Includes
INCLUDE_PATHS = -I$(BASEDIR)/lib/cmsis/stm32f4xx -I$(BASEDIR)/lib/cmsis/include -I$(BASEDIR)
INCLUDE_PATHS += -I$(BASEDIR)/lib/STM32F4xx_StdPeriph_Driver/include
# Library paths
LIBPATHS = -L$(BASEDIR)/lib/STM32F4xx_StdPeriph_Driver
# Libraries to link
LIBS = -lstdperiph -lc -lgcc -lnosys
OBJS = $(C_SRC:.c=.o)
OBJS += $(S_SRC:.s=.o)
###################################################
.PHONY: lib proj
all: dir lib proj
$(SIZE) $(OUTPATH)/$(PROJ_NAME).elf
lib:
$(MAKE) -C lib FLOAT_TYPE=$(FLOAT_TYPE) BINPATH=$(BINPATH) DEVICE_DEF=$(DEVICE_DEF) BASEDIR=$(BASEDIR)
proj: $(OUTPATH)/$(PROJ_NAME).elf
.s.o:
$(AS) $(CPU) -o $(addprefix $(OUTPATH)/, $@) $<
.c.o:
$(CC) $(CFLAGS) -std=gnu99 $(INCLUDE_PATHS) -o $(addprefix $(OUTPATH)/, $@) $<
$(OUTPATH)/$(PROJ_NAME).elf: $(OBJS)
$(LD) $(LDFLAGS) -T$(LINKER_SCRIPT) $(LIBPATHS) -o $@ $(addprefix $(OUTPATH)/, $^) $(LIBS) $(LD_SYS_LIBS)
$(OBJCOPY) -O ihex $(OUTPATH)/$(PROJ_NAME).elf $(OUTPATH)/$(PROJ_NAME).hex
$(OBJCOPY) -O binary $(OUTPATH)/$(PROJ_NAME).elf $(OUTPATH)/$(PROJ_NAME).bin
$(OBJDUMP) -S --disassemble $(OUTPATH)/$(PROJ_NAME).elf > $(OUTPATH)/$(PROJ_NAME).dis
dir:
$(MKDIR_P) $(OUTPATH)
clean:
rm -f $(OUTPATH)/*.o
rm -f $(OUTPATH)/$(PROJ_NAME).elf
rm -f $(OUTPATH)/$(PROJ_NAME).hex
rm -f $(OUTPATH)/$(PROJ_NAME).bin
rm -f $(OUTPATH)/$(PROJ_NAME).dis
rm -f $(OUTPATH)/$(PROJ_NAME).map
# Remove the following line if you don't want to clean the Libraries as well
$(MAKE) clean -C lib
flash:
st-flash --reset write $(OUTPATH)/$(PROJ_NAME).bin 0x08000000
Linker script:
/* Entry Point */
ENTRY(Reset_Handler)
/* Highest address of the user mode stack */
_estack = 0x20020000; /* end of RAM */
/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0x200;; /* required amount of heap */
_Min_Stack_Size = 0x400;; /* required amount of stack */
/* Specify the memory areas */
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
}
/* Define output sections */
SECTIONS
{
/* The startup code goes first into FLASH */
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(4);
} >FLASH
/* The program code and other data goes into FLASH */
.text :
{
. = ALIGN(4);
*(.text) /* .text sections (code) */
*(.text*) /* .text* sections (code) */
*(.glue_7) /* glue arm to thumb code */
*(.glue_7t) /* glue thumb to arm code */
*(.eh_frame)
KEEP (*(.init))
KEEP (*(.fini))
. = ALIGN(4);
_etext = .; /* define a global symbols at end of code */
} >FLASH
/* Constant data goes into FLASH */
.rodata :
{
. = ALIGN(4);
*(.rodata) /* .rodata sections (constants, strings, etc.) */
*(.rodata*) /* .rodata* sections (constants, strings, etc.) */
. = ALIGN(4);
} >FLASH
.ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH
.ARM : {
__exidx_start = .;
*(.ARM.exidx*)
__exidx_end = .;
} >FLASH
.preinit_array :
{
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array*))
PROVIDE_HIDDEN (__preinit_array_end = .);
} >FLASH
.init_array :
{
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array*))
PROVIDE_HIDDEN (__init_array_end = .);
} >FLASH
.fini_array :
{
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP (*(SORT(.fini_array.*)))
KEEP (*(.fini_array*))
PROVIDE_HIDDEN (__fini_array_end = .);
} >FLASH
/* used by the startup to initialize data */
_sidata = LOADADDR(.data);
/* Initialized data sections goes into RAM, load LMA copy after code */
.data :
{
. = ALIGN(4);
_sdata = .; /* create a global symbol at data start */
*(.data) /* .data sections */
*(.data*) /* .data* sections */
. = ALIGN(4);
_edata = .; /* define a global symbol at data end */
} >RAM AT> FLASH
/* Uninitialized data section */
. = ALIGN(4);
.bss :
{
/* This is used by the startup in order to initialize the .bss secion */
_sbss = .; /* define a global symbol at bss start */
__bss_start__ = _sbss;
*(.bss)
*(.bss*)
*(COMMON)
. = ALIGN(4);
_ebss = .; /* define a global symbol at bss end */
__bss_end__ = _ebss;
} >RAM
/* User_heap_stack section, used to check that there is enough RAM left */
._user_heap_stack :
{
. = ALIGN(4);
PROVIDE ( end = . );
PROVIDE ( _end = . );
. = . + _Min_Heap_Size;
. = . + _Min_Stack_Size;
. = ALIGN(4);
} >RAM
/* Remove information from the standard libraries */
/DISCARD/ :
{
libc.a ( * )
libm.a ( * )
libgcc.a ( * )
}
.ARM.attributes 0 : { *(.ARM.attributes) }
}
EDIT:
I have installed cubeMX and used their generated code. However the blinking LED is still not in sight.
Here is my main method in the main.c file:
int main(void)
{
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART2_UART_Init();
while (1) {
// write pin state
// NOTE: You can in turn use HAL_GPIO_TogglePin
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_3);
// synchronous delay for 500 ms
HAL_Delay(500);
}
}
I am assuming the generated code is working properly so the error is either in my main.c or just a hardware issue.
You are not toggling the correct I/O. From the Nucleo-F411RE user manual:
User LD2: the green LED is a user LED connected to Arduino signal D13 corresponding to STM32 I/O PA5 (pin 21) or PB13 (pin 34) depending on the STM32 target.
D13 heare refers to the Arduino connector D13 pin - the name is for compatibility with Arduino Shields and is not related to the STM32 GPIO pin name. In your case it is PA5 (Table 16 in the user manual).
Your (original) delay function is fundamentally flawed. A busy-loop delay will vary depending on the clock rate of the processor, the compiler used and even the compiler options used. But more especially because decrementing from 1000 will take no appreciable length of time, such that the "flashing" will be too rapid to perceive with the human eye, and probably even exceeds the on/off time of the LED itself.
You should instead use a hardware timer or clock source. All Cortex-M devices have a SYSCLK that by default runs at the system clock rate divided by 8. So for example:
void delay_millisec(unsigned ms )
{
unsigned ticks = (ms * (SystemCoreClock/ 8)) / 1000 ;
SysTick->LOAD = ticks;
SysTick->VAL = 0;
SysTick->CTRL = SysTick_CTRL_ENABLE_Msk;
while ((SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) == 0);
SysTick->CTRL = 0;
}
Then:
while (1)
{
GPIO_ToggleBits(GPIOD, GPIO_Pin_13);
delay_millisec(500);
}
Will result in a 1Hz flash rate.
A more sophisticated solution is to have the SYSCLK ISR increment a tick counter at 1ms intervals and have the delay function count elapsed tick intervals. That is how the default HAL_delay()
implementation works for example.