STM32F103 Bare Metal Programming Issue - Page 1
Forum section: electronics / general

author: rcbuck
author_rank: Frequent Contributor
timestamp: December 30, 2021, 10:44:08 pm
content: I am experimenting with bare metal programming a STM32F103 bluepill board. GPIOC13 is the onboard LED which is turned on when the pin is low. I just want to toggle the LED to confirm my code is correct. Apparently I am missing something.There is an ongoing discussion of this here:https://www.eevblog.com/forum/microcontrollers/is-st-cube-ide-a-piece-of-buggy-crapHowever rather than post there I started a new thread for my problem. Code is very simple:Code:#include <stdio.h>#include <stdlib.h>#include "main.h"int main(void){volatile uint32_t val = 0x36000000;setclocks();setup();while(1){GPIOC->ODR = (uint32_t)0x00000000; // GPIOC13 low - LED ONpause(val);GPIOC->ODR = (1<<13); // GPIOC13 high - LED OFFpause(val);}}void setup(void){// GPIO PORTARCC->APB2ENR |= RCC_APB2ENR_IOPAEN; // enable GPIOA clockGPIOA->CRL = 0x22222222; // GPIOA0-A7 all push-pull outputs @ 2 MHzGPIOA->CRH = 0x2222222B; // GPIOA9-A15 all push-pull outputs @ 2 MHz (A8 @ 50 MHz)// GPIO PORTCRCC->APB2ENR |= RCC_APB2ENR_IOPCEN; // enable GPIOC clockGPIOC->CRL = 0x22222222; // GPIOC0-C7 all push-pull outputs @ 2 MHzGPIOC->CRH = 0x22222222; // GPIOA9-A15 all push-pull outputs @ 2 MHzGPIOC->ODR = ((uint32_t)0x00000000); // default value}void setclocks(void){RCC->CR = ((uint32_t)0x00000000); // kill internal HSI clockRCC->CFGR |= RCC_CFGR_PLLMULL9; // PLL X 9 ( 8 MHz x 9 == 72 MHz)RCC->CFGR |= (1<<16); // HSE drives PLL with no divisionRCC->CFGR |= RCC_CFGR_PPRE1_DIV2; // APB1 prescale 2RCC->CFGR |= RCC_CFGR_SW_PLL; // PLL selected as system clockRCC->CFGR |= RCC_CFGR_MCOSEL_PLL_DIV2; // MCO output is PLL output /2 (72/2 == 36 MHz)RCC->CR |= RCC_CR_HSEON; // HSE on and selected as PLL input clock/RCC->CR |= RCC_CR_PLLON; // Turn PLL on}volatile void pause(uint32_t val){while(val > 0){val--;}}Apparently the pause loop is not functioning properly. The LED should toggle at approximately a 1 second rate. When the board is reset the LED turns on and never turns off. If I comment out line 1 in the main while loop, the LED flashes on for approximately 100 msecs and then goes out. If I comment out the first 2 lines the LED never turns on which is what you would expect. If I comment out the last 3 lines the LED remains on forever which is what you would expect.I tried adding this line: GPIOC->ODR ^= (1<<13); and commenting out all the other code except for one pause(val) line. The LED doesn't turn on.I think the system clock is running at 72 MHz with the code I have. A counter on GPIOA8 shows a 36 MHz signal as expected.I also tried using the BRR and BSRR registers to toggle the LED with no luck. I have optimization set to 00 but made the pause stuff all volatile anyway. My linker script is attached. Script has a .txt extension but it is really a .ld extension in the IDE. What am I missing?

author: ajb
author_rank: Super Contributor
timestamp: December 30, 2021, 11:15:34 pm
content: In `volatile void pause(uint32_t val)` the `volatile` qualifies the return type of the function, which is void. Since void means the function doesn't return anything, there's nothing to be qualified by `volatile`.There is a non-standard meaning to `volatile void` in a function declaration as explained here, but this is still not what you want:https://stackoverflow.com/questions/14288603/what-does-or-did-volatile-void-function-doIn a delay function like this, what you really want to do is qualify the actual variable that you are decrementing (or incrementing). THAT is what will prevent the compiler from optimizing out the loop. So you could do:Code:void pause(volatile uint32_t val){while(val > 0){val--;}}

author: rcbuck
author_rank: Frequent Contributor
timestamp: December 31, 2021, 02:05:17 am
content: ajb, I had it the way you show in your snippet and that did not work. So I made the function volatile. If you look at the variable "var" that I am passing to the function you can see I made it volatile to prevent it from being optimized away. I didn't think the variable in the function needed to be volatile. Both the passed and the incoming variable are named "var" but obviously they are not the same variable. With the optimization level set to -00 the function should not have been optimized away. I just looked at the assembly listing. The function doesn't get optimized out whether the incoming variable is declared as volatile or not.To prevent confusion I changed the function incoming variable to "inp" and made it volatile the way you suggest. The code still behaves the same way as before.

author: eutectique
author_rank: Frequent Contributor
timestamp: December 31, 2021, 03:29:50 am
content: Assembly listing would be very handy.

author: ajb
author_rank: Super Contributor
timestamp: December 31, 2021, 03:46:43 am
content: You can't pass a volatile object to a function and expect the function to treat it as volatile if the function isn't declared to take a volatile argument. C is pass-by-value, so that's equivalent to just assigning the value from the volatile object to the not-volatile object, which will be handled as the unqualified type it is. This becomes an extremely important thing to understand when you get into volatile pointers, but fortunately the compiler will usually issue warnings about type mismatches when you get that wrong.But anyway, if you see the decrement loop in your assembly listing then that isn't your problem. I think your clock configuration is wrong, in several ways. Generally MCUs start up from their internal oscillator, so you can't start your clock setup by disabling that. If that worked, then there would be no clock running the chip and it would just stop, so usually that isn't allowed by the clock controller. So that first line in setclocks() likely isn't doing anything (although some parts have fuses to set a different default config, or might start from a low speed rc oscillator, so check the datasheet). The fact that you're seeing 36MHz on MCO suggests your PLL config is okay,but I don't see anywhere that you're switching the main clock to the PLL[edit: was looking at this on my phone earlier and missed it], I see that you are selecting the PLL as the system clock but you're not waiting for the PLL to be ready first. PLLs can take some time to stabilize, and the RCC probably prohibits selecting it as the clock source until the PLLRDY bit is set. So I suspect that your core is still running at the default clock speed, which is often 1MHz, in which case your LED would be blinking correctly, just once every 72 seconds instead of once every second. Actually it would likely be even longer, because the decrement loop is likely more than one instruction, so it would be some multiple of that. A quick test would be to cut your delay value down by a factor of a hundred or so. Of course you could also just stare at it for a few minutes and see if it blinks but that's boring.Clock configuration on modern MCUs can be pretty complex, so it's important to be sure you pay close attention to the requirements given in the datasheet/reference manual and follow those closely. In particular, make sure the PLLRDY bit is set before changing the core clock, and make sure the core is running from the new clock before disabling the old clock.

author: wek
author_rank: Frequent Contributor
timestamp: December 31, 2021, 07:51:01 am
content: For higher system clock frequencies, you have to setFLASH waitstatesin FLASH_ACR.LATENCY.JW

author: Silenos
author_rank: Regular Contributor
timestamp: December 31, 2021, 10:37:46 am
content: Make it simpler: get rid of the clock setup for now, get rid of the blinking loop, just figure that you can just enable/disable the led on the default clock, then make the loop running the default clock.I looked at the RM, unfortunately they seem they don't provide full step-by-step pll initialization sequence with nice state charts for the F1, like those you get in RMs of newer STMs. But in general disable/enable operations of PLL/HSE... are followed by polling the adjacent RDY flag. it always takes relevant moments.+ yes, mind the possibility of crashing the device by writing invalid values into pll, by exceeding clocks on buses, or by the invalid flash waitstates.

author: rcbuck
author_rank: Frequent Contributor
timestamp: December 31, 2021, 06:50:33 pm
content: Quoteajb, The FRM states that the default value for the default value for the RCC_CR register is for the HSI to be on. I commented out my code that killed the HSI but system still did not work.QuoteI inserted the wait for HSE and PLL to become ready but it did not make a difference. I did find that if I cut the delay value down to 256,000 the LED would flash very fast, approximately 3 times per second. This tended to indicate the clock was running at 72 MHz. If I set the delay value to 257,000 the system returned to a non-working state. See my next commentsQuotewek, that was the problem. The FRM states that "010 Two wait states, if 48 MHz < SYSCLK <= 72 MHz" As soon as I added code to set the wait state to 2, the system started working; LED flashing as expected.I ran a few more experiments to confirm register settings. I found that I could kill the HSI on startup and the system continued to function. I also found it would work without waiting for the HSE or PLL to be ready. However, as per ajb's suggestion, I left the HSI running on startup. I also left the code in place that waits for the HSE and PLL to be ready as ajb's and the RFM suggested.The final code size compiles to 840 bytes. The same experiment using _HAL function calls compiles to 2968 bytes. This is a significant size difference. I now need to create my own default templates for other miscellaneous things such as UART, I2C, SPI, Timers, etc. The only time I will use _HAL in my future projects will be if I need USB or Ethernet features. Those would be a nightmare to try to handle on my own.Thanks to everyone who helped with the problem I was having.

author: wek
author_rank: Frequent Contributor
timestamp: December 31, 2021, 08:29:11 pm
content: > I found that I could kill the HSI on startupNo, you can't. As RCC_CR.HSION's description in RM0008 says,This bit cannot be reset if the internal 8 MHz RC is used directly or indirectlyas system clock or is selected to become the system clock.If you'd read RCC_CR back immediately after writing 0, you'd see it's 0x0000xx03.> I also found it would work without waiting for the HSE or PLL to be ready.After switching to a clock which does not run, the processor stalls, as it has... well... no clock... :-) And when clock appears, it simply continues to run.But I wouldn't recommend this to be used normally. There's no documentation about what exactly happens upon the clock startup, while the HSE's/PLL's output is not stable. It's most likely to be gated by the HSERDY/PLLRDY signals, but there's no guarantee for that.JW

author: rcbuck
author_rank: Frequent Contributor
timestamp: December 31, 2021, 09:37:36 pm
content: QuoteI am not using the internal oscillator directly and I'm don't know what they mean by "indirectly".But I see what you are saying. The manual says this: "Set by hardware to force the internal 8 MHz RC oscillator ON when leaving Stop or Standby mode."I will connect the debugger to the board out of curiosity and see what RCC_CR contains after I turn the HSI oscillator off.

author: ajb
author_rank: Super Contributor
timestamp: December 31, 2021, 10:02:50 pm
content: Quote from: rcbuck on December 31, 2021, 09:37:36 pmIn most cases "indirectly" just means via the PLL. So if you have the HSI feeding the PLL and the PLL feeding the system clock, then the system clock isindirectlybased on the HSI, just with the PLL in between. Some parts may have other configurations that would count as an indirect use of an oscillator, like an FLL or something.I looked at the F103 RM and yeah, it does say attempting to switch the system clock to an available clock source that is not ready will occur whenever that source becomes ready, which is interesting. I think I would rather it simply not accept the change, since having the system clock frequency suddenly change at some undefined subsequent point during the program could cause a number of problems that would be a pain to debug. It'll be safest to wait for the desired clock configuration to be in place before moving on with the program unless you can guarantee that the clock change won't hurt anything.

author: bson
author_rank: Unknown
timestamp: December 31, 2021, 10:31:20 pm
content: Quote from: rcbuck on December 30, 2021, 10:44:08 pmAfter setting HSEON in RCC CR I'd poll HSERDY to wait for it to start up before enabling the PLL.Also, don't kill HSI until HSE has started. And don't set the PLL to use it as input until it's confirmed to be running.I think the default behavior is that you can't select a non-running HSE, so your attempt to kill HSI will do nothing (because that would leave the clock module without a source), and setting the PLL to use HSE will do nothing because HSERDY isn't set. So you end up sourcing the PLL form HSI. But that's just a guess.I'd propose this order:1. Start the HSE and wait for it to start up2. Configure the PLL and set it to use HSE3. Start the PLL and wait for it to start up4. Stop the HSI

author: rcbuck
author_rank: Frequent Contributor
timestamp: January 01, 2022, 12:11:42 am
content: bson, thanks for the suggestions. I decided to just let the HSI oscillator run. Here are the code settings I am have settled on.Code:FLASH->ACR |= FLASH_ACR_LATENCY_2;RCC->CFGR |= RCC_CFGR_PLLMULL9; // PLL X 9 ( 8 MHz x 9 == 72 MHz)RCC->CFGR |= (1<<16); // HSE drives PLL with no divisionRCC->CFGR |= RCC_CFGR_PPRE1_DIV2; // APB1 prescale 2RCC->CFGR |= RCC_CFGR_SW_PLL; // PLL selected as system clockRCC->CFGR |= RCC_CFGR_MCOSEL_PLL_DIV2; // MCO output is PLL output /2 (72/2 == 36 MHz)RCC->CR |= RCC_CR_HSEON; // HSE on and selected as PLL input clock/while (!(RCC->CR & RCC_CR_HSEON)); // wait for HSE to go activeRCC->CR |= RCC_CR_PLLON; // Turn PLL onwhile(!(RCC->CR & RCC_CR_PLLRDY)); // wait for PLL to go activeI was going to try to debug the HSI behavior but I have a problem. I'm sure it is an incorrect setting somewhere in the IDE.I am able to compile the code in Release mode and it works fine on the board. When I try to compile in Debug mode the compiler complains that it can't find main.h. Been trying to solve this for over an hour with no luck.

author: thm_w
author_rank: Super Contributor
timestamp: January 01, 2022, 12:28:14 am
content: Quote from: rcbuck on December 31, 2021, 06:50:33 pmYou can also consider some options that are a bit more readable:https://www.eevblog.com/forum/microcontrollers/stm32-macro-for-port-and-pin-read-and-write/https://github.com/ataradov/mcu-starter-projects/blob/master/stm32g031/main.c

author: rcbuck
author_rank: Frequent Contributor
timestamp: January 01, 2022, 06:00:43 pm
content: QuoteYes, that will be part of my template creation process.Quotewek, I managed to get my debug code to compile so I could watch the HSI behavior. It is as you said, it cannot be turned off. I turn it off but when the next instruction executes and I look at HSI again, it is back on. I can turn it off after the HSE oscillator and PLL are up and running and it stays off.