- STM32常见延迟介绍
在程序的设计中,经常遇到需要定时或延迟情况,比如闪灯、定时发送数据、模拟总线等。对于定时来说一般都是精确时间来触发,而延迟常见的由非精确或精确延迟。
-
精确延迟
精确延迟我们常见使用的是,STM32自带的滴答或者通过其定时器来产生。
- 滴答定时器的使用
初始化:
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
图1 delay_us(1)的波形图
我们看到高电平持续时间约为2us,这里不测量周期,这里的周期其实延迟函数时间的2赔,因为我们高低转换2次延迟的2次。因此这个函数参数为1的时候延迟约为2us。
下面我们在看图2,参数为10的波形:
图2 delay_us(10)波形图
我们看到参数为10的时候,约为10us;
我在看图3 参数为1000的时候,
图3 delay_us(1000)波形图
显然的延迟时间约为1.26ms。
我们继续看下图4
图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