第一讲——Delay精准延时函数(详细,适合新手)
文章目录
前言
在网上有关STM32编程的资料和文章琳琅满目,但大多使用的是标准库和HAL库,使用LL库的文章少之又少,趁着自己正在使用LL库,来记录分享学习下
使用的开发板是正点原子的STM32F4探索者V2,芯片是STM32F407ZGT6。资料去正点原子官网下载即可。
一、什么是标准库、LL库、HAL库?
STM32标准库、LL库和HAL库是针对STMicroelectronics的STM32微控制器系列的不同软件库。它们之间的区别如下:
-
STM32标准库:STM32标准库是STMicroelectronics提供的最基本的软件库,提供了对STM32微控制器的基本功能的访问。它提供了对寄存器的直接访问,需要用户自行编写底层驱动代码。STM32标准库较为底层,灵活性较高,但编程复杂度也较高。
-
LL库(Low-Level库):LL库是STMicroelectronics推出的低级别抽象层库,位于标准库和HAL库之间。LL库提供了对STM32微控制器底层功能的更高级别的抽象,减少了用户编写底层驱动代码的工作量。LL库相比标准库提供了更多的抽象,但相对于HAL库来说仍然较为接近底层。
-
HAL库(Hardware Abstraction Layer库):HAL库是STMicroelectronics提供的高级别抽象库,为用户提供了更加简化和易用的API,使得用户可以更快速地开发应用程序而不必关心底层硬件细节。HAL库提供了对各种外设(如UART、SPI、I2C等)的封装,使得用户可以通过简单的函数调用实现对外设的控制。
总的来说,STM32标准库提供了最基本的功能,LL库提供了更高级别的抽象,而HAL库则提供了更简化和易用的API,使得用户能够更轻松地开发应用程序。选择使用哪种库取决于用户的需求和对开发的控制程度。
对于刚开始学习我认为从标准库开始是比较合理的,对一个外设的初始化都需要自行配置,虽然有些麻烦,但对理解各外设的工作都有这很大的帮助,这也是经验的积累。用标准库把大多外设驱动成功后,就可以接触LL库和HAL库了,这里会用到STM32CubeMX 软件,使用该软件能直观快速生成LL库和标准库工程。LL库封装少,更接近底层,从标准库能很快上手;但HAL库封装程度深、资源占用大、灵活性低,很难找到底层逻辑。但网上LL库资源少,大家都不得不转到HAL了。好了,废话结束,正式开始!
二、使用CubeMX新建工程
先在电脑建立一个空白文件夹,注意路径只允许英文、数字和下划线。我的工程路径如下:
E:\keil_Projects\STM32F407_LL\delay
接着打开STM32CubeMX,如要下载安装,网上有很多教程,去学就好。
点击框选1图标新建工程,等待一下,进入下面页面
现在左边框图2找到芯片大致型号,再在框图3找到具体芯片,双击进入配置页面
左边点击RCC进入时钟配置,选择HSE(外部高速时钟),选择陶瓷晶振。
接着左边点击SYS,Debug选择Serial Wire也就是SW模式下载,还有JTAG下载方式,区别不大,SW占用IO口更少,所以选择SW下载,时钟源默认了Systick,可以更改成其他定时器,但是没必要,这里默认就好。
STM32的systick是一个系统定时器,通常用于实现操作系统的时钟节拍或者延时功能。它可以通过配置来产生固定时间间隔的中断,常用于处理周期性的任务或者时间相关的操作。在STM32微控制器中,systick定时器通常与系统时钟关联,可以提供较为精确的定时功能。
本文仅仅写延时函数,所以就不开其他外设了,IO配置就到这里了。
接着点击NVIC,进入中断管理,不知道是不是版本原因,每次systick抢占优先级默认15,也就是最低,也有这个情况手动改回0即可。
点击上方Clock Configuration进入时钟树配置
这是刚进入的页面,接下来注意标号说明。
找到input frequency,这是时钟输入频率,改成8M(写8就行,单位默认M)具体数值看开发板手册,也可以直接对光看晶振,接入多少晶振,就填多少,一般都是8M。
将PLL source Mux 选择HSE,/M选择8,晶振分频为1M;*N处选择336,倍频为336M;/P处选择2,分频为168M;system clock Mux 选择PLLCLK。以上操作保证了最大168M频率的输出。
将APB1 prescaler 选择4;APB2 prescaler选择2。其他默认即可,保证所有总线最大时钟输出。如图
网上很多教程一般到这就结束了,但是请同学们注意框选出时钟,这里是systick的两个具体时钟源,从图中可以看出都是168M,其实上面一个可以选择/8,变成21M,如图
这样才是正确的,但是为了给同学们减少困惑,我们就保持不变,不分频,最后时钟配置如图
接下来进入projeck manager
proieck name 选择工程名,用C语言变量命名规则命名
projeck location 选择工程路径,选择前面建的文件夹
toolchain/IDE 改成MDK-ARM 版本选择最高就行,你用其他的编译器也行,大部分都是keil5
从左边进入页面code generattor,配置直接看图吧
最后进入advanced settings ,将外设都选择为LL库即可
到这里工程就配置完了,点击GENERATE CODE 生成工程文件,如果路径有问题会报错,成功如下
打开刚才新建的文件夹可以看到工程文件加载进来
找到delay.uvprojx双击进入keil(keil的下载安装及破解网上有大量教程)如图
接下来,对步骤操作就不大量描述了,主要讲原理
二、系统延时LL_mDelay()原理讲解
找到下面代码,LL_Init1msTick(168000000);写入了168M,如果cubemx配置to cortex system timer 选择8分频,这里填入的就是21000000。但这样系统延时函数就错了,为什么错,接着看。
LL_Init1msTick(168000000);
LL_SetSystemCoreClock(168000000);
可以先编译一下,没有错误,选中函数,邮件,按F12,进入函数LL_Init1msTick,再进入LL_InitTick代码如下
__STATIC_INLINE void LL_InitTick(uint32_t HCLKFrequency, uint32_t Ticks)
{
/* Configure the SysTick to have interrupt in 1ms time base */
SysTick->LOAD = (uint32_t)((HCLKFrequency / Ticks) - 1UL); /* set reload register */
SysTick->VAL = 0UL; /* Load the SysTick Counter Value */
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_ENABLE_Msk; /* Enable the Systick Timer */
}
__STATIC_INLINE是一个宏定义,用于定义静态内联函数,这里不过多讲述。
接下来附上三个systick的寄存器(引用正点原子的图)
SysTick->LOAD = (uint32_t)((HCLKFrequency / Ticks) - 1UL); /* set reload register */
将LOAD写入了168000-1 这是系统重装载的值,装载时系统会自动加1,所以装载值为168000。
SysTick->VAL = 0UL;
将VAL写0,VAL的值会按照时钟频率依次递减。
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_ENABLE_Msk;
进入宏定义可以看到,将CTRL低三位赋值101,时钟源选择HCLK,也就是168M,但是LOAD写入的计时数是基于HCLK/8的,因为一旦cubemx配置to cortex system timer 选择8分频,这里LOAD写入的值就是21000-1,那么系统延时就是错的,会快8倍,但cubemx的to cortex system timer 不分频,直接让两个时钟频率一致,那就没有问题,但理论上还是错误的,正确的应该是将CTRL低三位赋值001,也就是
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk;
这样逻辑才是正确的,但是可以不去更改回来,理论错了知道就好,以后cubemx的to cortex system timer 就不分频,那样也就没有错误了。
当CTRL最低位为1,定时器使能,LOAD的值会加1装载到VAL,VAL会按照168M频率递减,也就是1s内减少168000000个数,装载的168000减到0正好耗时1ms,实现延时。
同学们可以进入void LL_mDelay(uint32_t Delay)函数,可以看到该函数就是以1ms为基准进行延时的。
二、delay自定义延时函数编程
delay.c代码如下
#include "delay.h"
/// @brief nus延时
/// @param nus 延时的nus数
void delay_us(uint32_t nus)
{
uint32_t temp;
SysTick->LOAD=nus*168-1; // 计数值加载
SysTick->VAL=0x00; // 清空计数器
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; // 开始计数
do
{
temp=SysTick->CTRL; // 读取控制寄存器状态
}while((temp&0x01)&&!(temp&(1<<16))); // temp&0x01:定时器使能,!(temp&(1<<16)):定时器计数值不为0
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; // 关闭计数
SysTick->VAL=0x00;// 清空计数器
}
/// @brief nms延时
/// @param nus 延时的ms数
void delay_ms(uint32_t nms)
{
uint32_t repeat=nms/50;
uint32_t remain=nms%50;
while(repeat)
{
delay_us(50*1000); // 延时 50 ms
repeat--;
}
if(remain)
{
delay_us(remain*1000); // 延时remain ms
}
}
按照寄存器的写法,先将LOAD赋值装载值,然后清零VAL,最后使能CTRL开始计时
LOAD值有限制的,这是一个24位寄存器,最大值为2^24-1
,及最大一次可以延时(2^24)/168 us
多说一句,如果cubemx配置to cortex system timer 选择8分频,那么改成如下即可
SysTick->LOAD=nus*168/8-1; // 计数值加载
一旦使能,VAL = LOAD+1,并且开始递减。接着再循环中读取CTRL的值,当VAL计数到0,CTRL最高位为1。代表延时结束,这里加入了CTRL使能位判断是很关键的,避免没使能进入循环,从而循环里出不来。
循环结束后关闭使能,清空VAL,us延时函数结束
ms延时基于1000us延时,重复运行delay_us(1000);的倍数解决的,这里50ms做一个分度,就是因为LOAD写入的值有限制。
到这里延时函数就写完了,附上头文件delay.h代码
#ifndef __DELAY_H
#define __DELAY_H
#include "stdint.h"
#include "stm32f407xx.h"
void delay_us(uint32_t nus);
void delay_ms(uint32_t nms);
#endif
直接头文件加入到mian.c中就可以使用了,注意工程中不可同时使用LL_mDelay()和自定义延时函数,否则陷入死循环。可以舍弃LL_mDelay()函数,不再用了。
这里写入4个延时来验证精准度
while (1)
{
/* USER CODE END WHILE */
delay_us(10);
delay_us(100);
delay_us(1000);
delay_ms(10);
delay_ms(1000);
/* USER CODE BEGIN 3 */
}
我这里使用的是STLink仿真器,
这里core clock一定要选择对应时钟频率,这里是168M,不然不准(默认的画是10M)
勾选Reset and run 即可
编译后将程序下载到开发板中,点击进入调试模式,如图
接下来看视频演示
delay演示验证
到这里就结束了。工程会上传百度网盘打包免费发布出来
链接:https://pan.baidu.com/s/1_nCZ4yaZwYbB0JLz1rumWg?pwd=1234
提取码:1234
尾言
这是本人CSDN第一讲,后面会继续更新STM32LL库编程操作,时间匆忙,如有错误之处还请指出(应该会有些错别字,不影响阅读就行)