I am trying to learn to program the STM32G030K6 by directly manipulating the registers (without relying on CubeMX). My program is intended to set pin PA5 to high.
// Target: STM32G030K6T6
// Goal: Set pin PA5 to high
#include "stm32g0xx.h" // Device header
int main(void)
{
RCC->IOPENR |= 1; // Enable GPIOA Clock
GPIOA->MODER |= 0x400; // Set GPIOA MODE5 to a general purpose output
GPIOA->ODR = 0x20; // Set PA5 high
while(1)
{
}
}
The program does not effect PA5 at all. I have successfully tested the setup with a CubeMX blink program to prove it is not a hardware issue.
So what I have figured out from you so far is that you bought/acquired this part put it down on a breakout board. Have applied power and ground, added an led and resistor, and have an stlink hooked up. Can use CubeMX and make it work are using Kiel.
So I have made many a breakout board put the leds and such on the board because I got tired of wiring up items separately. The parts I have used you needed to make sure VDD and VDDA were connected but yours it is the same pin, check. VDD and VSS no doubt if you have it working. NRST pulled up for good measure although I think not required as there is an internal pull up, but BOOT0 did need a pull down, but this is an STM32G and you have pointed out that SWCLK and BOOT0 share the same pin. ST sadly is going away from the on chip bootloader or at least it is disabled by the factory
ST production value: 0xDFFF E1AA
Bit 24 nBOOT_SEL
0: BOOT0 signal is defined by BOOT0 pin value (legacy mode)
1: BOOT0 signal is defined by nBOOT0 option bit
So as shipped a new part BOOT0 is not something you can rely on to get into the bootloader and use a uart solution to download code into the flash, nor can you use it to get yourself unbricked while doing this level of work.
So the stlink is connected you said Kiel can talk to the part, so that is all in theory fine, not the problem.
I don't have Kiel off hand, but everyone can get a gnu cross compiler or build one from sources.
apt-get install binutils-arm-linux-gnueabi gcc-arm-linux-gnueabi
The code below does not care about arm-non-eabi- vs arm-linux-gnueabi- variations on the cross compiler it is independent of those differences, it just needs the compiler assembler and linker.
Now this will probably again get into a personal opinion battle with certain other SO users. Work through the noise. I am specifically avoiding CMSIS, I have seen the implementation, and you should inspect it to, for now you don't want to add that risk to your code, remove it and add it later as desired. This is my style it specifically controls the instruction used for access, everything about is based on a lot of experience even though you don't see that, designed for the reader to have a high chance of success. Make it your own if/when you get this to work and/or the side comments which is my real goal may help you examine the binary you are building with your own tool to eliminate common traps.
It is not simply a case of getting the C code in main() right for bare-metal code to work you need the whole thing from reset on to be right.
Flash based version:
flash.s
.cpu cortex-m0
.thumb
.thumb_func
.global _start
_start:
stacktop: .word 0x20001000
.word reset
.word hang
.word hang
.thumb_func
reset:
bl notmain
b hang
.thumb_func
hang: b .
.thumb_func
.globl PUT32
PUT32:
str r1,[r0]
bx lr
.thumb_func
.globl GET32
GET32:
ldr r0,[r0]
bx lr
.thumb_func
.globl dummy
dummy:
bx lr
notmain.c
void PUT32 ( unsigned int, unsigned int );
unsigned int GET32 ( unsigned int );
void dummy ( unsigned int );
#define RCC_BASE 0x40021000
#define RCC_IOPENR (RCC_BASE+0x34)
#define GPIOA_BASE 0x50000000
#define GPIOA_MODER (GPIOA_BASE+0x00)
#define GPIOA_OTYPER (GPIOA_BASE+0x04)
#define GPIOA_BSRR (GPIOA_BASE+0x18)
#define DCOUNT 2000000
int notmain ( void )
{
unsigned int ra;
unsigned int rx;
ra=GET32(RCC_IOPENR);
ra|=1<<0; //enable port a
PUT32(RCC_IOPENR,ra);
ra=GET32(GPIOA_MODER);
ra&=~(3<<(5<<1)); //clear bits 10,11
ra|= (1<<(5<<1)); //set bit 10
PUT32(GPIOA_MODER,ra);
ra=GET32(GPIOA_OTYPER);
ra&=~(1<<5); //clear bit 5
PUT32(GPIOA_OTYPER,ra);
for(rx=0;;rx++)
{
PUT32(GPIOA_BSRR, (1<<(5+ 0)) );
for(ra=0;ra<DCOUNT;ra++) dummy(ra);
PUT32(GPIOA_BSRR, (1<<(5+16)) );
for(ra=0;ra<DCOUNT;ra++) dummy(ra);
}
return(0);
}
flash.ld
MEMORY
{
rom : ORIGIN = 0x08000000, LENGTH = 0x1000
ram : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > rom
.rodata : { *(.rodata*) } > rom
.bss : { *(.bss*) } > ram
}
build
arm-none-eabi-as --warn --fatal-warnings -mcpu=cortex-m0 flash.s -o flash.o
arm-none-eabi-gcc -Wall -Werror -O2 -nostdlib -nostartfiles -ffreestanding -mcpu=cortex-m0 -mthumb -c notmain.c -o notmain.o
arm-none-eabi-ld -o notmain.elf -T flash.ld flash.o notmain.o
arm-none-eabi-objdump -D notmain.elf > notmain.list
arm-none-eabi-objcopy notmain.elf notmain.bin -O binary
Again you can replace arm-none-eabi with arm-linux-gnueabi if that is what you have/found. This code doesn't care about the differences.
The point here is for the processor to boot:
Disassembly of section .text:
08000000 <_start>:
8000000: 20001000 andcs r1, r0, r0
8000004: 08000011 stmdaeq r0, {r0, r4}
8000008: 08000017 stmdaeq r0, {r0, r1, r2, r4}
800000c: 08000017 stmdaeq r0, {r0, r1, r2, r4}
08000010 <reset>:
8000010: f000 f808 bl 8000024 <notmain>
8000014: e7ff b.n 8000016 <hang>
08000016 <hang>:
8000016: e7fe b.n 8000016 <hang>
The application flash starts at 0x08000000 in the ARM memory space, called the Main Flash Memory in the reference manual. Depending on the boot strap settings 0x08000000 will be mirrored at 0x00000000, as documented in the ARM manuals this is where the vector table lives. The first word is a value loaded into the stack pointer on reset, the word at address 0x00000004 (which would be mirrored to 0x08000004) is the reset vector.
The above used the disassembler so it is trying to disassemble those values as instructions they are values/vectors ignore the disassembly for that table.
Assuming we can get the tools to put this binary in the flash at the desired location then
08000000 <_start>:
8000000: 20001000 value loaded into sp on reset
8000004: 08000011 reset vector
The reset vector is the address of the code to execute for that exception with the lsbit set to indicate thumb mode, the lsbit is stripped it does not go into the pc. So the reset vector address here is 0x08000010 which is correct:
08000010 <reset>:
8000010: f000 f808 bl 8000024 <notmain>
8000014: e7ff b.n 8000016 <hang>
And can follow this to notmain, name of the C entry point is not important, and some tools will add extra stuff it sees the label main(), have not seen one of those for years but continue to do this to also prove the point it doesn't matter.
So if this is put in the main flash at arm address 0x08000000 then this code will boot and run up to the C code.
Note sram starts at 0x20000000 and the RM shows this part has 32MBytes of sram so it has at least 0x1000 bytes to cover this project with plenty of extra room.
8000026: 481b ldr r0, [pc, #108] ; (8000094 <notmain+0x70>)
8000028: f7ff fff8 bl 800001c <GET32>
800002c: 2101 movs r1, #1
800002e: 4301 orrs r1, r0
8000030: 4818 ldr r0, [pc, #96] ; (8000094 <notmain+0x70>)
8000032: f7ff fff1 bl 8000018 <PUT32>
...
8000094: 40021034 andmi r1, r2, r4, lsr r0
Be it as I have programmed or through your program and CMSIS or HAL headers, you should see 0x40021034 being used in some form. Note this part of yours is a cortex-m0+ so it only has a limited number of thumb2 extensions note that bl is two separate 16 instructions that can be spaced apart, but are pretty much always found as a pair, they are two instructions, the rest of the instructions need to be 16 bit, if you see something.w in the disassembly or instructions other than bl being 32 or 16*2 bits then that may be a thumb2 instruction and that won't run on this processor and may be some setting you have used when building this code, you can see with this toolchain I have specifically called out an m0 which is effectively the same as m0+ from an instruction set perspective (architecture armv6-m). You do not want armv7-m for this chip it won't work, there are about a 100 or so instructions in armv7-m that won't work on armv6-m based chips.
The orring of the bit in the io enable register should resemble a read (ldr) from 0x40021034 a modification of the value read and a write (str) to that same address.
Your code as posted would have worked on other STM32 parts as many of them initialize the MODER register (if that part uses that flavor of GPIO peripheral) to zeros for most of the pins which is input. This part documents that most of the pins reset to 0b11 which is analog mode, curious why but whatever.
Reset value:
0xEBFF FFFF for port A
0xFFFF FFFF for other ports
So you can't simply set one of the two bits to change the mode if the bits started off as 0b00 then setting one can turn it into 0b01, but for this part you can either just clear bit 11 or better control both bits and not rely on the reset state, so clear the two bits and set one of them or clear one and set the other
5<<1 means 5 shifted left one 0b101 shift a zero in from the right gives 0b1010 which is a 0xA which is 10 this is a visual way to see that I am messing with PA5 and the number 5 is there, but for this register pin 5 mode settings are bits 10 and 11. 3 << (5<<1) means 3<<10 which is bits 10 and 11. the tilde means invert the whole thing so 00000C00 is the 3<<10 invert that you get FFFFF3FF which anded with the moder value will zero bits 10 and 11. now orr with 00000400 1<<10 to set bit 10.
We want the output at least for now to be a push-pull not open drain so even though the reset value is already push-pull, I clear it for good measure. Now I normally don't bother with the pull up or other gpio setup register, I mess with these two MODER and OTYPER for the STM32 parts that use this GPIO peripheral (you will see that not all STM32 parts use the same IP, the STM32F103 uses a different one for example, check it out.
So in some way confirm that CMSIS or not that the code produced is messing with these registers. From the documentation GPIOA starts at 0x50000000. so 0x50000000 and 0x50000004 registers.
Because this part has a GPIO BSRR register its a nice feature just use it for now so that you don't accidentally mess with other pins.
The dummy loop burns time so that in this case the led blinks on and off, you have to tune the DCOUNT based on the clock used for the processor when you get this running not too fast not too slow, just right. Doing it this way with an external function it is no longer dead code ( for(ra=0;ra<DCOUNT;ra++) continue; ) the compiler is forced to build it without using a volatile request.
No the code doesn't actually hit return(0); some compilers are not that smart and complain. (some are that smart and complain that you can't get there, YMMV)
All of these pieces need to be in place to have a half a chance of this working. Its not just about a few lines of C code.
With an stlink the kiel tools are fine and I would hope there is a way to examine memory space, you will want to examine 0x08000000 and compare that to the binary generated by the tool, and hopefully there is a way to examine the output of the tool as well to see what it built, easy to do with gnu.
You can use openocd instead of kiel to load and examine things from a command line it would be something in the form
openocd -f stlink.cfg -f target.cfg
and then in another window
telnet localhost 4444
gdb adds a whole lot more unknowns...
then you can use
mdw 0x08000000 40
In the telnet window to see what is in that main flash and then compare it to the loadable portion of the binary to see if your program is really there, if your program is not actually there then no matter what you do to the C code it wont make it blink.
There are ways to use openocd to flash parts, but it is very vendor/part specific as they have to add that capability to openocd and you have to have the right version, from memory it is something along the lines of
flash write_image erase notmain.elf
if using a "binary" with address information in it, if you are using a memory image then you need to put the address on that command line 0x08000000
Some st parts come locked or let's say boards like some blue pills where this doesn't work, virgin parts I don't know that I have seen locked, you bought loose parts it appears so they should not be locked.
If you get openocd working and gnu then you could also try using sram without having to have flash support initially.
sram.s
.cpu cortex-m0
.thumb
.thumb_func
.global _start
_start:
ldr r0,=0x20001000
mov sp,r0
bl notmain
b .
.thumb_func
.globl PUT32
PUT32:
str r1,[r0]
bx lr
.thumb_func
.globl GET32
GET32:
ldr r0,[r0]
bx lr
.thumb_func
.globl dummy
dummy:
bx lr
sram.ld
MEMORY
{
ram : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > ram
.rodata : { *(.rodata*) } > ram
.bss : { *(.bss*) } > ram
}
Since this part uses a vector table and what is about to be described is using the debugger to place and run a program in sram, volatile so when you reset/reboot it is lost, but it provides a way to experiment without having to get flash writing working.
We will tell the debugger to start execution at 0x20000000 so we want there to be an instruction there not a vector table.
arm-none-eabi-as --warn --fatal-warnings -mcpu=cortex-m0 sram.s -o sram.o
arm-none-eabi-ld -o notmain.elf -T sram.ld sram.o notmain.o
arm-none-eabi-objdump -D notmain.elf > notmain.list
arm-none-eabi-objcopy notmain.elf notmain.bin -O binary
always inspect your binary on a new project before running
Disassembly of section .text:
20000000 <_start>:
20000000: 4804 ldr r0, [pc, #16] ; (20000014 <dummy+0x2>)
20000002: 4685 mov sp, r0
20000004: f000 f808 bl 20000018 <notmain>
20000008: e7fe b.n 20000008 <_start+0x8>
2000000a <PUT32>:
2000000a: 6001 str r1, [r0, #0]
2000000c: 4770 bx lr
2000000e <GET32>:
2000000e: 6800 ldr r0, [r0, #0]
20000010: 4770 bx lr
20000012 <dummy>:
20000012: 4770 bx lr
20000014: 20001000 andcs r1, r0, r0
20000018 <notmain>:
20000018: b570 push {r4, r5, r6, lr}
and that looks good.
with openocd you can now
reset halt load_image notmain.elf resume 0x20000000
To run the program (might need a path, if you run openocd in the directory where the elf file is and/or you copy the elf file to the directory where you launched openocd (not telnet, openocd) then you usually don't need to put a path.
This is in sram not flash so may run faster and may want a larger value in the delay loop.
If you simply want to make the output high or low then just use the desired bsrr line and get rid of the loops, this code as written puts you in a safe infinite loop when you return from notmain, one that will not interfere with the gpio port, as part of your investigation of the binary you are building with your tool you need to confirm that the while loop you have placed is actually not dead code and was implemented (clang has been known to dead code this so others might as well) and some sandboxes undo stuff when you return from main so it could be that your code is now fine, but exits from main and the bootstrap undoes what you did to PA5 faster than you can see it.
That's all I can do so far, I have an stm32 cortex-m0+ part with a working openocd config if that helps, this is a different part but the core is the same if there isn't another tap then it should just work but you never know.
Short answer, your moder code wouldn't have worked, otherwise it looked good, but the C code is only part of the story required for success. This long answer highlights the main points that have to be there for success in booting and setting up the led. It is possible that both of us missed an additional enable, I don't have this part specifically so I cannot actually pull one out and run this code on it.