第1章:裸机程序开发——锐龙平台最小系统搭建、GPIO驱动开发、UART串口通信实现
各位同学,欢迎来到《AMD实时操作系统在锐龙平台移植实战》的第一章。
说实话,很多做嵌入式的老手,一听到「锐龙平台」四个字,第一反应就是——这玩意儿能跑裸机?能直接操作GPIO?
能。而且我告诉你,不仅能用,还很好用。
这一章,我们就从零开始,在锐龙平台上搭建一个最小系统。然后点亮一颗LED,最后用UART跟PC说一句「Hello World」。嗯,就是这么实在。
1.1 锐龙平台的最小系统长什么样?
先别急着写代码。你想想看,一个CPU要跑起来,至少需要什么?
- 电源——得供电吧?
- 时钟——得有个心跳吧?
- 复位——得知道从哪开始跑吧?
- 内存——得有个地方放代码和数据吧?
锐龙平台也不例外。只不过,它把这些东西都集成在了SoC内部。你不需要像当年搞STM32那样,自己画个晶振电路、搞个复位芯片。锐龙内部有PLL,有Boot ROM,有L2/L3缓存。上电之后,它自己就能从SPI Flash或者eMMC里把代码搬进内存,然后跳过去执行。
核心要点:锐龙平台的最小系统 = 供电 + 时钟 + 复位 + 内存 + 启动介质。缺一不可。
我在项目中遇到过一位同事,他以为锐龙跟单片机一样,上电就能跑。结果焊好板子,发现CPU纹丝不动。查了半天,原来是供电时序没满足——锐龙对电压的上电顺序有严格要求,VDDCR_SOC必须先于VDDCR_CPU稳定。嗯,这个坑我替你们踩过了。
1.2 搭建最小系统的硬件准备
我个人习惯,在开始任何裸机开发之前,先把硬件环境理清楚。锐龙平台做裸机,你至少需要:
| 组件 | 说明 | 备注 |
|---|---|---|
| 锐龙CPU(如Ryzen 4000/5000系列) | 目标芯片 | 建议带G后缀,有集成显卡方便调试 |
| DDR4/DDR5内存条 | 运行代码和数据 | 至少4GB,推荐8GB |
| SPI Flash(如W25Q128) | 存放裸机程序 | 16MB以上,支持Quad SPI |
| USB转UART模块(如CH340/FT232) | 串口通信 | 波特率115200起步 |
| LED + 限流电阻 | GPIO输出测试 | 220Ω电阻,3.3V供电 |
| JTAG/SWD调试器(如J-Link) | 调试和下载 | 锐龙支持JTAG,但需要转接板 |
你可能会问:为什么不用开发板?
其实市面上锐龙的开发板不多,而且贵。我建议你直接买一块锐龙笔记本主板或者迷你主机主板,引出必要的引脚,自己搭一个最小系统。我在项目里就是这么干的——一块报废的笔记本主板,飞了几根线,就成了裸机开发平台。省钱,还练手。
1.3 启动流程:从Reset到main()
锐龙CPU上电后,会经历这么几步:
- 硬件复位——CPU内部逻辑初始化,所有寄存器回到默认值。
- Boot ROM执行——锐龙内部有一段不可修改的Boot ROM代码,它会检测启动介质(SPI Flash、eMMC、UART等),把第一段代码加载到L2缓存中。
- 跳转到入口点——Boot ROM找到有效的启动镜像后,跳转到镜像的入口地址(通常是0xFFFFF000附近)。
- 初始化C环境——设置栈指针、清零BSS段、初始化数据段。
- 调用main()——终于,你的代码开始执行。
小技巧:锐龙的Boot ROM支持从UART直接下载程序。这意味着你甚至不需要SPI Flash,一根USB转UART线就能把程序灌进去。我刚开始调试时就用这个方式,省去了反复烧写Flash的麻烦。
说白了,锐龙的启动流程跟ARM Cortex-A系列很像。如果你做过ARM Linux移植,那这套流程你肯定不陌生。区别在于,锐龙的Boot ROM更封闭——你不能修改它,只能按照它的规则来。
1.4 GPIO驱动开发——点亮第一颗LED
好,理论说完了,咱们来点实际的。
锐龙平台的GPIO,是通过芯片组(如Promontory)或者SoC内部的GPIO控制器来管理的。以Ryzen 4000系列为例,它的GPIO控制器挂在LPC总线上,基地址通常是0xFEDC0000。
每个GPIO引脚由两个寄存器控制:
- GPIO_OUT——输出数据寄存器,写1拉高,写0拉低。
- GPIO_DIR——方向寄存器,1表示输出,0表示输入。
下面是一个最简单的GPIO点灯代码:
// 定义GPIO寄存器基地址
#define GPIO_BASE 0xFEDC0000
#define GPIO_OUT_OFFSET 0x00
#define GPIO_DIR_OFFSET 0x04
// 定义LED引脚(假设是GPIO 12)
#define LED_PIN 12
void gpio_init(void)
{
unsigned int *dir_reg = (unsigned int *)(GPIO_BASE + GPIO_DIR_OFFSET);
// 设置LED引脚为输出
*dir_reg |= (1 << LED_PIN);
}
void gpio_set_led(int on)
{
unsigned int *out_reg = (unsigned int *)(GPIO_BASE + GPIO_OUT_OFFSET);
if (on) {
*out_reg |= (1 << LED_PIN); // 拉高,点亮
} else {
*out_reg &= ~(1 << LED_PIN); // 拉低,熄灭
}
}
void delay(volatile int count)
{
while (count--);
}
void main(void)
{
gpio_init();
while (1) {
gpio_set_led(1);
delay(1000000);
gpio_set_led(0);
delay(1000000);
}
}
注意:锐龙平台的GPIO寄存器访问,必须使用32位读写。不要用8位或16位操作,否则可能引发总线错误。我曾经因为这个bug,查了整整两天——LED死活不亮,最后发现是代码里用了*(volatile unsigned char*)来写寄存器。嗯,血的教训。
这段代码编译后,烧写到SPI Flash里,上电复位。如果一切正常,你会看到LED以大约1Hz的频率闪烁。恭喜你,锐龙平台的最小系统跑起来了。
1.5 UART串口通信实现
LED闪烁虽然酷,但没法输出调试信息。接下来,我们把UART串口搞通。
锐龙平台内部集成了多个UART控制器,通常挂在LPC总线或者PCIe总线上。以最常见的UART0为例,它的寄存器基地址是0x3F8(传统PC兼容地址)。
UART的寄存器不多,但每个都很关键:
| 偏移 | 寄存器 | 功能 |
|---|---|---|
| 0x00 | RBR/THR | 接收缓冲/发送保持 |
| 0x01 | IER | 中断使能 |
| 0x02 | IIR/FCR | 中断标识/FIFO控制 |
| 0x03 | LCR | 线路控制(数据位、停止位、校验位) |
| 0x04 | MCR | 调制解调器控制 |
| 0x05 | LSR | 线路状态(是否有数据可读、是否可发送) |
| 0x06 | MSR | 调制解调器状态 |
| 0x07 | SCR | 暂存寄存器 |
初始化UART的步骤很简单:
- 设置波特率(通过分频寄存器DLH/DLL)。
- 配置数据格式(8位数据、1位停止位、无校验)。
- 使能FIFO(可选,但推荐)。
- 检查LSR寄存器,确认可以发送。
来看代码:
#define UART_BASE 0x3F8
void uart_init(int baudrate)
{
unsigned int divisor = 115200 / baudrate; // 假设基准时钟1.8432MHz
// 设置DLAB位(除数锁存访问位)
unsigned char lcr = *(volatile unsigned char *)(UART_BASE + 3);
lcr |= 0x80;
*(volatile unsigned char *)(UART_BASE + 3) = lcr;
// 写分频值
*(volatile unsigned char *)(UART_BASE + 0) = divisor & 0xFF; // DLL
*(volatile unsigned char *)(UART_BASE + 1) = (divisor >> 8) & 0xFF; // DLH
// 清除DLAB位,设置8N1
lcr = 0x03; // 8位数据,1位停止,无校验
*(volatile unsigned char *)(UART_BASE + 3) = lcr;
// 使能FIFO
*(volatile unsigned char *)(UART_BASE + 2) = 0x07;
}
void uart_putchar(char c)
{
// 等待发送缓冲区空
while (!(*(volatile unsigned char *)(UART_BASE + 5) & 0x20));
*(volatile unsigned char *)(UART_BASE + 0) = c;
}
void uart_puts(const char *str)
{
while (*str) {
uart_putchar(*str++);
}
}
void main(void)
{
gpio_init();
uart_init(115200);
uart_puts("Hello from AMD Ryzen!\r\n");
while (1) {
gpio_set_led(1);
delay(1000000);
gpio_set_led(0);
delay(1000000);
}
}
提示:UART的基准时钟频率,不同锐龙型号可能不一样。有的用1.8432MHz,有的用24MHz。我建议你先用示波器量一下UART的TX引脚,看看波特率是否准确。如果串口输出乱码,十有八九是分频值算错了。
把这段代码烧进去,打开串口助手(比如PuTTY或者SecureCRT),设置波特率115200、8N1。按下复位键,你应该能看到:
Hello from AMD Ryzen!
嗯,那一刻的感觉,怎么说呢——就像你第一次在51单片机上点亮LED一样,兴奋。
1.6 避坑指南
最后,我总结几个锐龙裸机开发常见的坑:
- 缓存问题:锐龙默认开启L1/L2缓存。如果你直接操作外设寄存器,一定要用volatile关键字,并且考虑缓存一致性。我建议在初始化阶段暂时关闭数据缓存,等MMU和缓存管理成熟了再打开。
- 内存映射:锐龙的内存空间很大,但有些地址是保留的。别随便往0x00000000写东西,那是Boot ROM的地盘。
- 中断控制器:锐龙使用APIC(高级可编程中断控制器),跟ARM的GIC完全不同。如果你后面要做RTOS移植,APIC的初始化是个大头。
- 电源管理:锐龙有复杂的电源状态(C状态、P状态)。裸机下,我建议先把所有核心固定在最高性能状态,省得CPU自己睡过去,你的代码就卡死了。
曾经有一次:我在调试UART时,发现发送几个字符后,串口就再也不动了。查了半天,发现是CPU进入了C1状态,UART时钟被关了。解决办法是在启动时通过MSR寄存器禁用C状态。嗯,这种问题,手册上写得清清楚楚,但你不遇到一次,根本不会往那想。
1.7 本章小结
这一章,我们从零开始,在锐龙平台上搭建了最小系统。你学会了:
- 锐龙平台的硬件组成和启动流程
- 如何操作GPIO寄存器点亮LED
- 如何初始化UART并实现串口输出
说白了,这些就是裸机开发的「三板斧」。有了它们,你就能跟硬件对话了。
下一章,我们会深入锐龙的中断系统,为RTOS移植打下基础。到时候,你会发现——裸机只是热身,真正的挑战才刚刚开始。
好,今天就到这里。动手试试吧,别光看代码。烧进去,跑起来,你才能真正理解我说的一切。