1. 首页
  2. 技术文章

STM32F10x学习笔记—常见延迟函数种类与实现

  • STM32常见延迟介绍

在程序的设计中,经常遇到需要定时或延迟情况,比如闪灯、定时发送数据、模拟总线等。对于定时来说一般都是精确时间来触发,而延迟常见的由非精确或精确延迟。

  • 精确延迟

精确延迟我们常见使用的是,STM32自带的滴答或者通过其定时器来产生。

  1. 滴答定时器的使用

初始化:

void SysTick_Configuration(void)
{
 if (SysTick_Config(SystemCoreClock / 1000))		// 1ms 中断一次
   { 
     while (1);
   }
  SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk;//关闭滴答定时器
}

一个外设的使用首先是初始化,嘀嗒的初始化比较简单,一个函数,其中除以1000表示1ms中断1次,如果1000000就代表1us中断一次,我们可以根据需求来换参数。例如我们需要读取DS18B20的,我们就可以选择为

if (SysTick_Config(SystemCoreClock / 1000000))

1us为基准。
延迟函数为:

void Delay_us_for_Sys(unsigned int nTime)
{ 
	TimingDelay = nTime;
	SysTick->CTRL |=  SysTick_CTRL_ENABLE_Msk;//使能嘀嗒
	while(TimingDelay);
   SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk;	//关闭嘀嗒
}

为了加入嘀嗒的基准,我们在中断文件中(stm32f10x_it.c)文件中首先定义全局变量:

unsigned int TimingDelay;

然后在中断中加入:

void SysTick_Handler(void)
{
  TimingDelay--;
}

我们每次调用Delay_us_for_Sys(xxx),这个函数就可以完成了精准的延迟。

 

  • TIME定时器的使用

TIME的使用和嘀嗒其实基本一致,我们先初始化TIME,我以TIM5为例,初始化代码如下:

void TIM5_Init(void)
{
  TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
   RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE);
  //这个就是自动装载的计数值,由于计数是从0开始的
  TIM_TimeBaseStructure.TIM_Period = (20-1);
  // 这个就是预分频系数,当由于为0时表示不分频所以要减1
  TIM_TimeBaseStructure.TIM_Prescaler = (36 - 1);
  // 使用的采样频率之间的分频比例
  TIM_TimeBaseStructure.TIM_ClockDivision = 0;
  //向上计数
  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
  TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure);
  TIM_ClearITPendingBit(TIM5, TIM_IT_Update);
  TIM_ITConfig(TIM5, TIM_IT_Update, ENABLE);
  TIM_Cmd(TIM5, DISABLE);  //计数器关闭
}

这里我们分频系数为36,计数的周期为20,那么计算中断时间为:(36/72000000)*20=10us,其中72000000为CPU的主频。我测试过,如果分频到10us以下更小间隔的话,时间会非常不准,所以这里没有分频到1us,1us测试估计大概在7~9us之间变化。10us以上比较稳定,所以这里使用分频到10us中断一次,
然后初始化中断向量表

void NVIC_Configuration(void)
{
  /*  结构声明*/
  NVIC_InitTypeDef NVIC_InitStructure;
   /* Set the Vector Table base address at 0x08000000 */
  NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x00000);
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);	    
  NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
}

最后,中断部分基本就和前面嘀嗒一致了,代码如下:

unsigned int TimingDelay;

void TIM5_IRQHandler(void)
{
   if (TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET)
  {
    TIM_ClearITPendingBit(TIM5, TIM_IT_Update) ;
	TimingDelay--;
  }
}

延迟函数代码如下:

void Delay_us_for_time(unsigned int nTime)
{ 
	TimingDelay = nTime;
	TIM_Cmd(TIM5, ENABLE);	

	while(TimingDelay);
    TIM_Cmd(TIM5, DISABLE);	
}

这样就实现了精确延迟。不过我习惯使用嘀嗒用于系统定时发送,不用于设计到延迟。

  • 非精确延迟

代码如下:

void delay_us(unsigned int us)
{
	int i;
	while(us--)
	{
	 for(i=0;i<10;i++);
	}
}

这种非精确延迟一般我们称之为软延迟,没有任何基准,不知道延迟多久,常常用于闪个LED等对延迟要求低的地方使用。
当然这种延迟函数也可以有一定的精确性,这么办呢?
通过外部设备进行测量,来确定。
仍然是这个函数:

void delay_us(unsigned int us)
{
	int i;
	while(us--)
	{
	 for(i=0;i<10;i++);
	}
}

为了测试延迟函数的时间,我们在主函数的循环中翻转IO:

GPIO_SET_L;
delay_us(1);
GPIO_SET_H;
delay_us(1);

我们通过示波器,捕捉该IO口的波形:如下图1

STM32F10x学习笔记—常见延迟函数种类与实现

图1 delay_us(1)的波形图

我们看到高电平持续时间约为2us,这里不测量周期,这里的周期其实延迟函数时间的2赔,因为我们高低转换2次延迟的2次。因此这个函数参数为1的时候延迟约为2us。

下面我们在看图2,参数为10的波形:

STM32F10x学习笔记—常见延迟函数种类与实现

图2 delay_us(10)波形图

我们看到参数为10的时候,约为10us;

我在看图3 参数为1000的时候,

STM32F10x学习笔记—常见延迟函数种类与实现

图3 delay_us(1000)波形图

显然的延迟时间约为1.26ms。

我们继续看下图4

STM32F10x学习笔记—常见延迟函数种类与实现

图4 delay_us(1000000)波形图

从上面4个波形图我们发现,数字大于10后就很稳定了。因为1us很短偏差大很正常,随着时间的变长,延迟就很稳定。所以我们把这个函数的延迟基准定为1次1us,这个延迟函数用在DS18B20读取温度完全没看有问题。模拟I2C使用的也是它。我们利用其基准,定义ms级延迟函数:

viod delay_ms(unsigned int time)
{
delay_us(time*1000);
}

这样就会省很多事,不用使用系统定时器和滴答。省去不断中断的影响,优点也是很明显。
好了延迟函数实现我们就讲到这里。

原创文章,作者:小峰,如若转载,请注明出处:http://www.wfblog.com/archives/451.html

联系我们

400-800-8888

在线咨询:点击这里给我发消息

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息