PlatformIO Community

Build GD32 project with Platformio

Hi,

Due to flexibility and ease, I would like to build and flash my GD32F130C6T6 project with Platformio. I tryied using a genericSTM32F103C8 with Flash size and Ram size updated to match the one of GD32F130C6T6, but did not work. It did compile and flash, however it doesn’t run.
With Keil uVision the firmware runs without problems.

What should I do, any suggestions?
Thanks in advance!

According to the linked datasheet,

your GD32F130C6T6 has 32KB Flash and 4KB SRAM, while a STM32F103C8 has 64k Flash 20k RAM , so that’s quiet different… But if the firmware builds for the GD32 device, then you couldn’t have gone over 4KB anyways. Also the STM32 is operating at 72MHz vs your 48MHz.

I don’t think these chips are interchangable, especially since they have different memory maps for the peripherals and a changed inner peripheral design. Check e.g. the name of the “RCC” and “RCU” or the positions of PORT A,B,…E peripherals in STM32F103 vs GD32F103. Or do you have a reference which says otherwise regarding compatibility?

grafik
STM32 vs…

grafik

GD32F

Interestingly this says it’s “STM32F030C8 compatible”, but that chip has a Cortex M0 core and not an M3…

I’ll check out if I can add proper GD32F1xx support to PIO in a sensible and quick way.

You probably confused the GD32F103C8T6 (64KB Flash, 20KB SRAM, like the STM32F103C8T6 and probably compatible?) with the GD32F130C6T6, your microcontroller.

Could you please test out this simple Blink project I’ve ported for your microcontroller.

It should toggle the PA0 every second. Instructions regarding the modified SPL framework are also in the repo.

3 Likes

First of all… let me say I am impressed… with the speed and clarity.
Now,
The positive:

  • I managed to build and flash your repo Blink example via Platfomio. No issues there.
  • The LED lights up

The negative

  • It doesn’t blink. It stays on all the time.

I tried few more things to see what’s going on:

  • if I use delay_1ms(), it feels like is getting stuck in it, I guess that the interrupt SysTick_Handler() doesn’t trigger
  • if I remove the delay_1ms() and use the Photo switch to turn on/off the LED, it feels like the while(1) is only executing once, because the Photo switch is not read (see code below).

I must say, I have similar behavior if I build with Makefile under Ubuntu. So, I think the issue is somewhere in the Startup file .s or the Linker script .ld. I created both based on this github project (because GD library does not provide them).

The guys from GD, provide only this startup file to be used in Keil. It has a different format than the one gcc needs. And this is the one I use in Keil which works (of course together with the Scatter file in Keil instead of .ld).

Can you have a look into the startup and Linker script files? I think the issue is there and I feel we are very close to the solution.

This is my modified main.c

#include "gd32f1x0.h"
#include "gd32f1x0_libopt.h"
#include "systick.h"
#include <stdio.h>

#define LED1_PIN                         GPIO_PIN_0
#define LED1_GPIO_PORT                   GPIOA
#define LED1_GPIO_CLK                    RCU_GPIOA

#define LED2_GPIO_PORT					GPIOB
#define LED2_PIN 						GPIO_PIN_9
#define LED2_GPIO_CLK 					RCU_GPIOB

#define SENSOR1_GPIO_Port 			    GPIOA
#define SENSOR1_Pin 					GPIO_PIN_4

void init_led() {
    /* enable the led clock */
    rcu_periph_clock_enable(LED1_GPIO_CLK);
    /* configure led GPIO port */
    gpio_mode_set(LED1_GPIO_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, LED1_PIN);
    gpio_output_options_set(LED1_GPIO_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,LED1_PIN);
    GPIO_BC(LED1_GPIO_PORT) = LED1_PIN; /* clear port / set to 0*/

    rcu_periph_clock_enable(LED2_GPIO_CLK);
    gpio_mode_set(LED2_GPIO_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, LED2_PIN);
    gpio_output_options_set(LED2_GPIO_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,LED2_PIN);
    GPIO_BC(LED2_GPIO_PORT) = LED2_PIN; /* clear port / set to 0*/

   	rcu_periph_clock_enable(RCU_GPIOA);
	/* configure GPIO port */
	gpio_mode_set(SENSOR1_GPIO_Port, GPIO_MODE_INPUT, GPIO_PUPD_NONE, SENSOR1_Pin);

}

void toggle_led() {
	GPIO_OCTL(LED1_GPIO_PORT) ^= LED1_PIN;
}

int main(void)
{
    systick_config();
    init_led();
    
    while(1){
    	toggle_led();
    	//delay_1ms(1000);
		if (gpio_input_bit_get(SENSOR1_GPIO_Port, SENSOR1_Pin)) {
	        gpio_bit_set(LED2_GPIO_PORT, LED2_PIN);	
		} else {
            gpio_bit_reset(LED2_GPIO_PORT, LED2_PIN);			
		}
    }
}
1 Like

One partial success. I think I know where the problem lies. The guys from GigaDevice wrote the systick.c implementation in such a way that the delay variable (which is decremented on every systick) is not marked as volatile. Thus the compiler may have been smart and optimized away reading this variable every time.

I have added commit https://github.com/maxgerhardt/pio-gd32f130c6/commit/0185c669f345a7b7866026e760e0aeeb58e67494. Can you recheck this?

There are still loads of other factors involved, like wrong SysTick interrupt configuration related to the handler function in the startup file or gd32f1x0_it.c, out-of-spec chip frequency (72MHz?!) or wrong ISR definitions / order / linking, stack setup failure, …

Do you have STLink for flashing at hand? We might have to play around a bit but maybe a live-debug in VSCode via openocd would be good (if there is configuration file for the GD32F130C6 or a compatible one… maybe steal an from F1/F3/L1 Cortex M3 chip…)

1 Like

I am amazed! It’s working!! Blinks every second.
In my Keil project the ‘delay’ is volatile. So, what magic did you do to get all working? :slight_smile: And how do you suggest to go with this solution? Will this board be added as supported board, or I do the approach you did now?

For some reason I still cannot read the SENSOR1, I am not sure why… because I configured it as in keil. If not I will check later.

1 Like

Ha :sunglasses:.

I copied the example firmware based on GD32F1x0_Firmware_Library_v3.1.0\Examples\GPIO\Running_led and there in systick.c it’s not volatile :frowning:.

The example GPIO code also activates the comparator clock… Can you try this main.c?

#include "gd32f1x0.h"
#include "gd32f1x0_libopt.h"
#include "systick.h"
#include <stdio.h>

#define LED1_PIN                         GPIO_PIN_0
#define LED1_GPIO_PORT                   GPIOA
#define LED1_GPIO_CLK                    RCU_GPIOA

#define LED2_GPIO_PORT					GPIOB
#define LED2_PIN 						GPIO_PIN_9
#define LED2_GPIO_CLK 					RCU_GPIOB

#define SENSOR1_GPIO_Port 			    GPIOA
#define SENSOR1_Pin 					GPIO_PIN_4

void init_led() {
	/* enable the led clock */
	rcu_periph_clock_enable(LED1_GPIO_CLK);
	/* configure led GPIO port */
	gpio_mode_set(LED1_GPIO_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, LED1_PIN);
	gpio_output_options_set(LED1_GPIO_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,
			LED1_PIN);
	GPIO_BC(LED1_GPIO_PORT) = LED1_PIN; /* clear port / set to 0*/

	rcu_periph_clock_enable(LED2_GPIO_CLK);
	gpio_mode_set(LED2_GPIO_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, LED2_PIN);
	gpio_output_options_set(LED2_GPIO_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,
			LED2_PIN);
	GPIO_BC(LED2_GPIO_PORT) = LED2_PIN; /* clear port / set to 0*/

	//GPIOA already turned on for this sensor pin
	//rcu_periph_clock_enable(RCU_GPIOA);
	/* configure GPIO port */
    rcu_periph_clock_enable(RCU_CFGCMP);
	gpio_mode_set(SENSOR1_GPIO_Port, GPIO_MODE_INPUT, GPIO_PUPD_NONE,
			SENSOR1_Pin);

}

void toggle_led() {
	GPIO_OCTL(LED1_GPIO_PORT) ^= LED1_PIN;
}

int main(void) {
	systick_config();
	init_led();

	while (1) {
		//toggle_led();
		//delay_1ms(1000);
		if (gpio_input_bit_get(SENSOR1_GPIO_Port, SENSOR1_Pin) == RESET) {
			gpio_bit_set(LED2_GPIO_PORT, LED2_PIN);
		} else {
			gpio_bit_reset(LED2_GPIO_PORT, LED2_PIN);
		}
	}
	return 0; //never reached but for safety
}

I’ll submit this as pull request to the STM32 platform repository (yeah probably not fitting) for discussion and integration in the mainline.

We shouldn’t be too far away from getting your whole target project to compile (after we understand why input is not working) so that’s a good function test. (I don’t have the hardware / chip here)

1 Like

LED2 remains on at all times now and not responding still… hmm
I will for sure try to compile everything… I think tomorrow.

And you are right, in the library delay was not set as volatile, I must have taken the volatile from the Demo suite

1 Like

Alright there are still some unexplored paths:

  • measuring the voltage at pin PA4 to make sure it’s GND or 3.3V
  • again compiler optimizations screwing up the setting / getting of GPIO registers
  • wrong / missing init code…

An additional test would be the UART code so that we can printf() stuff.

1 Like

I agree. Tomorrow I will set-up the UART with printf to debug in more detail. I am constrained by the time and I need to stop. However, it was a very good input from you and I would like to thank you.
I will come back to you tomorrow.

Maybe you show me the “Live debug” in VScode, never used it :slight_smile:

1 Like

No problem and I’m keen to see how this goes :slight_smile:

I’ve added UART init and test code in the firmware per the latest commit. It goes to the PA2/3 debug header on the board at 115200 baud.

1 Like

Max, I am coming back. All works fine… I did the most stupid mistake ever, I used a piece of paper to cover the photo sensor and apparently the paper was too transparent and the sensor was still conducting. Folding the paper did fix it. Both version of code worked, your last one and my original one.

Tomorrow I will compile full project in Platformio and see how can I make all the steps in a nice easy way, to be used by others too.

I mark it as solutioned as soon as I have the full project test.

2 Likes

I did a quick Compilation build of the full project.
Using VARIANT_HOVERBOARD (doesn’t uses printf) it compiles:

Processing GD32F130C6T6 (platform: ststm32; board: gd32f130c6; framework: spl)
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Verbose mode can be enabled via `-v, --verbose` option
CONFIGURATION: https://docs.platformio.org/page/boards/ststm32/gd32f130c6.html
PLATFORM: ST STM32 6.0.0 > Generic GD32F130C6T6
HARDWARE: GD32F130C6T6 72MHz, 4KB RAM, 32KB Flash
DEBUG: Current (stlink) On-board (stlink) External (blackmagic, jlink)
PACKAGES: toolchain-gccarmnoneeabi 1.70201.0 (7.2.1), framework-spl 2.10201.0 (1.2.1)
Warning! Cannot find a linker script for the required board! Firmware will be linked with a default linker script!
LDF: Library Dependency Finder -> http://bit.ly/configure-pio-ldf
LDF Modes: Finder ~ chain, Compatibility ~ soft
Found 0 compatible libraries
Scanning dependencies...
No dependencies
Building in release mode
Compiling .pio\build\GD32F130C6T6\src\util.o
Linking .pio\build\GD32F130C6T6\firmware.elf
Checking size .pio\build\GD32F130C6T6\firmware.elf
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
DATA:    [===       ]  32.5% (used 1332 bytes from 4096 bytes)
PROGRAM: [========= ]  85.3% (used 27944 bytes from 32768 bytes)
====================================================================== [SUCCESS] Took 1.26 seconds ======================================================================

Using the VARIANT_DEBUG (which uses the printf) I get overflow memory error.

Processing GD32F130C6T6 (platform: ststm32; board: gd32f130c6; framework: spl)
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Verbose mode can be enabled via `-v, --verbose` option
CONFIGURATION: https://docs.platformio.org/page/boards/ststm32/gd32f130c6.html
PLATFORM: ST STM32 6.0.0 > Generic GD32F130C6T6
HARDWARE: GD32F130C6T6 72MHz, 4KB RAM, 32KB Flash
DEBUG: Current (stlink) On-board (stlink) External (blackmagic, jlink)
PACKAGES: toolchain-gccarmnoneeabi 1.70201.0 (7.2.1), framework-spl 2.10201.0 (1.2.1)
Warning! Cannot find a linker script for the required board! Firmware will be linked with a default linker script!
LDF: Library Dependency Finder -> http://bit.ly/configure-pio-ldf
LDF Modes: Finder ~ chain, Compatibility ~ soft
Found 0 compatible libraries
Scanning dependencies...
No dependencies
Building in release mode
Linking .pio\build\GD32F130C6T6\firmware.elf
c:/users/37ef/.platformio/packages/toolchain-gccarmnoneeabi/bin/../lib/gcc/arm-none-eabi/7.2.1/../../../../arm-none-eabi/bin/ld.exe: .pio\build\GD32F130C6T6\firmware.elf section `.text' will not fit in region `FLASH'
c:/users/37ef/.platformio/packages/toolchain-gccarmnoneeabi/bin/../lib/gcc/arm-none-eabi/7.2.1/../../../../arm-none-eabi/bin/ld.exe: .pio\build\GD32F130C6T6\firmware.elf section `._user_heap_stack' will not fit in region `RAM'
c:/users/37ef/.platformio/packages/toolchain-gccarmnoneeabi/bin/../lib/gcc/arm-none-eabi/7.2.1/../../../../arm-none-eabi/bin/ld.exe: region `RAM' overflowed by 216 bytes
c:/users/37ef/.platformio/packages/toolchain-gccarmnoneeabi/bin/../lib/gcc/arm-none-eabi/7.2.1/../../../../arm-none-eabi/bin/ld.exe: region `FLASH' overflowed by 15868 bytes
collect2.exe: error: ld returned 1 exit status
*** [.pio\build\GD32F130C6T6\firmware.elf] Error 1
====================================================================== [FAILED] Took 1.11 seconds ======================================================================

I am quite sure there is an issue with my printf() function in util.c

void consoleLog(char *message)
{
  #ifdef SERIAL_DEBUG
		log_i("%s", message);
  #endif
}


/* retarget the C library printf function to the USART */
int fputc(int ch, FILE *f)
{
	usart_data_transmit(USART_MAIN, (uint8_t)ch);
	while(RESET == usart_flag_get(USART_MAIN, USART_FLAG_TBE));
	return ch;
}

Where log_i is the printf() given in defines.h as

#define log_i       	printf	// redirect the log_i debug function to printf

I see in your example you did the printf() re-targeting differently. Is it due to gcc? I still have to try that.

That’s great :slight_smile:. Does it also run properly?

Indeed this is what I read for GCC + newlib (C library): https://electronics.stackexchange.com/a/279945/158783

The printf() of the C library in GCC is huge. Keil seems to do a better job here. Does adding --specs=nano.specs to the build_flags of the platformio.ini help?

Adding a more leightweight printf() alternative like here or this one could also be an option.

On the other hand, you can quickly see if the UART, a simple print string and printf() works with my minimal firmware linked above.

Oh if that’s the only part where printf() is used you can also send the string directly with the usart_data_transmit() function byte-wise, and no bloated printf() with format specifiers is needed. My firmware implements such a print_str() method. That would make the printing code as slim as possible.

  • In a few hours I will have the HW with me and I will test the VARIANT_HOVERBOARD.
  • Regarding the printf() or log_i() I use it in many places, with specifiers and float (legacy code from Invense on MPU6050). I am a bit frustrated why this function takes so much space :frowning: . I noticed also on my STM32 version… when I enable the float support it added another 10 kB, luckily the STM32 version has 64 kB Flash.
  • -specs=nano.specs gives the same overflow size with or without. I have it anyways in my platformio.ini as you have it in your example.
  • I have to see if I can work around printf()…
  • EDIT1: Will it be a solution to use sprintf() with a buffer to construct my string with specifiers and then print it on serial or is the same impact on memory as with printf()?
  • EDIT2: Doing this to enable nanolib produces this result:
Processing GD32F130C6T6 (platform: ststm32; board: gd32f130c6; framework: spl)
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Verbose mode can be enabled via `-v, --verbose` option
CONFIGURATION: https://docs.platformio.org/page/boards/ststm32/gd32f130c6.html
PLATFORM: ST STM32 6.0.0 > Generic GD32F130C6T6
HARDWARE: GD32F130C6T6 72MHz, 4KB RAM, 32KB Flash
DEBUG: Current (stlink) On-board (stlink) External (blackmagic, jlink)
PACKAGES: toolchain-gccarmnoneeabi 1.70201.0 (7.2.1), framework-spl 2.10201.0 (1.2.1)
Warning! Cannot find a linker script for the required board! Firmware will be linked with a default linker script!
LDF: Library Dependency Finder -> http://bit.ly/configure-pio-ldf
LDF Modes: Finder ~ chain, Compatibility ~ soft
Found 0 compatible libraries
Scanning dependencies...
No dependencies
Building in release mode
Checking size .pio\build\GD32F130C6T6\firmware.elf
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
DATA:    [=         ]   7.8% (used 320 bytes from 4096 bytes)
PROGRAM: [========= ]  93.4% (used 30616 bytes from 32768 bytes)
====================================================================== [SUCCESS] Took 1.16 seconds ======================================================================

eheh :slight_smile: I need to test…

Some questions to understand the current mechanism of Platformio:

  • I guess now it is using the startup file user\.platformio\packages\framework-spl\gd32\cmsis\variants\gd32f13\startup_gd32f1x0.S Can you confirm?
  • and which linker script is using? I don’t see it specified in platformio.ini and I see 3 linker scripts for GD32 in user\.platformio\packages\framework-spl\platformio\ldscripts

So, I tested the code…

  • the things that works well: LEDs, UART, printf, Input to serial… so basically everything except… the I2C
  • the only place where I have issues is the I2C communication with MPU6050. It fails at the first i2c_writeBytes() command. And it is really strange because it does reach this point in the interrupt, and still somehow the i2c_status is -1.
  • Surprisingly, If I put a delay_1ms(10) here then it works
  • In Keil it works without the delay… can it be that the code is now faster than in Keil?

Exactly, the builder script (spl.py in https://github.com/platformio/platform-ststm32/blob/develop/builder/frameworks/spl.py) adds the whole \gd32\cmsis\variants\gd32f13 path to CPPPATH so it all gets compiled / assembled. Note that this startup file has a commented-out call to the libc array init functions (C++ constructor calls on startup), because that produces an error I haven’t quiet figured out how to solve yet…

There should be GD32F130C6T_FLASH_BACKUP.ld and GD32F130C6T_DEFAULT.ld after compilation. The auto-generated linker script is perfectly fine and the pre-provided one seemed very fishy to me. E.g.,

The _estack is the start value of the stack pointer (SP) and it should point to the end of RAM (stack grows downwards). However 0x20000000 + 4*1024 = 0x20001000, not 0x20000400… The autogenerated link file gets it right. Also a non-existing memory bank MEMORY_B1 of 0 Kilobytes was suspicious…

Hm first of all this sounds like a race condition or compiler optimization. But I checked and all the I2C related variables have been marked with volatile, so the variables i2c_nDABytes and i2c_status should be freshly read from memory every time. Otherwise it could again, like with systick.c do tricks regarding reading the variable only once and quick-exiting, before it could have been modified by the ISR.
However, I find

quiete suspicious because an empty loop with a local uint16_t k variable can be completely optimized away by the compiler (has no outside effect), but I think this is some needed intra-byte delay for I2C. But then please use a microsecond delay function from somewhere or force a volatile asm section with a NOP instruction that will not get optimized away.

for(k=0; k<500; k++) {
   //unoptimizable NOP loop, 500 times. 
   asm volatile ("nop");
}

How much delay is needed there?

Overall there seem to be some problems regarding different compiler optimization levels. What settings are you using for the Keil compiler?

You can also disable GCC optimizations by writing

build_unflags = -Os
build_flags = -O0

in the platformio.ini for optimization level 0. But then you need to disable some serial output again or use the slimmer “print raw string” function.

Works!! The asm volatile ("nop"); did the job. You are right, most probably the compiler wiped it away. But I need this delay otherwise loading the DMP (Digital Motion Processing) firmware will fail. I guess it needs some time to be written in the MPU6050 memory.

For the i2c_status I think I found a bug on my side. I should set the i2c_status = 0 in the interrupt immediately after all data was transmitted, i.e.,:

if (i2c_nDABytes > 0) {            
		i2c_data_transmit(I2C0, *i2c_txbuffer++);// the master sends a data byte
		i2c_nDABytes--;		
		if (0 == i2c_nDABytes) {
			i2c_status = 0;				// 0 = Success -> set HERE
		}
		for(k=0; k<500; k++) {
			asm volatile ("nop"); 		// unoptimizable NOP loop, 500 times.  	// make some clock cycles delay (otherwise DMP writing will fail!! Reason unknown yet.. could be that writing to MPU6050 memory takes a bit more time)
		}	
} else {     						     
		i2c_stop_on_bus(I2C0);	 		// the master sends a stop condition to I2C bus 							
		// i2c_status = 0;				// NOT here. Might be too late
		i2c_interrupt_disable(I2C0, I2C_INT_ERR | I2C_INT_BUF | I2C_INT_EV); 	// disable the I2C0 interrupt						
}

Otherwise if the processor is too fast, the while(i2c_nDABytes > 0); will exit and i2c_status is not yet set to 0.
Regarding Linker Script I will use GD32F130C6T_DEFAULT.ld then for the make under Ubuntu.

Edit: In Keil I used both O0 and O3. I have O3 now to get smaller code size.

Now everything works… so from my point of view this is more than solutioned. :slight_smile: For now, I will save your framework-spl.zip in my repo as well and describe the steps for other people. I guess this will not be necessary anymore when this board will be integrated in Platformio by default. What do you think about the approach?