27 Mar 2019 - tsp
Last update 16 Apr 2019
4 mins
This is a (really) short summary on how to get started with compiling and uploading code to the STM32F401RE nucleo starter board (for example available via Amazon) without using any of the bloated IDEs or code generators that are normally suggested. This is a simple development board for STM32F401 and an easy starting point for further experiments with this microcontroller.
Required software:
devel/arm-none-eabi-gcc
devel/arm-none-eabi-binutils
devel/arm-none-eabi-newlib
devel/openocd
One can either install them from binary package management:
pkg install devel/arm-non-eabi-gcc
pkg install devel/arm-non-eabi-binutils
pkg install devel/arm-non-eabi-newlib
pkg install devel/openocd
or via ports
cd /usr/ports/devel/arm-non-eabi-gcc && make install clean
cd /usr/ports/devel/arm-non-eabi-binutils && make install clean
cd /usr/ports/devel/arm-non-eabi-newlib && make install clean
cd /usr/ports/devel/openocd && make install clean
One should also download the stm32cubef4
package and extract it somewhere in ones home directory or on the local
machine. This is primary required for the CMSIS drivers and templates (of course it’s possible to develop applications
without them but they make life much easier)
The basic STM32F4 project requires an SystemInit()
function that gets called from the startup_stm32f401xe.s
assembly file before libc initialization and an main()
function that will be called after libc initialization.
If you want to use startup_stm32f401xe.s
copy that file from the stm32cubef4
packages
Drivers/CMSIS/Device/ST/STM32F4xx/Source/Templates/arm/ directory
into your project directory.
One can disable initialization of libc by commenting the line bl __libc_init_array
. With the previously mentioned
newlib it was crucial to disable these initializations. In this case one could also merge SystemInit and main into
a single function. The system initialization function should at least enable basic clocks, configure the main PLL,
set the clock source to the PLL, and configure the APB clock prescalers.
An example function could look like the following and might be saved into system.c
:
void SystemInit() {
// Enable the power interface clock
RCC->APB1ENR |= RCC_APB1LPENR_PWRLPEN;
PWR->CR |= PWR_CR_VOS_1;
while ((RCC->CR & RCC_CR_HSIRDY) == 0);
PWR->CR |= (uint32_t)(16 << 3);
/*
PLL configuration
VCO=336 MHz, PLLOUT=84 MHz, USB: 48 MHz, M:16, N:336, P:4, Q:7
*/
// Disable PLL and wait till it's disabled
RCC->CR &= ~(RCC_CR_PLLON);
while ((RCC->CR & RCC_CR_PLLRDY) != 0);
// Load configuration as described above
RCC->PLLCFGR = 0x27005410 | RCC_PLLCFGR_PLLP_0;
// Enable PLL and wait till it's locked
RCC->CR |= RCC_CR_PLLON;
while ((RCC->CR & RCC_CR_PLLRDY) == 0);
// Configure flash memory
FLASH->ACR |= FLASH_ACR_ICEN | FLASH_ACR_PRFTEN | FLASH_ACR_LATENCY_2WS;
if ((FLASH->ACR & FLASH_ACR_LATENCY) != FLASH_ACR_LATENCY_2WS) {
for(;;) { /* Endless loop in case of error */ }
}
// Select PLL as clock source
RCC->CFGR |= RCC_CFGR_SW_PLL;
while ((RCC->CFGR & RCC_CFGR_SWS_PLL) != RCC_CFGR_SWS_PLL);
// Set prescaler
RCC->CFGR &= ~(RCC_CFGR_HPRE);
RCC->CFGR |= RCC_CFGR_PPRE1_DIV2;
RCC->CFGR &= ~(RCC_CFGR_PPRE2);
}
One can then write a main program into main.c
, for example this small blinking test program:
#include <stm32f4xx.h>
// Simple busy-waiting sleep routine. Note that scaling is not fully accurate
void sleep_ms(uint32_t ms) {
volatile uint32_t i;
for (i = ms*1000; i != 0; i--) { }
}
int main() {
// Enable AHB1 clock for GPIO bank
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
// Configure as fast output
GPIOA->MODER |= (1 << (LED_PIN << 1));
GPIOA->OSPEEDR |= (3 << (LED_PIN << 1));
// Enable LED on A5 / D15
GPIOA->BSRR = 0x20;
for(;;) {
sleep_ms(500);
GPIOA->BSRR = 0x200000;
sleep_ms(500);
GPIOA->BSRR = 0x20;
}
}
or the same program using timer interrupts:
#include <stm32f4xx.h>
#define LED_PIN 5
#define LED_ON() GPIOA->BSRR |= (1 << LED_PIN)
#define LED_OFF() GPIOA->BSRR |= (1 << (16+LED_PIN))
void sleep_ms(uint32_t ms) {
volatile uint32_t i;
for (i = ms*1000; i != 0; i--) {
}
}
void TIM3_IRQHandler(void);
int main() {
/* Enbale GPIOB clock */
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
/* Configure GPIOA pin 5 as output */
GPIOA->MODER |= (1 << (LED_PIN << 1));
/* Configure GPIOA pin 5 in max speed */
GPIOA->OSPEEDR |= (3 << (LED_PIN << 1));
/* Turn on the LED */
GPIOA->BSRR = 0x20;
// Configure our timebase
RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;
TIM3->PSC = 599;
TIM3->ARR = 60000;
TIM3->DIER |= 0x01;
NVIC->ISER[0] |= 1 << 29;
TIM3->CR1 = TIM_CR1_CEN;
for(;;) { }
}
volatile int blink = 0;
void TIM3_IRQHandler(void) {
if(TIM3->SR & TIM_SR_UIF) {
if(blink == 0) {
blink = 1;
LED_ON();
} else {
blink = 0;
LED_OFF();
}
TIM3->SR &= ~TIM_SR_UIF;
}
}
Comiling and linking code is done with arm-none-eabi-gcc
:
arm-none-eabi-gcc -Wall -mcpu=cortex-m4 -mlittle-endian -mthumb -I${stm32cubef4Home}/Drivers/CMSIS/Device/ST/STM32F4xx/Include -I${stm32cubef4Home}/Drivers/CMSIS/Include -DSTM32F401xE -Os -c system.c -o system.o
arm-none-eabi-gcc -Wall -mcpu=cortex-m4 -mlittle-endian -mthumb -I${stm32cubef4Home}/Drivers/CMSIS/Device/ST/STM32F4xx/Include -I${stm32cubef4Home}/Drivers/CMSIS/Include -DSTM32F401xE -Os -c main.c -o main.o
arm-none-eabi-gcc -Wall -mcpu=cortex-m4 -mlittle-endian -mthumb -I${stm32cubef4Home}/Drivers/CMSIS/Device/ST/STM32F4xx/Include -I${stm32cubef4Home}/Drivers/CMSIS/Include -DSTM32F401xE -Os -c startup_stm32f401xe.s -o startup_stm32f401xe.o
arm-none-eabi-gcc -mcpu=cortex-m4 -mlittle-endian -mthumb -DSTM32F401xE -T${stm32cubef4Home}/Projects/STM32F401RE-Nucleo/Templates/TrueSTUDIO/STM32F4xx-Nucleo/STM32F401CE_FLASH.ld -Wl,--gc-sections system.o main.o startup_stm32f401xe.o -o main.elf
arm-none-eabi-objcopy -Oihex main.elf main.hex
The resulting hex file can now be written by using openocd
:
openocd -f /usr/local/share/openocd/scripts/board/st_nucleo_f4.cfg -c init -c "reset halt" -c "flash write_image erase main.hex" -c "verify_image main.hex" -c reset -c shutdown
Depending on your USB devfs configuration you might be required to run that command with elevated privileges or change access permission on USB devices (the latter one of course being the favored way)
This article is tagged:
Dipl.-Ing. Thomas Spielauer, Wien (webcomplains389t48957@tspi.at)
This webpage is also available via TOR at http://rh6v563nt2dnxd5h2vhhqkudmyvjaevgiv77c62xflas52d5omtkxuid.onion/