Brushless Motor Car Development Log 01
Author: Jiligulu
Introduction
I’ve recently decided to start working on a small car powered by a brushless motor. Instead of using an existing solution, I plan to start from scratch and develop my own. For the hardware, I’m considering using the GD32E503 and EG2134 gate drive ICs among other components. This presents a challenge as it’s not easy to run SimpleFOC quickly with Arduino for prototyping. Therefore, I’m thinking of developing the software based on the open-source operating system RT-Thread. This way, I only need to port some hardware drivers and algorithms like FOC and Kalman filter. Many other features can be implemented using RT-Thread components, avoiding the cost of reinventing the wheel 😅. My goal in playing with this small car is to delve deeper into control algorithms. The entire development cycle of this small car may be quite long, at least measured in years. Firstly, because I’m quite busy with work and can only use some spare time to work on it. Secondly, because I plan to learn as I go, researching interesting areas in depth rather than rushing through. I’ll document the entire development process. The documentation in the form of pictures and text will likely be recorded on the RT-Thread forum. I hope interested folks can join me in this endeavor, and you’re free to choose your own hardware.
Preliminary Work
I’ve spent about a week designing the first version of the test hardware circuit and mechanical structure.
Below is a 3D view of the hardware circuit.
Below is a 3D rendering of the chassis of the three-wheeled car:
The following image is a 3D rendering of the chassis of the two-stage self-balancing car:
At present, the first version of the hardware test circuit has been soldered, and the first version of the test car has also been assembled. The image below shows the newly assembled self-balancing brushless motor car. I plan to start with it first, and then gradually explore other forms. The car is quite compact, and once testing is complete, I plan to make a more integrated and smaller version for further experimentation.
The Journey of a Thousand Miles Begins with Creating BSP
Everything is ready, and it’s time to start writing the program. The first problem I encountered is that the GD32E503 processor I want to use is not currently in the RT-Thread’s BSP form. So, the first step is to learn how to add my own BSP.
Download GD32E503 Firmware Library and RT-Thread Source Code
The first step in creating a BSP is naturally to download the RT-Thread source code and the GD32E503 firmware library. The current stable release of RT-Thread should be 4.1.0, but since I’m just experimenting and don’t require high stability, I decided to download the latest master version from Github.
/* RT-Thread version information */
#define RT_VERSION_MAJOR 5 /**< Major version number (X.x.x) */
#define RT_VERSION_MINOR 0 /**< Minor version number (x.X.x) */
#define RT_VERSION_PATCH 1 /**< Patch version number (x.x.X) */
`
The GD32E503 firmware library is included in the demo materials for the development board, and the latest version is V1.2.2.
Add GD32E503 Device to MDK
The AddOn resource package for GD32E50x is currently at version 1.3.3. If you haven’t installed MDK yet, you’ll need to do that first (I won’t go into detail here), and then install the two “.pack” files included in the AddOn resource package (you can also just install the E50x version):
As shown in the figure below, you can find a list of GD32E50x series devices in MDK’s DeviceDatabase, which means that the resource package has been successfully installed.
Add libraries
Copy the firmware library to the libraries directory of the BSP. Unzip the RT-Thread source code package and firmware library zip package, and first copy the “GD32E50x_Firmware_Library” folder from the firmware library to the “bsp/gd32/arm/libraries/” path in the RT-Thread source code package:
Modify the configuration files in the libraries directory
Here you can see that there are two configuration files in the libraries directory, “Kconfig” and “.ignore_format.yml”. The Kconfig file will definitely be used later when configuring with menuconfig or RT-Thread Studio IDE. The other file’s purpose is not known for now, but it doesn’t hurt to modify it as well. Modify the Kconfig file as follows (add GD32E5 configuration information at the end of the document):
config SOC_FAMILY_GD32
bool
config SOC_SERIES_GD32F1
bool
select ARCH_ARM_CORTEX_M3
select SOC_FAMILY_GD32
config SOC_SERIES_GD32F2
bool
select ARCH_ARM_CORTEX_M3
select SOC_FAMILY_GD32
config SOC_SERIES_GD32F3
bool
select ARCH_ARM_CORTEX_M4
select SOC_FAMILY_GD32
config SOC_SERIES_GD32F4
bool
select ARCH_ARM_CORTEX_M4
select SOC_FAMILY_GD32
config SOC_SERIES_GD32E5 #add by cjl
bool
select ARCH_ARM_CORTEX_M33
select SOC_FAMILY_GD32
Add the path name of GD32E50x’s firmware library to the end of the array in “.yml” file:
dir_path:
- GD32F10x_Firmware_Library
- GD32F20x_Firmware_Library
- GD32F30x_Firmware_Library
- GD32F4xx_Firmware_Library
- GD32E50x_Firmware_Library
Add SConscript Build Configuration File to Firmware Library
Copy an “SConscript” file from another firmware library (I used F4xx) to the newly added firmware library path:
Modify its content (replace F4xx with E50x):
import rtconfig
from building import *
# get current directory
cwd = GetCurrentDir()
# The set of source files associated with this SConscript file.
src = Split('''
CMSIS/GD/GD32E50x/Source/system_gd32e50x.c
GD32E50x_standard_peripheral/Source/gd32e50x_gpio.c
GD32E50x_standard_peripheral/Source/gd32e50x_rcu.c
GD32E50x_standard_peripheral/Source/gd32e50x_exti.c
GD32E50x_standard_peripheral/Source/gd32e50x_misc.c
''')
# remove: GD32E50x_standard_peripheral/Source/gd32e50x_syscfg.c
if GetDepend(['RT_USING_SERIAL']):
src += ['GD32E50x_standard_peripheral/Source/gd32e50x_usart.c']
if GetDepend(['RT_USING_I2C']):
src += ['GD32E50x_standard_peripheral/Source/gd32e50x_i2c.c']
if GetDepend(['RT_USING_SPI']):
src += ['GD32E50x_standard_peripheral/Source/gd32e50x_spi.c']
if GetDepend(['RT_USING_CAN']):
src += ['GD32E50x_standard_peripheral/Source/gd32e50x_can.c']
if GetDepend(['BSP_USING_ETH']):
src += ['GD32E50x_standard_peripheral/Source/gd32e50x_enet.c']
if GetDepend(['RT_USING_ADC']):
src += ['GD32E50x_standard_peripheral/Source/gd32e50x_adc.c']
if GetDepend(['RT_USING_DAC']):
src += ['GD32E50x_standard_peripheral/Source/gd32e50x_dac.c']
if GetDepend(['RT_USING_RTC']):
src += ['GD32E50x_standard_peripheral/Source/gd32e50x_rtc.c']
src += ['GD32E50x_standard_peripheral/Source/gd32e50x_pmu.c']
if GetDepend(['RT_USING_WDT']):
src += ['GD32E50x_standard_peripheral/Source/gd32e50x_wwdgt.c']
src += ['GD32E50x_standard_peripheral/Source/gd32e50x_fwdgt.c']
if GetDepend(['RT_USING_SDIO']):
src += ['GD32E50x_standard_peripheral/Source/gd32e50x_sdio.c']
src += ['GD32E50x_standard_peripheral/Source/gd32e50x_dma.c']
path = [
cwd + '/CMSIS/GD/GD32E50x/Include',
cwd + '/CMSIS',
cwd + '/GD32E50x_standard_peripheral/Include',]
CPPDEFINES = ['USE_STDPERIPH_DRIVER']
group = DefineGroup('Libraries', src, depend = [''], CPPPATH = path, CPPDEFINES = CPPDEFINES)
Return('group')
Create BSP
Copy BSP Template
Arbitrarily copy a BSP folder (I also used 407) and rename it to E503:
Modify board.h Header File
The main content that needs to be modified is under the board path. First, look at the board.h file. I didn’t do external SDRAM here, so I commented out the related definitions. My board is actually soldered with GD32E503RCT6, so the SRAM size is changed to 96KB, and then the related header file names are modified:
#ifndef __BOARD_H__
#define __BOARD_H__
#include "gd32e50x.h"
#include "drv_usart.h"
#include "drv_gpio.h"
#include "gd32e50x_exti.h"
//#define EXT_SDRAM_BEGIN (0xC0000000U) /* the begining address of external SDRAM */
//#define EXT_SDRAM_END (EXT_SDRAM_BEGIN + (32U * 1024 * 1024)) /* the end address of external SDRAM */
// <o> Internal SRAM memory size[Kbytes] <8-64>
// <i>Default: 64
#ifdef __ICCARM__
// Use *.icf ram symbal, to avoid hardcode.
extern char __ICFEDIT_region_RAM_end__;
#define GD32_SRAM_END &__ICFEDIT_region_RAM_end__
#else
#define GD32_SRAM_SIZE 96 //96 for GD32E503xC 128 for GD32E503xE ...
#define GD32_SRAM_END (0x20000000 + GD32_SRAM_SIZE * 1024)
#endif
#ifdef __ARMCC_VERSION
extern int Image$$RW_IRAM1$$ZI$$Limit;
#define HEAP_BEGIN (&Image$$RW_IRAM1$$ZI$$Limit)
#elif __ICCARM__
#pragma section="HEAP"
#define HEAP_BEGIN (__segment_end("HEAP"))
#else
extern int __bss_end;
#define HEAP_BEGIN (&__bss_end)
#endif
#define HEAP_END GD32_SRAM_END
#endif
Add gd32e50x_libopt.h
Copy a “gd32e50x_libopt.h” from any example path in the firmware library to the current bsp’s board path, replacing the original “gd32f4xx_libopt.h”
Modify Kconfig and SConscript Configuration Files in board Directory
The “Kconfig” and “SConscript” configuration files in the board directory also need to be slightly modified. Basically, all F4xx strings are changed to E50x. It should be noted that there are two versions of E50x in the “SConscript” file for startup file related configuration, high density (hd) and interconnect (cl). I used E503RC or RE here, which are high-density versions, so I chose the hd assembly startup file. And what needs to be explained is that although I did not comment out the startup file under the gcc compiler here, in fact, there is no gcc version of the startup file in the E503 firmware library, which means that RT-Thread’s IDE cannot be used for development later. When it’s available, add it. Or if necessary, modify a version of the gcc compiler’s startup file yourself. For now, these two files are just modified like this, and if there are any problems later, they will be modified. The modification of the “SConscript” file is as follows:
import os
import rtconfig
from building import *
Import('SDK_LIB')
cwd = GetCurrentDir()
# add general drivers
src = Split('''
board.c
''')
path = [cwd]
startup_path_prefix = SDK_LIB
if rtconfig.PLATFORM in ['gcc']:
src += [startup_path_prefix + '/GD32E50x_Firmware_Library/CMSIS/GD/GD32E50x/Source/GCC/startup_gd32e50x_hd.s']
elif rtconfig.PLATFORM in ['armcc', 'armclang']:
src += [startup_path_prefix + '/GD32E50x_Firmware_Library/CMSIS/GD/GD32E50x/Source/ARM/startup_gd32e50x_hd.s']
elif rtconfig.PLATFORM in ['iccarm']:
src += [startup_path_prefix + '/GD32E50x_Firmware_Library/CMSIS/GD/GD32E50x/Source/IAR/startup_gd32e50x_hd.s']
CPPDEFINES = ['GD32E50X','GD32E50X_HD']
group = DefineGroup('Drivers', src, depend = [''], CPPPATH = path, CPPDEFINES = CPPDEFINES)
Return('group')
Modify Link Scripts for Three Compilers
Modify the link files in the “board/linker_scripts” directory. If there are no special requirements, you don’t need to make too many modifications to the link script. You just need to modify the RAM and ROM addresses and sizes. Take “link.sct” as an example:
LR_IROM1 0x08000000 0x00040000 { ; load region size_region 256KB
ER_IROM1 0x08000000 0x00040000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
}
RW_IRAM1 0x20000000 0x00018000 { ; RW data 96KB
.ANY (+RW +ZI)
}
}
Modify Top-level SConstruct and rtconfig.py Files of BSP
Modify the SConstruct file and rtconfig.py file in the newly created bsp directory:
The SConstruct file only needs to modify the value of “gd32_library”:
gd32_library = 'GD32E50x_Firmware_Library'
Modify Project Template
Next, modify the project template file. I only modify the MDK5 version of the project template here:
Modify Device to actual use GD32E503RC:
Change debugging tool to DAP:
Use RT-Thread Env Tool to Configure RT-Thread and Generate MDK Project
Run env tool in new bsp directory (for installation and use of env tool, please refer to RT-Thread official document), make preliminary configuration for RT-Thread:
Finally, according to the prompt, enter command to generate mdk5 project (if you want to submit your own made BSP, please refer to official BSP production specification):
Download & Verification
Finally open project, slightly modify content inside main.c, add a debug statement, modify corresponding LED pin. Compile it. If it can be compiled successfully, it means that there is no problem with the above modification. If there is an error, please carefully check whether the content of each configuration file modified above is correct or modify it according to error prompt. Burn program can see running effect.
#include <stdio.h>
#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>
/* defined the LED2 pin: PC6 */
#define LED_PIN GET_PIN(B, 11)
#define BUZZER_PIN GET_PIN(B, 12)
int main(void)
{
int count = 1;
/* set LED2 pin mode to output */
rt_pin_mode(LED_PIN, PIN_MODE_OUTPUT);
rt_pin_write(BUZZER_PIN, PIN_LOW);
rt_pin_mode(BUZZER_PIN, PIN_MODE_OUTPUT);
rt_kprintf("无刷电机小车测试程序!\n");
while (count++)
{
rt_pin_write(LED_PIN, PIN_HIGH);
rt_thread_mdelay(500);
rt_pin_write(LED_PIN, PIN_LOW);
rt_thread_mdelay(500);
}
return RT_EOK;
}
This article will stop here for now. I will continue updating this part later! Welcome everyone’s feedback!