如何在STM32F103C8T6上使裸机LED闪烁?

如何在STM32F103C8T6上使裸机LED闪烁?,c,arm,embedded,stm32,keil,C,Arm,Embedded,Stm32,Keil,我刚刚开始探索STM32 MCU。我想使BluePill(具有STM32F103C8T6 MCU)板上的LED闪烁。我怀疑我被什么东西误导了。根据F1系列参考手册,主要有3个步骤: 为端口启用时钟(此处为PORTC) 配置CNF/模式寄存器 根据需要配置ODR寄存器,即引脚上的高/低 我已经按照手册在KEIL MDK中编写了代码,但在加载后,代码不会运行,我按下重置按钮,然后LED亮起,即使我已将设置更改为在KEIL中重置和运行 这是代码和参考手册的部分 #include<stm32f

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

  • 为端口启用时钟(此处为PORTC)
  • 配置CNF/模式寄存器
  • 根据需要配置ODR寄存器,即引脚上的高/低
我已经按照手册在KEIL MDK中编写了代码,但在加载后,代码不会运行,我按下重置按钮,然后LED亮起,即使我已将设置更改为在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

    }

}

#包括
int main(){
RCC->APB2ENR |=1APB2ENR |=(1)
实际上,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;
}
const int N=500000;
而(1){
对于(inti=0;iODR |=0x00002000;
对于(inti=0;iODR&=~0x00002000;
}

它可以工作,因为
GPIOC
is指向一个易失性对象,编译器无法优化访问。

当您编写一个虚拟延迟循环时,智能编译器通常会发现在这段代码中没有什么值得做的事情,并对整个过程进行优化

如果您想知道发生了什么,最好的方法是查看生成的二进制文件的反汇编

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

您可以看到一些示例代码,这些代码显示了使用优秀的godbolt工具生成的具有和不具有
volatile
关键字的汇编代码之间的差异。没有volatile,for循环将优化为零,并且不会增加或检查
i

因此,循环应写成:

for(volatile int i = 0; i < 500000; i++); //dummy delay
for(volatile int i=0;i<500000;i++);//虚拟延迟

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

是否检查了生成的代码?延迟可能会得到优化。您可以尝试声明循环计数器作为易失性。@th33lf很抱歉,我不明白您的意思。实际上,LED在while循环中切换,但仅在调试模式下。它还显示没有为PORTC启用时钟。您的意思是,如果您单步执行,LED会切换,但如果您让代码连续运行,LED会一直亮着吗?如果是这样,延迟可能会消失由编译器编辑。是的,不亮,但关闭。如果代码正确编译和上传,指示灯应该在我给电路板通电时立即闪烁,但事实并非如此。我仍然不完全理解您所面临的问题。它是否在调试模式下闪烁,仅当您简单地闪烁和重置时不工作?还是仅当您逐行通过l时才工作ine?如果是这样,我会首先尝试将循环计数器声明为volatile。此外,端口配置似乎是一个两步过程。您应该配置方向(输入/输出),然后配置它应该是什么类型的输出。对于LED,它通常是开漏输出,但取决于您的板。我不确定是否只是这样做(1)好,现在它工作了,但是代码< > COXECPRP</C>是C++,这里不支持。我做了代码> int n/COD>,它也是有效的,也就是说,你必须在循环的每个迭代中配置PIN来模拟延迟吗?@ SjjIL登记访问是“代码> Value合格的,这就是为什么这个版本有效。你可以选择做COD。e> 对于(volatile int i=0;i
也一样。但不要编写这样的垃圾循环,请使用硬件计时器。@Lundin baby steps最好只做一件事(gpio输出)然后找出计时器。不要同时做两件事。繁忙的计数器循环最适合第一步,然后再进入计时器。不,你不必每次迭代都配置pin。这会误导你的答案。如果你要使用虚拟延迟循环,那么你必须写一个延迟循环,而这个延迟循环不会被编译器优化。您可以通过关闭编译器优化或将循环计数器变量声明为@Lundin
for(int i=0;i
哦,这是一个非常好的解释,带有链接参考!之前我不理解您试图解释的内容。非常感谢!事实上,我对ARM硬件级别非常陌生,因此无法理解反汇编(尽管我正在尝试)@Sajil很高兴听到这个消息!虽然你的问题已经解决了,但我还是希望你能看一看以获得更深入的了解。