最近没啥事,就研究了一下在使用ST官方固件库的延时实现,然后在上网搜寻代码研究时,发现了一些坑,所以写一篇博文来和大家分享延时函数的编写。
程序放在Q群里:659512171
一,新建工程:
与之前使用STM32CubeMX不一样,这里不使用工具生成基础配置文件,而是使用工程模板的方式创建,工程模板的创建教程很多,大家根据自己习惯使用就好,我的模板放在Q群和网盘里,下面是网盘链接 提取码:ao0d链接https://pan.baidu.com/s/1o1m3jqlX7BSICDTJbAmDFQ?pwd=ao0d 工程模板的使用也有教程,以后有时间会专门写一篇,这里就简单说说
将文件下载后解压出来,找到Sample文件夹,打开
打开USER文件夹,把Keil工程文件改名为需要的名字(为了方便区分,Sample的文件夹名字也可以改一下)
然后双击打开工程,下图是各文件夹的基本功能
另外,如果想要记录一些说明,可以新建一个名为Doc(或其他,看个人习惯)的文件夹,里面放入TXT文档用于说明
我这里使用的是 F103C8T6 的MCU,属于中容量产品,所以只保留了 “startup_stm32f10x_md.s”这个文件, 具体保留哪个根据自己情况确定。
二,添加文件:
新建一个 gpio.c 文件,保存在工程中Core文件夹下的Src中,再新建一个 gpio.h 文件,保存在Core中的Inc中(方便管理,根据个人习惯就行了,但要求文件夹路径包含在编译器中)
三,编写程序:
在gpio.h中添加如下代码:
#ifndef __GPIO_H_
#define __GPIO_H_
#include "stm32f10x.h"
void MY_GPIO_Init(void);
#endif
接着在gpio.c中添加代码用于初始化GPIO口:
#include "gpio.h"
void MY_GPIO_Init(void){
GPIO_InitTypeDef GPIO_InitStructure;//定义结构体
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);//使能GPIOC时钟
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC,&GPIO_InitStructure);//初始化GPIOC13为输出
GPIO_SetBits(GPIOC,GPIO_Pin_13);//操作IO口,输出高电平(低电平:GPIO_ResetBits();)
}
然后在 main.c 中编写延时函数(也可以新建一个delay.c和delay.h文件)
void delay_us(u16 time)
{
u16 i=0;
while(time--)
{
i=10; //根据单片机速度定义
while(i--) ;
}
}
void delay_ms(u16 time)
{
u16 i=0;
while(time--)
{
i=12000; //根据单片机速度定义
while(i--) ;
}
}
这种延时属于最简单的,但同时也是误差最大的,所以一般不会使用
void delay_us(u32 nus)
{
u32 temp;
SysTick->LOAD = SystemCoreClock/8000000*nus;
SysTick->VAL=0X00;//清空计数器
SysTick->CTRL=0X01;//开启计数器
do {
temp=SysTick->CTRL;//读取当前控制位
} while((temp&0x01)&&(!(temp&(1<<16))));//确定控制位bit0为1(即有效)同时等待控制位bit16为1
SysTick->CTRL=0x00; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
void delay_ms(u16 nms)
{
u32 temp;
SysTick->LOAD = SystemCoreClock/8000*nms;
SysTick->VAL=0X00;//清空计数器
SysTick->CTRL=0X01;//开启计数器
do {
temp=SysTick->CTRL;//读取当前控制位
} while((temp&0x01)&&(!(temp&(1<<16))));//确定控制位bit0为1(即有效)同时等待控制位bit16为1
SysTick->CTRL=0x00; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
以上程序是我根据原子哥的程序写的,直接复制即可。下面来讲解一下这个查询法延时
在这个程序中,用到了 SystemCoreClock 和 SysTick 这两个东西,其中SystemCoreClock是固件库中的一个宏定义,与单片机主频相等,所以不管是什么单片机都可以直接使用此程序,SysTick就是STM32中的滴答定时器,是一个倒计时的定时器,当数值减到0时重装为自动重装值寄存器(LOAD)中的数值,在用HAL库或者RTOS时一般作为时基信号,SysTick的频率为主频的 1/8,所以这里用主频除以8(上面程序是将算式化简了)即为SysTick的频率,然后根据延时的单位长度,确定再次除以多少,比如我要延时 1ms,SysTick的频率为 9M(即9000000),那么 9000000 * 1/1000 就是每ms内的计数次数,然后再以ms为单位乘以要延时的时间,最后将上述式子整合就得到了 SystemCoreClock/8000*nms 这么一个式子(us类同)。
然后在主程序中添加如下程序:
/*Main Code*/
int main(void){
/*Init*/
MY_GPIO_Init();
while(1){
/*Code*/
GPIO_ResetBits(GPIOC,GPIO_Pin_13);
delay_ms(1000);
GPIO_SetBits(GPIOC,GPIO_Pin_13);
delay_ms(1000);
}
}
最后完整的main.c:
/*
************************************************
*工程名称:延时示例
*作者:岁心
*CSDN主页:https://blog.csdn.net/m0_73677866
************************************************
*/
/*Include*/
#include "main.h"
#include "gpio.h"
/*Function Code*/
void delay_us(u32 nus)
{
u32 temp;
SysTick->LOAD = SystemCoreClock/8000000*nus;
SysTick->VAL=0X00;//清空计数器
SysTick->CTRL=0X01;//开启计数器
do {
temp=SysTick->CTRL;//读取当前控制位
} while((temp&0x01)&&(!(temp&(1<<16))));//确定控制位bit0为1(即有效)同时等待控制位bit16为1
SysTick->CTRL=0x00; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
void delay_ms(u16 nms)
{
u32 temp;
SysTick->LOAD = SystemCoreClock/8000*nms;
SysTick->VAL=0X00;//清空计数器
SysTick->CTRL=0X01;//开启计数器
do {
temp=SysTick->CTRL;//读取当前控制位
} while((temp&0x01)&&(!(temp&(1<<16))));//确定控制位bit0为1(即有效)同时等待控制位bit16为1
SysTick->CTRL=0x00; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
/*Main Code*/
int main(void){
/*Init*/
MY_GPIO_Init();
while(1){
/*Code*/
GPIO_ResetBits(GPIOC,GPIO_Pin_13);
delay_ms(1000);
GPIO_SetBits(GPIOC,GPIO_Pin_13);
delay_ms(1000);
}
}
编译、下载、运行,可以看到,连接在PC13接口上的LED按照1s的频率一亮一灭,本篇实验结束
总结:
对于固件库开发,由于没有第三方库的延时,所以自己拥有一个好用精准的延时函数还是很重要的,我一般习惯于使用查询法的延时函数,大家根据实际情况选择即可。