如何在 STM32F103C8T6 上进行裸机 LED 闪烁?

Posted

技术标签:

【中文标题】如何在 STM32F103C8T6 上进行裸机 LED 闪烁?【英文标题】:How to do bare-metal LED blink on STM32F103C8T6? 【发布时间】:2021-03-06 06:36:36 【问题描述】:

我刚刚开始探索 STM32 MCU。我想闪烁 BluePill(具有 STM32F103C8T6 MCU)板上的 LED。我怀疑我被什么东西误导了。根据 F1 系列的参考手册,有 3 个主要步骤:

为 PORT 启用时钟(此处为 PORTC) 配置 CNF/MODE 寄存器 根据需要配置 ODR 寄存器,即引脚上的 HIGH/LOW。

我已按照手册在 KEIL MDK 中编写代码,但加载后,代码无法运行,我按下重置按钮然后 LED 亮起,即使我已将设置更改为 RESET &在 KEIL 中运行

这里是代码和参考手册的部分。

#include<stm32f10x.h>

int main()
    
    RCC->APB2ENR |= 1<<4; //PORTC is on APB2 bus
    GPIOC->CRH |= (1<<20);
    
    while(1)
        GPIOC->ODR |= 0x00002000;
        for(int i = 0; i < 500000;i++); //dummy delay
        GPIOC->ODR &= ~0x00002000;
        for(int i = 0; i < 500000;i++); // dummy delay

    



参考手册:

当我使用调试模式时,我注意到在执行RCC-&gt;APB2ENR |= (1&lt;&lt;4) 之后没有为 PORTC 启用时钟的一件事。

LED 不闪烁。在整个过程中我找不到错误。

【问题讨论】:

你检查生成的代码了吗?延迟可能会得到优化。也许您可以尝试将循环计数器声明为 volatile。 @th33lf 很抱歉,我不明白你的意思。实际上 LED 在 while 循环内切换,但仅在调试模式下。它还显示没有为 PORTC 启用时钟。 你的意思是如果你单步执行,LED 会切换,但如果你让代码连续运行,LED 会一直亮着?如果是这样,编译器可能会消除延迟。 是的,不亮但不亮。如果代码被正确编译和上传,当我给电路板供电时,LED 应该会闪烁,但事实并非如此。 我还是不明白你到底面对的是什么。它是否在调试模式下闪烁并且仅在您简单地闪烁和重置时才起作用?还是仅当您逐行执行时才有效?如果是这样,我会先尝试将循环计数器声明为 volatile。此外,端口配置似乎是一个两步过程。您应该配置方向(输入/输出),然后它应该是什么样的输出。对于 LED,它通常是开漏输出,但取决于您的电路板。我不确定是否只是做 (1 【参考方案1】:

当您编写一个虚拟延迟循环时,智能编译器通常会发现这段代码中没有任何值得发生的事情并优化整个事情。

如果你想知道这件事发生了,最好的办法是看一下生成的二进制文件的反汇编。

谢天谢地,C 提供了volatile 关键字来解决这类问题。它明确告诉编译器不要优化对使用此限定符声明的变量的内存访问。

Here 您可以看到一些示例代码,这些示例代码显示了使用出色的 godbolt 工具生成的带有和不带有 volatile 关键字的汇编代码之间的区别。如果没有 volatile,for 循环将被优化为无,并且不会发生对 i 的递增或检查。

因此循环应该写成:

for(volatile int i = 0; i < 500000; i++); //dummy delay

在其他情况下,您也可能在嵌入式系统上遇到此类问题,例如当您从多个上下文/线程访问变量时。

【讨论】:

哦,这是一个非常好的链接参考解释!以前我不明白你试图解释什么。非常感谢!实际上,我对 ARM 硬件级别还很陌生,因此不太了解反汇编(尽管我正在尝试)。 @Sajil 很高兴听到这个消息!尽管您的问题已经解决,但我有点希望您能看一下它以获得更深入的了解。【参考方案2】:

实际上 LED 在 while 循环内切换,但仅在调试模式下

是的,因为这是生成的机器代码唯一一次包含这些延迟循环。在释放模式下,LED 仍会切换,除非您需要示波器或逻辑分析仪来查看输出引脚的状态以查看它是否在切换 - 您不会只用眼睛看到它:)

在发布模式下,延迟循环被移除,因为您无法以这种方式实现延迟。理想情况下,您应该使用计时器,但作为一种快速破解,这将起作用:

const int N = 500000;
while(1)
    for(int i = 0; i < N;i++) GPIOC->ODR |= 0x00002000;
    for(int i = 0; i < N;i++) GPIOC->ODR &= ~0x00002000;

它会起作用,因为GPIOC 指向一个 volatile 对象,编译器无法优化访问。

【讨论】:

好的,现在可以了!但是,constexpr 在 C++ 中,此处不支持。我做了int N 并且它有效。此外,您是否必须在循环的每次迭代中配置引脚以模拟延迟? @Sajil 注册访问权限为volatile 合格,这就是此版本有效的原因。你也可以选择for(volatile int i = 0; i &lt; N;i++)。但是不要写那样的废话循环,使用硬件计时器。 @Lundin 婴儿步骤最好让一件事正常工作(gpio 输出),然后找出计时器。不是同时做两件事。繁忙的计数器循环最适合第一步,然后进入计时器。 不,您不必每次迭代都配置引脚。这对这个答案有误导性。如果您要使用虚拟延迟循环,那么您必须编写一个不会被编译器优化的延迟循环。您可以通过关闭编译器优化或将循环计数器变量声明为 volatile 来做到这一点。 @Lundin for(int i = 0; i &lt; N;i++) asm("");

以上是关于如何在 STM32F103C8T6 上进行裸机 LED 闪烁?的主要内容,如果未能解决你的问题,请参考以下文章

STM32f103C8T6 Bootloader设计(转)

stm32f103c8t6最小系统板可以调速吗

STM32学习笔记 二基于STM32F103C8T6和STM32CubeMX实现UART串口通信数据收发

STM32学习笔记 二基于STM32F103C8T6和STM32CubeMX实现UART串口通信数据收发

stm32f103c8t6 usart1不工作

三实战小例程 基于STM32F103C8T6最小系统板和STM32CubeMX驱动WS2812B光立方