GD32VF103 GCC+MAKE环境配置
脱离各种大型IDE的GD32VF103的开发环境。
我常用的代码编辑器是sublime text,除了界面风格符合我的审美外,在安装了python和mingw后,它可以使用快捷键ctrl+b直接运行程序,已经形成肌肉记忆了。
此前使用开发环境为segger的 embedded studio + 官方的DFU下载器,但这个下载器有个致命问题,它会随机触发windows的蓝屏,已经弄崩过一次我的系统,所幸只是系统崩了,资料仍能备份出来。
工具链
1.编译器: riscv-none-elf-gcc
无法翻墙的可以下载官方的ide,在ide中集成了一个riscv-none-embed-gcc
2.make: mingw64
3.烧录器: dfu-util 0.11: dfu-util-0.11.tar.gz
4.固件库: GD32VF103_Demo_Suites_V1.6.0
5.git:git 2.52.0
powershell下运行会存在问题,windows下命令行工具使用 git bash
工程构建
gd32vf103_demo:
bash
docs/ # 各类手册及工程资料
src/ # 代码目录
toolchain/ # 编译器及烧录器
tools/ # 解决工程构建过程所需工具
sdk构建
bash
为了让整个工程结构精简,我将官方sdk划分为两个部分,一个部分为CMSIS,一个部分为Peripheral
其文件结构是:
src/GD32SDK/CMSIS/ # 外设所需的.c文件和.h文件,其他启动所需的.s文件、.ld文件、.lds文件都放在该文件夹下
src/GD32SDK/Peripheral/ # 仅存放外设所需的.c和.h文件,其下source存放.c文件,include存放.h文件
CMSIS下文件来源
│ gd32vf103.c
│ gd32vf103.h # 从Firmware\GD32VF103_standard_peripheral下复制
│ startup_gd32vf103.S # 从Firmware\GD32VF103_standard_peripheral下复制
│ system_gd32vf103.c # 从Firmware\GD32VF103_standard_peripheral下复制
│ system_gd32vf103.h # 从Firmware\GD32VF103_standard_peripheral下复制
│ systick.c # 从Examples\下随便找一个示例获得
│ systick.h # 从Examples\下随便找一个示例获得
│
├─drivers # 从固件库\Firmware\RISCV\drivers复制得到
│ n200_eclic.h
│ n200_func.c
│ n200_func.h
│ n200_timer.h
│ riscv_bits.h
│ riscv_const.h
│ riscv_encoding.h
│
└─env # 从固件库\Firmware\RISCV\env_Eclipse复制得到
entry.S
GD32VF103x4.lds
GD32VF103x6.lds
GD32VF103x8.lds
GD32VF103xB.lds
handlers.c
init.c
start.S
gd32vf103.c:
c
#include "gd32vf103.h"
/* 系统初始化 */
void SystemInit(void) {
/* 默认使用HSI 8MHz */
RCU->CTL |= (1U << 0); /* 开启HSI */
/* 等待HSI就绪 */
while(!(RCU->CTL & (1U << 1)));
/* 配置时钟 */
RCU->CFG0 = 0;
RCU->CFG0 |= (0x02 << 4); /* AHB不分频 */
RCU->CFG0 |= (0x04 << 8); /* APB1 2分频 */
RCU->CFG0 |= (0x00 << 11); /* APB2 1分频 */
/* 选择HSI为系统时钟 */
RCU->CFG0 &= ~(0x03 << 0);
RCU->CFG0 |= (0x00 << 0);
/* 更新系统时钟变量 */
// SystemCoreClock = 8000000;
}
/* 简单延时函数 */
void delay_ms(uint32_t ms) {
volatile uint32_t i, j;
for(i = 0; i < ms; i++) {
for(j = 0; j < 8000; j++) {
__asm__ volatile ("nop");
}
}
}
/* 时钟使能函数 */
void rcu_periph_clock_enable(uint32_t periph) {
if(periph <= 0xFFFF) {
RCU->APB2EN |= periph;
} else {
RCU->APB1EN |= (periph >> 16);
}
}
/* GPIO初始化 */
void gpio_init(uint32_t gpio_port, uint32_t mode, uint32_t pin) {
GPIO_Type *gpio = (GPIO_Type *)gpio_port;
for(uint8_t i = 0; i < 16; i++) {
if(pin & (1U << i)) {
if(i < 8) {
/* 低8位配置在CTL0 */
uint32_t ctl = gpio->CTL0;
ctl &= ~(0xFU << (i * 4));
ctl |= (mode << (i * 4));
gpio->CTL0 = ctl;
} else {
/* 高8位配置在CTL1 */
uint32_t ctl = gpio->CTL1;
ctl &= ~(0xFU << ((i - 8) * 4));
ctl |= (mode << ((i - 8) * 4));
gpio->CTL1 = ctl;
}
}
}
}
main.c
c
#include "gd32vf103.h"
#include "systick.h"
#include "gd32vf103_libopt.h"
int main(void) {
/* 系统初始化 */
SystemInit();
/* 使能GPIOC时钟 */
rcu_periph_clock_enable(RCU_GPIOC);
/* 配置PC13为推挽输出 */
gpio_init(GPIOC, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_13);
while(1) {
/* 点亮LED(PC13置0,根据电路设计可能是低电平点亮)*/
gpio_bit_reset(GPIOC, GPIO_PIN_13);
delay_1ms(500); // 延时500ms
/* 熄灭LED(PC13置1)*/
gpio_bit_set(GPIOC, GPIO_PIN_13);
delay_1ms(500); // 延时500ms
}
return 0;
}
bash
gd32vf103_libopt.h 从固件库中示例获得,存放于src/inc下
makefile编写
bash
# 工具链配置
TOOLDIR = ./toolchain/xpack-riscv-none-elf-gcc-15.2.0-1/bin
PREFIX = $(TOOLDIR)riscv-none-elf-
CC = $(PREFIX)gcc
OBJCOPY = $(PREFIX)objcopy
OBJDUMP = $(PREFIX)objdump
SIZE = $(PREFIX)size
# MCU配置
MCU = -march=rv32imac_zicsr -mabi=ilp32 -mcmodel=medlow
OPT = -O1
DEBUG = -g
# 包含路径
INCLUDES = -I./src/inc -I./src/GD32SDK/CMSIS -I./src/GD32SDK/Peripheral/Include -I./src/GD32SDK/CMSIS/drivers
# 编译选项
CFLAGS = $(MCU) $(OPT) $(DEBUG) $(INCLUDES) \
-std=gnu11 -ffunction-sections -fdata-sections \
-Wall -Wextra -Wno-unused-parameter \
-DGD32VF103R_START # 添加这个宏避避免报错
# 链接选项
LDFLAGS = $(MCU) $(DEBUG) -Tsrc/GD32SDK/CMSIS/env/GD32VF103xB.lds \
-nostartfiles -Wl,--gc-sections \
-Wl,--print-memory-usage \
-Wl,-Map=$(MAP) # 使用MAP变量
# 源文件
PERIPHERAL_SRCS = $(wildcard src/GD32SDK/Peripheral/Source/*.c)
SRCS = src/src/main.c\
src/GD32SDK/CMSIS/system_gd32vf103.c \
src/GD32SDK/CMSIS/systick.c\
src/GD32SDK/CMSIS/drivers/n200_func.c\
src/GD32SDK/CMSIS/env/handlers.c \
src/GD32SDK/CMSIS/env/init.c\
$(PERIPHERAL_SRCS)
ASMSRCS = src/GD32SDK/CMSIS/env/entry.S \
src/GD32SDK/CMSIS/env/start.S
#src/GD32SDK/CMSIS/startup_gd32vf103.S\
# 目标文件 - 将所有.o文件放在obj目录下
C_OBJS = $(patsubst %.c,$(OBJ_DIR)/%.o,$(SRCS))
ASM_OBJS = $(patsubst %.S,$(OBJ_DIR)/%.o,$(ASMSRCS))
OBJS = $(C_OBJS) $(ASM_OBJS)
# 输出目录
BUILD_DIR = src/build
OBJ_DIR = $(BUILD_DIR)/obj
# 输出文件
TARGET = src/build/GD32_DEMO
ELF = $(TARGET).elf
BIN = $(TARGET).bin
HEX = $(TARGET).hex
MAP = $(TARGET).map
# 构建规则
all: $(ELF) $(MAP) $(BIN) $(HEX)
$(ELF): $(OBJS) | $(BUILD_DIR) $(OBJ_DIR)
$(CC) $(LDFLAGS) -o $@ $(OBJS)
# 创建构建目录
$(BUILD_DIR) $(OBJ_DIR):
mkdir -p "$@"
# C文件编译规则 - 自动创建子目录
$(OBJ_DIR)/%.o: %.c | $(OBJ_DIR)
@mkdir -p "$(dir $@)"
$(CC) $(CFLAGS) -c -o $@ $<
# 汇编文件编译规则
$(OBJ_DIR)/%.o: %.S | $(OBJ_DIR)
@mkdir -p "$(dir $@)"
$(CC) $(CFLAGS) -c -o $@ $<
$(HEX): $(ELF)
$(OBJCOPY) -O ihex $< $@
# 生成正确的BIN文件
$(BIN): $(ELF) $(MAP)
$(OBJCOPY) -O binary \
-j .init \
-j .text \
-j .rodata \
-j .data \
-j .init_array \
-j .fini_array \
--gap-fill=0xFF \
$< $@
elf: $(ELF)
bin: $(BIN)
hex: $(HEX)
# 反汇编用于调试
disasm: $(ELF)
$(OBJDUMP) -d $< > $(TARGET).txt
clean:
rm -f $(OBJS) $(ELF) $(BIN) $(HEX) $(TARGET).txt $(MAP)
rm -r $(OBJ_DIR)
flash: $(BIN)
dfu-util --alt 0 -D $(BIn) -s 0x8000000
dfu-util -i 0 -s 0x8000000:leave
# 调试目标
debug:
# 未配备
# openocd -f interface/cmsis-dap.cfg -f target/gd32vf103.cfg
.PHONY: all clean flash debug disasm
bash
该文件代码掺和了deepseek编写的内容
修复usb驱动
从官方下载的dfu驱动仅官方的DFU工具可用,使用dfu-util会报错。折腾了我很长一段时间,期间我试过重编译dfu-util,试过安装低版本dfu-util,后查资料时,在https://github.com/riscv-mcu/gd32-dfu-utils/issues/2获得解决方案
libuse-win32:libusb-win32-bin-1.2.6.0.zip
bash
运行bin/x86/install-filter-win.exe
找到对应的gd32,安装驱动后就能正常使用dfu-util下载了
make flash 集成了烧录后跳转到运行代码
若整个过程没有错的话,会跟我实机上的红灯闪烁,我手上的是很久之前的longan开发版,它上面的是一块gd32vf103cbt6,有一个3色灯,其中红色的gpio是pc13.
macos下可以通过brew安装dfu-util,不会出现报驱动出错的问题,然后需要下载对应版本riscv-none-elf-gcc,需要给工具授权,否则在运行过程中,要在安全选项中给所涉及的程序点仍然允许
其他踩过的坑
bash
1.elf转bin
makefile中可以看到,我尝试过使用map来让objcopy生成正确的bin文件,deepseek写了一部分参数,虽然能够生成合理大小的bin文件,但烧录后不能正常运行.
最初一般的objcopy生成的bin文件是空的,后面生成的bin文件是384mb的,原因是flash的初始地址,跟sram之间的相隔384mb.最后经trae完善,能够生成正确的bin文件了。
2.ai编程的应用
不建议新手,特别没有编程基础的新手,一下生成大量的代码。
现阶段,ai只能辅助编程,你对整个工程认识越清晰,对如何实现它认识越清晰,你获得的最终产品才会越趋向正确。
在大型的工程辅助开发中,尽量将项目拆分成多个模块,然后一个模块一个模块实现,然后review代码。
在我使用ai来编程的过程中,一次性生成完整代码,在项目规模超出一个学生管理系统的规模后,容易出现无法控制的错误,在我尝试多次让ai自己修复后,我追踪过一些bug,在定位到产生bug的代码后,多次无语,ai严格按照我的要求生成了代码,但实际上我对功能的描述有时候是不准确的,或者我认识的某些概念,与ai认识的是不一样的。
所以,有必要在ai写代码前,让它用语言描述一遍需求。
这种经验,在其他实践中也是通用的,比如在企业运作过程中,需要安排或者接受安排的时候,被安排者有必要以他自己的语言描述一遍任务,以便消除认识上的不同产生的错误