【STM32】__IO(volatile)在单片机定时器编程中的作用

笔者的第一篇博客,由于知识水平有限,如有错误,欢迎指正


前言

今天笔者学习完B站野火科技的STM32教程中的系统滴答定时器(SysTick)部分,自己着手写代码时却出现了一些问题。

明明代码,逻辑写得都是一样的,为什么LED就是不闪烁?后来仔细对照了自己的代码和野火科技的例程,才发现我的变量类型少了__IO(volatile)限定符。因为自己写代码时很少关注限定符,所以这才注意到限定符在单片机编程中的重要性。

volatile限定符

在STM32的HAL库中,给出了以下宏定义:

#define     __IO    volatile             /*!< Defines 'read / write' permissions */

其实从中从注释中就可以看出volatile的作用。为了讲解的更清楚,笔者引用权威书籍《C Primer Plus》中关于volatile的说明:

        volatile 限定符告知计算机,代理(而不是变量所在的程序)可以改变
该变量的值。通常,它被用于硬件地址以及在其他程序或同时运行的线程中
共享数据。例如,一个地址上可能储存着当前的时钟时间,无论程序做什
么,地址上的值都随时间的变化而改变。或者一个地址用于接受另一台计算
机传入的信息。

        现在,如果声明中没有volatile关键字,编译器会假定变量的值在使用
过程中不变,然后再尝试优化代码。

——《C Primer Plus》

简单来说,volatile就是避免了编译器对代码的一种优化方式。假设有以下代码:

XBYTE[2]=0x55;
XBYTE[2]=0x56;
XBYTE[2]=0x57;
XBYTE[2]=0x58;

编译器会预先判断到XBYTE[2]的最终值为0x58,那么就会优化代码,只执行最后一条赋值语句,而一旦加上了volatile限定符,那么编译器就会执行每一条赋值语句。 

为什么必须要使用volatile?

以野火STM32教程的编程思路为例。我们在主程序中传递需要中断次数,然后在中断初始化函数中传递重装载值,在定时器运转的过程中,每隔(1/频率)秒的时间,重装载值向下递减一次。因此,重装载值/频率*中断次数=延迟的时间。在这个过程中,中断服务函数调用中断次数递减的函数,这就类似于《C Primer Plus》中提到的“代理”。如果没有volatile限定符,那么代理就没法对延迟的最小时间单位的次数进行修改,主进程就会卡在while死循环中,导致LED灯并没有向预期的那样闪烁。

/*在stm32f7xx_it.c中*/

void SysTick_Handler(void)//中断服务函数,每次重装载值为0时触发中断,调用次数-1的函数
{
    TimingDelay_Decrement();
}


/*在用户自定义的bsp_SysTick.c中*/

void Delay_us(__IO u32 nTime)//主函数调用的延时函数,传递中断次数
{
    TimingDelay = nTime;
    while (TimingDelay != 0);
}

void SysTick_Init(void)
{
    if (HAL_SYSTICK_Config(SystemCoreClock / 100000)) {//设置重装载值,延时最小单位时间为10us(10^-5s)
    /* Capture error */
        while (1);
    }
}

void TimingDelay_Decrement(void)//每调用一次该函数,中断次数-1
{
    if (TimingDelay != 0x00) {
    TimingDelay--;
    }
}


/*在main.c中*/

int main(void)
{
    /* 系统时钟初始化成 72MHz */
    SystemClock_Config();
    /* LED 端口初始化 */
    LED_GPIO_Config();
    /* 配置 SysTick 为 10us 中断一次, 时间到后触发定时中断,进入 stm32f7xx_it.c 
    文件的 SysTick_Handler()句柄函数处理,通过中断次数计时*/
    SysTick_Init();
    while (1) {
        LED_RED;
        Delay_us(100000); // 100000 * 10us = 1s
        LED_GREEN;
        Delay_us(100000); // 100000 * 10us = 1s
        LED_BLUE;
        Delay_us(100000); // 100000 * 10us = 1s
    }
}

拓展

打开一个Keil5工程,搜索__IO,可以发现许多变量都使用__IO的限定符,这也进一步证明了__IO是不可以任意省略的

结语        

从这个例子中,我们就能看到限定符对底层的编程时有一定影响的。仔细阅读HAL库中的限定符,而不是随意的删减,或许能有很大的收获。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bahair_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值