January 13, 2024

STM32CubeMX - my "blink"

Now that I have the business of using make with the code generated by CubeMX, it should be easy to put together my own LED blink demo, or a simple "Hello" demo using the serial port. And it is! The problem is that they don't work.

Code layout

There is no telling why they do things this way, but the layout is pretty much like this:
CM4 - code for the Cortex M4
CM7 - code for the Cortex M7
Drivers - all the HAL stuff
Makefile - the make file and build area
Common - one file (dualcore_boot)
A quick note on "Drivers". I told CubeMX to only copy the drivers I actually need, but that ends up being quite a lot of code:
Src - 86,000 lines of code in 42 files
Inc - 107,000 lines of code in 63 files
That is 193,000 lines of code by my count, almost 200,000 lines of code. Supposedly only what I need, but I see things like usb, ethernet, i2c and plenty of other things that I am not directly using.

I am just ignoring the CM4 directories (for now). The CM7 directory looks like this:

Core/Inc - include files
Core/Src - the all important main.c and some other files
The Makefile directory looks like:
Makefile
CM4
CM7
Inside of "CM7" we have:
Makefile
build
xxx.ld
xxx.s
I find this layout clumsy and stupid. Why not copy all the files from Core/Src and Core/Inc into the Makefile/CM7 directory? It has always been a symptom of insanity to separate Src and Inc in this way. And why have the startup.s file here and main.c over there? Why force the user to jump back and forth. Perhaps I will fix this when I get fed up enough with it.

Code for my demos

For ether case, I just add a few lines of code to main.c in ~/CubeMX/First1/CM7/Core/Src/main.c

For the blink demo, I put these lines into the final "while" loop:

#define LED_PIN  GPIO_PIN_15
        HAL_GPIO_WritePin (GPIOI, LED_PIN, GPIO_PIN_RESET);
        HAL_Delay(200);
        HAL_GPIO_WritePin (GPIOI, LED_PIN, GPIO_PIN_SET);
        HAL_Delay(200);
For the serial demo, I put these lines instead into that final "while" loop:
    // This goes outside of main()
    unsigned char howdy[] = "Hello Cowboy!\n\r";

    // This goes inside the while loop.
    HAL_UART_Transmit ( &huart1, howdy, sizeof(howdy), 10);
    HAL_Delay(1000);

A first pass at debugging

I rebuild the blink demo. Then I load it into the board using my "flashit" script which is just this:
openocd -f /usr/share/openocd/scripts/board/stm32h747i-disco.cfg -c "program FirstDefaults_CM7.elf verify reset" -c "exit"
I start up openocd, then "gdb -tui" and it tells me that the code is in the function "Error_Handler()" in main.c where there is a endless while loop. This certainly explains why I am getting no blinking, but what could the error be? There are 5 calls to Error_Handler(). I change Error_Handler to accept an integer argument, which is copies to a int variable "error_why". I have to go find main.h and change a prototype there, then build again. I am already irritated with their directory scheme.

I make "Error_Handler" static to smoke out other calls, and indeed, there are also calls from usart.c. Back we go, jumping between the two source directories, the make directory, and the build directory. Is this stupid or what? There are 5 calls to Error_handler in usart.c.

I finally get a clean build after jumping between directories about 500 times, then I load the new code, and use gdb to examine "error_why". It is set to 1. What we see here is that it is waiting for CPU2 to boot (wake up from stop mode). This is never going to happen given that I never load code into the other core, nor are the option bytes set to try to run it. So I comment out this business and see what happens next. Actually there are two such sections with timeouts and I need to comment them both out.

Now it is still not blinking, but it is not calling Error_Handler(). It seems to be forever in the routine HAL_GetTick() in stm32h7xx_hal.c. This is a weak function that should perhaps be overwritten by something else (perhaps timer related)? A backtrace shows this being called by HAL_Delay().

I replace HAL_Delay() with my own simple busy loop delay routine. Still no blinking. gdb shows the code inside of my delay function, which is what you would expect.

This is enough for one night I guess. I cannot say that using STM32CubeMX is making my code development fast nor easy, as it claimed in the advertisement -- but I am well accustomed to being lied to in such a way. The more it told me such things, the less I believed it.

The serial demo works now !!!

With my own delay function and the synchronization with the M4 processor bypassed, my serial demo now works just fine! I use "picocom /dev/ttyACM0" and I see:
Hello Cowboy!
Hello Cowboy!
Hello Cowboy!
...
And my delay seems to be about 1 second. So the question(s) now is/are why I cannot blink an LED via GPIO-I and why the HAL_Delay() function does not work.

Dump the GPIO registers

This is fast and easy to do using GDB. I see the first GPIO register (the mode register) for GPIO-I set to 0xffffffff. This is the reset value and sets the pin to "analog mode" which certainly won't work to drive an LED> I look at GPIO-A and see the first register set to 0xabeaffff. The first 4 registers for GPIO-A are:
0xabeaffff  0  0x0c000000  0x64000000
We may have some byte order issues here. Lets ignore that for now. All 32 bits in this register do something. abea = 1010_1011_1110_1010.
Here:
00 = input
10 = alt function
01 = output (never seen)
11 = analog
Whatever the case, it looks like GPIO-A has been initialized with several bits being configured for alternate functions. GPIO-I does not look like it has been initialized. PA9 and PA10 are Rx and Tx for UART1 and these do seem to be set to alternate function. (Note that PA8, PA13, PA14, and PA15 also have been set to alternate function.

PA8 is MCO1.

The question is how to get CubeMX to initialize port I for us.


Feedback? Questions? Drop me a line!

Tom's Computer Info / tom@mmto.org