ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

一,传感器控制LED

一,传感器控制LED 在开始编写代码之前我们首先需要完成硬件电路的搭建。传感器控制 LED 的核心思路是通过传感器感知外部环境变化将物理信号转换为电信号再由 STM32 微控制器读取该信号并根据预设逻辑控制 LED 的亮灭状态。具体来说我们需要将传感器的信号输出引脚连接到 STM32 的某个 GPIO 输入端口同时将 LED 的正极通过限流电阻连接到另一个 GPIO 输出端口。这样当传感器检测到目标事件时STM32 就能通过读取输入引脚的电平状态来决定是否翻转输出引脚从而控制 LED 的亮灭。上图展示了完整的电路连接方式。在接线时需要注意传感器的供电电压必须与 STM32 的 GPIO 电平兼容通常为 3.3VLED 的限流电阻一般取 330Ω 到 1kΩ 之间以防止电流过大烧毁 LED。代码电路搭建完成后接下来就是编写控制代码。在 STM32 的 HAL 库开发框架下我们通常使用 CubeMX 进行图形化配置自动生成初始化代码然后在用户代码区编写具体的业务逻辑。读取引脚状态输出引脚状态读取引脚状态的核心函数是HAL_GPIO_ReadPin()它用于读取指定 GPIO 引脚的电平状态返回GPIO_PIN_SET高电平或GPIO_PIN_RESET低电平。而输出引脚状态则使用HAL_GPIO_WritePin()或HAL_GPIO_TogglePin()函数。下面是一个典型的读取与输出示例// 读取传感器引脚状态 GPIO_PinState sensorState HAL_GPIO_ReadPin(SENSOR_GPIO_Port, SENSOR_Pin); // 根据传感器状态控制 LED if (sensorState GPIO_PIN_SET) { HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET); // 点亮 LED } else { HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET); // 熄灭 LED }这种轮询方式实现简单但 CPU 需要不断循环检测引脚状态效率较低。如果希望提高响应速度并降低 CPU 占用可以考虑使用中断方式也就是接下来要介绍的内容。中断与 SysTick中断是嵌入式系统中非常重要的机制。所谓中断就是当某个事件发生时CPU 暂停当前正在执行的任务转而去处理这个突发事件处理完成后再回到原来的任务继续执行。这样可以大大提高系统的实时响应能力。在 STM32 中中断源非常丰富包括外部中断EXTI、定时器中断、串口中断等。而 SysTick 定时器则是 Cortex-M 内核自带的系统节拍定时器HAL 库的延时函数HAL_Delay()正是依赖它来实现的。上图展示了中断系统的整体架构。可以看到各种外设事件通过中断控制器NVIC统一管理NVIC 负责中断的使能、优先级配置和响应调度。这张图进一步说明了中断触发到响应处理的完整流程。当外设产生中断请求后NVIC 会根据优先级决定是否立即响应如果当前没有更高优先级的中断正在执行CPU 就会跳转到对应的中断服务函数ISR中执行处理代码。NVICNVICNested Vectored Interrupt Controller嵌套向量中断控制器是 Cortex-M 内核中负责中断管理的核心模块。它支持中断的嵌套处理即高优先级的中断可以打断低优先级的中断服务函数。在 HAL 库中我们通常使用HAL_NVIC_SetPriority()函数来设置中断优先级使用HAL_NVIC_EnableIRQ()函数来使能中断。中断优先级分为抢占优先级和子优先级两部分抢占优先级决定中断是否可以打断其他中断而子优先级则用于同抢占优先级下的响应顺序。上图是 NVIC 的配置界面。在 CubeMX 中我们可以直观地为每个中断源分配抢占优先级和子优先级。合理配置中断优先级是保证系统稳定运行的关键尤其是当多个中断同时发生时优先级决定了它们的处理顺序。定时器配置原理定时器是 STM32 中非常强大的外设可以实现精确的时间控制、波形生成、输入捕获等功能。在 HAL 库中SysTick 定时器被用作系统心跳为延时函数和超时检测提供时间基准。上图展示了 SysTick 定时器的基本配置。SysTick 是一个 24 位的向下递减计数器当计数值减到 0 时会产生中断然后自动重新装载初值继续下一轮计数。 这行代码到底在算什么在 HAL 库的初始化代码中有一行非常关键的配置HAL_SYSTICK_Config(SystemCoreClock / (1000U / uwTickFreq))它的目标很简单让 SysTick 定时器每 1 毫秒产生一次中断也就是 HAL 库的uwTick变量每 1ms 加 1你调HAL_Delay()靠的就是它。这行代码的本质是计算 SysTick 定时器的重装载值。SysTick 的时钟源通常是系统主时钟这里为 72MHz每来一个时钟脉冲计数器就减 1。要让计数器从初值减到 0 恰好耗时 1ms就需要计算出 1ms 内有多少个时钟脉冲这个数量就是我们要写入的重装载值。 我们拿实际数字来算一遍假设你的 STM32F103C8T6 主频是72 MHz即SystemCoreClock 72,000,000。在 HAL 库里uwTickFreq默认被定义为1U表示 1ms。那么先算括号里(1000U / uwTickFreq)1000 / 11000再算除法SystemCoreClock / 100072,000,000 / 100072,000所以这行代码实际执行的是HAL_SYSTICK_Config(72000);也就是说SysTick 定时器的重装载值被设置为 72000。这个值决定了定时器从 72000 开始向下计数减到 0 时触发一次中断。⏱️ 72,000 这个数字是什么意思SysTick 是一个24 位向下递减的计数器。你把72,000这个值写进去它就会从 72,000 开始每来一个时钟脉冲72MHz就减 1。减到 0 需要72,000 个脉冲72,000 个脉冲 ÷ 72,000,000 个脉冲/秒 0.001 秒 1 毫秒减到 0 的那一刻它触发中断然后硬件自动把 72,000 重新装进去接着再数一遍。这就实现了每 1ms 准时打断 CPU 一次的效果。简单来说72,000 这个数字就是1 毫秒内有多少个时钟脉冲的答案。因为系统时钟是 72MHz也就是每秒有 72,000,000 个脉冲那么 1 毫秒千分之一秒内自然就有 72,000 个脉冲。SysTick 计数器从 72,000 开始倒数每收到一个脉冲减 1减到 0 时恰好经过 1 毫秒。❓ 为什么中间要除一个(1000U / uwTickFreq)这是 HAL 库为了兼容性留的一个口子。如果你想让心跳变成10ms一次就把uwTickFreq改成10U那么1000 / 10 100最终算出来就是72,000,000 / 100 720,000即 10ms 才减到 0但因为绝大部分时候我们都是要1ms所以uwTickFreq就固定等于1U这一坨括号算出来永远等于1000。这个设计的好处在于当开发者需要调整系统心跳频率时只需要修改uwTickFreq这一个宏定义而不需要去修改底层的 SysTick 配置代码。HAL 库通过这种参数化的方式提高了代码的可移植性和可维护性。上图展示了 SysTick 中断的触发流程。当计数器减到 0 时硬件自动将重装载值重新装入计数器同时向 NVIC 发出中断请求CPU 响应后进入 SysTick 中断服务函数。在中断函数中写执行程序了解了 SysTick 的工作原理后我们就可以在中断函数中编写需要周期性执行的任务了。SysTick 的中断服务函数是SysTick_Handler()在 HAL 库中它默认会调用HAL_IncTick()来更新系统时钟计数。每执行一次中断函数时经过一毫秒每次给 static 变量 count 自加 1当 count 等于 500 时使用HAL_GPIO_TogglePin翻转电平。这样LED 就会以 500ms 为周期进行翻转实现闪烁效果。具体代码如下void SysTick_Handler(void) { HAL_IncTick(); static uint16_t count 0; count; if (count 500) { count 0; HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); } }这段代码的逻辑非常清晰每次 SysTick 中断每 1ms进入一次count 自加 1当 count 累加到 500 时说明已经过去了 500ms此时翻转 LED 的电平状态并将 count 清零重新计数。这样 LED 就会以 1Hz 的频率闪烁亮 500ms灭 500ms。最终上图是最终的运行效果。可以看到LED 按照预期的频率进行闪烁说明中断配置和代码逻辑都正确无误。外部中断除了定时器中断外部中断EXTI也是嵌入式开发中非常常用的中断类型。外部中断允许 GPIO 引脚在检测到电平变化上升沿、下降沿或双边沿时触发中断非常适合用于按键检测、传感器信号触发等场景。上图展示了外部中断的配置界面。在 CubeMX 中我们可以为每个 GPIO 引脚单独配置外部中断模式并选择触发方式上升沿、下降沿或双边沿触发。与轮询方式相比外部中断的最大优势在于实时性和效率。CPU 不需要持续检测引脚状态只有当引脚电平发生变化时才会被中断唤醒从而大大降低了 CPU 的占用率。中断优先级当系统中存在多个中断源时中断优先级的配置就显得尤为重要。STM32 的中断优先级分为抢占优先级Preemption Priority和子优先级Sub Priority两部分。抢占优先级决定了中断是否可以打断正在执行的其他中断。如果两个中断的抢占优先级相同则它们之间不能互相打断只能按照子优先级的高低依次响应。如果抢占优先级和子优先级都相同则按照中断号的大小决定响应顺序。上图展示了中断优先级的配置方法。在 CubeMX 中我们可以为每个中断源分别设置抢占优先级和子优先级。数值越小优先级越高。合理配置优先级可以确保关键任务如紧急故障处理能够及时响应而不被次要中断阻塞。外部中断/事件控制器原理图外部中断/事件控制器EXTI的原理图如下所示。它负责将 GPIO 引脚的电平变化转换为中断请求信号并传递给 NVIC 进行处理。从原理图中可以看到EXTI 支持两种输出模式中断模式和事件模式。中断模式会触发 CPU 执行中断服务函数而事件模式则可以直接触发其他外设的动作如 DMA 传输不需要 CPU 参与。在实际开发中我们最常用的是中断模式。回调函数在 HAL 库的中断处理机制中回调函数Callback Function扮演着非常重要的角色。HAL 库将中断处理分为两个层次底层的中断服务函数ISR和上层的回调函数。底层 ISR 负责处理硬件相关的操作如清除中断标志位等而回调函数则是 HAL 库留给用户的钩子用户可以在回调函数中编写自己的业务逻辑而不需要修改 HAL 库的底层代码。上图展示了 HAL 库中断处理的整体流程。当外部中断触发时CPU 跳转到对应的 ISRISR 完成硬件处理后调用回调函数用户编写的业务逻辑就在回调函数中执行。MX 配置 GPIO 端口初始化端口记录上升沿或下降沿在使用外部中断之前我们需要先在 CubeMX 中完成 GPIO 的初始化配置。配置内容包括引脚模式输入/输出、上下拉电阻、触发方式上升沿/下降沿/双边沿等。上图展示了 GPIO 外部中断的配置界面。在配置时我们需要将引脚模式设置为External Interrupt Mode with Falling edge trigger detection下降沿触发或Rising edge上升沿触发具体取决于传感器的输出特性。找到端点专属中断函数配置完成后CubeMX 会自动生成中断服务函数的框架。对于外部中断每个引脚组如 PA0-PA15都有对应的中断服务函数例如EXTI0_IRQHandler()对应 PA0 引脚的中断。上图展示了中断服务函数的位置。在生成的代码中HAL_GPIO_EXTI_IRQHandler()函数负责处理中断标志位的清除并调用对应的回调函数。找到回调函数弱函数HAL 库为每个外设都提供了默认的回调函数这些函数被定义为弱函数Weak Function意味着用户可以重新定义同名的函数来覆盖默认实现而不需要修改 HAL 库的源代码。上图展示了回调函数的默认定义。对于外部中断对应的回调函数是HAL_GPIO_EXTI_Callback()它接收一个参数GPIO_Pin用于指示具体是哪个引脚触发了中断。自己写入最后我们需要在用户代码区重新实现这个回调函数编写自己的业务逻辑。例如void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin SENSOR_Pin) { // 传感器触发翻转 LED 状态 HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); } }这样当传感器引脚检测到设定的边沿触发时就会自动进入回调函数执行 LED 翻转操作。整个过程不需要 CPU 轮询实现了高效的事件驱动处理。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表