折腾 RISC-V 单片机 Part1
大约半年前在 SparkFun 上买了一块 RED-V 开发板。基本算是 HiFive1 Rev B 的克隆,比 Rev B 便宜一些,同样使用 SiFive 的 FE310-G002 处理器。SiFive 的 MCU 自带一套 SDK,不过要搭配指定的 IDE 才好用,作为有洁癖的 Linux 用户这当然是不行的,所以就有了这次的折腾。
准备工作
我一开始是打算用 SDK 的代码,不过看了一圈感觉这样和玩 Arduino 也没区别了,失去了折腾的意义,所以决定放弃 SDK 直接用汇编艹寄存器。
这里是需要准备的文档和工具:
- SparkFun RED-V Schematic: 这是板子的电路图,用来看板子上的接口都具体连接到 MCU 的哪个针脚。
- Freedom E310-G002 Datasheet/Manual: 这两份文件可以从 SiFive 的网站上下载到,包含几乎所有重要信息。
- Clang: 编译器。为啥不用 GCC?因为我不想单独折腾一份 GCC 的工具链。Clang 既可以编译出 x86 程序又可以编译出 RISC-V 程序,从软件源里装好就可以直接用了。
- LLD: LLVM Linker。这下也不用配置 RISC-V 的 binutils 了
- OpenOCD: 负责和板子通信,烧录程序,调试等。
三份 PDF 文档需要自己下载,而其他三个程序应该能直接用包管理装。
单片机基础
简单而言,单片机不像 CPU,有各种内存保护/分页机制等。所有的硬件控制/非易失存储(硬盘)/易失存储(内存)都位于同一个 Memory space。
详细信息可以在 Manual Chapter 4 中看到,比如说,[0x20000, 0x21FFF]
就属于 “OTP Memory Region”。
另外一些地址可以用于控制硬件,比如[0x1001_2000, 0x1001_2FFF]
属于 GPIO,稍后可以看到,如果往某个特定的地址写1
,那么芯片上的某个针脚的电平(电压)就会发生变化,如果这个针脚上连了一个 LED,那么它的亮灭状态也会发生变化。这些可以控制硬件行为的地址我一般称之为寄存器,注意不要和汇编语言里的寄存器搞混了。
程序应该放在哪儿?
一个重要的问题是,单片机在启动的时候到底会执行哪条指令?查看 Manual Chapter 5.1 可知:
On power-on, the core's reset vector is 0x1004.
即会首先执行 0x1004 处的指令(练习:请翻阅 Memory Map 查看该地址属于哪片区域?)并且手册也列出了预先烧录在该地址的指令列表。不过很可惜我并没有找到文档里所说的 MSEL pin
在哪里,不过从调试结果来看,0x1018处的jr指令最终会跳转到0x10000处。
继续阅读 Manual 5.1.1, 0x10000处的指令会跳转到0x20000,继续阅读Datasheet Chapter 5 “Boot code”,我们最终会跳转到地址0x20000000,查看memory map,该地址属于QSPI 0 Flash,也就是非易失存储器,看上去这里就是我们应该写入代码的地方了。(PC程序员初次看到Program counter能直接指向外部存储设备实在是刷新三观)。
编程器
向单片机写入程序的过程被称作 program(编程),所以用来编程的硬件也就叫做“编程器”了。还有一个我无论如何都无法理解的翻译叫做“仿真器”,英文叫 emulator, 这应该也是一个上古词汇,但是比起“编程器”,这个词更强调你可以调试单片机上的程序,比如下断点,单步执行等。现在一般用来指链接单片机和电脑的那根线。总而言之,你需要一个东西把单片机和电脑连起来,以便两者通信。
一般来说,这个硬件一头用JTAG等方式连接到单片机,另一头通过USB协议连接电脑,OpenOCD则是一个开源的程序允许你通过这个“编程器”以各种方式操纵各种单片机。当然也包括向 SPI Flash 中写入我们的程序。
在 RED-V 上,最大的芯片是一块 MK22FN128 的ARM单片机,这块单片机里预先写入了一个 J-Link OB 的商业程序,这个程序让这个ARM单片机实现了编程器的功能,所以我们只要直接用普通的USB线连接板子和电脑就可以了。J-Link 使用一种特殊的协议与电脑通信,好在Segger公司公开了这种协议的spec,所以OpenOCD也能和编程器通信啦。
TLDR
- 单片机通电后会经过一系列跳转,最终开始执行 0x20000000 处的指令。
- 该地址实际访问的是外部的存储器。
- 可以通过编程器向该存储器中写入数据。
To be continued in part 2.