一、问题描述
在学习野火霸天虎F407寄存器点亮LED时,出现实验现象:LED灯不亮,野火霸天虎F407资料。
main.c代码如下:
#include "stm32f4xx.h"
void Delay(unsigned int count);
int main(void)
{
#if 0
/* 第一步:开启GPIO端口的时钟 */
/* 打开GPIOF端口的时钟 */
*(unsigned int *)(0x40023800+0x3f0) |= (1<<5);
/* 第二步:控制GPIO的方向 */
/* GPIOF 配置为输出 */
*(unsigned int *)(0x40021400+0x00) &= ~((0x03) << (2*6));
*(unsigned int *)(0x40021400+0x00) |= (1 << (2*6));
/* 第三步:控制GPIO的数据输出寄存器 */
/* PF6 输出高电平 */
*(unsigned int *)(0x40021400+0x14) |= (1 << 6);
/* PF6 输出低电平 */
*(unsigned int *)(0x40021400+0x14) &= ~(1 << 6);
#elif 0
/* 第一步:开启GPIO端口的时钟 */
/* 打开GPIOF端口的时钟 */
RCC_AHB1ENR |= (1<<5);
/* 第二步:控制GPIO的方向 */
/* GPIOF 配置为输出 */
GPIO_MODER &= ~((0x03) << (2*6));
GPIO_MODER |= (1 << (2*6));
/* 第三步:控制GPIO的数据输出寄存器 */
/* PF6 输出高电平 */
GPIO_ODR |= (1 << 6);
/* PF6 输出低电平 */
GPIO_ODR &= ~(1 << 6);
#elif 0
//任务1-把其他两个灯也点亮
RCC_AHB1ENR |= (1<<5); //开启GPIO端口时钟
//设置GPIOF6为推挽输出
GPIO_MODER &= ~((0x03) << (2*6));
GPIO_MODER |= (1 << (2*6));
GPIO_ODR |= (1 << 6);
GPIO_ODR &= ~(1 << 6);
//设置GPIOF7为推挽输出
GPIO_MODER &= ~((0x03) << (2*7));
GPIO_MODER |= (1 << (2*7));
GPIO_ODR |= (1 << 7);
GPIO_ODR &= ~(1 << 7);
//设置GPIOF8为推挽输出
GPIO_MODER &= ~((0x03) << (2*8));
GPIO_MODER |= (1 << (2*8));
GPIO_ODR |= (1 << 8);
GPIO_ODR &= ~(1 << 8);
#elif 1
//任务1-把其他两个灯也点亮
RCC_AHB1ENR |= (1<<5); //开启GPIO端口时钟
//设置GPIOF6为推挽输出
GPIO_MODER &= ~((0x03) << (2*6));
GPIO_MODER |= (1 << (2*6));
//设置GPIOF7为推挽输出
GPIO_MODER &= ~((0x03) << (2*7));
GPIO_MODER |= (1 << (2*7));
//设置GPIOF8为推挽输出
GPIO_MODER &= ~((0x03) << (2*8));
GPIO_MODER |= (1 << (2*8));
while(1)
{
GPIO_ODR &= ~(1 << 6);
Delay(0xfffff);
GPIO_ODR |= (1 << 6);
Delay(0xfffff);
GPIO_ODR &= ~(1 << 7);
Delay(0xfffff);
GPIO_ODR |= (1 << 7);
Delay(0xfffff);
GPIO_ODR &= ~(1 << 8);
Delay(0xfffff);
GPIO_ODR |= (1 << 8);
Delay(0xfffff);
}
#endif
}
//延时函数
void Delay(unsigned int count)
{
for(;count!=0;count--);
}
void SystemInit(void)
{
/* 函数体为空,目的是为了骗过编译器不报错 */
}
/*
1-把其他两个灯也点亮
2-实现三个灯闪烁(时间的控制使用软件延时)
*/
二、问题分析
通过分析main.c代码,导致出现上述现象的间接原因是延时函数没有起作用。检查延时函数的实现代码,并没有错误。这不禁使我想起《程序员的自我修养——链接、装载、库》一书所提到的程序源代码经过预编译-》编译-》汇编-》链接,所以极大可能是编译器在编译过程中优化掉了我的延时函数,使得整个程序不能按照预定功能实现。
打开keil5的调试功能,查看对应main.c的反汇编文件:
经过优化的delay函数:
未经过优化的delay函数:
优化之后的 delay 函数没有for循环延时操作,因此失去延时的效果。
三、问题解决
3.1 降低ARM Compiler version
在Target设置界面下,Code Generation默认的是ARM Compiler version 6。
将ARM Compiler version 6改为ARM Compiler version 5即可。
3.2 volatile关键字修饰
volatile关键字影响编译器编译的结果,用volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错。volatile关键字最通俗的解释是,告诉编译器这个变量我有其他用,不要给我随便优化掉。
原延时函数
//延时函数
void Delay(unsigned int count)
{
for(;count!=0;count--);
}
添加volate关键字修饰
//延时函数
void Delay(volatile unsigned int count)
{
for(;count!=0;count--);
}
推荐使用方法2,原因如下:
1.自定义延时函数中使用 volatile 去声明 val 变量可以解决编译器优化带来的延时失效问题;
2.编译器优化可以使代码更加精炼,执行效率更高。
参考资料
1. Keil AC5 和 AC6的一些区别
2. 编译器优化对自定义延时程序的影响(volatile详解实验一)
3. C语言丨深入理解volatile关键字