2
votes

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.

3
Decrementing an integer Fromm 1000 to zero will take no more than a few tens of microseconds, you will not perceive any flashing. Use the SYSCLK or a hardware timer to measure time passing. - Clifford
Wow, progress! A LED blinking program used to take not more than 10 assembly language instructions in the old days! :) - tonypdmtr
confused you wanted to do it from scratch but are using the libraries... - old_timer
@tonypdmtr it still only takes about that many lines of asm code, the libraries are....well as expected....thus the what did you mean by from scratch question...for this chip/board two writes to setup the led then a write to turn on a write to turn off then loops to delay - old_timer
@old_timer : I guess he meant, the project build system from scratch rather then using an IDE project manager - so less than everything perhaps. - Clifford

3 Answers

4
votes

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.

2
votes

From the comments...this is the 10 lines (more like 20, or more depends on how you count) of assembly code program.

NUCLEO-F411RE which uses the STM32F411RE.

.cpu cortex-m7
.syntax unified
.thumb

stacktop: .word 0x20001000
.word reset

.thumb_func
reset:
/*
Address offset: 0x30
Reset value: 0x0000 0000
*/
    ldr r0,=0x40023830
    ldr r1,=0x00000001
    str r1,[r0]
/*
Address offset: 0x00
Reset value: 0xA800 0000 for port A
*/
    ldr r0,=0x40020000
    ldr r1,=0xA8000400
    str r1,[r0]

    add r0,#0x18
    ldr r1,=0x00000020
    ldr r2,=0x00200000

d0:
    str r1,[r0]
    mov r3,#0x00100000
d1:
    subs r3,#1
    bne d1

    str r2,[r0]
    mov r3,#0x00100000
d2:
    subs r3,#1
    bne d2

    b d0

I used reset values instead of read-modify-write to save a few instructions/locations.

arm-none-eabi-as --warn --fatal-warnings -mcpu=cortex-m7 flash.s -o flash.o
arm-none-eabi-ld -Ttext=0x08000000 flash.o -o flash.elf
arm-none-eabi-ld: warning: cannot find entry symbol _start; defaulting to 0000000008000000
arm-none-eabi-objdump -d flash.elf > flash.list
arm-none-eabi-objcopy -O binary flash.elf flash.bin

_start doesn't matter could add the two lines to make that comment go away.

flash.elf:     file format elf32-littlearm


Disassembly of section .text:

08000000 <stacktop>:
 8000000:   20001000    .word   0x20001000
 8000004:   08000009    .word   0x08000009

08000008 <reset>:
 8000008:   480b        ldr r0, [pc, #44]   ; (8000038 <d2+0x6>)
 800000a:   f04f 0101   mov.w   r1, #1
 800000e:   6001        str r1, [r0, #0]
 8000010:   480a        ldr r0, [pc, #40]   ; (800003c <d2+0xa>)
 8000012:   490b        ldr r1, [pc, #44]   ; (8000040 <d2+0xe>)
 8000014:   6001        str r1, [r0, #0]
 8000016:   f100 0018   add.w   r0, r0, #24
 800001a:   f04f 0120   mov.w   r1, #32
 800001e:   f44f 1200   mov.w   r2, #2097152    ; 0x200000

08000022 <d0>:
 8000022:   6001        str r1, [r0, #0]
 8000024:   f44f 1380   mov.w   r3, #1048576    ; 0x100000

08000028 <d1>:
 8000028:   3b01        subs    r3, #1
 800002a:   d1fd        bne.n   8000028 <d1>
 800002c:   6002        str r2, [r0, #0]
 800002e:   f44f 1380   mov.w   r3, #1048576    ; 0x100000

08000032 <d2>:
 8000032:   3b01        subs    r3, #1
 8000034:   d1fd        bne.n   8000032 <d2>
 8000036:   e7f4        b.n 8000022 <d0>
 8000038:   40023830    .word   0x40023830
 800003c:   40020000    .word   0x40020000
 8000040:   a8000400    .word   0xa8000400

copy flash.bin over to your card and the led should blink as it did on mine.

Per the documentation, the LED is on the Arduino D1 which is PA5 is where D13 is connected for the F411RE and F401RE NUCLEO boards. (It is PB13 on other NUCLEO products that use this PCB). If you only read so far as to see that the doc said PB13 or PA5, then at least try one then the other. And as covered in comments or answers your delay needs to be big enough between gpio state changes for the human eye to see, so put a good sized delay in there.

mov r3,#0x00100000

Changing these r3 lines (if it complains change it to ldr r3,#0xwhatever_you_want) to see the blink rate change on the next build for example

mov r3,#0x00400000
0
votes

If it is your first project - do not use SPL (Standard Peripheral Library). It is not supported by STM any more and there are no libraries for newer STM32 uCs. Install cubeMX and use startup generated by this tool projects.

In your amended code you need

HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_13); instead of HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_3);