江协的延时函数

一、起因:看不懂江协的延时函数配置

图片内容来自江协Stm32的延时函数配置。

其中参数来源于

也就是说江协是不通过封装好的函数进行传参,而是自行通过赋值变量达到效果,可能是为了方便启动和关闭Systick。

二、Stm32标准库函数的一些内容---分部说明

1.简述:关键词——volatile:用于定义直接访问内存的变量,不让编译器优化访问流程。

        前补知识:当CPU需要访问某个变量时,如果该变量已经在缓存区中,则可以直接从缓存区获取,而不需要访问主内存,从而提高访问速度。

        非volatile声明的变量,可能会被编译器认为是不变的,然后直接访问缓存区里面的值(来源于内存区,但需要更新),但是可能内存区的值≠缓存区的值,为了读取到真实的值,就要volatile。

#define A B;的语句的作用是把B加了个外号名为A。这条语句之后,可以同时用A,B定义变量。

volatile声明一个变量是易变的,这意味着该变量的值可能会被程序之外的因素(如中断服务程序、多线程环境或硬件)改变。

对于非volatile变量,编译器会进行优化,可能会将变量的值缓存到寄存器中,从而减少对内存的访问次数。

这种优化在单线程环境中通常是安全的,但在多线程或多处理器环境中可能会导致可见性问题。编译器不会对volatile变量进行这种优化,从而确保了变量的可见性和安全性。

使用volatile关键字可以提高变量的可见性和安全性:

        ①可见性:在多线程或多处理器环境中,volatile关键字确保变量的值在内存中是可见的。每次读取或写入操作都会直接从内存中获取或更新变量的值,而不是使用缓存中的旧值。这有助于避免由于编译器优化导致的变量值不一致问题。

        ②安全性volatile关键字防止编译器对变量的访问进行优化,确保每次访问都是直接的内存操作。这在处理硬件寄存器或共享内存时尤为重要,因为这些变量可能会被外部因素修改,而编译器优化可能会导致程序行为不符合预期。

2.简述:认识变量名:

        CTRL :系统控制和状态寄存器。LOAD :重新加载值寄存器。

        VAL    : 当前值寄存器。CALIB  :校准寄存器

Offset:偏移量。

                意思是变量所描述的寄存器位置为  内存基地址+偏移量地址。地址在物理层面是固定的,要访问特定寄存器只要知道  基地址和偏移量   就行。

SysTick:系统滴答

CTRL   0x00:系统控制和状态寄存器。

                用于控制systick的启动与关闭。

LOAD   0x04:重新加载值寄存器。

                对这个写值就是多少个systick进入一次中断。也就是中断周期

VAL      0x08: 当前值寄存器。

                记录当前systick的值。在定时时,需要清空,否则会从未清空的值开始计数,导致第一个中断的时间不一定等于目标时间。

CALIB  0x0C:校准寄存器。

3.简述:SysTick是一个(2.)提到的结构体的指针型变量名

绿色选定的SysTicks那一行的意思:将SysTick_BASE 取个别名  SysTick。

SysTick_BASE前面的(SysTick_Type *) 在②中有说明是一个结构体类型名,

整体( (SysTick_Type *)  SysTick_BASE )的意思是:定义了一个(SysTick_Type *)类型的变量SysTick_BASE ,并且给 SysTick_BASE 取个别名 SysTick。

补充:

        结构体定义和结构体指针定义法的区别:

                用SysTick_Type定义:SysTick_Type A,那么变量访问形式是A.CTRL

                用SysTick_Type *定义:SysTick_Type * A,那么变量访问形式是A->CTRL

4.简述:Pos是地址,Msk是标志位。

①SysTick_CTRL_COUNTFLAG_Msk:标志计数器是否减到0(完成计数周期)(1是0非,1时读取会清零)

②SysTick_CTRL_CLKSOURCE_Msk: 系统时钟(SYSCLK)。

                        配置:  |=        SysTick_CTRL_CLKSOURCE_Msk

③SysTick_CTRL_TICKINT_Msk:使能或禁止SysTick定时器的中断。1开启,0关闭。

                        配置: |=SysTick_CTRL_TICKINT_Msk

④SysTick_CTRL_ENABLE_Msk:定时器使能标志位。

                        配置: |=SysTick_CTRL_ENABLE_Msk。

⑤SysTick_LOAD_RELOAD_Msk:表示SysTick定时器的重装载寄存器(LOAD)的最大值(0xFFFFFF )。

        16进制表示24位,一位16进制表示四个2进制,0xFFFFFF =1111 1111 1111 1111 1111 1111

        配置:SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;

⑥SysTick_VAL_CURRENT_Msk:表示SysTick定时器的当前值掩码。

                SysTick_VAL_CURRENT_Msk = 0;

                A = SysTick_VAL_CURRENT_Msk;

⑦SysTick_CALIB_NOREF_Msk:这个宏定义用于检查SysTick定时器的校准寄存器中是否设置了NOREF位。NOREF位表示SysTick定时器是否使用外部参考时钟。这个宏定义用于检查SysTick定时器的校准寄存器中是否设置了NOREF位。NOREF位表示SysTick定时器是否使用外部参考时钟。

                配置:|= SysTick_CALIB_NOREF_Msk;

⑧SysTick_CALIB_SKEW_Msk:如果SKEW位被设置,表示SysTick计数器的周期不精确,即SysTick计数器的周期与预期的周期之间存在偏差。

                配置:|= SysTick_CALIB_SKEW_Msk;

⑨SysTick_CALIB_TENMS_Msk:1毫秒的计数值,通常为10500。由时钟频率计算得出,取决于系统的时钟配置。

SysTick定时器:24位的倒计数定时器,常用于实现精确的延时功能。

位置(position),用于说明状态标志位在CTRL、LOAD、VAL、CALLB状态寄存器的位置。SysTick_XXX_YYYY_Pos   表示XXX寄存器中YYYY状态标志位的位置。pos=Position。

掩码(mask):标识SysTick定时器的带有的状态寄存器的各标志位。

SysTick_CTRL_COUNTFLAG_Msk:

        COUNTFLAG标志位用于指示SysTick定时器是否已经完成了计数周期。当SysTick定时器的计数值从非零值减到0时,COUNTFLAG标志位会被置位(即设置为1),表示计数周期已经结束。读取COUNTFLAG标志位,该位自动清零。

        所以可以用(这句函数最后面会解释,看不懂也可接着往下看)

        while(!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk));

        等待计数到0

SysTick_CTRL_CLKSOURCE_Msk:选择SysTick定时器的时钟源是系统时钟(SYSCLK)。

SysTick_CTRL_TICKINT_Msk:用于控制SysTick定时器的中断使能,用于使能或禁止SysTick定时器的中断。1开启,0关闭。

SysTick_CTRL_ENABLE_Msk:用于控制系统定时器(SysTick)的使能。用于控制SysTick定时器是否开始计数。当 SysTick_CTRL_ENABLE_Msk 被设置1时,SysTick定时器开始倒数;当它被清除时,SysTick定时器停止倒数。

SysTick_LOAD_RELOAD_Msk:用于表示SysTick定时器的重装载寄存器(LOAD)的最大值。

具体来说,SysTick_LOAD_RELOAD_Msk 的值是 0xFFFFFFul << SysTick_LOAD_RELOAD_Pos,其中 SysTick_LOAD_RELOAD_Pos 是重装载寄存器的位置,位置是0,所以相当于没改变值。

        配置SysyTick定时器:SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;

        这里的 ticks 是用户希望设置的重装载值,通过 & SysTick_LOAD_RELOAD_Msk 操作,确保 ticks 的值不会超过 SysTick_LOAD_RELOAD_Msk 定义的最大值。然后减去1是为了确保定时器能够正确地从 ticks-1 开始计数,从而经过 0~ticks 个脉冲

SysTick_VAL_CURRENT_Msk:表示SysTick定时器的当前值掩码。

SysTick_CALIB_NOREF_Msk:(延迟函数配置不涉及)这个宏定义用于检查SysTick定时器的校准寄存器中是否设置了NOREF位。NOREF位表示SysTick定时器是否使用外部参考时钟。这个宏定义用于检查SysTick定时器的校准寄存器中是否设置了NOREF位。NOREF位表示SysTick定时器是否使用外部参考时钟。

SysTick_CALIB_SKEW_Msk:(延迟函数配置不涉及)SysTick_CALIB_SKEW_Msk用于检查SYST_CALIB寄存器中的SKEW位是否被设置。如果SKEW位被设置,表示SysTick计数器的周期不精确,即SysTick计数器的周期与预期的周期之间存在偏差。

SysTick_CALIB_TENMS_Msk:(延迟函数配置不涉及)这个字段用于指定SysTick定时器每1毫秒的计数值。在STM32的SysTick定时器中,TENMS字段的值是固定的,通常为10500,表示每1毫秒有10500个计数周期。这个值是根据SysTick定时器的时钟频率计算得出的,具体取决于系统的时钟配置。

三、看了那么多,再分析江协延时函数

这里我用江协的注释来对应代码

①SysTick->LOAD = 72 * xus;

        设置重装值是 72*us:因为系统时钟是72Mhz(72 000 000Hz) = 1s = 1 000ms = 1 000 000us。所以1us = 72 Hz。(这里不是正确对应结果,只是为了理解而作)

②SysTick->VAL = 0x00;

        清空当前计数值:因为Val是不知道,为了保证第一个计数周期准确,需要清零val。

③SysTick->CTRL = 0x00000005;(难点1)

        设置时钟源为HCLK,启动定时器:先看到Stm32封装好的函数定义

  SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk | 
                   SysTick_CTRL_TICKINT_Msk   | 
                   SysTick_CTRL_ENABLE_Msk; 

        再回想起CTRL相关标志位的基地址是0x00

CLKSOURCE时钟源标志位的位是2,定时中断标志位的位置是1,时钟使能位的位置是0,

由于延时函数不需要在完成标志位的时候做什么,所以不需要开启中断

所以由二进制的数值表示为(位2 = 1)(位1  = 1)(位0  = 1)=> 101 =>16进制是5。

0x00000005 = 0000 0000 0000 0000 0000 0000 0101。

④ while(!(SysTick->CTRL & 0x00010000));    (难点2)

        等待计数到0 :

Stm32给的封装是while(!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk));

而SysTick_CTRL_COUNTFLAG_Msk的地址偏移是16

(1ul << SysTick_CTRL_COUNTFLAG_Pos):(unsigned long 类型的)1<<16 得到SysTick_CTRL_COUNTFLAG_Msk。

所以SysTick_CTRL_COUNTFLAG_Msk 的值是 1000 0000 0000 0000,而0x00010000=1*(16^4) = 1 * ((2)^ 4)^ 4 = 1*2^16 = 1000 0000 0000 0000。

        补充说明:SysTick_CTRL_COUNTFLAG_Msk就是一个在二进制数的表示形式上只有第16位是1,其余是0的一个变量。而SysTick->CTRL是一个很多位都表示不同状态标志位值的一个组合值,其中SysTick->CTRL的第16位为计数标志位COUNTFLAG,只有当计数值为0的时候,第16位计数值标志位COUNTFLAG才会被硬件置0,否则为1。

在代码中使用while(!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk));时,程序会不断检查COUNTFLAG标志位是否被设置为1。只有当COUNTFLAG标志位被设置为1时,循环才会退出。由于COUNTFLAG标志位在每次读取后会自动清零,(也就是当COUNTFLAG == 1时,下一次执行SysTick->CTRL就会把COUNTFLAG值0),因此循环会在SysTick计数器完成一次递减到0的过程后退出,而不会导致死循环。

⑤SysTick->CTRL = 0x00000004;

        关闭定时器:如果看懂了③,那就可以知道⑤是把101对应的时钟使能位置0从而关闭了时钟,没有时钟自然就无法工作,也就是关闭了定时器。

### 关于STM32单片机项目的资源与教程 对于初学者来说,学习STM32单片机可以通过多种渠道获取丰富的资源教程。以下是关于如何利用科技的相关资源进行学习的内容: #### 1. **科技的STM32课程** 科技提供了系统的STM32教学视频文档,这些资料覆盖了从基础知识到实际应用的多个方面。例如,在其课程笔记中提到,“片上资源又叫做外设,英文是peripheral”,并列举了一个表格展示STM32F1系列的主要外设资源[^2]。 #### 2. **推荐的学习路径** 学习STM32可以从以下几个方向入手: - **硬件基础**:了解STM32芯片的基本结构及其内部外设的工作原理。 - **软件开发环境搭建**:熟悉Keil MDK或者STM32CubeIDE等集成开发工具的安装与使用方法。 - **GPIO操作**:掌握通用输入/输出端口的基础配置,这是控制LED灯闪烁等功能的核心知识点。 - **定时器与中断机制**:深入研究定时器模块的应用场景以及中断服务函数的设计思路。 #### 3. **具体项目实践建议** 为了更好地巩固理论知识,可以尝试完成一些简单的工程项目,比如实现如下功能: ```c // 配置PA8引脚为推挽输出模式点亮LED示例代码 #include "stm32f10x.h" void GPIO_Configuration(void){ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能GPIOA时钟 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //设置GPIO Pin GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //最大速度50MHz GPIO_Init(GPIOA,&GPIO_InitStructure); } int main(){ GPIO_Configuration(); while(1){ GPIO_SetBits(GPIOA, GPIO_Pin_8); //高电平点亮LED Delay_ms(500); //延时500ms GPIO_ResetBits(GPIOA, GPIO_Pin_8); //低电平熄灭LED Delay_ms(500); //再次延时500ms } } ``` 此段代码展示了如何初始化一个GPIO引脚并通过循环改变它的状态来驱动外部设备(如LED)。这属于非常基础但也十分重要的练习之一[^1]。 #### 4. **社区支持与其他补充材料** 除了官方教材之外,还可以关注像优快云这样的技术博客平台上的分享文章;另外正点原子也是一个不错的选择,它同样提供详尽的教学案例供参考学习者模仿实战演练从而提升动手能力水平。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值