A while ago I bought some STM32G4 nucleos, with the intent to eventually switch to them for my motor control needs. The G4 series is ST’s newest line of micros, incorporating the rich set of analog peripherals of the F3 series with the clock speed of the F4 series. The G4s have on average more memory than the F3 series, but not as much as the F4s. Here are the datasheets of the three previous micros I have used extensively and some of their features:
STM32F303K8: 72 MHz, 64 Kb flash, 16 Kb RAM, dual 5 MSPS ADCs, Ultrafast Comparators, and some op amps. Nucleo in LQFP32 package. Easy to solder!
STM32F401RE: 84 MHz, 512 Kb flash, 96 Kb RAM, single 2.4 MSPS ADC. Nucleo in LQFP64 package.
STM32F446RE: 180 MHz, 512 Kb flash, 128 Kb RAM, three 2.4 MSPS ADCs, LOTS of communication interfaces. Nucleo in LQFP64 package.
And the newcomer: STM32G431RB: 170MHz, 128 KB Flash, 32 KB RAM, dual 4 MSPS ADCs with hardware oversampling, op amps, comparators, Math Accelerator. Here is the nucleo. It is a little different than the old nucleo, most importantly it lacks the break-off programmer we have become used to seeing.
The G4 has some interesting features which are definitely worth noting:
- It has a math accelerator which consists of two modules: a filter accelerator and a CORDIC. Both are super useful for motor control. The FMAC can run FIR filters in hardware and the CORDIC can quickly calculate both a sine and a cosine quickly, at the same time, also in hardware!
- Cool timer features such as timer dithering. I haven’t looked too deeply into this but it could provide some benefits for motor control when running at low duty cycles.
- It also includes the op-amps and the comparators from the F303, which is cool. So far I haven’t used them for any motor control but I have used the comparators for a boost converter and the op amp for a school project.
- It includes some resistors on the chip to charge the VBAT battery, which is pretty cool! Useful for data loggers.
- It is available in the UFQFPN packages! These packages are both very small and surprisingly easy to solder. For my next motor controller, I will likely use the UFQFPN48 package. This will give me a bunch of extra pins to use for LEDs and overvoltage/overcurrent comparators. The UFQFPN48’s footprint of footprint 53 mm^2 is substantially smaller than the LQFP32’s 94 mm^2.
At the time of this writing, the F303K8 is $4.20, the F4446 is $7.28, and the G431CB is $4.75, all in quantity 1 on Digikey.
ONWARD- LETS TURN IT ON. Turning on this nucleo is actually quite annoying because it is not Mbed-compatible (yet). Which means that I had no idea what to do.
Bayley recommended this thing called “AC6” which I’d never heard of before. But apparently it is a thing that you can download from the interwebs, so I did. I also downloaded “STM32CubeIDE” as a backup. At the time all I knew about these programs was that they were compilers, but just also somehow had loads and loads of additional buttons as well for no apparent reason. So I plugged in my nucleo and loaded up an example program in AC6, and immediately just started getting random, weird errors:
????? I’m not even programming in java??? I got similarly cryptic errors in STM32CubeIDE. Eventually though I was able to make Cube work about half of the time, well enough to write some code. I was able to eventually was able to get the LED to blink using STM32CubeIDE in ‘Debug’ mode:
After enough head banging and trying random stuff I figured out that the problems with AC6 were coming from the launcher (whatever that is? who knows). It was building OK but not copying that onto the nucleo for whatever reason. I also figured out that I needed to make the IDE spit out a .bin file instead of the .elf file. I then could manually drag and drop said .bin onto the nucleo. I made a crappy “turn on the LED with registers” program andddd……
Nothing happened!
Back to CubeIDE… This is the classic Austin strategy for making firmware stuff work: try two approaches with different programs and just yell at try both alternately until one works. I loaded up the example program into CubeIDE and saw how they were configuring things, and selectively copied into AC6. Eventually I figured out some real dumb things.
1. the GPIO MODER registers initialize to all ones by default, not all zeros. So when I was trying to write them into 01 by just |= 0b01 they just stayed as 11 and didn’t work.
2. the line “RCC->AHB2ENR |= RCC_AHB2ENR_GPIOAEN” must be called twice. I think it actually just needs to be called once and then wait for one instruction but calling it twice just works.
And with that things worked!!! Yay. The LED turned on!
Next up was the clock configuration. There are a few settings which must be diddled before the clock speed can be increased. Here is my function:
FLASH->ACR |= FLASH_ACR_LATENCY_8WS; // adjust number of wait states to 8
RCC->CFGR |= RCC_CFGR_HPRE_3; // divide sysclk by 2 with AHB prescaler- for some reason this is necessary (page 191)
PWR->CR5 &= ~PWR_CR5_R1MODE; // clear R1MODE
for (int i = 0; i < 20; i++) {GPIOA->ODR = 0x00;} // wait
RCC->CFGR &= ~RCC_CFGR_HPRE_3; // reset sysclk prescaler to 1
for (int i = 0; i < 20; i++) {GPIOA->ODR = 0x00;} // wait
Then the PLL can be configured to use the HSE and run the core at that juicy 170 MHz:
// ======= configure PLL =========
RCC->CR |= RCC_CR_HSEON; // turn on HSE
while (((RCC->CR)&RCC_CR_HSERDY)==0) {} //wait til HSE is ready
RCC->PLLCFGR |= RCC_PLLCFGR_PLLSRC_0 | RCC_PLLCFGR_PLLSRC_1; // PLL source = HSE
RCC->PLLCFGR |= RCC_PLLCFGR_PLLM_0 | RCC_PLLCFGR_PLLM_2; // PLL input div (M) = 6
RCC->PLLCFGR |= 85<<RCC_PLLCFGR_PLLN_Pos;
RCC->PLLCFGR |= RCC_PLLCFGR_PLLREN;
RCC->PLLCFGR |= RCC_PLLCFGR_PLLPDIV_1;
RCC->CR |= RCC_CR_PLLON; // turn on PLL
while (((RCC->CR)&RCC_CR_PLLRDY)==0) {} //wait til PLL is ready
for (int i = 0; i < 20; i++) {GPIOA->ODR = 0x00;} // wait
RCC->CFGR |= RCC_CFGR_SW_0 | RCC_CFGR_SW_1; // use PLL as sysclk
for (int i = 0; i < 20; i++) {GPIOA->ODR = 0x00;} // wait
All this had to be done with zero debugging at all because I am too stupid to figure out how to use the debugger. But luckily it worked on the first try. I checked this with the MCO output set to SYSCLK/16, by some miracle it output the expected 10.6 MHz. I’m not sure if the “wait by doing 20 GPIOA writes” are necessary but they can’t hurt.
I set up the serial which worked after some head scratching. Initially I set up the wrong serial but eventually found the right one which for some reason is the LPUART:
I tried to get printf working but it seemed very confusing. I decided to ignore it for now.
Next up was turning on TIM1 which was not too bad luckily:
Next up was turning on the FPU. For some reason this turned out to be unnecessarily hard. I spent about an afternoon/night yelling at the compiler calmly browsing the interwebs to find the problem. It turned out that I was missing a bunch of .c files and those were necessary to turn on the FPU. I am also to stupid to figure out how to include files properly, so I copied the entire pile of files straight into the src folder and that solved the problem!
With the proper .c files included I tested by making some random ramps and exponentials out the DAC. Turns out including the proper files makes it work.
Next up was doing something incredibly dumb, which I’ve wanted to do for a while: hardware in the loop boost converter simulator. You have one Nucleo think its driving a boost converter, and then you have another Nucleo pretending to be a boost converter. This enables testing without the fear of blowing up expensive hardware.
I’ve wanted to make a boost converter for a while, for charging my electric bike. It is hard to find a 160V charger lying around. A battery charger is just a power supply, which consists of two stages: an isolation stage and a CC/CV stage. The isolation stage is a pain to build because it requires weird optoisolators and stuff like that, and it is actually quite easy to find in the form of a fixed voltage output supply. I plan to just make the CC/CV stage, in the form of a boost converter. You could also have a power supply which outputs 300V followed by a buck converter, but I opted to go the boost route mostly because I found a 48 volt, 500W power supply lying around.
I also decided to include a bonus diode in my battery charger. This eliminates the problem of needing to precharge the charger, as it can be left outputting 0 volts until the battery is plugged in.
After some code writing, it worked! Yellow is simulated input gate drive signal, blue is simulated inductor current, and purple is simulators output capacitor voltage, which ripples due to simulated battery resistance. I was able to get the simulation running at 500 kHz.
TLDR: G4s are really cool- expect to see them on my next version of motor controllers. Stay tuned!