Cortex-M 是 ARM 面向微控制器 (MCU) 领域的核心架构,其发展至今已有约二十年历史。其中,Cortex-M3 内核堪称经典,是目前市场上应用最广泛的内核之一。
而截至目前,Arm 推出的 Cortex-M85 内核依然是其 Cortex-M 系列中性能最强大的成员。

Cortex-M85 相较于经典的 Cortex-M3 内核,在功能和性能上都有了大幅升级。那么,对于普通的嵌入式开发者而言,使用基于 Cortex-M85 和 Cortex-M3 内核的单片机,在编程层面究竟有哪些异同呢?
cm3.h 与 cm85.h 的核心文件对比
对于单片机开发者来说,最常接触的内核相关文件莫过于 core_cm3.h 或 core_cm85.h。这些头文件定义了与内核相关的大部分功能,我们日常调用的许多底层接口也源于此。
首先,我们可以直观地对比一下这两个源文件:

通过对比源代码可以清晰地看到,cm85.h 的文件行数(4672行)远多于 cm3.h(1943行)。虽然左侧标记的差异部分看起来很多,但其中大部分是新增的代码行和宏定义。
如果仔细比对,你会发现许多基础函数是完全一致的。例如,常用的系统复位函数 __NVIC_SystemReset,其在两个文件中的实现几乎一模一样:

__NO_RETURN __STATIC_INLINE void __NVIC_SystemReset(void)
{
__DSB(); /* Ensure all outstanding memory accesses included
buffered write are completed before reset */
SCB->AIRCR = (uint32_t)((0x5FAUL << SCB_AIRCR_VECTKEY_Pos) |
(SCB->AIRCR & SCB_AIRCR_PRIGROUP_Msk) |
SCB_AIRCR_SYSRESETREQ_Msk ); /* Keep priority group unchanged */
__DSB(); /* Ensure completion of memory access */
for(;;) /* wait until reset */
{
__NOP();
}
}
再比如,作为本文重点的系统滴答定时器配置函数 SysTick_Config,其实现代码在两个内核中也高度一致:

__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk)
{
return (1UL); /* Reload value impossible */
}
SysTick->LOAD = (uint32_t)(ticks - 1UL); /* set reload register */
NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); /* set Priority for Systick Interrupt */
SysTick->VAL = 0UL; /* Load the SysTick Counter Value */
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */
return (0UL); /* Function successful */
}
由此可见,在 Cortex-M3 单片机上常用的这些核心函数接口,在 Cortex-M85 上基本被原样保留。这充分说明了 Cortex-M85 在软件接口层面做到了对 Cortex-M3 的良好向下兼容,降低了开发者的迁移成本。
Cortex-M85 单片机上的 SysTick 实战
定时器是单片机开发中最常用的模块之一,无论是实现精准延时,还是作为实时操作系统 (RTOS) 的心跳时钟,都离不开它。为此,Arm 在所有 Cortex-M 内核中都集成了一个标准的 SysTick 定时器模块。
下面,我们结合瑞萨电子的 RA8D1(基于 Cortex-M85 内核)单片机,手把手演示 SysTick 的实际用法。
开发环境与工具准备
本次演示使用瑞萨的 e2 studio 集成开发环境及其配套的 FSP 软件包。我们将通过一个简单的 GPIO 翻转示例,来验证 SysTick 的延时精度。
1. 创建 RA8D1 单片机项目
首先,在 e2 studio 中新建一个 C/C++ 项目,并选择对应的“Renesas RA C/C++ Project”模板。


将项目命名为 RA8D1_SysTick。

在设备选择页面,找到并选中我们目标芯片的型号:R7FA8D1BEC。

后续依次选择项目类型(非安全项目)、构建工件类型(可执行文件)、RTOS(无)和项目模板(Bare Metal - Minimal)。这些步骤只需按照向导默认或推荐选项点击“下一步”即可。



最终,一个完整的、包含必要BSP和CMSIS支持的工程就创建好了。

2. 配置工程参数
我们需要配置一个 GPIO 引脚用于测试。这里选择 PA01 引脚,并将其配置为通用输出模式。

接着,配置系统时钟树。一个正确的时钟配置是 SysTick 能够准确计时的基础。

为了方便烧录,我们还可以在工具链设置中,勾选生成 Intel HEX 格式的输出文件。

3. 编写代码并演示效果
在自动生成的 hal_entry.c 文件的主循环中,我们添加两行代码:一行用于翻转 PA01 引脚电平,另一行调用 SysTick 延时函数。
while(1)
{
R_PORT10->PODR ^= 1<<(BSP_IO_PORT_10_PIN_01 & 0xFF); // PA01电平翻转
R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_MILLISECONDS); // SysTick延时1毫秒
}

编译并下载程序到开发板后,使用示波器或逻辑分析仪测量 PA01 引脚波形。
当设置为 1毫秒 翻转一次时,波形周期为 2ms,频率 500Hz,占空比 50%。在这个时间尺度下,SysTick 的延时误差极小,几乎可以忽略不计(使用 100kHz 采样率观测不到明显误差)。

如果将采样率提升至 100MHz,则能观察到纳秒级别的微小误差。当然,这类误差是由晶振精度、软件开销等多种因素共同造成的,相对于毫秒级的延时,其影响微乎其微。

4. 关键源码解析
为了让代码执行效率最高、软件误差最小,示例中直接通过操作寄存器来翻转 GPIO:
R_PORT10->PODR ^= 1<<(BSP_IO_PORT_10_PIN_01 & 0xFF);
而延时所调用的 R_BSP_SoftwareDelay 函数,是 FSP 软件包提供的一个阻塞式延时接口,其延时单位通过第二个参数指定:
R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_MILLISECONDS);
其中 BSP_DELAY_UNITS_MILLISECONDS 是一个枚举值,系统共定义了三种时间单位:
typedef enum
{
BSP_DELAY_UNITS_SECONDS = 1000000, ///< 延时单位为秒
BSP_DELAY_UNITS_MILLISECONDS = 1000, ///< 延时单位为毫秒
BSP_DELAY_UNITS_MICROSECONDS = 1 ///< 延时单位为微秒
} bsp_delay_units_t;
R_BSP_SoftwareDelay 函数的本质就是利用 SysTick 定时器实现的精准延时。 查看其底层实现(如下图所示)可以发现,它通过计算系统时钟频率和所需延时周期,最终在循环中查询或等待 SysTick 的计数,这与在 Cortex-M0/M23 等内核上使用 SysTick 的原理是完全一致的。

因此,从实际使用的角度来看,在 Cortex-M85 上使用 SysTick 进行延时,与在 Cortex-M3 上并无二致。这再次印证了 Arm 在 计算机架构 设计上对软件生态兼容性的重视,确保了开发者能够在升级硬件平台的同时,最大化地复用已有的知识和代码。