外设

什么是外围设备?

大多数微控制器都是SoC,不仅仅具有CPU,RAM或闪存,芯片内部还集成了各种设备,用于与外部系统进行交互. 比如通过传感器,电机控制器直接或间接与周围环境进行交互。 或其他的人机界面,例如显示器或键盘。这些组件统称为外围设备。

这些外围设备很有用,因为它们使开发人员可以将一部分工作分派出去,而不必全部由软件来实现。与台式机开发人员将图形处理任务分派给显卡的方式类似,嵌入式开发人员可以将某些任务分派到外围设备,从而使CPU可以将时间花在更重要的事情上,或者不做任何事以节省功耗。

如果您看一下1970年代或1980年代的老式家用计算机中的主板(实际上,以前的台式机与今天的嵌入式系统相去不远),您会看到:

  • 处理器
  • RAM芯片
  • ROM芯片
  • I/O控制器

RAM芯片,ROM芯片和I/O控制器(此系统中的外围设备)通过“总线”连接到处理器。处理器通过地址总线选择与哪个设备进行通信,通过数据总线传输数据。在我们的嵌入式微控制器中,原理都是一样的--只是将所有内容包装在一块芯片内。

但是与显卡不同的是,显卡一般提供了像Vulkan,Metal或OpenGL之类的软件API,而嵌入式外设通过内存映射的方式,直接将硬件接口暴露给我们的微控制器。

线性和物理内存地址空间

在微控制器上,将一些数据写入任意地址,例如0x4000_00000x0000_0000,也可能是完全有效的操作。

与嵌入式系统不同,在台式机系统上,对内存的访问由内存管理单元(MMU)严格控制,MMU有两个主要职责:强制执行对内存的访问权限(防止一个进程读取或修改另一进程的内存);并将物理内存的地址重新映射到软件中使用的虚拟内存地址。微控制器通常没有MMU,而仅使用实际物理地址。

尽管32位微控制器具有从0x0000_0000到0xFFFF_FFFF的物理和线性地址空间,但它们通常仅使用该范围的几百K字节作为实际内存。这留下了大量的可用地址空间。在前面的章节中,我们讨论了位于地址“0x2000_0000”上的RAM。如果我们的RAM大小为64 KiB(即最大地址为0xFFFF),则地址“0x2000_0000”到“0x2000_FFFF”将对应于我们的RAM。当我们写入位于地址“0x2000_1234”的变量时, 某些逻辑检测地址的上半部分(在此示例中为0x2000),然后激活RAM控制器,由RAM控制器来处理地址的下半部分(在这种情况下为0x1234)。在Cortex-M上,我们将Flash ROM映射到地址“0x0000_0000”到地址“0x0007_FFFF”之间(如果我们有512 KiB Flash ROM)。微控制器设计人员没有忽略这两个区域之间的剩余地址空间,而是将某些内存位置映射给了外设。最终看起来像这样:

Nordic nRF52832手册(pdf)

内存映射的外围设备

乍看之下,与这些外设的交互非常简单--将正确的数据写入正确的地址。例如,通过串行端口发送32位数据可能与将32位数据写入某个内存地址一样直接。写入后,串口外设将接管并自动发送数据。

这些外设的配置工作与内存操作类似。无需调用函数来进行配置一个外设,而是公开了一块用作配置硬件的内存区域。比如将“0x8000_0000”写入SPI频率配置寄存器,SPI端口将以每秒8兆位的速度发送数据。将“0x0200_0000”写入相同的地址,SPI端口将以每秒125 Kilobits的速度发送数据。这些配置寄存器看起来像这样:

Nordic nRF52832手册(pdf)

无论使用哪种语言,无论该语言是Assembly,C还是Rust,都是这样与硬件进行交互。