1

I'm attempting to learn embedded development, and I'm currently playing around with a STM32F407G board.

So far, I've been able to toggle the LEDs based on the User button press using the high level driver APIs provided by the CubeMX.

However, I now want to recreate the same functionality without any API help. Instead, using the base addresses and registers provided in the reference manual, I want to basically recreate the APIs.

Thus far, I've disabled all the peripherals using the GUI:

enter image description here

But I feel like there's a better way to do this. I'm not entirely sure what peripherals I definitely need to even debug the code on the board.

Essentially, I want enough start-up code so that I'm able to load (flash?) the code into the microcontroller, and debug main(). Everything else (such as toggling the LEDs, detecting the User button interrupt, etc) will be something I want to take care of.

halfer
  • 19,824
  • 17
  • 99
  • 186
gjvatsalya
  • 1,129
  • 13
  • 29
  • Jtag debugger. No hardware debugger and you will struggle. – Martin James May 15 '18 at 18:04
  • IMO it is easier to do it yourself than use the libraries. ST docs are pretty good, there are examples on stackoverflow and various ones on the net if you look. – old_timer May 15 '18 at 19:33
  • actually I have posted blinker examples for this discovery board more than once. If you want to do the blinking yourself that is an even easier example. – old_timer May 15 '18 at 19:34
  • I'm not sure how this question is too broad. I need help with CubeMX for the STM32F407G board. Perhaps I don't understand the embedded world to get into more specifics. – gjvatsalya May 15 '18 at 20:38
  • This question was good enough to attract two adequate answers so far, and I could write a third one, addressing a few points that both answers have missed. Nominated for reopen. – followed Monica to Codidact May 16 '18 at 08:05
  • The answer is then _not_ to use CubeMX or the associated HAL library. You can just use `stm32f4xx.h` file, or one level above that, ST's standard peripheral library, which is similar but not the same as the CubeMX HAL layer, and not so actively maintained. I use the CubeMX tool for its useful PLL clock tree calculator, and little else. – Clifford May 16 '18 at 12:07

2 Answers2

3

You do not need to recreate the APIs. Jest program using the registers. I do it all the time in almost all of my projects (unless using some kind of HAL is my client requirement)

You need to have:

  1. startup code with the vector table.
  2. CMSIS headers for the convenience.

How to archive it using the CubeMx. It is actually quite easy.

  1. Create the project
  2. Import to your favorite IDE.
  3. In the project options delete the USE_HAL_DRIVER definition
  4. Exclude from the build (or delete) all the files from the /Driver/STMxxxxx_HAL_Driver
  5. Delete everything from the main.c file

Add:

#include "stm32f4xx.h" // CMSIS headers

int main(void)
{
}

and enjoy :)

0___________
  • 60,014
  • 4
  • 34
  • 74
  • Any comments if you do no like my answer. – 0___________ May 15 '18 at 16:55
  • Thanks for the help! I'll definitely try this out. Do you recommend that I initialize the peripherals with their default mode when starting a project in CubeMX? Does it matter? – gjvatsalya May 15 '18 at 17:03
  • Also - I did not downvote you btw. Another question I do have is why you don't use the HAL? Is it not good practice? – gjvatsalya May 15 '18 at 17:04
  • I prefer the hard way. Nothing about good or bad practice. – 0___________ May 15 '18 at 17:09
  • 2
    @gjvatsalya There's no universal answer regarding HAL. All answers you're going to get are going to be opinion based and it all depends on who you ask. Some prefer playing with registers, some prefer calling high level functions. Neither approach is universally perfect. If I was to answer that, I'd say HAL is good for certain tasks - like prototyping something very quickly - and it's better the more common your problem is. It's quite good when it comes to peripheral initialization. However if your problem is very specific, you may end up writing your own piece of low level code. – J_S May 15 '18 at 17:27
  • @JacekŚlimok That makes sense. Thanks! – gjvatsalya May 15 '18 at 18:25
  • @PeterJ_01 I do have more questions if you don't mind: 1) I'm not sure what you mean by delete the USE_HAL_DRIVER definition. 2) Do you configure configure IRQHandlers through CubeMX as well? Curious about how you deal with adding more handlers once a project gets bigger. 3) Do we still need files other than main.c in the src/ dir (such as usb_host.c, usbh_conf.c, stm32f4xx_hal_msp.c, etc)? – gjvatsalya May 15 '18 at 18:28
3

flash.s

.cpu cortex-m4
.thumb

.thumb_func
.global _start
_start:
stacktop: .word 0x20001000
.word reset
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang

.thumb_func
reset:
    bl notmain
    b hang
.thumb_func
hang:   b .

.align

.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

so.c

void PUT32 ( unsigned int, unsigned int );
unsigned int GET32 ( unsigned int );
void dummy ( unsigned int );

int notmain ( void )
{
    unsigned int ra;

    for(ra=0;ra<1000;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 (dont need the none-eabi, with this code you can use the arm-linux-gnueabi or whichever arm-whatever-gcc/as/ld you want (within reason))

arm-none-eabi-gcc -Wall -Werror -O2 -nostdlib -nostartfiles -ffreestanding  -mthumb -mcpu=cortex-m4 -c so.c -o so.o
arm-none-eabi-ld -o so.elf -T flash.ld flash.o so.o
arm-none-eabi-objdump -D so.elf > so.list
arm-none-eabi-objcopy so.elf so.bin -O binary

And from notes I took a while back you can use dfu-util to write your binary

dfu-util -d 0483:df11 -c 1 -i 0 -a 0 -s 0x08000000 -D myprogram.bin

The above does nothing much, but is a framework for you to add the enable of the gpio block, make the led pin an output, then turn it off and on with some code to kill time in a loop. and/or poll one gpio pin and drive another to match (use the button to light or turn off an led).

openocd connects up fine to this board/family with stlink...

you wont need interrupts or clock configs at first (if ever).

Ahh, right...After building no matter what path you take examine the binary and make sure it has a chance of working.

08000000 <_start>:
 8000000:   20001000    andcs   r1, r0, r0
 8000004:   08000041    stmdaeq r0, {r0, r6}
 8000008:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 800000c:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 8000010:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 8000014:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 8000018:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 800001c:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 8000020:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 8000024:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 8000028:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 800002c:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 8000030:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 8000034:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 8000038:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 800003c:   08000047    stmdaeq r0, {r0, r1, r2, r6}

08000040 <reset>:
 8000040:   f000 f808   bl  8000054 <notmain>
 8000044:   e7ff        b.n 8000046 <hang>

08000046 <hang>:
 8000046:   e7fe        b.n 8000046 <hang>

vector table up front with the stack pointer and thats fine, dont have to use it, ignore the stmdaeq disassembly it is just trying to disassemble the vector entries because I used objdump to examine the binary. the vector table needs odd numbers the address of the entry ORRED with one. Technically if small enough you can use 0x00000000 on these parts as that is really where it will be mapped when it boots this code, but because it is also mapped at 0x08000000, full amount of flash for these ST parts you will typically see it done like this. if you switch to another cortex-m based part from another family (NXP, Atmel/Microchip, etc) then you may need to use another address be it 0x00000000 or some other that that part family uses.

if you dont see the beginning of the binary looking like this with the stack pointer init value and the vector table, then you are not likely to have much luck with booting...no matter what library/software path you take.

Note the correct answer was given in a comment to this question. It is primarily opinion based. there are multiple library solutions and over time vendors will keep changing them for various (generally non-technical) reaasons. Professionally you should be able to go down either path, truly bare metal or some sandbox or somewhere in the middle. If you dont write all of the code and use someone elses you are still responsible for that project, so you should spend the time to dig into it and check the quality and accuracy of that code. You should be surprised by what you find, you own it you should fix it if you dont like it and/or replace it.

Neither path is automatically simpler nor faster nor more reliable. There is no right answer to this so you need to be flexible.

Both documents and library code are buggy, expect this, expect to deal with this.

old_timer
  • 69,149
  • 8
  • 89
  • 168
  • This is wonderful. Thank you so much. I'm not going to lie, some of the things you talked about are going over my head. But I'll read up on it to understand better. Once again, thanks for taking the time to write this out! – gjvatsalya May 15 '18 at 20:37
  • it is a bit too minimalistic - no .data and .bss sections and init code for them. No initialized or zeroed static storage variables. Not C anymore. – 0___________ May 15 '18 at 22:36
  • This is bare metal, no operating system, C falls apart at this level. The OP asked for something to start with, this is something to start with. There is .bss, .data is not required, nor is zeroing .bss, those are features that can be added if needed, are not needed for this kind of work but can be as desired...Definitely not needed to blink an led and watch a gpio input. – old_timer May 16 '18 at 00:35
  • But the C language used require it. As I understand he wants to use the C language. – 0___________ May 16 '18 at 11:11
  • so he wants printf (which by itself will consume all of the flash), open, fopen, close, fscanf, fgets, etc, etc, etc, etc...You do not need .bss nor .data in order to use the C language to blink an led on this board, in fact you can pretty much do anything from blinking leds to very large scale projects without .bss init and .data copied. It is personal choice. Makes baremetal much easier. I believe that I have seen the compilers starting to warn if you use a bss variable before writing it, which is a very good warning to have you should never read before you write memory. – old_timer May 16 '18 at 14:05
  • To blink an led without the hal, but not to give the whole answer as to how to do it was also a stated goal for this question. No need to create pain with a tool specific complicated linker script plus boot strap that is married to the linker script and thus married to the toolchain, when it is not needed for this task. Less is more. – old_timer May 16 '18 at 14:07
  • And yes, certainly, if you want to support more/most of the language you need to allow for globals and initialize those variables before the "C" code executes (in the bootstrap). Very many examples of that out there for each toolchain. And if you want the C library calls there are examples of that out there if you can fit it in an MCU and build enough of an OS like thing to support it. but you learn pretty much nothing by just taking that code and using it, might as well just write a command line app on your laptop and call it a day. – old_timer May 16 '18 at 14:09