1、前言
计算机、单片机或者其他各嵌入式系统中包括各种各样的硬件,各类硬件通过系统中的各级总线进行互联,由 CPU 居中控制和协调,进而完成各类工作。 对硬件的控制包括总线上互联的各类硬件,如内存控制器、Flash 控制器、USB & PCIe 等各类高速总线、SPI、 I2C & UART 等各类低速外设,以及通过 IO 直接相连的 LED、开关器件等。 此外,CPU 本身也是硬件。
2、硬件连接方式
硬件的连接方式在不同的体系结构中存在差异,如 X86 平台中可以通过 IO 总线进行连接,也可以通过 PCIe 的 MMIO(Memory Mapping I/O,即内存映射 IO)机制连接(通常高速外设才会使用)。对于 ARM 架构来说,不存在 I/O 空间,因此只能通过内存映射的方式访问各类外设。以 Cortex-M3 处理器来说,其默认的内存映射方式如下: 由上图可见,外设(Peripheral)部分和软件代码(Code)一样,以不同的物理地址映射在 CPU 的 32 位地址空间中。因此软件就可以通过对特定地址的读写完成和硬件的交互了。
3、C 语言如何控制硬件
假设 0x40001000~0x40001FFF 地址处为一个 UART 控制器,其中 0x40001000 和 0x40001004 地址处均为一个 32 位的寄存器,分别是 UART 的数据发送寄存器和数据接收寄存器。如下代码即可完成 UART 中的数据收发:
volatile unsigned *rport = (unsigned *)0x40001000;
volatile unsigned *wport = (unsigned *)0x40001004;
*wport = value; // write to uart
value = *rport; // read from uart
添加 volatile 关键字的主要目的是防止编译器优化。 如果对 UART 的访问是在循环体中,不加 volatile 关键字时,可能只会有一次真实的读写操作发出。 硬件地址也可能是 8 bit、16 bit 或者 32 bit,要根据寄存器的位宽选择对应的数据类型。
当然,真实的硬件要复杂许多,要通过复杂的步骤才能完成特定操作,具体步骤及注意事项需要参考对应的 datasheet.
此外,CPU 本身的控制也可能以 memory mapping I/O 的方式实现(上图 Cortex-M3 的 Memory Map 中的 Private peripheral bus 部分就是 COU 内部的硬件单元,如 MPU、WatchDog、TIMER 等),对这类资源的控制和普通外设没有区别。
也有很多 CPU 相关的操作是通过特定的汇编指令实现,如 ARM 的内存屏障指令(DSB、ISB 等)、CACHE 维护指令、CPU 状态控制(特权模式切换、中断控制)指令等。
这类操作没有对应的 C 关键字定义,只能通过内嵌汇编的方式实现。