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 软件工具准备
软件方面,我们需要准备以下工具链:
RISC-V交叉编译工具链:
- 官方推荐使用平头哥提供的工具链
- 也可以选择从源码编译riscv-gnu-toolchain
代码编辑器:
- VSCode + PlatformIO插件
- 或者Eclipse + RISC-V插件
烧录工具:
- PhoenixSuit(全志官方烧录工具)
- xfel工具(命令行烧录工具)
串口调试工具:
- Windows: Putty/TeraTerm
- Linux: minicom/picocom
- macOS: screen/minicom
其他辅助工具:
- 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 --version3.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,但我们可以从以下几个渠道获取相关资料:
- 官方BSP包中的裸机示例
- 《RISC-V体系结构编程与实践》书籍配套代码
- GitHub上的开源项目(如xboot等)
建议的获取方式:
git clone https://github.com/riscv-mcu/riscv-d1s-baremetal.git cd riscv-d1s-baremetal git submodule update --init4. 第一个裸机程序:点亮LED
4.1 硬件连接检查
在开始编程前,我们需要确认开发板的硬件连接:
- 将USB转TTL模块连接到开发板的UART0(通常是PA4-TX, PA5-RX)
- 连接电源(注意电压必须是5V)
- 确保启动模式选择开关设置在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 0x400000005. 程序烧录与调试
5.1 使用xfel工具烧录
xfel是全志系列芯片的通用命令行工具,支持D1s的裸机程序烧录:
- 首先安装xfel:
git clone https://github.com/xboot/xfel.git cd xfel make sudo make install- 烧录程序:
make flash这个过程会:
- 初始化DDR
- 将程序写入内存
- 跳转到程序入口执行
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 常见问题排查
程序烧录后无反应:
- 检查电源是否稳定
- 确认烧录地址是否正确(D1s通常为0x40000000)
- 检查串口连接是否正确
串口无输出:
- 确认波特率设置为115200
- 检查TX/RX线是否接反
- 确认UART引脚配置正确
LED不闪烁:
- 确认GPIO引脚号是否正确
- 检查LED电路是否有上拉/下拉电阻
- 用万用表测量GPIO电压变化
调试技巧:在关键代码处添加串口输出,可以帮助定位问题所在。例如在GPIO操作前后打印状态信息。
6. 进阶环境配置
6.1 使用OpenOCD进行调试
虽然xfel可以烧录程序,但对于复杂调试,我们需要更强大的工具:
- 安装OpenOCD:
sudo apt-get install openocd- 创建配置文件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- 启动调试会话:
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.ld7.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 .这段代码会:
- 设置栈指针
- 调用main函数
- 进入死循环(防止跑飞)
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 寄存器操作最佳实践
- 使用结构体映射寄存器组:
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)- 使用位域定义:
typedef union { struct { uint32_t MODE:4; uint32_t PULL:2; uint32_t DRV:2; }; uint32_t val; } GPIO_Config;- 操作寄存器时先读后写:
uint32_t reg = GPIO->CFG[0]; reg &= ~(0xf << 12); // 清除原有配置 reg |= (0x1 << 12); // 设置新配置 GPIO->CFG[0] = reg;8.3 性能优化技巧
- 使用-Os优化级别(优化代码大小)
- 关键函数添加__attribute__((section(".fastcode")))
- 频繁调用的函数声明为static inline
- 使用memcpy/memset等内置函数
- 避免浮点运算(RISC-V需要软浮点)
8.4 调试技巧
- 使用GPIO模拟逻辑分析仪:
#define DEBUG_PIN 10 void debug_pulse() { GPIO_DATA |= (1 << DEBUG_PIN); GPIO_DATA &= ~(1 << DEBUG_PIN); }- 在关键代码处插入断点指令:
asm volatile("ebreak");- 使用串口打印调用栈:
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 电源管理
裸机环境下需要注意电源管理:
- 关闭未使用的外设时钟
- 进入低功耗模式:
void enter_wfi() { asm volatile("wfi"); }- 合理配置IO口状态以降低功耗
9. 从裸机到RTOS
当项目复杂度增加时,可以考虑引入RTOS。以下是移植FreeRTOS的基本步骤:
- 下载FreeRTOS源码
- 实现port.c中的架构相关代码
- 配置内存管理
- 实现必要的系统调用(如printf)
- 初始化任务和调度器
关键点:
- 堆大小配置
- 上下文切换实现
- 中断优先级设置
- 系统时钟配置
一个简单的任务示例:
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 推荐资源
官方文档:
- 《D1s Datasheet》
- 《C906 User Manual》
书籍:
- 《RISC-V体系结构编程与实践》
- 《计算机组成与设计 RISC-V 版》
开源项目:
- XBoot(支持D1s的bootloader)
- RT-Thread(支持D1s的RTOS)
10.2 进阶方向
- 外设驱动开发
- 硬件加速器使用(如DSP、NPU)
- 低功耗优化
- 安全启动实现
- 固件升级方案
10.3 社区支持
- 全志官方论坛
- RISC-V国际基金会社区
- GitHub上的相关项目
- 电子工程师社区(如EEVblog)
我在实际开发中发现,D1s的裸机开发虽然入门门槛较高,但一旦掌握了基本环境搭建和调试方法,就能充分发挥RISC-V架构的优势。特别是在需要直接硬件控制的场景下,裸机开发提供了最大的灵活性和控制力。建议初学者从简单的GPIO控制开始,逐步扩展到中断处理、外设驱动等更复杂的功能,同时注意保持代码的模块化和可移植性。