I'm learning STM32F103 MCU and I'm trying to implement "blink led" program with bare metal and systick timer.
Here's what I've came up with:
#include <stdint.h>
static void enable_port_clock(void);
static void configure_pin(void);
static void configure_sys_tick_timer(void);
void start(void)
{
enable_port_clock();
configure_pin();
configure_sys_tick_timer();
for (;;)
{
// asm("WFI");
}
}
static void toggle_pin(void);
void sys_tick_exception_handler(void)
{
toggle_pin();
}
static void enable_port_clock(void)
{
// enable I/O port C clock
uint32_t rcc_base_address = 0x40021000;
uint32_t rcc_apb2enr_address = rcc_base_address + 0x18;
volatile uint32_t *rcc_apb2enr_pointer = (uint32_t *)rcc_apb2enr_address;
uint32_t rcc_apb2enr_value = *rcc_apb2enr_pointer;
rcc_apb2enr_value |= 0x00000010;
*rcc_apb2enr_pointer = rcc_apb2enr_value;
}
static void configure_pin(void)
{
// configure PC13 as open-drain output with 10 MHz speed
uint32_t gpioc_base_address = 0x40011000;
uint32_t gpioc_crh_address = gpioc_base_address + 0x04;
volatile uint32_t *gpioc_crh_pointer = (uint32_t *)gpioc_crh_address;
uint32_t gpioc_crh_value = *gpioc_crh_pointer;
gpioc_crh_value |= 0x00600000;
gpioc_crh_value &= ~0x00900000;
*gpioc_crh_pointer = gpioc_crh_value;
}
static void configure_sys_tick_timer(void)
{
uint32_t stk_base_address = 0xE000E010;
uint32_t stk_ctrl_address = stk_base_address + 0x00;
volatile uint32_t *stk_ctrl_pointer = (uint32_t *)stk_ctrl_address;
uint32_t stk_load_address = stk_base_address + 0x04;
volatile uint32_t *stk_load_pointer = (uint32_t *)stk_load_address;
// enable systick exception, enable systick counter
uint32_t stk_ctrl_value = *stk_ctrl_pointer;
stk_ctrl_value = stk_ctrl_value | 0x00000003;
*stk_ctrl_pointer = stk_ctrl_value;
// set reload value to 500 ms
*stk_load_pointer = 8000000 / 8 / 2;
}
static void toggle_pin(void)
{
// toggle PC13
uint32_t gpioc_base_address = 0x40011000;
uint32_t gpioc_odr_address = gpioc_base_address + 0x0c;
volatile uint32_t *gpioc_odr_pointer = (uint32_t *)gpioc_odr_address;
uint32_t gpioc_odr_value;
gpioc_odr_value = *gpioc_odr_pointer;
gpioc_odr_value ^= 0x00002000;
*gpioc_odr_pointer = gpioc_odr_value;
}
Basically I configure peripherals, switch LED in the systick exception handler and just run endless loop in my main code.
This code works just fine, however I don't like that I need to waste cycles in the endless loop, so I tried to insert WFI
instruction which is supposed to put CPU to the sleep until the next interrupt. Well, program with this WFI instruction uncommented, works fine, at least it blinks the led. However after I flash this program, I can't connect to my MCU with ST-link anymore. I have to switch BOOT0 pin to "1", then I can connect to it again. I tried it with both WFI
and WFE
instructions, they act identically.
I guess it breaks some kind of debug functionality, however I don't understand what's the proper way to "sleep forever".
I'm using "blue pill" board with STM32F103 CPU and ST-link debugger ripped from nucleo board. For software I'm using st-flash
utility from the opensource stlink
project.
After this "buggy" firmware is written to the MCU, even st-info --probe
does not show connected MCU anymore.
Actually all this code just doesn't matter. I even tried to comment out everything but the WFI loop and it acts the same.
I also tried to configure System control register (SCB_SCR) and enable SEVEONPEND or SLEEPONEXIT bits, which didn't make a difference.
The effect of WFI is, that it switches off the processor clock (see Low-power modes subchapter of PWR chapter in RM0008). The on-chip debugger is part of the processor, so it won't work at that moment, either.
You can set DBGMCU_CR.DBG_SLEEP, which is a bit intended to facilitate debugging with WFI. Some debuggers allow to set this from the debugging interface, IDEs often have some tickbox for this.
Btw. if the blue-pill was cheap, it's unlikely that you have an STM32F103.
JW