延时,就是停在那,啥都不干,发呆。精准延时,就是发呆多长时间,是精确的。
比如,要求某个IO口维持低电平1毫秒后,再维持高电平3毫秒,就需要把IO口拉高,然后延时1毫秒,再拉低,再维持3毫秒。
类似这种情况在硬件接口时序里经常遇到,比如,用IO口模拟SPI协议,用IO口模拟I2C协议等等。
既然延时就是发呆,那我们让系统发发呆就得了呗,让它执行空代码,执行很多很多很多,就能达到预期的效果。
for(i=0; i<20000; i++)
{
for(j=0; j<20000; j++)
{
;;;;;;;;;;;;;;;;;;;;;;;;;
}
}
但这种方法很难测量它发呆的时间,在51单片机中,我们可以通过大概计算代码周期来确定必须运行多少个循环能达到指它的时间。
STM32是三级流水线作业,指令周期不好算,用这种方式延时,精准度有限。
既然定时器能够计时,那就用它来延时吧。
我们就来讲一讲使用定时器来做微秒级(us)的和毫秒级(ms)的精准延时吧。
上一篇《STM32 基本定时器》我们讲过定时器,设置定时器并启动后,定时器会不断数数,可以正着数也可以倒着数,还可以先正着数后倒着数。
既然这样,我们要做一个确定时间的延时,我们就可以启动一个的定时器,让它数数,在它数数这时间内,我们让程序发呆 while(1){},直到数数完成后,再清醒过来 break;
那么,我们就可以这样写程序:
喂,我要定时器数数 xx 微秒。
while(1)
{
if(定时器数完了)
break;
}
依葫芦画瓢,我们照着上节课的TIM1,配置一个TIM2,作为精准延时用的定时器。
这个就不需要中断咯,因为我们让程序发呆,让它边发呆边判断定时器到时间了没,不需要中断。
PS:当然,你也可以用中断做,让它一直发呆,边发呆边判断标志位,定时器到时间了,设置标志位,发呆程序退出,不过,何必呢?
怎么判断定时器到时间了没呢?
可以设置向下计数,那计数寄存器就会从xxxxx开始数数,一直到0,0了,就是到时间了,所以,只需要不断地读这个计数寄存器就行了。
画瓢,初始化:
设置为向下计数,到时判断计数是否为0,若为0,说明计时完成,发呆可以结束。
不需要中断,也暂时不开启,要延时的时候才开启;
/* TIM2init function */
void MX_TIM2_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig;
TIM_MasterConfigTypeDef sMasterConfig;
htim2.Instance = TIM2;
htim2.Init.Prescaler = 1; // 这个随便设置,后面计时前再设置
htim2.Init.CounterMode = TIM_COUNTERMODE_DOWN;
htim2.Init.Period = 1; // 这个随便设置,后面计时前再设置
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.RepetitionCounter = 0;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
//HAL_TIM_Base_Start(&htim2); // 这里不需要中断,也暂时不需要开启,计时的时候再开咯。
}
继续把初始化瓢画完,这个瓢就不需要设置中断了:
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{
if(tim_baseHandle->Instance==TIM1)
{
/* USER CODE BEGIN TIM1_MspInit 0 */
/* USER CODE END TIM1_MspInit 0 */
/* TIM1 clock enable */
__HAL_RCC_TIM1_CLK_ENABLE();
/* TIM1 interrupt Init */
HAL_NVIC_SetPriority(TIM1_UP_TIM10_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM1_UP_TIM10_IRQn);
/* USER CODE BEGIN TIM1_MspInit 1 */
/* USER CODE END TIM1_MspInit 1 */
}
else if(tim_baseHandle->Instance==TIM2)
{
__HAL_RCC_TIM2_CLK_ENABLE();
}
}
void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* tim_baseHandle)
{
if(tim_baseHandle->Instance==TIM1)
{
/* USER CODE BEGIN TIM1_MspDeInit 0 */
/* USER CODE END TIM1_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_TIM1_CLK_DISABLE();
/* TIM1 interrupt Deinit */
HAL_NVIC_DisableIRQ(TIM1_UP_TIM10_IRQn);
/* USER CODE BEGIN TIM1_MspDeInit 1 */
/* USER CODE END TIM1_MspDeInit 1 */
}
else if(tim_baseHandle->Instance==TIM2)
{
__HAL_RCC_TIM2_CLK_DISABLE();
}
}
这个瓢在main()里面,初始化。
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_UART4_Init();
MX_TIM1_Init();
MX_TIM2_Init(); // 刚刚画的瓢!
/* USER CODE BEGIN 2 */
USR_UartInit();
/* USER CODE END 2 */
初始化瓢画完了,接下来,没有瓢画了,得自己做瓢,写延时函数,基本思想就是,设置一个计数器,数数,数到0完事。
先看一下TIM2的Internal Clock = 60Mhz,知道这个,才可以确定怎么设置计数器,具体参照《STM32 基本定时器》,有详细图解。
做两个瓢,一个负责微秒级延时,一个负责毫秒级延时。
先声明一下,在tim.h里面加这个
/* USER CODE BEGIN Private defines */
#define USER_Delay1us(us) TIM2_Delay1us(us)
#define USER_Delay1ms(ms) TIM2_Delay1ms(ms)
/* USER CODE END Private defines */
extern void _Error_Handler(char *, int);
void MX_TIM1_Init(void);
void MX_TIM2_Init(void);
void TIM1_Handler(void);
/* USER CODE BEGIN Prototypes */
void TIM2_Delay1us(uint16_t cnt);
void TIM2_Delay1ms(uint16_t cnt);
/* USER CODE END Prototypes */
说一下为什么要弄这两个宏定义
#define USER_Delay1us(us) TIM2_Delay1us(us)
#define USER_Delay1ms(ms) TIM2_Delay1ms(ms)
为了移植或者修改方便啊。
难道还想如果改用TIM3、TIM4、TIMxxxxxx做延时的时候,把工程里面所有用到延时的地方都修改一遍?
void TIM2_Delay1us(uint16_t cnt)
{
// Prescalar
// TIM2 Internal Clock is 60,000,000Hz, So, if delay 1us, Prescalar must set 60-1=59
// HAL_RCC_GetPCLK1Freq()<<1/1000/1000;
__HAL_TIM_SET_PRESCALER(&htim2, 59); // 设置预分频,这个决定定时器数一次需要多长时间。
// Count
__HAL_TIM_SET_AUTORELOAD(&htim2, cnt); // 设置数数,这个决定数多少个数
SET_BIT(htim2.Instance->EGR, TIM_EGR_UG); // 产生一个UEV(Update Event),用来更新定时器。
__HAL_TIM_ENABLE(&htim2); // 开启定时器。
// 看一下数到0了没有。
while(1)
{
if(!__HAL_TIM_GET_COUNTER(&htim2))
break;
}
// 关掉定时器,没必要再数了。
__HAL_TIM_DISABLE(&htim2);
}
这个可以以微秒级延时为葫芦,画瓢~~~
void TIM2_Delay1ms(uint16_t cnt)
{
// Prescalar
// TIM2 Internal Clock is 60,000,000Hz, So, if delay 1ms, Prescalar must set 60000-1=59999
// HAL_RCC_GetPCLK1Freq()<<1/1000/1000;
__HAL_TIM_SET_PRESCALER(&htim2, 59999);
// Count
__HAL_TIM_SET_AUTORELOAD(&htim2, cnt);
SET_BIT(htim2.Instance->EGR, TIM_EGR_UG);
__HAL_TIM_ENABLE(&htim2);
while(1)
{
if(!__HAL_TIM_GET_COUNTER(&htim2))
break;
}
__HAL_TIM_DISABLE(&htim2);
}
在这两个函数里面,有一个函数要注意:
SET_BIT(htim2.Instance->EGR, TIM_EGR_UG);
一定务必必须加这个,产生UEV(Update Event),更新定时器,看文档:
PS: 其实TIM2是32BIT 的定时器,TIM2_Delay1us(uint16_t cnt),这个cnt,其实是可以写32位的,主要决定于定时器。
接下来,测试它!
main循环里面,闪灯!
while (1)
{
#if 0
USR_LedHandler();
SERDEB_Handler();
TIM1_Handler();
#endif
if(GPIO_PIN_SET==HAL_GPIO_ReadPin(GPIOG, GPIO_PIN_6))
{
HAL_GPIO_WritePin(GPIOG, GPIO_PIN_6, GPIO_PIN_RESET);
}else
{
HAL_GPIO_WritePin(GPIOG, GPIO_PIN_6, GPIO_PIN_SET);
}
USER_Delay1ms(2); // 这个就是延时
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
可以把延时更改为其他值,测试不同的闪灯频率;
注意一点:人眼的极限频率是24Hz,如果高于这个值,你就看不到闪灯在。
可以上研发三宝之:示波器,量波形!
编译->烧录->执行。
量一下,看一下延时 2ms 的波形是怎么样的,如图:
整个工程及代码呢,请上百度网盘上下载:
链接:https://pan.baidu.com/s/19usUcgZPX8cCRTKt_NPcfg
密码:07on
文件夹:\Stm32CubeMx\Code\TimDelay.rar
懂得IO口控制,知道精准延时,下一篇,我们就开始I2C协议!
上一篇:《STM32 基本定时器》
下一篇:《I2C协议详解》
回目录:《目录》