全志D1s RISC-V裸机开发入门指南
1. 全志D1s裸机开发环境初探第一次接触全志D1s这颗RISC-V芯片时我手里拿着百问网的DongshanPI-D1S开发板这块板子最吸引我的地方是集成了CKLink调试器——这意味着不需要额外购买J-Link或者ST-Link就能开始调试。板载的OTG烧录功能也很实用用Type-C线连接电脑就能直接烧写程序对新手特别友好。开发环境搭建有几个关键点需要注意工具链选择官方推荐使用平头哥的Xuantie-900系列工具链调试器配置由于板载CKLink不需要额外驱动但需要安装OpenOCD串口终端建议使用MobaXterm这类集成工具方便同时查看调试输出和串口日志我第一次搭建环境时犯了个典型错误——直接用了Ubuntu默认的gcc工具链结果编译出来的程序根本无法运行。后来才发现必须使用专门的RISC-V交叉编译工具链这个坑浪费了我整整一个下午。2. 裸机程序的基本框架解析裸机程序与常规嵌入式开发最大的区别在于没有任何操作系统支持所有硬件操作都要自己实现。对于D1s来说一个最简单的裸机程序需要包含以下几个关键部分2.1 启动文件(startup.s)这个汇编文件是芯片上电后执行的第一段代码主要完成.section .init .global _start _start: la sp, _stack_end // 设置栈指针 call main // 跳转到C语言的main函数 j . // 无限循环2.2 链接脚本(mem.ld)定义内存布局非常重要D1s的内存映射比较特殊MEMORY { RAM (rwx) : ORIGIN 0x40000000, LENGTH 64K } SECTIONS { .text : { *(.init) *(.text*) } RAM .data : { *(.data*) } RAM .bss : { *(.bss*) } RAM _stack_end ORIGIN(RAM) LENGTH(RAM); }2.3 最小化的main.c第一个程序我建议就点亮LED这能验证整个工具链是否正常工作#define GPIO_BASE 0x02000000 void main() { volatile unsigned int *gpio_ctrl (unsigned int*)(GPIO_BASE 0x30); *gpio_ctrl 0x01; // 设置GPIO模式 volatile unsigned int *gpio_data (unsigned int*)(GPIO_BASE 0x34); while(1) { *gpio_data ^ 0x01; // LED闪烁 for(int i0; i1000000; i); // 简单延时 } }3. 编译与烧录实战有了代码后编译过程需要特别注意3.1 交叉编译命令riscv64-unknown-elf-gcc -marchrv64imafdc -mabilp64d -nostartfiles \ -T mem.ld -o firmware.elf startup.s main.c3.2 生成二进制文件riscv64-unknown-elf-objcopy -O binary firmware.elf firmware.bin3.3 烧录到开发板DongshanPI-D1S支持USB烧录连接Type-C线后sudo sunxi-fel -v spl sun20i-d1-spl.bin sudo sunxi-fel -v write 0x40000000 firmware.bin sudo sunxi-fel -v exec 0x40000000我第一次烧录时遇到了权限问题解决方法是在/etc/udev/rules.d/下添加规则文件SUBSYSTEMusb, ATTR{idVendor}1f3a, MODE06664. 调试技巧与常见问题4.1 OpenOCD调试配置创建openocd.cfg文件adapter driver cmsis-dap transport select jtag source [find target/xuanTie_c910.cfg]启动调试会话openocd -f openocd.cfg4.2 GDB连接riscv64-unknown-elf-gdb firmware.elf (gdb) target remote :3333 (gdb) load (gdb) b main (gdb) c4.3 常见问题排查程序跑飞检查启动文件是否正确设置了栈指针LED不亮确认GPIO引脚号是否正确D1s的GPIO映射与全志其他系列不同烧录失败确保先烧录SPL引导程序再烧录主程序调试断点无效检查编译时是否带了-g选项我在调试时发现一个有趣的现象如果忘记在启动文件中设置栈指针程序会随机崩溃。这是因为C函数调用需要使用栈空间没有正确初始化栈指针会导致不可预知的行为。5. 进阶开发建议当第一个LED程序成功运行后可以尝试以下进阶实验5.1 添加串口输出#define UART0_BASE 0x02500000 void uart_putc(char c) { volatile unsigned int *uart_tx (unsigned int*)(UART0_BASE 0x00); while(!(*uart_tx 0x40000000)); // 等待发送就绪 *uart_tx c; } void main() { // 初始化UART volatile unsigned int *uart_clk (unsigned int*)(UART0_BASE 0x24); *uart_clk 0x0d; // 设置时钟分频 while(1) { uart_putc(H); uart_putc(i); uart_putc(\r); uart_putc(\n); } }5.2 使用中断D1s的中断控制器比较特殊需要先配置PLIC#define PLIC_BASE 0x10000000 void enable_interrupt() { // 设置外部中断优先级 volatile unsigned int *plic_prio (unsigned int*)(PLIC_BASE 0x2000); *plic_prio 1; // 使能中断 volatile unsigned int *plic_enable (unsigned int*)(PLIC_BASE 0x2080); *plic_enable 0x01; // 设置全局中断使能 asm volatile(csrs mie, %0 :: r(0x888)); }5.3 优化代码大小裸机开发时Flash空间有限可以添加编译选项优化-Os -ffunction-sections -fdata-sections -Wl,--gc-sections从第一个LED程序到能够处理中断、串口通信这个过程让我对RISC-V架构有了更深的理解。D1s作为一款性价比极高的RISC-V芯片其裸机开发体验既充满挑战又令人兴奋。