前言
在上一篇文章已经讲了使用通用定时器的方式实现ms和s级别的延时,为什么没有us级别的呢,因为在示波器测量时,没有计算好程序执行的时间。这次找到了方法,测试出通用定时器延时的精准性。
同时,也查找了网上常用的使用系统定时器非中断的方式进行us级延时的方式进行对比,可以在下面测试数据查看精准度。
程序逻辑
- 与上一章的使用通用定时器实现ms级延时执行逻辑相同,根据公式((arr+1)*(psc+1))/Tclk,计算定时器的定时时间,当Tclk为72MHz,arr=71,psc=0时,定时时间刚好是1us。
- 但是这个1us到底准不准,需要考虑:
①因为电平转换代码执行需要时间,延时函数执行也需要时间,定时器服务程序中的子程序执行也需要时间。花了很长时间测试了,电平转换代码和延时函数的执行时间搞定了,但是定时器服务子程序的执行时间没法算出来。而这些时间会影响us的测试结果。
②于是才想出了新的办法。测试1us的延时时间和2us的延时时间,用2us延时程序执行时间-1us延时程序执行时间就能得出延时的时间是否准确。
测试数据
-
延时2us函数程序执行时间
-
延时1us函数程序执行时间
-
经过测试,延时1us时,一个电平周期执行时间16.08us,延时2us时,一个电平的执行周期是18.08us,因为一个电平周期里延时两次,(18.08-16.08)/2=1us。所以通过示波器实测,us级的延时是相当准确的。
-
为了作对比,查找了在网上常用的,使用系统定时器非中断的方式进行us级延时的程序作对比。
通过示波器测试,延时1us时,一个电平周期执行时间16.08us,延时2us时,一个电平的执行周期是18.00us,
因为一个电平周期里延时两次,(18.00-16.08)/2=0.96us,所以结果也相对准确。文章来源:https://www.toymoban.com/news/detail-708858.html -
同时为了测试波形的稳定性和误差,分别对 通用定时器中断服务程序计数延时的方式和系统定时器非中断延时方式 采集了三组数据。
①通用定时器中断服务程序计数延时
②系统定时器非中断延时
这样对比起来通用定时器中断服务程序计数延时的方式会更加稳定一些,不过视不同的使用场景还需要综合考虑。文章来源地址https://www.toymoban.com/news/detail-708858.html
代码示例
- 通用定时器中断服务程序计数延时方式
#include "stm32f10x.h"
uint32_t TimingDelay; //定义进入中断服务程序次数
/*初始化LED灯*/
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //开启外设时钟
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz ;
GPIO_Init(GPIOB,&GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_15); //先熄灭LED
}
/*LED灯状态转换*/
void LED1_Turn(void)
{
if (GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_15) == 0)
{
GPIO_SetBits(GPIOB, GPIO_Pin_15);
}
else
{
GPIO_ResetBits(GPIOB, GPIO_Pin_15);
}
}
//延时函数
void Delay(uint32_t nTime)
{
//也就是延时时间=进入定时器的次数*定时器每次进入中断的时间
TimingDelay = nTime;
while(TimingDelay != 0);
}
//通用定时器3配置
void TIM3_Init(u16 arr,u16 psc)
{
//结构体初始化
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
//分配时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
//初始化定时器的相关配置
TIM_TimeBaseStructure.TIM_Period = arr; //重装载的值
TIM_TimeBaseStructure.TIM_Prescaler = psc; //分频系数
//TIM_ClockDivision 是设置与进行输入捕获相关的分频
//设置这个值不会影响定时器的时钟频率,我们一般设置为TIM_CKD_DIV1,也就是不分频
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStructure);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
//设置 TIM3_DIER 允许更新中断
TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
//打开定时器
TIM_Cmd(TIM3,ENABLE);
}
//定时器时间到了之后自动跳转到中断函数当中
void TIM3_IRQHandler(void)
{
if ( TIM_GetITStatus(TIM3 , TIM_IT_Update) != RESET )
{
TIM_ClearITPendingBit(TIM3 , TIM_FLAG_Update);
//这里的TimingDelay相当于计时次数,计算进入定时器的次数
if(TimingDelay!=0x00){TimingDelay--;}
}
}
int main(void)
{
/*这里定时器进入中断的时间是1us*/
TIM3_Init(71,0); //初始化定时器
LED_Init();
while(1){
LED1_Turn();
Delay(1); //延时1us
}
}
- 系统定时器非中断延时方式
//仿原子延时,不进入systic中断
void delay_us(u32 nus)
{
u32 temp;
SysTick->LOAD = 9*nus;
SysTick->VAL=0X00;//清空计数器
SysTick->CTRL=0X01;//使能,减到零是无动作,采用外部时钟源
do
{
temp=SysTick->CTRL;//读取当前倒计数值
}while((temp&0x01)&&(!(temp&(1<<16))));//等待时间到达
SysTick->CTRL=0x00; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
void delay_ms(u16 nms)
{
u32 temp;
SysTick->LOAD = 9000*nms;
SysTick->VAL=0X00;//清空计数器
SysTick->CTRL=0X01;//使能,减到零是无动作,采用外部时钟源
do
{
temp=SysTick->CTRL;//读取当前倒计数值
}while((temp&0x01)&&(!(temp&(1<<16))));//等待时间到达
SysTick->CTRL=0x00; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
到了这里,关于STM32实现精准us级延时的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!