ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

TinyGo 中的 ARM SysTick 系统定时器:用中断驱动 LED 闪烁的完整示例解析

TinyGo 中的 ARM SysTick 系统定时器:用中断驱动 LED 闪烁的完整示例解析 TinyGo 中的 ARM SysTick 系统定时器用中断驱动 LED 闪烁的完整示例解析【免费下载链接】tinygoGo compiler for small places. Microcontrollers, WebAssembly (WASM/WASI), and command-line tools. Based on LLVM.项目地址: https://gitcode.com/GitHub_Trending/ti/tinygo导读本文围绕 TinyGo 仓库中位于 src/examples/systick/README.md 的官方示例展开深入讲解如何利用 ARM Cortex-M 处理器内置的 SysTick 系统定时器以中断方式驱动板载 LED 按 10Hz 频率闪烁。读完本文你将掌握 SysTick 定时器的硬件工作原理、TinyGo 提供的device/arm.SetupSystemTimer封装实现、中断处理函数与 Go 通道的协作模式以及如何在任意基于 ARM Cortex-M 的 TinyGo 目标板上运行并验证该示例。SysTick 示例概述TinyGo 官方在 src/examples/systick/ 目录下提供了一个短小精悍的示例它使用 ARM 系统定时器System Timer让 LED 闪烁。定时器每秒钟触发 10 次中断中断处理函数负责翻转 LED 的亮灭状态。该示例最直接的验证价值在于由于几乎所有 ARM Cortex-M 芯片从低端 M0 到高性能 M7/M33都内置了 SysTick 外设只要运行后 LED 能够规律闪烁就说明这块芯片拥有该定时器特性可以放心复用这套中断驱动的代码模式。提示SysTick 定时器基于处理器周期计数器cycle counter工作计数值越大LED 闪烁越慢同时该计数器只有 24 位宽这为周期值设定了上限也决定了闪烁频率的下限。这一特性在 README 中有明确说明下文会结合寄存器细节展开。完整示例代码解读示例由两个文件组成文件作用src/examples/systick/systick.go示例主体配置 LED、启动 SysTick、主循环消费定时器事件src/examples/systick/README.md官方说明文档核心代码如下package main import ( device/arm machine ) var timerCh make(chan struct{}, 1) func main() { machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput}) // timer fires 10 times per second arm.SetupSystemTimer(machine.CPUFrequency() / 10) for { machine.LED.Low() -timerCh machine.LED.High() -timerCh } } //export SysTick_Handler func timer_isr() { select { case timerCh - struct{}{}: default: // The consumer is running behind. } }整个程序可以分为四个部分理解。1. 缓冲区大小为 1 的通道中断与主循环之间的桥梁var timerCh make(chan struct{}, 1)中断上下文与主循环通过一个有缓冲的通道通信缓冲区容量为 1。这样设计有两个作用主循环每次-timerCh都会阻塞等待下一次 tick实现精确的 10Hz 节奏控制即使中断比主循环执行得更快例如主循环正在做耗时操作多出的 tick 事件也只会被丢弃而不会阻塞中断处理避免在 ISR 中发生调度。2. 配置 LED 引脚为输出模式machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput})machine.LED是 TinyGo 的 machine 包为每个开发板预定义的 LED 引脚常量。不同目标板的定义分散在各板级文件中例如 src/machine/board_feather_rp2040.go 中const LED GPIO13、src/machine/board_challenger_rp2040.go 中LED GPIO24等读者可以在src/machine/目录下找到自己目标板的定义。3. 启动 SysTick 定时器每 1/10 秒触发一次中断arm.SetupSystemTimer(machine.CPUFrequency() / 10)这是整个示例的关键一行。machine.CPUFrequency()返回当前 CPU 的主频单位 Hz除以 10 后得到每 1/10 秒对应的处理器周期数即 SysTick 的重装载值。定时器每计满该数值就触发一次SysTick_Handler中断从而实现 10 次/秒的中断频率。4. 主循环按 tick 节奏交替拉低/拉高 LEDfor { machine.LED.Low() -timerCh machine.LED.High() -timerCh }主循环先拉低 LED等待一次 tick 后再拉高再等待一次 tick如此循环。因为每两个 tick 才完成一次完整的亮灭周期所以 LED 实际以 5Hz每秒 5 次完整闪烁的频率工作视觉上即为标准的心跳闪烁效果。5. 中断处理函数SysTick_Handler//export SysTick_Handler func timer_isr() { select { case timerCh - struct{}{}: default: // The consumer is running behind. } }通过//export SysTick_Handler注释TinyGo 编译器会将这个 Go 函数暴露为 ARM 中断向量表中的SysTick_Handler入口。当中断发生时它尝试向timerCh写入一个空结构体如果通道已满说明主循环还没消费完上一次事件则走default分支直接丢弃本次事件保证 ISR 永远是非阻塞、有界、无锁的。SysTick 定时器的硬件原理与 TinyGo 实现什么是 SysTickSysTick 是 ARM Cortex-M 处理器内核自带的一个简单定时器在 ARMv6-M / ARMv7-M / ARMv8-M 架构中均有提供它不依赖任何芯片外设因此几乎每一块 Cortex-M 芯片都具备。它由一个从重装载值向下递减的 24 位计数器构成递减到 0 时可触发异常中断并自动重新装载。TinyGo 在 src/device/arm/arm.go 中为它定义了寄存器结构与位域常量type SYST_Type struct { SYST_CSR volatile.Register32 SYST_RVR volatile.Register32 SYST_CVR volatile.Register32 SYST_CALIB volatile.Register32 }对应 ARM 定义的四个寄存器寄存器名称作用SYST_CSRSysTick 控制与状态寄存器使能定时器ENABLE、使能中断TICKINT、选择时钟源CLKSOURCE、查询计数到 0 标志COUNTFLAGSYST_RVR重装载值寄存器24 位计数到 0 后重新装载的周期数SYST_CVR当前计数值寄存器24 位当前递减计数值写任意值即清零SYST_CALIB校准值寄存器10ms 校准值TENMS及相关标志位域常量定义位于 src/device/arm/arm.go 中例如SYST_CSR_ENABLE 0x1使能定时器SYST_CSR_TICKINT 0x2计数到 0 时触发 SysTick 异常SYST_CSR_CLKSOURCE 0x4使用处理器时钟而非外部参考时钟SYST_RVR_RELOAD_Msk 0xffffff重装载值 24 位掩码即最大周期数为 2^24-1 16777215。SetupSystemTimerTinyGo 的官方封装示例中调用的arm.SetupSystemTimer(cyclecount uint32)实现在 src/device/arm/arm.go源码如下// Set up the system timer to generate periodic tick events. // This will cause SysTick_Handler to fire once per tick. // The cyclecount parameter is a counter value which can range from 0 to // 0xffffff. A value of 0 disables the timer. func SetupSystemTimer(cyclecount uint32) error { // turn it off SYST.SYST_CSR.ClearBits(SYST_CSR_TICKINT | SYST_CSR_ENABLE) if cyclecount 0 { // leave the system timer turned off. return nil } if cyclecountSYST_RVR_RELOAD_Msk ! cyclecount { // The cycle refresh register is only 24 bits wide. The user-specified value will overflow. return errCycleCountTooLarge } // set refresh count SYST.SYST_RVR.Set(cyclecount) // set current counter value SYST.SYST_CVR.Set(cyclecount) // enable clock, enable SysTick interrupt when clock reaches 0, run it off of the processor clock SYST.SYST_CSR.SetBits(SYST_CSR_TICKINT | SYST_CSR_ENABLE | SYST_CSR_CLKSOURCE) return nil }该函数内部依次完成四件事先关闭清除TICKINT与ENABLE位确保重新配置期间不会产生意外中断零值关闭若cyclecount 0保持定时器关闭并直接返回因此传 0 可用于停用 SysTick溢出检查因为重装载寄存器只有 24 位若传入值大于0xffffff会返回errCycleCountTooLarge错误——这正是 README 中24 位计数器限制周期数上限的源码级印证。计算周期值时应保证CPUFrequency() / 期望频率 ≤ 0xffffff例如 72MHz 主频下最低可达约 2.3Hz周期 0xffffff若想以更低的频率触发就需要换用其他定时器方案写入并启动设置重装载值SYST_RVR、清零当前值SYST_CVR最后同时置位TICKINT | ENABLE | CLKSOURCE即以处理器时钟为源、计数到 0 时触发中断并自动重装实现周期性 tick。中断向量如何绑定到 Go 函数//export SysTick_Handler是 TinyGo 的导出机制编译器会将该符号放入中断向量表对应槽位。在 TinyGo 的标准库中某些使用 SysTick 实现时间基准的运行时文件也采用了完全相同的模式例如 src/runtime/runtime_mimxrt1062_time.go 与 src/runtime/time_nxpmk66f18.go 中均以//go:export SysTick_Handler导出中断入口。这说明该示例采用的写法与 TinyGo 运行时内部的时间驱动机制一脉相承——SysTick 在 TinyGo 生态里既是教学示例也是真实时间基准的来源之一。如何编译、烧录与验证编译在示例目录下执行以 bluepill 开发板为例它基于 STM32F103 的 Cortex-M3 内核tinygo build -o systick.hex -targetbluepill ./src/examples/systick/-target参数可以替换为src/targets/目录下任意基于 ARM Cortex-M 的目标板例如picoRP2040Cortex-M0、feather-m0ATSAMD21Cortex-M0、nucleo-f103rb、microbit等。编译产物为systick.hex。烧录TinyGo 支持直接烧录大部分开发板tinygo flash -targetbluepill ./src/examples/systick/或使用与目标板匹配的烧录工具如openocd、dfu-util、uf2conv等刷写生成的systick.hex。验证上电后观察板载 LED如果 LED 以约 0.2 秒亮 / 0.2 秒灭的节奏每秒 5 次完整闪烁规律闪动即证明该芯片具备 SysTick 系统定时器定时器按CPUFrequency() / 10的周期正确触发中断SysTick_Handler中断处理函数与 Go 通道的事件传递链路工作正常。调整闪烁频率LED 的完整亮灭周期需要两个 tick因此闪烁频率 中断频率 / 2 CPUFrequency() / cyclecount / 2。若希望更快或更慢只需调整传给SetupSystemTimer的除数改为arm.SetupSystemTimer(machine.CPUFrequency() / 100)中断频率 100HzLED 每秒完整闪烁 50 次肉眼将看到连续点亮频闪改为arm.SetupSystemTimer(machine.CPUFrequency() / 2)中断频率 2HzLED 每秒完整闪烁 1 次节奏明显放慢传0可完全关闭 SysTickLED 停留在当前状态不再变化。从示例到实战可复用的中断驱动模式该示例虽小却演示了嵌入式 Go 开发中一套非常重要的模式——用有界通道把中断事件安全地传递给业务主循环硬件侧arm.SetupSystemTimer完成 SysTick 的初始化周期性产生中断中断侧SysTick_Handler通过非阻塞的select写入通道永不阻塞、永不丢失数据宁可丢弃旧事件也不影响 ISR 及时返回业务侧主循环以-timerCh为节拍源按固定时间片执行任务。这套模式可以直接推广到任意周期性任务如定时采样传感器、周期刷新显示、软件定时轮询按键去抖等。相比time.Sleep忙等基于 SysTick 中断的方案在 CPU 空闲时不会空转且不依赖操作系统定时器抽象非常适合 TinyGo 这种面向资源受限 MCU 的裸机 Go 运行时。小结SysTick 是 ARM Cortex-M 内核自带的 24 位递减计数器几乎所有 Cortex-M 芯片都具备运行示例时 LED 能闪即证明芯片支持示例通过arm.SetupSystemTimer(machine.CPUFrequency() / 10)让定时器每秒触发 10 次中断SysTick_Handler将事件写入容量为 1 的通道主循环按 tick 节奏驱动 LED 亮灭24 位计数器的上限0xffffff约束了周期值范围设置过大会返回errCycleCountTooLarge错误中断处理函数使用带default分支的select写入通道保证 ISR 非阻塞这是值得在真实项目中复用的设计。参考资源示例源码src/examples/systick/systick.go官方说明src/examples/systick/README.mdSysTick 寄存器与SetupSystemTimer实现src/device/arm/arm.go运行时中同样的SysTick_Handler导出模式src/runtime/runtime_mimxrt1062_time.go、src/runtime/time_nxpmk66f18.go【免费下载链接】tinygoGo compiler for small places. Microcontrollers, WebAssembly (WASM/WASI), and command-line tools. Based on LLVM.项目地址: https://gitcode.com/GitHub_Trending/ti/tinygo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表