全志D1s开发板RISC-V裸机开发环境搭建指南

📅 2026/7/17 3:57:27 👁️ 阅读次数 📝 编程学习
全志D1s开发板RISC-V裸机开发环境搭建指南

1. 全志D1s开发板裸机开发环境概述

第一次接触全志D1s开发板时,我完全被它独特的RISC-V架构吸引住了。作为一款基于阿里平头哥C906内核的芯片,D1s在IoT和嵌入式领域有着独特的优势。但真正开始裸机开发时,环境搭建这个看似简单的第一步就给了我一个下马威。

裸机开发与常规的Linux环境开发完全不同,它意味着我们需要从零开始构建整个运行环境,没有现成的操作系统支持,所有的硬件操作都需要直接通过寄存器来完成。这种开发方式虽然原始,但能让我们对硬件有最直接的控制权,也是理解计算机底层工作原理的最佳途径。

在开始之前,我们需要明确几个关键点:首先,D1s采用的是RISC-V指令集架构,这意味着传统的ARM工具链在这里不适用;其次,裸机开发需要特殊的编译工具和调试方法;最后,环境搭建的完整性直接决定了后续开发的顺畅程度。

2. 开发环境准备

2.1 硬件准备清单

工欲善其事,必先利其器。在开始搭建环境前,我们需要准备以下硬件设备:

  • 全志D1s开发板(核心板+底板)
  • USB转TTL串口模块(推荐使用CH340或CP2102芯片的版本)
  • 5V/2A电源适配器
  • Micro USB数据线
  • TF卡(建议8GB以上,Class10速度等级)
  • 杜邦线若干
  • 可选:JTAG调试器(如DAP-Link或J-Link)

这里特别提醒一下,串口模块的选择很重要。我曾经因为贪便宜买了个劣质的PL2303芯片的串口模块,结果在调试过程中出现了各种莫名其妙的通信问题,最后不得不重新购买CH340的模块才解决。

2.2 软件工具准备

软件方面,我们需要准备以下工具链:

  1. RISC-V交叉编译工具链:

    • 官方推荐使用平头哥提供的工具链
    • 也可以选择从源码编译riscv-gnu-toolchain
  2. 代码编辑器:

    • VSCode + PlatformIO插件
    • 或者Eclipse + RISC-V插件
  3. 烧录工具:

    • PhoenixSuit(全志官方烧录工具)
    • xfel工具(命令行烧录工具)
  4. 串口调试工具:

    • Windows: Putty/TeraTerm
    • Linux: minicom/picocom
    • macOS: screen/minicom
  5. 其他辅助工具:

    • Git(用于源码管理)
    • Make(构建系统)
    • OpenOCD(调试用)

注意:工具链的版本兼容性非常重要。我曾经遇到过因为工具链版本不匹配导致的奇怪编译错误,建议使用官方推荐的版本。

3. 工具链安装与配置

3.1 安装RISC-V交叉编译工具链

对于Linux用户(推荐Ubuntu 20.04 LTS),可以按照以下步骤安装:

# 添加平头哥工具链仓库 sudo add-apt-repository ppa:thead-maintainers/tools sudo apt-get update # 安装基础编译工具 sudo apt-get install build-essential git wget flex bison libgmp-dev libmpfr-dev libmpc-dev texinfo # 安装RISC-V工具链 sudo apt-get install gcc-riscv64-unknown-elf gdb-riscv64-unknown-elf

对于Windows用户,建议使用WSL2安装Ubuntu后按照上述步骤操作,或者直接下载预编译的工具链包。

验证安装是否成功:

riscv64-unknown-elf-gcc --version

3.2 配置开发环境

环境变量配置是很多人容易忽略的一步。我们需要将工具链路径添加到系统PATH中:

# 编辑~/.bashrc文件 echo 'export PATH=$PATH:/opt/riscv/bin' >> ~/.bashrc source ~/.bashrc

对于VSCode用户,建议安装以下插件:

  • C/C++
  • RISC-V Support
  • Cortex-Debug
  • Makefile Tools

3.3 获取D1s裸机开发SDK

全志官方并没有提供专门的裸机开发SDK,但我们可以从以下几个渠道获取相关资料:

  1. 官方BSP包中的裸机示例
  2. 《RISC-V体系结构编程与实践》书籍配套代码
  3. GitHub上的开源项目(如xboot等)

建议的获取方式:

git clone https://github.com/riscv-mcu/riscv-d1s-baremetal.git cd riscv-d1s-baremetal git submodule update --init

4. 第一个裸机程序:点亮LED

4.1 硬件连接检查

在开始编程前,我们需要确认开发板的硬件连接:

  1. 将USB转TTL模块连接到开发板的UART0(通常是PA4-TX, PA5-RX)
  2. 连接电源(注意电压必须是5V)
  3. 确保启动模式选择开关设置在SPI Flash启动模式

4.2 编写简单的LED闪烁程序

创建一个简单的main.c文件:

#include <stdint.h> // 定义GPIO寄存器 #define GPIO_BASE 0x02000000 #define GPIO_CFG0 (*(volatile uint32_t *)(GPIO_BASE + 0x00)) #define GPIO_DATA (*(volatile uint32_t *)(GPIO_BASE + 0x10)) // 简单延时函数 void delay(uint32_t count) { while(count--); } int main() { // 配置PG6引脚为输出模式 GPIO_CFG0 &= ~(0xf << 24); // 清除原有配置 GPIO_CFG0 |= (0x1 << 24); // 设置为输出模式 while(1) { GPIO_DATA |= (1 << 6); // 点亮LED delay(1000000); GPIO_DATA &= ~(1 << 6); // 熄灭LED delay(1000000); } return 0; }

4.3 编写链接脚本

创建link.ld文件,定义内存布局:

MEMORY { RAM (rwx) : ORIGIN = 0x40000000, LENGTH = 64K } SECTIONS { .text : { *(.text*) } > RAM .data : { *(.data*) } > RAM .bss : { *(.bss*) } > RAM _stack_top = ORIGIN(RAM) + LENGTH(RAM); }

4.4 编写Makefile

创建Makefile自动化构建过程:

CC = riscv64-unknown-elf-gcc OBJCOPY = riscv64-unknown-elf-objcopy CFLAGS = -march=rv64imafdc -mabi=lp64d -nostdlib -fno-builtin -O1 all: led.bin led.elf: main.c $(CC) $(CFLAGS) -Tlink.ld -o $@ $^ led.bin: led.elf $(OBJCOPY) -O binary $^ $@ clean: rm -f *.elf *.bin flash: led.bin xfel ddr d1 xfel write 0x40000000 led.bin xfel exec 0x40000000

5. 程序烧录与调试

5.1 使用xfel工具烧录

xfel是全志系列芯片的通用命令行工具,支持D1s的裸机程序烧录:

  1. 首先安装xfel:
git clone https://github.com/xboot/xfel.git cd xfel make sudo make install
  1. 烧录程序:
make flash

这个过程会:

  1. 初始化DDR
  2. 将程序写入内存
  3. 跳转到程序入口执行

5.2 串口调试输出

为了便于调试,我们可以添加串口输出功能。首先需要初始化UART0:

void uart_init() { // 配置PA4为UART0_TX, PA5为UART0_RX *(volatile uint32_t *)(0x02000000 + 0x30) &= ~(0xff << 8); *(volatile uint32_t *)(0x02000000 + 0x30) |= (0x22 << 8); // 配置UART0参数 *(volatile uint32_t *)(0x02500000 + 0x08) = 0x0d; // 115200波特率 *(volatile uint32_t *)(0x02500000 + 0x04) = 0x03; // 8N1 *(volatile uint32_t *)(0x02500000 + 0x0c) = 0x01; // 使能UART } void uart_putc(char c) { while(!(*(volatile uint32_t *)(0x02500000 + 0x14) & 0x02)); *(volatile uint32_t *)(0x02500000 + 0x00) = c; } void uart_puts(const char *s) { while(*s) { uart_putc(*s++); } }

然后在main函数中调用:

uart_init(); uart_puts("Hello, D1s Baremetal!\r\n");

5.3 常见问题排查

  1. 程序烧录后无反应

    • 检查电源是否稳定
    • 确认烧录地址是否正确(D1s通常为0x40000000)
    • 检查串口连接是否正确
  2. 串口无输出

    • 确认波特率设置为115200
    • 检查TX/RX线是否接反
    • 确认UART引脚配置正确
  3. LED不闪烁

    • 确认GPIO引脚号是否正确
    • 检查LED电路是否有上拉/下拉电阻
    • 用万用表测量GPIO电压变化

调试技巧:在关键代码处添加串口输出,可以帮助定位问题所在。例如在GPIO操作前后打印状态信息。

6. 进阶环境配置

6.1 使用OpenOCD进行调试

虽然xfel可以烧录程序,但对于复杂调试,我们需要更强大的工具:

  1. 安装OpenOCD:
sudo apt-get install openocd
  1. 创建配置文件d1s.cfg:
interface jlink transport select jtag adapter_khz 1000 set _CHIPNAME riscv jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x1000563d set _TARGETNAME $_CHIPNAME.cpu target create $_TARGETNAME riscv -chain-position $_TARGETNAME riscv set_reset_timeout_sec 10 riscv set_command_timeout_sec 10 init halt
  1. 启动调试会话:
openocd -f d1s.cfg

然后可以在VSCode中使用Cortex-Debug插件进行源码级调试。

6.2 集成外设驱动

一个完整的裸机环境通常需要以下驱动:

  • GPIO
  • UART
  • SPI
  • I2C
  • Timer
  • PWM
  • ADC

建议将这些驱动模块化组织:

drivers/ ├── gpio/ ├── uart/ ├── spi/ ├── i2c/ ├── timer/ └── adc/

每个驱动提供标准的接口,例如GPIO驱动可以提供:

void gpio_set_mode(uint32_t pin, uint32_t mode); void gpio_write(uint32_t pin, uint32_t value); uint32_t gpio_read(uint32_t pin);

6.3 内存管理

裸机环境下需要自己管理内存,可以实现简单的malloc/free:

#define HEAP_SIZE 4096 static uint8_t heap[HEAP_SIZE]; static uint32_t heap_ptr = 0; void *malloc(size_t size) { if(heap_ptr + size > HEAP_SIZE) return NULL; void *ptr = &heap[heap_ptr]; heap_ptr += size; return ptr; } void free(void *ptr) { // 简单实现,实际上不释放内存 }

对于更复杂的需求,可以考虑实现内存池或分块管理。

7. 项目组织与构建系统

7.1 项目目录结构

一个良好的项目结构能大大提高开发效率:

project/ ├── build/ ├── drivers/ ├── include/ ├── lib/ ├── src/ │ ├── main.c │ └── startup.S ├── scripts/ ├── Makefile └── link.ld

7.2 自动化构建

扩展之前的Makefile支持多文件编译:

SRCS = $(wildcard src/*.c) $(wildcard drivers/*/*.c) OBJS = $(patsubst %.c,%.o,$(SRCS)) %.o: %.c $(CC) $(CFLAGS) -Iinclude -c $< -o $@ project.elf: $(OBJS) src/startup.o $(CC) $(CFLAGS) -Tlink.ld -o $@ $^

7.3 启动代码

RISC-V需要基本的启动代码,创建startup.S:

.section .init .global _start _start: la sp, _stack_top call main j .

这段代码会:

  1. 设置栈指针
  2. 调用main函数
  3. 进入死循环(防止跑飞)

7.4 中断处理

裸机环境下也可以处理中断,需要配置中断向量表:

.section .vectors .global _vectors _vectors: j _start .word 0 .word 0 // 其他中断向量...

然后在main函数中初始化中断控制器:

void interrupt_init() { // 配置PLIC *(volatile uint32_t *)(0x10000000 + 0x200004) = 0x01; // 使能UART0中断 // 设置全局中断使能 asm volatile("csrs mstatus, 0x8"); }

8. 实用技巧与经验分享

8.1 如何阅读全志芯片手册

全志的芯片手册往往信息分散,建议重点关注以下章节:

  • 内存映射表(Memory Map)
  • 时钟系统(CCU)
  • GPIO控制器
  • UART控制器
  • 中断控制器(PLIC)

一个小技巧:使用PDF阅读器的搜索功能,直接搜索寄存器名称或地址。

8.2 寄存器操作最佳实践

  1. 使用结构体映射寄存器组:
typedef struct { volatile uint32_t CFG[4]; volatile uint32_t DATA; volatile uint32_t DRV[2]; volatile uint32_t PUL[2]; } GPIO_Type; #define GPIO ((GPIO_Type *)0x02000000)
  1. 使用位域定义:
typedef union { struct { uint32_t MODE:4; uint32_t PULL:2; uint32_t DRV:2; }; uint32_t val; } GPIO_Config;
  1. 操作寄存器时先读后写:
uint32_t reg = GPIO->CFG[0]; reg &= ~(0xf << 12); // 清除原有配置 reg |= (0x1 << 12); // 设置新配置 GPIO->CFG[0] = reg;

8.3 性能优化技巧

  1. 使用-Os优化级别(优化代码大小)
  2. 关键函数添加__attribute__((section(".fastcode")))
  3. 频繁调用的函数声明为static inline
  4. 使用memcpy/memset等内置函数
  5. 避免浮点运算(RISC-V需要软浮点)

8.4 调试技巧

  1. 使用GPIO模拟逻辑分析仪:
#define DEBUG_PIN 10 void debug_pulse() { GPIO_DATA |= (1 << DEBUG_PIN); GPIO_DATA &= ~(1 << DEBUG_PIN); }
  1. 在关键代码处插入断点指令:
asm volatile("ebreak");
  1. 使用串口打印调用栈:
void print_stack(uint32_t *sp, int depth) { uart_puts("Call stack:\r\n"); for(int i=0; i<depth; i++) { uart_printf("0x%08x\r\n", sp[i]); } }

8.5 电源管理

裸机环境下需要注意电源管理:

  1. 关闭未使用的外设时钟
  2. 进入低功耗模式:
void enter_wfi() { asm volatile("wfi"); }
  1. 合理配置IO口状态以降低功耗

9. 从裸机到RTOS

当项目复杂度增加时,可以考虑引入RTOS。以下是移植FreeRTOS的基本步骤:

  1. 下载FreeRTOS源码
  2. 实现port.c中的架构相关代码
  3. 配置内存管理
  4. 实现必要的系统调用(如printf)
  5. 初始化任务和调度器

关键点:

  • 堆大小配置
  • 上下文切换实现
  • 中断优先级设置
  • 系统时钟配置

一个简单的任务示例:

void led_task(void *arg) { while(1) { GPIO_DATA ^= (1 << 6); vTaskDelay(500 / portTICK_PERIOD_MS); } } void main() { xTaskCreate(led_task, "LED", 128, NULL, 1, NULL); vTaskStartScheduler(); while(1); }

10. 资源推荐与后续学习

10.1 推荐资源

  1. 官方文档:

    • 《D1s Datasheet》
    • 《C906 User Manual》
  2. 书籍:

    • 《RISC-V体系结构编程与实践》
    • 《计算机组成与设计 RISC-V 版》
  3. 开源项目:

    • XBoot(支持D1s的bootloader)
    • RT-Thread(支持D1s的RTOS)

10.2 进阶方向

  1. 外设驱动开发
  2. 硬件加速器使用(如DSP、NPU)
  3. 低功耗优化
  4. 安全启动实现
  5. 固件升级方案

10.3 社区支持

  1. 全志官方论坛
  2. RISC-V国际基金会社区
  3. GitHub上的相关项目
  4. 电子工程师社区(如EEVblog)

我在实际开发中发现,D1s的裸机开发虽然入门门槛较高,但一旦掌握了基本环境搭建和调试方法,就能充分发挥RISC-V架构的优势。特别是在需要直接硬件控制的场景下,裸机开发提供了最大的灵活性和控制力。建议初学者从简单的GPIO控制开始,逐步扩展到中断处理、外设驱动等更复杂的功能,同时注意保持代码的模块化和可移植性。