First steps with the STM32F4

Those chip close-ups are always so cute...

How to start with the STM32F4?

Of course, the first document to find for any chip is the datasheet [1], and the reference manual [2]. To write the start-up code, the manual of the CPU core [3] will be necessary as well. Then the header files with all peripherals register as macro symbol [4] would come to mind. Yes, you have to download the full 150+MB archive to get the (not even 1MB) header files, which are in:

STM32Cube_FW_F4_V1.3.0/Drivers/CMSIS/Device/ST/STM32F4xx/

Later it will also be interesting to know what is connected with what [5] outside of the chip, on the board.

Linker file and memory layout

Funnily, the SRAM is divided into 4 parts: 64KB (CCM) + 112KB (SRAM1) + 16KB (SRAM2) + 64KB (SRAM3) = 256KB. Note, that the CPU core has 3 separate busses: instruction (I), data (D), "system" (S).

/* Memory mapping
 * p. 84 in DM00071990 (rev.4) */

/* Block 0, Code                0x0000_0000 to 0x1FFF_FFFF
 * 0x0000_0000 to 0x001F_FFFF (flash,     2MB = 0x20_0000)
 * 0x1000_0000 to 0x0000_FFFF (CCM SRAM, 64KB = 0x01_0000)
 * Block 1, SRAM                0x2000_0000 to 0x3FFF_FFFF
 * 0x2000_0000 to 0x2001_BFFF (SRAM1,   112KB = 0x01_C000)
 * 0x2001_C000 to 0x2001_FFFF (SRAM2,    16KB = 0x00_4000)
 * 0x2002_0000 to 0x2002_FFFF (SRAM3,    64KB = 0x01_0000)
 */

/* size of the stack */
__stack_size = 4K;
/* size of the variables */
__var_size = 0x10000 - __stack_size;

MEMORY
{
  FLASH (rx)            : ORIGIN = 0x08000000, LENGTH = 0x200000 /* 2MB */
  SRAM_VAR (rw) : ORIGIN = 0x10000000, LENGTH = 0x9000 /*__var_size*/
  SRAM_STACK (rw)       : ORIGIN = 0x20009000, LENGTH = 0x1000 /*__stack_size*/
}

SECTIONS
{
        .text : {
                *(.vector);                             /* exception and interrupt vector */
                *(.text);                               /* code */
                *(.rodata);                             /* read-only variable */
        }                               > FLASH
        .data_flash : {}                > FLASH         /* flash image of .data will go here */

        .data : AT (ADDR(.data_flash))
        { *(.data) }                    > SRAM_VAR      /* initialized variables (initial value in flash) */
        .bss (NOLOAD) : { *(.bss) }     > SRAM_VAR      /* uninitialized variables (initial value = 0) */
        .stack (NOLOAD) : {} > SRAM_STACK               /* stack */
}

/* for the startup code
 * http://sourceware.org/ml/binutils/2007-07/msg00154.html
 * only addresses can be used in C code */
__data_flash_start_addr = ADDR(.data_flash);
__data_flash_end_addr = ADDR(.data_flash) + SIZEOF(.data);
__data_sram_start_addr = ADDR(.data);
__data_sram_end_addr = ADDR(.data) + SIZEOF(.data);
__bss_start_addr = ADDR(.bss);
__bss_end_addr = ADDR(.bss) + SIZEOF(.bss);
__stack_end_addr = ADDR(.stack) + __stack_size;

Clocks and initial settings

On start-up, the CPU core runs with the high speed internal clock (HSI) at 16MHz. Before increasing the clock frequency, flash settings must be updated: the CPU will run significantly faster than the flash.

Settings for a CPU at maximum clock (168MHz) and normal power supply:

/* Flash settings (see RM0090 rev9, p80) */
FLASH->ACR =
            FLASH_ACR_LATENCY_5WS               /* 6 CPU cycle wait */
          | FLASH_ACR_PRFTEN                    /* enable prefetch */
          | FLASH_ACR_ICEN                      /* instruction cache enable */
          | FLASH_ACR_DCEN;                     /* data cache enable */
/* Configure clocks
 * Max SYSCLK: 168MHz
 * Max AHB:  SYSCLK
 * Max APB1: SYSCLK/4 = 48MHz
 * Max APB2: SYSCLK/2 = 86MHz
 * + enable sys clock output 2 with clock divider = 4 */
RCC->CFGR =
          0x0                           /* Clock output 2 is SYSCLK (RCC_CFGR_MCO2) */
        | ( 0x6 << 27)                  /* Clock output divider */
        | RCC_CFGR_PPRE2_DIV2           /* APB2 prescaler */
        | RCC_CFGR_PPRE1_DIV4;          /* APB2 prescaler */

My board has an 8MHz external crystal oscillator. In order to generate a 168MHz system clock, the PLL must be used:

/* Clock control register */
RCC->CR = RCC_CR_HSEON;         /* Enable external oscillator */

/* Wait for locked external oscillator */
while((RCC->CR & RCC_CR_HSERDY) != RCC_CR_HSERDY);

/* PLL config */
RCC->PLLCFGR =
          RCC_PLLCFGR_PLLSRC_HSE                /* PLL source */
        | (4 << 0)                              /* PLL input division */
        | (168 << 6)                            /* PLL multiplication */
        | (0 << 16)                             /* PLL sys clock division */
        | (7 << 24);                            /* PLL usb clock division =48MHz */

/* crystal:  8MHz
 * PLL in:   2MHz (div 4)
 * PLL loop: 336MHz (mul 168)
 * PLL out:  168MHz (div 2)
 * PLL usb:  48MHz (div 7)
 */

/* Enable PLL */
RCC->CR |= RCC_CR_PLLON;


/* Wait for locked PLL */
while((RCC->CR & RCC_CR_PLLRDY) != RCC_CR_PLLRDY);

/* select system clock */
RCC->CFGR &= ~RCC_CFGR_SW; /* clear */
RCC->CFGR |= RCC_CFGR_SW_PLL;   /* SYSCLK is PLL */

/* Wait for SYSCLK to be PPL */
while((RCC->CFGR & RCC_CFGR_SW_PLL) != RCC_CFGR_SW_PLL);

A quick test of the clock settings can be done using the Microcontroller Clock Output pins (MCO1 and MCO2). According to the clock tree diagram, SYSCLK is connected to MCO2. This pin as to be set as alternate function.

/* PC9 = MCO2 = alternate func */
GPIOC->MODER   = 0x00090000; /* output */
GPIOC->OTYPER  = 0x00000000; /* push-pull */
GPIOC->OSPEEDR = 0x000D0000; /* medium speed */

The system clock divided by 4 is on the pin 40. Since my vjCanUsb board makes it easy to reach any pi...

Oscilloscope result:

42MHz * 4 = 168MHz (Success!)